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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .cursor/rules/localization-workflow.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -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/<section>/<page>.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 `<section>/<page>.mdx`) and targets are under locale directories (for example `es/<section>/<page>.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 `<locale>/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 `<locale>/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 <method> <path>` or `openapi: openapi.yml webhook <eventKey>`. 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 `<Accordion>` 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`.
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/validate-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ 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"
- "scripts/*.mjs"

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
Expand Down Expand Up @@ -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

Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@ 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 ".../<locale>/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/<section>/<page>.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/<section>/...` or `es/<section>/...`, while shared files sit in repo-root `assets/`. Use a path like `../../assets/...` from a typical `locale/<section>/<page>.mdx` file.
- **Localized React components (`components/*.jsx` for default language, `<locale>/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 `<section>/<page>.mdx` and target locales live under `<locale>/<section>/<page>.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 **`<h2 id="slug">…</h2>`** left from older tooling back to `{#slug}` syntax.
- `scripts/validate-glossary-csv.mjs`: Validates glossary schema and duplicate canonical keys. Prefer `npm run lingo:validate-glossary`.

#### 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.
Expand All @@ -89,12 +89,12 @@ Use `npm run <script> -- <args>` when a script needs CLI flags (the `--` forward
| `translate:localize-links:check` | CI-style check: all locales, no writes (`--all --check`). |
| `translate:localize-component-imports` | Localize MDX imports that reference `<locale>/components/*.jsx` to the active locale. |
| `translate:localize-component-imports:check` | CI-style check: fail if any MDX imports point at another locale’s components. |
| `translate:sync-anchors` | Apply English-derived `{#slug}` on ATX headings in `en/` and target locales (see `i18n.json` `locale.targets`). |
| `translate:sync-anchors` | Apply English-derived `{#slug}` on default-language root MDX and target locales (see `i18n.json` `locale.targets`). |
| `translate:sync-anchors:check` | Fail if any MDX needs re-sync (CI). |
| `translate:sync-openapi-titles` | Set `en/` API & webhook `title:` from `openapi.yml` summaries; bootstrap missing target `title` from English. |
| `translate:sync-openapi-titles` | Set default-language API & webhook `title:` from `openapi.yml` summaries; bootstrap missing target `title` from English. |
| `translate:sync-openapi-titles:check` | CI: fail if any OpenAPI-backed page title differs from the spec. |
| `lingo:validate-glossary` | Validate `lingo/glossary.csv`. |
| `translate:validate` | MDX parity and frontmatter checks (`en` / `es`). No `.env` required. |
| `translate:validate` | MDX parity and frontmatter checks (source root -> target locales, e.g. `es`). No `.env` required. |
| `translate:verify` | `lingo.dev run --frozen` (freshness gate). |
| `mint:validate` | Mintlify `validate`. |
| `mint:broken-links` | Mintlify `broken-links` with anchor and snippet checks. |
Expand Down Expand Up @@ -134,13 +134,13 @@ Glossary sync workflow (manual via Cursor + MCP):

1. Add the locale to `docs.json`:
- In `navigation.languages`, add a new object with `language: "<locale>"`.
- Copy the structure from `en` (tabs/groups/pages) and prefix pages with `<locale>/...`.
- Copy the default-language structure (tabs/groups/pages) and prefix pages with `<locale>/...`.
- Add language-specific `anchors` and `navbar` fields in that locale block.
2. Add the locale to `i18n.json`:
- In `locale.targets`, append the locale code (for example `fr` or `de`).
3. Create the language content directory:
- Mirror the `en/` structure under `<locale>/` with the same filenames.
- Example: `en/platform/overview.mdx` -> `fr/platform/overview.mdx`.
- Mirror the root default-language structure under `<locale>/` with the same filenames.
- Example: `platform/overview.mdx` -> `fr/platform/overview.mdx`.
- Keep MDX snippets shared in repo-root `snippets/` (no per-locale snippet trees).
- Keep React components locale-local under `<locale>/components/*.jsx` and mirror updates manually across locales (not via Lingo automation).
4. Configure Lingo.dev engine controls for the new locale:
Expand All @@ -160,5 +160,5 @@ Glossary sync workflow (manual via Cursor + MCP):

Notes:
- The workflow `.github/workflows/translate-on-main.yml` automatically reads locales from `i18n.json` `locale.targets`.
- Keep `en` as source/default and preserve identical file paths across locales.
- Keep English as source/default and preserve identical relative file paths across locales.
- `--force` on `translate:generate` purges **all** Lingo-managed content for that target locale, then re-runs translation—use only for recovery/backfill (for example, cache stuck after bootstrap). Do not use `--force` in CI; CI should run delta translation based on `i18n.lock`.
24 changes: 12 additions & 12 deletions en/account/billing.mdx → account/billing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how Trophy controls costs by charging based on monthly active
icon: gauge
---

import { PlanBadge } from "../../en/components/plan-badge.jsx";
import { PlanBadge } from "../components/plan-badge.jsx";

## Usage-based Billing {#usage-based-billing}

Expand All @@ -18,7 +18,7 @@ Trophy follows a usage-based pricing model where customers only pay for the unit

## Monthly Active Users (MAUs) {#monthly-active-users-maus}

Trophy defines an MAU as a single user that sends at least one [metric event](/en/platform/events) to Trophy in a given month.
Trophy defines an MAU as a single user that sends at least one [metric event](/platform/events) to Trophy in a given month.

<Tip>
Bear in mind **you never pay for churned users**. If a user signs up for your
Expand Down Expand Up @@ -83,21 +83,21 @@ Here's a list of all Trophy features and the plan they become available on:

<PlanBadge plan="free" />

- [Achievements](/en/platform/achievements)
- [Streaks](/en/platform/streaks)
- [Points](/en/platform/points)
- [Leaderboards](/en/platform/leaderboards)
- [Emails](/en/platform/emails)
- [Push Notifications](/en/platform/push-notifications)
- [Achievements](/platform/achievements)
- [Streaks](/platform/streaks)
- [Points](/platform/points)
- [Leaderboards](/platform/leaderboards)
- [Emails](/platform/emails)
- [Push Notifications](/platform/push-notifications)

<PlanBadge plan="starter" />

- [DNS Verification](/en/platform/emails#dns-verification-advanced)
- [DNS Verification](/platform/emails#dns-verification-advanced)

<PlanBadge plan="pro" />

- [Webhooks](/en/webhooks)
- [Custom Attributes](/en/platform/users#custom-user-attributes)
- [Webhooks](/webhooks)
- [Custom Attributes](/platform/users#custom-user-attributes)

## Custom Contracts {#custom-contracts}

Expand All @@ -112,7 +112,7 @@ You can view your usage for the current billing period on the [billing page](htt
height="200"
width="50%"
noZoom
src="../../assets/account/billing/plan-usage.png"
src="../assets/account/billing/plan-usage.png"
/>
</Frame>

Expand Down
4 changes: 2 additions & 2 deletions en/account/branding.mdx → account/branding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ icon: palette

## Configure Branding {#configure-branding}

The [branding page](https://app.trophy.so/branding) in the Trophy dashboard allows you to configure the following account-level settings. These setting will be used in any communications you configure Trophy to send to users, like [Emails](/en/platform/emails).
The [branding page](https://app.trophy.so/branding) in the Trophy dashboard allows you to configure the following account-level settings. These setting will be used in any communications you configure Trophy to send to users, like [Emails](/platform/emails).

<Frame>
<img height="200" noZoom src="../../assets/account/branding/branding_page.png" />
<img height="200" noZoom src="../assets/account/branding/branding_page.png" />
</Frame>

- **Logo**: Upload your brand’s logo to be displayed in the header of Trophy emails. We recommend using a horizontal logo with a transparent background or a plain background color with rounded corners.
Expand Down
4 changes: 2 additions & 2 deletions en/account/members.mdx → account/members.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ To invite a team member to your Trophy organization, open up the organization ma
loop
playsInline
className="w-full aspect-15/4"
src="../../assets/account/members/add_new_member.mp4"
src="../assets/account/members/add_new_member.mp4"
></video>
</Frame>

Expand Down Expand Up @@ -46,7 +46,7 @@ To manage roles, open up the organization management dialog and head into the _M
loop
playsInline
className="w-full aspect-15/4"
src="../../assets/account/members/manage_roles.mp4"
src="../assets/account/members/manage_roles.mp4"
></video>
</Frame>

Expand Down
6 changes: 3 additions & 3 deletions en/account/overview.mdx → account/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ description: Learn how to manage your Trophy account.
From inviting team members to understanding billing, this section of the documentation is dedicated to managing your Trophy account.

<CardGroup>
<Card title="Members" icon="user-plus" href="/en/account/members">
<Card title="Members" icon="user-plus" href="/account/members">
Give team members access to Trophy and manage your account settings.
</Card>
<Card title="Branding" icon="palette" href="/en/account/branding">
<Card title="Branding" icon="palette" href="/account/branding">
Configure your logo and brand colors used across features built with Trophy.
</Card>
<Card title="Billing" icon="gauge" href="/en/account/billing">
<Card title="Billing" icon="gauge" href="/account/billing">
Learn how Trophy tracks usage according to the number of users that use your
product.
</Card>
Expand Down
Loading