Autor:
    Criação:2026-09-09Última atualização:2026-09-09

    Translate your Remix 3 website using Intlayer | Internationalization (i18n)

    This guide demonstrates how to integrate Intlayer for seamless internationalization in Remix 3 applications with locale-aware routing, type-safe content declarations, safe HTML templates, and cross-runtime support across Node.js, Bun, Deno, and Cloudflare Workers.


    What is Remix 3?

    Remix 3 represents a fundamental architectural shift towards a composable, runtime-agnostic web framework built entirely on web standards. Rather than being coupled to specific bundlers or proprietary server APIs, Remix 3 is distributed as single-purpose, composable packages:

    • remix/fetch-router (or remix/router): Lightweight, standard-compliant routing built on the Fetch API (Request and Response).
    • remix/html-template: Safe HTML template literals with automatic XSS protection and fragment composition.
    • remix/response/html: Response helper utilities for serving HTML with standard HTTP semantics.
    • remix/node-fetch-server: Server adapters for Node.js, while natively supporting Bun, Deno, and edge runtimes.
    • remix/cookie: Cryptographically secure cookie parsing and serialization.

    Combined with Intlayer, you get a complete internationalization system that delivers compile-time safety, automated AI translations, zero-overhead server rendering, and seamless locale routing.


    Table of Contents


    Why Intlayer over alternatives?

    Compared to traditional solutions like i18next or bespoke translation loaders, Intlayer offers an integrated developer experience optimized for modern web architecture:

    Intlayer is built to work seamlessly with web standards (Request, Response, Headers, and URL). It integrates effortlessly into Remix 3's Fetch router through lightweight middleware, extracting locales from URL paths, cookies, or Accept-Language headers without locking you into a specific runtime.

    Say goodbye to loose JSON keys and runtime missing-key crashes. Intlayer enforces TypeScript checks across all declared locales, warning you at build time if a translation is missing or invalid.

    When using Remix 3's server-rendered HTML templates (remix/html-template), only the resolved text for the requested locale is rendered into the output stream. No client hydration bundles or bulky translation catalogs are needed unless explicitly required.

    Intlayer co-locates content declarations (.content.ts) with your route logic, reducing the token context required by Large Language Models (LLMs). Built-in CLI commands like intlayer fill and intlayer test let you automate translations in CI/CD pipelines at the cost of your own AI provider.

    Beyond code-first workflows, Intlayer provides a self-hosted Visual Editor and a Remote CMS allowing non-technical editors, translators, and copywriters to update content without redeploying code.


    Step-by-Step Guide

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

    See the Application Template on GitHub.

    1. Install Dependencies

      Install intlayer and remix (version 3) using your preferred package manager:

      bash
      npm install intlayer remix@next
      
      • intlayer: Core internationalization engine providing configuration management, dictionary declaration (t(), Dictionary), CLI tools, and runtime interpreter.
      • remix: The unified Remix 3 framework package exporting remix/router, remix/routes, remix/html-template, and remix/node-fetch-server.
    2. Configure Intlayer

      Create an intlayer.config.ts in the root of your project to declare your supported languages and internationalization settings:

      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;
      
      For additional configuration settings (such as strict mode or routing storage preferences), refer to the configuration documentation.
    3. Declare Your Multilingual Content

      Declare your localized content in a .content.ts file:

      src/home.content.ts
      import { t, type Dictionary } from "intlayer";
      
      const homeContent = {
        key: "home",
        content: {
          title: t({
            en: "Welcome to Remix 3",
            fr: "Bienvenue sur Remix 3",
            es: "Bienvenido a Remix 3",
          }),
          description: t({
            en: "A composable, web-standard application with native i18n.",
            fr: "Une application composable basée sur les standards web avec i18n native.",
            es: "Una aplicación componible basada en estándares web con i18n nativa.",
          }),
          switchLanguage: t({
            en: "Switch language:",
            fr: "Changer de langue :",
            es: "Cambiar idioma:",
          }),
        },
      } satisfies Dictionary;
      
      export default homeContent;
      
      Intlayer also supports JSON, YAML, and CommonJS declaration formats. See the Content Declaration Documentation.
    4. Build Intlayer Dictionaries

      Compile the dictionary definitions to generate TypeScript types and runtime registries:

      bash
      npx intlayer build
      

      This compiles your content into the .intlayer artifact directory, enabling full TypeScript autocompletion and rapid dictionary lookup.

    5. Implement the Intlayer Middleware

      Remix 3 provides a composable middleware pipeline via createRouter({ middleware: [...] }).

      Create an Intlayer middleware that resolves the locale of each incoming request using:

      1. The URL path prefix via Intlayer's getLocaleFromPath (e.g. /fr or /es).
      2. Intlayer's getLocale helper, which automatically negotiates across storage cookies (INTLAYER_LOCALE), custom headers (x-intlayer-locale), standard Accept-Language headers, and your configured defaultLocale.
      src/middleware/intlayer.ts
      import {
        defaultLocale,
        getCookie,
        getLocale,
        getLocaleFromPath,
        type Locale,
      } from "intlayer";
      import { createContextKey, type Middleware } from "remix/router";
      
      /**
       * Type-safe context key to retrieve the resolved locale from Remix 3 RequestContext.
       */
      export const localeKey = createContextKey<Locale>(defaultLocale);
      
      /**
       * Intlayer middleware for Remix 3.
       *
       * Resolves the request locale following priority:
       * 1. URL path prefix (e.g. `/fr/...`) via `getLocaleFromPath`
       * 2. Storage & headers negotiation via Intlayer `getLocale` (cookie, custom header, Accept-Language negotiation, fallback defaultLocale)
       *
       * Attaches the resolved locale to the Remix 3 RequestContext.
       */
      export const intlayer = (): Middleware => {
        return async (context, next) => {
          // Path detection (/fr/about -> "fr", /about -> undefined)
          const pathLocale = getLocaleFromPath(context.url.pathname);
      
          if (pathLocale) {
            // Attach resolved locale to Remix 3 request context
            context.set(localeKey, pathLocale);
      
            return next();
          }
      
          const storedLocale = await getLocale({
            getHeader: (name) => context.headers.get(name),
            getCookie: (name) =>
              getCookie(name, context.headers.get("cookie") ?? undefined),
          });
      
          // Attach resolved locale to Remix 3 request context
          context.set(localeKey, storedLocale ?? defaultLocale);
      
          return next();
        };
      };
      
    6. Define Type-Safe Routes

      Define your application routes using route() from remix/routes:

      src/routes.ts
      import { route } from "remix/routes";
      
      export const routes = route({
        // Default locale route
        home: "/",
      
        // Localized route with dynamic :locale segment
        localizedHome: "/:locale",
      });
      

      Using route() gives you type-safe URL generation across your application:

      typescript
      routes.home.href(); // "/"
      routes.localizedHome.href({ locale: "fr" }); // "/fr"
      
    7. Render Localized HTML Templates

      Remix 3 uses remix/html-template for safe, auto-escaped HTML generation. Create a view function that extracts the localized dictionary using getIntlayer, sets the <html lang="..." dir="..."> attributes, and displays a language switcher:

      src/views/home.ts
      import { html, type SafeHtml } from "remix/html-template";
      import {
        getIntlayer,
        getHTMLTextDir,
        getLocaleName,
        getLocalizedPath,
        type Locale,
        locales,
      } from "intlayer";
      import { routes } from "../routes";
      
      export const renderHomePage = (locale: Locale): SafeHtml => {
        const home = getIntlayer("home", locale);
      
        return html`
          <!doctype html>
          <html lang="${locale}" dir="${getHTMLTextDir(locale)}">
            <head>
              <meta charset="utf-8" />
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <title>${home.title}</title>
            </head>
            <body>
              <header>
                <nav aria-label="Languages">
                  <span>${home.switchLanguage}</span>
                  ${locales.map((loc) => {
                    const href = getLocalizedPath(routes.home.href(), loc);
                    const isActive = loc === locale;
                    return html`
                      <a
                        href="${href}"
                        class="${isActive ? "active" : ""}"
                        aria-current="${isActive ? "true" : "false"}"
                      >
                        ${getLocaleName(loc, locale)}
                      </a>
                    `;
                  })}
                </nav>
              </header>
              <main>
                <h1>${home.title}</h1>
                <p>${home.description}</p>
              </main>
            </body>
          </html>
        `;
      };
      
    8. Wire Up the Server Application

      Connect your router, middleware, and route actions together in src/server.ts:

      src/server.ts
      import * as http from "node:http";
      import { createRouter } from "remix/router";
      import { createRequestListener } from "remix/node-fetch-server";
      import { createHtmlResponse } from "remix/response/html";
      import { isDeclaredLocale } from "intlayer";
      import { intlayer, localeKey } from "./middleware/intlayer";
      import { routes } from "./routes";
      import { renderHomePage } from "./views/home";
      
      // 1. Initialize router with Intlayer middleware
      export const router = createRouter({
        middleware: [intlayer()],
      });
      
      // 2. Map route handlers
      router.map(routes, {
        actions: {
          // Default locale route
          home(context) {
            const locale = context.get(localeKey);
            return createHtmlResponse(renderHomePage(locale));
          },
      
          // Localized route
          localizedHome(context) {
            if (!isDeclaredLocale(context.params.locale)) {
              return new Response("Not Found", { status: 404 });
            }
            const locale = context.get(localeKey);
            return createHtmlResponse(renderHomePage(locale));
          },
        },
      });
      
      // 3. Start server
      const PORT = Number(process.env.PORT || 3000);
      const server = http.createServer(
        createRequestListener((request) => router.fetch(request))
      );
      
      server.listen(PORT, () => {
        console.log(`Server running at http://localhost:${PORT}`);
      });
      
      export default {
        port: PORT,
        fetch(request: Request) {
          return router.fetch(request);
        },
      };
      
    9. Audit and Auto-Fill Translations

      Intlayer provides a CLI to audit for missing translations and automatically fill them using AI:

      bash
      # Audit missing translations
      npx intlayer test
      
      # Fill missing translations using AI
      npx intlayer fill
      

    TypeScript Configuration

    Ensure that your tsconfig.json includes the generated .intlayer types:

    tsconfig.json
    {
      "compilerOptions": {
        "moduleResolution": "Bundler",
        "module": "ESNext",
        "target": "ESNext",
        "skipLibCheck": true,
        "strict": true
      },
      "include": ["src/**/*", ".intlayer/**/*.ts"]
    }
    

    Conclusion

    With Remix 3 and Intlayer, you have a lean, fully typed, runtime-portable stack that adheres to open web standards. Your application can scale effortlessly from simple localized marketing pages to globally distributed, edge-rendered services.