domingo, 29 de octubre de 2017

Internacionalización en Angular (i18n)

Las herramientas de internacionalización de Angular (i18n) nos dan la posibilidad de tener nuestra aplicación en multiples idiomas.

En esta entrada veremos un ejemplo de las herramientas de i18n disponibles en Angular para crear un componente en varios idiomas.

Para poder hacer traducir nuestra aplicación  en varios idiomas tener que realizar los siguientes pasos:

1. Marcar los textos estáticos que forman parte de nuestros templates de componentes como "traducibles", o dicho te otra manera, identificar aquellos textos que queremos traducir.

Para marcar los textos estáticos que serán traducidos tenemos que marcarlos con el atributo i18n de Angular.
Por ejemplo, en nuestra plantilla del componente vamos a tener dos títulos que queremos traducir. Un h3 y un h4, y en ambos casos los marcamos con el atributo i18n tal como se muestra a continuación:

<h3 i18n>Ejemplo de traduccion en Angular</h3>
<h4 i18n>Traducciendo se entiende la gente</h4>

A la hora de marcar elementos del html con el atributo i18n podemos definir descripciones, identificadores, etc. (en la página de Angular https://angular.io/guide/i18n podemos ver ejemplos de todo esto)
Tambien podemos traducir propiedades de elementos html como por ejemplo la propiedad title de una imagen:

<img [src]="logo" i18n-title title="Angular logo" />

2. Extraer los mensajes marcados a un archivo fuente de traducción

Para crear el fichero de traducciones haremos uso de la herramienta ng-xi18n de Angular cli.
Para generar el fichero, desde un terminal teclearemos:

"./node_modules/.bin/ng-xi18n"

Existen varios parametros que podemos indicar en este comando. Uno de ellos es el formato de l fichero, y otro el nombre del fichero generado. En el siguiente ejemplo indicamos que vamos a generar el fichero messages en formato xlf:

"./node_modules/.bin/ng-xi18n  --i18nFormat=xlf  --outFile=messages.xlf"

Angular i18n soporta los formatos XLIFF 1.2, XLIFF 2 y el formato XML Message Bundle (XMB).

Para especificar el fichero anterior en estos otros formatos:

"./node_modules/.bin/ng-xi18n  --i18nFormat=xlf2 --outFile=xliff2.xlf"
"./node_modules/.bin/ng-xi18n  --i18nFormat=xmb  --outFile=messages.xmb"

Una opción que nos va a permitir ahorrar tiempo y no tener que estar ejecutando este comando es incluirlo en la sección de "scripts" de nuestro fichero package.json:

"scripts": {
...
"i18n": "./node_modules/.bin/ng-xi18n --i18nFormat=xlf --outFile=messages.xlf"
},

3. Un traductor edita ese archivo, traduce los mensajes de texto especificados al idioma indicado y lo escribe en el archivo.

Una vez que tenemos generado nuestro fichero messages.xlf lo que tenemos que hacer es crear los ficheros de traducción especificos para cada lenguaje que queramos.
Para tener nuestro proyecto organizado vamos a crear una carpeta que llamaremos "locale" donde vamos a crear los ficheros de traducción para ingles y francés (messages.en-US.xlf y messages.fr-FR.xlf, además de nuestro fichero original messages.xlf).






Dentro de nuestros ficheros de traducción colocaremos las traducciones para cada uno de los idiomas. Veamos como queda en nuestro ejemplo la traducción en inglés:


Para crear el fichero de traducción lo que tenemos que hacer es coger cada estructura <trans-unit> del fchero messages.xlf, y traducir cada <source> en su correspondiente <target>

Es decir, si en el messages.xlf tenemos:


<trans-unit id="2d570136ebde320fb881d3e3742479a2f1c91e98" datatype="html">
<source>Ejemplo de traduccion en Angular</source>
<context-group purpose="location">
<context context-type="sourcefile">src\app\app.component.ts</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>

En  el fichero messages.en-US.xlf se convierte en:

<trans-unit id="2d570136ebde320fb881d3e3742479a2f1c91e98" datatype="html">
<source>Ejemplo de traduccion en Angular</source>
<target>Example of translation in Angular</target>
</trans-unit>

Y haremos los mismo para cada una de las unidades de traducción (trans-unit) para cada uno de los idiomas.

4. El compilador de Angular importa los archivos de traducción con la traducción incluida y
reemplaza los mensajes originales con el texto traducido y genera una nueva versión de la aplicación en el idioma especificado. 

Existen en Angular tres proveedores que le dicen al compilador JIT cómo traducir los textos de la plantilla para un idioma particular mientras se compila la aplicación.

- TRANSLATIONS es una cadena que contiene el contenido del archivo de traducción.
- TRANSLATIONS_FORMAT es el formato del archivo: xlf, xlf2, o xtb.
- LOCALE_ID es la localización del idioma de destino.

Con eso, lo qeue tenemos que hacer es indicar en el bootstrap que use los providers mencionados. Esto lo hacemos en la función bootstrapModule del fichero main.ts


export function getTranslationProviders(): Promise<Object[]> {
let locale = 'en-US';
const noProviders: Object[] = [];
if (!locale || locale === 'es-ES') {
return Promise.resolve(noProviders);
}
const translationFile = `../locale/messages.${locale}.xlf`;
return getTranslationsWithImports(translationFile)
.then((translations: string) => [
{ provide: TRANSLATIONS, useValue: translations },
{ provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
{ provide: LOCALE_ID, useValue: locale }
])
.catch(() => noProviders);
}
function getTranslationsWithImports(file: string) {
const util = new Utility();
return util.getFile(file);
}
getTranslationProviders().then(providers => {
const options = { providers };
platformBrowserDynamic().bootstrapModule(AppModule, options);
});

En el código anterior, cuando se resuelve la promesa antes de iniciar la apliación tenemos los providers que pasamos como parámetro (options) al bootstrapModule.

(Nota:  const options = { providers }; es equivalente a const options = { providers: providers };)

En el método getTranslationProviders estamos estableciendo que el lenguaje sea inglés:

let locale = 'en-US';

e indicando donde se encuentra el fichero de traducción del idioma especificado:

const translationFile = `../locale/messages.${locale}.xlf`;

 para finalmente devolver los providers mencionados anteriormente:

return getTranslationsWithImports(translationFile)
.then((translations: string) => [
{ provide: TRANSLATIONS, useValue: translations },
{ provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
{ provide: LOCALE_ID, useValue: locale }
])
.catch(() => noProviders);


Como hamos visto, angular provee una forma razonablemente fácil de tener nuestras aplicaciones en multiples idiomas.

Puede descargar el código fuente de: https://github.com/jorgetrigueros/ng4-translations