Skip to content

Add Japanese localization pipeline: NT-tag do-not-translate mechanism, translation scripts, and ja locale wiring - #140

Merged
matenadasdi merged 8 commits into
mainfrom
ja-localization-full
Aug 19, 2026
Merged

Add Japanese localization pipeline: NT-tag do-not-translate mechanism, translation scripts, and ja locale wiring#140
matenadasdi merged 8 commits into
mainfrom
ja-localization-full

Conversation

@zoltan-baba

@zoltan-baba zoltan-baba commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Japanese localization pipeline: the scripts that generate the term glossary, tag protected terms in the docs source, and translate changed pages — plus the ja locale content and the locale switcher.

The infrastructure prep that used to live here has been split out and merged (#188, #189), and main is merged back in, so this branch is now purely additive: 19 files, +4,892 / −0, and up to date with main.

Pipeline

  • .github/scripts/build_ui_library.py — scans the product frontends + steplib + docs, emits ui_copy_library.json and the tiered do-not-translate glossary.
  • scripts/add_notranslate_tags.py — reads that glossary and wraps protected terms in <NT> directly in the .mdx source. Idempotent, and inserts its own import.
  • .github/scripts/translate_docs.py — mask → translate → unmask. Masks whole <NT> spans, code, URLs, env vars, filenames and MDX syntax before the model sees the text. JTF house-style system prompt, plus a preferred-translations map for terms that are translated. Front matter is split off and never sent, so slug can't be touched. Auto-generated OpenAPI pages are excluded — they'd go stale on the next spec sync.
  • Workflows: translate-ja-docs.yml (translate changed pages on docs PRs), refresh-ui-library.yml (weekly glossary regeneration + re-tag), ja-style-check.yml + .textlintrc.yaml (JTF textlint).

Locale content and switcher

  • i18n/ja/ — theme strings (code.json, translated), sidebar labels (current.json, scaffolded but still English), and one fully translated pilot page.
  • Navbar locale dropdown (docusaurus.config.ts) plus the navbar text colour it needs to be readable on the purple header (src/css/custom.css).

Docs

  • localization/README.md and JA-TRANSLATION-GUIDE.md — how the pipeline fits together, with a LIVE/PROCESS/ROADMAP status legend.

Open issues — read before merging

All re-verified against the merged tree.

No <NT> tags are applied yet. The tagger has never been run against the tree: zero files under docs/ or src/partials/ contain a tag, while a dry run reports 17,140 wraps across 499 files. Until that pass lands, translate_docs.py has nothing to mask beyond code, URLs and ALL-CAPS runs — measured on one real page, 99 occurrences of unambiguous protected terms (Bitrise ×18, Workspace ×13, Workflow ×6, Release Management ×5, …) are fully exposed to the model. The glossary is not a fallback: it is the tagger's term source and is never read at translation time.

translate-ja-docs.yml runs on every docs PR and auto-commits, so merging before the tagging pass turns on unprotected translation immediately. Deciding where that pass lands is the one thing blocking this PR from being safe to merge.

translate_docs.py truncates silently. max_tokens=8000 with no stop_reason check (line 179). Seven pages exceed 20KB, the largest is 44KB. A truncated response is written to disk and committed by the workflow with no signal. No retry or error handling either.

ja-style-check.yml is a no-op — and its green check here is misleading. Verified: textlint exits 0 with no output on .mdx, silently skipping unsupported extensions, and every page is .mdx now. The same content as .md reports 2 errors, both Docusaurus :::note[title] syntax tripping JTF rule 4.3.2, so that rule needs disabling before MDX support is worth adding. It also has no continue-on-error, so despite being described as non-blocking it becomes a hard gate the moment it starts working.

The weekly refresh will drop glossary tiers. build_ui_library.py emits step_names:, but add_notranslate_tags.py reads step_names_and_field_labels — and the generator emits no code_literals at all, which the tagger also reads. refresh-ui-library.yml regenerates the glossary then re-runs the tagger and opens a PR, so the first scheduled run silently drops all 156 Step names and produces a mass-untagging PR that looks like a routine chore.

Stale comments in the glossary. Line 13 still says do_not_translate terms "are injected into the translation prompt as a hard rule", describing the design this PR replaced — reading the file top-down gives the wrong model of how protection works. The preferred_translations: {} stub at the bottom is also dead; ja-preferred-translations.yaml supersedes it.

Known gaps (by design, documented in JA-TRANSLATION-GUIDE.md)

  • No translation memory / segment reuse yet — roadmap.
  • Partials (src/partials/) aren't translated. They're inlined via JSX import, so a translated page still renders an English partial.
  • Front matter isn't translated: title and description stay English, so JA pages show English titles in nav and English social preview cards.
  • The JA sidebar tree is untranslated — current.json is scaffolded but still English, and nothing in the pipeline translates it.
  • ja-preferred-translations.yaml seed values need a fluent Japanese reviewer before being treated as authoritative.
  • static/_headers still noindexes /ja/*. Correct while one page of ~440 is translated, but main's commit 3444eb4 asks for the rule to be revisited here — worth an explicit decision and a drop criterion.

Test plan

  • Full production build (npm run build) succeeds for both en and ja.
  • One real page (bitrise-ci/getting-started/getting-started) translated through the full mask/translate/unmask pipeline and verified rendering in a served production build.
  • Locale switching verified in a served production build (dev mode serves one locale at a time).
  • main merged in; check-links-source, check-partials-source and check-generated-partials all clean on the merged tree.
  • Decide whether the 17k-tag pass lands here, as its own PR, or via the first weekly refresh run.
  • Fix the glossary tier-name mismatch before the first refresh-ui-library.yml run.

🤖 Generated with Claude Code

@zoltan-baba
zoltan-baba marked this pull request as draft July 23, 2026 15:29
@github-actions

Copy link
Copy Markdown

zoltan-baba added a commit that referenced this pull request Jul 23, 2026
- translate_docs.py: deterministically rewrite **<NT>...</NT>** to
  <strong><NT>...</NT></strong> after unmasking. Markdown ** emphasis
  depends on whitespace/punctuation next to the delimiter to tell one **
  pair from another; Japanese chains bold <NT> terms back-to-back with
  only a particle between them, which reliably breaks that disambiguation
  (verified against a real build: bold attached to the wrong span, a
  literal ** leaked into the page). <strong> sidesteps delimiter matching
  entirely. Ordinary bold with no <NT> inside is left untouched.
- check-links-source.js: strip JSX/MDX tags before slugifying heading
  text. The NT-tagging pass wrapped terms inside some headings (e.g.
  "The <NT>Dashboards</NT> page"); the checker's slugify() had no rule
  for JSX, so the tag name survived as literal text and produced a
  mismatched slug for every such heading — 182 false "broken anchor"
  failures on PR #140's check-links job. The real Docusaurus build was
  never affected (its slugger already strips JSX); this only fixes the
  separate lightweight source-level checker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zoltan-baba and others added 7 commits August 18, 2026 14:49
…n/-prefix infra

Rebuilds ja-localization-full's tooling on top of main's now-merged
strip-en-prefix-urls PR, instead of merging the two histories directly:
that PR and this branch's NT-tagging commit both touched almost every
file under docs/ and src/partials/ for unrelated reasons (link-prefix
stripping vs. do-not-translate tagging), so a real merge produced 280+
conflicts. Content-bearing commits are cherry-picked as targeted patches
onto main's versions instead; superseded fixes (this branch's own navbar
/en/ regex, GlossTerm path, DocSidebar/DocBreadcrumbs hrefs) are dropped
in favor of main's better versions of the same fix.

Brings over:
- ja translation pipeline: .github/scripts/{build_ui_library,translate_docs}.py,
  the ja-style-check/refresh-ui-library/translate-ja-docs workflows,
  .textlintrc.yaml, localization/ (glossary, preferred translations,
  translation guide, README).
- Locale-switch UI: navbar localeDropdown item in docusaurus.config.ts,
  white-text CSS for it against the purple header (it would otherwise
  render dark-on-dark), i18n/ja/'s UI-string scaffold.
- scripts/patch-api-info.js: disambiguates duplicate OpenAPI sidebar
  labels, which broke `docusaurus write-translations` outright once a
  second locale existed.
- scripts/check-links-source.js: strips JSX/MDX tags before slugifying
  heading text, so a heading like "The <NT>Dashboards</NT> page" slugifies
  the same as Docusaurus's real build-time slugger.
- src/components/NT + 'NT' added to docusaurus.config.ts's tag-escaping
  preprocessor allowlist: the do-not-translate marker component itself,
  not yet applied to any doc content (that's a separate change).
- scripts/add_notranslate_tags.py, the term-wrapping script this pipeline
  runs — included here with a fix for a bug never caught before because it
  was never rebased onto other real content changes: it wrapped terms
  inside a heading's explicit anchor ID too (`{#workspace}` ->
  `{#<NT>workspace</NT>}`), producing an invalid anchor. Fixed by excluding
  `{#...}` spans the same way frontmatter/code/JSX attributes already are.
  Still not run against docs content — that's the next commit.
- .claude/launch.json: a production-build-all-locales entry, since dev
  mode only ever serves one locale at a time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bulk, mechanical rename of the 83 remaining plain-Markdown doc pages to
.mdx — no content changes. Needed so scripts/add_notranslate_tags.py
(reintroduced in the previous commit but not yet run) can wrap terms in
these pages too: the <NT> marker it inserts is a JSX component, which
.md files can't carry. Frontmatter `slug` values are untouched, so no
URLs change and no redirects.json entries are needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Plain Markdown tolerates an unclosed <br>, but MDX parses it as JSX,
which requires a matching closing tag or a self-closing slash. Renaming
these two pages to .mdx in the previous commit surfaced the mismatch
("Unexpected closing tag </details>, expected corresponding closing tag
for <br>") and failed the build. Verified: a full build of both en and
ja locales now succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…generated docs

scripts/patch-api-info.js gained duplicate-sidebar-label disambiguation
in an earlier commit, but that only takes effect by actually running the
script — it doesn't retroactively touch the already-generated API
reference files committed to the repo. Without this, building the ja
locale fails outright: "Download files" duplicated across
codespaces-service-session-download and
codespaces-service-session-download-file broke
`docusaurus write-translations`'s sidebar translation keys. Ran
`node scripts/patch-api-info.js` and committed the result; the next
`npm run gen-api-docs` will reproduce the same output automatically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This translated page predates main's strip-en-prefix-urls merge, so its
internal links still carried the old hardcoded /en/ prefix. Under the
new per-locale baseUrl, that resolved to broken /ja/en/... URLs — the
same class of bug that PR fixed everywhere else. Fixed with the same
tool: `python3 scripts/strip_en_prefix.py` (i18n/ is already in its
scope). Verified: building the ja locale no longer logs any /ja/en/
link warnings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Left over from before the src_root/dest_root path-mapping logic it's a
fallback for existed. Every real call passes a docs/-relative path
(from git diff output, --src-root defaults to "docs"), so this branch
was unreachable and, if it ever were reached, would guess wrong under
the current per-locale routing anyway (paths here are file paths, not
URLs, but the string was still a leftover assumption worth not carrying
forward). Raises loudly instead of guessing, matching this repo's
convention elsewhere (see promote-root-static-files.js) of failing hard
rather than drifting silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ran `docusaurus write-translations --locale ja` to sync
current.json with main's sidebar changes since this scaffold was last
generated: two new RDE API sidebar entries (git provider integration
get/update) and a stale /en/-prefixed description string for the API
reference link, now bare. No other i18n JSON file changed — navbar,
footer, and code.json scaffolds are already in sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brings in the infrastructure that was split out of this PR and has since
merged — #188 (the <NT> component, the 83 .md -> .mdx renames, the
check-links-source.js anchor fix, the patch-api-info.js duplicate-label fix)
and #189 (the MCP sync /en/ fix) — plus #187's docusaurus-plugin-llms bump.

The overlapping work is identical on both sides, so it resolves without
conflict. What remains unique to this branch is the translation pipeline,
the localization glossary and guides, the i18n/ja content, and the locale
switcher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@matenadasdi
matenadasdi merged commit 5896fc8 into main Aug 19, 2026
3 checks passed
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.

2 participants