@intlayer/docs 9.2.0 → 9.3.1

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 (233) hide show
  1. package/README.md +24 -24
  2. package/blog/ar/index.md +1 -1
  3. package/blog/ar/rag_powered_documentation_assistant.md +1 -1
  4. package/blog/de/index.md +1 -1
  5. package/blog/en/index.md +1 -1
  6. package/blog/en-GB/index.md +1 -1
  7. package/blog/es/index.md +1 -1
  8. package/blog/fr/index.md +1 -1
  9. package/blog/hi/index.md +1 -1
  10. package/blog/id/index.md +1 -1
  11. package/blog/it/index.md +1 -1
  12. package/blog/ja/index.md +1 -1
  13. package/blog/ko/index.md +1 -1
  14. package/blog/pl/index.md +1 -1
  15. package/blog/pl/rag_powered_documentation_assistant.md +1 -1
  16. package/blog/pt/index.md +1 -1
  17. package/blog/ru/index.md +1 -1
  18. package/blog/uk/index.md +1 -1
  19. package/blog/vi/index.md +1 -1
  20. package/blog/zh/index.md +1 -1
  21. package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -2
  22. package/dist/cjs/authors2.cjs +0 -1
  23. package/dist/cjs/common.cjs +24 -1
  24. package/dist/cjs/common.cjs.map +1 -1
  25. package/dist/cjs/generated/blog.entry.cjs +35 -6
  26. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  27. package/dist/cjs/generated/docs.entry.cjs +35 -6
  28. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  29. package/dist/cjs/generated/frequentQuestions.entry.cjs +35 -6
  30. package/dist/cjs/generated/frequentQuestions.entry.cjs.map +1 -1
  31. package/dist/cjs/generated/legal.entry.cjs +35 -6
  32. package/dist/cjs/generated/legal.entry.cjs.map +1 -1
  33. package/dist/esm/common.mjs +24 -1
  34. package/dist/esm/common.mjs.map +1 -1
  35. package/dist/esm/generated/blog.entry.mjs +35 -6
  36. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  37. package/dist/esm/generated/docs.entry.mjs +35 -6
  38. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  39. package/dist/esm/generated/frequentQuestions.entry.mjs +35 -6
  40. package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
  41. package/dist/esm/generated/legal.entry.mjs +35 -6
  42. package/dist/esm/generated/legal.entry.mjs.map +1 -1
  43. package/dist/types/common.d.ts.map +1 -1
  44. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  45. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  46. package/dist/types/generated/frequentQuestions.entry.d.ts.map +1 -1
  47. package/dist/types/generated/legal.entry.d.ts.map +1 -1
  48. package/docs/ar/bundle_optimization.md +58 -25
  49. package/docs/ar/configuration.md +10 -10
  50. package/docs/ar/interest_of_intlayer.md +24 -22
  51. package/docs/ar/intlayer_with_express.md +1 -1
  52. package/docs/ar/intlayer_with_svelte_kit.md +1 -1
  53. package/docs/ar/intlayer_with_tanstack+solid.md +5 -3
  54. package/docs/ar/intlayer_with_tanstack.md +5 -3
  55. package/docs/ar/live-sync.md +4 -0
  56. package/docs/ar/lsp.md +114 -175
  57. package/docs/ar/readme.md +25 -25
  58. package/docs/bn/bundle_optimization.md +58 -25
  59. package/docs/bn/configuration.md +10 -10
  60. package/docs/bn/interest_of_intlayer.md +24 -22
  61. package/docs/cs/bundle_optimization.md +58 -25
  62. package/docs/cs/configuration.md +10 -10
  63. package/docs/cs/interest_of_intlayer.md +24 -22
  64. package/docs/de/bundle_optimization.md +58 -25
  65. package/docs/de/configuration.md +10 -10
  66. package/docs/de/interest_of_intlayer.md +24 -22
  67. package/docs/de/intlayer_with_svelte_kit.md +1 -1
  68. package/docs/de/intlayer_with_tanstack+solid.md +5 -3
  69. package/docs/de/intlayer_with_tanstack.md +5 -3
  70. package/docs/de/live-sync.md +4 -0
  71. package/docs/de/lsp.md +111 -172
  72. package/docs/de/readme.md +24 -24
  73. package/docs/en/bundle_optimization.md +51 -23
  74. package/docs/en/configuration.md +10 -10
  75. package/docs/en/interest_of_intlayer.md +24 -22
  76. package/docs/en/intlayer_with_svelte_kit.md +1 -1
  77. package/docs/en/intlayer_with_tanstack+solid.md +5 -3
  78. package/docs/en/intlayer_with_tanstack.md +5 -3
  79. package/docs/en/live-sync.md +4 -0
  80. package/docs/en/lsp.md +109 -170
  81. package/docs/en/readme.md +24 -24
  82. package/docs/en-GB/bundle_optimization.md +58 -25
  83. package/docs/en-GB/configuration.md +10 -10
  84. package/docs/en-GB/interest_of_intlayer.md +24 -22
  85. package/docs/en-GB/intlayer_with_svelte_kit.md +1 -1
  86. package/docs/en-GB/intlayer_with_tanstack+solid.md +5 -3
  87. package/docs/en-GB/intlayer_with_tanstack.md +5 -3
  88. package/docs/en-GB/live-sync.md +4 -0
  89. package/docs/en-GB/lsp.md +109 -170
  90. package/docs/en-GB/readme.md +24 -24
  91. package/docs/es/bundle_optimization.md +58 -25
  92. package/docs/es/configuration.md +10 -10
  93. package/docs/es/interest_of_intlayer.md +24 -22
  94. package/docs/es/intlayer_with_svelte_kit.md +1 -1
  95. package/docs/es/intlayer_with_tanstack+solid.md +5 -3
  96. package/docs/es/intlayer_with_tanstack.md +5 -3
  97. package/docs/es/live-sync.md +4 -0
  98. package/docs/es/lsp.md +114 -175
  99. package/docs/es/readme.md +24 -24
  100. package/docs/fr/bundle_optimization.md +58 -25
  101. package/docs/fr/configuration.md +10 -10
  102. package/docs/fr/interest_of_intlayer.md +24 -22
  103. package/docs/fr/intlayer_with_svelte_kit.md +1 -1
  104. package/docs/fr/intlayer_with_tanstack+solid.md +5 -3
  105. package/docs/fr/intlayer_with_tanstack.md +5 -3
  106. package/docs/fr/live-sync.md +4 -0
  107. package/docs/fr/lsp.md +110 -171
  108. package/docs/fr/readme.md +24 -24
  109. package/docs/hi/bundle_optimization.md +58 -25
  110. package/docs/hi/configuration.md +10 -10
  111. package/docs/hi/interest_of_intlayer.md +24 -22
  112. package/docs/hi/intlayer_with_express.md +1 -1
  113. package/docs/hi/intlayer_with_svelte_kit.md +1 -1
  114. package/docs/hi/intlayer_with_tanstack+solid.md +5 -3
  115. package/docs/hi/intlayer_with_tanstack.md +5 -3
  116. package/docs/hi/live-sync.md +4 -0
  117. package/docs/hi/lsp.md +113 -174
  118. package/docs/hi/readme.md +24 -24
  119. package/docs/id/bundle_optimization.md +58 -25
  120. package/docs/id/configuration.md +10 -10
  121. package/docs/id/interest_of_intlayer.md +24 -22
  122. package/docs/id/intlayer_with_svelte_kit.md +1 -1
  123. package/docs/id/intlayer_with_tanstack+solid.md +5 -3
  124. package/docs/id/intlayer_with_tanstack.md +5 -3
  125. package/docs/id/live-sync.md +4 -0
  126. package/docs/id/lsp.md +113 -174
  127. package/docs/id/readme.md +24 -24
  128. package/docs/it/bundle_optimization.md +58 -25
  129. package/docs/it/configuration.md +10 -10
  130. package/docs/it/interest_of_intlayer.md +24 -22
  131. package/docs/it/intlayer_with_svelte_kit.md +1 -1
  132. package/docs/it/intlayer_with_tanstack+solid.md +5 -3
  133. package/docs/it/intlayer_with_tanstack.md +5 -3
  134. package/docs/it/live-sync.md +4 -0
  135. package/docs/it/lsp.md +115 -176
  136. package/docs/it/readme.md +24 -24
  137. package/docs/ja/bundle_optimization.md +58 -25
  138. package/docs/ja/configuration.md +10 -10
  139. package/docs/ja/interest_of_intlayer.md +24 -22
  140. package/docs/ja/intlayer_with_tanstack+solid.md +5 -3
  141. package/docs/ja/intlayer_with_tanstack.md +5 -3
  142. package/docs/ja/live-sync.md +4 -0
  143. package/docs/ja/lsp.md +113 -174
  144. package/docs/ja/readme.md +24 -24
  145. package/docs/ko/bundle_optimization.md +58 -25
  146. package/docs/ko/configuration.md +10 -10
  147. package/docs/ko/interest_of_intlayer.md +24 -22
  148. package/docs/ko/intlayer_with_svelte_kit.md +1 -1
  149. package/docs/ko/intlayer_with_tanstack+solid.md +5 -3
  150. package/docs/ko/intlayer_with_tanstack.md +5 -3
  151. package/docs/ko/live-sync.md +4 -0
  152. package/docs/ko/lsp.md +112 -173
  153. package/docs/ko/readme.md +24 -24
  154. package/docs/nl/bundle_optimization.md +58 -25
  155. package/docs/nl/configuration.md +10 -10
  156. package/docs/nl/interest_of_intlayer.md +24 -22
  157. package/docs/pl/bundle_optimization.md +58 -25
  158. package/docs/pl/configuration.md +10 -10
  159. package/docs/pl/interest_of_intlayer.md +4 -2
  160. package/docs/pl/intlayer_with_svelte_kit.md +1 -1
  161. package/docs/pl/intlayer_with_tanstack+solid.md +5 -3
  162. package/docs/pl/intlayer_with_tanstack.md +5 -3
  163. package/docs/pl/live-sync.md +4 -0
  164. package/docs/pl/lsp.md +115 -176
  165. package/docs/pl/readme.md +24 -24
  166. package/docs/pt/bundle_optimization.md +58 -24
  167. package/docs/pt/configuration.md +10 -10
  168. package/docs/pt/interest_of_intlayer.md +24 -22
  169. package/docs/pt/intlayer_with_svelte_kit.md +1 -1
  170. package/docs/pt/intlayer_with_tanstack+solid.md +5 -3
  171. package/docs/pt/intlayer_with_tanstack.md +5 -3
  172. package/docs/pt/live-sync.md +4 -0
  173. package/docs/pt/lsp.md +113 -174
  174. package/docs/pt/readme.md +24 -24
  175. package/docs/ru/bundle_optimization.md +58 -25
  176. package/docs/ru/configuration.md +10 -10
  177. package/docs/ru/interest_of_intlayer.md +24 -22
  178. package/docs/ru/intlayer_with_nextjs_14.md +1 -1
  179. package/docs/ru/intlayer_with_nextjs_15.md +1 -1
  180. package/docs/ru/intlayer_with_svelte_kit.md +1 -1
  181. package/docs/ru/intlayer_with_tanstack+solid.md +5 -3
  182. package/docs/ru/intlayer_with_tanstack.md +5 -3
  183. package/docs/ru/live-sync.md +4 -0
  184. package/docs/ru/lsp.md +112 -173
  185. package/docs/ru/readme.md +24 -24
  186. package/docs/tr/bundle_optimization.md +58 -25
  187. package/docs/tr/configuration.md +10 -10
  188. package/docs/tr/interest_of_intlayer.md +24 -22
  189. package/docs/tr/intlayer_with_svelte_kit.md +1 -1
  190. package/docs/tr/intlayer_with_tanstack+solid.md +5 -3
  191. package/docs/tr/intlayer_with_tanstack.md +5 -3
  192. package/docs/tr/live-sync.md +4 -0
  193. package/docs/tr/lsp.md +113 -174
  194. package/docs/tr/readme.md +24 -24
  195. package/docs/uk/bundle_optimization.md +58 -25
  196. package/docs/uk/configuration.md +10 -10
  197. package/docs/uk/interest_of_intlayer.md +4 -2
  198. package/docs/uk/intlayer_with_svelte_kit.md +1 -1
  199. package/docs/uk/intlayer_with_tanstack+solid.md +5 -3
  200. package/docs/uk/intlayer_with_tanstack.md +5 -3
  201. package/docs/uk/live-sync.md +4 -0
  202. package/docs/uk/lsp.md +113 -174
  203. package/docs/uk/per_locale_file.md +1 -1
  204. package/docs/uk/readme.md +24 -24
  205. package/docs/ur/bundle_optimization.md +58 -25
  206. package/docs/ur/configuration.md +10 -10
  207. package/docs/ur/interest_of_intlayer.md +24 -22
  208. package/docs/vi/bundle_optimization.md +58 -25
  209. package/docs/vi/configuration.md +10 -10
  210. package/docs/vi/interest_of_intlayer.md +24 -22
  211. package/docs/vi/intlayer_with_svelte_kit.md +1 -1
  212. package/docs/vi/intlayer_with_tanstack+solid.md +5 -3
  213. package/docs/vi/intlayer_with_tanstack.md +5 -3
  214. package/docs/vi/live-sync.md +4 -0
  215. package/docs/vi/lsp.md +115 -176
  216. package/docs/vi/readme.md +24 -24
  217. package/docs/zh/bundle_optimization.md +58 -25
  218. package/docs/zh/configuration.md +10 -10
  219. package/docs/zh/interest_of_intlayer.md +24 -22
  220. package/docs/zh/intlayer_with_svelte_kit.md +1 -1
  221. package/docs/zh/intlayer_with_tanstack+solid.md +5 -3
  222. package/docs/zh/intlayer_with_tanstack.md +5 -3
  223. package/docs/zh/live-sync.md +4 -0
  224. package/docs/zh/lsp.md +113 -174
  225. package/docs/zh/readme.md +21 -21
  226. package/docs/zh-TW/bundle_optimization.md +58 -25
  227. package/docs/zh-TW/interest_of_intlayer.md +24 -22
  228. package/package.json +7 -7
  229. package/src/common.ts +39 -2
  230. package/src/generated/blog.entry.ts +39 -7
  231. package/src/generated/docs.entry.ts +39 -7
  232. package/src/generated/frequentQuestions.entry.ts +39 -7
  233. package/src/generated/legal.entry.ts +39 -7
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  createdAt: 2025-11-25
3
- updatedAt: 2026-06-07
3
+ updatedAt: 2026-08-09
4
4
  title: 最佳化 i18n 打包體積與效能
5
5
  description: 透過最佳化國際化(i18n)內容來縮減應用程式包的大小。了解如何利用 Intlayer 實現字典的 tree shaking 和延遲載入(lazy loading)。
6
6
  keywords:
@@ -16,6 +16,12 @@ slugs:
16
16
  - concept
17
17
  - bundle-optimization
18
18
  history:
19
+ - version: 9.2.1
20
+ date: 2026-08-09
21
+ changes: "`purge` 與 `minify` 現在可透過 `@intlayer/swc` 在 Next.js 上運作 — 不需要 `babel.config.js`"
22
+ - version: 8.12.0
23
+ date: 2026-06-24
24
+ changes: "在參考表中依所需的流水線順序列出 Babel 外掛(extract → purge → minify → optimize)"
19
25
  - version: 8.12.0
20
26
  date: 2026-06-07
21
27
  changes: "為 Babel/Webpack 引入了 `intlayerPurgeBabelPlugin` 和 `intlayerMinifyBabelPlugin`,明確了外掛程式管線"
@@ -191,12 +197,14 @@ Intlayer 的建置最佳化被劃分為若干個職責單一的外掛程式。
191
197
 
192
198
  這些被直接運用在基於 Webpack 設定的 `babel.config.js` 當中(比如使用了 Babel 的 Next.js、CRA,或是自訂的 Webpack 等)。
