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,307 +1,307 @@
1
- # 前端动态切换语言 - 最佳实践指南
2
-
3
-
4
- > **场景**: 前后端分离架构,前端需要动态切换验证错误消息语言
5
-
6
- ---
7
-
8
- ## 📋 目录
9
-
10
- 1. [使用方法](#使用方法)
11
- 2. [完整示例](#完整示例)
12
- 3. [常见问题](#常见问题)
13
-
14
- ---
15
-
16
- ## 完整示例
17
-
18
- ### 示例1:完整的 Express 应用
19
-
20
- ```javascript
21
- // server.js
22
- const express = require('express');
23
- const cors = require('cors');
24
- const { dsl, validate } = require('schema-dsl');
25
- const path = require('path');
26
-
27
- const app = express();
28
- app.use(cors());
29
- app.use(express.json());
30
-
31
- // ========== 应用启动时配置(只执行一次)==========
32
- dsl.config({
33
- i18n: path.join(__dirname, 'locales') // 一次性加载所有语言包
34
- });
35
-
36
- // Schema 定义
37
- const schemas = {
38
- user: dsl({
39
- username: 'string:3-32!',
40
- email: 'email!',
41
- password: 'string:8-64!',
42
- age: 'number:18-120',
43
- phone: 'string'
44
- }),
45
-
46
- post: dsl({
47
- title: 'string:1-200!',
48
- content: 'string:10-10000!',
49
- tags: 'array:1-5<string:1-20>'
50
- })
51
- };
52
-
53
- // 通用验证端点
54
- app.post('/api/validate/:type', (req, res) => {
55
- const { type } = req.params;
56
- const schema = schemas[type];
57
-
58
- if (!schema) {
59
- return res.status(404).json({ error: 'Schema not found' });
60
- }
61
-
62
- // 从请求头获取语言偏好
63
- const locale = req.headers['accept-language']?.split(',')[0]?.trim() || 'en-US';
64
-
65
- // 验证(直接切换语言,无需重新加载)
66
- const result = validate(schema, req.body, { locale });
67
-
68
- res.json(result);
69
- });
70
-
71
- // 用户注册(带验证)
72
- app.post('/api/register', (req, res) => {
73
- // 从请求头获取语言偏好
74
- const locale = req.headers['accept-language']?.split(',')[0]?.trim() || 'en-US';
75
-
76
- // 验证数据
77
- const result = validate(schemas.user, req.body, { locale });
78
-
79
- if (!result.valid) {
80
- return res.status(400).json({
81
- success: false,
82
- errors: result.errors // 自动使用用户偏好的语言
83
- });
84
- }
85
-
86
- // 保存用户...
87
- res.json({ success: true, message: '注册成功' });
88
- });
89
-
90
- app.listen(3000, () => {
91
- console.log('Server running on http://localhost:3000');
92
- console.log('语言包已加载,支持动态切换');
93
- });
94
- ```
95
-
96
- ### 示例2:Vue 3 前端
97
-
98
- ```vue
99
- <template>
100
- <div class="validation-form">
101
- <!-- 语言切换 -->
102
- <div class="language-selector">
103
- <button
104
- v-for="lang in languages"
105
- :key="lang.code"
106
- :class="{ active: locale === lang.code }"
107
- @click="locale = lang.code"
108
- >
109
- {{ lang.label }}
110
- </button>
111
- </div>
112
-
113
- <!-- 表单 -->
114
- <form @submit.prevent="handleSubmit">
115
- <div class="form-group">
116
- <label>用户名</label>
117
- <input v-model="form.username" />
118
- <span v-if="getError('username')" class="error">
119
- {{ getError('username') }}
120
- </span>
121
- </div>
122
-
123
- <div class="form-group">
124
- <label>邮箱</label>
125
- <input v-model="form.email" type="email" />
126
- <span v-if="getError('email')" class="error">
127
- {{ getError('email') }}
128
- </span>
129
- </div>
130
-
131
- <div class="form-group">
132
- <label>密码</label>
133
- <input v-model="form.password" type="password" />
134
- <span v-if="getError('password')" class="error">
135
- {{ getError('password') }}
136
- </span>
137
- </div>
138
-
139
- <button type="submit">提交</button>
140
- </form>
141
- </div>
142
- </template>
143
-
144
- <script setup>
145
- import { ref, reactive } from 'vue';
146
-
147
- const locale = ref('zh-CN');
148
- const languages = [
149
- { code: 'zh-CN', label: '中文' },
150
- { code: 'en-US', label: 'English' },
151
- { code: 'ja-JP', label: '日本語' }
152
- ];
153
-
154
- const form = reactive({
155
- username: '',
156
- email: '',
157
- password: ''
158
- });
159
-
160
- const errors = ref([]);
161
-
162
- const handleSubmit = async () => {
163
- try {
164
- const response = await fetch('http://localhost:3000/api/validate/user', {
165
- method: 'POST',
166
- headers: {
167
- 'Content-Type': 'application/json',
168
- 'Accept-Language': locale.value
169
- },
170
- body: JSON.stringify(form)
171
- });
172
-
173
- const result = await response.json();
174
-
175
- if (!result.valid) {
176
- errors.value = result.errors;
177
- } else {
178
- alert('验证通过!');
179
- errors.value = [];
180
- }
181
- } catch (error) {
182
- console.error('验证失败:', error);
183
- }
184
- };
185
-
186
- const getError = (field) => {
187
- const error = errors.value.find(e => e.path === field);
188
- return error?.message;
189
- };
190
- </script>
191
-
192
- <style scoped>
193
- .error {
194
- color: red;
195
- font-size: 0.9em;
196
- }
197
-
198
- .language-selector button.active {
199
- background: #007bff;
200
- color: white;
201
- }
202
- </style>
203
- ```
204
-
205
- ---
206
-
207
- ## 常见问题
208
-
209
- ### Q1: 为什么不能直接使用 `Locale.setLocale()`?
210
-
211
- **A**: 因为 Node.js 是单线程异步的,多个请求可能同时修改全局状态,导致语言混乱。
212
-
213
- ```javascript
214
- // ❌ 错误示例
215
- app.post('/api/validate', (req, res) => {
216
- Locale.setLocale('zh-CN'); // 全局修改
217
- // 如果此时另一个请求设置了 'en-US',当前请求可能得到英文消息
218
- const result = validate(schema, req.body);
219
- res.json(result);
220
- });
221
- ```
222
-
223
- ### Q2: 每次请求创建 Validator 实例会影响性能吗?
224
-
225
- **A**: 实例创建本身很轻量,但**仍然建议复用同一个 `Validator` 实例**。原因不是构造函数慢,而是编译缓存挂在实例上;如果每个请求都 `new Validator()`,同一份 Schema 会反复出现首次编译 miss。
226
-
227
- ```javascript
228
- const validator = new Validator();
229
-
230
- app.post('/api/validate', (req, res) => {
231
- const locale = resolveLocale(req);
232
- const result = validator.validate(schema, req.body, { locale });
233
- res.json(result);
234
- });
235
-
236
- // 说明:
237
- // - 共享实例:同一 schema 的后续请求可以复用编译缓存
238
- // - 语言仍通过 validate(..., { locale }) 按次传入,不要写进构造函数
239
- ```
240
-
241
- ### Q3: 如何支持更多语言?
242
-
243
- **A**: 使用 `Locale.addLocale()` 添加自定义语言包。
244
-
245
- ```javascript
246
- const { Locale } = require('schema-dsl');
247
-
248
- Locale.addLocale('de-DE', {
249
- required: '{{#label}} ist erforderlich',
250
- 'format.email': '{{#label}} muss eine gültige E-Mail-Adresse sein'
251
- // ... 更多消息
252
- });
253
- ```
254
-
255
- ### Q4: 如何在前端缓存语言包?
256
-
257
- **A**: 后端返回错误消息已经是本地化的,前端无需处理。如果需要前端验证:
258
-
259
- ```javascript
260
- // 前端可以复用同一套 schema-dsl 校验规则
261
- import { dsl, validate } from 'schema-dsl';
262
-
263
- const schema = dsl({ /* ... */ });
264
- const result = validate(schema, formData, {
265
- locale: currentLocale
266
- });
267
- ```
268
-
269
- ### Q5: 如何处理 Cookie 或 Session 中的语言?
270
-
271
- ```javascript
272
- // 中间件:优先级 Header > Cookie > Session > 默认
273
- app.use((req, res, next) => {
274
- const locale =
275
- req.headers['accept-language']?.split(',')[0]?.trim() ||
276
- req.cookies?.locale ||
277
- req.session?.locale ||
278
- 'en-US';
279
-
280
- req.locale = locale;
281
- next();
282
- });
283
- ```
284
-
285
- ---
286
-
287
- ## 总结
288
-
289
- ### ✅ 推荐做法
290
-
291
- 1. **复用共享 Validator 实例**:按次通过 `validate(..., { locale })` 传入语言
292
- 2. **通过请求头传递语言**:符合 HTTP 标准
293
- 3. **使用中间件统一处理**:提高代码复用性
294
-
295
- ---
296
-
297
- **相关文档**:
298
- - [API 参考](api-reference.md)
299
- - [最佳实践](best-practices.md)
300
-
301
- ---
302
-
303
- ## 对应示例文件
304
-
305
- **示例入口**: [frontend-i18n-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/frontend-i18n-guide.ts)
306
- **说明**: 覆盖前端常见的语言优先级解析、表单提交验证,以及把错误数组整理成字段级错误映射。
307
-
1
+ # 前端动态切换语言 - 最佳实践指南
2
+
3
+
4
+ > **场景**: 前后端分离架构,前端需要动态切换验证错误消息语言
5
+
6
+ ---
7
+
8
+ ## 📋 目录
9
+
10
+ 1. [使用方法](#使用方法)
11
+ 2. [完整示例](#完整示例)
12
+ 3. [常见问题](#常见问题)
13
+
14
+ ---
15
+
16
+ ## 完整示例
17
+
18
+ ### 示例1:完整的 Express 应用
19
+
20
+ ```javascript
21
+ // server.js
22
+ const express = require('express');
23
+ const cors = require('cors');
24
+ const { dsl, validate } = require('schema-dsl');
25
+ const path = require('path');
26
+
27
+ const app = express();
28
+ app.use(cors());
29
+ app.use(express.json());
30
+
31
+ // ========== 应用启动时配置(只执行一次)==========
32
+ dsl.config({
33
+ i18n: path.join(__dirname, 'locales') // 一次性加载所有语言包
34
+ });
35
+
36
+ // Schema 定义
37
+ const schemas = {
38
+ user: dsl({
39
+ username: 'string:3-32!',
40
+ email: 'email!',
41
+ password: 'string:8-64!',
42
+ age: 'number:18-120',
43
+ phone: 'string'
44
+ }),
45
+
46
+ post: dsl({
47
+ title: 'string:1-200!',
48
+ content: 'string:10-10000!',
49
+ tags: 'array:1-5<string:1-20>'
50
+ })
51
+ };
52
+
53
+ // 通用验证端点
54
+ app.post('/api/validate/:type', (req, res) => {
55
+ const { type } = req.params;
56
+ const schema = schemas[type];
57
+
58
+ if (!schema) {
59
+ return res.status(404).json({ error: 'Schema not found' });
60
+ }
61
+
62
+ // 从请求头获取语言偏好
63
+ const locale = req.headers['accept-language']?.split(',')[0]?.trim() || 'en-US';
64
+
65
+ // 验证(直接切换语言,无需重新加载)
66
+ const result = validate(schema, req.body, { locale });
67
+
68
+ res.json(result);
69
+ });
70
+
71
+ // 用户注册(带验证)
72
+ app.post('/api/register', (req, res) => {
73
+ // 从请求头获取语言偏好
74
+ const locale = req.headers['accept-language']?.split(',')[0]?.trim() || 'en-US';
75
+
76
+ // 验证数据
77
+ const result = validate(schemas.user, req.body, { locale });
78
+
79
+ if (!result.valid) {
80
+ return res.status(400).json({
81
+ success: false,
82
+ errors: result.errors // 自动使用用户偏好的语言
83
+ });
84
+ }
85
+
86
+ // 保存用户...
87
+ res.json({ success: true, message: '注册成功' });
88
+ });
89
+
90
+ app.listen(3000, () => {
91
+ console.log('Server running on http://localhost:3000');
92
+ console.log('语言包已加载,支持动态切换');
93
+ });
94
+ ```
95
+
96
+ ### 示例2:Vue 3 前端
97
+
98
+ ```vue
99
+ <template>
100
+ <div class="validation-form">
101
+ <!-- 语言切换 -->
102
+ <div class="language-selector">
103
+ <button
104
+ v-for="lang in languages"
105
+ :key="lang.code"
106
+ :class="{ active: locale === lang.code }"
107
+ @click="locale = lang.code"
108
+ >
109
+ {{ lang.label }}
110
+ </button>
111
+ </div>
112
+
113
+ <!-- 表单 -->
114
+ <form @submit.prevent="handleSubmit">
115
+ <div class="form-group">
116
+ <label>用户名</label>
117
+ <input v-model="form.username" />
118
+ <span v-if="getError('username')" class="error">
119
+ {{ getError('username') }}
120
+ </span>
121
+ </div>
122
+
123
+ <div class="form-group">
124
+ <label>邮箱</label>
125
+ <input v-model="form.email" type="email" />
126
+ <span v-if="getError('email')" class="error">
127
+ {{ getError('email') }}
128
+ </span>
129
+ </div>
130
+
131
+ <div class="form-group">
132
+ <label>密码</label>
133
+ <input v-model="form.password" type="password" />
134
+ <span v-if="getError('password')" class="error">
135
+ {{ getError('password') }}
136
+ </span>
137
+ </div>
138
+
139
+ <button type="submit">提交</button>
140
+ </form>
141
+ </div>
142
+ </template>
143
+
144
+ <script setup>
145
+ import { ref, reactive } from 'vue';
146
+
147
+ const locale = ref('zh-CN');
148
+ const languages = [
149
+ { code: 'zh-CN', label: '中文' },
150
+ { code: 'en-US', label: 'English' },
151
+ { code: 'ja-JP', label: '日本語' }
152
+ ];
153
+
154
+ const form = reactive({
155
+ username: '',
156
+ email: '',
157
+ password: ''
158
+ });
159
+
160
+ const errors = ref([]);
161
+
162
+ const handleSubmit = async () => {
163
+ try {
164
+ const response = await fetch('http://localhost:3000/api/validate/user', {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ 'Accept-Language': locale.value
169
+ },
170
+ body: JSON.stringify(form)
171
+ });
172
+
173
+ const result = await response.json();
174
+
175
+ if (!result.valid) {
176
+ errors.value = result.errors;
177
+ } else {
178
+ alert('验证通过!');
179
+ errors.value = [];
180
+ }
181
+ } catch (error) {
182
+ console.error('验证失败:', error);
183
+ }
184
+ };
185
+
186
+ const getError = (field) => {
187
+ const error = errors.value.find(e => e.path === field);
188
+ return error?.message;
189
+ };
190
+ </script>
191
+
192
+ <style scoped>
193
+ .error {
194
+ color: red;
195
+ font-size: 0.9em;
196
+ }
197
+
198
+ .language-selector button.active {
199
+ background: #007bff;
200
+ color: white;
201
+ }
202
+ </style>
203
+ ```
204
+
205
+ ---
206
+
207
+ ## 常见问题
208
+
209
+ ### Q1: 为什么不能直接使用 `Locale.setLocale()`?
210
+
211
+ **A**: 因为 Node.js 是单线程异步的,多个请求可能同时修改全局状态,导致语言混乱。
212
+
213
+ ```javascript
214
+ // ❌ 错误示例
215
+ app.post('/api/validate', (req, res) => {
216
+ Locale.setLocale('zh-CN'); // 全局修改
217
+ // 如果此时另一个请求设置了 'en-US',当前请求可能得到英文消息
218
+ const result = validate(schema, req.body);
219
+ res.json(result);
220
+ });
221
+ ```
222
+
223
+ ### Q2: 每次请求创建 Validator 实例会影响性能吗?
224
+
225
+ **A**: 实例创建本身很轻量,但**仍然建议复用同一个 `Validator` 实例**。原因不是构造函数慢,而是编译缓存挂在实例上;如果每个请求都 `new Validator()`,同一份 Schema 会反复出现首次编译 miss。
226
+
227
+ ```javascript
228
+ const validator = new Validator();
229
+
230
+ app.post('/api/validate', (req, res) => {
231
+ const locale = resolveLocale(req);
232
+ const result = validator.validate(schema, req.body, { locale });
233
+ res.json(result);
234
+ });
235
+
236
+ // 说明:
237
+ // - 共享实例:同一 schema 的后续请求可以复用编译缓存
238
+ // - 语言仍通过 validate(..., { locale }) 按次传入,不要写进构造函数
239
+ ```
240
+
241
+ ### Q3: 如何支持更多语言?
242
+
243
+ **A**: 使用 `Locale.addLocale()` 添加自定义语言包。
244
+
245
+ ```javascript
246
+ const { Locale } = require('schema-dsl');
247
+
248
+ Locale.addLocale('de-DE', {
249
+ required: '{{#label}} ist erforderlich',
250
+ 'format.email': '{{#label}} muss eine gültige E-Mail-Adresse sein'
251
+ // ... 更多消息
252
+ });
253
+ ```
254
+
255
+ ### Q4: 如何在前端缓存语言包?
256
+
257
+ **A**: 后端返回错误消息已经是本地化的,前端无需处理。如果需要前端验证:
258
+
259
+ ```javascript
260
+ // 前端可以复用同一套 schema-dsl 校验规则
261
+ import { dsl, validate } from 'schema-dsl';
262
+
263
+ const schema = dsl({ /* ... */ });
264
+ const result = validate(schema, formData, {
265
+ locale: currentLocale
266
+ });
267
+ ```
268
+
269
+ ### Q5: 如何处理 Cookie 或 Session 中的语言?
270
+
271
+ ```javascript
272
+ // 中间件:优先级 Header > Cookie > Session > 默认
273
+ app.use((req, res, next) => {
274
+ const locale =
275
+ req.headers['accept-language']?.split(',')[0]?.trim() ||
276
+ req.cookies?.locale ||
277
+ req.session?.locale ||
278
+ 'en-US';
279
+
280
+ req.locale = locale;
281
+ next();
282
+ });
283
+ ```
284
+
285
+ ---
286
+
287
+ ## 总结
288
+
289
+ ### ✅ 推荐做法
290
+
291
+ 1. **复用共享 Validator 实例**:按次通过 `validate(..., { locale })` 传入语言
292
+ 2. **通过请求头传递语言**:符合 HTTP 标准
293
+ 3. **使用中间件统一处理**:提高代码复用性
294
+
295
+ ---
296
+
297
+ **相关文档**:
298
+ - [API 参考](api-reference.md)
299
+ - [最佳实践](best-practices.md)
300
+
301
+ ---
302
+
303
+ ## 对应示例文件
304
+
305
+ **示例入口**: [frontend-i18n-guide.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/frontend-i18n-guide.ts)
306
+ **说明**: 覆盖前端常见的语言优先级解析、表单提交验证,以及把错误数组整理成字段级错误映射。
307
+