Estoy creando una aplicación que deberá estar disponible en varios idiomas y configuraciones regionales.
Mi pregunta no es puramente técnica, sino más bien sobre la arquitectura y los patrones que la gente está utilizando en la producción para resolver este problema. No pude encontrar ningún "libro de cocina" para eso, así que voy a mi sitio web favorito de preguntas y respuestas :)
Estos son mis requisitos (son realmente "estándar"):
- El usuario puede elegir el idioma (trivial)
- Al cambiar el idioma, la interfaz debería traducirse automáticamente al nuevo idioma seleccionado
- No estoy demasiado preocupado por formatear números, fechas, etc. en este momento, quiero una solución simple para simplemente traducir cadenas
Aquí están las posibles soluciones en las que podría pensar:
Cada componente se ocupa de la traducción de forma aislada
Esto significa que cada componente tiene, por ejemplo, un conjunto de archivos en.json, fr.json, etc. junto con las cadenas traducidas. Y una función auxiliar para ayudar a leer los valores de los que dependen del idioma seleccionado.
- Ventaja: más respetuoso con la filosofía React, cada componente es "independiente"
- Contras: no puede centralizar todas las traducciones en un archivo (para que otra persona agregue un nuevo idioma, por ejemplo)
- Contras: todavía necesita pasar el idioma actual como un accesorio, en cada componente sangriento y sus hijos
Cada componente recibe las traducciones a través de los accesorios.
Por lo tanto, no conocen el idioma actual, solo toman una lista de cadenas como accesorios que coinciden con el idioma actual
- Ventaja: dado que esas cadenas vienen "desde arriba", se pueden centralizar en algún lugar
- Contras: cada componente ahora está vinculado al sistema de traducción, no puede simplemente reutilizar uno, debe especificar las cadenas correctas cada vez
Omite un poco los accesorios y posiblemente use el contexto para transmitir el idioma actual
- Ventaja: es mayormente transparente, no es necesario pasar el idioma actual y / o las traducciones a través de accesorios todo el tiempo
- Contras: parece incómodo de usar
Si tienes alguna otra idea, ¡dilo!
¿Cómo lo haces?
fuente
Respuestas:
Después de probar bastantes soluciones, creo que encontré una que funciona bien y debería ser una solución idiomática para React 0.14 (es decir, no usa mixins, sino componentes de orden superior) ( editar : ¡también perfectamente bien con React 15, por supuesto! ).
Entonces aquí la solución, comenzando por la parte inferior (los componentes individuales):
El componente
Lo único que necesitaría su componente (por convención) es un
strings
accesorio. Debe ser un objeto que contenga las diversas cadenas que necesita su componente, pero realmente la forma depende de usted.Contiene las traducciones predeterminadas, por lo que puede usar el componente en otro lugar sin la necesidad de proporcionar ninguna traducción (funcionaría de inmediato con el idioma predeterminado, inglés en este ejemplo)
El componente de orden superior
En el fragmento anterior, es posible que haya notado esto en la última línea:
translate('MyComponent')(MyComponent)
translate
en este caso, es un componente de orden superior que envuelve su componente y proporciona alguna funcionalidad adicional (esta construcción reemplaza los mixins de versiones anteriores de React).El primer argumento es una clave que se usará para buscar las traducciones en el archivo de traducción (usé el nombre del componente aquí, pero podría ser cualquier cosa). El segundo (observe que la función está modificada, para permitir que los decoradores de ES7) sea el componente en sí para envolver.
Aquí está el código para el componente de traducción:
No es mágico: simplemente leerá el idioma actual del contexto (y ese contexto no se desangra en toda la base del código, solo se usa aquí en este contenedor), y luego obtendrá el objeto de cadenas relevante de los archivos cargados. Esta pieza de lógica es bastante ingenua en este ejemplo, podría hacerse de la manera que realmente desee.
La pieza importante es que toma el lenguaje actual del contexto y lo convierte en cadenas, dada la clave proporcionada.
En lo más alto de la jerarquía
En el componente raíz, solo necesita establecer el idioma actual desde su estado actual. El siguiente ejemplo usa Redux como implementación similar a Flux, pero se puede convertir fácilmente usando cualquier otro marco / patrón / biblioteca.
Y para terminar, los archivos de traducción:
Archivos de traducción
¿Qué piensan ustedes?
Creo que esto resuelve todo el problema que estaba tratando de evitar en mi pregunta: la lógica de traducción no sangra en todo el código fuente, está bastante aislada y permite reutilizar los componentes sin ella.
Por ejemplo, MyComponent no necesita ser envuelto por translate () y podría estar separado, permitiendo su reutilización por cualquier otra persona que desee proporcionar el
strings
por sus propios medios.[Edición: 31/03/2016]: Recientemente trabajé en un tablero de retrospectiva (para retrospectivas ágiles), construido con React & Redux, y es multilingüe. Dado que muchas personas pidieron un ejemplo de la vida real en los comentarios, aquí está:
Puede encontrar el código aquí: https://github.com/antoinejaussoin/retro-board/tree/master
fuente
dangerouslySetInnerHTML
accesorio, solo tenga en cuenta las implicaciones (desinfecte manualmente la entrada). Ver facebook.github.io/react/tips/dangerously-set-inner-html.htmlconst formStrings = { cancel, create, required }; export default { fooForm: { ...formStrings, foo: 'foo' }, barForm: { ...formStrings, bar: 'bar' } }
Desde mi experiencia, el mejor enfoque es crear un estado i18n redux y usarlo, por muchas razones:
1- Esto te permitirá pasar el valor inicial desde la base de datos, archivo local o incluso desde un motor de plantilla como EJS o jade
2- Cuando el usuario cambia el idioma, puede cambiar todo el idioma de la aplicación sin siquiera actualizar la interfaz de usuario.
3- Cuando el usuario cambia el idioma, esto también le permitirá recuperar el nuevo idioma de API, archivo local o incluso de constantes
4- También puede guardar otras cosas importantes con las cadenas como zona horaria, moneda, dirección (RTL / LTR) y lista de idiomas disponibles
5- Puede definir el cambio de idioma como una acción normal de reducción
6- Puede tener sus cadenas de backend y front-end en un solo lugar, por ejemplo, en mi caso, uso i18n-node para la localización y cuando el usuario cambia el idioma de la interfaz de usuario, solo hago una llamada a la API normal y en el backend, simplemente regreso
i18n.getCatalog(req)
esto devolverá todas las cadenas de usuario solo para el idioma actualMi sugerencia para el estado inicial del i18n es:
Módulos extra útiles para i18n:
1- plantilla de cadena esto le permitirá inyectar valores entre las cadenas de su catálogo, por ejemplo:
2- formato humano este módulo le permitirá convertir un número a / desde una cadena legible por humanos, por ejemplo:
3- momentjs la biblioteca npm de fechas y horas más famosas, puede traducir moment pero ya tiene una traducción incorporada, solo necesita pasar el idioma del estado actual, por ejemplo:
Actualización (14/06/2019)
Actualmente, hay muchos marcos que implementan el mismo concepto usando la API de contexto de reacción (sin redux), yo personalmente recomendé I18next
fuente
La solución de Antoine funciona bien, pero tiene algunas advertencias:
Es por eso que hemos construido redux-políglota en la parte superior de ambos Redux y de Airbnb Políglota .
(Soy uno de los autores)
Proporciona :
setLanguage(lang, messages)
getP(state)
selector que recupera unP
objeto que expone 4 métodos:t(key)
: función T políglota originaltc(key)
: traducción en mayúsculatu(key)
: traducción en mayúsculastm(morphism)(key)
: traducción personalizada modificadagetLocale(state)
selector para obtener el idioma actualtranslate
componente de orden superior para mejorar sus componentes de React inyectando elp
objeto en accesoriosEjemplo de uso simple:
enviar nuevo idioma:
en componente:
¡Por favor dígame si tiene alguna pregunta / sugerencia!
fuente
_()
funciones, por ejemplo, para obtener todas esas cadenas. Por lo tanto, puede traducirlo en un archivo de idioma más fácilmente y no meterse con variables locas. En algunos casos, las páginas de destino necesitan que una parte específica del diseño se muestre de manera diferente. Por lo tanto, también debería estar disponible alguna función inteligente de cómo elegir la opción predeterminada frente a otras opciones posibles.A partir de mi investigación sobre esto, parece haber dos enfoques principales que se utilizan para i18n en JavaScript, ICU y gettext .
Solo he usado gettext, así que soy parcial.
Lo que me sorprende es lo pobre que es el apoyo. Vengo del mundo PHP, ya sea CakePHP o WordPress. En ambas situaciones, es un estándar básico que todas las cadenas estén simplemente rodeadas por
__('')
, luego, más adelante, obtienes traducciones usando archivos PO muy fácilmente.gettext
Obtiene la familiaridad de sprintf para formatear cadenas y los archivos PO serán traducidos fácilmente por miles de agencias diferentes.
Hay dos opciones populares:
Ambos tienen soporte de estilo gettext, formato de cadenas de estilo sprintf e importación / exportación a archivos PO.
i18next tiene una extensión React desarrollada por ellos mismos. Jed no lo hace. Sentry.io parece usar una integración personalizada de Jed con React. La publicación React + Redux , sugiere usar
Sin embargo, Jed parece una implementación más centrada en gettext, es decir, su intención expresa, mientras que i18next solo la tiene como una opción.
UCI
Esto tiene más apoyo para los casos extremos en torno a las traducciones, por ejemplo, para tratar el género. Creo que verá los beneficios de esto si tiene idiomas más complejos para traducir.
Una opción popular para esto es messageformat.js . Discutido brevemente en este tutorial del blog sentry.io . messageformat.js fue desarrollado por la misma persona que escribió Jed. Hace afirmaciones bastante sólidas sobre el uso de la UCI :
Comparación aproximada
gettext con sprintf:
messageformat.js (mi mejor suposición al leer la guía ):
fuente
Si aún no ha terminado, eche un vistazo a https://react.i18next.com/ puede ser un buen consejo . Se basa en i18next: aprender una vez - traducir en todas partes.
Su código se verá así:
Viene con muestras para:
https://github.com/i18next/react-i18next/tree/master/example
Además de eso, también debe considerar el flujo de trabajo durante el desarrollo y luego para sus traductores -> https://www.youtube.com/watch?v=9NOzJhgmyQE
fuente
Me gustaría proponer una solución simple usando create-react-app .
La aplicación se creará para cada idioma por separado, por lo que toda la lógica de traducción se moverá fuera de la aplicación.
El servidor web proporcionará el idioma correcto automáticamente, según el encabezado Accept-Language , o manualmente mediante la configuración de una cookie. .
En general, no cambiamos el idioma más de una vez, si es que lo hacemos)
Los datos de traducción se colocan dentro del mismo archivo de componente, que lo usa, junto con estilos, html y código.
Y aquí tenemos un componente totalmente independiente que es responsable de su propio estado, vista, traducción:
Agregue la variable de entorno de idioma a su package.json
¡Eso es!
Además, mi respuesta original incluía un enfoque más monolítico con un solo archivo json para cada traducción:
lang / ru.json
lib / lang.js
src / App.jsx
fuente