|
| 1 | +--- |
| 2 | +name: localize |
| 3 | +description: Use when adding support for a new locale/language to the Adapty docs site. Covers all hardcoded and dynamic locale registration points, translation commands, sitemap setup, Algolia search config, and the step-by-step order to follow. |
| 4 | +--- |
| 5 | + |
| 6 | +# Adding a New Locale to Adapty Docs |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +Adding a locale touches ~8 files plus new content/sitemap files. Miss any one and the locale will partially break (no search, broken auto-redirect, missing from sitemap, etc.). Follow this checklist in order. |
| 11 | + |
| 12 | +**Active locales:** `zh` (Chinese), `tr` (Turkish). All examples use `{LOCALE}` as placeholder — replace with the actual code (e.g. `ja`, `ko`, `de`). |
| 13 | + |
| 14 | +--- |
| 15 | + |
| 16 | +## Step 1 — Register the locale in source code |
| 17 | + |
| 18 | +Edit these 6 files before running any translation: |
| 19 | + |
| 20 | +### 1a. `src/data/locales.ts` |
| 21 | + |
| 22 | +Add the locale code to `SUPPORTED_LOCALES` and `LOCALE_NAMES`: |
| 23 | + |
| 24 | +```ts |
| 25 | +export const SUPPORTED_LOCALES = ['zh', 'tr', '{LOCALE}'] as const; |
| 26 | +export const LOCALE_NAMES: Record<Locale, string> = { |
| 27 | + zh: '中文', |
| 28 | + tr: 'Türkçe', |
| 29 | + '{LOCALE}': '{NativeName}', // e.g. ja: '日本語' |
| 30 | +}; |
| 31 | +``` |
| 32 | + |
| 33 | +### 1b. `scripts/translate.mjs` |
| 34 | + |
| 35 | +Add to both `LANGUAGE_NAMES` and `METADATA_TITLE_SUFFIXES`: |
| 36 | + |
| 37 | +```js |
| 38 | +const LANGUAGE_NAMES = { |
| 39 | + zh: 'Simplified Chinese (zh-CN)', |
| 40 | + tr: 'Turkish (tr-TR)', |
| 41 | + '{LOCALE}': '{Full language name}', // e.g. ja: 'Japanese (ja-JP)' |
| 42 | +}; |
| 43 | + |
| 44 | +const METADATA_TITLE_SUFFIXES = { |
| 45 | + zh: '| Adapty 文档', |
| 46 | + tr: '| Adapty Dokümanları', |
| 47 | + '{LOCALE}': '| Adapty {LocaleDocWord}', // e.g. ja: '| Adapty ドキュメント' |
| 48 | +}; |
| 49 | +``` |
| 50 | + |
| 51 | +### 1c. `src/locales/ui-strings.ts` |
| 52 | + |
| 53 | +Add translations for every key in every group. Groups: `feedback`, `header`, `search`, `articleButtons`, `toc`, `mobileSidebar`, `footer`. Pattern: |
| 54 | + |
| 55 | +```ts |
| 56 | +question: { en: 'Was this page helpful?', zh: '...', tr: '...', '{LOCALE}': '...' }, |
| 57 | +``` |
| 58 | + |
| 59 | +### 1d. `src/locales/dictionary.json` |
| 60 | + |
| 61 | +Add a `"{LOCALE}"` key to every term entry alongside existing `zh`, `ja`, `tr` keys: |
| 62 | + |
| 63 | +```json |
| 64 | +"A/B test": { |
| 65 | + "_note": "...", |
| 66 | + "zh": "A/B 测试", |
| 67 | + "ja": "A/B テスト", |
| 68 | + "tr": "A/B testi", |
| 69 | + "{LOCALE}": "..." |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +### 1e. `src/components/Search.astro` |
| 74 | + |
| 75 | +Add to `LOCALE_INDEX` mapping (lines 17-20) and to the data attributes on the `<input>` (lines 47-50): |
| 76 | + |
| 77 | +```ts |
| 78 | +const LOCALE_INDEX: Record<string, string | undefined> = { |
| 79 | + zh: import.meta.env.PUBLIC_ALGOLIA_INDEX_NAME_ZH, |
| 80 | + tr: import.meta.env.PUBLIC_ALGOLIA_INDEX_NAME_TR, |
| 81 | + '{LOCALE}': import.meta.env.PUBLIC_ALGOLIA_INDEX_NAME_{LOCALE_UPPER}, |
| 82 | +}; |
| 83 | +``` |
| 84 | + |
| 85 | +```html |
| 86 | +data-index-name-{LOCALE}={import.meta.env.PUBLIC_ALGOLIA_INDEX_NAME_{LOCALE_UPPER} ?? ''} |
| 87 | +``` |
| 88 | + |
| 89 | +### 1f. `src/components/Homepage.tsx` |
| 90 | + |
| 91 | +The homepage component has its own hardcoded `T` constant (lines 4–80) with strings for `en`, `zh`, and `tr` — **separate from `ui-strings.ts` and not touched by the translate script**. Add a new locale key with all ~20 strings: |
| 92 | + |
| 93 | +```ts |
| 94 | +const T = { |
| 95 | + en: { hero: "...", discoverTitle: "...", ... }, |
| 96 | + zh: { ... }, |
| 97 | + tr: { ... }, |
| 98 | + '{LOCALE}': { |
| 99 | + hero: "...", |
| 100 | + discoverTitle: "...", |
| 101 | + discoverDesc: "...", |
| 102 | + quickstartTitle: "...", |
| 103 | + quickstartDesc: "...", |
| 104 | + quickstartBtn: "...", |
| 105 | + nextTitle: "...", |
| 106 | + abTitle: "...", |
| 107 | + abDesc: "...", |
| 108 | + analyticsTitle: "...", |
| 109 | + analyticsDesc: "...", |
| 110 | + integrationsTitle: "...", |
| 111 | + integrationsDesc: "...", |
| 112 | + paywallTitle: "...", |
| 113 | + paywallDesc: "...", |
| 114 | + platformsTitle: "...", |
| 115 | + ios: "...", |
| 116 | + android: "...", |
| 117 | + reactNative: "...", |
| 118 | + flutter: "...", |
| 119 | + unity: "...", |
| 120 | + kmp: "...", |
| 121 | + capacitor: "...", |
| 122 | + }, |
| 123 | +} as const; |
| 124 | +``` |
| 125 | + |
| 126 | +### 1g. `src/components/Header.astro` |
| 127 | + |
| 128 | +The header is `transition:persist` and uses two client-side JS objects (in the inline `<script>`) that mirror other locale files but must be updated independently: |
| 129 | + |
| 130 | +**`LOCALE_NAMES_CLIENT`** (around line 387) — mirrors `src/data/locales.ts`: |
| 131 | +```js |
| 132 | +const LOCALE_NAMES_CLIENT: Record<string, string> = { zh: '中文', tr: 'Türkçe', '{LOCALE}': '{NativeName}' }; |
| 133 | +``` |
| 134 | + |
| 135 | +**`UI_STRINGS_CLIENT`** (around line 390) — a subset of `ui-strings.ts` for client-side reactivity. Add a new locale key: |
| 136 | +```js |
| 137 | +'{LOCALE}': { |
| 138 | + documentation: '...', |
| 139 | + mobileSdk: '...', |
| 140 | + serverApi: '...', |
| 141 | + whatsNew: '...', |
| 142 | + supportForum: '...', |
| 143 | + signIn: '...', |
| 144 | + signUpFree: '...', |
| 145 | + searchPlaceholder: '...', |
| 146 | + searchNoResults: '...', |
| 147 | +}, |
| 148 | +``` |
| 149 | + |
| 150 | +### 1h. `src/layouts/DocsLayout.astro` |
| 151 | + |
| 152 | +Add auto-redirect logic to the inline script (around lines 87-95): |
| 153 | + |
| 154 | +```js |
| 155 | +} else if (lang.startsWith('{LOCALE}')) { |
| 156 | + localStorage.setItem('preferred-locale', '{LOCALE}'); |
| 157 | + var newPath = window.location.pathname.replace(/^(\/docs\/)/, '$1{LOCALE}/'); |
| 158 | + if (newPath !== window.location.pathname) window.location.replace(newPath); |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Step 2 — Add env vars |
| 165 | + |
| 166 | +In `.env` (and in the deployment environment / Vercel env vars): |
| 167 | + |
| 168 | +``` |
| 169 | +PUBLIC_ALGOLIA_INDEX_NAME_{LOCALE_UPPER}=adapty_{LOCALE} |
| 170 | +``` |
| 171 | + |
| 172 | +e.g. for Japanese: `PUBLIC_ALGOLIA_INDEX_NAME_JA=adapty_ja` |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +## Step 3 — Create sitemap files |
| 177 | + |
| 178 | +Copy `src/pages/sitemap-tr.xml.ts` → `src/pages/sitemap-{LOCALE}.xml.ts` and change `tr` → `{LOCALE}`. |
| 179 | + |
| 180 | +Copy `src/pages/sitemap-tr-index.xml.ts` → `src/pages/sitemap-{LOCALE}-index.xml.ts` and change the URLs inside to use `sitemap-{LOCALE}.xml`. |
| 181 | + |
| 182 | +### Update `astro.config.mjs` sitemap filter |
| 183 | + |
| 184 | +In the `sitemap({ filter: ... })` call (around line 90), add the new locale to the exclusion: |
| 185 | + |
| 186 | +```js |
| 187 | +filter: (page) => !page.includes('/docs/zh/') && !page.includes('/docs/tr/') && !page.includes('/docs/{LOCALE}/'), |
| 188 | +``` |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## Step 4 — Add the `package.json` scripts (optional convenience) |
| 193 | + |
| 194 | +```json |
| 195 | +"translate:{LOCALE}": "node scripts/translate.mjs --lang {LOCALE}", |
| 196 | +"translate:{LOCALE}:build": "node scripts/translate.mjs --lang {LOCALE} --incremental", |
| 197 | +``` |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## Step 5 — Create the locale content directory |
| 202 | + |
| 203 | +``` |
| 204 | +mkdir -p src/locales/{LOCALE} |
| 205 | +``` |
| 206 | + |
| 207 | +The translation script creates `.mdx` files and `.hashes/` automatically. You only need to create the directory. |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Step 6 — Translate content (run in this order) |
| 212 | + |
| 213 | +> **Note on `CustomDocCardList`:** This component is already locale-aware — it reads titles from `_sidebar-labels.json` and descriptions from the translated article frontmatter automatically. No extra step is needed; it works correctly once sidebar labels (Step 6c `--sidebars`) and article translations are done. |
| 214 | +
|
| 215 | +All commands require `ANTHROPIC_API_KEY` to be set. |
| 216 | + |
| 217 | +### 6a. Translate the dictionary first |
| 218 | + |
| 219 | +The dictionary (`src/locales/dictionary.json`) should already have translations added manually in Step 1d. Review it before running article translations — the script uses it as a glossary. |
| 220 | + |
| 221 | +``` |
| 222 | +# Check dictionary for any missing {LOCALE} entries, then continue. |
| 223 | +``` |
| 224 | + |
| 225 | +### 6b. Translate tutorial sidebar first 7 articles |
| 226 | + |
| 227 | +These are the entry-point articles every user sees first. Translate them as a batch: |
| 228 | + |
| 229 | +```bash |
| 230 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --ids "what-is-adapty,is-adapty-right-for-me,integrate-payments,quickstart-products,quickstart-paywalls,quickstart-sdk,quickstart-test" |
| 231 | +``` |
| 232 | + |
| 233 | +### 6c. Translate sidebar labels and step-by-step docs per sidebar |
| 234 | + |
| 235 | +Translate one sidebar at a time — each command is independent and can be run, reviewed, and committed separately. |
| 236 | + |
| 237 | +**Step-by-step sidebar labels only (all sidebars at once):** |
| 238 | +```bash |
| 239 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebars |
| 240 | +``` |
| 241 | + |
| 242 | +**Or one sidebar's labels at a time:** |
| 243 | +```bash |
| 244 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar tutorial |
| 245 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar ios |
| 246 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar android |
| 247 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar react-native |
| 248 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar flutter |
| 249 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar unity |
| 250 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar kmp |
| 251 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar capacitor |
| 252 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --sidebar api |
| 253 | +``` |
| 254 | + |
| 255 | +**Article docs, one platform/sidebar at a time:** |
| 256 | +```bash |
| 257 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform tutorial |
| 258 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform ios |
| 259 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform android |
| 260 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform react-native |
| 261 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform flutter |
| 262 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform unity |
| 263 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform kmp |
| 264 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --platform capacitor |
| 265 | +``` |
| 266 | + |
| 267 | +### 6d. Translate API docs |
| 268 | + |
| 269 | +```bash |
| 270 | +ANTHROPIC_API_KEY=sk-... node scripts/translate.mjs --lang {LOCALE} --api-specs |
| 271 | +``` |
| 272 | + |
| 273 | +--- |
| 274 | + |
| 275 | +## Step 7 — Algolia: update English crawler + create new locale crawler |
| 276 | + |
| 277 | +### 7a. Add locale to the English crawler exclusion pattern |
| 278 | + |
| 279 | +In the Algolia dashboard, open the **English crawler config** and add `/{LOCALE}/` to the URL exclusion pattern so English-only content is not contaminated with locale paths. The existing exclusion already covers `zh` and `tr` — add `{LOCALE}` alongside them. |
| 280 | + |
| 281 | +### 7b. Create a new crawler for the locale |
| 282 | + |
| 283 | +In Algolia, create a new crawler pointing to: |
| 284 | +- **Start URL:** `https://adapty.io/docs/{LOCALE}/` |
| 285 | +- **Sitemap:** `https://adapty.io/docs/sitemap-{LOCALE}-index.xml` |
| 286 | +- Index name: `adapty_{LOCALE}` |
| 287 | + |
| 288 | +Use the existing `zh` or `tr` crawler config as a template — the only changes are the start URL, sitemap URL, and index name. |
| 289 | + |
| 290 | +### 7c. Create the Algolia index |
| 291 | + |
| 292 | +Create a new index named `adapty_{LOCALE}` in Algolia. Copy replica/ranking settings from the `adapty_zh` index to ensure consistent relevance tuning. |
| 293 | + |
| 294 | +--- |
| 295 | + |
| 296 | +## Step 8 — Update GitHub Actions deploy workflows |
| 297 | + |
| 298 | +The `translate.yml` workflow is locale-agnostic (uses `src/locales/*/` glob) — no changes needed there. |
| 299 | + |
| 300 | +Both deploy workflows have `[zh, tr]` hardcoded and **must be updated** in multiple places each. |
| 301 | + |
| 302 | +### `s3-deploy-production.yml` |
| 303 | + |
| 304 | +**1. `build-locale-full` matrix** (line ~99): |
| 305 | +```yaml |
| 306 | +strategy: |
| 307 | + matrix: |
| 308 | + locale: [zh, tr, {LOCALE}] |
| 309 | +``` |
| 310 | +
|
| 311 | +**2. `deploy-full` artifact downloads** (lines ~150–156) — add a new step alongside the existing `build-zh` and `build-tr` blocks: |
| 312 | +```yaml |
| 313 | +- uses: actions/download-artifact@v4 |
| 314 | + with: |
| 315 | + name: build-{LOCALE} |
| 316 | + path: build/{LOCALE}/ |
| 317 | +``` |
| 318 | + |
| 319 | +**3. `build-locale-only` matrix** (line ~183): |
| 320 | +```yaml |
| 321 | +strategy: |
| 322 | + matrix: |
| 323 | + locale: [zh, tr, {LOCALE}] |
| 324 | +``` |
| 325 | + |
| 326 | +**4. `deploy-translations` artifact downloads** (lines ~229–235) — add a new step alongside the existing `build-tr-only-zh` and `build-tr-only-tr` blocks: |
| 327 | +```yaml |
| 328 | +- uses: actions/download-artifact@v4 |
| 329 | + with: |
| 330 | + name: build-tr-only-{LOCALE} |
| 331 | + path: build/{LOCALE}/ |
| 332 | +``` |
| 333 | + |
| 334 | +**5. `deploy-translations` S3 sync** (lines ~241–242): |
| 335 | +```bash |
| 336 | +aws s3 sync build/{LOCALE}/ s3://${{ secrets.S3_BUCKET }}/{LOCALE}/ --delete |
| 337 | +``` |
| 338 | + |
| 339 | +**6. `deploy-translations` CloudFront invalidation** (lines ~246–248): |
| 340 | +```bash |
| 341 | +aws cloudfront create-invalidation \ |
| 342 | + --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ |
| 343 | + --paths "/zh/*" "/tr/*" "/{LOCALE}/*" |
| 344 | +``` |
| 345 | + |
| 346 | +### `s3-deploy-development.yml` |
| 347 | + |
| 348 | +**1. `build-locale` matrix** (line ~47): |
| 349 | +```yaml |
| 350 | +strategy: |
| 351 | + matrix: |
| 352 | + locale: [zh, tr, {LOCALE}] |
| 353 | +``` |
| 354 | + |
| 355 | +**2. `deploy` artifact downloads** (lines ~96–104) — add a new step: |
| 356 | +```yaml |
| 357 | +- uses: actions/download-artifact@v4 |
| 358 | + with: |
| 359 | + name: build-{LOCALE} |
| 360 | + path: build/{LOCALE}/ |
| 361 | +``` |
| 362 | + |
| 363 | +--- |
| 364 | + |
| 365 | +## Complete checklist |
| 366 | + |
| 367 | +| # | What | File(s) | |
| 368 | +|---|------|---------| |
| 369 | +| 1a | Add to `SUPPORTED_LOCALES` + `LOCALE_NAMES` | `src/data/locales.ts` | |
| 370 | +| 1b | Add to `LANGUAGE_NAMES` + `METADATA_TITLE_SUFFIXES` | `scripts/translate.mjs` | |
| 371 | +| 1c | Add UI string translations for all groups | `src/locales/ui-strings.ts` | |
| 372 | +| 1d | Add dictionary translations | `src/locales/dictionary.json` | |
| 373 | +| 1e | Add to `LOCALE_INDEX` + `data-index-name-{LOCALE}` attr | `src/components/Search.astro` | |
| 374 | +| 1f | Add locale key to `T` object (~20 strings) | `src/components/Homepage.tsx` | |
| 375 | +| 1g | Add to `LOCALE_NAMES_CLIENT` + `UI_STRINGS_CLIENT` | `src/components/Header.astro` | |
| 376 | +| 1h | Add auto-redirect block | `src/layouts/DocsLayout.astro` | |
| 377 | +| 2 | Add env vars | `.env` + deployment config | |
| 378 | +| 3 | Create sitemap files + update astro.config.mjs | `src/pages/sitemap-{LOCALE}*.xml.ts`, `astro.config.mjs` | |
| 379 | +| 4 | Add npm scripts (optional) | `package.json` | |
| 380 | +| 5 | Create content directory | `src/locales/{LOCALE}/` | |
| 381 | +| 6a | Review/complete dictionary | `src/locales/dictionary.json` | |
| 382 | +| 6b | Translate first 7 tutorial articles | `--ids` command | |
| 383 | +| 6c | Translate sidebar labels + platform docs | `--sidebars` + `--platform` commands | |
| 384 | +| 6d | Translate API specs | `--api-specs` command | |
| 385 | +| 7a | Update English crawler exclusion | Algolia dashboard | |
| 386 | +| 7b | Create new locale crawler | Algolia dashboard | |
| 387 | +| 7c | Create new Algolia index | Algolia dashboard | |
| 388 | +| 8a | Add to `matrix: locale: [...]` (×2 jobs) | `s3-deploy-production.yml` | |
| 389 | +| 8b | Add artifact download + S3 sync + CloudFront invalidation | `s3-deploy-production.yml` | |
| 390 | +| 8c | Add to `matrix: locale: [...]` + artifact download | `s3-deploy-development.yml` | |
0 commit comments