<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 ("1 item" vs "2 items"), 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 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
i18n
// Detecta automaticamente o idioma do navegador
.use(LanguageDetector)
// Carrega traduções de arquivos JSON
.use(HttpBackend)
// Integra com React
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
ns: ['translation'],
defaultNS: 'translation',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
},
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 'react';
import ReactDOM from 'react-dom/client';
import i18n from './i18n';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);</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">{
"welcome": "Welcome to our application",
"greeting": "Hello, {{name}}!",
"items": "You have {{count}} item",
"items_plural": "You have {{count}} items",
"settings": "Settings",
"language": "Language",
"logout": "Logout"
}</code></pre>
<p>E <code>src/locales/pt/translation.json</code>:</p>
<pre><code class="language-json">{
"welcome": "Bem-vindo à nossa aplicação",
"greeting": "Olá, {{name}}!",
"items": "Você tem {{count}} item",
"items_plural": "Você tem {{count}} itens",
"settings": "Configurações",
"language": "Idioma",
"logout": "Sair"
}</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 'react-i18next';
function Dashboard() {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'João' })}</p>
<p>{t('items', { count: 5 })}</p>
<button onClick={() => i18n.changeLanguage('pt')}>
Português
</button>
<button onClick={() => i18n.changeLanguage('en')}>
English
</button>
</div>
);
}
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">{
"bank": {
"noun": "Banco",
"verb": "Depositar"
}
}</code></pre>
<p>E acesse com: <code>t('bank.noun')</code> ou <code>t('bank.verb')</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 'react-i18next';
function TermsPage() {
return (
<p>
<Trans i18nKey="termsIntro">
Leia nossos <a href="/terms">termos de serviço</a> e <a href="/privacy">política de privacidade</a>
</Trans>
</p>
);
}</code></pre>
<p>No JSON:</p>
<pre><code class="language-json">{
"termsIntro": "Leia nossos <1>termos de serviço</1> e <3>política de privacidade</3>"
}</code></pre>
<p>Os números <code><1></code>, <code><3></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 = 'USD', locale = 'en-US' }) {
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
return <span>{formatter.format(price)}</span>;
}
// Uso
export default function App() {
return (
<>
<PriceDisplay price={1234.5} currency="USD" locale="en-US" /> {/ $1,234.50 /}
<PriceDisplay price={1234.5} currency="BRL" locale="pt-BR" /> {/ R$ 1.234,50 /}
<PriceDisplay price={1234.5} currency="EUR" locale="de-DE" /> {/ 1.234,50 € /}
</>
);
}</code></pre>
<p>Para números simples sem moeda:</p>
<pre><code class="language-javascript">function PercentageDisplay({ value, locale = 'en-US' }) {
const formatter = new Intl.NumberFormat(locale, {
style: 'percent',
minimumFractionDigits: 1
});
return <span>{formatter.format(value)}</span>;
}</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 = 'en-US', options = {} }) {
const formatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: '2-digit',
minute: '2-digit',
...options
});
return <span>{formatter.format(new Date(date))}</span>;
}
// Uso
export default function App() {
const now = new Date('2024-01-15T14:30:00');
return (
<>
<DateDisplay date={now} locale="en-US" /> {/ Monday, January 15, 2024, 02:30 PM /}
<DateDisplay date={now} locale="pt-BR" /> {/ segunda-feira, 15 de janeiro de 2024 14:30 /}
<DateDisplay date={now} locale="de-DE" /> {/ Montag, 15. Januar 2024, 14:30 /}
</>
);
}</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 'react-i18next';
import { useMemo } from 'react';
export function useLocaleFormatting() {
const { i18n } = useTranslation();
const localeMap = {
'en': 'en-US',
'pt': 'pt-BR',
'es': 'es-ES',
'de': 'de-DE'
};
const locale = useMemo(() => localeMap[i18n.language] || 'en-US', [i18n.language]);
return {
formatCurrency: (value, currency = 'USD') => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(value);
},
formatDate: (date, options = {}) => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
...options
}).format(new Date(date));
},
formatNumber: (value, options = {}) => {
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 './hooks/useLocaleFormatting';
function Invoice({ amount, dueDate }) {
const { formatCurrency, formatDate } = useLocaleFormatting();
return (
<div>
<p>Amount: {formatCurrency(amount)}</p>
<p>Due: {formatDate(dueDate)}</p>
</div>
);
}</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 = 'en-US' }) {
const formatter = new Intl.ListFormat(locale, {
style: 'long',
type: 'conjunction'
});
return <span>{formatter.format(tags)}</span>;
}
// Uso
<TagList tags={['React', 'JavaScript', 'i18n']} locale="en-US" />
// Result: "React, JavaScript, and i18n"
<TagList tags={['React', 'JavaScript', 'i18n']} locale="pt-BR" />
// Result: "React, JavaScript e i18n"</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 'react-i18next';
import { useLocaleFormatting } from './hooks/useLocaleFormatting';
import { useState } from 'react';
function App() {
const { t, i18n } = useTranslation();
const { formatCurrency, formatDate, formatNumber } = useLocaleFormatting();
const [cart, setCart] = useState([
{ id: 1, name: 'Laptop', price: 1299.99, quantity: 1 },
{ id: 2, name: 'Mouse', price: 29.99, quantity: 2 }
]);
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
<div style={{ padding: '20px', fontFamily: 'Arial' }}>
<header style={{ marginBottom: '20px' }}>
<h1>{t('welcome')}</h1>
<div>
<button onClick={() => i18n.changeLanguage('en')}>English</button>
<button onClick={() => i18n.changeLanguage('pt')}>Português</button>
<button onClick={() => i18n.changeLanguage('es')}>Español</button>
<p>{t('currentLanguage', { lng: i18n.language })}</p>
</div>
</header>
<section style={{ marginBottom: '30px' }}>
<h2>{t('shoppingCart')}</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>{t('product')}</th>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>{t('price')}</th>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>{t('quantity')}</th>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>{t('subtotal')}</th>
</tr>
</thead>
<tbody>
{cart.map(item => (
<tr key={item.id}>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{item.name}</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
{formatCurrency(item.price, 'USD')}
</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
{formatNumber(item.quantity)}
</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
{formatCurrency(item.price * item.quantity, 'USD')}
</td>
</tr>
))}
</tbody>
</table>
<p style={{ fontSize: '18px', fontWeight: 'bold', marginTop: '10px' }}>
{t('total')}: {formatCurrency(total, 'USD')}
</p>
</section>
<footer style={{ marginTop: '30px', borderTop: '1px solid #ddd', paddingTop: '10px' }}>
<p>{t('lastUpdated', { date: formatDate(new Date()) })}</p>
</footer>
</div>
);
}
export default App;</code></pre>
<p>Arquivo de tradução <code>src/locales/en/translation.json</code>:</p>
<pre><code class="language-json">{
"welcome": "Welcome to ShopHub",
"currentLanguage": "Current language: {{lng}}",
"shoppingCart": "Shopping Cart",
"product": "Product",
"price": "Price",
"quantity": "Quantity",
"subtotal": "Subtotal",
"total": "Total",
"lastUpdated": "Last updated: {{date}}"
}</code></pre>
<p>E <code>src/locales/pt/translation.json</code>:</p>
<pre><code class="language-json">{
"welcome": "Bem-vindo ao ShopHub",
"currentLanguage": "Idioma atual: {{lng}}",
"shoppingCart": "Carrinho de Compras",
"product": "Produto",
"price": "Preço",
"quantity": "Quantidade",
"subtotal": "Subtotal",
"total": "Total",
"lastUpdated": "Última atualização: {{date}}"
}</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 "Hello" 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><!-- FIM --></p>