Author:
    Creation:2026-08-29Last update:2026-08-29

    Intlayer рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдкрдиреЗ htmx рдРрдкреНрд▓рд┐рдХреЗрд╢рди рдХрд╛ рдЕрдиреБрд╡рд╛рдж рдХрд░реЗрдВ | рдЕрдВрддрд░реНрд░рд╛рд╖реНрдЯреНрд░реАрдпрдХрд░рдг (i18n)

    htmx рдЕрдкрдиреА рдХреЛрдИ рд╕рд╛рдордЧреНрд░реА render рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рд╣рд░ рд▓реЗрдмрд▓ рдЬреЛ рдХреЛрдИ рдЖрдЧрдВрддреБрдХ рдкрдврд╝рддрд╛ рд╣реИ, рд╡рд╣ HTML рд╣реИ рдЬреЛ рдЖрдкрдХрд╛ server рддреИрдпрд╛рд░ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рд╣рд░ swap рдПрдХ рдЕрд▓рдЧ HTTP request рд╣реИред рдЗрд╕рд▓рд┐рдП, htmx рдРрдк рдХреЛ internationalize рдХрд░рдирд╛ рдПрдХ server concern рд╣реИ: locale рдХреЛ рд╣рд░ request рдкрд░ resolve рдХрд░рдирд╛ рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рд╣рд░ fragment рдХреЛ рдЙрд╕ locale рдореЗрдВ render рдХрд░рдирд╛ рд╣реЛрддрд╛ рд╣реИред

    Intlayer рдЗрд╕реЗ рдЕрдкрдиреЗ backend integrations рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдХрд╡рд░ рдХрд░рддрд╛ рд╣реИ, рдЬреЛ рд╣рд░ request рдкрд░ locale рдХреЛ detect рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдЖрдкрдХреА рдШреЛрд╖рд┐рдд content рдХреЛ рдЙрд╕ handler рдХреЗ рд▓рд┐рдП expose рдХрд░рддреЗ рд╣реИрдВ рдЬреЛ HTML рдмрдирд╛рддрд╛ рд╣реИред

    рд╡рд┐рд╖рдп рд╕реВрдЪреА

    htmx рдРрдк рдореЗрдВ i18n рдХреЗ рддреАрди рдирд┐рдпрдо

    рдПрдХ рднреА рдкреГрд╖реНрда рджрд░реНрдЬрдиреЛрдВ swaps рдХреЛ рдЯреНрд░рд┐рдЧрд░ рдХрд░ рд╕рдХрддрд╛ рд╣реИред рдкреНрд░рддреНрдпреЗрдХ рдПрдХ рддрд╛рдЬрд╝реА request рд╣реИ рдЬрд┐рд╕реЗ рдЙрд╕ рдкреГрд╖реНрда рдХреА рдХреЛрдИ рд╕реНрдореГрддрд┐ рдирд╣реАрдВ рд╣реИ рдЬреЛ рдЗрд╕реЗ рдЬрд╛рд░реА рдХрд░рддрд╛ рд╣реИред рдпрджрд┐ locale рдкреНрд░рд╛рд░рдВрднрд┐рдХ render рдХреЗ рджреМрд░рд╛рди рд╕реЗрдЯ рдХрд┐рдП рдЧрдП variable рдореЗрдВ рд░рд╣рддрд╛ рд╣реИ, рддреЛ рдЗрд╕рдХреЗ рдмрд╛рдж рдХрд╛ рдкреНрд░рддреНрдпреЗрдХ fragment рдбрд┐рдлрд╝реЙрд▓реНрдЯ рднрд╛рд╖рд╛ рдореЗрдВ рд╡рд╛рдкрд╕ рдЖрддрд╛ рд╣реИред

    Intlayer middleware request рд╕реЗ рд╣реА locale рдХреЛ resolve рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдорд┐рдирдЯ рджрд╕ рдкрд░ рджрд┐рдпрд╛ рдЧрдпрд╛ рдПрдХ fragment рдЙрд╕реА рднрд╛рд╖рд╛ рдореЗрдВ рдЙрддреНрддрд░ рджреЗрддрд╛ рд╣реИ рдЬреИрд╕реЗ рдорд┐рдирдЯ рд╢реВрдиреНрдп рдкрд░ рджрд┐рдпрд╛ рдЧрдпрд╛ рдкреГрд╖реНрдаред

    htmx рдХреЗ рд╕рд╛рде рджреЛ carriers рдХрд╛рдо рдХрд░рддреЗ рд╣реИрдВред рдПрдХ cookie (INTLAYER_LOCALE) рдХреЛ browser рджреНрд╡рд╛рд░рд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рд╣рд░ request рдкрд░ рднреЗрдЬрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ htmx рднреА рд╢рд╛рдорд┐рд▓ рд╣реИрдВред рдПрдХ header (x-intlayer-locale) рдХреЛ htmx requests рдХреЗ рд╕рд╛рде hx-headers attribute рдХреЗ рд╕рд╛рде attach рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рджреЛрдиреЛрдВ рдХреЛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рдкрдврд╝рд╛ рдЬрд╛рддрд╛ рд╣реИред

    рдПрдХ рдЕрдиреБрд╡рд╛рджрд┐рдд рдорд╛рди рдЬреЛ рдПрдХ fragment рдореЗрдВ рдкреНрд░рдХреНрд╖реЗрдкрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ, markup рд╣реИред рдЗрд╕реЗ escape рдХрд░реЗрдВ, рдмрд┐рд▓реНрдХреБрд▓ рд╡реИрд╕реЗ рд╣реА рдЬреИрд╕реЗ рдЖрдк рдХрд┐рд╕реА рдЕрдиреНрдп dynamic value рдХрд░реЗрдВрдЧреЗ, рддрд╛рдХрд┐ рдПрдХ рдЕрдиреБрд╡рд╛рдж рдЬрд┐рд╕рдореЗрдВ < рд╣реИ, рд╡рд╣ document рдХреЛ рддреЛрдбрд╝ рди рд╕рдХреЗ рдЬрд┐рд╕рдореЗрдВ рдЗрд╕реЗ рд╕реНрд╡реИрдк рдХрд┐рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИред


    Step-by-Step Guide

    ide.intlayer.org

    рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛ GitHub рдкрд░ рджреЗрдЦреЗрдВред

    1. рдирд┐рд░реНрднрд░рддрд╛рдПрдВ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ

      intlayer рдкреНрд▓рд╕ рдЕрдкрдиреЗ рд╕рд░реНрд╡рд░ рдХреЗ рд▓рд┐рдП рдЗрдВрдЯреАрдЧреНрд░реЗрд╢рди рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред

      bash
      npm install intlayer express-intlayer cookie-parser
      
      bash
      npm install intlayer fastify-intlayer @fastify/cookie @fastify/formbody
      
      bash
      npm install intlayer hono-intlayer
      
      bash
      npm install intlayer elysia-intlayer
      
      bash
      bun add intlayer elysia-intlayer
      
      Express рдФрд░ Fastify рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ cookie parsers рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ locale cookie рдХреЛ рдкрдврд╝рддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдЙрдиреНрд╣реЗрдВ рд╕рд╛рде рдореЗрдВ install рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред Hono рдФрд░ Elysia рдиреЗрдЯрд┐рд╡рд▓реА cookies рдХреЛ parse рдХрд░рддреЗ рд╣реИрдВред

      htmx рд╕реНрд╡рдпрдВ рдПрдХ рдПрдХрд▓ script tag рд╣реИ, рдЬреЛ step 4 рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рддрд╛ рд╣реИред

    2. рдЕрдкрдиреА project рдХрд╛ Configuration

      рдЕрдкрдиреА project root рдкрд░ рдПрдХ intlayer.config.ts рдмрдирд╛рдПрдВ:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH, Locales.ARABIC],
          defaultLocale: Locales.ENGLISH,
        },
      };
      
      export default config;
      
      рдкреВрд░реНрдг рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреА рд╕реВрдЪреА рдХреЗ рд▓рд┐рдП, рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рджреЗрдЦреЗрдВред
    3. рдЕрдкрдиреА рд╕рд╛рдордЧреНрд░реА рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ

      рд╕рднреА рд▓реЗрдмрд▓ рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ рдЬреЛ рд╕рд░реНрд╡рд░ рд░реЗрдВрдбрд░ рдХрд░реЗрдЧрд╛, рдЬрд┐рдирдореЗрдВ рд╡реЗ рднреА рд╢рд╛рдорд┐рд▓ рд╣реИрдВ рдЬреЛ рдХреЗрд╡рд▓ рдПрдХ fragment рдХреЗ рдЕрдВрджрд░ рджрд┐рдЦрд╛рдИ рджреЗрддреЗ рд╣реИрдВ:

      src/app.content.ts
      import { insert, t, type Dictionary } from "intlayer";
      
      const appContent = {
        key: "app",
        content: {
          pageTitle: "Intlayer + htmx",
      
          localeLabel: t({
            hi: "рднрд╛рд╖рд╛",
            en: "Language",
            fr: "Langue",
            es: "Idioma",
            ar: "╪з┘Д┘Д╪║╪й",
          }),
      
          cartSummary: insert(
            t({
              hi: "рдЖрдкрдХреА рдХрд╛рд░реНрдЯ рдореЗрдВ рдЖрдЗрдЯрдо: {{count}}",
              en: "Items in your cart: {{count}}",
              fr: "Articles dans votre panier : {{count}}",
              es: "Art├нculos en tu carrito: {{count}}",
              ar: "╪з┘Д┘Е┘Ж╪к╪м╪з╪к ┘Б┘К ╪│┘Д╪к┘Г: {{count}}",
            })
          ),
      
          addItem: t({
            hi: "рдПрдХ рдЖрдЗрдЯрдо рдЬреЛрдбрд╝реЗрдВ",
            en: "Add an item",
            fr: "Ajouter un article",
            es: "A├▒adir un art├нculo",
            ar: "╪г╪╢┘Б ┘Е┘Ж╪к╪м┘Л╪з",
          }),
        },
      } satisfies Dictionary;
      
      export default appContent;
      
      Content declarations contentDir рдХреЗ рдЕрдВрддрд░реНрдЧрдд рдХрд┐рд╕реА рднреА рд╕реНрдерд╛рди рдкрд░ рд░рд╣ рд╕рдХрддреЗ рд╣реИрдВ (рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ ./src) рдФрд░ .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml} рд╕реЗ рдореЗрд▓ рдЦрд╛рддреЗ рд╣реИрдВред content declaration documentation рджреЗрдЦреЗрдВред
    4. Intlayer middleware рдХреЛ рд░рдЬрд┐рд╕реНрдЯрд░ рдХрд░реЗрдВ

      middleware рдкреНрд░рддреНрдпреЗрдХ request рдХреА locale рдХреЛ resolve рдХрд░рддрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдЖрдкрдХреЗ handlers рдореЗрдВ expose рдХрд░рддрд╛ рд╣реИред

      src/index.ts
      import cookieParser from "cookie-parser";
      import express from "express";
      import { intlayer } from "express-intlayer";
      
      const app = express();
      
      // cookie parser рдХреЛ рдкрд╣рд▓реЗ рдЪрд▓рдирд╛ рд╣реЛрдЧрд╛: `express-intlayer` locale рдХреЛ
      // `req.cookies` рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкрдврд╝рддрд╛ рд╣реИред
      app.use(cookieParser());
      app.use(express.urlencoded({ extended: false }));
      app.use(intlayer());
      

      resolve рдХреА рдЧрдИ locale res.locals.locale рдкрд░ рд╣реИред

      src/index.ts
      import cookie from "@fastify/cookie";
      import formbody from "@fastify/formbody";
      import Fastify from "fastify";
      import { intlayer } from "fastify-intlayer";
      
      const fastify = Fastify();
      
      await fastify.register(cookie);
      await fastify.register(formbody);
      await fastify.register(intlayer);
      

      рд╕рдорд╛рдзрд╛рдирд┐рдд locale req.intlayer.locale рдкрд░ рд╣реИред

      src/index.ts
      import { Hono } from "hono";
      import { intlayer } from "hono-intlayer";
      
      const app = new Hono();
      
      app.use("*", intlayer());
      

      рд╕рдорд╛рдзрд╛рдирд┐рдд locale c.get("locale") рд╣реИред

      src/index.ts
      import { Elysia } from "elysia";
      import { intlayer } from "elysia-intlayer";
      
      const app = new Elysia().use(intlayer());
      

      The resolved locale is intlayer!.locale on the route context.

      рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ locale рдХреЛ INTLAYER_LOCALE cookie рд╕реЗ рд▓рд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдлрд┐рд░ x-intlayer-locale header рд╕реЗ, рдлрд┐рд░ Accept-Language negotiation рд╕реЗред

    5. рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ locale рдХреЗ рд╕рд╛рде fragments рдХреЛ render рдХрд░реЗрдВ

      рдЕрдкрдиреЗ fragment renderers рдХреЛ рдПрдХ locale рдХреЗ pure functions рдХреЗ рд░реВрдк рдореЗрдВ рд▓рд┐рдЦреЗрдВ, рдФрд░ middleware рджреНрд╡рд╛рд░рд╛ resolve рдХрд┐рдП рдЧрдП locale рдХреЛ рдкрд╛рд╕ рдХрд░реЗрдВред рдЗрд╕реЗ explicitly рдкрд╛рд╕ рдХрд░рдиреЗ рд╕реЗ рдПрдХ fragment рдЙрд╕ request рд╕реЗ рдЬреБрдбрд╝рд╛ рд░рд╣рддрд╛ рд╣реИ рдЬрд┐рд╕рдиреЗ рдЗрд╕реЗ рдорд╛рдВрдЧрд╛ рдерд╛, рдЖрдк рдХрд┐рд╕реА рднреА server рдкрд░ рд╣реЛрдВред

      src/views.ts
      import { currency, getIntlayer, type Locale } from "intlayer";
      
      const HTML_ENTITIES: Record<string, string> = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#39;",
      };
      
      /** рдорд╛рд░реНрдХрдЕрдк рд╕реЗ рдмрд╛рд╣рд░ рди рдирд┐рдХрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрдиреБрд╡рд╛рджрд┐рдд рдорд╛рди рдХреЛ рдПрд╕реНрдХреЗрдк рдХрд░рддрд╛ рд╣реИред */
      const escapeHtml = (value: string): string =>
        value.replace(
          /[&<>"']/g,
          (character) => HTML_ENTITIES[character] ?? character
        );
      
      export const renderCart = (locale: Locale, itemCount: number): string => {
        const content = getIntlayer("app", locale);
      
        return `<section id="cart">
        <p>${escapeHtml(String(content.cartSummary({ count: itemCount })))}</p>
        <p>${escapeHtml(currency(itemCount * 12.5, { locale, currency: "EUR" }))}</p>
        <button
          hx-post="/cart/items"
          hx-vals='{"itemCount": ${itemCount}}'
          hx-target="#cart"
          hx-swap="outerHTML"
        >${escapeHtml(String(content.addItem))}</button>
      </section>`;
      };
      

      рдЗрд╕реЗ рдПрдХ route рд╕реЗ serve рдХрд░реЗрдВ:

      src/index.ts
      app.post("/cart/items", (req, res) => {
        // itemCount рдХреЛ request body рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ, рдбрд┐рдлрд╝реЙрд▓реНрдЯ 0 рд╣реИ, рдлрд┐рд░ 1 рдЬреЛрдбрд╝реЗрдВ
        const itemCount = Number(req.body?.itemCount ?? 0) + 1;
      
        // HTML рдкреНрд░рдХрд╛рд░ рд╕реЗрдЯ рдХрд░реЗрдВ рдФрд░ renderCart рдХрд╛ рдкрд░рд┐рдгрд╛рдо рднреЗрдЬреЗрдВ
        res.type("html").send(renderCart(res.locals.locale, itemCount));
      });
      
      src/index.ts
      fastify.post("/cart/items", async (req, reply) => {
        // itemCount рдХреЛ request body рд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ, рдбрд┐рдлрд╝реЙрд▓реНрдЯ 0 рд╣реИ, рдлрд┐рд░ 1 рдЬреЛрдбрд╝реЗрдВ
        const itemCount =
          Number((req.body as { itemCount?: string })?.itemCount ?? 0) + 1;
      
        // text/html content-type рдХреЗ рд╕рд╛рде renderCart рдХрд╛ рдкрд░рд┐рдгрд╛рдо рднреЗрдЬреЗрдВ
        return reply
          .type("text/html")
          .send(renderCart(req.intlayer.locale, itemCount));
      });
      
      src/index.ts
      app.post("/cart/items", async (c) => {
        const body = await c.req.parseBody();
        const itemCount = Number(body["itemCount"] ?? 0) + 1;
      
        return c.html(renderCart(c.get("locale"), itemCount));
      });
      
      src/index.ts
      app.post("/cart/items", ({ body, intlayer }) => {
        const itemCount =
          Number((body as { itemCount?: string })?.itemCount ?? 0) + 1;
      
        return new Response(renderCart(intlayer!.locale, itemCount), {
          headers: { "content-type": "text/html" },
        });
      });
      

      рдПрдХ рд╣реА рдлреНрд░реИрдЧрдореЗрдВрдЯ рдЕрдм рдлреНрд░рд╛рдВрд╕реАрд╕реА рдореЗрдВ рдЙрддреНрддрд░ рджреЗрддрд╛ рд╣реИ рдЬрд┐рд╕ рд╡рд┐рдЬрд╝рд┐рдЯрд░ рдХрд╛ рдХреБрдХреА fr рдХрд╣рддрд╛ рд╣реИ, рдФрд░ рдЕрд░рдмреА рдореЗрдВ рдЬрд┐рд╕рдХрд╛ рдХреБрдХреА ar рдХрд╣рддрд╛ рд╣реИ, рдХреЙрд▓рд┐рдВрдЧ рдорд╛рд░реНрдХрдЕрдк рдореЗрдВ рдХреЛрдИ рдмрджрд▓рд╛рд╡ рдирд╣реАрдВред

    6. рдкрд╣рд▓реЗ рдкреГрд╖реНрда рдХреЛ рд╕рд░реНрд╡ рдХрд░реЗрдВ

      <body> рдХреЛ рдЕрдкрдиреЗ рдЖрдк рдХреЛ рд░реЗрдВрдбрд░ рдХрд░реЗрдВ, рддрд╛рдХрд┐ рдЪрд░рдг 7 рдореЗрдВ рд▓реЛрдХреЗрд▓ рд╕реНрд╡рд┐рдЪрд░ рдЗрд╕реЗ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рд╕реНрд╡реИрдк рдХрд░ рд╕рдХреЗ, рдлрд┐рд░ рдЗрд╕реЗ рдЙрд╕ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдореЗрдВ рд▓рдкреЗрдЯреЗрдВ рдЬреЛ htmx рд▓реЛрдб рдХрд░рддрд╛ рд╣реИ:

      src/views.ts
      import { getHTMLTextDir, getIntlayer, type Locale } from "intlayer";
      
      export const renderBody = (locale: Locale, itemCount: number): string => {
        const content = getIntlayer("app", locale);
      
        return `<body lang="${locale}" dir="${getHTMLTextDir(locale)}">
        <main>
          <h1>${escapeHtml(String(content.pageTitle))}</h1>
          ${renderLocaleSwitcher(locale)}
          ${renderCart(locale, itemCount)}
        </main>
      </body>`;
      };
      
      export const renderPage = (locale: Locale, itemCount: number): string =>
        `<!doctype html>
      <html lang="${locale}" dir="${getHTMLTextDir(locale)}">
      <head>
        <meta charset="utf-8" />
        <title>${escapeHtml(String(getIntlayer("app", locale).pageTitle))}</title>
        <script src="https://unpkg.com/htmx.org@2.0.4"></script>
      </head>
      ${renderBody(locale, itemCount)}
      </html>`;
      

      getHTMLTextDir locale рдХреЗ рд▓рд┐рдП ltr, rtl рдпрд╛ auto return рдХрд░рддрд╛ рд╣реИ, рдЬреЛ рдЕрд░рдмреА рдФрд░ рд╣рд┐рдмреНрд░реВ рдХреЛ рд╕рд╣реА рддрд░реАрдХреЗ рд╕реЗ рд▓реЗрдЖрдЙрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИред

    7. рднрд╛рд╖рд╛ рд╕реНрд╡рд┐рдЪ рдХрд░реЗрдВ

      рднрд╛рд╖рд╛ рд╕реНрд╡рд┐рдЪ рдХрд░рдирд╛ рдХрд┐рд╕реА рдЕрдиреНрдп request рдЬреИрд╕реЗ рд╣реА рд╣реИред рд╕рд░реНрд╡рд░ middleware рдЬреЛ рдкрдврд╝рддрд╛ рд╣реИ рдЙрд╕ cookie рдореЗрдВ рдЪреБрдирд╛рд╡ рдХреЛ рд╕реНрдЯреЛрд░ рдХрд░рддрд╛ рд╣реИ, рдлрд┐рд░ рдирдП locale рдореЗрдВ рдлрд┐рд░ рд╕реЗ render рдХрд┐рдП рдЧрдП рдкреГрд╖реНрда рдХреЛ return рдХрд░рддрд╛ рд╣реИред

      <select> рдХреЛ рд░реЗрдВрдбрд░ рдХрд░реЗрдВ рдЬреЛ рд╕реНрд╡рдпрдВ рдХреЛ рдкреЛрд╕реНрдЯ рдХрд░реЗ рдФрд░ рдкреВрд░реЗ <body> рдХреЛ рд╕реНрд╡реИрдк рдХрд░реЗ, рддрд╛рдХрд┐ рдЖрдкрдХреЗ рдлреНрд░реИрдЧрдореЗрдВрдЯреНрд╕ рдХреЗ рдЪрд╛рд░реЛрдВ рдУрд░ рд╕реНрдерд┐рд░ рд▓реЗрдмрд▓ рднреА рдмрджрд▓ рдЬрд╛рдПрдВ:

      src/views.ts
      import { getIntlayer, getLocaleName, type Locale, locales } from "intlayer";
      
      const renderLocaleSwitcher = (locale: Locale): string => {
        // рд╡рд░реНрддрдорд╛рди locale рдХреЗ рд▓рд┐рдП content рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ
        const content = getIntlayer("app", locale);
      
        // рд╕рднреА рдЙрдкрд▓рдмреНрдз locales рдХреЗ рд▓рд┐рдП рд╡рд┐рдХрд▓реНрдк рддреИрдпрд╛рд░ рдХрд░реЗрдВ
        const options = locales
          .map(
            (availableLocale: Locale) =>
              `<option value="${availableLocale}"${availableLocale === locale ? " selected" : ""}>${escapeHtml(getLocaleName(availableLocale, locale))}</option>`
          )
          .join("");
      
        // locale switcher form рдХреЛ HTML рдХреЗ рд░реВрдк рдореЗрдВ рд░рд┐рдЯрд░реНрди рдХрд░реЗрдВ
        return `<form>
        <label for="locale">${escapeHtml(String(content.localeLabel))}</label>
        <select
          id="locale"
          name="locale"
          hx-post="/locale"
          hx-trigger="change"
          hx-target="body"
          hx-swap="outerHTML"
        >${options}</select>
      </form>`;
      };
      
      getLocaleName(availableLocale, locale) рдкреНрд░рддреНрдпреЗрдХ рднрд╛рд╖рд╛ рдХреЛ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рднрд╛рд╖рд╛ рдореЗрдВ рд▓рд┐рдЦрддрд╛ рд╣реИред рдЗрд╕рдХреЗ рдмрдЬрд╛рдп рдкреНрд░рддреНрдпреЗрдХ рдХреЛ рдЕрдкрдиреА рднрд╛рд╖рд╛ рдореЗрдВ рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдИ рджреВрд╕рд░рд╛ argument рдкрд╛рд╕ рди рдХрд░реЗрдВред

      post рдХреЛ handle рдХрд░реЗрдВ value рдХреЛ validate рдХрд░рдХреЗ, cookie рд╕реЗрдЯ рдХрд░рдХреЗ, рдФрд░ рдирдпрд╛ body return рдХрд░рдХреЗ:

      src/index.ts
      import { isDeclaredLocale } from "intlayer";
      
      app.post("/locale", (req, res) => {
        const requestedLocale = String(req.body?.locale);
      
        if (!isDeclaredLocale(requestedLocale)) {
          res.status(400).send("Unknown locale");
          return;
        }
      
        res.cookie("INTLAYER_LOCALE", requestedLocale, {
          sameSite: "lax",
          path: "/",
        });
        res.type("html").send(renderBody(requestedLocale, 0));
      });
      
      src/index.ts
      import { isDeclaredLocale } from "intlayer";
      
      fastify.post("/locale", async (req, reply) => {
        const requestedLocale = String((req.body as { locale?: string })?.locale);
      
        // рдЬрд╛рдВрдЪреЗрдВ рдХрд┐ рдХреНрдпрд╛ locale рдШреЛрд╖рд┐рдд рд╣реИ
        if (!isDeclaredLocale(requestedLocale)) {
          return reply.status(400).send("Unknown locale");
        }
      
        return reply
          .setCookie("INTLAYER_LOCALE", requestedLocale, {
            sameSite: "lax",
            path: "/",
          })
          .type("text/html")
          .send(renderBody(requestedLocale, 0));
      });
      
      src/index.ts
      import { setCookie } from "hono/cookie";
      import { isDeclaredLocale } from "intlayer";
      
      app.post("/locale", async (c) => {
        // рдЕрдиреБрд░реЛрдз рдмреЙрдбреА рдХреЛ рдкрд╛рд░реНрд╕ рдХрд░реЗрдВ
        const body = await c.req.parseBody();
        // рдЕрдиреБрд░реЛрдзрд┐рдд locale рдХреЛ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдореЗрдВ рдХрдирд╡рд░реНрдЯ рдХрд░реЗрдВ
        const requestedLocale = String(body["locale"]);
      
        // рдЬрд╛рдВрдЪреЗрдВ рдХрд┐ рдХреНрдпрд╛ locale рдШреЛрд╖рд┐рдд рд╣реИ
        if (!isDeclaredLocale(requestedLocale)) {
          return c.text("Unknown locale", 400);
        }
      
        // INTLAYER_LOCALE рдХреБрдХреА рд╕реЗрдЯ рдХрд░реЗрдВ
        setCookie(c, "INTLAYER_LOCALE", requestedLocale, {
          sameSite: "Lax",
          path: "/",
        });
        // HTML рд░реЗрдВрдбрд░ рдХрд░рдХреЗ рднреЗрдЬреЗрдВ
        return c.html(renderBody(requestedLocale, 0));
      });
      
      src/index.ts
      import { isDeclaredLocale } from "intlayer";
      
      app.post("/locale", ({ body, cookie, status }) => {
        const requestedLocale = String((body as { locale?: string })?.locale);
      
        if (!isDeclaredLocale(requestedLocale)) {
          return status(400, "Unknown locale");
        }
      
        cookie["INTLAYER_LOCALE"]!.set({
          value: requestedLocale,
          sameSite: "lax",
          path: "/",
        });
      
        return new Response(renderBody(requestedLocale, 0), {
          headers: { "content-type": "text/html" },
        });
      });
      
      isDeclaredLocale рдЖрдкрдХреА рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХреА рдЧрдИ locales рдореЗрдВ рд╕реЗ рдПрдХ рдХреЛ рдПрдХ рдЖрд░реНрдмрд┐рдЯреНрд░реЗрд░реА string рдХреЛ narrow рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдПрдХ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд value рдХрднреА рднреА рдЖрдкрдХреЗ renderers рддрдХ рдирд╣реАрдВ рдкрд╣реБрдВрдЪрддрд╛ рд╣реИред
    8. swap рдХреЗ рдмрд╛рдж lang рдФрд░ dir рдХреЛ рд╕рд┐рдВрдХ рдореЗрдВ рд░рдЦреЗрдВ

      рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдПрдХ swap <body> рдХреЛ replace рдХрд░ рд╕рдХрддрд╛ рд╣реИ, рдХрднреА рднреА рдЗрд╕рдХреЗ рдЪрд╛рд░реЛрдВ рдУрд░ <html> рдХреЛ рдирд╣реАрдВред Swapped body рдкрд░ lang рдФрд░ dir рдХреЛ render рдХрд░реЗрдВ рдФрд░ рдЙрдиреНрд╣реЗрдВ head рд╕реЗ рдПрдХ рдмрд╛рд░ root element рдкрд░ рд╡рд╛рдкрд╕ рдХреЙрдкреА рдХрд░реЗрдВ:

      src/views.ts
      <script>
        document.addEventListener("htmx:afterSwap", () => {
          document.documentElement.lang = document.body.lang;
          document.documentElement.dir = document.body.dir;
        });
      </script>
      

      рдЗрд╕рдХреЗ рдмрд┐рдирд╛, рдЕрд░рдмреА рдореЗрдВ switch рдХрд░рдиреЗ рд╕реЗ body рдХреЗ рдЕрдВрджрд░ рджрд╛рдПрдВ рд╕реЗ рдмрд╛рдПрдВ render рд╣реЛрддрд╛ рд╣реИ рдЬрдмрдХрд┐ document рдЕрднреА рднреА рдкрд┐рдЫрд▓реА рднрд╛рд╖рд╛ рдХреЛ assistive technology рдФрд░ crawlers рдХреЛ advertise рдХрд░рддрд╛ рд╣реИред

    9. рд╡реИрдХрд▓реНрдкрд┐рдХ

      рдпрджрд┐ рдХреБрдХреА рдЖрдкрдХреЗ рд▓рд┐рдП рдЙрдкрдпреБрдХреНрдд рдирд╣реАрдВ рд╣реИ, рддреЛ hx-headers рдХреЗ рд╕рд╛рде рдПрдХ ancestor element рдкрд░ рд╣рд░ htmx request рдореЗрдВ locale рдХреЛ attach рдХрд░реЗрдВред Descendants рдЗрд╕реЗ inherit рдХрд░рддреЗ рд╣реИрдВ:

      html
      <body hx-headers='{"x-intlayer-locale": "fr"}'>
        ...
      </body>
      

      Middleware рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ x-intlayer-locale рдХреЛ рдкрдврд╝рддрд╛ рд╣реИред рдЖрдк рдЕрдкрдиреЗ configuration рдореЗрдВ рджреЛрдиреЛрдВ carriers рдХреЛ rename рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      // рдЕрдкрдиреЗ configuration рдХреЗ рд▓рд┐рдП constant
      const config: IntlayerConfig = {
        // ... рдЕрдиреНрдп configuration рд╡рд┐рдХрд▓реНрдк
        routing: {
          storage: [
            { type: "header", name: "my-locale-header" },
            { type: "cookie", name: "my-locale-cookie" },
          ],
        },
      };
      
      export default config;
      

    TypeScript рдХреЛ Configure рдХрд░реЗрдВ

    рдСрдЯреЛрдЬреЗрдирд░реЗрдЯреЗрдб рдЯрд╛рдЗрдкреНрд╕ рдХреЛ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВ рддрд╛рдХрд┐ рдПрдХ рдЕрдШреЛрд╖рд┐рдд рдХреБрдВрдЬреА рд░рдирдЯрд╛рдЗрдо рдкрд░ рдЦрд╛рд▓реА рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЗ рдмрдЬрд╛рдп рдПрдХ compile error рд╣реЛред

    tsconfig.json
    {
      // ... рдЖрдкрдХреЗ рдореМрдЬреВрджрд╛ TypeScript рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди
      "include": [
        // ... рдЖрдкрдХреЗ рдореМрдЬреВрджрд╛ TypeScript рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди
        ".intlayer/**/*.ts", // рдСрдЯреЛ-рдЬреЗрдирд░реЗрдЯреЗрдб рдЯрд╛рдЗрдкреНрд╕ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВ
      ],
    }
    

    Git рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди

    Intlayer рджреНрд╡рд╛рд░рд╛ рдЬреЗрдирд░реЗрдЯ рдХреА рдЧрдИ рдлрд╛рдЗрд▓реЛрдВ рдХреЛ рдЕрдирджреЗрдЦрд╛ рдХрд░рдирд╛ рдЕрдиреБрд╢рдВрд╕рд┐рдд рд╣реИ:

    .gitignore
    # Intlayer рджреНрд╡рд╛рд░рд╛ рдЬреЗрдирд░реЗрдЯ рдХреА рдЧрдИ рдлрд╛рдЗрд▓реЛрдВ рдХреЛ рдЕрдирджреЗрдЦрд╛ рдХрд░реЗрдВ
    .intlayer
    

    VS Code Extension

    Intlayer рдХреЗ рд╕рд╛рде рдЕрдкрдиреЗ development experience рдХреЛ рдмреЗрд╣рддрд░ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк official Intlayer VS Code Extension рдХреЛ install рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

    VS Code Marketplace рд╕реЗ Install рдХрд░реЗрдВ

    рдпрд╣ extension рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкреНрд░рджрд╛рди рдХрд░рддрд╛ рд╣реИ:

    • Autocompletion рдЕрдиреБрд╡рд╛рдж keys рдХреЗ рд▓рд┐рдПред
    • Real-time error detection рд▓рд╛рдкрддрд╛ рдЕрдиреБрд╡рд╛рджреЛрдВ рдХреЗ рд▓рд┐рдПред
    • Inline previews рдЕрдиреБрд╡рд╛рджрд┐рдд content рдХрд╛ред
    • Quick actions рдЖрд╕рд╛рдиреА рд╕реЗ рдЕрдиреБрд╡рд╛рдж рдмрдирд╛рдиреЗ рдФрд░ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред

    Extension рдХрд╛ рдЙрдкрдпреЛрдЧ рдХреИрд╕реЗ рдХрд░реЗрдВ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рд╡рд┐рд╡рд░рдг рдХреЗ рд▓рд┐рдП, Intlayer VS Code Extension documentation рджреЗрдЦреЗрдВред


    рдЖрдЧреЗ рдмрдврд╝реЗрдВ

    рдЖрдЧреЗ рдмрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк рдЕрдкрдиреЗ content рдХреЛ CMS рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ externalize рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдЕрдиреБрд╡рд╛рджрдХ deployment рдХреЗ рдмрд┐рдирд╛ copy рдмрджрд▓ рд╕рдХрддреЗ рд╣реИрдВред

    рдЕрдХреНрд╕рд░ рдкреВрдЫреЗ рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рдкреНрд░рд╢реНрди

    рдХреНрдпреЛрдВрдХрд┐ рдлреНрд░реИрдЧрдореЗрдВрдЯ рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ рдореЗрдВ рдХреЛрдИ рд▓реЛрдХреЗрд▓ рдирд╣реАрдВ рдерд╛ред htmx рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ рдЙрди рдкреЗрдЬ рд╕реЗ рд╕реНрд╡рддрдВрддреНрд░ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЛрдВрдиреЗ рдЙрдиреНрд╣реЗрдВ рдЬрд╛рд░реА рдХрд┐рдпрд╛, рдЗрд╕рд▓рд┐рдП рд▓реЛрдХреЗрд▓ рдХреЛ рдкреНрд░рддреНрдпреЗрдХ рдкрд░ travel рдХрд░рдирд╛ рдкрдбрд╝рддрд╛ рд╣реИ, INTLAYER_LOCALE cookie рдпрд╛ hx-headers рдХреЗ рд╕рд╛рде рд╕реЗрдЯ рдХрд┐рдП рдЧрдП x-intlayer-locale рд╣реЗрдбрд░ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗред рдЬрд╛рдВрдЪреЗрдВ рдХрд┐ Express рдФрд░ Fastify рдкрд░ Intlayer middleware рд╕реЗ рдкрд╣рд▓реЗ cookie parser рдЪрд▓рддрд╛ рд╣реИ, рдЕрдиреНрдпрдерд╛ cookie рдХрднреА рдирд╣реАрдВ рдкрдврд╝реА рдЬрд╛рддреА рд╣реИ рдФрд░ рд╣рд░ рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ Accept-Language рдкрд░ fallback рд╣реЛ рдЬрд╛рддреА рд╣реИред

    рдЗрд╕реЗ рдкрд╛рд╕ рдХрд░реЗрдВред integrations resolved locale рдХреЛ expose рдХрд░рддреЗ рд╣реИрдВ (res.locals.locale, req.intlayer.locale, c.get("locale"), intlayer!.locale), рдФрд░ рдЗрд╕реЗ getIntlayer рдХреЛ hand рдХрд░рдирд╛ рдкреНрд░рддреНрдпреЗрдХ renderer рдХреЛ рдПрдХ locale рдХрд╛ pure function рдмрдирд╛рддрд╛ рд╣реИред рдпрд╣ test рдХрд░рдирд╛ рдЖрд╕рд╛рди рд╣реИ, рдФрд░ рдпрд╣ рдЖрдкрдХреЗ fragment renderers рдХреЛ portable рд░рдЦрддрд╛ рд╣реИ рдпрджрд┐ рдЖрдк server рдмрджрд▓рддреЗ рд╣реИрдВред

    рдирд╣реАрдВред рд╕рдм рдХреБрдЫ рдЬреЛ рдПрдХ visitor рджреЗрдЦрддрд╛ рд╣реИ server рджреНрд╡рд╛рд░рд╛ produced рд╣реЛрддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП browser рдореЗрдВ translate рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рдирд╣реАрдВ рд╣реИред рдпрд╣ рднреА рд╣реИ рдХрд┐ рдХреНрдпреЛрдВ рдПрдХ htmx app рдореЗрдВ i18n рдХреА page weight cost рд▓рдЧрднрдЧ zero рд╣реИ: рдХреЛрдИ рднреА catalog рдХрднреА client рдХреЛ shipped рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИред

    рдЕрдкрдиреЗ рдкреГрд╖реНрдареЛрдВ рдХреЛ locale prefix рдХреЗ рддрд╣рдд рдкрд░реЛрд╕реЗрдВ (/fr/cart) рдФрд░ рдЕрдкрдиреЗ route handler рдореЗрдВ path рд╕реЗ locale рдХреЛ рдкрдврд╝реЗрдВ, рдмрдЬрд╛рдп cookie рд╕реЗ, рдкреВрд░реНрдг рдкреГрд╖реНрда render рдХреЗ рд▓рд┐рдПред Fragments cookie рдпрд╛ header рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд░рд╣ рд╕рдХрддреЗ рд╣реИрдВред Routing options рдХреЗ рд▓рд┐рдП configuration рдФрд░ custom URL rewrites рджреЗрдЦреЗрдВред

    getHTMLTextDir(locale) ltr, rtl рдпрд╛ auto return рдХрд░рддрд╛ рд╣реИред рдЗрд╕реЗ document рдкрд░ initial render рдХреЗ рд▓рд┐рдП рд╕реЗрдЯ рдХрд░реЗрдВ, рдФрд░ swap рдХреЗ рдмрд╛рдж рдЗрд╕реЗ рдлрд┐рд░ рд╕реЗ рд▓рд╛рдЧреВ рдХрд░реЗрдВ рдЬреИрд╕рд╛ рдХрд┐ step 8 рджрд┐рдЦрд╛рддрд╛ рд╣реИред CSS logical properties рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ (margin-inline-start рдмрдЬрд╛рдп margin-left рдХреЗ) рддрд╛рдХрд┐ рдЖрдкрдХрд╛ layout follow рдХрд░реЗред

    рд╣рд╛рдВ, рдХрд┐рд╕реА рднреА рдЪреАрдЬрд╝ рдХреЗ рд▓рд┐рдП рдЬреЛ рдЖрдк рдПрдХ template string рдореЗрдВ interpolate рдХрд░рддреЗ рд╣реИрдВ, рдмрд┐рд▓реНрдХреБрд▓ рдХрд┐рд╕реА рднреА рдЕрдиреНрдп dynamic value рдХреА рддрд░рд╣ред CMS рд╕реЗ рдпрд╛ translator рд╕реЗ рдЖрдиреЗ рд╡рд╛рд▓реА content markup рдирд╣реАрдВ рд╣реИ рдЬрд┐рд╕реЗ рдЖрдк рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░рддреЗ рд╣реИрдВред Step 5 рдПрдХ minimal escaper рджрд┐рдЦрд╛рддрд╛ рд╣реИред