Índice
React i18n: La Guía Completa de Internacionalización en Aplicaciones React
La internacionalización (i18n) es una de esas funcionalidades que parece sencilla desde fuera y se convierte en un laberinto de semanas una vez que empiezas a implementarla. Una aplicación React que saluda a los usuarios en inglés necesita más que simple reemplazo de cadenas para servir verdaderamente a una audiencia global. Necesita formato de moneda, reglas de pluralización, localización de fechas, soporte de diseño de derecha a izquierda, y un flujo de trabajo de traducción que no se rompa cada vez que un desarrollador renombra una clave.
Esta guía explica todo lo que necesitas saber para lanzar una aplicación React multilingüe lista para producción en 2026 — desde elegir la biblioteca correcta hasta evitar los errores que te costarán tiempo más adelante.
TL;DR / Conclusiones clave
- React no tiene i18n integrado. La API
Intldel navegador maneja el formato, pero necesitas una biblioteca dedicada para gestionar cadenas de traducción, carga dinámica y enrutamiento de idioma. - react-intl (FormatJS) y react-i18next son las opciones más adoptadas con grandes comunidades y años de uso en producción.
- LinguiJS es la mejor opción si quieres extracción en tiempo de compilación y el mínimo tamaño de bundle en tiempo de ejecución.
- Better i18n es el participante más nuevo — añade traducciones con IA y entrega por CDN, pero su comunidad es más pequeña que las alternativas establecidas.
- La biblioteca correcta depende del tamaño de tu equipo, los requisitos de SSR y cuánto valoras la automatización del flujo de trabajo de traducción frente a la madurez del ecosistema.
Por qué React necesita una biblioteca i18n
React renderiza UI. No gestiona traducciones, detecta idiomas del navegador, carga archivos de idioma de forma asíncrona, ni maneja las complejas reglas de formato que difieren entre idiomas. Eso es por diseño — React es una biblioteca de vistas.
El navegador incluye la API Intl, que es genuinamente poderosa. Intl.DateTimeFormat, Intl.NumberFormat e Intl.PluralRules proporcionan formato consciente del idioma sin dependencias externas. Pero Intl resuelve el formato, no la gestión de cadenas.
Lo que aún necesitas más allá de Intl:
- Un catálogo de cadenas — Una estructura para almacenar cadenas traducidas por idioma e ID de mensaje.
- Un binding de React — Hooks y componentes que leen del catálogo y vuelven a renderizar cuando cambia el idioma.
- Carga dinámica — Cargar solo las cadenas del idioma activo, no todas a la vez.
- Pluralización e interpolación — Manejar
"1 elemento"frente a"3 elementos"e insertar valores dinámicos como nombres de usuario en los mensajes. - Un flujo de trabajo de traducción — Cómo los traductores reciben cadenas nuevas, las traducen y cómo esas traducciones vuelven a tu aplicación.
Hacer todo esto tú mismo es posible pero desaconsejable. Las bibliotecas a continuación han resuelto estos problemas en millones de despliegues en producción. Empieza con una de ellas.
Comparativa de bibliotecas populares de React i18n
| Biblioteca | Tamaño del bundle | Soporte ICU | Soporte TypeScript | Soporte SSR | Curva de aprendizaje |
|---|---|---|---|---|---|
| react-intl (FormatJS) | ~20KB gzipped | Completo | Fuerte | Sí | Media |
| react-i18next | ~6KB gzipped | Mediante plugin | Fuerte | Sí | Baja-Media |
| LinguiJS | ~2KB runtime | Completo | Fuerte | Sí | Media-Alta |
| Better i18n React SDK | ~2KB | Parcial | Completo (inferido) | Sí | Baja |
Los tamaños del bundle son aproximados y dependen del tree-shaking y las funcionalidades que uses. El formato de mensaje ICU (International Components for Unicode) es el estándar para pluralización compleja, selección de género y texto condicional — no todas las bibliotecas lo soportan de forma nativa.
react-intl (FormatJS)
react-intl es parte de la suite FormatJS mantenida por Formatly (anteriormente Yahoo). Ha sido el estándar de facto para i18n complejo en React durante años y ofrece la implementación más completa del formato de mensaje ICU en el ecosistema.
Instalación
npm install react-intl
Envolviendo tu aplicación con IntlProvider
IntlProvider establece el contexto de idioma para todo tu árbol de componentes. Cada llamada a FormattedMessage y useIntl por debajo de él lee de este contexto.
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { IntlProvider } from "react-intl";
import App from "./App";
import enMessages from "./locales/en.json";
import frMessages from "./locales/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
fr: frMessages,
};
const userLocale = navigator.language.split("-")[0] ?? "en";
const activeLocale = userLocale in messages ? userLocale : "en";
ReactDOM.createRoot(document.getElementById("root")!).render(
<IntlProvider locale={activeLocale} messages={messages[activeLocale]}>
<App />
</IntlProvider>
);
Tu catálogo de mensajes es un archivo JSON plano:
// src/locales/en.json
{
"greeting": "Hello, {name}!",
"itemCount": "{count, plural, one {# item} other {# items}}",
"welcomeBack": "Welcome back, <bold>{name}</bold>!"
}
Componente FormattedMessage
Usa FormattedMessage cuando necesites formato de texto enriquecido o JSX en línea dentro de tus traducciones:
// src/components/Welcome.tsx
import React from "react";
import { FormattedMessage } from "react-intl";
interface WelcomeProps {
name: string;
itemCount: number;
}
export function Welcome({ name, itemCount }: WelcomeProps) {
return (
<div>
<h1>
<FormattedMessage
id="greeting"
values={{ name }}
/>
</h1>
<p>
<FormattedMessage
id="itemCount"
values={{ count: itemCount }}
/>
</p>
<p>
<FormattedMessage
id="welcomeBack"
values={{
name,
bold: (chunks) => <strong>{chunks}</strong>,
}}
/>
</p>
</div>
);
}
Hook useIntl
Para uso imperativo — formatear cadenas fuera de JSX, dentro de manejadores de eventos, o para atributos aria-label — usa el hook useIntl:
// src/components/SearchBar.tsx
import React from "react";
import { useIntl } from "react-intl";
export function SearchBar() {
const intl = useIntl();
const placeholder = intl.formatMessage({
id: "search.placeholder",
defaultMessage: "Search products...",
});
const formattedPrice = intl.formatNumber(29.99, {
style: "currency",
currency: "USD",
});
const formattedDate = intl.formatDate(new Date(), {
year: "numeric",
month: "long",
day: "numeric",
});
return (
<div>
<input type="search" placeholder={placeholder} aria-label={placeholder} />
<span>{formattedPrice}</span>
<time>{formattedDate}</time>
</div>
);
}
La documentación de react-intl está en formatjs.io/docs/react-intl.
react-i18next
react-i18next es el binding de React para i18next, el framework JavaScript de i18n más ampliamente utilizado. Su fortaleza es la flexibilidad: funciona en React, React Native, Node.js y entornos que no son React. Si tu equipo ya usa i18next en otro lugar, el modelo mental compartido es una ventaja real.
Instalación
npm install i18next react-i18next
Configuración
// src/i18n/config.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import enCommon from "./locales/en/common.json";
import enDashboard from "./locales/en/dashboard.json";
import frCommon from "./locales/fr/common.json";
import frDashboard from "./locales/fr/dashboard.json";
i18n
.use(initReactI18next)
.init({
resources: {
en: {
common: enCommon,
dashboard: enDashboard,
},
fr: {
common: frCommon,
dashboard: frDashboard,
},
},
lng: "en",
fallbackLng: "en",
defaultNS: "common",
interpolation: {
escapeValue: false, // React ya escapa los valores
},
});
export default i18n;
Importa la configuración en el punto de entrada de tu aplicación antes de que cualquier componente se renderice:
// src/main.tsx
import "./i18n/config";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
Hook useTranslation y división por espacios de nombres
La división por espacios de nombres es una de las funcionalidades más útiles de react-i18next para aplicaciones grandes. En lugar de cargar un enorme archivo JSON, divides las traducciones por funcionalidad o dominio y cargas los espacios de nombres bajo demanda.
// src/i18n/locales/en/common.json
{
"nav.home": "Home",
"nav.dashboard": "Dashboard",
"nav.settings": "Settings",
"button.save": "Save",
"button.cancel": "Cancel"
}
// src/i18n/locales/en/dashboard.json
{
"title": "Your Dashboard",
"stats.users": "{{count}} active user",
"stats.users_other": "{{count}} active users",
"welcome": "Welcome back, {{name}}!"
}
// src/components/Dashboard.tsx
import React from "react";
import { useTranslation } from "react-i18next";
interface DashboardProps {
userName: string;
activeUsers: number;
}
export function Dashboard({ userName, activeUsers }: DashboardProps) {
// Carga el espacio de nombres "dashboard" específicamente
const { t } = useTranslation("dashboard");
return (
<main>
<h1>{t("title")}</h1>
<p>{t("welcome", { name: userName })}</p>
<p>{t("stats.users", { count: activeUsers })}</p>
</main>
);
}
// src/components/Nav.tsx
import React from "react";
import { useTranslation } from "react-i18next";
export function Nav() {
// Usa el espacio de nombres predeterminado "common"
const { t, i18n } = useTranslation();
const switchLanguage = (locale: string) => {
i18n.changeLanguage(locale);
};
return (
<nav>
<a href="/">{t("nav.home")}</a>
<a href="/dashboard">{t("nav.dashboard")}</a>
<button onClick={() => switchLanguage("fr")}>Francais</button>
<button onClick={() => switchLanguage("en")}>English</button>
</nav>
);
}
La documentación de react-i18next está en react.i18next.com.
LinguiJS
LinguiJS adopta un enfoque fundamentalmente diferente. En lugar de gestionar claves de traducción en archivos JSON que referencias por ID, escribes tu UI en lenguaje natural directamente en tu JSX. Un CLI luego extrae esas cadenas en un catálogo, que los traductores rellenan. En tiempo de compilación, los mensajes se compilan en un formato binario optimizado.
El resultado es un bundle en tiempo de ejecución más pequeño y un menor riesgo de referencias de claves rotas — pero requiere un paso de compilación que las otras bibliotecas no necesitan.
Instalación
npm install @lingui/react @lingui/core npm install --save-dev @lingui/cli @lingui/macro @lingui/vite-plugin
Configuración
// lingui.config.js
/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
locales: ["en", "fr", "de"],
sourceLocale: "en",
catalogs: [
{
path: "src/locales/{locale}/messages",
include: ["src"],
},
],
format: "po",
};
Usando @lingui/macro
El macro transforma cadenas en lenguaje natural en tiempo de compilación, por lo que tu código fuente se lee como inglés simple mientras sigue siendo completamente localizable:
// src/components/ProductCard.tsx
import React from "react";
import { Trans, Plural } from "@lingui/macro";
import { useLingui } from "@lingui/react";
interface ProductCardProps {
productName: string;
price: number;
reviewCount: number;
}
export function ProductCard({ productName, price, reviewCount }: ProductCardProps) {
const { i18n } = useLingui();
const formattedPrice = i18n.number(price, {
style: "currency",
currency: "USD",
});
return (
<article>
<h2>
<Trans>Buy {productName} today</Trans>
</h2>
<p>{formattedPrice}</p>
<p>
<Plural
value={reviewCount}
one="# customer review"
other="# customer reviews"
/>
</p>
<p>
<Trans>
Free shipping on orders over <strong>$50</strong>.
</Trans>
</p>
</article>
);
}
Flujo de trabajo de extracción
Después de escribir tus componentes, ejecuta el CLI para extraer cadenas:
# Extrae todas las cadenas de los archivos fuente al catálogo npx lingui extract # Traduce los archivos .po (o envíalos a tu servicio de traducción) # Luego compílalos para producción npx lingui compile
Los catálogos compilados son pequeños y cargan rápido. LinguiJS es particularmente atractivo para equipos que quieren un flujo de trabajo con archivos PO amigable para traductores y la carga útil en tiempo de ejecución más pequeña posible.
La documentación completa está en lingui.dev.
Better i18n React SDK
Better i18n es un participante más nuevo en el espacio de React i18n. En lugar de competir puramente como una biblioteca de renderizado de traducciones, incluye una plataforma de flujo de trabajo completa: traducciones con IA con conciencia del contexto de marca, descubrimiento automático de claves mediante escaneo AST, y entrega por CDN para que las actualizaciones de traducción estén disponibles sin necesidad de una reconstrucción.
Sé honesto sobre las compensaciones antes de elegirlo: La comunidad es una fracción del tamaño de react-intl o i18next. Las respuestas en Stack Overflow son escasas. Si te encuentras con un caso extremo, es más probable que dependas de la documentación oficial o el canal de soporte que de soluciones de la comunidad. La biblioteca está bien mantenida y en desarrollo activo, pero aún no tiene los años de pruebas en batalla que tienen las opciones establecidas.
Instalación
npm install @better-i18n/react
Configuración
// src/providers/I18nProvider.tsx
import React, { type ReactNode } from "react";
import { BetterI18nProvider } from "@better-i18n/react";
interface I18nProviderProps {
children: ReactNode;
locale: string;
}
export function I18nProvider({ children, locale }: I18nProviderProps) {
return (
<BetterI18nProvider
projectId={process.env.VITE_BETTER_I18N_PROJECT_ID!}
locale={locale}
>
{children}
</BetterI18nProvider>
);
}
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { I18nProvider } from "./providers/I18nProvider";
const locale = navigator.language.split("-")[0] ?? "en";
ReactDOM.createRoot(document.getElementById("root")!).render(
<I18nProvider locale={locale}>
<App />
</I18nProvider>
);
Hook useTranslations
// src/components/HeroSection.tsx
import React from "react";
import { useTranslations } from "@better-i18n/react";
interface HeroSectionProps {
userName: string;
planName: string;
}
export function HeroSection({ userName, planName }: HeroSectionProps) {
const t = useTranslations("hero");
return (
<section>
<h1>{t("title", { name: userName })}</h1>
<p>{t("subtitle", { plan: planName })}</p>
<a href="/signup">{t("cta")}</a>
</section>
);
}
Las claves de traducción se definen en el panel de Better i18n o se descubren automáticamente desde tu código fuente por el escáner CLI. Cuando un traductor aprueba un cambio, se entrega a tu aplicación a través de la red de borde CDN de Cloudflare — sin necesidad de reconstruir ni volver a desplegar.
La documentación de Better i18n está en better-i18n.com/docs.
Cuándo elegir Better i18n sobre las alternativas: Tu equipo quiere traducciones asistidas por IA de serie, te sientes cómodo con una plataforma gestionada en lugar de una biblioteca completamente autocontenida, y la automatización del flujo de trabajo (descubrimiento automático de claves, entrega por CDN, sincronización basada en PR de GitHub) vale más para ti que la amplitud del ecosistema.
Cuándo elegir otra cosa: Necesitas la comunidad más grande posible, años de respuestas en Stack Overflow, o una solución completamente autoalojada sin dependencia de servicios externos. En ese caso, react-i18next o react-intl son la opción más segura.
Patrones clave de implementación
Cambio de idioma
El cambio de idioma debe ser con estado y reflejado en la URL para que los usuarios puedan compartir enlaces localizados. El enfoque más simple almacena el idioma en el estado y lo pasa a tu proveedor, pero un enfoque basado en URL es más robusto para SSR y enlaces profundos.
// src/hooks/useLocale.ts
import { useState, useCallback } from "react";
const SUPPORTED_LOCALES = ["en", "fr", "de", "es"] as const;
type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
function getInitialLocale(): SupportedLocale {
const fromUrl = window.location.pathname.split("/")[1] as SupportedLocale;
if (SUPPORTED_LOCALES.includes(fromUrl)) return fromUrl;
const fromBrowser = navigator.language.split("-")[0] as SupportedLocale;
if (SUPPORTED_LOCALES.includes(fromBrowser)) return fromBrowser;
return "en";
}
export function useLocale() {
const [locale, setLocale] = useState<SupportedLocale>(getInitialLocale);
const switchLocale = useCallback((next: SupportedLocale) => {
setLocale(next);
document.documentElement.lang = next;
// Opcional: empujar al historial del router para enrutamiento basado en URL
}, []);
return { locale, switchLocale, supportedLocales: SUPPORTED_LOCALES };
}
Carga diferida de traducciones
Cargar todos los bundles de idioma de antemano desperdicia ancho de banda. Carga solo el idioma activo y obtén otros bajo demanda:
// src/i18n/loader.ts
const localeCache = new Map<string, Record<string, string>>();
export async function loadLocale(locale: string): Promise<Record<string, string>> {
if (localeCache.has(locale)) {
return localeCache.get(locale)!;
}
// Importación dinámica — el bundler divide esto en un chunk separado por idioma
const messages = await import(`./locales/${locale}.json`);
const resolved = messages.default as Record<string, string>;
// Devuelve una nueva entrada del mapa en lugar de mutar en su lugar
const updated = new Map(localeCache);
updated.set(locale, resolved);
localeCache.clear();
updated.forEach((v, k) => localeCache.set(k, v));
return resolved;
}
Consideraciones de SSR / SSG
Al renderizar en el servidor, la detección de idioma pasa de navigator.language a cabeceras HTTP (Accept-Language) o segmentos de URL. El idioma debe resolverse antes de que comience el renderizado para que el servidor y el cliente produzcan HTML idéntico.
// src/app/[locale]/layout.tsx (ejemplo de Next.js App Router)
import { notFound } from "next/navigation";
import { type ReactNode } from "react";
const SUPPORTED_LOCALES = ["en", "fr", "de"];
interface LocaleLayoutProps {
children: ReactNode;
params: { locale: string };
}
export default function LocaleLayout({ children, params }: LocaleLayoutProps) {
if (!SUPPORTED_LOCALES.includes(params.locale)) {
notFound();
}
return (
<html lang={params.locale} dir={params.locale === "ar" ? "rtl" : "ltr"}>
<body>{children}</body>
</html>
);
}
export function generateStaticParams() {
return SUPPORTED_LOCALES.map((locale) => ({ locale }));
}
Enrutamiento de idioma basado en URL
Los motores de búsqueda tratan example.com/es/productos y example.com/en/products como páginas separadas e indexables. El enrutamiento basado en URL es fuertemente preferido sobre la detección basada en cookies o cabeceras para SEO. Estructura tus rutas con un segmento [locale] en la raíz y añade etiquetas de enlace hreflang en el <head> para cada idioma soportado.
Pluralización
Cada biblioteca cubierta aquí maneja la pluralización de forma diferente, pero las reglas subyacentes provienen del CLDR (Common Locale Data Repository). El inglés tiene dos formas plurales. El árabe tiene seis. Nunca codifiques la lógica de pluralización tú mismo.
Con react-i18next (usando sufijos _one / _other):
{
"notification": "{{count}} notificación",
"notification_other": "{{count}} notificaciones"
}
t("notification", { count: 1 }); // "1 notificación"
t("notification", { count: 5 }); // "5 notificaciones"
Formato de fechas y números
Usa las utilidades de formato integradas de la biblioteca o recurre directamente a Intl. Nunca concatenes valores formateados como cadenas — el orden de las unidades difiere según el idioma.
// Usando Intl directamente (funciona con cualquier biblioteca)
const formatter = new Intl.NumberFormat(locale, {
style: "currency",
currency: "EUR",
});
// "€1,234.56" en en-US, "1 234,56 €" en fr-FR — mismo número, formato diferente
formatter.format(1234.56);
Errores comunes
Concatenación de cadenas. Nunca construyas oraciones uniendo fragmentos traducidos con + o literales de plantilla. El orden de las palabras varía según el idioma. "Hola " + t("mundo") se rompe en idiomas donde el saludo viene después del sustantivo. Siempre pasa variables de interpolación a la función de traducción.
Olvidar el contexto. La palabra inglesa "File" puede significar el sustantivo (un documento) o el verbo (presentar una queja). Los traductores necesitan contexto para elegir la palabra correcta. La mayoría de las bibliotecas soportan un parámetro context o campo de descripción — úsalo.
No manejar estados de carga. Cuando las traducciones se cargan de forma asíncrona, hay una ventana en la que tu componente se renderiza antes de que las cadenas estén disponibles. Renderiza un esqueleto, un spinner, o nada — pero nunca renderices claves de traducción crudas como "dashboard.title" al usuario.
Agrupando todos los idiomas juntos. Enviar traducciones al francés, alemán, español y japonés a un usuario que solo lee inglés desperdicia ancho de banda y aumenta tu Tiempo de Interactividad. Usa importaciones dinámicas o una estrategia de entrega basada en CDN para cargar solo lo que sea necesario.
Codificación de formato específico del idioma. No codifiques formatos de fecha como MM/DD/YYYY. Los usuarios alemanes esperan DD.MM.YYYY. Siempre delega el formato a Intl o las utilidades de formato de tu biblioteca.
Renderizar HTML traducido como marcado crudo. Inyectar cadenas traducidas como HTML crudo es un riesgo XSS si tu fuente de traducción alguna vez se ve comprometida. Usa los componentes de texto enriquecido seguros de tu biblioteca — FormattedMessage con valores de elementos en react-intl, o el componente Trans en react-i18next — en lugar de renderizar cadenas HTML directamente.
Preguntas frecuentes
P: ¿Necesito soporte del formato de mensaje ICU?
ICU es el estándar para mensajes complejos que involucran plurales, selección de género y texto condicional. Si tu aplicación tiene oraciones como "A Juan y 3 más les gustó esto", ICU hace que expresar esas reglas sea limpio y portable. Si tu aplicación solo necesita reemplazo de cadenas simple, ICU es una sobrecarga opcional. react-intl y LinguiJS soportan ICU de forma nativa. react-i18next requiere el plugin i18next-icu.
P: ¿Qué biblioteca funciona mejor con Next.js App Router?
react-i18next y react-intl ambos soportan App Router con algo de configuración. El ecosistema de Next.js ha convergido en torno a next-intl para proyectos con App Router debido a su soporte de primera clase para Server Components. Better i18n también incluye un SDK dedicado para Next.js. LinguiJS funciona pero requiere un manejo cuidadoso del paso de compilación en el pipeline de construcción.
P: ¿Cómo debo estructurar mis archivos de traducción?
Para aplicaciones pequeñas, un único archivo JSON por idioma (en.json, fr.json) está bien. Para aplicaciones más grandes, divide por funcionalidad o dominio (espacios de nombres en i18next, catálogos separados en LinguiJS). Evita objetos profundamente anidados — las claves planas o de un nivel de profundidad son más fáciles de gestionar a escala y menos propensas a conflictos de fusión.
P: ¿Cómo manejo traducciones que incluyen marcado como texto en negrita o enlaces?
La mayoría de las bibliotecas soportan texto enriquecido mediante un render prop o sistema de ranuras. En react-intl, pasa un elemento JSX como valor a FormattedMessage. En react-i18next, usa el componente Trans. Estos enfoques mantienen el HTML en React donde puede ser correctamente escapado y saneado, en lugar de mezclar marcado en tu catálogo de cadenas.
P: ¿Cuándo debería empezar a pensar en i18n? Antes de lo que crees. Adaptar i18n a una base de código existente significa buscar cada cadena codificada, cada formato de fecha, cada suposición de plural, y cada diseño que se rompe con las palabras alemanas más largas. Si hay alguna posibilidad de que tu aplicación llegue a hablantes no ingleses, añade infraestructura i18n en tu configuración inicial, incluso si solo lanzas en inglés el primer día.
Conclusión
React i18n no tiene una única respuesta correcta. La biblioteca correcta es la que se adapta al flujo de trabajo de tu equipo, la complejidad de tu aplicación y tu tolerancia al riesgo del ecosistema.
Si estás iniciando un nuevo proyecto y quieres el mayor soporte de la comunidad y el ecosistema más profundo, react-i18next es el valor predeterminado seguro. Su sistema de espacios de nombres escala bien, su documentación es exhaustiva, y encontrarás respuestas a la mayoría de los casos extremos en Stack Overflow.
Si tu aplicación tiene requisitos de formato de mensajes genuinamente complejos — género condicional, plurales de múltiples niveles, marcado enriquecido en línea — react-intl es la implementación más completa del estándar ICU en el ecosistema de React.
Si quieres el bundle en tiempo de ejecución más pequeño y un flujo de trabajo de extracción reforzado por el compilador, LinguiJS ofrece una alternativa convincente, particularmente para equipos cómodos añadiendo un paso de compilación.
Si quieres traducciones asistidas por IA, entrega por CDN sin un ciclo de reconstrucción, y una plataforma gestionada que automatiza el pipeline de traducción, Better i18n vale la pena evaluarlo — con la advertencia honesta de que estás apostando por un ecosistema más joven con una comunidad más pequeña.
Cualquiera que elijas, comprométete con él temprano, mantén tus archivos de traducción organizados, y trata i18n como una funcionalidad de primera clase en lugar de una idea de último momento. Tus usuarios internacionales notarán la diferencia.
Referencias
- react-intl (FormatJS): formatjs.io/docs/react-intl
- react-i18next: react.i18next.com
- i18next core: www.i18next.com
- LinguiJS: lingui.dev
- Better i18n React SDK: better-i18n.com/docs
- Unicode CLDR Plural Rules: cldr.unicode.org/index/cldr-spec/plural-rules
- MDN Intl API reference: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl