schema-dsl 2.0.0 → 2.0.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 (145) hide show
  1. package/CHANGELOG.md +130 -113
  2. package/LICENSE +21 -21
  3. package/README.md +628 -628
  4. package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
  5. package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
  6. package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
  7. package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
  8. package/dist/index.cjs +75 -29
  9. package/dist/index.d.cts +10 -4
  10. package/dist/index.d.ts +10 -4
  11. package/dist/index.js +75 -29
  12. package/dist/plugins/custom-format.cjs +33 -17
  13. package/dist/plugins/custom-format.d.cts +1 -1
  14. package/dist/plugins/custom-format.d.ts +1 -1
  15. package/dist/plugins/custom-format.js +33 -17
  16. package/dist/plugins/custom-type-example.cjs +33 -17
  17. package/dist/plugins/custom-type-example.d.cts +1 -1
  18. package/dist/plugins/custom-type-example.d.ts +1 -1
  19. package/dist/plugins/custom-type-example.js +33 -17
  20. package/dist/plugins/custom-validator.cjs +0 -2
  21. package/dist/plugins/custom-validator.d.cts +1 -1
  22. package/dist/plugins/custom-validator.d.ts +1 -1
  23. package/dist/plugins/custom-validator.js +0 -2
  24. package/docs/FEATURE-INDEX.md +553 -553
  25. package/docs/add-custom-locale.md +496 -496
  26. package/docs/add-keyword.md +24 -24
  27. package/docs/api-reference.md +1047 -1047
  28. package/docs/api.md +13 -13
  29. package/docs/best-practices-project-structure.md +417 -417
  30. package/docs/best-practices.md +712 -712
  31. package/docs/cache-manager.md +344 -344
  32. package/docs/compile.md +45 -45
  33. package/docs/conditional-api.md +1307 -1307
  34. package/docs/custom-extensions-guide.md +339 -339
  35. package/docs/design-philosophy.md +606 -606
  36. package/docs/doc-index.md +324 -324
  37. package/docs/dsl-syntax.md +714 -714
  38. package/docs/dynamic-locale.md +608 -608
  39. package/docs/enum.md +482 -482
  40. package/docs/error-handling.md +1975 -1975
  41. package/docs/export-guide.md +501 -501
  42. package/docs/export-limitations.md +567 -567
  43. package/docs/faq.md +596 -596
  44. package/docs/frontend-i18n-guide.md +307 -307
  45. package/docs/i18n-user-guide.md +487 -487
  46. package/docs/i18n.md +476 -476
  47. package/docs/index.md +48 -48
  48. package/docs/json-schema-basics.md +40 -40
  49. package/docs/label-vs-description.md +271 -271
  50. package/docs/markdown-exporter.md +406 -406
  51. package/docs/mongodb-exporter.md +302 -302
  52. package/docs/multi-language.md +26 -26
  53. package/docs/multi-type-support.md +322 -322
  54. package/docs/mysql-exporter.md +280 -280
  55. package/docs/number-operators.md +449 -449
  56. package/docs/optional-marker-guide.md +326 -326
  57. package/docs/performance-guide.md +49 -49
  58. package/docs/plugin-system.md +381 -381
  59. package/docs/plugin-type-registration.md +34 -34
  60. package/docs/postgresql-exporter.md +311 -311
  61. package/docs/public/favicon.svg +4 -4
  62. package/docs/quick-start.md +435 -435
  63. package/docs/runtime-locale-support.md +532 -532
  64. package/docs/schema-helper.md +345 -345
  65. package/docs/schema-utils-advanced-issues.md +23 -23
  66. package/docs/schema-utils-best-practices.md +20 -20
  67. package/docs/schema-utils-chaining.md +150 -150
  68. package/docs/schema-utils.md +524 -524
  69. package/docs/security-checklist.md +20 -20
  70. package/docs/string-extensions.md +488 -488
  71. package/docs/troubleshooting.md +486 -486
  72. package/docs/type-converter.md +310 -310
  73. package/docs/type-reference.md +242 -242
  74. package/docs/typescript-guide.md +584 -584
  75. package/docs/union-type-guide.md +157 -157
  76. package/docs/union-types.md +284 -284
  77. package/docs/validate-async.md +491 -491
  78. package/docs/validate-batch.md +49 -49
  79. package/docs/validate-dsl-object-support.md +578 -578
  80. package/docs/validate.md +506 -506
  81. package/docs/validation-guide.md +502 -502
  82. package/docs/validator.md +39 -39
  83. package/package.json +131 -131
  84. package/plugins/custom-format.cjs +8 -8
  85. package/plugins/custom-type-example.cjs +8 -8
  86. package/plugins/custom-validator.cjs +8 -8
  87. package/src/adapters/DslAdapter.ts +111 -111
  88. package/src/adapters/index.ts +1 -1
  89. package/src/config/constants.ts +83 -83
  90. package/src/config/index.ts +2 -2
  91. package/src/config/patterns.ts +77 -77
  92. package/src/core/CacheManager.ts +169 -159
  93. package/src/core/ConditionalBuilder.ts +382 -382
  94. package/src/core/ConditionalRuntime.ts +27 -27
  95. package/src/core/ConditionalValidator.ts +254 -254
  96. package/src/core/DslBuilder.ts +687 -677
  97. package/src/core/ErrorCodes.ts +38 -38
  98. package/src/core/ErrorFormatter.ts +271 -271
  99. package/src/core/JSONSchemaCore.ts +65 -65
  100. package/src/core/Locale.ts +187 -187
  101. package/src/core/MessageTemplate.ts +42 -42
  102. package/src/core/ObjectDslBuilder.ts +64 -64
  103. package/src/core/PluginManager.ts +326 -326
  104. package/src/core/StringExtensions.ts +140 -140
  105. package/src/core/TemplateEngine.ts +44 -44
  106. package/src/core/Validator.ts +448 -448
  107. package/src/errors/I18nError.ts +159 -159
  108. package/src/errors/ValidationError.ts +105 -105
  109. package/src/exporters/BaseExporter.ts +60 -60
  110. package/src/exporters/MarkdownExporter.ts +305 -305
  111. package/src/exporters/MongoDBExporter.ts +126 -126
  112. package/src/exporters/MySQLExporter.ts +156 -155
  113. package/src/exporters/PostgreSQLExporter.ts +222 -222
  114. package/src/exporters/index.ts +18 -18
  115. package/src/index.ts +651 -633
  116. package/src/locales/en-US.ts +160 -160
  117. package/src/locales/es-ES.ts +160 -160
  118. package/src/locales/fr-FR.ts +160 -160
  119. package/src/locales/index.ts +103 -103
  120. package/src/locales/ja-JP.ts +160 -160
  121. package/src/locales/types.ts +156 -156
  122. package/src/locales/zh-CN.ts +160 -160
  123. package/src/parser/ConstraintParser.ts +101 -101
  124. package/src/parser/DslParser.ts +470 -470
  125. package/src/parser/SchemaCompiler.ts +66 -66
  126. package/src/parser/TypeRegistry.ts +250 -250
  127. package/src/parser/index.ts +6 -6
  128. package/src/plugins/custom-format.ts +124 -126
  129. package/src/plugins/custom-type-example.ts +106 -108
  130. package/src/plugins/custom-validator.ts +138 -140
  131. package/src/types/conditional.ts +28 -28
  132. package/src/types/config.ts +59 -59
  133. package/src/types/dsl.ts +131 -131
  134. package/src/types/error.ts +60 -60
  135. package/src/types/index.ts +17 -17
  136. package/src/types/infer.ts +127 -127
  137. package/src/types/plugin.ts +58 -58
  138. package/src/types/safe-regex.d.ts +9 -9
  139. package/src/types/schema.ts +66 -66
  140. package/src/types/validate.ts +71 -71
  141. package/src/utils/SchemaHelper.ts +196 -196
  142. package/src/utils/SchemaUtils.ts +365 -346
  143. package/src/utils/TypeConverter.ts +215 -215
  144. package/src/utils/index.ts +10 -10
  145. package/src/validators/CustomKeywords.ts +477 -477
@@ -1,487 +1,487 @@
1
- # 多语言支持用户指南
2
-
3
- > **更新日期**: 2026-04-30
4
-
5
- ---
6
-
7
- ## 📋 目录
8
-
9
- 1. [快速开始](#快速开始)
10
- 2. [配置方式](#配置方式)
11
- 3. [Schema 定义](#schema-定义)
12
- 4. [前端集成](#前端集成)
13
- 5. [最佳实践](#最佳实践)
14
- 6. [常见问题](#常见问题)
15
-
16
- ---
17
-
18
- ## 快速开始
19
-
20
- > **Node.js 要求**:`>=18.0.0`
21
- >
22
- > **目录加载(Node >=18)默认支持的语言文件格式**:`.js`(CommonJS)、`.cjs`、`.json`、`.jsonc`、`.json5`。
23
- > **推荐**:如果你的应用是 `type: module` / ESM 项目,优先使用 `.cjs`、`.json`、`.jsonc`、`.json5`。
24
-
25
- ### 5 分钟上手
26
-
27
- ```javascript
28
- const { dsl, validate } = require('schema-dsl');
29
-
30
- // 1. 配置用户语言包
31
- dsl.config({
32
- i18n: {
33
- 'zh-CN': {
34
- 'username': '用户名',
35
- 'email': '邮箱地址'
36
- },
37
- 'en-US': {
38
- 'username': 'Username',
39
- 'email': 'Email Address'
40
- }
41
- }
42
- });
43
-
44
- // 2. 定义 Schema(使用 key)
45
- const schema = dsl({
46
- username: 'string:3-32!'.label('username'),
47
- email: 'email!'.label('email')
48
- });
49
-
50
- // 3. 验证(动态切换语言)
51
- const result = validate(schema, data, { locale: 'zh-CN' });
52
- ```
53
-
54
- ---
55
-
56
- ## 配置方式
57
-
58
- ### 方式 1:传入对象配置(推荐小型项目)
59
-
60
- `schema-dsl` 同时支持两种对象写法:
61
-
62
- - 兼容包装层:`{ i18n: { locales: { ... } } }`
63
- - 简写形式:`{ i18n: { 'zh-CN': { ... }, 'en-US': { ... } } }`
64
-
65
- ```javascript
66
- dsl.config({
67
- i18n: {
68
- locales: {
69
- 'zh-CN': {
70
- 'username': '用户名',
71
- 'email': '邮箱地址',
72
- 'custom.invalidEmail': '邮箱格式不正确'
73
- },
74
- 'en-US': {
75
- 'username': 'Username',
76
- 'email': 'Email Address',
77
- 'custom.invalidEmail': 'Invalid email format'
78
- }
79
- }
80
- }
81
- });
82
- ```
83
-
84
- **简写形式**:
85
-
86
- ```javascript
87
- dsl.config({
88
- i18n: {
89
- 'zh-CN': {
90
- 'username': '用户名',
91
- 'email': '邮箱地址'
92
- },
93
- 'en-US': {
94
- 'username': 'Username',
95
- 'email': 'Email Address'
96
- }
97
- }
98
- });
99
- ```
100
-
101
- **优点**:
102
- - ✅ 简单直接
103
- - ✅ 适合小型项目
104
- - ✅ 无需额外文件
105
-
106
- **缺点**:
107
- - ❌ 语言包较大时代码臃肿
108
- - ❌ 不利于维护
109
-
110
- ---
111
-
112
- ### 方式 2:从目录加载(推荐大型项目)
113
-
114
- **目录结构**:
115
- ```text
116
- project/
117
- ├── i18n/
118
- │ └── labels/
119
- │ ├── zh-CN.cjs
120
- │ ├── en-US.jsonc
121
- │ └── ja-JP.json5
122
- ├── app.js
123
- └── routes/
124
- ```
125
-
126
- **配置**:
127
- ```javascript
128
- const path = require('path');
129
-
130
- dsl.config({
131
- i18n: {
132
- localesPath: path.join(__dirname, 'i18n/labels')
133
- }
134
- });
135
- ```
136
-
137
- **语言包文件**(`i18n/labels/zh-CN.cjs`):
138
- ```javascript
139
- module.exports = {
140
- // 字段标签
141
- 'username': '用户名',
142
- 'email': '邮箱地址',
143
- 'password': '密码',
144
- 'age': '年龄',
145
-
146
- // 嵌套字段
147
- 'address.city': '城市',
148
- 'address.street': '街道',
149
-
150
- // 自定义错误消息
151
- 'custom.invalidEmail': '邮箱格式不正确',
152
- 'custom.emailTaken': '该邮箱已被注册'
153
- };
154
- ```
155
-
156
- **优点**:
157
- - ✅ 清晰维护
158
- - ✅ 支持大型项目
159
- - ✅ 易于协作
160
-
161
- ---
162
-
163
- ### 缓存配置(可选)
164
-
165
- ```javascript
166
- dsl.config({
167
- cache: {
168
- maxSize: 10000, // 缓存最大条目数
169
- ttl: 7200000 // 缓存过期时间(ms)
170
- }
171
- });
172
- ```
173
-
174
- **推荐配置**:
175
-
176
- | 项目规模 | maxSize | 说明 |
177
- |---------|---------|------|
178
- | 小型(< 100 Schema) | 1000 | 够用 |
179
- | 中型(100-1000) | 5000(默认) | 推荐 |
180
- | 大型(1000-5000) | 10000 | 推荐 |
181
- | 超大型(> 5000) | 20000 | 推荐 |
182
-
183
- ---
184
-
185
- ## Schema 定义
186
-
187
- ### 使用 key 引用语言包
188
-
189
- ```javascript
190
- const userSchema = dsl({
191
- // label 使用 key
192
- username: 'string:3-32!'.label('username'),
193
- email: 'email!'.label('email'),
194
-
195
- // messages 使用 key
196
- password: 'string:8-32!'.label('password').messages({
197
- 'minLength': 'custom.passwordWeak'
198
- })
199
- });
200
- ```
201
-
202
- ### 嵌套字段
203
-
204
- ```javascript
205
- const addressSchema = dsl({
206
- address: dsl({
207
- city: 'string!'.label('address.city'),
208
- street: 'string!'.label('address.street'),
209
- zipCode: 'string!'.label('address.zipCode')
210
- })
211
- });
212
- ```
213
-
214
- **语言包**:
215
- ```javascript
216
- const labels = {
217
- 'address.city': '城市',
218
- 'address.street': '街道',
219
- 'address.zipCode': '邮编'
220
- }
221
- ```
222
-
223
- ---
224
-
225
- ## 前端集成
226
-
227
- ### Express 中间件
228
-
229
- ```javascript
230
- const express = require('express');
231
- const { validate } = require('schema-dsl');
232
-
233
- const app = express();
234
- app.use(express.json());
235
-
236
- // 中间件:提取语言参数(简化版:query > Accept-Language > 默认)
237
- app.use((req, res, next) => {
238
- req.locale = req.query.lang ||
239
- req.headers['accept-language']?.split(',')[0]?.trim() ||
240
- 'zh-CN';
241
- next();
242
- });
243
-
244
- // API 路由
245
- app.post('/api/register', (req, res) => {
246
- // 使用全局 validate,传递 locale
247
- const result = validate(userSchema, req.body, {
248
- locale: req.locale
249
- });
250
-
251
- if (!result.valid) {
252
- return res.status(400).json({
253
- success: false,
254
- errors: result.errors
255
- });
256
- }
257
-
258
- res.json({ success: true });
259
- });
260
- ```
261
-
262
- ---
263
-
264
- ### React 集成
265
-
266
- ```javascript
267
- import { useState } from 'react';
268
-
269
- function RegisterForm() {
270
- const [locale, setLocale] = useState('zh-CN');
271
- const [errors, setErrors] = useState([]);
272
-
273
- const handleSubmit = async (formData) => {
274
- const response = await fetch('/api/register', {
275
- method: 'POST',
276
- headers: {
277
- 'Content-Type': 'application/json',
278
- 'Accept-Language': locale // ← 传递语言
279
- },
280
- body: JSON.stringify(formData)
281
- });
282
-
283
- const result = await response.json();
284
-
285
- if (!result.success) {
286
- setErrors(result.errors); // 错误消息已经是对应语言
287
- }
288
- };
289
-
290
- return (
291
- <div>
292
- {/* 语言切换 */}
293
- <select value={locale} onChange={(e) => setLocale(e.target.value)}>
294
- <option value="zh-CN">中文</option>
295
- <option value="en-US">English</option>
296
- <option value="ja-JP">日本語</option>
297
- </select>
298
-
299
- <form onSubmit={(e) => {
300
- e.preventDefault();
301
- handleSubmit({
302
- username: e.target.username.value,
303
- email: e.target.email.value
304
- });
305
- }}>
306
- <input name="username" />
307
- <input name="email" />
308
- <button type="submit">提交</button>
309
- </form>
310
-
311
- {errors.map(err => (
312
- <div key={err.path}>{err.message}</div>
313
- ))}
314
- </div>
315
- );
316
- }
317
- ```
318
-
319
- ---
320
-
321
- ### Vue 集成
322
-
323
- ```vue
324
- <template>
325
- <div>
326
- <select v-model="locale">
327
- <option value="zh-CN">中文</option>
328
- <option value="en-US">English</option>
329
- </select>
330
-
331
- <form @submit.prevent="handleSubmit">
332
- <input v-model="form.username" />
333
- <input v-model="form.email" />
334
- <button type="submit">提交</button>
335
- </form>
336
-
337
- <div v-for="error in errors" :key="error.path">
338
- {{ error.message }}
339
- </div>
340
- </div>
341
- </template>
342
-
343
- <script setup>
344
- import { ref, reactive } from 'vue';
345
-
346
- const locale = ref('zh-CN');
347
- const form = reactive({ username: '', email: '' });
348
- const errors = ref([]);
349
-
350
- const handleSubmit = async () => {
351
- const response = await fetch('/api/register', {
352
- method: 'POST',
353
- headers: {
354
- 'Content-Type': 'application/json',
355
- 'Accept-Language': locale.value
356
- },
357
- body: JSON.stringify(form)
358
- });
359
-
360
- const result = await response.json();
361
- errors.value = result.errors || [];
362
- };
363
- </script>
364
- ```
365
-
366
- ---
367
-
368
- ## 最佳实践
369
-
370
- ### 1. 语言包组织
371
-
372
- **推荐结构**:
373
- ```text
374
- i18n/
375
- ├── labels/ # 字段标签
376
- │ ├── zh-CN.cjs
377
- │ ├── en-US.jsonc
378
- │ └── ja-JP.json5
379
- └── messages/ # 自定义消息(可选)
380
- ├── zh-CN.cjs
381
- └── en-US.json
382
- ```
383
-
384
- ### 2. 命名规范
385
-
386
- **字段标签**:
387
- ```javascript
388
- const fieldLabels = {
389
- 'username': '用户名', // 简单字段
390
- 'address.city': '城市', // 嵌套字段
391
- 'order.items[0].name': '商品名称' // 数组字段
392
- }
393
- ```
394
-
395
- **自定义消息**:
396
- ```javascript
397
- const customMessages = {
398
- 'custom.emailTaken': '邮箱已被注册',
399
- 'custom.passwordWeak': '密码强度不够',
400
- 'custom.orderExpired': '订单已过期'
401
- }
402
- ```
403
-
404
- ### 3. 语言检测优先级
405
-
406
- ```javascript
407
- // 推荐优先级
408
- const locale =
409
- req.query.lang || // 1. URL 参数(最高优先级)
410
- req.cookies.lang || // 2. Cookie
411
- req.headers['accept-language']?.split(',')[0]?.trim() || // 3. Accept-Language 头(取首个语言标签)
412
- 'en-US'; // 4. 默认语言
413
- ```
414
-
415
- ### 4. 语言持久化
416
-
417
- **前端**:
418
- ```javascript
419
- // 保存用户语言偏好
420
- localStorage.setItem('userLanguage', locale);
421
-
422
- // 恢复语言偏好
423
- const savedLang = localStorage.getItem('userLanguage') || 'zh-CN';
424
- ```
425
-
426
- ---
427
-
428
- ## 常见问题
429
-
430
- ### Q1: 如何添加新语言?
431
-
432
- **A**: 创建新的语言包文件并重启应用
433
-
434
- ```javascript
435
- // i18n/labels/ko-KR.cjs(韩语)
436
- module.exports = {
437
- 'username': '사용자 이름',
438
- 'email': '이메일 주소'
439
- };
440
- ```
441
-
442
- ### Q2: 如何处理缺失的翻译?
443
-
444
- **A**: 系统会自动回退
445
-
446
- ```text
447
- 查找顺序:
448
- 1. 用户语言包(例如 `i18n/labels/zh-CN.cjs` / `zh-CN.jsonc`)
449
- 2. 内置语言包(包内预置的 `zh-CN` / `en-US` / `ja-JP` / `es-ES` / `fr-FR`)
450
- 3. 使用 key 本身
451
- ```
452
-
453
- ### Q3: 缓存配置对性能有多大影响?
454
-
455
- **A**: 大型项目提升 3-10 倍
456
-
457
- ```text
458
- 场景:3000 个 Schema
459
- 原配置(1000):33% 命中率
460
- 优化后(5000):100% 命中率
461
- 性能提升:3 倍
462
- ```
463
-
464
- ### Q4: 是否支持动态加载语言包?
465
-
466
- **A**: 支持,在应用启动后调用 `dsl.config()`
467
-
468
- ```javascript
469
- // 动态添加语言
470
- const frFR = require('./i18n/fr-FR.cjs');
471
-
472
- dsl.config({
473
- i18n: {
474
- locales: {
475
- 'fr-FR': frFR
476
- }
477
- }
478
- });
479
- ```
480
-
481
- ---
482
-
483
- ## 对应示例文件
484
-
485
- **示例入口**: [i18n-user-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/i18n-user-guide.ts)
486
- **说明**: 覆盖 `dsl.config({ i18n: { locales: ... } })` 的对象配置方式、已加载语言列表,以及不同 locale 的运行时切换。
487
-
1
+ # 多语言支持用户指南
2
+
3
+ > **更新日期**: 2026-04-30
4
+
5
+ ---
6
+
7
+ ## 📋 目录
8
+
9
+ 1. [快速开始](#快速开始)
10
+ 2. [配置方式](#配置方式)
11
+ 3. [Schema 定义](#schema-定义)
12
+ 4. [前端集成](#前端集成)
13
+ 5. [最佳实践](#最佳实践)
14
+ 6. [常见问题](#常见问题)
15
+
16
+ ---
17
+
18
+ ## 快速开始
19
+
20
+ > **Node.js 要求**:`>=18.0.0`
21
+ >
22
+ > **目录加载(Node >=18)默认支持的语言文件格式**:`.js`(CommonJS)、`.cjs`、`.json`、`.jsonc`、`.json5`。
23
+ > **推荐**:如果你的应用是 `type: module` / ESM 项目,优先使用 `.cjs`、`.json`、`.jsonc`、`.json5`。
24
+
25
+ ### 5 分钟上手
26
+
27
+ ```javascript
28
+ const { dsl, validate } = require('schema-dsl');
29
+
30
+ // 1. 配置用户语言包
31
+ dsl.config({
32
+ i18n: {
33
+ 'zh-CN': {
34
+ 'username': '用户名',
35
+ 'email': '邮箱地址'
36
+ },
37
+ 'en-US': {
38
+ 'username': 'Username',
39
+ 'email': 'Email Address'
40
+ }
41
+ }
42
+ });
43
+
44
+ // 2. 定义 Schema(使用 key)
45
+ const schema = dsl({
46
+ username: 'string:3-32!'.label('username'),
47
+ email: 'email!'.label('email')
48
+ });
49
+
50
+ // 3. 验证(动态切换语言)
51
+ const result = validate(schema, data, { locale: 'zh-CN' });
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 配置方式
57
+
58
+ ### 方式 1:传入对象配置(推荐小型项目)
59
+
60
+ `schema-dsl` 同时支持两种对象写法:
61
+
62
+ - 兼容包装层:`{ i18n: { locales: { ... } } }`
63
+ - 简写形式:`{ i18n: { 'zh-CN': { ... }, 'en-US': { ... } } }`
64
+
65
+ ```javascript
66
+ dsl.config({
67
+ i18n: {
68
+ locales: {
69
+ 'zh-CN': {
70
+ 'username': '用户名',
71
+ 'email': '邮箱地址',
72
+ 'custom.invalidEmail': '邮箱格式不正确'
73
+ },
74
+ 'en-US': {
75
+ 'username': 'Username',
76
+ 'email': 'Email Address',
77
+ 'custom.invalidEmail': 'Invalid email format'
78
+ }
79
+ }
80
+ }
81
+ });
82
+ ```
83
+
84
+ **简写形式**:
85
+
86
+ ```javascript
87
+ dsl.config({
88
+ i18n: {
89
+ 'zh-CN': {
90
+ 'username': '用户名',
91
+ 'email': '邮箱地址'
92
+ },
93
+ 'en-US': {
94
+ 'username': 'Username',
95
+ 'email': 'Email Address'
96
+ }
97
+ }
98
+ });
99
+ ```
100
+
101
+ **优点**:
102
+ - ✅ 简单直接
103
+ - ✅ 适合小型项目
104
+ - ✅ 无需额外文件
105
+
106
+ **缺点**:
107
+ - ❌ 语言包较大时代码臃肿
108
+ - ❌ 不利于维护
109
+
110
+ ---
111
+
112
+ ### 方式 2:从目录加载(推荐大型项目)
113
+
114
+ **目录结构**:
115
+ ```text
116
+ project/
117
+ ├── i18n/
118
+ │ └── labels/
119
+ │ ├── zh-CN.cjs
120
+ │ ├── en-US.jsonc
121
+ │ └── ja-JP.json5
122
+ ├── app.js
123
+ └── routes/
124
+ ```
125
+
126
+ **配置**:
127
+ ```javascript
128
+ const path = require('path');
129
+
130
+ dsl.config({
131
+ i18n: {
132
+ localesPath: path.join(__dirname, 'i18n/labels')
133
+ }
134
+ });
135
+ ```
136
+
137
+ **语言包文件**(`i18n/labels/zh-CN.cjs`):
138
+ ```javascript
139
+ module.exports = {
140
+ // 字段标签
141
+ 'username': '用户名',
142
+ 'email': '邮箱地址',
143
+ 'password': '密码',
144
+ 'age': '年龄',
145
+
146
+ // 嵌套字段
147
+ 'address.city': '城市',
148
+ 'address.street': '街道',
149
+
150
+ // 自定义错误消息
151
+ 'custom.invalidEmail': '邮箱格式不正确',
152
+ 'custom.emailTaken': '该邮箱已被注册'
153
+ };
154
+ ```
155
+
156
+ **优点**:
157
+ - ✅ 清晰维护
158
+ - ✅ 支持大型项目
159
+ - ✅ 易于协作
160
+
161
+ ---
162
+
163
+ ### 缓存配置(可选)
164
+
165
+ ```javascript
166
+ dsl.config({
167
+ cache: {
168
+ maxSize: 10000, // 缓存最大条目数
169
+ ttl: 7200000 // 缓存过期时间(ms)
170
+ }
171
+ });
172
+ ```
173
+
174
+ **推荐配置**:
175
+
176
+ | 项目规模 | maxSize | 说明 |
177
+ |---------|---------|------|
178
+ | 小型(< 100 Schema) | 1000 | 够用 |
179
+ | 中型(100-1000) | 5000(默认) | 推荐 |
180
+ | 大型(1000-5000) | 10000 | 推荐 |
181
+ | 超大型(> 5000) | 20000 | 推荐 |
182
+
183
+ ---
184
+
185
+ ## Schema 定义
186
+
187
+ ### 使用 key 引用语言包
188
+
189
+ ```javascript
190
+ const userSchema = dsl({
191
+ // label 使用 key
192
+ username: 'string:3-32!'.label('username'),
193
+ email: 'email!'.label('email'),
194
+
195
+ // messages 使用 key
196
+ password: 'string:8-32!'.label('password').messages({
197
+ 'minLength': 'custom.passwordWeak'
198
+ })
199
+ });
200
+ ```
201
+
202
+ ### 嵌套字段
203
+
204
+ ```javascript
205
+ const addressSchema = dsl({
206
+ address: dsl({
207
+ city: 'string!'.label('address.city'),
208
+ street: 'string!'.label('address.street'),
209
+ zipCode: 'string!'.label('address.zipCode')
210
+ })
211
+ });
212
+ ```
213
+
214
+ **语言包**:
215
+ ```javascript
216
+ const labels = {
217
+ 'address.city': '城市',
218
+ 'address.street': '街道',
219
+ 'address.zipCode': '邮编'
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## 前端集成
226
+
227
+ ### Express 中间件
228
+
229
+ ```javascript
230
+ const express = require('express');
231
+ const { validate } = require('schema-dsl');
232
+
233
+ const app = express();
234
+ app.use(express.json());
235
+
236
+ // 中间件:提取语言参数(简化版:query > Accept-Language > 默认)
237
+ app.use((req, res, next) => {
238
+ req.locale = req.query.lang ||
239
+ req.headers['accept-language']?.split(',')[0]?.trim() ||
240
+ 'zh-CN';
241
+ next();
242
+ });
243
+
244
+ // API 路由
245
+ app.post('/api/register', (req, res) => {
246
+ // 使用全局 validate,传递 locale
247
+ const result = validate(userSchema, req.body, {
248
+ locale: req.locale
249
+ });
250
+
251
+ if (!result.valid) {
252
+ return res.status(400).json({
253
+ success: false,
254
+ errors: result.errors
255
+ });
256
+ }
257
+
258
+ res.json({ success: true });
259
+ });
260
+ ```
261
+
262
+ ---
263
+
264
+ ### React 集成
265
+
266
+ ```javascript
267
+ import { useState } from 'react';
268
+
269
+ function RegisterForm() {
270
+ const [locale, setLocale] = useState('zh-CN');
271
+ const [errors, setErrors] = useState([]);
272
+
273
+ const handleSubmit = async (formData) => {
274
+ const response = await fetch('/api/register', {
275
+ method: 'POST',
276
+ headers: {
277
+ 'Content-Type': 'application/json',
278
+ 'Accept-Language': locale // ← 传递语言
279
+ },
280
+ body: JSON.stringify(formData)
281
+ });
282
+
283
+ const result = await response.json();
284
+
285
+ if (!result.success) {
286
+ setErrors(result.errors); // 错误消息已经是对应语言
287
+ }
288
+ };
289
+
290
+ return (
291
+ <div>
292
+ {/* 语言切换 */}
293
+ <select value={locale} onChange={(e) => setLocale(e.target.value)}>
294
+ <option value="zh-CN">中文</option>
295
+ <option value="en-US">English</option>
296
+ <option value="ja-JP">日本語</option>
297
+ </select>
298
+
299
+ <form onSubmit={(e) => {
300
+ e.preventDefault();
301
+ handleSubmit({
302
+ username: e.target.username.value,
303
+ email: e.target.email.value
304
+ });
305
+ }}>
306
+ <input name="username" />
307
+ <input name="email" />
308
+ <button type="submit">提交</button>
309
+ </form>
310
+
311
+ {errors.map(err => (
312
+ <div key={err.path}>{err.message}</div>
313
+ ))}
314
+ </div>
315
+ );
316
+ }
317
+ ```
318
+
319
+ ---
320
+
321
+ ### Vue 集成
322
+
323
+ ```vue
324
+ <template>
325
+ <div>
326
+ <select v-model="locale">
327
+ <option value="zh-CN">中文</option>
328
+ <option value="en-US">English</option>
329
+ </select>
330
+
331
+ <form @submit.prevent="handleSubmit">
332
+ <input v-model="form.username" />
333
+ <input v-model="form.email" />
334
+ <button type="submit">提交</button>
335
+ </form>
336
+
337
+ <div v-for="error in errors" :key="error.path">
338
+ {{ error.message }}
339
+ </div>
340
+ </div>
341
+ </template>
342
+
343
+ <script setup>
344
+ import { ref, reactive } from 'vue';
345
+
346
+ const locale = ref('zh-CN');
347
+ const form = reactive({ username: '', email: '' });
348
+ const errors = ref([]);
349
+
350
+ const handleSubmit = async () => {
351
+ const response = await fetch('/api/register', {
352
+ method: 'POST',
353
+ headers: {
354
+ 'Content-Type': 'application/json',
355
+ 'Accept-Language': locale.value
356
+ },
357
+ body: JSON.stringify(form)
358
+ });
359
+
360
+ const result = await response.json();
361
+ errors.value = result.errors || [];
362
+ };
363
+ </script>
364
+ ```
365
+
366
+ ---
367
+
368
+ ## 最佳实践
369
+
370
+ ### 1. 语言包组织
371
+
372
+ **推荐结构**:
373
+ ```text
374
+ i18n/
375
+ ├── labels/ # 字段标签
376
+ │ ├── zh-CN.cjs
377
+ │ ├── en-US.jsonc
378
+ │ └── ja-JP.json5
379
+ └── messages/ # 自定义消息(可选)
380
+ ├── zh-CN.cjs
381
+ └── en-US.json
382
+ ```
383
+
384
+ ### 2. 命名规范
385
+
386
+ **字段标签**:
387
+ ```javascript
388
+ const fieldLabels = {
389
+ 'username': '用户名', // 简单字段
390
+ 'address.city': '城市', // 嵌套字段
391
+ 'order.items[0].name': '商品名称' // 数组字段
392
+ }
393
+ ```
394
+
395
+ **自定义消息**:
396
+ ```javascript
397
+ const customMessages = {
398
+ 'custom.emailTaken': '邮箱已被注册',
399
+ 'custom.passwordWeak': '密码强度不够',
400
+ 'custom.orderExpired': '订单已过期'
401
+ }
402
+ ```
403
+
404
+ ### 3. 语言检测优先级
405
+
406
+ ```javascript
407
+ // 推荐优先级
408
+ const locale =
409
+ req.query.lang || // 1. URL 参数(最高优先级)
410
+ req.cookies.lang || // 2. Cookie
411
+ req.headers['accept-language']?.split(',')[0]?.trim() || // 3. Accept-Language 头(取首个语言标签)
412
+ 'en-US'; // 4. 默认语言
413
+ ```
414
+
415
+ ### 4. 语言持久化
416
+
417
+ **前端**:
418
+ ```javascript
419
+ // 保存用户语言偏好
420
+ localStorage.setItem('userLanguage', locale);
421
+
422
+ // 恢复语言偏好
423
+ const savedLang = localStorage.getItem('userLanguage') || 'zh-CN';
424
+ ```
425
+
426
+ ---
427
+
428
+ ## 常见问题
429
+
430
+ ### Q1: 如何添加新语言?
431
+
432
+ **A**: 创建新的语言包文件并重启应用
433
+
434
+ ```javascript
435
+ // i18n/labels/ko-KR.cjs(韩语)
436
+ module.exports = {
437
+ 'username': '사용자 이름',
438
+ 'email': '이메일 주소'
439
+ };
440
+ ```
441
+
442
+ ### Q2: 如何处理缺失的翻译?
443
+
444
+ **A**: 系统会自动回退
445
+
446
+ ```text
447
+ 查找顺序:
448
+ 1. 用户语言包(例如 `i18n/labels/zh-CN.cjs` / `zh-CN.jsonc`)
449
+ 2. 内置语言包(包内预置的 `zh-CN` / `en-US` / `ja-JP` / `es-ES` / `fr-FR`)
450
+ 3. 使用 key 本身
451
+ ```
452
+
453
+ ### Q3: 缓存配置对性能有多大影响?
454
+
455
+ **A**: 大型项目提升 3-10 倍
456
+
457
+ ```text
458
+ 场景:3000 个 Schema
459
+ 原配置(1000):33% 命中率
460
+ 优化后(5000):100% 命中率
461
+ 性能提升:3 倍
462
+ ```
463
+
464
+ ### Q4: 是否支持动态加载语言包?
465
+
466
+ **A**: 支持,在应用启动后调用 `dsl.config()`
467
+
468
+ ```javascript
469
+ // 动态添加语言
470
+ const frFR = require('./i18n/fr-FR.cjs');
471
+
472
+ dsl.config({
473
+ i18n: {
474
+ locales: {
475
+ 'fr-FR': frFR
476
+ }
477
+ }
478
+ });
479
+ ```
480
+
481
+ ---
482
+
483
+ ## 对应示例文件
484
+
485
+ **示例入口**: [i18n-user-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/i18n-user-guide.ts)
486
+ **说明**: 覆盖 `dsl.config({ i18n: { locales: ... } })` 的对象配置方式、已加载语言列表,以及不同 locale 的运行时切换。
487
+