React
Next.js
Internacionalização
i18n
Frontend

Boas Práticas de Internacionalização (i18n) em React e Next.js em 2025

Em um mundo digital cada vez mais conectado, desenvolver aplicações web que atendam a usuários globais não é mais um diferencial, mas uma necessidade. A internacionalização (i18n) tornou-se um componente crítico no desenvolvimento de aplicações modernas, permitindo que seu produto alcance públicos diversos, independentemente de idioma, região ou preferências culturais.

Neste artigo, exploraremos as melhores práticas, ferramentas e estratégias para implementar internacionalização eficaz em aplicações React e Next.js em 2025, fornecendo exemplos práticos e soluções para desafios comuns.

Fundamentos da Internacionalização

O que é i18n e por que é importante?

O termo "i18n" é uma abreviação para "internacionalização" (a letra "i" seguida por 18 letras e terminando com "n"). É o processo de projetar e desenvolver aplicações de software que podem ser adaptadas para diferentes idiomas e regiões sem alterações de engenharia ou código.

Em 2025, a importância da i18n é amplificada por vários fatores:

  1. Alcance global: Aplicações com suporte multilíngue podem atingir mercados internacionais e expandir sua base de usuários.
  2. Experiência do usuário: Usuários se sentem mais confortáveis e engajados quando interagem com aplicações em seu idioma nativo.
  3. Conformidade regulatória: Muitos países têm requisitos legais para sistemas digitais oferecerem suporte a idiomas locais.
  4. Vantagem competitiva: Aplicações bem internacionalizadas se destacam em mercados globais competitivos.

Diferença entre i18n, l10n e g11n

É importante compreender a distinção entre termos frequentemente usados neste domínio:

  • Internacionalização (i18n): Processo de design e desenvolvimento de um produto para que possa ser adaptado a diferentes idiomas e regiões.
  • Localização (l10n): Processo de adaptar um produto internacionalizado para um local específico ou mercado, incluindo tradução de textos e adaptação de elementos culturais.
  • Globalização (g11n): Estratégia de negócios que abrange tanto i18n quanto l10n, considerando todos os aspectos de levar um produto a mercados globais.

Bibliotecas e Ferramentas Modernas para i18n em React

Ecossistema de i18n em 2025

O ecossistema de internacionalização para React evoluiu significativamente nos últimos anos. Aqui estão as bibliotecas mais populares e avançadas em 2025:

1. React-i18next

O react-i18next continua sendo uma das soluções mais robustas e populares, evoluindo para atender às necessidades modernas:

// Configuração básica do react-i18next em 2025 import i18n from 'i18next'; import { initReactI18next, useTranslation } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; i18n // Carregamento sob demanda de traduções .use(Backend) // Detecção automática de idioma .use(LanguageDetector) // Integração com React .use(initReactI18next) .init({ fallbackLng: 'pt-BR', supportedLngs: ['pt-BR', 'en-US', 'es', 'fr', 'zh-CN'], // Novo em 2025: Detecção avançada de idioma com preferências de usuário detection: { order: ['localStorage', 'navigator', 'querystring', 'cookie'], caches: ['localStorage'], cookieExpirationDate: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), lookupQuerystring: 'lng', lookupCookie: 'i18n', }, // Novo em 2025: Cache inteligente com estratégia adaptativa backend: { loadPath: '/locales//.json', requestOptions: { cache: 'smart-default', // Nova estratégia de cache adaptativa }, }, interpolation: { escapeValue: false, // React já escapa por padrão format: function(value, format, lng) { // Novo suporte para formatação avançada if (format === 'uppercase') return value.toUpperCase(); if (format === 'currency') return new Intl.NumberFormat(lng, { style: 'currency', currency: 'BRL' }).format(value); return value; } }, // Novo em 2025: Análise de uso de traduções para otimização telemetry: { enabled: process.env.NODE_ENV === 'development', endpoint: '/api/i18n-telemetry', sampleRate: 0.1 } }); export default i18n;

2. Formato ICU com react-intl

O formato ICU (International Components for Unicode) tornou-se o padrão predominante para manipulação de textos internacionalizados:

// Exemplo usando react-intl com sintaxe ICU moderna import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; function ProductDetails({ product, inventory, lastUpdated }) { const intl = useIntl(); return ( <div className="product-card"> <h2>{product.name}</h2> {/* Pluralização avançada */} <FormattedMessage id="product.inventory" defaultMessage="{inventory, plural, =0 {Fora de estoque} one {Última unidade disponível!} other {# unidades em estoque}}" values= /> {/* Formatação de data relativa */} <FormattedMessage id="product.lastUpdated" defaultMessage="Atualizado {lastUpdated, relativeTime, style=long}" values= /> {/* Formatação de valores variáveis dependentes de idioma */} <p> {intl.formatMessage( { id: 'product.price', defaultMessage: 'Preço: {price, number, currency}' }, { price: product.price } )} </p> {/* Formatação condicional com seleção */} <FormattedMessage id="product.status" defaultMessage="{status, select, new {Novo} sale {Promoção} limited {Edição limitada} other {Regular}}" values= /> </div> ); }

3. LinguiJS: Uma alternativa poderosa

LinguiJS ganhou popularidade significativa por sua simplicidade e desempenho:

// Exemplo com Lingui v5 (versão 2025) import React from 'react'; import { Trans, Plural, t } from '@lingui/macro'; function ShoppingCart({ items, totalPrice, lastUpdated }) { return ( <div className="shopping-cart"> <h2><Trans id="cart.title">Seu Carrinho</Trans></h2> <Plural value={items.length} zero={<Trans id="cart.empty">Seu carrinho está vazio</Trans>} one={<Trans id="cart.oneItem">1 item no carrinho</Trans>} other={<Trans id="cart.items">{items.length} itens no carrinho</Trans>} /> {items.map(item => ( <div key={item.id} className="cart-item"> <span>{item.name}</span> <span>{t({ id: 'cart.item.price', message: 'Preço: {price, number, currency}', values: { price: item.price } })}</span> </div> ))} <div className="cart-footer"> <div className="total"> <Trans id="cart.total" values=> Total: {totalPrice, number, currency} </Trans> </div> <div className="updated"> <Trans id="cart.updated" values=> Atualizado em {lastUpdated, date, long} </Trans> </div> </div> </div> ); }

Implementação de i18n em Next.js

Next.js se consolidou como um dos frameworks React mais populares, e suas capacidades de internacionalização evoluíram consideravelmente em 2025.

Configuração Moderna de i18n no Next.js

A abordagem integrada do Next.js para i18n se tornou mais robusta:

// next.config.js em 2025 /** @type {import('next').NextConfig} */ const nextConfig = { i18n: { // Idiomas suportados locales: ['pt-BR', 'en-US', 'es', 'fr', 'zh-CN'], // Idioma padrão defaultLocale: 'pt-BR', // Novos recursos em 2025 localeDetection: true, // Detecção automática de idioma automaticLocalePrefix: true, // Novo em 2025 - prefixos de URL simplificados // Domínios específicos por idioma (para SEO otimizado) domains: [ { domain: 'meuapp.com.br', defaultLocale: 'pt-BR', }, { domain: 'myapp.com', defaultLocale: 'en-US', }, { domain: 'miapp.es', defaultLocale: 'es', }, ], // Novo em 2025: estratégia de fallback para conteúdo parcialmente traduzido fallbackStrategy: 'partial', // Padrões de URL que não devem ser traduzidos excludeFromTranslation: [ '/api/*', '/admin/*', '/static/*', ], }, // Outras configurações do Next.js }; module.exports = nextConfig;

Utilizando Traduções em Componentes Server e Client

O Next.js App Router introduziu uma clara separação entre componentes de servidor e cliente, que requer abordagens específicas para i18n:

// Exemplo para componentes Server no Next.js App Router // Em /app/[lang]/layout.tsx import { Locale } from '@/i18n/config'; import { getTranslations } from '@/i18n/server'; export default async function RootLayout({ children, params: { lang } }: { children: React.ReactNode; params: { lang: Locale }; }) { // Obter traduções no servidor const { t } = await getTranslations(lang, 'common'); return ( <html lang={lang}> <body> <header> <h1>{t('site.title')}</h1> <nav> <ul> <li>{t('nav.home')}</li> <li>{t('nav.products')}</li> <li>{t('nav.contact')}</li> </ul> </nav> </header> <main>{children}</main> <footer>{t('site.footer')}</footer> </body> </html> ); } // Para componentes Client 'use client'; import { useTranslation } from '@/i18n/client'; import { useParams } from 'next/navigation'; export default function LanguageSwitcher() { const params = useParams(); const lang = params.lang as Locale; const { t } = useTranslation(lang, 'common'); return ( <div className="language-selector"> <p>{t('language.select')}</p> <select> <option value="pt-BR">{t('language.portuguese')}</option> <option value="en-US">{t('language.english')}</option> <option value="es">{t('language.spanish')}</option> </select> </div> ); }

Implementação de uma solução completa

Para mostrar como tudo se junta, aqui está uma implementação mais completa no App Router do Next.js:

// Em /i18n/config.ts export const defaultLocale = 'pt-BR'; export const locales = ['pt-BR', 'en-US', 'es', 'fr', 'zh-CN'] as const; export type Locale = typeof locales[number]; // Em /i18n/server.ts import { createInstance } from 'i18next'; import resourcesToBackend from 'i18next-resources-to-backend'; import { initReactI18next } from 'react-i18next/initReactI18next'; import { Locale, defaultLocale } from './config'; export async function getTranslations(locale: Locale, namespace: string) { const i18nInstance = createInstance(); await i18nInstance .use(initReactI18next) .use(resourcesToBackend((language: string, ns: string) => import(`./locales/${language}/${ns}.json`))) .init({ lng: locale, fallbackLng: defaultLocale, supportedLngs: locales, defaultNS: 'common', ns: namespace, fallbackNS: 'common', }); return { t: i18nInstance.getFixedT(locale, namespace), i18n: i18nInstance }; } // Em /middleware.ts - para roteamento e redirecionamento baseado em idioma import { NextRequest, NextResponse } from 'next/server'; import { match as matchLocale } from '@formatjs/intl-localematcher'; import Negotiator from 'negotiator'; import { locales, defaultLocale } from './i18n/config'; function getLocale(request: NextRequest): string { // Simulando cabeçalhos para o negotiator const headers = { 'accept-language': request.headers.get('accept-language') || defaultLocale }; const languages = new Negotiator({ headers }).languages(); // Usar @formatjs/intl-localematcher para escolher o melhor idioma const locale = matchLocale(languages, locales, defaultLocale); return locale; } export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; // Verificar se a URL já inclui uma localidade const pathnameHasLocale = locales.some( locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ); if (pathnameHasLocale) return NextResponse.next(); // Redirecionar se a localidade não estiver na URL const locale = getLocale(request); const newUrl = new URL(`/${locale}${pathname}`, request.url); return NextResponse.redirect(newUrl); } export const config = { matcher: [ // Excluir arquivos estáticos e API '/((?!api|_next/static|_next/image|favicon.ico).*)', ], };

Decisões de Arquitetura que Importam

Para além das bibliotecas, internacionalizar bem é, antes de tudo, uma decisão de arquitetura. Três escolhas definem o quão sustentável será a operação multilíngue ao longo do tempo.

A primeira é separar conteúdo de código desde o início. Strings nunca devem viver embutidas em componentes; elas pertencem a arquivos de tradução versionados, organizados por namespace, de modo que tradutores e desenvolvedores trabalhem sem pisar um no território do outro. A segunda é tratar pluralização, gênero e formatação de números, datas e moeda como responsabilidade da biblioteca de i18n, e não de condicionais espalhadas pelo código, já que essas regras variam de idioma para idioma de formas que não se resolvem com concatenação de strings. A terceira é definir, no nível do roteamento, uma estratégia clara de detecção de idioma, prefixos de URL e fallback para conteúdo parcialmente traduzido, garantindo que cada usuário chegue ao idioma certo sem páginas quebradas no caminho.

No App Router do Next.js, isso significa decidir conscientemente o que é traduzido no servidor e o que é entregue ao cliente, mantendo o máximo de tradução no servidor para reduzir o JavaScript enviado ao navegador.

Conclusão

Internacionalização deixou de ser um recurso opcional para se tornar parte da fundação de qualquer produto com ambição global. Em React e Next.js, o ecossistema de 2025 oferece ferramentas maduras, react-i18next, react-intl com ICU e LinguiJS, capazes de cobrir desde pluralização avançada até formatação sensível a localidade.

O diferencial, porém, raramente está na biblioteca escolhida, e sim na disciplina de arquitetura: separar conteúdo de código, delegar as regras linguísticas a quem sabe tratá-las e desenhar o roteamento de idiomas como cidadão de primeira classe. Equipes que tratam i18n como decisão estratégica desde o primeiro commit chegam a novos mercados mais rápido e com menos retrabalho do que aquelas que a deixam para depois.

Leia também