# Internationalización (i18n) en Angular
Español | English
Un paso básico en prácticamente todos los proyectos es configurar un sistema que nos permita idiomatizar nuestra aplicación. En ese sentido, el uso del sistema de internacionalización oficial de Angular nos permite estar más alineados con la evolución natural de Angular. Dicho sistema, basado en la generación de las traducciones en tiempo de compilación, crea tantas versiones de nuestra aplicación como diferentes idiomas hayamos definido, por lo que no necesitaremos un servicio de traducción o diccionario especial, simplemente tendremos los textos finales en la aplicación.
Cuando hablamos de traducciones en tiempo de compilación, Angular reemplaza cada texto marcado con el texto de traducción correspondiente del archivo de traducción. Este proceso se ejecuta una vez para cada idioma, por lo que finalmente terminaremos con un paquete de aplicación compuesto por varios paquetes, uno para cada idioma.
Tengamos en cuenta las ventajas de este sistema:
- Más rendimiento porque no hay un diccionario de traducción cargado en memoria en tiempo de ejecución.
- Más rendimiento porque no es necesario traducir el texto de cada aplicación en cada evento de detección de cambio de aplicación.
- Mejor mantenibilidad por la mejor experiencia de desarrollo en la forma de usarlo.
- Mejor mantenibilidad por utilizar un formato de archivo bien extendido como es XLIFF.
Y la principal desventaja:
- Recarga de la aplicación al cambiar el idioma.
Este sistema incluye varios pasos básicos:
- Configuración inicial.
- Localización del contenido de la aplicación para formateo o extracción.
- Extracción de texto para traducciones.
- Generación de archivos de traducción.
- Construcción de un proyecto idiomatizado.
Si quieres conocer las diferentes opciones existentes a la hora de idiomatizar una aplicación Angular, te recomiendo que le eches un vistazo a este otro artículo que he creado sobre las Mejores alternativas para la internationalización (i18n) en Angular.
# Configuración inicial
El primer paso es instalar las dependencias necesarias utilizando Angular CLI:
ng add @angular/localize
Esto añadirá el paquete @angular/localize y cargará la función $localize
en el ámbito global.
Angular utiliza Unicode locale ID para establecer la configuración regional (localize en inglés, de ahí que a veces use la expresión localizar) de los datos, siendo su valor por defecto en-US. Si no, se puede especificar a través de la propiedad sourceLocale en el archivo angular.json.
# Localización del contenido de la aplicación para formateo o extracción
El siguiente paso es preparar nuestro contenido para ser transformado de la forma correcta utilizando el idioma y la configuración regional basados en el LOCALE_ID y para ser extraído y ser traducido externamente.
Para mostrar nuestros datos en el formato correcto contamos con Angular pipes (opens new window) que nos permite transformarlos en base a la configuración idiomática que hayamos establecido:
{{ myDateTime | date:'shortTime' }} // will be '5:15 PM' for 'en-US' and '17:15' for 'en-GB'
{{ myAmount | number }} // will be '2,138.62' for 'es' and '2 138,62' for 'fr'
Para preparar nuestro contenido para ser extraído tenemos que utilizar el atributo i18n y la función $localize para marcar nuestras cadenas de texto como traducibles.
<p i18n>Text to translate.</p>
<ng-container i18n>Text to translate.</ng-container>
<img [src]="image.png" i18n-title title="Image title" i18n-alt alt="Image alt"/>
private myText: $localize 'Text to translate';
private myText: $localize `Text to translate ${variable}`;
# Extracción de texto para traducciones
Después de preparar nuestro contenido para ser extraído, debemos utilizar Angular CLI para extraer a un archivo de traducciones base todas las cadenas marcadas. En este caso, crearé una nueva carpeta locale durante el proceso de generación dentro de la cual se creará un nuevo archivo messages.xlf con todos los textos marcados de nuestra aplicación.
ng extract-i18n --output-path src/locale
# Actualizar el archivo de traducción base
- Realizamos los cambios en la aplicación.
- Ejecutamos
ng extract-i18n --output-path src/locale
para actualizar el archivo messages.xlf.
# Generación de archivos de traducción
Es hora de crear y traducir cada archivo de configuración regional utilizando un editor de archivos XLIFF o crearlo y editarlo nosotros mismos:
- Hacemos una copia del archivo de idioma fuente (messages.xlf por defecto) en la misma carpeta locale y lo renombramos con cada localización requerida (message.{locale}.xlf).
- Añadimos un atributo target-language al nodo file con el código de localización, por ejemplo target-language="es".
- Buscamos el primer nodo trans-unit.
- Duplicamos el nodo hijo source y le cambiamos el nombre a target.
- Traducimos el contenido del nodo target.
- Repitimos los mismos pasos anteriores con cada nodo trans-unit.
Para ayudarnos a gestionar los archivos de traducción podemos utilizar la extensión XLIFF Sync (opens new window) y usamos el comando Create New Target File(s) para configurar el entorno de proyecto para la extensión.
# Añadir nuevas configuraciones regionales
- Hacemos una copia del archivo del idioma de origen y lo renombramos con la configuración regional requerida o utilizamos la extensión XLIFF Sync (Create New Target File(s)) para crear un nuevo archivo de destino (un nuevo idioma alternativo).
- Cambiamos el archivo angular.json para añadir la nueva configuración regional a la sección projects -> Your.Project.Name -> i18n -> locales. Hablaré de esta sección más adelante.
# Gestión de los archivos de traducción
- Utilizamos la extensión XLIFF Sync (comando Synchronize Translation Units) para sincronizar todas las unidades de traducción entre el archivo base y todos los idiomas alternativos.
- Podemos editar los archivos de destino manualmente o si utilizamos un servicio externo (por ejemplo, https://smartcat.com/) para traducirlos, y luego utilizar la extensión XLIFF Sync (comando Import Translations from File(s)) para copiar las traducciones de los archivos seleccionados a los archivos de destino con el mismo idioma (recordemos comprobar los atributos de archivo source-language y target-language del archivo de traducción de origen para asegurarse de que funciona correctamente).
# Construcción de un proyecto idiomatizado
Lo primero que tenemos que hacer es definir todas las configuraciones regionales soportadas utilizando la propiedad i18n del proyecto en el archivo angular.json:
{
// ...
"projects": {
"my-app": {
// ...
"i18n": {
"sourceLocale": "en-US",
"locales": {
"es": {
"translation": "src/locale/messages.es.xlf",
"baseHref": "/es/"
},
"pt": {
"translation": "src/locale/messages.pt.xlf",
"baseHref": "/pt/"
},
// ...
}
},
"architect": {
// ...
}
}
}
// ...
}
angular.json
Entonces podemos usar nuestra definición de configuración regional en la configuración de compilación para decidir qué localizaciones deben generarse durante el proceso de compilación. Para ello, debemos utilizar la opción localize de la sessión build del archivo angular.json:
{
// ...
"projects": {
"my-app": {
// ...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"localize": true,
"i18nMissingTranslation": "error",
// ...
},
"configurations": {
"production": {
// ...
},
"development": {
"localize": false,
// ...
},
"es": {
"localize": ["es"]
},
"pt": {
"localize": ["pt"]
}
},
"defaultConfiguration": "production"
},
// ...
}
}
}
// ...
}
angular.json
Los valores admitidos para localize son:
- true, generará los paquetes para todas las configuraciones regionales definidas.
- false, generará sólo el paquete del idioma de origen.
- ["locale-id-1", "locale-id-2", ...], generará sólo los paquetes de las localizaciones especificadas.
Cuando se utiliza ng server, los únicos valores permitidos para la opción _localize son false y ["locale-id-1"] (matriz de un solo elemento).
Otra propiedad interesante para configurar en nuestro archivo angular.json es la opción de compilación i18nMissingTranslation con el valor error que lanza un error en la compilación cuando falta una traducción. El valor por defecto permite la compilación y sólo mensajes de advertencia sobre las traducciones.
Por último, podemos añadir nuevas configuraciones de compilación para locales específicos y también utilizarlo para servir la aplicación en un idioma específico. Sólo hay que añadir nuevos pequeños cambios a nuestro archivo angular.json:
{
// ...
"projects": {
"my-app": {
// ...
"architect": {
"build": {
// ...
"configurations": {
// ...
"es": {
"localize": ["es"]
},
"pt": {
"localize": ["pt"]
}
},
// ...
},
"serve": {
// ...
"configurations": {
// ...
"es": {
"browserTarget": "my-app:build:development,es"
},
"pt": {
"browserTarget": "my-app:build:development,pt"
}
},
// ...
},
// ...
}
}
}
// ...
}
angular.json
Y el script para ejecutar nuestra aplicación en el idioma específico:
ng serve --configuration=es
ng serve --configuration=pt
¿No cambia el idioma al depurar? Cuando se utiliza la plantilla ASP.NET Core Angular podríamos tener problemas al ejecutar la aplicación localmente con ng serve modificando la configuración regional a mostrar. Después de ejecutar la aplicación por primera vez y apagarla usando Ctrl+C, si reiniciamos la aplicación después de cambiar la configuración localize a otro locale ID, el proceso anterior (Node.js connection listener) seguirá vivo y deberemos pararlo manualmente para ver tu aplicación en el nuevo localize. Para parar el proceso anterior utilizamos la respuesta que se ajuste a nuestro sistema y puerto según esta pregunta de Stack Overflow "Port 4200 is already in use" when running the ng serve command (opens new window). Puedes encontrar más información sobre cómo las instancias de Node.js continúan ejecutándose después de depurar ASP.NET Core en el tema 38897 (opens new window) de GitHub.
Aquí hay un proyecto básico de demostración con internacionalización Angular (opens new window) en Stackblitz.
# Enrutamiento de aplicaciones
Por último al generar nuestra aplicación, si my-app es el directorio que contiene los ficheros del proyecto, al generar múltiples idiomas, se generarán tantos subdirectorios como configuraciones regionales hayamos establecido. Por ejemplo, la versión en español de my-app estará en el directorio my-app/es.
Una vez generada y desplegada la aplicación, ésta se encargará de enrutar al usuario al directorio específico de cada idioma en función de la configuración de enrutamiento especificada en la propiedad baseHref de nuestro archivo angular.json para cada configuración regional.
Por defecto, los usuarios serán redirigidos al idioma indicado en la cabecera HTTP Accept-Language a menos que se acceda a la ruta de un idioma específico. En todos los demás casos en los que no se reciba información sobre el idioma, se utilizará el idioma por defecto de la aplicación.
# Utilizar un idioma por defecto distinto de en-US
Por defecto Angular usa en-US como configuración regional por defecto, así que necesitamos hacer algún cambio para reescribir la configuración por defecto usada en nuestra app.
Para establecer la configuración regional de la aplicación a es (todos los textos de la plantilla los tenemos en español):
- Establecemos el token LOCALE_ID a es en el archivo app.module.ts para propagar la configuración regional a través de la aplicación.
- Usamos la función registerLocaleData en el archivo app.module.ts para registrar la configuración regional que usará internamente Angular.
- Establecemos la propiedad i18n\sourceLocale a es en archivo angular.json para generar la aplicación sólo para esta configuración regional.
import { registerLocaleData } from '@angular/common';
import localeEs from '@angular/common/locales/es';
import { LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [{ provide: LOCALE_ID, useValue: 'es' }],
})
export class AppModule {
constructor() {
registerLocaleData(localeEs);
}
}
app.module.ts
{
// ...
"projects": {
"my-app": {
// ...
"i18n": {
"sourceLocale": "es"
},
"architect": {
// ...
}
}
}
// ...
}
angular.json