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