Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions i18n/04-rollout-playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# M4 · Language prioritization and rollout playbook

**Linear:** [DF-833](https://linear.app/mixpanel/issue/DF-833), [DF-834](https://linear.app/mixpanel/issue/DF-834)

---

## Which language is next

### The signal Mixpanel already has

`mixpanel.com` publishes `hreflang` alternates for exactly four locales:

```
en-US ko-KR ja-JP id-ID
```

Marketing has already decided which non-English markets are worth translating for, and paid for it. Docs
should follow that list rather than invent a new one — same markets, same sales motion, same customers.

(Note for whoever picks this up: `mixpanel.com/ko/` currently returns 200 but serves English. The alternate
is declared, the content is not there yet. Worth flagging to whoever owns the marketing site — and worth
knowing that a Korean docs launch may land before a Korean marketing site.)

### Recommended order

| # | Locale | Why | Confidence |
| --- | --- | --- | --- |
| 1 | `ko` | In flight. Marketing-declared market. | — |
| 2 | `ja` | Marketing-declared; large enterprise analytics market; Mintlify supports `ja` and `ja-JP` | high |
| 3 | `id` | Marketing-declared. Verify with docs traffic first — a declared alternate is weaker evidence than pageviews | medium |
| 4 | `es` / `pt-BR` | Large developer populations, no marketing signal yet. Only on traffic evidence | low |
| 5 | `de` / `fr` | Strong English proficiency among the developer audience; usually the lowest-yield locales for developer docs | low |

### Confirm with data before locale 3

Do not extend past `ja` on inference. `docs.json` already reports into a Mixpanel project
(`integrations.mixpanel.projectToken: "metrics-1"`), so the answer is a report away:

- Docs pageviews broken down by `$country_code` / browser language, over 90 days
- Trend of `/ko` pageviews after Korean launch — the actual measured lift from localizing, which is the
only honest input to "is the next language worth it"
- Whether non-English-locale readers reach `/reference` at all → settles
[wave 2](02-scope-and-preservation-spec.md#waves) for every language at once

One report answers the prioritization question for the rest of the project. We sell this product; we should
use it.

---

## Per-language rollout playbook

Once Korean is live and CI is running, a new language should be a checklist, not a project. Budget **~1 week**,
most of it native review.

### 1 · Configure — ~1 hour

```bash
# translate the 128 nav labels for the new locale
cp i18n/nav/labels.ko.json i18n/nav/labels.<locale>.json
# ...translate the values...

node i18n/scripts/build-locale-nav.mjs <locale> # → i18n/nav/<locale>.json
node i18n/scripts/build-locale-nav.mjs <locale> --check # must exit 0

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 Copied labels fail required check

When an engineer copies labels.ko.json as instructed, the retained Community key is not consumed because the builder traverses only navigation.tabs; --check therefore reports it as unused and exits nonzero, blocking the rollout.

```

Then add the locale to `docs.json` (`{ "$ref": "./<locale>.json" }`) and to the engine's target list
(`gt.config.json` → `locales`, or the Mintlify dashboard).

Confirm the code is on [Mintlify's supported list](https://www.mintlify.com/docs/guides/internationalization)
first. `ko`, `ja`/`ja-JP`, `id`, `es`, `pt-BR`, `de`, `fr`, `zh-Hans`, `zh-Hant` all are.

### 2 · Extend the glossary — ~half a day

```bash
cp i18n/glossary/ko.md i18n/glossary/<locale>.md
# ...translate the term column; keep the section structure and the Never-translate list...
node i18n/scripts/build-glossary.mjs <locale>
```

**The do-not-translate list and the pitfalls carry over unchanged** — "retention" is ambiguous in every
language, and brand names are brand names everywhere. What changes is the target column and the
language-specific note at the end of `glossary/ko.md` (Korean particles; for Japanese, katakana vs kanji for
feature names; for German, compound-noun length in the sidebar).

**Do this before bulk translation, not after.** Fixing terminology across 295 already-translated pages costs
far more than deciding it once.

### 3 · Translate — hours, unattended

```bash
npx gtx-cli translate --config gt.config.json # or: Mintlify dashboard → add locale

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 Translation config path is wrong

When an engineer follows this command from the repository root before the staged configuration is relocated, gtx-cli looks for nonexistent root-level gt.config.json instead of i18n/config/gt.config.json, causing translation to fail before it starts.

```

352,500 words in the wave-1 tier. Half of it is in five sections, and `docs/tracking-methods` (91.5k words,
68 files) repeats the same headings across every SDK — so translation memory makes the big block the cheap
block.

### 4 · Validate — minutes

```bash
node i18n/scripts/validate-mdx-parity.mjs \
--source docs --target <locale>/docs \
--locale <locale> --link-prefix --require-anchors --allow-code-comments
Comment on lines +101 to +103

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 Validation omits translated guides

When a rollout contains both docs and guides, this step validates only <locale>/docs, leaving structural defects in translated guides undetected before review and publishing.

Suggested change
node i18n/scripts/validate-mdx-parity.mjs \
--source docs --target <locale>/docs \
--locale <locale> --link-prefix --require-anchors --allow-code-comments
node i18n/scripts/validate-mdx-parity.mjs \
--source docs --target <locale>/docs \
--locale <locale> --link-prefix --require-anchors --allow-code-comments
node i18n/scripts/validate-mdx-parity.mjs \
--source guides --target <locale>/guides \
--locale <locale> --link-prefix --require-anchors --allow-code-comments
```

Must exit 0. Non-negotiable, and it is not a matter of judgement — if it fails, the engine is misconfigured,
not the translation.

### 5 · Native review — the real cost, ~3–5 days

This is the only step that does not compress. Sequence it so terminology is fixed before volume:

1. Quickstart + intro pages (~6k words) — sets the vocabulary for everything after
2. Conceptual core: data model, reports, boards, users
3. `docs/tracking-methods` as **one batch with one reviewer** — 68 files of near-identical headings; split
it across reviewers and the inconsistency will be visible on adjacent pages
4. The rest

### 6 · Ship and watch

Merge, confirm in `mintlify dev`, then check the language switcher round-trips and internal links stay
inside the locale. Two weeks later, pull pageviews for the new prefix — that number is the input to whether
locale N+1 happens.

---

## Reviewing a translation PR

For reviewers landing on an automated PR from [`config/translate.yml`](config/translate.yml). Structure is
already machine-verified — **your job is only whether the language is right.**

1. **Terminology** — spot-check against [`glossary/ko.csv`](glossary/ko.csv). Grep the PR for the five
highest-frequency terms and confirm one rendering each: 이벤트 / 속성 / 코호트 / 보드 / 사용자.
2. **The `retention` trap** — 리텐션 (the report) and 데이터 보관 (the policy) are different words. Machines
collapse them. Highest-yield single check in the review.
3. **Particles after Latin words** — `Mixpanel을` not `Mixpanel를`, `SDK를` not `SDK을`. Chosen by how the
Latin word is *read aloud* in Korean. Frequent, visible, and consistently gotten wrong by machines.
4. **Register** — `-합니다 / -하세요`. Flag any drift into 해요체.
5. **Untranslated leftovers** — search the diff for runs of English prose outside code and brand names.
6. **Render it** — `mintlify dev` on two or three changed pages. Korean is denser than English; check that
sidebar labels, `<Card>` grids, and table cells still lay out.

What you do **not** need to check, because CI already did: JSX components, code fences, imports, image
paths, link targets, heading anchors, frontmatter keys.

---

## Exit criteria for M4

- [ ] Locale 2 (`ja`, most likely) live and maintained by the same CI as `ko`, with no new pipeline
- [ ] Adding it required only the steps above — any deviation gets folded back into this playbook
- [ ] Docs traffic by locale is a report someone can pull on demand, and it drives locale 3
84 changes: 84 additions & 0 deletions i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Docs localization (Mintlify i18n)

Working directory for [Docs Localization (Mintlify i18n)](https://linear.app/mixpanel/project/docs-localization-mintlify-i18n-25fabd3b5c76) — shipping `docs.mixpanel.com` in Korean, then scaling to more languages.

**Nothing here is live.** `docs.json` is untouched, no workflow is installed, and no `ko/` content tree
exists at the repo root. Everything is staged so the switch-on is a deliberate, reviewable step.

---

## Read this first

Two findings change the shape of the project:

**1. Mintlify sells the pipeline we were about to build.** Native AI translations (Pro/Enterprise) already
bulk-translates docs and opens a PR per change, on a continuous schedule, never publishing without a human
merge — which is M2's bulk translation plus all of M3. Every observable signal says Mixpanel is on
Enterprise. **Before DF-828 starts, someone should open the Mintlify dashboard → Settings → Translations.**
→ [`01-plan-and-entitlements.md`](01-plan-and-entitlements.md)

**2. 710 internal links will break silently on translation.** Every heading anchor in this repo derives from
its English text; translate the heading and the anchor changes, and 710 deep links land at the top of the
page instead of the right section. No error, nothing in the diff. The fix is mechanical — pin
`## 제목 [#english-anchor]` — but the engine has to do it, and not every engine can.
→ [`02-scope-and-preservation-spec.md`](02-scope-and-preservation-spec.md#heading-anchors--the-failure-nobody-sees-in-review)

---

## Contents

| | Covers | Linear |
| --- | --- | --- |
| [`01-plan-and-entitlements.md`](01-plan-and-entitlements.md) | Is localization on our plan; what Mintlify's own translation feature does; the decision gate it opens | DF-823 |
| [`02-scope-and-preservation-spec.md`](02-scope-and-preservation-spec.md) | What gets translated, what must survive byte-for-byte, and how that is enforced | DF-826 |
| [`03-tooling-evaluation.md`](03-tooling-evaluation.md) | Mintlify-native vs General Translation vs Lingo.dev, and a 12-page bake-off to settle it | DF-827 |
| [`04-rollout-playbook.md`](04-rollout-playbook.md) | Which language is next, the per-language checklist, and the reviewer's checklist | DF-833, DF-834 |
| [`glossary/ko.md`](glossary/ko.md) | 121 Korean terms, 60 do-not-translate, and the pitfalls that need a human | DF-826 |
| [`nav/`](nav/README.md) | Korean navigation tree + the exact `docs.json` edit, ready to apply | DF-824 |
| [`config/`](config/README.md) | Staged `gt.config.json` and translation workflow | DF-830, DF-831 |
| [`pilot/`](pilot/) | Three hand-translated pages — the quality bar engines get scored against | DF-828 |
| [`scripts/`](scripts/) | The tooling below | |

## Tooling

Zero dependencies — plain Node, no `package.json` needed.

```bash
# Verify a translation preserves structure. The M3 CI gate, and the bake-off scoring function.
node i18n/scripts/validate-mdx-parity.mjs --source docs --target ko/docs \
--locale ko --link-prefix --require-anchors --allow-code-comments

# Rebuild the Korean navigation from docs.json (--check fails if labels drift)
node i18n/scripts/build-locale-nav.mjs ko

# Rebuild machine-readable glossary artifacts from the human glossary
node i18n/scripts/build-glossary.mjs ko

# Show the docs.json change that enables i18n — without making it
node i18n/scripts/preview-docsjson-patch.mjs ko --write
```

`validate-mdx-parity.mjs` catches what review cannot: renamed JSX components, translated code identifiers,
localized image paths, dropped imports, unprefixed links, drifted heading anchors, mangled `{…}`
expressions. It is quiet on a correct translation and specific on a broken one. All three pilot pages pass
it on the strictest settings.

## Where the project stands

| Milestone | State |
| --- | --- |
| **M1 · Mintlify config** | Plan question answered on the evidence; one dashboard check left. Korean nav built and verified. `docs.json` edit staged, not applied. |
| **M2 · Korean translation** | Scope, preservation spec, and glossary done. Engine recommended, pending a 12-page bake-off. Three pages hand-translated and passing. Bulk translation blocked on the engine decision. |
| **M3 · CI on merge** | Config and workflow written and staged. Redundant if Mintlify-native wins — which is the point of deciding first. |
| **M4 · Other languages** | Prioritization and playbook done. Blocked on M2/M3, as intended. |

## Open decisions

1. **Which engine?** Mintlify-native vs General Translation. Run the
[bake-off](03-tooling-evaluation.md#the-bake-off) — 12 pages, scored by the validator, one afternoon.
*Blocks DF-828, DF-830, DF-831.*
2. **Does the API reference get translated?** It is two artifacts (137 MDX files + 14 OpenAPI specs, ~20k
words) and translating one without the other is worse than translating neither. Recommended: defer, then
let docs traffic decide. *Affects DF-824's Korean nav shape.*
3. **Who reviews Korean, and how much of their time is available?** The only step in the whole pipeline that
does not compress. ~3–5 days per language.