@intlayer/docs 8.7.4 → 8.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/id/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/pl/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/vi/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/dist/cjs/generated/docs.entry.cjs +60 -0
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +60 -0
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +3 -0
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/benchmark/index.md +29 -0
- package/docs/ar/benchmark/nextjs.md +227 -0
- package/docs/ar/benchmark/tanstack.md +193 -0
- package/docs/ar/intlayer_with_tanstack.md +0 -2
- package/docs/de/benchmark/index.md +29 -0
- package/docs/de/benchmark/nextjs.md +227 -0
- package/docs/de/benchmark/tanstack.md +193 -0
- package/docs/en/benchmark/___NOTE.md +82 -0
- package/docs/en/benchmark/___nextjs.md +195 -0
- package/docs/en/benchmark/___tanstack.md +187 -0
- package/docs/en/benchmark/index.md +29 -0
- package/docs/en/benchmark/nextjs.md +228 -0
- package/docs/en/benchmark/tanstack.md +217 -0
- package/docs/en-GB/benchmark/index.md +29 -0
- package/docs/en-GB/benchmark/nextjs.md +228 -0
- package/docs/en-GB/benchmark/tanstack.md +193 -0
- package/docs/es/benchmark/index.md +29 -0
- package/docs/es/benchmark/nextjs.md +226 -0
- package/docs/es/benchmark/tanstack.md +193 -0
- package/docs/fr/benchmark/index.md +29 -0
- package/docs/fr/benchmark/nextjs.md +227 -0
- package/docs/fr/benchmark/tanstack.md +193 -0
- package/docs/hi/benchmark/index.md +29 -0
- package/docs/hi/benchmark/nextjs.md +227 -0
- package/docs/hi/benchmark/tanstack.md +193 -0
- package/docs/id/benchmark/index.md +29 -0
- package/docs/id/benchmark/nextjs.md +227 -0
- package/docs/id/benchmark/tanstack.md +193 -0
- package/docs/id/intlayer_with_react_native+expo.md +0 -2
- package/docs/it/benchmark/index.md +29 -0
- package/docs/it/benchmark/nextjs.md +227 -0
- package/docs/it/benchmark/tanstack.md +193 -0
- package/docs/ja/benchmark/index.md +29 -0
- package/docs/ja/benchmark/nextjs.md +227 -0
- package/docs/ja/benchmark/tanstack.md +193 -0
- package/docs/ko/benchmark/index.md +29 -0
- package/docs/ko/benchmark/nextjs.md +227 -0
- package/docs/ko/benchmark/tanstack.md +193 -0
- package/docs/ko/intlayer_with_tanstack.md +0 -2
- package/docs/pl/benchmark/index.md +29 -0
- package/docs/pl/benchmark/nextjs.md +227 -0
- package/docs/pl/benchmark/tanstack.md +193 -0
- package/docs/pt/benchmark/index.md +29 -0
- package/docs/pt/benchmark/nextjs.md +227 -0
- package/docs/pt/benchmark/tanstack.md +193 -0
- package/docs/ru/benchmark/index.md +29 -0
- package/docs/ru/benchmark/nextjs.md +227 -0
- package/docs/ru/benchmark/tanstack.md +193 -0
- package/docs/tr/benchmark/index.md +29 -0
- package/docs/tr/benchmark/nextjs.md +227 -0
- package/docs/tr/benchmark/tanstack.md +193 -0
- package/docs/uk/benchmark/index.md +29 -0
- package/docs/uk/benchmark/nextjs.md +227 -0
- package/docs/uk/benchmark/tanstack.md +193 -0
- package/docs/vi/benchmark/index.md +29 -0
- package/docs/vi/benchmark/nextjs.md +227 -0
- package/docs/vi/benchmark/tanstack.md +193 -0
- package/docs/zh/benchmark/index.md +29 -0
- package/docs/zh/benchmark/nextjs.md +227 -0
- package/docs/zh/benchmark/tanstack.md +193 -0
- package/package.json +6 -6
- package/src/generated/docs.entry.ts +60 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Beste i18n-Lösung für Next.js im Jahr 2026 - Benchmark-Bericht
|
|
5
|
+
description: Vergleichen Sie Next.js Internationalisierungs-Software (i18n) wie next-intl, next-i18next und Intlayer. Detaillierter Performance-Bericht zu Bundle-Größe, Leakage und Reaktivität.
|
|
6
|
+
keywords:
|
|
7
|
+
- Benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- Performance
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- nextjs
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Benchmark initialisiert"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Next.js i18n-Bibliotheken — Benchmark-Bericht 2026
|
|
26
|
+
|
|
27
|
+
Diese Seite ist ein Benchmark-Bericht für i18n-Lösungen auf Next.js.
|
|
28
|
+
|
|
29
|
+
## Inhaltsverzeichnis
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Interaktiver Benchmark
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="nextjs" vertical/>
|
|
36
|
+
|
|
37
|
+
## Ergebnis-Referenz:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md
|
|
47
|
+
|
|
48
|
+
Das vollständige Benchmark-Repository finden Sie [hier](https://github.com/intlayer-org/benchmark-i18n).
|
|
49
|
+
|
|
50
|
+
## Einführung
|
|
51
|
+
|
|
52
|
+
Internationalisierungs-Bibliotheken haben einen starken Einfluss auf Ihre Anwendung. Das Hauptrisiko besteht darin, Inhalte für jede Seite und jede Sprache zu laden, wenn der Benutzer nur eine einzige Seite besucht.
|
|
53
|
+
|
|
54
|
+
Wenn Ihre App wächst, kann die Bundle-Größe exponentiell zunehmen, was die Performance spürbar beeinträchtigen kann.
|
|
55
|
+
|
|
56
|
+
Beispielsweise kann Ihre Seite nach der Internationalisierung bei den schlimmsten Beispielen fast viermal größer sein.
|
|
57
|
+
|
|
58
|
+
Ein weiterer Effekt von i18n-Bibliotheken ist die langsamere Entwicklung. Die Umwandlung von Komponenten in mehrsprachige Inhalte über verschiedene Sprachen hinweg ist zeitaufwendig.
|
|
59
|
+
|
|
60
|
+
Da das Problem komplex ist, gibt es viele Lösungen – einige konzentrieren sich auf die DX (Developer Experience), andere auf Performance oder Skalierbarkeit usw.
|
|
61
|
+
|
|
62
|
+
Intlayer versucht, über all diese Dimensionen hinweg zu optimieren.
|
|
63
|
+
|
|
64
|
+
## Testen Sie Ihre App
|
|
65
|
+
|
|
66
|
+
Um diese Probleme aufzudecken, habe ich einen kostenlosen Scanner entwickelt, den Sie [hier](https://intlayer.org/i18n-seo-scanner) ausprobieren können.
|
|
67
|
+
|
|
68
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
69
|
+
|
|
70
|
+
## Das Problem
|
|
71
|
+
|
|
72
|
+
Es gibt zwei wesentliche Möglichkeiten, die Auswirkungen einer mehrsprachigen App auf Ihr Bundle zu begrenzen:
|
|
73
|
+
|
|
74
|
+
- Aufteilung Ihres JSONs (oder Inhalts) auf Dateien / Variablen / Namespaces, damit der Bundler ungenutzte Inhalte für eine bestimmte Seite herausfiltern (Tree-shaking) kann.
|
|
75
|
+
- Dynamisches Laden Ihrer Seiteninhalte nur in der Sprache des Benutzers.
|
|
76
|
+
|
|
77
|
+
Technische Einschränkungen für diese Ansätze:
|
|
78
|
+
|
|
79
|
+
**Dynamisches Laden**
|
|
80
|
+
|
|
81
|
+
Auch wenn Sie Routen wie `[locale]/page.tsx` deklarieren, behandeln Webpack oder Turbopack `locale` nicht als statische Konstante, selbst wenn `generateStaticParams` definiert ist. Das bedeutet, dass Inhalte für alle Sprachen in jede Seite gezogen werden können. Der Hauptweg, dies zu begrenzen, besteht darin, Inhalte über einen dynamischen Import zu laden (z. B. `import('./locales/${locale}.json')`).
|
|
82
|
+
|
|
83
|
+
Beim Build-Vorgang erzeugt Next.js ein JS-Bundle pro Sprache (z. B. `./locales_fr_12345.js`). Wenn die Seite im Client ausgeführt wird, stellt der Browser eine zusätzliche HTTP-Anfrage für die benötigte JS-Datei (z. B. `./locales_fr_12345.js`).
|
|
84
|
+
|
|
85
|
+
> Ein anderer Weg, dasselbe Problem zu lösen, ist die Verwendung von `fetch()`, um JSON dynamisch zu laden. So arbeitet `Tolgee`, wenn JSON unter `/public` liegt, oder `next-translate`, das sich auf `getStaticProps` zum Laden von Inhalten verlässt. Der Ablauf ist derselbe: Der Browser stellt eine zusätzliche HTTP-Anfrage, um das Asset zu laden.
|
|
86
|
+
|
|
87
|
+
**Content-Splitting (Inhaltsaufteilung)**
|
|
88
|
+
|
|
89
|
+
Wenn Sie eine Syntax wie `const t = useTranslation()` + `t('mein-objekt.mein-unterobjekt.mein-schluessel')` verwenden, muss normalerweise das gesamte JSON im Bundle enthalten sein, damit die Bibliothek den Schlüssel auflösen kann. Ein Großteil dieses Inhalts wird also mitgeliefert, auch wenn er auf der Seite nicht verwendet wird.
|
|
90
|
+
|
|
91
|
+
Um dies abzumildern, verlangen einige Bibliotheken, dass Sie pro Seite deklarieren, welche Namespaces geladen werden sollen – z. B. `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
|
|
92
|
+
|
|
93
|
+
Im Gegensatz dazu fügt `Paraglide` vor dem Build einen zusätzlichen Schritt hinzu, um JSON in flache Symbole wie `const en_my_var = () => 'mein wert'` umzuwandeln. Theoretisch ermöglicht dies das Tree-shaking ungenutzter Inhalte auf der Seite. Wie wir sehen werden, hat diese Methode dennoch Nachteile.
|
|
94
|
+
|
|
95
|
+
Schließlich wendet `Intlayer` eine Build-Optimierung an, sodass `useIntlayer('mein-key')` direkt durch den entsprechenden Inhalt ersetzt wird.
|
|
96
|
+
|
|
97
|
+
## Methodik
|
|
98
|
+
|
|
99
|
+
Für diesen Benchmark haben wir die folgenden Bibliotheken verglichen:
|
|
100
|
+
|
|
101
|
+
- `Base App` (Ohne i18n-Bibliothek)
|
|
102
|
+
- `next-intlayer` (v8.7.5)
|
|
103
|
+
- `next-i18next` (v16.0.5)
|
|
104
|
+
- `next-intl` (v4.9.1)
|
|
105
|
+
- `@lingui/core` (v5.3.0)
|
|
106
|
+
- `next-translate` (v3.1.2)
|
|
107
|
+
- `next-international` (v1.3.1)
|
|
108
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
109
|
+
- `tolgee` (v7.0.0)
|
|
110
|
+
- `@lingo.dev/compiler` (v0.4.0)
|
|
111
|
+
- `wuchale` (v0.22.11)
|
|
112
|
+
- `gt-next` (v6.16.5)
|
|
113
|
+
|
|
114
|
+
Ich habe `Next.js` Version `16.2.4` mit dem App Router verwendet.
|
|
115
|
+
|
|
116
|
+
Ich habe eine mehrsprachige App mit **10 Seiten** und **10 Sprachen** erstellt.
|
|
117
|
+
|
|
118
|
+
Ich habe **vier Ladestrategien** verglichen:
|
|
119
|
+
|
|
120
|
+
| Strategie | Ohne Namespaces (global) | Mit Namespaces (scoped) |
|
|
121
|
+
| :-------------------- | :-------------------------------------------- | :-------------------------------------------------------------------- |
|
|
122
|
+
| **Statisches Laden** | **Statischer**: Alles beim Start im Speicher. | **Scoped static**: Nach Namespace getrennt; alles beim Start geladen. |
|
|
123
|
+
| **Dynamisches Laden** | **Dynamic**: Laden bei Bedarf pro Sprache. | **Scoped dynamic**: Granulares Laden pro Namespace und Sprache. |
|
|
124
|
+
|
|
125
|
+
## Strategie-Zusammenfassung
|
|
126
|
+
|
|
127
|
+
- **Statischer (Static)**: Einfach; keine Netzwerklatenz nach dem ersten Laden. Nachteil: große Bundle-Größe.
|
|
128
|
+
- **Dynamic**: Reduziert das Anfangsgewicht (Lazy-Loading). Ideal bei vielen Sprachen.
|
|
129
|
+
- **Scoped static**: Hält den Code organisiert (logische Trennung) ohne komplexe zusätzliche Netzwerkanfragen.
|
|
130
|
+
- **Scoped dynamic**: Bester Ansatz für _Code-Splitting_ und Performance. Minimiert den Speicherbedarf, indem nur das geladen wird, was der aktuelle View und die aktive Sprache benötigen.
|
|
131
|
+
|
|
132
|
+
### Was ich gemessen habe:
|
|
133
|
+
|
|
134
|
+
Ich habe dieselbe mehrsprachige Anwendung in einem echten Browser für jedes Framework ausgeführt und dokumentiert, was tatsächlich über das Netzwerk übertragen wurde und wie lange die Vorgänge dauerten. Die Größenangaben beziehen sich auf den Zustand **nach normaler Web-Komprimierung**, da dies näher an dem liegt, was Benutzer tatsächlich herunterladen.
|
|
135
|
+
|
|
136
|
+
- **Größe der Internationalisierungs-Bibliothek**: Nach dem Bundling, Tree-shaking und Minifizieren entspricht die Größe der i18n-Bibliothek der Größe der Provider (z. B. `NextIntlClientProvider`) + dem Code der Hooks (z. B. `useTranslations`) in einer leeren Komponente – ohne das Laden von Übersetzungsdateien. Dies beantwortet die Frage, wie "teuer" die Bibliothek ist, bevor Ihre Inhalte hinzukommen.
|
|
137
|
+
|
|
138
|
+
- **JavaScript pro Seite**: Für jede Benchmark-Route die Menge an Skripten, die der Browser für diesen Besuch abruft, gemittelt über alle Seiten der Suite (und über die Sprachen hinweg). Schwere Seiten sind langsame Seiten.
|
|
139
|
+
|
|
140
|
+
- **Leakage von anderen Sprachen**: Dies ist der Inhalt derselben Seite, aber in einer anderen Sprache, der fälschlicherweise in die untersuchte Seite geladen wird. Dieser Inhalt ist unnötig und sollte vermieden werden (z. B. Inhalt der `/fr/about`-Seite im Bundle der `/en/about`-Seite).
|
|
141
|
+
|
|
142
|
+
- **Leakage von anderen Routen**: Das gleiche Konzept für **andere Ansichten** in der App: ob deren Texte mitgeliefert werden, obwohl Sie nur eine einzige Seite geöffnet haben (z. B. Inhalt der `/en/about`-Seite im Bundle der `/en/contact`-Seite). Ein hoher Wert deutet auf schwaches Splitting oder zu weit gefasste Bundles hin.
|
|
143
|
+
|
|
144
|
+
- **Durchschnittliche Bundle-Größe pro Komponente**: Gängige UI-Elemente werden **einzeln** gemessen, anstatt in einer riesigen App-Zahl unterzugehen. Es zeigt, ob die Internationalisierung alltägliche Komponenten heimlich aufbläht. Wenn Ihre Komponente beispielsweise neu gerendert wird, lädt sie all diese Daten aus dem Speicher. Das Anhängen eines riesigen JSONs an eine Komponente ist wie der Anschluss eines großen Speichers für ungenutzte Daten, der die Performance Ihrer Komponenten verlangsamt.
|
|
145
|
+
|
|
146
|
+
- **Reaktionszeit beim Sprachwechsel**: Ich wechsle die Sprache über das eigene Steuerelement der App und messe die Zeit, bis die Seite sichtlich umgeschaltet hat – das, was ein Besucher bemerken würde, kein technischer Mikroschritt.
|
|
147
|
+
|
|
148
|
+
- **Render-Arbeit nach einem Sprachwechsel**: Eine detailliertere Untersuchung: Wie viel Aufwand die Oberfläche betreiben musste, um nach dem Umschalten die neue Sprache zu zeichnen. Nützlich, wenn die "gefühlte" Zeit und die Framework-Kosten auseinanderklaffen.
|
|
149
|
+
|
|
150
|
+
- **Initiale Seitenladezeit**: Von der Navigation bis zu dem Zeitpunkt, an dem der Browser die Seite für die von mir getesteten Szenarien als vollständig geladen betrachtet. Gut zum Vergleich von Kaltstarts (Cold Starts).
|
|
151
|
+
|
|
152
|
+
- **Hydrierungszeit**: Sofern die App dies ausgibt: Wie lange der Client benötigt, um Server-HTML in etwas Interaktives zu verwandeln. Ein Bindestrich in den Tabellen bedeutet, dass diese Implementierung in diesem Benchmark keinen zuverlässigen Hydrierungswert geliefert hat.
|
|
153
|
+
|
|
154
|
+
## Ergebnisse im Detail
|
|
155
|
+
|
|
156
|
+
### 1 — Zu vermeidende Lösungen
|
|
157
|
+
|
|
158
|
+
Einige Lösungen wie `gt-next` oder `lingo.dev` sollten klar vermieden werden. Sie kombinieren Vendor-Lock-in mit einer Verunreinigung Ihrer Codebasis. Trotz vieler Stunden Arbeit ist es mir nicht gelungen, sie zum Laufen zu bringen – weder auf TanStack Start noch auf Next.js.
|
|
159
|
+
|
|
160
|
+
Aufgetretene Probleme:
|
|
161
|
+
|
|
162
|
+
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
|
+
|
|
164
|
+
- Bei einer 110-KB-App fügt `gt-react` mehr als 440 KB hinzu.
|
|
165
|
+
- `Quota Exceeded, please upgrade your plan` bereits beim allerersten Build mit General Translation.
|
|
166
|
+
- Übersetzungen werden nicht gerendert; ich erhalte den Fehler `Error: <T> used on the client-side outside of <GTProvider>`, was anscheinend ein Bug in der Bibliothek ist.
|
|
167
|
+
- Bei der Implementierung von **gt-tanstack-start-react** stieß ich ebenfalls auf ein [Problem](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) mit der Bibliothek: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, was zum Absturz der Anwendung führte. Nach Meldung dieses Problems behob der Maintainer es innerhalb von 24 Stunden.
|
|
168
|
+
- Die Bibliothek blockiert das statische Rendering von Next.js-Seiten.
|
|
169
|
+
|
|
170
|
+
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
171
|
+
|
|
172
|
+
- AI-Quota überschritten, was den Build komplett blockiert – man kann also nicht ohne Bezahlung produktiv gehen.
|
|
173
|
+
- Der Compiler überging fast 40 % der übersetzten Inhalte. Ich musste alle `.map`-Aufrufe in flache Komponentenblöcke umschreiben, damit es funktioniert.
|
|
174
|
+
- Ihr CLI ist fehleranfällig und setzte die Konfigurationsdatei grundlos zurück.
|
|
175
|
+
- Beim Build löschte es die generierten JSONs vollständig, sobald neuer Inhalt hinzugefügt wurde. Dies konnte dazu führen, dass eine Handvoll Schlüssel mehr als 300 bestehende Schlüssel vernichtete.
|
|
176
|
+
|
|
177
|
+
### 2 — Experimentelle Lösungen
|
|
178
|
+
|
|
179
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
180
|
+
|
|
181
|
+
Die Idee hinter `Wuchale` ist interessant, aber noch nicht reif für den Einsatz. Ich stieß auf Reaktivitätsprobleme und musste den Provider-Re-render erzwingen, um die App zum Laufen zu bringen. Die Dokumentation ist zudem recht unklar, was den Einstieg erschwert.
|
|
182
|
+
|
|
183
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
184
|
+
|
|
185
|
+
`Paraglide` bietet einen innovativen, gut durchdachten Ansatz. Dennoch funktionierte in diesem Benchmark das beworbene Tree-shaking für meine Next.js- oder TanStack Start-Setups nicht. Der Workflow und die DX sind komplexer als bei anderen Optionen.
|
|
186
|
+
Persönlich missfällt mir die Notwendigkeit, JS-Dateien vor jedem Push neu zu generieren, was ein ständiges Risiko für Merge-Konflikte in PRs darstellt. Das Tool scheint zudem stärker auf Vite als auf Next.js ausgerichtet zu sein.
|
|
187
|
+
Schließlich nutzt Paraglide im Vergleich zu anderen Lösungen keinen Store (z. B. React Context), um die aktuelle Sprache für das Rendering abzurufen. Für jeden geparsten Node wird die Sprache aus dem localStorage / Cookie etc. angefragt. Dies führt zur Ausführung unnötiger Logik, die die Reaktivität der Komponenten beeinträchtigt.
|
|
188
|
+
|
|
189
|
+
### 3 — Akzeptable Lösungen
|
|
190
|
+
|
|
191
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
192
|
+
|
|
193
|
+
`Tolgee` adressiert viele der oben genannten Probleme. Ich fand es schwieriger zu adoptieren als ähnliche Tools. Es bietet keine Typsicherheit, was es zudem erschwert, fehlende Schlüssel zur Kompilierzeit zu finden. Ich musste die Tolgee-Funktionen mit eigenen Funktionen umhüllen, um eine Erkennung fehlender Schlüssel hinzuzufügen.
|
|
194
|
+
|
|
195
|
+
**(Next Intl)** (`next-intl@4.9.1`):
|
|
196
|
+
|
|
197
|
+
`next-intl` ist die derzeit angesagteste Option und diejenige, die KI-Assistenten am häufigsten empfehlen, meiner Ansicht nach jedoch fälschlicherweise. Der Einstieg ist einfach. In der Praxis ist die Optimierung zur Begrenzung von Leakage komplex. Die Kombination aus dynamischem Laden + Namespacing + TypeScript-Typen verlangsamt die Entwicklung enorm. Das Paket ist zudem recht schwer (~13 KB für `NextIntlClientProvider` + `useTranslations`, mehr als doppelt so viel wie `next-intlayer`). **next-intl** blockierte früher das statische Rendering von Next.js-Seiten. Es bietet einen Helper namens `setRequestLocale()`. Dies scheint für zentralisierte Dateien wie `en.json` / `fr.json` teilweise gelöst zu sein, aber das statische Rendering bricht immer noch ab, wenn Inhalte in Namespaces wie `en/shared.json` / `fr/shared.json` / `es/shared.json` aufgeteilt sind.
|
|
198
|
+
|
|
199
|
+
**(Next I18next)** (`next-i18next@16.0.5`):
|
|
200
|
+
|
|
201
|
+
`next-i18next` ist wahrscheinlich die beliebteste Option, da es eine der ersten i18n-Lösungen für JavaScript-Apps war. Es gibt viele Community-Plugins. Es teilt die gleichen großen Nachteile wie `next-intl`. Das Paket ist besonders schwer (~18 KB für `I18nProvider` + `useTranslation`, etwa dreimal so viel wie `next-intlayer`).
|
|
202
|
+
|
|
203
|
+
Die Nachrichtenformate unterscheiden sich ebenfalls: `next-intl` verwendet ICU MessageFormat, während `i18next` sein eigenes Format nutzt.
|
|
204
|
+
|
|
205
|
+
**(Next International)** (`next-international@1.3.1`):
|
|
206
|
+
|
|
207
|
+
`next-international` geht die oben genannten Probleme ebenfalls an, unterscheidet sich aber nicht wesentlich von `next-intl` oder `next-i18next`. Es enthält `scopedT()` für Namespace-spezifische Übersetzungen, aber dessen Verwendung hat praktisch keinen Einfluss auf die Bundle-Größe.
|
|
208
|
+
|
|
209
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
210
|
+
|
|
211
|
+
`Lingui` wird oft gelobt. Persönlich fand ich den `lingui extract` / `lingui compile` Workflow komplexer als Alternativen, ohne einen klaren Vorteil. Mir fielen zudem inkonsistente Syntaxen auf, die KIs verwirren (z. B. `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
212
|
+
|
|
213
|
+
### 4 — Empfehlungen
|
|
214
|
+
|
|
215
|
+
**(Next Translate)** (`next-translate@3.1.2`):
|
|
216
|
+
|
|
217
|
+
`next-translate` ist meine Hauptempfehlung, wenn Sie eine API im `t()`-Stil bevorzugen. Es ist durch das `next-translate-plugin` elegant gelöst und lädt Namespaces über `getStaticProps` mit einem Webpack / Turbopack Loader. Es ist zudem die leichteste Option hier (~2,5 KB). Für das Namespacing ist die Definition pro Seite oder Route in der Konfiguration gut durchdacht und einfacher zu warten als die Hauptalternativen wie **next-intl** oder **next-i18next**. In Version `3.1.2` stellte ich fest, dass das statische Rendering nicht funktionierte; Next.js fiel auf dynamisches Rendering zurück.
|
|
218
|
+
|
|
219
|
+
**(Intlayer)** (`next-intlayer@8.7.5`):
|
|
220
|
+
|
|
221
|
+
Ich werde `next-intlayer` der Objektivität halber nicht persönlich bewerten, da es meine eigene Lösung ist.
|
|
222
|
+
|
|
223
|
+
### Persönliche Anmerkung
|
|
224
|
+
|
|
225
|
+
Diese Anmerkung ist persönlicher Natur und beeinflusst die Benchmark-Ergebnisse nicht. In der i18n-Welt sieht man oft einen Konsens um das Muster `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>`.
|
|
226
|
+
|
|
227
|
+
In React-Apps ist das Injizieren einer Funktion als `ReactNode` meiner Meinung nach ein Anti-Pattern. Es fügt zudem vermeidbare Komplexität und JavaScript-Ausführungs-Overhead hinzu (wenn auch kaum spürbar).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Beste i18n-Lösung für TanStack Start im Jahr 2026 - Benchmark-Bericht
|
|
5
|
+
description: Vergleichen Sie TanStack Start Internationalisierungs-Bibliotheken wie react-i18next, use-intl und Intlayer. Detaillierter Performance-Bericht zu Bundle-Größe, Leakage und Reaktivität.
|
|
6
|
+
keywords:
|
|
7
|
+
- Benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- tanstack
|
|
11
|
+
- Performance
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- tanstack
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-tanstack-start-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Benchmark initialisiert"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# TanStack Start i18n-Bibliotheken — Benchmark-Bericht 2026
|
|
26
|
+
|
|
27
|
+
Diese Seite ist ein Benchmark-Bericht für i18n-Lösungen auf TanStack Start.
|
|
28
|
+
|
|
29
|
+
## Inhaltsverzeichnis
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Interaktiver Benchmark
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
36
|
+
|
|
37
|
+
## Ergebnis-Referenz:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md
|
|
47
|
+
|
|
48
|
+
Das vollständige Benchmark-Repository finden Sie [hier](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Einführung
|
|
51
|
+
|
|
52
|
+
Internationalisierungs-Lösungen gehören zu den schwersten Abhängigkeiten in einer React-App. Bei TanStack Start besteht das Hauptrisiko darin, unnötige Inhalte mitzuliefern: Übersetzungen für andere Seiten und andere Sprachen im Bundle einer einzelnen Route.
|
|
53
|
+
|
|
54
|
+
Wenn Ihre App wächst, kann dieses Problem den an den Client gesendeten JavaScript-Code schnell aufblähen und die Navigation verlangsamen.
|
|
55
|
+
|
|
56
|
+
In der Praxis kann eine internationalisierte Seite bei den am wenigsten optimierten Implementierungen um ein Vielfaches schwerer sein als die Version ohne i18n.
|
|
57
|
+
|
|
58
|
+
Ein weiterer Aspekt ist die Developer Experience (DX): Wie deklarieren Sie Inhalte, Typen, Namespace-Organisation, dynamisches Laden und Reaktivität bei Sprachwechseln.
|
|
59
|
+
|
|
60
|
+
## Testen Sie Ihre App
|
|
61
|
+
|
|
62
|
+
Um i18n-Leakage-Probleme schnell zu erkennen, habe ich einen kostenlosen Scanner eingerichtet, den Sie [hier](https://intlayer.org/i18n-seo-scanner) finden.
|
|
63
|
+
|
|
64
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
65
|
+
|
|
66
|
+
## Das Problem
|
|
67
|
+
|
|
68
|
+
Zwei Hebel sind entscheidend, um die Kosten einer mehrsprachigen App zu begrenzen:
|
|
69
|
+
|
|
70
|
+
- Aufteilung der Inhalte pro Seite / Namespace, um nicht ganze Wörterbücher zu laden, wenn sie nicht benötigt werden.
|
|
71
|
+
- Dynamisches Laden der richtigen Sprache nur bei Bedarf.
|
|
72
|
+
|
|
73
|
+
Die technischen Einschränkungen dieser Ansätze verstehen:
|
|
74
|
+
|
|
75
|
+
**Dynamisches Laden**
|
|
76
|
+
|
|
77
|
+
Ohne dynamisches Laden behalten die meisten Lösungen Nachrichten ab dem ersten Rendern im Speicher, was bei Apps mit vielen Routen und Sprachen einen erheblichen Overhead verursacht.
|
|
78
|
+
|
|
79
|
+
Beim dynamischen Laden gehen Sie einen Kompromiss ein: Weniger initiales JS, aber manchmal eine zusätzliche Anfrage beim Sprachwechsel.
|
|
80
|
+
|
|
81
|
+
**Content-Splitting (Inhaltsaufteilung)**
|
|
82
|
+
|
|
83
|
+
Syntaxansätze um `const t = useTranslation()` + `t('a.b.c')` sind sehr bequem, fördern aber oft das Vorhalten großer JSON-Objekte zur Laufzeit. Dieses Modell erschwert das Tree-shaking, es sei denn, die Bibliothek bietet eine echte seitenbasierte Aufteilungsstrategie.
|
|
84
|
+
|
|
85
|
+
## Methodik
|
|
86
|
+
|
|
87
|
+
Für diesen Benchmark haben wir die folgenden Bibliotheken verglichen:
|
|
88
|
+
|
|
89
|
+
- `Base App` (Ohne i18n-Bibliothek)
|
|
90
|
+
- `react-intlayer` (v8.7.5-canary.0)
|
|
91
|
+
- `react-i18next` (v17.0.2)
|
|
92
|
+
- `use-intl` (v4.9.1)
|
|
93
|
+
- `@lingui/core` (v5.3.0)
|
|
94
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
95
|
+
- `tolgee` (v7.0.0)
|
|
96
|
+
- `react-intl` (v10.1.1)
|
|
97
|
+
- `wuchale` (v0.22.11)
|
|
98
|
+
- `gt-react` (vlatest)
|
|
99
|
+
- `lingo.dev` (v0.133.9)
|
|
100
|
+
|
|
101
|
+
Das Framework ist `TanStack Start` mit einer mehrsprachigen App mit **10 Seiten** und **10 Sprachen**.
|
|
102
|
+
|
|
103
|
+
Wir haben **vier Ladestrategien** verglichen:
|
|
104
|
+
|
|
105
|
+
| Strategie | Ohne Namespaces (global) | Mit Namespaces (scoped) |
|
|
106
|
+
| :-------------------- | :-------------------------------------------- | :-------------------------------------------------------------------- |
|
|
107
|
+
| **Statisches Laden** | **Statischer**: Alles beim Start im Speicher. | **Scoped static**: Nach Namespace getrennt; alles beim Start geladen. |
|
|
108
|
+
| **Dynamisches Laden** | **Dynamic**: Laden bei Bedarf pro Sprache. | **Scoped dynamic**: Granulares Laden pro Namespace und Sprache. |
|
|
109
|
+
|
|
110
|
+
## Strategie-Zusammenfassung
|
|
111
|
+
|
|
112
|
+
- **Statischer (Static)**: Einfach; keine Netzwerklatenz nach dem ersten Laden. Nachteil: große Bundle-Größe.
|
|
113
|
+
- **Dynamic**: Reduziert das Anfangsgewicht (Lazy-Loading). Ideal bei vielen Sprachen.
|
|
114
|
+
- **Scoped static**: Hält den Code organisiert (logische Trennung) ohne komplexe zusätzliche Netzwerkanfragen.
|
|
115
|
+
- **Scoped dynamic**: Bester Ansatz für _Code-Splitting_ und Performance. Minimiert den Speicherbedarf, indem nur das geladen wird, was der aktuelle View und die aktive Sprache benötigen.
|
|
116
|
+
|
|
117
|
+
## Ergebnisse im Detail
|
|
118
|
+
|
|
119
|
+
### 1 — Zu vermeidende Lösungen
|
|
120
|
+
|
|
121
|
+
Einige Lösungen wie `gt-react` oder `lingo.dev` sollten klar gemieden werden. Sie kombinieren Vendor-Lock-in mit einer Verunreinigung Ihrer Codebasis. Schlimmer noch: Trotz vieler Stunden Arbeit ist es mir nicht gelungen, sie auf TanStack Start zum Laufen zu bringen (ähnlich wie bei Next.js mit `gt-next`).
|
|
122
|
+
|
|
123
|
+
Aufgetretene Probleme:
|
|
124
|
+
|
|
125
|
+
**(General Translation)** (`gt-react@latest`):
|
|
126
|
+
|
|
127
|
+
- Bei einer App von etwa 110 KB kann `gt-react` mehr als 440 KB zusätzlich hinzufügen (Größenordnung gesehen bei der Next.js-Implementierung im selben Benchmark).
|
|
128
|
+
- `Quota Exceeded, please upgrade your plan` bereits beim allerersten Build mit General Translation.
|
|
129
|
+
- Übersetzungen werden nicht gerendert; ich erhalte den Fehler `Error: <T> used on the client-side outside of <GTProvider>`, was anscheinend ein Bug in der Bibliothek ist.
|
|
130
|
+
- Bei der Implementierung von **gt-tanstack-start-react** stieß ich ebenfalls auf ein [Problem](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) mit der Bibliothek: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, was zum Absturz der Anwendung führte. Nach Meldung dieses Problems behob der Maintainer es innerhalb von 24 Stunden.
|
|
131
|
+
- Diese Bibliotheken nutzen durch die Funktion `initializeGT()` ein Anti-Pattern, das verhindert, dass das Bundle sauber per Tree-shaking bereinigt wird.
|
|
132
|
+
|
|
133
|
+
**(Lingo.dev)** (`lingo.dev@0.133.9`):
|
|
134
|
+
|
|
135
|
+
- AI-Quota überschritten (oder blockierte Server-Abhängigkeit), was Build / Produktion ohne Bezahlung riskant macht.
|
|
136
|
+
- Der Compiler überging fast 40 % der übersetzten Inhalte. Ich musste alle `.map`-Aufrufe in flache Komponentenblöcke umschreiben, damit es funktioniert.
|
|
137
|
+
- Ihr CLI ist fehleranfällig und setzte die Konfigurationsdatei grundlos zurück.
|
|
138
|
+
- Beim Build löschte es die generierten JSONs vollständig, sobald neuer Inhalt hinzugefügt wurde. Dies konnte dazu führen, dass nur wenige Schlüssel hunderte bestehende Schlüssel vernichteten.
|
|
139
|
+
- Ich stieß auf Reaktivitätsprobleme mit der Bibliothek auf TanStack Start: Beim Sprachwechsel musste ich ein Re-render des Providers erzwingen, damit es funktioniert.
|
|
140
|
+
|
|
141
|
+
### 2 — Experimentelle Lösungen
|
|
142
|
+
|
|
143
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
144
|
+
|
|
145
|
+
Die Idee hinter `Wuchale` ist interessant, aber noch keine tragfähige Lösung. Ich stieß auf Reaktivitätsprobleme und musste einen Re-render des Providers erzwingen, um die App auf TanStack Start zum Laufen zu bringen. Die Dokumentation ist zudem recht unklar, was den Einstieg erschwert.
|
|
146
|
+
|
|
147
|
+
### 3 — Akzeptable Lösungen
|
|
148
|
+
|
|
149
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
150
|
+
|
|
151
|
+
`Paraglide` bietet einen innovativen, gut durchdachten Ansatz. Dennoch funktionierte in diesem Benchmark das beworbene Tree-shaking weder für meine Next.js-Implementierung noch für TanStack Start. Der Workflow und die DX sind zudem komplexer als bei anderen Optionen. Persönlich bin ich kein Fan davon, JS-Dateien vor jedem Push neu generieren zu müssen, was ständige Risiken für Merge-Konflikte in PRs birgt.
|
|
152
|
+
|
|
153
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
154
|
+
|
|
155
|
+
`Tolgee` adressiert viele der oben genannten Probleme. Ich fand den Einstieg schwieriger als bei anderen Tools mit ähnlichen Ansätzen. Es bietet keine Typsicherheit, was es zudem sehr schwer macht, fehlende Schlüssel zur Kompilierzeit zu finden. Ich musste die Tolgee-APIs mit eigenen Funktionen umhüllen, um eine Erkennung fehlender Schlüssel hinzuzufügen.
|
|
156
|
+
|
|
157
|
+
Auf TanStack Start hatte ich ebenfalls Reaktivitätsprobleme: Beim Sprachwechsel musste ich den Provider zum Re-render zwingen und Locale-Wechsel-Events abonnieren, damit das Laden in einer anderen Sprache korrekt funktionierte.
|
|
158
|
+
|
|
159
|
+
**(use-intl)** (`use-intl@4.9.1`):
|
|
160
|
+
|
|
161
|
+
`use-intl` ist derzeit das angesagteste "intl"-Stück im React-Ökosystem (aus der gleichen Familie wie `next-intl`) und wird oft von KI-Assistenten empfohlen, meiner Ansicht nach jedoch fälschlicherweise in einem Performance-orientierten Umfeld. Der Einstieg ist recht einfach. In der Praxis ist der Prozess zur Optimierung und Begrenzung von Leakage jedoch ziemlich komplex. Gleichermaßen verlangsamt die Kombination aus dynamischem Laden + Namespacing + TypeScript-Typen die Entwicklung erheblich.
|
|
162
|
+
|
|
163
|
+
Bei TanStack Start vermeiden Sie Next.js-spezifische Fallen (`setRequestLocale`, statisches Rendering), aber das Kernproblem bleibt gleich: Ohne strikte Disziplin transportiert das Bundle schnell zu viele Nachrichten, und die Namespace-Wartung pro Route wird mühsam.
|
|
164
|
+
|
|
165
|
+
**(react-i18next)** (`react-i18next@17.0.2`):
|
|
166
|
+
|
|
167
|
+
`react-i18next` ist wahrscheinlich die beliebteste Option, da es eine der ersten war, die i18n-Anforderungen für JavaScript-Apps bediente. Es verfügt zudem über ein breites Spektrum an Community-Plugins für spezifische Probleme.
|
|
168
|
+
|
|
169
|
+
Dennoch teilt es dieselben großen Nachteile wie Stacks, die auf `t('a.b.c')` basieren: Optimierungen sind möglich, aber sehr zeitaufwendig, und große Projekte riskieren schlechte Praktiken (Namespaces + dynamisches Laden + Typen).
|
|
170
|
+
|
|
171
|
+
Die Nachrichtenformate weichen ebenfalls voneinander ab: `use-intl` verwendet ICU MessageFormat, während `i18next` sein eigenes Format nutzt – was Tooling oder Migrationen kompliziert macht, wenn man sie mischt.
|
|
172
|
+
|
|
173
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
174
|
+
|
|
175
|
+
`Lingui` wird oft gelobt. Persönlich fand ich den Workflow um `lingui extract` / `lingui compile` komplexer als andere Ansätze, ohne einen klaren Vorteil in diesem TanStack Start-Benchmark. Mir fielen zudem inkonsistente Syntaxen auf, die KIs verwirren (z. B. `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
176
|
+
|
|
177
|
+
**(react-intl)** (`react-intl@10.1.1`):
|
|
178
|
+
|
|
179
|
+
`react-intl` ist eine performante Implementierung des Format.js-Teams. Die DX bleibt wortreich: `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` erhöht die Komplexität, den zusätzlichen JavaScript-Aufwand und bindet die globale i18n-Instanz an viele Knoten im React-Tree.
|
|
180
|
+
|
|
181
|
+
### 4 — Empfehlungen
|
|
182
|
+
|
|
183
|
+
Dieser TanStack Start-Benchmark hat kein direktes Äquivalent zu `next-translate` (Next.js-Plugin + `getStaticProps`). Für Teams, die unbedingt eine `t()`-API mit einem ausgereiften Ökosystem wollen, bleiben `react-i18next` und `use-intl` "vernünftige" Entscheidungen, aber stellen Sie sich darauf ein, viel Zeit in die Optimierung zu investieren, um Leakage zu vermeiden.
|
|
184
|
+
|
|
185
|
+
**(Intlayer)** (`react-intlayer@8.7.5-canary.0`):
|
|
186
|
+
|
|
187
|
+
Ich werde `react-intlayer` der Objektivität halber nicht persönlich bewerten, da es meine eigene Lösung ist.
|
|
188
|
+
|
|
189
|
+
### Persönliche Anmerkung
|
|
190
|
+
|
|
191
|
+
Diese Anmerkung ist persönlicher Natur und beeinflusst die Benchmark-Ergebnisse nicht. Dennoch sieht man in der i18n-Welt oft einen Konsens um ein Muster wie `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` für übersetzte Inhalte.
|
|
192
|
+
|
|
193
|
+
In React-Apps ist das Injizieren einer Funktion als `ReactNode` meiner Meinung nach ein Anti-Pattern. Es fügt zudem vermeidbare Komplexität und JavaScript-Ausführungs-Overhead hinzu (selbst wenn es kaum spürbar ist).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
**gt-react** / **gt-next**
|
|
2
|
+
|
|
3
|
+
I was not able to test gt-react / gt-next. The libraries are not functional and break the application.
|
|
4
|
+
|
|
5
|
+
- I was not able to test the app reactivity.
|
|
6
|
+
- Translations are not rendered; I get the error `Error: <T> used on the client-side outside of <GTProvider>`, which seems to be a bug in the library.
|
|
7
|
+
- While implementing **gt-tanstack-start-react**, I also came across an [issue](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) with the library: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, which was making the application break. After reporting this issue, the maintainer fixed it within 24 hours.
|
|
8
|
+
- This library requires a dependency on the General Translation server for translation. Even a single translation attempt returned an error: `Quota Exceeded, please upgrade your plan`. The risk of vendor lock-in is high.
|
|
9
|
+
- These libraries use an anti-pattern through the `initializeGT()` function, blocking the bundle from tree-shaking cleanly.
|
|
10
|
+
- For nextjs implementation, the library blocks static rendering of Next.js pages.
|
|
11
|
+
|
|
12
|
+
**Lingo.dev**
|
|
13
|
+
|
|
14
|
+
- Heavy dependency on the lingo.dev server to generate the translations. Even if they include a more generous free tier than General Translation, the risk of vendor lock-in is high.
|
|
15
|
+
- The setup was not straightforward. I had to reverse engineer their code and force overwriting an environment variable to make it work.
|
|
16
|
+
- Their CLI is buggy and used to rewrite the config file each time I used it.
|
|
17
|
+
- The compiler was missing almost 40% of the translated content. I had to rewrite all `.map` into flat component blocks to make it work.
|
|
18
|
+
- I met reactivity issues with the library. On locale change I had to force rerendering of the provider to make it work.
|
|
19
|
+
- On rebuild it used to randomly remove all generated translations without any reason.
|
|
20
|
+
|
|
21
|
+
**next-translate**
|
|
22
|
+
|
|
23
|
+
- For Next.js applications, this library was a great discovery; the approach is extremely well thought out and easy to implement, and the results are impressive. The gain in comparison to other libraries using `t()` syntax is significant, such as **next-intl** or **next-i18next**.
|
|
24
|
+
- This library uses a build transformation approach with `react-translate-plugin`: it rewrites your pages and inserts content retrieval using `getStaticProps` or `getServerSideProps` via a Webpack / Turbopack loader. The result is that you never fetch content from other locales.
|
|
25
|
+
- For namespacing, picking namespaces at the config level is also well thought out: you can define namespaces for each page or route. It makes maintenance much easier than the main alternatives, such as **next-intl** or **next-i18next**.
|
|
26
|
+
- This library supports both Webpack and Turbopack, client and server components, and the Pages and App Router.
|
|
27
|
+
- Even if that solution is >6 years old, the library is still actively maintained.
|
|
28
|
+
- There is no website and no online documentation; they are only available on GitHub. I like finding great libraries and fixing problems in a clean way, without extra marketing layers.
|
|
29
|
+
- All constants are transformed into functions, leading to an anti-pattern. Syntax such as `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` introduces unnecessary complexity and leads to JavaScript execution overhead.
|
|
30
|
+
- The library blocks static rendering of Next.js pages.
|
|
31
|
+
|
|
32
|
+
**Tolgee**
|
|
33
|
+
|
|
34
|
+
- The implementation was difficult to get working, especially because the library does not provide type safety. To detect wrong keys, I had to manually rewrite the type-safety logic and reuse my own implementation of the `getTranslation` function.
|
|
35
|
+
- This library supports both Webpack and Turbopack, client and server components, and the Pages and App Router.
|
|
36
|
+
- There is no website and no online documentation; they are only available on GitHub.
|
|
37
|
+
- On tanstack start I also met reactivity issues with the library. On locale change I had to force the rerendering of the provider to make it work. I had to subcribe to the locale change event to make it work when loading the page in another locale.
|
|
38
|
+
- For nextjs implementation, the library blocks static rendering of Next.js pages.
|
|
39
|
+
- I also met issues with the DevTools provided by the library. It used to log errors in the server side console.
|
|
40
|
+
|
|
41
|
+
**next-translate**
|
|
42
|
+
|
|
43
|
+
- Even if that solution is >6 years old, the library is still actively maintained.
|
|
44
|
+
- This library uses a build transformation approach with `react-translate-plugin`: it rewrites your pages and inserts content retrieval using `getStaticProps` or `getServerSideProps`. The result is that you never fetch content from other locales.
|
|
45
|
+
|
|
46
|
+
**next-intl** / **use-intl** / **next-i18next** / **react-i18next** / **next-international**
|
|
47
|
+
|
|
48
|
+
- Same pros and cons for all these libraries that use a `t()` function syntax.
|
|
49
|
+
- Optimizations are possible but extremely time consuming. Good practices are not clearly highlighted, and it may be easy for agents or junior developers to harm application performance through bad practices. Especially when using namespacing plus dynamic loading, maintaining type safety, and knowing exactly which namespace should be included on which page is a nightmare.
|
|
50
|
+
- All these libraries offer a similar approach, well connected with the React reactivity system. But `next-intl` offers additional features, such as middleware support, formatters, etc.
|
|
51
|
+
- The message formats diverge between libraries. `next-intl` uses the ICU MessageFormat format, while `i18next` uses its own format.
|
|
52
|
+
- **next-intl** is a much lighter solution than **i18next**, but remains heavy and poorly optimized.
|
|
53
|
+
- **next-intl** used to block static rendering of Next.js pages. It provides a fix function named `setRequestLocale()`, that was not easy to understand the cause of the issue or how to fix it. This issue seems to have been solved and no longer blocks static rendering for centralized content such as `en.json` / `fr.json` / `es.json` / etc. Note that it still blocks static rendering when content is scoped into namespaces such as `en/shared.json` / `fr/shared.json` / `es/shared.json` / etc.
|
|
54
|
+
- Both solutions do not offer a way to translate synchronous server components such as design-system navbar, footer, etc. As a result, all components have to be translated on the client side.
|
|
55
|
+
- All constants are transformed into functions, leading to an anti-pattern. Syntax such as `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` introduces unnecessary complexity and leads to JavaScript execution overhead.
|
|
56
|
+
- **next-international** includes a custom `scopedT()` function that allows you to translate content in a specific namespace which make the refactoring process harder for optimization purpose. But because there is no way to split the jsons in namespaces, this function is quite useless. It only improves the DX, but has no impact on the bundle size.
|
|
57
|
+
|
|
58
|
+
**Lingui**
|
|
59
|
+
|
|
60
|
+
- Reasonable performance and impact, with good respect for best practices.
|
|
61
|
+
- Implementation is complex for functions that are not page components, like in Next.js `generateMetadata`. You have to create a new i18n instance, pick the messages to load, then translate them.
|
|
62
|
+
- The flow can be a bit complex to understand on first implementation.
|
|
63
|
+
- Confusing syntax, e.g. `t()`, `t''`, `i18n.t()`, `<Trans>`. Knowing which practices to use in the application is not intuitive.
|
|
64
|
+
|
|
65
|
+
**Wuchale**
|
|
66
|
+
|
|
67
|
+
- Great approach trying to automate the extraction and translation process without any `t()` function syntax.
|
|
68
|
+
- Svelte-first solution. The React adaptation of the library is not as mature as the Svelte version. The React application reactivity did not work. I had to force rerendering of the provider on locale change using a custom `isMounted()` state/effect to make it work.
|
|
69
|
+
- Uses an anti-pattern for the initialization function to make it load before the router is initialized.
|
|
70
|
+
|
|
71
|
+
**Paraglide**
|
|
72
|
+
|
|
73
|
+
- Theoretically for applications including multiple locales, Paraglide includes all messages in the bundle. There is no way to dynamically load the messages. Even if they promote `tree-shaking` optimization, the library is mainly moving the bundling problem somewhere else. A future improvement for dynamic loading seems impossible given the library architecture. Using dynamic loading would lead to thousands of requests to the server.
|
|
74
|
+
- In practice, the result using Paraglide is even worse then expected. On nextjs (Turbopack) as for tanstack start (Rolldown), the bundler does not even tree shake the messages. As a result, the final bundle size includes content from all pages and all locales.
|
|
75
|
+
- Complex flow using JSON declaration files as the source of truth, plus generation of the message files at build time, then manual imports in each component.
|
|
76
|
+
- All constants are transformed into functions, leading to an anti-pattern. Syntax such as `import * as m from '@/messages'` + `<>{m.myContent()}</>` introduces unnecessary complexity and leads to JavaScript execution overhead.
|
|
77
|
+
- In comparison of other solution that use a react context to retrieve in a efficient way the current locale to render the content, Paraglide will read for each content node imported the locale from the localeStorage / cookie etc. It leads to execution of unnecessary logic.
|
|
78
|
+
|
|
79
|
+
**react-intl**
|
|
80
|
+
|
|
81
|
+
- Performant implementation, made by the Format.js team.
|
|
82
|
+
- Confusing DX using the `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` introduces unnecessary complexity, leads to JavaScript execution overhead, and connect the global i18n instance to the all components node.
|