Files
mai-bot/docs/i18n.md

219 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.json`
- `startup.json`
- `config.json`
- `prompts.json`
长 Prompt 模板使用单文件本地化目录:
```text
prompts/
zh-CN/
replyer.prompt
planner.prompt
```
注意:
- `prompts/zh-CN/**/*.prompt` 是唯一中文 source 模板目录。
- 不要把 `zh-CN` 原文整批复制到 `prompts/en-US/` 后直接提交。
- 目标语言 prompt 文件应该由 Crowdin 下载生成;在本地还没有目标文件时,运行时会自动回退到 `zh-CN`
Dashboard WebUI 的 locale 文件位于 `dashboard/src/i18n/locales/*.json`
```text
dashboard/src/i18n/locales/
zh.json
en.json
ja.json
ko.json
```
注意:
- `dashboard/src/i18n/locales/zh.json` 是 dashboard 在 Git 中的 source of truth。
- dashboard 运行时继续使用 `zh` / `en` / `ja` / `ko` 这组短 locale code但 Crowdin 侧仍把 `zh.json` 当作仓库里的 `zh-CN` source 资产来同步。
- `en.json` / `ja.json` / `ko.json` 是同步产物和可评审输出,不是常规长期手工编辑面。
## 在代码中使用
统一从 [`src/common/i18n/__init__.py`](../src/common/i18n/__init__.py) 导入:
```python
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()`:设置或读取当前默认 locale
- `format_datetime_localized(...)`
- `format_number_localized(...)`
- `format_decimal_localized(...)`
Prompt 模板统一从 [`src/common/prompt_i18n.py`](../src/common/prompt_i18n.py) 加载:
```python
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
1. 显式传入 `locale`
2. 当前上下文中的 locale 覆盖(如使用 `use_locale(...)`
3. 环境变量 `MAIBOT_LOCALE`
4. 默认值 `zh-CN`
## key 规范
- 使用稳定的点分 key例如 `startup.env_created`
- 全部小写
- 不要把中文原文直接当 key
## 日常翻译流程
1. 先在 `locales/zh-CN/*.json``prompts/zh-CN/**/*.prompt``dashboard/src/i18n/locales/zh.json` 添加或修改 source 内容。
2. 在代码中用 `t()` / `tn()` / `load_prompt()` 替换硬编码字符串。
3. 运行 `python scripts/i18n_validate.py` 校验结构。
4. 把 source 变更推送到 `main``r-dev`,或手动触发 [`crowdin-sync.yml`](../.github/workflows/crowdin-sync.yml)。
5. 目标语言翻译在 Crowdin 中完成。
6. GitHub Actions 下载当时 Crowdin 中可用的翻译结果,并通过 `l10n_main` / `l10n_r-dev` pull request 回流到仓库。
对于非 `zh-CN` 的目标 locale
- 下面这两条是本仓库的 repository-specific 校验策略,不是 Crowdin 默认行为。
- 不要手工把中文 source 文案直接复制进目标语言文件后提交。
- 英文 locale 文件中不应保留中文字符;这类残留会被校验脚本拦截。
- `dashboard/src/i18n/locales/en.json` / `ja.json` / `ko.json` 也遵循同样规则。
### 什么时候可以直接改目标语言文件
以下场景才适合直接在 GitHub 中改非 `zh-CN` 文件:
- 需要把仓库里已经存在的历史目标语言文件一次性 bootstrap 到 Crowdin。
- 需要做紧急修复,而且你确认后续会把同样的修改补回 Crowdin避免下一次同步被覆盖。
除了上面这些例外,不要把目标语言文件当作常规编辑入口。
## 校验脚本
运行:
```bash
python scripts/i18n_validate.py
```
校验内容包括:
- JSON 语法是否合法
- 是否存在重复 key
- 是否存在空字符串 key
- 各语言 key 集合是否与 `zh-CN` 对齐
- dashboard 嵌套 JSON 的 key 集合是否与 `dashboard/src/i18n/locales/zh.json` 对齐
- 占位符集合是否一致
- dashboard i18next `{{placeholder}}` 占位符集合是否一致
- plural 结构是否一致
-`zh-CN` locale 是否直接保留了包含中文字符的 source 文案
- prompt 模板已存在时,其占位符集合必须与 `prompts/zh-CN/` 对齐
对于 prompt 模板:
- 缺少目标 locale 文件只会给 warning不会阻断因为运行时有 fallback
- 目标 locale 文件如果存在但占位符漂移,会直接校验失败
## 候选扫描
如果你想继续做下一批迁移,可以运行:
```bash
python scripts/i18n_extract_candidates.py
```
这个脚本会扫描仓库中的 Python 文件,输出仍然包含中文字符串常量的位置,方便人工挑选下一批适合迁移到 i18n 的文案。
## Crowdin
项目根目录的 [`crowdin.yml`](../crowdin.yml) 现在会上传三类 source
- `locales/zh-CN/*.json`
- `prompts/zh-CN/**/*.prompt`
- `dashboard/src/i18n/locales/zh.json`
对于 dashboard
- Crowdin 下载结果会回写到 `dashboard/src/i18n/locales/en.json``ja.json``ko.json`
- 其中英文文件名保持为 `en.json`,但在 Crowdin 配置里仍映射到 `en-US`
GitHub Actions 中的 [`crowdin-sync.yml`](../.github/workflows/crowdin-sync.yml) 是日常稳态同步入口:
- push 到 `main` / `r-dev` 时,只有 source 资产(包括 dashboard 的 `zh.json` source 文件)和 `crowdin.yml` 会触发正常上传。
- workflow 运行时会上传 source并下载当时 Crowdin 中可用的翻译结果。
- 下载结果通过 `l10n_*` pull request 回流,而不是直接写回 `main` / `r-dev`
[`crowdin-bootstrap.yml`](../.github/workflows/crowdin-bootstrap.yml) 是一次性或例外场景使用的 bootstrap 入口:
- 只能手动触发。
- 会把仓库当前已提交的目标语言文件上传到 Crowdin用来保留历史翻译。
- 不会作为日常 workflow 持续上传 GitHub 中的目标语言改动。
- 这个 workflow 必须先存在于仓库默认分支后,才会在 GitHub 网页端出现,也才能被 `gh workflow run` 调用。
常用命令:
```bash
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`](./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 文案