Añadir internacionalización a una aplicación React
Configura la internacionalización en React con react-i18next: interpolación, plurales, diseño RTL, formato local y SSR en Next.js.
Añadir internacionalización a una aplicación React implica externalizar cada cadena de texto visible para el usuario en archivos por idioma y renderizarlas a través de una capa de traducción, en lugar de codificar el texto directamente en JSX.
Si alguna vez has publicado una build donde un t('main.header') sin procesar apareció en la pantalla de un cliente, o has visto cómo una cadena en alemán desbordaba un botón que se veía bien en inglés, ya sabes que la configuración inicial no es la parte difícil. El cableado lleva una tarde; los casos extremos específicos de cada idioma ocupan el resto del sprint. La forma estándar en producción de abordar esto es react-i18next, el binding de React para el framework i18next. Estandariza con react-i18next: está basado en hooks, admite namespaces y carga diferida, funciona con renderizado del lado del servidor y se apoya en el ecosistema de plugins de i18next más amplio del mercado. Recurre a react-intl solo si estás comprometido con la sintaxis de mensajes ICU. Esta guía cubre la configuración actual correcta y, a continuación, los cinco problemas que aparecen en producción: interpolación, pluralización, formateo de números y fechas según el idioma regional, diseño de derecha a izquierda (RTL) y SSR.
Puntos clave
- Establece
interpolation.escapeValue: falseen tu configuración de i18next porque React ya escapa los valores antes de renderizarlos; dejar activado el escapado de i18next provoca un doble escapado de tus cadenas. - En la versión actual de i18next, las claves de plural utilizan los sufijos CLDR/Intl (
_zero,_one,_two,_few,_many,_other), y el sufijo heredado_pluralpertenece al antiguo formato JSON v3; la variable de selección debe llamarsecount. - El formateo de números y fechas depende de la región, no solo del idioma, por lo que debes calificar los locales (
en-US,ar-EG) y formatear con los formateadores Intl de i18next mediante{{value, number}}y{{date, datetime}}. - Carga las traducciones desde archivos JSON con
i18next-http-backendy unloadPath; incluirlas en línea conrequire()añade todos los idiomas al bundle principal y elimina la posibilidad de carga diferida. - En Next.js, no implementes SSR i18n de forma manual:
next-i18nextv16 conecta tanto el App Router como el Pages Router en un único paquete.
¿Cómo se configura react-i18next?
Instala el framework principal, el binding de React y dos plugins que gestionan la detección y la carga de archivos. Cuatro paquetes sostienen la configuración, cada uno con una función específica:
| Paquete | Versión | Función |
|---|---|---|
i18next | 26.x | Motor principal: búsqueda, interpolación, plurales, formateo |
react-i18next | 17.x | Binding de React: useTranslation, Trans |
i18next-browser-languagedetector | 8.x | Detecta el idioma del usuario |
i18next-http-backend | 4.x | Carga el JSON de traducción mediante HTTP |
Ten en cuenta una advertencia: i18next-http-backend v4 requiere fetch nativo. Node ≥ 18, todos los navegadores modernos, Deno y Bun incluyen fetch por defecto. En entornos de ejecución más antiguos, proporciona un ponyfill o mantente en la v3.
npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend
Crea src/i18n.ts e inicializa una sola vez:
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
supportedLngs: ['en', 'es', 'ar'],
load: 'languageOnly',
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
interpolation: { escapeValue: false },
});
export default i18n;
Establece interpolation.escapeValue: false porque React ya escapa los valores antes de renderizarlos; dejar activado el escapado de i18next provoca un doble escapado de tus cadenas. El loadPath es importante: incluir recursos en línea con require() (un patrón de la era de Create React App / Webpack) añade todos los idiomas al bundle principal y anula la carga diferida. Importa la configuración una sola vez en tu punto de entrada, antes de renderizar: import './i18n'; en main.tsx.
Discover how at OpenReplay.com.
Externalizar cadenas con el hook useTranslation
Las traducciones residen en archivos JSON por idioma en public/locales/<lng>/translation.json, y los componentes las leen a través de la función t del hook useTranslation. Reemplaza cada cadena codificada directamente por una búsqueda de clave.
{ "main": { "header": "Welcome to the app!" } }
import { useTranslation } from 'react-i18next';
export default function Header() {
const { t } = useTranslation();
return <h1>{t('main.header')}</h1>;
}
Las claves anidadas (main.header) y los namespaces organizan conjuntos de cadenas de gran tamaño. Para contenido que incluye marcado en línea o enlaces, la llamada simple a t() rompe el JSX. Usa en su lugar el componente Trans, que interpola elementos React dentro de una oración traducida manteniendo el marcado en tu componente, no en tu JSON.
<Trans i18nKey="main.docs" components={{ docsLink: <a href="https://react.i18next.com/" /> }} />
¿Cómo se cambia y detecta el idioma?
Cambia el idioma activo con i18n.changeLanguage(lng); todos los componentes que usen useTranslation se rerenderizarán automáticamente. Un selector de idioma no es más que botones o un <select> que llama a ese método:
const { i18n } = useTranslation();
<select
value={i18n.resolvedLanguage}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="ar">العربية</option>
</select>
La detección la gestiona el plugin de detección de idioma, que comprueba las fuentes en un orden fijo: cadena de consulta (?lng=en), una cookie, localStorage, el navigator del navegador y, finalmente, el atributo <html lang>. Se detiene en la primera coincidencia compatible. Almacena en caché el idioma resuelto en localStorage, de modo que los usuarios que regresan conservan su elección, y una llamada manual a changeLanguage también actualiza esa caché.
Los cinco problemas que aparecen en producción
La mayoría de los errores de i18n se producen fuera del camino habitual. Estos son los modos de fallo que superan el QA local y solo afloran en el entorno regional de un usuario.
Interpolación. Inyecta valores dinámicos con la sintaxis {{var}} y pásalos como segundo argumento: t('greeting', { name }) frente a "Hello, {{name}}". El escapado de React combinado con escapeValue: false mantiene esto a salvo de XSS.
Pluralización. El inglés necesita dos formas de plural y el árabe necesita seis, que es exactamente la razón por la que nunca debes escribir manualmente if (count === 1). Pasa count a t() y deja que Intl.PluralRules seleccione la clave. Define las formas con sufijos CLDR: _zero, _one, _two, _few, _many, _other. La variable debe llamarse count.
{
"messages_one": "You have one message",
"messages_other": "You have {{count}} new messages"
}
El sufijo antiguo _plural es el JSON v3 heredado. i18next simplificó sus sufijos de plural para que coincidan con los utilizados por la API Intl al introducir el formato JSON v4. Desde la v24, la API Intl es obligatoria: si tu entorno de ejecución carece de Intl.PluralRules, debes incluir un polyfill, ya que el antiguo comportamiento de reserva para el manejo de plurales v3 ha desaparecido y compatibilityJSON ya no acepta 'v3'.
Formateo de números y fechas. Formatea con los formateadores Intl integrados de i18next: {{value, number}} y {{date, datetime}}, con opciones como {{value, number(style: percent)}}. Dado que el formateo depende de la región, califica tus locales (en-US, ar-EG) para que los numerales y el orden de las fechas sean consistentes entre navegadores.
Derecha a izquierda (RTL). Para los idiomas RTL, establece la dirección del documento con i18n.dir() en cada cambio de idioma para que todo el diseño se reajuste sin necesidad de CSS por componente:
useEffect(() => {
const apply = (lng: string) => {
document.documentElement.lang = lng;
document.documentElement.dir = i18n.dir(lng);
};
i18n.on('languageChanged', apply);
return () => i18n.off('languageChanged', apply);
}, [i18n]);
Lee i18n.dir() dentro del manejador de languageChanged, no de forma síncrona durante el cambio: después de changeLanguage(), i18next.language refleja el nuevo idioma solo una vez que los recursos se han cargado.
SSR. No implementes i18n del lado del servidor de forma manual en Next.js. next-i18next v16 es una capa ligera sobre i18next y react-i18next que se encarga del cableado específico de Next.js: middleware, la separación servidor/cliente e hidratación de recursos. Admite el App Router (Server Components, Client Components, middleware) y el Pages Router, con getT() para Server Components y useT() para Client Components. Envuelve los árboles de cliente en <Suspense> en lugar de asumir que window existe. Estos defectos específicos de cada idioma (una clave sin procesar como main.header renderizada al usuario, el recorte de texto por el padding en RTL, o texto en el idioma de reserva que se filtra en una pantalla traducida) son precisamente los que superan el QA con el idioma predeterminado y solo aparecen cuando observas una sesión real en el idioma de destino, que es exactamente donde la reproducción de sesiones demuestra su valor.
Escalar con namespaces y extracción de claves
A medida que crece el número de cadenas, divide las traducciones en namespaces y cárgalos por ruta con useTranslation('dashboard'), de modo que cada página obtenga solo su propio JSON y los bundles se mantengan pequeños. Una vez que las cadenas se dispersan por toda la base de código, recurre a herramientas automatizadas: i18next-cli es la herramienta oficial de línea de comandos todo en uno que gestiona la extracción de claves, el linting del código, la sincronización de locales y la generación de tipos. Un sistema de gestión de traducciones como Lokalise, Phrase o Crowdin coordina a los traductores una vez que comienza la localización real.
Ahora dispones de una configuración correcta de react-i18next y un mapa de los aspectos avanzados a considerar. Configura el setup, externaliza tus cadenas y, a continuación, recurre a los namespaces cuando los bundles crezcan y a next-i18next cuando renderices en el servidor. Verifica las versiones exactas de los paquetes en npm en el momento de la instalación, ya que el núcleo de i18next y sus bindings se publican con frecuencia.
Preguntas frecuentes
¿Cuál es la diferencia entre i18next y react-i18next?
i18next es el framework principal que gestiona la lógica de traducción real: búsqueda de claves, interpolación, pluralización y formateo. react-i18next es el binding de React construido sobre él, que proporciona hooks como useTranslation, el componente Trans y el rerenderizado automático cuando cambia el idioma. Instalas ambos: i18next hace el trabajo, react-i18next lo conecta a tus componentes. react-i18next requiere un peer moderno de i18next, así que mantenlos en versiones principales compatibles.
¿Por qué mi clave de traducción aparece como texto literal en lugar de la cadena traducida?
Una clave sin procesar como main.header renderizada al usuario significa que la búsqueda no pudo resolverse, casi siempre porque el archivo JSON para ese idioma o namespace nunca se cargó. Causas habituales: un loadPath que no coincide con la ubicación de tu archivo, un namespace no registrado, la configuración de i18n no importada antes del renderizado, o una clave que no existe en el archivo. Comprueba la pestaña de red para detectar una solicitud fallida a tu ruta de locales y confirma que la clave existe en el archivo del idioma correcto.
¿Sigo usando el sufijo _plural para las claves de plural en i18next?
No. El sufijo _plural pertenece al formato JSON v3 heredado. La versión actual de i18next utiliza sufijos de palabras CLDR/Intl que coinciden con Intl.PluralRules: _zero, _one, _two, _few, _many y _other. El inglés usa dos formas (_one y _other), mientras que el árabe usa las seis. La variable que selecciona la forma debe llamarse count y debe estar presente, ya que no hay comportamiento de reserva si count falta. Si Intl.PluralRules no está disponible, debes incluir un polyfill: desde la v24 no existe comportamiento de reserva para el manejo de plurales v3 antiguo, y compatibilityJSON ya no acepta v3.
¿Necesito almacenar las traducciones en archivos JSON o puedo incluirlas en línea en la configuración?
Puedes incluir las traducciones en línea mediante la opción resources, pero para cualquier aplicación que vaya más allá de lo trivial deberías cargarlas desde archivos JSON con i18next-http-backend y un loadPath como /locales/{{lng}}/{{ns}}.json. Incluir todos los idiomas en línea con require() añade todas las traducciones al bundle principal y anula la carga diferida, de modo que los usuarios descargan cadenas de idiomas que nunca utilizan. La carga basada en archivos obtiene solo el idioma y el namespace activos bajo demanda. Ten en cuenta que i18next-http-backend v4 requiere fetch nativo, es decir, Node 18 o superior.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k