tutoriales.com

Internacionalización (i18n) en Next.js App Router: Guía Definitiva con Server Components

Descubre cómo configurar un sitio web multilingüe en Next.js utilizando el App Router y Server Components. Una guía completa desde la estructura de rutas hasta el cambio dinámico de idioma de forma eficiente.

Intermedio12 min de lectura6 views
Reportar error

Introducción a la Internacionalización en Next.js 🌍

La internacionalización (i18n) es un pilar fundamental para cualquier aplicación web moderna que busque expandir su audiencia a nivel global. Tradicionalmente, configurar múltiples idiomas en React y frameworks antiguos era una tarea compleja que requería configuraciones manuales de Webpack o routers de terceros pesados. Sin embargo, con el App Router de Next.js y los Server Components, tenemos un ecosistema nativo y optimizado para servir contenido localizado de manera ultrarrápida.

En este tutorial exhaustivo, aprenderás a construir una aplicación multilingüe desde cero utilizando patrones modernos, evitando el sobrecargue de JavaScript en el cliente y optimizando cada página para los motores de búsqueda (SEO multi-idioma).

📌 Nota: Aunque usaremos next-intl como la librería principal por su excelente compatibilidad con Server Components, los conceptos que aprenderás se aplican a la arquitectura general de localización en Next.js.

🛠️ Planificación de la Arquitectura i18n

Antes de escribir código, es crucial entender cómo estructurar las URLs para soportar múltiples idiomas. La estrategia recomendada por la industria y por Next.js es utilizar segmentos de ruta basados en el código de idioma (por ejemplo, /es/about y /en/about).

/ Redirección /es (Español) /en (Inglés) /contacto /productos /contact /products Arquitectura de Enrutamiento i18n

Ventajas de las URLs basadas en prefijos de idioma:

  • 🚀 SEO Amigable: Los motores de búsqueda indexan cada idioma de forma independiente.
  • 🎯 Claridad para el Usuario: El usuario siempre sabe en qué idioma está navegando.
  • 🌐 Caché Eficiente: Permite cachear páginas renderizadas por servidor basadas en el idioma actual.

⚙️ Paso 1: Configuración Inicial del Proyecto

Comenzaremos creando un nuevo proyecto de Next.js o integrándolo en uno existente. Asegúrate de tener Node.js instalado en tu sistema.

Paso 1: Crear el proyecto base de Next.js con TypeScript y Tailwind CSS.
Paso 2: Instalar la librería next-intl para la gestión de traducciones.
Paso 3: Configurar el archivo de middleware para la detección automática de idioma.

Ejecuta el siguiente comando en tu terminal para crear la aplicación:

npx create-next-app@latest mi-app-i18n --typescript --tailwind --app

Una vez dentro del directorio del proyecto, instala next-intl:

npm install next-intl

📂 Paso 2: Estructura de Archivos y Directorios

Para manejar rutas dinámicas por idioma en el App Router, utilizaremos un parámetro dinámico [locale] que envolverá todas nuestras páginas. La estructura de carpetas se verá así:

mi-app-i18n/
├── messages/
│   ├── en.json
│   └── es.json
├── src/
│   ├── app/
│   │   ├── [locale]/
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   └── layout.tsx
│   ├── i18n.ts
│   └── middleware.ts

Creando los archivos de traducción

Crea una carpeta llamada messages en la raíz de tu proyecto. Aquí guardaremos los diccionarios de texto en formato JSON.

💡 Consejo: Mantén las claves de tus JSON organizadas por componentes o secciones (ej. `HomePage`, `Navbar`) para evitar colisiones a medida que tu aplicación crezca.

Contenido de messages/es.json:

{
  "HomePage": {
    "title": "Bienvenido a Nuestra Plataforma Global",
    "description": "Desarrolla aplicaciones rápidas y accesibles en múltiples idiomas.",
    "button": "Explorar características"
  }
}

Contenido de messages/en.json:

{
  "HomePage": {
    "title": "Welcome to Our Global Platform",
    "description": "Build fast and accessible applications in multiple languages.",
    "button": "Explore features"
  }
}

🔌 Paso 3: Configuración de la Librería i18n

Crea un archivo src/i18n.ts en la raíz de tu carpeta src. Este archivo se encargará de cargar los mensajes correspondientes según el idioma detectado.

import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';

// Lista de idiomas soportados
const locales = ['es', 'en'];

export default getRequestConfig(async ({ locale }) => {
  // Validar que el idioma entrante sea soportado
  if (!locales.includes(locale as any)) notFound();

  return {
    messages: (await import(`../messages/${locale}.json`)).default
  };
});

🚦 Paso 4: Implementación del Middleware

El middleware es indispensable para interceptar las peticiones del usuario, detectar su idioma preferido mediante las cabeceras del navegador y redirigir al prefijo correspondiente (ej. / -> /es).

Crea el archivo src/middleware.ts:

import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // Lista de todos los idiomas soportados
  locales: ['es', 'en'],

  // Idioma por defecto si no se puede detectar
  defaultLocale: 'es'
});

export const config = {
  // Coincidir con todas las rutas excepto archivos estáticos y API
  matcher: ['/', '/(es|en)/:path*']
};

🖥️ Paso 5: Creación del Layout y Páginas con Server Components

Ahora configuraremos el layout principal dentro de la carpeta dinámica [locale]. Esto asegurará que el contexto de traducción esté disponible para todos los componentes hijos.

Crea src/app/[locale]/layout.tsx:

import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';

export default async function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  // Proveer los mensajes al cliente si es necesario
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Construyendo la Página Principal

Crea src/app/[locale]/page.tsx aprovechando los Server Components para traducir el contenido de forma nativa en el servidor:

import { useTranslations } from 'next-intl';

export default function HomePage() {
  const t = useTranslations('HomePage');

  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
        <h1 className="text-4xl font-bold mb-4">{t('title')}</h1>
        <p className="text-lg mb-6">{t('description')}</p>
        <button className="bg-blue-600 text-white px-6 py-3 rounded-lg shadow-md hover:bg-blue-700 transition">
          {t('button')}
        </button>
      </div>
    </main>
  );
}

🔄 Paso 6: Creación de un Selector de Idioma Interactivo

Para que los usuarios puedan cambiar de idioma manualmente, necesitamos un componente cliente (Client Component) que navegue a la ruta correspondiente.

Crea un componente src/components/LocaleSwitcher.tsx:

'use client';

import { useLocale } from 'next-intl';
import { useRouter, usePathname } from 'navigation'; // O usa next/navigation adaptado
import { useTransition } from 'react';

export default function LocaleSwitcher() {
  const locale = useLocale();
  const router = useRouter();
  const pathname = usePathname();
  const [isPending, startTransition] = useTransition();

  const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const nextLocale = e.target.value;
    startTransition(() => {
      router.replace(pathname, { locale: nextLocale });
    });
  };

  return (
    <div className="inline-flex items-center">
      <select
        defaultValue={locale}
        disabled={isPending}
        onChange={onSelectChange}
        className="bg-white border border-gray-300 rounded-md px-3 py-1.5 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
      >
        <option value="es">🇪🇸 Español</option>
        <option value="en">🇬🇧 English</option>
      </select>
    </div>
  );
}

🧪 Buenas Prácticas y Optimización SEO

Implementar internacionalización no es solo traducir textos; requiere cuidar aspectos técnicos para garantizar una experiencia de usuario impecable.

Optimización SEO: Hreflang Referencia cruzada entre versiones de idioma URL: /es/inicio Contenido en Español rel="alternate" hreflang="en" URL: /en/home Content in English rel="alternate" hreflang="es" Apunta a versión EN Apunta a versión ES ✓ Retorno de etiqueta válido Googlebot detecta reciprocidad

Checklist de Verificación SEO:

  • Etiquetas hreflang: Asegúrate de incluir en tus metadatos las URLs alternativas para cada idioma.
  • Atributo lang dinámico: El elemento <html> siempre debe reflejar el idioma actual (ej. <html lang="es">).
  • URLs limpias: Evita parámetros en query strings (?lang=es) y prioriza las rutas basadas en subdirectorios.
⚠️ Advertencia: No olvides manejar las rutas no encontradas (404) de manera localizada para evitar que los usuarios vean páginas rotas al cambiar de idioma incorrectamente.

❓ Preguntas Frecuentes (FAQ)

¿Puedo usar archivos de traducción separados para cada página? Sí, puedes organizar tus archivos JSON por carpetas y módulos dentro de `messages/`, y cargarlos dinámicamente según la página que esté visitando el usuario.
¿Cómo manejo la traducción en componentes de servidor frente a componentes de cliente? En Server Components utilizas getTranslations() o useTranslations() de forma asíncrona o directa, mientras que en Client Components utilizas el hook useTranslations() importado desde next-intl asegurándote de que el proveedor esté activo en el layout.
¿Cómo afecta la internacionalización al rendimiento de Next.js? Con el App Router y Server Components, el impacto en rendimiento es mínimo ya que las traducciones se resuelven directamente en el servidor y solo se envía el HTML resultante al cliente.

Conclusión

¡Felicidades! Has configurado con éxito un sistema de internacionalización moderno, escalable y optimizado para SEO en Next.js utilizando el App Router y Server Components. Esta arquitectura te permitirá escalar tu aplicación a decenas de idiomas sin sacrificar el rendimiento ni la experiencia de desarrollo.

🔥 Importante: Mantén tus archivos JSON sincronizados y utiliza herramientas de traducción asistida o servicios externos cuando tu base de código crezca significativamente.

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!