Sierra 工程师如何用 AI 编码智能体四个月完成产品本地化
AI-native product localization
Sierra 工程师 Stephen Burgess 用 AI 编码智能体在不到四个月内完成产品本地化,而十年前在 Slack 做同类项目需要 9 到 12 个月、10 人团队。
作者以一人四个月完成产品本地化的亲身经历,拆解了批量脚本、linter 与文档反馈循环的具体做法。
大约十年前,我在 Slack 时曾参与一个 10 人团队,负责应用的本地化。那花了 9 到 12 个月。今年在 Sierra,我参与了一个类似的项目,基本上独自完成,使用 AI 编码代理。耗时不到四个月。
AI 让工作变得更快更轻松,因为以前需要专门的跨职能团队才能完成的事情,现在可以由一名工程师在很大的范围内编排 AI 工作流来完成。
在这篇文章中,我将介绍我是如何着手这次迁移的,以及我学到了什么。
为本地化准备应用
在进入 AI 部分之前,值得先描述一下为本地化准备应用涉及哪些工作,因为“直接丢给翻译器跑一遍”严重低估了这项工作的量。
Slack 和 Sierra 的 Agent Studio 最初都是把英文文本直接写死在代码库里的。因此,React 组件中的文本、API 字符串、只是追加一个“s”的复数化辅助函数、拼接字符串等等,全都默认英文是唯一的语言。
为本地化准备应用,意味着在整个技术栈中系统性地移除这些假设。
- 找出面向用户的字符串,并用本地化函数包裹它们
- 重构以英文为中心的模式,将字符串转换为能够正确支持多语言的 ICU MessageFormat 语法
- 将字符串提取到翻译文件中
- 生成本地化翻译
- 修复因翻译后字符串变长而导致的 UI 破损
- 添加 lint 和 CI 自动化,使新字符串长期保持本地化
除此之外,你还需要语言区域偏好、语言区域感知的格式化,以及在整个技术栈中传播语言区域状态的基础设施。
项目与团队对比
Slack —— 9 到 12 个月,4 种语言区域
- 3 名后端工程师
- 3 名前端工程师
- 2 名移动端工程师
- 1 名工程经理
- 1 名产品经理
- QA 与设计
Sierra —— 4 个月,最初 2 种语言区域(西班牙语和日语),后续还有 4 种
- 我
- 编码代理
虽然我仍然依赖母语者、内部试用和人工审查来保证翻译质量、语言细微差别以及整体 UX 的正确性,但更少的人眼意味着更少的 QA 发现——这是一个真实的权衡。例如,我仍然会在应用较少访问的部分发现隐藏的未翻译字符串。
而且这也不是完美的 1:1 对比。我之前的经验帮了很大忙。而且这两个产品的范围和成熟度也不同。
削减协调开销
单靠 AI 并不能让这项工作快 10 倍——真正起作用的是其后果:协调工作大幅减少。过去,像这样的项目需要拆分工作、同步迁移、协调评审,并在各团队之间管理 QA。
当一名工程师借助 AI 就能完成其中大部分工作时,很多开销就消失了。
字符串包裹之旅
这正是 AI 节省数百小时繁琐工作的地方。
在 Slack 时,我们尝试过用代码生成方法来部分自动化这一过程,但它们从来都不够可靠。为本地化准备字符串,不只是机械地用 i18n.t() 包裹文本。它们通常需要被重写、重构以消除拼接,或转换为 ICU MessageFormat 语法。
这正是 AI 擅长的任务,因为它能理解意图,而不仅仅是转换语法。话虽如此,这仍然是大量的工作。我们在 900 多个前端文件中都有面向用户的字符串,而每一个 AI 生成的改动仍然需要人工审查。
以下是我经历的演进过程。
方法 1:IDE 代理
我最初先让 Cursor 为本地化准备单独的文件。
结果出奇地好,但工作流本质上是阻塞且串行的。一次处理一个文件,在大型代码库中根本无法扩展。
方法 2:云端智能体
接着我尝试了云端智能体,这样多个智能体可以同时处理许多文件。
这立刻提升了吞吐量,但引入了一个新问题:规模化地追踪错误。
这些智能体在字符串包裹方面通常做得很好,但仍有一小部分错误率——错误拼接的字符串、细微的 ICU 语法错误、变量周围别扭的所有格。
还有一个工作流问题。每个智能体都会生成自己的 pull request,这意味着每一批工作都会产生另一个审查队列。作为一名独立工程师,我仍然需要人工审批,而等待几十个范围狭窄的 PR 很快就成了瓶颈。
我发现自己不断在智能体之间来回切换,审查输出、纠正错误、管理 PR,并更新文档以防止未来运行中出现同样的失败。
方法 3:批处理脚本
真正的突破是编写一个批处理脚本,直接调用 Claude,完全绕过智能体 UI。
该脚本接受文件列表或 glob 模式,将每个文件连同本地化技能文档一起发送到 API,并以可配置的并发数将转换后的结果写回磁盘。
这把工作流变成了可重复的流水线,并让我能够将更改批量合并为更大、范围更紧密的迁移,审查起来容易得多。
我以大约三十个文件为一批进行处理。每批之后,我都会手动审查每个更改过的文件——不是因为 AI 通常会出错,而是因为当它出错时,我需要在错误传播到数百个更多文件之前捕捉到这种模式。
每个审查周期之后,我会收集错误,让一个智能体解释它们发生的原因,然后用明确的指导更新技能文档,以供未来运行使用。
随着时间的推移,文档演变成了一本高度专业化的操作手册,涵盖边缘情况、项目特定模式和常见的本地化失败。
到那时,这个过程变得出奇地高效:
- 运行一批
- 审查模式性失败
- 改进指令
- 重复
linter:共同进化
一旦大部分字符串包裹工作完成,我就转而构建一条 linter 规则,用于标记未包裹在 t() 中的面向用户的字符串。
linter 本身主要由 AI 编写。我会描述我想要的行为,智能体会生成或修改规则,迭代循环足够快,我几乎可以实时地完善它。
工作流最终变成了:
- 运行 linter
- 使用批处理脚本准备被标记的文件
- 再次运行 linter
- 调查剩余的警告
一些剩余的警告是迁移过程中真正的遗漏,但大多数结果是误报。
到那时,我可以直接把 AI 智能体指向准备好的文件,问它哪些警告是误报,然后完善 linter,以避免在未来运行中标记这些模式。
由此出现了一种有趣的共同进化形式。迁移流水线和 linter 实际上在相互交叉验证,AI 帮助同时维护这两个系统。
这两个系统单独来看都不完美,但合在一起,它们对文件是否真正准备好进行本地化产生了置信度高得多的信号。
重要的部分在于经济性:由于优化任一系统的成本已经变得如此之低,我不断改进它们,而不是容忍已知的不准确之处并继续前进。
剧情反转:上下文窗口
当我回到 Cursor 中——迭代 linter、处理边缘情况、继续完善技能文档——时,意想不到的事情发生了:错误率又开始上升。
更奇怪的是,这些错误都是技能文件中已经记录过的问题——文档明确告诉 AI 不要使用的模式。
答案原来是上下文窗口。
负责更新技能文档的 agent 逐渐为我们发现的每一个失败添加了冗长的解释和大型代码示例。随着时间的推移,文档变得足够大,以至于 API 不再能可靠地处理全部内容。它会消耗文档的开头部分,并静默地丢失埋在后面的指令。与批处理脚本不同——后者对每个文件发起一次全新的无状态 API 调用——交互式 Cursor 会话会累积状态:对话历史、工具结果和文件读取都会在多个回合中堆叠起来。
整个项目中最大的技术教训是意识到,一个旨在改进 AI 输出的反馈循环,最终可能会因为让上下文变得太大而无法有效消耗,从而降低输出质量。
修复需要两项改动。
首先,我重写了文档,使其大幅精简:更少的文字、更少的示例、每行更多的有效信息。
其次,我将文档拆分成更小的、聚焦的文件,可以有选择地引用,而不是一次性全部加载。
系统不再是一份涵盖所有边缘情况的巨型文档,而变得更像一个索引:
- 如果你在处理面板,请阅读 panels-and-typing.md
- 如果你不确定某些内容是否应该翻译,请阅读 what-not-to-translate.md
由此产生的文档结构与传统的面向人类的文档截然不同。它密集、高度可扫描,并且针对选择性检索而非顺序阅读进行了优化。
字符串上下文和描述
AI 显著改变工作流程的另一个领域是字符串描述。
本地化中更微妙的问题之一是上下文。英语单词“close”可能意味着关闭对话框、结束会话,或描述距离很近。译者需要足够的信息才能知道哪种解释是正确的。
在 Slack,我们通过在本地化字符串上方使用 @i18n 注释来处理这个问题。在提取过程中,这些注释被拉入翻译文件并展示给译者。
最初我在 Sierra 也遵循同样的模式。由于我们使用 AI 来帮助生成翻译,这些描述变得更加重要。我让 AI 生成注释,这使得它们产出很快——同时也极其冗长。
在大多数情况下,AI 的冗长是一个缺陷。但对于本地化上下文来说,它往往是一个特性,因为当翻译模型对字符串的实际含义及其使用方式有更丰富的上下文时,它们的表现会更好。
但在早期概念验证评审期间,我收到反馈说这些注释让组件变得难以阅读。UI 文件被人类实际上并不需要的元数据弄得杂乱不堪。
这个反馈结果证明很重要。
实际上,我们在代码库里塞满了只有 AI 才会读的注释,而且只在生成翻译时才会读。一旦意识到这一点,整个架构就开始显得本末倒置了。如果描述是 AI 生成的,而 AI 又是这些描述的主要消费者,那它们为什么还要内联在源代码里呢?
智能体生成描述的过程本来就很直接:它读取周围的代码来推断字符串的用途和上下文。这意味着描述可以在提取过程中动态生成,而不必永久嵌入代码库。
于是我们就这么做了。
在提取过程中,系统会记录每个字符串的文件位置和源码位置。随后的富化步骤会把一小段周围代码发送给 Claude,让它生成上下文描述。该描述会直接存储在翻译文件中,而不是放在应用代码旁边。
结果出奇地干净。我们可以为译者生成详细的上下文描述,而不会用人类其实并不需要阅读的元数据把代码库撑大。
结论
在开始这个项目时,我原本以为 AI 主要是帮我加快速度。我没想到的是,它会如此深刻地改变工作本身的结构。
最大的收益并非来自 AI 神奇地写出完美代码,而是来自减少协调开销、缩短打磨循环,以及让繁琐的工程工作变得足够廉价,从而可以持续改进。
随着时间推移,这套工作流演变成了一种与传统软件开发截然不同的东西。我不再手动转换文件,而是在构建能够生成转换、验证转换、识别失败、改进指令并重复这一过程的系统。工作从直接执行迁移,转向设计反馈循环。
与此同时,这一切都没有消除对工程判断力的需求。AI 帮助执行了迁移,但它没有设计架构、识别系统性失败模式,也没有决定什么才算“好”。在许多方面,这些技能反而变得更加重要。
以一人团队的方式工作有利有弊。人工代码审查有时会成为瓶颈,而且这种工作方式显然存在知识孤岛的风险。保持文档最新——无论是对编码智能体,还是对未来的工程师/考古学家——都变得非常重要。
我也从以前做过这类工作中受益匪浅。经验让我更容易区分表面问题和关键问题,更早识别失败模式,并设计出能在实际使用中站得住脚的工作流。AI 加速了执行,但经验塑造了过程。
本地化恰好是我最清晰地体验到这些转变的项目,但我并不认为这些经验只适用于本地化。我怀疑许多软件工程工作流都将围绕类似的模式被重新设计。
来源:Sierra Blog · sierra.ai