Minientrada

Internacionalizando una aplicación web basada en JSF 2.x

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

2016-09-09_14-18-13

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.

2016-09-09_15-17-48

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

El pase de diapositivas requiere JavaScript.

Link

Configurar Aplicaciones JSF
Documentación oficial JSF 2.x

 

Internacionalizando aplicaciones con Java. Visión básica.

La internacionalización, también conocida como i18n (es curioso, pero en inglés hay 18 letras entre la letra «i» y la letra «n» de la palabra internacionalization), es la habilidad de una aplicación para adaptarse a distintos idiomas sin tener que realizar cambios en el código fuente.

Las características principales que debe cumplir una aplicación internacionalizada son:

  • con un mismo ejecutable de la aplicación se debe poder ejecutar la aplicación en distintos idioma simplemente cambiando la información de localización regional del usuario.
  • los textos susceptibles de cambiar de idioma no pueden incluirse directamente en el código. A cambio en el código aparecerá una variable que servirá para identificar el verdadero texto a mostrar.
  • soportar nuevos idiomas no debe requerir la re-compilación de la aplicación.
  • la información que pueda depender de la configuración regional del usuario aparecerá con el formato adecuado. Por ejemplo, moneda, números, fechas, etcétera.
  • el diseño de la aplicación (desde un inicio) debe estar orientado a la internacionalización.

En un alto porcentaje de los proyectos en los que participo conviven Euskera y Castellano, por lo que es muy necesario tener claros los conceptos básicos de la internacionalización.

Mi intención en esta entrada es, a través de un ejemplo, explorar los conceptos más básicos de i18n, entendiendo las ventajas que proporciona y la manera en la que lo hace.

La aplicación de ejemplo.

La aplicación de ejemplo que voy a utilizar para ilustrar el uso de la internacionalización recoge un nombre por teclado y lo escribe en la consola. Es un ejemplo muy simple pero muy útil para mostrar el funcionamiento de i18n.

La aplicación en funcionamiento.

El código fuente de la aplicación, sin internacionalizar es una clase verdaderamente simple; BasicConsoleLogin.java. Consta de dos métodos, uno privado y otro publico, los dos estáticos. El método privado readInput() es el encargado de leer el nombre introducido por el usuario a través del teclado. El método publico main(String[] args) contiene el código funcional de la aplicación.

/**
 * <p>Recoge la entrada del usuario vía teclado. </p>
 * @return java.lang.String la entrada insertada por teclado.
 */
 private static String readInput()
 {
   BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
   String s = new String("");
   try {
     s = bufferRead.readLine(); 
   } catch (IOException e) {
     s = getStackTrace(e);
   }
   return s;
 }

 public static void main(String[] args) {
    System.out.println("Introduce tú nombre:");
    String nombre= readInput();
    System.out.println("Tu nombre es " + nombre);
    System.out.println("Muchas gracias por darme uso");
 
 }

Paso 1:  creación de los bundles de internacionalización.

A través de la internacionalización, el texto de la aplicación que es susceptible de ser traducido a diferentes idiomas se organiza en ficheros de propiedades, uno por idioma.

Estos ficheros se conocen como ‘bundles’ y contienen múltiples filas que constan de una estructura clave=mensaje_traducido. La clave aparecerá en todos los bundles y el mensaje_traducido variará conteniendo la cadena de texto que corresponde con el idioma.

Para entender lo anterior lo mejor es explicarlo con los ‘bundles’ que voy a utilizar en la aplicación de ejemplo:

Bundle: BasicConsoleLoginMessages_es_ES.properties

LanguageSelection = El idioma elegido es:
InputMessage = Introduce tú nombre:
OutputMessage = Tu nombre es 
AcknowledgeMessage = Muchas gracias por darme uso.

Bundle: BasicConsoleLoginMessages_eu_ES.properties

LanguageSelection = Hizkuntza hau aukeratu duzu:
InputMessage = Idatzi zure izena:
OutputMessage = Zure izena ... da: 
AcknowledgeMessage = Eskerrik asko erabiltzeagatik!

