Estou construindo um aplicativo que precisará estar disponível em vários idiomas e localidades.
Minha pergunta não é puramente técnica, mas sim sobre a arquitetura e os padrões que as pessoas estão realmente usando na produção para resolver esse problema. Não consegui encontrar nenhum "livro de receitas" para isso, então estou acessando meu site de perguntas e respostas favorito :)
Aqui estão meus requisitos (eles são realmente "padrão"):
- O usuário pode escolher o idioma (trivial)
- Ao alterar o idioma, a interface deve ser traduzida automaticamente para o novo idioma selecionado
- Não estou muito preocupado com a formatação de números, datas, etc. no momento, quero uma solução simples para apenas traduzir strings
Aqui estão as possíveis soluções que eu poderia imaginar:
Cada componente lida com a tradução isoladamente
Isso significa que cada componente tem, por exemplo, um conjunto de arquivos en.json, fr.json etc. junto com as strings traduzidas. E uma função auxiliar para ajudar a ler os valores daqueles que dependem do idioma selecionado.
- Pro: mais respeitoso com a filosofia React, cada componente é "autônomo"
- Contras: você não pode centralizar todas as traduções em um arquivo (para alguém adicionar um novo idioma, por exemplo)
- Contras: você ainda precisa passar a linguagem atual como adereço, em cada componente sangrento e seus filhos
Cada componente recebe as traduções por meio dos adereços
Portanto, eles não estão cientes do idioma atual, eles apenas pegam uma lista de strings como acessórios que por acaso correspondem ao idioma atual
- Pro: como essas strings estão vindo "de cima", elas podem ser centralizadas em algum lugar
- Contras: cada componente agora está vinculado ao sistema de tradução, você não pode apenas reutilizar um, você precisa especificar as strings corretas todas as vezes
Você contorna os adereços um pouco e possivelmente usa o contexto para passar o idioma atual
- Pro: é quase sempre transparente, não precisa passar o idioma atual e / ou traduções por adereços o tempo todo
- Contras: parece complicado de usar
Se você tiver alguma outra idéia, diga!
Como você faz isso?
fonte
Respostas:
Depois de tentar algumas soluções, acho que encontrei uma que funciona bem e deve ser uma solução idiomática para React 0.14 (ou seja, não usa mixins, mas componentes de ordem superior) ( editar : também perfeitamente adequado com React 15, é claro! )
Então aqui está a solução, começando pela parte inferior (os componentes individuais):
O componente
A única coisa que seu componente precisa (por convenção), são
strings
adereços. Deve ser um objeto contendo as várias strings de que seu componente precisa, mas na verdade o formato dele é com você.Ele contém as traduções padrão, então você pode usar o componente em outro lugar sem a necessidade de fornecer qualquer tradução (funcionaria fora da caixa com o idioma padrão, inglês neste exemplo)
O componente de ordem superior
No snippet anterior, você deve ter notado isso na última linha:
translate('MyComponent')(MyComponent)
translate
neste caso, é um componente de ordem superior que envolve seu componente e fornece algumas funcionalidades extras (esta construção substitui os mixins das versões anteriores do React).O primeiro argumento é uma chave que será usada para pesquisar as traduções no arquivo de tradução (usei o nome do componente aqui, mas pode ser qualquer coisa). O segundo (observe que a função é curryed, para permitir decoradores ES7) é o próprio componente para embrulhar.
Aqui está o código para o componente de tradução:
Não é mágico: ele apenas lerá a linguagem atual do contexto (e esse contexto não se espalhará por toda a base de código, apenas usado aqui neste wrapper) e, em seguida, obterá o objeto de strings relevante dos arquivos carregados. Este pedaço de lógica é bastante ingênuo neste exemplo, poderia ser feito da maneira que você realmente quiser.
A parte importante é que ele pega o idioma atual do contexto e o converte em strings, dada a chave fornecida.
No topo da hierarquia
No componente raiz, você só precisa definir o idioma atual do seu estado atual. O exemplo a seguir está usando Redux como a implementação do tipo Flux, mas pode ser facilmente convertido usando qualquer outro framework / pattern / library.
E para finalizar, os arquivos de tradução:
Arquivos de tradução
O que é que vocês acham?
Acho que resolve todo o problema que estava tentando evitar na minha pergunta: a lógica da tradução não esgota o código-fonte, é bastante isolada e permite reutilizar os componentes sem ela.
Por exemplo, MyComponent não precisa ser encapsulado por translate () e pode ser separado, permitindo sua reutilização por qualquer pessoa que deseje fornecer o
strings
por seu próprio meio.[Edit: 31/03/2016]: Eu trabalhei recentemente em uma Retrospective Board (para Agile Retrospectives), construída com React & Redux, e é multilíngue. Já que muitas pessoas pediram um exemplo da vida real nos comentários, aqui está:
Você pode encontrar o código aqui: https://github.com/antoinejaussoin/retro-board/tree/master
fonte
dangerouslySetInnerHTML
suporte, apenas esteja ciente das implicações ( limpe manualmente a entrada). Veja facebook.github.io/react/tips/dangerously-set-inner-html.htmlconst formStrings = { cancel, create, required }; export default { fooForm: { ...formStrings, foo: 'foo' }, barForm: { ...formStrings, bar: 'bar' } }
Pela minha experiência, a melhor abordagem é criar um estado redux i18n e usá-lo, por vários motivos:
1- Isso permitirá que você passe o valor inicial do banco de dados, arquivo local ou mesmo de um mecanismo de template como EJS ou jade
2- Quando o usuário altera o idioma, você pode alterar o idioma de todo o aplicativo sem nem mesmo atualizar a IU.
3- Quando o usuário altera o idioma, isso também permitirá que você recupere o novo idioma da API, arquivo local ou mesmo de constantes
4- Você também pode salvar outras coisas importantes com as strings, como fuso horário, moeda, direção (RTL / LTR) e lista de idiomas disponíveis
5- Você pode definir a mudança de idioma como uma ação normal de redux
6- Você pode ter suas strings de back-end e front end em um só lugar, por exemplo, no meu caso eu uso i18n-node para localização e quando o usuário altera o idioma da IU, eu apenas faço uma chamada API normal e no back-end, eu apenas retorno
i18n.getCatalog(req)
isso retornará todas as strings do usuário apenas para o idioma atualMinha sugestão para o estado inicial i18n é:
Módulos extras úteis para i18n:
1- string-template isso permitirá que você injete valores entre suas strings de catálogo, por exemplo:
2- formato humano este módulo permitirá que você converta um número de / para uma string legível por humanos, por exemplo:
3- momentjs a biblioteca npm de datas e horas mais famosa, você pode traduzir moment, mas ela já tem uma tradução embutida, apenas você precisa passar o idioma do estado atual por exemplo:
Atualização (14/06/2019)
Atualmente, existem muitos frameworks que implementam o mesmo conceito usando a API de contexto de reação (sem redux), eu pessoalmente recomendei o I18next
fonte
A solução de Antoine funciona bem, mas tem algumas ressalvas:
É por isso que construímos redux-poliglota sobre Redux e Poliglota do AirBNB .
(Eu sou um dos autores)
Ele fornece :
setLanguage(lang, messages)
getP(state)
seletor que recupera umP
objeto que expõe 4 métodos:t(key)
: função T poliglota originaltc(key)
: tradução em maiúsculastu(key)
: tradução em maiúsculastm(morphism)(key)
: tradução modificada personalizadagetLocale(state)
seletor para obter o idioma atualtranslate
componente de ordem superior para aprimorar seus componentes React, injetando op
objeto em adereçosExemplo de uso simples:
enviar novo idioma:
no componente:
Por favor, me diga se você tem alguma dúvida / sugestão!
fonte
_()
funções, por exemplo, para obter todas essas strings. Assim você pode traduzir em um arquivo de linguagem mais fácil e não mexer com variáveis malucas. Em alguns casos, as páginas de destino precisam que uma parte específica do layout seja exibida de forma diferente. Portanto, algumas funções inteligentes de como escolher o padrão versus outras opções possíveis também devem estar disponíveis.De minha pesquisa sobre isso, parece haver duas abordagens principais sendo usadas para i18n em JavaScript, ICU e gettext .
Eu só usei gettext, então sou tendencioso.
O que me surpreende é como o suporte é ruim. Eu venho do mundo do PHP, CakePHP ou WordPress. Em ambas as situações, é um padrão básico que todas as strings são simplesmente rodeadas por
__('')
, então mais adiante na linha você obtém traduções usando arquivos PO com muita facilidade.gettext
Você obtém a familiaridade do sprintf para formatar strings e os arquivos PO serão traduzidos facilmente por milhares de agências diferentes.
Existem duas opções populares:
Ambos têm suporte ao estilo gettext, formatação do estilo sprintf de strings e importação / exportação para arquivos PO.
i18next tem uma extensão React desenvolvida por eles mesmos. Jed não. Sentry.io parece usar uma integração personalizada de Jed com React. O post React + Redux sugere o uso
No entanto, Jed parece uma implementação mais focada no gettext - isto é, sua intenção expressa, onde, como i18next, apenas a tem como uma opção.
unidade de Terapia Intensiva
Isso tem mais suporte para os casos extremos em torno das traduções, por exemplo, para lidar com gênero. Acho que você verá os benefícios disso se tiver idiomas mais complexos para os quais traduzir.
Uma opção popular para isso é messageformat.js . Discutido brevemente neste tutorial do blog sentry.io . messageformat.js foi desenvolvido pela mesma pessoa que escreveu Jed. Ele faz afirmações bastante fortes sobre o uso da UTI :
Comparação aproximada
gettext com sprintf:
messageformat.js (meu melhor palpite lendo o guia ):
fonte
Se ainda não terminou, dê uma olhada em https://react.i18next.com/ pode ser um bom conselho. É baseado no i18next: aprender uma vez - traduzir em todos os lugares.
Seu código será semelhante a:
Vem com amostras para:
https://github.com/i18next/react-i18next/tree/master/example
Além disso, você também deve considerar o fluxo de trabalho durante o desenvolvimento e posteriormente para seus tradutores -> https://www.youtube.com/watch?v=9NOzJhgmyQE
fonte
Eu gostaria de propor uma solução simples usando criar-reagir-app .
O aplicativo será criado para cada idioma separadamente, portanto, toda a lógica de tradução será removida do aplicativo.
O servidor da web servirá o idioma correto automaticamente, dependendo do cabeçalho Accept-Language , ou manualmente, definindo um cookie .
Principalmente, não mudamos o idioma mais de uma vez, se é que o fazemos)
Os dados de tradução são colocados dentro do mesmo arquivo do componente que o utiliza, junto com os estilos, html e código.
E aqui temos um componente totalmente independente que é responsável por seu próprio estado, visão, tradução:
Adicione a variável de ambiente de linguagem ao seu package.json
É isso!
Além disso, minha resposta original incluía uma abordagem mais monolítica com um único arquivo json para cada tradução:
lang / ru.json
lib / lang.js
src / App.jsx
fonte