DSH / Atlas
2026-09-23implementedprocessarchived 2026-09-30

Section-keyed translation pairing records

按分节记录翻译配对

A consistency record held the full blob hash of each language file. Any edit to a pair changed both lines, so two branches that edited different parts of the same pair always conflicted on the record, even when Git merged both Markdown files cleanly. Generated references such as `docs/config-catalog.md`, `docs/module-graph.md`, and `docs/event-producer-consumer.md` were affected most: most PRs that touch packages, co

English

Problem

A consistency record held the full blob hash of each language file. Any edit to a pair changed both lines, so two branches that edited different parts of the same pair always conflicted on the record, even when Git merged both Markdown files cleanly. Generated references such as docs/config-catalog.md, docs/module-graph.md, and docs/event-producer-consumer.md were affected most: most PRs that touch packages, config, or events regenerate one of them, and each regeneration also required a hand-copied Chinese update and a new record. A worktree-local merge driver composed such records, but GitHub's mergeability check never runs it, so PRs still showed a conflict until someone merged the base locally and pushed.

Decision

foo.i18n.yaml holds one entry per heading section that contains hashed content. verify-translation-pairing computes the entries; scripts/translation-pairing-record.ts owns the format.

  • Both sides are split at every heading. Section i of the English file corresponds to section i of the Chinese file; different heading counts are a structure error and cannot be recorded.
  • The key is the English heading-slug path, such as /plugin-config-catalog/loadable-plugins-with-no-config. Text before the first heading is /, and a repeated path gets ~2, ~3, and so on. Keys are counted over every section, including sections without entries, so a key depends only on headings.
  • Each side's hash covers the section's top-level blocks after paired-document link localization, except fenced code blocks and generated regions. Those two kinds of content are exactly what the pairing gate already requires to be identical on both sides; any other content is hashed even when it happens to be identical. The entry holds a 16-hex SHA-256 prefix of each side's hashed blocks. A section with no hashed blocks has no entry.
  • Each entry spans three lines: an unchanged key line followed by the en: and zh: hash lines. The key line separates neighboring entries, so Git's text merge treats changes to adjacent entries as separate hunks.

A record conflict therefore requires both branches to change hashed content in the same section.

Generators put their generated data in generated regions and write each region into both pages, so regeneration changes neither the record nor the Chinese prose. gen-config-catalog writes both languages and the record, with one region per package and one per package table; labels inside a region are the code identifiers inject, refs, source, and class, which the translated introduction explains. gen-module-graph keeps its table in a region with identifier column headers. gen-doc-graphs splices the event matrix tables into the authored Chinese page, and gen-cordis-catalog already did the same for subsystem regions.

gen-translation-brief recovers the last-confirmed text from the newest commit of the pair whose contents produce the committed record. Records no longer point to Git blobs, and --write no longer stores snapshot objects or refs.

Archived Agent Notes keep their sealed whole-file records; verify-archived-agent-notes owns that format.

Alternatives considered

Keep whole-file hashes and the merge driver. The driver resolved the conflict only in local worktrees where it was installed. GitHub still reported the PR as conflicted, raw merges without the installed runtime stopped, and the driver, resolver, installer integration, and their tests were about 1,400 lines of code to maintain.

One entry per top-level block instead of per heading section. Fewer false conflicts inside long sections, but the record would grow to roughly the block count of the document. Positional block keys would also renumber on every insertion and create conflicts of their own.

Positional section keys (h2[3]). Inserting a section renumbers every later key and changes every later record line, which recreates the conflicts this format removes. Heading-slug keys change only when that heading's English text changes.

Resolve on GitHub through Actions or an app. Hosted automation would need credentials, concurrency control, and permission to rewrite PR branches. A record that Git's default text merge composes needs none of these.

Hash whole sections, including code blocks and regions. Every regenerated row or code block would still change a record line, so generated references would keep conflicting on their largest sections.

Leave out any block that is byte-identical on both sides. It also keeps generated rows out of the record, but it decides implicitly what needs no translation: an English paragraph copied untranslated into the Chinese file would be left out too, and a later edit to it would no longer change the record. Code blocks and generated regions are declared shared content that existing gates check.

