Carga de traducciones

Uso de un proveedor para gestionar mensajes

Problema

Codificar texto de forma fija, como 'Hello World', directamente en los componentes de una aplicación acopla el contenido con el código. Para mostrar un idioma diferente, los desarrolladores deben duplicar el componente o agregar lógica if/else, lo que hace que la traducción no sea escalable y requiera un cambio completo de código para cada nuevo fragmento de texto.

Solución

Usa IntlProvider de react-intl para proporcionar traducciones. Carga los mensajes de traducción en el servidor en el layout raíz, pásalos a un componente proveedor del lado del cliente y luego consúmelos en otros componentes cliente usando el hook useIntl.

Pasos

1. Instala react-intl

Primero, agrega react-intl como dependencia a tu proyecto.

npm install react-intl

2. Crea archivos de traducción planos

Crea una carpeta dictionaries. Dentro, agrega un archivo JSON para cada idioma. react-intl funciona mejor con una estructura plana de clave-valor.

// dictionaries/en.json
{
  "home.title": "Home Page",
  "home.welcome": "Hello, welcome to our site!",
  "about.title": "About Us"
}
// dictionaries/es.json
{
  "home.title": "Página de Inicio",
  "home.welcome": "¡Hola, bienvenido a nuestro sitio!",
  "about.title": "Sobre Nosotros"
}

3. Crea una función para cargar diccionarios

Crea una función auxiliar para cargar el archivo de diccionario correcto en el servidor según el parámetro lang.

// app/get-dictionary.ts
import 'server-only';

// Define the type for our flat message object
type Messages = Record<string, string>;

const dictionaries: { [key: string]: () => Promise<Messages> } = {
  en: () => import('@/dictionaries/en.json').then((module) => module.default),
  es: () => import('@/dictionaries/es.json').then((module) => module.default),
  // fr: () => import('@/dictionaries/fr.json').then((module) => module.default),
};

export const getDictionary = async (lang: string) => {
  const load = dictionaries[lang];
  if (load) {
    return load();
  }
  // Fallback to English
  return dictionaries.en();
};

4. Crea un proveedor del lado del cliente

IntlProvider es un componente cliente que usa el Context de React. Debemos crear un wrapper para él que pueda aceptar mensajes cargados desde el servidor.

// app/components/IntlClientProvider.tsx
'use client';

import { IntlProvider } from 'react-intl';

type Props = {
  children: React.ReactNode;
  locale: string;
  messages: Record<string, string>; // Flat messages object
};

export default function IntlClientProvider({
  children,
  locale,
  messages,
}: Props) {
  return (
    <IntlProvider messages={messages} locale={locale} defaultLocale="en">
      {children}
    </IntlProvider>
  );
}

5. Actualiza el layout raíz

Modifica tu app/[lang]/layout.tsx para que sea un componente async. Cargará los mensajes y los pasará al IntlClientProvider.

// app/[lang]/layout.tsx
import { getDictionary } from '@/app/get-dictionary';
import IntlClientProvider from '@/app/components/IntlClientProvider';

export async function generateStaticParams() {
  return [{ lang: 'en' }, { lang: 'es' }];
}

export default async function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { lang: string };
}) {
  // Load messages on the server
  const messages = await getDictionary(params.lang);

  return (
    <html lang={params.lang}>
      <body>
        {/* Pass messages to the client provider */}
        <IntlClientProvider locale={params.lang} messages={messages}>
          {children}
        </IntlClientProvider>
      </body>
    </html>
  );
}

6. Usa traducciones en un componente cliente

Ahora puedes usar el hook useIntl en cualquier componente cliente. Los componentes servidor no pueden usar este hook.

Crea un nuevo componente de cliente para mostrar el texto traducido:

// app/components/HomePageContent.tsx
'use client';

import { useIntl } from 'react-intl';

export default function HomePageContent() {
  const intl = useIntl();

  return (
    <div>
      <h1>{intl.formatMessage({ id: 'home.title' })}</h1>
      <p>{intl.formatMessage({ id: 'home.welcome' })}</p>
    </div>
  );
}

7. Añade el componente a tu página

Finalmente, añade tu nuevo componente de cliente a tu página.

// app/[lang]/page.tsx
import HomePageContent from '@/app/components/HomePageContent';

export default function Home() {
  // This page is a Server Component
  return (
    <div>
      {/* It renders the Client Component */}
      <HomePageContent />
    </div>
  );
}