schema-dsl 1.1.4 → 1.1.6
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 +30 -3
- package/README.md +149 -4
- package/STATUS.md +37 -3
- package/changelogs/v1.1.5.md +493 -0
- package/changelogs/v1.1.6.md +211 -0
- package/docs/error-handling.md +247 -2
- package/docs/runtime-locale-support.md +27 -0
- package/index.d.ts +77 -2
- package/lib/core/ErrorFormatter.js +6 -1
- package/lib/core/Locale.js +28 -22
- package/lib/core/MessageTemplate.js +30 -21
- package/lib/core/Validator.js +6 -1
- package/lib/errors/I18nError.js +38 -9
- package/lib/locales/en-US.js +15 -3
- package/lib/locales/es-ES.js +2 -0
- package/lib/locales/fr-FR.js +2 -0
- package/lib/locales/ja-JP.js +2 -0
- package/lib/locales/zh-CN.js +16 -3
- package/lib/validators/CustomKeywords.js +10 -2
- package/package.json +1 -1
- package/test-three-rounds-check.js +375 -0
package/docs/error-handling.md
CHANGED
|
@@ -688,16 +688,261 @@ if (!result.valid) {
|
|
|
688
688
|
|
|
689
689
|
---
|
|
690
690
|
|
|
691
|
+
## v1.1.5 新功能:对象格式错误配置
|
|
692
|
+
|
|
693
|
+
### 概述
|
|
694
|
+
|
|
695
|
+
从 v1.1.5 开始,语言包支持对象格式 `{ code, message }`,实现统一的错误代码管理。
|
|
696
|
+
|
|
697
|
+
### 基础用法
|
|
698
|
+
|
|
699
|
+
**语言包配置**:
|
|
700
|
+
```javascript
|
|
701
|
+
// lib/locales/zh-CN.js (或自定义语言包)
|
|
702
|
+
module.exports = {
|
|
703
|
+
// 字符串格式(向后兼容)
|
|
704
|
+
'user.notFound': '用户不存在',
|
|
705
|
+
|
|
706
|
+
// 对象格式(v1.1.5 新增)✨ - 使用数字错误码
|
|
707
|
+
'account.notFound': {
|
|
708
|
+
code: 40001,
|
|
709
|
+
message: '账户不存在'
|
|
710
|
+
},
|
|
711
|
+
'account.insufficientBalance': {
|
|
712
|
+
code: 40002,
|
|
713
|
+
message: '余额不足,当前余额{{#balance}},需要{{#required}}'
|
|
714
|
+
},
|
|
715
|
+
'order.notPaid': {
|
|
716
|
+
code: 50001,
|
|
717
|
+
message: '订单未支付'
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**使用示例**:
|
|
723
|
+
```javascript
|
|
724
|
+
const { dsl } = require('schema-dsl');
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
dsl.error.throw('account.notFound');
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.log(error.originalKey); // 'account.notFound'
|
|
730
|
+
console.log(error.code); // 40001 ✨ 数字错误码
|
|
731
|
+
console.log(error.message); // '账户不存在'
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### 核心特性
|
|
736
|
+
|
|
737
|
+
#### 1. originalKey 字段(新增)
|
|
738
|
+
|
|
739
|
+
保留原始的 key,便于调试和日志追踪:
|
|
740
|
+
|
|
741
|
+
```javascript
|
|
742
|
+
try {
|
|
743
|
+
dsl.error.throw('account.notFound');
|
|
744
|
+
} catch (error) {
|
|
745
|
+
error.originalKey // 'account.notFound' (原始 key)
|
|
746
|
+
error.code // 40001 (数字错误码)
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### 2. 多语言共享 code
|
|
751
|
+
|
|
752
|
+
不同语言使用相同的数字 `code`,便于前端统一处理:
|
|
753
|
+
|
|
754
|
+
```javascript
|
|
755
|
+
// zh-CN.js
|
|
756
|
+
'account.notFound': {
|
|
757
|
+
code: 40001, // ← 数字 code 一致
|
|
758
|
+
message: '账户不存在'
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// en-US.js
|
|
762
|
+
'account.notFound': {
|
|
763
|
+
code: 40001, // ← 数字 code 一致
|
|
764
|
+
message: 'Account not found'
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 前端处理 - 不受语言影响
|
|
768
|
+
switch (error.code) {
|
|
769
|
+
case 40001:
|
|
770
|
+
redirectToLogin();
|
|
771
|
+
break;
|
|
772
|
+
case 40002:
|
|
773
|
+
showTopUpDialog();
|
|
774
|
+
break;
|
|
775
|
+
case 50001:
|
|
776
|
+
showPaymentDialog();
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
#### 3. 增强的 error.is() 方法
|
|
780
|
+
|
|
781
|
+
同时支持 `originalKey` 和数字 `code` 判断:
|
|
782
|
+
|
|
783
|
+
```javascript
|
|
784
|
+
try {
|
|
785
|
+
dsl.error.throw('account.notFound');
|
|
786
|
+
} catch (error) {
|
|
787
|
+
// 两种方式都可以
|
|
788
|
+
if (error.is('account.notFound')) { } // ✅ 使用 originalKey
|
|
789
|
+
if (error.is(40001)) { } // ✅ 使用数字 code
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
#### 4. toJSON 包含 originalKey
|
|
794
|
+
|
|
795
|
+
```javascript
|
|
796
|
+
const json = error.toJSON();
|
|
797
|
+
// {
|
|
798
|
+
// error: 'I18nError',
|
|
799
|
+
// originalKey: 'account.notFound', // ✨ v1.1.5 新增
|
|
800
|
+
// code: 'ACCOUNT_NOT_FOUND',
|
|
801
|
+
// message: '账户不存在',
|
|
802
|
+
// params: {},
|
|
803
|
+
// statusCode: 400,
|
|
804
|
+
// locale: 'zh-CN'
|
|
805
|
+
// }
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### 向后兼容
|
|
809
|
+
|
|
810
|
+
**完全向后兼容** ✅ - 字符串格式自动转换:
|
|
811
|
+
|
|
812
|
+
```javascript
|
|
813
|
+
// 字符串格式(原有)
|
|
814
|
+
'user.notFound': '用户不存在'
|
|
815
|
+
|
|
816
|
+
// 自动转换为对象
|
|
817
|
+
dsl.error.throw('user.notFound');
|
|
818
|
+
// error.code = 'user.notFound' (使用 key 作为 code)
|
|
819
|
+
// error.originalKey = 'user.notFound'
|
|
820
|
+
// error.message = '用户不存在'
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### 最佳实践
|
|
824
|
+
|
|
825
|
+
#### 1. 何时使用对象格式
|
|
826
|
+
|
|
827
|
+
**推荐使用对象格式**:
|
|
828
|
+
- ✅ 需要在多语言中统一处理的错误
|
|
829
|
+
- ✅ 需要前端统一判断的错误
|
|
830
|
+
- ✅ 核心业务错误(账户、订单、支付等)
|
|
831
|
+
|
|
832
|
+
**可以使用字符串格式**:
|
|
833
|
+
- ✅ 简单的验证错误
|
|
834
|
+
- ✅ 内部错误(不暴露给前端)
|
|
835
|
+
- ✅ 不需要统一处理的错误
|
|
836
|
+
|
|
837
|
+
#### 2. 错误代码命名规范
|
|
838
|
+
|
|
839
|
+
推荐使用**数字错误码**,按模块分段:
|
|
840
|
+
|
|
841
|
+
```javascript
|
|
842
|
+
// 错误码规范(5位数字)
|
|
843
|
+
// 4xxxx - 客户端错误
|
|
844
|
+
// 5xxxx - 业务逻辑错误
|
|
845
|
+
// 6xxxx - 系统错误
|
|
846
|
+
|
|
847
|
+
'account.notFound': {
|
|
848
|
+
code: 40001, // ✅ 推荐:账户模块,序号001
|
|
849
|
+
message: '账户不存在'
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
'account.insufficientBalance': {
|
|
853
|
+
code: 40002, // 账户模块,序号002
|
|
854
|
+
message: '余额不足'
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
'order.notPaid': {
|
|
858
|
+
code: 50001, // ✅ 订单模块,序号001
|
|
859
|
+
message: '订单未支付'
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
'order.cancelled': {
|
|
863
|
+
code: 50002, // 订单模块,序号002
|
|
864
|
+
message: '订单已取消'
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
'database.connectionError': {
|
|
868
|
+
code: 60001, // ✅ 系统错误
|
|
869
|
+
message: '数据库连接失败'
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**错误码分段建议**:
|
|
874
|
+
- `40001-49999` - 客户端错误(账户、权限、参数验证等)
|
|
875
|
+
- `50001-59999` - 业务逻辑错误(订单、支付、库存等)
|
|
876
|
+
- `60001-69999` - 系统错误(数据库、服务不可用等)
|
|
877
|
+
|
|
878
|
+
#### 3. 前端统一错误处理
|
|
879
|
+
|
|
880
|
+
```javascript
|
|
881
|
+
// API 调用
|
|
882
|
+
try {
|
|
883
|
+
const response = await fetch('/api/account');
|
|
884
|
+
const data = await response.json();
|
|
885
|
+
} catch (error) {
|
|
886
|
+
// 使用数字 code 统一处理,不受语言影响
|
|
887
|
+
switch (error.code) {
|
|
888
|
+
case 40001: // ACCOUNT_NOT_FOUND
|
|
889
|
+
showNotFoundPage();
|
|
890
|
+
break;
|
|
891
|
+
case 40002: // INSUFFICIENT_BALANCE
|
|
892
|
+
showTopUpDialog(error.params);
|
|
893
|
+
break;
|
|
894
|
+
case 50001: // ORDER_NOT_PAID
|
|
895
|
+
showPaymentDialog();
|
|
896
|
+
break;
|
|
897
|
+
case 60001: // SYSTEM_ERROR
|
|
898
|
+
showSystemErrorPage();
|
|
899
|
+
break;
|
|
900
|
+
default:
|
|
901
|
+
showGenericError(error.message);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**更优雅的方式 - 错误码映射**:
|
|
907
|
+
```javascript
|
|
908
|
+
// errorCodeMap.js
|
|
909
|
+
const ERROR_HANDLERS = {
|
|
910
|
+
40001: () => router.push('/account-not-found'),
|
|
911
|
+
40002: (error) => showDialog('topup', error.params),
|
|
912
|
+
50001: (error) => showDialog('payment', error.params),
|
|
913
|
+
60001: () => showSystemErrorPage(),
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// 统一错误处理
|
|
917
|
+
function handleError(error) {
|
|
918
|
+
const handler = ERROR_HANDLERS[error.code];
|
|
919
|
+
if (handler) {
|
|
920
|
+
handler(error);
|
|
921
|
+
} else {
|
|
922
|
+
showGenericError(error.message);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### 更多信息
|
|
928
|
+
|
|
929
|
+
- [v1.1.5 完整变更日志](../changelogs/v1.1.5.md)
|
|
930
|
+
- [升级指南](../changelogs/v1.1.5.md#升级指南)
|
|
931
|
+
- [最佳实践](../changelogs/v1.1.5.md#最佳实践)
|
|
932
|
+
|
|
933
|
+
---
|
|
934
|
+
|
|
691
935
|
## 相关文档
|
|
692
936
|
|
|
693
937
|
- [API 参考文档](./api-reference.md)
|
|
694
938
|
- [DSL 语法指南](./dsl-syntax.md)
|
|
695
939
|
- [String 扩展文档](./string-extensions.md)
|
|
696
940
|
- [多语言配置](./dynamic-locale.md)
|
|
941
|
+
- [v1.1.5 变更日志](../changelogs/v1.1.5.md)
|
|
697
942
|
|
|
698
943
|
---
|
|
699
944
|
|
|
700
|
-
|
|
701
|
-
|
|
945
|
+
**最后更新**: 2026-01-17
|
|
946
|
+
**版本**: v1.1.5
|
|
702
947
|
|
|
703
948
|
|
|
@@ -11,6 +11,33 @@ schema-dsl 的 `dsl.error` 和 `I18nError` 现在支持**运行时指定语言**
|
|
|
11
11
|
|
|
12
12
|
这对于 **API 开发**特别有用,可以根据每个请求的语言偏好(如 `Accept-Language` 请求头)动态返回对应语言的错误消息。
|
|
13
13
|
|
|
14
|
+
### 🎨 支持的模板语法(v1.1.4+)
|
|
15
|
+
|
|
16
|
+
schema-dsl 现在支持**多种模板语法格式**,提供更好的兼容性:
|
|
17
|
+
|
|
18
|
+
| 语法格式 | 示例 | 说明 | 版本 |
|
|
19
|
+
|---------|------|------|------|
|
|
20
|
+
| `{{#variable}}` | `余额{{#balance}}元` | 井号格式(现有) | v1.0.0+ |
|
|
21
|
+
| `{{variable}}` | `余额{{balance}}元` | 无井号格式(新增) | v1.1.4+ |
|
|
22
|
+
| `{variable}` | `余额{balance}元` | 单花括号(新增) | v1.1.4+ |
|
|
23
|
+
| 混合格式 | `{{#user}}在{date}购买{{product}}` | 可混用多种格式 | v1.1.4+ |
|
|
24
|
+
|
|
25
|
+
**示例**:
|
|
26
|
+
```javascript
|
|
27
|
+
// 所有格式都支持
|
|
28
|
+
Locale.addLocale('zh-CN', {
|
|
29
|
+
'msg1': '余额不足,当前{{#balance}}元', // {{#}} 格式
|
|
30
|
+
'msg2': '用户{{name}}已登录', // {{}} 格式
|
|
31
|
+
'msg3': '订单{orderId}已支付', // {} 格式
|
|
32
|
+
'msg4': '{{#user}}在{date}购买了{{product}}' // 混合格式
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**向后兼容**:
|
|
37
|
+
- ✅ 现有的 `{{#variable}}` 格式完全兼容
|
|
38
|
+
- ✅ 所有单元测试通过(921个测试)
|
|
39
|
+
- ✅ 无破坏性变更
|
|
40
|
+
|
|
14
41
|
---
|
|
15
42
|
|
|
16
43
|
## 🎯 两种使用方式
|
package/index.d.ts
CHANGED
|
@@ -1,9 +1,64 @@
|
|
|
1
|
-
// Type definitions for schema-dsl v1.1.
|
|
1
|
+
// Type definitions for schema-dsl v1.1.5
|
|
2
2
|
// Project: https://github.com/vextjs/schema-dsl
|
|
3
3
|
// Definitions by: schema-dsl Team
|
|
4
4
|
|
|
5
5
|
// ========== 核心类型 ==========
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 错误消息配置(字符串或对象)
|
|
9
|
+
*
|
|
10
|
+
* @description v1.1.5 新增:支持对象格式配置错误代码和消息
|
|
11
|
+
*
|
|
12
|
+
* @example 字符串格式(向后兼容)
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const messages = {
|
|
15
|
+
* 'user.notFound': '用户不存在'
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example 对象格式(v1.1.5 新增)
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const messages = {
|
|
22
|
+
* 'account.notFound': {
|
|
23
|
+
* code: 'ACCOUNT_NOT_FOUND',
|
|
24
|
+
* message: '账户不存在'
|
|
25
|
+
* }
|
|
26
|
+
* };
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @since v1.1.5
|
|
30
|
+
*/
|
|
31
|
+
export type ErrorMessageConfig =
|
|
32
|
+
| string // 向后兼容:'账户不存在'
|
|
33
|
+
| { // 新格式:{ code, message }
|
|
34
|
+
/** 错误代码(可选,默认使用 key) */
|
|
35
|
+
code?: string;
|
|
36
|
+
/** 错误消息(必需) */
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 语言包定义
|
|
42
|
+
*
|
|
43
|
+
* @description 语言包对象,key 为错误代码,value 为错误消息配置
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const zhCN: LocaleMessages = {
|
|
48
|
+
* 'user.notFound': '用户不存在',
|
|
49
|
+
* 'account.notFound': {
|
|
50
|
+
* code: 'ACCOUNT_NOT_FOUND',
|
|
51
|
+
* message: '账户不存在'
|
|
52
|
+
* }
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @since v1.1.5
|
|
57
|
+
*/
|
|
58
|
+
export interface LocaleMessages {
|
|
59
|
+
[key: string]: ErrorMessageConfig;
|
|
60
|
+
}
|
|
61
|
+
|
|
7
62
|
/**
|
|
8
63
|
* JSON Schema 对象
|
|
9
64
|
*
|
|
@@ -1700,7 +1755,10 @@ export class I18nError extends Error {
|
|
|
1700
1755
|
/** 错误消息(已翻译) */
|
|
1701
1756
|
message: string;
|
|
1702
1757
|
|
|
1703
|
-
/**
|
|
1758
|
+
/** 原始 key(v1.1.5 新增) */
|
|
1759
|
+
originalKey: string;
|
|
1760
|
+
|
|
1761
|
+
/** 错误代码(从对象提取或使用 key) */
|
|
1704
1762
|
code: string;
|
|
1705
1763
|
|
|
1706
1764
|
/** 错误参数(用于插值) */
|
|
@@ -1841,9 +1899,26 @@ export class I18nError extends Error {
|
|
|
1841
1899
|
/**
|
|
1842
1900
|
* 转为 JSON 格式(用于 API 响应)
|
|
1843
1901
|
* @returns JSON 对象
|
|
1902
|
+
*
|
|
1903
|
+
* @example
|
|
1904
|
+
* ```typescript
|
|
1905
|
+
* const json = error.toJSON();
|
|
1906
|
+
* // {
|
|
1907
|
+
* // error: 'I18nError',
|
|
1908
|
+
* // originalKey: 'account.notFound', // v1.1.5 新增
|
|
1909
|
+
* // code: 'ACCOUNT_NOT_FOUND',
|
|
1910
|
+
* // message: '账户不存在',
|
|
1911
|
+
* // params: {},
|
|
1912
|
+
* // statusCode: 400,
|
|
1913
|
+
* // locale: 'zh-CN'
|
|
1914
|
+
* // }
|
|
1915
|
+
* ```
|
|
1916
|
+
*
|
|
1917
|
+
* @since v1.1.5 - 新增 originalKey 字段
|
|
1844
1918
|
*/
|
|
1845
1919
|
toJSON(): {
|
|
1846
1920
|
error: string;
|
|
1921
|
+
originalKey: string; // v1.1.5 新增
|
|
1847
1922
|
code: string;
|
|
1848
1923
|
message: string;
|
|
1849
1924
|
params: Record<string, any>;
|
|
@@ -282,7 +282,12 @@ class ErrorFormatter {
|
|
|
282
282
|
actual: err.data === null ? 'null' :
|
|
283
283
|
err.data === undefined ? 'undefined' :
|
|
284
284
|
Array.isArray(err.data) ? 'array' :
|
|
285
|
-
typeof err.data
|
|
285
|
+
typeof err.data,
|
|
286
|
+
// ✅ 修复 enum 错误:将 allowedValues 映射为 valids 和 allowed
|
|
287
|
+
valids: err.params && err.params.allowedValues ? err.params.allowedValues.join(', ') : undefined,
|
|
288
|
+
allowed: err.params && err.params.allowedValues ? err.params.allowedValues.join(', ') : undefined,
|
|
289
|
+
// ✅ 修复 additionalProperties 错误:将 additionalProperty 映射为 key
|
|
290
|
+
key: err.params && err.params.additionalProperty ? err.params.additionalProperty : undefined
|
|
286
291
|
};
|
|
287
292
|
|
|
288
293
|
message = this._interpolate(message, interpolateData);
|
package/lib/core/Locale.js
CHANGED
|
@@ -62,11 +62,12 @@ class Locale {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
65
|
+
* 获取错误消息配置
|
|
66
66
|
* @param {string} type - 错误类型或消息字符串
|
|
67
67
|
* @param {Object} [customMessages] - 自定义消息
|
|
68
68
|
* @param {string} [locale] - 指定语言(可选,默认使用当前语言)
|
|
69
|
-
* @returns {string}
|
|
69
|
+
* @returns {Object|string} 消息配置对象 { code, message } 或字符串(向后兼容)
|
|
70
|
+
* @version 1.1.5 - 支持对象格式
|
|
70
71
|
*/
|
|
71
72
|
static getMessage(type, customMessages = {}, locale = null) {
|
|
72
73
|
// 使用指定的语言或当前全局语言
|
|
@@ -74,32 +75,37 @@ class Locale {
|
|
|
74
75
|
|
|
75
76
|
// 优先级: 自定义消息 > 全局自定义消息 > 语言包 > ErrorCodes > 原字符串
|
|
76
77
|
|
|
77
|
-
// 1.
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// 1. 查找消息配置
|
|
79
|
+
let messageConfig = customMessages[type]
|
|
80
|
+
|| this.customMessages[type]
|
|
81
|
+
|| (this.locales[targetLocale] && this.locales[targetLocale][type]);
|
|
82
|
+
|
|
83
|
+
// 2. 如果未找到,尝试从 ErrorCodes 获取
|
|
84
|
+
if (!messageConfig) {
|
|
85
|
+
const errorInfo = getErrorInfo(type);
|
|
86
|
+
if (errorInfo.code === 'UNKNOWN_ERROR') {
|
|
87
|
+
// ✅ 向后兼容:直接返回原字符串(支持硬编码消息)
|
|
88
|
+
return type;
|
|
89
|
+
}
|
|
90
|
+
messageConfig = errorInfo.message;
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
//
|
|
83
|
-
if (
|
|
84
|
-
|
|
93
|
+
// 3. 规范化为对象格式(v1.1.5 新增)
|
|
94
|
+
if (typeof messageConfig === 'string') {
|
|
95
|
+
// 字符串格式 → 转换为对象格式(向后兼容)
|
|
96
|
+
return { code: type, message: messageConfig };
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
if (typeof messageConfig === 'object' && messageConfig !== null && messageConfig.message) {
|
|
100
|
+
// 对象格式 → 直接使用
|
|
101
|
+
return {
|
|
102
|
+
code: messageConfig.code || type, // 无 code 时使用 type 作为 code
|
|
103
|
+
message: messageConfig.message
|
|
104
|
+
};
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
// 4.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// 5. 如果是未知错误,说明不是预定义的错误码
|
|
97
|
-
if (errorInfo.code === 'UNKNOWN_ERROR') {
|
|
98
|
-
// ✅ 向后兼容:直接返回原字符串(支持硬编码消息)
|
|
99
|
-
return type;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return errorInfo.message;
|
|
107
|
+
// 4. 降级处理(边界情况)
|
|
108
|
+
return { code: type, message: type };
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
/**
|
|
@@ -29,32 +29,41 @@ class MessageTemplate {
|
|
|
29
29
|
render(context = {}) {
|
|
30
30
|
let message = this.template;
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// 定义支持的模板格式(按优先级)
|
|
33
|
+
const patterns = [
|
|
34
|
+
/\{\{#(\w+)\}\}/g, // {{#variable}} - 优先级1(现有格式)
|
|
35
|
+
/\{\{(\w+)\}\}/g, // {{variable}} - 优先级2(无井号)
|
|
36
|
+
/\{(\w+)\}/g // {variable} - 优先级3(单花括号)
|
|
37
|
+
];
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
// 按优先级依次替换
|
|
40
|
+
for (const pattern of patterns) {
|
|
41
|
+
message = message.replace(pattern, (match, key) => {
|
|
42
|
+
const value = context[key];
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// 特殊处理
|
|
45
|
+
if (value === undefined || value === null) {
|
|
46
|
+
return match; // 保留原样
|
|
47
|
+
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// 数组转字符串
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
return value.join(', ');
|
|
52
|
+
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
// RegExp转字符串
|
|
55
|
+
if (value instanceof RegExp) {
|
|
56
|
+
return value.toString();
|
|
57
|
+
}
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// Date转字符串
|
|
60
|
+
if (value instanceof Date) {
|
|
61
|
+
return value.toISOString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return String(value);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
return message;
|
|
60
69
|
}
|
package/lib/core/Validator.js
CHANGED
|
@@ -477,7 +477,12 @@ class Validator {
|
|
|
477
477
|
const errorMsg = failedMessage || cond.message;
|
|
478
478
|
// 支持多语言:如果 message 是 key(如 'conditional.underAge'),从语言包获取翻译
|
|
479
479
|
// 传递 locale 参数以支持动态语言切换
|
|
480
|
-
const
|
|
480
|
+
const messageConfig = Locale.getMessage(errorMsg, options.messages || {}, locale);
|
|
481
|
+
|
|
482
|
+
// v1.1.5: Locale.getMessage 返回对象 { code, message },需要提取 message
|
|
483
|
+
const errorMessage = typeof messageConfig === 'object' && messageConfig.message
|
|
484
|
+
? messageConfig.message
|
|
485
|
+
: messageConfig;
|
|
481
486
|
|
|
482
487
|
return {
|
|
483
488
|
valid: false,
|
package/lib/errors/I18nError.js
CHANGED
|
@@ -61,15 +61,34 @@ const MessageTemplate = require('../core/MessageTemplate');
|
|
|
61
61
|
class I18nError extends Error {
|
|
62
62
|
/**
|
|
63
63
|
* 构造函数
|
|
64
|
-
* @param {string}
|
|
64
|
+
* @param {string} key - 错误代码(多语言 key)
|
|
65
65
|
* @param {Object} params - 错误参数(用于插值)
|
|
66
66
|
* @param {number} statusCode - HTTP 状态码(默认 400)
|
|
67
67
|
* @param {string} locale - 语言环境(默认使用当前语言)
|
|
68
|
+
* @version 1.1.5 - 支持对象格式配置
|
|
68
69
|
*/
|
|
69
|
-
constructor(
|
|
70
|
-
//
|
|
70
|
+
constructor(key, params = {}, statusCode = 400, locale = null) {
|
|
71
|
+
// 获取语言环境
|
|
71
72
|
const actualLocale = locale || Locale.getLocale();
|
|
72
|
-
|
|
73
|
+
|
|
74
|
+
// 获取消息配置(v1.1.5: 返回对象 { code, message })
|
|
75
|
+
const messageConfig = Locale.getMessage(key, {}, actualLocale);
|
|
76
|
+
|
|
77
|
+
// 判断返回类型(向后兼容)
|
|
78
|
+
let errorCode, template;
|
|
79
|
+
if (typeof messageConfig === 'object' && messageConfig.code && messageConfig.message) {
|
|
80
|
+
// 对象格式:提取 code 和 message
|
|
81
|
+
errorCode = messageConfig.code;
|
|
82
|
+
template = messageConfig.message;
|
|
83
|
+
} else if (typeof messageConfig === 'string') {
|
|
84
|
+
// 字符串格式(向后兼容)
|
|
85
|
+
errorCode = key;
|
|
86
|
+
template = messageConfig;
|
|
87
|
+
} else {
|
|
88
|
+
// 降级处理
|
|
89
|
+
errorCode = key;
|
|
90
|
+
template = key;
|
|
91
|
+
}
|
|
73
92
|
|
|
74
93
|
// 使用 MessageTemplate 进行参数插值
|
|
75
94
|
const messageTemplate = new MessageTemplate(template);
|
|
@@ -78,7 +97,8 @@ class I18nError extends Error {
|
|
|
78
97
|
super(message);
|
|
79
98
|
|
|
80
99
|
this.name = 'I18nError';
|
|
81
|
-
this.
|
|
100
|
+
this.originalKey = key; // v1.1.5 新增:保留原始 key
|
|
101
|
+
this.code = errorCode; // v1.1.5 修改:从对象提取或使用 key
|
|
82
102
|
this.params = params || {};
|
|
83
103
|
this.statusCode = statusCode;
|
|
84
104
|
this.locale = actualLocale;
|
|
@@ -169,7 +189,7 @@ class I18nError extends Error {
|
|
|
169
189
|
/**
|
|
170
190
|
* 检查错误是否为指定代码
|
|
171
191
|
*
|
|
172
|
-
* @param {string}
|
|
192
|
+
* @param {string} codeOrKey - 错误代码或原始 key
|
|
173
193
|
* @returns {boolean} 是否匹配
|
|
174
194
|
*
|
|
175
195
|
* @example
|
|
@@ -179,10 +199,16 @@ class I18nError extends Error {
|
|
|
179
199
|
* if (error instanceof I18nError && error.is('account.notFound')) {
|
|
180
200
|
* // 处理账户不存在的情况
|
|
181
201
|
* }
|
|
202
|
+
*
|
|
203
|
+
* // v1.1.5: 也可以用 code 判断
|
|
204
|
+
* if (error instanceof I18nError && error.is('ACCOUNT_NOT_FOUND')) {
|
|
205
|
+
* // 也能匹配
|
|
206
|
+
* }
|
|
182
207
|
* }
|
|
183
208
|
*/
|
|
184
|
-
is(
|
|
185
|
-
|
|
209
|
+
is(codeOrKey) {
|
|
210
|
+
// v1.1.5: 同时比较 code 和 originalKey(向后兼容)
|
|
211
|
+
return this.code === codeOrKey || this.originalKey === codeOrKey;
|
|
186
212
|
}
|
|
187
213
|
|
|
188
214
|
/**
|
|
@@ -190,6 +216,7 @@ class I18nError extends Error {
|
|
|
190
216
|
*
|
|
191
217
|
* @returns {Object} JSON 对象
|
|
192
218
|
* @returns {string} return.error - 错误名称
|
|
219
|
+
* @returns {string} return.originalKey - 原始 key(v1.1.5 新增)
|
|
193
220
|
* @returns {string} return.code - 错误代码
|
|
194
221
|
* @returns {string} return.message - 错误消息(已翻译)
|
|
195
222
|
* @returns {Object} return.params - 错误参数
|
|
@@ -201,7 +228,8 @@ class I18nError extends Error {
|
|
|
201
228
|
* res.status(error.statusCode).json(json);
|
|
202
229
|
* // {
|
|
203
230
|
* // error: 'I18nError',
|
|
204
|
-
* //
|
|
231
|
+
* // originalKey: 'account.notFound', // v1.1.5 新增
|
|
232
|
+
* // code: 'ACCOUNT_NOT_FOUND',
|
|
205
233
|
* // message: '找不到账户',
|
|
206
234
|
* // params: { accountId: '123' },
|
|
207
235
|
* // statusCode: 404,
|
|
@@ -211,6 +239,7 @@ class I18nError extends Error {
|
|
|
211
239
|
toJSON() {
|
|
212
240
|
return {
|
|
213
241
|
error: this.name,
|
|
242
|
+
originalKey: this.originalKey, // v1.1.5 新增
|
|
214
243
|
code: this.code,
|
|
215
244
|
message: this.message,
|
|
216
245
|
params: this.params,
|