Files
mai-bot/docs/i18n.md
春河晴 55eb911dd3 fix(i18n): 修复 PROMPT_EXTENSIONS 元组声明、消除重复代码、优化锁策略
- 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>
2026-03-13 01:59:20 +09:00

159 lines
4.5 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` 运行时。
## 目录结构
翻译文件位于 `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 文案