Comenzando la Internacionalización (i18n) con Intlayer, Vite y React

    ¿Qué es Intlayer?

    Intlayer es una innovadora biblioteca de internacionalización (i18n) de código abierto diseñada para simplificar el soporte multilingüe en aplicaciones web modernas.

    Con Intlayer, puedes:

    • Administrar fácilmente las traducciones utilizando diccionarios declarativos a nivel de componente.
    • Localizar dinámicamente metadatos, rutas y contenido.
    • Asegurar el soporte de 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 locales.

    Guía Paso a Paso para Configurar Intlayer en una Aplicación Vite y React

    Paso 1: Instalar Dependencias

    Instala los paquetes necesarios usando npm:

    bash
    npm install intlayer react-intlayer
    • intlayer

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

    • react-intlayer El paquete que integra Intlayer con la aplicación React. Proporciona proveedores de contexto y hooks para la internacionalización de React. Además, incluye el plugin Vite para integrar Intlayer con el bundler Vite, así como middleware para detectar el locale preferido del usuario, gestionar cookies y manejar la redirección de URL.

    Paso 2: Configuración de 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 locales
        ],
        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 registros 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 Vite

    Agrega el plugin intlayer en tu configuración.

    vite.config.ts
    import { defineConfig } from "vite";
    import react from "@vitejs/plugin-react-swc";
    import { intLayerPlugin } from "react-intlayer/vite";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [react(), intLayerPlugin()],
    });

    El plugin intLayerPlugin() de Vite se utiliza para integrar Intlayer con Vite. Asegura la construcción de archivos de declaración de contenido y los monitorea en el modo de desarrollo. Define variables de entorno de Intlayer dentro de la aplicación Vite. Además, proporciona alias para optimizar el rendimiento.

    Paso 4: Declarar Tu Contenido

    Crea y gestiona tus declaraciones de contenido para almacenar traducciones:

    src/app.content.tsx
    import { t, type DeclarationContent } from "intlayer";
    import type { ReactNode } from "react";
    
    const appContent = {
      key: "app",
      content: {
        viteLogo: t({
          en: "Vite logo",
          fr: "Logo Vite",
          es: "Logo Vite",
        }),
        reactLogo: t({
          en: "React logo",
          fr: "Logo React",
          es: "Logo React",
        }),
    
        title: "Vite + React",
    
        count: t({
          en: "count is ",
          fr: "le compte est ",
          es: "el recuento es ",
        }),
    
        edit: t<ReactNode>({
          // No olvides importar React si utilizas un nodo de React en tu contenido
          en: (
            <>
              Edit <code>src/App.tsx</code> and save to test HMR
            </>
          ),
          fr: (
            <>
              Éditez <code>src/App.tsx</code> et enregistrez pour tester HMR
            </>
          ),
          es: (
            <>
              Edita <code>src/App.tsx</code> y guarda para probar HMR
            </>
          ),
        }),
    
        readTheDocs: t({
          en: "Click on the Vite and React logos to learn more",
          fr: "Cliquez sur les logos Vite et React pour en savoir plus",
          es: "Haga clic en los logotipos de Vite y React para obtener más información",
        }),
      },
    } satisfies DeclarationContent;
    
    export default appContent;

    Tus declaraciones de contenido pueden definirse en cualquier parte de tu aplicación tan pronto como se incluyan 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. Si tu archivo de contenido incluye código TSX, debes considerar importar import React from "react"; en tu archivo de contenido.

    Paso 5: Utilizar Intlayer en Tu Código

    Accede a tus diccionarios de contenido a lo largo de tu aplicación:

    src/App.tsx
    import { useState, type FC } from "react";
    import reactLogo from "./assets/react.svg";
    import viteLogo from "/vite.svg";
    import "./App.css";
    import { IntlayerProvider, useIntlayer } from "react-intlayer";
    
    const AppContent: FC = () => {
      const [count, setCount] = useState(0);
      const content = useIntlayer("app");
    
      return (
        <>
          <div>
            <a href="https://vitejs.dev" target="_blank">
              <img src={viteLogo} className="logo" alt={content.viteLogo.value} />
            </a>
            <a href="https://react.dev" target="_blank">
              <img
                src={reactLogo}
                className="logo react"
                alt={content.reactLogo.value}
              />
            </a>
          </div>
          <h1>{content.title}</h1>
          <div className="card">
            <button onClick={() => setCount((count) => count + 1)}>
              {content.count}
              {count}
            </button>
            <p>{content.edit}</p>
          </div>
          <p className="read-the-docs">{content.readTheDocs}</p>
        </>
      );
    };
    
    const App: FC = () => (
      <IntlayerProvider>
        <AppContent />
      </IntlayerProvider>
    );
    
    export default App;

    Si deseas usar tu contenido en un atributo de tipo 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 6: 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 permite configurar el locale de la aplicación y actualizar el contenido en consecuencia.

    src/components/LocaleSwitcher.tsx
    import type { FC } from "react";
    import { Locales } from "intlayer";
    import { useLocale } from "react-intlayer";
    
    const LocaleSwitcher: FC = () => {
      const { setLocale } = useLocale();
    
      return (
        <button onClick={() => setLocale(Locales.English)}>
          Change Language to English
        </button>
      );
    };

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

    (Opcional) Paso 7: Añadir Rutas Localizadas a tu Aplicación

    El propósito de este paso es hacer rutas únicas para cada idioma. Esto es útil para SEO y URLs amigables para SEO. Ejemplo:

    plaintext
    - https://example.com/about
    - https://example.com/es/about
    - https://example.com/fr/about

    Por defecto, las rutas no tienen prefijo para el locale por defecto. Si deseas prefijar el locale por defecto, puedes establecer la opción middleware.prefixDefault en true en tu configuración. Consulta la documentación de configuración para más información.

    Para añadir enrutamiento localizado a tu aplicación, puedes crear un componente LocaleRouter que envuelva las rutas de tu aplicación y maneje el enrutamiento basado en el locale. Aquí hay un ejemplo usando React Router:

    src/components/LocaleRouter.tsx
    // Importando las dependencias y funciones necesarias
    import { Locales, getConfiguration, getPathWithoutLocale } from "intlayer"; // Funciones y tipos de utilidad de 'intlayer'
    import type { FC, PropsWithChildren } from "react"; // Tipos de React para componentes funcionales y props
    import { IntlayerProvider } from "react-intlayer"; // Proveedor para el contexto de internacionalización
    import {
      BrowserRouter,
      Routes,
      Route,
      useParams,
      Navigate,
      useLocation,
    } from "react-router-dom"; // Componentes de Router para gestionar la navegación
    
    // Desestructurando la configuración de Intlayer
    const { internationalization, middleware } = getConfiguration();
    const { locales, defaultLocale } = internationalization;
    
    /**
     * Un componente que maneja la localización y envuelve a los hijos con el contexto de locale apropiado.
     * Maneja la detección de locale basada en URL y validación.
     */
    const AppLocalized: FC<PropsWithChildren> = ({ children }) => {
      const path = useLocation().pathname; // Obtener la ruta URL actual
      const { locale } = useParams<{ locale: Locales }>(); // Extraer el parámetro locale de la URL
    
      // Determinar el locale actual, retrocediendo al default si no se proporciona
      const currentLocale = locale ?? defaultLocale;
    
      // Eliminar el prefijo de locale de la ruta para construir una ruta base
      const pathWithoutLocale = removeLocaleFromUrl(
        path // Ruta URL actual
      );
    
      /**
       * Si middleware.prefixDefault es verdadero, el locale por defecto siempre debe tener prefijo.
       */
      if (middleware.prefixDefault) {
        // Validar el locale
        if (!locale || !locales.includes(locale)) {
          // Redirigir al locale por defecto con la ruta actualizada
          return (
            <Navigate
              to={`/${defaultLocale}/${pathWithoutLocale}`}
              replace // Reemplazar la entrada actual del historial con la nueva
            />
          );
        }
    
        // Envolver a los hijos con el IntlayerProvider y establecer el locale actual
        return (
          <IntlayerProvider locale={currentLocale}>{children}</IntlayerProvider>
        );
      } else {
        /**
         * Cuando middleware.prefixDefault es falso, el locale por defecto no tiene prefijo.
         * Asegurarse de que el locale actual sea válido y no el locale por defecto.
         */
        if (
          currentLocale.toString() !== defaultLocale.toString() &&
          !locales
            .filter(
              (locale) => locale.toString() !== defaultLocale.toString() // Excluir el locale por defecto
            )
            .includes(currentLocale) // Comprobar si el locale actual está en la lista de locales válidos
        ) {
          // Redirigir a la ruta sin prefijo de locale
          return <Navigate to={pathWithoutLocale} replace />;
        }
    
        // Envolver a los hijos con el IntlayerProvider y establecer el locale actual
        return (
          <IntlayerProvider locale={currentLocale}>{children}</IntlayerProvider>
        );
      }
    };
    
    /**
     * Un componente de router que configura rutas específicas de locale.
     * Utiliza React Router para gestionar la navegación y renderizar componentes localizados.
     */
    export const LocaleRouter: FC<PropsWithChildren> = ({ children }) => (
      <BrowserRouter>
        <Routes>
          <Route
            // Patrón de ruta para capturar el locale (e.g., /en/, /fr/) y coincidir con todas las rutas siguientes
            path="/:locale/*"
            element={<AppLocalized>{children}</AppLocalized>} // Envuelve a los hijos con la gestión de locale
          />
    
          {
            // Si se desactiva el prefijo del locale por defecto, renderiza los hijos directamente en la ruta raíz
            !middleware.prefixDefault && (
              <Route
                path="*"
                element={<AppLocalized>{children}</AppLocalized>} // Envuelve a los hijos con la gestión de locale
              />
            )
          }
        </Routes>
      </BrowserRouter>
    );

    En paralelo, también puedes utilizar el intLayerMiddlewarePlugin para añadir enrutamiento del lado del servidor a tu aplicación. Este plugin automáticamente detectará el locale actual basado en la URL y establecerá la cookie de locale apropiada. Si no se especifica ningún locale, el plugin determinará el locale más apropiado basado en las preferencias de idioma del navegador del usuario. Si no se detecta ningún locale, redirigirá al locale por defecto.

    vite.config.ts
    import { defineConfig } from "vite";
    import react from "@vitejs/plugin-react-swc";
    import { intLayerPlugin, intLayerMiddlewarePlugin } from "react-intlayer/vite";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [react(), intLayerPlugin(), intLayerMiddlewarePlugin()],
    });

    (Opcional) Paso 8: Cambiar la URL cuando cambie el locale

    Para cambiar la URL cuando cambie el locale, puedes usar la prop onLocaleChange proporcionada por el hook useLocale. En paralelo, puedes usar los hooks useLocation y useNavigate de react-router-dom para actualizar la ruta de la URL.

    src/components/LocaleSwitcher.tsx
    import { useLocation, useNavigate } from "react-router-dom";
    import {
      Locales,
      getHTMLTextDir,
      getLocaleName,
      getLocalizedUrl,
    } from "intlayer";
    import { useLocale } from "react-intlayer";
    import { type FC } from "react";
    
    const LocaleSwitcher: FC = () => {
      const location = useLocation(); // Obtener la ruta URL actual. Ejemplo: /fr/about
      const navigate = useNavigate();
    
      const changeUrl = (locale: Locales) => {
        // Construir la URL con el locale actualizado
        // Ejemplo: /es/about con el locale establecido en español
        const pathWithLocale = getLocalizedUrl(location.pathname, locale);
    
        // Actualizar la ruta de la URL
        navigate(pathWithLocale);
      };
    
      const { locale, availableLocales, setLocale } = useLocale({
        onLocaleChange: changeUrl,
      });
    
      return (
        <ol>
          {availableLocales.map((localeItem) => (
            <li key={localeItem}>
              <a
                href={getLocalizedUrl(location, localeItem)}
                hrefLang={localeItem}
                aria-current={locale === localeItem ? "page" : undefined}
                onClick={(e) => {
                  e.preventDefault();
                  setLocale(localeItem);
                }}
              >
                <span>
                  {/* Idioma en su propio Locale - e.g. Français */}
                  {getLocaleName(localeItem, locale)}
                </span>
                <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
                  {/* Idioma en el Locale actual - e.g. Francés con el locale actual establecido en Locales.SPANISH */}
                  {getLocaleName(localeItem)}
                </span>
                <span dir="ltr" lang={Locales.ENGLISH}>
                  {/* Idioma en inglés - e.g. French */}
                  {getLocaleName(localeItem, Locales.ENGLISH)}
                </span>
                <span>
                  {/* Idioma en su propio Locale - e.g. FR */}
                  {localeItem}
                </span>
              </a>
            </li>
          ))}
        </ol>
      );
    };

    Referencias de documentación:

    Configurar TypeScript

    Intlayer utiliza la ampliación de módulos para obtener beneficios de TypeScript y hacer tu base de código más fuerte.

    alt text

    alt text

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

    tsconfig.json
    {
      // tu configuración personalizada
      "include": [
        "src",
        "types", // <- Incluir los tipos autogenerados
      ],
    }

    Configuración de Git

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

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

    plaintext
    # Ignorar los archivos generados por Intlayer
    .intlayer

    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