Traducción y localización en Angular

Traducción y localización en Angular
Photo by Kevin Walker / Unsplash

En este artículo vamos a explorar las diferentes opciones que Angular ofrece para integrar i18n, desde la funcionalidad incorporada hasta algunas bibliotecas de terceros, por lo menos las más populares. A medida que avancemos, veremos las ventajas e inconvenientes de cada una de ellas.

TL;DR

Este artículo explora las opciones para internacionalización (i18n) en Angular, comparando los métodos de traducción en tiempo de compilación y tiempo de ejecución.

Traducciones en tiempo de compilación (usando @angular/localize):

Las traducciones se integran en el build de la aplicación.

  • Ventajas: rendimiento óptimo, integración directa en el código, y soporte para formatos estándar como XLIFF.
  • Desventajas: genera múltiples builds (uno por idioma) y puede complicar el uso de service-workers.
Traducciones en tiempo de ejecución (con bibliotecas como ngx-translate o transloco):

Las traducciones se cargan desde archivos JSON en tiempo real.

  • Ventajas: cambio de idioma sin recargar la app, sencillo de implementar y sin necesidad de múltiples builds.
  • Desventajas: manejo más complejo de claves de traducción y menor rendimiento.
    Conclusión: Usa traducciones en tiempo de compilación para pocos idiomas y rendimiento óptimo. Usa traducciones en tiempo de ejecución para apps dinámicas que necesiten cambiar de idioma sin recarga.

¿Qué es i18n?

Antes de explorar las herramientas de i18n que podemos encontrar en el ecosistema de Angular, vamos a recordar qué es i18n.

La internacionalización, comúnmente abreviada como i18n (donde "18" representa el número de letras entre la "i" y la "n"), es el proceso de diseñar y desarrollar aplicaciones de software de tal manera que puedan adaptarse a diferentes idiomas y regiones sin necesidad de cambios en la estructura del código (por lo menos en teoría).

Tipos de traducciones en Angular

La internacionalización puede ser complicada, y se vuelve aún más compleja si tienes que decidir qué enfoque utilizar para gestionar tus traducciones y no sabes qué problemas pueden surgir con cada uno de ellos. En una aplicación Angular tenemos dos formas de manejar las traducciones:

  1. Traducciones en tiempo de compilación
  2. Traducciones en tiempo de ejecución

Vamos a comparar estos dos enfoques y ver cuál nos puede beneficiar más según nuestro caso de uso 🤓:

Traducciones en tiempo de compilación

Este es el enfoque principal que utiliza @angular/localize para las traducciones, que es el paquete oficial mantenido por Angular.

Ejemplo

Para crear traducciones con este método tendrás que utilizar el texto localizado real dentro de tu código y marcar el texto para traducción. Angular i18n utiliza el atributo HTML i18n para hacer esto en las plantillas.

<div>
  <label for="id" i18n>Este texto se marcará para traducción</label>
  <img src="sample.png" i18n-alt alt="este texto también se marcará para traducción">
</div>

Y en tu código TypeScript puedes hacerlo utilizando la función $localize.

$localize`texto a traducir`

Hay muchas formas de gestionar este tipo de traducciones; si quieres explorarlas más a fondo puedes consultar la documentación de @angular/localize.

Cómo funciona

Vamos a centrarnos en cómo funciona este tipo de traducción:

Antes de crear un build de tu aplicación, ejecutas un extractor que escanea todos los archivos en busca de los textos marcados para traducción y los extrae en un solo archivo. Normalmente esto se hace con ng extract-i18n. A cada entrada se le asigna un ID único basado en varios parámetros, como la ruta del archivo, el nombre, el texto, etc. El extractor generará un archivo que comúnmente puede llamarse messages.xlf, pero puedes configurarlo a tu gusto. El archivo tendría un aspecto parecido a este:

<trans-unit id="2543613062843710564">
  <source>Hello i18n!</source>
</trans-unit>

También se generan los archivos para cada traducción messages.{lang}.xlf, uno por cada idioma configurado en el angular.json de tu proyecto.

<trans-unit id="2543613062843710564" datatype="html">
  <source>Hello i18n!</source>
  <target>¡Hola i18n!</target>
</trans-unit>

Como se puede ver en el ejemplo de arriba, puedes añadir la traducción en el idioma correspondiente en la etiqueta <target>.

En el tiempo de compilación, la aplicación se compila una vez, donde todos los textos utilizados en el desarrollo se reemplazan con los IDs únicos del archivo de traducción principal. Luego, para cada idioma, el paquete se copia y todos los IDs se reemplazan con los textos de traducción correspondientes. Esto da como resultado que tengas un build por cada idioma, lo cual puede ser engorroso dependiendo de tu caso de uso.

Ventajas

Nos aporta tres beneficios clave:

  • Rendimiento: con este método, las traducciones de cada idioma están directamente en el build de cada idioma, lo que nos dará un rendimiento mucho mejor que cargarlas una vez que la aplicación se inicia.
  • Experiencia de desarrollo: al poder marcar cualquier string para traducir directamente en el código, no tenemos que preocuparnos por crear manualmente IDs y mantenerlos en un archivo externo, ya que el extractor hace esa parte por nosotros.
  • Formato: al usar el formato XLIFF para el fichero de traducciones, podemos usar herramientas de terceros para traducir estos archivos de una manera profesional, por ejemplo, localazy.

Desventajas

El mayor inconveniente de este tipo de traducciones es la necesidad de alojar todas las builds en vez de solo una, lo que nos obligaría, por ejemplo, a tener que desplegar nuestra página con diferentes rutas para cada idioma.

Otro gran inconveniente puede surgir al usar service-workers, ya que, como tenemos que alojar un build diferente por cada idioma, cada uno servirá su propio service worker, lo que puede darnos problemas para usuarios que puedan usar la aplicación en varios idiomas, por ejemplo, al recibir actualizaciones vía swUpdate.

Traducciones en tiempo de ejecución

Manejar tus traducciones en tiempo de ejecución normalmente implica usar un archivo tipo diccionario que asigna claves de traducción al texto traducido. Una de las librerías más populares, como ngx-translate, funciona de esta manera y usa archivos JSON para las traducciones. Aunque existen otras librerías como transloco que utilizan el mismo sistema.

Ejemplo

Mostraremos un ejemplo con ngx-translate. Para usar las traducciones podrás hacer uso tanto de pipes como de servicios que se encargaran de encontrar la traducción al código correspondiente:

Con pipe:

<div>
  <h1>{{ 'home.title' | translate }}</h1>
</div>

Con un servicio:

@Component({
  selector: 'app-root',
  template: `
     <div>
        <h1>{{ translatedTitle | async }}</h1>
    </div>
  `
})
export class AppComponent implements OnInit {

  translatedTitle: Observable<string>;

  constructor(private translateService: TranslateService) {
    this.translatedTitle = this.translateService.get('home.title');
  }
}

Cómo funciona

Como se aprecia en los ejemplos, nos vemos obligados a usar códigos para la traducción, en este caso home.title, que será el identificador que utilizaremos para definir el texto que se usará para cada idioma. Puede ser más engorroso que el método visto anteriormente, ya que de cierta forma desconecta las traducciones del código y puede dificultar su mantenimiento.

Para cada idioma tienes un archivo de diccionario. Cada archivo incluye todas las claves de traducción usadas en tu app y las asigna al texto traducido del idioma elegido.

// ENG
{
  "home": {
    "title": "Welcome to our homepage"
  }
}
// ESP
{
  "home": {
    "title": "Bienvenido a nuestra página de inicio"
  }
}

Ventajas

  • Fácil implementación: Solo necesitas instalar una biblioteca y agregar un archivo de diccionario.
  • Flexibilidad: Permite implementar traducciones en cualquier parte de la aplicación de forma sencilla.
  • Cambio de idioma en tiempo de ejecución: Se puede cambiar el idioma sin recargar la aplicación, evitando la necesidad de múltiples bundles por idioma.
  • Compatibilidad con service-workers: No requiere un service worker por idioma, lo que elimina problemas de actualización en apps multilingües.

Desventajas

Un inconveniente es la experiencia del desarrollador al manejar traducciones. En el código TypeScript y las plantillas HTML, siempre usas claves de traducción en lugar del texto real, lo que significa que debes consultar varios archivos para ver qué se muestra realmente. Los plugins como ngx-translate-lookup ayudan, pero sigue existiendo una desconexión.

El archivo JSON de diccionario es un formato no estándar para gestionar traducciones, lo que merma el abanico de herramientas específicas de traducción y complica el trabajo con servicios de traducción de terceros.

