Yazar:
    Oluşturma:2026-08-29Son güncelleme:2026-08-29

    Intlayer kullanarak htmx uygulamanızı çevirin | Uluslararasılaştırma (i18n)

    htmx kendi içeriğini render etmez. Bir ziyaretçinin okuduğu her etiket sunucunuzun ürettiği HTML'dir ve her swap ayrı bir HTTP isteğidir. Bu nedenle, bir htmx uygulamasını uluslararasılaştırmak bir sunucu sorumluluğudur: locale her istekte çözülmeli ve her fragment o locale'de render edilmelidir.

    Intlayer bunu backend entegrasyonları aracılığıyla kapsar ve bu entegrasyonlar her istekte locale'yi algılar ve bildirilen içeriğinizi HTML'yi oluşturan handler'a sunar.

    İçindekiler

    htmx uygulamasında i18n'nin üç kuralı

    Tek bir sayfa düzinelerce swap tetikleyebilir. Her biri, onu başlatan sayfanın belleğine sahip olmayan yeni bir istek. Eğer yerel ayar, ilk render sırasında ayarlanan bir değişkende bulunuyorsa, sonraki her fragment varsayılan dile geri döner.

    Intlayer middleware, yerel ayarı istek kendisinden çözer, bu nedenle onuncu dakikada sunulan bir fragment, sıfırıncı dakikada sunulan sayfa ile aynı dilde cevap verir.

    htmx ile iki taşıyıcı çalışır. Bir cookie (INTLAYER_LOCALE), tarayıcı tarafından htmx olanlar da dahil olmak üzere her istek üzerinde otomatik olarak gönderilir. Bir başlık (x-intlayer-locale), hx-headers özniteliği ile htmx isteklerine eklenebilir. Her ikisi de varsayılan olarak okunur.

    Bir parçaya interpole edilen çevrilmiş bir değer markup'tır. Bunu diğer dinamik değerler gibi tam olarak escape edin, böylece < içeren bir çeviri, değiştirildiği belgeyi bozamaz.


    Adım Adım Rehber

    ide.intlayer.org

    GitHub'ta Uygulama Şablonu sayfasına bakın.

    1. Bağımlılıkları Yükleyin

      intlayer ve sunucunuz için entegrasyonu yükleyin.

      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 ve Fastify, locale cookie'sini kendi cookie parser'ları aracılığıyla okurlar, bu nedenle bunlar yanında yüklenmelidir. Hono ve Elysia, cookie'leri yerel olarak ayrıştırırlar.

      htmx'in kendisi, adım 4'te eklenen tek bir script tag'idir.

    2. Projenizin Konfigürasyonu

      Proje kökinizde bir intlayer.config.ts dosyası oluşturun:

      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;
      
      Konfigürasyonun tam seçenekleri için yapılandırma belgelerine bakın.
    3. İçeriğinizi Bildirin

      Sunucunun oluşturacağı tüm etiketleri bildirin; bunlar yalnızca bir fragment içinde görünen etiketleri de içerir:

      src/app.content.ts
      import { insert, t, type Dictionary } from "intlayer";
      
      const appContent = {
        key: "app",
        content: {
          pageTitle: "Intlayer + htmx",
      
          localeLabel: t({
            tr: "Dil",
            en: "Language",
            fr: "Langue",
            es: "Idioma",
            ar: "اللغة",
          }),
      
          cartSummary: insert(
            t({
              tr: "Sepetinizde yer alan ürünler: {{count}}",
              en: "Items in your cart: {{count}}",
              fr: "Articles dans votre panier : {{count}}",
              es: "Artículos en tu carrito: {{count}}",
              ar: "المنتجات في سلتك: {{count}}",
            })
          ),
      
          addItem: t({
            tr: "Bir öğe ekle",
            en: "Add an item",
            fr: "Ajouter un article",
            es: "Añadir un artículo",
            ar: "أضف منتجًا",
          }),
        },
      } satisfies Dictionary;
      
      export default appContent;
      
      İçerik bildirimleri contentDir altında herhangi bir yerde bulunabilir (varsayılan olarak ./src) ve .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml} ile eşleşir. Bkz. içerik bildirimi belgeleri.
    4. Intlayer middleware'ini kaydet

      Middleware, her isteğin locale'ini çözer ve handler'larınıza açığa çıkarır.

      src/index.ts
      import cookieParser from "cookie-parser";
      import express from "express";
      import { intlayer } from "express-intlayer";
      
      const app = express();
      
      // Cookie parser önce çalışmalıdır: `express-intlayer` locale
      // cookie'sini `req.cookies` aracılığıyla okur.
      app.use(cookieParser());
      app.use(express.urlencoded({ extended: false }));
      app.use(intlayer());
      

      Çözümlenen locale res.locals.locale üzerindedir.

      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);
      

      Çözümlenen yerel ayar req.intlayer.locale üzerindedir.

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

      Çözümlenen yerel ayar c.get("locale") üzerindedir.

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

      Çözümlenen locale, route context'inde intlayer!.locale üzerindedir.

      Varsayılan olarak locale, INTLAYER_LOCALE cookie'sinden, sonra x-intlayer-locale header'ından, sonra Accept-Language görüşmesinden alınır.

    5. İstek locale'i ile fragment'ları render edin

      Fragment renderer'larınızı bir locale'in saf fonksiyonları olarak yazın ve middleware'in çözümlediği locale'i geçin. Bunu açık bir şekilde geçmek, fragment'ı hangi sunucuda olursa olsun, onu isteyen istekle bağlı tutar.

      src/views.ts
      import { currency, getIntlayer, type Locale } from "intlayer";
      
      const HTML_ENTITIES: Record<string, string> = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#39;",
      };
      
      /** Çevirilen bir değeri markup'tan çıkamaması için escape eder. */
      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>`;
      };
      

      Bunu bir route'tan sunun:

      src/index.ts
      app.post("/cart/items", (req, res) => {
        const itemCount = Number(req.body?.itemCount ?? 0) + 1;
      
        res.type("html").send(renderCart(res.locals.locale, itemCount));
      });
      
      src/index.ts
      fastify.post("/cart/items", async (req, reply) => {
        const itemCount =
          Number((req.body as { itemCount?: string })?.itemCount ?? 0) + 1;
      
        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" },
        });
      });
      

      Aynı fragment artık fr diyen bir ziyaretçi için Fransızca, ar diyen bir ziyaretçi için Arapça olarak yanıt veriyor, çağrı işaretlemesinde herhangi bir değişiklik olmaksızın.

    6. İlk sayfayı sunun

      <body> öğesini kendi başına render edin, böylece 7. adımdaki locale anahtarı onu tamamen değiştirebilsin, sonra htmx yükleyen belgeyle sarın:

      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 için ltr, rtl veya auto döndürür, bu da Arapça ve İbranice'nin doğru şekilde düzenlenmesini sağlar.

    7. Dili değiştir

      Dil değiştirmek diğer herhangi bir istek gibidir. Sunucu seçimi middleware'in okuduğu cookie'de depolar, ardından sayfayı yeni locale'de yeniden render ederek döndürür.

      Anahtarı bir select olarak işleyin ve tüm <body>'yi değiştirecek şekilde kendinizi gönderip takas edin, böylece statik etiketleriniz de değişsin:

      src/views.ts
      import { getIntlayer, getLocaleName, type Locale, locales } from "intlayer";
      
      const renderLocaleSwitcher = (locale: Locale): string => {
        const content = getIntlayer("app", locale);
      
        const options = locales
          .map(
            (availableLocale: Locale) =>
              `<option value="${availableLocale}"${availableLocale === locale ? " selected" : ""}>${escapeHtml(getLocaleName(availableLocale, locale))}</option>`
          )
          .join("");
      
        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) her dilde mevcut olan dili yazar. Bunun yerine her birini kendi dilinde yazmak için ikinci bir argüman iletmeyin.

      POST'u, değeri doğrulayarak, cookie'yi ayarlayarak ve yeni body'yi döndürerek işleyin:

      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);
      
        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();
        // İstenen locale'i body'den al
        const requestedLocale = String(body["locale"]);
      
        if (!isDeclaredLocale(requestedLocale)) {
          return c.text("Unknown locale", 400);
        }
      
        setCookie(c, "INTLAYER_LOCALE", requestedLocale, {
          sameSite: "Lax",
          path: "/",
        });
        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);
      
        // Yerel ayarın bildirilmiş bir yerel ayar olup olmadığını kontrol et
        if (!isDeclaredLocale(requestedLocale)) {
          return status(400, "Unknown locale");
        }
      
        // INTLAYER_LOCALE cookie'sini ayarla
        cookie["INTLAYER_LOCALE"]!.set({
          value: requestedLocale,
          sameSite: "lax",
          path: "/",
        });
      
        // Yanıt olarak HTML gövdesini döndür
        return new Response(renderBody(requestedLocale, 0), {
          headers: { "content-type": "text/html" },
        });
      });
      
      isDeclaredLocale, keyfi bir string'i yapılandırılmış yerel ayarlarınızdan birine daraltır, bu nedenle beklenmeyen bir değer hiçbir zaman rendererlarınıza ulaşmaz.
    8. Swap sonrasında lang ve dir'i senkronize tut

      İsteğe bağlı

      Bir swap <body> öğesini değiştirebilir, etrafındaki <html> öğesini asla değiştiremez. Swap yapılan body öğesinde lang ve dir özniteliklerini render edin ve bunları başlık bölümünden kök öğeye bir kez geri kopyalayın:

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

      Bunu yapmazsanız, Arapçaya geçiş body içinde sağdan sola doğru render olurken, belge hala önceki dili yardımcı teknolojilere ve tarayıcılara bildirir.

    9. İsteğe bağlı

      Bir cookie size uygun değilse, bir üst öğedeki hx-headers ile her htmx isteğine locale'i ekleyin. Alt öğeler bunu miras alır:

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

      Middleware varsayılan olarak x-intlayer-locale okur. Her iki taşıyıcıyı da yapılandırmanızda yeniden adlandırabilirsiniz:

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        // ... Diğer yapılandırma seçenekleri
        routing: {
          storage: [
            { type: "header", name: "my-locale-header" },
            { type: "cookie", name: "my-locale-cookie" },
          ],
        },
      };
      
      export default config;
      

    TypeScript'i Yapılandırın

    Otomatik olarak oluşturulan türleri ekleyerek, bildirilmemiş bir anahtarın runtime'da boş bir string yerine compile hatası olmasını sağlayın.

    tsconfig.json
    {
      // ... Mevcut TypeScript konfigürasyonlarınız
      "include": [
        // ... Mevcut TypeScript konfigürasyonlarınız
        ".intlayer/**/*.ts", // Otomatik olarak oluşturulan türleri ekleyin
      ],
    }
    

    Git Konfigürasyonu

    Intlayer tarafından oluşturulan dosyaları yok saymak önerilir:

    .gitignore
    # Intlayer tarafından oluşturulan dosyaları yok sayın
    .intlayer
    

    VS Code Uzantısı

    Intlayer ile geliştirme deneyiminizi iyileştirmek için resmi Intlayer VS Code Uzantısı'nı yükleyebilirsiniz.

    VS Code Marketplace'ten Yükleyin

    Bu extension şunları sağlar:

    • Çeviri anahtarları için otomatik tamamlama.
    • Eksik çeviriler için gerçek zamanlı hata algılama.
    • Çevrilmiş içeriğin satır içi önizlemeleri.
    • Çevirileri kolayca oluşturmak ve güncellemek için hızlı eylemler.

    Extension'ın nasıl kullanılacağı hakkında daha fazla bilgi için Intlayer VS Code Extension belgelerine bakın.


    Daha İleri Gidin

    Daha ileri gitmek için, içeriğinizi CMS kullanarak dışsallaştırabilirsiniz, böylece çevirmenler bir deployment olmadan metni değiştirebilir.

    Sıkça Sorulan Sorular

    Çünkü fragment isteği hiçbir locale taşımadı. htmx istekleri onu yayınlayan sayfadan bağımsızdır, bu nedenle locale her birinde seyahat etmelidir, INTLAYER_LOCALE cookie'si veya hx-headers ile ayarlanan x-intlayer-locale başlığı aracılığıyla. Cookie parser'ın Express ve Fastify'de Intlayer middleware'den önce çalıştığını kontrol edin, aksi takdirde cookie hiçbir zaman okunmaz ve her istek Accept-Language'e geri döner.

    Bunu geçin. Entegrasyonlar çözümlenmiş locale'i (res.locals.locale, req.intlayer.locale, c.get("locale"), intlayer!.locale) açığa çıkarır ve bunu getIntlayer'a vermek her renderer'ı bir locale'in saf fonksiyonu yapar. Bu test etmeyi kolaylaştırır ve sunucuyu değiştirirseniz fragment renderer'larınızı taşınabilir tutar.

    Hayır. Ziyaretçinin gördüğü her şey sunucu tarafından üretilir, bu nedenle tarayıcıda çevirisi yapılacak bir şey yoktur. Bu ayrıca htmx uygulamasında i18n'nin sayfa ağırlığı maliyetinin neden sıfıra yakın olduğunun sebebidir: hiçbir katalog client'e gönderilmez.

    Sayfalarınızı bir yerel ön eki altında sunun (/fr/cart) ve tam sayfa renderi için yerel kodu yoldan okuyun, tanımlama bilgisinden değil, rota işleyicinizde. Parçalar tanımlama bilgisini veya başlığı kullanmaya devam edebilir. konfigürasyon için yönlendirme seçeneklerine ve özel URL yeniden yazımları bölümüne bakın.

    getHTMLTextDir(locale) ltr, rtl veya auto döndürür. İlk render için belgeye ayarlayın ve adım 8'in gösterdiği gibi bir takas sonrasında yeniden uygulayın. CSS mantıksal özellikleri kullanın (margin-left yerine margin-inline-start) böylece düzeniniz buna uyar.

    Evet, bir şablon dizesine interpolate ettiğiniz herhangi bir şey için, diğer dinamik değerler gibi tam olarak aynı şekilde. CMS'den veya çevirmen tarafından gelen içerik kontrol ettiğiniz markup değildir. Adım 5 minimal bir escaper göstermektedir.