Establish EN->ZH bilingual documentation for the README and docs tree: - docs/i18n/README.md — the pairing contract: sibling foo.md <-> foo.zh.md, English canonical, blob-hash source fingerprints, language switchers, scope/exclusions, and a manifest-driven rollout ratchet. - docs/i18n/translation-rules.md — how to translate: faithfulness, structure preservation, terminology discipline over docs/i18n/terminology.md, and typography rules grounded in MDN/K8s/Vue/clreq conventions. - .agents/skills/dsh-translate-docs — the committed agent workflow, following the dsh-code-review pattern of deferring to docs as sources of truth. - scripts/verify-translation-pairing.ts + manifest — a doc-sync gate: required pairs exist; every existing .zh.md is fresh (fingerprint = current source blob), switcher-linked, structure-matched, and non-orphaned; excluded (generated) docs stay unpaired. --list prints the translation work list. - RFC (implemented/process) recording the decision and the alternatives. - Dogfood: README.zh.md and the two i18n docs translated under their own rules. Gates: doc-sync green including the new gate; red/green proven for stale fingerprint, orphan, and excluded-file violations.
5.1 KiB
Bilingual documentation
English | 中文
This repo's documentation is read by people and agents both inside and outside the company, so the README and the docs tree are maintained in English and Simplified Chinese. This page defines the pairing contract, the enforcement gate, and the rollout policy; translation-rules.md defines how to translate; terminology.md is the terminology source of truth. The committed agent workflow lives in .agents/skills/dsh-translate-docs.
The pairing contract
-
English is canonical. Every document is authored in English at its existing path, and the Chinese file is derived from it — translation flows EN → ZH only. A content change starts in the English file; the Chinese file never carries information its English source lacks.
-
Paired sibling files. The translation of
foo.mdisfoo.zh.mdin the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. -
Source fingerprint. The FIRST line of every
.zh.mdfile is an HTML comment recording the repo-relative path and the git blob hash (first 12 hex digits ofgit hash-object) of the English source it was translated from:<!-- i18n-source: docs/architecture.md@8a9f0c21d3e4 -->A blob hash, not a commit hash, so the fingerprint is computable for an English file edited in the same PR (
git hash-object docs/foo.md), and so staleness is a pure content comparison. The fingerprint is also the update tool:git cat-file -p <hash>recovers the exact source text a stale translation was based on, andgit diff <hash> <current-blob>isolates what changed so the translation can be updated minimally instead of re-translated. -
Language switcher. Both files link to each other immediately after their H1 heading: the English file carries
English | [中文](foo.zh.md)and the Chinese file carries[English](foo.md) | 中文. -
Structure mirrors the source. Heading hierarchy, list shape, table columns, and code blocks match the English file one to one — see translation-rules.md for the full preservation rules. Existing Markdown gates apply to
.zh.mdfiles unchanged (verify-md-wrap,verify-md-links).
The gate: verify-translation-pairing
pnpm run verify-translation-pairing (part of doc-sync, so CI and the pre-push hook run it) enforces the contract mechanically:
- Every English file listed as
requiredin scripts/translation-pairing.manifest.json has a.zh.mdsibling. - Every existing
.zh.mdfile — required or not — passes all of: its English source exists (no orphans), its fingerprint matches the source's current blob hash (no stale translations), both sides carry the language switcher, and its fenced-code-block and heading counts equal the source's. - Files listed as
excludedhave no.zh.mdsibling at all.
pnpm run verify-translation-pairing --list prints the current translation state of every document in scope — missing, stale, or ok — and is the work list for translation batches. It never fails; it reports.
The practical rule this gate creates: when a PR edits an English document that has a .zh.md sibling, the same PR updates the translation (run the dsh-translate-docs skill), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a translation stale goes red in CI.
Scope, exclusions, and rollout
Scope: the root README.md and everything under docs/**. Package READMEs (packages/**) join the scope in a later batch.
Excluded (never paired, and the gate rejects a .zh.md for them):
docs/cordis-catalog/,docs/tool-catalog/,docs/module-graph.md— generated files; their generators emit English only, so a translation would go stale on every regeneration.docs/AGENTS.md— agent instructions, maintained in English only like the rootAGENTS.md.docs/i18n/terminology.md— the terminology table is itself bilingual by construction.
Rollout: the required list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Translation lands in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to required, so the gate ratchets forward and never regresses. Documents not yet in required are backlog — visible in --list — but any translation that already exists is held to the full contract regardless of the list.
Division of labor
Translations here are produced by an agent running dsh-translate-docs and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate exists so that neither the agent nor the reviewer has to remember the contract: pairing, freshness, and structure are checked mechanically, and review attention goes to translation quality and terminology, where human judgment is the whole point.