Skip to content

Commit b96ddb5

Browse files
Merge pull request #41 from trophyso/translations-setup
Translations setup + es
2 parents e2899ad + 9adbf89 commit b96ddb5

242 files changed

Lines changed: 15700 additions & 1376 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
description: Localization workflow and Lingo guardrails
3+
alwaysApply: true
4+
---
5+
6+
# Localization Workflow
7+
8+
- Treat `en/` as the canonical source locale and keep identical file paths/filenames across all locales.
9+
- Keep all locale navigation in `docs.json` under `navigation.languages`; do not reintroduce `navigation.global` or top-level `navbar`.
10+
- Keep SEO metatags in English unless explicitly requested otherwise.
11+
- Do not translate code snippets, inline code, URLs, route slugs, or API identifiers.
12+
- 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.
13+
- `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).
14+
- 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.
15+
- 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/`).
16+
- 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.
17+
- 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.
18+
- 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.
19+
- 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.
20+
- Do not add script-based brand voice sync; sync brand voice on demand via Lingo MCP tools from chat.
21+
- Treat `lingo/glossary.csv` as the glossary source of truth; use MCP sync with canonical key `sourceLocale|targetLocale|type|sourceText`.
22+
- For glossary sync, always do dry-run first, show create/update/delete counts, then apply on confirmation.
23+
- When adding locales, update both `docs.json` language blocks and `i18n.json` `locale.targets`.

.cursorignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Lingo.dev configuration
2+
# Copy this file to .env and replace placeholders with real values.
3+
4+
# Lingo.dev API key (used by CLI and CI workflows)
5+
LINGO_API_KEY=lingo_sk_your_api_key_here
6+
7+
# Lingo.dev localization engine id
8+
LINGO_ENGINE_ID=eng_your_engine_id_here
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Translate on main
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
pull-requests: write
11+
12+
jobs:
13+
translate:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Node
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 20
23+
cache: npm
24+
25+
- name: Install dependencies
26+
run: npm ci
27+
28+
- name: Inject Lingo engine id into i18n config
29+
env:
30+
LINGO_ENGINE_ID: ${{ secrets.LINGO_ENGINE_ID }}
31+
run: |
32+
if [ -z "${LINGO_ENGINE_ID}" ]; then
33+
echo "LINGO_ENGINE_ID secret is required."
34+
exit 1
35+
fi
36+
node -e 'const fs=require("fs");const p="i18n.json";const j=JSON.parse(fs.readFileSync(p,"utf8"));j.engineId=process.env.LINGO_ENGINE_ID;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");'
37+
38+
- name: Sync API and webhook titles from OpenAPI
39+
run: node scripts/sync-openapi-titles.mjs
40+
41+
- name: Run Lingo translation
42+
env:
43+
LINGO_API_KEY: ${{ secrets.LINGO_API_KEY }}
44+
run: |
45+
locales=$(node -e 'const fs=require("fs");const j=JSON.parse(fs.readFileSync("i18n.json","utf8"));console.log((j.locale?.targets||[]).join(" "));')
46+
if [ -z "$locales" ]; then
47+
echo "No target locales configured in i18n.json locale.targets"
48+
exit 1
49+
fi
50+
for locale in $locales; do
51+
echo "Running Lingo translation for $locale"
52+
npx lingo.dev@latest run --target-locale "$locale"
53+
done
54+
55+
- name: Translate docs.json language-specific navigation
56+
env:
57+
LINGO_ENGINE_ID: ${{ secrets.LINGO_ENGINE_ID }}
58+
LINGO_API_KEY: ${{ secrets.LINGO_API_KEY }}
59+
run: |
60+
locales=$(node -e 'const fs=require("fs");const j=JSON.parse(fs.readFileSync("i18n.json","utf8"));console.log((j.locale?.targets||[]).join(" "));')
61+
for locale in $locales; do
62+
echo "Translating docs.json labels for $locale"
63+
node scripts/translate-docs-json.mjs --target "$locale"
64+
echo "Localizing internal links for $locale"
65+
node scripts/localize-internal-links.mjs --target "$locale"
66+
echo "Localizing component imports for $locale"
67+
node scripts/localize-component-imports.mjs --target "$locale"
68+
done
69+
70+
- name: Sync Mintlify heading anchors
71+
run: node scripts/sync-heading-anchors.mjs
72+
73+
- name: Create translation PR
74+
uses: peter-evans/create-pull-request@v6
75+
with:
76+
token: ${{ secrets.GITHUB_TOKEN }}
77+
commit-message: "chore(i18n): update translations from main"
78+
title: "chore(i18n): update translations from main"
79+
body: "Automated translation update from main branch push."
80+
branch: "chore/i18n/auto-translations"
81+
delete-branch: true
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Validate translations
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "docs.json"
7+
- "i18n.json"
8+
- "openapi.yml"
9+
- "package.json"
10+
- "en/**"
11+
- "es/**"
12+
- ".github/workflows/translate-on-main.yml"
13+
- ".github/workflows/validate-translations.yml"
14+
- "scripts/*.mjs"
15+
16+
jobs:
17+
validate:
18+
if: ${{ !contains(fromJson('["translations-setup"]'), github.head_ref) }}
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Node
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: 20
28+
cache: npm
29+
30+
- name: Install dependencies
31+
run: npm ci
32+
33+
- name: Inject Lingo engine id into i18n config
34+
env:
35+
LINGO_ENGINE_ID: ${{ secrets.LINGO_ENGINE_ID }}
36+
run: |
37+
if [ -z "${LINGO_ENGINE_ID}" ]; then
38+
echo "LINGO_ENGINE_ID is not set, skipping engine injection."
39+
else
40+
node -e 'const fs=require("fs");const p="i18n.json";const j=JSON.parse(fs.readFileSync(p,"utf8"));j.engineId=process.env.LINGO_ENGINE_ID;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");'
41+
fi
42+
43+
- name: Validate translation parity and frontmatter
44+
run: npm run translate:validate
45+
46+
- name: Ensure internal links are locale-localized
47+
run: npm run translate:localize-links:check
48+
49+
- name: Ensure component imports are locale-localized
50+
run: npm run translate:localize-component-imports:check
51+
52+
- name: Ensure heading anchors match English slugs
53+
run: npm run translate:sync-anchors:check
54+
55+
- name: Ensure API and webhook titles match OpenAPI summaries
56+
run: npm run translate:sync-openapi-titles:check
57+
58+
- name: Mintlify validate
59+
run: npx mint@latest validate
60+
61+
- name: Check broken links
62+
run: npx mint@latest broken-links --check-anchors --check-snippets
63+
64+
- name: Translation freshness gate
65+
env:
66+
LINGO_API_KEY: ${{ secrets.LINGO_API_KEY }}
67+
LINGO_ENGINE_ID: ${{ secrets.LINGO_ENGINE_ID }}
68+
run: |
69+
if [ -z "${LINGO_API_KEY}" ] || [ -z "${LINGO_ENGINE_ID}" ]; then
70+
echo "LINGO_API_KEY or LINGO_ENGINE_ID is not set, skipping frozen translation gate."
71+
else
72+
npx lingo.dev@latest run --frozen
73+
fi

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
2-
.DS_Store
2+
.DS_Store
3+
.env

.vale.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ mdx = md
1616
BasedOnStyles = Vale
1717
Vale.Terms = NO # Enforces really harsh capitalization rules, keep off
1818

19+
# Exclude Spanish locale pages from spellchecking.
20+
# Vale may evaluate paths with leading directories, so match both forms.
21+
[es/**/*.mdx]
22+
Vale.Spelling = NO
23+
[**/es/**/*.mdx]
24+
Vale.Spelling = NO
25+
1926
# `import ...`, `export ...`
2027
# `<Component ... />`
2128
# `<Component>...</Component>`

0 commit comments

Comments
 (0)