193
199
 
200
+ 下表依所需的流水線順序列出它們(與它們必須在 `babel.config.js` 中出現的順序相同):
201
+
194
202
  | 外掛程式 | 功能說明 |
195
203
  | :---------------------------- | :----------------------------------------------------------------------------------------------------- |
196
204
  | `intlayerExtractBabelPlugin` | 掃描 `.content.ts` 檔案並把編譯好的字典寫入 `.intlayer/` |
197
- | `intlayerOptimizeBabelPlugin` | 將 `useIntlayer('key')` 重寫為 `useDictionary(hash)` 並注入匹配對應字典的 `import` 語句 |
198
205
  | `intlayerPurgeBabelPlugin` | 掃描所有原始碼檔案,從已編譯的 `.intlayer/**/*.json` 字典檔案中刪除**未被使用的內容欄位** |
199
206
  | `intlayerMinifyBabelPlugin` | **重新命名內容欄位的鍵(keys)** 為簡短的字母別名(例如 `title` 變成 `a`),作用範圍包括 JSON 與原始碼 |
207
+ | `intlayerOptimizeBabelPlugin` | 將 `useIntlayer('key')` 重寫為 `useDictionary(hash)` 並注入匹配對應字典的 `import` 語句 |
200
208
 
201
209
  > **外掛程式的執行順序很重要。** 在您的 `babel.config.js` 裡,purge 和 minify 的外掛程式必須放置在 optimize 外掛程式**之前**。最佳化步驟(optimize)會把 `useIntlayer('key')` 替換為模糊的 `useDictionary(hash)`,此舉抹除了能夠讓 purge 和 minify 識別哪些欄位被使用過的字典 key 資訊。
202
210
 
@@ -205,9 +213,9 @@ Intlayer 的建置最佳化被劃分為若干個職責單一的外掛程式。
205
213
  | 選項助手 | 配套外掛程式 |
206
214
  | :--------------------------- | :---------------------------- |
207
215
  | `getExtractPluginOptions()` | `intlayerExtractBabelPlugin` |
208
- | `getOptimizePluginOptions()` | `intlayerOptimizeBabelPlugin` |
209
216
  | `getPurgePluginOptions()` | `intlayerPurgeBabelPlugin` |
210
217
  | `getMinifyPluginOptions()` | `intlayerMinifyBabelPlugin` |
218
+ | `getOptimizePluginOptions()` | `intlayerOptimizeBabelPlugin` |
211
219
 
212
220
  ### Vite 外掛程式 (`vite-intlayer`)
213
221
 
@@ -220,6 +228,20 @@ Vite 使用者**不需要直接對它們進行設定**。當您在 `vite.config.
220
228
  | Dictionary minify | 等同於 `intlayerMinifyBabelPlugin` 的 JSON 寫入步驟 |
221
229
  | Babel transform | 等同於 `intlayerMinifyBabelPlugin` 的程式碼重新命名步驟 + `intlayerOptimizeBabelPlugin` |
222
230
 
231
+ ### SWC 外掛(`@intlayer/swc`)
232
+
233
+ Next.js 使用者同樣**從不直接設定這些**。自 **v9.2.1** 起,`next.config.ts` 中的 `withIntlayer()` 僅憑 `build.purge` 與 `build.minify` 兩個旗標就會執行完整流水線 —— 清除、壓縮與匯入重寫。
234
+
235
+ 工作被分成兩部分,因為 SWC Wasm 外掛一次只轉換一個檔案,且無法存取檔案系統:
236
+
237
+ | 階段 | 執行位置 | 作用 |
238
+ | :----------------------------------- | :----------------------------- | :------------------------------------------------------------- |
239
+ | 使用分析 + JSON 清除/壓縮 | Node,位於 `withIntlayer()` 內 | 讀取每個元件原始檔,重寫 `.intlayer/**/*.json`,產生重新命名表 |
240
+ | 原始碼重寫(`content.title` → `.a`) | `@intlayer/swc`(Wasm) | 將重新命名表套用到你程式碼中對應的屬性存取 |
241
+ | 匯入重寫(`useIntlayer` → dict) | `@intlayer/swc`(Wasm) | 與 `intlayerOptimizeBabelPlugin` 相同 |
242
+
243
+ 判斷_哪些_欄位未被使用以及每個欄位取得_什麼_別名,需要跨檔案狀態與檔案 I/O,因此這一半在 Node 中執行;SWC 外掛只接收產生的表。
244
+
223
245
  ## 各平台設定指南
224
246
 
225
247
  <Tabs>
@@ -227,10 +249,12 @@ Vite 使用者**不需要直接對它們進行設定**。當您在 `vite.config.
227
249
 
