6.9 KiB
i18n Guide
MaiBot 现在使用 JSON + Crowdin + Babel 的国际化方案,不依赖 gettext 的 .po/.mo 运行时。
仓库翻译策略
zh-CN是仓库内唯一的 source language,也是唯一的 GitHub 侧 source of truth。- 非
zh-CN的目标语言文件是同步产物和可评审输出,不是常规长期编辑面。 - 仓库中已经提交过的目标语言文件,需要先通过一次 bootstrap 同步进 Crowdin,避免 Crowdin 把这些历史翻译当成“未翻译”并用 source 文本导出覆盖。
- bootstrap 完成后,目标语言的常规维护应在 Crowdin 中完成,而不是直接在 GitHub 中持续编辑。
- GitHub Actions 是仓库与 Crowdin 之间唯一允许的同步方式;不要把 Crowdin 的原生 GitHub integration 当作第二条回写路径。
- 翻译回流仍然通过
l10n_*pull request 完成,不直接写回main或r-dev。
目录结构
翻译文件位于 locales/<locale>/*.json,当前默认语言是 zh-CN。
建议按模块拆分文件:
core.jsonstartup.jsonconfig.jsonprompts.json
长 Prompt 模板使用单文件本地化目录:
prompts/
zh-CN/
replyer.prompt
planner.prompt
注意:
prompts/zh-CN/**/*.prompt是唯一中文 source 模板目录。- 不要把
zh-CN原文整批复制到prompts/en-US/后直接提交。 - 目标语言 prompt 文件应该由 Crowdin 下载生成;在本地还没有目标文件时,运行时会自动回退到
zh-CN。
在代码中使用
统一从 src/common/i18n/__init__.py 导入:
from src.common.i18n import t, tn
logger.info(t("startup.launching_script", script_file=script_file))
logger.info(tn("core.tasks_cancelled", count))
可用能力:
t(key, locale=None, **kwargs):普通翻译tn(key, count, locale=None, **kwargs):plural 翻译set_locale(locale)/get_locale():设置或读取当前默认 localeformat_datetime_localized(...)format_number_localized(...)format_decimal_localized(...)
Prompt 模板统一从 src/common/prompt_i18n.py 加载:
from src.common.prompt_i18n import load_prompt
template = load_prompt("replyer")
rendered = load_prompt("replyer", identity="Mai", bot_name="麦麦")
Prompt 加载规则:
- 优先读取
prompts/<当前 locale>/ - 找不到时回退到
prompts/zh-CN/
locale 优先级
运行时按以下顺序决定 locale:
- 显式传入
locale - 当前上下文中的 locale 覆盖(如使用
use_locale(...)) - 环境变量
MAIBOT_LOCALE - 默认值
zh-CN
key 规范
- 使用稳定的点分 key,例如
startup.env_created - 全部小写
- 不要把中文原文直接当 key
日常翻译流程
- 先在
locales/zh-CN/*.json或prompts/zh-CN/**/*.prompt添加或修改 source 内容。 - 在代码中用
t()/tn()/load_prompt()替换硬编码字符串。 - 运行
python scripts/i18n_validate.py校验结构。 - 把 source 变更推送到
main或r-dev,或手动触发crowdin-sync.yml。 - 目标语言翻译在 Crowdin 中完成。
- GitHub Actions 下载当时 Crowdin 中可用的翻译结果,并通过
l10n_main/l10n_r-devpull request 回流到仓库。
对于非 zh-CN 的目标 locale:
- 下面这两条是本仓库的 repository-specific 校验策略,不是 Crowdin 默认行为。
- 不要手工把中文 source 文案直接复制进目标语言文件后提交。
- 英文 locale 文件中不应保留中文字符;这类残留会被校验脚本拦截。
什么时候可以直接改目标语言文件
以下场景才适合直接在 GitHub 中改非 zh-CN 文件:
- 需要把仓库里已经存在的历史目标语言文件一次性 bootstrap 到 Crowdin。
- 需要做紧急修复,而且你确认后续会把同样的修改补回 Crowdin,避免下一次同步被覆盖。
除了上面这些例外,不要把目标语言文件当作常规编辑入口。
校验脚本
运行:
python scripts/i18n_validate.py
校验内容包括:
- JSON 语法是否合法
- 是否存在重复 key
- 是否存在空字符串 key
- 各语言 key 集合是否与
zh-CN对齐 - 占位符集合是否一致
- plural 结构是否一致
- 非
zh-CNlocale 是否直接保留了包含中文字符的 source 文案 - prompt 模板已存在时,其占位符集合必须与
prompts/zh-CN/对齐
对于 prompt 模板:
- 缺少目标 locale 文件只会给 warning,不会阻断,因为运行时有 fallback
- 目标 locale 文件如果存在但占位符漂移,会直接校验失败
候选扫描
如果你想继续做下一批迁移,可以运行:
python scripts/i18n_extract_candidates.py
这个脚本会扫描仓库中的 Python 文件,输出仍然包含中文字符串常量的位置,方便人工挑选下一批适合迁移到 i18n 的文案。
Crowdin
项目根目录的 crowdin.yml 使用 locales/zh-CN/*.json 作为 source。
现在也会把 prompts/zh-CN/**/*.prompt 作为单文件 Prompt 模板 source 上传到 Crowdin。
GitHub Actions 中的 crowdin-sync.yml 是日常稳态同步入口:
- push 到
main/r-dev时,只有zh-CNsource 资产和crowdin.yml会触发正常上传。 - workflow 运行时会上传 source,并下载当时 Crowdin 中可用的翻译结果。
- 下载结果通过
l10n_*pull request 回流,而不是直接写回main/r-dev。
crowdin-bootstrap.yml 是一次性或例外场景使用的 bootstrap 入口:
- 只能手动触发。
- 会把仓库当前已提交的目标语言文件上传到 Crowdin,用来保留历史翻译。
- 不会作为日常 workflow 持续上传 GitHub 中的目标语言改动。
- 这个 workflow 必须先存在于仓库默认分支后,才会在 GitHub 网页端出现,也才能被
gh workflow run调用。
常用命令:
python scripts/i18n_validate.py
gh workflow run crowdin-sync.yml --ref main
gh workflow run crowdin-bootstrap.yml --ref main -f base_branch=r-dev -f confirm_bootstrap=yes-bootstrap-current-target-translations
gh run list --workflow crowdin-sync.yml --limit 5
gh pr list --head l10n_r-dev
更完整的 GitHub 侧操作说明见 docs/github-actions-crowdin-workflow-report.md。
当前迁移范围
这一批已经覆盖:
bot.py启动、重启、退出与协议确认提示src/config中第一批配置加载、热重载、校验异常提示src/main.py的主要启动链路提示src/prompt/prompt_manager.py的 locale 感知 Prompt 加载prompts/<locale>/的单文件 Prompt 模板结构
仍然可以继续做但这次没有全量翻译的内容:
- 所有 Prompt 模板的高质量英文翻译
- 内部协议字段
- debug-only 文案