Traducir una página JSF con Java

TraducirCon la penetración que está teniendo el Internet en todo el mundo, cada vez es más importante traducir nuestras páginas web, debido a que los visitantes ya pueden venir de cualquier parte del mundo, es por eso que los sitios más importantes o que buscan captar visitantes de otras regiones, permitan a sus usuarios cambiar de idioma.

Pueda que de entrada parezca algo muy complicado o laborioso de hacer, sin embargo, es mucho más simple de lo que te puedas imaginar.

 

Para explicar cómo funciona la Internacionalización (I18N), lo explicaremos mediante un ejemplo simple pero eficaz, implementaremos una página de Login, la cual se verá de la siguiente manera:

 

Traducir

El código completo lo puedes descargar de GitHub (https://github.com/oscarjb1/Blog-TranslateApp.git).

 

Básicamente, esta página nos permitirá cambiar entre inglés y español, seleccionando el idioma deseado en la parte superior derecha de la pantalla. Tras seleccionar el nuevo idioma, la página de recargara con el nuevo idioma.

 

Veamos cómo es que esto es posible. Primero que nada, será necesario crear un archivo properties por cada idioma que soportará nuestra aplicación. Para nuestro ejemplo, solo requerimos de dos archivos como se ven a continuación:

 

messages.properties:

Contiene las traducciones por default, en este caso Ingles.

 

areYou = ¿Are you Oscar?
changeUsr = change user
userName = Username
userNamePlaceholder = oscar@gmail.com
password = Password
passwordPlaceholder = Your password
remember = Remember
login = login

 

message_es_ES.properties:

Contiene las traducciones en español. Prestemos atención en el nombre, pues es sumamente importante, la primera parte “message”  podría tener cualquier valor, pero todos los idiomas soportados deberán iniciar con el mismo nombre., sin embargo, lo que viene a continuación es sumamente importante para identificar el idioma, la segunda sección “es”  indica el idioma (español) y la tercera parte “ES”  el país (España).

 

Podemos observar que las traducciones en inglés, no requieren país ni idioma, pues los hemos configurado como el idioma default, esto quiere decir que este idioma se utilizara en caso de no tener una coincidencia para el idioma del usuario.

 

Para configurar el idioma default, es necesario definirlo en el archivo faces-config.xml y deberá quedar de la siguiente manera:

 

areYou = ¿No eres Oscar?
changeUsr = cambia de usuario
userName = Usuario
userNamePlaceholder = oscar@gmail.com
password = Contraseña
passwordPlaceholder = Tu contraseña
remember = Recordarme
login = Entrar

 

Básicamente definiremos el idioma default (<default-locale> ) y los idiomas soportados (<supported-locale>). La sección <base-name> debe de coincidir con el paquete en el que están los archivos de traducción, junto con la primera sección del nombre (messages). De esta forma JSF sabe, basado en este path + el idioma y el país, es posible determinar el archivo exacto a utilizar.  Finalmente, le indicamos que las traducciones las pase a una variable (<var> ) llamada msg.

 

Por otra parte, tenemos el archivo JSF donde creamos la presentación:

<?xml version='1.0' encoding='UTF-8' ?>
<!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://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
    <h:head>
        <title>Facelet Title</title>

    </h:head>
    <h:body>
        <h:outputStylesheet library="styles" name="styles.css"/>
        <section class="center">
            <div class="log-container"> 

                <div class="log-header">
                    <h:form>
                        <h:selectOneMenu class="frigth" value="#{loginBean.lang}"  valueChangeListener="#{loginBean.changeLang}" onchange="submit();">
                            <f:selectItem itemValue="es_ES" itemLabel="Español"/>
                            <f:selectItem itemValue="en" itemLabel="English" />
                        </h:selectOneMenu>
                    </h:form>

                </div>

                <div class="avatar">
                    <h:graphicImage library="images" name="oscar-sm.jpg"/>
                    <p>#{msg['areYou']} <a href="#">#{msg['changeUsr']}</a></p>
                </div>

                <div class="log-form">
                    <h:form>
                        <div class="input-group">
                            <h:outputLabel value="#{msg['userName']}" for="username" />
                            <h:inputText id="username"  value="#{loginBean.username}" a:placeholder="#{msg['userNamePlaceholder']}"/>
                        </div>
                        <div class="input-group">
                            <h:outputLabel value="#{msg['password']}" for="password"  />
                            <h:inputText id="password"  value="#{loginBean.password}" a:placeholder="#{msg['passwordPlaceholder']}"/>
                        </div>
                        <div class="input-group">
                            <h:selectBooleanCheckbox id="remember" value="#{loginBean.remember}"/>
                            <h:outputLabel value="#{msg['remember']}" for="remember" />
                        </div>
                        <div class="input-group-r">
                            <h:commandLink value="#{msg['login']}" styleClass="btn-default" action="#{loginBean.login}"/>
                        </div>
                    </h:form>
                </div>
            </div>
        </section>
    </h:body>
</html>

 

Observemos que todos los valores traducibles están con la sintaxis #{msg[‘property’]} , donde property es el nombre de la propiedad a traducir de los archivos de traducción.

La traducción se dispara cuando cambiamos el idioma en el componte, selectOneMenu el cual dispara un evento que es escuchado por el Bean LoginBean , el cual vemos a continuación:

 

package com.osb.translateapp.beans;

import java.io.Serializable;
import java.util.Locale;
import java.util.logging.Logger;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;

/**
 * @author Oscar Blancarte
 */
@ManagedBean
@SessionScoped
public class LoginBean implements Serializable{

    private String lang;
    private String username;
    private String password;
    private boolean remember;
    
    public String getLang() {
        return lang;
    }

    public void setLang(String lang) {
        this.lang = lang;
    }

    public boolean isRemember() {
        return remember;
    }

    public void setRemember(boolean remember) {
        this.remember = remember;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void changeLang(ValueChangeEvent e) {
        Object newValue = e.getNewValue();
        this.lang = newValue.toString();
        Logger.getAnonymousLogger().severe("new lang ==> " + newValue.toString());
        UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
        viewRoot.setLocale(new Locale(newValue.toString()));
    }

    public void login() {
    }
}

 

Cuando un nuevo idioma es seleccionado el método changeLang es ejecutado y el nuevo idioma es establecido mediante la línea:

viewRoot.setLocale(new Locale(newValue.toString()));

Después de esto, la pantalla es recargada con el nuevo idioma. Es tan simple como eso.

 

NOTA: Los estilos para que la página se vea tal cuan aparece en la imagen, los puedes descargar del repositorio de GitHub que menciona anteriormente.

2 thoughts to “Traducir una página JSF con Java”

  1. Oscar buen día. Soy Luis Carlos, escribo desde Colombia. Hace un par de años escribí sobre adf java jsf weblogic, en ese entonces estaba incursionando con esas herramientas. Hoy en día estoy en un proyecto de esos, aprendiendo cosas.

    Quería hacerle una consulta, imagino que aún está activo ayudando en su blog, así que quisiera molestarlo con una nueva consulta, asi que lo contextualizo al respecto para que por favor me dé su recomendación como siempre.

    Tengo una jsf nueva llamada asignacionCarga que es una réplica de otra jsf llamada estadosFormulario, prácticamente lo que hice fue copiar y pegar la consulta original de estadosFormulario en mi jsf asignacionCarga, solo hice pequeños ajustes porque es una consulta que ya está funcionando bien en otro módulo y me funciona correctamente en mi nueva jsf. El inconveniente inició porque al hacer la copia de toda esa funcionalidad en mi jsf el IDE jdeveloper me mostró, dentro de mi jsf, las referencias hacia el Bean como si fueran errores, es decir, referencias como:
    value=”#{backingBeanScope.backing_pageCritico_estadosFormulario.listaAnios}”
    las tuve que cambiar a:
    value=”#{backing_pageCritico_estadosFormulario.listaAnios}”

    , …es decir tuve que quitarle el scope del Bean “backingBeanScope” y también tuve que cambiar el alcance del Bean en adf-config.xml, es decir, de :
    backingBean
    lo tuve que cambiar a:
    session
    De esta forma el IDE quitó los supuestos errores de las referencias.

    La base del inconveniente es que al salir de esa jsf asignacionCarga ir a otra jsf directamente a través del menú de la aplicación y regresar a la jsf asignacionCarga, todos los componentes como Select one choice y Table siguen cargados con las mismas selecciones, datos y valores que se utilizaron previamente en la jsf asignacionCarga, es decir al regresar a esa jsf no se blanquean los componentes ni campos de captura ni nada, todo sigue cargado como quedaron cuando se cargó la jsf por ultima vez. Dado que para levantar los supuestos errores del IDE (con las referencias hacia el Bean) tuve que dejar el alcance como “sesión” que mantiene todos los valores de las variables y demás mientras dure la sesión, pues menos se me van a blanquearan los valores cuando salga y vuelva a la misma jsf. Así las cosas probé cambiar nuevamente el alcance del Bean, y lo dejé como:
    view
    Con este alcance “view” me blanquea los componentes de la jsf cada vez que regreso a esa jsf, pero el tema es que el IDE volvió a mostrar las referencias como errores, aunque desplegó bien, y corrió bien la aplicación, y se comportó bien.

    La idea hubiera sido seguir usando el alcance: backingBean usado en la jsf estadosFormulario y sin necesidad de cambiar las referencias ni el alcance en mi jsf asignarCarga. Mi pregunta es si ese alcance “backingBean“ debo registrarlo o definirlo en algún otro lado del proyecto para que el IDE no me muestre como error todas esas referencias. Es decir si cambio backingBean y utilico otro alcance para el Bean, el IDE me pone como errores todas las referencias dentro de mi jsf.

    Cualquier recomendación agradezco,
    Saludos desde Colombia.

    1. Ups… tu pregunta es demasiada especifica como para darte una respuesta acertada, mi consejo es que cuides bien los Scope de los Beans, en ningún caso te recomiendo usar un Scope de Sesión para guardar los datos de un formulario, pues se mantendrá los valores durante toda la sesión, creo que la mejor opción es que utilices un View Scope o si es posible Request Scope.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *