AI帮做国际化:抽文案能省事,占位符和复数最容易被悄悄改坏

2026-07-29

数据截至 2026-07。文中的复数类别、格式化行为按 CLDR 与各运行时的通用规范描述,具体版本差异与各产品的能力边界以官方最新说明为准。

国际化上线后炸的那一下,八成不是翻译不准,而是占位符和消息格式的契约在翻译环节被改坏了。 翻译生硬是体验问题,可以下个版本慢慢磨;{count} 被译成了别的词、ICU 消息里的花括号少了一只、俄语的复数分支缺了一档,这些是运行时错误,轻则界面显示 undefined,重则整页渲染失败。把这类事故归到「AI 翻译质量不行」,方向就错了——它们本质上是代码改动事故,该用治代码改动的办法治。

站内另外两篇讲的是相邻但不同的层:字符集与乱码那篇(存进去是中文,取出来是一串问号)管的是字节到字符这一层,时间戳与时区那篇(时间对不上)管的是时间数据怎么存、怎么带时区。本篇只管中间那一段:文案怎么从代码里抽出来、交给 AI 处理、再安全地回填回去,以及回填坏了怎么查。三篇不重叠,出问题时先判断自己踩的是哪一层。

一、先把这件事拆成四层,AI 只在其中两层可信

国际化不是一件事,是四件性质完全不同的事被打包在一个需求里。分不清层,就会用错工具、也会问错人。

第一层,抽取。 把散落在代码里的硬编码字符串挖出来、归到资源文件、起 key 名。这层是纯机械劳动,AI 干得又快又好,它能一次扫完几十个文件,还能顺手给出 key 的命名建议。人需要做的只有一件事:先把 key 的命名规范定死(按页面分组还是按功能分组、层级多深、复数用什么后缀),再让 AI 照着做。规范不定,它每个文件都会给你换一种风格。

第二层,文案本体的翻译。 AI 可用,但输入决定质量。只把一句孤零零的字符串丢给它,它译出来的东西大概率脱离语境——Save 是保存还是节省,Post 是发布还是帖子,光看词无解。你得连同这句话出现在哪个页面、旁边有什么控件、变量代表什么、UI 上大概能放多宽一起给。术语表必须由人拍板,AI 只负责查表照用;让它自己生成术语表,它会给你一份自洽但没有依据的东西,而自洽的编造恰恰是最难被发现的一类。

第三层,占位符、复数、性别与富文本嵌套。 这层是事故高发区,也是本篇的重点。因为在模型眼里 {userName} 和普通单词没有本质区别,它有相当概率把它当成待翻译内容处理掉;ICU 的 {count, plural, one{...} other{...}} 结构对它来说也只是一段文本,改着改着括号就不配对了。这层不能靠肉眼审,必须上机器校验。

第四层,日期、数字、货币、排序。 这层根本不该翻译。正确做法是它们压根不进资源文件,运行时由格式化 API 按 locale 生成。人要定的是策略——日期显示用用户设置的时区还是数据本身的时区、货币显示是否换算、排序按什么规则——策略定完,实现是死的。凡是在资源文件里看到 YYYY-MM-DD 这种字面量,就是这层漏了。

二、现象到成因的判别表

界面出了问题,先别改文案,按现象对号入座。下面这张表覆盖了这类事故里绝大多数的形态。

现象大概率成因怎么验证处置动作
某语言界面显示 undefined,或直接显示 {count} 原文占位符被翻译、被改名或被吞脚本比对源语言与目标语言同一 key 的占位符集合该 key 立即回退到源语言兜底,再单独修
切到某语言整页白屏或渲染报错ICU 消息语法括号不配对,解析器抛错用消息解析器批量 parse 整个 catalog,不依赖人眼修语法;把 parse 加进构建门禁
俄语、波兰语等语言下,数量取 2、5、21 时词形不对;或同一个 key 无论数量多少都输出同一句目标语言只填了 one/other,缺 few/many 等该语言必需的档;或整条复数结构被压平成了一条普通字符串先按目标语言的 CLDR 复数类别列出它必需的分支名,再逐 key 检查分支是否齐全、plural 结构是否还在补分支或还原结构;分支不齐时让构建失败,不要静默落到 other
日期显示成对方看不懂的顺序,月和日被读反日期格式在文案里硬拼进去了在 catalog 里搜日期格式字面量与月份英文缩写改由运行时格式化 API 输出
句子语序古怪,变量前后是半截话源码用字符串拼接组句,不是整句消息搜代码里对文案变量做加号拼接和模板串的地方合并成单条完整消息,变量走命名参数
某语言整块内容回退到源语言key 缺失,或资源文件层级被重排丢了嵌套diff 两侧的 key 集合,要求完全相等补齐 key;把 key 集合相等作为硬性检查
富文本区域标签错乱、链接失效文案里的 HTML 标签被翻译或吞掉校验标签的数量与配对是否与源一致改用组件插值,不让裸标签进 catalog
本机好好的,容器里非英语 locale 全变英文运行环境的本地化数据是精简版,或 locale 环境变量是 C/POSIX在目标环境里分两步:先打印 locale 相关环境变量,再实测一次非英语 locale 的格式化输出。变量为空或是 C/POSIX,是环境变量问题;变量正常但输出仍退回英文,是本地化数据被裁掉了前者显式设置环境变量,后者补装对应的本地化数据,别在本机下结论

