12k
All articles

Adicionando Internacionalização a um Aplicativo React

Configure a internacionalização no React com react-i18next: interpolação, pluralização, layout RTL, formatação local e SSR no Next.js.

OpenReplay Team
OpenReplay Team
Adicionando Internacionalização a um Aplicativo React

Adicionar internacionalização a um aplicativo React significa externalizar todas as strings voltadas ao usuário em arquivos por idioma e renderizá-las por meio de uma camada de tradução, em vez de codificar texto diretamente no JSX.

Se você já publicou um build onde um t('main.header') bruto apareceu na tela de um cliente, ou viu uma string em alemão estourar o layout de um botão que parecia perfeito em inglês, você já sabe que a configuração inicial não é a parte difícil. A integração leva uma tarde; os casos extremos específicos de cada locale levam o resto da sprint. A abordagem padrão de produção para isso é o react-i18next, o binding React para o framework i18next. Padronize com o react-i18next: ele é baseado em hooks, suporta namespaces e lazy loading, funciona com server-side rendering e conta com o maior ecossistema de plugins do i18next. Recorra ao react-intl apenas se você estiver comprometido com a sintaxe de mensagens ICU. Este guia cobre a configuração correta e atual, seguida dos cinco problemas que surgem em produção: interpolação, pluralização, formatação de números e datas por locale, layout da direita para a esquerda e SSR.

Principais Conclusões

  • Defina interpolation.escapeValue: false na sua configuração do i18next porque o React já escapa os valores antes de renderizá-los; manter o escape do i18next ativado faz com que suas strings sejam escapadas duas vezes.
  • No i18next atual, as chaves de plural usam sufixos CLDR/Intl (_zero, _one, _two, _few, _many, _other), e o sufixo legado _plural pertence ao formato JSON v3 antigo; a variável seletora deve se chamar count.
  • A formatação de números e datas depende da região, não apenas do idioma, portanto qualifique os locales (en-US, ar-EG) e formate com os formatadores Intl do i18next via {{value, number}} e {{date, datetime}}.
  • Carregue as traduções de arquivos JSON com i18next-http-backend e um loadPath; incluí-las inline com require() empacota todos os idiomas no seu bundle principal e elimina o lazy loading.
  • No Next.js, não implemente SSR i18n manualmente: o next-i18next v16 integra tanto o App Router quanto o Pages Router em um único pacote.

Como configurar o react-i18next?

Instale o framework principal, o binding React e dois plugins que cuidam da detecção e do carregamento de arquivos. Quatro pacotes compõem a configuração, cada um com uma função distinta:

PacoteVersãoFinalidade
i18next26.xMotor principal: lookup, interpolação, plurais, formatação
react-i18next17.xBinding React: useTranslation, Trans
i18next-browser-languagedetector8.xDetecta o idioma do usuário
i18next-http-backend4.xCarrega o JSON de tradução via HTTP

Observe uma ressalva: o i18next-http-backend v4 requer fetch nativo. Node ≥ 18, todos os navegadores modernos, Deno e Bun já incluem o fetch por padrão. Em runtimes mais antigos, forneça um ponyfill ou permaneça na v3.

npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend

Crie o arquivo src/i18n.ts e inicialize uma única vez:

import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    supportedLngs: ['en', 'es', 'ar'],
    load: 'languageOnly',
    backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
    interpolation: { escapeValue: false },
  });

export default i18n;

Defina interpolation.escapeValue: false porque o React já escapa os valores antes de renderizá-los; manter o escape do i18next ativado faz com que suas strings sejam escapadas duas vezes. O loadPath é importante: incluir recursos inline com require() (um padrão da era Create React App / Webpack) empacota todos os idiomas no seu bundle principal e invalida o lazy loading. Importe a configuração uma única vez no seu ponto de entrada, antes da renderização: import './i18n'; em main.tsx.

Externalize strings com o hook useTranslation

As traduções ficam em arquivos JSON por idioma em public/locales/<lng>/translation.json, e os componentes as leem por meio da função t do hook useTranslation. Substitua cada string codificada diretamente por uma consulta de chave.

