@intlayer/docs 9.5.7 → 9.5.9-canary.0

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 (162) hide show
  1. package/blog/ar/is_intlayer_lighter_than_paraglide.md +284 -0
  2. package/blog/de/is_intlayer_lighter_than_paraglide.md +284 -0
  3. package/blog/en/is_intlayer_lighter_than_paraglide.md +288 -0
  4. package/blog/en-GB/is_intlayer_lighter_than_paraglide.md +285 -0
  5. package/blog/es/is_intlayer_lighter_than_paraglide.md +284 -0
  6. package/blog/fr/is_intlayer_lighter_than_paraglide.md +284 -0
  7. package/blog/hi/is_intlayer_lighter_than_paraglide.md +284 -0
  8. package/blog/id/is_intlayer_lighter_than_paraglide.md +284 -0
  9. package/blog/it/is_intlayer_lighter_than_paraglide.md +284 -0
  10. package/blog/ja/is_intlayer_lighter_than_paraglide.md +284 -0
  11. package/blog/ko/is_intlayer_lighter_than_paraglide.md +284 -0
  12. package/blog/pl/is_intlayer_lighter_than_paraglide.md +284 -0
  13. package/blog/pt/is_intlayer_lighter_than_paraglide.md +284 -0
  14. package/blog/ru/is_intlayer_lighter_than_paraglide.md +284 -0
  15. package/blog/tr/is_intlayer_lighter_than_paraglide.md +284 -0
  16. package/blog/uk/is_intlayer_lighter_than_paraglide.md +284 -0
  17. package/blog/vi/is_intlayer_lighter_than_paraglide.md +284 -0
  18. package/blog/zh/is_intlayer_lighter_than_paraglide.md +284 -0
  19. package/dist/cjs/generated/blog.entry.cjs +20 -0
  20. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  21. package/dist/cjs/generated/docs.entry.cjs +20 -0
  22. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  23. package/dist/cjs/index.cjs +2 -2
  24. package/dist/esm/generated/blog.entry.mjs +20 -0
  25. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  26. package/dist/esm/generated/docs.entry.mjs +20 -0
  27. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  28. package/dist/esm/index.mjs +2 -2
  29. package/dist/types/generated/blog.entry.d.ts +1 -0
  30. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  31. package/dist/types/generated/docs.entry.d.ts +1 -0
  32. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  33. package/docs/ar/benchmark/nextjs.md +6 -3
  34. package/docs/ar/benchmark/solid.md +21 -4
  35. package/docs/ar/benchmark/svelte.md +18 -3
  36. package/docs/ar/benchmark/tanstack.md +6 -3
  37. package/docs/ar/benchmark/vue.md +14 -5
  38. package/docs/ar/cli/index.md +6 -1
  39. package/docs/ar/cli/upgrade.md +125 -0
  40. package/docs/de/benchmark/nextjs.md +6 -3
  41. package/docs/de/benchmark/solid.md +21 -4
  42. package/docs/de/benchmark/svelte.md +18 -3
  43. package/docs/de/benchmark/tanstack.md +6 -3
  44. package/docs/de/benchmark/vue.md +14 -5
  45. package/docs/de/cli/index.md +6 -1
  46. package/docs/de/cli/upgrade.md +125 -0
  47. package/docs/en/agent_skills.md +29 -1
  48. package/docs/en/benchmark/nextjs.md +6 -3
  49. package/docs/en/benchmark/solid.md +21 -4
  50. package/docs/en/benchmark/svelte.md +18 -3
  51. package/docs/en/benchmark/tanstack.md +7 -4
  52. package/docs/en/benchmark/vue.md +15 -6
  53. package/docs/en/cli/index.md +6 -1
  54. package/docs/en/cli/upgrade.md +125 -0
  55. package/docs/en-GB/benchmark/nextjs.md +6 -3
  56. package/docs/en-GB/benchmark/solid.md +21 -4
  57. package/docs/en-GB/benchmark/svelte.md +18 -3
  58. package/docs/en-GB/benchmark/tanstack.md +6 -3
  59. package/docs/en-GB/benchmark/vue.md +15 -6
  60. package/docs/en-GB/cli/index.md +6 -1
  61. package/docs/en-GB/cli/upgrade.md +125 -0
  62. package/docs/es/benchmark/nextjs.md +6 -3
  63. package/docs/es/benchmark/solid.md +21 -4
  64. package/docs/es/benchmark/svelte.md +18 -3
  65. package/docs/es/benchmark/tanstack.md +6 -3
  66. package/docs/es/benchmark/vue.md +14 -5
  67. package/docs/es/cli/index.md +6 -1
  68. package/docs/es/cli/upgrade.md +125 -0
  69. package/docs/fr/benchmark/nextjs.md +6 -3
  70. package/docs/fr/benchmark/solid.md +21 -4
  71. package/docs/fr/benchmark/svelte.md +18 -3
  72. package/docs/fr/benchmark/tanstack.md +6 -3
  73. package/docs/fr/benchmark/vue.md +15 -6
  74. package/docs/fr/cli/index.md +6 -1
  75. package/docs/fr/cli/upgrade.md +125 -0
  76. package/docs/hi/benchmark/nextjs.md +6 -3
  77. package/docs/hi/benchmark/solid.md +21 -4
  78. package/docs/hi/benchmark/svelte.md +18 -3
  79. package/docs/hi/benchmark/tanstack.md +6 -3
  80. package/docs/hi/benchmark/vue.md +15 -6
  81. package/docs/hi/cli/index.md +6 -1
  82. package/docs/hi/cli/upgrade.md +125 -0
  83. package/docs/id/benchmark/nextjs.md +6 -3
  84. package/docs/id/benchmark/solid.md +21 -4
  85. package/docs/id/benchmark/svelte.md +18 -3
  86. package/docs/id/benchmark/tanstack.md +6 -3
  87. package/docs/id/benchmark/vue.md +15 -6
  88. package/docs/id/cli/index.md +6 -1
  89. package/docs/id/cli/upgrade.md +125 -0
  90. package/docs/it/benchmark/nextjs.md +6 -3
  91. package/docs/it/benchmark/solid.md +21 -4
  92. package/docs/it/benchmark/svelte.md +18 -3
  93. package/docs/it/benchmark/tanstack.md +6 -3
  94. package/docs/it/benchmark/vue.md +14 -5
  95. package/docs/it/cli/index.md +6 -1
  96. package/docs/it/cli/upgrade.md +125 -0
  97. package/docs/ja/benchmark/nextjs.md +6 -3
  98. package/docs/ja/benchmark/solid.md +21 -4
  99. package/docs/ja/benchmark/svelte.md +18 -3
  100. package/docs/ja/benchmark/tanstack.md +5 -2
  101. package/docs/ja/benchmark/vue.md +14 -5
  102. package/docs/ja/cli/index.md +6 -1
  103. package/docs/ja/cli/upgrade.md +125 -0
  104. package/docs/ko/benchmark/nextjs.md +6 -3
  105. package/docs/ko/benchmark/solid.md +21 -4
  106. package/docs/ko/benchmark/svelte.md +18 -3
  107. package/docs/ko/benchmark/tanstack.md +6 -3
  108. package/docs/ko/benchmark/vue.md +15 -6
  109. package/docs/ko/cli/index.md +6 -1
  110. package/docs/ko/cli/upgrade.md +126 -0
  111. package/docs/pl/benchmark/nextjs.md +6 -3
  112. package/docs/pl/benchmark/solid.md +21 -4
  113. package/docs/pl/benchmark/svelte.md +18 -3
  114. package/docs/pl/benchmark/tanstack.md +6 -3
  115. package/docs/pl/benchmark/vue.md +15 -6
  116. package/docs/pl/cli/index.md +6 -1
  117. package/docs/pl/cli/upgrade.md +125 -0
  118. package/docs/pt/benchmark/nextjs.md +6 -3
  119. package/docs/pt/benchmark/solid.md +21 -4
  120. package/docs/pt/benchmark/svelte.md +18 -3
  121. package/docs/pt/benchmark/tanstack.md +6 -3
  122. package/docs/pt/benchmark/vue.md +14 -5
  123. package/docs/pt/cli/index.md +6 -1
  124. package/docs/pt/cli/upgrade.md +125 -0
  125. package/docs/ru/benchmark/nextjs.md +6 -3
  126. package/docs/ru/benchmark/solid.md +21 -4
  127. package/docs/ru/benchmark/svelte.md +18 -3
  128. package/docs/ru/benchmark/tanstack.md +6 -3
  129. package/docs/ru/benchmark/vue.md +15 -6
  130. package/docs/ru/cli/index.md +6 -1
  131. package/docs/ru/cli/upgrade.md +125 -0
  132. package/docs/tr/benchmark/nextjs.md +6 -3
  133. package/docs/tr/benchmark/solid.md +21 -4
  134. package/docs/tr/benchmark/svelte.md +18 -3
  135. package/docs/tr/benchmark/tanstack.md +6 -3
  136. package/docs/tr/benchmark/vue.md +15 -6
  137. package/docs/tr/cli/index.md +6 -1
  138. package/docs/tr/cli/upgrade.md +125 -0
  139. package/docs/uk/benchmark/nextjs.md +6 -3
  140. package/docs/uk/benchmark/solid.md +21 -4
  141. package/docs/uk/benchmark/svelte.md +18 -3
  142. package/docs/uk/benchmark/tanstack.md +6 -3
  143. package/docs/uk/benchmark/vue.md +15 -6
  144. package/docs/uk/cli/index.md +6 -1
  145. package/docs/uk/cli/upgrade.md +125 -0
  146. package/docs/vi/benchmark/nextjs.md +6 -3
  147. package/docs/vi/benchmark/solid.md +21 -4
  148. package/docs/vi/benchmark/svelte.md +18 -3
  149. package/docs/vi/benchmark/tanstack.md +6 -3
  150. package/docs/vi/benchmark/vue.md +15 -6
  151. package/docs/vi/cli/index.md +6 -1
  152. package/docs/vi/cli/upgrade.md +125 -0
  153. package/docs/zh/benchmark/nextjs.md +6 -3
  154. package/docs/zh/benchmark/solid.md +21 -4
  155. package/docs/zh/benchmark/svelte.md +18 -3
  156. package/docs/zh/benchmark/tanstack.md +5 -2
  157. package/docs/zh/benchmark/vue.md +15 -6
  158. package/docs/zh/cli/index.md +6 -1
  159. package/docs/zh/cli/upgrade.md +125 -0
  160. package/package.json +6 -6
  161. package/src/generated/blog.entry.ts +20 -0
  162. package/src/generated/docs.entry.ts +20 -0