最后一行值得单独说一句:格式化行为依赖运行环境的本地化数据和 LANG/LC_ALL 这类环境变量,而容器镜像为了瘦身经常把它们裁掉。这类问题的排查思路和运行环境不一致那类故障是同一套,判据是「本机对、线上错,且代码没差异」。

三、三段流水线与每一段的验收动作

把流程固定成三段,每段结束跑一个能出 0/1 结论的验收动作,AI 参与的风险就被框住了。

第一段,抽取。 让 AI 扫代码列出硬编码字符串并给出 key 建议,人审 key 规范。验收动作:抽取完成后在源码里搜被抽走的字面量,应该搜不到残留;再走查几个关键页面,确认没有漏抽的动态拼接串。

第二段,翻译。 每条输入都带上下文,同时给一份不翻译清单:品牌名、占位符、代码片段、单位符号、协议名。更稳的做法是翻译前把占位符替换成不可译的哨兵标记,译完再还原,还原不上就是这条出了问题。验收动作是一段脚本,这段脚本值得直接放进仓库:

import json, re, sys

PAT = re.compile(r"\{[A-Za-z0-9_]+\}")

with open("locales/en.json", encoding="utf-8") as f:
    src = json.load(f)
with open(sys.argv[1], encoding="utf-8") as f:
    dst = json.load(f)

bad = []
for k, v in src.items():
    if k not in dst:
        bad.append((k, "missing key"))
        continue
    if sorted(PAT.findall(v)) != sorted(PAT.findall(dst[k])):
        bad.append((k, "placeholder mismatch"))

for k, why in bad:
    print(why, k)
sys.exit(1 if bad else 0)

这段只处理扁平结构的资源文件,嵌套结构自己先拍平;正则也只认最简单的花括号占位符,用 ICU 完整语法的项目要换成对应的消息解析器。但形状是对的:校验必须能返回非零退出码,能被构建流程拦住,靠人看 diff 拦不住。

第三段,回填与格式化。 日期、数字、货币交给运行时 API,人只定策略。回填之后跑一遍伪本地化——把源语言字符串自动加长三到四成、加上重音符号、两端加边界标记,然后肉眼过关键页面。这一招能同时暴露三类问题:文案截断、UI 溢出,以及还没被抽出来的硬编码字符串(因为它不会被加长,一眼就能挑出来)。

回填的动作本身要可回滚。每种语言一个文件、每次一个语言单独提交,出事时一条命令就能退回去:

# 改动还没提交时,看这次碰了哪些语言文件、各改了多少行
git diff --numstat -- locales/

# 已经提交了就看那次提交本身
git show --numstat <commit> -- locales/

# 只退某一个语言:先查它自己的提交历史,再从最后一个正常的提交里把它取回来
git log --oneline -- locales/de.json
git checkout <最后一个正常的 commit> -- locales/de.json

注意别顺手写成 git checkout HEAD~1 -- locales/de.json:只有当这个文件最近一次被改动恰好就发生在 HEAD 这次提交里,HEAD~1 拿到的才是它的正常版本;如果坏改动是好几次提交之前混进来的,HEAD~1 里存的同样是坏版本。按语言分开提交之后,各语言文件的「上一个正常版本」落在不同的提交上,所以得先 git log -- <文件> 查清楚它自己的历史再取。

如果一次提交里 diff 大到读不动,那不是效率高,是把回滚成本抬上去了。让 AI 改动文件的范围必须被明确框住,这一点在控制 AI 改动范围里有更一般的讨论,国际化只是它最容易失控的场景之一——因为资源文件又长又规整,模型特别喜欢顺手「整理」一下。

四、什么情况下别再折腾

修国际化问题容易上头,因为每个 bug 看起来都只差一点点。给自己设几条硬线。

线上正在报错时,先兜底再修。 把出问题的语言整体回退到源语言,或把出问题的 key 单独回退,让页面先能用。不要在生产环境上一条条调翻译,那是把排查过程暴露给用户。

同一个 key 改到第三轮还在报格式错,停手改结构。 说明这条消息的结构本身太复杂——嵌套复数、嵌套选择、里面还带标签。拆成两句,或者改写成规避句式(把「你有 3 条新消息」改成「新消息:3」),语法难题直接消失。为了保住一句漂亮的话去搏一个跨语言的语法结构,投入产出不划算。