{ "main": { "header": "Welcome to the app!" } }
import { useTranslation } from 'react-i18next';

export default function Header() {
  const { t } = useTranslation();
  return <h1>{t('main.header')}</h1>;
}

Chaves aninhadas (main.header) e namespaces organizam grandes conjuntos de strings. Para textos que contêm marcação inline ou links, a chamada simples a t() quebra o JSX. Nesse caso, use o componente Trans, que interpola elementos React em uma frase traduzida mantendo a marcação no seu componente, e não no seu JSON.

<Trans i18nKey="main.docs" components={{ docsLink: <a href="https://react.i18next.com/" /> }} />

Como alternar e detectar idiomas?

Altere o idioma ativo com i18n.changeLanguage(lng); todo componente que usa useTranslation é re-renderizado automaticamente. Um seletor de idioma é apenas um conjunto de botões ou um <select> que chama esse método:

const { i18n } = useTranslation();
<select
  value={i18n.resolvedLanguage}
  onChange={(e) => i18n.changeLanguage(e.target.value)}
>
  <option value="en">English</option>
  <option value="ar">العربية</option>
</select>

A detecção é gerenciada pelo plugin de detecção de idioma, que verifica as fontes em uma ordem fixa: query string (?lng=en), um cookie, localStorage, o navigator do navegador e, por fim, o atributo <html lang>. Ele para na primeira correspondência suportada. O idioma resolvido é armazenado em cache no localStorage, de modo que usuários recorrentes mantêm sua escolha, e uma chamada manual a changeLanguage também atualiza esse cache.

Os cinco problemas que surgem em produção

A maioria dos bugs de i18n ocorre fora do caminho feliz. Estes são os modos de falha que passam pelo QA local e só aparecem no locale de um usuário real.

Interpolação. Injete valores dinâmicos com a sintaxe {{var}} e passe-os como segundo argumento: t('greeting', { name }) contra "Hello, {{name}}". O escape do React combinado com escapeValue: false mantém isso seguro contra XSS.

Pluralização. O inglês precisa de duas formas de plural e o árabe precisa de seis, o que é exatamente o motivo pelo qual você nunca deve escrever if (count === 1) manualmente. Passe count para t() e deixe o Intl.PluralRules selecionar a chave. Defina as formas com sufixos CLDR: _zero, _one, _two, _few, _many, _other. A variável deve se chamar count.

{
  "messages_one": "You have one message",
  "messages_other": "You have {{count}} new messages"
}

O sufixo antigo _plural é legado do JSON v3. O i18next padronizou seus sufixos de plural para corresponder aos usados pela API Intl quando introduziu o formato JSON v4. Desde a v24, a API Intl é obrigatória: se seu runtime não tiver Intl.PluralRules, você precisa aplicar um polyfill, pois o fallback antigo para o tratamento de plurais v3 foi removido e compatibilityJSON não aceita mais 'v3'.

Formatação de números e datas. Formate com os formatadores Intl integrados do i18next: {{value, number}} e {{date, datetime}}, com opções como {{value, number(style: percent)}}. Como a formatação depende da região, qualifique seus locales (en-US, ar-EG) para que os numerais e a ordem das datas permaneçam consistentes entre os navegadores.

Direita para a esquerda. Para idiomas RTL, defina a direção do documento a partir de i18n.dir() a cada mudança de idioma, para que todo o layout seja refeito sem CSS por componente:

useEffect(() => {
  const apply = (lng: string) => {
    document.documentElement.lang = lng;
    document.documentElement.dir = i18n.dir(lng);
  };
  i18n.on('languageChanged', apply);
  return () => i18n.off('languageChanged', apply);
}, [i18n]);

Leia i18n.dir() dentro do handler languageChanged, e não de forma síncrona durante a troca: após changeLanguage(), i18next.language reflete o novo idioma somente depois que os recursos forem carregados.

