schema-dsl 1.0.0

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