Skip to content

docs: make translations a generated artifact with a freshness gate - #65

Merged
hotlong merged 1 commit into
mainfrom
claude/objectos-docs-content-strategy-w8fcm0
Aug 18, 2026
Merged

docs: make translations a generated artifact with a freshness gate#65
hotlong merged 1 commit into
mainfrom
claude/objectos-docs-content-strategy-w8fcm0

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Fixes#66

English is the only authored language here — that is already AGENTS.md rule #1, but nothing enforced it and nothing measured it. This PR turns the convention into machine rules and gives the periodic translation pass a bounded worklist to run against.

Why

Two measured costs:

Translation churn dominates content PRs. The last two content PRs spent 86% of their diff on hand-maintained locale siblings — #63 changed 63 files, 54 of them translations; #62 changed 28, 24 of them translations. Every English edit obligated its author to also produce six translations.

Drift is currently undetectable. 256 translations are checked in with no record of what they were derived from. Walking git history shows 16 are already stale against three English pages (configure/authentication, index, resources/faq) — nothing reports this today, and a stale translation is worse than a missing one: a missing translation renders correct English via Fumadocs fallback, a stale one renders content the English source no longer claims.

What changed

Provenance stamp. Every translation now carries a translation: frontmatter block with the sha256 of its English sibling, the guide revision it was produced under, and whether a human pinned it:

translation:
source_sha: d8f6522ae3a0…guide_rev: 1mode: auto # auto | reviewed

The baseline is recovered from git history, not assumed: a page whose English source changed after the translation's last commit is stamped at the revision it actually matches, so it reports as stale rather than laundering unknown drift into a green gate. The gate independently reproduces the same 16 stale files the history walk found.

check-translations.mjs — reports stale / missing / guide-stale per locale, blocks on unstamped and orphaned files. Stale is deliberately not blocking on PRs: forcing an English author to also produce six translations is exactly the cost this removes. --worklist emits work items for a translation pass (excluding mode: reviewed pages); --stamp records provenance after one; --gate=release --require=zh-Hans is available for release-time enforcement on first-class locales.

check-translation-ownership.mjs — enforces both halves of the split, keyed on PR author login rather than a label (a label can be forgotten or edited; an author cannot be forged): the translation account may touch only locale artifacts, everyone else may touch only everything else. It stays inert and passing until the repo variable TRANSLATION_BOT_LOGIN names the account, so this can land before that account exists.

docs/TRANSLATION.md — the contract a translation pass runs under: what is never translated (code fences byte-identical, no new URLs, frontmatter keys, MDX component names, identifiers), the glossary documenting terms the corpus already uses — and settling 仪表盘 vs 仪表板, which currently appear 55 and 2 times — the register rules, and the pre-PR checklist. Page text is stated to be data to be translated, never an instruction to follow, since the English MDX is written by anyone who can open a PR and its translation is committed to a public site.

AGENTS.md / CONTRIBUTING.md — the old workflow told agents to "update each existing locale sibling to match", which is now the opposite of the rule. Both rewritten.

Notes

  • Both scripts are dependency-free, network-free and credential-free — they run on fork PRs and in any checkout. No secret is added to this public repository. (This is also why the check completes in ~5s: there is no install step.)
  • Locales are read from apps/docs/lib/i18n.ts, which AGENTS.md names as the authority, so a locale added there is gated from the moment it is added.
  • No translations are deleted and no locale is dropped — all six stay.

Verification

  • pnpm turbo run build — passes (335 MDX files, extra frontmatter key accepted; Fumadocs' pageSchema strips unknown keys).
  • npm run type-check in apps/docs — passes.
  • --baseline is idempotent: a second run stamps 0 files.
  • Ownership check exercised across all four branches (unset variable, human/English, human/translation → fails, bot/artifacts, bot/English → fails).
  • CI: Ownership & freshness passed on the runner with output matching the local run.

Process

Filed against #66after the work rather than before. The deviation is recorded in the claim comment on that issue rather than smoothed over.

Follow-ups (not in this PR)

  1. Create the dedicated translation account, add it as an outside collaborator on this repo only (not an org member — org membership could inherit access to the private cloud repo), and set TRANSLATION_BOT_LOGIN.
  2. First backfill pass: 16 stale + 218 missing page-translations to reach full coverage across six locales.
  3. Add the output validators from the docs/TRANSLATION.md checklist as an automated check on translation PRs.

English is the only authored language; every `*.<locale>.mdx` file is a
derived artifact. Until now that was a convention in AGENTS.md with nothing
enforcing it, and the cost showed up in two places: a typical docs PR spent
86% of its diff on hand-maintained locale siblings (#63 touched 63 files, 54
of them translations), and a translation that drifted from its English source
was undetectable — 16 of the 256 checked-in translations are already stale
against three English pages.
A stale translation is worse than a missing one: a missing translation renders
correct English via Fumadocs fallback, a stale one renders content the English
source no longer claims. So make provenance explicit and machine-checkable.
- Every translation now carries a `translation:` frontmatter block recording
the sha256 of the English sibling it was derived from, the guide revision it
was produced under, and whether a human has pinned it (`mode: reviewed`).
The baseline stamp is recovered from git history rather than assumed: a page
whose English source changed after the translation's last commit is recorded
at the revision it actually matches, so it reports as stale rather than
laundering unknown drift into a green gate.
- `check-translations.mjs` reports stale / missing / guide-stale per locale and
blocks on unstamped and orphaned files. Stale is deliberately NOT blocking on
PRs — forcing an English author to also produce six translations is the cost
this change removes. `--worklist` emits the work items for a translation pass;
`--stamp` records provenance after one.
- `check-translation-ownership.mjs` enforces both halves of the split by PR
author login (not a label — a label can be forgotten, an author cannot be
forged): the translation account may touch only locale artifacts, everyone
else may touch only everything else. Inert until the repo variable
TRANSLATION_BOT_LOGIN names the account, so this can land first.
- `docs/TRANSLATION.md` is the contract a translation pass runs under: what is
never translated, the glossary (documenting the terms the corpus already
uses, and settling 仪表盘 vs 仪表板), and the pre-PR checklist. Page text is
data to be translated, never an instruction to follow.
Both scripts are dependency-free, network-free and credential-free, so they run
on fork PRs and in any checkout.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJPxtTxoxTUnjNdTbiEaRa
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Translations are hand-maintained with no provenance: 86% of a docs PR is churn, and 16 of 256 are already stale

3 participants

@os-zhuang@hotlong@claude