本地化国际化AI 翻译工程

三天十种语言:AI 翻译产品本地化实战笔记

我们在一个长周末里将 Transept 本地化成了德语、乌克兰语、中文、葡萄牙语、法语、西班牙语、捷克语、意大利语、波兰语和土耳其语,并采用了我们向用户推荐的模式:结合上下文、术语决策以及人工后期编辑。这些是我们在此过程中积累的复数规则、语体切换、排版调整以及 hreflang 方面的经验教训。

Mariia Ivakhnenko
Mariia Ivakhnenko34 分钟阅读
三天十种语言:AI 翻译产品本地化实战笔记
本页内容

从 7 月初的一个周五早晨到周日晚上,Transept 从一款纯英文产品,一跃成为了支持 11 种语言的产品。德语、乌克兰语和中文率先发布,巴西葡萄牙语紧随其后;法语、西班牙语、捷克语、意大利语和波兰语在次日上线;土耳其语则在周日完成了收官。

我们开发的是一款 AI 翻译工具。如果一直只支持英语,那无异于承认我们连自己的宣传口号都不信。因此,我们按照自己建议用户的方式进行了本地化:用机器翻译处理广度,用人工判断进行决策,并记录下每一个决策,确保无需重复决策。

每种语言意味着应用中约 3,300 条字符串、营销网站和帮助中心约 3,000 条字符串、六个法律页面以及产品发送的所有电子邮件。这篇文章就是我们的实战笔记:那些让我们意外的发现、我们踩过的坑,以及几个被我们及时化解的危机。如果你正准备进行产品本地化,或者你是一名职业翻译并想从内部视角了解软件本地化,那么这篇文章就是为你准备的。

为什么我们不直接用谷歌翻译?

这是一个很实在的首要问题。机器翻译既便宜又神速,全世界的字符串表都可以在一个下午的时间里塞进 API 跑完。既然如此,为什么不直接这么做呢?

因为我们尝试过类似的做法,并在用户察觉之前,就先在自家产品里发现了这种失败。在处理营销页面的第一轮翻译时,我们沿用了大多数字符串流水线的常规做法:把它们看作电子表格中一个个孤立的单元格,按长度分组,每一行都是“盲译”出来的。输出结果语法正确,读起来也通顺,但却隐约透着一种死气沉沉的僵硬感。标题和与之配套的强调短语是由两次完全独立的 API 调用翻译的,彼此素未谋面。常见问题的答案对对应的问题一无所知。对比表中的两列内容各说各的,因为谁也不知道对方的存在。与此同时,应用内的 UI 界面在每种语言下的阅读体验都明显更好,因为那里的每条字符串在编写时都附带了上下文注释。同样的模型,同一天,同样的流程。唯一的区别就在于上下文

这成了我们处理一切内容的准则:模型看到的是连贯的整体,而非被打乱的片段;每条字符串都附带说明,解释它出现在哪里,以及用户看到它时正在做什么。“保存按钮”算不上上下文注释。“每个表单提交时的通用保存按钮标签;用户刚刚编辑了一个数值并正在提交”,这才是合格的注释。它是写给从未打开过应用的翻译人员看的。无论翻译者是人还是模型,这都至关重要:没有它,两者都会产出垃圾;有了它,两者的表现都会好得惊人。

翻译质量首先取决于上下文,其次才是翻译者的水平。任何职业翻译都会告诉你这一点。事实证明,当翻译者是机器时,这一规律同样适用。这很方便,因为给机器提供更多上下文,比对它进行责备更容易实现规模化。

我们最终确定的这套工作流,行业术语叫作 MTPE(机器翻译后编辑)。机器负责通篇初译,人类则在关乎信任的环节进行把关。但 MTPE 只有按这一个顺序才行得通:人类决策,机器执行,人类验证。决策先行。这就聊到了德语。

du 还是 Sie:语体选择是一项产品决策

德语是我们支持的第一种语言,在上线后的几小时内,它就给我们上了第一课。

德语中有两种表达“你”的方式:非正式的 du 和正式的 Sie。我们上线时使用的是 du:亲切友好,带有初创公司的风格,这也是你手机上一半的应用都在使用的语体。随后,我们审视了真正的受众(专业翻译人员、翻译机构、法律团队),并在当天就将整个产品切换成了 Sie

这一课让我们明白了语体切换的代价:所有内容都要重新翻译一遍。代词的选择会产生连锁反应,影响到动词变位、祈使句、物主代词,甚至大写规则。如果逐条字符串进行修补,到处都会留下转换不彻底的残留:比如一个用了 Sie 的句子,中间却藏着一个 du 的动词变位。我们从头开始重新翻译了所有语料库,随后通过检索 du/dein/dich 这些明显的词干,来确保切换彻底生效。

The German homepage of Transept: "Wo jede Entscheidung zur Erinnerung wird"

德语版首页。品**牌口号“让每一个决策都成为记忆”,最终成了对本地化本身的精辟总结。

从那时起,语体成了我们为每种新语言确定的首要问题,甚至在翻译第一条字符串之前就要定下来,因为它会渗透进六千条字符串中。西班牙语选择了正式的 usted,且采用了欧陆西班牙语而非拉美西班牙语,从而与德语的 Sie 和法语的 vous 保持一致。意大利语则采用了正式的 Lei。至于波兰语?好吧,波兰语值得专门拿出一节来聊。

那些没人提醒过你的复数规则

每个对英文软件进行本地化的团队都会掉进同一个陷阱:英语的复数规则极其简单,以至于你的字符串格式可能只预留了两个位置:“一个”和“多个”。德语的表现也如出一辙,这让你进一步放松了警惕。接着,当你加入一种斯拉夫语时,UI 的大片区域就会在不知不觉中回退到英文。

乌克兰语是我的母语,按理说我最不该在这上面出差错,但它其实有四种复数类别。一份文档是一种形式;两份、三份、四份是另一种形式;五到二十份是第三种;分数则是第四种。由于我们的字符串表只预留了英语式的两个位置,因此对于 2、3、4、5、11、22(这些用户实际上能看到的绝大多数数字),应用显示的都是英文。大约有 80 条字符串(涉及积分、文档、成员、文件等),每一条都像是一道扎眼的缝隙,横在乌克兰语的句子中间。

Engineering notes from the Ukrainian build-out, describing how counts of 2, 3, 4, 5, 11, 22 fell through to English before the plural forms were filled in

发现这个 Bug 当天的构建说明。“回退到英语——这是一个非常明显的 Bug”,这真是工程师式的轻描淡写。

解决办法在原理上并不复杂(即提供该语言语法定义的每一种复数形式,而不仅仅是英语中的那两种),但在梳理这些形式究竟什么样的过程中,我收集到了整个项目里最令我着迷的一组冷知识:

语言复数类别意外之处
德语2无。这正是陷阱所在:它让你误以为全世界都只有两种复数形式。
乌克兰语421 使用的是单数形式:«21 крок»。
捷克语4第四种形式仅用于小数,是一个 UI 永远不会渲染出来的“幽灵”类别。
波兰语4复数类别与捷克语相同,但分布规律截然不同。21 使用复数形式:„21 kroków”。
法语30 是单数。而第三种形式仅在整百万时触发。
西班牙语30 是复数,与法语恰恰相反。
土耳其语2在任何数字之后,名词都保持单数形式:是“5 belge”,而绝非“5 belgeler”。
中文1一种形式涵盖所有情况。它是这张表里最简单的语言。

其中有两个例子值得深入研究。

**波兰语不是捷克语。**它们同属西斯拉夫语支,是兄弟语言,所以我们曾以为波兰语会遵循捷克语的模式。但事实并非如此:尽管谱系关系不同,波兰语的复数分布规律反倒与其东斯拉夫表亲——乌克兰语一致。“21步”在波兰语中写作 „21 kroków”,在乌克兰语中则是 «21 крок»:这两种语言在几乎所有其他方面都高度一致,唯独在这里,一个是复数,另一个却是单数。这一教训具有普适性:看语法,别看语系。语言亲缘关系充其量只是个参考。

**法语有一种复数形式,仅在数值达到一百万时才会出现。**并非“一百万左右”,而是恰好在 1,000,000、2,000,000 等数值上。对于其他所有数字,法语的表现都与英语一致。我们现在已经支持了这种处理方式,希望哪天有位字数余额正好是整百万的用户能注意到这个细节。

本章还有一个坑要提醒工程师们:乌克兰语的语言代码是 uk,而不是 uaua国家代码,如果你用了它,复数处理机制并不会报错,而是会解析为英式英语规则,导致前面提到的那些问题再次上演。同样的混淆也出现在捷克语(是 cs 而非 cz)和丹麦语(是 da 而非 dk)身上。

“Воркфлоу”还是“робочий процес”:术语是一系列决策的产物

最棘手的问题往往不在于语法,而在于围绕单个词汇展开的博弈。

