React
Next.js
Internacionalização
i18n
Frontend

Buenas Prácticas de Internacionalización (i18n) en React y Next.js en 2025

Buenas Prácticas de Internacionalización (i18n) en React y Next.js en 2025

En un mundo digital cada vez más conectado, desarrollar aplicaciones web que sirvan a usuarios globales ya no es un diferenciador, sino una necesidad. La internacionalización (i18n) se ha convertido en un componente crítico en el desarrollo de aplicaciones modernas, permitiendo que su producto llegue a audiencias diversas independientemente del idioma, la región o las preferencias culturales.

En este artículo, exploraremos las mejores prácticas, herramientas y estrategias para implementar una internacionalización efectiva en React y aplicaciones Next.js en 2025, brindando ejemplos prácticos y soluciones a desafíos comunes.

Fundamentos de la Internacionalización

¿Qué es i18n y por qué es importante?

El término "i18n" es una abreviatura de "internacionalización" (la letra "i" seguida de 18 letras y termina en "n"). Es el proceso de diseñar y desarrollar aplicaciones de software que puedan adaptarse a diferentes idiomas y regiones sin cambios de ingeniería ni de código.

En 2025, la importancia del i18n se verá amplificada por varios factores:

  1. Alcance global: las aplicaciones con soporte multilingüe pueden llegar a mercados internacionales y ampliar su base de usuarios.
  2. Experiencia de usuario: los usuarios se sienten más cómodos y comprometidos al interactuar con aplicaciones en su idioma nativo.
  3. Cumplimiento normativo: muchos países tienen requisitos legales para que los sistemas digitales admitan los idiomas locales.
  4. Ventaja competitiva: las aplicaciones bien internacionalizadas se destacan en mercados globales competitivos.

Diferencia entre i18n, l10n y g11n

Es importante comprender la distinción entre los términos que se utilizan con frecuencia en este ámbito:

  • Internacionalización (i18n): Proceso de diseño y desarrollo de un producto para que pueda adaptarse a diferentes idiomas y regiones.
  • Localización (l10n): Proceso de adaptación de un producto internacionalizado a una ubicación o mercado específico, incluyendo la traducción de textos y la adaptación de elementos culturales.
  • Globalización (g11n): Estrategia de negocio que abarca tanto i18n como l10n, considerando todos los aspectos de llevar un producto a los mercados globales.

Bibliotecas y herramientas modernas para i18n en React

ecosistema i18n en 2025

El ecosistema de internacionalización de React ha evolucionado significativamente en los últimos años. Estas son las bibliotecas más populares y avanzadas en 2025:

1. Reaccionar-i18siguiente

El react-i18next sigue siendo una de las soluciones más robustas y populares, y evoluciona para satisfacer las necesidades 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 UCI con reaccionar-intl

El formato ICU (International Components for Unicode) se ha convertido en el estándar predominante para el manejo 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: Una alternativa poderosa

LinguiJS ha ganado una gran popularidad por su simplicidad y rendimiento:

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

Implementación de i18n en Next.js

Next.js se ha consolidado como uno de los marcos de React más populares y sus capacidades de internacionalización han evolucionado considerablemente en 2025.

Configuración moderna de i18n en Next.js

El enfoque integrado de Next.js para i18n se ha vuelto más sólido:

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

Uso de traducciones en componentes de servidor y cliente

Next.js App Router introdujo una separación clara entre los componentes del servidor y del cliente, lo que requiere enfoques específicos de 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> ); }

Implementación de una solución completa

Para mostrar cómo funciona todo, aquí hay una implementación más completa de App Router de 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).*)', ], };

Decisiones arquitectónicas que importan

Más allá de las bibliotecas, internacionalizarse bien es, ante todo, una decisión arquitectónica. Tres opciones definen cuán sostenible será la operación multilingüe en el tiempo.

La primera es separar el contenido del código desde el principio. Las cadenas nunca deben estar incrustadas en los componentes; pertenecen a archivos de traducción versionados, organizados por espacio de nombres, para que los traductores y desarrolladores trabajen sin pisar el territorio de los demás. La segunda es tratar la pluralización, el género y el formato de números, fechas y moneda como responsabilidad de la biblioteca i18n, y no de los condicionales repartidos por todo el código, ya que estas reglas varían de un idioma a otro de maneras que no se pueden resolver con la concatenación de cadenas. El tercero es definir, a nivel de enrutamiento, una estrategia clara para la detección de idioma, prefijos de URL y respaldo para contenido parcialmente traducido, asegurando que cada usuario llegue en el idioma correcto sin páginas rotas en el camino.

En el App Router de Next.js, esto significa decidir conscientemente qué se traduce en el servidor y qué se entrega al cliente, manteniendo al mismo tiempo la mayor cantidad de traducción en el servidor para reducir el JavaScript enviado al navegador.

Conclusión

La internacionalización ya no es una característica opcional sino que se ha convertido en parte de la base de cualquier producto con ambición global. En React y Next.js, el ecosistema 2025 ofrece herramientas maduras, react-i18next, react-intl con ICU y LinguiJS, capaces de cubrir todo, desde pluralización avanzada hasta formato sensible a la configuración regional.

La diferencia, sin embargo, rara vez está en la biblioteca elegida, sino en la disciplina de la arquitectura: separar el contenido del código, delegar reglas lingüísticas a quienes saben cómo manejarlas y diseñar el enrutamiento del lenguaje como un ciudadano de primera. Los equipos que tratan a i18n como una decisión estratégica desde el primer compromiso llegan a nuevos mercados más rápido y con menos retrabajo que aquellos que lo dejan para más tarde.

Lea también