Skip to content

Translate Docusaurus theme chrome (navbar, footer, code.json) for ES/PT#189

Merged
luandro merged 15 commits into
mainfrom
fix/translation-headers-theme
Jul 19, 2026
Merged

Translate Docusaurus theme chrome (navbar, footer, code.json) for ES/PT#189
luandro merged 15 commits into
mainfrom
fix/translation-headers-theme

Conversation

@luandro

@luandro luandro commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

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.json strings (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 generic write-translations placeholders ("Nueva Página", "Nuevo título de sección", "Nueva Palanca") instead of real translated content.

  • Adds i18n/{es,pt}/docusaurus-theme-classic/{navbar,footer}.json with translated navbar item labels (including logo.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.
  • Replaces placeholder/English messages in i18n/{es,pt}/code.json with real ES/PT translations, covering all ~90 active Docusaurus base theme keys (theme-common, plugin-pwa, plugin-ideal-image) plus Notion-sourced content labels.
  • Rewrites 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.
  • Changes 4 CI/deploy workflows (test.yml, deploy-pr-preview.yml, deploy-production.yml, deploy-staging.yml): each checks out i18n/ from the content branch (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 restore i18n/*/code.json and i18n/*/docusaurus-theme-classic/* from the ref actually being built/tested immediately after that overlay. Per AGENTS.md's "do not modify CI without approval" — flagging this explicitly for maintainer sign-off.
  • Selectively un-ignores the new theme-classic files in .gitignore (previously the whole /i18n/ tree was fully generated/ignored) and fixes the translate-docs.yml content-sync sed step that adjusts it, while leaving the rest of i18n/ — 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 content re-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:

  • Navbar logo.alt extraction was missing from the generator — would've been silently dropped on next regeneration.
  • The 4 workflow files above needed the i18n-restore fix — CI was validating this PR's own locale files, but a regression check found the first version of that fix only worked inside PR check runs: it went silent on a bare push to main (post-merge) and was entirely absent from the production/staging deploy workflows, which would have let this PR's actual payload (translated code.json) never reach real users. Fixed with a simpler, unconditional restore that doesn't depend on diffing.
  • The untranslated-string check's coverage gap (9 keys → ~90) also turned out to be fail-open on first pass (didn't catch keys missing entirely, only present-but-untranslated ones) — caught 3 real missing PWA translation keys as a result, now added using Docusaurus's own official ES/pt-BR strings.

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.ts and scripts/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 keys
  • Full project suite (bun run test) — 2113/2113 passing (96 files, 8 skipped live/integration tests requiring real API credentials)
  • eslint + tsc --noEmit on all changed files — zero errors, zero warnings
  • Manually simulated the post-merge main-push CI failure mode and confirmed the fix resolves it (content overlay → 65 keys, restore → full 159-key set, clean git status against HEAD)
  • Verified locally: rebuilt this branch against a fresh pipeline-generated corpus and confirmed in a live Docusaurus build/browser check that navbar, footer, breadcrumbs, and sidebar categories all render translated in ES and PT
  • All CI checks green on the final commit (f8c87b1); PR is MERGEABLE with mergeStateStatus: CLEAN

Greptile Summary

This PR adds translated Docusaurus UI chrome for Spanish and Portuguese. The main changes are:

  • ES/PT code.json catalogs now replace placeholder and English theme messages.
  • ES/PT navbar and footer theme catalogs are added for Docusaurus runtime keys.
  • CI and deploy workflows restore repo-owned theme translations after content-branch overlays.
  • Locale verification now checks Docusaurus base keys, untranslated defaults, placeholders, and expected theme files.
  • .gitignore and 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.json restores are addressed. No accepted issues remain.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Validated Spanish localization by inspecting the ES home page image, ES docs page image, and the ES Playwright video artifact.
  • Validated Portuguese localization by inspecting the PT home page image, PT docs page image, and the PT Playwright video artifact.
  • Reviewed the runtime artifacts, including dev-server logs for ES and PT and the Playwright outputs, to verify end-to-end test execution surfaced in the build.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
.github/workflows/deploy-production.yml Restores theme locale files after locked content checkout, merges code.json, and scopes the content-lock commit to avoid committing overlaid locale state.
.github/workflows/test.yml CI tests now overlay content i18n, restore tracked theme catalogs, and merge code.json before running locale-dependent tests.
.github/workflows/translate-docs.yml Updates content-branch .gitignore adjustment to remove the new i18n allowlist block before committing generated translations.
.gitignore Keeps generated docs/images ignored while selectively unignoring ES/PT theme-classic navbar and footer translation JSON.
i18n/es/code.json Replaces Spanish placeholder strings and adds Docusaurus base theme/PWA/ideal-image translation keys.
i18n/es/docusaurus-theme-classic/footer.json Adds Spanish footer translation catalog in legacy and Docusaurus runtime key formats.
i18n/pt/code.json Replaces Portuguese placeholder strings and adds Docusaurus base theme/PWA/ideal-image translation keys.
i18n/pt/docusaurus-theme-classic/footer.json Adds Portuguese footer translation catalog in legacy and Docusaurus runtime key formats.
scripts/notion-translate/translateCodeJson.ts Extends translation extraction to include Docusaurus runtime navbar/footer keys, including logo.alt.
scripts/verify-locale-output.test.ts Broadens locale verification to catch placeholders, missing Docusaurus base keys, untranslated defaults, and expected theme catalog keys.

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
Loading
%%{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 result
Loading

Comments Outside Diff (5)

  1. scripts/verify-locale-output.test.ts, line 294-298 (link)

    P2 Locale Key Drift Passes Verification

    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
    This is a comment left during a code review.
    Path: scripts/verify-locale-output.test.ts
    Line: 294-298
    
    Comment:
    **Locale Key Drift Passes Verification**
    
    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](https://app.greptile.com/awana-digital/github/digidem/comapeo-docs/-/custom-context?memory=ca3c2441-375f-4d23-944a-2910a76252b7))
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. General comment

    P1 Localized documentation pages still render English sidebar and breadcrumb category labels

    • Bug
      • On the real changed Spanish page /es/docs/introduction, the breadcrumb renders Overview / Introducción and the sidebar renders English categories including Overview, Customizing CoMapeo, Gathering Observations & Tracks, Managing Projects, and Troubleshooting. The Portuguese browser assertion also failed because Visão Geral was not rendered. This contradicts the stated localized breadcrumb/sidebar behavior while navbar and footer localization works.
    • Cause
      • The visible autogenerated category labels are sourced from the English docs category metadata rather than the translated locale category metadata or translated code.json entries. The changed code.json messages exist but are not applied to these rendered category labels.
    • Fix
      • Ensure localized docs resolve _category_.json from i18n/<locale>/docusaurus-plugin-content-docs/current, or generate/use translated category metadata in the sidebar. Add a browser assertion that the ES breadcrumb/sidebar includes Vista General and the PT equivalent includes Visão Geral, while representative English category labels are absent.

    T-Rex Ran code and verified through T-Rex

  3. General comment

    P1 Localized documentation pages still render English breadcrumb and sidebar category labels

    • Bug
      • On the real /es/docs/introduction and /pt/docs/introduction pages, the visible breadcrumb begins with Overview and the desktop sidebar categories remain English, including Overview, 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.
    • Cause
      • The translated catalogs do not match or override the category metadata used by the active autogenerated sidebar. Although localized _category_.json files exist, their directory/category structure differs from the categories actually rendered by the current documentation corpus, so Docusaurus continues using English category labels.
    • Fix
      • Ensure every active source-doc category has a path-matching localized _category_.json in each locale, or generate localized category metadata against the exact current docs/ hierarchy. Add browser/output assertions for the actual rendered category routes—not only catalog key parity—and verify Overview plus at least one additional category is localized in both ES and PT.

    T-Rex Ran code and verified through T-Rex

  4. General comment

    P1 Portuguese documentation sidebar and breadcrumb still render English category labels

    • Bug
      • On the real /pt/docs/introduction page, the visible breadcrumb is Overview > Introdução, and most sidebar categories remain English. This produces mixed-language documentation chrome despite the Portuguese strings added to i18n/pt/code.json. Navbar, footer, and generic ARIA labels localize correctly.
    • Cause
      • The category translations added as arbitrary entries in i18n/pt/code.json are 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.
    • Fix
      • Provide the category-label translations through the Docusaurus docs plugin’s generated translation mechanism used by autogenerated sidebars, or otherwise configure sidebar/category metadata so the localized _category_.json labels are consumed. Re-run the PT page and assert that both breadcrumb text and all visible category labels are Portuguese.

    T-Rex Ran code and verified through T-Rex

  5. General comment

    P1 Localized documentation sidebar and breadcrumbs still render English category labels

    • Bug
      • On the real ES page, the rendered breadcrumb is Overview Introducción and the sidebar includes Overview, Customizing CoMapeo, Gathering Observations & Tracks, and other English labels. The PT page likewise renders Overview Introdução and the same English sidebar categories. This contradicts the stated localization objective even though navbar/footer localization works.
    • Cause
      • The translated category strings added to i18n/es/code.json and i18n/pt/code.json are not the catalog entries Docusaurus consumes for autogenerated documentation category metadata. The locale tree lacks the appropriate docusaurus-plugin-content-docs/current.json translation catalog, so Docusaurus falls back to category metadata from the English source docs. The updated tests only inspect catalog contents and non-empty _category_.json labels; they do not prove those values are used at runtime.
    • Fix
      • Generate and commit the Docusaurus content-docs translation catalog for each locale—normally i18n/<locale>/docusaurus-plugin-content-docs/current.json from docusaurus write-translations—and translate its category label keys. Add browser-level or generated-output assertions that the rendered sidebar and breadcrumb contain Vista General/Visão Geral and other localized categories rather than English source labels.

    T-Rex Ran code and verified through T-Rex

Reviews (9): Last reviewed commit: "fix(ci): reject malformed code.json inst..." | Re-trigger Greptile

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.
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Translate Docusaurus theme chrome for ES/PT (navbar, footer, code.json)

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Replace ES/PT placeholder + English UI strings with real Docusaurus theme translations.
• Add localized navbar/footer label files for Docusaurus theme-classic in ES and PT.
• Strengthen locale-output tests to block placeholder, empty, or missing theme translation keys.
Diagram

graph TD
  A["Docusaurus UI"] --> B["i18n/es/code.json"] --> E["Rendered ES chrome"]
  A --> C["i18n/pt/code.json"] --> F["Rendered PT chrome"]
  A --> D["i18n/*/docusaurus-theme-classic/{navbar,footer}.json"]
  G["scripts/verify-locale-output.test.ts"] --> B --> H["CI locale checks"]
  G --> C
  G --> D
  I["scripts/locale-parity.test.ts"] --> D --> H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Commit full `i18n/` output from `write-translations` / pipeline
  • ➕ Simplifies gitignore rules (everything i18n is checked in)
  • ➕ Fewer special cases about which i18n files are source-of-truth
  • ➖ Likely noisy diffs on every sync/build due to regenerated content
  • ➖ Harder to review meaningful translation changes vs churn
2. Move theme chrome strings into the content pipeline output only
  • ➕ Single generator/source of truth for all locale strings
  • ➕ May reduce manual translation maintenance in this repo
  • ➖ Tighter coupling between site theme and pipeline internals
  • ➖ Pipeline would need to understand Docusaurus theme translation contracts/keys
3. Automate theme translation verification only (no committed theme JSON)
  • ➕ Avoids storing theme translation JSON; rely on generated artifacts
  • ➕ Still prevents placeholders via CI assertions
  • ➖ Runtime would still need those files available at build time from somewhere
  • ➖ Harder for contributors to make targeted, reviewable translation fixes

Recommendation: Keep the current approach: commit only the stable Docusaurus theme-classic navbar/footer translation files and update code.json with real ES/PT messages, while relying on tests to prevent placeholder regressions. This minimizes generated-content churn while ensuring the most visible UI chrome is correct and reviewable.

Files changed (8) +1470 / -475

Enhancement (4) +240 / -0
footer.jsonAdd Spanish footer section/link translations for theme-classic +106/-0

Add Spanish footer section/link translations for theme-classic

• Introduces Spanish footer translations for section titles and common links (Website, Discord, GitHub, etc.). Includes translated copyright string.

i18n/es/docusaurus-theme-classic/footer.json

navbar.jsonAdd Spanish navbar item translations for theme-classic +14/-0

Add Spanish navbar item translations for theme-classic

• Introduces Spanish navbar translations for key items (Documentation, GitHub) and the logo alt text.

i18n/es/docusaurus-theme-classic/navbar.json

footer.jsonAdd Portuguese footer section/link translations for theme-classic +106/-0

Add Portuguese footer section/link translations for theme-classic

• Introduces Portuguese footer translations for section titles and common links (Site, Discord, GitHub, etc.). Includes translated copyright string.

i18n/pt/docusaurus-theme-classic/footer.json

navbar.jsonAdd Portuguese navbar item translations for theme-classic +14/-0

Add Portuguese navbar item translations for theme-classic

• Introduces Portuguese navbar translations for key items (Documentação, GitHub) and the logo alt text.

i18n/pt/docusaurus-theme-classic/navbar.json

Bug fix (2) +831 / -106
code.jsonReplace ES placeholder/English UI strings with real theme translations +414/-52

Replace ES placeholder/English UI strings with real theme translations

• Replaces Docusaurus placeholder strings (e.g., "Nueva Página", section placeholders) with real Spanish labels. Adds a large set of Docusaurus theme UI keys (breadcrumbs, paginator, TOC, code block copy, a11y labels, etc.) with Spanish messages/descriptions.

i18n/es/code.json

code.jsonReplace PT placeholder/English UI strings with real theme translations +417/-54

Replace PT placeholder/English UI strings with real theme translations

• Replaces Docusaurus placeholder strings (e.g., "Nova Página") and improves Portuguese labels. Adds a large set of Docusaurus theme UI keys (breadcrumbs, paginator, TOC, code block copy, a11y labels, etc.) with Portuguese messages/descriptions.

i18n/pt/code.json

Tests (2) +399 / -369
locale-parity.test.tsAdd real-project parity checks for empty translations and category labels +97/-0

Add real-project parity checks for empty translations and category labels

• Adds a new test suite that runs parity validation against the real repo output when available. Explicitly checks for empty translations, optional frontmatter mismatch validation, and non-empty '_category_.json' labels for ES/PT.

scripts/locale-parity.test.ts

verify-locale-output.test.tsRefactor locale verification and enforce no placeholder/untranslated theme strings +302/-369

Refactor locale verification and enforce no placeholder/untranslated theme strings

• Refactors test utilities (JSON readers, file existence assertions, expected key checks) and replaces heuristic English-detection with explicit placeholder regex detection plus "message equals key" untranslated checks. Adds expected-key enforcement for theme navbar/footer and validates that common UI labels are not left as English; includes justified security linter disables for hardcoded key access.

scripts/verify-locale-output.test.ts

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Your documentation preview is ready!

Preview URL: https://pr-189.comapeo-docs.pages.dev

📦 Content: From content branch (no script changes)

💡 Tip: Add a label to force regeneration if you want to test with fresh Notion data.

This preview will update automatically when you push new commits to this PR.


Built with commit a9118af

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread i18n/es/docusaurus-theme-classic/footer.json
@qodo-code-review

qodo-code-review Bot commented Jul 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 13 rules

Grey Divider


Remediation recommended

1. Empty code.json not flagged ✓ Resolved 🐞 Bug ☼ Reliability
Description
scripts/verify-locale-output.test.ts skips placeholder/untranslated checks when entry.message is an
empty string, and the structure assertions only validate that message is a string (not non-empty).
As a result, empty/blank translations in i18n/{es,pt}/code.json can pass the verification suite and
ship as missing UI text.
Code

scripts/verify-locale-output.test.ts[R72-105]

+const assertNoPlaceholderMessages = (
+  translations: TranslationCodeJson,
+  patterns: RegExp[],
+  fileLabel: string
+): void => {
+  const violations: string[] = [];
+  for (const [key, entry] of Object.entries(translations)) {
+    if (!entry.message) continue;
+    for (const pattern of patterns) {
+      if (pattern.test(entry.message.trim())) {
+        violations.push(`  "${key}": "${entry.message}"`);
+        break;
+      }
+    }
+  }
+  if (violations.length > 0) {
+    throw new Error(
+      `${fileLabel}: found ${violations.length} placeholder message(s):\n${violations.join("\n")}`
+    );
+  }
+};
+
+const assertNoUntranslatedMessages = (
+  translations: TranslationCodeJson,
+  fileLabel: string
+): void => {
+  const violations: string[] = [];
+  for (const [key, entry] of Object.entries(translations)) {
+    if (!entry.message) continue;
+    if (entry.message === key) {
+      violations.push(`  "${key}": message identical to key (untranslated)`);
+    }
+  }
+  if (violations.length > 0) {
Relevance

⭐⭐⭐ High

Repo previously tightened locale-output tests to prevent blank UI labels (PR #160); similar
reliability hardening is accepted.

PR-#160
PR-#144

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Empty-string messages are skipped by the placeholder/untranslated checks, and the structure tests
don’t assert non-empty content; additionally, the parity harness only scans markdown docs, not
code.json. This combination leaves a gap where blank message values in i18n/*/code.json are not
rejected by the updated test suite.

scripts/verify-locale-output.test.ts[72-110]
scripts/verify-locale-output.test.ts[235-246]
scripts/verify-locale-output.test.ts[263-274]
scripts/locale-parity.test.ts[38-61]
scripts/locale-parity.test.ts[69-104]
scripts/notion-fetch/translationManager.ts[84-95]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`verify-locale-output.test.ts` currently does not fail when a `code.json` entry has an empty-string (or whitespace-only) `message`. Both `assertNoPlaceholderMessages` and `assertNoUntranslatedMessages` skip falsy messages (`""`), and the “valid structure” tests only assert the type is `string`.

### Issue Context
The new `locale-parity.test.ts` harness only walks docs roots and checks `.md/.mdx` content, so it won’t catch empty messages inside `i18n/*/code.json`.

### Fix Focus Areas
- scripts/verify-locale-output.test.ts[72-110]
- scripts/verify-locale-output.test.ts[221-274]

### Suggested fix
1. Add a helper like `assertNoEmptyMessages(translations, fileLabel)` that fails when `typeof entry.message !== "string"` OR `entry.message.trim().length === 0`.
2. Call it for both `es/code.json` and `pt/code.json` in the “no placeholder or untranslated messages” tests.
3. Optionally remove the `if (!entry.message) continue;` skips in the placeholder/untranslated helpers (or change them to fail loudly) so empty strings can’t silently bypass validation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread scripts/verify-locale-output.test.ts
Comment thread .gitignore
Comment thread scripts/verify-locale-output.test.ts
luandro added 7 commits July 16, 2026 19:44
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.
luandro added a commit to digidem/comapeo-content-pipeline that referenced this pull request Jul 17, 2026
…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.
Comment thread .github/workflows/deploy-production.yml Outdated
luandro added 4 commits July 18, 2026 11:24
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.
Comment thread .github/workflows/deploy-production.yml Outdated
…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.
@luandro
luandro merged commit c7f9d26 into main Jul 19, 2026
4 checks passed
@luandro
luandro deleted the fix/translation-headers-theme branch July 19, 2026 13:22
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Deployment Cleanup

The preview deployment for this PR has been cleaned up.

Preview URL was: https://pr-189.comapeo-docs.pages.dev


Note: Cloudflare Pages deployments follow automatic retention policies. Old previews are cleaned up automatically.

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.

1 participant