AI 一次改动的 diff 超出你能通读的范围,直接整文件回滚重来。 不要在一个读不懂的 diff 上做局部修补,那样你永远不知道它还顺手动了什么。回滚,然后按语言、按模块切小批次重跑。

遇到复数分档多的语言、有语法性别或敬语层级的语言,别指望一次到位。 这类语言的规则和语境绑定很深,机器给的结果通过了语法校验也可能读着别扭。要么找母语审校把关,要么在源文案阶段就规避掉这些结构。

有几类文案不该让 AI 定稿: 法律条款、合规与风险提示、隐私政策、涉及金额与计费的表述。前三类错了是法务问题,最后一类的坑还叠加了金额本身的浮点精度问题。这些交给人写、人审,AI 最多做初稿参考。

顺带一句选型上的前置约束:如果你打算把海外的翻译或模型服务接进这条流水线,先去读它的服务条款和地区可用性说明——不少海外产品对中国大陆有明确的区域限制、不支持直连使用,账号主体和结算方式也可能一并受限。这类限制本文不讨论任何规避办法;能不能合规使用、待翻译的文案里是否含有不便出境的数据,都要在动手前跟法务和合规确认。确认下来接不上,就换成境内可合规采购的服务,或者把这段改成人工加术语表的做法,别等整条流水线搭完了才发现跑不通。

五、避坑清单

占位符被当词翻译。 为什么会踩:模型没有「这段是代码」的概念,{userName} 在它眼里就是一个陌生词,翻译是本能。怎么避:翻译前用哨兵标记替换占位符,译后还原并比对集合;把比对脚本挂进构建流程,让它能拦住合并。

资源文件被顺手重排。 为什么会踩:模型倾向于输出「整洁」的完整文件,于是 key 顺序变了、缩进变了、嵌套层级也可能被压平。怎么避:只让它输出变更条目(key 加译文),回填动作由脚本执行;文件级 diff 行数超预期就当作事故处理。

复数只给了 one 和 other。 为什么会踩:源语言多半是英语或中文,两类分支足够用,模型就照着源的形状填目标。怎么避:按目标语言的 CLDR 复数类别先生成分支骨架再让它填空;缺分支时构建失败,不要静默回退到 other。

日期和货币被硬拼进文案。 为什么会踩:源文案本来就是拼的,抽取时原样搬进了资源文件,后面所有环节都以为它是文案。怎么避:抽取阶段就把格式化字面量单列出来,禁止进 catalog,一律改走运行时格式化。

用字符串拼接组句。 为什么会踩:写源码时觉得「前半句 + 变量 + 后半句」很自然,而这个语序只在源语言成立。怎么避:一句话一个 key,变量用命名参数,让译者能整体调整语序。

按中文或英文的长度估 UI 宽度。 为什么会踩:本地开发只看源语言,视觉上没有压力。怎么避:伪本地化跑一遍,再挑一个明显更长的目标语言过一遍关键页面和按钮。

改文案时顺手改了 key。 为什么会踩:key 名和内容不匹配看着难受,改了显得整洁。怎么避:把 key 当接口对待,只增不改;确实要重命名就走一次显式的映射迁移,并保留旧 key 一个版本。

只测了界面,忘了别的出口。 为什么会踩:邮件、导出文件、短信、PDF 往往走另一条渲染链路,用的是服务端 locale,跟前端不是一套。怎么避:把所有会向用户输出文案的出口列成清单,每次国际化改动按清单过一遍。

RTL 语言只翻不改布局。 为什么会踩:翻译完看着字都对,但整个界面的方向没镜像。怎么避:布局用逻辑方向属性而不是写死左右,并保留至少一个 RTL 冒烟页面。

六、收束

这件事的分工其实很清楚:AI 擅长的是「量」——扫代码、抽字符串、逐条翻译、批量补分支骨架;人要守的是「契约」——key 的稳定性、占位符的完整性、消息结构的合法性、格式化的归属。契约不靠人眼守,靠脚本守,人只负责把脚本写对、把它挂进构建。

上线前过一遍这份自检:

  • 所有语言的 key 集合与源语言完全相等,缺失会让构建失败;
  • 每条消息的占位符集合与源语言一致,哨兵标记全部还原;
  • 整个 catalog 能被消息解析器完整 parse,没有括号不配对;
  • 目标语言的复数分支按其 CLDR 类别齐全,缺分支会让构建失败;
  • catalog 里搜不到日期格式字面量与货币符号硬拼;
  • 伪本地化跑过一遍,关键页面无截断、无未抽取的硬编码;
  • 目标运行环境里实测过一次非英语 locale 的日期与数字输出;
  • 邮件、导出、通知这些非界面出口都过了一遍;
  • 每种语言的资源文件是独立提交,能单独回滚。

九条都能打勾,剩下的问题就只是翻译润色了——那类问题不会让你半夜被叫起来。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。