SSR. Não implemente i18n server-side manualmente no Next.js. O next-i18next v16 é uma camada fina sobre o i18next e o react-i18next que cuida da integração específica do Next.js: middleware, a divisão servidor/cliente e a hidratação de recursos. Ele suporta o App Router (Server Components, Client Components, middleware) e o Pages Router, com getT() para Server Components e useT() para Client Components. Envolva árvores de cliente em <Suspense> em vez de assumir que window existe. Esses defeitos específicos de locale — uma chave bruta como main.header renderizada para o usuário, padding RTL cortando texto ou texto no idioma de fallback vazando para uma tela traduzida — são exatamente os que passam pelo QA no locale padrão e só aparecem quando você observa uma sessão real no locale de destino, que é onde o session replay demonstra seu valor.

Escale com namespaces e extração de chaves

À medida que o número de strings cresce, divida as traduções em namespaces e carregue-os por rota com useTranslation('dashboard'), para que cada página busque apenas seu próprio JSON e os bundles permaneçam pequenos. Quando as strings se espalharem pelo codebase, recorra a ferramentas automatizadas: o i18next-cli é a ferramenta de linha de comando oficial e completa que gerencia extração de chaves, linting de código, sincronização de locales e geração de tipos; e um sistema de gerenciamento de traduções como Lokalise, Phrase ou Crowdin coordena os tradutores quando a localização real começa.

Agora você tem uma configuração correta do react-i18next e um mapa das questões avançadas. Integre a configuração, externalize suas strings, recorra a namespaces quando os bundles crescerem e ao next-i18next quando renderizar no servidor. Verifique as versões exatas dos pacotes no npm no momento da instalação, pois o core do i18next e seus bindings são atualizados com frequência.

Perguntas Frequentes

Qual é a diferença entre i18next e react-i18next?

O i18next é o framework principal que lida com a lógica de tradução em si: lookup de chaves, interpolação, pluralização e formatação. O react-i18next é o binding React construído sobre ele, fornecendo hooks como useTranslation, o componente Trans e re-renderização automática quando o idioma muda. Você instala ambos: o i18next faz o trabalho, o react-i18next o conecta aos seus componentes. O react-i18next requer um peer moderno do i18next, portanto mantenha-os em majors compatíveis.

Por que minha chave de tradução está aparecendo como texto literal em vez da string traduzida?

Uma chave bruta como main.header sendo renderizada para o usuário significa que o lookup falhou ao resolver, quase sempre porque o arquivo JSON para aquele idioma ou namespace nunca foi carregado. Causas comuns: um loadPath que não corresponde à localização do seu arquivo, um namespace não registrado, a configuração do i18n não importada antes da renderização, ou uma chave que não existe no arquivo. Verifique a aba de rede em busca de uma requisição com falha para o seu caminho de locales e confirme que a chave existe no arquivo do idioma correto.

Ainda devo usar o sufixo _plural para chaves de plural no i18next?

Não. O sufixo _plural pertence ao formato legado JSON v3. O i18next atual usa sufixos de palavras CLDR/Intl que correspondem ao Intl.PluralRules: _zero, _one, _two, _few, _many e _other. O inglês usa duas formas (_one e _other), enquanto o árabe usa todas as seis. A variável que seleciona a forma deve se chamar count e deve estar presente, pois não há fallback se count estiver ausente. Se Intl.PluralRules não estiver disponível, você deve aplicar um polyfill: desde a v24 não há mais fallback para o tratamento de plurais v3 antigo, e compatibilityJSON não aceita mais v3.

Preciso armazenar as traduções em arquivos JSON ou posso incluí-las inline na configuração?

Você pode incluir as traduções inline via opção resources, mas para qualquer aplicação além de algo trivial, você deve carregá-las de arquivos JSON com i18next-http-backend e um loadPath como /locales/{{lng}}/{{ns}}.json. Incluir todos os idiomas inline com require() empacota todas as traduções no seu bundle principal e invalida o lazy loading, fazendo com que os usuários baixem strings de idiomas que nunca usarão. O carregamento baseado em arquivos busca apenas o idioma e namespace ativos sob demanda. Observe que o i18next-http-backend v4 requer fetch nativo, ou seja, Node 18 ou mais recente.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.