Files
lucky_shop/scripts/I18N_AUDIT.md
ZF sun f37baad045 feat(i18n): add full i18n audit toolchain and pre-commit hook
- 新增 GitHub Actions 工作流实现 PR 时自动审计国际化内容
- 添加 pre-commit 钩子,提交前自动检查相关文件并拦截问题提交
- 编写完整的 i18n 审计脚本、文档与示例报告
- 实现多维度国际化校验:键匹配、占位符、data()误用、硬编码标题等
2026-07-04 09:57:43 +08:00

105 lines
4.4 KiB
Markdown

# i18n 审计流程
> 把所有"$lang 是否能正常翻译"的检查收口到一个入口:
> **新增组件 / 新增翻译 / 拉取新 bundle.json 之后,跑这一个命令就能出报告。**
## 快速开始
```bash
# 标准体检(最常用)
python scripts/i18n_audit.py
# 严格模式 — 出现真问题就 exit 1,适合 CI / pre-commit hook
python scripts/i18n_audit.py --strict
# 拉了新 bundle.json(覆盖了 lang-bundle.json),指定新的:
python scripts/i18n_audit.py --bundle ./lang-bundle.2026-07-04.json
```
报告结束时会输出:
```
健康度评分:
✓ 99.95% 翻译覆盖,0 个真问题
或
! 存在 N 类问题需要修:
- <类目 1>
- <类目 2>
```
完整结构化报告写到 `scripts/lang_audit_report_v2.json`,适合 diff 或后续处理。
## 它在做什么(7 个检查)
| # | 检查维度 | 暴露什么问题 | 真实程度 |
| --- | --- | --- | --- |
| 1 | **静态 `$lang(key)` 字面 → bundle leaf** | key 字符串不存在于 bundle | 直接失效 |
| 2 | **占位符参数匹配** | `key` 含 `{name}` 但调用没传 `{ name: ... }`,运行时会**字面显示 `{name}`** | 直接失效 |
| 3 | **`data()` 中误用** | `data() { return { x: this.$lang(...) } }` 在 mp/鸿蒙 remote-only 模式下会**永久缓存空串** | 远程首屏失效 |
| 4 | **bundle ↔ 本地 lang drift** | `lang/zh-cn/...` 有的 key,bundle 没有(可能影响兜底) | 加载策略变更时显现 |
| 5 | **pages.json 硬编码导航标题** | `navigationBarTitleText: "商品分类"`,运行时**必须 `lang.refresh()` 兜底** | 取决于 refresh() 调用时机 |
| 6 | **i18n-lazy mixin 使用情况** | 哪些组件没遵循延迟初始化约定 | 标记数据风险点 |
| 7 | **P2 动态 key** (`verify-p2-keys.js`) | 拼接类 `$lang('prefix.' + var)` 在所有值下都能解析 | 模拟 runtime |
## 误报防御(已针对历史坑修过)
- **JS 注释剥离** 改用等长空白,正则偏移不偏(以前的 ghost key 大都是这个原因)
- **`[^'\"]+` 跨换行陷阱** 已用手动 paren-depth 解析替代,不会把整个文件吃进 key
- **JS 对象简写** `{ appName }` 等同于 `{ appName: appName }`,正则同时识别
- **`/api/lang/bundle` 真会有的** `tabBar.*` 走 root level,审计对 `common.tabBar.home` 故意不报警
- **`lang.js` JSDoc 内** 的 `$lang('title')`、`$lang('welcomeText')` 示例被注释剥离规则前置过滤
## 文件清单
```
scripts/
├── i18n_audit.py ← 唯一入口(run this)
├── audit_lang_bundle_v2.py ← 静态扫描(被 i18n_audit.py 调)
├── audit_lang_bundle.js ← Node 端 lang 加载工具
├── verify-p2-keys.js ← 动态 key 验证(被 i18n_audit.py 调)
├── I18N_AUDIT.md ← 本文档
└── lang_audit_report_v2.json ← 最近一次报告(自动生成)
```
## 何时该跑
1. **拉了新 bundle.json**:`/api/lang/bundle` 返回结果存到 `lang-bundle.json` 后,跑一次。
2. **新增 / 修改翻译 key**:`lang/zh-cn/...` 或 `lang/en-us/...` 文件改动后,跑一次。
3. **新增组件**:任何 .vue/.js 引入 `$lang(...)` 后,跑一次。
4. **修复 i18n bug 后**:跑一次,确认`健康度评分`达 `99.95%`。
5. **PR 合并前(CI)**:`python scripts/i18n_audit.py --strict`,exit code 控制是否合入。
## 想加新的检查怎么办
在 `audit_lang_bundle_v2.py` 加一个 section,产生新字段 → 在 `i18n_audit.py::summarize` 里加一行 `print`。`audit_lang_bundle_v2.py` 写好了自动落入结构化报告。
## 已知"非 bug 但需注意"(已知风险不需修)
- **`pages.json` 中 20 个 `navigationBarTitleText` 硬编码**:由 `lang.refresh()` 自动覆盖,只要 refresh 在页面 mount 后被调到即可。
- **`components-diy/diy-tab.vue` 的 title 是运行时 DIY 数据**:DIY 数据规范/白名单层面解决,不在审计范围。
## 最近一次结果速查
```bash
cat scripts/lang_audit_report_v2.json
```
主要字段:
```json
{
"code_calls_total": 2675,
"code_unique_keys": 2083,
"found_in_bundle": 2082,
"missing_summary": {"SYNTAX_BUG": 1},
"missing_classified": {...},
"placeholder_issues": [],
"data_lang_issues": {...},
"hardcoded_navbar_titles": [...],
"tabbar_missing": [...],
"local_only_keys": []
}
```
`placeholder_issues` 为 `[]` 是绿;`data_lang_issues` 为 `{}` 是绿;`missing_summary.SYNTAX_BUG` ≥ 1 通常是 grep 误报,详见 `missing_classified` 字段的 `reason`。