fix(docs): derive the sitemap's static-page locales from the pages that own the copy - #195
Merged
Merged
Conversation
…at own the copy `sitemap.ts` restated the locale surface of `privacy` and `terms` as a hand-maintained `STATIC_PAGE_LOCALES = ['en', 'zh-Hans']`. That was correct today and silently wrong the moment a key was added to either page's `content` record: the translation would exist, be reachable, render — and never be advertised, with no gate failing. Each route module now exports the keys of its own `content` record, and `sitemap.ts` reads the two of them instead of restating the pair. The two are read separately because they are separate records; they agree today and nothing requires them to. The advertised set is unchanged: `en` + `zh-Hans` for both pages, 346 sitemap URLs before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5zjYc2BoFV2NjKBBapC7C
…ve-static-page-locales
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.
Fixes #173
apps/docs/app/sitemap.tsrestated the locale surface ofprivacyandtermsas a hand-maintained constant:Correct today, and silently wrong the moment a key is added to either page's
contentrecord: the translation would exist, be reachable, render — and never be advertised, with no gate failing. It was the one hand-maintained thing left in a file #169 otherwise made fully derived.What changed
Each route module now exports the keys of its own
contentrecord:and
sitemap.tsreads the two of them instead of restating the pair.Why this shape rather than a shared module
The card offered either. Exporting from the route modules wins on this code:
sitemap.tscan reach two records.privacyLocalesandtermsLocales, not one merged set. They agree today and nothing requires them to; the old constant could not express a disagreement, and this can. The probe below shows a locale added toprivacyalone advertisingprivacyalone.The thing worth checking is whether a named export beside a page's default is legal App Router. It is: Next's generated route validator (
.next/types/validator.ts) constrains the known page exports through a structuralextendsand ignores additional ones.pnpm run type-check— which isfumadocs-mdx && next typegen && tsc --noEmit, so the validator is regenerated against the edited files — passes with the export present.Relationship to the locale-surface gate (#175, merged as
d6355de)origin/mainhas been merged in (not rebased); this branch's base now carries.github/scripts/check-locale-surface.mjs, so CI exercises the sitemap gate against this change rather than predating it.That gate holds the privacy/terms locale set as its own declared constant:
It is deliberately not derived from the route modules — a gate that takes its expectations from the thing it checks cannot catch that thing being wrong. This PR makes
sitemap.tsderive the same set from those modules. Both are correct, and they are supposed to be independent.A consequence worth stating plainly, because it is now a real property of the repo: translating a legal page is a two-file action by design. Adding a locale to
privacyortermsmeans editing the route module'scontentrecord and the gate'sSTATIC_PAGESconstant. Whoever does it finds out from a red gate that names the page — which is the intended way to find out, not a gap. Before this PR the same edit would have neededsitemap.tstoo, and forgetting that one failed silently.Verification
Evidence below is from
.next/server/app/sitemap.xml.body, built withpnpm run buildinapps/docs. Final commit3c02400.The advertised set does not move
main@665374499e4815privacy/privacy,/zh-Hans/privacy/privacy,/zh-Hans/privacyterms/terms,/zh-Hans/terms/terms,/zh-Hans/termsa57adad5...a57adad5...The last row is the whole comparison: sorting the url entries and hashing them gives the same digest before and after — every entry, with its hreflang cluster, changefreq and priority, byte for byte.
en+zh-Hanson both sides, as adjudicated.Byte-identical raw output was the stated criterion and it is not meetable, for a reason that is not this change: sitemap entry order is not reproducible. Two consecutive builds of this branch at the same commit, tree untouched, produced different raw bytes (
d2d5c9a1...,e1c28e8b...) and the same sorted digesta57adad5.... Pre-existing onmain; filed separately as #194. It does not threaten the locale-surface gate — that gate compares url sets throughMap/Setmembership and never compares bytes or positions, so it is order-invariant by construction. #194 stands on reproducible-builds grounds alone.The derivation is exercised — and confirmed twice
A derivation that is never run is indistinguishable from the constant it replaced. So a third locale key was temporarily added to
privacy'scontentrecord only, with no edit tositemap.ts(blob09728c37...before, during and after) and no edit to the gate (blobbcdd2e90...before and after), and the app was rebuilt.Confirmation 1 — the sitemap picked it up on its own:
jainprivacy's recordprivacy/privacy,/zh-Hans/privacy/privacy,/zh-Hans/privacy,/ja/privacyterms/terms,/zh-Hans/terms/terms,/zh-Hans/terms— unchangedtermsstaying at two is the load-bearing half: it proves the two records are genuinely read as two, which the old shared constant could not have expressed.Confirmation 2 — the independent gate noticed, and said why:
Exit 1. That red is the point, not a problem: the derivation picked the locale up automatically, and the gate — whose expectation comes from its own constant, not from the route module — noticed the site had started advertising something the oracle did not declare. Two mechanisms, independent, both working.
After reverting the probe, the gate is green again:
The revert is proven on disk, not assumed: the working blob for
privacy/page.tsxis back to1ecb2c65..., identical to itsHEADblob, withgit diff HEADandgit statusempty and the probe marker absent from every tracked file. It is not in this diff.check-locale-surface.mjswas never edited — it is outside this PR's file surface and its constant going red on a real locale change is its intended behaviour.Rendering
Both pages still render in both locales — checked in the prerendered output of the final build:
langattributeen/privacyenzh-Hans/privacyzh-Hansen/termsenzh-Hans/termszh-Hansja/privacystill prerenders the English copy under ajadocument language — the untranslated-fallback behaviour this PR deliberately leaves alone and correctly declines to advertise.A real browser was not driven (AGENTS.md rule 2): none is available in this environment, so this is prerendered-HTML inspection, not a browser check. The change adds a module-level export and touches no component body, no JSX and no markup.
Gates
Run locally on the merged tree at
3c02400; each exit status captured before any pipe.pnpm turbo run type-check --continue --forceTasks: 1 successful, 1 total— exit 0pnpm turbo run build✓ Compiled successfully,✓ Generating static pages (996/996)— exit 0node .github/scripts/check-locale-surface.mjs✓ every advertised URL has a source file, and every source file is advertised— exit 0pnpm turbo run test --forceTasks: 1 successful, 1 total,✓ 2 self-test(s) passed— exit 0node .github/scripts/check-node-floor.mjs✅ Every declared floor clears what the dependency tree requires— exit 0That is the whole of
ci.yml: thenode-floorjob plus thebuildjob's four steps.translations.ymldoes not run on this PR — the diff touches none of its trigger paths. No changeset flow in this repo, and nopackages/touched.Files
Exactly the three files the card declared, plus the merge commit bringing in
origin/main:apps/docs/app/[lang]/privacy/page.tsxapps/docs/app/[lang]/terms/page.tsxapps/docs/app/sitemap.tsGenerated by Claude Code