React
Next.js
Internacionalização
i18n
Frontend

Internationalization Good Practices (i18n) in React and Next.js in 2025

Internationalization Good Practices (i18n) in React and Next.js in 2025

In an increasingly connected digital world, developing web applications that serve global users is no longer a differentiator, but a necessity. Internationalization (i18n) has become a critical component in modern application development, allowing your product to reach diverse audiences regardless of language, region or cultural preferences.

In this article, we will explore best practices, tools, and strategies for implementing effective internationalization in React and Next.js applications in 2025, providing practical examples and solutions to common challenges.

Fundamentals of Internationalization

What is i18n and why is it important?

The term "i18n" is an abbreviation for "internationalization" (the letter "i" followed by 18 letters and ending with "n"). It is the process of designing and developing software applications that can be adapted for different languages ​​and regions without engineering or code changes.

In 2025, the importance of i18n is amplified by several factors:

  1. Global reach: Applications with multilingual support can reach international markets and expand their user base.
  2. User experience: Users feel more comfortable and engaged when interacting with applications in their native language.
  3. Regulatory Compliance: Many countries have legal requirements for digital systems to support local languages.
  4. Competitive advantage: Well-internationalized applications stand out in competitive global markets.

Difference between i18n, l10n and g11n

It is important to understand the distinction between terms frequently used in this domain:

  • Internationalization (i18n): Process of designing and developing a product so that it can be adapted to different languages and regions.
  • Localization (l10n): Process of adapting an internationalized product to a specific location or market, including translating texts and adapting cultural elements.
  • Globalization (g11n): Business strategy that covers both i18n and l10n, considering all aspects of taking a product to global markets.

Modern Libraries and Tools for i18n in React

i18n ecosystem in 2025

The internationalization ecosystem for React has evolved significantly in recent years. Here are the most popular and advanced libraries in 2025:

1. React-i18next

The react-i18next continues to be one of the most robust and popular solutions, evolving to meet modern needs:

// 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. ICU format with react-intl

The ICU (International Components for Unicode) format has become the predominant standard for handling internationalized texts:

// 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: A powerful alternative

LinguiJS has gained significant popularity for its simplicity and performance:

// 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> ); }

Implementation of i18n in Next.js

Next.js has established itself as one of the most popular React frameworks, and its internationalization capabilities have evolved considerably in 2025.

Modern i18n Configuration in Next.js

Next.js’s integrated approach for i18n has become more robust:

// 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;

Using Translations in Server and Client Components

Next.js App Router introduced a clear separation between server and client components, which requires i18n-specific approaches:

// 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> ); }

Implementation of a complete solution

To show how it all comes together, here's a more complete App Router implementation of 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).*)', ], };

Architectural Decisions that Matter

Beyond libraries, internationalizing well is, first and foremost, an architectural decision. Three choices define how sustainable the multilingual operation will be over time.

The first is to separate content from code from the beginning. Strings should never be embedded in components; they belong to versioned translation files, organized by namespace, so that translators and developers work without stepping on each other's territory. The second is to treat pluralization, gender and formatting of numbers, dates and currency as the responsibility of the i18n library, and not of conditionals spread throughout the code, as these rules vary from language to language in ways that cannot be resolved with string concatenation. The third is to define, at the routing level, a clear strategy for language detection, URL prefixes, and fallback for partially translated content, ensuring that each user arrives in the right language without broken pages along the way.

In Next.js's App Router, this means consciously deciding what is translated on the server and what is delivered to the client, while keeping as much translation on the server to reduce the JavaScript sent to the browser.

Conclusion

Internationalization is no longer an optional feature but has become part of the foundation of any product with global ambition. In React and Next.js, the 2025 ecosystem offers mature tools, react-i18next, react-intl with ICU and LinguiJS, capable of covering everything from advanced pluralization to locale-sensitive formatting.

The difference, however, is rarely in the chosen library, but in the discipline of architecture: separating content from code, delegating linguistic rules to those who know how to handle them and designing language routing as a first-class citizen. Teams that treat i18n as a strategic decision from the first commit reach new markets faster and with less rework than those that leave it until later.

Also read