diff --git a/.cursor/rules/localization-workflow.mdc b/.cursor/rules/localization-workflow.mdc index a241acc..6cba1e6 100644 --- a/.cursor/rules/localization-workflow.mdc +++ b/.cursor/rules/localization-workflow.mdc @@ -5,17 +5,19 @@ alwaysApply: true # Localization Workflow -- Treat `en/` as the canonical source locale and keep identical file paths/filenames across all locales. +- Treat repo-root docs paths as the canonical English source and keep identical relative file paths/filenames across all locales. - Keep all locale navigation in `docs.json` under `navigation.languages`; do not reintroduce `navigation.global` or top-level `navbar`. - Keep SEO metatags in English unless explicitly requested otherwise. - Do not translate code snippets, inline code, URLs, route slugs, or API identifiers. -- Shared media lives under repo-root `assets/`. Locale pages are nested under `en/` or `es/` (typically `locale/
/.mdx`), so relative `src` paths must account for that depth (for example `../../assets/...`). `scripts/localize-internal-links.mjs` rewrites markdown `](/...)` and JSX `href="/..."` only: for whichever **`locale` is being processed**, bare paths get that prefix, and paths prefixed with **any other** locale from `i18n.json` are re-prefixed to that same active locale; it does not change media `src` or snippet imports. +- Shared media lives under repo-root `assets/`. Default-language pages are at repo-root section paths (for example `
/.mdx`) and targets are under locale directories (for example `es/
/.mdx`), so relative `src` paths must account for each file depth. `scripts/localize-internal-links.mjs` rewrites markdown `](/...)` and JSX `href="/..."` only: for target locales, bare paths get that locale prefix and paths prefixed with another locale are re-prefixed to the active locale; for source/default processing, locale prefixes are stripped back to root paths. It does not change media `src` or snippet imports. - `scripts/localize-component-imports.mjs` rewrites MDX import paths that reference `/components/*.jsx` so each locale page imports its own locale components (for whichever locale is being processed). - Shared MDX snippets live under repo-root `snippets/` (not per-locale). Import with relative paths from each page; do not add shared MDX snippets to Lingo `i18n.json` buckets unless you intend to translate them. -- Locale-aware React components that can contain text live under `[locale]/components/*.jsx` and are manually maintained per locale (do not add them to Lingo `i18n.json` buckets; do not keep these in shared `snippets/`). +- Locale-aware React components that can contain text live under `components/*.jsx` for default language and `/components/*.jsx` for targets, and are manually maintained per locale (do not add them to Lingo `i18n.json` buckets; do not keep these in shared `snippets/`). - Keep a **single** OpenAPI document at repo-root `openapi.yml` (not under `en/` or `es/`). Endpoint and webhook MDX must use Mintlify’s form `openapi: openapi.yml ` or `openapi: openapi.yml webhook `. Do not translate the `openapi:` line. - English **nav titles** for those pages come from OpenAPI **`summary`** via `scripts/sync-openapi-titles.mjs` (`npm run translate:sync-openapi-titles`). It runs at the start of `translate:generate` and on the translate-on-main workflow. Lingo translates the `title` field for target locales; the script only fills a missing target `title` with English (bootstrap)—it does not overwrite existing translations. -- Mintlify custom heading IDs use markdown `## Title {#slug}` (see Mintlify docs). Slugs are aligned from English via `scripts/sync-heading-anchors.mjs`. Never translate or alter `{#…}`; sync runs after Lingo in PIT/CI. Do not merge heading-count drift between `en/` and target locales without fixing structure first. +- Mintlify custom heading IDs use markdown `## Title {#slug}` (see Mintlify docs). Slugs are aligned from English source root files via `scripts/sync-heading-anchors.mjs`. Never translate or alter `{#…}`; sync runs after Lingo in PIT/CI. Do not merge heading-count drift between source root and target locales without fixing structure first. +- For Mintlify `` components that may be deep-linked, always set explicit `id` values (do not rely on title-derived hashes) and keep those `id`s identical across locales. +- Do not use fragile OpenAPI parameter hash links like `#parameter-*` in docs links; link to the endpoint page and reference parameter names inline in prose/code. - Treat `lingo/brand-voice.md` as the source of truth for **brand voice** (tone and style) only. Do not add structural or tooling rules there (for example Mintlify `{#slug}` handling); those stay in this rule, README, and optionally in the Lingo engine **Instructions** field—not brand voice. - Do not add script-based brand voice sync; sync brand voice on demand via Lingo MCP tools from chat. - Treat `lingo/glossary.csv` as the glossary source of truth; use MCP sync with canonical key `sourceLocale|targetLocale|type|sourceText`. diff --git a/.github/workflows/validate-translations.yml b/.github/workflows/validate-translations.yml index 0957519..f14edb6 100644 --- a/.github/workflows/validate-translations.yml +++ b/.github/workflows/validate-translations.yml @@ -7,7 +7,15 @@ on: - "i18n.json" - "openapi.yml" - "package.json" - - "en/**" + - "account/**" + - "admin-api/**" + - "api-reference/**" + - "components/**" + - "experimentation/**" + - "getting-started/**" + - "guides/**" + - "platform/**" + - "webhooks/**" - "es/**" - ".github/workflows/translate-on-main.yml" - ".github/workflows/validate-translations.yml" @@ -15,7 +23,7 @@ on: jobs: validate: - if: ${{ !contains(fromJson('["translations-setup"]'), github.head_ref) }} + if: ${{ !contains(fromJson('["translations-setup", "restructure-translations"]'), github.head_ref) }} runs-on: ubuntu-latest steps: - name: Checkout @@ -49,6 +57,9 @@ jobs: - name: Ensure component imports are locale-localized run: npm run translate:localize-component-imports:check + - name: Ensure fragile link fragments are not used + run: npm run translate:validate-links + - name: Ensure heading anchors match English slugs run: npm run translate:sync-anchors:check diff --git a/README.md b/README.md index 703969f..7e4e8d0 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ Notes: - `scripts/localize-internal-links.mjs`: Rewrites internal absolute **navigation** links in each locale’s MDX for whatever locale is being processed (`--target`, `--all`, or default targets): bare paths get that locale’s prefix (e.g. `/platform/points` → `/es/platform/points` when processing `es/`), and any path already prefixed with **another** locale from `i18n.json` (`en`, `es`, future `fr`, etc.) is re-prefixed to the active locale (so `/en/...` or stale `/es/...` on a `fr/` page become `/fr/...`). Longer codes are matched first (e.g. `en-US` before `en`). It does **not** change relative image or video `src` paths, MDX/JSX **import** paths, or shared **MDX snippets**; those must be correct in source so all locales stay aligned. Prefer `npm run translate:localize-links --` with `--target`, `--all`, or `--all --check`. - `scripts/localize-component-imports.mjs`: Rewrites MDX `import ... from "...//components/...jsx"` paths so they match the active locale being processed (`--target`, `--all`, or default targets). This keeps locale pages importing locale-local components after PIT/CI translation. - **Shared MDX snippets (`snippets/*.mdx`)**: Reusable MDX blocks stay in repo-root `snippets/` (not per-locale). Import them with relative paths from each page (for example `../../snippets/foo.mdx` from `locale/
/.mdx`, or more `../` segments for deeper pages). They are excluded from Lingo buckets in `i18n.json` so they stay English and identical everywhere. -- **Localized React components (`[locale]/components/*.jsx`)**: UI components that can contain locale text are stored per locale (for example `en/components/rate-limit-badge.jsx`, `es/components/rate-limit-badge.jsx`) and manually maintained per locale (not translated by PIT/CI automation). -- **Media paths**: Pages live under `en/
/...` or `es/
/...`, while shared files sit in repo-root `assets/`. Use a path like `../../assets/...` from a typical `locale/
/.mdx` file. +- **Localized React components (`components/*.jsx` for default language, `/components/*.jsx` for targets)**: UI components that can contain locale text are stored per locale (for example `components/rate-limit-badge.jsx`, `es/components/rate-limit-badge.jsx`) and manually maintained per locale (not translated by PIT/CI automation). +- **Media paths**: Default-language pages live at repo root paths like `
/.mdx` and target locales live under `/
/.mdx`; shared files sit in repo-root `assets/`. Keep relative media paths correct for each file depth. - **`openapi.yml`**: One OpenAPI 3.1 spec at the **repository root** (alongside `docs.json`). API and webhook pages reference it explicitly in frontmatter, for example `openapi: openapi.yml get /users/{id}` or `openapi: openapi.yml webhook points.changed`. Do not duplicate the YAML under locale folders; Lingo must not alter `openapi:` lines. - **`scripts/sync-openapi-titles.mjs`**: Copies each operation/webhook **`summary`** from `openapi.yml` into the English page’s **`title:`** frontmatter (Mintlify’s default when `title` is omitted). Target locales get an English `title` only if missing (bootstrap); run **`npm run translate:generate`** so Lingo translates those titles. Runs automatically at the start of `translate:generate` and in translate-on-main before Lingo. - `scripts/sync-heading-anchors.mjs`: Writes Mintlify [custom heading IDs](https://www.mintlify.com/docs/create/text#custom-heading-ids) as **`## Title {#slug}`** markdown. Slugs match Mintlify’s auto rules from the **English** title so hashes like `#pro-plan` stay stable across locales. Run `npm run translate:sync-anchors` after bulk heading edits; translation pipelines run it automatically (see **Heading anchors and Lingo** below). The script can also migrate one-line **`

`** left from older tooling back to `{#slug}` syntax. @@ -71,7 +71,7 @@ Notes: #### Heading anchors and Lingo -- **Canonical behavior** is enforced by **`scripts/sync-heading-anchors.mjs`** (runs at the end of `translate:generate` and on the translate-on-main workflow). It rewrites `en/` from current English titles and reapplies the **same** `{#slug}` suffixes to translated headings in document order. Hash links stay aligned even if Lingo alters titles or fragments. +- **Canonical behavior** is enforced by **`scripts/sync-heading-anchors.mjs`** (runs at the end of `translate:generate` and on the translate-on-main workflow). It rewrites default-language root files from current English titles and reapplies the **same** `{#slug}` suffixes to translated headings in document order. Hash links stay aligned even if Lingo alters titles or fragments. - **Lingo:** Keep **`lingo/brand-voice.md`** to tone and style only. For structural rules (for example Mintlify `{#slug}` on headings), optionally add a separate line in your Lingo engine **Instructions** field (not brand voice), such as: “Preserve `{#…}` heading fragments exactly—do not translate or alter the slug.” That only reduces churn; **the script + `translate:sync-anchors:check` are authoritative.** - **Strict MDX parsers** (for example unconfigured `mdx-js`) can treat `{` as JSX and error on `{#slug}`; **Mintlify’s `mint dev` / deploy pipeline** is expected to handle this documented syntax. If you see Acorn errors locally, update the Mintlify CLI (`npm i -D mint@latest` or global `mintlify`) or check [Mintlify support](https://mintlify.com/docs); do not switch to raw HTML headings unless Mintlify asks you to. - **Do not** fold this into `localize-internal-links.mjs`: that script only handles absolute path `href`s; heading anchors are a separate structural pass and must run **after** Lingo (and after link localization) so all three stay consistent. @@ -89,12 +89,12 @@ Use `npm run