schema-dsl 2.3.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.
- package/.eslintignore +10 -0
- package/.eslintrc.json +27 -0
- package/.github/CODE_OF_CONDUCT.md +45 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
- package/.github/SECURITY.md +184 -0
- package/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.md +633 -0
- package/CONTRIBUTING.md +368 -0
- package/LICENSE +21 -0
- package/README.md +1122 -0
- package/STATUS.md +273 -0
- package/docs/FEATURE-INDEX.md +521 -0
- package/docs/INDEX.md +224 -0
- package/docs/api-reference.md +1098 -0
- package/docs/best-practices.md +672 -0
- package/docs/cache-manager.md +336 -0
- package/docs/design-philosophy.md +602 -0
- package/docs/dsl-syntax.md +654 -0
- package/docs/dynamic-locale.md +552 -0
- package/docs/error-handling.md +703 -0
- package/docs/export-guide.md +459 -0
- package/docs/faq.md +576 -0
- package/docs/frontend-i18n-guide.md +290 -0
- package/docs/i18n-user-guide.md +488 -0
- package/docs/label-vs-description.md +262 -0
- package/docs/markdown-exporter.md +398 -0
- package/docs/mongodb-exporter.md +279 -0
- package/docs/multi-type-support.md +319 -0
- package/docs/mysql-exporter.md +257 -0
- package/docs/plugin-system.md +542 -0
- package/docs/postgresql-exporter.md +290 -0
- package/docs/quick-start.md +761 -0
- package/docs/schema-helper.md +340 -0
- package/docs/schema-utils.md +492 -0
- package/docs/string-extensions.md +480 -0
- package/docs/troubleshooting.md +471 -0
- package/docs/type-converter.md +319 -0
- package/docs/type-reference.md +219 -0
- package/docs/validate.md +486 -0
- package/docs/validation-guide.md +484 -0
- package/examples/array-dsl-example.js +227 -0
- package/examples/custom-extension.js +85 -0
- package/examples/dsl-match-example.js +74 -0
- package/examples/dsl-style.js +118 -0
- package/examples/dynamic-locale-configuration.js +348 -0
- package/examples/dynamic-locale-example.js +287 -0
- package/examples/export-demo.js +130 -0
- package/examples/i18n-full-demo.js +310 -0
- package/examples/i18n-memory-safety.examples.js +268 -0
- package/examples/markdown-export.js +71 -0
- package/examples/middleware-usage.js +93 -0
- package/examples/password-reset/README.md +153 -0
- package/examples/password-reset/schema.js +26 -0
- package/examples/password-reset/test.js +101 -0
- package/examples/plugin-system.examples.js +205 -0
- package/examples/simple-example.js +122 -0
- package/examples/string-extensions.js +297 -0
- package/examples/user-registration/README.md +156 -0
- package/examples/user-registration/routes.js +92 -0
- package/examples/user-registration/schema.js +150 -0
- package/examples/user-registration/server.js +74 -0
- package/index.d.ts +1999 -0
- package/index.js +270 -0
- package/index.mjs +30 -0
- package/lib/adapters/DslAdapter.js +653 -0
- package/lib/adapters/index.js +20 -0
- package/lib/config/constants.js +286 -0
- package/lib/config/patterns/creditCard.js +9 -0
- package/lib/config/patterns/idCard.js +9 -0
- package/lib/config/patterns/index.js +8 -0
- package/lib/config/patterns/licensePlate.js +4 -0
- package/lib/config/patterns/passport.js +4 -0
- package/lib/config/patterns/phone.js +9 -0
- package/lib/config/patterns/postalCode.js +5 -0
- package/lib/core/CacheManager.js +376 -0
- package/lib/core/DslBuilder.js +740 -0
- package/lib/core/ErrorCodes.js +233 -0
- package/lib/core/ErrorFormatter.js +342 -0
- package/lib/core/JSONSchemaCore.js +347 -0
- package/lib/core/Locale.js +119 -0
- package/lib/core/MessageTemplate.js +89 -0
- package/lib/core/PluginManager.js +448 -0
- package/lib/core/StringExtensions.js +209 -0
- package/lib/core/Validator.js +316 -0
- package/lib/exporters/MarkdownExporter.js +420 -0
- package/lib/exporters/MongoDBExporter.js +162 -0
- package/lib/exporters/MySQLExporter.js +212 -0
- package/lib/exporters/PostgreSQLExporter.js +289 -0
- package/lib/exporters/index.js +24 -0
- package/lib/locales/en-US.js +65 -0
- package/lib/locales/es-ES.js +66 -0
- package/lib/locales/fr-FR.js +66 -0
- package/lib/locales/index.js +8 -0
- package/lib/locales/ja-JP.js +66 -0
- package/lib/locales/zh-CN.js +93 -0
- package/lib/utils/LRUCache.js +174 -0
- package/lib/utils/SchemaHelper.js +240 -0
- package/lib/utils/SchemaUtils.js +313 -0
- package/lib/utils/TypeConverter.js +245 -0
- package/lib/utils/index.js +13 -0
- package/lib/validators/CustomKeywords.js +203 -0
- package/lib/validators/index.js +11 -0
- package/package.json +70 -0
- package/plugins/custom-format.js +101 -0
- package/plugins/custom-validator.js +200 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# 前端动态切换语言 - 最佳实践指南
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
> **场景**: 前后端分离架构,前端需要动态切换验证错误消息语言
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📋 目录
|
|
9
|
+
|
|
10
|
+
1. [使用方法](#使用方法)
|
|
11
|
+
2. [完整示例](#完整示例)
|
|
12
|
+
3. [常见问题](#常见问题)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 完整示例
|
|
17
|
+
|
|
18
|
+
### 示例1:完整的 Express 应用
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// server.js
|
|
22
|
+
const express = require('express');
|
|
23
|
+
const cors = require('cors');
|
|
24
|
+
const { Validator, dsl } = require('schema-dsl');
|
|
25
|
+
|
|
26
|
+
const app = express();
|
|
27
|
+
app.use(cors());
|
|
28
|
+
app.use(express.json());
|
|
29
|
+
|
|
30
|
+
// 语言中间件
|
|
31
|
+
app.use((req, res, next) => {
|
|
32
|
+
const acceptLanguage = req.headers['accept-language'] || 'en-US';
|
|
33
|
+
const locale = acceptLanguage.split(',')[0].trim();
|
|
34
|
+
|
|
35
|
+
req.validator = new Validator({
|
|
36
|
+
locale,
|
|
37
|
+
allErrors: true // 返回所有错误
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
next();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Schema 定义
|
|
44
|
+
const schemas = {
|
|
45
|
+
user: dsl({
|
|
46
|
+
username: 'string:3-32!',
|
|
47
|
+
email: 'email!',
|
|
48
|
+
password: 'string:8-64!',
|
|
49
|
+
age: 'number:18-120',
|
|
50
|
+
phone: 'string'
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
post: dsl({
|
|
54
|
+
title: 'string:1-200!',
|
|
55
|
+
content: 'string:10-10000!',
|
|
56
|
+
tags: 'array:1-5<string:1-20>'
|
|
57
|
+
})
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// 通用验证端点
|
|
61
|
+
app.post('/api/validate/:type', (req, res) => {
|
|
62
|
+
const { type } = req.params;
|
|
63
|
+
const schema = schemas[type];
|
|
64
|
+
|
|
65
|
+
if (!schema) {
|
|
66
|
+
return res.status(404).json({ error: 'Schema not found' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = req.validator.validate(schema, req.body);
|
|
70
|
+
res.json(result);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 用户注册(带验证)
|
|
74
|
+
app.post('/api/register', (req, res) => {
|
|
75
|
+
const result = req.validator.validate(schemas.user, req.body);
|
|
76
|
+
|
|
77
|
+
if (!result.valid) {
|
|
78
|
+
return res.status(400).json({
|
|
79
|
+
success: false,
|
|
80
|
+
errors: result.errors
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 保存用户...
|
|
85
|
+
res.json({ success: true, message: '注册成功' });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
app.listen(3000, () => {
|
|
89
|
+
console.log('Server running on http://localhost:3000');
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 示例2:Vue 3 前端
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<template>
|
|
97
|
+
<div class="validation-form">
|
|
98
|
+
<!-- 语言切换 -->
|
|
99
|
+
<div class="language-selector">
|
|
100
|
+
<button
|
|
101
|
+
v-for="lang in languages"
|
|
102
|
+
:key="lang.code"
|
|
103
|
+
:class="{ active: locale === lang.code }"
|
|
104
|
+
@click="locale = lang.code"
|
|
105
|
+
>
|
|
106
|
+
{{ lang.label }}
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- 表单 -->
|
|
111
|
+
<form @submit.prevent="handleSubmit">
|
|
112
|
+
<div class="form-group">
|
|
113
|
+
<label>用户名</label>
|
|
114
|
+
<input v-model="form.username" />
|
|
115
|
+
<span v-if="getError('username')" class="error">
|
|
116
|
+
{{ getError('username') }}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="form-group">
|
|
121
|
+
<label>邮箱</label>
|
|
122
|
+
<input v-model="form.email" type="email" />
|
|
123
|
+
<span v-if="getError('email')" class="error">
|
|
124
|
+
{{ getError('email') }}
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div class="form-group">
|
|
129
|
+
<label>密码</label>
|
|
130
|
+
<input v-model="form.password" type="password" />
|
|
131
|
+
<span v-if="getError('password')" class="error">
|
|
132
|
+
{{ getError('password') }}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<button type="submit">提交</button>
|
|
137
|
+
</form>
|
|
138
|
+
</div>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
141
|
+
<script setup>
|
|
142
|
+
import { ref, reactive } from 'vue';
|
|
143
|
+
|
|
144
|
+
const locale = ref('zh-CN');
|
|
145
|
+
const languages = [
|
|
146
|
+
{ code: 'zh-CN', label: '中文' },
|
|
147
|
+
{ code: 'en-US', label: 'English' },
|
|
148
|
+
{ code: 'ja-JP', label: '日本語' }
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const form = reactive({
|
|
152
|
+
username: '',
|
|
153
|
+
email: '',
|
|
154
|
+
password: ''
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const errors = ref([]);
|
|
158
|
+
|
|
159
|
+
const handleSubmit = async () => {
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch('http://localhost:3000/api/validate/user', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'Accept-Language': locale.value
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify(form)
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await response.json();
|
|
171
|
+
|
|
172
|
+
if (!result.valid) {
|
|
173
|
+
errors.value = result.errors;
|
|
174
|
+
} else {
|
|
175
|
+
alert('验证通过!');
|
|
176
|
+
errors.value = [];
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('验证失败:', error);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const getError = (field) => {
|
|
184
|
+
const error = errors.value.find(e => e.path === field);
|
|
185
|
+
return error?.message;
|
|
186
|
+
};
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<style scoped>
|
|
190
|
+
.error {
|
|
191
|
+
color: red;
|
|
192
|
+
font-size: 0.9em;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.language-selector button.active {
|
|
196
|
+
background: #007bff;
|
|
197
|
+
color: white;
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 常见问题
|
|
205
|
+
|
|
206
|
+
### Q1: 为什么不能直接使用 `Locale.setLocale()`?
|
|
207
|
+
|
|
208
|
+
**A**: 因为 Node.js 是单线程异步的,多个请求可能同时修改全局状态,导致语言混乱。
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
// ❌ 错误示例
|
|
212
|
+
app.post('/api/validate', (req, res) => {
|
|
213
|
+
Locale.setLocale('zh-CN'); // 全局修改
|
|
214
|
+
// 如果此时另一个请求设置了 'en-US',当前请求可能得到英文消息
|
|
215
|
+
const result = validate(schema, req.body);
|
|
216
|
+
res.json(result);
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Q2: 每次请求创建 Validator 实例会影响性能吗?
|
|
221
|
+
|
|
222
|
+
**A**: 不会。Validator 实例创建非常轻量,且验证器内部有编译缓存。
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// 性能测试结果
|
|
226
|
+
// 创建实例: ~0.001ms
|
|
227
|
+
// 验证数据: ~0.1-1ms
|
|
228
|
+
// 总计: 可忽略不计
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Q3: 如何支持更多语言?
|
|
232
|
+
|
|
233
|
+
**A**: 使用 `Locale.addLocale()` 添加自定义语言包。
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
const { Locale } = require('schema-dsl');
|
|
237
|
+
|
|
238
|
+
Locale.addLocale('de-DE', {
|
|
239
|
+
required: '{{#label}} ist erforderlich',
|
|
240
|
+
'format.email': '{{#label}} muss eine gültige E-Mail-Adresse sein'
|
|
241
|
+
// ... 更多消息
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Q4: 如何在前端缓存语言包?
|
|
246
|
+
|
|
247
|
+
**A**: 后端返回错误消息已经是本地化的,前端无需处理。如果需要前端验证:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
// 前端可以使用相同的 SchemaIO(浏览器版)
|
|
251
|
+
import { dsl, validate } from 'schema-dsl/browser';
|
|
252
|
+
|
|
253
|
+
const schema = dsl({ /* ... */ });
|
|
254
|
+
const result = validate(schema, formData, {
|
|
255
|
+
locale: currentLocale
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Q5: 如何处理 Cookie 或 Session 中的语言?
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
// 中间件:优先级 Header > Cookie > Session > 默认
|
|
263
|
+
app.use((req, res, next) => {
|
|
264
|
+
const locale =
|
|
265
|
+
req.headers['accept-language'] ||
|
|
266
|
+
req.cookies?.locale ||
|
|
267
|
+
req.session?.locale ||
|
|
268
|
+
'en-US';
|
|
269
|
+
|
|
270
|
+
req.validator = new Validator({ locale });
|
|
271
|
+
next();
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## 总结
|
|
278
|
+
|
|
279
|
+
### ✅ 推荐做法
|
|
280
|
+
|
|
281
|
+
1. **使用实例级配置**:每个请求创建独立 Validator
|
|
282
|
+
2. **通过请求头传递语言**:符合 HTTP 标准
|
|
283
|
+
3. **使用中间件统一处理**:提高代码复用性
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
**相关文档**:
|
|
288
|
+
- [API 参考](api-reference.md)
|
|
289
|
+
- [最佳实践](best-practices.md)
|
|
290
|
+
|