DF-826: Korean scope, preservation spec, glossary + parity validator - #206
Conversation
…idator The contract every translation engine is measured against, written to be machine-checkable rather than aspirational. Scope, measured: docs/ 243 files / 293,894 prose words, guides/ 52 / 58,606, reference/ 137 / 10,076 plus ~9,774 words in openapi/*.yaml. Wave 1 is docs + guides — 295 files, 352,500 words. The API reference is deferred, and not for cost: 105 of its 137 pages are thin MDX shells whose parameter tables render from the OpenAPI specs, so translating the MDX alone gives Korean prose around English tables. Two rules that came out of actually translating pages: - Heading anchors break silently. Anchors derive from English heading text, and this repo has 3,723 headings with no explicit anchors and 710 internal links carrying a #fragment. Translating a heading changes its anchor and those links start landing at the top of the page, with no error and nothing in the diff. Fixed by pinning `## 제목 [#english-anchor]`. - Code fences are verbatim, with one exception: comment lines may be translated when executable lines and line count are byte-identical. Glossary is 121 terms plus 60 do-not-translate, generated from the human markdown so the machine-readable copies cannot drift. Terminology policy turns on one fact — Mixpanel's UI is English-only, so feature names stay phonetically recognisable against the button the reader is looking at. validate-mdx-parity.mjs enforces the whole spec: JSX components, code fences, imports, image paths, src/href, link targets, heading anchors, frontmatter keys, and brace balance. Zero dependencies. It is also the scoring function for the DF-827 bake-off and the gate for the DF-831 pipeline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Confidence Score: 2/5The PR should not merge until the parity validator correctly handles localized JSX links and detects inline-code and standalone-expression mutations. The load-bearing gate currently rejects one required link transformation while allowing two classes of byte-verbatim content changes to pass. Files Needing Attention: i18n/scripts/validate-mdx-parity.mjs, i18n/02-scope-and-preservation-spec.md
|
| Filename | Overview |
|---|---|
| i18n/scripts/validate-mdx-parity.mjs | Adds the parity gate, but JSX links, inline code, and child expressions are not validated according to the documented contract. |
| i18n/scripts/build-glossary.mjs | Generates synchronized CSV and do-not-translate artifacts from the Markdown glossary. |
| i18n/02-scope-and-preservation-spec.md | Defines scope and preservation requirements, though JSX href preservation conflicts with the general internal-link rewrite rule. |
| i18n/glossary/ko.md | Defines Korean terminology, register, exclusions, and disambiguation guidance. |
Reviews (1): Last reviewed commit: "DF-826: Korean scope, preservation spec,..." | Re-trigger Greptile
| const srcDiff = bagDiff(s.srcAttrs, t.srcAttrs); | ||
| if (srcDiff.missing.length || srcDiff.added.length) | ||
| E(`src/href attribute(s) changed: ${[...srcDiff.missing, ...srcDiff.added.map((x) => `+${x}`)].join(", ")}`); | ||
|
|
||
| const expected = LINK_PREFIX && LOCALE | ||
| ? s.mdLinks.map((l) => (l.startsWith("/") && !l.startsWith(`/${LOCALE}/`) && !l.startsWith("/images/") ? `/${LOCALE}${l}` : l)) | ||
| : s.mdLinks; |
There was a problem hiding this comment.
JSX links bypass locale rewriting
When a wave-1 page contains an internal JSX link such as <Card href="/docs/...">, srcAttrs requires the URL to remain verbatim while --link-prefix rewrites only Markdown links. Rewriting the JSX link correctly fails validation, while leaving it unchanged passes and sends Korean readers to the English page.
Source Used: Linear — [M2] Define Korean translation scope, preservation spec, and glossary
| if (s.inlineCode.length !== t.inlineCode.length) | ||
| W(`inline code span count ${s.inlineCode.length} → ${t.inlineCode.length}`); |
There was a problem hiding this comment.
Inline code mutations pass validation
When translation changes an inline identifier such as distinct_id without changing the number of code spans, this check compares only the counts and emits no error. The parity gate therefore passes corrupted identifiers, URLs, or code fragments despite the byte-for-byte preservation contract.
Source Used: Linear — [M2] Define Korean translation scope, preservation spec, and glossary
| /* balanced MDX expression braces outside code */ | ||
| const braces = (x) => [...x].reduce((n, c) => n + (c === "{" ? 1 : c === "}" ? -1 : 0), 0); | ||
| if (braces(s.stripped) !== braces(t.stripped)) | ||
| E(`unbalanced MDX braces: source ${braces(s.stripped)}, target ${braces(t.stripped)}`); |
There was a problem hiding this comment.
Expression mutations pass brace checks
When translation changes a child expression such as {ssn} to another balanced expression, this check compares only aggregate brace counts. The page can reference the wrong variable or execute changed MDX while still passing the parity gate.
Source Used: Linear — [M2] Define Korean translation scope, preservation spec, and glossary
Documents in
i18n/cross-reference each other, so relative links to files added later in the stackresolve once the stack has landed. The final state is link-checked; every relative link resolves.
Operational sequencing — none of this happens on merge
Merging this stack changes nothing a reader sees.
docs.jsonis untouched, no workflow is installed, andno
ko/tree exists at the repo root. Turning Korean on is a separate, deliberate sequence:docs.jsonlanguagesarray;cp i18n/nav/ko.json ./ko.jsondocs/+guides/intoko/gt.config.jsonto root,translate.ymlto.github/workflows/, setGT_API_KEY+GT_PROJECT_ID/koSteps 3 and 6 are the only ones that change live behaviour. Both are reversible by reverting one file.
The contract every translation engine is measured against — written to be machine-checkable rather than aspirational.
Adds:
02-scope-and-preservation-spec.md,glossary/{ko.md,ko.csv,ko.dnt.txt},scripts/{validate-mdx-parity.mjs,build-glossary.mjs}Scope, measured
docs/guides/reference/MDXopenapi/*.yamlWave 1 = docs + guides — 295 files, 352,500 words. The API reference is deferred, and not for cost (it's only ~20k words): 105 of its 137 pages are thin MDX shells whose parameter tables render from the OpenAPI specs, so translating the MDX alone gives Korean prose wrapped around English tables — worse than leaving it English.
Two rules that came out of actually translating pages
Heading anchors break silently. Anchors derive from English heading text. This repo has 3,723 headings with no explicit anchors and 710 internal links carrying a
#fragment. Translate a heading and those links start landing at the top of the page — no error, nothing in the diff. Fixed by pinning## 제목 [#english-anchor].Code fences are verbatim, with one exception. Comment lines may be translated when executable lines and line count are byte-identical.
docs/data-structure/concepts.mdxforced this: its JSON example is 61% English commentary explaining$insert_id,distinct_idandtime.The validator is the load-bearing piece
validate-mdx-parity.mjsenforces the whole spec — JSX components, code fences, imports, image paths,src/href, link targets, heading anchors, frontmatter keys, brace balance. Zero dependencies; nopackage.jsonneeded.It is also the scoring function for the DF-827 bake-off and the gate for the DF-831 pipeline, whichever engine wins.
node i18n/scripts/build-glossary.mjs ko --check # generated glossary in syncThe pilot pages that exercise it land in the DF-828 PR later in this stack.
Reviewing
The Korean terminology is the part that most needs a native reader —
glossary/ko.md. Policy turns on one fact: Mixpanel's UI is English-only, so feature names stay phonetically recognisable against the button the reader is looking at. Documented pitfalls a machine gets wrong:리텐션(report) vs데이터 보관(policy), Board vs Dashboard, and particles after Latin words (Mixpanel을, notMixpanel를).