- fix: PROMPT_EXTENSIONS = (".prompt") 是字符串非元组,改为 (".prompt",)
- refactor: 将 extract_placeholders/format_template 统一到 loaders.py,
消除 formatting.py、prompt_i18n.py、i18n_validate.py 三处重复
- perf: _get_catalog 和 load_prompt 改为双重检查锁定,I/O 不再阻塞其他线程
- perf: _log_once 使用独立 _warning_lock,不再与 _cache_lock 竞争
- fix: _scan_legacy_prompt_directory 添加 prompts_root 参数,修正 relative_to 语义
- refactor: 合并 _supported_prompt_files 两个变体为单函数 + recursive 参数
- docs: i18n.md 强化 repository-specific 校验策略标注,修正时间表述冗余
- fix: 验证脚本错误消息移除 Crowdin 暗示,标注为仓库级校验策略
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
159 lines
4.5 KiB
Markdown
159 lines
4.5 KiB
Markdown
# i18n Guide
|
||
|
||
MaiBot 现在使用 `JSON + Crowdin + Babel` 的国际化方案,不依赖 gettext 的 `.po/.mo` 运行时。
|
||
|
||
## 目录结构
|
||
|
||
翻译文件位于 `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`。
|
||
|
||
## 在代码中使用
|
||
|
||
统一从 [`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/`
|
||
- 如果新目录中还没有对应文件,再回退到历史兼容目录 `prompts/*.prompt`
|
||
|
||
## 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` 添加 source 文案。
|
||
2. 在 `locales/en-US/*.json` 中补上同名 key。
|
||
3. 在代码中用 `t()` 或 `tn()` 替换硬编码字符串。
|
||
4. 运行 `python scripts/i18n_validate.py` 校验结构。
|
||
|
||
对于非 `zh-CN` 的目标 locale:
|
||
|
||
- 下面这两条是本仓库的 repository-specific 校验策略,不是 Crowdin 默认行为。
|
||
- 不要手工把中文 source 文案直接复制进目标语言文件后提交。
|
||
- 英文 locale 文件中不应保留中文字符;这类残留会被校验脚本拦截。
|
||
|
||
## 校验脚本
|
||
|
||
运行:
|
||
|
||
```bash
|
||
python scripts/i18n_validate.py
|
||
```
|
||
|
||
校验内容包括:
|
||
|
||
- JSON 语法是否合法
|
||
- 是否存在重复 key
|
||
- 是否存在空字符串 key
|
||
- 各语言 key 集合是否与 `zh-CN` 对齐
|
||
- 占位符集合是否一致
|
||
- 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) 使用 `locales/zh-CN/*.json` 作为 source。
|
||
现在也会把 `prompts/zh-CN/**/*.prompt` 作为单文件 Prompt 模板 source 上传到 Crowdin。
|
||
|
||
GitHub Actions 中的 [`crowdin-sync.yml`](../.github/workflows/crowdin-sync.yml) 会在 workflow 运行时上传 source,并下载当时 Crowdin 中可用的翻译结果。
|
||
|
||
常用命令:
|
||
|
||
```bash
|
||
crowdin upload sources
|
||
crowdin upload translations
|
||
crowdin download translations
|
||
python scripts/i18n_validate.py
|
||
```
|
||
|
||
## 当前迁移范围
|
||
|
||
这一批已经覆盖:
|
||
|
||
- `bot.py` 启动、重启、退出与协议确认提示
|
||
- `src/config` 中第一批配置加载、热重载、校验异常提示
|
||
- `src/main.py` 的主要启动链路提示
|
||
- `src/prompt/prompt_manager.py` 的 locale 感知 Prompt 加载
|
||
- `prompts/<locale>/` 的单文件 Prompt 模板结构
|
||
|
||
仍然可以继续做但这次没有全量翻译的内容:
|
||
|
||
- 所有 Prompt 模板的高质量英文翻译
|
||
- 内部协议字段
|
||
- debug-only 文案
|