@intlayer/docs 9.3.1 → 9.3.2

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 (60) hide show
  1. package/dist/cjs/generated/docs.entry.cjs +20 -0
  2. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  3. package/dist/esm/generated/docs.entry.mjs +20 -0
  4. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  5. package/dist/types/generated/docs.entry.d.ts +1 -0
  6. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  7. package/docs/ar/eslint.md +336 -0
  8. package/docs/ar/intlayer_with_react_router_v7_fs_routes.md +1 -1
  9. package/docs/bn/eslint.md +336 -0
  10. package/docs/cs/eslint.md +336 -0
  11. package/docs/de/eslint.md +336 -0
  12. package/docs/en/eslint.md +336 -0
  13. package/docs/en-GB/eslint.md +336 -0
  14. package/docs/en-GB/intlayer_with_create_react_app.md +32 -35
  15. package/docs/en-GB/intlayer_with_react_router_v7_fs_routes.md +1 -1
  16. package/docs/es/eslint.md +336 -0
  17. package/docs/fr/eslint.md +336 -0
  18. package/docs/hi/eslint.md +336 -0
  19. package/docs/hi/intlayer_with_react_router_v7_fs_routes.md +1 -1
  20. package/docs/hi/intlayer_with_vite+svelte.md +2 -2
  21. package/docs/id/eslint.md +336 -0
  22. package/docs/it/eslint.md +336 -0
  23. package/docs/ja/eslint.md +336 -0
  24. package/docs/ja/intlayer_with_react_router_v7.md +1 -146
  25. package/docs/ja/intlayer_with_vite+react.md +5 -1
  26. package/docs/ko/eslint.md +336 -0
  27. package/docs/ko/intlayer_with_lynx+react.md +4 -0
  28. package/docs/ko/intlayer_with_react_router_v7_fs_routes.md +1 -1
  29. package/docs/ko/intlayer_with_storybook.md +5 -5
  30. package/docs/nl/eslint.md +336 -0
  31. package/docs/pl/eslint.md +336 -0
  32. package/docs/pl/intlayer_with_astro.md +1 -114
  33. package/docs/pl/migration_from_i18next_to_intlayer.md +4 -0
  34. package/docs/pl/migration_from_next-i18next_to_intlayer.md +8 -4
  35. package/docs/pl/migration_from_next-intl_to_intlayer.md +11 -5
  36. package/docs/pl/migration_from_nuxtjs_i18n_to_intlayer.md +8 -4
  37. package/docs/pl/migration_from_react-i18next_to_intlayer.md +8 -4
  38. package/docs/pl/migration_from_vue-i18n_to_intlayer.md +4 -0
  39. package/docs/pt/eslint.md +336 -0
  40. package/docs/pt/intlayer_with_astro.md +1 -114
  41. package/docs/ru/eslint.md +336 -0
  42. package/docs/tr/eslint.md +336 -0
  43. package/docs/uk/eslint.md +336 -0
  44. package/docs/uk/packages/angular-intlayer/exports.md +2 -2
  45. package/docs/ur/eslint.md +336 -0
  46. package/docs/vi/eslint.md +336 -0
  47. package/docs/zh/eslint.md +336 -0
  48. package/docs/zh/intlayer_with_create_react_app.md +4 -0
  49. package/docs/zh/intlayer_with_lynx+react.md +4 -0
  50. package/docs/zh/intlayer_with_nextjs_14.md +0 -2
  51. package/docs/zh/intlayer_with_nextjs_15.md +0 -2
  52. package/docs/zh/intlayer_with_nextjs_page_router.md +0 -2
  53. package/docs/zh/intlayer_with_nuxt.md +1 -1
  54. package/docs/zh/intlayer_with_react_router_v7.md +4 -0
  55. package/docs/zh/intlayer_with_react_router_v7_fs_routes.md +4 -0
  56. package/docs/zh/intlayer_with_solid_start.md +1 -1
  57. package/docs/zh/intlayer_with_vite+vue.md +0 -2
  58. package/docs/zh-TW/eslint.md +336 -0
  59. package/package.json +6 -6
  60. package/src/generated/docs.entry.ts +20 -0
