GitHub Action自动化翻译README:开源项目国际化实践指南
1. 项目概述:一个为开源项目自动翻译README的GitHub Action
如果你维护着一个开源项目,并且希望它能被全球更多的开发者看到和使用,那么一个多语言的README文档几乎是必不可少的。然而,手动维护多个语言版本的README,尤其是在项目频繁更新时,会变得异常繁琐且容易出错。每次更新功能,你都需要同步修改中文、英文、日文等各个版本的文档,这个过程不仅消耗精力,还可能导致版本间的不一致。
“Lin-jun-xiang/action-translate-readme” 这个GitHub Action就是为了解决这个痛点而生的。它的核心功能是自动化:当你向仓库的主分支(通常是main或master)推送代码时,或者当你手动触发工作流时,这个Action会自动检测你的README文件(通常是README.md)的变更,并利用集成的翻译服务(如百度翻译、DeepL、Google Cloud Translation等),将其翻译成你预设的目标语言,然后自动提交一个新的翻译文件(例如README.zh-CN.md)到你的仓库中。
简单来说,它让你的项目文档实现了“一次编写,多语言同步发布”。这对于个人开发者或小型团队来说,极大地降低了国际化(i18n)的门槛。你不再需要精通多国语言,也不需要为翻译付费或寻找志愿者,整个流程完全集成在GitHub的CI/CD流水线中,静默且高效地运行。
2. 核心设计思路与方案选型
2.1 为什么选择GitHub Action来实现?
在自动化文档翻译这个场景下,有多种技术路径可选,比如本地脚本、Webhook服务、或者像GitHub Action这样的CI/CD工具。选择GitHub Action作为实现平台,主要基于以下几个核心考量:
- 与代码仓库深度集成:Action直接运行在GitHub的虚拟环境中,可以无缝访问仓库内容、响应推送(push)、拉取请求(pull request)等事件。这意味着翻译动作可以紧跟在代码变更之后,实现文档与代码的同步更新,上下文一致性最强。
- 事件驱动,精准触发:我们可以将Action配置为仅在
README.md文件发生变更时触发,避免不必要的翻译运行,节省计算资源和API调用次数。这种精细化的触发控制是本地脚本难以优雅实现的。 - 生态与复用性:GitHub Action拥有庞大的市场,可以方便地与其他Action组合使用(例如,先进行代码检查,再触发翻译)。将功能封装成Action,也便于其他开发者一键复用,只需在项目
.github/workflows/目录下添加一个YAML配置文件即可。 - 无需自建服务:如果使用Webhook方案,你需要维护一个接收GitHub webhook的服务器,处理认证、解析、调用翻译API、再回写仓库等一系列复杂操作,涉及服务器成本、网络安全和稳定性问题。GitHub Action将这些复杂性全部托管给了平台。
基于这些原因,将翻译逻辑封装成一个可复用的GitHub Action,是最贴合开源项目工作流、实现成本最低、用户体验最流畅的方案。
2.2 翻译服务的选择与权衡
翻译是整个Action的核心,翻译服务的选型直接决定了翻译质量、成本和可用性。市面上主流的翻译API各有优劣:
| 翻译服务 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 百度翻译API | 对中英互译优化好,免费额度充足(标准版每月200万字符),国内访问速度快。 | 对小语种或专业术语支持可能不如国际大厂。 | 项目主要面向中文社区,或中英翻译需求为主。 |
| DeepL API | 公认的翻译质量天花板,尤其擅长欧洲语言,上下文理解能力强。 | 价格相对昂贵,免费版限制严格。 | 对翻译质量要求极高,且目标用户在欧洲地区。 |
| Google Cloud Translation API | 支持语言最全,技术成熟稳定,与Google生态集成好。 | 需要绑定信用卡,虽然有免费额度,但配置稍复杂。 | 项目目标语言多样,或已在使用Google Cloud服务。 |
| Azure Translator | 企业级服务,稳定可靠,微软系技术栈友好。 | 同样需要Azure账户和付费订阅。 | 企业级项目或已在使用微软Azure云服务。 |
| 开源模型(如argos-translate) | 完全免费,可离线运行,数据隐私有保障。 | 翻译质量参差不齐,模型体积大,在Action运行环境中部署和加载耗时。 | 对数据隐私极度敏感,且能接受一定质量损失。 |
“action-translate-readme”的常见实践与建议:在实际构建中,一个健壮的Action通常会支持配置多种翻译服务,让用户根据自身情况选择。对于大多数个人开源项目,百度翻译API是一个性价比极高的起点,其免费额度足以支撑一个活跃项目的文档翻译需求。如果项目涉及更专业的术语或多语言,可以考虑提供DeepL或Google的选项。
注意:无论选择哪种服务,都必须将API密钥(如
BAIDU_APP_ID和BAIDU_APP_KEY)存储在GitHub仓库的Settings -> Secrets and variables -> Actions中,然后在工作流文件中以${{ secrets.XXX }}的方式引用,绝对不要将密钥硬编码在代码或配置文件中。
2.3 整体工作流设计
这个Action的完整工作流可以拆解为以下几个关键步骤,它们依次在GitHub的Runner(一个临时虚拟机)中执行:
- 触发:监听仓库的
push事件到主分支,并过滤出变更了README.md的提交。 - 检出:使用
actions/checkout@v4Action,将仓库代码拉取到Runner的工作目录。 - 翻译:这是核心步骤。Action的代码会:
- 读取
README.md文件内容。 - 对内容进行必要的预处理(例如,分割过长的文本以适配API长度限制,跳过代码块避免翻译代码逻辑)。
- 调用配置的翻译API,将内容从源语言(如英文)翻译为目标语言(如简体中文)。
- 对翻译结果进行后处理(例如,恢复代码块格式,处理Markdown链接和图片的本地化问题)。
- 读取
- 写入:将翻译后的内容写入一个新的文件,如
README.zh-CN.md。命名遵循常见的国际化惯例。 - 提交:检查新文件是否与现有文件有差异。如果有,则配置Git用户信息,将新文件提交并推送回原仓库。这一步通常使用
github-actions机器人作为提交者。
整个流程设计的关键在于幂等性和无感化。即无论运行多少次,只要源文件不变,生成的翻译文件内容应该不变;同时,整个流程对项目维护者应该是透明的,自动完成,无需人工干预。
3. 核心细节解析与实操要点
3.1 文件变更检测与智能触发
一个高效的Action不应该在每次推送时都运行。我们需要精确地只在README.md被修改时触发翻译。这可以通过GitHub Action的paths过滤器来实现。
on: push: branches: - main paths: - 'README.md'这个配置意味着:只有当推送(push)事件发生在main分支,并且推送的提交中包含了README.md文件的变更时,工作流才会被触发。这避免了因修改源代码、配置文件等其他文件而触发不必要的翻译任务,是控制成本和提高效率的关键。
实操心得:处理重命名或移动文件有时,你可能会重命名README文件(例如从readme.md改为README.md)。标准的paths过滤器可能无法捕获这种“删除旧文件,添加新文件”的变更。更健壮的做法是,在Action内部使用Git命令来检测前后两次提交中README.md文件内容的具体差异,而不仅仅是文件列表的变化。这需要更精细的脚本逻辑。
3.2 Markdown文档的预处理与后处理
直接向翻译API抛送原始的Markdown文本会导致灾难性的结果。翻译引擎可能会“好心”地翻译你本不希望动的内容:
- 代码块:
console.log(‘Hello World’)可能被翻译成控制台.日志(‘你好世界’),这完全破坏了代码。 - 内联代码:
`variableName`可能被翻译,导致文档中的技术术语失效。 - URL链接和图片:
[link text](https://example.com)中的https://example.com不应该被翻译。 - YAML Front Matter:一些文档工具使用的元数据块(如
---包裹的内容)也应保留原样。
因此,预处理步骤至关重要。常见的策略是:
- 使用正则表达式或Markdown解析器:将文档内容分解为不同的“片段”(segments),例如:普通文本段落、代码块(包括语言声明)、内联代码、链接、图片等。
- 分类处理:只将“普通文本段落”类型的片段送入翻译API。对于代码块、内联代码、URL等,则用唯一的占位符(如
{{CODE_BLOCK_1}}、{{INLINE_CODE_xyz}})替换,并将原内容保存在一个映射表(Map)中。 - 翻译后替换:获得翻译后的文本(其中包含占位符)后,再根据映射表,将占位符逐一替换回原始的技术内容。
后处理则关注翻译后文本的格式调整,例如确保翻译后的标题层级(#)依然正确,列表格式没有错乱,以及处理语言特有的标点转换(如英文引号"到中文引号“”)。
3.3 多目标语言与文件命名策略
一个项目可能需要支持多种语言。Action可以设计为支持一个语言列表(如[‘zh-CN’, ‘ja’, ‘es’])。工作流会遍历这个列表,为每种语言生成对应的README文件。
文件命名需要遵循清晰的约定,方便用户和静态站点生成器(如Docsify、VuePress)识别。常见的模式有:
README.zh-CN.md(简体中文)README.zh-TW.md(繁体中文)README.ja.md(日文)README.es.md(西班牙文)- 或者使用目录形式:
docs/README.zh-CN.md
在Action的配置中,应该允许用户自定义这个命名模板,例如:{name}.{locale}.{ext}。
3.4 提交策略与避免循环触发
自动提交是最后一步,但也最容易出问题。最大的风险是循环触发:Action提交了新的翻译文件,这个提交本身又包含了README.zh-CN.md的变更,如果监听路径配置不当,会再次触发Action,形成死循环。
解决方案:
- 精准的
paths过滤:确保工作流只监听源文件README.md,忽略所有翻译文件README.*.md。on: push: paths: - 'README.md' # 只监听源文件变更 - '!README.*.md' # 忽略所有翻译文件 - 使用特定的提交信息:在提交时,使用一个独特的、可识别的提交信息,例如“
ci: update translated README [skip ci]”。一些CI系统(包括GitHub Actions)会识别[skip ci]这样的标记,从而跳过新的工作流运行。这是一种广泛支持的约定。 - 在Action逻辑中判断提交者:可以在运行脚本中检查当前提交的作者是否是
github-actions机器人,如果是,则跳过某些操作。但这并非绝对可靠。
最推荐的是方案1和2的结合,这是最根本和有效的防循环触发机制。
4. 完整工作流配置与实现解析
下面,我们以一个具体的、使用百度翻译API的配置为例,拆解一个完整的.github/workflows/translate-readme.yml文件。
4.1 基础工作流配置骨架
name: Translate README on: push: branches: - main paths: - 'README.md' workflow_dispatch: # 允许手动触发 jobs: translate: runs-on: ubuntu-latest permissions: contents: write # 关键:赋予工作流写入仓库的权限 steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2 # 获取最近2次提交,便于比较变更 - name: Translate README to Simplified Chinese uses: Lin-jun-xiang/action-translate-readme@v1 # 假设这是发布的Action版本 id: translate # 给这一步一个ID,便于后续步骤引用其输出 with: source_file: 'README.md' target_languages: 'zh-CN' app_id: ${{ secrets.BAIDU_APP_ID }} app_key: ${{ secrets.BAIDU_APP_KEY }} # 以下为可选参数 # source_lang: 'en' # output_dir: '.' # file_name_template: '{name}.{locale}.{ext}' - name: Commit and push translation if: steps.translate.outputs.has_changes == 'true' # 仅在翻译文件有变化时提交 run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' git add . git commit -m "docs: update translated README [skip ci]" git push关键点解析:
permissions: contents: write:这是GitHub Actions较新的权限模型。必须显式声明写入权限,否则git push步骤会失败。actions/checkout@v4的fetch-depth: 2:设置获取深度为2,是为了让后续步骤有可能通过git diff来更精确地判断变更内容,虽然本例中主要依赖路径过滤。uses: Lin-jun-xiang/action-translate-readme@v1:这是调用Action的核心。@v1建议使用具体的版本号或标签,而非@main,以保证生产环境的稳定性。if: steps.translate.outputs.has_changes == 'true':这是一个优化。理想的Action应该在执行完毕后输出一个变量(如has_changes),表明是否生成了新的或修改了现有的翻译文件。只有确实有变更时,才执行提交推送操作,避免空提交。
4.2 Action内部实现逻辑浅析
虽然我们作为使用者主要关心配置,但了解Action内部的逻辑有助于排查问题。一个典型的Action(通常由JavaScript或Docker编写)会包含以下核心函数:
main函数:Action的入口点,从GitHub上下文中获取输入参数(with中定义的source_file,app_id等)。readSourceFile:读取source_file指定的文件内容。segmentMarkdown:实现前面提到的预处理逻辑,将Markdown分割并提取出待翻译文本和占位符。callTranslateAPI:构造请求,调用对应的翻译API(如百度翻译)。这里需要处理API的速率限制、错误重试、以及将长文本分批次发送(因为API有单次请求长度限制)。reconstructTranslatedDoc:将翻译API返回的文本片段,与之前保存的占位符(代码块等)重新组合,生成完整的翻译后Markdown文档。writeTargetFile:根据file_name_template和target_languages,将翻译内容写入目标文件(如README.zh-CN.md)。setOutput:比较新生成的文件与仓库中现有文件的内容是否一致,并设置输出变量(如has_changes)。
4.3 高级配置:多语言与自定义规则
对于更复杂的项目,配置可能如下所示:
- name: Translate README to multiple languages uses: Lin-jun-xiang/action-translate-readme@v1 with: source_file: 'README.md' target_languages: 'zh-CN, zh-TW, ja, ko, fr' # 支持多语言,用逗号分隔 app_id: ${{ secrets.BAIDU_APP_ID }} app_key: ${{ secrets.BAIDU_APP_KEY }} skip_patterns: | # 可选:定义跳过翻译的正则表达式 ^<!--.*-->$ # 跳过HTML注释 ^#{1,6}\s*Table of Contents$ # 跳过“目录”标题 glossary: | # 可选:自定义术语词典,确保特定词汇翻译一致 GitHub Actions: GitHub Actions(不翻译) Runner: Runner(不翻译) API: API(不翻译)skip_patterns和glossary是提升翻译质量的高级功能。skip_patterns允许你定义一些完全不需要翻译的模式(如特定的注释、标题)。glossary则是一个简单的术语表,确保像“GitHub Actions”这样的专有名词在全文保持不翻译或统一翻译,这对于技术文档的准确性至关重要。
5. 常见问题排查与实战经验
即便配置正确,在实际运行中也可能遇到各种问题。下面是一些常见场景及其解决方案。
5.1 工作流未触发
- 症状:推送了
README.md的修改,但Actions页面没有运行记录。 - 排查:
- 检查
.github/workflows/translate-readme.yml文件是否存在于正确的分支。 - 检查
on.push.paths配置,确认路径模式是否正确匹配了你的README.md文件(注意大小写)。 - 前往仓库的Settings -> Actions -> General,确保Actions权限已启用。
- 查看推送的提交是否真的包含了
README.md的更改。有时可能是合并提交或历史重写导致路径判断复杂。
- 检查
5.2 翻译失败,API报错
- 症状:工作流运行了,但在翻译步骤失败,日志显示“Invalid APP ID”或“QPS Limit Exceeded”。
- 排查:
- 密钥错误:确认在仓库Secrets中设置的
BAIDU_APP_ID和BAIDU_APP_KEY是否正确无误,且没有多余的空格。务必注意,百度翻译的“APP ID”和“密钥”需要从控制台获取,不是百度账号密码。 - 额度用尽:登录百度翻译开放平台控制台,查看当月免费字符额度是否已用完。
- 网络问题:GitHub Runner位于海外,访问国内API可能存在偶尔超时。Action内部应实现简单的重试机制。如果问题持续,可考虑在Action中增加更长的超时时间和指数退避重试。
- 文本过长:单个API请求有长度限制(如百度翻译是6000字节)。Action必须实现文本分割功能。如果日志显示相关错误,可能是Action的分割逻辑有缺陷。
- 密钥错误:确认在仓库Secrets中设置的
5.3 翻译文件已生成但未提交
- 症状:日志显示翻译成功并生成了
README.zh-CN.md,但最后没有提交推送。 - 排查:
- 检查
git push步骤的日志,是否有权限错误。确保工作流YAML中设置了permissions: contents: write。 - 检查
if: steps.translate.outputs.has_changes == 'true'条件。如果Action没有正确设置has_changes输出,或者新文件内容与现有文件完全一致,条件会为假,跳过提交。可以暂时移除if条件进行测试。 - 检查Runner中的Git配置。
user.name和user.email必须设置,否则git commit会失败。
- 检查
5.4 翻译质量不佳
- 症状:翻译后的文档读起来生硬,技术术语错误,或代码被误翻译。
- 解决方案:
- 优化预处理:这是最主要的原因。检查Action的预处理规则是否足够健壮,能完美保护所有代码块、内联代码和URL。可以尝试在本地用脚本模拟预处理过程,查看送入翻译的纯文本到底是什么。
- 使用术语表(Glossary):如果Action支持,将项目中关键的技术名词、产品名、专有词汇添加到
glossary配置中,强制其不翻译或按指定方式翻译。 - 更换翻译引擎:如果当前使用的免费API质量无法满足要求,考虑切换至DeepL等高质量API(需承担相应成本)。
- 人工校对与干预:自动化翻译无法达到100%完美。对于非常重要的项目,可以设置Action在生成翻译后,自动创建一个草稿(Draft)拉取请求(PR),而不是直接提交到主分支。这样维护者可以先审阅翻译结果,确认无误后再合并。这需要Action支持更高级的“创建PR”模式。
5.5 关于“直接提交”与“创建PR”模式的思考
上述配置采用的是“直接提交”模式,简单粗暴,适合文档变更频繁且对翻译质量要求“可用即可”的场景。但对于追求高质量或项目严谨度高的场景,“创建PR”模式是更优选择。
你可以修改最后一步,使用peter-evans/create-pull-request这样的Action来创建PR:
- name: Create Pull Request if: steps.translate.outputs.has_changes == 'true' uses: peter-evans/create-pull-request@v5 with: token: ${{ secrets.GITHUB_TOKEN }} branch: i18n/update-readme-zh-CN title: 'docs: update Chinese translation of README' body: 'Automated translation update triggered by changes to README.md.' labels: 'documentation, i18n'这样,每次更新都会生成一个待审核的PR,给了维护者一个把关的机会。两种模式各有优劣,需要根据项目实际情况进行权衡。
