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