@@ -0,0 +1,284 @@
1
+ ---
2
+ createdAt: 2026-09-23
3
+ updatedAt: 2026-09-23
4
+ title: Intlayer có nhẹ hơn Paraglide không?
5
+ description: Paraglide trông gần như miễn phí trong các bài kiểm chuẩn i18n vì mã nguồn của nó được tạo trực tiếp vào kho lưu trữ của bạn. Cùng tìm hiểu xem dung lượng đó thực sự đi đâu, tại sao việc đọc locale trên từng nút lại gây tốn tài nguyên và cách tải động của Intlayer chỉ gửi một ngôn ngữ thay vì tất cả.
6
+ keywords:
7
+ - Paraglide
8
+ - Intlayer
9
+ - Quốc tế hóa
10
+ - i18n
11
+ - Bundle size
12
+ - Tree shaking
13
+ - Benchmark
14
+ - Blog
15
+ slugs:
16
+ - blog
17
+ - is-intlayer-lighter-than-paraglide
18
+ author: aymericzip
19
+ ---
20
+
21
+ # Intlayer có nhẹ hơn Paraglide không?
22
+
23
+ Có.
24
+
25
+ `Paraglide` nổi tiếng là giải pháp i18n nhẹ nhất hiện nay, và thoạt nhìn bảng [benchmark](https://intlayer.org/vi/doc/benchmark/tanstack) cũng đồng tình với điều đó: kích thước thư viện của nó gần như bằng không. Tuy nhiên, kích thước thư viện bằng không không đồng nghĩa với việc không có byte nào được gửi đến trình duyệt. Nó chỉ có nghĩa là các byte đó nằm ở một nơi mà chỉ số này không quét tới.
26
+
27
+ <TOC/>
28
+
29
+ ## Những điểm cốt lõi
30
+
31
+ **Kích thước thư viện bị ẩn đi chứ không hề biến mất:**
32
+
33
+ Paraglide tạo runtime và các hàm thông điệp ngay trong codebase của bạn. Đoạn mã đó vẫn được gửi tới trình duyệt, nhưng được tính là mã của _chính bạn_, chứ không phải của thư viện.
34
+
35
+ **Không có provider không phải là một món hời miễn phí:**
36
+
37
+ Mỗi lần gọi `m.my_key()` đều tự giải quyết locale độc lập, đọc cookie hoặc bộ nhớ lưu trữ cho từng node được render, thay vì đọc một lần duy nhất từ context.
38
+
39
+ **Không có cơ chế tải động (Dynamic Loading):**
40
+
41
+ Paraglide import tất cả ngôn ngữ của một thông điệp vào client bundle. Ngược lại, Intlayer với `importMode: 'dynamic'` hoặc `'fetch'` chỉ tải duy nhất ngôn ngữ đang được hiển thị.
42
+
43
+ **Tree shaking không được đảm bảo:**
44
+
45
+ Trong một số bài kiểm tra hiệu năng của chúng tôi, tính năng tree shaking được quảng cáo của Paraglide đã không hoạt động. Hãy kiểm tra bundle thực tế của bạn.
46
+
47
+ ## Dung lượng của Paraglide thực sự nằm ở đâu?
48
+
49
+ Trong các báo cáo kiểm chuẩn, chỉ số "kích thước thư viện" đo lường provider và các hook của mỗi thư viện i18n bên trong một component rỗng, trước khi có bất kỳ nội dung nào được thêm vào.
50
+
51
+ | Thư viện (TanStack Start) | Dung lượng lib (gz) | Dung lượng lib (min) |
52
+ | ----------------------------- | ------------------- | -------------------- |
53
+ | `@inlang/paraglide-js@2.15.1` | 1.8 KB | 4.5 KB |
54
+ | `react-intlayer@9.5.1` | 5.0 KB | 15.2 KB |
55
+
56
+ Nếu chỉ nhìn riêng số liệu này, Paraglide dường như chiến thắng. Nhưng Paraglide là một trình biên dịch: nó đọc các tệp `messages/*.json` và tạo một thư mục `paraglide/` trong kho mã của bạn, chứa tệp `runtime.js` (phát hiện ngôn ngữ, chiến lược cookie và storage, bản địa hóa URL) cùng một hàm JavaScript cho mỗi thông điệp.
57
+
58
+ ```bash
59
+ src/paraglide/
60
+ ├── runtime.js # phát hiện locale, chiến lược, helper URL
61
+ ├── server.js
62
+ ├── messages.js # re-export tất cả thông điệp
63
+ └── messages/
64
+ ├── _index.js
65
+ ├── en.js
66
+ └── fr.js
67
+ ```
68
+
69
+ Vì mã này nằm trong thư mục `src/` và bạn import nó qua đường dẫn tương đối, bundler sẽ tính nó vào ứng dụng của bạn chứ không phải một gói trong `node_modules`. Cột kích thước thư viện hiển thị gần như bằng không, trong khi logic tương tự vẫn được chuyển vào bundle trang của bạn.
70
+
71
+ Việc tự động sinh mã không phải là ý tưởng tồi: runtime được sinh ra chỉ bao gồm logic mà cấu hình của bạn yêu cầu (chiến lược tiền tố, cookie so với local storage, v.v.). Intlayer đạt được kết quả tương tự theo cách khác, bằng cách chèn các biến môi trường vào thời điểm build để bundler tự động loại bỏ các nhánh mã không được sử dụng. Cả hai giải pháp đều nhẹ hơn từ 3 đến 10 lần so với `i18next` hoặc `next-intl`.
72
+
73
+ Vì vậy, so sánh công bằng không nằm ở kích thước thư viện. Đó là **lượng JavaScript thực sự được gửi trên mỗi trang**.
74
+
75
+ ## Dung lượng trang thực tế qua đo đạc
76
+
77
+ Ứng dụng TanStack Start, 10 trang, đo trên các route `en` và `fr`, nén bằng gzip:
78
+
79
+ | Cấu hình | JS trang TB (gz) | So với gốc | Rò rỉ locale | Rò rỉ trang khác |
80
+ | ---------------------------------- | ---------------- | ----------- | ------------ | ---------------- |
81
+ | Gốc (không i18n) | 111.0 KB | - | 0.0% | 0.0% |
82
+ | `paraglide` (mọi chiến lược) | 125.1 KB | +14.1 KB | 49.7% | 0.0% |
83
+ | `intlayer` (`importMode: static`) | 125.8 KB | +14.8 KB | 50.0% | 0.0% |
84
+ | `intlayer` (`importMode: dynamic`) | **118.6 KB** | **+7.6 KB** | **0.0%** | **0.0%** |
85
+
86
+ Next.js 16 App Router, cùng ứng dụng:
87
+
88
+ | Cấu hình | JS trang TB (gz) | So với gốc |
89
+ | ---------------- | ---------------- | ----------- |
90
+ | Gốc (không i18n) | 141.0 KB | - |
91
+ | `paraglide-next` | 155.3 KB | +14.3 KB |
92
+ | `next-intlayer` | **141.3 KB** | **+0.3 KB** |
93
+
94
+ <I18nBenchmark framework="tanstack" vertical/>
95
+
96
+ > Dữ liệu đầy đủ có trong [báo cáo benchmark TanStack Start](https://intlayer.org/vi/doc/benchmark/tanstack) và [báo cáo benchmark Next.js](https://intlayer.org/vi/doc/benchmark/nextjs). Từng bundle có thể được kiểm tra tại [kho lưu trữ benchmark](https://github.com/intlayer-org/benchmark-i18n).
97
+
98
+ Hai điểm nổi bật rõ ràng:
99
+
100
+ - Ở chế độ `static`, Intlayer gửi lượng nội dung gần như tương đương Paraglide (125.8 KB so với 125.1 KB). Điều này hoàn toàn dễ hiểu: cả hai đều bao gồm mọi ngôn ngữ của các thông điệp mà trang sử dụng.
101
+ - Paraglide giữ nguyên kích thước 125.1 KB dù áp dụng bất kỳ chiến lược nào, bởi vì nó không hỗ trợ chế độ tải động. Mỗi hàng trong bảng trên tương đương với chế độ tĩnh.
102
+
103
+ ## Không có Provider: Ý tưởng tưởng chừng tốt nhưng không phải
104
+
105
+ Paraglide không cần provider. Bạn import một thông điệp và gọi nó:
106
+
107
+ ```tsx fileName="Hero.tsx"
108
+ import { m } from "../paraglide/messages.js";
109
+
110
+ export const Hero = () => (
111
+ <section>
112
+ <h1>{m.hero_title()}</h1>
113
+ <p>{m.hero_description()}</p>
114
+ <button>{m.hero_cta()}</button>
115
+ </section>
116
+ );
117
+ ```
118
+
119
+ Không context, không wrapper, không hook. Nhìn qua có vẻ đơn giản hơn. Nhưng thông tin ngôn ngữ vẫn phải được lấy từ một nơi nào đó. Mỗi hàm thông điệp được tạo ra trông gần giống thế này (dạng rút gọn):
120
+
121
+ ```js fileName="paraglide/messages/_index.js"
122
+ export const hero_title = (inputs = {}, options = {}) => {
123
+ const locale = options.locale ?? getLocale(); // được xử lý mỗi lần hàm được gọi
124
+
125
+ if (locale === "en") return en.hero_title(inputs);
126
+ if (locale === "fr") return fr.hero_title(inputs);
127
+ // ...mỗi ngôn ngữ một nhánh
128
+ };
129
+ ```
130
+
131
+ Và hàm `getLocale()` duyệt qua các chiến lược đã cấu hình (cookie, local storage, URL, locale mặc định) để tìm ngôn ngữ hiện tại. Do đó, mỗi node văn bản bạn render (`<>{m.my_key()}</>`) đều tự thực hiện việc giải quyết locale riêng, bao gồm cả thao tác đọc `document.cookie` trong trình duyệt. Một trang có 200 chuỗi dịch sẽ thực hiện giải quyết locale 200 lần trong mỗi lượt render, và lặp lại liên tục sau mỗi lần re-render.
132
+
133
+ Một thư viện dùng provider chỉ đọc locale **một lần duy nhất**, lưu vào context (hoặc signal, hoặc store), và mỗi node chỉ việc đọc giá trị đã có sẵn trong bộ nhớ. Provider chỉ tốn vài trăm byte. Bỏ qua provider sẽ làm hao tốn chu kỳ CPU trong mỗi lần render, và điều này thể hiện rõ ràng trong bài benchmark: thời gian tải trang và tốc độ chuyển đổi ngôn ngữ của Paraglide luôn xếp sau Intlayer trên TanStack Start (thời gian tải trang 22.1 ms so với 14.6 ms, độ phản hồi E2E 4.3 ms so với 3.2 ms).
134
+
135
+ ## Trải nghiệm lập trình viên (DX)
136
+
137
+ Nguồn dữ liệu gốc của Paraglide là JSON, nhưng bạn không bao giờ import trực tiếp tệp JSON. Bạn import tệp `.js` được tạo ra:
138
+
139
+ <Tabs defaultTab="intlayer" group="techno">
140
+ <Tab label="Paraglide" value="paraglide">
141
+
142
+ ```json fileName="messages/en.json"
143
+ {
144
+ "hero_title": "Ship your app in every language"
145
+ }
146
+ ```
147
+
148
+ ```json fileName="messages/vi.json"
149
+ {
150
+ "hero_title": "Phát hành ứng dụng của bạn bằng mọi ngôn ngữ"
151
+ }
152
+ ```
153
+
154
+ ```tsx fileName="Hero.tsx"
155
+ // Chỉ tồn tại sau khi trình biên dịch tạo lại từ JSON
156
+ import { m } from "../paraglide/messages.js";
157
+
158
+ export const Hero = () => <h1>{m.hero_title()}</h1>;
159
+ ```
160
+
161
+ </Tab>
162
+ <Tab label="Intlayer" value="intlayer">
163
+
164
+ ```ts fileName="Hero.content.ts"
165
+ import { t, type Dictionary } from "intlayer";
166
+
167
+ export default {
168
+ key: "hero",
169
+ content: {
170
+ title: t({
171
+ vi: "Phát hành ứng dụng của bạn bằng mọi ngôn ngữ",
172
+ en: "Ship your app in every language",
173
+ }),
174
+ },
175
+ } satisfies Dictionary;
176
+ ```
177
+
178
+ ```tsx fileName="Hero.tsx"
179
+ import { useIntlayer } from "react-intlayer";
180
+
181
+ export const Hero = () => {
182
+ const { title } = useIntlayer("hero");
183
+
184
+ return <h1>{title}</h1>;
185
+ };
186
+ ```
187
+
188
+ </Tab>
189
+ </Tabs>
190
+
191
+ Quy trình phát triển này mang lại những bất tiện:
192
+
193
+ - Mỗi thay đổi đối với tệp JSON đều yêu cầu tạo lại mã trước khi import có thể nhận diện hoặc type được cập nhật.
194
+ - Thư mục sinh ra `paraglide/` hoặc phải được commit lên git (gây ra xung đột merge trên các tệp sinh tự động ở mọi PR chỉnh sửa nội dung), hoặc bị bỏ qua (đòi hỏi bước biên dịch trước mỗi lần kiểm tra type, test và chạy CI).
195
+ - Mọi chuỗi văn bản đều biến thành lời gọi hàm. Các hằng số trở thành `m.key()` ở khắp mọi nơi, ngay cả ở những vị trí chỉ cần một giá trị tĩnh đơn thuần.
196
+
197
+ ## Tree Shaking: Hãy kiểm tra bundle của bạn
198
+
199
+ Lời hứa hẹn lớn nhất của Paraglide là các thông điệp không dùng đến sẽ được loại bỏ thông qua tree shaking, vì mỗi thông điệp là một export độc lập. Trong thử nghiệm với Svelte + Vite, tính năng này hoạt động đúng như quảng cáo.
200
+
201
+ Nhưng trong các môi trường khác thì không như vậy. Trong lần thử nghiệm trên [Next.js](https://intlayer.org/vi/doc/benchmark/nextjs), các trang của Paraglide nặng hơn ứng dụng gốc tới 14 KB, trong khi `next-intlayer` chỉ thêm 0.3 KB. Các bài đo trước đó trên TanStack Start cũng cho thấy thông điệp từ những trang khác bị kéo vào bundle của route hiện tại.
202
+
203
+ Tree shaking phụ thuộc chặt chẽ vào bundler (Turbopack, Rolldown, Rollup), cách thức import thông điệp (`import { m }` so với `import * as m`) và khả năng phân tích tác dụng phụ (side-effects). Nếu bạn chọn Paraglide vì kích thước nhỏ gọn, hãy mở công cụ phân tích bundle và kiểm tra xem điều đó có thực sự đúng với ứng dụng của bạn hay không.
204
+
205
+ ## Không có cơ chế tải động
206
+
207
+ Đây là giới hạn về mặt kiến trúc. Paraglide không có cách nào để tải từng ngôn ngữ một: mỗi hàm thông điệp đều import tĩnh phần triển khai của từng ngôn ngữ, do đó toàn bộ ngôn ngữ đều kết thúc trong client bundle của bạn.
208
+
209
+ Với 2 ngôn ngữ, bạn đã lãng phí một nửa dữ liệu dịch thuật, khớp với mức ~50% rò rỉ locale được đo lường ở trên. Với 10 ngôn ngữ, con số lãng phí lên tới 90%. Với 30 ngôn ngữ, con số này là 97%.
210
+
211
+ Chuyển sang tải động cũng không thể giải quyết triệt để: với mỗi thông điệp là một hàm riêng biệt, việc lazy load từng hàm sẽ tạo ra hàng nghìn request qua mạng.
212
+
213
+ Intlayer cho phép bạn linh hoạt lựa chọn theo phạm vi toàn cục hoặc trên từng từ điển:
214
+
215
+ ```ts fileName="intlayer.config.ts"
216
+ import { Locales, type IntlayerConfig } from "intlayer";
217
+
218
+ const config: IntlayerConfig = {
219
+ internationalization: {
220
+ locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
221
+ defaultLocale: Locales.ENGLISH,
222
+ },
223
+ dictionary: {
224
+ importMode: "dynamic", // 'static' | 'dynamic' | 'fetch'
225
+ },
226
+ };
227
+
228
+ export default config;
229
+ ```
230
+
231
+ | `importMode` | Dữ liệu được gửi tới client | So sánh với Paraglide |
232
+ | ------------ | ------------------------------------------------------------ | -------------------------------- |
233
+ | `static` | Tất cả ngôn ngữ của các từ điển trang đang sử dụng | Về lý thuyết cùng dung lượng |
234
+ | `dynamic` | Chỉ ngôn ngữ hiện tại, tải theo cơ chế lazy cho từng từ điển | **Nhẹ hơn N lần** với N ngôn ngữ |
235
+ | `fetch` | Chỉ ngôn ngữ hiện tại, lấy trực tiếp qua Live Sync API | **Nhẹ hơn N lần** với N ngôn ngữ |
236
+
237
+ Nhờ [chuyển đổi trong quá trình build](https://intlayer.org/vi/doc/concept/bundle-optimization) và chế độ `importMode: 'static'`, Intlayer về mặt lý thuyết tải lượng nội dung hoàn toàn giống với Paraglide. Với `'dynamic'` hoặc `'fetch'`, nó chỉ tải những gì ngôn ngữ hiện tại cần: đối với ứng dụng có N ngôn ngữ, dung lượng dịch thuật nhẹ hơn N lần so với Paraglide.
238
+
239
+ ## Những trường hợp Paraglide vẫn phù hợp
240
+
241
+ <AccordionGroup>
242
+ <Accordion header="Svelte + Vite với số lượng ít ngôn ngữ">
243
+
244
+ Nếu hệ thống của bạn dùng Svelte với Vite và bạn chỉ hỗ trợ 2 đến 3 ngôn ngữ, tree shaking hoạt động hiệu quả như mong đợi và chi phí tăng thêm cho các ngôn ngữ khác vẫn ở mức nhỏ.
245
+
246
+ </Accordion>
247
+ <Accordion header="Quy trình làm việc có sẵn với inlang">
248
+
249
+ Nếu đội ngũ của bạn đã sử dụng hệ sinh thái inlang (Fink, Sherlock, plugin định dạng thông điệp), Paraglide sẽ tích hợp một cách tự nhiên.
250
+
251
+ </Accordion>
252
+ </AccordionGroup>
253
+
254
+ ## Trải nghiệm trực tiếp trên ứng dụng của bạn
255
+
256
+ Kiểm tra dung lượng và hiện tượng rò rỉ ngôn ngữ trên ứng dụng đang chạy thực tế với công cụ miễn phí [i18n SEO Scanner](https://intlayer.org/i18n-seo-scanner):
257
+
258
+ <ClickToOpenIframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
259
+
260
+ Cài đặt Intlayer:
261
+
262
+ ```bash packageManager="npm"
263
+ npx intlayer init --interactive
264
+ ```
265
+
266
+ ```bash packageManager="pnpm"
267
+ pnpm dlx intlayer init --interactive
268
+ ```
269
+
270
+ ```bash packageManager="yarn"
271
+ yarn dlx intlayer init --interactive
272
+ ```
273
+
274
+ ```bash packageManager="bun"
275
+ bunx intlayer init --interactive
276
+ ```
277
+
278
+ ## Đọc thêm
279
+
280
+ - [Benchmark i18n TanStack Start](https://intlayer.org/vi/doc/benchmark/tanstack)
281
+ - [Benchmark i18n Next.js](https://intlayer.org/vi/doc/benchmark/nextjs)
282
+ - [Tối ưu hóa Bundle và `importMode`](https://intlayer.org/vi/doc/concept/bundle-optimization)
283
+ - [Cách chọn thư viện i18n phù hợp cho React](https://intlayer.org/vi/blog/how-to-pick-react-i18n-library)
284
+ - [Lợi ích của quốc tế hóa dựa trên trình biên dịch](https://intlayer.org/vi/blog/compiler-vs-declarative-i18n)
@@ -0,0 +1,284 @@
1
+ ---
2
+ createdAt: 2026-09-23
3
+ updatedAt: 2026-09-23
4
+ title: Intlayer 比 Paraglide 更轻量吗?
5
+ description: Paraglide 在 i18n 基准测试中看起来几乎没有开销,因为其代码是直接生成到你的代码库中的。本文将深入分析其实际体积去了哪里、为什么按节点读取语言会带来性能损耗,以及 Intlayer 的动态加载如何做到仅下发单一语言而非全量语言包。
6
+ keywords:
7
+ - Paraglide
8
+ - Intlayer
9
+ - 国际化
10
+ - i18n
11
+ - Bundle size
12
+ - Tree shaking
13
+ - Benchmark
14
+ - Blog
15
+ slugs:
16
+ - blog
17
+ - is-intlayer-lighter-than-paraglide
18
+ author: aymericzip
19
+ ---
20
+
21
+ # Intlayer 比 Paraglide 更轻量吗?
22
+
23
+ 是的。
24
+
25
+ `Paraglide` 作为业界公认非常轻量的 i18n 解决方案享有良好声誉,初看之下,[基准测试](https://intlayer.org/zh/doc/benchmark/tanstack) 也印证了这一点:其库体积几乎为零。然而,库体积为零并不代表下发到浏览器的体积为零。这只意味着字节被转移到了该指标不统计的地方。
26
+
27
+ <TOC/>
28
+
29
+ ## 核心要点
30
+
31
+ **库体积只是被隐藏了,并没有消失:**
32
+
33
+ Paraglide 将其 runtime 和消息函数直接生成到你的代码库中。这部分代码虽然依然传输给浏览器,但被归类为_你的_代码,而不是库的代码。
34
+
35
+ **无 Provider 并非免费的午餐:**
36
+
37
+ 每次调用 `m.my_key()` 都会自行解析语言,在渲染每个节点时都要读取 cookie 或本地存储,而不是从 context 中一次性读取。
38
+
39
+ **缺乏动态加载机制:**
40
+
41
+ Paraglide 会将一条消息的所有语言全部引入客户端 bundle 中。而 Intlayer 在配置 `importMode: 'dynamic'` 或 `'fetch'` 时,仅加载当前正在渲染的单一语言。
42
+
43
+ **Tree Shaking 并非总是生效:**
44
+
45
+ 在我们的部分基准测试中,Paraglide 宣传的 Tree Shaking 未能起效。建议务必检查你自己的 bundle 体积。
46
+
47
+ ## Paraglide 的体积究竟去了哪里?
48
+
49
+ 在基准测试报告中,“库体积”指标衡量的是在添加任何业务内容之前,空组件中各 i18n 库的 Provider 和 Hook 体积。
50
+
51
+ | 库 (TanStack Start) | 库体积 (gz) | 库体积 (min) |
52
+ | ----------------------------- | ----------- | ------------ |
53
+ | `@inlang/paraglide-js@2.15.1` | 1.8 KB | 4.5 KB |
54
+ | `react-intlayer@9.5.1` | 5.0 KB | 15.2 KB |
55
+
56
+ 单看这项数据,Paraglide 确实获胜。但 Paraglide 本质上是一个编译器:它读取你的 `messages/*.json` 文件,并在你的仓库中生成一个 `paraglide/` 目录,其中包含 `runtime.js`(语言检测、cookie 和存储策略、URL 本地化)以及每个消息对应的 JavaScript 函数。
57
+
58
+ ```bash
59
+ src/paraglide/
60
+ ├── runtime.js # 语言检测、策略、URL 辅助工具
61
+ ├── server.js
62
+ ├── messages.js # 重新导出所有消息
63
+ └── messages/
64
+ ├── _index.js
65
+ ├── en.js
66
+ └── fr.js
67
+ ```
68
+
69
+ 由于这些代码位于你的 `src/` 目录下且通过相对路径导入,打包工具会将其归算到你的应用程序中,而不是 `node_modules` 包。因此,库体积一栏几乎显示为零,但相同的业务逻辑依旧被完整打包进了你的页面 bundle 中。
70
+
71
+ 生成代码本身并不是坏事:生成的 runtime 仅包含你配置中真正需要的逻辑(前缀策略、cookie 与 local storage 选择等)。Intlayer 则通过另一种方式达到相同效果,即在构建时注入环境变量,使打包工具自动剔除你配置未启用的代码分支。这两种方案最终都比 `i18next` 或 `next-intl` 轻量 3 到 10 倍。
72
+
73
+ 因此,真正公平的衡量标准并不是库体积,而是**每个页面实际传输的 JavaScript 体积**。
74
+
75
+ ## 页面实测体积
76
+
77
+ TanStack Start 应用,10 个页面,在 `en` 和 `fr` 路由上进行实测(经 gzip 压缩):
78
+
79
+ | 配置 | 页面 JS 平均 (gz) | 超出基准 | 语言泄露率 | 跨页面泄露率 |
80
+ | ---------------------------------- | ----------------- | ----------- | ---------- | ------------ |
81
+ | 基准(无 i18n) | 111.0 KB | - | 0.0% | 0.0% |
82
+ | `paraglide`(任何策略) | 125.1 KB | +14.1 KB | 49.7% | 0.0% |
83
+ | `intlayer` (`importMode: static`) | 125.8 KB | +14.8 KB | 50.0% | 0.0% |
84
+ | `intlayer` (`importMode: dynamic`) | **118.6 KB** | **+7.6 KB** | **0.0%** | **0.0%** |
85
+
86
+ Next.js 16 App Router,同一应用:
87
+
88
+ | 配置 | 页面 JS 平均 (gz) | 超出基准 |
89
+ | ---------------- | ----------------- | ----------- |
90
+ | 基准(无 i18n) | 141.0 KB | - |
91
+ | `paraglide-next` | 155.3 KB | +14.3 KB |
92
+ | `next-intlayer` | **141.3 KB** | **+0.3 KB** |
93
+
94
+ <I18nBenchmark framework="tanstack" vertical/>
95
+
96
+ > 完整数据请参阅 [TanStack Start 基准测试报告](https://intlayer.org/zh/doc/benchmark/tanstack) 与 [Next.js 基准测试报告](https://intlayer.org/zh/doc/benchmark/nextjs)。所有 bundle 均可在 [基准测试仓库](https://github.com/intlayer-org/benchmark-i18n) 中核查。
97
+
98
+ 实测结果呈现出两个明显特征:
99
+
100
+ - 在 `static` 模式下,Intlayer 传输的内容体积与 Paraglide 基本持平(125.8 KB 对比 125.1 KB)。这符合预期:两者都包含了页面所需消息的所有语言版本。
101
+ - 无论使用何种策略,Paraglide 的体积始终保持在 125.1 KB,因为它没有动态加载模式。上表中其每一行都等同于静态引入。
102
+
103
+ ## 无 Provider:看似美妙却代价不菲的方案
104
+
105
+ Paraglide 不需要 Provider。你只需导入消息并直接调用:
106
+
107
+ ```tsx fileName="Hero.tsx"
108
+ import { m } from "../paraglide/messages.js";
109
+
110
+ export const Hero = () => (
111
+ <section>
112
+ <h1>{m.hero_title()}</h1>
113
+ <p>{m.hero_description()}</p>
114
+ <button>{m.hero_cta()}</button>
115
+ </section>
116
+ );
117
+ ```
118
+
119
+ 没有 context,没有外层包裹组件,没有 hook。表面上看简单直观。但是语言依然必须由某个地方解析。每个生成的消息函数大致如下所示(简化代码):
120
+
121
+ ```js fileName="paraglide/messages/_index.js"
122
+ export const hero_title = (inputs = {}, options = {}) => {
123
+ const locale = options.locale ?? getLocale(); // 每次调用都要重新解析
124
+
125
+ if (locale === "en") return en.hero_title(inputs);
126
+ if (locale === "fr") return fr.hero_title(inputs);
127
+ // ...每个语言对应一个分支
128
+ };
129
+ ```
130
+
131
+ 而 `getLocale()` 会遍历已配置的各种策略(cookie、本地存储、URL、基础语言)以获取当前语言。这意味着你渲染的每个文本节点(`<>{m.my_key()}</>`)都会各自执行一次语言解析流程,包括在浏览器中读取 `document.cookie`。一个包含 200 个翻译字符串的页面,在单次渲染中就会解析 200 次语言,并且在每次重新渲染时周而复始。
132
+
133
+ 基于 Provider 的库仅需读取**一次**语言,将其保存在 context(或 signal、store)中,所有节点直接读取已存在于内存中的值。Provider 的开销仅有几百字节。省去 Provider 会导致每次渲染都消耗 CPU 周期,这在基准测试中表现得尤为明显:在 TanStack Start 上,Paraglide 的页面加载时间与语言切换延迟均落后于 Intlayer(页面加载时间 22.1 ms 对比 14.6 ms,端到端响应度 4.3 ms 对比 3.2 ms)。
134
+
135
+ ## 开发者体验(DX)
136
+
137
+ Paraglide 的事实来源是 JSON,但你从不直接导入这些 JSON 文件,而是导入生成的 `.js` 文件:
138
+
139
+ <Tabs defaultTab="intlayer" group="techno">
140
+ <Tab label="Paraglide" value="paraglide">
141
+
142
+ ```json fileName="messages/en.json"
143
+ {
144
+ "hero_title": "Ship your app in every language"
145
+ }
146
+ ```
147
+
148
+ ```json fileName="messages/zh.json"
149
+ {
150
+ "hero_title": "以所有语言发布你的应用程序"
151
+ }
152
+ ```
153
+
154
+ ```tsx fileName="Hero.tsx"
155
+ // 仅在编译器从 JSON 重新生成该文件后才存在
156
+ import { m } from "../paraglide/messages.js";
157
+
158
+ export const Hero = () => <h1>{m.hero_title()}</h1>;
159
+ ```
160
+
161
+ </Tab>
162
+ <Tab label="Intlayer" value="intlayer">
163
+
164
+ ```ts fileName="Hero.content.ts"
165
+ import { t, type Dictionary } from "intlayer";
166
+
167
+ export default {
168
+ key: "hero",
169
+ content: {
170
+ title: t({
171
+ zh: "以所有语言发布你的应用程序",
172
+ en: "Ship your app in every language",
173
+ }),
174
+ },
175
+ } satisfies Dictionary;
176
+ ```
177
+
178
+ ```tsx fileName="Hero.tsx"
179
+ import { useIntlayer } from "react-intlayer";
180
+
181
+ export const Hero = () => {
182
+ const { title } = useIntlayer("hero");
183
+
184
+ return <h1>{title}</h1>;
185
+ };
186
+ ```
187
+
188
+ </Tab>
189
+ </Tabs>
190
+
191
+ 这种开发循环需要付出相应代价:
192
+
193
+ - 每次修改 JSON 文件,都必须等待重新生成完成,导入才能被识别或类型才能得到更新。
194
+ - 生成的 `paraglide/` 目录要么提交至 Git(这意味着每次涉及文案修改的 PR 都会在生成文件上产生合并冲突),要么将其忽略(这意味着在每次类型检查、测试和 CI 任务之前都必须额外执行一次生成步骤)。
195
+ - 每个字符串都被包裹成函数调用。常量在各处变成了 `m.key()`,即便在原本只需静态值的地方也是如此。
196
+
197
+ ## Tree Shaking:请核查你的实际 Bundle
198
+
199
+ Paraglide 最主要的主打特性是:由于每个消息都是独立的 export,未使用的消息能被 Tree Shaking 剔除。在 Svelte + Vite 的基准测试中,它确实如宣传的那样生效了。
200
+
201
+ 但在其它技术栈中并非如此。在我们针对 [Next.js](https://intlayer.org/zh/doc/benchmark/nextjs) 的测试中,Paraglide 的页面体积比基础应用多出 14 KB,而 `next-intlayer` 仅增加了 0.3 KB。在 TanStack Start 上的早期测试也表明,来自其他页面的消息同样混入了当前路由的 bundle 中。
202
+
203
+ Tree Shaking 效果取决于你的打包工具(Turbopack、Rolldown、Rollup)、消息导入语法(`import { m }` 与 `import * as m`)以及副作用分析能力。如果你因体积小巧而选择 Paraglide,请务必打开 bundle 可视化工具并在自己的项目中实际验证。
204
+
205
+ ## 无动态加载机制
206
+
207
+ 这是架构上的硬伤。Paraglide 无法做到一次只加载一种语言:每个消息函数都会静态导入每种语言的实现代码,因此所有语言都会全部打包到你的客户端 bundle 中。
208
+
209
+ 在支持 2 种语言时,有一半的翻译数据传输被浪费了,这与前文测得的 ~50% 语言泄露率完全吻合。当支持 10 种语言时,浪费率高达 90%;当支持 30 种语言时,浪费率高达 97%。
210
+
211
+ 即使改成动态加载也无法根治:由于每个消息是一个独立函数,如果按函数进行懒加载,将会导致成千上万次碎小的网络请求。
212
+
213
+ Intlayer 允许你全局或按字典自由配置加载模式:
214
+
215
+ ```ts fileName="intlayer.config.ts"
216
+ import { Locales, type IntlayerConfig } from "intlayer";
217
+
218
+ const config: IntlayerConfig = {
219
+ internationalization: {
220
+ locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
221
+ defaultLocale: Locales.ENGLISH,
222
+ },
223
+ dictionary: {
224
+ importMode: "dynamic", // 'static' | 'dynamic' | 'fetch'
225
+ },
226
+ };
227
+
228
+ export default config;
229
+ ```
230
+
231
+ | `importMode` | 传输至客户端的内容 | 对比 Paraglide |
232
+ | ------------ | ------------------------------------------- | --------------------------- |
233
+ | `static` | 页面所用字典的所有语言版本 | 理论体积完全一致 |
234
+ | `dynamic` | 仅加载当前语言,按字典进行按需懒加载 | N 个语言时体积**轻量 N 倍** |
235
+ | `fetch` | 仅加载当前语言,直接通过 Live Sync API 获取 | N 个语言时体积**轻量 N 倍** |
236
+
237
+ 借助 [构建期转换优化](https://intlayer.org/zh/doc/concept/bundle-optimization) 与 `importMode: 'static'`,Intlayer 在理论上加载的内容与 Paraglide 毫无二致。而启用 `'dynamic'` 或 `'fetch'` 后,它仅加载当前语言所需的内容:对于支持 N 种语言的应用,翻译数据的网络传输量将比 Paraglide 小 N 倍。
238
+
239
+ ## Paraglide 依然适用的场景
240
+
241
+ <AccordionGroup>
242
+ <Accordion header="语种较少的 Svelte + Vite 项目">
243
+
244
+ 如果你的技术栈是 Svelte 搭配 Vite,且仅需支持两到三种语言,Tree Shaking 可以按预期工作,多语言带来的体积开销也相对可控。
245
+
246
+ </Accordion>
247
+ <Accordion header="现有基于 inlang 的工作流">
248
+
249
+ 如果你的团队已经深度依赖 inlang 生态(Fink、Sherlock、消息格式插件),Paraglide 能够与其实现原生无缝集成。
250
+
251
+ </Accordion>
252
+ </AccordionGroup>
253
+
254
+ ## 在你的应用中实际测验
255
+
256
+ 使用免费的 [i18n SEO Scanner](https://intlayer.org/i18n-seo-scanner) 检测线上生产应用的传输体积与语言泄露情况:
257
+
258
+ <ClickToOpenIframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
259
+
260
+ 安装并初始化 Intlayer:
261
+
262
+ ```bash packageManager="npm"
263
+ npx intlayer init --interactive
264
+ ```
265
+
266
+ ```bash packageManager="pnpm"
267
+ pnpm dlx intlayer init --interactive
268
+ ```
269
+
270
+ ```bash packageManager="yarn"
271
+ yarn dlx intlayer init --interactive
272
+ ```
273
+
274
+ ```bash packageManager="bun"
275
+ bunx intlayer init --interactive
276
+ ```
277
+
278
+ ## 拓展阅读
279
+
280
+ - [TanStack Start i18n 基准测试](https://intlayer.org/zh/doc/benchmark/tanstack)
281
+ - [Next.js i18n 基准测试](https://intlayer.org/zh/doc/benchmark/nextjs)
282
+ - [Bundle 优化与 `importMode`](https://intlayer.org/zh/doc/concept/bundle-optimization)
283
+ - [如何挑选合适的 React i18n 库](https://intlayer.org/zh/blog/how-to-pick-react-i18n-library)
284
+ - [编译器驱动与声明式国际化的优劣权衡](https://intlayer.org/zh/blog/compiler-vs-declarative-i18n)
@@ -574,6 +574,26 @@ const blogEntry = {
574
574
  vi: readLocale("is_i18next_outdated.md", "vi"),
575
575
  uk: readLocale("is_i18next_outdated.md", "uk")
576
576
  },
577
+ "./blog/en/is_intlayer_lighter_than_paraglide.md": {
578
+ en: readLocale("is_intlayer_lighter_than_paraglide.md", "en"),
579
+ es: readLocale("is_intlayer_lighter_than_paraglide.md", "es"),
580
+ ru: readLocale("is_intlayer_lighter_than_paraglide.md", "ru"),
581
+ ja: readLocale("is_intlayer_lighter_than_paraglide.md", "ja"),
582
+ fr: readLocale("is_intlayer_lighter_than_paraglide.md", "fr"),
583
+ ko: readLocale("is_intlayer_lighter_than_paraglide.md", "ko"),
584
+ zh: readLocale("is_intlayer_lighter_than_paraglide.md", "zh"),
585
+ de: readLocale("is_intlayer_lighter_than_paraglide.md", "de"),
586
+ ar: readLocale("is_intlayer_lighter_than_paraglide.md", "ar"),
587
+ it: readLocale("is_intlayer_lighter_than_paraglide.md", "it"),
588
+ "en-GB": readLocale("is_intlayer_lighter_than_paraglide.md", "en-GB"),
589
+ pt: readLocale("is_intlayer_lighter_than_paraglide.md", "pt"),
590
+ hi: readLocale("is_intlayer_lighter_than_paraglide.md", "hi"),
591
+ tr: readLocale("is_intlayer_lighter_than_paraglide.md", "tr"),
592
+ pl: readLocale("is_intlayer_lighter_than_paraglide.md", "pl"),
593
+ id: readLocale("is_intlayer_lighter_than_paraglide.md", "id"),
594
+ vi: readLocale("is_intlayer_lighter_than_paraglide.md", "vi"),
595
+ uk: readLocale("is_intlayer_lighter_than_paraglide.md", "uk")
596
+ },
577
597
  "./blog/en/is_next-intl_outdated.md": {
578
598
  en: readLocale("is_next-intl_outdated.md", "en"),
579
599
  es: readLocale("is_next-intl_outdated.md", "es"),