i18n: surface uncurated English pages instead of burying them - #2002
Merged
Conversation
Six English pages sat untranslated in all 18 locales for up to three weeks and
nothing said so. Two of them were noticed by a human browsing the live site.
The detector was not silent — that was my first diagnosis and it was wrong. It
already rendered an "Uncurated source pages" section. The problem is where: line
122 of a 133-line dashboard issue, below a Details list that runs one row per
stale pair (126 of them that day), and absent from the summary table at the top.
Technically visible, reliably unseen. The console line had the same shape:
`Uncurated source pages: 6 — e.g. <three of them>`, which reads as a footnote
rather than a work item.
Worse, when the corpus is otherwise clean the dashboard said:
✅ All 3654 curated (page × locale) pairs are fresh. Nothing to sync.
True and misleading at once. A page that was never curated cannot be stale or
missing, so it is counted in none of the columns — the all-clear is measured over
exactly the set that excludes the problem.
## Changes
* `uncurated` is now a column in the summary table, with a callout under it
explaining that those pages are counted in none of the other columns and why.
* the all-fresh banner no longer implies there is no work when uncurated pages
exist; it states both facts.
* the section itself now says what the state costs (untranslated in every
locale, English served) and how to change it (add to curated-pages.txt → shows
up as `missing` × 18 → next sync fills it).
* the console summary prints the FULL list, not `e.g.` plus three.
## New: advisory PR comment (.github/workflows/uncurated-page-advisory.yml)
Comments on a PR that adds English pages missing from the curated set, so the
debt is visible while someone is still looking at the PR. It is explicitly
ADVISORY: it never fails a check, and the comment opens by telling the
contributor nothing is wrong with their PR.
There is deliberately no rule that every new page must be curated. Curation is a
lasting editorial commitment — the wiki then maintains that page in 18 languages
— and it is not a drive-by contributor's call to make. A blocking gate would also
be worse than useless: the fix that makes CI green is to add the line, which
creates 18 items of real work with nobody having decided the page deserves them.
That is precisely how a backlog grows silently. `curated ⊆ site` is the
invariant; `site ⊆ curated` is not, by design.
Security: it uses pull_request_target, because most PRs here come from forks and
a plain `pull_request` token cannot comment on those. That is only safe if PR
code never runs, so the job has ONE step, no checkout of anything, and no `run:`
step at all. The PR is read purely as data through the API — the file list, and
the text of curated-pages.txt at the head sha. The header comment says not to add
a PR checkout; please keep it that way.
It reads curated-pages.txt at the HEAD sha, not base, so a PR that adds a page
AND curates it in the same change stays silent. It upserts a single comment, so
repeated pushes edit rather than pile up, and it withdraws its own warning if the
pages get curated later in the PR's life.
Verified against the six currently-open PRs: five stay silent (they only edit
existing pages) and exactly one fires — ZecHub#1930, which adds
Zcash_Tech/Project_Tachyon.md. That is a real 7th uncurated page about to land
with nothing today that would report it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEgDv6GG6Eybs1gu2mhcgY
Three reviewers (Fable, gpt-5.5, Zabaniya) all cleared the part I most wanted
challenged: the `pull_request_target` surface is safe as written — one
github-script step, no checkout, no `run:`, and the only `${{ }}` interpolation
anywhere is `pull_request.number` (an integer). PR content is read purely as API
data. Two of them verified independently that the getContent fallback cannot
post a wrong comment, that the >1MB `content`-omission case is ~150x away for a
7KB file and degrades safely regardless, and that the concurrency group cannot
produce duplicate or misdirected comments.
Four real defects, each fixed and re-verified by execution:
1. Renames were invisible. The filter only accepted `status === 'added'`, but a
PR that MOVES drafts/X.md to site/X.md creates exactly the same translation
debt and GitHub reports it as `renamed`. Now accepts added/renamed/copied.
Confirmed against real PR data that this adds no noise: the same 5 of 6 open
PRs stay silent, the same one fires.
2. The comment lookup matched on the marker alone, so anyone could pre-post a
comment containing `<!-- uncurated-page-advisory -->` and the job would try to
edit it — failing instead of advising, or rewriting someone else's comment.
Now requires the marker AND a github-actions bot author.
3. A stale warning could stand forever. If a later push dropped the added pages
while still touching site/**, `if (added.length === 0) return` fired BEFORE
the branch that rewrites the comment to "nothing outstanding". The PR then
permanently carried an advisory listing pages it no longer adds. The early
return is gone; the flow falls through to the withdraw branch.
4. Filenames are attacker-controlled and were interpolated straight into a
markdown code span. A backtick in a path closes the span and lets the rest
render as markdown — an @mention in a filename would ping people. Verified:
"x`</code>@everyone`.md" escapes the span raw and does not after stripping.
Also noted by review and deliberately left: the advisory hardcodes a link to
issue 1889, which goes stale if that dashboard issue is ever closed and
recreated. Not worth a config surface for an advisory comment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YEgDv6GG6Eybs1gu2mhcgY
Contributor
Author
|
Reviewed by Fable, gpt-5.5 and Zabaniya. Fable and Zabaniya approved, gpt-5.5 rejected on two workflow gaps; all four findings fixed in cab7933. All three independently checked the Fixed: renames were invisible (only |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six English pages sat untranslated in all 18 locales for up to three weeks and nothing reported it. The uncurated list was already on the dashboard, but at the bottom of a 130-line issue and missing from the summary table, so it was never seen.
uncuratedis now a column in that table, with a line explaining that those pages are counted in none of the others — a page that was never curated can't be stale or missing. The all-fresh banner no longer reads "nothing to sync" when uncurated pages exist.Also adds an advisory comment on PRs that introduce English pages missing from the curated set, so the backlog is visible while someone is still looking at the PR. It never fails a check and the comment says so. There's deliberately no rule requiring new pages to be curated: that's a lasting editorial commitment to maintain the page in 18 languages, and a blocking gate would make "add the line" the cheapest route to green CI.
It uses
pull_request_targetbecause fork PRs otherwise can't comment. The job is onegithub-scriptstep with no checkout and norun:step, and the PR is read only as API data.Currently blocked by the stale
translation/menu-titles/en.jsonon main — #1998 is the fix.🤖 Generated with Claude Code