workflow 为例。乌克兰语中有一个本土化的直译词(«робочий процес»,字面意思是“工作过程”),这也是词典里会给出的标准译法。但整天泡在这款软件里的翻译人员并不会说 «робочий процес»,而是直接用外来词 «воркфлоу»,就像英语吸收了 rendezvous 一样。经过反复权衡,我们最终决定采用这个外来词。这样读起来才符合业内的真实语境。

再以我们自己的功能名称 Memory 为例。在德语版中,我们习惯保留一些英文术语:CreditsTranslation Memory。德语专业人士会将这些词视为行业通用词汇,就像他们看待 UpdateLogin 一样。然而,当乌克兰语沿用同一份“不翻译列表”时,“Translation Memory”夹在西里尔文句子中间,读起来就像是有人忘了翻译这个词。西里尔文本中的拉丁单词显得格外突兀。更糟糕的是,乌克兰语是一种屈折语:随着句式变化,“Пам'ять”需要进行格变化,而未翻译的英文名词显然无法做到这一点。我们后来将其更正为 «перекладацька пам'ять»,并由此意识到,“不翻译列表”必须因语言而异。中文则从另一个角度印证了这一点:在中文里,所有词汇都要翻译(“翻译记忆库”对应 Translation Memory,“术语库”对应 glossary,“工作流”对应 workflow),只有产品名称本身保留拉丁字母形式。

我们的整个产品都构建在这一教训之上:术语是一系列不断累积的、有据可依的决策。选用这个词是出于行业惯例,保留那个英文词是因为它是品牌,翻译这一个词则是由于文字体系的要求。只需做一次决策并记录缘由,往后的每一份文档便都能直接沿用。这才是术语库真正用途,也是为什么我们将翻译记忆库视为决策背景,而非仅仅是一堆匹配句子的堆砌。

兄弟语言之间,亦是处处分歧

如果说复数规则是语法层面的考察,那么排版规范就是礼仪层面的修行。而我们学到的最深刻教训是:邻近语言之间没有任何经验是可以直接套用的。