Además, este enfoque afecta el rendimiento y aumenta el tamaño del bundle. Y aunque se incluyan los archivos en el bundle para mejorar la velocidad, aún aumenta el tamaño total, lo cual podría no ser un problema dependiendo del caso de uso.

Por último, gestionar el archivo de diccionario correctamente es complicado: las claves de traducción son cadenas sueltas, lo que significa que errores tipográficos o claves obsoletas pueden pasar desapercibidos. Aunque, por ejemplo, con ngx-translate existen formas de lidiar con este problema, no son totalmente perfectas y requieren un setup de código adicional.

Conclusiones

Elegir el enfoque de traducción en Angular depende bastante de lo que necesites para tu proyecto. Aquí van algunas recomendaciones que te pueden ayudar a decidir:

  • Traducciones en tiempo de compilación (@angular/localize):

    • Genial para proyectos con pocos idiomas: Si tu aplicación solo va a tener un par de idiomas y no planeas agregar más, este enfoque es perfecto.
    • Rendimiento superior: Si lo que buscas es que tu aplicación funcione rápido, este método es el mejor, ya que las traducciones se incluyen en el build y no se cargan mientras la app está en ejecución.
    • Simplicidad en el desarrollo: Si prefieres tener todo integrado y que los textos traducidos estén directamente en tu código, este es el camino a seguir.
  • Traducciones en tiempo de ejecución (ngx-translate u otras librerías):

    • Ideal para apps dinámicas: Si tu aplicación necesita poder cambiar de idioma al vuelo y no quieres tener que recargar la página, este enfoque es el más flexible.
    • Fácil de usar: Este método es bastante sencillo de implementar y no requiere múltiples builds, lo que facilita la gestión de traducciones a medida que tu proyecto crece.

Así que, en resumen: si quieres rendimiento y tienes un número limitado de idiomas, ve por las traducciones en tiempo de compilación. Pero si prefieres flexibilidad y rapidez para adaptarte a cambios, las traducciones en tiempo de ejecución son la mejor opción.

Comparativa

Tambien te dejo una tabla por si te ayuda a decidir

Característica Traducciones en Tiempo de Compilación (@angular/localize) Traducciones en Tiempo de Ejecución (ngx-translate, transloco)
Implementación Requiere marcar texto para traducción en el código, usando @angular/localize. Genera un archivo de traducciones estándar (XLIFF). Usa archivos de diccionario JSON para cada idioma, cargados en tiempo de ejecución.
Rendimiento Alto, ya que las traducciones están integradas en el build final. Menor, ya que las traducciones se cargan y procesan al ejecutarse la app.
Flexibilidad en Cambio de Idioma No permite cambiar el idioma sin recargar la página (un build por idioma). Permite cambiar el idioma al vuelo sin necesidad de recarga.
Compatibilidad con Service Workers Puede ser problemática debido a los múltiples builds, uno por idioma. Compatible, ya que no requiere un service worker por idioma.
Tamaño del Build Genera un build por cada idioma, aumentando el almacenamiento necesario. Un solo build que carga los archivos JSON cuando son necesarios.
Formato de Archivos de Traducción Usa formatos estándar (XLIFF), compatibles con herramientas profesionales de traducción. Usa JSON, que es fácil de manejar pero menos compatible con herramientas de traducción profesional.
Experiencia de Desarrollo Los textos traducidos están integrados en el código, mejorando la claridad. Se usan claves de traducción, lo que requiere verificar en archivos externos.
Recomendado para... Apps con pocos idiomas y necesidad de alto rendimiento. Apps que necesitan flexibilidad para cambiar de idioma en tiempo de ejecución y fácil escalabilidad.

Extras

Lint para @angular/localize

Si decides usar @angular/localize para tus traducciones puedes activar la siguiente opción en eslint para que te ayude a detectar textos que no están marcados para traducción:

'@angular-eslint/template/i18n'

Usar @angular/localize en tiempo de ejecución

Usando angular localize es posible tener un solo build y múltiples idiomas esto se puede hacer con la funcion loadTranslations pero solo afectará a las llamadas de $localize los tags i18n en nuestro html no tendrán traducción. Tengo que decir que este método no es para nada pulido por parte del equipo de angular pero si eres lo suficientemente valiente puedes seguir esta guia para intententar ponerlo en funcionamiento

Traducir archivos .json

Si usas ngx-localize puedes usar el siguiente paquete para generar rápidamente traducciones json-translator