3144 字
约 10 分钟
17
S10-A10 · 多语言内容的工程化:不是翻译就能上线
翻译做完了,术语表也过了,上线当天日文版把按钮撑破,德语版把一行标题挤成三行,阿拉伯语版整个布局从右往左错位。翻译质量没问题,出问题的是排版与语义——这两块在翻译阶段根本没人看。
多语言的成本从来不是一项,而是三项,且它们在不同的阶段暴露。把三项分开预算,很多"上线才发现"的问题会提前变成"设计时就知道"。
1. 三种成本与各自的发现阶段
| 成本 | 内容 | 通常在哪暴露 | 事后修复代价 |
|---|---|---|---|
| 翻译成本 | 人力、术语一致性、审校轮次 | 翻译阶段,可见 | 中,可增量补 |
| 排版成本 | 文案长度变化、断行、容器尺寸 | 上线后,肉眼可见 | 高,牵动布局与设计 |
| 语义成本 | 复数、性别、语序、占位符、语气 | 上线后,通常只有母语用户发现 | 最高,牵动文案结构 |
常见误判是把预算全压在翻译上。翻译错一句可以改一句;语义错会让整类文案都要重写,因为键的结构本身不支持那种语言。
2. 哪些内容该翻译、哪些不该
| 内容类型 | 策略 | 判据 |
|---|---|---|
| 界面文案、错误提示 | 翻译 | 用户直接阅读,影响任务完成 |
| 技术术语(API、token、commit) | 保留原文,必要时加简短注释 | 译名反而制造检索障碍 |
| 品牌名与产品代号 | 不翻译 | 翻译会破坏可识别性 |
| 法律条款、隐私政策 | 通常只保证一种语言为权威文本 | 多语言并存会产生解释冲突 |
| 日志与堆栈 | 不翻译 | 面向排查者,需与代码一致 |
| 帮助文档 | 按版本翻译,不全量 | 版本更新快,翻译会长期落后 |
"全部都翻"听上去最尊重用户,实际结果是术语混乱 + 维护面积翻倍。
3. 扩缩率与布局预留
同一句话在不同语言下的长度差异是结构性的,不是翻译不精炼。做布局时按最坏情况预留:
| 源语言 → 目标 | 典型长度变化 | 布局影响 |
|---|---|---|
| en → de | 明显变长,常见 1.3 倍量级 | 按钮、标签、表头溢出 |
| en → ja / zh | 明显变短 | 留白过大,对齐错位 |
| en → ru / fi | 变长且词形长 | 输入框、面包屑截断 |
// 预留策略:容器按最长目标语言设计,而不是按源语言
.btn { min-width: 9rem; padding-inline: 1rem; } // 不用固定 width
.label { white-space: normal; overflow-wrap: anywhere; } // 允许两行
.table th { min-width: 6rem; } // 表头按最坏语种
固定像素宽 + nowrap 是排版事故的第一组合;min-width + 允许换行是成本最低的防线。
4. 复数、性别、语序与占位符
占位符拼接是最容易在翻译里"合法地写错"的地方:
// 反例:拼接式,语序被写死在代码里
"items": "You have " + N + " new items"
// 正例:整句交给消息格式,语序由译者决定
"items": "{count, plural, =0 {No new items} one {# new item} other {# new items}}"
| 坑 | 表现 | 处理 |
|---|---|---|
| 复数规则 | 英、日只有两形,俄、阿有多形 | 用 ICU 复数类别,不写 if (n > 1) |
| 性别 | 部分语言形容词随性别变化 | 消息里带 gender 参数,或改写为中性句 |
| 语序 | 动词、宾语位置不同 | 整句做键,不做片段拼接 |
| 占位符顺序 | 译者调整顺序后参数错位 | 具名占位符,禁止位置参数 |
| 标点与空格 | 句末标点、全角半角不同 | 标点进消息体,不入代码 |
5. 日期、数字、货币与姓名
本地化不只影响显示,还影响存储:存显示值是最常见的不可逆错误。
| 数据 | 存储形态 | 渲染时机 |
|---|---|---|
| 时间 | 时间戳或 UTC | 按用户 locale 与时区渲染 |
| 数字 | 数值类型 | 按 locale 分组与小数位渲染 |
| 货币 | 金额 + 币种代码 | 渲染时决定符号与位置 |
| 姓名 | 单字段或分字段,按业务定 | 不假设"名在前" |
| 地址 | 分字段 + 国家 | 不假设省市区层级固定 |
// 反例:存渲染后的字符串,等于把语言绑进数据
INSERT INTO orders (amount_text) VALUES ('¥1,299.00');
// 正例:存金额与币种,渲染交给展示层
INSERT INTO orders (amount, currency) VALUES (1299.00, 'CNY');
// 渲染侧按请求 locale 输出,不做全局默认
String s = NumberFormat.getCurrencyInstance(locale).format(amount);
6. "语言"与"地区"是两个变量
| 组合 | 差异点 | 只按语言处理会怎样 |
|---|---|---|
| zh-CN / zh-TW | 用词与字形不同 | 用简体文案覆盖繁体用户 |
| pt-BR / pt-PT | 词汇与语序不同 | 一方读起来别扭但能懂 |
| en-US / en-GB | 日期顺序、拼写 | 日期被读错(03/04) |
| es-ES / es-MX | 称谓与货币 | 货币符号错,金额被误解 |
键的设计要按 language-REGION 定位,但回退链按 language 走:缺 pt-PT 时回退到 pt 再回退到默认语言,而不是直接跳到英文。
7. 源文案改了,怎么传导
更新机制的核心不是翻译工具,而是变更可追:
// 源文案变更 → 标出受影响的目标语言键
extract-strings --src src/ --out locale/en.json
diff -u locale/en.prev.json locale/en.json | grep '^[+-]' \
| grep -v '^[+-][+-]' > changed.txt // 只留变更键
for f in locale/*.json; do
missing=$(comm -23 <(sort changed.txt) <(sort "$f")); echo "$f $missing"
done
| 机制 | 传导时延 | 适合场景 |
|---|---|---|
| 键级 diff + 待翻译队列 | 分钟到小时 | 常规迭代 |
| 全量重翻 | 天 | 大版本重构 |
| 按页面整块重翻 | 中 | 文案成块改写的产品 |
| 不传导,靠人发现 | 无上限 | 不应存在 |
8. 回退策略:缺翻译时显示什么
// 回退链:语言 → 同语言其他地区 → 默认语言 → 占位提示
{ "fallback": ["ja-JP", "ja", "en"], "onMissing": "LOUD_PLACEHOLDER" }
| 策略 | 用户体验 | 风险 |
|---|---|---|
| 回退到默认语言 | 可读,但语言混杂 | 用户以为漏翻 |
显示源 key(如 btn.submit) |
明显异常 | 把内部标识暴露给用户 |
显示占位符([ja] 提交) |
可读且可识别 | 需前端支持 |
| 隐藏该条目 | 干净 | 功能入口消失,用户找不到 |
底线只有一条:绝不能把 key 直接显示给用户。其余三种按产品取舍。
9. 机器翻译的可用边界
| 场景 | 可用性 | 原因 |
|---|---|---|
| 短 UI 标签、按钮 | 可用,需抽检 | 上下文少,歧义小 |
| 错误提示 | 谨慎 | 一句错译会让用户做错动作 |
| 长文档、帮助中心 | 不可直接上线 | 术语与语气漂移,需术语表约束 |
| 法律与合规文本 | 不可 | 解释权与责任问题 |
| 术语表覆盖的词 | 强约束下可用 | 必须强制替换,不允许模型自选译名 |
机器翻译的正确位置是草稿与冷启动,不是终稿;术语表必须作为硬约束而不是提示词建议。
10. 验收判据:能不能完成任务,而不是像不像母语
| 判据 | 通过标准 | 反例 |
|---|---|---|
| 任务可完成 | 母语用户不看原文能完成操作 | 文案优美但按钮含义含糊 |
| 术语一致 | 同一概念全文一个译名 | 三处三种叫法 |
| 无截断溢出 | 最长语言下无遮挡、无省略号 | 按钮文字被裁 |
| 占位符正确 | 变量值出现在正确位置 | 金额与币种错位 |
| 无残留 | 无未翻译条目、无 key 外露 | 页面夹带英文片段 |
"读起来像母语"是加分项,不是验收项;验收项是用户能否完成任务。
11. RTL 的额外成本
| 维度 | LTR 做法 | RTL 需要额外处理 |
|---|---|---|
| 布局方向 | margin-left |
逻辑属性 margin-inline-start |
| 图标方向 | 箭头朝右 | 图标需镜像,非全部可镜像 |
| 数字与代码 | 随文本流 | 需显式 LTR 隔离,否则被翻转 |
| 滚动与对齐 | 左对齐 | 起点在右,进度条方向反转 |
| 设计稿 | 一套 | 通常需要一套镜像稿或严格逻辑属性 |
// 用逻辑属性替换方向属性,一套 CSS 覆盖两侧
.panel { margin-inline-start: 1rem; text-align: start; direction: inherit; }
.code { direction: ltr; unicode-bidi: isolate; } // 代码块不参与翻转
RTL 的成本主要在设计资产与图标,不在 CSS 本身。
12. 度量
| 指标 | 口径 | 异常信号 |
|---|---|---|
| 翻译覆盖率 | 已译键 / 源键 | 某语言长期低于其余语言 |
| 过期翻译数 | 源键变更后未更新 | 单调上升 |
| 布局溢出数 | 视觉回归中溢出的容器数 | 新增语言上线后突增 |
| 传导时延 | 源变更到目标语言上线 | 超过一个迭代 |
| 回退命中率 | 回退到默认语言的请求占比 | 高且不降,说明翻译长期缺位 |
13. 排错:五种"翻完了还是不能用"
| 现象 | 原因 | 处理 |
|---|---|---|
| 按钮文字被裁 | 固定宽度 + 不换行 | 改 min-width,允许两行 |
| 某语言永远显示英文 | 回退链跳过该语言 | 检查 locale 解析与键前缀 |
| 金额显示成另一种货币 | 存了渲染值或默认 locale 写死 | 存金额 + 币种,渲染按请求 locale |
| 复数句子总是"1 items" | 用 if 判复数 |
改用 ICU 复数类别 |
| 阿拉伯语页面整体错位 | 用物理方向属性 | 换逻辑属性,图标单独镜像 |
14. 边界:什么情况完整 i18n 框架是过度设计
| 情形 | 合理做法 | 理由 |
|---|---|---|
| 单语言产品 | 只做字符串外置 | 无翻译需求,但外置为后续留口 |
| 内部工具 | 字符串外置 + 默认语言 | 读者同语种,翻译无收益 |
| 用户集中在一个地区 | 区域格式按该地区固定 | 多 locale 分支是无效复杂度 |
| 内容一周内迭代多次 | 先稳文案,再谈翻译 | 译完即过期 |
| 只有一种权威法律文本 | 其余语言标注"仅供参考" | 避免解释冲突 |
但字符串外置这一层几乎总是值得做:它把"改一句话"从改代码变成改数据,成本极低,且是后续任何多语言方案的前置条件。
15. 自测
- 三种多语言成本里,哪一项最容易在上线首日暴露?
- 为什么技术术语通常保留原文而不翻译?
- 按钮宽度该按源语言还是最长目标语言设计?
- 复数处理为什么不能写成
if (n > 1)? - 缺翻译时唯一不能采取的回退方式是什么?
- 单语言产品为什么仍值得做字符串外置?
S10-A10 · 多语言内容的工程化:不是翻译就能上线
https://www.clxhxhhr.top/posts/6293/ 评论
0 条
还没有评论,先写一条吧。