法语坚持在每个冒号、分号、感叹号和问号前加空格(不换行空格,s'il vous plaît),并用带内侧空格的“角括号”括起引文(« guillemets »)。而一境之隔的西班牙语则完全相反:标点符号紧贴文字,角括号紧抱内容(«就像这样»),且疑问句必须以倒问号开头。¿ 和 ¡ 是强制性的语法要求。

最微妙的挑战来自意大利语。正式意大利语会将礼貌代词(LeiSuoSua)的首字母大写,部分原因是为了将正式的“您”与小写的 lei(意为“她”)区分开来。这一点对我们尤为重要,因为我们的文案经常提到 Literess——她是不折不扣的 lei——而紧挨着的句子可能就在用 Lei 称呼用户。一个大写字母在这里起到了定海神针 般的消歧作用。此外,意大利语还打破了按钮命名的惯例:法语和西班牙语习惯用动词不定式(EnregistrerGuardar)作为按钮标签,但意大利语使用的是裸命令式(SalvaAccedi)。这看起来像是非正式表达,实则不然;这纯粹是意大利语软件的表达习惯。

波兰语提出了一个之前所有语言都未曾遇到的问题:它的礼貌称呼是区分性别的。德语的 Sie、法语的 vous、西班牙语的 usted 都是一种形式适用于所有用户。而波兰语的礼貌称呼对男性用 Pan,对女性用 Pani,甚至连过去时动词都要根据所称呼对象的性别进行屈折变化。我们不知道用户的性别,也不该随意猜测,因此波兰语版产品采用了优秀波兰语软件的通用做法:使用无人称结构(“Zapisano”,意为已保存)、亲切的第一人称复数公司口吻(“Zapraszamy”,意为我们邀请您),并且仅在确实无法回避的情况下才使用区分性别的直接称呼。

土耳其语是我们支持的最后一种语言,起初看起来很简单(一个性别中立的正式称谓 siz,且完全没有语法性别),但随后就在拼写法上让我们交了学费。在土耳其语中,字母 i 的大写形式是带点的 İ,因此“iptal”(取消)大写后应为“İptal”;如果不带点写成 I,那就是另一个字母,属于明显的拼写错误。语言名称的首字母需要大写(如“Türkçe”),而罗曼语族和斯拉夫语族的所有语言则习惯将其小写。而且,由于土耳其语属于黏着语,即使是那些“不翻译”的品牌名称也要随之变格:格后缀通过撇号连接,所以用户会升级到“Pro'ya”,或者导入“Transept'e”。品牌名本身也要变格,这一点在任何国际化手册中都找不到。

作为平衡,中文则像是一堂物理课:全角标点(,。!?)、中西文混排时微妙的留白(“使用 Transept 翻译”),以及长度仅为英文一半左右的文本。在经历了会让文本膨胀并撑破布局的德语后,中文又会让文本缩水,导致按钮显得过大。你的 UI 必须能够同时兼容这两种极端情况。

漏网之鱼:从未进入翻译流程的字符串

本地化中有一类特殊的 Bug:那些从一开始就没进入系统的字符串。

我们的翻译覆盖率是由编译器强制保障的:只要缺少任何一种语言的译文,程序就根本无法完成构建。但这种检查只能识别那些请求过翻译的字符串。如果一个徽章渲染的是数据库原始值(直接取自枚举值 owner),它就从未提出过请求。于是,它一路绿灯通过了每一次构建,在所有 11 种语言中都显示为英文。它之所以能“隐形”,原因很简单:当应用还只有英文版时,硬编码的英文就是天然的伪装。

乌克兰语用户的第一次使用就发现了 owner 徽标。接着是分享对话框里的访问权限标签。然后是整个评论栏:大约有 25 个字符串从未经过翻译层。接着是这里的屏幕阅读器标签,那里的占位符。每增加一种新语言,都是对之前所有内容的一次审计:当周围的文本切换为乌克兰语的那一刻,每一个“漏网之鱼”都像信号弹一样显眼。

发布一种语言不只是翻译任务,更像是一次人口普查。直到第二种语言迫使每一个字符串都出来点名报到,你才会知道哪些字符串是真实的、可找到的、可翻译的。务必在计划中预留审计环节,因为编译器救不了那些从未请求过翻译的字符串。

Literess 拒绝在乌克兰语中表现出“自鸣得意”

整个项目里我最喜欢的 Bug,严格来说其实不算是个 Bug。我们至今仍无法完全解释它。

Literess 是我们的内部编辑助手(你可能在专门介绍她的文章中见过她),拥有 48 种情绪储备。她的虚拟形象会在工作时实时反馈:她像斟酌措辞一样挑选表情,并将其作为回复的一部分。而她的招牌神态是自鸣得意:那是当她抓到你漏掉的错误时,脸上浮现的一抹志得意满的坏笑。

她在每种语言里都会这么做。德语、法语、中文、波兰语——只要抓到错别字,她就会露出那种得意的神情。然而,当我们切换到乌克兰语并运行同样的场景(那些在其他语言中百分之百能触发得意表情的场景)时,她却不配合了。同样的模型,同样的提示词,同样的性格,菜单上同样有 48 种情绪可选。但在乌克兰语中,她始终保持着 happy(开心),或者顶多变得 serious(严肃)。她就是拒绝在乌克兰语里表现出“自鸣得意”。

我们最认可的推测,正是翻译人员会给出的那种。在英语中,smug 可以带有一种亲昵感:对于一个刚帮你找出错别字的卡通助手来说,这种表情几乎是一种褒奖。但乌克兰语里没有这样的词:最接近的对应词 «самовдоволена» 纯粹 是个贬义词。那是一种干巴巴、讨人嫌的自我陶醉,没有眨眼示意的俏皮,也毫无魅力可言。而一个用乌克兰语思考的模型似乎明白这一点。当她用一种根本不存在“亲昵的得意”这一概念的语言组织回复时,她也不会在情感层面去调用这种情绪。这个词存在于她的词汇表中,但这种抗拒却植根于她的行为逻辑里。

我觉得这既有趣又深刻。整篇文章都在讨论字符串(复数、语域、标点),而在这个案例中,即便每一个字符串都准确无误,产品本身还是发生了改变。语言并非只是蒙在 AI 产品上的一层外壳;它会引导承载它的模型。本地化一个智能体,意味着除了检查标签,还要检查她在每种语言中的性格:你必须亲自去了解她在每种语言中到底是什么样的人。

(顺便说一句,标签也需要留心:情绪词 content(意为“满足”)在德语版中差点被发布为“Inhalt”(名词“内容”),直到一行翻译上下文明确了其含义。在本地化中,成本最低的质量干预手段依然是写一句话告诉翻译人员这个字符串到底是什么。)

枯燥的另一半:hreflang、站点地图,以及避免自我竞争

如果没人能找到这些页面,上面提到的所有努力都将付诸东流。因此,我们将 SEO 这一半的内容浓缩如下。

当你同时发布 11 种语言版本的同一个页面时,Google 默认会认为你发布了 11 个几乎完全相同的副本,且它们之间存在竞争关系。防止这种情况发生的机制是 hreflang 标注,它比任何编译器都要严苛:

  • 它必须是互惠的。 英文页面指向对应的德文页面,同时德文页面也必须以完全相同的一组链接指回,否则 Google 会忽略整个设置。单向的 hreflang 毫无意义。
  • **x-default**** 指向你的规范版本**:这是针对那些语言与你提供的选项都不匹配的用户的备选方案。
  • 永远不要标注不存在的页面。 如果一个页面的乌克兰语版本尚未上线,就不要添加备选链接;指向 404 页面的无效 hreflang 比完全不加还要糟糕。这听起来显而易见,但当你面对部分翻译的界面时,就需要进行严密的记录管理:例如,我们的期刊文章是逐篇翻译的,因此每篇文章只声明它实际存在的语言版本。
  • **站点地图也带有相同的标注。**我们将自己的站点地图重构为按语言划分的索引,这在 Search Console 中带来了一个意外的好处:可以按语言查看索引统计数据。你可以分别观察每种语言页面的收录情况,并能立即发现是否有哪种语言进度滞后。

这一切都是基础架构工作。如果没有这些底层支持,本地化只会让你的权重分散到 11 个相互竞争的副本中。使用用户母语所带来的全部 SEO 收益,都取决于 Google 是否理解它们其实是同一个页面,而非 11 个。

我们拒绝机翻的内容

尽管上文对机器翻译推崇备至,但有三样东西是我们刻意排除在自动化流程之外的。

这些文章。 期刊的界面元素(标签、署名、导航)和其他内容一样是机翻的。但文章本身是由我们在 Transept 中手动翻译的。带有署名的长篇写作正是机器输出尚不足以胜任的领域,而在我们自己的编辑器中翻译自己的文章,是我们所能想到的最真诚的产品测试方式。你现在阅读的德语或乌克兰语版本的文章,都经过了我们对外销售的同一套编辑器、术语表和审核流程。

法律页面。 机器翻译会生成草案(并固定住每一个公司名称、地址、日期和条款编号),但服务条款和隐私政策是具有法律约束力的文件,需要律师针对每种语言进行审核。营销文案中的翻译错误损失的只是印象分;而合同中的错误损失的则是真金白银。

涉及信任的关键界面的最终把关。 定价、账单、身份验证、电子邮件:在确认一种语言完成本地化之前,母语使用者会审阅这些内容。这就是 MTPE(机器翻译后编辑)的初衷:机器完成所有文字工作,以便人类能将稀缺且昂贵的精力集中在那些带有风险的词句上。

这三项决策背后的逻辑如出一辙:机器翻译只是初稿,而判断初稿在何处已经足够,这本身就是本地化的核心技能。我们之所以能在三天内完成十种语言的本地化,是因为我们精准地知道哪些事情不该交给机器去做。

让每一个决策都化为记忆

回顾一下这份清单:正式或非正式的语体;选用“Воркфлоу”而非“робочий процес”;针对德国用户保留英文术语 Translation Memory,而针对乌克兰用户则使用“перекладацька пам'ять”;意大利语中大写的 Lei;波兰语的无人称表达;土耳其语后缀前的撇号;专为整数百万预留的复数形式;以及一个在九种语言中都会“坏笑”,却在第十种语言里拒绝这么做的助手。

这些几乎都无法提前查到,而最关键的部分在于决策:经过争论、敲定,随后被成千上万条字符串和未来的每一份文档所沿用。丢掉这些决策,意味着要为同样的争论再次付出代价;而如果只保留决策而没有记录理由,那么后来者终究会再次对此争论不休。

归根结底,这就是我们将产品设计成现在这样的原因:将翻译记忆库作为决策背景,将术语表和风格指南作为语体和术语的载体,并打造一个专门确保已定决策不被遗忘的编辑器。对 Transept 进行本地化,是我们第一次将这套理念全面应用于我们自己的产品。工具经受住了考验。上述经验正是我们正在反馈到产品中的内容,因为这十种语言的尝试只是排演,而非终章。

作者

Mariia Ivakhnenko
Mariia Ivakhnenko联合创始人

Transept 联合创始人。拥有三个英语语言文学学位——曾求学于基辅、俄斯特拉发,并在萨尔茨堡度过了一年。作为乌克兰人,她的写作生涯却大多以英语进行。她最初作为提示词工程师进入 AI 领域,随后转向产品和生命周期营销。她撰写关于真实人物的半虚构故事,并不断探讨在语言转换中究竟丢失了什么。