228
250
  ### Next.js
229
251
 
230
- Next.js 需要依靠 `@intlayer/swc` 外掛程式來進行最佳化步驟(匯入重寫),因為 Next.js 採用 SWC 作為編譯器。
252
+ Next.js 需要 `@intlayer/swc` 外掛,因為 Next.js 使用 SWC 進行建置。自 **v9.2.1** 起,這一個套件即可涵蓋整條流水線 —— 最佳化(匯入重寫)、清除與壓縮。
231
253
 
232
254
  > 該外掛程式並未預設安裝,因為 SWC 外掛程式在 Next.js 當中目前仍處於實驗階段。未來這部分有可能會發生改變。
233
255
 
256
+ > **Next.js 16.1.0 是最低版本。** 它是首個基於 SWC 向前相容 Wasm 外掛 ABI 建置的版本;更早的版本會拒絕該外掛。`withIntlayer` 會讀取你專案的 Next.js 版本,低於 16.1.0 時乾脆不註冊該外掛 —— 這些建置仍會成功,只是在沒有打包最佳化的情況下執行。
257
+
234
258
  <Tabs>
235
259
  <Tab value="npm">
236
260
 
@@ -265,33 +289,40 @@ intlayer-swc-plugin = "*"
265
289
 
266
290
  安裝完畢後,Intlayer 將會自動偵測並使用該外掛程式。
267
291
 
268
- 至於**清除(purge)和最小化(minify)**步驟(即欄位移除和欄位重新命名),請連同 `@intlayer/babel` 一併安裝並加入 Babel 外掛程式。由於 Next.js 依靠 SWC 處理程式碼轉化,但仍會評估 `babel.config.js` 以決定外掛程式設定,因此上述 Babel 外掛程式能夠在進入 SWC 前作為預處理步驟得以執行。
292
+ **清除與壓縮**階段(欄位移除與欄位重新命名)不需要額外套件,也不需要 `babel.config.js`。用 `withIntlayer` 包住你的設定,並在 `intlayer.config.ts` 中開啟相應旗標:
269
293
 
270
- ```bash packageManager="npm"
271
- npm install -D @intlayer/babel
294
+ ```typescript fileName="next.config.ts"
295
+ import { withIntlayer } from "next-intlayer/server";
296
+ import type { NextConfig } from "next";
297
+
298
+ const nextConfig: NextConfig = {/* 你的設定 */};
299
+
300
+ export default withIntlayer(nextConfig);
272
301
  ```
273
302
 
274
- ```javascript fileName="babel.config.js"
275
- const {
276
- intlayerPurgeBabelPlugin,
277
- intlayerMinifyBabelPlugin,
278
- getPurgePluginOptions,
279
- getMinifyPluginOptions,
280
- } = require("@intlayer/babel");
303
+ ```typescript fileName="intlayer.config.ts"
304
+ import type { IntlayerConfig } from "intlayer";
281
305
 
282
- module.exports = {
283
- presets: ["next/babel"],
284
- plugins: [
285
- // Purge: 移除 .intlayer/**/*.json 裡未被使用的內容欄位
286
- [intlayerPurgeBabelPlugin, getPurgePluginOptions()],
287
- // Minify: 對 JSON 以及原始碼裡的內容欄位的鍵(keys)進行重新命名
288
- [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],
289
- // 注意: 在這裡不需要使用 intlayerOptimizeBabelPlugin,因為
290
- // @intlayer/swc 已經處理了 useIntlayer → useDictionary 的重寫過程。
291
- ],
306
+ const config: IntlayerConfig = {
307
+ build: {
308
+ purge: true, // 從打包的 JSON 中移除未使用的內容欄位
309
+ minify: true, // 將內容欄位鍵重新命名為簡短別名
310
+ },
292
311
  };
312
+
313
+ export default config;
293
314
  ```
294
315
 
316
+ 在 `next build` 期間,`withIntlayer` 會分析你的原始碼、重寫已編譯的字典,並將產生的欄位重新命名表交給 `@intlayer/swc`,由它更新你程式碼中對應的屬性存取。
317
+
318
+ > 請使用非同步的 `withIntlayer`,而不是 `withIntlayerSync`。同步版本不會執行分析流水線,因此清除與壓縮對它沒有效果。
319
+
320
+ > 清除與壓縮僅在 `next build` 時執行 —— 最佳化流水線在 `next dev` 期間是關閉的。
321
+
322
+ > 當設定了相容轉接器呼叫方時它們也會被停用(`swcExtraCallers`,由 `@intlayer/next-intl`、`@intlayer/react-i18next` 等相容套件設定):這些呼叫點對使用分析器不可見,因此清除會移除程式碼仍在讀取的欄位。匯入重寫仍保持啟用。
323
+
324
+ **更早的版本(9.2.1 之前)** 需要 `@intlayer/babel` 以及一個宣告 `intlayerPurgeBabelPlugin` 與 `intlayerMinifyBabelPlugin` 的 `babel.config.js`。該檔案不再需要,可以刪除。
325
+
295
326
  </Tab>
296
327
  <Tab value="vite">
297
328
 
@@ -447,6 +478,8 @@ export default config;
447
478
 
448
479
  > 如果在 `optimize` 被設為 `false`、又或是啟用了視覺化編輯器也就是當 `editor.enabled` 設定為了 `true` 時(由於編輯器需要保留欄位名稱以便做後續處理),重新命名這個操作都將會被跳過。
449
480
 
481
+ > 在 Next.js 上,當 `@intlayer/swc` 未安裝或無法載入時(Next.js 低於 16.1.0),壓縮同樣會被略過。重寫原始碼存取的正是這個外掛,因此在沒有它的情況下重新命名字典,會讓你的程式碼讀取已不存在的欄位名稱。
482
+
450
483
  > 同理,如果是利用了 `importMode: 'fetch'` 來載入欄位時此過程同樣不適用。因為它們的內容會以原始命名由後端 API 所提供,對客戶端內容隨意重新命名會破壞客戶端與伺服器端的匹配契約。
451
484
 
452
485
  ### 欄位清除 / Purging (去掉未被引用的欄位內容)
@@ -475,7 +508,7 @@ export default config;
475
508
  { "title": "…", "subtitle": "…" }
