Empezando Internacionalización (i18n) con Intlayer y React Create App

    ¿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:

    • Gestionar fácilmente traducciones utilizando diccionarios declarativos a nivel de componente.
    • Localizar dinámicamente metadatos, rutas y contenido.
    • Asegurar soporte de TypeScript con tipos generados automáticamente, mejorando la autocompletación y la detección de errores.
    • Beneficiarte de características avanzadas, como detección y cambio dinámico del locale.

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

    Paso 1: Instalar Dependencias

    Instala los paquetes necesarios utilizando npm:

    bash
    npm install intlayer react-intlayer
    • intlayer

      El paquete central que proporciona herramientas de internacionalización para gestión de configuración, 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 ganchos para la internacionalización en React. Además, incluye el plugin para integrar Intlayer con la aplicación basada en Create React App.

    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, deshabilitar 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: Integra Intlayer en tu Configuración de CRA

    Cambia tus scripts para usar react-intlayer

    package.json
      "scripts": {
        "build": "react-intlayer build",
        "start": "react-intlayer start",
        "transpile": "intlayer build"
      },

    Los scripts de react-intlayer están basados en craco. También puedes implementar tu propia configuración basada en el plugin craco de intlayer. Ve el ejemplo aquí.

    Paso 4: Declara Tu Contenido

    Crea y gestiona tus declaraciones de contenido para almacenar traducciones:

    src/app.content.tsx
    import { t, type DeclarationContent } from "intlayer";
    import React, { type ReactNode } from "react";
    
    const appContent = {
      key: "app",
      content: {
        getStarted: t<ReactNode>({
          en: (
            <>
              Edit <code>src/App.tsx</code> and save to reload
            </>
          ),
          fr: (
            <>
              Éditez <code>src/App.tsx</code> et enregistrez pour recharger
            </>
          ),
          es: (
            <>
              Edita <code>src/App.tsx</code> y guarda para recargar
            </>
          ),
        }),
        reactLink: {
          href: "https://reactjs.org",
          content: t({
            en: "Learn React",
            fr: "Apprendre React",
            es: "Aprender React",
          }),
        },
      },
    } satisfies DeclarationContent;
    
    export default appContent;

    Tus declaraciones de contenido pueden definirse en cualquier lugar 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, deberías considerar importar import React from "react"; en tu archivo de contenido.

    Paso 5: Utiliza Intlayer en Tu Código

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

    src/App.tsx
    import logo from "./logo.svg";
    import "./App.css";
    import type { FC } from "react";
    import { IntlayerProvider, useIntlayer } from "react-intlayer";
    
    const AppContent: FC = () => {
      const content = useIntlayer("app");
    
      return (
        <div className="App">
          <img src={logo} className="App-logo" alt="logo" />
    
          {content.getStarted}
          <a
            className="App-link"
            href={content.reactLink.href.value}
            target="_blank"
            rel="noopener noreferrer"
          >
            {content.reactLink.content}
          </a>
        </div>
      );
    };
    
    const App: FC = () => (
      <IntlayerProvider>
        <AppContent />
      </IntlayerProvider>
    );
    
    export default App;

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

    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: Cambia 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 locale de la aplicación y actualizar el contenido en consecuencia.

    src/components/LocaleSwitcher.tsx
    import { Locales } from "intlayer";
    import { useLocale } from "react-intlayer";
    
    const LocaleSwitcher = () => {
      const { setLocale } = useLocale();
    
      return (
        <button onClick={() => setLocale(Locales.English)}>
          Cambiar idioma a inglés
        </button>
      );
    };

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

    (Opcional) Paso 7: Agrega enrutamiento localizado a tu Aplicación

    El propósito de este paso es crear 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 agregar 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 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 contexto de internacionalización
    import {
      BrowserRouter,
      Routes,
      Route,
      useParams,
      Navigate,
      useLocation,
    } from "react-router-dom"; // Componentes Router para manejar navegación
    
    // Desestructurando 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.
     * Administra la detección y validación del locale basado en URL.
     */
    const AppLocalized: FC<PropsWithChildren> = ({ children }) => {
      const path = useLocation().pathname; // Obtener la ruta actual de la URL
      const { locale } = useParams<{ locale: Locales }>(); // Extraer el parámetro locale de la URL
    
      // Determinar el locale actual, cayendo en el predeterminado 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 actual de URL
      );
    
      /**
       * Si middleware.prefixDefault es verdadero, el locale por defecto siempre debe estar prefijado.
       */
      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 por 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 está prefijado.
         * Asegúrate 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) // Verificar 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 establece rutas específicas del locale.
     * Utiliza React Router para manejar la navegación y renderizar componentes localizados.
     */
    export const LocaleRouter: FC<PropsWithChildren> = ({ children }) => (
      <BrowserRouter>
        <Routes>
          <Route
            // Patrón de ruta para capturar el locale (por ejemplo, /en/, /fr/) y coincidir con todas las rutas posteriores
            path="/:locale/*"
            element={<AppLocalized>{children}</AppLocalized>} // Envuelve a los hijos con gestión de locale
          />
    
          {
            // Si la prefijación del locale por defecto está deshabilitada, renderiza los hijos directamente en la ruta raíz
            !middleware.prefixDefault && (
              <Route
                path="*"
                element={<AppLocalized>{children}</AppLocalized>} // Envuelve a los hijos con gestión de locale
              />
            )
          }
        </Routes>
      </BrowserRouter>
    );

    (Opcional) Paso 8: Cambia la URL cuando cambia el locale

    Para cambiar la URL cuando cambia el locale, puedes usar la propiedad onLocaleChange proporcionada por el hook useLocale. En paralelo, puedes utilizar 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 actual de la URL. 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 aprovechar las ventajas de TypeScript y hacer que tu base de código sea más robusta.

    alt text

    alt text

    Asegúrate de que tu configuración de TypeScript incluya los tipos generados automáticamente.

    tsconfig.json
    {
      // ... Tu configuración existente de TypeScript
      "include": [
        // ... Tu configuración existente de TypeScript
        "types", // Incluir los tipos generados automáticamente
      ],
    }

    Configuración de Git

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

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

    .gitignore
    # 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