Yazar:
    Oluşturma:2026-09-13Son güncelleme:2026-09-13

    Lingui VS @intlayer/lingui | Aynı Makrolar, Farklı Çalışma Zamanı

    @intlayer/lingui, @lingui/core ve @lingui/react için bir uyumluluk adaptörüdür (compat adapter). t`...` , <Trans>, useLingui() ve i18n._() çağrılarınız tamamen aynı kalır; makrolar normal şekilde derlenmeye devam eder; değişen tek şey, çalışma zamanında (runtime) mesajların nereden geldiğidir. Dil başına tek bir derlenmiş katalog yerine, her çağrı noktası özel olarak kendisi için derlenmiş bir Intlayer sözlüğüne bağlanır.

    Bu makale, aynı TanStack Start uygulaması üzerinde bu değişimi ölçmektedir: biri saf Lingui ile, diğeri adaptör ile inşa edilmiştir. Veriler Benchmark Bloom deposundan alınmıştır. İki kütüphanenin doğrudan karşılaştırması için Lingui vs Intlayer yazısını okuyabilirsiniz. Bu yazı ise adaptörün neleri değiştirdiği ve nerede avantaj sağlamadığı üzerinedir.

    Özet (tl;dr): Aynı TanStack Start uygulamasında @intlayer/lingui, makrolara dokunmadan ortalama bileşen boyutunu gzip olarak 85.5 KB'tan 12.8 KB'a, hidrasyon süresini 28 ms'den 19.7 ms'ye ve dil değişimini 5.9 ms'den 2.9 ms'ye düşürdü. Basit kurulumda (her kataloğun baştan yüklendiği durum) ayrıca %90 sayfa sızıntısını ortadan kaldırdı ve sayfa başına 12 KB tasarruf sağladı. Ancak tembel yüklemeli (lazy-loaded) kurulumda saf Lingui'nin 115 KB'lık değerine kıyasla sayfa başına 137 KB gönderir: Adaptör ICU sözdizimini çalışma zamanında çözümlerken Lingui önceden derlenmiş belirteç (token) dizileri sunar. Kaynak dil sızıntısı (~%9-10), çalışma zamanından değil bileşenlere gömülü message yedeğinden kaynaklandığı için iki tarafta da aynıdır. Adaptör bir Vite eklentisidir ve TanStack Start üzerinde ölçülmüştür.

    @intlayer/lingui nedir

    Lingui bir derleyici ve bir çalışma zamanından oluşur. Kaynak kodunuzdaki makrolar her dil için bir .po (veya JSON) kataloğuna çıkarılır, dil başına bir JS modülüne derlenir ve i18n.load() + i18n.activate() aracılığıyla genel bir I18n örneğine yüklenir. Her useLingui() bu örneğe abone olur; her _() çağrısı etkin katalogda kendi kimliğini arar.

    @intlayer/lingui makroları ve API'yi korur, katalog arama mantığını değiştirir:

    1. İçe aktarma takma adlandırması (Import aliasing). @intlayer/lingui/plugin içindeki lingui() eklentisi vite-intlayer'ı sarmalar ve @lingui/core ile @lingui/react'in @intlayer/lingui'ye yönlenmesi için resolve.alias girdileri ekler. İçe aktarma kodlarınız değişmez.
    2. Tek doğruluk kaynağı olarak kataloglar. syncJSON eklentisi (.po dosyaları için syncPO), mevcut kataloglarınızı okuyarak Intlayer sözlüklerine dönüştürür ve CLI veya CMS güncelleme yaptığında çevirileri dosyalara geri yazar. splitKeys: "key-prefix" ile noktalı kimliklerin (footer.github, hero.title) düz kataloğu, 244 KB'lık tek bir dosya yerine önek başına küçük sözlüklere bölünür.
    3. Çağrı noktası bağlama (Call-site binding). Intlayer optimizasyon adımı, her dosyada _, t ve <Trans>'a iletilen kimlikleri toplar ve bileşene yalnızca eşleşen sözlükleri teslim eder. <Trans id="hero.title"> bağımsız olarak bağlanır; useLingui() dosyada kullanılan tüm öneklere bağlanır. Nokta içermeyen kimlikler (karma kimlikler, mockBanner), Lingui'nin tek messages yedek sözlüğüne yönlendirilir.
    src/components/Hero.tsx
    // Kodunuz, değişmeden kalır
    import { useLingui } from "@lingui/react";
    import { Trans } from "@lingui/react/macro";
    
    const Hero = () => {
      const { _ } = useLingui();
      return (
        <section>
          <h1>{_({ id: "hero.title", message: "Measure what you ship" })}</h1>
          <Trans id="hero.subtitle">Every byte counts</Trans>
        </section>
      );
    };
    
    Derleyicinin ürettiği kod (basitleştirilmiş)
    import _dicHash_hero from "../.intlayer/dictionaries/hero.mjs";
    import {
      useDictionary as useLingui,
      TransDictionary as Trans,
    } from "@intlayer/lingui";
    
    const Hero = () => {
      const { _ } = useLingui(_dicHash_hero);
      return (
        <section>
          <h1>{_({ id: "hero.title", message: "Measure what you ship" })}</h1>
          <Trans id="hero.subtitle" dictionary={_dicHash_hero}>
            Every byte counts
          </Trans>
        </section>
      );
    };
    

    Bileşen artık genel örneğe ve arkasındaki monolitik kataloğa erişmez. Yalnızca hero sözlüğüne erişir. Aşağıdaki tabloda bileşen boyutu sütununun 7 kat düşmesinin temel nedeni budur.

    Adaptörün koruduğu, yoksaydığı ve değiştirmediği özellikler

    Lingui API@intlayer/lingui ile
    t`...` , msg, plural, select, <Trans> makroları✅ Korunur. Intlayer adımından önce derleme sürecinde @lingui/babel-plugin-lingui-macro veya @lingui/swc-plugin'i tutun
    useLingui(){ i18n, _, t }✅ Korunur. Provider dışında da çalışır (dil bilgisi react-intlayer'dan türetilir)
    i18n._(id, values), i18n.t()✅ Korunur. Hem açık hem karma kimlikleri çözer
    ICU çoğul ekleri, select, selectordinal, #✅ Korunur, Intlayer'ın ICU çözümleyicisi aracılığıyla
    i18n.date(), i18n.number(), formats✅ Korunur, yerel Intl motoruyla desteklenir
    I18nProvider✅ Korunur. IntlayerProvider'ı sarmalar; activate() ile yeniden render için i18n.on("change") dinler
    i18n.activate(locale)✅ Korunur
    i18n.load(locale, messages) / loadAndActivate()⚠️ Çalışma zamanı yedeği olarak kabul edilir. Derlenmiş sözlükler önceliklidir; geliştirici uyarısı verir
    setupI18n({ messages, missing })⚠️ messages yedek olarak birleştirilir; missing yoksayılır
    lingui extract / lingui compile✅ Mevcut iş akışınız aynen sürer. syncPO / syncJSON'u çıkarılan kataloglara yönlendirin
    I18nProvider üzerindeki defaultComponent⚠️ Bağlamda saklanır ancak render sırasında uygulanmaz
    Next.js❌ Eklenti vite-intlayer'ı sarmalar. Yalnızca Vite, TanStack Start ve React Router desteklenir

    Benchmark karşılaştırması

    Neler ölçüldü

    Benchmark Bloom test paketi, her yapılandırmada aynı uygulamayı derler: 10 sayfa (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 dil (en, fr, es, de, it, pt, zh, ja, ko, ru), aynı bileşenler ve aynı içerik. Sayfalar en ve fr dillerinde ölçülmüştür.

    Lingui dört yükleme stratejisiyle test edildi: Derlenmiş tüm katalogların baştan içe aktarıldığı yapıdan (static), rota başına katalogların tembel yüklendiği yapıya (scoped-dynamic) kadar. Adaptör ise aynı bileşenler üzerinde, yalnızca vite.config.ts ve intlayer.config.ts dosyaları değiştirilerek çalıştırıldı. static satırı tüm dilleri paketler; dynamic satırı (importMode: 'dynamic') etkin dili isteğe bağlı olarak yükler. "Scoped" varyantı yoktur: Optimizasyon adımı çağrı noktası bazında otomatik kapsam oluşturur.

    Her derleme için şu metrikler kaydedilir:

    • Lib size: Yalnızca i18n kütüphanesini içe aktaran boş bir bileşenin gzip boyutu.
    • Page JS: Tüm sayfalar ve diller üzerinden ortalaması alınan, sayfa başına indirilen gzip JavaScript boyutu.
    • Locale leak %: İndirilen JS içindeki, kullanıcının o an görüntülemediği dillere ait çevirilerin oranı.
    • Page leak %: İndirilen JS içindeki, kullanıcının o an bulunmadığı sayfalara ait çevirilerin oranı.
    • Component avg: İzole biçimde derlenen her bir bileşenin ortalama gzip boyutu.
    • E2E reactivity: Yeni bir dil seçimi ile DOM'daki html[lang] etiketinin güncellenmesi arasındaki gerçek süre (Playwright, 5 tekrar).
    • Hydration: React hidrasyon aşamasının süresi.
    Aşağıdaki veriler @lingui/react 6.6.0 ve @intlayer/lingui 9.5.1 ile 2026-09-12 tarihindeki çalıştırmadan alınmıştır. Test uygulaması bilinçli olarak küçük tutulmuştur (dil başına birkaç düzine metin); bu nedenle sızıntı yüzdeleri yapısal bir örüntüyü yansıtır: İçerik büyüdükçe artar, çalışma zamanı maliyeti ise sabit kalır.

    TanStack Start üzerindeki sonuçlar

    YapılandırmaStratejiLib boyutu (gz)Sayfa JS ort (gz)Dil sızıntısıSayfa sızıntısıBileşen ort (gz)E2E tepkiHidrasyon
    taban (i18n yok)-0.0 KB111.0 KB0.0%0.0%0.7 KB8.1 ms21.6 ms
    Linguistatic11.2 KB152.2 KB50.0%90.0%58.0 KB3.9 ms19.9 ms
    Linguidynamic11.2 KB115.2 KB9.3%0.0%85.5 KB5.9 ms28.0 ms
    Linguiscoped-static11.2 KB120.8 KB4.0%0.0%147.9 KB7.1 ms33.9 ms
    Linguiscoped-dynamic11.2 KB120.2 KB8.6%0.0%83.7 KB42.1 ms32.9 ms
    @intlayer/linguistatic10.3 KB140.5 KB50.0%0.0%14.9 KB3.3 ms11.3 ms
    @intlayer/linguidynamic10.3 KB137.0 KB9.9%0.0%12.8 KB2.9 ms19.7 ms
    intlayer (yerel)static5.0 KB125.8 KB50.0%0.0%8.1 KB3.2 ms11.5 ms
    intlayer (yerel)dynamic5.0 KB118.6 KB0.0%0.0%6.3 KB3.6 ms14.1 ms

    Sonuçların analizi

    • Bileşenler: 7 kat daha küçük. Adaptörün ana etkisi budur. Bağımsız olarak derlenen bir Lingui bileşeni, stratejiye bağlı olarak ortalama 58-148 KB tutar; çünkü useLingui() genel örneğe ve ona yüklenen her kataloğa ulaşır. Adaptörlü aynı bileşen ortalama yalnızca 12.8-14.9 KB tutar: Yalnızca kendi sözlüklerine ve ICU çözümleyicisine erişir.
    • Hidrasyon: 8-14 ms daha hızlı. i18n.load() + i18n.activate() React hidrasyonundan önce istemcide çalışır; Lingui yapılandırması ne kadar tembel yüklemeliyse bu işlem o kadar uzar (28-34 ms). Adaptörde ise sözlükler, paketleyicinin sayfa parçasına önceden yerleştirdiği sıradan içe aktarmalar olarak gelir: static modda 11.3 ms, dynamic modda 19.7 ms.
    • Dil değiştirme: 2 kat daha hızlı ve takılma yok. Lingui'nin optimize edilmiş scoped-dynamic kurulumu, rota kataloğu getirilip etkinleştirilene kadar ekran güncellenemediğinden html[lang] değişimini 42 ms'de tamamlar. Adaptör her iki modda da istikrarlı şekilde 2.9-3.3 ms seviyesindedir.
    • Basit kurulum otomatik olarak iyileşir. Statik Lingui her sayfada tüm katalogları taşır: 152.2 KB ve %90 sayfa sızıntısı. Statik adaptör: 140.5 KB, %0 sayfa sızıntısı, aynı bileşenlerle.
    • Sayfa başına bayt: dynamic modda Lingui 22 KB ile öndedir. Bu nokta dürüstçe değerlendirilmelidir. Lingui, derleme anında mesajları belirteç dizilerine dönüştürür ve yalnızca bunları gezen 11 KB'lık hafif bir çalışma zamanı gönderir. Adaptör ise Intlayer'ın ICU çözümleyicisini (yerel yapıya göre yaklaşık 15 KB daha fazla @intlayer/core), adaptör katmanını (~10 KB) ve react-intlayer'ı (~6 KB) taşır. Bu uygulamada sonuç 137.0 KB'a karşı 115.2 KB'tır. Tek önceliğiniz sayfa başına asgari bayt ise ve zaten tembel yüklemeli Lingui kullanıyorsanız, adaptör bu metriğe katkı sağlamaz.
    • Dil sızıntısı iki tarafta da benzerdir. dynamic modda Lingui %9.3, adaptör %9.9'dur. Bu durum bileşenlerin kendi kodundan kaynaklanır: i18n._({ id: "careers-benefits.pay", message: "Top-of-market compensation" }) çağrısı yedek İngilizce metni taşır ve message alanı ayıklanmadıkça makro çıktısı da aynı şeyi yapar. Bu İngilizce metin, çeviriyi hangi çalışma zamanı sağlarsa sağlasın fr parçasına girer. Satır içi kaynak barındırmayan yerel Intlayer (.content.ts) ise tam %0'dır.

    Sayılar neden değişiyor ve biri neden sabit kalıyor

    Bu sonuçları iki faktör belirler: Bileşenin neye bağlandığı ve mesajların hangi biçimde taşındığı.

    Bağlama mantığı. Lingui'de temel bölme birimi dildir. fr için messages.mjs tek bir modüldür; onu yükleyen örneği içe aktaran her bileşen tüm içeriğe erişebilir, bu yüzden paketleyici dil seviyesinin altına inip bölme yapamaz. Adaptörde ise birim çağrı noktasıdır: hero ve footer bağımsız içe aktarmalardır; bileşen bazında bölünür ve talep üzerine yüklenir. Bileşen boyutu, hidrasyon ve sayfa sızıntısı iyileşmesi buradan doğar.

    bash
    .
    ├── lingui.config.ts
    └── src
        ├── i18n.ts                          # setupI18n(), load(), activate()
        ├── locales
       ├── en/messages.mjs              # lingui compile çıktısı, dil başına bir adet
       └── fr/messages.mjs
        └── components
            └── Hero.tsx                     # useLingui(); _("hero.title")
    
    bash
    .
    ├── intlayer.config.ts                   # syncJSON({ splitKeys: "key-prefix" })
    ├── .intlayer/                           # üretilen: dil başına, kimlik öneki başına bir sözlük
    └── src
        ├── locales
       ├── en/messages.json             # değişmedi, tek doğruluk kaynağı olmaya devam ediyor
       └── fr/messages.json
        └── components
            └── Hero.tsx                     # useLingui(); _("hero.title")  ← değişmedi
    

    Mesaj biçimi. Lingui'nin derleme adımı {count, plural, one {# item} other {# items}} ifadesini bir belirteç dizisine dönüştürür; çalışma zamanı hiçbir zaman ICU sözdizimini ayrıştırmaz. Adaptör ise mesajı metin olarak tutar ve Intlayer'ın ICU çözümleyicisiyle ayrıştırır. Bu, sayfa başına bir kez ödenen yaklaşık 15 KB'lık sabit bir ek yüktür ve dynamic satırının diğer tüm alanlarda kazanırken toplam bayt boyutunda geride kalmasının nedenidir. Yerel Intlayer bundan kaçınır; çünkü .content.ts sözlükleri derleyicinin önceden çözümlediği enu() / insert() düğümlerini kullanır.

    Üç adımda geçiş

    1. Yükleme

      bash
      npx intlayer init --interactive
      

      Bu komut Lingui'yi tespit eder, lingui.config.ts dosyasını okuyarak syncPO (.po katalogları) veya syncJSON (JSON katalogları) seçimini yapar, intlayer, react-intlayer, @intlayer/lingui ve uygun senkronizasyon eklentisini kurar ve vite.config.ts içindeki @lingui/vite-plugin'i adaptör eklentisiyle değiştirir. @lingui/core, @lingui/react ve makro eklentinizi kurulu tutun: Makrolar derlenmeye devam eder ve adaptör Lingui'nin tiplerini kullanır.

    2. Intlayer'ı kataloglarınıza bağlama

      JSON katalogları için (lingui.config.ts içinde format: "minimal" olduğunda):

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      import { syncJSON } from "@intlayer/sync-json-plugin";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
        dictionary: {
          importMode: "dynamic",
          format: "icu",
        },
        plugins: [
          syncJSON({
            format: "icu",
            source: ({ locale, key }) => `./src/locales/${locale}/${key}.json`,
            // Noktalı kimlikleri ilk segmentlerine göre grupla: `footer.github` → `footer` sözlüğü
            splitKeys: "key-prefix",
          }),
        ],
      };
      
      export default config;
      

      .po katalogları için, syncJSON yerine @intlayer/sync-po-plugin'den syncPO kullanın ve .po uzantılı aynı source desenini uygulayın. Sync PO eklenti belgelerine göz atabilirsiniz.

      splitKeys: "key-prefix" bileşen boyutunu dramatik şekilde küçülten temel unsurdur. Katalog dosyası düz yapısını korur; bölme yalnızca üretilen sözlüklerde var olur ve geri yazma işlemi anahtarları otomatik olarak yeniden birleştirir.

    3. Eklentiyi ekleme

      vite.config.ts
      import { defineConfig } from "vite";
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { lingui } from "@intlayer/lingui/plugin";
      
      export default defineConfig({
        plugins: [
          tanstackStart(),
          viteReact({
            // Makro eklentinizi koruyun; Intlayer adımından önce çalışmalıdır
            babel: { plugins: ["@lingui/babel-plugin-lingui-macro"] },
          }),
          lingui(),
        ],
      });
      

      lingui() eklentisi vite-intlayer'ı (içerik izleme, sözlük derleme, optimizasyon adımı) sarmalar ve @lingui/core ile @lingui/react'i adaptöre takma adlandırır. Projeyi derleyin ve ölçülen kazanımları hemen elde edin.

    Sonrasında silebilecekleriniz

    Dosya / kalıpNeden
    await import(`./locales/${locale}/messages.mjs`) | Sözlükler doğrudan kullanan bileşenler tarafından içe aktarılır. i18n.load() yedeğe dönüşür
    i18n.load() / i18n.loadAndActivate()i18n.activate(locale) kalsın; elle katalog yükleme kodlarını kaldırın
    Derleme betiğindeki lingui compileYalnızca JSON veya .po dosyalarını kaynak olarak kullanıp derlenmiş modülleri almıyorsanız

    Bayt tasarrufu dışındaki kazanımlar

    • Eksik çevirilerin tespiti. npx intlayer test, bir dilde eksik anahtar varsa CI derlemesini durdurur; lingui extract yalnızca istatistik raporlar.
    • npx intlayer fill ile otomatik doldurma. Eksik çevirileri tercih ettiğiniz yapay zeka sağlayıcısı (OpenAI, Anthropic, Mistral, Gemini...) ile tamamlar ve kataloglarınıza geri kaydeder.
    • Görsel Düzenleyici ve CMS. Aynı sözlükler üzerinde çalışır; teknik bilgisi olmayan ekip üyeleri bile arayüz üzerinden .po ve JSON dosyalarını düzenleyebilir.
    • .content.ts formatına kademeli geçiş. İstediğiniz bir bileşeni dilediğiniz zaman useLingui()'den yanındaki içerik dosyasıyla useIntlayer("hero")'ya geçirebilirsiniz. İki sözlük türü sorunsuz biçimde bir arada çalışır.

    Başlamadan önce bilinmesi gereken sınırlar

    • dynamic moddaki sayfa başı maliyet. Yukarıda belirtildiği gibi: Küçük bir uygulamada tembel yüklemeli Lingui kurulumuna kıyasla sayfa başına yaklaşık +20 KB beklenmelidir. Bu fark içerik arttıkça büyümez (kataloglardan değil çözümleyiciden kaynaklanır) ancak küçülmez de.
    • Kaynak dil sızıntısı devam eder. Mesaj tanımlayıcıları ve makro çıktıları yedek olarak orijinal İngilizce metni içerir. Bunu tamamen ortadan kaldırmak için message alanını temizlemek veya bileşeni .content.ts'ye taşımak gerekir.
    • i18n.load() yalnızca bir yedektir. Derlenmiş katalogları içe aktarmaya ve load() çağırmaya devam ederseniz hem eski hem yeni paketi yüklersiniz. Bu içe aktarmaları kaldırın.
    • Yalnızca Vite. @intlayer/lingui için Next.js eklentisi yoktur. Lingui kullanan Next.js projeleri doğrudan next-intlayer seçeneğini değerlendirmelidir.
    • defaultComponent uygulanmaz. Her <Trans> etiketini otomatik sarmalamak için bu özelliğe güveniyorsanız bileşenlerinizde sarmalayıcıyı açıkça ekleyin.

    Hangisi ne zaman tercih edilmeli?

    • Lingui'de kalın: Zaten scoped-dynamic mimarisini oturttuysanız, tek hedefiniz sayfa başına asgari bayt ise ve 42 ms'lik dil değişimi ile 30 ms'lik hidrasyon uygulamanız için uygunsa.
    • @intlayer/lingui kullanın: Halihazırda Lingui kullanıyorsanız ve makrolara dokunmadan daha hafif bileşenler, hızlı hidrasyon ve dil değişimi, basit kurulumda %0 sayfa sızıntısı, tiplenmiş kimlikler, CI doğrulaması ve yapay zeka çevirisi istiyorsanız. Mevcut Lingui projeleri için ideal geçiş köprüsüdür.
    • Yerel Intlayer'a (react-intlayer) geçin: Bileşenlerinizi kapsamlı biçimde elden geçirmeye başladığınızda. Karşılaştırma tablosunda %0 dil sızıntısı, 5 KB çalışma zamanı ve temel uygulamaya kıyasla yalnızca +7.6 KB sayfa başı fark sunan tek çözümdür.

    İlgili karşılaştırmalar

    Sonuç

    @intlayer/lingui, Lingui çağrı noktalarının bağlandığı kaynağı kökten yeniler: Genel örnek ve devasa tekil dil kataloğu yerine, doğrudan o bileşene özel derlenmiş sözlüğe bağlanır. Aynı TanStack Start uygulamasında bu, hiçbir makroya dokunmadan 7 kat daha küçük bileşenler, 8-14 ms daha hızlı hidrasyon, 2 kat daha hızlı dil değişimi ve 42 ms'lik gecikmenin ortadan kalkması anlamına gelir. Bileşenlerdeki yerleşik yedek metinleri değiştirmediği için kaynak dil sızıntısı sürer ve çalışma zamanında ICU ayrıştırdığı için dinamik modda saf Lingui'ye kıyasla sayfa başına yaklaşık 20 KB daha fazla veri gönderir. Tercihinizi yaparken uygulamanızın performans hedeflerini dikkate alın.

    Tüm ham veriler, test uygulamaları ve kıyaslama betikleri Benchmark Bloom deposunda mevcuttur. Kendiniz de çalıştırabilirsiniz.

    Daha fazla ayrıntı için 'Neden Intlayer?' dokümantasyonuna göz atabilirsiniz.

    Yorumlar

    Henüz yorum yok. Düşüncelerinizi paylaşan ilk kişi olun.

    İlgili Gönderiler

    Son Gönderiler