本地化最难的部分不在翻译,在于让句子从代码里独立出来。
一、一个拼接出来的句子,翻不了
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
许棠做一个团队日程工具。英文版里有一句提示:
const text = `You have ${n} new message${n === 1 ? '' : 's'}`;
翻成日文时,译者要求把语序改成「新着メッセージが 3 件あります」。但代码里只有两段碎片:You have 与 new message,中间夹着变量。译者只能在语言包里写两段,运行起来就是语序错乱的句子。
俄文更麻烦:1 файл、2 файла、5 файлов 是三种形式,代码里的 n === 1 ? '' : 's' 只能处理两种。
这不是翻译质量问题,是代码结构问题。只要句子在代码里被拆开,任何语言都拼不回正确的句子。
二、三条硬规则
规则一:整句外置,不拼片段。
语言包的最小单位是「一句完整的话」,不是「一个词」。以下三类都必须作为整句:
- 带变量的句子:
已邀请 {name} 加入 {workspace}。 - 带条件的句子:
{count} 个文件,其中 {failed} 个失败。 - 带单位或格式的句子:
剩余 {days} 天(不同语言的数量词与单位位置不同)。
规则二:key 按语义命名,不按文案内容命名。
common.save、billing.invoice.due、onboarding.step2.title 这类 key,改文案时不用改代码;保存、SaveButton 这类 key,文案一变就失配。
推荐的格式是「域.组件.语义」三段:
billing.invoice.due
billing.invoice.paid
onboarding.step2.title
errors.upload.too_large
规则三:复数、性别、序数交给规则,不写 if。
不同语言的复数类别数量不同。按 Unicode CLDR 的规则:中文只有 other 一类,英文有 one、other,俄文有 one、few、many、other,阿拉伯文有六类。用 n === 1 判断,最多只能覆盖一部分语言。
ICU MessageFormat 把这些规则写进文案本身:
{
"files.count": "{count, plural, one {# file} other {# files}}",
"invite.sent": "{name} 已加入 {workspace}"
}
{
"files.count": "{count, plural, one {# файл} few {# файла} many {# файлов} other {# файла}}",
"invite.sent": "{name} присоединился к {workspace}"
}
运行时由 ICU 库按当前语言选择形式,代码里不再出现任何语言判断。
三、语言包的结构与回退链
一个可维护的语言包至少要有四件事:
| 组成 | 作用 | 常见错误 |
|---|---|---|
| 源语言文件 | 唯一真源,其他语言从它派生 | 多份源文件互相覆盖 |
| 目标语言文件 | 按 key 对齐的翻译 | key 被手工改名后失配 |
| 回退链 | 缺翻译时用哪一级 | 回退到空字符串,界面出现空白 |
| 元数据 | 语言标记、版本、译者、日期 | 无法判断哪一版对应哪个发布 |
回退链建议写成显式配置,例如 pt-BR → pt → en,并规定「回退时记录一次缺失事件」。没有记录,就永远不知道哪种语言漏了多少条。
文件组织有两种常见做法:按页面拆分(billing.json、onboarding.json)与单文件(en.json)。前者适合大产品与多人协作,后者适合一个人的小产品。选择标准是「一次改动会碰到几个文件」,不是「哪种更规范」。
还有一个容易忽略的细节:不要把富文本标签写进文案值里再拼接 HTML。 用占位符配合组件,例如 terms.link 只放文案,链接作为参数传入;否则译者可能改坏标签,也可能引入注入风险。
四、迁移顺序与工时估算
改造不要一次做完,按「用户看得到 → 用户收得到 → 只有你看得到」的顺序:
- 界面文案:按钮、标题、空状态、错误提示。优先,因为直接影响转化。
- 邮件与通知:注册验证、付款收据、失败提醒。它们常常在另一套模板里,容易漏。
- 后台与日志:可以先保持源语言,不阻塞上线。
工时可以用一个粗略公式估:
工时 ≈ 字符串条数 × 1.5 分钟
+ 拼接句条数 × 5 分钟
+ 关键页面数 × 6 分钟(回归测试)
按 620 条字符串、74 条拼接句、28 个关键页面算:
620 × 1.5 = 930 分钟 = 15.5 小时
74 × 5 = 370 分钟 = 6.2 小时
28 × 6 = 168 分钟 = 2.8 小时
合计 ≈ 24.5 小时
这是「代码写得还算规整」的情况。如果模板里到处是内联字符串与条件拼接,实际工时通常是估算的两到三倍。先做一次扫描再报价,比事后加班便宜。
扫描可以用最笨的办法:搜索引号包住的文本、模板字符串与拼接操作符,人工过一遍。不要指望正则一次找全,它会把日志、CSS 类名与测试数据一起抓出来。
五、行动清单与复算验收
- 扫描全部字符串,统计条数与其中拼接句的数量
- 定 key 命名规则,写进 README,避免后人改回内容命名
- 把带变量的句子改成 ICU 格式,删掉代码里的单复数判断
- 建立源语言文件与回退链,缺失时记录事件而不是显示空白
- 按「界面 → 邮件 → 后台」的顺序迁移,每步跑一次回归
- 用公式估一次工时,与实际耗时对比,校准下一种语言
验收标准:语言包里不存在只有半句的条目,代码里不存在按语言分支的字符串判断,缺翻译时界面显示的是回退语言而不是空白。 三条同时成立,才算外置完成。
使用边界与实操验收
本文只讲代码侧的外置与结构,不涉及翻译质量与工作流,那属于第 04 篇;长度膨胀、断词与字体属于第 05 篇;漏译检测与回归测试属于第 06 篇。
站内已有面向游戏的本地化管线案例,处理的是贴图文字与字体图集,工程约束不同,可参考个人游戏技术选型案例:本地化管线为什么要先处理字体和文本长度 。本文面向有界面与表单的软件产品。
验证以实际渲染为准:切换语言后逐页检查,重点看带变量的句子、错误提示与邮件模板。自动化只能证明 key 存在,不能证明句子通顺。
关键参考与核验范围
本文的字符串条数、工时与比例为教学演示。核验日期:2026-10-23。
- ICU MessageFormat :复数、选择与插值的写法规范,是「把语法写进文案」的标准做法。
- Unicode CLDR :各语言的复数类别、数字与日期格式数据,决定了一门语言需要几种形式。
上述两条只用于说明技术规范,不构成对任何库或框架的推荐。各语言的具体复数规则以 CLDR 当日数据为准,本次核验未逐条比对。
系列导航
- 上一篇:先选市场:四个地区的矩阵与一张打分表
- 下一篇:翻译工作流:机翻、审校与术语表
- 回到目录:一个人把产品做出海:多语言、时区与本地化
- 相关阅读:排版与字体:长度膨胀、断词与复杂文字 、本地化测试:漏译、回退与截图回归
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。