Autor:
    Erstellung:2026-09-13Letzte Aktualisierung:2026-09-13

    i18next VS @intlayer/i18next | Gleiche API, anderes Bundle

    @intlayer/i18next, @intlayer/react-i18next und @intlayer/next-i18next sind Kompatibilitätsadapter. Sie stellen die i18next-API bereit, die Ihr Code bereits verwendet (useTranslation, t(), <Trans>, i18n.changeLanguage(), getFixedT, serverSideTranslations...), und bedienen sie aus von Intlayer kompilierten Wörterbüchern. Die Komponenten ändern sich nicht. Die Runtime darunter jedoch schon.

    Dieser Artikel misst diesen Austausch an derselben Next.js-Anwendung, die einmal mit next-i18next und einmal mit @intlayer/next-i18next gebaut wurde. Die Zahlen stammen aus Benchmark Bloom. Für einen Vergleich von i18next und Intlayer als eigenständige Bibliotheken lesen Sie i18next vs Intlayer. Hier geht es darum, was der Adapter verändert, wenn Sie Ihren bestehenden Code unverändert beibehalten.

    tl;dr: In derselben Next.js-App reduzierte der Wechsel von next-i18next zu @intlayer/next-i18next das JavaScript pro Seite von 218.5 KB auf 150.7 KB gzip (Basis-Setup) und unterbot selbst das vollständig optimierte next-i18next-Setup (163.4 KB) um 12.7 KB. Die durchschnittliche Komponentengröße sank von 78.5 KB auf 9.7 KB, das String-Leakage fremder Seiten von ~90% auf 0%, die Hydration-Dauer von 15.6 ms auf 11.3 ms und die Runtime von 19.7 KB auf 9.4 KB. Keine einzige Komponente musste angepasst werden; lediglich eine Provider-Datei wurde geändert. i18next-Plugins (Backends, Spracherkenner) werden akzeptiert, bewirken aber nichts: Zur Laufzeit muss nichts mehr geladen oder erkannt werden.

    Was @intlayer/i18next ist

    i18next ist eine Runtime. i18n.init({ resources }) oder ein Backend-Plugin lädt locales/{lng}/{ns}.json in eine globale Instanz; useTranslation("about") abonniert die Komponente darauf; t("title") schlägt den Schlüssel zur Renderzeit nach. Namespaces, Lazy Loading, seitenbasierte Namespace-Listen und Typsicherheit müssen von Ihnen konfiguriert und gepflegt werden.

    Die Adapter behalten die API bei und ersetzen die globale Instanz:

    1. Import-Aliasing. createNextI18nPlugin() aus @intlayer/next-i18next/plugin (oder withI18next) umschließt withIntlayer und richtet Webpack- / Turbopack-Aliase ein, sodass next-i18next, react-i18next und i18next auf ihre @intlayer/*-Gegenstücke aufgelöst werden. Unter Vite übernimmt reactI18nextVitePlugin() aus @intlayer/react-i18next/plugin die gleiche Aufgabe. Kein einziger Import muss umbenannt werden.
    2. JSON als Source of Truth. Das syncJSON-Plugin liest Ihre bestehenden locales/{lng}/{ns}.json-Dateien mit format: "i18next" (sodass {{name}}, $t()-Verschachtelungen, _one / _other und Kontext-Suffixe korrekt geparst werden) und schreibt Übersetzungen zurück, sobald die CLI oder das CMS sie aktualisiert.
    3. Bindung am Aufrufpunkt. Der Optimierungsschritt von Intlayer schreibt useTranslation("about") in einen Aufruf um, der das Wörterbuch about direkt in der aktiven Locale empfängt. Die Komponente greift nicht mehr auf den globalen Store zu.
    components/About.tsx
    // Ihr Code, unverändert
    import { useTranslation } from "react-i18next";
    
    const About = () => {
      const { t } = useTranslation("about");
      return <h1>{t("title")}</h1>;
    };
    
    Was der Compiler ausgibt (vereinfacht)
    import _dicHash_about from "../.intlayer/dictionaries/about.mjs";
    import { useDictionary as useTranslation } from "@intlayer/react-i18next";
    
    const About = () => {
      const { t } = useTranslation(_dicHash_about);
      return <h1>{t("title")}</h1>;
    };
    

    Dieser Umschreibprozess sorgt für die drastische Verkleinerung der Komponenten und den Wegfall des Seiten-Leakages in den folgenden Messungen.

    Was die Adapter beibehalten, ignorieren und nicht ersetzen

    i18next-APIMit @intlayer/*
    useTranslation("ns"), useTranslation("ns", { keyPrefix })✅ Beibehalten. Zur Build-Zeit an das ns-Wörterbuch gebunden; typisiert gegen Ihre Inhalte
    t("key", { name }), {{interpolation}}, $t(key)-Verschachtelung✅ Beibehalten
    Pluralformen key_one / key_other, Kontext key_male, returnObjects✅ Beibehalten. Plurale werden über Intl.PluralRules ausgewertet
    <Trans> mit components, nummerierten Tags <1>...</1>, values✅ Beibehalten
    withTranslation, Translation, I18nContext✅ Beibehalten
    i18n.changeLanguage(), i18n.language, i18n.dir(), on("languageChanged")✅ Beibehalten. changeLanguage steuert die Locale von Intlayer
    getFixedT(lng, ns, keyPrefix), i18n.exists(), hasLoadedNamespace()✅ Beibehalten
    i18n.use(Backend).use(LanguageDetector).init({...})⚠️ use() ruft init des Plugins auf und beendet; Backends und Detectors müssen nichts laden oder ermitteln
    init({ resources }), addResourceBundle()⚠️ resources wird mit einer Warnung ignoriert; entfernen Sie JSON-Imports für optimale Bundle-Größen
    I18nextProvider i18n={i18n}⚠️ Rendert einen IntlayerProvider; die i18n-Prop wird ignoriert. Im App Router Locale übergeben (siehe unten)
    serverSideTranslations(locale, ["common"]) (next-i18next)⚠️ Gibt die erwartete Struktur zurück und lädt nichts. Unschädlich beizubehalten, sicher zu löschen
    appWithTranslation(App) (next-i18next)✅ Beibehalten
    next-i18next.config.js⚠️ Wird nicht gelesen. Locales stammen aus intlayer.config.ts
    Einfaches useTranslation() ohne Namespace✅ Funktioniert gegen das translation-Gesamtwörterbuch der Datei (splitKeys: false)

    Der Benchmark

    Was gemessen wurde

    Die Testsuite Benchmark Bloom erstellt dieselbe Anwendung in jedem Setup: 10 Seiten (Home, About, Blog, Careers, Contact, FAQ, Pricing, Products, Settings, Team), 10 Locales (en, fr, es, de, it, pt, zh, ja, ko, ru), identische Komponenten und identischer Inhalt. Die Seiten werden in en und fr gemessen.

    next-i18next wurde in vier Ladestrategien getestet: vom Import aller Locale-JSONs in resources (static) bis hin zu einem Namespace pro Route, der über ein Backend nachgeladen wird (scoped-dynamic). Der Adapter lief auf denselben Komponenten wie das Basis-Setup, wobei lediglich next.config.ts, intlayer.config.ts und die Provider-Datei angepasst wurden. Er benötigt keine manuell "gescopte" Variante: Der Compiler grenzt den Inhalt automatisch pro Komponente ab.

    Für jeden Build erfasst die Suite:

    • Lib-Größe: gzip-Größe einer leeren Komponente, die lediglich die i18n-Bibliothek importiert.
    • Seiten-JS: Durchschnittliches gzip-JavaScript, das pro Seite über alle Seiten und Locales heruntergeladen wird.
    • Locale-Leakage %: Anteil übersetzter Strings im heruntergeladenen JS, der zu einer Sprache gehört, die der Nutzer nicht betrachtet.
    • Seiten-Leakage %: Anteil übersetzter Strings im heruntergeladenen JS, der zu einer Seite gehört, auf der sich der Nutzer nicht befindet.
    • Komponenten-Durchschnitt: Durchschnittliche gzip-Größe jeder isoliert kompilierten Komponente.
    • E2E-Reaktivität: Gemessene Zeitspanne zwischen der Sprachauswahl und der Aktualisierung von html[lang] im DOM (Playwright, 5 Durchläufe).
    • Hydration: Dauer der React-Hydration-Phase.
    Die folgenden Zahlen stammen aus dem Durchlauf vom 12.09.2026 mit next-i18next 16.3.0 (react-i18next 17.0.13, i18next 26.4.2) und @intlayer/next-i18next 9.5.1. Die Testanwendung ist bewusst kompakt gehalten (wenige Dutzend Zeichenketten pro Sprache), sodass die Leakage-Prozentwerte ein Muster abbilden: Sie steigen mit wachsendem Content, während die Runtime-Kosten konstant bleiben.

    Ergebnisse auf Next.js

    SetupStrategieLib-Größe (gz)Seiten-JS Ø (gz)Locale-LeakageSeiten-LeakageKomp Ø (gz)E2E-ReaktivitätHydration
    base (ohne i18n)-0.0 KB141.0 KB0.0%0.0%0.9 KB13.4 ms11.8 ms
    next-i18nextstatic19.7 KB218.5 KB0.0%89.8%78.5 KB16.4 ms15.6 ms
    next-i18nextdynamic19.7 KB169.5 KB50.0%89.8%26.1 KB15.4 ms27.7 ms
    next-i18nextscoped-static19.7 KB220.1 KB0.0%89.8%78.9 KB16.4 ms14.7 ms
    next-i18nextscoped-dynamic19.7 KB163.4 KB0.0%0.0%27.1 KB15.9 ms15.1 ms
    @intlayer/next-i18nextstatic9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18nextdynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms
    next-intlayer (nativ)static5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayer (nativ)dynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms

    Einordnung der Ergebnisse

    • 68 KB weniger pro Seite gegenüber dem Basis-Setup. resources: { en, fr, ... } liefert jede Sprache und jeden Namespace auf jeder Seite aus: 218.5 KB. Der Adapter-Build derselben Komponenten landet bei 150.7 KB. Er unterbietet auch die beste Konfiguration von next-i18next (163.4 KB, ein Namespace pro Route, nachgeladen) um 12.7 KB, da die i18next-Runtime allein 19.7 KB gegenüber 9.4 KB wiegt.
    • Leakage sinkt auf 0%, ohne Komponenten zu verändern. Jedes next-i18next-Setup außer der vollkommen isolierten Variante liefert ~90% fremde Seiten-Strings aus. Das dynamic-Setup schneidet schlechter ab als erwartet: Es behält das Seiten-Leakage bei und erzeugt zusätzlich 50% Locale-Leakage, da das Backend pro Sprache stets den gesamten translation-Namespace abruft. Der Adapter erreicht 0% / 0% direkt auf dem Ursprungscode.
    • Komponenten: 8x kleiner. Eine isoliert kompilierte useTranslation()-Komponente wiegt durchschnittlich 78.5 KB mit eingebetteten resources und 26-27 KB mit Backend, da t an den globalen Store gebunden ist. Mit dem Adapter sind es durchschnittlich 9.7 KB.
    • Schnellere Hydration und Sprachwechsel. Die Hydration sinkt von 15.6 ms auf 11.3 ms (und von 27.7 ms im dynamic-Setup, wo der Backend-Aufruf auf dem kritischen Pfad liegt). Der Sprachwechsel beschleunigt sich von 15-16 ms auf 11-12 ms.
    • Der Adapter ist nicht die native Runtime. next-intlayer kommt auf 141.3 KB, lediglich +0.3 KB über der Basis-App. Der Adapter bringt die API-Oberfläche von i18next (Interpolation, Plural- und Kontext-Suffixe, <Trans>-Parsing) auf dem Core von Intlayer mit: 9.4 KB und +9.4 KB pro Seite über nativ. Er ist die Brücke, nicht das Endziel.
    Der react-i18next-Adapter unter Vite / TanStack Start war nicht Teil dieser Messreihe. Die Werte für react-i18next auf TanStack Start finden sich in i18next vs Intlayer: 127-184 KB pro Seite und 123-185 ms beim Sprachwechsel bei nachgeladenem Backend.

    Warum sich die Zahlen verändern

    Am Ordner components/ wurde nichts geändert; die Einsparungen resultieren daraus, woran useTranslation gebunden ist.

    Bei i18next erfolgt die Bindung an die globale Instanz. Alles, was hineingeladen wurde (alle Sprachen bei static, der gesamte Namespace der aktiven Sprache bei dynamic), ist für jede Komponente erreichbar, die useTranslation() aufruft. Der Bundler kann nicht feiner trennen als der Instanzinhalt, und die Runtime kann nicht wissen, welche Schlüssel eine Komponente beim Rendern anfordern wird.

    bash
    .
    ├── next-i18next.config.js
    ├── public/locales
       ├── en/translation.json           # Strings aller Seiten
       └── fr/translation.json
    ├── i18n/i18n.ts                      # i18n.use(initReactI18next).init({ resources })
    └── components
        ├── AppProviders.tsx              # <I18nextProvider i18n={i18n}>
        └── About.tsx                     # useTranslation(); t("about.title")
    

    Bei @intlayer/next-i18next bindet der Aufruf direkt an das Wörterbuch. syncJSON wandelt jede Namespace-Datei in ein Wörterbuch um; der Optimierungsschritt übergibt der Komponente exakt das benötigte Wörterbuch als Import, den der Bundler pro Seite und pro Sprache isolieren und aufteilen kann.

    bash
    .
    ├── intlayer.config.ts                # syncJSON({ format: "i18next", source: ... })
    ├── public/locales
       ├── en/translation.json           # unverändert, weiterhin die Source of Truth
       └── fr/translation.json
    ├── .intlayer/                        # generiert: ein Wörterbuch pro Namespace, pro Sprache
    └── components
        ├── AppProviders.tsx              # <IntlayerClientProvider locale={locale}>
        └── About.tsx                     # useTranslation(); t("about.title")  ← unverändert
    

    i18n/i18n.ts und dessen resources-Import werden zu totem Code. Genau daraus resultieren die 68 KB Einsparung.

    Migration in drei Schritten

    1. Installation

      bash
      npx intlayer init --interactive
      

      Der Befehl erkennt i18next / react-i18next / next-i18next, installiert intlayer, das Framework-Paket (next-intlayer oder react-intlayer), den passenden @intlayer/*-Adapter sowie @intlayer/sync-json-plugin und füllt intlayer.config.ts vor. Behalten Sie die bisherigen Pakete installiert: Sie fungieren als Peer-Dependencies und liefern die TypeScript-Definitionen.

    2. Intlayer auf Ihre Sprachdateien ausrichten

      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: "i18next",
        },
        plugins: [
          syncJSON({
            // i18next-Dialekt: {{name}}, $t(key), key_one / key_other, key_male
            format: "i18next",
            // Eine Datei pro Namespace: `useTranslation("about")` → about.json
            source: ({ locale, key }) => `./public/locales/${locale}/${key}.json`,
            location: "public/locales",
          }),
        ],
      };
      
      export default config;
      

      Wenn Sie eine einzige translation.json pro Sprache verwenden (der Standard-Namespace von i18next), setzen Sie splitKeys: false, damit die gesamte Datei ein einziges Wörterbuch bleibt und einfache Aufrufe von useTranslation() weiterhin funktionieren.

    3. Plugin hinzufügen

      next.config.ts
      import type { NextConfig } from "next";
      import { withI18next } from "@intlayer/next-i18next/plugin";
      
      const nextConfig: NextConfig = {};
      
      export default withI18next(nextConfig);
      

      Im App Router erhalten Client-Komponenten ihre Sprache über das [locale]-Segment. Der I18nextProvider des Adapters nimmt keine Sprache entgegen, daher ersetzen Sie ihn einmalig in Ihrer Provider-Datei:

      components/AppProviders.tsx
      "use client";
      
      import { IntlayerClientProvider } from "next-intlayer";
      import type { LocalesValues } from "intlayer";
      
      export const AppProviders = ({
        locale,
        children,
      }: {
        locale: LocalesValues;
        children: React.ReactNode;
      }) => (
        <IntlayerClientProvider locale={locale}>{children}</IntlayerClientProvider>
      );
      

      Alle darunter liegenden Komponenten rufen weiterhin wie gewohnt useTranslation() auf.

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

      reactI18nextVitePlugin() umschließt vite-intlayer und richtet Aliase für react-i18next und i18next ein. Für ein Projekt ohne React aliast i18nextVitePlugin() aus @intlayer/i18next/plugin das Basispaket i18next.

    Was Sie anschließend entfernen können

    Datei / MusterGrund
    resources: { en, fr, ... } und die JSON-ImportsWerden vom Adapter ignoriert. Hier lagen die 68 KB
    i18next-http-backend, i18next-resources-to-backendZur Laufzeit muss nichts mehr abgerufen werden
    i18next-browser-languagedetectorDie Spracherkennung übernimmt das Routing von Intlayer (URL-Präfix, Cookie, Header)
    serverSideTranslations() in getStaticPropsLiefert eine leere Struktur; harmlos, aber überflüssig
    next-i18next.config.jsWird nicht gelesen. Sprachen liegen in intlayer.config.ts
    Listen mit ns: [...] pro SeiteDer Compiler wählt Namespaces komponentenweise aus

    Was Sie über Dateigrößen hinaus gewinnen

    • Typisierte Schlüssel. useTranslation("about") ist gegen das kompilierte about-Wörterbuch typisiert; t("does.not.exist") führt zu einem TypeScript-Fehler statt zu einem zurückgegebenen Key-String.
    • npx intlayer test lässt die CI bei fehlenden Schlüsseln in beliebigen Sprachen fehlschlagen. npx intlayer fill übersetzt fehlende Einträge mit Ihrem eigenen Provider-Schlüssel (OpenAI, Anthropic, Mistral, Gemini...) und schreibt sie in locales/{lng}/{ns}.json zurück.
    • Visueller Editor und CMS arbeiten auf demselben JSON, sodass Übersetzer Inhalte per UI bearbeiten können und Dateien synchronisiert werden.
    • Schrittweiser Wechsel zu .content.ts. Jede Komponente kann unabhängig von useTranslation("about") auf useIntlayer("about") mit einer zugehörigen Inhaltsdatei umgestellt werden. JSON und .content.ts-Dateien koexistieren reibungslos.

    Grenzen, die Sie vorab kennen sollten

    • Backends und Detektoren sind inaktiv. i18n.use(HttpBackend) ruft das init des Plugins auf und nichts weiter. Wenn Ihre App darauf angewiesen war, Übersetzungen zur Laufzeit von einem CMS abzurufen, entfällt dieser Pfad; nutzen Sie stattdessen das Intlayer-CMS oder die Befehle intlayer pull / push.
    • resources wird ignoriert, nicht zusammengeführt. Im Gegensatz zu manchen anderen Adaptern nutzt @intlayer/i18next eingebettete resources nicht als Fallback. Jeder Schlüssel muss in den synchronisierten Wörterbüchern vorhanden sein, was intlayer test absichert.
    • App Router erfordert die Provider-Änderung. Eine Datei, oben dargestellt. Pages Router mit appWithTranslation benötigt keinerlei Änderungen.
    • next-i18next.config.js wird ignoriert. localePath, fallbackLng, reloadOnPrerender und Verwandte haben keine Wirkung; Sprachen und Fallbacks werden in intlayer.config.ts definiert.
    • Der Adapter ist nicht kostenlos. 9.4 KB Runtime und +9.4 KB pro Seite gegenüber next-intlayer. Sobald alle Komponenten auf useIntlayer umgestellt sind, kann er entfernt werden.

    Wann welche Lösung wählen?

    • Bleiben Sie bei i18next, wenn Ihre Anwendung zwingend auf Runtime-Backends (zur Anfragezeit aus einem CMS geladene Übersetzungen), das Plugin-Ökosystem oder ein Nicht-React-Target angewiesen ist, das die Adapter nicht abdecken.
    • Nutzen Sie @intlayer/*, wenn Sie react-i18next / next-i18next einsetzen und 68 KB sparen, 8x kleinere Komponenten, 0% Leakage, typisierte Schlüssel und CI-Prüfungen ohne Code-Rewrite erhalten möchten. Dies ist der beste Einstieg für bestehende i18next-Codebasen.
    • Wählen Sie nativ (next-intlayer / react-intlayer) für Neuprojekte oder sobald der Adapter seinen Dienst getan hat. Es ist die schlankste Variante (5.5 KB, +0.3 KB pro Seite) und schaltet synchrone Server-Komponenten sowie komponentenbasierte .content.ts-Dateien frei.

    Verwandte Vergleiche

    Fazit

    i18next ist die schwerste Runtime in diesem Benchmark, und die Adapter entfernen den Großteil davon, ohne dass Sie die gewohnte API aufgeben müssen. Auf derselben Next.js-App bedeutet das 68 KB weniger pro Seite als im Basis-Setup, 12.7 KB weniger als in der am stärksten handoptimierten Version, 8x kleinere Komponenten, 0% Leakage und 4 ms schnellere Hydration für den Preis einer Konfigurationsdatei, einer Plugin-Zeile und einer Provider-Anpassung. Backends und Detektoren werden wirkungslos, resources wird ignoriert statt gemergt, und die native next-intlayer-Runtime bleibt nochmals 9 KB schlanker.

    Alle Rohdaten, Testanwendungen und Skripte stehen im Benchmark Bloom Repository bereit.

    Weitere Details finden Sie in der Dokumentation Warum Intlayer?.

    Kommentare

    Noch keine Kommentare. Seien Sie der Erste, der seine Gedanken teilt.

    Ähnliche Beiträge

    Letzte Beiträge