# 通过配对兄弟文件与配对门禁实现双语文档

[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文

## 背景

本仓库的 README 与 docs 目录树会被公司内外的人和 agent（智能体）以中英两种语言阅读。没有机制、纯靠手工维护第二语言，正是译文腐烂的方式：一侧继续演进，另一侧默默地说谎，而没有门禁会注意到。对这类不变式，本仓库一贯的答案是把它编码成机械检查（见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)），因此双语政策随附一道门禁一起交付。

## 决策

- **配对兄弟文件，两种语言同权。**一对文档是三个兄弟文件：英文 `foo.md`、中文 `foo.zh.md`，加一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典——一篇文档可以先用中文撰写和评审、之后再译成英文，反之亦可；约束这对文件的是两侧必须说同样的话，且配对整体合入（两种语言加记录，绝不单独落一侧）。政策见 [docs/i18n/README.md](../../../i18n/README.md)；翻译规则见 [docs/i18n/translation-rules.md](../../../i18n/translation-rules.md)；术语真源见 [docs/i18n/terminology.md](../../../i18n/terminology.md)。
- **旁挂记录两侧 blob hash，使一致性可检查。**`foo.i18n.yaml` 保存两侧文件在上一次确认一致状态下各自的完整 git blob hash。此后改了任一侧而没重新确认配对，都能被机械检测出来——纯内容比较、无需查询历史——而且同一个 PR 里改动的文件也能算出 hash，commit hash 式的记录做不到这一点。重新记录（`verify-translation-pairing --write`）产生一份可评审的 yaml diff：确认一致在 PR 里是一个显式、可见的动作。
- **`verify-translation-pairing` 加入 `doc-sync`。**门禁（[scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)）强制执行：required 的配对存在；任何已存在的配对完整（三个文件齐全）且一致（两个 hash 都匹配、切换行双向互链、结构签名一致）；被排除的文件（生成物或本身即双语的）保持不配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单是一个棘轮：每个合入的翻译批次把自己的文件加进去，覆盖面只增不减。
- **翻译是 agent 的工作，由人评审。**进仓的工作流是 [.agents/skills/dsh-translate-docs](../../../../.agents/skills/dsh-translate-docs/SKILL.md)，与 [dsh-code-review](../../../../.agents/skills/dsh-code-review/SKILL.md) 同一模式：skill 承载工作流，并把真源让给文档。

## 曾考虑的替代方案

- **英文为正典源、指纹放在译文内**——本 RFC 最初提出的设计：`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash，翻译只沿 EN → ZH 单向流动。评审中修订：团队需要中文先行的撰写方式（先写、先审中文 RFC，再译英文），两种语言同权，而单向正典模型无法表达这一点。覆盖**两侧**的旁挂记录取代了文件内的单向指纹；blob hash 的机制原样保留。
- **语言目录（`docs/en/` + `docs/zh/`，Kubernetes/ECharts 模式）**——否决：本仓库没有把 locale 映射到路由的文档站框架，挪动每个英文文件会搅动所有既有交叉引用，且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑而不是原样工作。
- **独立翻译仓库（PingCAP `docs`/`docs-cn` 模式）**——否决：适合有独立发布节奏的文档产品，对 monorepo 自己的文档而言过重；还会把译文置于本仓库门禁够不到的地方。
- **中英混排单文件（一个文件、两种语言）**——否决：每个 diff 都翻倍，破坏一段一行约定的 diff 工效，且局部不一致不可见。
- **Commit hash 式记录（MDN `l10n.sourceCommit` 模式）**——否决，改用 blob hash：同一个 PR 内的改动还没有 commit hash，MDN 模式无法表达「与本 PR 引入的状态一致」，且校验它需要 git 历史而非文件内容。
- **比较配对两侧的 git 时间戳（无记录）**——否决：纯格式化的改动会误报，一次无关改动之后提交的另一侧会漏报；只有内容同一性这个信号与门禁的承诺名实相符。

## 业界先例

带语言后缀的配对兄弟文件是中国大厂的主流约定（ant-design 的 `index.zh-CN.md`/`index.en-US.md`；arco-design 的 `README.zh-CN.md` 加顶部切换行；Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`）——但这些仓库都没有在 CI 里**强制**配对或一致性；约定纯靠评审维系。一致性自动化存在于中国之外：MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action（监视上游 commit、为陈旧译文自动开 issue/PR）、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator（CI 中由源 hash 驱动的 LLM 重译）。本设计把两者结合：中文生态的文件布局，加 hash 对门禁，再加一个进仓 agent skill（技能）替代 bot 服务。

## 后果

- 修改已配对文档的任一侧，同一个 PR 就有义务更新另一侧并重新记录配对——门禁把 doc-sync 规则双语化，不变式由 CI（而非评审者的记忆）承载。
- 每个配对给目录树多添一个文件。记录由机器写入（`--write`），代价是目录噪音而非维护负担；换来的是「谁在何时确认过这对一致」可以从 yaml 的 git blame 直接回答。
- 两侧说法冲突时，没有机械规则裁决谁赢——由 PR 评审裁决。这是同权的代价，是有意接受的：另一个选项（正典语言）禁止中文先行撰写。
- 生成文档（`cordis-catalog/`、`tool-catalog/`、`module-graph.md`）暂被排除；计划中的后续工作是让它们的生成器在输出英文的同时输出中文，届时移出排除清单。
- 推进天然是渐进的：`required` 之外的文档是可见的 backlog（`--list`），不是红的 CI，因此配对按可评审的批次落地，无需一个巨型 PR。
- 记录的 hash 兼作更新工具（`git cat-file -p <hash>` 能还原任一侧上次确认的文本，用于基于 diff 的最小更新），所以这套机制从不强迫整篇重译。
