如何在 CI/CD 管道中自动化 .po 文件翻译

您的团队每天将代码合并到 main 分支十四次。每一次合并都可能添加新的面向用户的字符串——例如按钮标签、错误消息或工具提示。而这些字符串最初都只有英文版本。在您的发布流程的某个环节,需要有人发现新的字符串,将其导出,进行翻译,然后粘贴回去并重新编译。这个人是一个瓶颈,对于一个快速迭代的团队来说,这个瓶颈意味着您的德国用户可能会在两个冲刺周期内看到英文占位符。
解决方案是在您的 CI/CD 管道中自动化翻译,以便新字符串在引入它们的同一合并中得到翻译——在常规情况下,关键路径中不再需要人工干预。这在今天完全可以实现,但前提是您要避免大多数团队常犯的错误:手动编写原始 LLM 调用,这些调用会悄无声息地破坏您的 %s 占位符和 .po 结构。本指南将介绍一个现实的 CI 管道模式、一个可用的 GitHub Actions 骨架,以及一些设计决策(例如幂等性、仅翻译新增内容、审查门控),这些决策将有效的管道与会发布损坏构建的管道区分开来。
为什么手动翻译是发布瓶颈
对于“为什么不在每次发布前翻译?”这个问题的答案是:时间总是无法同步。开发人员持续添加字符串,但翻译工作是由不同的人在不同的时间安排下批量进行的。这两种节奏之间的差距就是您的本地化债务累积的地方。
双重节奏问题
想象一下手动流程。开发人员添加 __( 'Export to CSV', 'mytextdomain' ) 并合并代码。没有人重新生成 .pot 文件。两周后,有人运行 wp i18n make-pot,发现四十个新的未翻译字符串(其中一些来自已经休假的开发人员),猜测其中一半的意图,然后将 .po 文件发送给翻译人员。翻译完成后返回,被粘贴进去,占位符可能保留了,也可能没有。与此同时,有三个发布版本带着这些英文字符串交付了。
这里的每一步都是手动的、批处理的且容易出错。CI/CD 自动化的目标是将这个过程简化为在合并时自动运行、仅翻译实际更改的内容并在出现错误时明确失败的流程——将翻译从一项周期性任务转变为管道中不可见的一部分。
管道模式:提取、对比、翻译、提交
从宏观上看,自动化翻译作业在合并到 main 分支时会运行四个步骤:重新生成模板,检测新内容,仅翻译这些字符串,并将结果提交回去。整个过程应该是幂等的——在没有字符串更改的合并上运行它,必须产生零差异和零噪音。
提取与对比
第一步是提取。从当前源代码中重新生成 .pot 文件,使其反映代码库中的所有字符串:
wp i18n make-pot . languages/myplugin.pot
第二步是对比。这是整个管道中最重要的设计决策。您不希望在每次合并时都重新翻译所有字符串——那会浪费 API 调用,有重新翻译人工已纠正字符串的风险,并且会产生巨大的审查差异。相反,您将新的 .pot 文件合并到每个现有的 .po 文件中,并仅翻译现在为空的条目(即真正的新字符串):
# Merge new template into each locale, preserving existing translations.
# Newly-added strings appear with empty msgstr; --backup=none keeps the tree clean.
for po in languages/*.po; do
msgmerge --update --backup=none "$po" languages/myplugin.pot
done
在 msgmerge 之后,只有全新的字符串才具有空的 msgstr。所有之前已翻译的内容都保持不变。这个特性使得管道具有幂等性,并使您的审查差异保持小而易于审查。
翻译与提交
第三步将这些空条目发送到翻译步骤。第四步提交更新的 .po 文件,编译 .mo 文件,并重新生成任何 JSON。接下来,我们将把所有这些都集成到 GitHub Actions 中。
GitHub Actions 骨架
以下是“这在工作流文件中实际是怎样的”的答案:一个在推送到 main 分支时触发的作业,它运行提取-对比-翻译-提交循环,并以拉取请求的形式提交结果,而不是直接提交到 main 分支。
工作流文件
name: Translate on merge
on:
push:
branches: [ main ]
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install WP-CLI and gettext
run: |
sudo apt-get update && sudo apt-get install -y gettext
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp && sudo chmod +x /usr/local/bin/wp
- name: Regenerate template and merge into locales
run: |
wp i18n make-pot . languages/myplugin.pot
for po in languages/*.po; do
msgmerge --update --backup=none "$po" languages/myplugin.pot
done
- name: Translate only new (empty) strings
env:
TRANSLATE_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
run: ./scripts/translate-new-strings.sh languages/
- name: Compile .mo and JSON
run: |
wp i18n make-mo languages/
wp i18n make-json languages/ --no-purge
- name: Open pull request with translations
uses: peter-evans/create-pull-request@v6
with:
branch: chore/auto-translations
title: "chore: auto-translate new strings"
commit-message: "chore: auto-translate new strings"
真正的工作隐藏之处
translate-new-strings.sh 步骤是实际翻译发生的地方。该脚本的简单版本会读取每个空条目,一次一个字符串地将其发送到 LLM API,然后将响应粘贴回去。这个简单版本恰恰是陷阱所在,有必要明确说明原因。
为什么手动编写的 LLM 调用会破坏您的文件
简短的答案是:原始 LLM 调用将您的 .po 字符串视为散文,但您的 .po 字符串并非散文——它们是带有占位符、复数形式和上下文的结构化数据,而聊天完成调用会轻易地破坏这些内容。
在测试中您不会看到的占位符损坏
将 Deleted %d of %s files 发送到通用聊天端点,您可能会得到 Supprimé %d des %s fichiers(正常)或 Supprimé %d de % s fichiers(占位符中混入了空格,现在 sprintf() 会在运行时报错)。发送复数条目时,模型可能会将两种形式合并为一种,从而破坏具有两个以上复数类别的语言。发送包含 <a href="%s"> 的字符串时,模型可能会翻译 URL 或删除标签。除非您在每个区域设置中专门测试渲染输出,否则这些失败都不会出现在您的测试中——它们会以运行时错误的形式出现在生产环境中,而这些用户的错误报告您无法解读。
维护陷阱
您可以尝试通过提示工程和正则表达式后处理来抵御这种情况,许多团队也确实这么做了。问题在于您现在正在作为一个副项目维护一个脆弱的翻译引擎,重新发现 Gettext 生态系统已经解决的每一个占位符边缘情况。我们在不破坏代码变量的情况下翻译 PO 文件中列出了变量被破坏的具体方式以及如何防止它们。其中的教训直接适用:模型质量很少是问题所在;问题在于其周围的结构处理。
将 API 驱动的云翻译器整合到 CI 中
这就是 API 驱动的翻译服务取代您的 translate-new-strings.sh 猜测工作的地方。您的 CI 步骤不再手动编写 LLM 调用和进行正则表达式后处理,而是将更改的 .po 文件上传到一个已理解 Gettext 结构并返回干净输出的服务。管道的形态保持不变——提取、对比、翻译、提交——但脆弱的中间步骤变成了一个简单的 API 调用。
取代您脚本的 API 调用
SimplePoTranslate 正是为此而生。它提供了一个适合自动化和 CI 的云 API,因此您工作流中的翻译步骤变成了一个请求,它传递 .po 文件并返回一个已翻译的文件,无需按字符串循环。其**语法锁定(Syntax Locking)**功能会自动将 %s、%1$s、{count}、HTML 和代码标记固定在原位——前一节中提到的所有此类错误都由引擎处理,而不是由您维护的正则表达式来处理。凭借全面的 Gettext 复数形式和 msgctxt 支持,复数形式和上下文在往返过程中得以保留,这是聊天完成调用无法保证的。
幂等性和审查门控仍由您掌控
无论您使用哪种引擎,有两项设计决策仍然由您掌握。首先是幂等性:在 msgmerge 之后,只翻译空条目,这样无操作的合并就不会产生差异。其次是审查门控:让作业开启一个拉取请求,就像上面的骨架一样,而不是直接提交到 main 分支。机器翻译在快速上线字符串方面表现出色,但在合并前的人工审视可以发现罕见的上下文遗漏——拉取请求可以在不阻碍常见情况的前提下提供这种审视。管理多个区域设置或多个客户站点的团队会从机构的理想本地化工作流中识别出这种模式,其中相同的自动化-然后-审查模式可扩展到数十个项目。
结论
为了在不发布损坏构建的情况下在 CI/CD 中自动化翻译,模式是一致的:在合并时重新生成 .pot 文件,将其 msgmerge 到每个区域设置中以仅显示新字符串,只翻译这些字符串,并在拉取请求审查门控后提交结果。幂等性保持您的差异干净;审查门控将罕见的错误翻译排除在生产之外。
需要正确处理的部分是翻译步骤本身。手动编写的 LLM 调用会以您的测试无法捕获的方式破坏占位符和复数形式,您将花费更多时间维护翻译粘合代码,而不是其所服务的特性代码。具有语法锁定的 API 驱动引擎消除了这种完整的故障模式,因此您的管道会在引入新字符串的同一合并中翻译它们——您的非英语用户将不再看到英文占位符。
准备好在不破坏占位符的情况下自动化您管道中的翻译了吗?免费试用 SimplePoTranslate——无需信用卡。从免费套餐开始,使用您自己的
.po文件验证 API,并在准备好时将其集成到 CI 中。