多语言文案治理与防漂移
九个语言版本最怕的不是翻译慢,而是「英文改了,其它八种还停在旧版本」—— 这就是文案漂移。
一、漏译、过期与孤儿文案
含义漂移 —— 同一个 key 在不同语言里表达的意思对不上。英文的「Place order」被某个语言译成了「加入购物车」,按钮的语义和它触发的动作就不一致了。这类问题最难发现,因为它看起来「翻译得很通顺」。
滞后漂移 —— 代码里的文案改了,翻译还在路上,线上出现新旧混杂。比如英文改成了「Submit order」,日文还停在「注文を送信」,功能没变但说法不一致。
重复漂移 —— 不同模块各造一个「确认」的 key,同一个词出现了五种译法,用户在同一趟流程里能看到两三种写法。
三种里面,只有重复漂移能被工具自动发现(比对译文相似度即可);前两种都要靠流程而不是靠扫一遍代码。
二、两种 key 策略的取舍
先说结论:语义 key 更稳,但必须带默认语言文案作为兜底。
用原文做 key 的吸引力很明显——代码可读,开发时不用查翻译表:
t('Submit') // 读起来很直观
但它的致命问题是原文一改,key 就失效。把「Submit」改成「Place order」,这个 key 连带它积累的所有历史翻译、翻译记忆、评审记录全部作废。而语义 key 不受影响:
t('checkout.buttons.placeOrder') // 英文原文可以随便改,key 不动
代价是开发者看不懂 key 是什么。解法是在资源文件里保留默认语言文案,让 key 与默认文案一一对应:
{
"checkout.buttons.placeOrder": "Place order",
"checkout.errors.paymentFailed": "Payment failed, please try again"
}
这样既保住了可读性,又让「改文案」和「改 key」解耦。翻译平台拿到的也是 key + 原文 + 上下文注释,译者不会在猜测中工作。
顺带两条:key 要稳定,不要在版本迭代中重命名;不要拼接,整句用 ICU 模板表达。
三、把翻译卡进流程
漂移的克星是流程,不是工具。三条最有效:
① PR 检查新增 key 必须带默认语言文案。 没有默认文案的 key 不允许合入——这是运行时兜底能生效的前提。
② 建立翻译平台与代码仓库的同步机制。 代码是源头,翻译平台是下游:新 key 自动导出,译文按分支或按版本回流。只同步一次的方案必然失效,因为代码会一直改。
③ 删除 key 要连带清理。 代码里删了文案,资源文件里还留着,日积月累会变成一堆没人敢删的死条目。可以在 CI 里做「未被引用的 key」检查,定期清理。
# CI 里可以做的一类检查:找出源码中未提取的硬编码中文/英文文案
i18n-scanner 'src/**/*.{ts,tsx}' --fail-on-missing
还有一条容易被忽略:给译者上下文。同一个词在不同位置译法不同,提取时带上「这是按钮上的文案」或截图,能省掉大量来回确认。
四、缺文案时怎么降级
缺翻译时最糟的做法是界面上直接显示 key(checkout.buttons.placeOrder),用户完全看不懂,而开发往往在线上才发现。
正确做法是回退到默认语言文案,同时把这个缺失上报:
function t(key, vars) {
const text = messages[locale]?.[key]
if (!text) {
reportMissingKey(locale, key) // 变成可观测指标
return format(messages[defaultLocale][key] ?? key, vars)
}
return format(text, vars)
}
把「缺翻译」变成一个可观测指标,比人工巡检可靠得多:上线后哪个语言缺了多少条、集中在哪个模块,看板上一目了然。开发环境可以更激进——把缺失项高亮或直接抛错,让问题在提交前就暴露。
五、译文变长后的排版
这一节的问题在只做中文或英文时完全不会出现,接了欧洲语言就集中爆发。
同一句话在不同语言里长度差异很大:德语通常比英语长三成左右,芬兰语更长;而日语、中文往往短得多。CJK 字符的渲染宽度也不同。
几条能直接用的对策:
- 不要给按钮、标签设固定宽高,让容器跟着内容走;
- 按钮允许换行,不要只让标签换行;
- 验收时按源语言长度的 130% 测一遍,这是成本最低的发现方式;
- 表格和长表单尤其危险,列宽写死时,一个长单词就能把布局撑破。
/* 错:宽度写死,德语必然溢出 */
.btn { width: 120px; }
/* 改进:给下限不给上限,内容自己撑 */
.btn { min-width: 120px; padding-inline: 16px; }
排版问题不是 CSS 能兜住的。 定宽容器里再优雅的截断也救不了,它必须在设计阶段就考虑进去——这也是为什么国际化要尽早介入设计评审,而不是等翻译回来再改样式。
六、翻完才刚开始
用原文做 key 看似直观,但原文一改 key 就失效,历史翻译全部作废。语义 key 更稳,但要带默认语言文案兜底。
翻译平台同步一次也不够——代码与翻译是两个系统,必须建立持续的同步与校验机制。
文案长度问题别指望 CSS 兜住:容器定宽时再好的 CSS 也救不了,设计阶段就要考虑换行与截断。
缺翻译显示 key 同样不能无所谓——用户看不懂,而且你不统计就永远不知道缺了多少。
翻译错了会被发现,缺翻译没人发现——所以兜底的同时一定要把它上报成指标。
ICU 消息语法的完整规则见 FormatJS:ICU Message Syntax,复数与选择分支的写法以此为准。