476
509
  ```
477
510
 
478
- > 與前邊提到的類似,當 `optimize` 是 `false` 或是開啟了視覺化編輯器(`editor.enabled` 取值 `true`) 時,它會選擇跳過對資料的清除操作。
511
+ > 與前邊提到的類似,當 `optimize` 是 `false` 或是開啟了視覺化編輯器(`editor.enabled` 取值 `true`) 時,它會選擇跳過對資料的清除操作。 在 Next.js 上,當 `@intlayer/swc` 不可用以及設定了相容轉接器呼叫方時,還會被額外略過。
479
512
 
480
513
  > 當檢測到某份程式碼因為異常無法順利解析、又或者當把由 `useIntlayer` 輸出的值以靜態解析器難以預測分析的模式在不同元件中來回丟(比如被打包成物件傳入等而未被進行解構)的時候,它同樣會跳過,以此保守地保留整部字典的全部資訊,避免意外發生。
481
514
 
@@ -188,7 +188,7 @@ export const ComponentExample = () => {
188
188
  這種方法允許你:
189
189
 
190
190
  1. **提高開發速度**
191
- - 可以使用 VSCode 插件創建 `.content.{{ts|mjs|cjs|json}}` 文件
191
+ - 可以使用 VSCode 插件創建 `.content.{ts|js|mjs|cjs|json|tsx|jsx|md|mdx|yaml|yml}` 文件
192
192
  - IDE 中的 AI 自動補全工具(例如 GitHub Copilot)可以幫助你宣告內容,減少複製/粘貼
193
193
 
194
194
  2. **保持代码庫的整潔**
@@ -212,27 +212,27 @@ export const ComponentExample = () => {
212
212
 
213
213
  ## Intlayer 附加功能
214
214
 
215
- | 功能 | 描述 |
216
- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
217
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true) | **跨框架支持**<br><br>Intlayer 兼容所有主流框架和庫,包括 Next.js, React, Vite, Vue.js, Nuxt, Preact, Express 等。 |
218
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.jpg?raw=true) | **JavaScript 驅動的內容管理**<br><br>利用 JavaScript 的靈活性來高效地定義和管理你的內容。<br><br> - [內容宣告](https://intlayer.org/doc/concept/content) |
219
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **編譯器**<br><br>Intlayer 編譯器可自動从組件中提取內容并生成字典文件。<br><br> - [編譯器](https://intlayer.org/doc/compiler) |
220
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true) | **單語言內容宣告文件**<br><br>在自動生成前,通過僅宣告一次你的內容來加速開發。<br><br> - [單語言內容宣告文件](https://intlayer.org/doc/concept/per-locale-file) |
221
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true) | **類型安全環境**<br><br>利用 TypeScript 確保你的內容定義和代码沒有錯誤,同時還能享受 IDE 的自動補全功能。<br><br> - [TypeScript 配置](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
222
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true) | **簡易配置**<br><br>僅需極簡的配置即可快速啟動并運行。輕鬆調整國際化、路由、AI、構築以及內容處理的設置。<br><br> - [探索 Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
223
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/content_retrieval.png?raw=true) | **簡化內容檢索**<br><br>無需為每一小段內容調用 `t` 函數。使用單一的 hook 即可直接檢索你的所有內容。<br><br> - [React 集成](https://intlayer.org/doc/environment/create-react-app) |
224
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/server_component.png?raw=true) | **一致的服務器組件實現**<br><br>完美適合 Next.js 服務器組件,客戶端和服務器組件使用相同的實現,無需跨每个服務器組件傳遞你的 `t` 函數。<br><br> - [服務器組件](https://intlayer.org/doc/environment/nextjs#step-7-utilize-content-in-your-code) |
225
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/file_tree.png?raw=true) | **有條理的代码庫**<br><br>保持代码庫更有條理:1 組件 = 在同一文件夾下的 1 個字典。靠近其各自組件的翻譯有助於提高可維護性和清晰度。<br><br> - [Intlayer 運行機制](https://intlayer.org/doc/concept/how-works-intlayer) |
226
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/url_routing.png?raw=true) | **增強的路由功能**<br><br>完全支持應用路由,無縫適應複雜的應用結構,適用於 Next.js, React, Vite, Vue.js 等。<br><br> - [探索 Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
227
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/markdown.png?raw=true) | **Markdown 支持**<br><br>導入并解譯本地文件以及遠程 Markdown,以獲得隱私政策、文檔等多語言內容。解譯并在你的代码中使 Markdown 元數據可被訪問。<br><br> - [內容文件](https://intlayer.org/doc/concept/content/file) |
228
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/visual_editor.png?raw=true) | **免費的可視化編輯器與 CMS**<br><br>視覺編輯器和 CMS 免費向內容創作者開放,消除对第三方本地化平台的依賴。使用 Git 保持內容同步,或使用 CMS 徹底或部分外置管理它。<br><br> - [Intlayer 編輯器](https://intlayer.org/doc/concept/editor) <br> - [Intlayer CMS](https://intlayer.org/doc/concept/cms) |
229
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) | **構築時搖樹優化 (Tree-shakable) 內容**<br><br>構築時搖樹優化內容,減小最終包的體積。按組件載入內容,并从打包體積中排除 any 未使用的內容。支持懶載入以提高應用載入效率。<br><br> - [應用構築優化](https://intlayer.org/doc/concept/how-works-intlayer#app-build-optimization) |
230
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/static_rendering.png?raw=true) | **靜態渲染**<br><br>不阻碍靜態渲染(Static Rendering)。<br><br> - [Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
231
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/AI_translation.png?raw=true) | **AI 驅動翻譯**<br><br>使用您自己的 AI 提供商/API 金鑰,點擊一下即可將您的網站翻譯成 231 種語言,得益於 Intlayer 先進的 AI 翻譯工具。<br><br> - [CI/CD 集成](https://intlayer.org/doc/concept/ci-cd) <br> - [Intlayer CLI](https://intlayer.org/doc/concept/cli) <br> - [自動填充](https://intlayer.org/doc/concept/auto-fill) |
232
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true) | **MCP 服務器集成**<br><br>提供用於 IDE 自動化的 MCP(Model Context Protocol)服務器,能夠直接在你的開發環境中實現無縫的內容管理和 i18n 工作流。<br><br> - [MCP 服務器](https://github.com/aymericzip/intlayer/blob/main/docs/zh-TW/mcp_server.md) |
233
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true) | **VSCode 插件**<br><br>Intlayer 提供 VSCode 插件協助管理你的內容和翻譯,構築你的字典,翻譯你的內容等。<br><br> - [VSCode 插件](https://intlayer.org/doc/vs-code-extension) |
234
- | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true) | **互操作性**<br><br>允許與 react-i18next、next-i18next, next-intl 和 react-intl 互操作。<br><br> - [Intlayer 與 react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer 與 next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer 與 next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) |
235
- | 測試缺失翻譯 (CLI/CI) | ✅ CLI: npx intlayer content test (对 CI 友好的審計) |
215
+ | 功能 | 描述 |
216
+ | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
217
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true) | **跨框架支持**<br><br>Intlayer 兼容所有主流框架和庫,包括 Next.js, React, Vite, Vue.js, Nuxt, Preact, Express 等。 |
218
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.jpg?raw=true) | **JavaScript 驅動的內容管理**<br><br>利用 JavaScript 的靈活性來高效地定義和管理你的內容。<br><br> - [內容宣告](https://intlayer.org/doc/concept/content) |
219
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **編譯器**<br><br>Intlayer 編譯器可自動从組件中提取內容并生成字典文件。<br><br> - [編譯器](https://intlayer.org/doc/compiler) |
220
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true) | **單語言內容宣告文件**<br><br>在自動生成前,通過僅宣告一次你的內容來加速開發。<br><br> - [單語言內容宣告文件](https://intlayer.org/doc/concept/per-locale-file) |
221
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true) | **類型安全環境**<br><br>利用 TypeScript 確保你的內容定義和代码沒有錯誤,同時還能享受 IDE 的自動補全功能。<br><br> - [TypeScript 配置](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
222
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true) | **簡易配置**<br><br>僅需極簡的配置即可快速啟動并運行。輕鬆調整國際化、路由、AI、構築以及內容處理的設置。<br><br> - [探索 Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
223
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/content_retrieval.png?raw=true) | **簡化內容檢索**<br><br>無需為每一小段內容調用 `t` 函數。使用單一的 hook 即可直接檢索你的所有內容。<br><br> - [React 集成](https://intlayer.org/doc/environment/create-react-app) |
224
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/server_component.png?raw=true) | **一致的服務器組件實現**<br><br>完美適合 Next.js 服務器組件,客戶端和服務器組件使用相同的實現,無需跨每个服務器組件傳遞你的 `t` 函數。<br><br> - [服務器組件](https://intlayer.org/doc/environment/nextjs#step-7-utilize-content-in-your-code) |
225
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/file_tree.png?raw=true) | **有條理的代码庫**<br><br>保持代码庫更有條理:1 組件 = 在同一文件夾下的 1 個字典。靠近其各自組件的翻譯有助於提高可維護性和清晰度。<br><br> - [Intlayer 運行機制](https://intlayer.org/doc/concept/how-works-intlayer) |
226
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/url_routing.png?raw=true) | **增強的路由功能**<br><br>完全支持應用路由,無縫適應複雜的應用結構,適用於 Next.js, React, Vite, Vue.js 等。<br><br> - [探索 Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
227
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/markdown.png?raw=true) | **Markdown 支持**<br><br>導入并解譯本地文件以及遠程 Markdown,以獲得隱私政策、文檔等多語言內容。解譯并在你的代码中使 Markdown 元數據可被訪問。<br><br> - [內容文件](https://intlayer.org/doc/concept/content/file) |
228
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/visual_editor.png?raw=true) | **免費的可視化編輯器與 CMS**<br><br>視覺編輯器和 CMS 免費向內容創作者開放,消除对第三方本地化平台的依賴。使用 Git 保持內容同步,或使用 CMS 徹底或部分外置管理它。<br><br> - [Intlayer 編輯器](https://intlayer.org/doc/concept/editor) <br> - [Intlayer CMS](https://intlayer.org/doc/concept/cms) |
229
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) | **構築時搖樹優化 (Tree-shakable) 內容**<br><br>構築時搖樹優化內容,減小最終包的體積。按組件載入內容,并从打包體積中排除 any 未使用的內容。支持懶載入以提高應用載入效率。<br><br> - [應用構築優化](https://intlayer.org/doc/concept/how-works-intlayer#app-build-optimization) |
230
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/static_rendering.png?raw=true) | **靜態渲染**<br><br>不阻碍靜態渲染(Static Rendering)。<br><br> - [Next.js 集成](https://intlayer.org/doc/environment/nextjs) |
231
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/AI_translation.png?raw=true) | **AI 驅動翻譯**<br><br>使用您自己的 AI 提供商/API 金鑰,點擊一下即可將您的網站翻譯成 231 種語言,得益於 Intlayer 先進的 AI 翻譯工具。<br><br> - [CI/CD 集成](https://intlayer.org/doc/concept/ci-cd) <br> - [Intlayer CLI](https://intlayer.org/doc/concept/cli) <br> - [自動填充](https://intlayer.org/doc/concept/auto-fill) |
232
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true) | **MCP 服務器集成**<br><br>提供用於 IDE 自動化的 MCP(Model Context Protocol)服務器,能夠直接在你的開發環境中實現無縫的內容管理和 i18n 工作流。<br><br> - [MCP 服務器](https://github.com/aymericzip/intlayer/blob/main/docs/zh-TW/mcp_server.md) |
233
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true) | **VSCode 插件**<br><br>Intlayer 提供 VSCode 插件協助管理你的內容和翻譯,構築你的字典,翻譯你的內容等。<br><br> - [VSCode 插件](https://intlayer.org/doc/vs-code-extension) |
234
+ | ![Feature](https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true) | **互操作性**<br><br>允許與 react-i18next、next-i18next, next-intl 和 react-intl 互操作。<br><br> - [Intlayer 與 react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer 與 next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer 與 next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) <br> - [Intlayer 相容轉接器](https://intlayer.org/doc/compatibility) |
235
+ | 測試缺失翻譯 (CLI/CI) | ✅ CLI: npx intlayer content test (对 CI 友好的審計) |
236
236
 
237
237
  ## Intlayer 與其他解決方案 of 比較
238
238
 
@@ -271,3 +271,5 @@ GitHub 星星數是衡量項目受歡迎程度、社區信任度以及長期相
271
271
  `intlayer` 還可以幫助管理你的 `react-intl`、`react-i18next`、`next-intl`、`next-i18next` 以及 `vue-i18n` 命名空間。
272
272
 
273
273
  使用 `intlayer`,你可以宣告你喜歡的 i18n 庫格式的內容,並且 intlayer 將在你想指定的路徑下生成命名空間(例如:`/messages/{{locale}}/{{namespace}}.json`)。
274
+
275
+ 如果你想繼續使用目前 i18n 函式庫的 API,`intlayer` 也提供 **相容轉接器(compat adapters)**:這些套件公開與 `react-i18next`、`next-intl`、`react-intl`、`vue-i18n` 等完全相同的 API,但內容由 Intlayer 字典提供。如此一來,你就能逐步遷移,而不需要重寫程式碼。請參閱[相容轉接器文件](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/compat/index.md)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/docs",
3
- "version": "9.2.0",
3
+ "version": "9.3.1",
4
4
  "private": false,
5
5
  "description": "Intlayer documentation",
6
6
  "keywords": [
@@ -73,14 +73,14 @@
73
73
  "watch": "webpack --config ./webpack.config.ts --watch"
74
74
  },
75
75
  "dependencies": {
76
- "@intlayer/config": "9.2.0",
77
- "@intlayer/core": "9.2.0",
78
- "@intlayer/types": "9.2.0"
76
+ "@intlayer/config": "9.3.1",
77
+ "@intlayer/core": "9.3.1",
78
+ "@intlayer/types": "9.3.1"
79
79
  },
80
80
  "devDependencies": {
81
- "@intlayer/api": "9.2.0",
82
- "@intlayer/cli": "9.2.0",
83
- "@types/node": "26.1.2",
81
+ "@intlayer/api": "9.3.1",
82
+ "@intlayer/cli": "9.3.1",
83
+ "@types/node": "26.2.0",
84
84
  "@utils/ts-config": "1.0.4",
85
85
  "@utils/ts-config-types": "1.0.4",
86
86
  "@utils/tsdown-config": "1.0.4",
package/src/common.ts CHANGED
@@ -131,11 +131,25 @@ export const getFileMetadata = async <
131
131
  return formatMetadata(docKey as string, file, locale) as R;
132
132
  };
133
133
 
134
- export const getFileMetadataRecord = async <
134
+ /**
135
+ * Per-locale metadata records, keyed by the entry map they were built from.
136
+ *
137
+ * Building a record parses the frontmatter of every document of a category, and
138
+ * callers such as {@link getFileMetadataBySlug} need the whole record just to
139
+ * resolve a single slug — which a static site generator repeats for every page
140
+ * it renders. Documents are read once per process anyway (each entry caches its
141
+ * own read), so the derived metadata is equally safe to keep.
142
+ */
143
+ const fileMetadataRecordCache = new WeakMap<
144
+ Record<string, unknown>,
145
+ Map<LocalesValues, Promise<Record<string, FileMetadata>>>
146
+ >();
147
+
148
+ const buildFileMetadataRecord = async <
135
149
  F extends Record<string, Record<LocalesValues, Promise<string>>>,
136
150
  >(
137
151
  files: F,
138
- locale: LocalesValues = defaultLocale as LocalesValues
152
+ locale: LocalesValues
139
153
  ): Promise<Record<keyof F, FileMetadata>> => {
140
154
  const results = await Promise.allSettled(
141
155
  Object.entries(files).map(async ([key]) => {
@@ -153,6 +167,29 @@ export const getFileMetadataRecord = async <
153
167
  return filesResult as Record<keyof F, FileMetadata>;
154
168
  };
155
169
 
170
+ export const getFileMetadataRecord = async <
171
+ F extends Record<string, Record<LocalesValues, Promise<string>>>,
172
+ >(
173
+ files: F,
174
+ locale: LocalesValues = defaultLocale as LocalesValues
175
+ ): Promise<Record<keyof F, FileMetadata>> => {
176
+ let localeCache = fileMetadataRecordCache.get(files);
177
+
178
+ if (!localeCache) {
179
+ localeCache = new Map();
180
+ fileMetadataRecordCache.set(files, localeCache);
181
+ }
182
+
183
+ let record = localeCache.get(locale);
184
+
185
+ if (!record) {
186
+ record = buildFileMetadataRecord(files, locale);
187
+ localeCache.set(locale, record);
188
+ }
189
+
190
+ return (await record) as Record<keyof F, FileMetadata>;
191
+ };
192
+
156
193
  export const getFileMetadataBySlug = async <
157
194
  F extends Record<string, Record<LocalesValues, Promise<string>>>,
158
195
  >(
@@ -32,26 +32,58 @@ try {
32
32
  }
33
33
  }
34
34
 
35
- const readLocale = (
35
+ /**
36
+ * Reads a document, preferring the requested locale and falling back to English.
37
+ */
38
+ const readLocaleFile = async (
36
39
  relativeAfterLocale: string,
37
40
  locale: LocalesValues
38
41
  ): Promise<string> => {
39
42
  const target1 = join(baseDir, `./blog/${locale}/${relativeAfterLocale}`);
40
43
  if (existsSync(target1)) {
41
- return readFile(target1, 'utf8');
44
+ return await readFile(target1, 'utf8');
42
45
  }
43
46
  const target2 = join(baseDir, `./blog/en/${relativeAfterLocale}`);
44
47
  if (existsSync(target2)) {
45
- return readFile(target2, 'utf8');
48
+ return await readFile(target2, 'utf8');
46
49
  }
47
50
 
48
- return Promise.reject(
49
- new Error(
50
- `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
51
- )
51
+ throw new Error(
52
+ `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
52
53
  );
53
54
  };
54
55
 
56
+ /**
57
+ * Builds a lazy, awaitable handle over a document.
58
+ *
59
+ * The entry map below holds one handle per document *per locale*, so reading
60
+ * eagerly would pull every markdown file of every locale into memory as soon as
61
+ * this module is imported — hundreds of megabytes, duplicated in every
62
+ * prerender worker, for the handful of documents a page actually renders.
63
+ *
64
+ * The returned value is a thenable rather than a promise: consumers only ever
65
+ * `await` it, and `await` triggers `then`, so the file is read on first use and
66
+ * the resulting promise is cached for subsequent reads.
67
+ */
68
+ const readLocale = (
69
+ relativeAfterLocale: string,
70
+ locale: LocalesValues
71
+ ): Promise<string> => {
72
+ let pendingRead: Promise<string> | undefined;
73
+
74
+ const read = (): Promise<string> => {
75
+ pendingRead ??= readLocaleFile(relativeAfterLocale, locale);
76
+ return pendingRead;
77
+ };
78
+
79
+ return {
80
+ // biome-ignore lint/suspicious/noThenProperty: the thenable is intentional — `await` is what triggers the lazy read.
81
+ then: (onFulfilled, onRejected) => read().then(onFulfilled, onRejected),
82
+ catch: (onRejected) => read().catch(onRejected),
83
+ finally: (onFinally) => read().finally(onFinally),
84
+ } as Promise<string>;
85
+ };
86
+
55
87
  export const blogEntry = {
56
88
  './blog/en/compiler_vs_declarative_i18n.md': {
57
89
  en: readLocale('compiler_vs_declarative_i18n.md', 'en'),
@@ -32,26 +32,58 @@ try {
32
32
  }
33
33
  }
34
34
 
35
- const readLocale = (
35
+ /**
36
+ * Reads a document, preferring the requested locale and falling back to English.
37
+ */
38
+ const readLocaleFile = async (
36
39
  relativeAfterLocale: string,
37
40
  locale: LocalesValues
38
41
  ): Promise<string> => {
39
42
  const target1 = join(baseDir, `./docs/${locale}/${relativeAfterLocale}`);
40
43
  if (existsSync(target1)) {
41
- return readFile(target1, 'utf8');
44
+ return await readFile(target1, 'utf8');
42
45
  }
43
46
  const target2 = join(baseDir, `./docs/en/${relativeAfterLocale}`);
44
47
  if (existsSync(target2)) {
45
- return readFile(target2, 'utf8');
48
+ return await readFile(target2, 'utf8');
46
49
  }
47
50
 
48
- return Promise.reject(
49
- new Error(
50
- `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
51
- )
51
+ throw new Error(
52
+ `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
52
53
  );
53
54
  };
54
55
 
56
+ /**
57
+ * Builds a lazy, awaitable handle over a document.
58
+ *
59
+ * The entry map below holds one handle per document *per locale*, so reading
60
+ * eagerly would pull every markdown file of every locale into memory as soon as
61
+ * this module is imported — hundreds of megabytes, duplicated in every
62
+ * prerender worker, for the handful of documents a page actually renders.
63
+ *
64
+ * The returned value is a thenable rather than a promise: consumers only ever
65
+ * `await` it, and `await` triggers `then`, so the file is read on first use and
66
+ * the resulting promise is cached for subsequent reads.
67
+ */
68
+ const readLocale = (
69
+ relativeAfterLocale: string,
70
+ locale: LocalesValues
71
+ ): Promise<string> => {
72
+ let pendingRead: Promise<string> | undefined;
73
+
74
+ const read = (): Promise<string> => {
75
+ pendingRead ??= readLocaleFile(relativeAfterLocale, locale);
76
+ return pendingRead;
77
+ };
78
+
79
+ return {
80
+ // biome-ignore lint/suspicious/noThenProperty: the thenable is intentional — `await` is what triggers the lazy read.
81
+ then: (onFulfilled, onRejected) => read().then(onFulfilled, onRejected),
82
+ catch: (onRejected) => read().catch(onRejected),
83
+ finally: (onFinally) => read().finally(onFinally),
84
+ } as Promise<string>;
85
+ };
86
+
55
87
  export const docsEntry = {
56
88
  './docs/en/CI_CD.md': {
57
89
  en: readLocale('CI_CD.md', 'en'),
@@ -32,7 +32,10 @@ try {
32
32
  }
33
33
  }
34
34
 
35
- const readLocale = (
35
+ /**
36
+ * Reads a document, preferring the requested locale and falling back to English.
37
+ */
38
+ const readLocaleFile = async (
36
39
  relativeAfterLocale: string,
37
40
  locale: LocalesValues
38
41
  ): Promise<string> => {
@@ -41,23 +44,52 @@ const readLocale = (
41
44
  `./frequent_questions/${locale}/${relativeAfterLocale}`
42
45
  );
43
46
  if (existsSync(target1)) {
44
- return readFile(target1, 'utf8');
47
+ return await readFile(target1, 'utf8');
45
48
  }
46
49
  const target2 = join(
47
50
  baseDir,
48
51
  `./frequent_questions/en/${relativeAfterLocale}`
49
52
  );
50
53
  if (existsSync(target2)) {
51
- return readFile(target2, 'utf8');
54
+ return await readFile(target2, 'utf8');
52
55
  }
53
56
 
54
- return Promise.reject(
55
- new Error(
56
- `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
57
- )
57
+ throw new Error(
58
+ `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
58
59
  );
59
60
  };
60
61
 
62
+ /**
63
+ * Builds a lazy, awaitable handle over a document.
64
+ *
65
+ * The entry map below holds one handle per document *per locale*, so reading
66
+ * eagerly would pull every markdown file of every locale into memory as soon as
67
+ * this module is imported — hundreds of megabytes, duplicated in every
68
+ * prerender worker, for the handful of documents a page actually renders.
69
+ *
70
+ * The returned value is a thenable rather than a promise: consumers only ever
71
+ * `await` it, and `await` triggers `then`, so the file is read on first use and
72
+ * the resulting promise is cached for subsequent reads.
73
+ */
74
+ const readLocale = (
75
+ relativeAfterLocale: string,
76
+ locale: LocalesValues
77
+ ): Promise<string> => {
78
+ let pendingRead: Promise<string> | undefined;
79
+
80
+ const read = (): Promise<string> => {
81
+ pendingRead ??= readLocaleFile(relativeAfterLocale, locale);
82
+ return pendingRead;
83
+ };
84
+
85
+ return {
86
+ // biome-ignore lint/suspicious/noThenProperty: the thenable is intentional — `await` is what triggers the lazy read.
87
+ then: (onFulfilled, onRejected) => read().then(onFulfilled, onRejected),
88
+ catch: (onRejected) => read().catch(onRejected),
89
+ finally: (onFinally) => read().finally(onFinally),
90
+ } as Promise<string>;
91
+ };
92
+
61
93
  export const frequentQuestionsEntry = {
62
94
  './frequent_questions/en/SSR_Next_no_[locale].md': {
63
95
  en: readLocale('SSR_Next_no_[locale].md', 'en'),
@@ -32,26 +32,58 @@ try {
32
32
  }
33
33
  }
34
34
 
35
- const readLocale = (
35
+ /**
36
+ * Reads a document, preferring the requested locale and falling back to English.
37
+ */
38
+ const readLocaleFile = async (
36
39
  relativeAfterLocale: string,
37
40
  locale: LocalesValues
38
41
  ): Promise<string> => {
39
42
  const target1 = join(baseDir, `./legal/${locale}/${relativeAfterLocale}`);
40
43
  if (existsSync(target1)) {
41
- return readFile(target1, 'utf8');
44
+ return await readFile(target1, 'utf8');
42
45
  }
43
46
  const target2 = join(baseDir, `./legal/en/${relativeAfterLocale}`);
44
47
  if (existsSync(target2)) {
45
- return readFile(target2, 'utf8');
48
+ return await readFile(target2, 'utf8');
46
49
  }
47
50
 
48
- return Promise.reject(
49
- new Error(
50
- `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
51
- )
51
+ throw new Error(
52
+ `[docs] File not found: ${relativeAfterLocale} - locale: ${locale} - path: ${target1} - path: ${target2}`
52
53
  );
