AI翻译多语言文案后,要同时检查译文含义和资源文件结构。键名必须能被程序找到,变量必须保留,数量句子则要按目标语言选择复数形式。中英文资源的键集合未必完全相同,逐字翻译或机械对齐键名都可能产生问题。
以购物车为例,可以把中文资源、使用场景和变量约定交给墨刀AI客户端整理英文资源与检查项,再把结果交给项目自己的校验程序。下面提供一个可在Node.js运行的完整例子:两份词典、变量规则和六类故意改坏的样本。

给译文上下文,也给它不能改的部分
只给“结算”两个字,AI可能译成Checkout、Settlement或其他词。购物车按钮与财务结算单的语境不同。准备资源时,将调用位置、对象和格式约束放在同一张表里,让译文服务于实际动作。
| 消息 | 出现位置与含义 | 固定约束 |
|---|---|---|
| cart.checkout | 购物车底部,进入结算流程的按钮 | 不得改键名;没有变量 |
| cart.owner | 标题,显示某位用户的购物车 | 必须保留name变量,不翻译变量名 |
| cart.items | 商品数量摘要 | 必须保留count,并按语言提供数量分支 |
本例使用扁平键名和双花括号变量,例如{{name}}。点号只是键名的一部分,检查程序不会把它当成嵌套对象路径。参考的是i18next默认插值语法,但下面的脚本是独立检查器,没有安装或调用i18next。
可以这样交代任务:“请根据购物车上下文补全英文词典。保留普通消息键和变量名,数量句采用目标语言所需的分支。只输出资源对象和你仍无法判断的语境,不擅自增加按钮、不把变量替换成某个人名。”如果产品采用ICU MessageFormat,应提供真实格式与解析器规则,不能混用本例语法。
中文一条数量句,英文可能需要两条
本例中文商品数使用other分支,英文使用one和other。因而中文只有cart.items_other,而英文还有cart.items_one。如果校验只要求“英文键与中文键完全相同”,反而会把英文正确的单数分支当成多余内容。
i18next复数文档说明了按语言划分的复数类别及count参数。本文使用JavaScript的Intl.PluralRules取得目标语言的类别,并根据数量选择后缀。其他语言可能需要更多分支,不能把one、other写死成所有语言的规则。
下面的校验限定为购物车三类消息:普通键名固定,数量分支根据语言生成;变量集合必须相同;空白译文、额外键和不支持的花括号语法会报错。它不处理嵌套资源、格式化器、HTML标签、ICU嵌套表达式或翻译质量判断。
复制词典与检查器,先看一轮真实输出
将下面代码保存为i18n_demo.mjs,执行node i18n_demo.mjs。脚本内置中英文字典,随后逐项制造缺键、改变量等错误。运行时无需额外安装依赖。
import assert from 'node:assert/strict';
const dictionaries = {
zh: { 'cart.checkout': '结算', 'cart.owner': '{{name}}的购物车',
'cart.items_other': '{{count}}件商品' },
en: { 'cart.checkout': 'Checkout', 'cart.owner': "{{name}}'s cart",
'cart.items_one': '{{count}} item', 'cart.items_other': '{{count}} items' }
};
const variables = { 'cart.checkout': [], 'cart.owner': ['name'], 'cart.items': ['count'] };
const token = /\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g;
function validate(locale, dictionary) {
const categories = new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
const expected = ['cart.checkout', 'cart.owner', ...categories.map(c => `cart.items_${c}`)];
const errors = [];
for (const key of expected) {
if (!Object.hasOwn(dictionary, key)) { errors.push(`missing:${key}`); continue; }
const value = dictionary[key];
if (typeof value !== 'string' || !value.trim()) { errors.push(`empty:${key}`); continue; }
const used = [...new Set([...value.matchAll(token)].map(m => m[1]))].sort();
const base = key.startsWith('cart.items_') ? 'cart.items' : key;
if (JSON.stringify(used) !== JSON.stringify([...variables[base]].sort())) {
errors.push(`variables:${key}`);
}
if (/[{}]/.test(value.replace(token, ''))) errors.push(`unsupported_syntax:${key}`);
}
for (const key of Object.keys(dictionary)) {
if (!expected.includes(key)) errors.push(`extra:${key}`);
}
return errors;
}
function itemText(locale, count) {
const suffix = new Intl.PluralRules(locale).select(count);
return dictionaries[locale][`cart.items_${suffix}`].replace('{{count}}', String(count));
}
assert.deepEqual(validate('zh', dictionaries.zh), []);
assert.deepEqual(validate('en', dictionaries.en), []);
const cases = [
['missing_key', d => { delete d['cart.checkout']; }, 'missing:cart.checkout'],
['extra_key', d => { d['cart.pay'] = 'Pay'; }, 'extra:cart.pay'],
['renamed_variable', d => { d['cart.owner'] = '{{user}} cart'; }, 'variables:cart.owner'],
['empty_translation', d => { d['cart.checkout'] = ' '; }, 'empty:cart.checkout'],
['missing_plural', d => { delete d['cart.items_one']; }, 'missing:cart.items_one'],
['unsupported_icu', d => { d['cart.owner'] = '{name} cart'; }, 'unsupported_syntax:cart.owner'],
];
for (const [label, change, expected] of cases) {
const changed = structuredClone(dictionaries.en);
change(changed);
assert.ok(validate('en', changed).includes(expected), label);
}
assert.deepEqual([0, 1, 2].map(n => itemText('en', n)), ['0 items', '1 item', '2 items']);
assert.deepEqual([0, 1, 2].map(n => itemText('zh', n)), ['0件商品', '1件商品', '2件商品']);
console.log(JSON.stringify({ node: process.version, validDictionaries: 2,
rejectedMutations: cases.map(c => c[0]), english: [0, 1, 2].map(n => itemText('en', n)),
chinese: [0, 1, 2].map(n => itemText('zh', n)) }, null, 2));
本地Node.js v24.19.0运行结果:两份词典通过,六类修改后的词典都被对应规则识别。数量0、1、2在英文分别输出“0 items”“1 item”“2 items”,中文分别为“0件商品”“1件商品”“2件商品”。这验证了此例的结构和分支选择,没有验证真实页面布局或所有语言的译文。
| 故意造成的问题 | 检查结果 | 实际项目中的意义 |
|---|---|---|
| 删除cart.checkout | missing | 避免按钮找不到资源 |
| 新增未约定的cart.pay | extra | 提示AI可能擅自改键或新增动作 |
| 将name改成user | variables | 避免运行时数据对不上 |
| 把按钮译文写成空格 | empty | 避免有键却没有可见文字 |
| 删除英文one分支 | missing | 避免单数调用缺少资源 |
| 将双花括号改成单花括号 | unsupported_syntax | 提示消息格式发生了变化 |
通过脚本后,还要把文案放回页面
结构正确的英文也可能不合适。例如“Checkout”是否准确,取决于点击后进入结算还是直接提交付款;“某人的购物车”是否应出现所有者,则取决于产品是否支持代选购。业务含义需要结合界面,由熟悉目标语言与场景的人复核。
回填时至少检查数量为0、1、2的页面,较长的用户名称,以及从中文切换到英文后的按钮和摘要。变量不是固定长度,不能只用“Tom”验证标题。布局处理可继续参考长文本与多语言UI的换行和省略方法。
若项目直接读取JSON文件,还应在解析前发现重复键;普通JSON解析后的对象通常只剩后出现的值,本例基于已创建的对象,无法替你发现源码里曾写过两个同名键。同样,变量名集合一致不代表文字顺序、礼貌程度和语气正确,不能把所有人工复核都压缩成一个正则。
新增一种语言时,沿用规则而不是复制英文分支
把新语言加入时,先确认它的消息格式和数量类别,再扩充校验器的消息范围。对每条消息保留上下文、允许的变量和实际调用位置;对每次AI改译记录资源差异和运行结果,出现问题才知道是词句修改还是程序约定被破坏。
如果中文原稿本身还含糊,应先完成按钮与错误提示的业务文案整理,再进入翻译。一个可靠的多语言交付包,需要译文、资源规则和页面复核相互对得上;单独一份流畅的英文JSON还不足以证明可以上线。