Bundle: BasicConsoleLoginMessages_en_UK.properties

LanguageSelection = The Language chosen is:
InputMessage = Please, type your name:
OutputMessage = Your name is 
AcknowledgeMessage = Thanks for using me!

La aplicación de ejemplo está traducida a tres idiomas: castellano (es_ES), euskera (eu_ES) e inglés (en_UK).

Por cada idioma hay un ‘bundles’ que contienen pares de valores clave = traducción. La clave es igual en todos los ‘bundles’ independientemente del idioma, sin embargo, la traducción es propia de cada uno. Por ejemplo, el mensaje que se usa para animar al usuario a teclear su nombre se llama InputMessage. Este nombre será el que se utilice en la aplicación y en cada ‘bundle’ tendrá una traducción distinta.

Paso 2: Recoger el idioma en el que se ejecuta la aplicación.

Para establecer el idioma en el que el usuario quiere ver la aplicación, inicialmente se pregunta por el idioma. El usuario podrá elegir entre castellano, euskera e inglés, y con esa información la aplicación continuará ejecutándose en el idioma elegido.

La aplicación en euskera.

Normalmente, las aplicaciones suelen recoger la configuración de idioma de la configuración regional del sistema operativo o pueden incorporar un icono en pantalla para el cambio de idioma. Existen múltiples métodos.

En nuestra aplicación de ejemplo, el usuario teclea el idioma que desea. La información que teclea el usuario sirve para crear una instancia de la clase java.util.Locale que usaremos para acceder al ‘bundle’ correspondiente del idioma elegido. El método getLocale(String appIdioma) muestra esta manera de proceder.

private static Locale getLocale(String appIdioma)
 {
   Locale locale;
   if ("es_ES".equals(appIdioma)) {
     locale = new Locale("es", "ES");
   } else if ("eu_ES".equals(appIdioma)) {
     locale = new Locale("eu", "ES");
   } else if ("en_UK".equals(appIdioma)) {
     locale = new Locale("en", "UK");
   } else {
     locale = new Locale("eu", "ES");
   }
   return locale;
 }

Paso 3. Internacionalizar la aplicación

El último paso es adaptar la aplicación para ser internacionalizada. El código fuente internacionalizado quedaría así:

public static void main(String[] args) {
  System.out.println("Sartu aplikazioaren hizkuntza / Introduce el idioma de la aplicación / Tell us application's language (eu_ES|es_ES|en_UK): ");
 
  String appIdioma = readInput();
 
  Locale miLocale = getLocale(appIdioma);
  ResourceBundle rb = ResourceBundle.getBundle("BasicConsoleLoginMessages", miLocale);
 
  System.out.println(rb.getString("LanguageSelection") + miLocale.getLanguage() + "_" + miLocale.getCountry());
  System.out.println(rb.getString("InputMessage"));
  String nombre= readInput();
 
  System.out.println(rb.getString("OutputMessage") + nombre);
  System.out.println(rb.getString("AcknowledgeMessage"));
 
 }

El punto clave es la recuperación del bundle correspondiente al idioma que elija el usuario. Esta recuperación se hace a través de la clase java.util.ResourceBundle, el único requisito es que los ficheros de los ‘bundles’ sean accesibles desde el classloader de la aplicación.

 ResourceBundle rb = ResourceBundle.getBundle("BasicConsoleLoginMessages", miLocale);

Una vez accedido el bundle, recuperar los mensajes de texto es una sencilla llamada al método getString(String clave);

rb.getString("OutputMessage")

Conclusión

La internacionalización no aporta excesiva dificultad adicional al código y permite traducir las aplicaciones a diferentes idiomas de forma muy sencilla.

Esta aplicación que he utilizado en el ejemplo es una aplicación excesivamente sencilla y no ilustra la dificultad que puede llegar a ser internacionalizar una aplicación no preparada para ello. En las aplicaciones complejas se hace necesario sino obligatorio un diseño inicial i18n, ya que incorporarlo posteriormente puede darnos más de un quebradero de cabeza.

Links