Cómo cargar traducciones desde archivos en React Router v7
Separar el contenido traducible del código
Problema
Codificar directamente las cadenas de texto destinadas al usuario en los componentes crea un acoplamiento estrecho entre el contenido y el código. Cada nuevo idioma requiere que los desarrolladores modifiquen los archivos de implementación, extendiendo la lógica condicional y aumentando la complejidad. Cuando el texto cambia, incluso ajustes menores en la redacción exigen despliegues de código. Este enfoque hace que los flujos de trabajo de traducción dependan de los ciclos de ingeniería e impide que los miembros no técnicos del equipo gestionen el contenido de forma independiente.
A medida que las aplicaciones crecen, las cadenas de texto dispersas se vuelven difíciles de rastrear y mantener. Encontrar cada ocurrencia de una frase en todo el código es propenso a errores, y garantizar la consistencia entre mensajes similares se vuelve casi imposible sin una fuente centralizada de información.
Solución
Extraer todas las cadenas traducibles a archivos JSON externos organizados por idioma, con un archivo por localización. Reemplazar las cadenas codificadas en los componentes con identificadores de mensaje que hagan referencia a entradas en estos archivos. En tiempo de ejecución, la aplicación carga el archivo de traducción apropiado basado en la configuración regional del usuario y proporciona esos mensajes a la biblioteca de internacionalización, que resuelve los identificadores a sus valores traducidos.
Esta separación permite a los traductores trabajar directamente con archivos JSON sin tocar el código, permite actualizaciones de contenido a través de simples cambios de archivo, y proporciona una única fuente de verdad para las cadenas de cada idioma.
Pasos
1. Crear archivos de traducción para cada localización
Organizar los archivos de traducción en un directorio dedicado, con un archivo JSON por idioma. Estructurar cada archivo como un objeto plano que mapee identificadores de mensaje a cadenas traducidas.
{
"welcome.title": "Welcome back",
"welcome.subtitle": "Continue where you left off",
"nav.home": "Home",
"nav.about": "About",
"nav.contact": "Contact"
}
Guardar esto como app/translations/en.json para inglés, luego crear archivos paralelos como app/translations/es.json y app/translations/fr.json con traducciones para otros idiomas. Utilizar claves consistentes en todos los archivos para que el mismo identificador resuelva a la traducción apropiada en cada localización.
2. Cargar traducciones en un cargador de ruta
Utiliza un cargador de ruta para obtener el archivo de traducción para el idioma actual antes de renderizar. Esto asegura que los mensajes estén disponibles cuando los componentes se montan.
import type { Route } from "./+types/root";
import enMessages from "./translations/en.json";
import esMessages from "./translations/es.json";
import frMessages from "./translations/fr.json";
const messages: Record<string, Record<string, string>> = {
en: enMessages,
es: esMessages,
fr: frMessages,
};
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
return {
locale,
messages: messages[locale] || messages.en,
};
}
El cargador lee el idioma desde el parámetro de consulta URL y devuelve tanto el idioma como sus mensajes correspondientes. Los componentes pueden acceder a estos datos a través de loaderData para configurar el proveedor de internacionalización.
3. Configurar el IntlProvider con los mensajes cargados
Envuelve tu aplicación con IntlProvider de react-intl, pasando el idioma y los mensajes desde los datos del cargador.
import { IntlProvider } from "react-intl";
import { Outlet } from "react-router";
import type { Route } from "./+types/root";
export default function Root({ loaderData }: Route.ComponentProps) {
return (
<IntlProvider locale={loaderData.locale} messages={loaderData.messages}>
<html lang={loaderData.locale}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<Outlet />
</body>
</html>
</IntlProvider>
);
}
El IntlProvider hace que el idioma y los mensajes estén disponibles para todos los componentes descendientes a través del contexto de React. Las rutas secundarias se renderizan a través del Outlet y heredan el acceso a los datos de traducción.
4. Referenciar mensajes por identificador en los componentes
Reemplaza las cadenas de texto codificadas con componentes FormattedMessage que hacen referencia a identificadores de mensajes de tus archivos de traducción.
import { FormattedMessage } from "react-intl";
export default function Welcome() {
return (
<div>
<h1>
<FormattedMessage id="welcome.title" />
</h1>
<p>
<FormattedMessage id="welcome.subtitle" />
</p>
<nav>
<a href="/">
<FormattedMessage id="nav.home" />
</a>
<a href="/about">
<FormattedMessage id="nav.about" />
</a>
<a href="/contact">
<FormattedMessage id="nav.contact" />
</a>
</nav>
</div>
);
}
Cada componente FormattedMessage busca su id en el objeto de mensajes proporcionado por IntlProvider y renderiza la cadena traducida correspondiente. Cuando el idioma cambia y el cargador se ejecuta nuevamente con diferentes mensajes, todos los componentes muestran automáticamente las nuevas traducciones sin cambios en el código.