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,483 +1,496 @@
1
- # 添加自定义语言包指南
2
-
3
- **版本**: v1.2.3
4
- **最后更新**: 2026-03-03
5
-
6
- ---
7
-
8
- ## 📖 概述
9
-
10
- 本指南将教你如何为 schema-dsl 添加自定义语言包或扩展现有语言。
11
-
12
- ---
13
-
14
- ## 🏗️ 多人协作:子目录拆分语言包(v1.2.3 新增)⭐
15
-
16
- > **适用场景**:多人/多模块开发,避免所有语言 key 堆在同一文件产生 Git 冲突和 code 码冲突。
17
-
18
- ### 目录结构
19
-
20
- ```bash
21
- project/
22
- ├── locales/
23
- │ ├── core/ # 公共 code 段:1000-1999(框架层维护)
24
- │ │ ├── zh-CN.js
25
- │ │ └── en-US.js
26
- │ ├── account/ # 账户模块 code 段:10000-10999(开发者A)
27
- │ │ ├── zh-CN.js
28
- │ └── en-US.js
29
- │ ├── order/ # 订单模块 code 段:20000-20999(开发者B)
30
- │ │ ├── zh-CN.js
31
- │ └── en-US.js
32
- └── payment/ # 支付模块 code 段:30000-30999(开发者C)
33
- ├── zh-CN.js
34
- └── en-US.js
35
- └── app.js
36
- ```
37
-
38
- ### 每个模块独立维护自己的语言文件
39
-
40
- ```javascript
41
- // locales/account/zh-CN.js — 开发者A 独立维护,互不干扰
42
- module.exports = {
43
- 'account.notFound': { code: 10001, message: '账户不存在' },
44
- 'account.locked': { code: 10002, message: '账户已锁定' },
45
- };
46
-
47
- // locales/order/zh-CN.js 开发者B 独立维护
48
- module.exports = {
49
- 'order.notFound': { code: 20001, message: '订单不存在' },
50
- 'order.notPaid': { code: 20002, message: '订单未支付' },
51
- };
52
- ```
53
-
54
- ### 应用启动:一行配置,自动递归合并
55
-
56
- ```javascript
57
- // app.js
58
- const { dsl, validate } = require('schema-dsl');
59
- const path = require('path');
60
-
61
- // 自动递归扫描 locales/ 下所有子目录,同语言文件合并为一个完整语言包
62
- dsl.config({
63
- i18n: path.join(__dirname, 'locales')
64
- });
65
- ```
66
-
67
- > - 子目录名(`account/`、`order/`)仅作为**模块组织层**,不影响最终语言 key 命名
68
- > - 加载顺序:按文件系统字母序递归扫描
69
- > - 同语言 key 出现重复时:默认打 `WARN` 日志,可开启严格模式阻断启动
70
-
71
- ### 严格模式:key 冲突时阻断启动(推荐 CI 环境)
72
-
73
- ```javascript
74
- dsl.config({
75
- i18n: path.join(__dirname, 'locales'),
76
- strict: true // 同名 key 冲突时直接抛 Error,防止静默覆盖
77
- });
78
-
79
- // 冲突示例输出:
80
- // Error: [schema-dsl] i18n key 冲突 in locale 'zh-CN'
81
- // 冲突 key: account.notFound
82
- // 来源文件: /project/locales/account/zh-CN.js
83
- ```
84
-
85
- ### Code 段划分建议
86
-
87
- 多人开发时建议在项目根目录维护一份 `locales/CODE-SEGMENTS.md`,约定各模块的 code 号段:
88
-
89
- | 模块 | code 范围 | 负责人 |
90
- |------|----------|--------|
91
- | core(公共) | 1000–1999 | 框架组 |
92
- | account | 10000–10999 | 开发者A |
93
- | order | 20000–20999 | 开发者B |
94
- | payment | 30000–30999 | 开发者C |
95
-
96
- > `CODE-SEGMENTS.md` / `CODE-SEGMENTS.js` 等非语言文件会被自动跳过,无需担心被误加载。
97
-
98
- ---
99
-
100
-
101
-
102
- ## 🚀 快速开始
103
-
104
- ### 推荐方式:配置语言包目录(一次性加载所有语言)⭐
105
-
106
- **正确的使用方式**:在应用启动时一次性加载所有语言包,运行时直接切换。
107
-
108
- #### 第1步:创建语言包文件
109
-
110
-
111
- #### 第1步:创建语言包文件
112
-
113
- ```bash
114
- # 项目结构
115
- my-project/
116
- ├── locales/ # 语言包目录
117
- │ ├── zh-CN.js # 中文(已存在,将被覆盖/合并)
118
- │ ├── en-US.js # 英文(已存在,将被覆盖/合并)
119
- │ └── pt-BR.js # 葡萄牙语(新增)
120
- └── app.js
121
- ```
122
-
123
- #### 第2步:定义语言包(`locales/pt-BR.js`)
124
-
125
- ```javascript
126
- module.exports = {
127
- // 通用验证错误
128
- 'required': '{{#label}} é obrigatório',
129
- 'type': '{{#label}} deve ser do tipo {{#expected}}',
130
- 'min': '{{#label}} deve ter pelo menos {{#limit}} caracteres',
131
- 'max': '{{#label}} não pode exceder {{#limit}} caracteres',
132
- 'length': '{{#label}} deve ter exatamente {{#limit}} caracteres',
133
- 'pattern': '{{#label}} formato inválido',
134
- 'enum': '{{#label}} deve ser um dos seguintes valores: {{#allowed}}',
135
-
136
- // 格式验证
137
- 'format.email': '{{#label}} deve ser um e-mail válido',
138
- 'format.url': '{{#label}} deve ser uma URL válida',
139
- 'format.uuid': '{{#label}} deve ser um UUID válido',
140
- 'format.date': '{{#label}} deve ser uma data válida (YYYY-MM-DD)',
141
- 'format.datetime': '{{#label}} deve ser uma data/hora válida (ISO 8601)',
142
-
143
- // 字符串验证
144
- 'string.minLength': '{{#label}} deve ter pelo menos {{#limit}} caracteres',
145
- 'string.maxLength': '{{#label}} não pode exceder {{#limit}} caracteres',
146
- 'string.pattern': '{{#label}} formato inválido',
147
- 'string.alphanum': '{{#label}} deve conter apenas letras e números',
148
-
149
- // 数字验证
150
- 'number.base': '{{#label}} deve ser um número',
151
- 'number.min': '{{#label}} não pode ser menor que {{#limit}}',
152
- 'number.max': '{{#label}} não pode ser maior que {{#limit}}',
153
- 'number.integer': '{{#label}} deve ser um inteiro',
154
- 'number.positive': '{{#label}} deve ser um número positivo',
155
- 'number.negative': '{{#label}} deve ser um número negativo',
156
-
157
- // 布尔验证
158
- 'boolean.base': '{{#label}} deve ser um booleano',
159
-
160
- // 对象验证
161
- 'object.base': '{{#label}} deve ser um objeto',
162
-
163
- // 数组验证
164
- 'array.base': '{{#label}} deve ser um array',
165
- 'array.min': '{{#label}} deve ter pelo menos {{#limit}} itens',
166
- 'array.max': '{{#label}} não pode ter mais de {{#limit}} itens',
167
-
168
- // 日期验证
169
- 'date.base': '{{#label}} deve ser uma data válida',
170
- 'date.min': '{{#label}} não pode ser anterior a {{#limit}}',
171
- 'date.max': '{{#label}} não pode ser posterior a {{#limit}}',
172
-
173
- // 自定义模式
174
- 'pattern.phone.cn': 'Número de telefone inválido',
175
- 'pattern.idCard.cn': 'Número de identidade inválido',
176
- 'pattern.creditCard': 'Número de cartão de crédito inválido',
177
- 'pattern.objectId': 'ObjectId inválido',
178
- 'pattern.hexColor': 'Código de cor hexadecimal inválido',
179
- 'pattern.macAddress': 'Endereço MAC inválido',
180
- 'pattern.cron': 'Expressão Cron inválida',
181
- 'pattern.slug': 'Slug deve conter apenas letras minúsculas, números e hífens'
182
- };
183
- ```
184
-
185
- #### 第3步:应用启动时一次性加载所有语言
186
-
187
- ```javascript
188
- const { dsl, validate } = require('schema-dsl');
189
- const path = require('path');
190
-
191
- // ========== 应用启动时配置(只执行一次)==========
192
- dsl.config({
193
- i18n: path.join(__dirname, 'locales') // 自动加载目录下所有语言文件
194
- });
195
-
196
- // 说明:
197
- // 1. 自动扫描 locales/ 目录下的所有 .js 和 .json 文件
198
- // 2. 从文件名提取语言代码(如 pt-BR.js → pt-BR)
199
- // 3. 自动加载并注册所有语言包
200
- // 4. 用户自定义的语言包会与系统默认语言包合并,用户的优先
201
-
202
- // ========== 运行时直接切换语言(无需重新加载)==========
203
- const schema = dsl({ username: 'string:3-32!' });
204
-
205
- // 使用葡萄牙语
206
- const result1 = validate(schema, { username: 'ab' }, { locale: 'pt-BR' });
207
- // 错误消息: "username deve ter pelo menos 3 caracteres"
208
-
209
- // 使用中文
210
- const result2 = validate(schema, { username: 'ab' }, { locale: 'zh-CN' });
211
- // 错误消息: "username长度不能少于3个字符"
212
-
213
- // 使用英文
214
- const result3 = validate(schema, { username: 'ab' }, { locale: 'en-US' });
215
- // 错误消息: "username length must be at least 3"
216
- ```
217
-
218
- #### 语言包合并策略
219
-
220
- ```javascript
221
- // 系统内置的 zh-CN 语言包
222
- const systemZhCN = {
223
- 'required': '{{#label}}是必填项',
224
- 'string.minLength': '{{#label}}长度不能少于{{#limit}}个字符'
225
- };
226
-
227
- // 用户自定义的 locales/zh-CN.js
228
- const userZhCN = {
229
- 'required': '{{#label}}必须填写', // 覆盖系统默认
230
- 'custom.myError': '自定义错误' // 新增自定义消息
231
- };
232
-
233
- // 最终合并结果(深度合并)
234
- const finalZhCN = {
235
- 'required': '{{#label}}必须填写', // ✅ 用户的优先
236
- 'string.minLength': '{{#label}}长度不能少于{{#limit}}个字符', // 保留系统默认
237
- 'custom.myError': '自定义错误' // 新增自定义消息
238
- };
239
- ```
240
-
241
- ---
242
-
243
- ### 方式2:直接传入对象(适合动态配置)
244
-
245
- ```javascript
246
- const { dsl } = require('schema-dsl');
247
-
248
- // 应用启动时配置
249
- dsl.config({
250
- i18n: {
251
- 'pt-BR': require('./locales/pt-BR'),
252
- 'de-DE': require('./locales/de-DE'),
253
- 'ko-KR': require('./locales/ko-KR')
254
- }
255
- });
256
-
257
- // 运行时直接切换
258
- validate(schema, data, { locale: 'pt-BR' });
259
- validate(schema, data, { locale: 'de-DE' });
260
- ```
261
-
262
- ---
263
-
264
- ## ⚠️ 错误示例(不推荐)
265
-
266
- ### ❌ 错误:运行时单个加载语言包
267
-
268
- ```javascript
269
- const { Locale } = require('schema-dsl');
270
-
271
- //不推荐:在每次验证前动态加载
272
- function validateUser(data, locale) {
273
- if (locale === 'pt-BR') {
274
- Locale.addLocale('pt-BR', require('./locales/pt-BR')); // 每次都加载,性能差
275
- }
276
- return validate(schema, data, { locale });
277
- }
278
- ```
279
-
280
- }
281
-
282
- // ✅ 正确:应用启动时一次性加载
283
- // app.js 启动入口
284
- dsl.config({ i18n: './locales' }); // 只加载一次
285
-
286
- // 运行时直接切换,无需重新加载
287
- function validateUser(data, locale) {
288
- return validate(schema, data, { locale }); // ✅ 直接切换,性能好
289
- }
290
- ```
291
-
292
- ### 为什么推荐"首次加载,运行时切换"?
293
-
294
- | 方式 | 加载次数 | 性能 | 内存 | 推荐度 |
295
- |------|---------|------|------|--------|
296
- | **首次加载所有** | 1次 | ⭐⭐⭐⭐⭐ 极快 | 低 | ✅ 强烈推荐 |
297
- | 运行时单个加载 | N次 | ⭐⭐ 慢 | 中 | ❌ 不推荐 |
298
-
299
- ---
300
-
301
- ## 🎯 完整示例
302
-
303
- ```javascript
304
- // ========== app.js(应用启动入口)==========
305
- const express = require('express');
306
- const { dsl, validate } = require('schema-dsl');
307
- const path = require('path');
308
-
309
- // 应用启动时一次性加载所有语言包
310
- dsl.config({
311
- i18n: path.join(__dirname, 'locales')
312
- });
313
-
314
- const app = express();
315
-
316
- // ========== routes/user.js(业务路由)==========
317
- const userSchema = dsl({
318
- username: 'string:3-32!',
319
- email: 'email!',
320
- age: 'number:18-120'
321
- });
322
-
323
- app.post('/api/users', (req, res) => {
324
- // 从请求头获取用户语言偏好
325
- const locale = req.headers['accept-language'] || 'en-US';
326
-
327
- // 验证(直接切换语言,无需加载)
328
- const result = validate(userSchema, req.body, { locale });
329
-
330
- if (!result.valid) {
331
- return res.status(400).json({
332
- errors: result.errors // 自动使用用户偏好的语言
333
- });
334
- }
335
-
336
- // 处理请求...
337
- });
338
- ```
339
-
340
- ---
341
-
342
- ## 📋 完整的消息键列表
343
-
344
- ### 通用键
345
-
346
- | 键名 | 说明 | 示例 |
347
- |-----|------|------|
348
- | `required` | 必填字段 | `{{#label}} é obrigatório` |
349
- | `type` | 类型错误 | `{{#label}} deve ser do tipo {{#expected}}` |
350
- | `min` | 最小长度(通用) | `{{#label}} deve ter pelo menos {{#limit}} caracteres` |
351
- | `max` | 最大长度(通用) | `{{#label}} não pode exceder {{#limit}} caracteres` |
352
- | `length` | 精确长度 | `{{#label}} deve ter exatamente {{#limit}} caracteres` |
353
- | `pattern` | 模式匹配 | `{{#label}} formato inválido` |
354
- | `enum` | 枚举值 | `{{#label}} deve ser um dos seguintes: {{#allowed}}` |
355
-
356
- ### 字符串验证键
357
-
358
- | 键名 | 说明 | 可用变量 |
359
- |-----|------|---------|
360
- | `string.minLength` | 最小长度 | `{{#label}}`, `{{#limit}}` |
361
- | `string.maxLength` | 最大长度 | `{{#label}}`, `{{#limit}}` |
362
- | `string.length` | 精确长度 | `{{#label}}`, `{{#limit}}` |
363
- | `string.pattern` | 模式匹配 | `{{#label}}` |
364
- | `string.alphanum` | 字母数字 | `{{#label}}` |
365
- | `string.enum` | 枚举值 | `{{#label}}`, `{{#valids}}` |
366
-
367
- ### 数字验证键
368
-
369
- | 键名 | 说明 | 可用变量 |
370
- |-----|------|---------|
371
- | `number.base` | 类型错误 | `{{#label}}` |
372
- | `number.min` | 最小值 | `{{#label}}`, `{{#limit}}` |
373
- | `number.max` | 最大值 | `{{#label}}`, `{{#limit}}` |
374
- | `number.integer` | 整数 | `{{#label}}` |
375
- | `number.positive` | 正数 | `{{#label}}` |
376
- | `number.negative` | 负数 | `{{#label}}` |
377
- | `number.precision` | 小数精度 | `{{#label}}`, `{{#limit}}` |
378
- | `number.port` | 端口号 | `{{#label}}` |
379
-
380
- ### 格式验证键
381
-
382
- | 键名 | 说明 |
383
- |-----|------|
384
- | `format.email` | 邮箱格式 |
385
- | `format.url` | URL格式 |
386
- | `format.uuid` | UUID格式 |
387
- | `format.date` | 日期格式 |
388
- | `format.datetime` | 日期时间格式 |
389
- | `format.time` | 时间格式 |
390
- | `format.ipv4` | IPv4地址 |
391
- | `format.ipv6` | IPv6地址 |
392
- | `format.binary` | Base64编码 |
393
-
394
- ### 自定义模式键
395
-
396
- | 键名 | 说明 |
397
- |-----|------|
398
- | `pattern.phone.cn` | 中国手机号 |
399
- | `pattern.phone.us` | 美国电话号 |
400
- | `pattern.idCard.cn` | 中国身份证 |
401
- | `pattern.creditCard` | 信用卡号 |
402
- | `pattern.objectId` | MongoDB ObjectId |
403
- | `pattern.hexColor` | 十六进制颜色 |
404
- | `pattern.macAddress` | MAC地址 |
405
- | `pattern.cron` | Cron表达式 |
406
- | `pattern.slug` | URL别名 |
407
- | `pattern.username` | 用户名 |
408
- | `pattern.password.weak` | 弱密码 |
409
- | `pattern.password.medium` | 中等密码 |
410
- | `pattern.password.strong` | 强密码 |
411
- | `pattern.password.veryStrong` | 超强密码 |
412
-
413
- ---
414
-
415
- ## 🎨 模板变量
416
-
417
- 所有错误消息支持以下模板变量:
418
-
419
- | 变量 | 说明 | 使用示例 |
420
- |------|------|---------|
421
- | `{{#label}}` | 字段标签 | `{{#label}} é obrigatório` |
422
- | `{{#limit}}` | 限制值(长度/大小) | `deve ter pelo menos {{#limit}} caracteres` |
423
- | `{{#allowed}}` | 允许的值列表 | `deve ser um dos seguintes: {{#allowed}}` |
424
- | `{{#expected}}` | 期望的类型 | `deve ser do tipo {{#expected}}` |
425
- | `{{#valids}}` | 有效值列表(数组) | `deve ser: {{#valids}}` |
426
- | `{{#path}}` | 字段路径 | `Erro no campo {{#path}}` |
427
-
428
- ---
429
-
430
- ## 📚 参考内置语言包
431
-
432
- 你可以参考内置的语言包作为模板:
433
-
434
- ```javascript
435
- // 查看中文语言包
436
- const zhCN = require('schema-dsl/lib/locales/zh-CN');
437
- console.log(zhCN);
438
-
439
- // 查看英文语言包
440
- const enUS = require('schema-dsl/lib/locales/en-US');
441
- console.log(enUS);
442
- ```
443
-
444
- 或者直接查看源码:
445
- - 中文:`node_modules/schema-dsl/lib/locales/zh-CN.js`
446
- - 英文:`node_modules/schema-dsl/lib/locales/en-US.js`
447
- - 日语:`node_modules/schema-dsl/lib/locales/ja-JP.js`
448
- - 西班牙语:`node_modules/schema-dsl/lib/locales/es-ES.js`
449
- - 法语:`node_modules/schema-dsl/lib/locales/fr-FR.js`
450
-
451
- ---
452
-
453
- ## ✅ 最佳实践
454
-
455
- 1. **完整性**:确保翻译所有常用的错误消息键
456
- 2. **一致性**:保持错误消息风格统一
457
- 3. **模板变量**:正确使用 `{{#label}}`、`{{#limit}}` 等变量
458
- 4. **测试**:添加语言包后进行测试,确保所有消息正确显示
459
- 5. **文档**:为自定义语言包编写使用说明
460
-
461
- ---
462
-
463
- ## 🤝 贡献语言包
464
-
465
- 如果你为 schema-dsl 添加了新语言包,欢迎提交 Pull Request:
466
-
467
- 1. Fork 项目
468
- 2. 在 `lib/locales/` 目录创建新语言文件(如 `pt-BR.js`)
469
- 3. 完整翻译所有消息键
470
- 4. 在 `lib/locales/index.js` 中注册新语言
471
- 5. 添加测试用例(在 `test/unit/locales/` 目录)
472
- 6. 提交 Pull Request
473
-
474
- ---
475
-
476
- ## 📞 支持
477
-
478
- 如果你在添加语言包时遇到问题:
479
-
480
- - 查看 [多语言配置指南](./i18n.md)
481
- - 查看 [动态多语言配置指南](./dynamic-locale.md)
482
- - 提交 Issue: https://github.com/your-repo/schema-dsl/issues
483
-
1
+ # 添加自定义语言包指南
2
+
3
+ **版本**: v2.0.0-beta.1
4
+ **最后更新**: 2026-04-30
5
+
6
+ ---
7
+
8
+ ## 📖 概述
9
+
10
+ 本指南将教你如何为 schema-dsl 添加自定义语言包或扩展现有语言。
11
+
12
+ > **Node.js 要求**:`>=18.0.0`
13
+ >
14
+ > **目录加载(Node >=18)默认支持的语言文件格式**:`.js`(CommonJS)、`.cjs`、`.json`、`.jsonc`、`.json5`。
15
+ > **推荐**:如果你的应用是 `type: module` / ESM 项目,优先使用 `.cjs`、`.json`、`.jsonc`、`.json5`。
16
+
17
+ ---
18
+
19
+ ## 🏗️ 多人协作:子目录拆分语言包(v1.2.3 新增)⭐
20
+
21
+ > **适用场景**:多人/多模块开发,避免所有语言 key 堆在同一文件产生 Git 冲突和 code 码冲突。
22
+
23
+ ### 目录结构
24
+
25
+ ```bash
26
+ project/
27
+ ├── locales/
28
+ ├── core/ # 公共 code 段:1000-1999(框架层维护)
29
+ ├── zh-CN.cjs
30
+ │ │ └── en-US.jsonc
31
+ ├── account/ # 账户模块 code 段:10000-10999(开发者A)
32
+ │ ├── zh-CN.cjs
33
+ │ └── en-US.jsonc
34
+ ├── order/ # 订单模块 code 段:20000-20999(开发者B)
35
+ │ │ ├── zh-CN.json5
36
+ │ │ └── en-US.json5
37
+ │ └── payment/ # 支付模块 code 段:30000-30999(开发者C)
38
+ │ ├── zh-CN.cjs
39
+ │ └── en-US.cjs
40
+ └── app.js
41
+ ```
42
+
43
+ ### 每个模块独立维护自己的语言文件
44
+
45
+ ```javascript
46
+ // locales/account/zh-CN.cjs — 开发者A 独立维护,互不干扰
47
+ module.exports = {
48
+ 'account.notFound': { code: 10001, message: '账户不存在' },
49
+ 'account.locked': { code: 10002, message: '账户已锁定' },
50
+ };
51
+
52
+ // locales/order/zh-CN.json5 — 开发者B 独立维护
53
+ const orderZhCN = {
54
+ 'order.notFound': { code: 20001, message: '订单不存在' },
55
+ 'order.notPaid': { code: 20002, message: '订单未支付' },
56
+ }
57
+ ```
58
+
59
+ ### 应用启动:一行配置,自动递归合并
60
+
61
+ ```javascript
62
+ // app.js
63
+ const { dsl, validate } = require('schema-dsl');
64
+ const path = require('path');
65
+
66
+ // 自动递归扫描 locales/ 下所有子目录,同语言文件合并为一个完整语言包
67
+ dsl.config({
68
+ i18n: path.join(__dirname, 'locales')
69
+ });
70
+ ```
71
+
72
+ > - 子目录名(`account/`、`order/`)仅作为**模块组织层**,不影响最终语言 key 命名
73
+ > - 加载顺序:按文件系统字母序递归扫描
74
+ > - 同语言 key 出现重复时:默认打 `WARN` 日志,可开启严格模式阻断启动
75
+
76
+ ### 严格模式:key 冲突时阻断启动(推荐 CI 环境)
77
+
78
+ ```javascript
79
+ dsl.config({
80
+ i18n: path.join(__dirname, 'locales'),
81
+ strict: true // 同名 key 冲突时直接抛 Error,防止静默覆盖
82
+ });
83
+
84
+ // 冲突示例输出:
85
+ // Error: [schema-dsl] i18n key 冲突 in locale 'zh-CN'
86
+ // 冲突 key: account.notFound
87
+ // 来源文件: /project/locales/account/zh-CN.cjs
88
+ ```
89
+
90
+ ### Code 段划分建议
91
+
92
+ 多人开发时建议在项目根目录维护一份 `locales/CODE-SEGMENTS.md`,约定各模块的 code 号段:
93
+
94
+ | 模块 | code 范围 | 负责人 |
95
+ |------|----------|--------|
96
+ | core(公共) | 1000–1999 | 框架组 |
97
+ | account | 10000–10999 | 开发者A |
98
+ | order | 20000–20999 | 开发者B |
99
+ | payment | 30000–30999 | 开发者C |
100
+
101
+ > `CODE-SEGMENTS.md` / `CODE-SEGMENTS.js` 等非语言文件会被自动跳过,无需担心被误加载。
102
+
103
+ ---
104
+
105
+
106
+
107
+ ## 🚀 快速开始
108
+
109
+ ### 推荐方式:配置语言包目录(一次性加载所有语言)⭐
110
+
111
+ **正确的使用方式**:在应用启动时一次性加载所有语言包,运行时直接切换。
112
+
113
+ #### 第1步:创建语言包文件
114
+
115
+
116
+ #### 第1步:创建语言包文件
117
+
118
+ ```bash
119
+ # 项目结构
120
+ my-project/
121
+ ├── locales/ # 语言包目录
122
+ │ ├── zh-CN.cjs # 中文(CommonJS / ESM 项目都稳定)
123
+ │ ├── en-US.jsonc # 英文(带注释 / 末尾逗号)
124
+ │ └── pt-BR.json5 # 葡萄牙语(JSON5 风格)
125
+ └── app.js
126
+ ```
127
+
128
+ #### 第2步:定义语言包(`locales/pt-BR.json5`)
129
+
130
+ ```javascript
131
+ module.exports = {
132
+ // 通用验证错误
133
+ 'required': '{{#label}} é obrigatório',
134
+ 'type': '{{#label}} deve ser do tipo {{#expected}}',
135
+ 'min': '{{#label}} deve ter pelo menos {{#limit}} caracteres',
136
+ 'max': '{{#label}} não pode exceder {{#limit}} caracteres',
137
+ 'length': '{{#label}} deve ter exatamente {{#limit}} caracteres',
138
+ 'pattern': '{{#label}} formato inválido',
139
+ 'enum': '{{#label}} deve ser um dos seguintes valores: {{#allowed}}',
140
+
141
+ // 格式验证
142
+ 'format.email': '{{#label}} deve ser um e-mail válido',
143
+ 'format.url': '{{#label}} deve ser uma URL válida',
144
+ 'format.uuid': '{{#label}} deve ser um UUID válido',
145
+ 'format.date': '{{#label}} deve ser uma data válida (YYYY-MM-DD)',
146
+ 'format.datetime': '{{#label}} deve ser uma data/hora válida (ISO 8601)',
147
+
148
+ // 字符串验证
149
+ 'string.minLength': '{{#label}} deve ter pelo menos {{#limit}} caracteres',
150
+ 'string.maxLength': '{{#label}} não pode exceder {{#limit}} caracteres',
151
+ 'string.pattern': '{{#label}} formato inválido',
152
+ 'string.alphanum': '{{#label}} deve conter apenas letras e números',
153
+
154
+ // 数字验证
155
+ 'number.base': '{{#label}} deve ser um número',
156
+ 'number.min': '{{#label}} não pode ser menor que {{#limit}}',
157
+ 'number.max': '{{#label}} não pode ser maior que {{#limit}}',
158
+ 'number.integer': '{{#label}} deve ser um inteiro',
159
+ 'number.positive': '{{#label}} deve ser um número positivo',
160
+ 'number.negative': '{{#label}} deve ser um número negativo',
161
+
162
+ // 布尔验证
163
+ 'boolean.base': '{{#label}} deve ser um booleano',
164
+
165
+ // 对象验证
166
+ 'object.base': '{{#label}} deve ser um objeto',
167
+
168
+ // 数组验证
169
+ 'array.base': '{{#label}} deve ser um array',
170
+ 'array.min': '{{#label}} deve ter pelo menos {{#limit}} itens',
171
+ 'array.max': '{{#label}} não pode ter mais de {{#limit}} itens',
172
+
173
+ // 日期验证
174
+ 'date.base': '{{#label}} deve ser uma data válida',
175
+ 'date.min': '{{#label}} não pode ser anterior a {{#limit}}',
176
+ 'date.max': '{{#label}} não pode ser posterior a {{#limit}}',
177
+
178
+ // 自定义模式
179
+ 'pattern.phone.cn': 'Número de telefone inválido',
180
+ 'pattern.idCard.cn': 'Número de identidade inválido',
181
+ 'pattern.creditCard': 'Número de cartão de crédito inválido',
182
+ 'pattern.objectId': 'ObjectId inválido',
183
+ 'pattern.hexColor': 'Código de cor hexadecimal inválido',
184
+ 'pattern.macAddress': 'Endereço MAC inválido',
185
+ 'pattern.cron': 'Expressão Cron inválida',
186
+ 'pattern.slug': 'Slug deve conter apenas letras minúsculas, números e hífens'
187
+ };
188
+ ```
189
+
190
+ #### 第3步:应用启动时一次性加载所有语言
191
+
192
+ ```javascript
193
+ const { dsl, validate } = require('schema-dsl');
194
+ const path = require('path');
195
+
196
+ // ========== 应用启动时配置(只执行一次)==========
197
+ dsl.config({
198
+ i18n: path.join(__dirname, 'locales') // 自动加载目录下所有语言文件
199
+ });
200
+
201
+ // 说明:
202
+ // 1. 自动扫描 locales/ 目录下的 `.js`(CommonJS)、`.cjs`、`.json`、`.jsonc`、`.json5`
203
+ // 2. 从文件名提取语言代码(如 pt-BR.cjs pt-BR)
204
+ // 3. 自动加载并注册所有语言包
205
+ // 4. 用户自定义的语言包会与系统默认语言包合并,用户的优先
206
+
207
+ // ========== 运行时直接切换语言(无需重新加载)==========
208
+ const schema = dsl({ username: 'string:3-32!' });
209
+
210
+ // 使用葡萄牙语
211
+ const result1 = validate(schema, { username: 'ab' }, { locale: 'pt-BR' });
212
+ // 错误消息: "username deve ter pelo menos 3 caracteres"
213
+
214
+ // 使用中文
215
+ const result2 = validate(schema, { username: 'ab' }, { locale: 'zh-CN' });
216
+ // 错误消息: "username长度不能少于3个字符"
217
+
218
+ // 使用英文
219
+ const result3 = validate(schema, { username: 'ab' }, { locale: 'en-US' });
220
+ // 错误消息: "username length must be at least 3"
221
+ ```
222
+
223
+ #### 语言包合并策略
224
+
225
+ ```javascript
226
+ // 系统内置的 zh-CN 语言包
227
+ const systemZhCN = {
228
+ 'required': '{{#label}}是必填项',
229
+ 'string.minLength': '{{#label}}长度不能少于{{#limit}}个字符'
230
+ };
231
+
232
+ // 用户自定义的 locales/zh-CN.cjs
233
+ const userZhCN = {
234
+ 'required': '{{#label}}必须填写', // 覆盖系统默认
235
+ 'custom.myError': '自定义错误' // 新增自定义消息
236
+ };
237
+
238
+ // 最终合并结果(深度合并)
239
+ const finalZhCN = {
240
+ 'required': '{{#label}}必须填写', // ✅ 用户的优先
241
+ 'string.minLength': '{{#label}}长度不能少于{{#limit}}个字符', // 保留系统默认
242
+ 'custom.myError': '自定义错误' // 新增自定义消息
243
+ };
244
+ ```
245
+
246
+ ---
247
+
248
+ ### 方式2:直接传入对象(适合动态配置)
249
+
250
+ ```javascript
251
+ const { dsl } = require('schema-dsl');
252
+
253
+ // 应用启动时配置
254
+ dsl.config({
255
+ i18n: {
256
+ 'pt-BR': require('./locales/pt-BR'),
257
+ 'de-DE': require('./locales/de-DE'),
258
+ 'ko-KR': require('./locales/ko-KR')
259
+ }
260
+ });
261
+
262
+ // 运行时直接切换
263
+ validate(schema, data, { locale: 'pt-BR' });
264
+ validate(schema, data, { locale: 'de-DE' });
265
+ ```
266
+
267
+ ---
268
+
269
+ ## ⚠️ 错误示例(不推荐)
270
+
271
+ ###错误:运行时单个加载语言包
272
+
273
+ ```javascript
274
+ const { Locale } = require('schema-dsl');
275
+
276
+ // 不推荐:在每次验证前动态加载
277
+ function validateUser(data, locale) {
278
+ if (locale === 'pt-BR') {
279
+ Locale.addLocale('pt-BR', require('./locales/pt-BR')); // 每次都加载,性能差
280
+ }
281
+ return validate(schema, data, { locale });
282
+ }
283
+ ```
284
+
285
+ ```javascript
286
+ // ✅ 正确:应用启动时一次性加载
287
+ // app.js 启动入口
288
+ dsl.config({ i18n: './locales' }); // 只加载一次
289
+
290
+ // 运行时直接切换,无需重新加载
291
+ function validateUser(data, locale) {
292
+ return validate(schema, data, { locale }); // ✅ 直接切换,性能好
293
+ }
294
+ ```
295
+
296
+ ### 为什么推荐"首次加载,运行时切换"?
297
+
298
+ | 方式 | 加载次数 | 性能 | 内存 | 推荐度 |
299
+ |------|---------|------|------|--------|
300
+ | **首次加载所有** | 1次 | ⭐⭐⭐⭐⭐ 极快 | 低 | ✅ 强烈推荐 |
301
+ | 运行时单个加载 | N次 | ⭐⭐ 慢 | 中 | ❌ 不推荐 |
302
+
303
+ ---
304
+
305
+ ## 🎯 完整示例
306
+
307
+ ```javascript
308
+ // ========== app.js(应用启动入口)==========
309
+ const express = require('express');
310
+ const { dsl, validate } = require('schema-dsl');
311
+ const path = require('path');
312
+
313
+ // 应用启动时一次性加载所有语言包
314
+ dsl.config({
315
+ i18n: path.join(__dirname, 'locales')
316
+ });
317
+
318
+ const app = express();
319
+
320
+ // ========== routes/user.js(业务路由)==========
321
+ const userSchema = dsl({
322
+ username: 'string:3-32!',
323
+ email: 'email!',
324
+ age: 'number:18-120'
325
+ });
326
+
327
+ app.post('/api/users', (req, res) => {
328
+ // 从请求头获取用户语言偏好
329
+ const locale = req.headers['accept-language']?.split(',')[0]?.trim() || 'en-US';
330
+
331
+ // 验证(直接切换语言,无需加载)
332
+ const result = validate(userSchema, req.body, { locale });
333
+
334
+ if (!result.valid) {
335
+ return res.status(400).json({
336
+ errors: result.errors // 自动使用用户偏好的语言
337
+ });
338
+ }
339
+
340
+ // 处理请求...
341
+ });
342
+ ```
343
+
344
+ ---
345
+
346
+ ## 📋 完整的消息键列表
347
+
348
+ ### 通用键
349
+
350
+ | 键名 | 说明 | 示例 |
351
+ |-----|------|------|
352
+ | `required` | 必填字段 | `{{#label}} é obrigatório` |
353
+ | `type` | 类型错误 | `{{#label}} deve ser do tipo {{#expected}}` |
354
+ | `min` | 最小长度(通用) | `{{#label}} deve ter pelo menos {{#limit}} caracteres` |
355
+ | `max` | 最大长度(通用) | `{{#label}} não pode exceder {{#limit}} caracteres` |
356
+ | `length` | 精确长度 | `{{#label}} deve ter exatamente {{#limit}} caracteres` |
357
+ | `pattern` | 模式匹配 | `{{#label}} formato inválido` |
358
+ | `enum` | 枚举值 | `{{#label}} deve ser um dos seguintes: {{#allowed}}` |
359
+
360
+ ### 字符串验证键
361
+
362
+ | 键名 | 说明 | 可用变量 |
363
+ |-----|------|---------|
364
+ | `string.minLength` | 最小长度 | `{{#label}}`, `{{#limit}}` |
365
+ | `string.maxLength` | 最大长度 | `{{#label}}`, `{{#limit}}` |
366
+ | `string.length` | 精确长度 | `{{#label}}`, `{{#limit}}` |
367
+ | `string.pattern` | 模式匹配 | `{{#label}}` |
368
+ | `string.alphanum` | 字母数字 | `{{#label}}` |
369
+ | `string.enum` | 枚举值 | `{{#label}}`, `{{#valids}}` |
370
+
371
+ ### 数字验证键
372
+
373
+ | 键名 | 说明 | 可用变量 |
374
+ |-----|------|---------|
375
+ | `number.base` | 类型错误 | `{{#label}}` |
376
+ | `number.min` | 最小值 | `{{#label}}`, `{{#limit}}` |
377
+ | `number.max` | 最大值 | `{{#label}}`, `{{#limit}}` |
378
+ | `number.integer` | 整数 | `{{#label}}` |
379
+ | `number.positive` | 正数 | `{{#label}}` |
380
+ | `number.negative` | 负数 | `{{#label}}` |
381
+ | `number.precision` | 小数精度 | `{{#label}}`, `{{#limit}}` |
382
+ | `number.port` | 端口号 | `{{#label}}` |
383
+
384
+ ### 格式验证键
385
+
386
+ | 键名 | 说明 |
387
+ |-----|------|
388
+ | `format.email` | 邮箱格式 |
389
+ | `format.url` | URL格式 |
390
+ | `format.uuid` | UUID格式 |
391
+ | `format.date` | 日期格式 |
392
+ | `format.datetime` | 日期时间格式 |
393
+ | `format.time` | 时间格式 |
394
+ | `format.ipv4` | IPv4地址 |
395
+ | `format.ipv6` | IPv6地址 |
396
+ | `format.binary` | Base64编码 |
397
+
398
+ ### 自定义模式键
399
+
400
+ | 键名 | 说明 |
401
+ |-----|------|
402
+ | `pattern.phone.cn` | 中国手机号 |
403
+ | `pattern.phone.us` | 美国电话号 |
404
+ | `pattern.idCard.cn` | 中国身份证 |
405
+ | `pattern.creditCard` | 信用卡号 |
406
+ | `pattern.objectId` | MongoDB ObjectId |
407
+ | `pattern.hexColor` | 十六进制颜色 |
408
+ | `pattern.macAddress` | MAC地址 |
409
+ | `pattern.cron` | Cron表达式 |
410
+ | `pattern.slug` | URL别名 |
411
+ | `pattern.username` | 用户名 |
412
+ | `pattern.password.weak` | 弱密码 |
413
+ | `pattern.password.medium` | 中等密码 |
414
+ | `pattern.password.strong` | 强密码 |
415
+ | `pattern.password.veryStrong` | 超强密码 |
416
+
417
+ ---
418
+
419
+ ## 🎨 模板变量
420
+
421
+ 所有错误消息支持以下模板变量:
422
+
423
+ | 变量 | 说明 | 使用示例 |
424
+ |------|------|---------|
425
+ | `{{#label}}` | 字段标签 | `{{#label}} é obrigatório` |
426
+ | `{{#limit}}` | 限制值(长度/大小) | `deve ter pelo menos {{#limit}} caracteres` |
427
+ | `{{#allowed}}` | 允许的值列表 | `deve ser um dos seguintes: {{#allowed}}` |
428
+ | `{{#expected}}` | 期望的类型 | `deve ser do tipo {{#expected}}` |
429
+ | `{{#valids}}` | 有效值列表(数组) | `deve ser: {{#valids}}` |
430
+ | `{{#path}}` | 字段路径 | `Erro no campo {{#path}}` |
431
+
432
+ ---
433
+
434
+ ## 📚 参考内置语言包
435
+
436
+ 你可以参考内置的语言包作为模板:
437
+
438
+ ```javascript
439
+ const { Locale } = require('schema-dsl');
440
+
441
+ // 查看中文语言包
442
+ const zhCN = Locale.getMessages('zh-CN');
443
+ console.log(zhCN);
444
+
445
+ // 查看英文语言包
446
+ const enUS = Locale.getMessages('en-US');
447
+ console.log(enUS);
448
+ ```
449
+
450
+ 或者直接查看源码:
451
+ - 中文:`src/locales/zh-CN.ts`
452
+ - 英文:`src/locales/en-US.ts`
453
+ - 日语:`src/locales/ja-JP.ts`
454
+ - 西班牙语:`src/locales/es-ES.ts`
455
+ - 法语:`src/locales/fr-FR.ts`
456
+
457
+ ---
458
+
459
+ ## ✅ 最佳实践
460
+
461
+ 1. **完整性**:确保翻译所有常用的错误消息键
462
+ 2. **一致性**:保持错误消息风格统一
463
+ 3. **模板变量**:正确使用 `{{#label}}`、`{{#limit}}` 等变量
464
+ 4. **测试**:添加语言包后进行测试,确保所有消息正确显示
465
+ 5. **文档**:为自定义语言包编写使用说明
466
+
467
+ ---
468
+
469
+ ## 🤝 贡献语言包
470
+
471
+ 如果你为 schema-dsl 添加了新语言包,欢迎提交 Pull Request:
472
+
473
+ 1. Fork 项目
474
+ 2. 在 `src/locales/` 目录创建新语言文件(如 `pt-BR.ts`)
475
+ 3. 完整翻译所有消息键
476
+ 4. `src/locales/index.ts` 中注册新语言
477
+ 5. 添加测试用例(在 `test/unit/locales/` 目录)
478
+ 6. 提交 Pull Request
479
+
480
+ ---
481
+
482
+ ## 📞 支持
483
+
484
+ 如果你在添加语言包时遇到问题:
485
+
486
+ - 查看 [多语言配置指南](./i18n.md)
487
+ - 查看 [动态多语言配置指南](./dynamic-locale.md)
488
+ - 提交 Issue: https://github.com/vextjs/schema-dsl/issues
489
+
490
+ ---
491
+
492
+ ## 对应示例文件
493
+
494
+ **示例入口**: [add-custom-locale.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/add-custom-locale.ts)
495
+ **说明**: 覆盖 `Locale.addLocale()` 注册新语言、读取消息文本,以及在自定义 locale 下执行验证的最小工作流。
496
+