React & Frontend

Internacionalização em React: react-i18next e Formatação de Dados na Prática

19 min de leitura

Internacionalização em React: react-i18next e Formatação de Dados na Prática

Entendendo Internacionalização em Aplicações React Internacionalização (i18n) é o processo de preparar sua aplicação para suportar múltiplos idiomas e regiões sem necessidade de modificar o código-fonte. Em React, isso significa extrair strings de texto do código, organizá-las em arquivos de tradução e implementar um sistema que alterne entre elas dinamicamente. O react-i18next é a biblioteca mais madura e poderosa para esse propósito, construída sobre a fundação do i18next — um framework agnóstico de JavaScript que já prova sua robustez em projetos enterprise há mais de uma década. A principal razão pela qual precisamos de uma biblioteca dedicada é simples: internacionalizar manualmente leva a código espalhado, difícil de manter e propenso a erros. Com o react-i18next, você obtém detecção automática de idioma, carregamento lazy de traduções, pluralização inteligente, interpolação de variáveis e integração perfeita com componentes React através de hooks e HOCs. Por que não simplesmente concatenar strings? Você poderia pensar em usar objetos JavaScript simples para armazenar traduções. Isso funciona

<h2>Entendendo Internacionalização em Aplicações React</h2>

<p>Internacionalização (i18n) é o processo de preparar sua aplicação para suportar múltiplos idiomas e regiões sem necessidade de modificar o código-fonte. Em React, isso significa extrair strings de texto do código, organizá-las em arquivos de tradução e implementar um sistema que alterne entre elas dinamicamente. O react-i18next é a biblioteca mais madura e poderosa para esse propósito, construída sobre a fundação do i18next — um framework agnóstico de JavaScript que já prova sua robustez em projetos enterprise há mais de uma década.</p>

<p>A principal razão pela qual precisamos de uma biblioteca dedicada é simples: internacionalizar manualmente leva a código espalhado, difícil de manter e propenso a erros. Com o react-i18next, você obtém detecção automática de idioma, carregamento lazy de traduções, pluralização inteligente, interpolação de variáveis e integração perfeita com componentes React através de hooks e HOCs.</p>

<h3>Por que não simplesmente concatenar strings?</h3>

<p>Você poderia pensar em usar objetos JavaScript simples para armazenar traduções. Isso funciona para projetos muito pequenos, mas quebra rapidamente quando você precisa lidar com pluralização (&quot;1 item&quot; vs &quot;2 items&quot;), contextos diferentes para a mesma palavra, namespaces grandes ou carregamento dinâmico. O react-i18next resolve esses problemas estruturalmente.</p>

<h2>Configuração Inicial do react-i18next</h2>

<p>Para começar, instale o react-i18next e suas dependências:</p>

<pre><code class="language-bash">npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend</code></pre>

<p>Crie uma estrutura de pastas para suas traduções:</p>

<pre><code>src/

├── locales/

│ ├── en/

│ │ └── translation.json

│ ├── pt/

│ │ └── translation.json

│ └── es/

│ └── translation.json

├── i18n.js

└── App.js</code></pre>

<p>Agora, configure o i18next no arquivo <code>src/i18n.js</code>:</p>

<pre><code class="language-javascript">import i18n from &#039;i18next&#039;;

import { initReactI18next } from &#039;react-i18next&#039;;

import LanguageDetector from &#039;i18next-browser-languagedetector&#039;;

import HttpBackend from &#039;i18next-http-backend&#039;;

i18n

// Detecta automaticamente o idioma do navegador

.use(LanguageDetector)

// Carrega traduções de arquivos JSON

.use(HttpBackend)

// Integra com React

.use(initReactI18next)

