Skip to content

DF-826: Korean scope, preservation spec, glossary + parity validator - #206

Open
spjtls9 wants to merge 1 commit into
sonyapark-df-823-m1-confirm-mintlify-plan-supports-localizationfrom
sonyapark-df-826-m2-define-korean-translation-scope-preservation-spec-and
Open

DF-826: Korean scope, preservation spec, glossary + parity validator#206
spjtls9 wants to merge 1 commit into
sonyapark-df-823-m1-confirm-mintlify-plan-supports-localizationfrom
sonyapark-df-826-m2-define-korean-translation-scope-preservation-spec-and

Conversation

@spjtls9

@spjtls9 spjtls9 commented Aug 29, 2026

Copy link
Copy Markdown

📚 Part 2 of an 8-PR stack — merge in order

Blocked by: #205 (DF-823) — this PR is branched off it, so review only the diff shown here.
Blocks: #207 (DF-827) and everything after it in the stack.

Do not merge out of order. Each PR is branched off the one above it; merging ahead of its base pulls
in unreviewed commits. As each lands, GitHub retargets the next onto main automatically.

PR Issue Scope
  1 #205 DF-823 Plan & entitlement findings
▸ 2 #206 DF-826 Scope, spec, glossary, validator ← this PR
  3 #207 DF-827 Engine evaluation
  4 #208 DF-824 Korean navigation tree
  5 #209 DF-828 Pilot Korean translations
  6 #210 DF-830 Staged translation config
  7 #211 DF-831 Staged CI workflow
  8 #212 DF-833 Prioritization, playbook, index

Documents in i18n/ cross-reference each other, so relative links to files added later in the stack
resolve 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.json is untouched, no workflow is installed, and
no ko/ tree exists at the repo root. Turning Korean on is a separate, deliberate sequence:

Step Action Gated on
1 Mintlify dashboard → Settings → Translations — confirm the feature, and whether it exposes glossary control DF-823
2 Run the 12-page bake-off; pick the engine step 1
3 Apply the docs.json languages array; cp i18n/nav/ko.json ./ko.json steps 1–2
4 Bulk-translate docs/ + guides/ into ko/ steps 2–3
5 Native Korean review (~3–5 days) — the step that does not compress step 4
6 Only if General Translation won: move gt.config.json to root, translate.yml to .github/workflows/, set GT_API_KEY + GT_PROJECT_ID steps 2–4
7 Backfill/reconcile, then publish /ko steps 5–6

Steps 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

Surface Files Prose words
docs/ 243 293,894
guides/ 52 58,606
reference/ MDX 137 10,076
openapi/*.yaml 14 specs ~9,774

Wave 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.mdx forced this: its JSON example is 61% English commentary explaining $insert_id, distinct_id and time.

The validator is the load-bearing piece

validate-mdx-parity.mjs enforces the whole spec — JSX components, code fences, imports, image paths, src/href, link targets, heading anchors, frontmatter keys, brace balance. Zero dependencies; no package.json needed.

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 sync

The 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을, not Mixpanel를).

…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>
@spjtls9
spjtls9 requested a review from a team as a code owner August 29, 2026 20:53
@spjtls9
spjtls9 requested review from caracheng and removed request for a team August 29, 2026 20:53
@linear-code

linear-code Bot commented Aug 29, 2026

Copy link
Copy Markdown
@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 2/5

The 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

Important Files Changed

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

Comment on lines +313 to +319
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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

Comment on lines +350 to +351
if (s.inlineCode.length !== t.inlineCode.length)
W(`inline code span count ${s.inlineCode.length} → ${t.inlineCode.length}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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

Comment on lines +353 to +356
/* 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)}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant