Comenzando con la internacionalización (i18n) con Intlayer y Next.js 14 con App Router

    ¿Qué es Intlayer?

    Intlayer es una biblioteca innovadora de internacionalización (i18n) de código abierto diseñada para simplificar el soporte multilingüe en aplicaciones web modernas. Intlayer se integra perfectamente con el último marco Next.js 14, incluyendo su potente App Router. Está optimizado para trabajar con Server Components para un renderizado eficiente y es totalmente compatible con Turbopack (desde Next.js >= 15).

    Con Intlayer, puedes:

    • Gestionar traducciones fácilmente utilizando diccionarios declarativos a nivel de componente.
    • Localizar dinámicamente metadatos, rutas y contenido.
    • Acceder a traducciones tanto en componentes del lado del cliente como del servidor.
    • Asegurar soporte para TypeScript con tipos autogenerados, mejorando la autocompletación y la detección de errores.
    • Beneficiarte de características avanzadas, como la detección y cambio dinámico de idioma.

    Intlayer es compatible con Next.js 12, 13, 14 y 15. Si estás utilizando Next.js Page Router, puedes consultar esta guía. Para Next.js 15 con o sin turbopack, consulta esta guía.


    Guía paso a paso para configurar Intlayer en una aplicación Next.js

    Paso 1: Instalar dependencias

    Instala los paquetes necesarios usando npm:

    bash
    npm install intlayer next-intlayer
    • intlayer

      El paquete principal que proporciona herramientas de internacionalización para la gestión de configuración, traducción, declaración de contenido, transpilación y comandos CLI.

    • next-intlayer

      El paquete que integra Intlayer con Next.js. Proporciona proveedores de contexto y hooks para la internacionalización en Next.js. Además, incluye el plugin de Next.js para integrar Intlayer con Webpack o Turbopack, así como middleware para detectar el idioma preferido del usuario, gestionar cookies y manejar redirecciones de URL.

    Paso 2: Configurar tu proyecto

    Crea un archivo de configuración para configurar los idiomas de tu aplicación:

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = {  internationalization: {    locales: [      Locales.ENGLISH,      Locales.FRENCH,      Locales.SPANISH,      // Tus otros idiomas    ],    defaultLocale: Locales.ENGLISH,  },};export default config;

    A través de este archivo de configuración, puedes configurar URLs localizadas, redirección de middleware, nombres de cookies, la ubicación y extensión de tus declaraciones de contenido, desactivar los logs de Intlayer en la consola y más. Para una lista completa de parámetros disponibles, consulta la documentación de configuración.

    Paso 3: Integrar Intlayer en tu configuración de Next.js

    Configura tu proyecto de Next.js para usar Intlayer:

    next.config.mjs
    import { withIntlayer } from "next-intlayer/server";/** @type {import('next').NextConfig} */const nextConfig = {};export default withIntlayer(nextConfig);

    El plugin withIntlayer() de Next.js se utiliza para integrar Intlayer con Next.js. Asegura la construcción de archivos de declaración de contenido y los monitorea en modo de desarrollo. Define variables de entorno de Intlayer dentro de los entornos Webpack o Turbopack. Además, proporciona alias para optimizar el rendimiento y asegura la compatibilidad con componentes del servidor.

    Paso 4: Configurar Middleware para la detección de idioma

    Configura el middleware para detectar el idioma preferido del usuario:

    src/middleware.ts
    export { intlayerMiddleware as middleware } from "next-intlayer/middleware";export const config = {  matcher:    "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\..*|_next).*)",};

    El intlayerMiddleware se utiliza para detectar el idioma preferido del usuario y redirigirlo a la URL adecuada según lo especificado en la configuración. Además, permite guardar el idioma preferido del usuario en una cookie.

    Adapta el parámetro matcher para que coincida con las rutas de tu aplicación. Para más detalles, consulta la documentación de Next.js sobre la configuración del matcher.

    Paso 5: Definir rutas dinámicas de idioma

    Elimina todo de RootLayout y reemplázalo con el siguiente código:

    src/app/layout.tsx
    import type { PropsWithChildren, FC } from "react";import "./globals.css";const RootLayout: FC<PropsWithChildren> = ({ children }) => children;export default RootLayout;

    Mantener el componente RootLayout vacío permite configurar los atributos lang y dir en la etiqueta <html>.

    Para implementar el enrutamiento dinámico, proporciona la ruta para el idioma añadiendo un nuevo layout en tu directorio [locale]:

    src/app/[locale]/layout.tsx
    import type { Next14LayoutIntlayer } from "next-intlayer";import { Inter } from "next/font/google";import { getHTMLTextDir } from "intlayer";const inter = Inter({ subsets: ["latin"] });const LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => (  <html lang={locale} dir={getHTMLTextDir(locale)}>    <body className={inter.className}>{children}</body>  </html>);export default LocaleLayout;

    El segmento de ruta [locale] se utiliza para definir el idioma. Ejemplo: /en-US/about se referirá a en-US y /fr/about a fr.

    Luego, implementa la función generateStaticParams en el Layout de tu aplicación.

    src/app/[locale]/layout.tsx
    export { generateStaticParams } from "next-intlayer"; // Línea a insertarconst LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => {  /*... Resto del código*/};export default LocaleLayout;

    generateStaticParams asegura que tu aplicación preconstruya las páginas necesarias para todos los idiomas, reduciendo el cálculo en tiempo de ejecución y mejorando la experiencia del usuario. Para más detalles, consulta la documentación de Next.js sobre generateStaticParams.

    Paso 6: Declarar tu contenido

    Crea y gestiona tus declaraciones de contenido para almacenar traducciones:

    src/app/[locale]/page.content.ts
    import { t, type Dictionary } from "intlayer";const pageContent = {  key: "page",  content: {    getStarted: {      main: t({        en: "Get started by editing",        fr: "Commencez par éditer",        es: "Comience por editar",      }),      pageLink: "src/app/page.tsx",    },  },} satisfies Dictionary;export default pageContent;

    Tus declaraciones de contenido pueden definirse en cualquier lugar de tu aplicación siempre que estén incluidas en el directorio contentDir (por defecto, ./src). Y coincidan con la extensión del archivo de declaración de contenido (por defecto, .content.{ts,tsx,js,jsx,mjs,cjs}). Para más detalles, consulta la documentación de declaración de contenido.

    Paso 7: Utilizar contenido en tu código

    Accede a tus diccionarios de contenido en toda tu aplicación:

    src/app/[locale]/page.tsx
    import { ClientComponentExample } from "@components/ClientComponentExample";import { ServerComponentExample } from "@components/ServerComponentExample";import { type Next14PageIntlayer, IntlayerClientProvider } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";const Page: Next14PageIntlayer = ({ params: { locale } }) => {  const content = useIntlayer("page", locale);  return (    <>      <p>        {content.getStarted.main}        <code>{content.getStarted.pageLink}</code>      </p>      <IntlayerServerProvider locale={locale}>        <IntlayerClientProvider locale={locale}>          <ServerComponentExample />          <ClientComponentExample />        </IntlayerClientProvider>      </IntlayerServerProvider>    </>  );};export default Page;
    • IntlayerClientProvider se utiliza para proporcionar el idioma a los componentes del lado del cliente. Puede colocarse en cualquier componente padre, incluido el layout. Sin embargo, se recomienda colocarlo en un layout porque Next.js comparte el código del layout entre páginas, haciéndolo más eficiente. Al usar IntlayerClientProvider en el layout, evitas reinicializarlo para cada página, mejorando el rendimiento y manteniendo un contexto de localización consistente en toda tu aplicación.
    • IntlayerServerProvider se utiliza para proporcionar el idioma a los hijos del servidor. No puede configurarse en el layout.

      El layout y la página no pueden compartir un contexto de servidor común porque el sistema de contexto del servidor se basa en un almacén de datos por solicitud (a través del mecanismo de React’s cache), causando que cada "contexto" se recree para diferentes segmentos de la aplicación. Colocar el proveedor en un layout compartido rompería este aislamiento, impidiendo la correcta propagación de los valores del contexto del servidor a tus componentes del servidor.

    src/components/ClientComponentExample.tsx
    "use client";import type { FC } from "react";import { useIntlayer } from "next-intlayer";const ClientComponentExample: FC = () => {  const content = useIntlayer("client-component-example"); // Crear declaración de contenido relacionada  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};
    src/components/ServerComponentExample.tsx
    import type { FC } from "react";import { useIntlayer } from "next-intlayer/server";const ServerComponentExample: FC = () => {  const content = useIntlayer("server-component-example"); // Crear declaración de contenido relacionada  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};

    Si deseas usar tu contenido en un atributo string, como alt, title, href, aria-label, etc., debes llamar al valor de la función, como:

    jsx
    <img src={content.image.src.value} alt={content.image.value} />

    Para aprender más sobre el hook useIntlayer, consulta la documentación.

    (Opcional) Paso 8: Internacionalización de tus metadatos

    En caso de que desees internacionalizar tus metadatos, como el título de tu página, puedes usar la función generateMetadata proporcionada por Next.js. Dentro de la función, usa la función getTranslation para traducir tus metadatos.

    src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx
    import {  type IConfigLocales,  getTranslation,  getMultilingualUrls,} from "intlayer";import type { Metadata } from "next";import type { LocalParams } from "next-intlayer";export const generateMetadata = ({  params: { locale },}: LocalParams): Metadata => {  const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);  /**   * Genera un objeto que contiene todas las URLs para cada idioma.   *   * Ejemplo:   * ```ts   *  getMultilingualUrls('/about');   *   *  // Devuelve   *  // {   *  //   en: '/about',   *  //   fr: '/fr/about',   *  //   es: '/es/about',   *  // }   * ```   */  const multilingualUrls = getMultilingualUrls("/");  return {    title: t<string>({      en: "My title",      fr: "Mon titre",      es: "Mi título",    }),    description: t({      en: "My description",      fr: "Ma description",      es: "Mi descripción",    }),    alternates: {      canonical: "/",      languages: { ...multilingualUrls, "x-default": "/" },    },    openGraph: {      url: multilingualUrls[locale],    },  };};// ... Resto del código

    Aprende más sobre la optimización de metadatos en la documentación oficial de Next.js.

    (Opcional) Paso 9: Internacionalización de tu sitemap.xml y robots.txt

    Para internacionalizar tu sitemap.xml y robots.txt, puedes usar la función getMultilingualUrls proporcionada por Intlayer. Esta función te permite generar URLs multilingües para tu sitemap.

    src/app/sitemap.ts
    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [  {    url: "https://example.com",    alternates: {      languages: getMultilingualUrls("https://example.com"),    },  },  {    url: "https://example.com/login",    alternates: {      languages: getMultilingualUrls("https://example.com/login"),    },  },  {    url: "https://example.com/register",    alternates: {      languages: getMultilingualUrls("https://example.com/register"),    },  },];export default sitemap;
    src/app/robots.ts
    import type { MetadataRoute } from "next";import { getMultilingualUrls } from "intlayer";const getAllMultilingualUrls = (urls: string[]) =>  urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({  rules: {    userAgent: "*",    allow: ["/"],    disallow: getAllMultilingualUrls(["/login", "/register"]),  },  host: "https://example.com",  sitemap: `https://example.com/sitemap.xml`,});export default robots;

    Aprende más sobre la optimización del sitemap en la documentación oficial de Next.js. Aprende más sobre la optimización del robots.txt en la documentación oficial de Next.js.

    (Opcional) Paso 10: Cambiar el idioma de tu contenido

    Para cambiar el idioma de tu contenido, puedes usar la función setLocale proporcionada por el hook useLocale. Esta función te permite establecer el idioma de la aplicación y actualizar el contenido en consecuencia.

    src/components/LocaleSwitcher.tsx
    "use client";import {  Locales,  getHTMLTextDir,  getLocaleName,  getLocalizedUrl,} from "intlayer";import { useLocale } from "next-intlayer";import { type FC } from "react";import Link from "next/link";const LocaleSwitcher: FC = () => {  const { locale, pathWithoutLocale, availableLocales, setLocale } =    useLocale();  return (    <div>      <button popoverTarget="localePopover">{getLocaleName(locale)}</button>      <div id="localePopover" popover="auto">        {availableLocales.map((localeItem) => (          <Link            href={getLocalizedUrl(pathWithoutLocale, localeItem)}            hrefLang={localeItem}            key={localeItem}            aria-current={locale === localeItem ? "page" : undefined}            onClick={(e) => {              e.preventDefault();              setLocale(localeItem);            }}          >            <span>              {/* Idioma - e.g. FR */}              {localeItem}            </span>            <span>              {/* Idioma en su propio idioma - e.g. Français */}              {getLocaleName(localeItem, locale)}            </span>            <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>              {/* Idioma en el idioma actual - e.g. Francés con idioma actual configurado en Locales.SPANISH */}              {getLocaleName(localeItem)}            </span>            <span dir="ltr" lang={Locales.ENGLISH}>              {/* Idioma en inglés - e.g. French */}              {getLocaleName(localeItem, Locales.ENGLISH)}            </span>          </Link>        ))}      </div>    </div>  );};

    Referencias de documentación:

    (Opcional) Paso 11: Crear un componente de enlace localizado

    Para asegurarte de que la navegación de tu aplicación respete el idioma actual, puedes crear un componente personalizado Link. Este componente automáticamente añade un prefijo a las URLs internas con el idioma actual. Por ejemplo, cuando un usuario que habla francés hace clic en un enlace a la página "Acerca de", se redirige a /fr/about en lugar de /about.

    Este comportamiento es útil por varias razones:

    • SEO y experiencia del usuario: Las URLs localizadas ayudan a los motores de búsqueda a indexar correctamente las páginas específicas de cada idioma y proporcionan a los usuarios contenido en su idioma preferido.
    • Consistencia: Al usar un enlace localizado en toda tu aplicación, garantizas que la navegación se mantenga dentro del idioma actual, evitando cambios de idioma inesperados.
    • Mantenibilidad: Centralizar la lógica de localización en un único componente simplifica la gestión de URLs, haciendo que tu base de código sea más fácil de mantener y ampliar a medida que tu aplicación crece.

    A continuación se muestra la implementación de un componente Link localizado en TypeScript:

    src/components/Link.tsx
    "use client";import { getLocalizedUrl } from "intlayer";import NextLink, { type LinkProps as NextLinkProps } from "next/link";import { useLocale } from "next-intlayer";import { forwardRef, PropsWithChildren, type ForwardedRef } from "react";/** * Función de utilidad para comprobar si una URL es externa. * Si la URL comienza con http:// o https://, se considera externa. */export const checkIsExternalLink = (href?: string): boolean =>  /^https?:///.test(href ?? "");/** * Un componente de enlace personalizado que adapta el atributo href según el idioma actual. * Para enlaces internos, utiliza `getLocalizedUrl` para añadir un prefijo a la URL con el idioma (por ejemplo, /fr/about). * Esto asegura que la navegación se mantenga dentro del mismo contexto de idioma. */export const Link = forwardRef<  HTMLAnchorElement,  PropsWithChildren<NextLinkProps>>(({ href, children, ...props }, ref: ForwardedRef<HTMLAnchorElement>) => {  const { locale } = useLocale();  const isExternalLink = checkIsExternalLink(href.toString());  // Si el enlace es interno y se proporciona un href válido, obtiene la URL localizada.  const hrefI18n: NextLinkProps["href"] =    href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;  return (    <NextLink href={hrefI18n} ref={ref} {...props}>      {children}    </NextLink>  );});Link.displayName = "Link";

    Cómo funciona

    • Detectar enlaces externos:
      La función auxiliar checkIsExternalLink determina si una URL es externa. Los enlaces externos se dejan sin cambios porque no necesitan localización.

    • Obtener el idioma actual:
      El hook useLocale proporciona el idioma actual (por ejemplo, fr para francés).

    • Localizar la URL:
      Para enlaces internos (es decir, no externos), se utiliza getLocalizedUrl para añadir automáticamente un prefijo a la URL con el idioma actual. Esto significa que si tu usuario está en francés, pasar /about como el href lo transformará en /fr/about.

    • Devolver el enlace:
      El componente devuelve un elemento <a> con la URL localizada, asegurando que la navegación sea consistente con el idioma.

    Al integrar este componente Link en toda tu aplicación, mantienes una experiencia de usuario coherente y consciente del idioma, mientras también te beneficias de un mejor SEO y usabilidad.

    Configurar TypeScript

    Intlayer utiliza la ampliación de módulos para obtener los beneficios de TypeScript y fortalecer tu base de código.

    alt text

    alt text

    Asegúrate de que tu configuración de TypeScript incluya los tipos autogenerados.

    tsconfig.json
    {  // ... Tus configuraciones existentes de TypeScript  "include": [    // ... Tus configuraciones existentes de TypeScript    ".intlayer/**/*.ts", // Incluir los tipos autogenerados  ],}

    Configuración de Git

    Se recomienda ignorar los archivos generados por Intlayer. Esto te permite evitar comprometerlos en tu repositorio Git.

    Para hacer esto, puedes agregar las siguientes instrucciones a tu archivo .gitignore:

    .gitignore
    # Ignorar los archivos generados por Intlayer.intlayer

    Ir más allá

    Para ir más allá, puedes implementar el editor visual o externalizar tu contenido utilizando el CMS.

    Si tienes una idea para mejorar esta documentación, no dudes en contribuir enviando una pull request en GitHub.

    Enlace de GitHub a la documentación