@intlayer/docs 6.1.5 → 6.1.6

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 (284) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +404 -173
  2. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +262 -113
  3. package/blog/en/intlayer_with_next-i18next.mdx +431 -0
  4. package/blog/en/intlayer_with_next-intl.mdx +335 -0
  5. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +463 -209
  6. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  7. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +185 -71
  8. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  9. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  10. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  11. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  12. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  13. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +36 -28
  14. package/blog/tr/next-i18next_vs_next-intl_vs_intlayer.md +2 -0
  15. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +38 -28
  16. package/dist/cjs/generated/docs.entry.cjs +32 -0
  17. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  18. package/dist/esm/generated/docs.entry.mjs +32 -0
  19. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  20. package/dist/types/generated/docs.entry.d.ts +2 -0
  21. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  22. package/docs/ar/component_i18n.md +186 -0
  23. package/docs/ar/intlayer_with_angular.md +2 -2
  24. package/docs/ar/intlayer_with_astro.md +246 -0
  25. package/docs/ar/intlayer_with_create_react_app.md +3 -2
  26. package/docs/ar/intlayer_with_express.md +2 -2
  27. package/docs/ar/intlayer_with_nestjs.md +2 -2
  28. package/docs/ar/intlayer_with_nextjs_14.md +2 -2
  29. package/docs/ar/intlayer_with_nextjs_15.md +2 -2
  30. package/docs/ar/intlayer_with_nextjs_page_router.md +2 -2
  31. package/docs/ar/intlayer_with_nuxt.md +2 -2
  32. package/docs/ar/intlayer_with_react_native+expo.md +11 -20
  33. package/docs/ar/intlayer_with_react_router_v7.md +195 -241
  34. package/docs/ar/intlayer_with_tanstack.md +198 -272
  35. package/docs/ar/intlayer_with_vite+preact.md +9 -9
  36. package/docs/ar/intlayer_with_vite+react.md +7 -7
  37. package/docs/ar/intlayer_with_vite+vue.md +9 -9
  38. package/docs/ar/vs_code_extension.md +48 -109
  39. package/docs/de/component_i18n.md +186 -0
  40. package/docs/de/intlayer_with_angular.md +2 -2
  41. package/docs/de/intlayer_with_astro.md +246 -0
  42. package/docs/de/intlayer_with_create_react_app.md +2 -2
  43. package/docs/de/intlayer_with_express.md +2 -2
  44. package/docs/de/intlayer_with_nestjs.md +2 -2
  45. package/docs/de/intlayer_with_nextjs_14.md +2 -2
  46. package/docs/de/intlayer_with_nextjs_15.md +2 -2
  47. package/docs/de/intlayer_with_nextjs_page_router.md +2 -2
  48. package/docs/de/intlayer_with_nuxt.md +2 -2
  49. package/docs/de/intlayer_with_react_native+expo.md +11 -20
  50. package/docs/de/intlayer_with_react_router_v7.md +193 -242
  51. package/docs/de/intlayer_with_tanstack.md +194 -266
  52. package/docs/de/intlayer_with_vite+preact.md +9 -9
  53. package/docs/de/intlayer_with_vite+react.md +9 -9
  54. package/docs/de/intlayer_with_vite+vue.md +11 -11
  55. package/docs/de/packages/vite-intlayer/index.md +3 -3
  56. package/docs/de/vs_code_extension.md +46 -107
  57. package/docs/en/component_i18n.md +186 -0
  58. package/docs/en/how_works_intlayer.md +1 -1
  59. package/docs/en/index.md +1 -1
  60. package/docs/en/intlayer_cli.md +1 -1
  61. package/docs/en/intlayer_with_angular.md +4 -4
  62. package/docs/en/intlayer_with_astro.md +246 -0
  63. package/docs/en/intlayer_with_create_react_app.md +4 -4
  64. package/docs/en/intlayer_with_express.md +3 -3
  65. package/docs/en/intlayer_with_lynx+react.md +1 -1
  66. package/docs/en/intlayer_with_nestjs.md +2 -2
  67. package/docs/en/intlayer_with_nextjs_14.md +31 -5
  68. package/docs/en/intlayer_with_nextjs_15.md +31 -5
  69. package/docs/en/intlayer_with_nextjs_page_router.md +5 -5
  70. package/docs/en/intlayer_with_nuxt.md +4 -4
  71. package/docs/en/intlayer_with_react_native+expo.md +46 -24
  72. package/docs/en/intlayer_with_react_router_v7.md +164 -211
  73. package/docs/en/intlayer_with_tanstack.md +166 -241
  74. package/docs/en/intlayer_with_vite+preact.md +12 -12
  75. package/docs/en/intlayer_with_vite+react.md +12 -12
  76. package/docs/en/intlayer_with_vite+solid.md +2 -2
  77. package/docs/en/intlayer_with_vite+svelte.md +2 -2
  78. package/docs/en/intlayer_with_vite+vue.md +12 -12
  79. package/docs/en/introduction.md +1 -1
  80. package/docs/en/packages/next-intlayer/useDictionary.md +1 -1
  81. package/docs/en/packages/next-intlayer/useIntlayer.md +1 -1
  82. package/docs/en/packages/react-intlayer/useDictionary.md +1 -1
  83. package/docs/en/packages/react-intlayer/useI18n.md +1 -1
  84. package/docs/en/packages/react-intlayer/useIntlayer.md +1 -1
  85. package/docs/en/releases/v6.md +1 -0
  86. package/docs/en/roadmap.md +1 -1
  87. package/docs/en/vs_code_extension.md +24 -114
  88. package/docs/en-GB/component_i18n.md +186 -0
  89. package/docs/en-GB/intlayer_with_angular.md +3 -3
  90. package/docs/en-GB/intlayer_with_astro.md +246 -0
  91. package/docs/en-GB/intlayer_with_create_react_app.md +5 -4
  92. package/docs/en-GB/intlayer_with_express.md +2 -2
  93. package/docs/en-GB/intlayer_with_nestjs.md +2 -2
  94. package/docs/en-GB/intlayer_with_nextjs_14.md +4 -4
  95. package/docs/en-GB/intlayer_with_nextjs_15.md +2 -2
  96. package/docs/en-GB/intlayer_with_nextjs_page_router.md +2 -2
  97. package/docs/en-GB/intlayer_with_nuxt.md +2 -2
  98. package/docs/en-GB/intlayer_with_react_native+expo.md +11 -20
  99. package/docs/en-GB/intlayer_with_react_router_v7.md +171 -220
  100. package/docs/en-GB/intlayer_with_tanstack.md +174 -248
  101. package/docs/en-GB/intlayer_with_vite+preact.md +9 -9
  102. package/docs/en-GB/intlayer_with_vite+react.md +9 -9
  103. package/docs/en-GB/intlayer_with_vite+vue.md +11 -11
  104. package/docs/en-GB/packages/next-intlayer/useIntlayer.md +1 -1
  105. package/docs/en-GB/packages/react-intlayer/useIntlayer.md +1 -1
  106. package/docs/en-GB/vs_code_extension.md +42 -103
  107. package/docs/es/component_i18n.md +182 -0
  108. package/docs/es/intlayer_with_angular.md +2 -2
  109. package/docs/es/intlayer_with_astro.md +246 -0
  110. package/docs/es/intlayer_with_create_react_app.md +3 -2
  111. package/docs/es/intlayer_with_express.md +2 -2
  112. package/docs/es/intlayer_with_nextjs_14.md +2 -2
  113. package/docs/es/intlayer_with_nextjs_15.md +2 -2
  114. package/docs/es/intlayer_with_react_native+expo.md +11 -20
  115. package/docs/es/intlayer_with_react_router_v7.md +188 -232
  116. package/docs/es/intlayer_with_tanstack.md +203 -273
  117. package/docs/es/intlayer_with_vite+preact.md +7 -7
  118. package/docs/es/intlayer_with_vite+react.md +7 -7
  119. package/docs/es/intlayer_with_vite+vue.md +9 -9
  120. package/docs/es/vs_code_extension.md +53 -114
  121. package/docs/fr/component_i18n.md +186 -0
  122. package/docs/fr/intlayer_with_angular.md +2 -2
  123. package/docs/fr/intlayer_with_astro.md +246 -0
  124. package/docs/fr/intlayer_with_create_react_app.md +3 -2
  125. package/docs/fr/intlayer_with_express.md +2 -2
  126. package/docs/fr/intlayer_with_nestjs.md +2 -2
  127. package/docs/fr/intlayer_with_nextjs_14.md +2 -2
  128. package/docs/fr/intlayer_with_react_native+expo.md +11 -20
  129. package/docs/fr/intlayer_with_react_router_v7.md +188 -248
  130. package/docs/fr/intlayer_with_tanstack.md +192 -265
  131. package/docs/fr/intlayer_with_vite+preact.md +7 -7
  132. package/docs/fr/intlayer_with_vite+react.md +7 -7
  133. package/docs/fr/intlayer_with_vite+vue.md +9 -9
  134. package/docs/fr/vs_code_extension.md +50 -111
  135. package/docs/hi/component_i18n.md +186 -0
  136. package/docs/hi/intlayer_cli.md +1 -4
  137. package/docs/hi/intlayer_with_angular.md +2 -2
  138. package/docs/hi/intlayer_with_astro.md +246 -0
  139. package/docs/hi/intlayer_with_create_react_app.md +2 -2
  140. package/docs/hi/intlayer_with_express.md +2 -2
  141. package/docs/hi/intlayer_with_nestjs.md +2 -2
  142. package/docs/hi/intlayer_with_nextjs_14.md +2 -2
  143. package/docs/hi/intlayer_with_nextjs_15.md +2 -2
  144. package/docs/hi/intlayer_with_nextjs_page_router.md +2 -2
  145. package/docs/hi/intlayer_with_nuxt.md +2 -2
  146. package/docs/hi/intlayer_with_react_native+expo.md +11 -20
  147. package/docs/hi/intlayer_with_react_router_v7.md +199 -243
  148. package/docs/hi/intlayer_with_tanstack.md +210 -285
  149. package/docs/hi/intlayer_with_vite+preact.md +9 -9
  150. package/docs/hi/intlayer_with_vite+react.md +9 -9
  151. package/docs/hi/intlayer_with_vite+solid.md +1 -1
  152. package/docs/hi/intlayer_with_vite+vue.md +11 -11
  153. package/docs/hi/vs_code_extension.md +49 -110
  154. package/docs/it/component_i18n.md +186 -0
  155. package/docs/it/intlayer_with_angular.md +2 -2
  156. package/docs/it/intlayer_with_astro.md +246 -0
  157. package/docs/it/intlayer_with_create_react_app.md +3 -2
  158. package/docs/it/intlayer_with_express.md +2 -2
  159. package/docs/it/intlayer_with_nestjs.md +2 -2
  160. package/docs/it/intlayer_with_nextjs_14.md +2 -2
  161. package/docs/it/intlayer_with_nextjs_15.md +2 -2
  162. package/docs/it/intlayer_with_nextjs_page_router.md +2 -2
  163. package/docs/it/intlayer_with_nuxt.md +2 -2
  164. package/docs/it/intlayer_with_react_native+expo.md +11 -21
  165. package/docs/it/intlayer_with_react_router_v7.md +195 -242
  166. package/docs/it/intlayer_with_tanstack.md +203 -267
  167. package/docs/it/intlayer_with_vite+preact.md +9 -9
  168. package/docs/it/intlayer_with_vite+react.md +13 -11
  169. package/docs/it/intlayer_with_vite+vue.md +11 -11
  170. package/docs/it/vs_code_extension.md +50 -111
  171. package/docs/ja/component_i18n.md +186 -0
  172. package/docs/ja/intlayer_with_angular.md +2 -2
  173. package/docs/ja/intlayer_with_astro.md +246 -0
  174. package/docs/ja/intlayer_with_create_react_app.md +3 -2
  175. package/docs/ja/intlayer_with_express.md +2 -2
  176. package/docs/ja/intlayer_with_nestjs.md +2 -2
  177. package/docs/ja/intlayer_with_nextjs_14.md +2 -2
  178. package/docs/ja/intlayer_with_nextjs_15.md +2 -2
  179. package/docs/ja/intlayer_with_nextjs_page_router.md +2 -2
  180. package/docs/ja/intlayer_with_nuxt.md +2 -2
  181. package/docs/ja/intlayer_with_react_native+expo.md +18 -29
  182. package/docs/ja/intlayer_with_react_router_v7.md +204 -250
  183. package/docs/ja/intlayer_with_tanstack.md +218 -286
  184. package/docs/ja/intlayer_with_vite+preact.md +9 -9
  185. package/docs/ja/intlayer_with_vite+react.md +11 -11
  186. package/docs/ja/intlayer_with_vite+vue.md +11 -11
  187. package/docs/ja/vs_code_extension.md +50 -111
  188. package/docs/ko/component_i18n.md +186 -0
  189. package/docs/ko/intlayer_with_angular.md +2 -2
  190. package/docs/ko/intlayer_with_astro.md +246 -0
  191. package/docs/ko/intlayer_with_create_react_app.md +3 -2
  192. package/docs/ko/intlayer_with_express.md +2 -2
  193. package/docs/ko/intlayer_with_nestjs.md +2 -2
  194. package/docs/ko/intlayer_with_nextjs_14.md +2 -2
  195. package/docs/ko/intlayer_with_nextjs_15.md +2 -2
  196. package/docs/ko/intlayer_with_nextjs_page_router.md +2 -2
  197. package/docs/ko/intlayer_with_nuxt.md +2 -2
  198. package/docs/ko/intlayer_with_react_native+expo.md +19 -28
  199. package/docs/ko/intlayer_with_react_router_v7.md +190 -244
  200. package/docs/ko/intlayer_with_tanstack.md +200 -270
  201. package/docs/ko/intlayer_with_vite+preact.md +9 -9
  202. package/docs/ko/intlayer_with_vite+react.md +9 -9
  203. package/docs/ko/intlayer_with_vite+vue.md +11 -11
  204. package/docs/ko/vs_code_extension.md +48 -109
  205. package/docs/pt/component_i18n.md +186 -0
  206. package/docs/pt/intlayer_with_angular.md +2 -2
  207. package/docs/pt/intlayer_with_astro.md +246 -0
  208. package/docs/pt/intlayer_with_create_react_app.md +3 -2
  209. package/docs/pt/intlayer_with_express.md +2 -2
  210. package/docs/pt/intlayer_with_nestjs.md +2 -2
  211. package/docs/pt/intlayer_with_nextjs_14.md +2 -2
  212. package/docs/pt/intlayer_with_nextjs_15.md +2 -2
  213. package/docs/pt/intlayer_with_nextjs_page_router.md +2 -2
  214. package/docs/pt/intlayer_with_nuxt.md +2 -2
  215. package/docs/pt/intlayer_with_react_native+expo.md +11 -20
  216. package/docs/pt/intlayer_with_react_router_v7.md +7 -13
  217. package/docs/pt/intlayer_with_tanstack.md +183 -258
  218. package/docs/pt/intlayer_with_vite+preact.md +9 -9
  219. package/docs/pt/intlayer_with_vite+react.md +9 -9
  220. package/docs/pt/intlayer_with_vite+vue.md +9 -9
  221. package/docs/pt/vs_code_extension.md +46 -107
  222. package/docs/ru/component_i18n.md +186 -0
  223. package/docs/ru/intlayer_with_angular.md +2 -2
  224. package/docs/ru/intlayer_with_astro.md +246 -0
  225. package/docs/ru/intlayer_with_create_react_app.md +3 -2
  226. package/docs/ru/intlayer_with_express.md +2 -2
  227. package/docs/ru/intlayer_with_nestjs.md +2 -2
  228. package/docs/ru/intlayer_with_nextjs_14.md +2 -2
  229. package/docs/ru/intlayer_with_nextjs_15.md +2 -2
  230. package/docs/ru/intlayer_with_nextjs_page_router.md +2 -2
  231. package/docs/ru/intlayer_with_nuxt.md +2 -2
  232. package/docs/ru/intlayer_with_react_native+expo.md +11 -20
  233. package/docs/ru/intlayer_with_react_router_v7.md +192 -238
  234. package/docs/ru/intlayer_with_tanstack.md +197 -269
  235. package/docs/ru/intlayer_with_vite+preact.md +9 -9
  236. package/docs/ru/intlayer_with_vite+react.md +9 -9
  237. package/docs/ru/intlayer_with_vite+vue.md +11 -11
  238. package/docs/ru/vs_code_extension.md +48 -109
  239. package/docs/tr/component_i18n.md +186 -0
  240. package/docs/tr/how_works_intlayer.md +1 -1
  241. package/docs/tr/index.md +1 -1
  242. package/docs/tr/intlayer_cli.md +1 -1
  243. package/docs/tr/intlayer_with_angular.md +4 -4
  244. package/docs/tr/intlayer_with_astro.md +246 -0
  245. package/docs/tr/intlayer_with_create_react_app.md +4 -4
  246. package/docs/tr/intlayer_with_express.md +3 -3
  247. package/docs/tr/intlayer_with_lynx+react.md +1 -1
  248. package/docs/tr/intlayer_with_nestjs.md +2 -2
  249. package/docs/tr/intlayer_with_nextjs_14.md +2 -2
  250. package/docs/tr/intlayer_with_nextjs_15.md +4 -4
  251. package/docs/tr/intlayer_with_nextjs_page_router.md +5 -5
  252. package/docs/tr/intlayer_with_nuxt.md +4 -4
  253. package/docs/tr/intlayer_with_react_native+expo.md +12 -21
  254. package/docs/tr/intlayer_with_react_router_v7.md +222 -267
  255. package/docs/tr/intlayer_with_tanstack.md +400 -303
  256. package/docs/tr/intlayer_with_vite+preact.md +12 -12
  257. package/docs/tr/intlayer_with_vite+react.md +12 -12
  258. package/docs/tr/intlayer_with_vite+solid.md +2 -2
  259. package/docs/tr/intlayer_with_vite+svelte.md +2 -2
  260. package/docs/tr/intlayer_with_vite+vue.md +12 -12
  261. package/docs/tr/introduction.md +1 -1
  262. package/docs/tr/packages/react-intlayer/useDictionary.md +1 -1
  263. package/docs/tr/packages/react-intlayer/useI18n.md +1 -1
  264. package/docs/tr/roadmap.md +1 -1
  265. package/docs/tr/vs_code_extension.md +54 -115
  266. package/docs/zh/component_i18n.md +186 -0
  267. package/docs/zh/intlayer_with_angular.md +2 -2
  268. package/docs/zh/intlayer_with_astro.md +246 -0
  269. package/docs/zh/intlayer_with_create_react_app.md +3 -2
  270. package/docs/zh/intlayer_with_express.md +2 -2
  271. package/docs/zh/intlayer_with_nestjs.md +2 -2
  272. package/docs/zh/intlayer_with_nextjs_14.md +2 -2
  273. package/docs/zh/intlayer_with_nextjs_15.md +2 -2
  274. package/docs/zh/intlayer_with_nextjs_page_router.md +2 -2
  275. package/docs/zh/intlayer_with_nuxt.md +2 -2
  276. package/docs/zh/intlayer_with_react_native+expo.md +19 -28
  277. package/docs/zh/intlayer_with_react_router_v7.md +200 -248
  278. package/docs/zh/intlayer_with_tanstack.md +208 -283
  279. package/docs/zh/intlayer_with_vite+preact.md +9 -9
  280. package/docs/zh/intlayer_with_vite+react.md +9 -9
  281. package/docs/zh/intlayer_with_vite+vue.md +9 -9
  282. package/docs/zh/vs_code_extension.md +51 -105
  283. package/package.json +10 -10
  284. package/src/generated/docs.entry.ts +32 -0
@@ -19,6 +19,8 @@ slugs:
19
19
 
20
20
  # next-i18next VS next-intl VS intlayer | Next.js Internationalization (i18n)
21
21
 
22
+ ![next-i18next VS next-intl VS intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/assets/i18next-next-intl-intlayer.png?raw=true)
23
+
22
24
  Let’s take a look into the similarities and differences between three i18n options for Next.js: next-i18next, next-intl, and Intlayer.
23
25
 
24
26
  This is not a full tutorial. It’s a comparison to help you pick.
@@ -35,7 +37,7 @@ We focus on **Next.js 13+ App Router** (with **React Server Components**) and ev
35
37
 
36
38
  > **tl;dr**: All three can localize a Next.js app. If you want **component-scoped content**, **strict TypeScript types**, **build-time missing-key checks**, **tree-shaken dictionaries**, and **first-class App Router + SEO helpers**, **Intlayer** is the most complete, modern choice.
37
39
 
38
- > One confusion often made by developers is to think that `next-intl` is the Next.js version of `react-intl`. It's not—`next-intl` is maintained by [Amann](https://github.com/amannn), while `react-intl` is maintained by [FormatJS](https://github.com/formatjs/formatjs).
40
+ > One confusion often made by developers is to think that `next-intl` is the Next.js version of `react-intl`. It's not, `next-intl` is maintained by [Amann](https://github.com/amannn), while `react-intl` is maintained by [FormatJS](https://github.com/formatjs/formatjs).
39
41
 
40
42
  ---
41
43
 
@@ -163,11 +165,13 @@ How the library handles fallbacks is also important. Let's consider that the app
163
165
 
164
166
  In the case of `next-intl` and `next-i18next`, the library requires loading the JSON related to the current locale, but also to the fallback locale. Thus, considering that all content has been translated, each page will load 100% unnecessary content. **In comparison, `intlayer` processes the fallback at dictionary build time. Thus, each page will load only the content used.**
165
167
 
168
+ > Note: To optimize the bundle using `intlayer`, you need to set the `importMode: 'dynamic'` option in your `intlayer.config.ts` file. And ensure the plugin `@intlayer/babel` / `@intlayer/swc` is installed (installed by default using `vite-intlayer`).
169
+
166
170
  Here an example of the impact of bundle size optimization using `intlayer` in a vite + react application:
167
171
 
168
- | Optimized bundle | Bundle not optimized |
169
- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
170
- | ![optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png) | ![no optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png) |
172
+ | Optimized bundle | Bundle not optimized |
173
+ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
174
+ | ![optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) | ![no optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) |
171
175
 
172
176
  ---
173
177
 
@@ -176,16 +180,16 @@ Here an example of the impact of bundle size optimization using `intlayer` in a
176
180
  <Columns>
177
181
  <Column>
178
182
 
179
- **next-intl**
183
+ **next-i18next**
180
184
 
181
- - Solid TypeScript support, but **keys aren’t strictly typed by default**; you’ll maintain safety patterns manually.
185
+ - Base typings for hooks. **strict key typing requires extra tooling/config**.
182
186
 
183
187
  </Column>
184
188
  <Column>
185
189
 
186
- **next-i18next**
190
+ **next-intl**
187
191
 
188
- - Base typings for hooks; **strict key typing requires extra tooling/config**.
192
+ - Solid TypeScript support, but **keys aren’t strictly typed by default**. you’ll maintain safety patterns manually.
189
193
 
190
194
  </Column>
191
195
  <Column>
@@ -206,16 +210,16 @@ Here an example of the impact of bundle size optimization using `intlayer` in a
206
210
  <Columns>
207
211
  <Column>
208
212
 
209
- **next-intl**
213
+ **next-i18next**
210
214
 
211
- - Relies on **runtime fallbacks** (e.g., show the key or default locale). Build doesn’t fail.
215
+ - Relies on **runtime fallbacks**. Build doesn’t fail.
212
216
 
213
217
  </Column>
214
218
  <Column>
215
219
 
216
- **next-i18next**
220
+ **next-intl**
217
221
 
218
- - Relies on **runtime fallbacks** (e.g., show the key or default locale). Build doesn’t fail.
222
+ - Relies on **runtime fallbacks**. Build doesn’t fail.
219
223
 
220
224
  </Column>
221
225
  <Column>
@@ -227,7 +231,7 @@ Here an example of the impact of bundle size optimization using `intlayer` in a
227
231
  </Column>
228
232
  </Columns>
229
233
 
230
- **Why it matters:** Catching gaps during build prevents “mystery strings in production and aligns with strict release gates.
234
+ **Why it matters:** Catching gaps during build prevents 'undefined' strings in production.
231
235
 
232
236
  ---
233
237
 
@@ -236,28 +240,30 @@ Here an example of the impact of bundle size optimization using `intlayer` in a
236
240
  <Columns>
237
241
  <Column>
238
242
 
239
- **next-intl**
243
+ **next-i18next**
240
244
 
241
- - Works with **Next.js localized routing** on the App Router.
245
+ - Allows localized routing. But middleware is not built-in.
242
246
 
243
247
  </Column>
244
248
  <Column>
245
249
 
246
- **next-i18next**
250
+ **next-intl**
247
251
 
248
- - Works with **Next.js localized routing** on the App Router.
252
+ - Allows localized routing.
253
+ - Provides middleware.
249
254
 
250
255
  </Column>
251
256
  <Column>
252
257
 
253
258
  **intlayer**
254
259
 
255
- - All of the above, plus **i18n middleware** (locale detection via headers/cookies) and **helpers** to generate localized URLs and `<link rel="alternate" hreflang="…">` tags.
260
+ - Allows localized routing.
261
+ - Provides middleware.
256
262
 
257
263
  </Column>
258
264
  </Columns>
259
265
 
260
- **Why it matters:** Fewer custom glue layers; **consistent UX** and **clean SEO** across locales.
266
+ **Why it matters:** Helps for SEO and discovery, as well as user experience.
261
267
 
262
268
  ---
263
269
 
@@ -266,58 +272,33 @@ Here an example of the impact of bundle size optimization using `intlayer` in a
266
272
  <Columns>
267
273
  <Column>
268
274
 
269
- **next-intl**
270
-
271
- - Supports Next.js 13+. Often requires passing t-functions/formatters through component trees in hybrid setups.
272
-
273
- </Column>
274
- <Column>
275
-
276
275
  **next-i18next**
277
276
 
278
- - Supports Next.js 13+. Similar constraints with passing translation utilities across boundaries.
277
+ - Support page and layout server components.
278
+ - Do not provide synchronous API for children server components.
279
279
 
280
280
  </Column>
281
281
  <Column>
282
282
 
283
- **intlayer**
284
-
285
- - Supports Next.js 13+ and smooths the **server/client boundary** with a consistent API and RSC-oriented providers, avoiding shuttling formatters or t-functions.
286
-
287
- </Column>
288
- </Columns>
289
-
290
- **Why it matters:** Cleaner mental model and fewer edge cases in hybrid trees.
291
-
292
- ---
293
-
294
- ## DX, tooling & maintenance
295
-
296
- <Columns>
297
- <Column>
298
-
299
283
  **next-intl**
300
284
 
301
- - Commonly paired with external localization platforms and editorial workflows.
302
-
303
- </Column>
304
- <Column>
305
-
306
- **next-i18next**
307
-
308
- - Commonly paired with external localization platforms and editorial workflows.
285
+ - Support page and layout server components.
286
+ - Do not provide synchronous API for children server components.
309
287
 
310
288
  </Column>
311
289
  <Column>
312
290
 
313
291
  **intlayer**
314
292
 
315
- - Ships a **free Visual Editor** and **optional CMS** (Git-friendly or externalized), plus a **VSCode extension** and **AI-assisted translations** using your own provider keys.
293
+ - Support page and layout server components.
294
+ - Provide synchronous API for children server components.
316
295
 
317
296
  </Column>
318
297
  </Columns>
319
298
 
320
- **Why it matters:** Lowers ops cost and shortens the loop between developers and content authors.
299
+ **Why it matters:** Server component suport is a key feature of Next.js 13+, helping for performance. Passing props the locale or the `t` function from the parent to the child server components make your components less reusable.
300
+
301
+ ---
321
302
 
322
303
  ## Integration with localization platforms (TMS)
323
304
 
@@ -338,6 +319,8 @@ Large organizations often rely on Translation Management Systems (TMS) like **Cr
338
319
 
339
320
  > Note: `next-intl` and `i18next` also accepts TypeScript catalogs. If your team stores messages in `.ts` files or decentralizes them by feature, you can face similar TMS friction. However, many `next-intl` setups remain centralized in a `locales/` folder, which is a bit easier to refactor to JSON for TMS.
340
321
 
322
+ ---
323
+
341
324
  ## Developer Experience
342
325
 
343
326
  This part makes a deep comparison between the three solutions. Rather than considering simple cases, as described in the 'getting started' documentation for each solution, we will consider a real use case, more similar to a real project.
@@ -352,25 +335,25 @@ The app structure is important to ensure good maintainability for your codebase.
352
335
 
353
336
  ```bash
354
337
  .
355
- ├── public
356
- │ └── locales
357
- │ ├── en
358
- │ │ ├── home.json
359
- │ │ └── navbar.json
360
- │ ├── fr
361
- │ │ ├── home.json
362
- │ │ └── navbar.json
363
- │ └── es
364
- │ ├── home.json
365
- │ └── navbar.json
366
- ├── next-i18next.config.js
338
+ ├── i18n.config.ts
367
339
  └── src
368
- ├── middleware.ts
340
+ ├── locales
341
+ │ ├── en
342
+ │ │ ├── common.json
343
+ │ │ └── about.json
344
+ │ └── fr
345
+ │ ├── common.json
346
+ │ └── about.json
369
347
  ├── app
370
- └── home.tsx
348
+ ├── i18n
349
+ │ │ └── server.ts
350
+ │ └── [locale]
351
+ │ ├── layout.tsx
352
+ │ └── about.tsx
371
353
  └── components
372
- └── Navbar
373
- └── index.tsx
354
+ ├── I18nProvider.tsx
355
+ ├── ClientComponent.tsx
356
+ └── ServerComponent.tsx
374
357
  ```
375
358
 
376
359
  </TabItem>
@@ -378,6 +361,7 @@ The app structure is important to ensure good maintainability for your codebase.
378
361
 
379
362
  ```bash
380
363
  .
364
+ ├── i18n.ts
381
365
  ├── locales
382
366
  │ ├── en
383
367
  │ │ ├── home.json
@@ -388,11 +372,13 @@ The app structure is important to ensure good maintainability for your codebase.
388
372
  │ └── es
389
373
  │ ├── home.json
390
374
  │ └── navbar.json
391
- ├── i18n.ts
392
375
  └── src
393
376
  ├── middleware.ts
394
377
  ├── app
395
- └── home.tsx
378
+ ├── i18n
379
+ │ │ └── server.ts
380
+ │ └── [locale]
381
+ │ └── home.tsx
396
382
  └── components
397
383
  └── Navbar
398
384
  └── index.tsx
@@ -407,9 +393,11 @@ The app structure is important to ensure good maintainability for your codebase.
407
393
  └── src
408
394
  ├── middleware.ts
409
395
  ├── app
410
- │ └── home
411
- └── index.tsx
412
- │ └── index.content.ts
396
+ │ └── [locale]
397
+ ├── layout.tsx
398
+ │ └── home
399
+ │ ├── index.tsx
400
+ │ └── index.content.ts
413
401
  └── components
414
402
  └── Navbar
415
403
  ├── index.tsx
@@ -432,141 +420,276 @@ How the library handles content loading is important.
432
420
  <Tab defaultTab="next-intl" group='techno'>
433
421
  <TabItem label="next-i18next" value="next-i18next">
434
422
 
435
- ```tsx fileName="next-i18next.config.js"
436
- module.exports = {
437
- i18n: {
438
- locales: ["en", "fr", "es"],
439
- defaultLocale: "en",
440
- },
441
- };
442
- ```
423
+ ```ts fileName="i18n.config.ts"
424
+ export const locales = ["en", "fr"] as const;
425
+ export type Locale = (typeof locales)[number];
443
426
 
444
- ```tsx fileName="src/app/_app.tsx"
445
- import { appWithTranslation } from "next-i18next";
427
+ export const defaultLocale: Locale = "en";
446
428
 
447
- const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
429
+ export const rtlLocales = ["ar", "he", "fa", "ur"] as const;
430
+ export const isRtl = (locale: string) =>
431
+ (rtlLocales as readonly string[]).includes(locale);
448
432
 
449
- export default appWithTranslation(MyApp);
433
+ export function localizedPath(locale: string, path: string) {
434
+ return locale === defaultLocale ? path : "/" + locale + path;
435
+ }
436
+
437
+ const ORIGIN = "https://example.com";
438
+ export function abs(locale: string, path: string) {
439
+ return ORIGIN + localizedPath(locale, path);
440
+ }
450
441
  ```
451
442
 
452
- ```tsx fileName="src/app/[locale]/about/page.tsx"
453
- import type { GetStaticProps } from "next";
454
- import { serverSideTranslations } from "next-i18next/serverSideTranslations";
455
- import { useTranslation } from "next-i18next";
456
- import { I18nextProvider, initReactI18next } from "react-i18next";
443
+ ```ts fileName="src/app/i18n/server.ts"
457
444
  import { createInstance } from "i18next";
458
- import { ClientComponent, ServerComponent } from "@components";
445
+ import { initReactI18next } from "react-i18next/initReactI18next";
446
+ import resourcesToBackend from "i18next-resources-to-backend";
447
+ import { defaultLocale } from "@/i18n.config";
448
+
449
+ // Load JSON resources from src/locales/<locale>/<namespace>.json
450
+ const backend = resourcesToBackend(
451
+ (locale: string, namespace: string) =>
452
+ import(`../../locales/${locale}/${namespace}.json`)
453
+ );
454
+
455
+ export async function initI18next(
456
+ locale: string,
457
+ namespaces: string[] = ["common"]
458
+ ) {
459
+ const i18n = createInstance();
460
+ await i18n
461
+ .use(initReactI18next)
462
+ .use(backend)
463
+ .init({
464
+ lng: locale,
465
+ fallbackLng: defaultLocale,
466
+ ns: namespaces,
467
+ defaultNS: "common",
468
+ interpolation: { escapeValue: false },
469
+ react: { useSuspense: false },
470
+ });
471
+ return i18n;
472
+ }
473
+ ```
459
474
 
460
- export default function HomePage({ locale }: { locale: string }) {
461
- // Déclarez explicitement le namespace utilisé par ce composant
462
- const resources = await loadMessagesFor(locale); // your loader (JSON, etc.)
475
+ ```tsx fileName="src/components/I18nProvider.tsx"
476
+ "use client";
463
477
 
464
- const i18n = createInstance();
465
- i18n.use(initReactI18next).init({
466
- lng: locale,
467
- fallbackLng: "en",
468
- resources,
469
- ns: ["common", "about"],
470
- defaultNS: "common",
471
- interpolation: { escapeValue: false },
478
+ import * as React from "react";
479
+ import { I18nextProvider } from "react-i18next";
480
+ import { createInstance } from "i18next";
481
+ import { initReactI18next } from "react-i18next/initReactI18next";
482
+ import resourcesToBackend from "i18next-resources-to-backend";
483
+ import { defaultLocale } from "@/i18n.config";
484
+
485
+ const backend = resourcesToBackend(
486
+ (locale: string, namespace: string) =>
487
+ import(`../../locales/${locale}/${namespace}.json`)
488
+ );
489
+
490
+ type Props = {
491
+ locale: string;
492
+ namespaces?: string[];
493
+ resources?: Record<string, any>; // { ns: bundle }
494
+ children: React.ReactNode;
495
+ };
496
+
497
+ export default function I18nProvider({
498
+ locale,
499
+ namespaces = ["common"],
500
+ resources,
501
+ children,
502
+ }: Props) {
503
+ const [i18n] = React.useState(() => {
504
+ const i = createInstance();
505
+
506
+ i.use(initReactI18next)
507
+ .use(backend)
508
+ .init({
509
+ lng: locale,
510
+ fallbackLng: defaultLocale,
511
+ ns: namespaces,
512
+ resources: resources ? { [locale]: resources } : undefined,
513
+ defaultNS: "common",
514
+ interpolation: { escapeValue: false },
515
+ react: { useSuspense: false },
516
+ });
517
+
518
+ return i;
472
519
  });
473
520
 
474
- const { t } = useTranslation("about");
521
+ return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
522
+ }
523
+ ```
524
+
525
+ ```tsx fileName="src/app/[locale]/layout.tsx"
526
+ import type { ReactNode } from "react";
527
+ import { locales, defaultLocale, isRtl, type Locale } from "@/i18n.config";
528
+
529
+ export const dynamicParams = false;
530
+
531
+ export function generateStaticParams() {
532
+ return locales.map((locale) => ({ locale }));
533
+ }
534
+
535
+ export default function LocaleLayout({
536
+ children,
537
+ params,
538
+ }: {
539
+ children: ReactNode;
540
+ params: { locale: string };
541
+ }) {
542
+ const locale: Locale = (locales as readonly string[]).includes(params.locale)
543
+ ? (params.locale as any)
544
+ : defaultLocale;
545
+
546
+ const dir = isRtl(locale) ? "rtl" : "ltr";
547
+
548
+ return (
549
+ <html lang={locale} dir={dir}>
550
+ <body>{children}</body>
551
+ </html>
552
+ );
553
+ }
554
+ ```
555
+
556
+ ```tsx fileName="src/app/[locale]/about.tsx"
557
+ import I18nProvider from "@/components/I18nProvider";
558
+ import { initI18next } from "@/app/i18n/server";
559
+ import type { Locale } from "@/i18n.config";
560
+ import ClientComponent from "@/components/ClientComponent";
561
+ import ServerComponent from "@/components/ServerComponent";
562
+
563
+ // Force static rendering for the page
564
+ export const dynamic = "force-static";
565
+
566
+ export default async function AboutPage({
567
+ params: { locale },
568
+ }: {
569
+ params: { locale: Locale };
570
+ }) {
571
+ const namespaces = ["common", "about"] as const;
572
+
573
+ const i18n = await initI18next(locale, [...namespaces]);
574
+ const tAbout = i18n.getFixedT(locale, "about");
475
575
 
476
576
  return (
477
- <I18nextProvider i18n={i18n}>
577
+ <I18nProvider locale={locale} namespaces={[...namespaces]}>
478
578
  <main>
479
- <h1>{t("title")}</h1>
579
+ <h1>{tAbout("title")}</h1>
580
+
480
581
  <ClientComponent />
481
- <ServerComponent />
582
+ <ServerComponent t={tAbout} locale={locale} count={0} />
482
583
  </main>
483
- </I18nextProvider>
584
+ </I18nProvider>
484
585
  );
485
586
  }
486
-
487
- export const getStaticProps: GetStaticProps = async ({ locale }) => {
488
- // Ne préchargez que les namespaces nécessaires à CETTE page
489
- return {
490
- props: {
491
- ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
492
- },
493
- };
494
- };
495
587
  ```
496
588
 
497
589
  </TabItem>
498
590
  <TabItem label="next-intl" value="next-intl">
499
591
 
500
- ```tsx fileName="i18n.ts"
592
+ ```tsx fileName="src/i18n.ts"
501
593
  import { getRequestConfig } from "next-intl/server";
502
594
  import { notFound } from "next/navigation";
503
595
 
504
- // Can be imported from a shared config
505
- const locales = ["en", "fr", "es"];
596
+ export const locales = ["en", "fr", "es"] as const;
597
+ export const defaultLocale = "en" as const;
598
+
599
+ async function loadMessages(locale: string) {
600
+ // Load only the namespaces your layout/pages need
601
+ const [common, about] = await Promise.all([
602
+ import(`../locales/${locale}/common.json`).then((m) => m.default),
603
+ import(`../locales/${locale}/about.json`).then((m) => m.default),
604
+ ]);
605
+
606
+ return { common, about } as const;
607
+ }
506
608
 
507
609
  export default getRequestConfig(async ({ locale }) => {
508
- // Validate that the incoming `locale` parameter is valid
509
610
  if (!locales.includes(locale as any)) notFound();
510
611
 
511
612
  return {
512
- messages: (await import(`../messages/${locale}.json`)).default,
613
+ messages: await loadMessages(locale),
513
614
  };
514
615
  });
515
616
  ```
516
617
 
517
- ```tsx fileName="src/app/[locale]/about/layout.tsx"
518
- import { NextIntlClientProvider } from "next-intl";
519
- import { getMessages, unstable_setRequestLocale } from "next-intl/server";
520
- import pick from "lodash/pick";
618
+ ```tsx fileName="src/app/[locale]/layout.tsx"
619
+ import type { ReactNode } from "react";
620
+ import { locales } from "@/i18n";
621
+ import {
622
+ getLocaleDirection,
623
+ unstable_setRequestLocale,
624
+ } from "next-intl/server";
625
+
626
+ export const dynamic = "force-static";
627
+
628
+ export function generateStaticParams() {
629
+ return locales.map((locale) => ({ locale }));
630
+ }
521
631
 
522
632
  export default async function LocaleLayout({
523
633
  children,
524
634
  params,
525
635
  }: {
526
- children: React.ReactNode;
527
- params: { locale: string };
636
+ children: ReactNode;
637
+ params: Promise<{ locale: string }>;
528
638
  }) {
529
- const { locale } = params;
639
+ const { locale } = await params;
530
640
 
531
641
  // Set the active request locale for this server render (RSC)
532
642
  unstable_setRequestLocale(locale);
533
643
 
534
- // Messages are loaded server-side via src/i18n/request.ts
535
- // (see next-intl docs). Here we only push a subset to the client
536
- // that's needed for client components (payload optimization).
537
- const messages = await getMessages();
538
- const clientMessages = pick(messages, ["common", "about"]);
644
+ const dir = getLocaleDirection(locale);
539
645
 
540
646
  return (
541
- <html lang={locale}>
542
- <body>
543
- <NextIntlClientProvider locale={locale} messages={clientMessages}>
544
- {children}
545
- </NextIntlClientProvider>
546
- </body>
647
+ <html lang={locale} dir={dir}>
648
+ <body>{children}</body>
547
649
  </html>
548
650
  );
549
651
  }
550
652
  ```
551
653
 
552
654
  ```tsx fileName="src/app/[locale]/about/page.tsx"
553
- import { getTranslations } from "next-intl/server";
554
- import { ClientComponent, ServerComponent } from "@components";
655
+ import { getTranslations, getMessages, getFormatter } from "next-intl/server";
656
+ import { NextIntlClientProvider } from "next-intl";
657
+ import pick from "lodash/pick";
658
+ import ServerComponent from "@/components/ServerComponent";
659
+ import ClientComponentExample from "@/components/ClientComponentExample";
555
660
 
556
- export default async function LandingPage({
661
+ export const dynamic = "force-static";
662
+
663
+ export default async function AboutPage({
557
664
  params,
558
665
  }: {
559
- params: { locale: string };
666
+ params: Promise<{ locale: string }>;
560
667
  }) {
561
- // Chargement strictement côté serveur (pas hydraté au client)
562
- const t = await getTranslations("about");
668
+ const { locale } = await params;
669
+
670
+ // Messages are loaded server-side. Push only what's needed to the client.
671
+ const messages = await getMessages();
672
+ const clientMessages = pick(messages, ["common", "about"]);
673
+
674
+ // Strictly server-side translations/formatting
675
+ const tAbout = await getTranslations("about");
676
+ const tCounter = await getTranslations("about.counter");
677
+ const format = await getFormatter();
678
+
679
+ const initialFormattedCount = format.number(0);
563
680
 
564
681
  return (
565
- <main>
566
- <h1>{t("title")}</h1>
567
- <ClientComponent />
568
- <ServerComponent />
569
- </main>
682
+ <NextIntlClientProvider locale={locale} messages={clientMessages}>
683
+ <main>
684
+ <h1>{tAbout("title")}</h1>
685
+ <ClientComponentExample />
686
+ <ServerComponent
687
+ formattedCount={initialFormattedCount}
688
+ label={tCounter("label")}
689
+ increment={tCounter("increment")}
690
+ />
691
+ </main>
692
+ </NextIntlClientProvider>
570
693
  );
571
694
  }
572
695
  ```
@@ -575,12 +698,16 @@ export default async function LandingPage({
575
698
  <TabItem label="intlayer" value="intlayer">
576
699
 
577
700
  ```tsx fileName="intlayer.config.ts"
578
- export default {
701
+ import { type IntlayerConfig, Locales } from "intlayer";
702
+
703
+ const config: IntlayerConfig = {
579
704
  internationalization: {
580
- locales: ["en", "fr", "es"],
581
- defaultLocale: "en",
705
+ locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
706
+ defaultLocale: Locales.ENGLISH,
582
707
  },
583
708
  };
709
+
710
+ export default config;
584
711
  ```
585
712
 
586
713
  ```tsx fileName="src/app/[locale]/layout.tsx"
@@ -593,14 +720,16 @@ import {
593
720
 
594
721
  export const dynamic = "force-static";
595
722
 
596
- const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
723
+ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
597
724
  const { locale } = await params;
598
725
 
599
726
  return (
600
727
  <html lang={locale} dir={getHTMLTextDir(locale)}>
601
- <IntlayerClientProvider locale={locale}>
602
- {children}
603
- </IntlayerClientProvider>
728
+ <body>
729
+ <IntlayerClientProvider locale={locale}>
730
+ {children}
731
+ </IntlayerClientProvider>
732
+ </body>
604
733
  </html>
605
734
  );
606
735
  };
@@ -652,10 +781,12 @@ Let's take an example of a client component rendering a counter.
652
781
  <Tab defaultTab="next-intl" group='techno'>
653
782
  <TabItem label="next-i18next" value="next-i18next">
654
783
 
655
- **Translations (must be real JSON in `public/locales/...`)**
784
+ **Translations (one JSON per namespace under `src/locales/...`)**
656
785
 
657
- ```json fileName="public/locales/en/about.json"
786
+ ```json fileName="src/locales/en/about.json"
658
787
  {
788
+ "title": "About",
789
+ "description": "About page description",
659
790
  "counter": {
660
791
  "label": "Counter",
661
792
  "increment": "Increment"
@@ -663,8 +794,10 @@ Let's take an example of a client component rendering a counter.
663
794
  }
664
795
  ```
665
796
 
666
- ```json fileName="public/locales/fr/about.json"
797
+ ```json fileName="src/locales/fr/about.json"
667
798
  {
799
+ "title": "À propos",
800
+ "description": "Description de la page À propos",
668
801
  "counter": {
669
802
  "label": "Compteur",
670
803
  "increment": "Incrémenter"
@@ -672,19 +805,18 @@ Let's take an example of a client component rendering a counter.
672
805
  }
673
806
  ```
674
807
 
675
- **Client component**
808
+ **Client component (loads only the required namespace)**
676
809
 
677
- ```tsx fileName="src/components/ClientComponentExample.tsx"
810
+ ```tsx fileName="src/components/ClientComponent.tsx"
678
811
  "use client";
679
812
 
680
- import React, { useMemo, useState } from "react";
681
- import { useTranslation } from "next-i18next";
813
+ import React, { useState } from "react";
814
+ import { useTranslation } from "react-i18next";
682
815
 
683
- const ClientComponentExample = () => {
816
+ const ClientComponent = () => {
684
817
  const { t, i18n } = useTranslation("about");
685
818
  const [count, setCount] = useState(0);
686
819
 
687
- // next-i18next doesn't expose useNumber; use Intl.NumberFormat
688
820
  const numberFormat = new Intl.NumberFormat(i18n.language);
689
821
 
690
822
  return (
@@ -692,17 +824,19 @@ const ClientComponentExample = () => {
692
824
  <p>{numberFormat.format(count)}</p>
693
825
  <button
694
826
  aria-label={t("counter.label")}
695
- onClick={() => setCount((count) => count + 1)}
827
+ onClick={() => setCount((c) => c + 1)}
696
828
  >
697
829
  {t("counter.increment")}
698
830
  </button>
699
831
  </div>
700
832
  );
701
833
  };
834
+
835
+ export default ClientComponent;
702
836
  ```
703
837
 
704
- > Don't forget to add "about" namespace on the page serverSideTranslations
705
- > We take here the version of react 19.x.x, but for lower versions, you will need to use useMemo to store the instance of the formatter as it's a heavy function
838
+ > Ensure the page/provider includes only the namespaces you need (e.g. `about`).
839
+ > If you use React < 19, memoize heavy formatters like `Intl.NumberFormat`.
706
840
 
707
841
  </TabItem>
708
842
  <TabItem label="next-intl" value="next-intl">
@@ -827,17 +961,15 @@ We will take the case of a UI component. This component is a server component, a
827
961
  <Tab defaultTab="next-intl" group='techno'>
828
962
  <TabItem label="next-i18next" value="next-i18next">
829
963
 
830
- ```tsx fileName="src/pages/about.tsx"
831
- import type { GetStaticProps } from "next";
832
- import { useTranslation } from "next-i18next";
833
-
964
+ ```tsx fileName="src/components/ServerComponent.tsx"
834
965
  type ServerComponentProps = {
966
+ t: (key: string) => string;
967
+ locale: string;
835
968
  count: number;
836
969
  };
837
970
 
838
- const ServerComponent = ({ count }: ServerComponentProps) => {
839
- const { t, i18n } = useTranslation("about");
840
- const formatted = new Intl.NumberFormat(i18n.language).format(count);
971
+ const ServerComponent = ({ t, locale, count }: ServerComponentProps) => {
972
+ const formatted = new Intl.NumberFormat(locale).format(count);
841
973
 
842
974
  return (
843
975
  <div>
@@ -846,6 +978,8 @@ const ServerComponent = ({ count }: ServerComponentProps) => {
846
978
  </div>
847
979
  );
848
980
  };
981
+
982
+ export default ServerComponent;
849
983
  ```
850
984
 
851
985
  </TabItem>
@@ -853,20 +987,29 @@ const ServerComponent = ({ count }: ServerComponentProps) => {
853
987
 
854
988
  ```tsx fileName="src/components/ServerComponent.tsx"
855
989
  type ServerComponentProps = {
856
- count: number;
857
990
  t: (key: string) => string;
991
+ locale: string;
992
+ count: number;
993
+ formatter: Intl.NumberFormat;
858
994
  };
859
995
 
860
- const ServerComponent = ({ t, count }: ServerComponentProps) => {
861
- const formatted = new Intl.NumberFormat(i18n.language).format(count);
996
+ const ServerComponent = ({
997
+ t,
998
+ locale,
999
+ count,
1000
+ formatter,
1001
+ }: ServerComponentProps) => {
1002
+ const formatted = formatter.format(count);
862
1003
 
863
1004
  return (
864
1005
  <div>
865
1006
  <p>{formatted}</p>
866
- <button aria-label={t("label")}>{t("increment")}</button>
1007
+ <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
867
1008
  </div>
868
1009
  );
869
1010
  };
1011
+
1012
+ export default ServerComponent;
870
1013
  ```
871
1014
 
872
1015
  > As the server component cannot be async, you need to pass the translations and formatter function as props.
@@ -875,7 +1018,7 @@ const ServerComponent = ({ t, count }: ServerComponentProps) => {
875
1018
  >
876
1019
  > - `import { getTranslations, getFormatter } from "next-intl/server";`
877
1020
  > - `const t = await getTranslations("about.counter");`
878
- > - `const format = await getFormatter();`
1021
+ > - `const formatter = await getFormatter().then((formatter) => formatter.number());`
879
1022
 
880
1023
  </TabItem>
881
1024
  <TabItem label="intlayer" value="intlayer">
@@ -945,17 +1088,20 @@ export function abs(locale: string, path: string) {
945
1088
  import type { Metadata } from "next";
946
1089
  import { locales, defaultLocale, localizedPath } from "@/i18n.config";
947
1090
 
948
- export async function generateMetadata({
1091
+ type GenerateMetadataParams = {
1092
+ params: Promise<{
1093
+ locale: string;
1094
+ }>;
1095
+ };
1096
+
1097
+ export const generateMetadata = async ({
949
1098
  params,
950
- }: {
951
- params: { locale: string };
952
- }): Promise<Metadata> {
953
- const { locale } = params;
1099
+ }: GenerateMetadataParams): Promise<Metadata> => {
1100
+ const { locale } = await params;
954
1101
 
955
- // Dynamically import the correct JSON file
956
- const messages = (
957
- await import("@/../public/locales/" + locale + "/about.json")
958
- ).default;
1102
+ // Import the correct JSON bundle from src/locales
1103
+ const messages = (await import("@/locales/" + locale + "/about.json"))
1104
+ .default;
959
1105
 
960
1106
  const languages = Object.fromEntries(
961
1107
  locales.map((locale) => [locale, localizedPath(locale, "/about")])
@@ -969,7 +1115,7 @@ export async function generateMetadata({
969
1115
  languages: { ...languages, "x-default": "/about" },
970
1116
  },
971
1117
  };
972
- }
1118
+ };
973
1119
 
974
1120
  export default async function AboutPage() {
975
1121
  return <h1>About</h1>;
@@ -980,7 +1126,7 @@ export default async function AboutPage() {
980
1126
  import type { MetadataRoute } from "next";
981
1127
  import { locales, defaultLocale, abs } from "@/i18n.config";
982
1128
 
983
- export default function sitemap(): MetadataRoute.Sitemap {
1129
+ export const sitemap = (): MetadataRoute.Sitemap => {
984
1130
  const languages = Object.fromEntries(
985
1131
  locales.map((locale) => [locale, abs(locale, "/about")])
986
1132
  );
@@ -993,7 +1139,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
993
1139
  alternates: { languages },
994
1140
  },
995
1141
  ];
996
- }
1142
+ };
997
1143
  ```
998
1144
 
999
1145
  ```ts fileName="src/app/robots.ts"
@@ -1009,7 +1155,7 @@ const expandAllLocales = (path: string) => [
1009
1155
  .map((locale) => localizedPath(locale, path)),
1010
1156
  ];
1011
1157
 
1012
- export default function robots(): MetadataRoute.Robots {
1158
+ export const robots = (): MetadataRoute.Robots => {
1013
1159
  const disallow = [
1014
1160
  ...expandAllLocales("/dashboard"),
1015
1161
  ...expandAllLocales("/admin"),
@@ -1020,7 +1166,7 @@ export default function robots(): MetadataRoute.Robots {
1020
1166
  host: ORIGIN,
1021
1167
  sitemap: ORIGIN + "/sitemap.xml",
1022
1168
  };
1023
- }
1169
+ };
1024
1170
  ```
1025
1171
 
1026
1172
  </TabItem>
@@ -1031,16 +1177,20 @@ import type { Metadata } from "next";
1031
1177
  import { locales, defaultLocale } from "@/i18n";
1032
1178
  import { getTranslations } from "next-intl/server";
1033
1179
 
1034
- function localizedPath(locale: string, path: string) {
1180
+ const localizedPath = (locale: string, path: string) => {
1035
1181
  return locale === defaultLocale ? path : "/" + locale + path;
1036
- }
1182
+ };
1037
1183
 
1038
- export async function generateMetadata({
1184
+ type GenerateMetadataParams = {
1185
+ params: Promise<{
1186
+ locale: string;
1187
+ }>;
1188
+ };
1189
+
1190
+ export const generateMetadata = async ({
1039
1191
  params,
1040
- }: {
1041
- params: { locale: string };
1042
- }): Promise<Metadata> {
1043
- const { locale } = params;
1192
+ }: GenerateMetadataParams): Promise<Metadata> => {
1193
+ const { locale } = await params;
1044
1194
  const t = await getTranslations({ locale, namespace: "about" });
1045
1195
 
1046
1196
  const url = "/about";
@@ -1056,7 +1206,7 @@ export async function generateMetadata({
1056
1206
  languages: { ...languages, "x-default": url },
1057
1207
  },
1058
1208
  };
1059
- }
1209
+ };
1060
1210
 
1061
1211
  // ... Rest of the page code
1062
1212
  ```
@@ -1070,7 +1220,7 @@ const origin = "https://example.com";
1070
1220
  const formatterLocalizedPath = (locale: string, path: string) =>
1071
1221
  locale === defaultLocale ? origin + path : origin + "/" + locale + path;
1072
1222
 
1073
- export default function sitemap(): MetadataRoute.Sitemap {
1223
+ export const sitemap = (): MetadataRoute.Sitemap => {
1074
1224
  const aboutLanguages = Object.fromEntries(
1075
1225
  locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
1076
1226
  );
@@ -1084,7 +1234,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
1084
1234
  alternates: { languages: aboutLanguages },
1085
1235
  },
1086
1236
  ];
1087
- }
1237
+ };
1088
1238
  ```
1089
1239
 
1090
1240
  ```tsx fileName="src/app/robots.ts"
@@ -1099,7 +1249,7 @@ const withAllLocales = (path: string) => [
1099
1249
  .map((locale) => "/" + locale + path),
1100
1250
  ];
1101
1251
 
1102
- export default function robots(): MetadataRoute.Robots {
1252
+ export const robots = (): MetadataRoute.Robots => {
1103
1253
  const disallow = [
1104
1254
  ...withAllLocales("/dashboard"),
1105
1255
  ...withAllLocales("/admin"),
@@ -1110,7 +1260,7 @@ export default function robots(): MetadataRoute.Robots {
1110
1260
  host: origin,
1111
1261
  sitemap: origin + "/sitemap.xml",
1112
1262
  };
1113
- }
1263
+ };
1114
1264
  ```
1115
1265
 
1116
1266
  </TabItem>
@@ -1181,7 +1331,111 @@ export default robots;
1181
1331
 
1182
1332
  > Intlayer provides a `getMultilingualUrls` function to generate multilingual URLs for your sitemap.
1183
1333
 
1184
- ---
1334
+ ### Middleware for locale routing
1335
+
1336
+ <Tab defaultTab="next-intl" group='techno'>
1337
+ <TabItem label="next-i18next" value="next-i18next">
1338
+
1339
+ Add a middleware to handle locale detection and routing:
1340
+
1341
+ ```ts fileName="src/middleware.ts"
1342
+ import { NextResponse, type NextRequest } from "next/server";
1343
+ import { defaultLocale, locales } from "@/i18n.config";
1344
+
1345
+ const PUBLIC_FILE = /\.[^/]+$/; // exclude files with extensions
1346
+
1347
+ export function middleware(request: NextRequest) {
1348
+ const { pathname } = request.nextUrl;
1349
+
1350
+ if (
1351
+ pathname.startsWith("/_next") ||
1352
+ pathname.startsWith("/api") ||
1353
+ pathname.startsWith("/static") ||
1354
+ PUBLIC_FILE.test(pathname)
1355
+ ) {
1356
+ return;
1357
+ }
1358
+
1359
+ const hasLocale = locales.some(
1360
+ (l) => pathname === "/" + l || pathname.startsWith("/" + l + "/")
1361
+ );
1362
+ if (!hasLocale) {
1363
+ const locale = defaultLocale;
1364
+ const url = request.nextUrl.clone();
1365
+ url.pathname = "/" + locale + (pathname === "/" ? "" : pathname);
1366
+ return NextResponse.redirect(url);
1367
+ }
1368
+ }
1369
+
1370
+ export const config = {
1371
+ matcher: [
1372
+ // Match all paths except the ones starting with these and files with an extension
1373
+ "/((?!api|_next|static|.*\\..*).*)",
1374
+ ],
1375
+ };
1376
+ ```
1377
+
1378
+ </TabItem>
1379
+ <TabItem label="next-intl" value="next-intl">
1380
+
1381
+ Add a middleware to handle locale detection and routing:
1382
+
1383
+ ```ts fileName="src/middleware.ts"
1384
+ import createMiddleware from "next-intl/middleware";
1385
+ import { locales, defaultLocale } from "@/i18n";
1386
+
1387
+ export default createMiddleware({
1388
+ locales: [...locales],
1389
+ defaultLocale,
1390
+ localeDetection: true,
1391
+ });
1392
+
1393
+ export const config = {
1394
+ // Skip API, Next internals and static assets
1395
+ matcher: ["/((?!api|_next|.*\\..*).*)"],
1396
+ };
1397
+ ```
1398
+
1399
+ </TabItem>
1400
+ <TabItem label="intlayer" value="intlayer">
1401
+
1402
+ Intlayer provides built-in middleware handling through the `next-intlayer` package configuration.
1403
+
1404
+ </TabItem>
1405
+ </Tab>
1406
+
1407
+ ### Setup checklist and good practices
1408
+
1409
+ <Tab defaultTab="next-intl" group='techno'>
1410
+ <TabItem label="next-i18next" value="next-i18next">
1411
+
1412
+ - Ensure `lang` and `dir` are set on the root `<html>` in `src/app/[locale]/layout.tsx`.
1413
+ - Split translations into namespaces (for example `common.json`, `about.json`) under `src/locales/<locale>/`.
1414
+ - Only load required namespaces in client components using `useTranslation('<ns>')` and by scoping `I18nProvider` with the same namespaces.
1415
+ - Keep pages static when possible: export `export const dynamic = 'force-static'` on pages; set `dynamicParams = false` and implement `generateStaticParams`.
1416
+ - Use sync server components nested under client boundaries by passing already-computed strings or the `t` function and the `locale`.
1417
+ - For SEO, set `alternates.languages` in metadata, list localized URLs in `sitemap.ts`, and disallow duplicate localized routes in `robots.ts`.
1418
+ - Prefer locale-aware formatters (e.g., `Intl.NumberFormat(locale)`) and memoize them on the client if using React < 19.
1419
+
1420
+ </TabItem>
1421
+ <TabItem label="next-intl" value="next-intl">
1422
+
1423
+ - **Set html `lang` and `dir`**: In `src/app/[locale]/layout.tsx`, compute `dir` via `getLocaleDirection(locale)` and set `<html lang={locale} dir={dir}>`.
1424
+ - **Split messages by namespace**: Organize JSON per locale and namespace (e.g., `common.json`, `about.json`).
1425
+ - **Minimize client payload**: On pages, send only required namespaces to `NextIntlClientProvider` (e.g., `pick(messages, ['common', 'about'])`).
1426
+ - **Prefer static pages**: Export `export const dynamic = 'force-static'` and generate static params for all `locales`.
1427
+ - **Synchronous server components**: Keep server components sync by passing precomputed strings (translated labels, formatted numbers) rather than async calls or non-serializable functions.
1428
+
1429
+ </TabItem>
1430
+ <TabItem label="intlayer" value="intlayer">
1431
+
1432
+ - **Modular content**: Co-locate content dictionaries with components using `.content.{ts|js|json}` files.
1433
+ - **Type safety**: Leverage TypeScript integration for compile-time content validation.
1434
+ - **Build-time optimization**: Use Intlayer's build tools for automatic tree-shaking and bundle optimization.
1435
+ - **Integrated tooling**: Take advantage of built-in routing, SEO helpers, and visual editor support.
1436
+
1437
+ </TabItem>
1438
+ </Tab>
1185
1439
 
1186
1440
  ---
1187
1441