@@ -0,0 +1,336 @@
1
+ ---
2
+ createdAt: 2026-08-12
3
+ updatedAt: 2026-08-12
4
+ title: Plugin ESLint | Quy tắc Lint cho Intlayer
5
+ description: Phát hiện chuỗi văn bản bị hardcode, các lệnh gọi động mà trình biên dịch Intlayer không thể tối ưu hóa và nội dung từ điển không sử dụng với eslint-plugin-intlayer. Hoạt động với ESLint và oxlint trên React, Vue, Svelte, Angular và Astro.
6
+ keywords:
7
+ - Intlayer
8
+ - ESLint
9
+ - oxlint
10
+ - Linting
11
+ - i18n
12
+ - Quốc tế hóa
13
+ - no-raw-text
14
+ - Chuỗi văn bản hardcoded
15
+ - Bản dịch không sử dụng
16
+ - Nội dung thừa
17
+ - React
18
+ - Vue
19
+ - Svelte
20
+ - Angular
21
+ slugs:
22
+ - doc
23
+ - eslint
24
+ history:
25
+ - version: 9.3.1
26
+ date: 2026-08-12
27
+ changes: "Lịch sử khởi tạo"
28
+ author: aymericzip
29
+ ---
30
+
31
+ # Plugin ESLint x OXLint
32
+
33
+ `eslint-plugin-intlayer` giúp bắt các lỗi i18n mà TypeScript không thể phát hiện:
34
+
35
+ 1. **Văn bản hardcode** chưa từng được đưa vào từ điển.
36
+ 2. **Các lệnh gọi động** vượt qua kiểm tra kiểu và thực thi được, nhưng trình biên dịch Intlayer không thể tối ưu hóa.
37
+ 3. **Nội dung thừa (Dead content)** — các từ điển và trường không có bất kỳ phần nào trong dự án đọc (tùy chọn kích hoạt).
38
+
39
+ Các khóa từ điển không xác định, đường dẫn trường không xác định và ngôn ngữ còn thiếu vốn đã là các lỗi biên dịch, vì vậy plugin sẽ không lặp lại chúng.
40
+
41
+ ## Cài đặt
42
+
43
+ ```bash packageManager="npm"
44
+ npm install --save-dev eslint-plugin-intlayer
45
+ ```
46
+
47
+ ```bash packageManager="pnpm"
48
+ pnpm add --save-dev eslint-plugin-intlayer
49
+ ```
50
+
51
+ ```bash packageManager="yarn"
52
+ yarn add --dev eslint-plugin-intlayer
53
+ ```
54
+
55
+ Yêu cầu ESLint 9 trở lên (flat config).
56
+
57
+ ## Cách sử dụng
58
+
59
+ Plugin hoạt động trên cả ESLint và [oxlint](https://oxc.rs) — cùng quy tắc, cùng tùy chọn.
60
+
61
+ <Tabs defaultTab="eslint">
62
+ <Tab label="ESLint" value="eslint">
63
+
64
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
65
+ import intlayer from "eslint-plugin-intlayer";
66
+
67
+ export default [...intlayer.configs.recommended];
68
+ ```
69
+
70
+ Hoặc bật từng quy tắc một:
71
+
72
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
73
+ import intlayer from "eslint-plugin-intlayer";
74
+
75
+ export default [
76
+ {
77
+ plugins: { intlayer },
78
+ rules: {
79
+ "intlayer/no-raw-text": "warn",
80
+ "intlayer/static-dictionary-key": "error",
81
+ "intlayer/no-dynamic-field-access": "error",
82
+ "intlayer/enforce-adapter-import": "warn",
83
+ "intlayer/no-unused-content": "warn",
84
+ },
85
+ },
86
+ ];
87
+ ```
88
+
89
+ </Tab>
90
+ <Tab label="oxlint" value="oxlint">
91
+
92
+ ```json fileName=".oxlintrc.json"
93
+ {
94
+ "jsPlugins": ["eslint-plugin-intlayer"],
95
+ "rules": {
96
+ "intlayer/no-raw-text": "warn",
97
+ "intlayer/static-dictionary-key": "error",
98
+ "intlayer/no-dynamic-field-access": "error",
99
+ "intlayer/enforce-adapter-import": "warn"
100
+ }
101
+ }
102
+ ```
103
+
104
+ Hai lưu ý: hỗ trợ plugin JS của oxlint vẫn đang ở giai đoạn alpha và oxlint không hỗ trợ trình phân tích cú pháp tùy chỉnh — vì vậy các tệp `.vue`, `.svelte`, `.astro` và template Angular không được lint tại đó. Hãy chạy oxlint trên các tệp JS/TS/JSX của bạn và giữ lại ESLint cho phần còn lại.
105
+
106
+ `no-unused-content` được cố tình lược bỏ ở trên: nó cần thư mục làm việc và đường dẫn tệp được lint từ ngữ cảnh quy tắc, điều mà cầu nối plugin JS alpha chưa đảm bảo. Hãy chạy quy tắc này dưới ESLint.
107
+
108
+ </Tab>
109
+ </Tabs>
110
+
111
+ ### Cấu hình (Configs)
112
+
113
+ | Cấu hình | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
114
+ | --------------- | ------------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
115
+ | `recommended` | warn | error | error | off | off |
116
+ | `strict` | error (+ chuỗi ngoài JSX) | error | error | error | off |
117
+ | `contract-only` | off | error | error | off | off |
118
+
119
+ `recommended` cố ý giữ `no-raw-text` ở mức `warn`: việc áp dụng quy tắc này vào một codebase hiện có sẽ hiển thị tất cả các chuỗi chưa được dịch cùng một lúc, điều này không nên làm gián đoạn bản build của bạn ngay từ ngày đầu tiên.
120
+
121
+ `enforce-adapter-import` bị tắt theo mặc định — hãy bật rõ ràng nếu bạn muốn.
122
+
123
+ `no-unused-content` bị tắt trong mọi cấu hình, bao gồm cả `strict`. Đây là quy tắc duy nhất đọc cấu hình Intlayer của bạn và duyệt qua các tệp nguồn từ đĩa, vì vậy việc bật nó nên là một lựa chọn có chủ đích thay vì được thiết lập sẵn tự động.
124
+
125
+ ## Các quy tắc
126
+
127
+ ### `no-raw-text`
128
+
129
+ Báo cáo văn bản hiển thị cho người dùng không được khai báo trong từ điển. Quy tắc sử dụng cơ chế phát hiện giống như `intlayer extract`, do đó tên thương hiệu, lớp CSS và định danh kỹ thuật sẽ bị bỏ qua.
130
+
131
+ ```jsx
132
+ // ✗ Bị báo cáo
133
+ <h1>Welcome to our documentation</h1>
134
+ <input placeholder="Enter your email address" />
135
+
136
+ // ✓ Hợp lệ
137
+ const { title } = useIntlayer("home");
138
+ <h1>{title}</h1>
139
+ ```
140
+
141
+ Các tệp khai báo nội dung (`*.content.ts`, …) được bỏ qua.
142
+
143
+ Để sửa toàn bộ tệp cùng lúc, hãy chạy `npx intlayer extract` và để trình biên dịch chuyển các chuỗi vào từ điển giúp bạn.
144
+
145
+ **Tùy chọn**
146
+
147
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
148
+ {
149
+ "intlayer/no-raw-text": [
150
+ "warn",
151
+ {
152
+ // Các thuộc tính có giá trị là văn bản hiển thị cho người dùng.
153
+ // Mặc định: title, placeholder, alt, aria-label, label
154
+ attributes: ["title", "placeholder", "alt", "aria-label", "label"],
155
+
156
+ // Các phần tử có nội dung không bao giờ là văn bản hiển thị cho người dùng.
157
+ // Mặc định: code, pre, script, style
158
+ ignoreElements: ["code", "pre", "script", "style"],
159
+
160
+ // Biểu thức chính quy cho văn bản không bao giờ bị báo cáo.
161
+ ignorePatterns: ["^Powered by"],
162
+
163
+ // Báo cáo cả chuỗi ký tự bên ngoài mã markup. Mặc định: false
164
+ includeStringLiterals: false,
165
+ },
166
+ ],
167
+ }
168
+ ```
169
+
170
+ ### `static-dictionary-key`
171
+
172
+ Yêu cầu khóa từ điển phải là một chuỗi ký tự cố định (string literal).
173
+
174
+ Trình biên dịch chỉ có thể tải trước từ điển khi có thể đọc trực tiếp khóa tại vị trí gọi. Với một khóa được tính toán động, nó sẽ âm thầm bỏ qua việc tối ưu hóa và đóng gói tất cả từ điển thay thế.
175
+
176
+ ```typescript
177
+ // ✗ Bị báo cáo
178
+ useIntlayer(dictionaryKey);
179
+ useIntlayer(`home-${suffix}`);
180
+ getTranslations({ namespace: page });
181
+
182
+ // ✗ Biến vẫn không phải là một chuỗi cố định
183
+ const key = "home";
184
+ useIntlayer(key);
185
+
186
+ // ✓ Hợp lệ
187
+ useIntlayer("home");
188
+ getTranslations({ namespace: "home" });
189
+ ```
190
+
191
+ Điều này áp dụng cho `useIntlayer`, `getIntlayer` và mọi adapter tương thích (`useTranslation`, `useTranslations`, `formatMessage`, `<FormattedMessage id>`, `<Trans i18nKey>`, …).
192
+
193
+ ### `no-dynamic-field-access`
194
+
195
+ Yêu cầu trường bạn đọc từ từ điển phải được xác định tĩnh.
196
+
197
+ Trình biên dịch sẽ loại bỏ các trường mà nó không thấy được sử dụng. Một truy cập được tính toán động là vô hình đối với nó, do đó việc đọc có thể trả về `undefined` trong thời gian chạy.
198
+
199
+ ```typescript
200
+ // ✗ Bị báo cáo
201
+ const content = useIntlayer("home");
202
+ content[fieldName];
203
+
204
+ const t = useTranslations("home");
205
+ t(messageKey);
206
+
207
+ // ✓ Hợp lệ
208
+ content.title;
209
+ content["title"];
210
+ content.items[0];
211
+ t("hero.title");
212
+ ```
213
+
214
+ ### `enforce-adapter-import`
215
+
216
+ Ưu tiên adapter tương thích `@intlayer/*` hơn gói gốc. Gói gốc chỉ phân giải thành Intlayer khi alias của bundler được cấu hình; adapter luôn luôn thực hiện được. Có thể tự động sửa bằng `--fix`.
217
+
218
+ ```typescript
219
+ // ✗ Bị báo cáo
220
+ import { useTranslation } from "react-i18next";
221
+ import { getTranslations } from "next-intl/server";
222
+
223
+ // ✓ Hợp lệ
224
+ import { useTranslation } from "@intlayer/react-i18next";
225
+ import { getTranslations } from "@intlayer/next-intl/server";
226
+ ```
227
+
228
+ ### `no-unused-content`
229
+
230
+ **Tắt theo mặc định.** Báo cáo nội dung không có bất kỳ phần nào trong dự án đọc, cùng với các khóa từ điển được khai báo ở nhiều nơi.
231
+
232
+ ```typescript fileName="src/home.content.ts"
233
+ export default {
234
+ key: "home", // ✗ Bị báo cáo nếu không có nơi nào trong dự án yêu cầu "home"
235
+ content: {
236
+ title: t({ vi: "Tiêu đề", en: "Title" }),
237
+
238
+ // ✗ Bị báo cáo nếu không có nơi nào đọc `hero`
239
+ hero: {
240
+ subtitle: t({ vi: "Phụ đề", en: "Subtitle" }),
241
+ },
242
+ },
243
+ };
244
+ ```
245
+
246
+ Không giống như các quy tắc khác, quy tắc này không thể quyết định chỉ từ tệp đang kiểm tra — một trường chỉ được xem là không sử dụng khi so với toàn bộ dự án. Khi gặp khai báo nội dung đầu tiên trong một lần lint, nó sẽ tải cấu hình Intlayer, quét các tệp nguồn mà cấu hình đó khai báo (`build.traversePattern`, `compiler.transformPattern`) và chạy cùng bộ phân tích mức độ sử dụng đang vận hành `@intlayer/lsp` và tính năng gạch ngang "không sử dụng" trong tiện ích mở rộng VS Code. Kết quả được lưu vào bộ nhớ cache trong `cacheTtl` mili giây, do đó quá trình quét diễn ra một lần cho mỗi lượt chạy thay vì mỗi tệp.
247
+
248
+ **Tùy chọn**
249
+
250
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
251
+ {
252
+ "intlayer/no-unused-content": [
253
+ "warn",
254
+ {
255
+ // Báo cáo các khóa từ điển không có nơi nào tham chiếu. Mặc định: true
256
+ reportUnusedDictionaries: true,
257
+
258
+ // Báo cáo các trường nội dung không có nơi nào đọc. Mặc định: true
259
+ reportUnusedFields: true,
260
+
261
+ // Báo cáo các khóa được khai báo ở nhiều nơi. Mặc định: true
262
+ reportDuplicateKeys: true,
263
+
264
+ // Biểu thức chính quy cho đường dẫn trường không bao giờ bị báo cáo.
265
+ ignoreFields: ["^meta"],
266
+
267
+ // Thư mục gốc của dự án bắt đầu quét. Mặc định: thư mục làm việc của ESLint
268
+ baseDir: process.cwd(),
269
+
270
+ // Thời gian một lần quét dự án được tái sử dụng, tính bằng ms. Mặc định: 30000
271
+ cacheTtl: 30000,
272
+ },
273
+ ],
274
+ }
275
+ ```
276
+
277
+ Giảm `cacheTtl` khi bạn lint từ một editor server hoạt động lâu dài và muốn các chỉnh sửa hiển thị sớm hơn; thiết lập `baseDir` khi một lần chạy lint trải rộng trên nhiều dự án Intlayer trong monorepo.
278
+
279
+ > **Thiên về sự an toàn (ít báo sai).** Một cảnh báo sai ở đây có thể dẫn đến việc xóa một bản dịch, vì vậy sẽ không có gì được báo cáo khi từ điển được sử dụng theo cách mà bộ phân tích không thể theo dõi: đối tượng nội dung được truyền nguyên vẹn, hàm dịch được liên kết từ đó (`const t = useTranslations("home")`), khai báo được truy cập qua import trực tiếp (`useDictionary(myDictionary)`), lệnh `nest()` từ từ điển khác, hoặc danh sách trường bị làm mờ bởi toán tử spread. Các component đơn tệp (`.vue`, `.svelte`, `.astro`) được tính là sử dụng mọi trường của từ điển mà chúng đề cập, vì các khối script của chúng không được phân tích cú pháp tại đây.
280
+
281
+ `reportDuplicateKeys` đọc các từ điển chưa hợp nhất mà bản build ghi dưới thư mục `.intlayer/`, do đó nó giữ im lặng cho đến khi dự án được build ít nhất một lần. Hai khai báo có chung một khóa sẽ được hợp nhất, đây là một mẫu hợp lệ — báo cáo tồn tại vì một trường được định nghĩa ở cả hai bên sẽ âm thầm chỉ giữ lại một trong hai giá trị.
282
+
283
+ Bộ phân tích được nạp từ `@intlayer/lsp`, phát hành dưới dạng ESM. Do đó quy tắc cần một phiên bản Node có thể `require()` module ES — Node 20.19+ hoặc 22.12+. Trên các phiên bản cũ hơn, nó sẽ không báo cáo gì thay vì làm hỏng lần chạy lint.
284
+
285
+ ## Frameworks
286
+
287
+ Mọi quy tắc đều hoạt động trên tất cả các tích hợp của Intlayer, bao gồm bên trong template của Vue, Svelte và Angular. Bạn chỉ cần chỉ định cho ESLint parser nào xử lý từng loại tệp.
288
+
289
+ | Framework | Tệp | Parser |
290
+ | ------------------------- | ----------------- | --------------------------------- |
291
+ | React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` |
292
+ | Next.js | `.jsx` `.tsx` | `typescript-eslint` |
293
+ | Vue, Nuxt | `.vue` | `vue-eslint-parser` |
294
+ | Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` |
295
+ | Angular | `.ts` | `typescript-eslint` |
296
+ | Template Angular | `.component.html` | `@angular-eslint/template-parser` |
297
+ | Astro | `.astro` | `astro-eslint-parser` |
298
+
299
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
300
+ import intlayer from "eslint-plugin-intlayer";
301
+ import tseslint from "typescript-eslint";
302
+ import vueParser from "vue-eslint-parser";
303
+ import svelteParser from "svelte-eslint-parser";
304
+ import angularTemplateParser from "@angular-eslint/template-parser";
305
+
306
+ export default [
307
+ ...intlayer.configs.recommended,
308
+
309
+ {
310
+ files: ["**/*.{ts,tsx,jsx}"],
311
+ languageOptions: { parser: tseslint.parser },
312
+ },
313
+ {
314
+ files: ["**/*.vue"],
315
+ languageOptions: {
316
+ parser: vueParser,
317
+ parserOptions: { parser: tseslint.parser },
318
+ },
319
+ },
320
+ {
321
+ files: ["**/*.svelte"],
322
+ languageOptions: {
323
+ parser: svelteParser,
324
+ parserOptions: { parser: tseslint.parser },
325
+ },
326
+ },
327
+ {
328
+ files: ["**/*.component.html"],
329
+ languageOptions: { parser: angularTemplateParser },
330
+ },
331
+ ];
332
+ ```
333
+
334
+ Chỉ cài đặt các parser mà dự án của bạn cần.
335
+
336
+ > **Hạn chế đã biết.** Trong template của Vue và Angular, một biểu thức như `{{ content[key] }}` sẽ không được kiểm tra bởi `no-dynamic-field-access`. Các truy cập động được viết trong khối script vẫn được phát hiện bình thường.
@@ -0,0 +1,336 @@
1
+ ---
2
+ createdAt: 2026-08-12
3
+ updatedAt: 2026-08-12
4
+ title: ESLint 插件 | Intlayer 的 Lint 规则
5
+ description: 使用 eslint-plugin-intlayer 捕获硬编码字符串、Intlayer 编译器无法优化的动态调用以及未使用的字典内容。支持 ESLint 和 oxlint,适用于 React、Vue、Svelte、Angular 和 Astro。
6
+ keywords:
7
+ - Intlayer
8
+ - ESLint
9
+ - oxlint
10
+ - 代码检查
11
+ - i18n
12
+ - 国际化
13
+ - no-raw-text
14
+ - 硬编码字符串
15
+ - 未使用的翻译
16
+ - 死内容
17
+ - React
18
+ - Vue
19
+ - Svelte
20
+ - Angular
21
+ slugs:
22
+ - doc
23
+ - eslint
24
+ history:
25
+ - version: 9.3.1
26
+ date: 2026-08-12
27
+ changes: "初始化历史"
28
+ author: aymericzip
29
+ ---
30
+
31
+ # ESLint x OXLint 插件
32
+
33
+ `eslint-plugin-intlayer` 能够捕获 TypeScript 无法发现的几类 i18n 错误:
34
+
35
+ 1. **硬编码文本**:从未写入字典中的文本。
36
+ 2. **动态调用**:能够通过类型检查并正常运行,但 Intlayer 编译器无法进行优化的调用。
37
+ 3. **死内容(Dead content)**:项目中没有任何地方读取的字典和字段(需手动开启)。
38
+
39
+ 未知的字典键、未知的字段路径和缺失的语言环境本身已是编译错误,因此插件不会重复报告它们。
40
+
41
+ ## 安装
42
+
43
+ ```bash packageManager="npm"
44
+ npm install --save-dev eslint-plugin-intlayer
45
+ ```
46
+
47
+ ```bash packageManager="pnpm"
48
+ pnpm add --save-dev eslint-plugin-intlayer
49
+ ```
50
+
51
+ ```bash packageManager="yarn"
52
+ yarn add --dev eslint-plugin-intlayer
53
+ ```
54
+
55
+ 需要 ESLint 9 或更高版本(Flat config)。
56
+
57
+ ## 使用方法
58
+
59
+ 该插件可在 ESLint 和 [oxlint](https://oxc.rs) 中运行 — 拥有相同的规则和配置选项。
60
+
61
+ <Tabs defaultTab="eslint">
62
+ <Tab label="ESLint" value="eslint">
63
+
64
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
65
+ import intlayer from "eslint-plugin-intlayer";
66
+
67
+ export default [...intlayer.configs.recommended];
68
+ ```
69
+
70
+ 或者逐个启用规则:
71
+
72
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
73
+ import intlayer from "eslint-plugin-intlayer";
74
+
75
+ export default [
76
+ {
77
+ plugins: { intlayer },
78
+ rules: {
79
+ "intlayer/no-raw-text": "warn",
80
+ "intlayer/static-dictionary-key": "error",
81
+ "intlayer/no-dynamic-field-access": "error",
82
+ "intlayer/enforce-adapter-import": "warn",
83
+ "intlayer/no-unused-content": "warn",
84
+ },
85
+ },
86
+ ];
87
+ ```
88
+
89
+ </Tab>
90
+ <Tab label="oxlint" value="oxlint">
91
+
92
+ ```json fileName=".oxlintrc.json"
93
+ {
94
+ "jsPlugins": ["eslint-plugin-intlayer"],
95
+ "rules": {
96
+ "intlayer/no-raw-text": "warn",
97
+ "intlayer/static-dictionary-key": "error",
98
+ "intlayer/no-dynamic-field-access": "error",
99
+ "intlayer/enforce-adapter-import": "warn"
100
+ }
101
+ }
102
+ ```
103
+
104
+ 两点注意事项:oxlint 对 JS 插件的支持仍处于 Alpha 阶段,且 oxlint 不支持自定义解析器 — 因此 `.vue`、`.svelte`、`.astro` 和 Angular 模板无法在此处进行 lint。请在 JS/TS/JSX 文件上运行 oxlint,其余文件保留使用 ESLint。
105
+
106
+ 上面特意排除了 `no-unused-content`:它需要从规则上下文中获取工作目录和被检查文件的路径,而 Alpha 阶段的 JS 插件桥接层无法保证提供这些信息。请在 ESLint 下运行该规则。
107
+
108
+ </Tab>
109
+ </Tabs>
110
+
111
+ ### 预设配置
112
+
113
+ | 配置 | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
114
+ | --------------- | ----------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
115
+ | `recommended` | warn | error | error | off | off |
116
+ | `strict` | error (+ 非 JSX 字面量) | error | error | error | off |
117
+ | `contract-only` | off | error | error | off | off |
118
+
119
+ `recommended` 特意将 `no-raw-text` 设为 `warn`:将其指向现有代码库会一次性暴露所有未翻译的字符串,这不应该在第一天就导致构建中断。
120
+
121
+ `enforce-adapter-import` 默认关闭 — 如果需要请显式启用。
122
+
123
+ `no-unused-content` 在所有配置中均默认关闭(包括 `strict`)。这是唯一一个需要读取 Intlayer 配置并从磁盘遍历源文件的规则,因此启用它应当是一项经过深思熟虑的选择,而非预设自动执行的行为。
124
+
125
+ ## 规则列表
126
+
127
+ ### `no-raw-text`
128
+
129
+ 报告未在字典中声明的面向用户的文本。它使用与 `intlayer extract` 相同的检测逻辑,因此品牌名称、CSS 类名和技术标识符都会被忽略。
130
+
131
+ ```jsx
132
+ // ✗ 报告错误
133
+ <h1>Welcome to our documentation</h1>
134
+ <input placeholder="Enter your email address" />
135
+
136
+ // ✓ 正常
137
+ const { title } = useIntlayer("home");
138
+ <h1>{title}</h1>
139
+ ```
140
+
141
+ 内容声明文件(`*.content.ts`, …)会被跳过。
142
+
143
+ 若要一次性修复整个文件,运行 `npx intlayer extract`,让编译器自动将字符串移入字典。
144
+
145
+ **配置选项**
146
+
147
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
148
+ {
149
+ "intlayer/no-raw-text": [
150
+ "warn",
151
+ {
152
+ // 值为面向用户文本的属性列表。
153
+ // 默认值: title, placeholder, alt, aria-label, label
154
+ attributes: ["title", "placeholder", "alt", "aria-label", "label"],
155
+
156
+ // 内容绝非面向用户文本的元素列表。
157
+ // 默认值: code, pre, script, style
158
+ ignoreElements: ["code", "pre", "script", "style"],
159
+
160
+ // 绝不报告的文本正则表达式。
161
+ ignorePatterns: ["^Powered by"],
162
+
163
+ // 是否同时报告标记语言之外的字符串字面量。默认值: false
164
+ includeStringLiterals: false,
165
+ },
166
+ ],
167
+ }
168
+ ```
169
+
170
+ ### `static-dictionary-key`
171
+
172
+ 要求字典键必须是字符串字面量。
173
+
174
+ 编译器只有在调用位置直接读取到键时,才能预加载字典。使用计算键会静默跳过优化,转而打包所有字典。
175
+
176
+ ```typescript
177
+ // ✗ 报告错误
178
+ useIntlayer(dictionaryKey);
179
+ useIntlayer(`home-${suffix}`);
180
+ getTranslations({ namespace: page });
181
+
182
+ // ✗ 变量仍然不是字面量
183
+ const key = "home";
184
+ useIntlayer(key);
185
+
186
+ // ✓ 正常
187
+ useIntlayer("home");
188
+ getTranslations({ namespace: "home" });
189
+ ```
190
+
191
+ 这适用于 `useIntlayer`、`getIntlayer` 以及所有兼容适配器(`useTranslation`、`useTranslations`、`formatMessage`、`<FormattedMessage id>`、`<Trans i18nKey>` 等)。
192
+
193
+ ### `no-dynamic-field-access`
194
+
195
+ 要求从字典中读取的字段必须是静态已知的。
196
+
197
+ 编译器会移除它未检测到使用的字段。动态计算访问对其不可见,因此该读取在运行时可能会返回 `undefined`。
198
+
199
+ ```typescript
200
+ // ✗ 报告错误
201
+ const content = useIntlayer("home");
202
+ content[fieldName];
203
+
204
+ const t = useTranslations("home");
205
+ t(messageKey);
206
+
207
+ // ✓ 正常
208
+ content.title;
209
+ content["title"];
210
+ content.items[0];
211
+ t("hero.title");
212
+ ```
213
+
214
+ ### `enforce-adapter-import`
215
+
216
+ 优先使用 `@intlayer/*` 兼容适配器而非原始包。原始包仅在配置了打包工具别名时才会解析为 Intlayer;而适配器始终生效。可通过 `--fix` 自动修复。
217
+
218
+ ```typescript
219
+ // ✗ 报告错误
220
+ import { useTranslation } from "react-i18next";
221
+ import { getTranslations } from "next-intl/server";
222
+
223
+ // ✓ 正常
224
+ import { useTranslation } from "@intlayer/react-i18next";
225
+ import { getTranslations } from "@intlayer/next-intl/server";
226
+ ```
227
+
228
+ ### `no-unused-content`
229
+
230
+ **默认关闭。** 报告项目中没有任何地方读取的内容,以及在多个位置声明的字典键。
231
+
232
+ ```typescript fileName="src/home.content.ts"
233
+ export default {
234
+ key: "home", // ✗ 当项目中没有任何调用方请求 "home" 时报告
235
+ content: {
236
+ title: t({ zh: "标题", en: "Title" }),
237
+
238
+ // ✗ 当没有任何地方读取 `hero` 时报告
239
+ hero: {
240
+ subtitle: t({ zh: "副标题", en: "Subtitle" }),
241
+ },
242
+ },
243
+ };
244
+ ```
245
+
246
+ 与其他规则不同,此规则无法仅凭眼前的文件给出判断 — 字段是否未使用仅相对于整个项目而言。在一次 lint 运行的首次内容声明时,它会加载你的 Intlayer 配置,匹配该配置声明的源文件(`build.traversePattern`、`compiler.transformPattern`),并运行驱动 `@intlayer/lsp` 和 VS Code 扩展中“未使用”删除线的同一套使用情况分析器。结果会缓存 `cacheTtl` 毫秒,因此每次运行只会扫描一次,而不是每个文件扫描一次。
247
+
248
+ **配置选项**
249
+
250
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
251
+ {
252
+ "intlayer/no-unused-content": [
253
+ "warn",
254
+ {
255
+ // 报告没有任何地方引用的字典键。默认值: true
256
+ reportUnusedDictionaries: true,
257
+
258
+ // 报告没有任何地方读取的内容字段。默认值: true
259
+ reportUnusedFields: true,
260
+
261
+ // 报告在多处声明的重复键。默认值: true
262
+ reportDuplicateKeys: true,
263
+
264
+ // 绝不报告的字段路径正则表达式。
265
+ ignoreFields: ["^meta"],
266
+
267
+ // 扫描起始的项目根目录。默认值: ESLint 的工作目录
268
+ baseDir: process.cwd(),
269
+
270
+ // 项目扫描结果复用时长(毫秒)。默认值: 30000
271
+ cacheTtl: 30000,
272
+ },
273
+ ],
274
+ }
275
+ ```
276
+
277
+ 如果你在长期运行的编辑器服务中进行 lint 且希望更快看到修改结果,可以降低 `cacheTtl`;当单次 lint 运行跨越 monorepo 中的多个 Intlayer 项目时,请设置 `baseDir`。
278
+
279
+ > **倾向于保持沉默。** 此处的误报会导致翻译被误删,因此当字典以分析器无法跟踪的方式被使用时,不会报告任何内容:内容对象被整体传递、从中绑定的翻译函数(`const t = useTranslations("home")`)、通过直接导入访问的声明(`useDictionary(myDictionary)`)、来自另一个字典的 `nest()`、或者因 spread 展开而不详尽的字段列表。单文件组件(`.vue`、`.svelte`、`.astro`)计为使用了它们提及的字典中的所有字段,因为它们的脚本块在此处不会被解析。
280
+
281
+ `reportDuplicateKeys` 读取构建时写入 `.intlayer/` 下的未合并字典,因此在项目至少构建过一次之前它会保持静默。共享一个键的两个声明会被合并,这是一种合法的模式 — 该报告之所以存在,是因为在两边同时定义的字段会静默保留两个值中的一个。
282
+
283
+ 分析器从以 ESM 形式分发的 `@intlayer/lsp` 中加载。因此该规则需要能够 `require()` ES 模块的 Node 版本 — Node 20.19+ 或 22.12+。在更低版本上,它不会报错中断 lint 运行,而是什么都不报告。
284
+
285
+ ## 框架支持
286
+
287
+ 每条规则均适用于所有 Intlayer 集成,包括 Vue、Svelte 和 Angular 模板内部。你只需告诉 ESLint 哪个解析器负责读取对应的文件类型即可。
288
+
289
+ | 框架 | 文件 | 解析器 |
290
+ | ------------------------- | ----------------- | --------------------------------- |
291
+ | React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` |
292
+ | Next.js | `.jsx` `.tsx` | `typescript-eslint` |
293
+ | Vue, Nuxt | `.vue` | `vue-eslint-parser` |
294
+ | Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` |
295
+ | Angular | `.ts` | `typescript-eslint` |
296
+ | Angular 模板 | `.component.html` | `@angular-eslint/template-parser` |
297
+ | Astro | `.astro` | `astro-eslint-parser` |
298
+
299
+ ```javascript fileName="eslint.config.mjs" codeFormat="esm"
300
+ import intlayer from "eslint-plugin-intlayer";
301
+ import tseslint from "typescript-eslint";
302
+ import vueParser from "vue-eslint-parser";
303
+ import svelteParser from "svelte-eslint-parser";
304
+ import angularTemplateParser from "@angular-eslint/template-parser";
305
+
306
+ export default [
307
+ ...intlayer.configs.recommended,
308
+
309
+ {
310
+ files: ["**/*.{ts,tsx,jsx}"],
311
+ languageOptions: { parser: tseslint.parser },
312
+ },
313
+ {
314
+ files: ["**/*.vue"],
315
+ languageOptions: {
316
+ parser: vueParser,
317
+ parserOptions: { parser: tseslint.parser },
318
+ },
319
+ },
320
+ {
321
+ files: ["**/*.svelte"],
322
+ languageOptions: {
323
+ parser: svelteParser,
324
+ parserOptions: { parser: tseslint.parser },
325
+ },
326
+ },
327
+ {
328
+ files: ["**/*.component.html"],
329
+ languageOptions: { parser: angularTemplateParser },
330
+ },
331
+ ];
332
+ ```
333
+
334
+ 请仅安装项目所需的解析器。
335
+
336
+ > **已知局限性。** 在 Vue 和 Angular 模板中,类似于 `{{ content[key] }}` 的表达式不会被 `no-dynamic-field-access` 检查。写在 script 块中的动态读取仍会被正常捕获。
@@ -612,6 +612,10 @@ export default App;
612
612
  - 根据语言环境调整 **文本方向** (`dir`),提升不同阅读顺序语言的可读性和可用性。
613
613
  - 提供更 **无障碍** 的体验,因为辅助技术依赖这些属性以实现最佳功能。
614
614
 
615
+ </Step>
616
+
617
+ </Steps>
618
+
615
619
  ### 配置 TypeScript
616
620
 
617
621
  Intlayer 使用模块增强来利用 TypeScript 的优势,使您的代码库更强大。