Propòsit
El mòdul d’internacionalització té com a objectiu principal permetre el desenvolupament d’aplicacions en el que no sigui necessària cap reenginyeria cada vegada que s’incorpori un nou llenguatge a l’aplicació.
En general tota aplicació internacionalitzada pot realitzar-se de dues formes diferenciades:
- Mitjançant la rèplica d’un conjunt de pàgines per a cada idioma.
- Mitjançant un únic conjunt de pàgines que obtenen diferents literals de forma externa segons l’idioma.
La primera solució, fins ara utilitzada en moltes pàgines Web, implica un cost gran de rèplica i manteniment, ja que qualsevol nou requeriment o canvi comporta realitzar més d’un desenvolupament, un per cada idioma.
La segona solució, recomanada per Canigó permet que:
- Es puguin afegir llenguatges sense haver de realitzar canvis al codi.
- Els texts, imatges i missatges s’emmagatzemen de forma externa al codi.
- La informació (per exemple dates) es pot formatejar segons l’idioma de l’usuari
Glossari
i18N (Internationalization)
i18N o Internationalization (el 18 correspon a les 18 lletres que hi ha entre la I inicial i la n final) és el procés d’agafar una aplicació dissenyada i reestructurar-la per a què pugui ser usada en diferents localitats o bé definir el procés de crear-la per a ser totalment flexible per executar-se en qualsevol localitat. Java defineix les classes bàsiques d’ús de la internacionalització.
Instal.lació i configuració
Instal.lació
El mòdul de configuració s’inclou per defecte dins del core de Canigó 3. Durant el procés de creació de l’aplicació (Struts o JSF), l’eina de suport al desenvolupament inclourà la referència dins del pom.xml. En cas d’una instal- lació manual afegir les següents línies al pom.xml de l’aplicació:
<canigo.core.version>[3.1.0,3.2.0)</canigo.core.version>
<dependency>
<groupId>cat.gencat.ctti</groupId>
<artifactId>canigo.core</artifactId>
<version>${canigo.core.version}</version>
</dependency>
Configuració
Per a configurar el mòdul d’internacionalització s’han de manipular els següents fitxers:
- Definició dels fitxers de traduccions (típicament els fitxers dels diferents idiomes: application.properties, application_XX_XX.properties)
- Definició de la integració amb el servei de presentació (web.xml)
- Definició dels idiomes i literals suportats (només JSF).
No requereix configuració per a inicialitzar el mòdul per part del desenvolupador. Els xmls de configuració del servei es troben internalitzats dins del jar del core, i s’inicialitzarà de manera automàtica un cop arrenqui l’aplicació.
Els arxius de configuració d’idiomes de l’aplicació han d’estar en la següent ubicació:
<PROJECT_ROOT>/src/main/resources/config/i18n/*.properties
Automàticament el mòdul d’internacionalització carregarà tots els arxius de configuració d’idiomes perquè posteriorment puguin ser explotats per l’aplicació.
Definició dels fitxers de traducció
Ubicació:
<PROJECT_ROOT>/src/main/resources/config/i18n/*.properties
Explicació:
Els fitxers de traducció són els fitxers que contenen els missatges en els diferents idiomes. Es basen en l’ús de la internacionalització de Java (i18n o Internationalization).
Aquests fitxers s’han de construir tenint en compte que:
- La seva extensió ha de ser ‘.properties’ (es denominen resource bundles).
- Per cada idioma que volguem suportar cal crear un nou fitxer. Aquest fitxer tindrà la següent nomenclatura: ’nomFitxer__lg_pa._properties’, on ’lg’ correspon al codi del llenguatge i ‘pa’ correspon al codi del país pel qual volem definir els literals.
- El seu contingut es basa en l’ús de parells clau-valor tal i com es mostra a continuació:
Fitxer de multiidioma application.properties
clau_1=valor_1
clau_2=valor_2
...
En cas de que l’idioma escollit per l’usuari no correspongui amb cap fitxer, es pot crear un fitxer per defecte. Aquest fitxer tindrà el format ’nomFitxer.properties’.
Exemple:
- ‘applicationResources_en.properties’, per representar els literals en anglès
- ‘applicationResources_es.properties’, per representar els literals en castellà
- ‘applicationResources_ca.properties’, per representar els literals català
- ‘applicationResources.properties’, per representar els literals de qualsevol altre idioma
Més informació dels codis de pais e idioma:
Definició de la integració amb el mòdul de presentació
Fitxer de configuració: web.xml
Ubicació proposada: <PROJECT_ROOT>/src/main/webapp/WEB-INF/web.xml
Per integrar el mòdul d’internacionalització amb el mòdul de presentació s’han de definir els següents filtres a nivell d’aplicació:
Localization Filter
Aquest filtre s’encarrega d’exposar el mòdul d’internacionalització per tal de que sigui accessible a tota la capa web. Comú tant aplicacions JSF com a Struts.
Atributs:
Atribut | Requerit | Valor |
---|---|---|
filter-name | Sí | Nom del filtre Exemple: Localization Filter |
filter-class | Sí | cat.gencat.ctti.canigo.arch.web.core.filters.LocalizationFilter |
<filter>
<filter-name>Localization Filter</filter-name>
<filter-class>cat.gencat.ctti.canigo.arch.web.core.filters.LocalizationFilter</filter-class>
</filter>
Struts Locale Filter
Aquest filtre s’encarrega de d’integrar el mòdul d’internacionalització exposat pel filtre comú de Localization Filter per tal que des de Struts es pugui fer ús de fitxers d’internacionalització.
Atributs:
Atribut | Requerit | Valor |
---|---|---|
filter-name | Sí | Nom del filtre Exemple: Struts Locale Filter |
filter-class | Sí | cat.gencat.ctti.canigo.arch.web.struts.filter.StrutsLocaleFilter |
<filter>
<filter-name>Struts Locale Filter</filter-name>
<filter-class>cat.gencat.ctti.canigo.arch.web.struts.filter.StrutsLocaleFilter</filter-class>
</filter>
Definició dels idiomes i literals suportats (només JSF)
Ubicació proposada: <PROJECT_ROOT>/src/main/webapp/WEB-INF/faces-config.xml
La configuració del locale permet definir amb quin nom s’exposaran les propietats a les pàgines JSF, i els idiomes permesos i per defecte del navegador en cas de que no sigui informat.
Per defecte, l’eina de suport al desenvolupament de Canigó informa aquest valors segons les opcions seleccionades durant la creació de l’aplicació.
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude" 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_1_2.xsd">
<application>
.......
<locale-config>
<default-locale>ca_ES</default-locale>
<supported-locale>es</supported-locale>
<supported-locale>en</supported-locale>
<supported-locale>de</supported-locale>
</locale-config>
<resource-bundle>
<base-name>config.i18n.applicationResources</base-name>
<var>msg</var>
</resource-bundle>
.......
</application>
........
</faces-config>
En l’exemple anterior l’idioma per defecte és Català, el locales suportats: espanyol, anglès i alemany. Els recursos de l’aplicació es podran accedir des de la JSF a partir del nom de variable msg i trobaran situats a:
<PROJECT_ROOT>/src/main/resources/config/i18n/applicationResources.properties
<h:outputText value="#{msg.formulari_agregarCanvisArxiusIntegrats}"/>
Definició dels literals suportats (només Struts)
Ubicació proposada: <PROJECT_ROOT>/src/main/resources/struts/struts-config.xml
Aquest arxiu engloba la configuració de Struts, dins d’aquest podem definir els recursos que seran accessibles des de les pàgines web. Per tal d’habilitar aquesta internacionalització s’ha d’informar el tag message-resources.
Atribut | Requerit | Valor |
---|---|---|
parameter | Sí | Ruta per punts on estan localitzats els literals |
null | No | Informant aquesta propietat com a “false” mostrará els recursos no informats com a ?key? enlloc de mostrar null |
<message-resources parameter="i18n.applicationResources" null="false" />
Si es vol exposar més d’un arxiu de literals crear tants tags message-resources com siguin necessaris.
<message-resources parameter="i18n.applicationResources" null="false" />
<message-resources parameter="i18n.anotherApplicationResources" null="false" />
<message-resources parameter="i18n.thirdApplicationResources" null="false" />
Utilització del mòdul
JSF
Suposant que els recursos estan exposats a la variable msg i com a literals tenim:
applicationResources.properties
nomFormulari=Nom del formulari
prova.jsf
<h:outputText value="#{msg.nomFormulari}"/>
Si la key del literal a mostrar conté un punt s’ha de mostrar de la següent manera:
<h:outputText value="#{msg['nom.formulari']"/>
I si el literal és del tipus cadena=Hola {0} {1} es poden passar paràmetres de la següent manera:
<h:outputFormat value="#{msg['cadena']}">
<f:param value="Ramon" />
<f:param value="Sala" />
</h:outputFormat>
Struts
Suposant els següents literals:
applicationResources.properties
nomFormulari=Nom del formulari
prova.jsf
<font face="verdana">
<bean:message key="formulari.errorActivacio" />
</font>
Preguntes freqüents
Accés manual al servei d’internacionalització
Encara que no és una pràctica comú es pot accedir al mòdul d’idiomes desde qualsevol bean de l’aplicació gestionat per Spring. Per recuperar aquest bean el desenvolupador pot realitzar una crida de forma externa mitjançant el patró ‘Dependency Injection’ al mòdul i les seves propietats d’entorn.
- Injecció de dependències mitjançant xml. Per exemple:
Ruta proposada: <PROJECT_ROOT>/src/main/resources/spring/exemple-beans-config.xml
<bean id="myBean" class="cat.gencat.app.exemples.Injection">
<property name="i18nResource" ref="messageSource" />
</bean>
La clase Injection tindria la següent estructura:
import cat.gencat.ctti.canigo.arch.core.i18n.I18nResourceBundleMessageSource;
public class Injection {
I18nResourceBundleMessageSource i18n;
private static final Log LOGGER= LogFactory.getLog(Injection.class);
public void setI18nResource(I18nResourceBundleMessageSource i18nResource){
this.i18n = i18nResource;
}
public void executeCommand(){
if (LOGGER.isDebugEnabled()){
LOGGER.debug(i18n.getMessage("test.label"));
}
}
}
Spring s’encarregarà d’injectar el bean d’internacionalització dins del bean “myBean” executant el mètode setI18nResource.
- Injecció de dependències mitjançant anotacions:
import cat.gencat.ctti.canigo.arch.core.i18n.I18nResourceBundleMessageSource;
public class Injection {
private static final Log LOGGER= LogFactory.getLog(Injection.class);
@Autowired
I18nResourceBundleMessageSource i18n;
article table {
margin:1em 0;
}
public void executeCommand(){
if (LOGGER.isDebugEnabled()){
LOGGER.debug(i18n.getMessage("test.label"));
}
}
}
L’anotació @Autowired injecta en aquest cas, un bean de tipus cat.gencat.ctti.canigo.arch.core.i18n.I18nResourceBundleMessageSource que Spring trobarà en el context de l’aplicació.
Claus amb punts en JSF
A diferencia de Struts l’accés a propietats amb punts a la seva clau canvia:
Exemple de claus amb punts:
usuaris.name=Nom
usuaris.dni=DNI
usuauri.surname=Cognoms
Per accedir aquest tipus de clau des d’una pàgina JSF es necessari accedir de la següent manera:
pageExemple.jsf
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes" ?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:t="http://myfaces.apache.org/tomahawk">
<ui:composition template="../layouts/template.jsf">
<h:outputText value="\#{msg['usuaris.name']}"/>
</ui:composition>
</html>
Exemples
Exemple de Test unitari
Com a exemple d’utilització es mostra com podem fer una prova unitària:
- Creem la classe de test ‘I18nTest’ i afegim un mètode ’testI18N()’
- Creem els diferents fitxers de literals
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"../canigo-core.xml"})
public class I18nTest{
@Autowired
I18nResourceBundleMessageSource messageResource;
@Test
public void testI18N() {
try {
Assert.assertEquals("Hola!", messageResource.getMessage("var1"));
Assert.assertEquals("Hi!", messageResource.getMessage("var1", Locale.UK));
Assert.assertEquals("Attaaaaaack!", messageResource.getMessage("var1", Locale.US));
}
catch(NoSuchMessageException ex){
Assert.assertTrue("var1 not found", false);
}
}
}
Fitxers dels diferents idiomes (application_xx_XX.properties):
-
application.properties (fitxer per defecte)
var1=Hola!
-
application_en_US.properties (fitxer pel ‘Locale’ ‘US’)
var1=Attaaaaaack!
-
application_en_GB.properties (fitxer pel ‘Locale’ ‘GB’)
var1=Hi!