intentaremos dar un paso más en la construcción de la aplicación PrimeFaces/JSF/Spring, en este caso agregaremos la característica de Localización (I10N) mediante el agregado de componentes de Internacionalización (I18N).
Introducción
Según wikipedia "La internacionalización es el proceso de diseñar software de manera tal que pueda adaptarse a diferentes idiomas y regiones sin la necesidad de realizar cambios de ingeniería ni en el código. La localización es el proceso de adaptar el software para una región específica mediante la adición de componentes específicos de un locale y la traducción de los textos, por lo que también se le puede denominar regionalización. No obstante la traducción literal del inglés es la más extendida.
En informática, un locale es un conjunto de parámetros que define el idioma, país y cualquier otra preferencia especial que el usuario desee ver en su interfaz de usuario.
Generalmente un identificador de locale consiste como mínimo de un identificador de idioma y un identificador de región. Este concepto es de fundamental importancia en el campo de la localización de idiomas."
Esto parece complejo, pero es sencillo de implementar con JSF.
Manos a la obra!
Archivos de recursos con mensajes
Los archivos de recursos con mensajes son archivos de texto plano que contienen una serie de claves y cada clave contiene un valor asociado, los valores serán las cadenas con mensajes internacionalizados. Luego estos archivos son transformados en objetos java.util.Map para su fácil manipulación.
Crearemos dos archivos de recursos en el paquete ar.com.magm.recursos, los recursos son simples archivos planos, se pueden crear haciendo botón derecho sobre el paquete y seleccionando New > Other... / General / File
Los archivos a crear y sus respectivos contenidos son:
mensajes.properties
lbl.login=Ingreso
lbl.username=Usuario
lbl.password=Clave
lbl.welcome=Bienvenid@
lbl.error.login=Error en el ingreso
lbl.invalidcredentials=Credenciales inválidas
lbl.ventas=Ventas
lbl.gauge=Tacómetro
lbl.logout=Salir
lbl.all.m=Todos
lbl.all.f=Todas
lbl.months=Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre
lbl.table.sales.empty=No hay ventas con este criterio de filtrado
lbl.find.all=Buscar en todos
lbl.col.zone=Zona
lbl.col.customer=Cliente
lbl.col.year=Año
lbl.col.month=Mes
lbl.col.salesamount=Importe Ventas
lbl.gauge.title=Tacómetro Personalizado
mensajes_en.properties
lbl.login=Login
lbl.username=User
lbl.password=Password
lbl.welcome=Welcome
lbl.error.login=Login error
lbl.invalidcredentials=Invalid credentials
lbl.ventas=Sales
lbl.gauge=Gauge
lbl.logout=Logout
lbl.all.m=All
lbl.all.f=All
lbl.months=January,February,March,April,May,June,July,August,September,October,November,December
lbl.table.sales.empty=There are no sales for this filter criteria
lbl.find.all=Find all
lbl.col.zone=Zone
lbl.col.customer=Customer
lbl.col.year=Year
lbl.col.month=Month
lbl.col.salesamount=Sales amount
lbl.gauge.title=Custom Gauge
Tres cosas son fáciles de notar con solo ver el contenido y nombre de los archivos, una es que el nombre del archivo contiene como parte de el el locale, en el caso de mensajes_en.properties, el locale es en, si por ejemplo necesitásemos crear un archivo con información de localización para Francia, el archivo debería llamarse mensajes_fr.properties. La extensión debe ser .properties, aunque ya no la mencionaremos más. Lo otro que se puede notar es que ambos archivos poseen las mismas claves, solo los valores son diferentes y por último se nota que uno de los archivos no tiene locale, este es el archivo con la localización por defecto o base, en nuestro caso es por Español.
Configuración de archivos de recursos en JSF
Ahora debemos "decirle" a JSF cual es el archivo de recursos base y cual será el nombre del bean que lo representará en tiempo de ejecución, además configuraremos el locale por defecto y los locales disponibles. Para ello debemos editar el archivo WEB-INF/faces-config.xml y agregar:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" version="2.1">
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
<locale-config>
<default-locale>es</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>ar.com.magm.recursos.mensajes</base-name>
<var>msg</var>
</resource-bundle>
</application>
...
...
Creo que el agregado no merece mucha explicación, la estructura XML y los nombres de elemento hablan por si solos. A partir de aquí solo debemos recordar que accederemos al recurso mediante msg, ya que <var>msg</var> y que el recurso es un mapa, por ello haremos cosas como msg['clave'].
Hasta aquí hemos hecho todo lo necesario a nivel de infraestructura para dar soporte a I18N y L10N, ahora debemos recodificar algunos componentes para que esto realmente funcione.
Componentes del lado del cliente
A continuación copiaré los códigos de los archivos a modificar y resaltaré las modificaciones en negrita.
login.xhtml
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body style="text-align:center">
<p:growl id="mensajes" showDetail="true" life="2000" />
<h:form>
<p:panel header="#{msg['lbl.login']}" style="width:300px">
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="#{msg['lbl.username']}:" />
<p:inputText value="#{loginBean.nombre}" id="username" required="true" label="username" />
<h:outputLabel for="password" value="#{msg['lbl.password']}:" />
<p:password value="#{loginBean.clave}" id="password" required="true" label="password" />
<f:facet name="footer">
<p:commandButton id="loginButton" value="#{msg['lbl.login']}" actionListener="#{loginBean.login}" update=":mensajes" oncomplete="manejarLogin(xhr, status, args)" />
</f:facet>
</h:panelGrid>
</p:panel>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
function manejarLogin(xhr, status, args) {
if (!args.validationFailed && args.estaLogeado) {
setTimeout(function() {
window.location = args.view;
}, 1000);
}
}
//]]>
</script>
</html>
menu.xhtml
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<p:layout fullPage="true">
<p:layoutUnit position="center">
<iframe id="frame" src="ventas.xhtml" style="width: 100%; height: 100%; text-align: center;" seamless='seamless' />
</p:layoutUnit>
</p:layout>
<h:form id="form">
<p:dock>
<p:menuitem value="#{msg['lbl.ventas']}" icon="/images/ventas.png" url="javascript:cambioPagina('ventas.xhtml')" />
<p:menuitem value="#{msg['lbl.gauge']}" icon="/images/gauge.jpg" url="javascript:cambioPagina('gauge.xhtml')" />
<p:menuitem value="#{msg['lbl.logout']}" icon="/images/logout.png" actionListener="#{loginBean.logout}" oncomplete="logout(xhr, status, args)" />
</p:dock>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
var actual = 'ventas.xhtml';
function cambioPagina(pagina) {
if (pagina != actual) {
$('#frame').attr('src', pagina);
actual=pagina;
}
}
function logout(xhr, status, args) {
setTimeout(function() {
window.location = 'login.xhtml';
}, 500);
}
//]]>
</script>
</html>
gauge.xhtml
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form id="formGauge">
<p:poll interval="2" update="gauge" />
<p:meterGaugeChart id="gauge" value="#{gaugeBean.meterGaugeModel}" showTickLabels="false" labelHeightAdjust="110" intervalOuterRadius="130" seriesColors="66cc66, 93b75f, E7E658, cc6666" style="width:400px;height:250px" title="#{msg['lbl.gauge.title']}" label="km/h" />
</h:form>
</h:body>
</html>
Componentes del lado del server
Los componentes del lado del server también contienen datos que se muestran en las vistas del usuario y deben ser internacionalizados. A continuación se mostrarán las porciones de código y las clases que hay que modificar.
ar.com.magm.web.primefaces.LoginBean (solo método login())
public void login(ActionEvent actionEvent) {
RequestContext context = RequestContext.getCurrentInstance();
FacesContext jsfCtx= FacesContext.getCurrentInstance();
ResourceBundle bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
FacesMessage msg = null;
if (usuarioValido(nombre, clave)) {
logeado = true;
msg = new FacesMessage(FacesMessage.SEVERITY_INFO, bundle.getString("lbl.welcome"), nombre);
} else {
logeado = false;
msg = new FacesMessage(FacesMessage.SEVERITY_WARN, bundle.getString("lbl.error.login"), bundle.getString("lbl.invalidcredentials"));
}
FacesContext.getCurrentInstance().addMessage(null, msg);
context.addCallbackParam("estaLogeado", logeado);
if (logeado)
context.addCallbackParam("view", "menu.xhtml");
}
FacesContext jsfCtx= FacesContext.getCurrentInstance();
ResourceBundle bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
Luego para obtener un valor utilizamos: bundle.getString("clave")
ar.com.magm.web.primefaces.VentasBean
public class VentasBean implements Serializable {
private static final long serialVersionUID = -6690574219803425728L;
private String[] meses ;
private String sql = "SELECT year(fecha) as anio, month(fecha) as mes, zona, cliente, sum(importe*cantidad) as ventas FROM dw_ventasfact v INNER JOIN clientes c ON v.idCliente=c.idCliente INNER JOIN zonas z ON z.idZona=c.idZona WHERE cliente like ? GROUP BY zona, cliente, anio, mes ORDER BY anio,mes,zona,cliente";
private List<Venta> ventas;
private List<Venta> ventasFiltradas;
private List<String> zonas;
private FacesContext jsfCtx;
private ResourceBundle bundle;
public VentasBean() {
jsfCtx = FacesContext.getCurrentInstance();
bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
// processList(null);
}
public SelectItem[] getMesesOptions() {
meses=bundle.getString("lbl.months").split(",");
SelectItem[] r = new SelectItem[13];
for (int t = 0; t < meses.length; t++)
r[t + 1] = new SelectItem(meses[t], meses[t]);
return r;
}
...
...
public SelectItem[] getZonasOptions() {
SelectItem[] r = new SelectItem[zonas.size() + 1];
r[0] = new SelectItem("", bundle.getString("lbl.all.f"));
for (int t = 0; t < zonas.size(); t++)
r[t + 1] = new SelectItem(zonas.get(t), zonas.get(t));
return r;
}
private void processList(Object args[]) {
meses=bundle.getString("lbl.months").split(",");
ventas = new ArrayList<Venta>();
zonas = new ArrayList<String>();
...
...
Probando la Aplicación
Para probar la aplicación, solo debemos reconfigurar nuestro brower y cambiar entre los idiomas Español e Inglés, a continuación adjunto un video del funcionamiento de la aplicación internacionalizada.
Como siempre el proyecto completo y el WAR en el repo de GITHUB: https://github.com/magm3333/workspace-pftuto
Espero les sea útil.
Tutorial anterior: http://jmagm.blogspot.com/2013/03/integrar-spring-en-nuestro-proyecto.html
Próximo tutorial: http://jmagm.blogspot.com/2013/03/agregar-soporte-para-hibernate-nuestra.html
Saludos
Mariano
No hay comentarios.:
Publicar un comentario