53
54
  };
54
55
 
56
+ /**
57
+ * Builds a lazy, awaitable handle over a document.
58
+ *
59
+ * The entry map below holds one handle per document *per locale*, so reading
60
+ * eagerly would pull every markdown file of every locale into memory as soon as
61
+ * this module is imported — hundreds of megabytes, duplicated in every
62
+ * prerender worker, for the handful of documents a page actually renders.
63
+ *
64
+ * The returned value is a thenable rather than a promise: consumers only ever
65
+ * `await` it, and `await` triggers `then`, so the file is read on first use and
66
+ * the resulting promise is cached for subsequent reads.
67
+ */
68
+ const readLocale = (
69
+ relativeAfterLocale: string,
70
+ locale: LocalesValues
71
+ ): Promise<string> => {
72
+ let pendingRead: Promise<string> | undefined;
73
+
74
+ const read = (): Promise<string> => {
75
+ pendingRead ??= readLocaleFile(relativeAfterLocale, locale);
76
+ return pendingRead;
77
+ };
78
+
79
+ return {
80
+ // biome-ignore lint/suspicious/noThenProperty: the thenable is intentional — `await` is what triggers the lazy read.
81
+ then: (onFulfilled, onRejected) => read().then(onFulfilled, onRejected),
82
+ catch: (onRejected) => read().catch(onRejected),
83
+ finally: (onFinally) => read().finally(onFinally),
84
+ } as Promise<string>;
85
+ };
86
+
55
87
  export const legalEntry = {
56
88
  './legal/en/privacy_notice.md': {
57
89
  en: readLocale('privacy_notice.md', 'en'),