Consequences

Edits to different sections, and regenerated regions and code blocks, merge without a local tool, including on GitHub. Replaying the merge commits reachable from master between 2026-08-20 and 2026-09-23 shows the effect on real history: see Measured effect.

A same-section conflict is resolved by resolving the Markdown and re-running verify-translation-pairing --write <pair>; no command automates it. Chinese readers of generated regions see code identifiers and English table headers instead of translated labels. Recovering last-confirmed text for the brief requires the confirmed pair to be committed.

Measured effect

The replay covered 2016 two-parent merge commits reachable from master between 2026-08-20 and 2026-09-23, most of them PR branches merging the base forward. For every active pair whose record both parents changed, it ran Git's default text merge on the old record, on the section-keyed record computed from the same commit contents, and on the two Markdown files. It counts a record whose headings did not align at some commit as a conflict. The old format conflicted in 2950 pair merges across 850 merge commits; the section-keyed format conflicts in 1586 pair merges across 595 merge commits, a 46% reduction per pair. The Markdown files themselves conflicted in 926 pair merges across 338 merge commits, which no record format can avoid. Merge commits whose only conflicts were records fell from 512 to 257 (50%).

The replay uses each commit's content as it was, before the generators wrapped their data in regions, so docs/module-graph.md (277 conflicts), docs/event-producer-consumer.md (220), and docs/config-catalog.md (110) still conflict on generated data in it. With that data in regions, their records change only when their prose changes; excluding their generated rows from the same replay left 0 and 14 conflicts for the first two. Substituting that for the three pages projects about 990 pair conflicts, about 66% below the old format; this projection is an estimate, not a replayed measurement.

Related

The bilingual pairing gate Agent Note owns the three-file pair and its equal-authority rule; this note replaces its whole-file hash record. The archived automatic pairing merges Agent Note records the retired merge driver.

中文

Problem

一致性记录过去保存每个语言文件完整的 blob hash。对配对的任何修改都会改动这两行,因此两个分支即使修改同一配对的不同部分、Git 也能干净合并两个 Markdown 文件,记录仍然必然冲突。受影响最大的是 docs/config-catalog.md、docs/module-graph.md 和 docs/event-producer-consumer.md 等生成参考文档:大多数涉及包、配置或事件的 PR 都会重新生成其中之一,而每次重新生成还要求手工复制中文更新并写入新记录。worktree 本地的合并驱动可以组合这类记录,但 GitHub 的可合并性检查从不运行它,因此在有人于本地合并基线分支并推送之前,PR 仍显示冲突。

Decision

foo.i18n.yaml 为每个含有计入 hash 内容的标题分节保存一个条目。verify-translation-pairing 计算这些条目;scripts/translation-pairing-record.ts 负责该格式。

  • 两侧都在每个标题处切分。英文文件的第 i 个分节对应中文文件的第 i 个分节;标题数量不同属于结构错误,无法记录。
  • 键是英文标题 slug 组成的路径,例如 /plugin-config-catalog/loadable-plugins-with-no-config。第一个标题之前的文本为 /,重复路径依次加 ~2、~3 等后缀。键的计数覆盖每个分节(包括没有条目的分节),因此键只取决于标题。
  • 每侧的 hash 覆盖该分节在配对文档链接本地化后的顶层块,只排除围栏代码块和生成区域。这两类内容正是配对门禁已要求两侧一致的内容;其他内容即使碰巧相同也计入 hash。条目保存每侧计入 hash 的块的 SHA-256 前 16 位十六进制值。没有计入 hash 的块的分节没有条目。
  • 每个条目占三行:一行不变的键,后跟 en: 与 zh: 两行 hash。键行把相邻条目隔开,因此 Git 文本合并把对相邻条目的修改视为不同的 hunk。

因此,只有两个分支都修改了同一分节中计入 hash 的内容时,记录才会冲突。

