Internacionalização (i18n) no React e Next.js
1. Fundamentos da Internacionalização (i18n)
Internacionalização (abreviada como i18n — "i" + 18 letras + "n") é o processo de projetar software para que ele possa ser adaptado a diferentes idiomas e regiões sem alterações no código-fonte. Enquanto a localização (l10n) adapta o conteúdo para um mercado específico, a globalização (g11n) combina ambos os processos para criar produtos verdadeiramente mundiais.
Os principais desafios do i18n incluem:
- Pluralização: regras como "1 item" vs "2 items" variam entre idiomas
- Formatação de datas e moedas: MM/DD/YYYY nos EUA vs DD/MM/YYYY no Brasil
- Direção de texto: idiomas como árabe e hebraico usam RTL (right-to-left)
- Gênero e contexto: em francês, "você" muda conforme o gênero do interlocutor
2. Configuração Inicial com Next.js (App Router)
O ecossistema Next.js oferece múltiplas bibliotecas para i18n. Para este artigo, usaremos next-intl, que se integra nativamente ao App Router e oferece performance otimizada.
Instalação e estrutura de pastas
npm install next-intl
Estrutura de diretórios recomendada:
src/
messages/
en.json
pt-BR.json
es.json
app/
[locale]/
page.js
layout.js
i18n.js
middleware.js
next.config.js
Configuração do middleware
// middleware.js
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'pt-BR', 'es'],
defaultLocale: 'en'
});
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
Configuração do next.config.js
// next.config.js
const withNextIntl = require('next-intl/plugin')();
module.exports = withNextIntl({
// outras configurações do Next.js
});
3. Gerenciamento de Traduções com Arquivos JSON
Os arquivos de tradução seguem uma estrutura de chaves aninhadas. O carregamento assíncrono otimiza o bundle, carregando apenas o idioma ativo.
// src/messages/pt-BR.json
{
"home": {
"title": "Bem-vindo ao nosso site",
"description": "Este é um exemplo de internacionalização",
"items": "{count} {count, plural, one {item} other {itens}} encontrados"
},
"common": {
"language": "Idioma",
"save": "Salvar",
"cancel": "Cancelar"
}
}
// src/messages/en.json
{
"home": {
"title": "Welcome to our website",
"description": "This is an internationalization example",
"items": "{count} {count, plural, one {item} other {items}} found"
},
"common": {
"language": "Language",
"save": "Save",
"cancel": "Cancel"
}
}
Boas práticas:
- Use namespaces para organizar por contexto (ex: home, checkout, errors)
- Evite chaves muito longas; prefira hierarquia de até 3 níveis
- Mantenha arquivos de tradução sincronizados com scripts de validação
4. Componentes e Hooks para Tradução no React
O next-intl fornece o hook useTranslations() e o componente NextIntlClientProvider.
Configuração do provedor e layout
// src/app/[locale]/layout.js
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({ children, params: { locale } }) {
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Uso do hook em componentes
// src/app/[locale]/page.js
import { useTranslations } from 'next-intl';
import { formatNumber, formatDate } from 'next-intl';
export default function HomePage() {
const t = useTranslations('home');
const price = 1234.56;
const date = new Date('2024-12-25');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<p>{t('items', { count: 3 })}</p>
<p>Preço: {formatNumber(price, { style: 'currency', currency: 'BRL' })}</p>
<p>Data: {formatDate(date, { dateStyle: 'full' })}</p>
</div>
);
}
Interpolação de variáveis e pluralização
// Componente de carrinho
import { useTranslations } from 'next-intl';
export default function CartSummary({ items }) {
const t = useTranslations('cart');
return (
<div>
<h2>{t('title')}</h2>
<p>
{t('items_count', {
count: items.length,
total: items.length
})}
</p>
{/* Exemplo de pluralização no JSON:
"items_count": "Você tem {count} {count, plural, one {item} other {itens}} no carrinho"
*/}
</div>
);
}
5. Roteamento e Mudança de Idioma no Next.js
Seletor de idioma com persistência
// src/components/LanguageSwitcher.js
'use client';
import { useLocale } from 'next-intl';
import { useRouter, usePathname } from 'next/navigation';
import { setCookie } from 'cookies-next';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (newLocale) => {
setCookie('NEXT_LOCALE', newLocale, { maxAge: 60 * 60 * 24 * 365 });
router.push(`/${newLocale}${pathname}`);
};
return (
<select
value={locale}
onChange={(e) => handleChange(e.target.value)}
>
<option value="en">English</option>
<option value="pt-BR">Português (Brasil)</option>
<option value="es">Español</option>
</select>
);
}
Redirecionamento automático baseado no navegador
// middleware.js (versão estendida)
import createMiddleware from 'next-intl/middleware';
import { NextResponse } from 'next/server';
const i18nMiddleware = createMiddleware({
locales: ['en', 'pt-BR', 'es'],
defaultLocale: 'en',
localeDetection: true
});
export default function middleware(request) {
const { pathname } = request.nextUrl;
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
// Se o usuário já escolheu um idioma, respeita a escolha
if (cookieLocale && !pathname.startsWith(`/${cookieLocale}`)) {
return NextResponse.redirect(new URL(`/${cookieLocale}${pathname}`, request.url));
}
return i18nMiddleware(request);
}
6. Internacionalização de Metadados e SEO
Metadados dinâmicos por locale
// src/app/[locale]/layout.js (versão completa)
import { getTranslations } from 'next-intl/server';
export async function generateMetadata({ params: { locale } }) {
const t = await getTranslations({ locale, namespace: 'metadata' });
return {
title: t('title'),
description: t('description'),
alternates: {
languages: {
'en': '/en',
'pt-BR': '/pt-BR',
'es': '/es'
}
}
};
}
Sitemap com alternativas de idioma
// src/app/sitemap.js
import { locales } from '../i18n';
export default async function sitemap() {
const baseUrl = 'https://seusite.com';
const entries = locales.flatMap(locale => [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
alternates: {
languages: Object.fromEntries(
locales.map(l => [l, `${baseUrl}/${l}`])
)
}
}
]);
return entries;
}
7. Testes e Ferramentas Avançadas
Testes unitários com Jest e Testing Library
// __tests__/HomePage.test.js
import { render, screen } from '@testing-library/react';
import { NextIntlClientProvider } from 'next-intl';
import HomePage from '../src/app/[locale]/page';
const messages = {
home: {
title: 'Bem-vindo',
description: 'Descrição de teste',
items: '{count} itens'
}
};
describe('HomePage i18n', () => {
it('renderiza título traduzido', () => {
render(
<NextIntlClientProvider locale="pt-BR" messages={messages}>
<HomePage />
</NextIntlClientProvider>
);
expect(screen.getByText('Bem-vindo')).toBeInTheDocument();
});
it('renderiza mensagem de itens com pluralização', () => {
render(
<NextIntlClientProvider locale="pt-BR" messages={messages}>
<HomePage />
</NextIntlClientProvider>
);
expect(screen.getByText('3 itens')).toBeInTheDocument();
});
});
Ferramentas de tradução colaborativa
Para projetos maiores, ferramentas como Crowdin e Lokalise oferecem:
- Interface visual para tradutores
- Integração CI/CD para atualização automática de arquivos JSON
- Controle de versão e aprovação de traduções
- Detecção de chaves ausentes
Debugging de chaves ausentes
// next.config.js (com configuração de debug)
const withNextIntl = require('next-intl/plugin')();
module.exports = withNextIntl({
experimental: {
// Ativa logs de chaves ausentes em desenvolvimento
missingKeys: process.env.NODE_ENV === 'development' ? 'warn' : 'ignore'
}
});
A internacionalização é um investimento que paga dividendos à medida que sua aplicação alcança novos mercados. Com Next.js e next-intl, você obtém uma base sólida para construir experiências verdadeiramente globais, mantendo performance e SEO otimizados.
Referências
- Documentação oficial do next-intl — Guia completo de internacionalização para Next.js App Router
- MDN Web Docs: Internationalization — API nativa de internacionalização do JavaScript
- Next.js Documentation: Internationalization — Documentação oficial do Next.js sobre roteamento i18n
- Crowdin: Translation Management Platform — Ferramenta colaborativa para gerenciamento de traduções
- Lokalise: Continuous Localization — Plataforma de localização com integração CI/CD
- React Intl (FormatJS) — Biblioteca madura de i18n para React com suporte a ICU MessageFormat
- i18next: Internationalization Framework — Framework popular de i18n para JavaScript/Node.js com adaptador para React