Skip to content

Report stale translations and broken anchors - #30

Merged
helmihyo merged 6 commits into
mainfrom
feat/translation-status-check
Aug 4, 2026
Merged

Report stale translations and broken anchors#30
helmihyo merged 6 commits into
mainfrom
feat/translation-status-check

Conversation

@helmihyo

@helmihyo helmihyo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Closes #28. Closes #29.

Closes the gap that made translating 21 pages risky: nothing told us when a translation fell behind its source.

What is here

scripts/translation_status.py — sorts every page into missing, unstamped, stale or current. For stale pages it resolves the stamped blob back through git and prints the English diff since the translation was written, which turns "this page is stale" into the specific change to apply. It prints the stamp value to record, so nobody computes a hash by hand.

scripts/check_anchors.py — walks the built HTML and verifies every internal anchor resolves. This broke 16 links during the Finnish translation and mkdocs build --strict did not notice one of them.

scripts/stamp_translation.py — writes the stamp. Deriving the English source from the translation's own path and editing frontmatter by hand is the mechanical step that goes wrong quietly.

.github/workflows/translation-status.yml — comments on PRs touching docs/en/**, scoped to the pages that PR actually changed, and writes the full report to the job summary on every run so pre-existing drift stays visible after the PR that caused it has merged.

.claude/skills/translate-page/SKILL.md — translation as a repeatable operation. Encodes the glossary-first rule, structure and unit requirements, the two markdown traps neither --strict nor GitHub's preview catches, and anchor handling — including the delayed fault where a link in an already-translated page breaks only once its target page is translated.

All 21 Finnish pages stamped, currently all current.

Design, as settled on #25

Report, do not block: fallback_to_default means a lagging translation serves English, so nothing is broken while it lags. State, not commits: the English page carries no token to bump, so an English edit needs no ceremony — the edit changes the content, which changes the hash, which makes its translations report stale on their own.

One decision, since resolved

I implemented anchor validation as reporting, because #25 says "report, do not block" twice — while thinking anchors are a different class: a stale translation is not broken (the page serves English), a broken anchor is a link that goes nowhere and has already shipped. @mairas decided on this PR that it should fail, and it now does. The check runs last so the status report is still published when it fails.

Verified

  • Unknown frontmatter key is inert in Material: does not render, does not reach the search index, builds clean under --strict. Checked rather than assumed.
  • Detection works, tested against real edits: changed 1.1A1.3A in the English errata page and confirmed the report flags exactly that page and shows exactly that line.
  • Re-stamp discipline holds: updating a translation alone does not clear the stale flag; only re-stamping does. The mechanism cannot go green by accident.
  • Anchor checker catches a real breakage: deliberately broke one anchor, confirmed it was reported and the exit status was 1.
  • Full loop end to end: English edit → stale + diff → translation updated → still stale → re-stamp → current.
  • The skill's embedded verification snippet runs verbatim and correctly ignores frontmatter.

Not verified

The PR comment path is not exercised by this PR, because it only fires on docs/en/** changes and this PR touches none. The job summary is exercised; the comment logic stays unproven until a PR edits an English page. Flagging so a green run is not read as broader than it is.

🤖 Generated with Claude Code

helmihyo and others added 4 commits August 4, 2026 12:40
Each Finnish page records the git blob hash of the English page it was
translated from, in its own frontmatter. The English pages carry
nothing, so editing one needs no ceremony: the edit changes its content,
which changes its hash, which makes its translations report as stale on
their own.

All 21 were translated from the English content as it stands, so they
are stamped current. An unknown frontmatter key is inert in Material —
verified that it does not render, does not reach the search index, and
builds clean under --strict.

Part of #28.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the gap that made translating 21 pages risky: nothing told us
when a translation fell behind its source.

scripts/translation_status.py compares each translation's stamp against
the current English blob and sorts pages into missing, unstamped, stale
and current. For stale pages it resolves the stamped blob back through
git and prints the English diff since the translation was written, which
turns "this page is stale" into the specific change to apply. It also
prints the stamp value to record, so nobody computes a hash by hand.

scripts/check_anchors.py walks the built HTML and verifies every
internal anchor resolves. Anchors derive from heading text, so
translating a heading breaks links to it — including links on pages
nobody touched — and mkdocs --strict does not check them at all. This
broke 16 links during the Finnish translation.

The workflow reports and does not block, per #25: fallback_to_default
means a lagging translation serves English, so nothing is broken while
it lags. It comments on PRs that touch docs/en/**, scoped to the pages
that PR actually changed, and writes a full report to the job summary on
every run so pre-existing drift stays visible after the PR that caused
it has merged.

Part of #28.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Computing a blob hash and editing frontmatter by hand is exactly the
mechanical step that goes wrong quietly. The helper derives the English
source from the translation's own path, replaces an existing stamp
rather than duplicating it, and leaves other frontmatter keys alone.

Part of #28.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Makes translating a page a repeatable operation rather than an ad-hoc
prompt. Encodes the glossary-first rule, the structure and unit
requirements, the two markdown traps that neither --strict nor GitHub's
preview catches, and the anchor handling — including the delayed fault
where a link in an already-translated page breaks only once its target
page gets translated.

The stamping discipline lives here: the skill re-stamps only when it has
actually re-translated. A stamp updated without translation work reports
green, which is the one failure the status check cannot detect.

Closes #29.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mairas

mairas commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Yeah, broken anchors should fail the run.

helmihyo and others added 2 commits August 4, 2026 12:57
@mairas on #30: broken anchors should fail. Unlike a stale translation,
which is not broken because the page serves English, a broken anchor is
a link that goes nowhere and has already shipped.

The check moves last so the status report and the PR comment are still
published when it fails.

Also fixes a false green in the checker itself: pointing it at a missing
or empty site directory reported "all anchors resolve" and exited 0,
which is exactly the case where CI should be loudest. It now exits 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two holes found by probing the tools rather than reading them.

A translation whose English source was deleted was invisible: the
report walks the sources, so it simply stopped counting the page while
the translation stayed in the tree and kept being served. Now reported
as orphaned.

stamp_translation.py would happily stamp a page under docs/en/, writing
a self-referential hash into the source. Source pages carry no stamp by
design — that is what makes an English edit ceremony-free — so it now
refuses, reading the default language from mkdocs.yml rather than
assuming "en".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@helmihyo

helmihyo commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Re-review

Your call is implemented

Broken anchors now fail the run. One non-obvious consequence worth knowing: the anchor check had to move to the last step. It was second, and a failing step aborts the ones after it — so a broken anchor would have suppressed the staleness report and the PR comment, hiding one problem behind another. The report is published first, then the run fails.

Two defects found in this pass

Both came from probing the tools rather than re-reading them, and both are fixed in 84e3b6e.

Orphaned translations were invisible. The report walks the English sources, so deleting docs/en/faq.md simply made it stop counting that page — while docs/fi/faq.md stayed in the tree and kept being served. The report went from 21 pages to 20 and said everything was current. There is now an orphaned bucket; verified by deleting a source and watching it appear.

stamp_translation.py would stamp a source page. Given docs/en/faq.md it wrote a self-referential hash into the English page. That inverts the whole design — source pages carry no stamp, which is exactly what makes an English edit ceremony-free, and a stamped source would have been silently wrong forever. It now refuses, and reads the default language from mkdocs.yml rather than assuming en.

There was also a false green in the anchor checker: pointed at a missing or empty site directory it reported "All anchors resolve" and exited 0 — the one case where CI should be loudest. Fixed in 5486c71, exits 2 now.

Deviation from the spec, flagged deliberately

#28 lists three buckets: missing, stale, current. I report five. unstamped distinguishes "no translation" from "a translation whose currency we cannot prove", which matters while stamps are being rolled out; orphaned is the hole above. Say the word if you want them collapsed.

Verified in CI, not just locally

Three green runs on this branch. The report and the anchor output now appear in the job logs as well as the job summary — the first run put everything in the summary only, which meant nothing was visible where you look when it misbehaves.

Still not verified

The PR comment path. It only fires on docs/en/** changes and this PR touches none, so the step has only ever taken its early exit — No English pages touched; nothing to report, which is itself correct. The comment body, the marker lookup and the update-instead-of-duplicate logic stay unproven until a PR edits an English page.

Fork PRs. github.token is read-only for forks, so the comment step would fail and take the run with it. Internal repo, so not worth guarding speculatively — noting it so it is a known limit rather than a surprise.

🤖 Generated with Claude Code

@mairas

mairas commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

LGTM!

@helmihyo
helmihyo merged commit 177cfb2 into main Aug 4, 2026
1 check passed
@helmihyo
helmihyo deleted the feat/translation-status-check branch August 4, 2026 10:04
Sign up for free to 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.

Claude Code skill for translating and stamping pages Translation status check: report missing and stale translations

2 participants