13/07/2020

Cómo hacer una Web App multiidioma en Angular: Textos y formatos (I)

Digital55
Programando Multiidioma 1.5

Uno de los principios más importantes a la hora del desarrollo Front-End es la accesibilidad. Adaptar un sitio web a distintos idiomas hará que atraiga a un público mucho más amplio, proporcionando la oportunidad de expandir tu negocio mediante una inversión pequeña. Como desarrolladores, planificar nuestros proyectos de forma que esta adaptación sea rápida y poco costosa es bastante más fácil de lo que parece y garantiza ciertas ventajas incluso aunque finalmente nuestro cliente decidiera no ofrecer varios idiomas en su web.

Hacer una web app multiidioma en Angular conlleva 4 procesos:

  1. Traducir los textos básicos de la web (títulos, párrafos…).
  2. Traducir las utilidades de Angular (DatePipe, DecimalPipe…).
  3. Traducir los componentes de Angular Material.
  4. Traducir módulos de terceros que estamos utilizando en nuestro proyecto (Calendarios, editores WYSIWYG…).

Nota: Todos los ejemplos pertenecen a un proyecto con Angular 9, es posible que algunas partes varíen si utilizas una versión distinta, consulta la documentación oficial para ver las diferencias.

Angular: traducir los textos básicos de la app

Para traducir los textos básicos de la web podemos utilizar ngx-translate. Su funcionamiento es bastante sencillo: cada idioma al que vayamos a dar soporte tendrá un archivo JSON en el proyecto, con una serie de claves que harán referencia a un fragmento de texto. La documentación del módulo es bastante buena y es recomendable su lectura, pero vamos a resumir cómo configurarlo:

Lo primero es instalarlo:

 npm install @ngx-translate/core @ngx-translate/http-loader --save

A continuación, añadimos TranslateModule.forRoot() a la lista de imports de AppModule de la siguiente forma:

Código Angular @NgModule

En el AppComponent, inicializamos TranslateService e indicamos el idioma a usar y el idioma que se usará en caso de no disponer de una traducción para el idioma que tenga el usuario establecido en su navegador. En este caso, indicaremos que inglés es el idioma por defecto.

código AppComponent - TranslateService

Nota: el motivo por el que usamos el método getBrowserLang() de TranslateService en vez de usar navigator.language, es que el último devuelve el locale completo (idioma + país o cultura, es decir, ‘es-ES’ en vez de ‘es’). Por norma general solo querremos conocer el idioma, ya que no proporcionaremos traducciones específicas por país.

Creamos un módulo llamado SharedModule (que importaremos en el resto de módulos) y añadimos TranslateModule a la lista de exports. Esto nos dará acceso a la pipe Translate en todos nuestros componentes, de forma que podremos traducir directamente desde el template HTML.

Finalmente, dentro de la carpeta assets del proyecto, creamos una carpeta i18n y un archivo JSON por cada idioma al que daremos soporte (es.json, en.json…). Para probar que todo funciona, añadiremos una clave a cada archivo con la traducción correspondiente.

Programando Hola Mundo

En el componente que queramos probar la traducción (por ejemplo, AppComponent), añadimos la clave al template HTML y utilizamos la pipe translate para obtener la traducción:

código hello: translate

Si todo ha ido bien, cuando accedamos a ese componente veremos el texto “Hola mundo”, si el navegador está en español, y “Hello world” tanto si está en inglés como en cualquier otro idioma (ya que hemos establecido el inglés como idioma por defecto). Si necesitamos mostrar texto desde la parte ts del componente (por ejemplo, para mostrar una alerta con un error), creamos una instancia de TranslateService en el componente y utilizamos el método instant() pasándole la clave a traducir.

código Angular: OnInit

Utilizar claves en vez de textos a mano es útil incluso aunque finalmente no se vaya a ofrecer la web en varios idiomas. Copiar y pegar (o peor, volver a escribir) textos que se repiten por toda la web en un montón de archivos HTML individuales probablemente provoque inconsistencias indeseadas y evitables. Desde errores ortográficos a acciones que deberían ser idénticas, pero no lo son al no provenir del mismo sitio. Modificar estos textos, aunque es posible, también será mucho más engorroso si los tenemos desperdigados por toda la web en lugar de centralizados en un archivo.

Con esto cubrimos la gran mayoría de textos de la app, aunque si queremos hacer un trabajo perfecto aún quedan algunos pasos adicionales.

 

Traducir las utilidades de Angular

