Autor:
    Data utworzenia:2024-08-11Ostatnia aktualizacja:2026-09-27

    vue-i18n VS Intlayer: Benchmark internacjonalizacji (i18n) dla Vue

    vue-i18n to referencyjna biblioteka i18n dla Vue. Intlayer to alternatywa oparta na kompilatorze, z treścią ograniczoną do komponentu, z integracją z Vue (vue-intlayer). Ten artykuł przygląda się temu, ile każda z nich kosztuje po zbudowaniu aplikacji.

    Dane pochodzą z Benchmark Bloom, open-source'owego zestawu, który buduje tę samą aplikację z każdą biblioteką i rejestruje, co przeglądarka faktycznie pobiera i wykonuje.

    tl;dr: Na tej samej aplikacji Vite + Vue 3 vue-i18n dostarcza 134,9 KB gzipowanego JavaScriptu na stronę wobec 41,3 KB dla aplikacji bez i18n. Intlayer dostarcza 57,1 KB. Sam runtime vue-i18n waży 24,3 KB gzip (6x więcej niż 3,9 KB Intlayera), każda strona niesie 90% ciągów z innych stron, a komponent skompilowany w izolacji wciąga 196 KB, ponieważ jest związany z globalnym drzewem komunikatów. Adapter @intlayer/vue-i18n zachowuje API vue-i18n i zmierzył 47,0 KB na stronę.

    W skrócie

    • vue-i18n - Faktyczny standard i18n dla Vue 2 / Vue 3 i rdzeń @nuxtjs/i18n. Komunikaty w stylu ICU, bloki <i18n> w SFC, dyrektywa v-t, formatery d() / n(), duży ekosystem. Komunikaty są rejestrowane na globalnej instancji w createI18n(); leniwe ładowanie per locale to ręczny wzorzec setLocaleMessage(), a podział per trasa jest do zbudowania samodzielnie.
    • Intlayer - Model treści skoncentrowany na komponentach. Słowniki .content.ts leżą obok komponentu, któremu służą, kompilator w czasie budowania (vite-intlayer) wykonuje tree-shaking i leniwe ładowanie per komponent i per locale, ścisłe typy TypeScript są generowane z treści, a brakujące tłumaczenia powodują błąd w czasie budowania. Zawiera helpery routera / SEO, Visual Editor / CMS oraz tłumaczenie wspomagane AI.
    BibliotekaGwiazdki GitHubŁączna liczba commitówOstatni commitPierwsza wersjaWersja NPMPobrania NPM
    aymericzip/intlayerGitHub Repo starsGitHub commit activityLast CommitKwiecień 2024npmnpm downloads
    intlify/vue-i18nGitHub Repo starsGitHub commit activityLast CommitGrudzień 2016npmnpm downloads
    Odznaki aktualizują się automatycznie. Zrzuty będą się zmieniać w czasie.

    Porównanie funkcji obok siebie

    Funkcjavue-intlayer (Intlayer)vue-i18n
    Tłumaczenia blisko komponentów✅ Tak, .content.ts obok każdego komponentu✅ Przez bloki SFC <i18n> (opcjonalne); globalne katalogi to typowa konfiguracja
    Integracja z TypeScript✅ Ścisłe typy generowane automatycznie z treści✅ Dobre typowania; ścisłe bezpieczeństwo kluczy wymaga typowania schematu i dyscypliny
    Wykrywanie brakujących tłumaczeń✅ Błąd TypeScript + błąd/ostrzeżenie w czasie budowania⚠️ Fallback w runtime + ostrzeżenie w konsoli
    Bogata treść (komponenty / Markdown)✅ Bezpośrednie wsparcie⚠️ Interpolacja komponentów <i18n-t>; Markdown przez zewnętrzne wtyczki
    Wsparcie ICU⚠️ W trakcie prac✅ Tak
    Formatowanie (daty, liczby, waluty)✅ Formatery oparte na Intl✅ d() / n() z datetimeFormats / numberFormats
    Zlokalizowany routing✅ Helpery dla Vue Router / Nuxt, getMultilingualUrls⚠️ Nie w rdzeniu (@nuxtjs/i18n lub własna konfiguracja routera)
    Helpery SEO (hreflang, sitemap, robots)✅ Wbudowane helpery❌ Nie w rdzeniu
    Tree-shaking (dostarczanie tylko używanej treści)✅ Per komponent, per locale, zautomatyzowane przez kompilator⚠️ Ręcznie: podział katalogów, setLocaleMessage() per trasa
    Leniwe ładowanie✅ importMode: 'dynamic' (jedna linia konfiguracji)✅ Ręczny import() + setLocaleMessage()
    Usuwanie nieużywanej treści✅ Martwe słowniki są odrzucane w czasie budowania❌ Nie wbudowane
    Testowanie brakujących tłumaczeń (CLI / CI)✅ npx intlayer content test⚠️ Zewnętrzne (vue-i18n-extract)
    Tłumaczenie wspomagane AI✅ Wbudowane, używa własnych kluczy dostawcy❌ Nie
    Visual Editor / CMS✅ Darmowy Visual Editor + opcjonalny CMS❌ Nie (zewnętrzne platformy lokalizacyjne)
    Serwer MCP i Agent Skills✅ Tak❌ Nie
    Ekosystem / społeczność⚠️ Mniejszy, ale szybko rosnący✅ Duży i dojrzały w ekosystemie Vue

    Benchmark

    Co zmierzono

    Zestaw Benchmark Bloom buduje tę samą aplikację Vite + Vue 3 z każdą biblioteką: 10 stron (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locale (en, fr, es, de, it, pt, zh, ja, ko, ru), identyczne komponenty i identyczna treść. Strony są mierzone w en i fr.

    Obie biblioteki testowano w konfiguracji static, tej, którą dostarcza większość projektów Vue: dla vue-i18n JSON każdego locale importowany i przekazany do createI18n({ messages }); dla Intlayera domyślny importMode: 'static'. W tym trybie Intlayer również pakuje wszystkie locale, ale kompilator nadal ogranicza treść per komponent, więc strona niesie tylko słowniki komponentów, które renderuje.

    Dla każdego builda zestaw rejestruje:

    • Lib size: rozmiar gzip pustego komponentu, który tylko importuje bibliotekę i18n. Stały koszt runtime'u.
    • Page JS: gzipowany JavaScript pobierany na stronę, uśredniony po wszystkich stronach i locale.
    • Locale leak %: udział przetłumaczonych ciągów znalezionych w pobranym JS, które należą do locale, którego użytkownik nie przegląda (fingerprint na en i fr, więc 50% oznacza „drugie mierzone locale jest w pełni obecne"; przy 10 spakowanych locale rzeczywista strata jest większa).
    • Page leak %: udział przetłumaczonych ciągów znalezionych w pobranym JS, które należą do strony, na której użytkownik nie jest.
    • Component avg: średni rozmiar gzip każdego komponentu skompilowanego w izolacji. Pokazuje, ile runtime'u i18n i katalogu wciąga pojedynczy komponent.
    • E2E reactivity: rzeczywisty czas między wybraniem nowego locale a aktualizacją html[lang] w DOM (Playwright, 5 iteracji).
    • Page load: PerformanceNavigationTiming.duration.
    Poniższe liczby pochodzą z uruchomienia z dnia 2026-09-12 z vue-i18n 11.4.0 i intlayer 9.5.0 / 9.5.1. Aplikacja testowa jest celowo mała (kilkadziesiąt ciągów na locale), więc procenty wycieku opisują wzorzec: rosną wraz z treścią, podczas gdy koszt runtime'u pozostaje stały.

    Wyniki na Vite + Vue 3

    Wybierz metryki i biblioteki, które Cię interesują:

    Metryka

    Dynamiczne ładowanie JSON

    Wczytuje tłumaczenia leniwie w czasie wykonywania

    Ograniczony JSON (przestrzenie nazw)

    Przestrzenie nazw tłumaczeń na stronę

    Czym jest ta metryka?

    Całkowity skompresowany przez gzip rozmiar pakietu biblioteki umiędzynarodowienia. Obejmuje tylko dostawcę i logikę pobierania treści po tree-shakingu i minifikacji.

    Dlaczego to jest ważne?

    Mniejszy rozmiar biblioteki zmniejsza obciążenie u klienta.

    Zobacz jako

    BibliotekaStrategiaLib size (gz)Lib size (min)Page JS śr. (gz)Locale leakPage leakComponent śr. (gz)Reaktywność E2EPage load
    base (bez i18n)-0,0 KB0,0 KB41,3 KB0,0%-1,1 KB1,8 ms10,8 ms
    vue-i18nstatic24,3 KB83,2 KB134,9 KB50,0%90,0%196,0 KB2,8 ms13,6 ms
    vue-intlayerstatic3,9 KB11,1 KB57,1 KB56,8%0,0%7,7 KB4,5 ms13,8 ms
    @intlayer/vue-i18n (compat)static7,9 KB23,2 KB47,0 KB15,0%0,0%8,4 KB1,5 ms9,3 ms
    Kolumna page-leak aplikacji bazowej jest pusta: bez biblioteki i18n fingerprinting wyłapuje zakodowane na sztywno ciągi w współdzielonych chunkach i liczba nie ma znaczenia.

    Jak to czytać

    • Koszt runtime'u. vue-i18n to jeden z najcięższych runtime'ów w całym benchmarku: 24,3 KB gzip / 83,2 KB po minifikacji dla pustego komponentu, który tylko go importuje. vue-intlayer kosztuje 3,9 KB gzip. Tę różnicę płaci się na każdej stronie niezależnie od tego, ile masz ciągów.
    • JavaScript na stronę. Aplikacja bez i18n waży 41,3 KB. vue-i18n ponad trzykrotnie ją zwiększa do 134,9 KB; Intlayer ląduje na 57,1 KB, +15,8 KB, z czego większość to dziesięć spakowanych locale (zobacz następny punkt).
    • Wyciek. Z createI18n({ messages: { en, fr, ... } }) każda strona dostarcza wszystkie locale i ciągi wszystkich stron: 50% wycieku locale (na dwóch fingerprintowanych locale) i 90% wycieku stron. Tryb static Intlayera również pakuje wszystkie locale (stąd porównywalna wartość wycieku locale), ale ma 0% wycieku stron: strona pobiera tylko słowniki komponentów, które renderuje. Przełączenie na importMode: 'dynamic' usuwa również wyciek locale; ta konfiguracja nie była częścią tego uruchomienia Vue.
    • Rozmiar komponentu to miejsce, gdzie widać architekturę. Komponent wywołujący useI18n() kompiluje się średnio do 196 KB, ponieważ t() jest związane z globalną instancją, która przechowuje każdy komunikat każdego locale. Ten sam komponent z useIntlayer() kompiluje się do 7,7 KB: sięga tylko po własny słownik.
    • Reaktywność nie jest problemem dla żadnej z nich (2-5 ms). System reaktywności Vue sprawia, że przełączanie locale jest tanie, gdy komunikaty są już w pamięci.
    • @intlayer/vue-i18n, adapter drop-in, zachowuje API vue-i18n i zmierzył 47,0 KB na stronę oraz 8,4 KB na komponent, bez zmian w kodzie aplikacji.
    Dla porównania, to samo uruchomienie zmierzyło fluent-vue na 171,8 KB na stronę, 29,7 KB runtime'u i 217 KB na komponent.
    Pełna tabela, ze wszystkimi bibliotekami i wszystkimi strategiami, w raporcie benchmarku Vue.

    Skąd ta różnica? Globalna instancja vs skompilowane słowniki

    vue-i18n to runtime. createI18n() buduje globalną instancję przechowującą drzewo komunikatów per locale; useI18n() wiąże z nią każdy komponent; t("footer.github") wyszukuje klucz w czasie renderowania. To właśnie umożliwia bloki SFC <i18n>, v-t i ładowanie komunikatów w runtime, i to również dlatego graf zależności każdego komponentu zawiera całe drzewo:

    bash
    .
    ├── locales
    │   ├── en.json
    │   ├── fr.json
    │   └── ...                        # jeden plik per locale, wszystkie strony w środku
    └── src
        ├── i18n.ts                    # createI18n({ messages: { en, fr, ... } })
        ├── main.ts
        └── components
            └── Footer.vue             # const { t } = useI18n(); t("footer.github")
    

    Optymalizacja oznacza, że ty dzielisz en.json na pliki per trasa, ty wywołujesz setLocaleMessage() w strażniku routera i ty utrzymujesz poprawne mapowanie trasa-plik, gdy komponenty się przemieszczają. Runtime nie może tego zrobić za ciebie, ponieważ nie ma pojęcia, o jakie klucze komponent poprosi.

    Intlayer przenosi tę wiedzę do builda. Treść jest deklarowana obok komponentu, a vite-intlayer ustala, który komponent importuje który słownik:

    bash
    .
    ├── intlayer.config.ts
    └── src
        ├── main.ts                    # createApp(App).use(intlayer)
        └── components
            └── Footer
                ├── Footer.vue         # useIntlayer("footer")
                └── Footer.content.ts
    

    Kompilator emituje, per słownik i per locale, dokładnie ten JSON, którego potrzebuje dany komponent, i odrzuca słowniki, których nic nie importuje. Ograniczenie per trasa jest konsekwencją ograniczenia per komponent, a nie zadaniem.

    Aby odrzucić również nieużywane locale, ustaw dictionary.importMode: 'dynamic' w intlayer.config.ts. Zobacz dokumentację optymalizacji bundle'a.

    Doświadczenie programisty

    Konfiguracja

    vue-i18n

    src/i18n.ts
    import { createI18n } from "vue-i18n";
    import en from "../locales/en.json";
    import fr from "../locales/fr.json";
    
    export const i18n = createI18n({
      legacy: false,
      locale: "en",
      fallbackLocale: "en",
      messages: { en, fr },
    });
    
    src/main.ts
    import { createApp } from "vue";
    import App from "./App.vue";
    import router from "./router";
    import { i18n } from "./i18n";
    
    createApp(App).use(router).use(i18n).mount("#app");
    

    Intlayer

    intlayer.config.ts
    import { type IntlayerConfig, Locales } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.FRENCH],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    
    vite.config.ts
    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    import { intlayer } from "vite-intlayer";
    
    export default defineConfig({
      plugins: [intlayer(), vue()],
    });
    
    src/main.ts
    import { createApp } from "vue";
    import { intlayer } from "vue-intlayer";
    import App from "./App.vue";
    import router from "./router";
    
    createApp(App).use(intlayer).use(router).mount("#app");
    

    Komponent

    vue-i18n

    locales/en.json
    {
      "counter": {
        "label": "Counter",
        "increment": "Increment"
      }
    }
    
    src/components/Counter.vue
    <script setup lang="ts">
    import { ref } from "vue";
    import { useI18n } from "vue-i18n";
    
    const { t, n } = useI18n();
    const count = ref(0);
    </script>
    
    <template>
      <div>
        <p>{{ n(count) }}</p>
        <button :aria-label="t('counter.label')" @click="count++">
          {{ t("counter.increment") }}
        </button>
      </div>
    </template>
    

    t('counter.label') jest stringiem, dopóki sam nie otypujesz schematu komunikatów; literówka renderuje klucz.

    Intlayer

    src/components/Counter/Counter.content.ts
    import { t, type Dictionary } from "intlayer";
    
    const counterContent = {
      key: "counter",
      content: {
        label: t({ en: "Counter", fr: "Compteur" }),
        increment: t({ en: "Increment", fr: "Incrémenter" }),
      },
    } satisfies Dictionary;
    
    export default counterContent;
    
    src/components/Counter/Counter.vue
    <script setup lang="ts">
    import { ref } from "vue";
    import { useIntlayer } from "vue-intlayer";
    import { useNumber } from "vue-intlayer/format";
    
    const { label, increment } = useIntlayer("counter");
    const number = useNumber();
    const count = ref(0);
    </script>
    
    <template>
      <div>
        <p>{{ number.value(count) }}</p>
        <button :aria-label="label" @click="count++">
          {{ increment }}
        </button>
      </div>
    </template>
    

    label i increment są otypowane; literówka to błąd TypeScript, brakująca wartość francuska to błąd builda.

    Leniwe ładowanie per locale

    vue-i18n

    src/i18n.ts
    import { nextTick } from "vue";
    import { createI18n } from "vue-i18n";
    
    export const i18n = createI18n({
      legacy: false,
      locale: "en",
      fallbackLocale: "en",
    });
    
    export const loadLocaleMessages = async (locale: string) => {
      const messages = await import(`../locales/${locale}.json`);
      i18n.global.setLocaleMessage(locale, messages.default);
      await nextTick();
      i18n.global.locale.value = locale;
    };
    

    Następnie wywołaj loadLocaleMessages() ze strażnika routera i samodzielnie podziel locales/{locale}.json per trasa, jeśli chcesz ograniczenia per strona.

    Intlayer

    intlayer.config.ts
    const config: IntlayerConfig = {
      // ...
      dictionary: {
        importMode: "dynamic",
      },
    };
    

    Zachowaj API vue-i18n, uzyskaj wynik Intlayera

    @intlayer/vue-i18n to adapter drop-in: useI18n(), t(), d(), n(), interpolacja {name} i {0}, liczba mnoga z pipe ("car | cars"), v-t i i18n.global.locale nadal działają, serwowane ze słowników Intlayera skompilowanych przez vite-intlayer.

    vite.config.ts
    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    import vueI18nVitePlugin from "@intlayer/vue-i18n/plugin";
    
    export default defineConfig({
      plugins: [vue(), vueI18nVitePlugin()],
    });
    

    W benchmarku build compat tej samej aplikacji zszedł ze 134,9 KB do 47,0 KB na stronę i ze 196 KB do 8,4 KB na komponent, bez zmian w komponentach. Twoje istniejące locales/{locale}.json mogą pozostać źródłem prawdy dzięki wtyczce synchronizacji JSON.

    Zobacz przewodnik migracji z vue-i18n i dokumentację kompatybilności. Użytkownicy Nuxt mają tę samą ścieżkę przez kompatybilność @nuxtjs/i18n.

    Kiedy wybrać które?

    • Wybierz vue-i18n, jeśli chcesz standardowego podejścia Vue, polegasz na komunikatach ICU lub blokach SFC <i18n>, używasz już @nuxtjs/i18n lub platforma tłumaczeniowa oczekuje scentralizowanego JSON-a. Zarezerwuj czas na podział katalogów i leniwe ładowanie per trasa, jeśli rozmiar bundle'a ma znaczenie.
    • Wybierz Intlayer, jeśli chcesz treści ograniczonej do komponentu, ścisłego TypeScriptu, błędów brakujących kluczy w czasie budowania, bezwysiłkowego tree-shakingu i leniwego ładowania oraz wbudowanych narzędzi redakcyjnych (Visual Editor, CMS, tłumaczenie AI, serwer MCP). Szczególnie istotne dla dużych, modularnych baz kodu Vue / Nuxt i design systemów.
    • Wybierz @intlayer/vue-i18n, jeśli jesteś już na vue-i18n i chcesz zysków w rozmiarze bundle'a bez przepisywania.

    Często zadawane pytania

    Jedno i drugie, w zależności od wybranego podejścia. vue-intlayer to natywny runtime z composable useIntlayer(). @intlayer/vue-i18n to adapter zgodności, który zachowuje API vue-i18n i podmienia jego źródło, dzięki czemu możesz migrować bez modyfikowania komponentów i stopniowo przechodzić plik po pliku.

    Adapter ich nie odczytuje. Przenieś te wiadomości do plików JSON lokalizacji lub do pliku .content.ts obok komponentu, co stanowi tę samą koncepcję z wygenerowanymi typami. To jedyna funkcja vue-i18n, która nie jest przenoszona.

    Tak. Intlayer z Nuxt obejmuje wielojęzyczny routing, middleware wykrywania języka i generowanie mapy witryny. Jeśli używasz @nuxtjs/i18n, adapter zgodności Nuxt i18n stanowi ścieżkę migracji.

    Tak. Wtyczka synchronizacji JSON odczytuje je w dialekcie vue-i18n ({name}, {0}, formy mnogie z kreską "car | cars") i zapisuje tłumaczenia z powrotem przy aktualizacji przez CLI lub CMS.

    Natywna obsługa ICU jest w trakcie opracowywania. Adapter @intlayer/vue-i18n obsługuje składnię wiadomości vue-i18n, w tym formy mnogie oraz interpolację nazwaną i listową. Informacje o modelu pluralizacji Intlayer znajdziesz w sekcji treści wyliczeniowe.

    Powiązane porównania

    Dokumentacja referencyjna:

    Raporty benchmarków:

    Aby zrozumieć, skąd wzięły się te biblioteki, przeczytaj historię i18n w JavaScript.

    Gwiazdki GitHub

    Gwiazdki GitHub są silnym wskaźnikiem popularności projektu, zaufania społeczności i długoterminowej istotności. Choć nie są bezpośrednią miarą jakości technicznej, odzwierciedlają, ilu programistów uważa projekt za użyteczny, śledzi jego postępy i prawdopodobnie go zaadoptuje.

    Star History Chart

    Aktywność commitów

    Gwiazdki pokazują popularność. Commity pokazują, ile pracy włożono w projekt. W chwili pisania Intlayer ma około 7 500 commitów, więcej niż większość porównywanych tu bibliotek i mniej więcej 5 razy więcej niż next-intl czy next-i18next.

    • intlify/vue-i18n
    • aymericzip/intlayer

    Commity na domyślnej gałęzi, źródło: GitHub API.

    Intlayer to monorepo, więc ta liczba obejmuje każdy pakiet dla frameworków, CLI i dokumentację. Traktuj commity jako sygnał aktywności, a nie jakości.

    Pobrania z npm

    • vue-i18n
    • vue-intlayer

    Źródło: API pobrań rejestru npm.

    Liczba pobrań nagradza najstarsze rozwiązania, a nie najlepsze. Biblioteka wydana lata temu wciąż jest instalowana przez każdy projekt, który ją wtedy wybrał, przez każde uruchomienie CI i przez każdy pakiet, który od niej zależy. Ta liczba mierzy bezwładność bardziej niż świadomy wybór.

    Asystenci AI wzmacniają ten efekt. next-intl, i18next i vue-i18n są wszędzie w kodzie, na którym ich trenowano, więc proponują je domyślnie, bez porównywania alternatyw. Każda sugestia dodaje pobrań, które napędzają kolejną sugestię. Porównuj na podstawie benchmarku, a nie liczby pobrań.

    Podsumowanie

    vue-i18n jest dojrzały, elastyczny i głęboko zintegrowany z Vue. Benchmark pokazuje, ile kosztuje jego projekt runtime-first w buildzie Vite: runtime 24 KB gzip, 134,9 KB na stronę dla aplikacji, która bez i18n waży 41 KB, 90% treści z innych stron na każdej stronie oraz komponenty, z których każdy sięga 196 KB, ponieważ wiszą na globalnym drzewie komunikatów.

    Intlayer przenosi pracę do kompilatora. Słowniki per komponent i usuwanie martwej treści to wyniki builda, a nie konwencje. Na tej samej aplikacji: runtime 3,9 KB, 57,1 KB na stronę, 0% wycieku stron, komponenty 25x mniejsze. A jeśli przepisanie nie wchodzi w grę, @intlayer/vue-i18n pokonuje większość tej drogi bez zmian w komponentach.

    Wszystkie surowe dane, aplikacje testowe i skrypty są w repozytorium Benchmark Bloom. Uruchom je sam.

    Więcej szczegółów znajdziesz w dokumencie „Dlaczego Intlayer?".

    Komentarze

    Nie ma jeszcze komentarzy. Bądź pierwszą osobą, która podzieli się swoimi przemyśleniami.

    Powiązane posty

    Ostatnie posty