Stellen Sie Ihre Frage und erhalten Sie einen Resümee des Dokuments, indem Sie diese Seite und den AI-Anbieter Ihrer Wahl referenzieren
Der Inhalt dieser Seite wurde mit einer KI übersetzt.
Den englischen Originaltext ansehenWenn Sie eine Idee haben, um diese Dokumentation zu verbessern, zögern Sie bitte nicht, durch das Einreichen eines Pull-Requests auf GitHub beizutragen.
GitHub-Link zur DokumentationMarkdown des Dokuments in die Zwischenablage kopieren
next-intl VS @intlayer/next-intl | Gleiche API, Unterschiedliches Bundle
@intlayer/next-intl ist ein Compat-Adapter: Er stellt die next-intl API (useTranslations, getTranslations, useLocale, t.rich(), ICU Plurals, NextIntlClientProvider...) bereit und serviert diese aus von Intlayer kompilierten Dictionaries. Der Application Code ändert sich nicht. Das Bundle schon.
Dieser Artikel vergleicht die beiden auf derselben Next.js-Anwendung, einmal gebaut mit next-intl und einmal mit dem Adapter. Die Zahlen stammen von Benchmark Bloom, einer Open-Source-Suite, die aufzeichnet, was der Browser tatsächlich herunterlädt. Wenn Sie den next-intl vs Intlayer Vergleich als Bibliotheken möchten, lesen Sie next-intl vs Intlayer. Dieser Artikel handelt davon, was der Adapter ändert, wenn Sie Ihre Komponenten unverändert lassen.
tl;dr: Bei derselben Next.js-App reduzierte der Austausch vonnext-intlgegen@intlayer/next-intldas JavaScript pro Seite von 153,6 KB auf 147,5 KB gzip, die durchschnittliche Komponente von 21,8 KB auf 8,1 KB, Zeichenlecks fremder Seiten von ~90% auf 0% und Hydration von 14,7 ms auf 12,8 ms, ohne eine Komponente zu bearbeiten. Bei TanStack Start reduzierte das Äquivalentuse-intl(@intlayer/use-intl) Komponenten von 76-87 KB auf 9-11 KB und Locale-Wechsel von 7-21 ms auf 4-9 ms. Der Adapter kostet 8,0 KB Runtime gegenüber 14,7 KB fürnext-intlund 5,5 KB für nativesnext-intlayer. Navigation und Middleware werden auf Intlayers Routing-Konfiguration neu implementiert; lokalisiertepathnamessind die einzige Funktion, die nicht übernommen wird.
Was @intlayer/next-intl ist
next-intl ist eine Runtime: getRequestConfig lädt eine messages/{locale}.json pro Request, NextIntlClientProvider sendet sie an den Client, und useTranslations("about") liest zur Render-Zeit Schlüssel aus diesem Objekt. Jede Optimierung (Namespaces, pick(messages, [...]) pro Seite, Lazy Loading) musst du selbst schreiben.
@intlayer/next-intl behält den ersten und letzten Teil dieser Kette bei und ersetzt den mittleren. Deine Komponenten rufen weiterhin useTranslations("about") auf; was sie erhalten, kommt aus einem zur Build-Zeit kompilierten Intlayer Dictionary, auf die jeweilige Komponente begrenzt, nur in der aktiven Sprache.
Drei Mechanismen machen das möglich:
- Import-Aliasing.
createNextIntlPlugin()aus@intlayer/next-intl/pluginumhülltwithIntlayerund fügt Webpack / Turbopack-Aliase hinzu, damitnext-intl,next-intl/server,next-intl/navigationundnext-intl/middlewarezu@intlayer/next-intlaufgelöst werden. Kein Import in deiner Codebase wird umbenannt. - JSON als Quelle der Wahrheit. Das
syncJSON-Plugin liest deine vorhandenenmessages/{locale}.json, teilt ihre Top-Level-Keys in ein Dictionary pro Namespace auf und schreibt Übersetzungen in dieselben Dateien zurück, wenn die CLI oder das CMS diese aktualisiert. Der Workflow deiner Übersetzer bleibt unverändert. - Call-site binding. Der Intlayer-Optimierungspass (Babel oder SWC) schreibt
useTranslations("about")in einen Aufruf um, der dasabout-Wörterbuch direkt empfängt. Die Komponente greift nicht mehr auf einen globalen Message-Tree zu; sie greift auf ihren eigenen Content zu.
Kopieren Sie den Code in die Zwischenablage
Kopieren Sie den Code in die Zwischenablage
Dieses Rewrite ist der Grund, warum die Spalten "component-size" und "page-leakage" unten verschoben werden: Eine Seite lädt nur die Dictionaries der Komponenten, die sie rendert, und nur in dem Locale, das bereitgestellt wird.
Was der Adapter behält, ignoriert und nicht ersetzt
Tabelle in einem Modal öffnen, um alle Daten übersichtlich anzuzeigen
next-intl API | Mit @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ Beibehalten. An das ns Dictionary zur Compile-Zeit gebunden. Schlüssel sind typisiert gegen Ihren Content. |
getTranslations({ locale, namespace }) | ✅ Beibehalten |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ Beibehalten. ICU plurals, select, selectordinal, #, {ts, date, long} werden durch Intlayers ICU-Resolver verarbeitet |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ Beibehalten |
useFormatter() | ✅ Beibehalten. dateTime, number, relativeTime, list, dateTimeRange werden zu nativem Intl weitergeleitet |
NextIntlClientProvider | ✅ Beibehalten. Die Props messages, timeZone und now werden akzeptiert, aber ignoriert (eine Entwicklerwarnung informiert Sie darüber) |
getMessages() | ✅ Beibehalten für Kompatibilität; nicht mehr erforderlich |
getRequestConfig() in src/i18n.ts | ⚠️ Nicht erforderlich. Wörterbücher werden zur Build-Zeit kompiliert; es gibt kein Laden von Pro-Request-Nachrichten |
defineRouting() | ✅ Beibehalten. Ausgelassene Felder (locales, defaultLocale, localePrefix) werden aus intlayer.config.ts gelesen |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ Beibehalten. Neu implementiert auf Intlayer's Routing-Konfiguration; das routing-Argument wird akzeptiert, aber ignoriert |
pathnames (lokalisierte Routennamen) | ❌ Für Typisierung akzeptiert, nicht interpoliert. Behalten Sie einfache Pfadnamen oder verschieben Sie diese Zuordnung zu Intlayer's rewrite |
createMiddleware() | ✅ Beibehalten. Gibt Intlayer's Proxy zurück; setzt das NEXT_LOCALE-Cookie, damit useLocale() und Ihr Switcher weiterhin funktionieren |
NEXT_LOCALE Cookie | ✅ Standardmäßig gelesen (es sei denn, Sie konfigurieren routing.storage selbst) |
Bare useTranslations() ohne Namespace | ⚠️ Funktioniert, aber die Aufrufstelle ist nicht gebunden: sie wird durch die Runtime-Registry aufgelöst. Übergeben Sie einen Namespace, um die Bundle-Gewinne zu erhalten |
Der Benchmark
Was wurde gemessen
Die Benchmark Bloom Suite erstellt dieselbe Anwendung mit 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. Seiten werden in en und fr gemessen.
next-intl wurde mit vier Ladestrategien entwickelt, von der naiven Einrichtung (messages/{locale}.json vollständig geladen) bis zur optimalen Lösung (ein Namespace pro Route + pro-Seite pick()). Der Adapter wurde auf denselben Komponenten wie die naive Einrichtung entwickelt, wobei nur next.config.ts und intlayer.config.ts geändert wurden. Er hat keine "scoped"-Variante: Der Compiler scoped den Inhalt pro Komponente, daher sind seine static- und dynamic-Zeilen bereits gescoped.
Für jeden Build zeichnet die Suite auf:
- Lib size: gzip-Größe einer leeren Komponente, die nur die i18n-Bibliothek importiert. Die fixen Kosten der Runtime.
- Page JS: gzip JavaScript, das pro Seite heruntergeladen wird, gemittelt über alle Seiten und Locales.
- Locale leak %: Anteil der übersetzten Strings im heruntergeladenen JS, die zu einem Locale gehören, das der Benutzer nicht anzeigt.
- Page leak %: Anteil der übersetzten Strings im heruntergeladenen JS, die zu einer Seite gehören, auf der sich der Benutzer nicht befindet.
- Component avg: durchschnittliche gzip-Größe jeder Komponente, die isoliert kompiliert wird. Zeigt, wie viel i18n-Runtime und Katalog eine einzelne Komponente mit sich bringt.
- E2E reactivity: Wanduhrzeit zwischen der Auswahl eines neuen Locales und dem Aktualisieren von
html[lang]im DOM (Playwright, 5 Iterationen). - Hydration: Dauer der React-Hydration-Phase.
Die nachfolgenden Zahlen stammen aus dem Durchlauf vom 2026-09-12 mitnext-intl/use-intl4.14.2 und@intlayer/*9.5.1. Die Test-Anwendung ist absichtlich klein (einige Dutzend Strings pro Locale), sodass die Leak-Prozentsätze ein Muster beschreiben: Sie wachsen mit Ihrem Inhalt, während die Runtime-Kosten konstant bleiben.
Ergebnisse auf Next.js
Tabelle in einem Modal öffnen, um alle Daten übersichtlich anzuzeigen
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Wie man es liest
- Gleiche Komponenten, 6 KB weniger pro Seite. Der Adapter-Build der naiven App landet bei 147.5 KB, unter jeder
next-intl-Konfiguration, einschließlich der vollständig optimierten (153.6 KB). Die Runtime selbst ist der Unterschied: 8.0 KB gegenüber 14.7 KB, auf jeder Seite zu zahlen. - Lecks gehen auf 0% ohne Änderung einer Komponente. Das naive
next-intl-Setup versendet ~90% von fremdsprachigen Seiten-Strings auf jeder Seite. Um 0% mitnext-intlzu erreichen, sind diescoped-*-Setups erforderlich: ein Namespace pro Route undpick(messages, [...])auf jeder Seite. Der Adapter erreicht 0% aus dem naiven Code, weil der Optimize-Pass jedenuseTranslations("ns")an sein eigenes Dictionary bindet. - Komponenten schrumpfen um das 2,7-fache. Eine isoliert kompilierte Komponente belegt durchschnittlich 21,8 KB mit
next-intl(sie erreicht den Provider und den Message-Tree) und 8,1 KB mit dem Adapter. Imscoped-static-Setup vonnext-intlgeht diese Zahl auf 80 KB, weil jede Route's Namespace-Datei von der Seite aus erreichbar wird, die sie auswählt. - Hydration ist 2 ms schneller (12,8 vs 14,7 ms): Es gibt kein Message-Objekt, das vor der React-Hydration aus der RSC-Payload deserialisiert werden muss.
- Der Adapter ist nicht die native Runtime.
next-intlayerliegt bei 141,3 KB, +0,3 KB über der Base-App, mit einer 5,5 KB Runtime. Der Adapter bietet dienext-intlAPI-Oberfläche (useFormatter,t.rich, der ICU-Resolver) auf top von Intlayers Core, daher 8,0 KB und +6 KB pro Seite. Es ist die Brücke, nicht das Ziel.
Ergebnisse auf TanStack Start (use-intl)
use-intl ist der Framework-agnostische Core von next-intl. Sein Adapter, @intlayer/use-intl, folgt dem gleichen Design mit einem Vite-Plugin (@intlayer/use-intl/plugin).
Tabelle in einem Modal öffnen, um alle Daten übersichtlich anzuzeigen
| Setup | Strategie | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
Wie man es liest
- Pro-Seite Bytes sind ein Unentschieden gegen das optimierte
use-intl.@intlayer/use-intlimdynamicModus (129.7 KB) liegt innerhalb von 1 KB vonuse-intl'sscoped-dynamic(128.7 KB) und 10 KB überuse-intl's einfachemdynamic(119.4 KB). Diese einfachedynamicZeile leckt immer noch 90% der Strings von Fremdseiten; die Bytegröße ist niedrig, weil der Inhalt der Test-App klein ist. Der 0% Wert des Adapters bleibt flach, wenn der Inhalt wächst. - Komponenten sind 7-9x kleiner.
use-intlKomponenten sind im Durchschnitt 76-87 KB in jeder Strategie, weiluseTranslationsan das gesamte Message-Objekt des Providers gebunden ist. Der Adapter hat durchschnittlich 9-11 KB. - Locale-Wechsel ist schneller. Die optimierten
use-intlSetups benötigen 13-21 ms umhtml[lang]zu aktualisieren; der Adapter benötigt 4-9 ms. Weniger Komponenten werden neu gerendert, und nichts wird aus einem Message-Baum neu ausgewählt. staticbehält jedes Locale. DiestaticZeile des Adapters zeigt 49,7% Locale-Leckage, dasselbe wie natives Intlayer imstaticModus: alle Locales werden gebündelt, nur die Dictionaries der Seite. Eine Konfigurationszeile (importMode: 'dynamic') entfernt es.
Warum sich die Zahlen ändern
Nichts in der Komponente hat sich geändert, daher stammen die Verbesserungen vollständig davon, woran useTranslations gebunden ist.
Mit next-intl ist die Bindung der Provider. NextIntlClientProvider erhält das gesamte messages-Objekt für das Locale; jeder useTranslations("about")-Aufruf liest daraus. Der Bundler sieht eine Komponente, die einen Hook importiert, der einen Context liest, und kann nicht wissen, dass nur der about-Zweig verwendet wird. Die Routen unten teilen sich alle das gleiche Message-Objekt, daher zeigt die Spalte page-leak ~90%, bis du die Datei selbst aufteilst.
Kopieren Sie den Code in die Zwischenablage
Mit @intlayer/next-intl ist die Bindung das Dictionary. syncJSON wandelt messages/en.json in ein Dictionary pro Top-Level-Key um; der Compiler löst auf, welche Komponente useTranslations("about") aufruft, und übergibt ihr about direkt, in der aktiven Sprache, als Import, den der Bundler nachverfolgen und aufteilen kann.
Kopieren Sie den Code in die Zwischenablage
src/i18n.ts und die messages prop entfallen. Alles andere bleibt identisch.
Migration in drei Schritten
Installation
bashCode kopierenKopieren Sie den Code in die Zwischenablage
Der Befehl erkennt
next-intlund installiertintlayer,next-intlayer,@intlayer/next-intlund@intlayer/sync-json-plugin. Behalten Sienext-intlinstalliert: Es ist eine Peer-Abhängigkeit des Adapters und stellt die Typen bereit.Intlayer auf deine Messages hinweisen
intlayer.config.tsCode kopierenKopieren Sie den Code in die Zwischenablage
messages/{locale}.jsonbleibt an seinem Platz. Jeder Top-Level-Schlüssel wird zu einem Dictionary;useTranslations("about")wird demaboutDictionary zugeordnet.next.config.ts umhüllen
next.config.tsCode kopierenKopieren Sie den Code in die Zwischenablage
createNextIntlPlugin()setztwithIntlayerzusammen (Content Watching, Dictionary Compilation, der Optimize Pass) und dienext-intl→@intlayer/next-intlAliases für Webpack und Turbopack. Bauen Sie, und die Zahlen in den obigen Tabellen sind Ihre.
Was Sie danach löschen können
Tabelle in einem Modal öffnen, um alle Daten übersichtlich anzuzeigen
| Datei / Muster | Grund |
|---|---|
getRequestConfig in src/i18n.ts | Kein per-Request Message Loading. Behalten Sie die Datei nur, wenn sie auch createNavigation Helfer exportiert |
messages={...} auf NextIntlClientProvider | Der Adapter liest die kompilierte Ausgabe; das Prop wird ignoriert und protokolliert eine Warnung in der Entwicklung |
await getMessages() in Layouts | Gleicher Grund |
Pro-Seite pick(messages, [...]) | Der Compiler führt das Picking pro Komponente durch |
Was du darüber hinaus gewinnst
- Typisierte Keys.
useTranslations("about")ist gegen das kompilierteabout-Dictionary typisiert.t("does.not.exist")ist ein TypeScript-Fehler, kein Runtime-Fallback. npx intlayer testschlägt fehl in CI, wenn einem Locale ein Schlüssel fehlt.npx intlayer fillübersetzt die fehlenden Schlüssel mit dem Anbieter Ihrer Wahl (OpenAI, Anthropic, Mistral, Gemini...) unter Verwendung Ihres eigenen Schlüssels und schreibt das Ergebnis zurück inmessages/{locale}.json.- Visual Editor und CMS arbeiten mit denselben Dictionaries, sodass Nicht-Entwickler
messages/fr.jsondurch eine Benutzeroberfläche bearbeiten können und die Datei aktualisiert wird. - Schrittweise Migration zu
.content.ts. Jede Komponente kann vonuseTranslations("about")zuuseIntlayer("about")mit einer Co-located Content-Datei wechseln, eins nach dem anderen. JSON- und.content.ts-Dictionaries koexistieren und werden zusammengeführt.
Zu beachtende Limits vor dem Start
- Routing-Konfiguration verschiebt sich zu
intlayer.config.ts.createNavigation(routing)undcreateMiddleware(routing)behalten ihre Signatur, ignorieren aber das Argument: Locales, Default-Locale und Präfix-Strategie kommen aus Intlayersrouting-Konfiguration. Wenn dunext-intl's lokalisiertepathnamesverwendest (/about→/a-propos), interpoliert der Adapter diese nicht; Intlayersrouting.rewritedeckt diesen Fall ab, aber es ist eine separate Änderung. - Namespace-loses
useTranslations()ist nicht gebunden. Der Optimize-Pass benötigt einen statischen Namespace, um zu wissen, welches Dictionary importiert werden soll. Ein bloßer Aufruf funktioniert immer noch über eine Runtime-Registry, die auf jedes Dictionary verweist, was genau das Leck ist, das du entfernen wolltest. Übergib den Namespace. - Der Adapter ist nicht kostenlos. 8,0 KB Runtime gegenüber 5,5 KB für
next-intlayer, und +6-7 KB pro Seite gegenüber dem nativen Build. Dies zahlt sich durch dienext-intlAPI-Oberfläche aus. Wenn Sie an den Punkt gelangen, an dem jede Komponente zuuseIntlayerverschoben wurde, lassen Sie den Adapter fallen. messages,timeZone,nowauf dem Provider werden ignoriert. Die Formatter werden durch nativesIntlgestützt und nur das Locale beeinflusst deren Ausgabe; wenn Sie auf eine erzwungene Zeitzone oder ein fixesnowfür Hydrations-stabile Daten angewiesen sind, handhaben Sie dies am Aufrufort.
Wann sollte man welche verwenden?
- Bleiben Sie bei
next-intl, wenn Ihre App klein ist, Ihr Bundle kein Problem ist, und Ihr Team sich damit wohlfühlt, Namespaces undpick()pro Seite zu verwalten. - Nutzen Sie
@intlayer/next-intl, wenn Sie derzeitnext-intlverwenden und von Bundlegröße, weniger Leakage, Hydration-Verbesserungen, typisierten Keys und den CLI-/CMS-Tools ohne vollständiges Rewrite profitieren möchten. Dies ist der empfohlene Einstiegspunkt für bestehendenext-intl-Codebasen. - Wechseln Sie zu nativen Lösungen (
next-intlayer) für neue Projekte oder sobald der Adapter seinen Zweck erfüllt hat. Es ist die leichteste der drei Varianten (5,5 KB, +0,3 KB pro Seite) und ermöglicht synchrone Server Components, per-Component.content.ts-Dateien und den vollständigen Feature-Set.
Verwandte Vergleiche
- next-intl vs Intlayer (die Bibliotheken, gleicher Benchmark)
- i18next vs @intlayer/i18next (gleiche Adapter-Serie)
- Lingui vs @intlayer/lingui (gleiche Adapter-Serie)
- vue-i18n vs @intlayer/vue-i18n (gleiche Adapter-Serie)
- Migrationsleitfaden: next-intl zu Intlayer
- Kompatibilitäts-Adapter-Referenz: next-intl
Fazit
@intlayer/next-intl macht eine Sache: Es ändert, woran useTranslations gebunden ist, von einem Provider, der jede Nachricht enthält, zu einem für diese Komponente kompilierten Dictionary. In derselben Next.js-App, die 6 KB pro Seite wert ist, 2,7x kleinere Komponenten, 0% Lecks und 2 ms Hydration, bevor jemand eine Komponentendatei öffnet. Navigation und Middleware behalten ihre API auf Intlayers Routing-Konfiguration, und die native next-intlayer-Runtime bleibt noch leichter.
Alle Rohdaten, die Test-Apps und die Scripts befinden sich im Benchmark Bloom Repository. Führen Sie es selbst aus.
Weitere Details finden Sie in der Dokumentation "Why Intlayer?".
Kommentare
Noch keine Kommentare. Seien Sie der Erste, der seine Gedanken teilt.
