Autor:
    Creación:2026-09-09Última actualización:2026-09-27

    Traduce tu sitio web Remix 3 usando Intlayer

    Esta guía demuestra cómo integrar Intlayer para una internacionalización fluida en aplicaciones Remix 3 con enrutamiento según el idioma, declaraciones de contenido con seguridad de tipos, componentes JSX renderizados en el servidor y soporte multi-entorno en Node.js, Bun, Deno y Cloudflare Workers.

    ¿Qué es Remix 3?

    Remix 3 representa un cambio arquitectónico fundamental hacia un framework web componible, independiente del entorno de ejecución y construido íntegramente sobre estándares web. En lugar de acoplarse a empaquetadores específicos o a APIs de servidor propietarias, Remix 3 se distribuye como paquetes componibles de un solo propósito:

    • remix/fetch-router (o remix/router): Enrutamiento ligero y conforme a los estándares basado en la API Fetch (Request y Response).
    • remix/ui: Un modelo de componentes JSX (jsxImportSource: "remix/ui"). Un componente es una función de setup que devuelve una función de render, por lo que parece React pero mantiene el estado en closures simples de JavaScript.
    • remix/middleware/render: Instala context.render(<Page />) en cada petición, transmitiendo el árbol JSX como una Response HTML.
    • remix/node-fetch-server: Adaptadores de servidor para Node.js, con soporte nativo para Bun, Deno y entornos edge.
    • remix/cookie: Análisis y serialización de cookies criptográficamente seguras.

    Combinado con Intlayer y el paquete remix-intlayer, un middleware de idioma más los mismos hooks useIntlayer / useDictionary / useLocale que react-intlayer, vinculados al contexto de petición de Remix, obtienes un sistema completo de internacionalización que ofrece seguridad en tiempo de compilación, traducciones automatizadas por IA, renderizado en servidor sin sobrecarga y enrutamiento fluido por idioma.

    Tabla de contenidos

    ¿Por qué elegir Intlayer frente a otras alternativas?

    En comparación con soluciones tradicionales como i18next o cargadores de traducción a medida, Intlayer ofrece una experiencia de desarrollador integrada y optimizada para la arquitectura web moderna:

    Intlayer está diseñado para funcionar de forma nativa con los estándares web (Request, Response, Headers y URL). remix-intlayer se integra en el router Fetch de Remix 3 como un middleware ligero, extrayendo el idioma de las rutas URL, cookies o encabezados Accept-Language y exponiéndolo al resto de la petición, handlers, vistas y componentes remix/ui, sin pasarlo manualmente ni atarte a un entorno de ejecución específico.

    Dile adiós a las claves JSON sueltas y a los errores en tiempo de ejecución por claves faltantes. Intlayer aplica validaciones de TypeScript en todos los idiomas declarados, advirtiéndote en tiempo de compilación si falta una traducción o si es inválida.

    Remix 3 renderiza componentes JSX en el servidor y transmite el HTML al cliente. Solo el texto resuelto para el idioma solicitado se incluye en la respuesta. No se requieren paquetes de hidratación en el cliente ni catálogos pesados a menos que un componente esté explícitamente marcado como clientEntry.

    Intlayer ubica las declaraciones de contenido (.content.ts) junto a la lógica de tus rutas, reduciendo el contexto de tokens necesario para los Modelos de Lenguaje (LLM). Los comandos CLI integrados como intlayer fill e intlayer test te permiten automatizar traducciones en pipelines de CI/CD al costo directo de tu proveedor de IA.

    Más allá de los flujos de trabajo basados en código, Intlayer ofrece un Editor Visual autohospedado y un CMS Remoto, permitiendo a editores y traductores actualizar el contenido sin tener que volver a desplegar la aplicación.

    Guía paso a paso

    ide.intlayer.org
    intlayer-remix-3-template.vercel.app

    Consulta la Plantilla de Aplicación en GitHub.

    1. Instalar dependencias

      Instala intlayer, remix-intlayer y remix (versión 3) usando tu gestor de paquetes preferido:

      bash
      npm install intlayer remix-intlayer remix@next
      
      • intlayer: Motor central de internacionalización que proporciona gestión de configuración, declaración de diccionarios (t(), Dictionary), herramientas CLI e intérprete en tiempo de ejecución.
      • remix-intlayer: La integración con Remix 3: el middleware de router intlayer() que resuelve el idioma de cada petición, y los hooks useIntlayer, useDictionary y useLocale que lo leen en cualquier punto posterior.
      • remix: El paquete unificado del framework Remix 3 que exporta remix/router, remix/routes, remix/ui, remix/middleware/render y remix/node-fetch-server.
    2. Configurar Intlayer

      Arquitectura

      En esta arquitectura, el middleware intlayer() de remix-intlayer se registra en createRouter() antes del middleware render(). Elimina el prefijo de locale antes de que el router coincida, por lo que las rutas se declaran una sola vez en src/routes.ts sin segmento :locale, y ejecuta el resto de la solicitud dentro de un alcance AsyncLocalStorage, lo que permite que useIntlayer / useLocale lean la locale sin argumentos en los manejadores de rutas y vistas de remix/ui. Las declaraciones de contenido se ubican junto a tus vistas en src/:

      bash
      .
      ├── src
      │   ├── home.content.ts               # Home page content declaration
      │   ├── router.tsx                    # createRouter() with the intlayer() and render() middleware
      │   ├── routes.ts                     # Type-safe routes, declared once without locale segment
      │   ├── server.ts                     # fetch handler (Node.js, Bun, Deno, Cloudflare Workers)
      │   └── views
      │       ├── document.tsx              # HTML shell setting <html lang dir> from the locale
      │       └── home.tsx                  # Localized page using useIntlayer / useLocale
      ├── intlayer.config.ts
      ├── package.json
      └── tsconfig.json
      

      Configuración

      Crea un archivo intlayer.config.ts en la raíz de tu proyecto para declarar los idiomas soportados y los ajustes de internacionalización:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
      };
      
      export default config;
      
      Para ajustes de configuración adicionales (como modo estricto o preferencias de almacenamiento de rutas), consulta la documentación de configuración.
    3. Declarar tu contenido multilingüe

      Declara tu contenido localizado en un archivo .content.ts:

      src/home.content.ts
      import { t, type Dictionary } from "intlayer";
      
      const homeContent = {
        key: "home",
        content: {
          title: t({
            es: "Bienvenido a Remix 3",
            en: "Welcome to Remix 3",
            fr: "Bienvenue sur Remix 3",
          }),
          description: t({
            es: "Una aplicación componible basada en estándares web con i18n nativa.",
            en: "A composable, web-standard application with native i18n.",
            fr: "Une application composable basée sur les standards web avec i18n native.",
          }),
          switchLanguage: t({
            es: "Cambiar idioma:",
            en: "Switch language:",
            fr: "Changer de langue :",
          }),
        },
      } satisfies Dictionary;
      
      export default homeContent;
      
      Intlayer también admite formatos JSON, YAML y CommonJS. Consulta la Documentación de Declaración de Contenido.
    4. Compilar diccionarios Intlayer

      Compila las definiciones de diccionarios para generar los tipos de TypeScript y los registros en tiempo de ejecución:

      bash
      npx intlayer build
      

      Esto compila tu contenido en el directorio de artefactos .intlayer, permitiendo autocompletado completo en TypeScript y acceso rápido a los diccionarios.

    5. Añadir el middleware de Intlayer

      Remix 3 proporciona un pipeline de middleware componible mediante createRouter({ middleware: [...] }).

      remix-intlayer incluye el middleware intlayer(). Para cada petición entrante, resuelve el idioma utilizando:

      1. La URL, en todos los modos de enrutamiento excepto no-prefix: el prefijo de ruta (ej. /fr o /es) o el parámetro de búsqueda ?locale=.
      2. El idioma persistido por el cliente: la cookie de almacenamiento (INTLAYER_LOCALE) o el encabezado personalizado (x-intlayer-locale).
      3. La negociación estándar de Accept-Language, recurriendo a tu defaultLocale configurado.

      El resultado se almacena en el contexto de petición de Remix como context.intlayer (o context.get(Intlayer)), con locale, defaultLocale y availableLocales. A continuación, el middleware ejecuta el resto de la petición dentro de un ámbito de AsyncLocalStorage vinculado a ese contexto, lo que permite que los hooks del paquete lean el idioma sin argumentos, tanto en manejadores de rutas como en vistas y componentes de remix/ui:

      typescript
      import { useIntlayer, useLocale } from "remix-intlayer";
      
      // En cualquier punto posterior al middleware
      const { locale, availableLocales } = useLocale();
      const { title } = useIntlayer("home");
      

      useIntlayer("home", "fr") o useIntlayer("faq", { item: 2 }) anulan el idioma de la petición para una llamada, y useDictionary(homeContent) lee un diccionario importado en lugar de una clave. Fuera de una petición, los hooks recurren al idioma predeterminado.

      El middleware también prepara los diccionarios de Intlayer cuando se inicia el servidor, por lo que la falta de un intlayer build no deja el registro vacío.
      Establece routing.enableProxy: false en intlayer.config.ts para conservar solo la resolución de la locale y gestionar el enrutamiento tú mismo. intlayer({ ignore }) deja intactas las solicitudes coincidentes (un prefijo de API, por ejemplo), e intlayer({ isDevServer }) controla si la locale almacenada determina las redirecciones en el modo automático predeterminado de enableProxy.
    6. Definir rutas con seguridad de tipos

      Define las rutas de tu aplicación usando route() de remix/routes:

      src/routes.ts
      import { route } from "remix/routes";
      
      export const routes = route({
        // Ruta para el idioma por defecto
        home: "/",
      
        // Ruta localizada con segmento dinámico :locale
        localizedHome: "/:locale",
      });
      

      El uso de route() te ofrece generación de URL con seguridad de tipos en toda la aplicación:

      typescript
      routes.home.href(); // "/"
      routes.localizedHome.href({ locale: "es" }); // "/es"
      
    7. Renderizar páginas localizadas con JSX

      Remix 3 renderiza la interfaz de usuario con componentes JSX de remix/ui. Un componente es una función de setup que recibe un Handle y devuelve una función de render. El setup se ejecuta una sola vez por instancia, el render se ejecuta en cada actualización, y las props se leen a través de handle.props.

      Comienza con un shell compartido Document que establece los atributos <html lang="..." dir="..."> a partir del idioma resuelto por el middleware:

      src/views/document.tsx
      import { getHTMLTextDir } from "intlayer";
      import { useLocale } from "remix-intlayer";
      import type { Handle, RemixNode } from "remix/ui";
      
      type DocumentProps = {
        title: string;
        children?: RemixNode;
      };
      
      export const Document = (handle: Handle<DocumentProps>) => () => {
        const { title, children } = handle.props;
        const { locale } = useLocale();
      
        return (
          <html lang={locale} dir={getHTMLTextDir(locale)}>
            <head>
              <meta charSet="utf-8" />
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <title>{title}</title>
            </head>
            <body>{children}</body>
          </html>
        );
      };
      

      Luego crea la página de inicio. Lee el diccionario localizado con useIntlayer y renderiza un selector de idioma:

      src/views/home.tsx
      import { getLocaleName, getLocalizedUrl, getPathWithoutLocale } from "intlayer";
      import { useIntlayer, useLocale } from "remix-intlayer";
      import { Document } from "./document";
      
      export const HomePage = () => () => {
        const { locale, availableLocales } = useLocale();
        const home = useIntlayer("home");
        const pathWithoutLocale = getPathWithoutLocale();
      
        return (
          <Document title={home.title}>
            <header>
              <nav aria-label="Languages">
                <span>{home.switchLanguage}</span>
                <ul>
                  {availableLocales.map((localeItem) => {
                    const isActive = localeItem === locale;
      
                    return (
                      <li key={localeItem} class="p-1">
                        <a
                          href={getLocalizedUrl(pathWithoutLocale, localeItem)}
                          class={isActive ? "active" : undefined}
                          aria-current={isActive ? "page" : undefined}
                        >
                          {getLocaleName(localeItem, locale)}
                        </a>
                      </li>
                    );
                  })}
                </ul>
              </nav>
            </header>
            <main>
              <h1>{home.title}</h1>
              <p>{home.description}</p>
            </main>
          </Document>
        );
      };
      
      Remix JSX no es React: class se escribe tal cual (className también se acepta) y los re-renderizados se activan explícitamente con handle.update(). Los valores interpolados se escapan automáticamente. Los hooks de Intlayer son funciones simples que leen el ámbito de la petición, por lo que se pueden llamar tanto desde la función de setup como desde la de renderizado.
    8. Conectar el router y el servidor

      Añade el middleware render() de remix/middleware/render junto al middleware de Intlayer. Este instala context.render(node, init) en cada petición, lo cual transmite el árbol JSX en una Response HTML (anteponiendo <!DOCTYPE html> y configurando el encabezado Content-Type):

      src/router.tsx
      import { isDeclaredLocale } from "intlayer";
      import { intlayer } from "remix-intlayer";
      import { render } from "remix/middleware/render";
      import { createRouter } from "remix/router";
      import { routes } from "./routes";
      import { HomePage } from "./views/home";
      
      // 1. Initialize router with Intlayer + render middleware
      export const router = createRouter({
        middleware: [intlayer(), render()],
      });
      
      // 2. Map route handlers
      router.map(routes, {
        actions: {
          // Default locale route
          home(context) {
            return context.render(<HomePage />);
          },
      
          // Localized route
          localizedHome(context) {
            if (!isDeclaredLocale(context.params.locale)) {
              return new Response("Not Found", { status: 404 });
            }
            return context.render(<HomePage />);
          },
        },
      });
      
      context.render acepta un ResponseInit opcional como segundo argumento, ej. context.render(<NotFoundPage />, { status: 404 }). El idioma resuelto permanece accesible desde el manejador como context.intlayer.locale, por ejemplo para construir una respuesta Response.json.

      Por último, expón el router a través de un manejador fetch estándar. El mismo router funciona en Node.js, Bun, Deno y Cloudflare Workers:

      src/server.ts
      import * as http from "node:http";
      import { createRequestListener } from "remix/node-fetch-server";
      import { router } from "./router";
      
      const PORT = Number(process.env.PORT || 3000);
      
      // Node.js
      const server = http.createServer(
        createRequestListener((request) => router.fetch(request))
      );
      
      server.listen(PORT, () => {
        console.log(`Servidor ejecutándose en http://localhost:${PORT}`);
      });
      
      // Bun / Deno / Cloudflare Workers
      export default {
        port: PORT,
        fetch(request: Request) {
          return router.fetch(request);
        },
      };
      
    9. Auditar y autocompletar traducciones

      Intlayer proporciona una CLI para auditar traducciones faltantes y completarlas automáticamente mediante IA:

      bash
      # Auditar traducciones faltantes
      npx intlayer test
      
      # Completar traducciones faltantes mediante IA
      npx intlayer fill
      

    Configuración de TypeScript

    Apunta JSX al runtime remix/ui y asegúrate de que tu tsconfig.json incluya los tipos generados por .intlayer:

    tsconfig.json
    {
      "compilerOptions": {
        "moduleResolution": "Bundler",
        "module": "ESNext",
        "target": "ESNext",
        "jsx": "react-jsx",
        "jsxImportSource": "remix/ui",
        "skipLibCheck": true,
        "strict": true
      },
      "include": ["src/**/*", ".intlayer/**/*.ts"]
    }
    
    jsxImportSource: "remix/ui" es lo que hace que <HomePage /> se resuelva mediante el createElement de Remix en lugar del de React.

    Conclusión

    Con Remix 3 e Intlayer, dispones de una pila tecnológica ligera, tipada y portable que cumple con los estándares web abiertos. Tu aplicación puede escalar sin problemas desde simples páginas de marketing localizadas hasta servicios distribuidos globalmente y renderizados en el edge.