Translate Docusaurus theme chrome (navbar, footer, code.json) for ES/PT#189
Conversation
Docusaurus theme UI strings — navbar labels, footer links, and
site.docsSidebar's `code.json` bucket (breadcrumbs, search, pagination,
and other generic chrome) — were either untranslated (English defaults)
or, for section/category labels sourced from Notion Title/Toggle rows,
carried Docusaurus's own generic write-translations placeholders
("Nueva Página", "Nuevo título de sección", "Nueva Palanca") instead of
real content. This is the theme-side half of the untranslated-header
fix; the content-side half (canonical hierarchy engine, sidebar
category generation) lives in comapeo-content-pipeline PR #6.
Adds i18n/{es,pt}/docusaurus-theme-classic/{navbar,footer}.json with
translated navbar item labels and footer section/link labels.
Replaces placeholder/English messages in i18n/{es,pt}/code.json with
real translations. Selectively un-ignores the new theme-classic files
in .gitignore (previously the whole /i18n/ tree was generated/ignored)
while leaving the rest of i18n/ — which is regenerated per-sync from
the content pipeline — untouched.
Test coverage: scripts/locale-parity.test.ts (new markdown structural-
parity harness) and scripts/verify-locale-output.test.ts (refactored)
assert no placeholder/empty translations, matching key counts across
locales, and expected theme-translation keys. Two pre-existing
security/detect-object-injection lint warnings in the latter (dynamic
property access against a hardcoded, test-local key list, never
external input) are resolved with justified inline disables so the
repo's zero-warnings pre-commit policy passes cleanly.
PR Summary by QodoTranslate Docusaurus theme chrome for ES/PT (navbar, footer, code.json)
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
🚀 Preview DeploymentYour documentation preview is ready! Preview URL: https://pr-189.comapeo-docs.pages.dev 📦 Content: From content branch (no script changes)
This preview will update automatically when you push new commits to this PR. Built with commit a9118af |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8aecfc8a69
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Code Review by Qodo
Context used✅ Compliance rules (platform):
13 rules 1.
|
extractTranslatableText only generated links.title.* / links.<section>.<label> keys for footer.json, but @docusaurus/theme-classic reads link.title.* and link.item.label.* at build time. Every bun notion:translate run was overwriting footer.json and silently dropping the working translations. Now both key formats are emitted so regeneration is idempotent. Addresses Codex review comment on PR #189.
The content-sync .gitignore adjustment only removed the old exact "/i18n/" line. Since main's .gitignore now has a negation block (/i18n/*, !/i18n/es/, etc. to allowlist theme JSON), that block survived the sed and kept everything under i18n/ ignored except navbar.json/footer.json — so git add i18n silently dropped code.json and translated doc pages from the content branch. Addresses greptile review comment on PR #189.
assertNoPlaceholderMessages/assertNoUntranslatedMessages both skip falsy messages, so an empty-string translation in code.json passed verification silently. And the untranslated check only caught entries where key === message, missing English defaults left under different keys (e.g. theme.TOC.title = "On this page"). Adds assertNoEmptyMessages and a curated KNOWN_ENGLISH_THEME_DEFAULTS/assertNoKnownEnglishDefaults check for the highest-risk theme UI strings, wired into both es/pt code.json tests. Addresses qodo-code-review and greptile comments on PR #189.
extractTranslatableText only extracted navbar item.label.* keys, never logo.alt — the next bun notion:translate run would have overwritten both committed navbar.json files and dropped a key the test suite now requires. Added logo.alt extraction plus direct unit tests on extractTranslatableText covering exact navbar/footer key output. Found by Codex (gpt-5.6-sol) PR-readiness review.
…anch's test.yml and deploy-pr-preview.yml both overlay i18n/ from origin/content before running tests or building the preview, silently discarding any i18n/ files a PR itself modifies (theme translation navbar.json, footer.json, code.json in this PR's case). Green CI was therefore never actually validating this PR's own locale changes, and the preview would have rendered unrelated content-branch state instead. Both workflows now restore any i18n/ path the PR itself changed (relative to its base ref) immediately after the content overlay, so CI validates and the preview renders what the PR is actually proposing. Found by Codex (gpt-5.6-sol) PR-readiness review.
assertNoKnownEnglishDefaults only checked 9 hand-picked theme strings,
leaving 79 of the 87 Docusaurus base theme keys unguarded — e.g.
theme.docs.sidebar.closeSidebarButtonAriaLabel left as its English
default ("Close navigation bar") would have passed every existing
check undetected.
Replaced it with a dynamic comparison against the actual Docusaurus
translation catalogs this site uses (theme-common, plugin-pwa,
plugin-ideal-image), with a small allowlist for pure interpolation
templates that have no literal English words to translate.
This immediately caught two real untranslated strings in es/code.json
(theme.admonition.info, theme.admonition.tip — left as "info"/"tip"
while every other admonition label was translated) and one legitimate
false positive (theme.blog.author.pageTitle, a "{authorName} - {nPosts}"
template) which is now allowlisted.
Found by Codex (gpt-5.6-sol) PR-readiness review.
The dynamic base-catalog comparison added in 41de9ae only validated keys that already existed in the locale files, and silently swallowed catalog read/parse failures — so a translation key missing entirely (not just left in English) passed undetected. This is exactly the failure mode the check was built to catch. Found in practice: three PWA plugin keys (PwaReloadPopup.info, .refreshButtonText, .closeButtonAriaLabel) were absent from both i18n/es/code.json and i18n/pt/code.json, so ES/PT users would see the English PWA update prompt at runtime despite the check passing. - assertHasAllDocusaurusBaseKeys now requires every loaded base-catalog key to be present in each locale file. - Catalog loading no longer catches and swallows errors — since every file in DOCUSAURUS_BASE_TRANSLATION_FILES corresponds to a plugin already confirmed active, a read/parse failure means something is actually broken and should fail the test, not silently pass. - Added the missing PwaReloadPopup translations to es/pt code.json, using Docusaurus's own shipped es/pt-BR strings. Found by Codex (gpt-5.6-sol) PR-readiness review, iteration 2.
…ngine (#6) Adds a canonical hierarchy engine (src/lib/hierarchy.ts) shared by docs:pull, manifest generation, and the Worker, replacing the ad-hoc slug/section/dedupe logic previously duplicated across all three. Fixes untranslated ES/PT sidebar category structure and content caused by Title/Toggle structural rows being discarded, translation containers being detected by locale instead of structure, and duplicate same-route candidates being resolved by document position instead of content quality. Companion PR digidem/comapeo-docs#189 fixes the theme-chrome half (navbar, footer, generic code.json) of the same untranslated-header issue — both are needed for the fix to be complete.
… PR-diff-scoped
The restore-after-content-overlay fix added earlier only restored i18n/
paths found in a diff against the ref's merge-base with main. That diff
is empty on a push to main itself (HEAD == origin/main after this PR
merges) and for any future PR that doesn't touch i18n/ — meaning it
silently restored nothing on exactly the runs that matter most.
Confirmed the actual failure: origin/content's i18n/{es,pt}/code.json
has only 65 keys (missing all 90 Docusaurus base keys added by this
PR), so the first post-merge push to main — and every subsequent
non-i18n PR — would have failed the new fail-closed locale checks.
Also found deploy-production.yml and deploy-staging.yml do the exact
same content-branch overlay (git checkout <sha|content> -- docs/ i18n/
static/images/) but were never given any restore step at all, meaning
this PR's actual payload (the code.json theme-chrome translations —
breadcrumbs, buttons, admonitions, PWA popup) would have silently
never reached production or staging. Only navbar/footer.json survived,
by the accident that the content branch has no docusaurus-theme-classic
directory to overlay over them.
Replaced the diff-based restore in test.yml and deploy-pr-preview.yml,
and added the same restore to deploy-production.yml and
deploy-staging.yml, with a simpler and more robust approach: always
restore the repo-tracked hand-maintained i18n paths (code.json,
docusaurus-theme-classic/*) from the ref being tested, rather than
computing what that specific ref's diff touched. These files are never
supposed to come from the content branch regardless of what changed.
Found by Fable 5 independent PR-readiness review — caught after the
Codex gpt-5.6-sol 3-round readiness loop had already returned "ready
to merge," because that loop only ever validated the fix from inside
PR check runs and never simulated a post-merge push to main or the
separate production/staging deploy workflows.
deploy-staging.yml's top-level `on.push.paths` trigger includes both docs/** and i18n/**, so a push to main touching either fires the workflow. But a separate internal bash gate re-filters what counts as "deploy-relevant" for a main-branch push, and its regex only matched src/, static/img/, docusaurus.config.ts, sidebars.ts, and package.json — missing both docs/ and i18n/. Concretely: merging this PR (i18n + workflow files only) would fire the workflow via the top-level trigger, then the internal gate would set SHOULD_DEPLOY=false with reason "main push without deploy-relevant code/config changes", silently skipping the staging deploy that was supposed to ship these translations. Only reachable via manual workflow_dispatch. Found independently by both a Codex gpt-5.6-sol high-effort review round and my own direct verification of the same claim.
Same content-branch overlay bug fixed in test.yml, deploy-pr-preview.yml, deploy-production.yml, and deploy-staging.yml — this workflow does the identical `git checkout origin/content -- docs/ i18n/ static/images/` overlay but was missed entirely when those four were fixed. Manual test deployments (workflow_dispatch) would render stale content-branch theme data (65 keys) instead of the correct hand-maintained translations (159 keys) this repo owns. Added the same restore step used in the other four workflows. Found by Codex gpt-5.6-sol high-effort PR-readiness review (round 4).
- theme.blog.paginator.navAriaLabel: trailing space - theme.docs.sidebar.closeSidebarButtonAriaLabel: "Cerrar barra de lateral" -> "Cerrar barra lateral" (extraneous "de") - theme.IdealImageMessage.load: untranslated "Click" -> "Haz clic", and corrected verb (initial load, not reload): "cargar" not "recargar" - theme.IdealImageMessage.error: untranslated "Click" -> "Haz clic" (reload/retry context here, so "recargar" is correct) - theme.contentVisibility.unlistedBanner.message: missing accent, "indexaran" -> "indexarán" Portuguese catalog was independently spot-checked and found clean. Found by Codex gpt-5.6-sol high-effort PR-readiness review (round 4).
…tore The i18n-restore step in all five overlay-consuming workflows blindly checked out code.json from HEAD after overlaying content, fully replacing it. This silently discarded content-only translation keys that the Notion pipeline adds to code.json on the content branch, since those keys never exist in the repo-tracked HEAD version. Split the restore: docusaurus-theme-classic/* still restores unconditionally from HEAD (never content-generated), but code.json now merges the content-overlaid version with HEAD, letting HEAD win on any overlapping key while preserving content-only entries. Verified against real repo data: content-branch code.json has 2 locale-specific keys absent from HEAD per locale; the old restore silently dropped both on every build, the merge preserves them while still landing all of this PR's curated key fixes.
…rors Two bugs in the round-6 code.json merge fix: 1. deploy-production.yml's content promotion commit was a plain `git commit`, which commits the entire index — not just the newly `git add`ed content-lock.sha. Since the content overlay checkout earlier in the same job stages the raw (unmerged, content-only) code.json into the index, and the jq merge only updates the working tree, that stale index entry would ride along into the commit on main. Scope the commit to content-lock.sha explicitly so other staged changes are left alone. 2. All five `jq ... > "$f.tmp" && mv "$f.tmp" "$f"` merge lines put jq on the non-final side of `&&`. Per bash's documented -e semantics, a command before the final && in a list is exempt from errexit, so a jq failure (e.g. malformed content-branch JSON) doesn't stop the step — the loop just moves to the next locale and reports success, silently leaving that locale's code.json unmerged. Split jq and mv onto separate lines so a jq failure actually halts the step. Verified: reproduced the swallowed-failure behavior of the old &&-joined pattern in an isolated bash simulation (subshell exits 0 despite a jq parse error) and confirmed the split-line version correctly propagates the failure. Confirmed `git commit <pathspec>` scopes the commit to that path only, leaving other staged changes untouched, in an isolated git simulation. actionlint: 232 warnings before and after (unchanged baseline, zero new). Full test suite: 2113 passed, 8 skipped, 0 failures.
… merge `jq -s '.[0] + .[1]' "$f" <(git show "HEAD:$f")` slurps every JSON document across both input files into one flat array and blindly indexes [0]/[1] — it never verifies each file actually contributed exactly one object. Four ways this silently produced a wrong result instead of erroring: - Empty content-branch code.json: falls back to HEAD alone (exit 0), masking an unexpectedly empty/corrupted content file. - `null` content root: same silent fallback to HEAD alone. - Two JSON documents concatenated in the content file: merges those two documents together and drops HEAD's catalog entirely — HEAD's curated theme keys vanish with no error. - A failed `git show HEAD:$f` inside the process substitution: jq still exits 0, silently proceeding with content alone. Before this PR's restore/merge logic existed, Docusaurus consumed the overlaid file directly and its schema validation would reject these malformed states; this merge step's fail-open behavior is new. Fix: validate each side is exactly one JSON object (`jq -s 'length'` == 1, then `type == "object"`) before merging, and read HEAD into a real temp file instead of process substitution so a failed `git show` halts the step under `set -e` instead of being silently absorbed. Verified: reproduced all four failure modes in an isolated sandbox against the old pattern (all exited 0 with wrong output — the two-doc case dropped HEAD's data entirely), then confirmed the new validation correctly rejects all four with exit 1 and a clear error, while the happy path still merges correctly (content-only keys preserved, HEAD wins overlaps). actionlint: 232 warnings before and after (unchanged baseline). Full test suite: 2113 passed, 8 skipped, 0 failures.
🧹 Preview Deployment CleanupThe preview deployment for this PR has been cleaned up. Preview URL was: Note: Cloudflare Pages deployments follow automatic retention policies. Old previews are cleaned up automatically. |
Summary
Companion PR to digidem/comapeo-content-pipeline#6 (merged), which fixes the content-side cause of untranslated ES/PT sidebar headers (a new canonical hierarchy engine in the content pipeline). This PR fixes the theme-side half: Docusaurus UI chrome — navbar, footer, and generic
code.jsonstrings (breadcrumbs, search, pagination, section/category labels sourced from Notion Title/Toggle rows) — were either plain English or, for pipeline-sourced labels, carried Docusaurus's own genericwrite-translationsplaceholders ("Nueva Página", "Nuevo título de sección", "Nueva Palanca") instead of real translated content.i18n/{es,pt}/docusaurus-theme-classic/{navbar,footer}.jsonwith translated navbar item labels (includinglogo.alt) and footer section/link labels, in both Docusaurus's actual runtime key format (link.title.*,link.item.label.*) and a legacy format kept for backward compatibility.i18n/{es,pt}/code.jsonwith real ES/PT translations, covering all ~90 active Docusaurus base theme keys (theme-common, plugin-pwa, plugin-ideal-image) plus Notion-sourced content labels.scripts/verify-locale-output.test.ts's untranslated-string detection to dynamically compare every locale message against Docusaurus's own shipped English catalogs (instead of a small hand-picked sample), fail-closed on both missing keys and catalog load errors.test.yml,deploy-pr-preview.yml,deploy-production.yml,deploy-staging.yml): each checks outi18n/from thecontentbranch (or a locked content SHA) before running tests or building, which would otherwise silently discard this repo's hand-maintained theme translation files. All four now restorei18n/*/code.jsonandi18n/*/docusaurus-theme-classic/*from the ref actually being built/tested immediately after that overlay. PerAGENTS.md's "do not modify CI without approval" — flagging this explicitly for maintainer sign-off..gitignore(previously the whole/i18n/tree was fully generated/ignored) and fixes thetranslate-docs.ymlcontent-syncsedstep that adjusts it, while leaving the rest ofi18n/— regenerated per-sync from the content pipeline — untouched.Since both PRs are now merged, sidebar category structure/content (pipeline PR) and the surrounding theme chrome (this PR) should both be covered once
contentre-syncs against the merged pipeline output.Review process
Went through 3 rounds of an automated PR-readiness loop (Codex, gpt-5.6-sol) plus an independent second-opinion pass (Fable 5). Real issues found and fixed along the way:
logo.altextraction was missing from the generator — would've been silently dropped on next regeneration.main(post-merge) and was entirely absent from the production/staging deploy workflows, which would have let this PR's actual payload (translatedcode.json) never reach real users. Fixed with a simpler, unconditional restore that doesn't depend on diffing.Known non-blocking limitations
i18n/{es,pt}/docusaurus-theme-classic/footer.json's copyright message is a static snapshot ("... - 2026") — Docusaurus's English default uses a live${new Date().getFullYear()}, but translated locale JSON catalogs can't hold a JS expression, so these will need a manual bump (or theme-chrome regeneration) at the next year boundary.verify-locale-output.test.ts's es/pt key-count parity check still tolerates ~10% drift — loose enough that a handful of Notion-content keys could differ between locales without failing. Left as-is since tightening it risks false positives unrelated to this PR's scope.Test plan
scripts/locale-parity.test.tsandscripts/verify-locale-output.test.ts— 35/35 passing, asserting no placeholder/empty/untranslated messages, full Docusaurus base-catalog coverage (fail-closed on missing keys), matching key counts across locales, and expected theme-translation keysbun run test) — 2113/2113 passing (96 files, 8 skipped live/integration tests requiring real API credentials)eslint+tsc --noEmiton all changed files — zero errors, zero warningsmain-push CI failure mode and confirmed the fix resolves it (content overlay → 65 keys, restore → full 159-key set, cleangit statusagainst HEAD)f8c87b1); PR isMERGEABLEwithmergeStateStatus: CLEANGreptile Summary
This PR adds translated Docusaurus UI chrome for Spanish and Portuguese. The main changes are:
code.jsoncatalogs now replace placeholder and English theme messages..gitignoreand content-sync handling now keep generated i18n ignored while allowing tracked theme JSON files.Confidence Score: 5/5
Safe to merge with low risk.
Changed workflows, locale catalogs, and tests align with the repo's generated-content and i18n flow. Prior issues around ignored generated files and destructive
code.jsonrestores are addressed. No accepted issues remain.No files require special attention.
What T-Rex did
Important Files Changed
code.json, and scopes the content-lock commit to avoid committing overlaid locale state.code.jsonbefore running locale-dependent tests..gitignoreadjustment to remove the new i18n allowlist block before committing generated translations.logo.alt.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant BuildRef as PR/main ref participant Content as content branch or locked SHA participant Workflow as CI/deploy workflow participant I18n as i18n/** working tree participant Tests as Locale tests / Docusaurus build Workflow->>Content: checkout docs/, i18n/, static/images/ Content-->>I18n: generated localized docs + content code.json keys Workflow->>BuildRef: "restore i18n/*/docusaurus-theme-classic/*" BuildRef-->>I18n: repo-owned navbar/footer catalogs Workflow->>BuildRef: "read HEAD i18n/*/code.json" Workflow->>I18n: validate JSON objects and merge content + HEAD code.json I18n->>Tests: translated theme chrome + content labels Tests-->>Workflow: locale verification/build result%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant BuildRef as PR/main ref participant Content as content branch or locked SHA participant Workflow as CI/deploy workflow participant I18n as i18n/** working tree participant Tests as Locale tests / Docusaurus build Workflow->>Content: checkout docs/, i18n/, static/images/ Content-->>I18n: generated localized docs + content code.json keys Workflow->>BuildRef: "restore i18n/*/docusaurus-theme-classic/*" BuildRef-->>I18n: repo-owned navbar/footer catalogs Workflow->>BuildRef: "read HEAD i18n/*/code.json" Workflow->>I18n: validate JSON objects and merge content + HEAD code.json I18n->>Tests: translated theme chrome + content labels Tests-->>Workflow: locale verification/build resultComments Outside Diff (5)
scripts/verify-locale-output.test.ts, line 294-298 (link)With the current 156 keys, this threshold allows up to 16 keys to exist in only one locale. If generation omits several Portuguese labels while Spanish succeeds, the test passes and Portuguese pages fall back to English for those labels.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
General comment
/es/docs/introduction, the breadcrumb rendersOverview / Introducciónand the sidebar renders English categories includingOverview,Customizing CoMapeo,Gathering Observations & Tracks,Managing Projects, andTroubleshooting. The Portuguese browser assertion also failed becauseVisão Geralwas not rendered. This contradicts the stated localized breadcrumb/sidebar behavior while navbar and footer localization works.code.jsonentries. The changedcode.jsonmessages exist but are not applied to these rendered category labels._category_.jsonfromi18n/<locale>/docusaurus-plugin-content-docs/current, or generate/use translated category metadata in the sidebar. Add a browser assertion that the ES breadcrumb/sidebar includesVista Generaland the PT equivalent includesVisão Geral, while representative English category labels are absent.General comment
/es/docs/introductionand/pt/docs/introductionpages, the visible breadcrumb begins withOverviewand the desktop sidebar categories remain English, includingOverview,Customizing CoMapeo,Gathering Observations & Tracks,Reviewing Observations, and other categories. This contradicts the stated live-browser result that breadcrumb and sidebar categories are translated. Navbar, footer, and generic breadcrumb ARIA text are localized correctly._category_.jsonfiles exist, their directory/category structure differs from the categories actually rendered by the current documentation corpus, so Docusaurus continues using English category labels._category_.jsonin each locale, or generate localized category metadata against the exact currentdocs/hierarchy. Add browser/output assertions for the actual rendered category routes—not only catalog key parity—and verifyOverviewplus at least one additional category is localized in both ES and PT.General comment
/pt/docs/introductionpage, the visible breadcrumb isOverview > Introdução, and most sidebar categories remain English. This produces mixed-language documentation chrome despite the Portuguese strings added toi18n/pt/code.json. Navbar, footer, and generic ARIA labels localize correctly.i18n/pt/code.jsonare present in Docusaurus code translations but are not applied to metadata-derived labels in the autogenerated docs sidebar. Runtime sidebar data continues to use category labels from the default English docs metadata._category_.jsonlabels are consumed. Re-run the PT page and assert that both breadcrumb text and all visible category labels are Portuguese.General comment
Overview Introducciónand the sidebar includesOverview,Customizing CoMapeo,Gathering Observations & Tracks, and other English labels. The PT page likewise rendersOverview Introduçãoand the same English sidebar categories. This contradicts the stated localization objective even though navbar/footer localization works.i18n/es/code.jsonandi18n/pt/code.jsonare not the catalog entries Docusaurus consumes for autogenerated documentation category metadata. The locale tree lacks the appropriatedocusaurus-plugin-content-docs/current.jsontranslation catalog, so Docusaurus falls back to category metadata from the English source docs. The updated tests only inspect catalog contents and non-empty_category_.jsonlabels; they do not prove those values are used at runtime.i18n/<locale>/docusaurus-plugin-content-docs/current.jsonfromdocusaurus write-translations—and translate its category label keys. Add browser-level or generated-output assertions that the rendered sidebar and breadcrumb containVista General/Visão Geraland other localized categories rather than English source labels.Reviews (9): Last reviewed commit: "fix(ci): reject malformed code.json inst..." | Re-trigger Greptile