@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.
Files changed (81) hide show
  1. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  2. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  3. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  4. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  5. package/blog/id/list_i18n_technologies/frameworks/svelte.md +0 -2
  6. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  7. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  8. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  9. package/blog/pl/list_i18n_technologies/frameworks/svelte.md +0 -2
  10. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  11. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  12. package/blog/vi/list_i18n_technologies/frameworks/svelte.md +0 -2
  13. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
  14. package/dist/cjs/generated/docs.entry.cjs +60 -0
  15. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  16. package/dist/esm/generated/docs.entry.mjs +60 -0
  17. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  18. package/dist/types/generated/docs.entry.d.ts +3 -0
  19. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  20. package/docs/ar/benchmark/index.md +29 -0
  21. package/docs/ar/benchmark/nextjs.md +227 -0
  22. package/docs/ar/benchmark/tanstack.md +193 -0
  23. package/docs/ar/intlayer_with_tanstack.md +0 -2
  24. package/docs/de/benchmark/index.md +29 -0
  25. package/docs/de/benchmark/nextjs.md +227 -0
  26. package/docs/de/benchmark/tanstack.md +193 -0
  27. package/docs/en/benchmark/___NOTE.md +82 -0
  28. package/docs/en/benchmark/___nextjs.md +195 -0
  29. package/docs/en/benchmark/___tanstack.md +187 -0
  30. package/docs/en/benchmark/index.md +29 -0
  31. package/docs/en/benchmark/nextjs.md +228 -0
  32. package/docs/en/benchmark/tanstack.md +217 -0
  33. package/docs/en-GB/benchmark/index.md +29 -0
  34. package/docs/en-GB/benchmark/nextjs.md +228 -0
  35. package/docs/en-GB/benchmark/tanstack.md +193 -0
  36. package/docs/es/benchmark/index.md +29 -0
  37. package/docs/es/benchmark/nextjs.md +226 -0
  38. package/docs/es/benchmark/tanstack.md +193 -0
  39. package/docs/fr/benchmark/index.md +29 -0
  40. package/docs/fr/benchmark/nextjs.md +227 -0
  41. package/docs/fr/benchmark/tanstack.md +193 -0
  42. package/docs/hi/benchmark/index.md +29 -0
  43. package/docs/hi/benchmark/nextjs.md +227 -0
  44. package/docs/hi/benchmark/tanstack.md +193 -0
  45. package/docs/id/benchmark/index.md +29 -0
  46. package/docs/id/benchmark/nextjs.md +227 -0
  47. package/docs/id/benchmark/tanstack.md +193 -0
  48. package/docs/id/intlayer_with_react_native+expo.md +0 -2
  49. package/docs/it/benchmark/index.md +29 -0
  50. package/docs/it/benchmark/nextjs.md +227 -0
  51. package/docs/it/benchmark/tanstack.md +193 -0
  52. package/docs/ja/benchmark/index.md +29 -0
  53. package/docs/ja/benchmark/nextjs.md +227 -0
  54. package/docs/ja/benchmark/tanstack.md +193 -0
  55. package/docs/ko/benchmark/index.md +29 -0
  56. package/docs/ko/benchmark/nextjs.md +227 -0
  57. package/docs/ko/benchmark/tanstack.md +193 -0
  58. package/docs/ko/intlayer_with_tanstack.md +0 -2
  59. package/docs/pl/benchmark/index.md +29 -0
  60. package/docs/pl/benchmark/nextjs.md +227 -0
  61. package/docs/pl/benchmark/tanstack.md +193 -0
  62. package/docs/pt/benchmark/index.md +29 -0
  63. package/docs/pt/benchmark/nextjs.md +227 -0
  64. package/docs/pt/benchmark/tanstack.md +193 -0
  65. package/docs/ru/benchmark/index.md +29 -0
  66. package/docs/ru/benchmark/nextjs.md +227 -0
  67. package/docs/ru/benchmark/tanstack.md +193 -0
  68. package/docs/tr/benchmark/index.md +29 -0
  69. package/docs/tr/benchmark/nextjs.md +227 -0
  70. package/docs/tr/benchmark/tanstack.md +193 -0
  71. package/docs/uk/benchmark/index.md +29 -0
  72. package/docs/uk/benchmark/nextjs.md +227 -0
  73. package/docs/uk/benchmark/tanstack.md +193 -0
  74. package/docs/vi/benchmark/index.md +29 -0
  75. package/docs/vi/benchmark/nextjs.md +227 -0
  76. package/docs/vi/benchmark/tanstack.md +193 -0
  77. package/docs/zh/benchmark/index.md +29 -0
  78. package/docs/zh/benchmark/nextjs.md +227 -0
  79. package/docs/zh/benchmark/tanstack.md +193 -0
  80. package/package.json +6 -6
  81. package/src/generated/docs.entry.ts +60 -0
@@ -0,0 +1,195 @@
1
+ ---
2
+ createdAt: 2026-04-20
3
+ updatedAt: 2026-04-21
4
+ title: Best i18n solution for Next.js in 2026 - Benchmark Report
5
+ description: Compare Next.js internationalization (i18n) libraries like next-intl, next-i18next, and Intlayer. Detailed performance report on bundle size, leakage, and reactivity.
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
+ history:
19
+ - version: 8.7.5
20
+ date: 2026-01-06
21
+ changes: "Init benchmark"
22
+ ---
23
+
24
+ # Next.js i18n Libraries — 2026 Benchmark Report
25
+
26
+ Cette page est un rapport de benchmark pour les solutions i18n pour Next.js.
27
+
28
+ ## Table of Contents
29
+
30
+ <Toc/>
31
+
32
+ ## Interactive Benchmark
33
+
34
+ <I18nBenchmark framework="nextjs" vertical/>
35
+
36
+ ## Results reference:
37
+
38
+ <iframe
39
+ src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md"
40
+ width="100%"
41
+ height="600px"
42
+ style="border:none;">
43
+ </iframe>
44
+
45
+ > https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md
46
+
47
+ ## Introduction
48
+
49
+ Les solution d'internationalization sont des librairies impactantes pour votre application. Le risque principal est de faire charger le contenu de toutes les pages et dans toutes les langues pour une seule page consultée.
50
+
51
+ Au moment ou votre application grandit, la taille du bundle va evoluer exponentiellement, ce qui peut significativement ralentir les performances de votre application.
52
+
53
+ En guise d'example, pour les plus mauvais elèves, une fois internationalisée, votre page peut se retrouver pres de 4x plus volumineuse.
54
+
55
+ Un autre impact des solution d'internationalization est le ralentissement du developement. Transformer vos components et organizer vos contenu dans toutes les langues, est chonophage.
56
+
57
+ Le probleme etant complexe, un grand nombres de solutions ont vu le jour pour resoudre le probleme de l'i18n. Certaines solutions se focusant sur la DX, d'autres sur la performance, d'autres sur la scalabilite, etc.
58
+
59
+ Intlayer est une solution tentant d'optimiser l'ensemble de ces aspects.
60
+
61
+ ## Teste ton application
62
+
63
+ Pour deteacter ces problematiques, j'ai mis en place un scanner gratuit que vous pouvez tester [ici](https://intlayer.org/i18n-seo-scanner).
64
+
65
+ ## Problematic
66
+
67
+ Il existe deux grands aspects pour limiter l'impact d'une application multilingue sur le bundle de l'application:
68
+
69
+ - Diviser vos JSON (ou contenu) en plusieurs fichiers / variables / namespaces pour permettre au bundler de tree-shaker le contenu non utilisé dans votre page
70
+ - Loader dynamicement le contenu de votre page uniquement dans la langue de la page
71
+
72
+ Comprendre les limitations techniques pour ces approaches:
73
+
74
+ **Dynamic loading**
75
+
76
+ Meme en declarant vos routes tel que `[locale]/page.tsx`, que ce soit avec Webpack ou Turbopack, et bien que `getStaticParams` soit definie, le bundler de traite pas `locale` comme une variable statique. Cela signifie que le bundler va charger le contenu de toutes les langues pour chaque page. La seule option pour limiter ce probleme est de faire charger le contenu via un import dynamic (e.g. `import('./locales/${locale}.json')`).
77
+
78
+ Ce qu'il se passe au moment du build, c'est que Next.js va generer un fichier JS (bundle) pour chaque langue (e.g. `./locales_fr_12345.js`). Puis apres que le site soit envoyé au client, au moment ou la page est executée, c'est le browser qui va se changera d'efectuer une requette HTTP supplémentaire, demandant le fichier JS requis (e.g. `./locales_fr_12345.js`).
79
+
80
+ > Un autre moyen de repondre au meme probleme est d'utiliser la methode `fetch()` pour requester le fichier JSON dynamiquement. C'est le cas de `Tolgee` via le fait de placer les JSON dans le docier `/public` ou bien `next-transalte`, qui se base sur la fonction `getStaticProps` pour charger le contenu. Mais le flow reste le meme, le browser va faire une requette HTTP supplémentaire pour charger l'asset.
81
+
82
+ **Split du contenu**
83
+
84
+ Si vous utiliser une syntax tel que `const t = useTranslation()` + `t('my-object.my-sub-object.my-key')`, le JSON doit etre integralement chargé dans le bundle, pour ensuite etre parsé par la librairie afin d'en deduire la valeur de la clé. Or cela signifie qu'une grande partie du contenu sera chargée dans votre bundle, bien que non utilisé dans la page.
85
+
86
+ Pour repondre a ce probleme, certaines librairies suggerent de declarer manuellement pour chaque page quelles namespaces doivent etre chargées. C'est le cas de `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
87
+
88
+ En compaison, `Paraglide` propose l'ajout d'une etape supplimentaire, avant le build pour transformer le JSON en flat variable, tel que `const en_my_var = () => 'my value'`. Cela permet (en théorie) de tree-shaker le contenu non utilisé dans la page. Mais on le vera, cette methode n'est pas sans compromis.
89
+
90
+ Enfin, `Intlayer` propose une opitimization au build-time, permettant replacer l'usage de `useIntlayer('my-key')` et d'y attacher directement le contenu correspondant.
91
+
92
+ ## Methodology
93
+
94
+ Pour ce benchmark, nous avons comparé les librairies suivantes:
95
+
96
+ - `Base App` (No i18n library)
97
+ - `next-intlayer` (v8.7.5)
98
+ - `next-i18next` (v16.0.5)
99
+ - `next-intl` (v4.9.1)
100
+ - `@lingui/core` (v5.3.0)
101
+ - `next-translate` (v3.1.2)
102
+ - `next-international` (v1.3.1)
103
+ - `@inlang/paraglide-js` (v2.15.1)
104
+ - `tolgee` (v7.0.0)
105
+ - `@lingo.dev/compiler` (v0.4.0)
106
+ - `wuchale` (v0.22.11)
107
+ - `gt-next` (v6.16.5)
108
+
109
+ J'ai utilisé le framework `Next.js` version `16.2.4` avec le mode `app router`.
110
+
111
+ J'ai créé une application multilingue avec **10 pages**, dans **10 langues**.
112
+
113
+ Et j'ai comparé **4 strategies de chargement**:
114
+
115
+ | Stratégie | Pas de Namespaces (Global) | Avec Namespaces (Scoped) |
116
+ | :--------------------- | :------------------------------------------------ | :---------------------------------------------------------------------- |
117
+ | **Chargement Static** | **Static** : Tout en mémoire au démarrage. | **Scoped Static** : Segmenté par namespace, tout chargé au démarrage. |
118
+ | **Chargement Dynamic** | **Dynamic** : Chargement à la demande par locale. | **Scoped Dynamic** : Chargement granulaire par namespace et par locale. |
119
+
120
+ ## Synthèse des stratégies
121
+
122
+ - **Static** : Simple, aucune latence réseau après le chargement initial. Inconvénient : bundle size élevé.
123
+ - **Dynamic** : Réduit le poids initial (lazy-loading). Idéal pour un grand nombre de locales.
124
+ - **Scoped Static** : Permet une organisation propre du code (séparation logique) sans surcoût de requêtes réseau complexes.
125
+ - **Scoped Dynamic** : Approche optimale pour le _Code Splitting_ et la performance. Minimise la charge mémoire en ne chargeant que le strict nécessaire pour la vue courante et la locale active.
126
+
127
+ ## Details des Resultats
128
+
129
+ ### 1 - Les solutions a fuire
130
+
131
+ Certaines solutions tel que `gt-next` ou `lingo.dev` sont clairement des solutions à éviter. C'est solutions integrent un combo vendor lock-in + poisonning de votre codebase. Et le comble etant que malgré de nombreuse heures a tenrer des les implementer, je n'ai jamais reussi a les faire fonctionner. Que ce soit pour Tanstack start ou Next.js.
132
+ Liste de problemes rencontrés:
133
+
134
+ **(General Translation)** (`gt-next@6.16.5`):
135
+
136
+ - Pour une application 110kb, `gt-react` y ajoute plus de 440kb additionel.
137
+ - `Quota Exceeded, please upgrade your plan`, dès le premier build avec la solution de general ransaltion.
138
+ - 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.
139
+ - 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.
140
+ - The library blocks static rendering of Next.js pages.
141
+
142
+ **(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
143
+
144
+ - Quota AI dépassé, bloquant totalement le build. Il est alors impossible de push en production sans payer.
145
+ - The compiler was missing almost 40% of the translated content. I had to rewrite all `.map` into flat component blocks to make it work.
146
+ - Their CLI is buggy. and used to rezet the config file with no reason.
147
+ - At build, it totally erase the generated JSONs when there is new content added. As a result, you have only one 3 keys erasing >300 existing keys.
148
+
149
+ ### 2 - Les solutions experimentales
150
+
151
+ **(Wuchale)** (`wuchale@0.22.11`):
152
+
153
+ L'idee de `Wuchale` est interessante, mais n'est pas encore au stade de solution viable. J'ai rencontré des problemes de reactivity avec la librairie, et ai du forcer le rerendering de la provider pour faire fonctionner l'application. La documentation est aussi relativement peu claire, complexifant la prise en main.
154
+
155
+ **(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
156
+
157
+ `Paraglide` est une solution offrant une approache inovante, et bien pensée. Pour autant, dans le cadre de mon benchmark, on se rend compte que le tree-shaking que vend leur entreprise ne fonctionne ni pour mon implementation de Next.js, ni pour Tanstack start. Le flow et la DX est également plus complexe que les autres solutions. Et je ne suis personellement pas fan du fait de devoir regerer des fichiers JS avant chaque push, créant un risque de conflits constant entre les developpeurs via leur PR. Enfin, la solution semble davantage se focaliser sur Vite que sur Next.js.
158
+
159
+ ### 3 - Les solutions 'ok'
160
+
161
+ **(Tolgee)** (`tolgee@7.0.0`):
162
+
163
+ `Tolgee` est une solution repondant aux different problemes evoqués plus tot. Mais j'ai trouvé la prise en main de la solution plus complexe que les autres solutions propsant des approches similaires. La solution ne propose pas de typesafety. Ce qui rend aussi la detection des clés manquantes a la compilation bien plus complexe. J'ai du rewraper les functions de Tolgee par les miennes pour y ajouter la detection de clés manquantes.
164
+
165
+ **(Next Intl)** (`next-intl@4.9.1`):
166
+
167
+ `next-intl` est la solution la plus en vogue, et aussi la plus mise en avant les agent AI, mais selon moi a tord. Le get-started est assez simple. Mais en pratique, le process d'optimization pour limiter le leakage est assez complexe. De meme, la cohabitation du dynamic loading + namespacing + typescript types ralentit enormement le developpement. De plus, librairie est assez lourde (~13kb pour `NextIntlClientProvider` + `useTranslations` hook, ce qui est >2x plus lourde que `next-intlayer`). **next-intl** used to block static rendering of Next.js pages. It provides a fix function named `setRequestLocale()`. This issue seems to have been partially solved for centralized content such as `en.json` / `fr.json` / etc. But the solution still blocks static rendering when content is scoped into namespaces such as `en/shared.json` / `fr/shared.json` / `es/shared.json` / etc.
168
+
169
+ **(Next I18next)** (`next-i18next@16.0.5`):
170
+
171
+ `next-i18next` est probablement la solution la plus populaire par le fait que ce soit la premiere solution a avoir vu le jour pour repondre aux besoins d'internationalisation d'application Javascript. Cette solution propose aussi un tat de pluggins communautaires pour repondre a certaines problematiques. Pour autant, cette solution presente les exactes meme points noirs que `next-intl`. De plus, librairie est particuliement lourde (~18kb pour `I18nProvider` + `useTranslation` hook, ce qui est 3x plus lourde que `next-intlayer`).
172
+ Aussi, les formats de messages divergent entre les librairies. `next-intl` utilise le format ICU MessageFormat, alors que `i18next` utilise son propre format.
173
+
174
+ **(Next International)** (`next-international@1.3.1`):
175
+
176
+ `next-international` est une solution repondant aussi aux different problemes evoqués plus tot, mais ne propose pas de difference significatif en rapport a `next-intl`, ou meme `next-i18next`. La solution inclut une fonction `scopedT()` qui permet de traduire le contenu dans un namespace spécifique. Cependant, l'utilisation de cette fonction n'a purement et simplement aucun impact sur la taille du bundle.
177
+
178
+ **(Lingui)** (`@lingui/core@5.3.0`):
179
+
180
+ `Lingui` est une solution pour laquelle j'ai entendu beaucoup de bien. Mais j'ai personnellement trouvé le flow basé sur l'execution de commandes `lingui extract` / `lingui compile` plus complexe que les autres approches, sans vrai avantage significatif. J'ai aussi noté le mangue d'armonie entre les syntaxes qui porte les IA a confution. (e.g. `t()`, `t''`, `i18n.t()`, `<Trans>`)
181
+
182
+ ### 4 - Mes recommendations
183
+
184
+ **(Next Translate)** (`next-translate@3.1.2`):
185
+
186
+ `next-translate` est ici ma principale recommandation pour ceux aimant l'usage d'une fonction `t()` pour traduire leur contenu. Cette solution offre une approche elegante via `next-transalte-plugin`, optimisant le loading de vos namespaces via `getStaticProps` via un loader Webpack / Turbopack. C'est aussi la solution la plus légère de toute (~2.5kb only). 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**. Cependant j'ai noté qu'en version `3.1.2`, le build en static rendering ne fonctionne pas. Next.js fallback alors sur le dynamic rendering.
187
+
188
+ **(Intlayer)** (`next-intlayer@8.7.5`):
189
+
190
+ Enfin, je ne jugerais pas personellement `next-intlayer`, par soucis d'objectivité, etant donnée qu'il s'agit de ma propre solution.
191
+
192
+ ### Personal note
193
+
194
+ Cette note est personnelle et n'a pas d'impact sur les resultats du benchmark. Cependant, dans le monde de l'i18n on voit un concensus clair qui est d'utiliser une syntax tel que `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` pour traduire leur contenu.
195
+ Cependant, dans le cadre d'appplication React, l'injection d'une fonction sous forme de `ReactNode` est selon moi un anti-pattern. De plus, cela introduit une complexité additionnel, pouvant etre evitée, et un overhead de JavaScript execution (bien qu'imperceptible).
@@ -0,0 +1,187 @@
1
+ ---
2
+ createdAt: 2026-04-20
3
+ updatedAt: 2026-04-21
4
+ title: Best i18n solution for TanStack Start in 2026 - Benchmark Report
5
+ description: Compare TanStack Start internationalization libraries like react-i18next, use-intl, and Intlayer. Detailed performance report on bundle size, leakage, and reactivity.
6
+ keywords:
7
+ - benchmark
8
+ - i18n
9
+ - intl
10
+ - tanstack
11
+ - performance
12
+ - intlayer
13
+ slugs:
14
+ - doc
15
+ - benchmark
16
+ - tanstack
17
+ history:
18
+ - version: 8.7.5
19
+ date: 2026-01-06
20
+ changes: "Init benchmark"
21
+ ---
22
+
23
+ # TanStack Start i18n Libraries — 2026 Benchmark Report
24
+
25
+ Cette page est un rapport de benchmark pour les solutions i18n pour TanStack Start.
26
+
27
+ ## Table of Contents
28
+
29
+ <Toc/>
30
+
31
+ ## Interactive Benchmark
32
+
33
+ <I18nBenchmark framework="tanstack" vertical/>
34
+
35
+ ## Results reference:
36
+
37
+ <iframe
38
+ src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md"
39
+ width="100%"
40
+ height="600px"
41
+ style="border:none;">
42
+ </iframe>
43
+
44
+ > https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md
45
+
46
+ ## Introduction
47
+
48
+ Les solutions d'internationalisation font partie des dependances les plus impactantes pour une application React. Sur TanStack Start, le risque principal est de charger du contenu inutile: des traductions d'autres pages et d'autres locales dans le bundle d'une seule route.
49
+
50
+ Au fur et a mesure que votre application grandit, ce probleme peut vite faire exploser le JavaScript envoye au client et ralentir la navigation.
51
+
52
+ En pratique, pour les implementations les moins optimisees, une page internationalisee peut devenir plusieurs fois plus lourde que la version sans i18n.
53
+
54
+ L'autre impact est sur la DX: declaration des contenus, types, organisation par namespaces, chargement dynamique, et gestion de la reactivite au changement de locale.
55
+
56
+ ## Teste ton application
57
+
58
+ Pour detecter rapidement les problemes de leakage i18n, j'ai mis en place un scanner gratuit disponible [ici](https://intlayer.org/i18n-seo-scanner).
59
+
60
+ ## Problematic
61
+
62
+ Deux axes sont essentiels pour limiter l'impact d'une application multilingue:
63
+
64
+ - Splitter le contenu par page / namespace pour eviter de charger des dictionnaires entiers inutilement
65
+ - Charger dynamiquement la bonne locale uniquement quand necessaire
66
+
67
+ Comprendre les limitations techniques de ces approches:
68
+
69
+ **Dynamic loading**
70
+
71
+ Sans chargement dynamique, la plupart des solutions incluent les messages en memoire des le premier rendu, ce qui cree un surcout important pour les applications avec beaucoup de routes et de locales.
72
+
73
+ Avec un chargement dynamique, il faut accepter un compromis: moins de JS initial, mais parfois une requete additionnelle au changement de langue.
74
+
75
+ **Split du contenu**
76
+
77
+ Les syntaxes basees sur `const t = useTranslation()` + `t('a.b.c')` sont tres pratiques mais poussent souvent a conserver de gros objets JSON en runtime. Ce modele rend le tree-shaking difficile si la bibliotheque ne propose pas une vraie strategie de split par page.
78
+
79
+ ## Methodology
80
+
81
+ Pour ce benchmark, nous avons compare les librairies suivantes:
82
+
83
+ - `Base App` (No i18n library)
84
+ - `react-intlayer` (v8.7.5-canary.0)
85
+ - `react-i18next` (v17.0.2)
86
+ - `use-intl` (v4.9.1)
87
+ - `@lingui/core` (v5.3.0)
88
+ - `@inlang/paraglide-js` (v2.15.1)
89
+ - `tolgee` (v7.0.0)
90
+ - `react-intl` (v10.1.1)
91
+ - `wuchale` (v0.22.11)
92
+ - `gt-react` (vlatest)
93
+ - `lingo.dev` (v0.133.9)
94
+
95
+ Le framework utilise est `TanStack Start` avec une application multilingue de **10 pages** et **10 langues**.
96
+
97
+ Et nous avons comparé **4 stratégies de chargement** :
98
+
99
+ | Stratégie | Pas de Namespaces (Global) | Avec Namespaces (Scoped) |
100
+ | :--------------------- | :------------------------------------------------ | :---------------------------------------------------------------------- |
101
+ | **Chargement Static** | **Static** : Tout en mémoire au démarrage. | **Scoped Static** : Segmenté par namespace, tout chargé au démarrage. |
102
+ | **Chargement Dynamic** | **Dynamic** : Chargement à la demande par locale. | **Scoped Dynamic** : Chargement granulaire par namespace et par locale. |
103
+
104
+ ## Synthèse des stratégies
105
+
106
+ - **Static** : Simple, aucune latence réseau après le chargement initial. Inconvénient : bundle size élevé.
107
+ - **Dynamic** : Réduit le poids initial (lazy-loading). Idéal pour un grand nombre de locales.
108
+ - **Scoped Static** : Permet une organisation propre du code (séparation logique) sans surcoût de requêtes réseau complexes.
109
+ - **Scoped Dynamic** : Approche optimale pour le _Code Splitting_ et la performance. Minimise la charge mémoire en ne chargeant que le strict nécessaire pour la vue courante et la locale active.
110
+
111
+ ## Details des Resultats
112
+
113
+ ### 1 - Les solutions a fuire
114
+
115
+ Certaines solutions tel que `gt-react` ou `lingo.dev` sont clairement des solutions à éviter. Ces solutions intègrent un combo vendor lock-in + empoisonnement de votre codebase. Et le comble étant que malgré de nombreuses heures à tenter de les implémenter, je n'ai jamais réussi à les faire fonctionner correctement sur TanStack Start (comme sur Next.js pour `gt-next`).
116
+
117
+ Liste de problèmes rencontrés :
118
+
119
+ **(General Translation)** (`gt-react@latest`) :
120
+
121
+ - Pour une application ~110kb, `gt-react` peut y ajouter plus de 440kb additionnel (ordre de grandeur observé sur l'implémentation Next.js du même benchmark).
122
+ - `Quota Exceeded, please upgrade your plan`, dès le premier build avec la solution General Translation.
123
+ - 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.
124
+ - 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.
125
+ - These libraries use an anti-pattern through the `initializeGT()` function, blocking the bundle from tree-shaking cleanly.
126
+
127
+ **(Lingo.dev)** (`lingo.dev@0.133.9`) :
128
+
129
+ - Quota AI dépassé (ou dépendance serveur bloquante), rendant le flux de build / prod risqué sans payer.
130
+ - The compiler was missing almost 40% of the translated content. I had to rewrite all `.map` into flat component blocks to make it work.
131
+ - Their CLI is buggy and used to reset the config file with no reason.
132
+ - At build, it totally erased the generated JSONs when there was new content added. As a result, you could end up with only a few keys erasing hundreds of existing keys.
133
+ - I met reactivity issues with the library on TanStack Start: on locale change I had to force rerendering of the provider to make it work.
134
+
135
+ ### 2 - Les solutions experimentales
136
+
137
+ **(Wuchale)** (`wuchale@0.22.11`) :
138
+
139
+ L'idée de `Wuchale` est intéressante, mais n'est pas encore au stade de solution viable. J'ai rencontré des problèmes de réactivité avec la librairie, et ai dû forcer le rerendering de la provider pour faire fonctionner l'application sur TanStack Start. La documentation est aussi relativement peu claire, complexifiant la prise en main.
140
+
141
+ ### 3 - Les solutions 'ok'
142
+
143
+ **(Paraglide)** (`@inlang/paraglide-js@2.15.1`) :
144
+
145
+ `Paraglide` est une solution offrant une approche innovante, et bien pensée. Pour autant, dans le cadre de mon benchmark, on se rend compte que le tree-shaking que vend leur entreprise ne fonctionne ni pour mon implémentation Next.js, ni pour TanStack Start. Le flow et la DX est également plus complexe que les autres solutions. Et je ne suis personnellement pas fan du fait de devoir régénérer des fichiers JS avant chaque push, créant un risque de conflits constant entre les développeurs via leur PR.
146
+
147
+ **(Tolgee)** (`tolgee@7.0.0`) :
148
+
149
+ `Tolgee` est une solution répondant aux différents problèmes évoqués plus tôt. Mais j'ai trouvé la prise en main de la solution plus complexe que les autres solutions proposant des approches similaires. La solution ne propose pas de typesafety. Ce qui rend aussi la détection des clés manquantes à la compilation bien plus complexe. J'ai dû ré-encapsuler les fonctions de Tolgee par les miennes pour y ajouter la détection de clés manquantes.
150
+
151
+ Sur TanStack Start, j'ai aussi rencontré des problèmes de réactivité : au changement de locale j'ai dû forcer le rerendering du provider, et m'abonner aux événements de changement de locale pour que le chargement dans une autre langue se comporte correctement.
152
+
153
+ **(use-intl)** (`use-intl@4.9.1`) :
154
+
155
+ `use-intl` est la brique « intl » la plus en vogue dans l'écosystème React (même famille que `next-intl`), et aussi une des plus mise en avant par les agents AI, mais selon moi à tort dans un contexte performance-first. Le get-started est assez simple. Mais en pratique, le process d'optimisation pour limiter le leakage est assez complexe. De même, la cohabitation du dynamic loading + namespacing + TypeScript types ralentit énormément le développement.
156
+
157
+ Sur TanStack Start, vous n'avez pas les pièges spécifiques à Next.js (`setRequestLocale`, static rendering), mais vous gardez le même coeur du problème : sans discipline stricte, le bundle embarque vite trop de messages et la maintenance des namespaces par route devient vite pénible.
158
+
159
+ **(react-i18next)** (`react-i18next@17.0.2`) :
160
+
161
+ `react-i18next` est probablement la solution la plus populaire par le fait que ce soit l'une des premières à avoir répondu aux besoins d'internationalisation d'applications JavaScript. Cette solution propose aussi tout un tas de plugins communautaires pour répondre à certaines problématiques.
162
+
163
+ Pour autant, cette solution présente les mêmes grands points noirs que les stacks basées sur une fonction `t('a.b.c')` : optimisations possibles mais extrêmement chronophages, et risque élevé de mauvaises pratiques sur les gros projets (namespaces + chargement dynamique + types).
164
+
165
+ De plus, les formats de messages divergent : `use-intl` s'appuie sur le format ICU MessageFormat, alors que `i18next` utilise son propre format — ce qui complique encore plus si vous mélangez outillage ou migrations.
166
+
167
+ **(Lingui)** (`@lingui/core@5.3.0`) :
168
+
169
+ `Lingui` est une solution pour laquelle j'ai entendu beaucoup de bien. Mais j'ai personnellement trouvé le flow basé sur l'exécution de commandes `lingui extract` / `lingui compile` plus complexe que les autres approches, sans vrai avantage significatif dans ce benchmark TanStack Start. J'ai aussi noté le manque d'harmonie entre les syntaxes qui porte les IA à confusion (e.g. `t()`, `t''`, `i18n.t()`, `<Trans>`).
170
+
171
+ **(react-intl)** (`react-intl@10.1.1`) :
172
+
173
+ `react-intl` est une implémentation performante, produite par l'équipe Format.js. En revanche, la DX reste verbeuse : `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` introduit de la complexité, un surcoût d'exécution JavaScript, et connecte l'instance i18n globale à beaucoup de nœuds du graphe React.
174
+
175
+ ### 4 - Mes recommendations
176
+
177
+ Ce benchmark TanStack Start ne contient pas d'équivalent direct à `next-translate` (plugin Next.js + `getStaticProps`). Pour les équipes qui veulent absolument une API `t()` avec un écosystème mature, `react-i18next` et `use-intl` restent des choix « raisonnables », mais attendez-vous à investir beaucoup de temps dans l'optimisation pour éviter le leakage.
178
+
179
+ **(Intlayer)** (`react-intlayer@8.7.5-canary.0`) :
180
+
181
+ Enfin, je ne jugerais pas personnellement `react-intlayer`, par souci d'objectivité, étant donné qu'il s'agit de ma propre solution.
182
+
183
+ ### Personal note
184
+
185
+ Cette note est personnelle et n'a pas d'impact sur les résultats du benchmark. Cependant, dans le monde de l'i18n on voit un consensus clair qui est d'utiliser une syntaxe telle que `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` pour traduire leur contenu.
186
+
187
+ Cependant, dans le cadre d'applications React, l'injection d'une fonction sous forme de `ReactNode` est selon moi un anti-pattern. De plus, cela introduit une complexité additionnelle, pouvant être évitée, et un overhead de JavaScript execution (bien qu'imperceptible).
@@ -0,0 +1,29 @@
1
+ ---
2
+ createdAt: 2026-04-20
3
+ updatedAt: 2026-04-20
4
+ title: Benchmark i18n libraries
5
+ description: Learn how Intlayer compares to other i18n libraries in terms of performance and bundle size.
6
+ keywords:
7
+ - benchmark
8
+ - i18n
9
+ - intl
10
+ - nextjs
11
+ - tanstack
12
+ - intlayer
13
+ slugs:
14
+ - doc
15
+ - benchmark
16
+ history:
17
+ - version: 8.7.5
18
+ date: 2026-01-06
19
+ changes: "Init benchmark"
20
+ ---
21
+
22
+ # Benchmark - Report
23
+
24
+ Benchmark Bloom is a performance benchmarking suite that measures the real-world impact of i18n (internationalization) libraries across multiple React frameworks and loading strategies.
25
+
26
+ Find the detailed reports and technical documentation for each framework below:
27
+
28
+ - [**Next.js Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/nextjs.md)
29
+ - [**TanStack Start Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/tanstack.md)
@@ -0,0 +1,228 @@
1
+ ---
2
+ createdAt: 2026-04-20
3
+ updatedAt: 2026-04-21
4
+ title: Best i18n solution for Next.js in 2026 - Benchmark Report
5
+ description: Compare Next.js internationalization (i18n) libraries like next-intl, next-i18next, and Intlayer. Detailed performance report on bundle size, leakage, and reactivity.
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: "Init benchmark"
23
+ ---
24
+
25
+ # Next.js i18n Libraries - 2026 Benchmark Report
26
+
27
+ This page is a benchmark report for i18n solutions on Next.js.
28
+
29
+ ## Table of Contents
30
+
31
+ <Toc/>
32
+
33
+ ## Interactive Benchmark
34
+
35
+ <I18nBenchmark framework="nextjs" vertical/>
36
+
37
+ ## Results reference:
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
+ See complete benchmark repository [here](https://github.com/intlayer-org/benchmark-i18n).
49
+
50
+ ## Introduction
51
+
52
+ Internationalization libraries have a heavy impact on your application. The main risk is loading content for every page and every language when the user only visits one page.
53
+
54
+ As your app grows, bundle size can grow exponentially, which can noticeably hurt performance.
55
+
56
+ As an example, for the worst offenders, once internationalized your page can end up nearly 4× larger.
57
+
58
+ Another impact of i18n libraries is slower development. Turning components into multilingual content across languages is time-consuming.
59
+
60
+ Because the problem is hard, many solutions exist—some focused on DX, others on performance or scalability, and so on.
61
+
62
+ Intlayer tries to optimize across these dimensions.
63
+
64
+ ## Test your app
65
+
66
+ To surface these issues, I built a free scanner you can try [here](https://intlayer.org/i18n-seo-scanner).
67
+
68
+ <iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
69
+
70
+ ## The problem
71
+
72
+ There are two major ways to limit the impact of a multilingual app on your bundle:
73
+
74
+ - Split your JSON (or content) across files / variables / namespaces so the bundler can tree-shake unused content for a given page
75
+ - Dynamically load your page content only in the page’s language
76
+
77
+ Technical limitations for these approaches:
78
+
79
+ **Dynamic loading**
80
+
81
+ Even when you declare routes like `[locale]/page.tsx`, with Webpack or Turbopack, and even if `generateStaticParams` is defined, the bundler does not treat `locale` as a static constant. That means it may pull content for all languages into each page. The main way to limit this is to load content via a dynamic import (e.g. `import('./locales/${locale}.json')`).
82
+
83
+ What happens at build time is that Next.js emits one JS bundle per locale (e.g. `./locales_fr_12345.js`). After the site is sent to the client, when the page runs, the browser performs an extra HTTP request for the needed JS file (e.g. `./locales_fr_12345.js`).
84
+
85
+ > Another way to address the same problem is to use `fetch()` to load JSON dynamically. That is how `Tolgee` works when JSON lives under `/public`, or `next-translate`, which relies on `getStaticProps` to load content. The flow is the same: the browser makes an extra HTTP request to load the asset.
86
+
87
+ **Content splitting**
88
+
89
+ If you use syntax like `const t = useTranslation()` + `t('my-object.my-sub-object.my-key')`, the entire JSON usually has to be in the bundle so the library can parse it and resolve the key. Much of that content then ships even when it is unused on the page.
90
+
91
+ To mitigate this, some libraries ask you to declare per page which namespaces to load, e.g. `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
92
+
93
+ By contrast, `Paraglide` adds an extra step before build to turn JSON into flat symbols like `const en_my_var = () => 'my value'`. In theory that enables tree-shaking unused content on the page. As we will see, that method still has trade-offs.
94
+
95
+ Finally, `Intlayer` applies a build-time optimization so `useIntlayer('my-key')` is replaced with the corresponding content directly.
96
+
97
+ ## Methodology
98
+
99
+ For this benchmark, we compared the following libraries:
100
+
101
+ - `Base App` (No i18n library)
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/react` (v7.0.0)
110
+ - `@lingo.dev/compiler` (v0.4.0)
111
+ - `wuchale` (v0.22.11)
112
+ - `gt-next` (v6.16.5)
113
+
114
+ I used `Next.js` version `16.2.4` with the App Router.
115
+
116
+ I built a multilingual app with **10 pages** and **10 languages**.
117
+
118
+ I compared **four loading strategies**:
119
+
120
+ | Strategy | No namespaces (global) | With namespaces (scoped) |
121
+ | :------------------ | :------------------------------------------- | :------------------------------------------------------------------- |
122
+ | **Static loading** | **Static**: Everything in memory at startup. | **Scoped static**: Split by namespace; everything loaded at startup. |
123
+ | **Dynamic loading** | **Dynamic**: On-demand loading per locale. | **Scoped dynamic**: Granular loading per namespace and locale. |
124
+
125
+ ## Strategy summary
126
+
127
+ - **Static**: Simple; no network latency after the initial load. Downside: large bundle size.
128
+ - **Dynamic**: Reduces initial weight (lazy-loading). Ideal when you have many locales.
129
+ - **Scoped static**: Keeps code organized (logical separation) without complex extra network requests.
130
+ - **Scoped dynamic**: Best approach for _code splitting_ and performance. Minimizes memory by loading only what the current view and active locale need.
131
+
132
+ ### What I measured:
133
+
134
+ I ran the same multilingual app in a real browser for every stack, then wrote down what actually showed up on the wire and how long things took. Sizes are reported **after normal web compression**, because that is closer to what people download than raw source counts.
135
+
136
+ - **Internationalization library size**: After bundling, tree-shaking and minification, the size of the i18n library is the size of the providers (e.g. `NextIntlClientProvider`) + hooks (e.g. `useTranslations`) code in an empty component. It does not include the loading of translation files. It answers how expensive the library is before your content enters the picture.
137
+
138
+ - **JavaScript per page**: For each benchmark route, how much script the browser pulls in for that visit, averaged across the pages in the suite (and across locales where the report rolls them up). Heavy pages are slow pages.
139
+
140
+ - **Leakage from other locales**: It's the content of the same page but in another language that would be loaded by mistake in the audited page. This content is unnecessary and should be avoided. (e.g. `/fr/about` page content in `/en/about` page bundle)
141
+
142
+ - **Leakage from other routes**: The same idea for **other screens** in the app: whether their copy is riding along when you only opened one page. (e.g. `/en/about` page content in `/en/contact` page bundle). A high score hints at weak splitting or over-broad bundles.
143
+
144
+ - **Average component bundle size**: Common UI pieces are measured **one at a time** instead of hiding inside one giant app number. It shows whether internationalization quietly inflates everyday components. For instance, if your component rerenders, it will load all that data from memory. Attaching a giant JSON to any component is like connecting a big store of unused data that will slow down your components’ performance.
145
+
146
+ - **Language switch responsiveness**: I flip the language using the app’s own control and time how long it takes until the page has clearly switched, what a visitor would notice, not a lab micro-step.
147
+
148
+ - **Rendering work after a language change**: A narrower follow-up: how much effort the interface took to repaint for the new language once the switch is in flight. Useful when the “felt” time and the framework cost diverge.
149
+
150
+ - **Initial page load time**: From navigation to the browser considering the page fully loaded for the scenarios I tested. Good for comparing cold starts.
151
+
152
+ - **Hydration time**: When the app exposes it, how long the client spends turning server HTML into something you can actually click. A dash in the tables means that implementation did not provide a reliable hydration figure in this benchmark.
153
+
154
+ ## Results in detail
155
+
156
+ ### 1 - Solutions to avoid
157
+
158
+ Some solutions, such as `gt-next` or `lingo.dev`, are clearly best avoided. They combine vendor lock-in with polluting your codebase. Despite many hours trying to implement them, I never got them working on TanStack Start or Next.js.
159
+
160
+ Issues encountered:
161
+
162
+ **(General Translation)** (`gt-next@6.16.5`):
163
+
164
+ - For a 110kb app, `gt-next` adds more than 440kb extra.
165
+ - `Quota Exceeded, please upgrade your plan` on the very first build with General Translation.
166
+ - 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.
167
+ - While implementing **gt-next**, 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.
168
+ - The library blocks static rendering of Next.js pages.
169
+
170
+ **(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
171
+
172
+ - AI quota exceeded, blocking the build entirely, so you cannot ship to production without paying.
173
+ - The compiler was missing almost 40% of the translated content. I had to rewrite all `.map` into flat component blocks to make it work.
174
+ - Their CLI is buggy and used to reset the config file for no reason.
175
+ - At build, it totally erased the generated JSONs when new content was added. As a result, a handful of keys could wipe out more than 300 existing keys.
176
+
177
+ ### 2 - Experimental solutions
178
+
179
+ **(Wuchale)** (`wuchale@0.22.11`):
180
+
181
+ The idea behind `Wuchale` is interesting but not yet viable. I hit reactivity issues and had to force rerendering of the provider to get the app working. The documentation is also fairly unclear, which makes onboarding harder.
182
+
183
+ **(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
184
+
185
+ `Paraglide` offers an innovative, well-thought-out approach. Even so, in this benchmark the advertised tree-shaking did not work for my Next.js or TanStack Start setups. The workflow and DX are more complex than other options.
186
+ Personally I dislike having to regenerate JS files before every push, which creates constant merge conflict risk via PRs. The tool also seems more focused on Vite than on Next.js.
187
+ Even if in theory the tree-shaking strategy works, it does include all locales in the bundle anyway. Paraglide offers no way to lazy-load the content. That means your page size grows in line with the number of locales you have.
188
+ Finally, in comparison with other solutions, Paraglide does not use a store (e.g. React context) to retrieve the current locale to render the content. For each node parsed, it will request the locale from the localStorage / cookie etc. It leads to execution of unnecessary logic that impacts the component reactivity.
189
+
190
+ ### 3 - Acceptable solutions
191
+
192
+ **(Tolgee)** (`@tolgee/react@7.0.0`):
193
+
194
+ `Tolgee` addresses many of the issues mentioned earlier. I found it harder to adopt than similar tools. It does not provide type safety, which also makes catching missing keys at compile time harder. I had to wrap Tolgee’s functions with my own to add missing-key detection.
195
+
196
+ **(Next Intl)** (`next-intl@4.9.1`):
197
+
198
+ `next-intl` is the trendiest option and the one AI agents push most, but in my view wrongly so. Getting started is easy. In practice, optimizing to limit leakage is complex. Combining dynamic loading + namespacing + TypeScript types slows development a lot. The package is also fairly heavy (~13kb for `NextIntlClientProvider` + `useTranslations`, which is more than 2× `next-intlayer`). **next-intl** used to block static rendering of Next.js pages. It provides a helper named `setRequestLocale()`. That seems partially addressed for centralized files like `en.json` / `fr.json`, but static rendering still breaks when content is split into namespaces such as `en/shared.json` / `fr/shared.json` / `es/shared.json`.
199
+
200
+ **(Next I18next)** (`next-i18next@16.0.5`):
201
+
202
+ `next-i18next` is probably the most popular option because it was among the first i18n solutions for JavaScript apps. It has many community plugins. It shares the same major downsides as `next-intl`. The package is especially heavy (~18kb for `I18nProvider` + `useTranslation`, about 3× `next-intlayer`).
203
+
204
+ Message formats also differ: `next-intl` uses ICU MessageFormat, while `i18next` uses its own format.
205
+
206
+ **(Next International)** (`next-international@1.3.1`):
207
+
208
+ `next-international` also tackles the issues above but does not differ much from `next-intl` or `next-i18next`. It includes `scopedT()` for namespace-specific translations, but using it has essentially no impact on bundle size.
209
+
210
+ **(Lingui)** (`@lingui/core@5.3.0`):
211
+
212
+ `Lingui` is often praised. Personally I found the `lingui extract` / `lingui compile` workflow more complex than alternatives, without a clear upside. I also noticed inconsistent syntaxes that confuse AIs (e.g. `t()`, `t''`, `i18n.t()`, `<Trans>`).
213
+
214
+ ### 4 - Recommendations
215
+
216
+ **(Next Translate)** (`next-translate@3.1.2`):
217
+
218
+ `next-translate` is my main recommendation if you like a `t()`-style API. It is elegant via `next-translate-plugin`, loading namespaces through `getStaticProps` with a Webpack / Turbopack loader. It is also the lightest option here (~2.5kb). For namespacing, defining namespaces per page or route in config is well thought out and easier to maintain than main alternatives like **next-intl** or **next-i18next**. In version `3.1.2`, I noted that static rendering did not work; Next.js fell back to dynamic rendering.
219
+
220
+ **(Intlayer)** (`next-intlayer@8.7.5`):
221
+
222
+ I will not personally judge `next-intlayer` for objectivity’s sake, since it is my own solution.
223
+
224
+ ### Personal note
225
+
226
+ This note is personal and does not affect the benchmark results. In the i18n world you often see consensus around `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>`.
227
+
228
+ In React apps, injecting a function as a `ReactNode` is, in my view, an anti-pattern. It also adds avoidable complexity and JavaScript execution overhead (even if barely noticeable).