Cómo cargar traducciones desde archivos en React Router v7

Separa el contenido traducible del código

Problema

Codificar cadenas de texto de cara al usuario directamente en los componentes crea un acoplamiento estrecho entre contenido y código. Cada nuevo idioma requiere que los desarrolladores modifiquen archivos de implementación, extendiendo la lógica condicional y aumentando la complejidad. Cuando el texto cambia, incluso ajustes menores de redacción exigen despliegues de código. Este enfoque hace que los flujos de trabajo de traducción dependan de ciclos de ingeniería e impide que miembros no técnicos del equipo gestionen el contenido de forma independiente.

A medida que las aplicaciones crecen, las cadenas literales dispersas se vuelven difíciles de rastrear y mantener. Encontrar cada ocurrencia de una frase en una base de código es propenso a errores, y garantizar la coherencia entre mensajes similares se vuelve casi imposible sin una fuente única de verdad centralizada.

Solución

Extrae todas las cadenas traducibles en archivos JSON externos organizados por idioma, con un archivo por configuración regional. Reemplaza las cadenas codificadas en los componentes con identificadores de mensajes que hacen referencia a entradas en estos archivos. En tiempo de ejecución, la aplicación carga el archivo de traducción apropiado según 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, habilita actualizaciones de contenido mediante simples cambios de archivos y proporciona una única fuente de verdad para las cadenas de cada idioma.

Pasos

1. Crea archivos de traducción para cada configuración regional

Organiza los archivos de traducción en un directorio dedicado, con un archivo JSON por idioma. Estructura cada archivo como un objeto plano que mapea identificadores de mensajes a cadenas traducidas.

{
"welcome.title": "Welcome back",
"welcome.subtitle": "Continue where you left off",
"nav.home": "Home",
"nav.about": "About",
"nav.contact": "Contact"
}

Guarda esto como app/translations/en.json para inglés, luego crea archivos paralelos como app/translations/es.json y app/translations/fr.json con traducciones para otros idiomas. Usa claves consistentes en todos los archivos para que el mismo identificador se resuelva a la traducción apropiada en cada configuración regional.

2. Cargar traducciones en un loader de ruta

Utiliza un loader de ruta para obtener el archivo de traducción del locale actual antes del renderizado. Esto garantiza que los mensajes estén disponibles cuando se monten los componentes.

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 loader lee el locale del parámetro de consulta de la URL y devuelve tanto el locale 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 locale y los mensajes de los datos del loader.

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 locale y los mensajes estén disponibles para todos los componentes descendientes a través del contexto de React. Las rutas hijas 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 fijas con componentes FormattedMessage que referencien 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 locale cambia y el loader se ejecuta nuevamente con mensajes diferentes, todos los componentes muestran automáticamente las nuevas traducciones sin cambios en el código.