En esta entrada voy a realizar una aplicación muy básica con JSF 2.x y voy a internacionalizarla, de forma que se pueda ejecutar en dos lenguajes diferentes: español e inglés.
Es conveniente si tenemos claro que la aplicación debe aceptar varios idiomas que la internacionalización la apliquemos en la fase más temprana posible. Incorporar la internacionlización cuando la aplicación ya está muy avanzada suele ser bastante más costoso que hacerlo en etapas tempranas por varios motivos, pero principalmente las diferencias que hay entre los diferentes idiomas en cuanto a decir lo mismo con más o menos palabras pueden afectar al diseño de las páginas.
Requisitos para desarrollar el ejemplo.
Para que se pueda reproducir el ejemplo y esté operativo al 100% comento qué entorno he utilizado para hacerlo funcionar.
IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x
Tecnología: JSF 2.2 (Java EE 7)
Recomiendo la lectura de la entrada JSF 2.x Hola Mundo pues en ella comento todos los aspectos de configuración que en esta entrada voy a dar por supuestos, como la elección de librerías, configuración de servlets, etcétera.
Módulo web de aplicación.
Este ejemplo es tan sencillo que va a contar con un único módulo; el módulo web. El módulo web genera un WAR que se despliega directamente en Apache Tomcat.
Estructura

Fichero: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>es.egv.jee6.jsf2</groupId>
<artifactId>i18nBasico</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b04</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
Fichero: WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>BasicoI18N</display-name> <welcome-file-list> <welcome-file>i18nwelcome.xhtml</welcome-file> </welcome-file-list> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <context-param> <param-name>javax.faces.CONFIG_FILES</param-name> <param-value>WEB-INF/faces-config.xml</param-value> </context-param> <context-param> <param-name>javax.faces.PROJECT_STAGE</param-name> <param-value>Development</param-value> </context-param> </web-app>
Destacar la inclusión de un parámetro de configuración que indica donde se encuentra el fichero de configuración de JSF. En este ejemplo vamos a incluir este fichero de configuración ya que va a ser necesario indicar los ficheros donde se encuentran las traducciones del idioma y el idioma por defecto de la aplicación.
Fichero: WEB-INF/faces-config.xml
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> <application> <locale-config> <default-locale>es</default-locale> </locale-config> <resource-bundle> <base-name>i18n.i18nBasico</base-name> <var>i18n</var> </resource-bundle> </application> </faces-config>
El campo <locale-config> sirve para definir las configuraciones relativas a la situación geográfica, entre ellas las localizaciones permitidas <supported-locale> o la localización por defecto <default-locale>. La localización es imprescindible para internacionalizar la aplicación.
El campo <resource-bundle> es la piedra angular de la internacionalización, ya que en él se indica donde va a encontrar la aplicación los ficheros que contienen las etiquetas en sus respectivos idiomas <base-name> y el nombre de la variable que se va a utilizar para hacer referencias a ellas <var>. Así podremos encontrar en las páginas dinámicas código como el siguiente donde se hace referencia a la etiqueta i18nwelcome.saludo del grupo de recursos identificado por el nombre de variable i18n:
<h:outputText value="#{i18n['i18nwelcome.saludo']}" />
El fichero faces-config.xml añade un montón de posibilidades de configuración más, pero se escapan al ámbito de esta entrada. Pueden consultarse en el siguiente link.
Los ficheros de recursos para los idiomas.
Estos ficheros contienen las traducciones a diferentes idiomas de las etiquetas de la aplicación. El nombre es el indicado en el campo <base-name> al que se le concatena el lenguaje de la variable Locale. Por ejemplo, para contener las etiquetas en inglés de la aplicación, el fichero se llama i18nBasico_en.properties.
Fichero: i18n/i18nBasico_en.properties
i18nwelcome.titulo = JSF 2.x internacionalization example. i18nwelcome.saludo = Internacionalization with JSF 2.x. i18nwelcome.etiquetaIdioma = Language i18nwelcome.descripcion-metodo1 = Combo Box Method i18nwelcome.descripcion-metodo2 = Image Method
Fichero: i18n/i18nBasico_es.properties
i18nwelcome.titulo = Ejemplo de internacionalización con JSF 2.x. i18nwelcome.saludo = Internacionalización con JSF 2.x. i18nwelcome.etiquetaIdioma = Idioma i18nwelcome.descripcion-metodo1 = Método basado en una lista seleccionable. i18nwelcome.descripcion-metodo2 = Método basado en imágenes.
Se puede ver que el nombre de las etiquetas es el mismo en ambos ficheros, pero el valor de las mismas está traducido al idioma que se indica en el nombre del fichero.
El Managed Bean que maneja la lógica de presentación del idioma.
Fichero: es.egv.jee6.jsf2.i18n.IdiomaBean.java
package es.egv.jee6.jsf2.i18n;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {
private static final long serialVersionUID = 1L;
private String codigoIdioma;
private Map<String,Object> listaIdiomas;
public IdiomaBean() {
super();
this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
this.listaIdiomas = new LinkedHashMap<String,Object>();
this.listaIdiomas.put("Español", new Locale("es"));
this.listaIdiomas.put("Inglés", new Locale("en"));
}
public String getCodigoIdioma() {
return codigoIdioma;
}
public void setCodigoIdioma(String codigoIdioma) {
this.codigoIdioma = codigoIdioma;
}
public Map<String, Object> getListaIdiomas() {
return listaIdiomas;
}
public void doCambioIdiomaConLista(ValueChangeEvent e)
{
String newCodigoIdioma = e.getNewValue().toString();
System.out.println("newCodigoIdioma=" + newCodigoIdioma);
System.out.println("idiomaBean=" + this.toString());
//loop country map to compare the locale code
for (Map.Entry<String, Object> entry : listaIdiomas.entrySet())
{
if(entry.getValue().toString().equals(newCodigoIdioma))
{
System.out.println("Asignando nueva locale al contexto de Faces.");
FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
}
}
}
public void doCambioIdiomaConImagen(String nuevoIdioma)
{
//loop country map to compare the locale code
for (Map.Entry<String, Object> entry : listaIdiomas.entrySet())
{
if(entry.getValue().toString().equals(nuevoIdioma))
{
System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
this.codigoIdioma = nuevoIdioma;
FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
}
}
}
@Override
public String toString() {
return "IdiomaBean [codigoIdioma=" + codigoIdioma + ", listaIdiomas=" + listaIdiomas + "]";
}
}
Defino un managed bean específico para manejar el idioma. Será el encargado de conservar el idioma seleccionado por el usuario, una lista de idiomas seleccionables, y las diferentes acciones que se llevan a cabo cuando se realizan acciones relacionadas con el idioma.
@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {
Este ManagedBean lo defino en el ámbito de la sesión @SessionScoped. De esta manera, el idioma queda almacenado en todas las navegaciones que realice el usuario hasta que cierre el navegador, o la sesión.
private String codigoIdioma; private Map<String,Object> listaIdiomas;
Se definen la variable codigoIdioma para contener el idioma seleccionado por el usuario y la variable listaIdiomas para contener los idiomas que van a mostrarse al usuario para proceder a la traducción de la aplicación.Se incluyen también sus getters y sus setters.
public IdiomaBean() {
super();
this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
this.listaIdiomas = new LinkedHashMap<String,Object>();
this.listaIdiomas.put("Español", new Locale("es"));
this.listaIdiomas.put("Inglés", new Locale("en"));
}
Se incluye un constructor para rellenar el código del idioma con el idioma configurado por JSF en el arranque, que será el idioma que se ha configurado en el <default-locale> del fichero faces-config.xml, y rellenar la lista de idiomas con los idiomas válidos para la aplicación.
public void doCambioIdiomaConLista(ValueChangeEvent e)
{
String newCodigoIdioma = e.getNewValue().toString();
System.out.println("newCodigoIdioma=" + newCodigoIdioma);
System.out.println("idiomaBean=" + this.toString());
//loop country map to compare the locale code
for (Map.Entry<String, Object> entry : listaIdiomas.entrySet())
{
if(entry.getValue().toString().equals(newCodigoIdioma))
{
System.out.println("Asignando nueva locale al contexto de Faces.");
FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
}
}
}
Se añade un método para manejar el cambio del idioma cuando se seleccione un idioma de la lista.
<h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">
Y, se añade un método que se ejecutará cuando se seleccione un cambio de idioma pinchando en la imágen del mismo.
public void doCambioIdiomaConImagen(String nuevoIdioma)
{
//loop country map to compare the locale code
for (Map.Entry<String, Object> entry : listaIdiomas.entrySet())
{
if(entry.getValue().toString().equals(nuevoIdioma))
{
System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
this.codigoIdioma = nuevoIdioma;
FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
}
}
}
<h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">
Destacar que se ha incluido la asignación del idioma seleccionado a la variable codigoIdioma del bean para que quede guardada la sección.
this.codigoIdioma = nuevoIdioma;
Páginas web dinámicas.
El ejemplo consta de tres páginas web dinámicas: i18nwelcome.xhtml que es la página básica de bienvenida, i18nidiomametodo1.xhtml que es la página con el manejo de idioma basado en una lista seleccionable, e i18nidiomametodo2.xhtml que es la página con el manejo del idioma basado en imágenes.

Fichero: i18nwelcome.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:body>
<h1>#{i18n['i18nwelcome.titulo']}</h1>
<h:form>
<h2>
<h:outputText value="#{i18n['i18nwelcome.saludo']}" />
</h2>
<ui:include src="i18nidiomametodo1.xhtml" />
<ui:include src="i18nidiomametodo2.xhtml" />
</h:form>
</h:body>
</html>
Esta página muestra dos etiquetas a internacionalizar; i18nwelcome.titulo e i18nwelcome.saludo. Para mostrar correctamente el idioma se utiliza expresiones EL que son interpretadas por JSF en tiempo de ejecución.
#{i18n['i18nwelcome.titulo']}
Esta expresión indica que se va a recoger del bundle i18n la etiqueta i18nwelcome.titulo.
Por claridad, los dos métodos que se van a utilizar para la selección del idioma se han realizado en dos páginas dinámicas independientes que se incluyen en la página básica mediante la sentencia <ui:include src=»»/>.
<ui:include src="i18nidiomametodo1.xhtml" />
Fichero: i18nidiomametodo1.xhtml
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:outputText value="#{i18n['i18nwelcome.descripcion-metodo1']}."/>
<h:panelGrid columns="2">
<h:outputText value="#{i18n['i18nwelcome.etiquetaIdioma']}:" />
<h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">
<f:selectItems value="#{idiomaBean.listaIdiomas}" />
</h:selectOneMenu>
</h:panelGrid>
</ui:composition>
Estos ficheros como se han diseñado para ser incluidos en un fichero padre y no para llamarse directamente se crean como elementos <ui:composition/>.
El valor seleccionado en la lista seleccionable es el codigoIdioma almacenado en el Managed Bean.
<h:selectOneMenu value="#{idiomaBean.codigoIdioma}"
La lista se carga con los valores almacenados en la listaIdiomas del Managed Bean.
<f:selectItems value="#{idiomaBean.listaIdiomas}" />
Cuando se cambia el elemento seleccionado se lanza el método doCambioIdiomaConLista.
onchange="submit()"
valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}"
Fichero: i18nidiomametodo2.xhtml
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<h:outputText value="#{i18n['i18nwelcome.descripcion-metodo2']}."/>
<h:panelGrid columns="1">
<h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">
<h:graphicImage library="images" name="bandera-en.png" rendered="#{idiomaBean.codigoIdioma == 'es'}"/>
</h:commandLink>
<h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('es')}">
<h:graphicImage library="images" name="bandera-es.png" rendered="#{idiomaBean.codigoIdioma == 'en'}"/>
</h:commandLink>
</h:panelGrid>
</ui:composition>
Destacar que se muestra el botón con la bandera del idioma que no está seleccionado, para así poder cambiarlo. Para ello se utiliza el atributo rendered y expresiones EL.
rendered="#{idiomaBean.codigoIdioma == 'es'}"
Demo
Link
Configurar Aplicaciones JSF
Documentación oficial JSF 2.x

