Internacionalização no frontend com i18next e formatação de datas
1. Por que internacionalizar o frontend é essencial para produtos globais
1.1. O impacto da localização na experiência do usuário e retenção
Produtos digitais que alcançam audiências globais precisam falar a língua do usuário — literalmente. Estudos mostram que 75% dos consumidores preferem comprar em seu idioma nativo, e 60% raramente ou nunca compram em sites apenas em inglês. A internacionalização (i18n) não é apenas um detalhe técnico; é um fator decisivo para retenção e conversão.
1.2. Diferenças culturais além do idioma: formatos de data, moeda e números
Traduzir textos é apenas o começo. Formatos de data variam dramaticamente entre regiões: enquanto nos EUA escreve-se "12/31/2024", no Brasil usa-se "31/12/2024", e no Japão "2024年12月31日". Moedas, separadores decimais e fusos horários adicionam camadas de complexidade que exigem soluções robustas.
1.3. i18next como solução madura e modular para React, Vue e Angular
O i18next é a biblioteca de internacionalização mais popular do ecossistema JavaScript, com mais de 30 mil estrelas no GitHub. Sua arquitetura modular permite integração com React, Vue, Angular e até frameworks server-side, oferecendo detecção automática de idioma, lazy loading e suporte a pluralização complexa.
2. Configuração inicial do i18next no projeto frontend
2.1. Instalação e inicialização com i18next e react-i18next
npm install i18next react-i18next i18next-browser-languagedetector
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import en from './locales/en.json';
import pt from './locales/pt.json';
import ja from './locales/ja.json';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
pt: { translation: pt },
ja: { translation: ja }
},
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
});
export default i18n;
2.2. Estrutura de arquivos de tradução (JSON por namespace)
// locales/en.json
{
"welcome": "Welcome, {{name}}!",
"days_ago": "{{count}} day ago",
"days_ago_plural": "{{count}} days ago",
"date_format": "MM/DD/YYYY"
}
// locales/pt.json
{
"welcome": "Bem-vindo, {{name}}!",
"days_ago": "{{count}} dia atrás",
"days_ago_plural": "{{count}} dias atrás",
"date_format": "DD/MM/YYYY"
}
// locales/ja.json
{
"welcome": "ようこそ、{{name}}!",
"days_ago": "{{count}}日前",
"days_ago_plural": "{{count}}日前",
"date_format": "YYYY年MM月DD日"
}
2.3. Detecção automática de idioma do navegador com i18next-browser-languagedetector
O LanguageDetector identifica automaticamente o idioma preferido do usuário a partir do cabeçalho Accept-Language do navegador, podendo também consultar localStorage, cookies ou parâmetros de URL. Isso elimina a necessidade de configuração manual inicial.
3. Gerenciamento de traduções com chaves, interpolação e pluralização
3.1. Uso de chaves aninhadas e interpolação de variáveis ({{name}})
// Componente React
import { useTranslation } from 'react-i18next';
function Greeting({ userName }) {
const { t } = useTranslation();
return <h1>{t('welcome', { name: userName })}</h1>;
}
3.2. Pluralização inteligente com count e regras por idioma
function DaysAgo({ days }) {
const { t } = useTranslation();
return <span>{t('days_ago', { count: days })}</span>;
}
// Para count=1: "1 day ago" (en), "1 dia atrás" (pt), "1日前" (ja)
// Para count=5: "5 days ago" (en), "5 dias atrás" (pt), "5日前" (ja)
3.3. Contextos e gênero: adaptação para idiomas como francês e alemão
// locales/fr.json
{
"friend": "ami",
"friend_male": "ami",
"friend_female": "amie"
}
// Uso com contexto
t('friend', { context: userGender }) // 'ami' ou 'amie'
4. Formatação de datas com i18next e bibliotecas complementares
4.1. Integração com Intl.DateTimeFormat para formatação nativa
function formatDate(date, locale) {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
}
// Exemplos:
// en-US: "December 31, 2024"
// pt-BR: "31 de dezembro de 2024"
// ja-JP: "2024年12月31日"
4.2. Uso de dayjs com plugins de localidade como alternativa leve
import dayjs from 'dayjs';
import 'dayjs/locale/pt';
import 'dayjs/locale/ja';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
function RelativeDate({ date, locale }) {
dayjs.locale(locale);
return <span>{dayjs(date).fromNow()}</span>;
}
// Exemplos:
// en: "3 days ago"
// pt: "há 3 dias"
// ja: "3日前"
4.3. Formatação de datas relativas (ex.: "há 3 dias") com Intl.RelativeTimeFormat
function relativeTimeAgo(date, locale) {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const diff = Math.floor((new Date() - new Date(date)) / (1000 * 60 * 60 * 24));
if (diff === 0) return rtf.format(0, 'day');
return rtf.format(-diff, 'day');
}
// en: "3 days ago"
// pt: "há 3 dias"
// ja: "3日前"
5. Personalização de formatos por região (locale-aware)
5.1. Diferenças entre en-US, pt-BR, de-DE e ja-JP no calendário
const locales = ['en-US', 'pt-BR', 'de-DE', 'ja-JP'];
const date = new Date(2024, 11, 31);
locales.forEach(locale => {
console.log(locale, new Intl.DateTimeFormat(locale).format(date));
});
// en-US: "12/31/2024"
// pt-BR: "31/12/2024"
// de-DE: "31.12.2024"
// ja-JP: "2024/12/31"
5.2. Configuração de fuso horário e timezone no frontend
function formatWithTimezone(date, locale, timezone) {
return new Intl.DateTimeFormat(locale, {
timeZone: timezone,
hour: '2-digit',
minute: '2-digit'
}).format(date);
}
// Exemplo: formatWithTimezone(new Date(), 'pt-BR', 'America/Sao_Paulo')
5.3. Formatação de números e moedas com Intl.NumberFormat integrado
function formatCurrency(value, locale, currency) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(value);
}
// pt-BR: "R$ 1.234,56"
// en-US: "$1,234.56"
// ja-JP: "¥1,235"
6. Troca dinâmica de idioma e persistência da preferência
6.1. Implementação de seletor de idioma com i18n.changeLanguage()
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('pt')}>Português</button>
<button onClick={() => changeLanguage('ja')}>日本語</button>
</div>
);
}
6.2. Persistência em localStorage e cookies com languageDetector
// Configuração do LanguageDetector para persistir em localStorage
const detectorOptions = {
order: ['localStorage', 'navigator', 'htmlTag'],
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage']
};
6.3. Atualização reativa de componentes sem perda de estado
O react-i18next garante que todos os componentes que usam o hook useTranslation sejam re-renderizados automaticamente quando o idioma muda, preservando o estado interno dos componentes.
7. Boas práticas e armadilhas comuns na internacionalização
7.1. Evitar concatenação manual de strings e hardcoding de datas
// ❌ Errado
const message = 'Olá, ' + userName + '!';
// ✅ Correto
t('greeting', { name: userName })
7.2. Testes automatizados de tradução com snapshot e falso locale
// Jest test example
import { render } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n-test';
test('renders welcome message in Portuguese', () => {
i18n.changeLanguage('pt');
const { getByText } = render(
<I18nextProvider i18n={i18n}>
<Greeting userName="Maria" />
</I18nextProvider>
);
expect(getByText('Bem-vindo, Maria!')).toBeInTheDocument();
});
7.3. Performance: lazy loading de namespaces e fallback de idioma
// Carregamento sob demanda de namespaces
i18n.loadNamespaces(['dashboard', 'admin'], (err) => {
if (!err) {
// Namespaces carregados
}
});
8. Exemplo prático: aplicação multi-idioma com datas localizadas
8.1. Estrutura de componentes: Header, DatePicker e Timeline
// App.js
import { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import Header from './Header';
import Timeline from './Timeline';
function App() {
const { i18n } = useTranslation();
return (
<div>
<Header />
<Timeline events={sampleEvents} />
</div>
);
}
8.2. Código completo de configuração do i18next com dayjs/locale
// i18n.js (configuração completa)
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import dayjs from 'dayjs';
import 'dayjs/locale/pt';
import 'dayjs/locale/ja';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
const resources = {
en: {
translation: {
title: "Event Timeline",
createEvent: "Create Event",
daysAgo: "{{count}} day ago",
daysAgo_plural: "{{count}} days ago"
}
},
pt: {
translation: {
title: "Linha do Tempo",
createEvent: "Criar Evento",
daysAgo: "{{count}} dia atrás",
daysAgo_plural: "{{count}} dias atrás"
}
},
ja: {
translation: {
title: "イベントタイムライン",
createEvent: "イベント作成",
daysAgo: "{{count}}日前"
}
}
};
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
interpolation: { escapeValue: false }
});
export default i18n;
8.3. Demonstração de troca entre en, pt e ja com datas reativas
// Timeline.js
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
function Timeline({ events }) {
const { t, i18n } = useTranslation();
dayjs.locale(i18n.language);
return (
<div>
<h1>{t('title')}</h1>
{events.map((event, index) => (
<div key={index}>
<h3>{event.title}</h3>
<p>{dayjs(event.date).fromNow()}</p>
<p>{new Intl.DateTimeFormat(i18n.language).format(new Date(event.date))}</p>
</div>
))}
</div>
);
}
A internacionalização no frontend é um investimento que paga dividendos em alcance global, satisfação do usuário e conformidade cultural. Com i18next e as APIs nativas de formatação, é possível construir aplicações verdadeiramente globais sem sacrificar performance ou experiência do desenvolvedor.
Referências
- Documentação oficial do i18next — Guia completo de configuração, plugins e APIs para internacionalização em JavaScript.
- react-i18next - Documentação — Tutorial oficial para integração do i18next com React, incluindo hooks e componentes.
- MDN Web Docs - Intl.DateTimeFormat — Referência completa para formatação nativa de datas no navegador.
- Day.js - Localização e plugins — Guia de uso de locales e plugins de data relativa com Day.js.
- i18next-browser-languagedetector — Repositório e documentação do detector automático de idioma para navegador.
- MDN Web Docs - Intl.RelativeTimeFormat — API nativa para formatação de tempo relativo em diferentes idiomas.
- Artigo: Boas práticas de i18n em React — Smashing Magazine sobre padrões e armadilhas na internacionalização com i18next.