Name Last Update
i18n Loading commit data...
Example1.jsx Loading commit data...
LanguageHelper.js Loading commit data...
README.md Loading commit data...
translate-context.jsx Loading commit data...
translate-redux.jsx Loading commit data...

Introducción

Alrededor de agosto del 2016 nos pidieron que uno de nuestros sistemas tuviera soporte para múltiples idiomas. La idea era que los usuarios pudieran usar la aplicación en el idioma que ellos quisieran, que en un principio iba a ser, aparte del español original, inglés y portugués.

Puesto que la aplicación en cuestión se estructura en un backend y en un frontend, la internacionalización (o i18n para abreviar) se tuvo que hacer en ambos componentes. En este artículo voy a hablar de la i18n del frontend de la aplicación.

La traducción de cualquier tipo de frontend implica que, en vez de usarse strings estáticos para desplegar texto, se deben usar strings dinámicos, las cuales vienen de alguna parte y dependen de un parámetro idioma, el cual se fija de algún modo. Hay muchas alternativas para fijar el idioma y proveer los strings dinámicos.

Para esta implementación me basé en una respuesta específica de stackoverflow, e implementé en base a ella un sistema de provisión de strings para componentes de React.

En nuestro repositorio de Gitlab pueden encontrar el código usado. Este código es prácticamente el mismo que está en producción actualmente, en la aplicación traducida.

Parámetro idioma

Para configurar el idioma de forma global se usa el contexto de React, guardándolo también en localStorage para hacerlo persistente; sin embargo, como los cambios en el contexto no actualizan todos los componentes, hace falta recargar la aplicación completa, configurando el contexto al inicializar la aplicación.
La primera vez que se fija el idioma, este se obtiene del parámetro navigator.language, y se revisa si existe en los idiomas disponibles. Si no existe, se deja un lenguaje por defecto.
De ser posible, es muy recomendable usar una store como Redux para guardar el parámetro idioma, con lo cual al cambiar el idioma todos los componentes se traducen automáticamente, sin necesidad de recargar.

En el repositorio, esto se hace en el archivo LanguageHelper.js.

Diccionario de strings dinámicos

Tras fijar el parámetro idioma, se pueden generar los strings dinámicos. Para este caso, estos están centralizados en un archivo JSON: estos definen un objeto con la siguiente estructura:

code: 'es', Languages: { es: 'Español', en: 'Inglés' }, FirstComponent: { first: 'Primero', other: 'Otro', }, SecondComponent: { second: 'Segundo', stuff: 'Cosas', }

En la estructura va un parámetro code, que identifica al idioma; un parámetro Languages, que nombra el resto de los idiomas; y un objeto por cada componente que quiera traducirse, compuesto de claves y valores que después son accedidas directamente por el componente a traducir.
Una posibilidad era hacer un diccionario completo de strings que podría usar cualquiera de los componentes que requieran traducción; sin embargo se descartó esa opción por considerar esta más ordenada. Cada grupo de componentes tiene su propio subdiccionario, los cuales no interactúan entre sí, pudiendo dejar cada frase con su propio contexto.

Puse dos diccionarios de ejemplo en la carpeta i18n.

Provisión de diccionarios a componentes

Luego de hacer un diccionario para cada idioma, es necesaria alguna manera de pasarle este diccionario a cada componente que lo requiera. Esto se logra en React con componentes de alto orden (o higher order component en inglés), los cuales son componentes que devuelven otro componente, con alguna modificación.
En este caso, se usan componentes de alto orden para inyectar a un componente el diccionario de strings.

Este componente realiza los siguientes pasos:

  • carga los diccionarios de todos los idiomas disponibles
  • obtiene el idioma seleccionado mediante el contexto (o mediante Redux), y lo ocupa para cargar el diccionario adecuado
  • recibe la clave del subdiccionario deseado, como FirstComponent o SecondComponent
  • obtiene el subdiccionario y lo inyecta al componente con el nombre "strings"

Agregando las validaciones correspondientes a la obtención del diccionario de idioma y al subdiccionario de componente, resulta una implementación robusta que no deshabilita el programa completo, si es que se le pide un idioma que no existe o un componente mal escrito.

Este componente se encuentra en los archivos translate.jsx: uno usando contexto, otro usando Redux.

Usando el diccionario

Una vez que ya se le pasa el subdiccionario al componente, basta con que este acceda a el, mediante sus props. Esto implica reemplazar cada instancia de un string por su equivalente this.props.strings.string, lo cual es la parte más larga de toda la operación.
A modo de ilustración: implementar la solución de stackoverflow tomó alrededor de tres días. Reemplazar los 750 strings que tenía el programa tomó más de dos semanas.
Desde entonces, recomiendo encarecidamente que, si usted está planeando una aplicación y quisiera que tuviera más de un idioma, se avise lo antes posible: reemplazar todas esas líneas es carísimo...

Hacer que un componente dependa de un prop strings tiene el potencial de aumentar mucho el acoplamiento; se recomienda usar el parámetro defaultProps para configurar un componente de forma inicial. Así, un componente puede reutilizarse fácilmente en otros proyectos.

Acá hay un ejemplo del componente translate en acción.

Conclusión

He intentado describir a grandes rasgos los pasos tomados para implementar una traducción de una aplicación en React.

La solución implementada es altamente especializada, sin embargo se puede reutilizar con poco trabajo; teniendo un lugar donde poner el idioma, y pudiendo usar componentes de alto orden, no debería haber mucha dificultad en replicar este método. Espero que quede más clara la explicación mirando el código fuente.

En una próxima entrada, explicaré cómo se implementó la traducción del backend de la misma aplicación, el cual es usado para obtener distintos tipos de datos, cada uno con su nombre y características especiales.