Entre las utilidades más destacables de Angular están las pipes. Estas nos permiten adaptar cierto contenido al formato que deseemos y, en ciertos casos, al locale (idioma/cultura) que queramos. Por ejemplo, DatePipe toma como valor una fecha (ya sea un objeto Date, un valor numérico en milisegundos o un string con una fecha en formato ISO) y la formatea de forma que sea más legible al usuario (“08/07/2020”, “8 de Julio de 2020”, “Miércoles, 8 de Julio”, etc). Como parámetros, todos ellos opcionales, acepta el formato que se desea obtener, la zona horaria en la que queremos mostrar la fecha (si no se le proporciona utilizará la del dispositivo del usuario) y un locale.

Ejemplo de uso:

{{ today | date: ‘long’ }}

Suponiendo que today sea un objeto Date con fecha del 8 de julio de 2020, esto mostraría la fecha con el formato: “July 8, 2020 at 5:09:26 PM GMT+2”.

Aquí entramos en uno de los errores más comunes a la hora de utilizar las pipes de Angular. Varias de estas pipes (DatePipe, CurrencyPipe, DecimalPipe, PercentPipe, entre otras) aceptan locale como parámetro a la hora de utilizarlas. En el ejemplo anterior hemos visto como DatePipe nos devuelve una fecha con formato en inglés. Puede parecer tentador indicar a mano el locale a la hora de usar estas pipes, pero, como veremos a continuación, se trata de un error. Si añadimos ‘es’ a la pipe (null como segundo parámetro indica que no queremos una timezone distinta a la del usuario):

{{ today | date: ‘long’: null: ‘es’ }}

Angular nos devolverá un error puesto que aún no hemos importado ese locale en el proyecto.

código error: InvalidPipeArgument

En el AppModule añadimos lo siguiente:

código registerLocaleData

Si ahora volvemos a comprobar el resultado de usar esa pipe en el navegador, efectivamente muestra la fecha con el formato que le hemos indicado y en español (en este caso: “8 de julio de 2020, 17:18:18 GMT+2”).

Sin embargo, esto es un error. El parámetro locale de las pipes de Angular es una forma de forzar a que ignore el locale en el que hayamos configurado el proyecto y utilice, sí o sí, el que le indicamos como parámetro. Independientemente del idioma del navegador, desde este momento esa fecha se va a mostrar siempre en español, y no hay forma de cambiarlo salvo dejar de pasarle ese parámetro. Esto presenta también el problema de que cada vez que utilicemos una de estas pipes, tendremos que indicar el locale a mano, una y otra vez. Indicar a mano el locale tiene sus usos puntuales, pero no es algo a utilizar de manera general.

Lo que debemos hacer es decirle a Angular qué locale por defecto queremos usar, de forma que todas esas pipes empiecen a usarlo sin indicárselo a mano. Para ello, dentro del array de providers en el AppModule debemos añadir algo así:

{ provide: LOCALE_ID: useValue: ‘es’ }

Nota: LOCALE_ID se importa de @angular/core.

Con añadir esa línea, todas las pipes empezarán a usar el idioma español por defecto. Cambiar el locale de todas las pipes del proyecto es ahora muy sencillo, solo tenemos que registrar el locale (utilizando la función registerLocaleData() como vimos antes) y en el provider indicar el idioma a usar.

Como nuestro propósito es hacer la web multiidioma, es conveniente que el valor que pasemos al LOCALE_ID sea el que esté usando el usuario. Vamos a ello.

Creamos un LanguageService en tu proyecto de la siguiente forma:

código angular: injectable - LanguageService

Posteriormente, modificamos nuestro array de providers en el AppModule de la siguiente forma:

código providers: locale_id

El funcionamiento es bastante sencillo: tenemos un array de idiomas a los que la web dará soporte y un idioma por defecto. Si el usuario está usando alguno de los idiomas a los que damos soporte, le decimos a Angular que utilice ese locale. En caso contrario y como hicimos con ngx-translate, usamos el idioma por defecto de la web.

Importante: no podemos olvidar que debemos usar el método registerLocaleData() para importar cualquier locale que pretendamos usar en la web (salvo el inglés, que Angular incluye por defecto). De lo contrario, si un usuario con el navegador en italiano (por ejemplo) entrase a la web, le estaríamos diciendo a Angular que queremos usar un locale que no hemos importado, lo cual dará error.

 

En la Parte II veremos cómo traducir componentes tanto de Angular Material como de terceros.

Digital55

Comparte el post:

¿Hablamos?

Si necesitas desarrollar o mejorar tu negocio digital, cuenta con nosotros. Puedes escribirnos un mail a hello@digital55.com, llamarnos al teléfono 913 091 641 o rellenar el formulario que encontrarás más abajo.