生成器把生成数据放在生成区域中,并把每个区域写入两种语言的页面,因此重新生成既不改变记录,也不改变中文说明文字。gen-config-catalog 写出两种语言和记录,每个包一个区域,每个包表格一个区域;区域内的标签是代码标识符 inject、refs、source 和 class,由已翻译的介绍段落解释。gen-module-graph 把表格放在区域中,列头使用标识符。gen-doc-graphs 把事件矩阵的表格写入人工撰写的中文页面,gen-cordis-catalog 已经以同样方式处理子系统区域。

gen-translation-brief 从配对的提交中找到内容能生成已提交记录的最新提交,并从中还原上次确认的文本。记录不再指向 Git blob,--write 也不再存储快照对象或 ref。

已归档的 Agent Note 保留其已封存的整文件记录;该格式由 verify-archived-agent-notes 负责。

Alternatives considered

保留整文件 hash 和合并驱动。 驱动只在已安装它的本地 worktree 中解决冲突。GitHub 仍把 PR 报告为冲突,未安装运行时的原始合并会停止,而驱动、解决命令、安装脚本集成及其测试共约 1,400 行需要维护的代码。

按顶层块而非按标题分节记录条目。 长分节内的误报冲突更少,但记录会增长到约等于文档的块数。按位置编号的块键还会在每次插入时重新编号,自身又制造冲突。

按位置编号的分节键(h2[3])。 插入一个分节会让之后的每个键重新编号,并改动之后的每一行记录,重新造成这种格式要消除的冲突。标题 slug 键只在该标题的英文文本改变时才改变。

通过 Actions 或 GitHub App 在 GitHub 上解决。 托管自动化需要凭证、并发控制以及改写 PR 分支的权限。Git 默认文本合并即可组合的记录不需要这些。

对整个分节(包括代码块和区域)取 hash。 每次重新生成的行或代码块仍会改动一行记录,因此生成参考文档最大的分节仍会冲突。

排除两侧逐字节相同的任何块。 这同样能让生成的行不进入记录,但它隐式地判定哪些内容不需要翻译:原样复制到中文文件而未翻译的英文段落也会被排除,之后对它的修改也不再改变记录。代码块和生成区域是现有门禁已检查的、明确声明的共享内容。

Consequences

对不同分节的修改,以及重新生成的区域和代码块,无需本地工具即可合并,在 GitHub 上也是如此。重放 2026-08-20 至 2026-09-23 期间可从 master 到达的合并提交可以看到它在真实历史上的效果:见实测效果。

同一分节的冲突需要先解决 Markdown 冲突,再重新运行 verify-translation-pairing --write <pair>;没有命令会自动完成。生成区域的中文读者看到的是代码标识符和英文表头,而不是译文标签。为 brief 还原上次确认的文本,要求已确认的配对已经提交。

Measured effect

重放覆盖了 2026-08-20 至 2026-09-23 期间可从 master 到达的 2016 个双父合并提交,其中大多数是 PR 分支向前合并基线分支。对两个父提交都修改了其记录的每个活跃配对,它分别对旧记录、根据同一提交内容计算出的按分节记录,以及两个 Markdown 文件运行 Git 默认文本合并。某个提交中标题未对齐而无法计算的记录计为冲突。旧格式在 850 个合并提交中的 2950 次配对合并里冲突;按分节格式在 595 个合并提交中的 1586 次配对合并里冲突,按配对计减少 46%。Markdown 文件本身在 338 个合并提交中的 926 次配对合并里冲突,这部分任何记录格式都无法避免。只有记录冲突的合并提交从 512 个降到 257 个(减少 50%)。

重放使用各提交当时的内容,那时生成器还未把数据放进区域,因此 docs/module-graph.md(277 次)、docs/event-producer-consumer.md(220 次)和 docs/config-catalog.md(110 次)仍因其中的生成数据冲突。数据放进区域后,它们的记录只在说明文字改变时才变;在同一重放中排除其生成行后,前两者分别剩 0 次和 14 次冲突。用这一结果替换这三个页面后,预计约 990 次配对冲突,比旧格式少约 66%;这是估算,不是重放实测。

Related

双语配对门禁 Agent Note 负责三文件配对及其两种语言同权的规则;本文取代其中的整文件 hash 记录。已归档的自动配对合并 Agent Note 记录已退役的合并驱动。