.init({

fallbackLng: &#039;en&#039;,

debug: true,

ns: [&#039;translation&#039;],

defaultNS: &#039;translation&#039;,

backend: {

loadPath: &#039;/locales/{{lng}}/{{ns}}.json&#039;

},

interpolation: {

escapeValue: false

}

});

export default i18n;</code></pre>

<p>Importe este arquivo no seu <code>src/index.js</code> <strong>antes de renderizar a aplicação</strong>:</p>

<pre><code class="language-javascript">import React from &#039;react&#039;;

import ReactDOM from &#039;react-dom/client&#039;;

import i18n from &#039;./i18n&#039;;

import App from &#039;./App&#039;;

ReactDOM.createRoot(document.getElementById(&#039;root&#039;)).render(

&lt;React.StrictMode&gt;

&lt;App /&gt;

&lt;/React.StrictMode&gt;

);</code></pre>

<h3>Criando os arquivos de tradução</h3>

<p>Crie <code>src/locales/en/translation.json</code>:</p>

<pre><code class="language-json">{

&quot;welcome&quot;: &quot;Welcome to our application&quot;,

&quot;greeting&quot;: &quot;Hello, {{name}}!&quot;,

&quot;items&quot;: &quot;You have {{count}} item&quot;,

&quot;items_plural&quot;: &quot;You have {{count}} items&quot;,

&quot;settings&quot;: &quot;Settings&quot;,

&quot;language&quot;: &quot;Language&quot;,

&quot;logout&quot;: &quot;Logout&quot;

}</code></pre>

<p>E <code>src/locales/pt/translation.json</code>:</p>

<pre><code class="language-json">{

&quot;welcome&quot;: &quot;Bem-vindo à nossa aplicação&quot;,

&quot;greeting&quot;: &quot;Olá, {{name}}!&quot;,

&quot;items&quot;: &quot;Você tem {{count}} item&quot;,

&quot;items_plural&quot;: &quot;Você tem {{count}} itens&quot;,

&quot;settings&quot;: &quot;Configurações&quot;,

&quot;language&quot;: &quot;Idioma&quot;,

&quot;logout&quot;: &quot;Sair&quot;

}</code></pre>

<h2>Usando Traduções em Componentes React</h2>

<p>O react-i18next fornece principalmente dois mecanismos: o hook <code>useTranslation</code> e o componente <code>Trans</code>. O hook é mais moderno e recomendado para a maioria dos casos, enquanto <code>Trans</code> é útil quando você precisa renderizar elementos HTML dentro de uma string traduzida.</p>

<h3>Hook useTranslation</h3>

<p>Este é o caminho principal. Importe-o em qualquer componente e obtenha acesso às funções de tradução:</p>

<pre><code class="language-javascript">import { useTranslation } from &#039;react-i18next&#039;;

function Dashboard() {

const { t, i18n } = useTranslation();

return (

&lt;div&gt;

&lt;h1&gt;{t(&#039;welcome&#039;)}&lt;/h1&gt;

&lt;p&gt;{t(&#039;greeting&#039;, { name: &#039;João&#039; })}&lt;/p&gt;

&lt;p&gt;{t(&#039;items&#039;, { count: 5 })}&lt;/p&gt;

&lt;button onClick={() =&gt; i18n.changeLanguage(&#039;pt&#039;)}&gt;

Português

&lt;/button&gt;

&lt;button onClick={() =&gt; i18n.changeLanguage(&#039;en&#039;)}&gt;

English

&lt;/button&gt;

&lt;/div&gt;

);

}

export default Dashboard;</code></pre>

<p>A função <code>t()</code> retorna a string traduzida. Quando você passa um objeto como segundo argumento, essas variáveis são interpoladas na string. O parâmetro <code>count</code> é especial — ele ativa a pluralização automática. O objeto <code>i18n</code> permite mudar o idioma atual e acessar metadados.</p>

<h3>Pluralização e Contextos</h3>

<p>Perceba que em ambos os arquivos JSON você tem <code>items</code> e <code>items_plural</code>. O i18next detecta automaticamente que se <code>count: 1</code>, deve usar <code>items</code>, e se <code>count !== 1</code>, usa <code>items_plural</code>. Isso funciona em qualquer idioma que siga as regras de pluralização padrão.</p>

<p>Para contextos (a mesma palavra com significados diferentes), use namespaces ou estruture assim:</p>

<pre><code class="language-json">{

&quot;bank&quot;: {

&quot;noun&quot;: &quot;Banco&quot;,

&quot;verb&quot;: &quot;Depositar&quot;

}

}</code></pre>

<p>E acesse com: <code>t(&#039;bank.noun&#039;)</code> ou <code>t(&#039;bank.verb&#039;)</code>.</p>

<h3>Componente Trans para HTML</h3>

<p>Quando você precisa renderizar tags HTML dentro de uma tradução, o hook simples não funciona bem:</p>

<pre><code class="language-javascript">import { Trans } from &#039;react-i18next&#039;;

function TermsPage() {

return (

&lt;p&gt;

&lt;Trans i18nKey=&quot;termsIntro&quot;&gt;

Leia nossos &lt;a href=&quot;/terms&quot;&gt;termos de serviço&lt;/a&gt; e &lt;a href=&quot;/privacy&quot;&gt;política de privacidade&lt;/a&gt;

&lt;/Trans&gt;

&lt;/p&gt;

);

}</code></pre>

<p>No JSON:</p>

<pre><code class="language-json">{

&quot;termsIntro&quot;: &quot;Leia nossos &lt;1&gt;termos de serviço&lt;/1&gt; e &lt;3&gt;política de privacidade&lt;/3&gt;&quot;

}</code></pre>

<p>Os números <code>&lt;1&gt;</code>, <code>&lt;3&gt;</code> mapeiam para os elementos filhos do componente <code>Trans</code>. Isso preserva a estrutura HTML sem permitir injeção de código.</p>

<h2>Formatação de Dados com Intl API</h2>

<p>Internacionalização não é apenas tradução de strings — também envolve formatar números, datas e moedas de acordo com a localidade. O JavaScript moderno oferece a API <code>Intl</code> nativa, que é poderosa e deve ser sua primeira opção antes de adicionar bibliotecas externas.</p>

<h3>Formatando Números</h3>

<p>A API <code>Intl.NumberFormat</code> formata números respeitando convenções regionais:</p>

<pre><code class="language-javascript">function PriceDisplay({ price, currency = &#039;USD&#039;, locale = &#039;en-US&#039; }) {

const formatter = new Intl.NumberFormat(locale, {

style: &#039;currency&#039;,

currency: currency,

minimumFractionDigits: 2,

maximumFractionDigits: 2

});

return &lt;span&gt;{formatter.format(price)}&lt;/span&gt;;

}

// Uso

export default function App() {

return (

&lt;&gt;

&lt;PriceDisplay price={1234.5} currency=&quot;USD&quot; locale=&quot;en-US&quot; /&gt; {/ $1,234.50 /}

&lt;PriceDisplay price={1234.5} currency=&quot;BRL&quot; locale=&quot;pt-BR&quot; /&gt; {/ R$ 1.234,50 /}

&lt;PriceDisplay price={1234.5} currency=&quot;EUR&quot; locale=&quot;de-DE&quot; /&gt; {/ 1.234,50 € /}

&lt;/&gt;

);

}</code></pre>

<p>Para números simples sem moeda:</p>

<pre><code class="language-javascript">function PercentageDisplay({ value, locale = &#039;en-US&#039; }) {

const formatter = new Intl.NumberFormat(locale, {

style: &#039;percent&#039;,

minimumFractionDigits: 1

});

return &lt;span&gt;{formatter.format(value)}&lt;/span&gt;;

}</code></pre>

<h3>Formatando Datas</h3>

<p><code>Intl.DateTimeFormat</code> formata datas e horas conforme o padrão da localidade:</p>

<pre><code class="language-javascript">function DateDisplay({ date, locale = &#039;en-US&#039;, options = {} }) {

const formatter = new Intl.DateTimeFormat(locale, {

year: &#039;numeric&#039;,

month: &#039;long&#039;,

day: &#039;numeric&#039;,

weekday: &#039;long&#039;,

hour: &#039;2-digit&#039;,

minute: &#039;2-digit&#039;,

...options

});

return &lt;span&gt;{formatter.format(new Date(date))}&lt;/span&gt;;

}

// Uso

export default function App() {

const now = new Date(&#039;2024-01-15T14:30:00&#039;);

return (

&lt;&gt;

&lt;DateDisplay date={now} locale=&quot;en-US&quot; /&gt; {/ Monday, January 15, 2024, 02:30 PM /}

&lt;DateDisplay date={now} locale=&quot;pt-BR&quot; /&gt; {/ segunda-feira, 15 de janeiro de 2024 14:30 /}

&lt;DateDisplay date={now} locale=&quot;de-DE&quot; /&gt; {/ Montag, 15. Januar 2024, 14:30 /}

&lt;/&gt;

);

}</code></pre>

<h3>Integrando com react-i18next</h3>

<p>Para centralizar a localidade em sua aplicação, crie um hook customizado:</p>

<pre><code class="language-javascript">import { useTranslation } from &#039;react-i18next&#039;;

import { useMemo } from &#039;react&#039;;

export function useLocaleFormatting() {

const { i18n } = useTranslation();

const localeMap = {

&#039;en&#039;: &#039;en-US&#039;,

&#039;pt&#039;: &#039;pt-BR&#039;,

&#039;es&#039;: &#039;es-ES&#039;,

&#039;de&#039;: &#039;de-DE&#039;

};

const locale = useMemo(() =&gt; localeMap[i18n.language] || &#039;en-US&#039;, [i18n.language]);

return {

formatCurrency: (value, currency = &#039;USD&#039;) =&gt; {

return new Intl.NumberFormat(locale, {

style: &#039;currency&#039;,

currency

}).format(value);

},

formatDate: (date, options = {}) =&gt; {

return new Intl.DateTimeFormat(locale, {

year: &#039;numeric&#039;,

month: &#039;long&#039;,

day: &#039;numeric&#039;,

...options

}).format(new Date(date));

},

formatNumber: (value, options = {}) =&gt; {

return new Intl.NumberFormat(locale, options).format(value);

}

};

}</code></pre>

<p>Agora use em qualquer componente:</p>

<pre><code class="language-javascript">import { useLocaleFormatting } from &#039;./hooks/useLocaleFormatting&#039;;

function Invoice({ amount, dueDate }) {

const { formatCurrency, formatDate } = useLocaleFormatting();

return (

&lt;div&gt;

&lt;p&gt;Amount: {formatCurrency(amount)}&lt;/p&gt;

&lt;p&gt;Due: {formatDate(dueDate)}&lt;/p&gt;

&lt;/div&gt;

);

}</code></pre>

<h3>Formatação de Listas</h3>

<p>Para listas com separadores corretos por localidade, use <code>Intl.ListFormat</code>:</p>

<pre><code class="language-javascript">function TagList({ tags, locale = &#039;en-US&#039; }) {

const formatter = new Intl.ListFormat(locale, {

style: &#039;long&#039;,

type: &#039;conjunction&#039;

});

return &lt;span&gt;{formatter.format(tags)}&lt;/span&gt;;

}

// Uso

&lt;TagList tags={[&#039;React&#039;, &#039;JavaScript&#039;, &#039;i18n&#039;]} locale=&quot;en-US&quot; /&gt;

// Result: &quot;React, JavaScript, and i18n&quot;

&lt;TagList tags={[&#039;React&#039;, &#039;JavaScript&#039;, &#039;i18n&#039;]} locale=&quot;pt-BR&quot; /&gt;

// Result: &quot;React, JavaScript e i18n&quot;</code></pre>

<h2>Exemplo Prático Completo: Aplicação de E-commerce</h2>

<p>Aqui está uma aplicação real que demonstra todos os conceitos integrados:</p>

<pre><code class="language-javascript">// src/App.js

import { useTranslation } from &#039;react-i18next&#039;;

import { useLocaleFormatting } from &#039;./hooks/useLocaleFormatting&#039;;

import { useState } from &#039;react&#039;;

function App() {

const { t, i18n } = useTranslation();

const { formatCurrency, formatDate, formatNumber } = useLocaleFormatting();

const [cart, setCart] = useState([

{ id: 1, name: &#039;Laptop&#039;, price: 1299.99, quantity: 1 },

{ id: 2, name: &#039;Mouse&#039;, price: 29.99, quantity: 2 }

]);

const total = cart.reduce((sum, item) =&gt; sum + item.price * item.quantity, 0);

return (

&lt;div style={{ padding: &#039;20px&#039;, fontFamily: &#039;Arial&#039; }}&gt;

&lt;header style={{ marginBottom: &#039;20px&#039; }}&gt;

&lt;h1&gt;{t(&#039;welcome&#039;)}&lt;/h1&gt;

&lt;div&gt;

&lt;button onClick={() =&gt; i18n.changeLanguage(&#039;en&#039;)}&gt;English&lt;/button&gt;

&lt;button onClick={() =&gt; i18n.changeLanguage(&#039;pt&#039;)}&gt;Português&lt;/button&gt;

&lt;button onClick={() =&gt; i18n.changeLanguage(&#039;es&#039;)}&gt;Español&lt;/button&gt;

&lt;p&gt;{t(&#039;currentLanguage&#039;, { lng: i18n.language })}&lt;/p&gt;

&lt;/div&gt;

&lt;/header&gt;

&lt;section style={{ marginBottom: &#039;30px&#039; }}&gt;

&lt;h2&gt;{t(&#039;shoppingCart&#039;)}&lt;/h2&gt;

&lt;table style={{ width: &#039;100%&#039;, borderCollapse: &#039;collapse&#039; }}&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;{t(&#039;product&#039;)}&lt;/th&gt;

&lt;th style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;{t(&#039;price&#039;)}&lt;/th&gt;

&lt;th style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;{t(&#039;quantity&#039;)}&lt;/th&gt;

&lt;th style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;{t(&#039;subtotal&#039;)}&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

{cart.map(item =&gt; (

&lt;tr key={item.id}&gt;

&lt;td style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;{item.name}&lt;/td&gt;

&lt;td style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;

{formatCurrency(item.price, &#039;USD&#039;)}

&lt;/td&gt;

&lt;td style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;

{formatNumber(item.quantity)}

&lt;/td&gt;

&lt;td style={{ border: &#039;1px solid #ddd&#039;, padding: &#039;8px&#039; }}&gt;

{formatCurrency(item.price * item.quantity, &#039;USD&#039;)}

&lt;/td&gt;

&lt;/tr&gt;

))}

&lt;/tbody&gt;

&lt;/table&gt;

&lt;p style={{ fontSize: &#039;18px&#039;, fontWeight: &#039;bold&#039;, marginTop: &#039;10px&#039; }}&gt;

{t(&#039;total&#039;)}: {formatCurrency(total, &#039;USD&#039;)}

&lt;/p&gt;

&lt;/section&gt;

&lt;footer style={{ marginTop: &#039;30px&#039;, borderTop: &#039;1px solid #ddd&#039;, paddingTop: &#039;10px&#039; }}&gt;

&lt;p&gt;{t(&#039;lastUpdated&#039;, { date: formatDate(new Date()) })}&lt;/p&gt;

&lt;/footer&gt;

&lt;/div&gt;

);

}

export default App;</code></pre>

<p>Arquivo de tradução <code>src/locales/en/translation.json</code>:</p>

<pre><code class="language-json">{

&quot;welcome&quot;: &quot;Welcome to ShopHub&quot;,

&quot;currentLanguage&quot;: &quot;Current language: {{lng}}&quot;,

&quot;shoppingCart&quot;: &quot;Shopping Cart&quot;,

&quot;product&quot;: &quot;Product&quot;,

&quot;price&quot;: &quot;Price&quot;,

&quot;quantity&quot;: &quot;Quantity&quot;,

&quot;subtotal&quot;: &quot;Subtotal&quot;,

&quot;total&quot;: &quot;Total&quot;,

&quot;lastUpdated&quot;: &quot;Last updated: {{date}}&quot;

}</code></pre>

<p>E <code>src/locales/pt/translation.json</code>:</p>

<pre><code class="language-json">{

&quot;welcome&quot;: &quot;Bem-vindo ao ShopHub&quot;,

&quot;currentLanguage&quot;: &quot;Idioma atual: {{lng}}&quot;,

&quot;shoppingCart&quot;: &quot;Carrinho de Compras&quot;,

&quot;product&quot;: &quot;Produto&quot;,

&quot;price&quot;: &quot;Preço&quot;,

&quot;quantity&quot;: &quot;Quantidade&quot;,

&quot;subtotal&quot;: &quot;Subtotal&quot;,

&quot;total&quot;: &quot;Total&quot;,

&quot;lastUpdated&quot;: &quot;Última atualização: {{date}}&quot;

}</code></pre>

<h2>Conclusão</h2>

<p>Ao longo deste artigo, você aprendeu que internacionalização é muito mais do que tradução de strings — é uma arquitetura que permite sua aplicação se adaptar a qualquer idioma e formato regional. O react-i18next oferece uma solução robusta e bem pensada para gerenciar traduções, enquanto a API Intl nativa do JavaScript fornece formatação de dados poderosa e agnóstica de dependências.</p>

<p>O segundo ponto crítico é que a configuração inicial é fundamental. Investir tempo em estruturar corretamente seus arquivos de tradução, configurar detecção de idioma e criar hooks reutilizáveis economiza horas de trabalho técnico quando você precisar adicionar novos idiomas no futuro. A maioria dos problemas em projetos i18n vem de decisões arquiteturais ruins no começo, não de problemas técnicos.</p>

<p>Por fim, lembre-se que formatação de dados é tão importante quanto tradução. Um aplicativo que diz &quot;Hello&quot; em português mas exibe datas no formato americano é confuso e pouco profissional. Use sempre a API Intl integrada ao i18n para garantir que toda a experiência seja coerente e culturalmente apropriada para cada usuário.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.i18next.com/" target="_blank" rel="noopener noreferrer">Documentação oficial do i18next</a></li>

<li><a href="https://github.com/i18next/react-i18next" target="_blank" rel="noopener noreferrer">react-i18next - GitHub</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl" target="_blank" rel="noopener noreferrer">MDN: Intl API - Internationalization</a></li>

<li><a href="https://www.i18next.com/translation-function/plurals" target="_blank" rel="noopener noreferrer">i18next Pluralization Rules</a></li>

<li><a href="https://www.smashingmagazine.com/2017/01/comprehensive-guide-to-react-i18n/" target="_blank" rel="noopener noreferrer">A Comprehensive Guide to Building Multilingual Applications</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção
Como Usar Virtualização de Listas em React: react-window e react-virtual em Produção

O Problema da Renderização em Listas Grandes Quando trabalha com listas conte...

useMemo e useCallback: Memoização Real com Análise de Custo na Prática
useMemo e useCallback: Memoização Real com Análise de Custo na Prática

Entendendo Memoização em React Memoização é uma técnica de otimização que con...

O que Todo Dev Deve Saber sobre Hooks para WebSockets: Conexão Reativa e Reconexão Automática
O que Todo Dev Deve Saber sobre Hooks para WebSockets: Conexão Reativa e Reconexão Automática

Entendendo WebSockets e a Necessidade de Hooks Reativos WebSockets estabelece...