fix(config-tool): 统一 contains 与本地化提示文案

- 修复 dependentRequired 校验消息键缺失导致的隐式 undefined 文案映射

- 统一 contains 与 dependent schema 相关中文提示措辞并补齐 maxContains hint 输出

- 补充本地化与 contains 摘要测试覆盖新增文案与回归场景
This commit is contained in:
gewuyou 2026-04-30 11:44:44 +08:00 committed by GeWuYou
parent 3f335f19d6
commit 7f98cafbfa
5 changed files with 55 additions and 10 deletions

View File

@ -39,7 +39,7 @@ function describeContainsSchema(containsSchema, localizer) {
/** /**
* Build localized contains-related hint lines for array fields. * Build localized contains-related hint lines for array fields.
* *
* @param {{contains?: {type?: string, enumValues?: string[], constValue?: string, constDisplayValue?: string, pattern?: string, refTable?: string}, minContains?: number}} propertySchema Array property schema metadata. * @param {{contains?: {type?: string, enumValues?: string[], constValue?: string, constDisplayValue?: string, pattern?: string, refTable?: string}, minContains?: number, maxContains?: number}} propertySchema Array property schema metadata.
* @param {{t: (key: string, params?: Record<string, string | number>) => string}} localizer Runtime localizer. * @param {{t: (key: string, params?: Record<string, string | number>) => string}} localizer Runtime localizer.
* @returns {string[]} Localized contains hint lines. * @returns {string[]} Localized contains hint lines.
*/ */
@ -51,7 +51,7 @@ function buildContainsHintLines(propertySchema, localizer) {
const effectiveMinContains = typeof propertySchema.minContains === "number" const effectiveMinContains = typeof propertySchema.minContains === "number"
? propertySchema.minContains ? propertySchema.minContains
: 1; : 1;
return [ const lines = [
localizer.t("webview.hint.contains", { localizer.t("webview.hint.contains", {
summary: describeContainsSchema(propertySchema.contains, localizer) summary: describeContainsSchema(propertySchema.contains, localizer)
}), }),
@ -59,6 +59,14 @@ function buildContainsHintLines(propertySchema, localizer) {
value: effectiveMinContains value: effectiveMinContains
}) })
]; ];
if (typeof propertySchema.maxContains === "number") {
lines.push(localizer.t("webview.hint.maxContains", {
value: propertySchema.maxContains
}));
}
return lines;
} }
module.exports = { module.exports = {

View File

@ -247,9 +247,9 @@ const zhCnMessages = {
"webview.hint.format": "格式:{value}", "webview.hint.format": "格式:{value}",
"webview.hint.minItems": "最少元素数:{value}", "webview.hint.minItems": "最少元素数:{value}",
"webview.hint.maxItems": "最多元素数:{value}", "webview.hint.maxItems": "最多元素数:{value}",
"webview.hint.contains": "Contains 约束{summary}", "webview.hint.contains": "Contains 条件{summary}",
"webview.hint.minContains": "最少 contains 匹配数:{value}", "webview.hint.minContains": "最少匹配数:{value}",
"webview.hint.maxContains": "最多 contains 匹配数:{value}", "webview.hint.maxContains": "最多匹配数:{value}",
"webview.hint.uniqueItems": "元素必须唯一", "webview.hint.uniqueItems": "元素必须唯一",
"webview.hint.required": "必填字段:{properties}", "webview.hint.required": "必填字段:{properties}",
"webview.hint.itemMinimum": "元素最小值:{value}", "webview.hint.itemMinimum": "元素最小值:{value}",
@ -265,7 +265,7 @@ const zhCnMessages = {
"webview.hint.minProperties": "最少属性数:{value}", "webview.hint.minProperties": "最少属性数:{value}",
"webview.hint.maxProperties": "最多属性数:{value}", "webview.hint.maxProperties": "最多属性数:{value}",
"webview.hint.dependentRequired": "当 {trigger} 出现时:还必须声明 {dependencies}", "webview.hint.dependentRequired": "当 {trigger} 出现时:还必须声明 {dependencies}",
"webview.hint.dependentSchemas": "当 {trigger} 出现时:还必须满足 {schema}", "webview.hint.dependentSchemas": "当 {trigger} 出现时:还必须满足以下条件:{schema}",
"webview.hint.allOf": "还必须满足:{schema}", "webview.hint.allOf": "还必须满足:{schema}",
"webview.hint.ifThen": "当满足 {condition} 时:还必须满足 {schema}", "webview.hint.ifThen": "当满足 {condition} 时:还必须满足 {schema}",
"webview.hint.ifElse": "否则(当 {condition} 不匹配时):还必须满足 {schema}", "webview.hint.ifElse": "否则(当 {condition} 不匹配时):还必须满足 {schema}",
@ -277,7 +277,7 @@ const zhCnMessages = {
[ValidationMessageKeys.allOfViolation]: "对象“{displayPath}”必须满足全部 `allOf` schema第 {index} 项未匹配。", [ValidationMessageKeys.allOfViolation]: "对象“{displayPath}”必须满足全部 `allOf` schema第 {index} 项未匹配。",
[ValidationMessageKeys.constMismatch]: "属性“{displayPath}”必须匹配固定值 {value}。", [ValidationMessageKeys.constMismatch]: "属性“{displayPath}”必须匹配固定值 {value}。",
[ValidationMessageKeys.dependentRequiredViolation]: "属性“{triggerProperty}”存在时,必须同时声明属性“{displayPath}”。", [ValidationMessageKeys.dependentRequiredViolation]: "属性“{triggerProperty}”存在时,必须同时声明属性“{displayPath}”。",
[ValidationMessageKeys.dependentSchemasViolation]: "对象“{displayPath}”在属性“{triggerProperty}”存在时,必须满足对应的 dependent schema。", [ValidationMessageKeys.dependentSchemasViolation]: "对象“{displayPath}”在属性“{triggerProperty}”存在时,必须满足对应的依赖 schema。",
[ValidationMessageKeys.elseViolation]: "对象“{displayPath}”在内联 `if` 条件未命中时,必须满足对应的 `else` schema。", [ValidationMessageKeys.elseViolation]: "对象“{displayPath}”在内联 `if` 条件未命中时,必须满足对应的 `else` schema。",
[ValidationMessageKeys.exclusiveMaximumViolation]: "属性“{displayPath}”必须小于 {value}。", [ValidationMessageKeys.exclusiveMaximumViolation]: "属性“{displayPath}”必须小于 {value}。",
[ValidationMessageKeys.exclusiveMinimumViolation]: "属性“{displayPath}”必须大于 {value}。", [ValidationMessageKeys.exclusiveMinimumViolation]: "属性“{displayPath}”必须大于 {value}。",

View File

@ -1,6 +1,7 @@
const ValidationMessageKeys = Object.freeze({ const ValidationMessageKeys = Object.freeze({
allOfViolation: "validation.allOfViolation", allOfViolation: "validation.allOfViolation",
constMismatch: "validation.constMismatch", constMismatch: "validation.constMismatch",
dependentRequiredViolation: "validation.dependentRequiredViolation",
dependentSchemasViolation: "validation.dependentSchemasViolation", dependentSchemasViolation: "validation.dependentSchemasViolation",
elseViolation: "validation.elseViolation", elseViolation: "validation.elseViolation",
enumMismatch: "validation.enumMismatch", enumMismatch: "validation.enumMismatch",

View File

@ -51,6 +51,7 @@ test("buildContainsHintLines should use explicit minContains when provided", ()
const lines = buildContainsHintLines( const lines = buildContainsHintLines(
{ {
minContains: 2, minContains: 2,
maxContains: 3,
contains: { contains: {
type: "string", type: "string",
constValue: "\"potion\"", constValue: "\"potion\"",
@ -62,7 +63,8 @@ test("buildContainsHintLines should use explicit minContains when provided", ()
assert.deepEqual(lines, [ assert.deepEqual(lines, [
"Contains: string, Const: \"potion\", Ref table: item", "Contains: string, Const: \"potion\", Ref table: item",
"Min contains: 2" "Min contains: 2",
"Max contains: 3"
]); ]);
}); });
@ -93,3 +95,24 @@ test("describeContainsSchema should format pattern-based contains schema in Chin
assert.equal(summary, "string, 正则模式:^potion-, 引用表item"); assert.equal(summary, "string, 正则模式:^potion-, 引用表item");
}); });
test("buildContainsHintLines should use updated Chinese contains hint wording", () => {
const localizer = createLocalizer("zh-cn");
const lines = buildContainsHintLines(
{
minContains: 1,
maxContains: 2,
contains: {
type: "string",
enumValues: ["potion", "elixir"]
}
},
localizer);
assert.deepEqual(lines, [
"Contains 条件string, 允许值potion, elixir",
"最少匹配数1",
"最多匹配数2"
]);
});

View File

@ -70,6 +70,19 @@ test("createLocalizer should expose contains-count validation keys", () => {
"属性“dropRates”最多只能包含 1 个匹配 contains 条件的元素。"); "属性“dropRates”最多只能包含 1 个匹配 contains 条件的元素。");
}); });
test("createLocalizer should resolve dependentRequired through the explicit validation key", () => {
const localizer = createLocalizer("en");
assert.equal(ValidationMessageKeys.dependentRequiredViolation, "validation.dependentRequiredViolation");
assert.equal(
localizer.t(ValidationMessageKeys.dependentRequiredViolation, {
displayPath: "reward.itemCount",
triggerProperty: "reward.itemId"
}),
"Property 'reward.itemCount' is required when sibling property 'reward.itemId' is present.");
assert.equal(localizer.t("undefined"), "undefined");
});
test("createLocalizer should expose not validation keys", () => { test("createLocalizer should expose not validation keys", () => {
const englishLocalizer = createLocalizer("en"); const englishLocalizer = createLocalizer("en");
const chineseLocalizer = createLocalizer("zh-cn"); const chineseLocalizer = createLocalizer("zh-cn");
@ -132,7 +145,7 @@ test("createLocalizer should expose dependentSchemas validation keys", () => {
trigger: "reward.itemId", trigger: "reward.itemId",
schema: "object, 必填字段itemCount" schema: "object, 必填字段itemCount"
}), }),
"当 reward.itemId 出现时:还必须满足 object, 必填字段itemCount"); "当 reward.itemId 出现时:还必须满足以下条件:object, 必填字段itemCount");
assert.equal( assert.equal(
englishLocalizer.t(ValidationMessageKeys.dependentSchemasViolation, { englishLocalizer.t(ValidationMessageKeys.dependentSchemasViolation, {
displayPath: "reward", displayPath: "reward",
@ -144,7 +157,7 @@ test("createLocalizer should expose dependentSchemas validation keys", () => {
displayPath: "reward", displayPath: "reward",
triggerProperty: "reward.itemId" triggerProperty: "reward.itemId"
}), }),
"对象“reward”在属性“reward.itemId”存在时必须满足对应的 dependent schema。"); "对象“reward”在属性“reward.itemId”存在时必须满足对应的依赖 schema。");
}); });
test("createLocalizer should expose allOf validation keys", () => { test("createLocalizer should expose allOf validation keys", () => {