ai-translated-localization
Localization scope and AI-translation execution for Apple-platform Apps on `Localizable.xcstrings` — default locale set, source / primary language rule, per-locale review gotchas, completeness gates. Use when deciding which locales to ship at project setup; when adding or refresh
Install
npx skills add https://github.com/wei18/apple-dev-skills/tree/main/apple-dev-skills/skills/ai-translated-localization
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wei18-apple-dev-skills@llmmart
git clone https://github.com/wei18/apple-dev-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wei18/apple-dev-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
AI-Translated Localization
When to invoke
Scope decisions:
- Starting a new App and deciding which locales to support.
- Choosing a string catalog format (
.stringsvs.xcstrings). - Planning the translation flow (manual / agency / AI agent).
- User asks "are 7 locales too many", "how do translations enter git", "how to handle multi-locale App Store metadata".
Execution:
- Adding new user-facing strings (UI keys, GC titles, ASC metadata, achievement descriptions) and need to fill out their non-source locales.
- Refreshing strings whose source text changed after translation shipped (locales showing
stringUnit.state: needs_reviewin xcstrings — not to be confused withextractionState: stale, which flags a key no longer referenced in code, not a changed source). - Auditing whether a release's xcstrings is complete (every key has every locale, no
<TRANSLATE>placeholders shipping).
Default decisions
Default 7 locales
| Locale | Code | Notes |
|---|---|---|
| English | en |
Catalog sourceLanguage; translation source for the fan-out |
| Traditional Chinese | zh-Hant |
Primary language (author's native locale in the origin project; substitute yours) — author-written alongside en, never AI-translated |
| Japanese | ja |
Largest adjacent market outside the Chinese sphere |
| Simplified Chinese | zh-Hans |
Converted from zh-Hant + Mainland phrasing review |
| Spanish | es |
World's second largest native-speaker base |
| Thai | th |
Southeast Asia representative |
| Korean | ko |
High-penetration Asian market |
- Locale codes are the script-based BCP-47 forms (
zh-Hant/zh-Hans), not region forms (zh-TW/zh-CN) — matches the committed catalogs and, if the repo has one (recommended, see Step 5), its L10n completeness gate. - Minimum set: zh-Hant + en (every project includes at least these two).
- Per-project locale lists can be adjusted, but English and zh-Hant are always included.
Translation flow
- Handled by an AI agent, recorded as a step in
plan.md: "authoren+zh-Hantby hand, fan out the other 5 locales fromen, write intoLocalizable.xcstrings" (see Execution playbook Step 1). - Covers:
- In-app strings
- Game Center leaderboard / achievement names (for games)
- App Store metadata (title / description / keywords / what's new)
- Info.plist purpose strings (
NS…UsageDescription, incl.NSUserTrackingUsageDescription) inInfoPlist.xcstrings— the privacy manifest itself has no localizable text
- Every time strings are added / modified, run another round of the AI translation flow; the diff lands in a PR.
Catalog format
- Use Xcode's
Localizable.xcstrings(String Catalog, introduced in Xcode 15+). - Stop using legacy
.strings/.stringsdict(unless an external tool forces it). - xcstrings is JSON-structured: PR-friendly diffs and easy for AI to manipulate.
Rationale
- 7 locales cover most of the global market while remaining a polish scope a solo developer can sustain.
- AI translation quality for App UI strings (short, clear context) is at commercial level; long marketing copy is still recommended for human review.
- xcstrings JSON structure is naturally friendly to AI / diff / version control.
- Primary = the author's native locale (
zh-Hantin the origin project);enis the fan-out source because translator competence is broader from English (see Field notes).
Deviation considerations
- Focused target market: shrink to zh-Hant + en + one target-market locale.
- No budget / no time: ship zh-Hant + en first; leave the other locales absent, or set them to
stringUnit.state: new, for later — notextractionState: stale, which marks a key no longer used in code, not a not-yet-translated locale. - Regulated / sensitive content (medical / financial / kids): mandatory human review after AI translation; add a review step to
plan.md. - Special scripts / RTL (Arabic / Hebrew): UI needs additional layout verification, not just translation.
Execution playbook
Use when actually performing a translation pass. The flow is source-pair seed → AI fan-out → tricky-case review → completeness check.
Step 1 — Seed source pair (en + zh-Hant)
- Source language for translation must be English (broader translator competence across all target locales than zh-Hant).
- Authors write English first; the primary locale (the author's native locale —
zh-Hantin the origin project) is hand-written in parallel — not AI-translated from English. Both are written by the author with intent; the other 5 locales fan out fromen. - Each new key lands in xcstrings with at minimum
en+zh-Hantpopulated andextractionState: manual. - Other 5 locales (
ja,zh-Hans,es,th,ko) start either absent or with the placeholder string<TRANSLATE>so the fan-out pass can find them with a single grep.
Step 2 — AI fan-out pass
For each target locale, translate each key from en (source-of-truth) into the target locale. The AI doing the translation should:
- Preserve substitutions:
%@,%lld,%1$@,\n, markdown markers (**,_), and any${...}interpolation tokens are copied verbatim. - Match plural/variation forms: if xcstrings declares plural variations for
en, all forms must exist in the target locale (zero/one/two/few/many/other) per CLDR plural categories for that locale — don't blindly copy English's two forms. - Match length budget: if source is a button label (≤ 12 chars typical), keep target short. Don't let "OK" become a 6-word phrase. UI labels lose to long translations.
- Match register / tone: derive from
en's tone. Buttons are imperative, descriptions are neutral, error messages are direct. Don't add politeness markers absent in source (see locale gotchas below). - Preserve product nouns: app name, brand terms, mode names that share visual identity across locales (e.g., "Practice" → 練習 in both zh-Hant and ja for visual consistency). Maintain a small glossary captured per-project to enforce this.
After translation, set that locale's stringUnit.state to translated — translated is a value of stringUnit.state, not of extractionState; leave extractionState at manual or extracted_with_value — so Xcode no longer flags the locale as needing attention.
Step 3 — Tricky-case review (locale-specific gotchas)
Lessons captured from real translation passes, to apply as a second-pass review after the bulk
AI fan-out — per-locale gotchas for ja, th, ko, es, zh-Hans, and en are in
references/locale-gotchas.md.
Step 4 — Visual / glossary consistency
Beyond per-string correctness, enforce a project-level glossary so the same concept renders identically across locales and screens:
- Mode names (e.g., "Daily" / "Practice") — pick one term per locale and use it everywhere (HomeView card, hub header, navigation title, Settings labels).
- Product nouns (app name, branded features) — don't translate.
- Difficulty labels (Easy / Medium / Hard) — short, consistent.
- Action verbs (Submit / Confirm / Cancel) — match iOS native terminology in each locale (Apple has localized Human Interface Guidelines for major locales).
Maintain the glossary as either inline comments in xcstrings, or a sidecar Localization-Glossary.md in docs/ if it grows.
Step 5 — Completeness check (before PR)
Verification gates before merging a translation pass:
<TRANSLATE>count across xcstrings = 0 (none shipped).stringUnit.state: needs_reviewcount = 0 across all locales (source text changed since last translation, now resolved).stringUnit.state: newcount = 0 across all locales (nothing left untranslated).extractionState: stalecount = 0 (no orphaned keys left in the catalog that the source code no longer references).- Per-key locale coverage = 100% (parse xcstrings JSON; every
localizationsdict has every declared locale). - Same coverage for
InfoPlist.xcstrings: every purpose string (NS…UsageDescription, incl.NSUserTrackingUsageDescription) has every shipped locale — the purpose-string rules themselves are inapp-store-review-rejections's privacy-manifest reference. - For plural keys: every locale has every plural form required by CLDR for that locale.
- Substitution token parity per key:
enhas N%@→ all locales have N%@(or locale-specific reordering via%1$@/%2$@). - Spot-check 3-5 keys per locale visually in the simulator with
Scheme → Run → Options → App Language.
L10n gate scope — and its two blind spots
If the repo has a per-key completeness gate (recommended, see Step 5), it checks per-key
locale completeness: every key present in a catalog has all declared locales, no <TRANSLATE>.
For the two blind spots it does NOT catch (a required key being absent entirely, and
multi-app repos sharing UI modules only) — both have shipped English-fallback bugs in real
projects — read references/l10n-gates.md.
xcstrings editing footgun
When adding keys to a Localizable.xcstrings, text-splice the new entries
into the file — do not round-trip the whole catalog through a Python/JSON
load → dump. The round-trip reformats Xcode's style ("k" : v space-before-
colon + Xcode key order) and produces a multi-thousand-line noise diff that
buries the real change. Splicing keeps a clean, reviewable diff (real example:
4400-line noise diff collapsed to 192 lines after switching to splice approach).
Tooling notes
For the xcstrings JSON schema (including the "version" field and plural variations) and LLM fan-out / ASC-metadata tooling notes, read references/xcstrings-format.md.
Verification checklist
Localizable.xcstringsexists and each key has an entry for every declared locale (2 for the minimum set).- App Store Connect metadata is complete per locale (including screenshot captions).
- Game Center / achievement display names are complete per locale.
PrivacyInfo.xcprivacyhas no localizable text; the Info.plist purpose strings inInfoPlist.xcstringsand the App Store privacy policy page do need every shipped locale.
Related skills
apple-platform-targets: xcstrings requires Xcode 15+; aligns with the deployment target's toolchain.collaboration-skills:spec-phase-orchestration: "translation" should be an explicit step inplan.md.asc-api-automation: uploading localized App Store metadata once the strings are final.- Official sources: when verifying or updating a factual or version-sensitive claim, read
references/official-docs.md.
Field notes
Real translation passes have surfaced these recurring decisions:
- Source =
en, primary = the author's native locale (zh-Hantin the origin project) — both written by author. The other 5 fan out fromenbecause translator competence is broader from English than from Chinese forth/ko/es. - Glossary beats per-string correctness — visual consistency across screens matters more than the perfect translation of any single string.
- 5 locales in one pass is the sweet spot — fewer wastes the per-pass setup; more risks AI fatigue degradation on the last locales. 1 commit per locale keeps PR review tractable.
Files (apple-dev-skills)
-
references
-
l10n-gates.md 1.9 KB
# L10n Gate Scope And Blind Spots ### L10n gate scope — and its two blind spots If the repo has a per-key completeness gate (recommended, see Step 5), it checks **per-key locale completeness**: every key present in a catalog has all declared locales, no `<TRANSLATE>`. Two things it does **NOT** catch — both have shipped English-fallback bugs in real projects: 1. **A required key being *absent* entirely.** The gate validates keys that exist; it cannot know a newly-activated capability *needs* a key that no catalog has. When an app adopts a shared feature (audio settings, ATT primer, reminders), its catalog can ship missing keys → the UI renders raw dotted keys or English literals, and the gate stays green. After wiring any shared-UI capability into a new app, **diff its catalog's key set against an app that already has the feature** (real-world: a new game adopted shared audio-settings UI without copying the required catalog keys; another adopted an ATT primer and the raw dotted keys appeared at runtime — both passed the gate). 2. **Multi-app repos sharing UI modules only:** a key referenced from shared UI code but absent from an app's catalog. Per-key completeness alone will NOT catch this — the gate only validates keys that already exist in a catalog, so a key referenced from a shared UI module that the app's own catalog never declares renders raw at runtime while the gate stays green. **Build a shared-code dotted-key gate:** every dotted-namespace key (e.g. `leave.game.close`, `att.primer.title`) referenced from a shared UI module (SharedUI / SettingsUI / any shared UI module) must exist in **every** app catalog or CI fails. Scope it to *dotted* keys to avoid app-conditional English-phrase false positives — English-phrase shared keys are a separate, harder case (no dotted namespace to anchor on; track them with a dedicated audit rather than this gate). -
locale-gotchas.md 2.3 KB
# Locale Gotchas ### Step 3 — Tricky-case review (locale-specific gotchas) Lessons captured from real translation passes. Apply these as a second-pass review after the bulk AI fan-out. **Japanese (`ja`):** - Prefer **semantic** over literal. "Pencil" (notes / candidate input) → メモ, NOT 鉛筆. The literal kanji confuses Japanese users; the semantic UI term is correct. - Drop politeness markers (`です` / `ます`) in button labels and short UI strings — Japanese app UIs are typically declarative/imperative, not polite. - 漢字 vs かな: prefer 漢字 for nouns, ひらがな for particles, カタカナ for loanwords. Don't romanize unless the source is romanized. **Thai (`th`):** - **No politeness markers** (`ครับ` / `ค่ะ`) in UI strings unless the app's voice is deliberately conversational. Calm/neutral apps drop them. - Compound nouns: "leaderboard" → กระดานผู้นำ (no single-word equivalent). Accept the multi-word form. - Thai has **no word boundaries** — sentence length affects line break behavior. UI strings >24 chars need spot-check rendering. **Korean (`ko`):** - Use **informal-formal** style (해요체) for app UI by default — neither too casual (반말) nor overly formal (합쇼체). - 한자 should be avoided unless disambiguation is needed; pure 한글 is the modern default. - Postpositions (조사) change based on preceding character's final consonant; AI usually handles this, but spot-check `을/를`, `이/가`, `은/는`. **Spanish (`es`):** - Default to **neutral Latin American Spanish** unless the project explicitly targets Spain (`es-ES`). Avoid `vosotros` forms; use `ustedes`. - Gender agreement: nouns referring to the user (e.g., "completed") need gender-neutral phrasing if the user's gender is unknown. **Simplified Chinese (`zh-Hans`):** - Convert from `zh-Hant` (not from `en`) for terminology consistency; the AI still needs to substitute Mainland-preferred terms (软件 vs 軟體, 移动 vs 行動, 视频 vs 影片). - DON'T just run `tongwen` character conversion — phrase choice differs (e.g., zh-Hant 「設定」 → zh-Hans 「设置」, not 「設定 → 设定」). **English (`en`):** - US English by default. UK spellings (`colour`, `centre`, `analyse`) only if explicitly targeted. - Sentence case for buttons and UI; Title Case only for proper nouns and app section headers. -
official-docs.md 1.3 KB
Official pages backing this skill's claims; read when verifying or updating a factual or version-sensitive claim. | Page | URL | Backs | |---|---|---| | Localizing and varying text with a string catalog | https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog | Catalog format section; the `translated` state in Step 2 | | Discover String Catalogs (WWDC23) | https://developer.apple.com/videos/play/wwdc2023/10155/ | New / Needs Review / Stale semantics; String Catalogs introduced in Xcode 15 | | Testing localizations when running your app | https://developer.apple.com/documentation/xcode/testing-localizations-when-running-your-app | Step 5 App Language spot-check | | Requesting access to protected resources | https://developer.apple.com/documentation/uikit/requesting-access-to-protected-resources | Naming the string catalog `InfoPlist.xcstrings` | | Privacy manifest files | https://developer.apple.com/documentation/bundleresources/privacy-manifest-files | Privacy manifests carry no localizable text | | Plural Rules (CLDR) | https://cldr.unicode.org/index/cldr-spec/plural-rules | CLDR plural categories | | App Store localizations | https://developer.apple.com/help/app-store-connect/reference/app-information/app-store-localizations | ASC metadata locale list | -
xcstrings-format.md 987 B
# Xcstrings Format And Tooling Notes ### Tooling notes - xcstrings is JSON; parse with any JSON library. Schema: `{"version": "1.0", "sourceLanguage": "en", "strings": {<key>: {"localizations": {<locale>: {"stringUnit": {"state": "translated", "value": "..."}}}}}}`. The top-level `"version"` field auto-bumps to `1.1` when Xcode 26's type-safe symbol generation or AI-generated comments touch the catalog — preserve it verbatim when splicing (see the splice footgun above). - Plural variations use `{"variations": {"plural": {<cldr-form>: {"stringUnit": {...}}}}}` instead of a single `stringUnit`. - When fanning out via an LLM, send a single key + source + glossary in the prompt; don't batch hundreds at once (latency wins less than accuracy loses). - For ASC metadata (App Store description, keywords, "what's new"), use the **same flow** but route to the ASC API endpoints rather than xcstrings. The translation principles (length budget, register, gotchas) apply identically.
-
-
SKILL.md 11.8 KB
--- name: ai-translated-localization description: 'Localization scope and AI-translation execution for Apple-platform Apps on `Localizable.xcstrings` — default locale set, source / primary language rule, per-locale review gotchas, completeness gates. Use when deciding which locales to ship at project setup; when adding or refreshing user-facing strings (UI keys, Game Center, App Store metadata) in non-source locales; when `extractionState: stale` entries or untranslated placeholder markers appear; or when asked "how many locales", "how do translations enter git". Does NOT cover RTL layout or ASC upload mechanics → asc-api-automation.' --- # AI-Translated Localization ## When to invoke **Scope decisions:** - Starting a new App and deciding which locales to support. - Choosing a string catalog format (`.strings` vs `.xcstrings`). - Planning the translation flow (manual / agency / AI agent). - User asks "are 7 locales too many", "how do translations enter git", "how to handle multi-locale App Store metadata". **Execution:** - Adding new user-facing strings (UI keys, GC titles, ASC metadata, achievement descriptions) and need to fill out their non-source locales. - Refreshing strings whose source text changed after translation shipped (locales showing `stringUnit.state: needs_review` in xcstrings — not to be confused with `extractionState: stale`, which flags a key no longer referenced in code, not a changed source). - Auditing whether a release's xcstrings is complete (every key has every locale, no `<TRANSLATE>` placeholders shipping). ## Default decisions ### Default 7 locales | Locale | Code | Notes | |---|---|---| | English | `en` | Catalog `sourceLanguage`; translation source for the fan-out | | Traditional Chinese | `zh-Hant` | Primary language (author's native locale in the origin project; substitute yours) — author-written alongside `en`, never AI-translated | | Japanese | `ja` | Largest adjacent market outside the Chinese sphere | | Simplified Chinese | `zh-Hans` | Converted from `zh-Hant` + Mainland phrasing review | | Spanish | `es` | World's second largest native-speaker base | | Thai | `th` | Southeast Asia representative | | Korean | `ko` | High-penetration Asian market | - Locale codes are the **script-based BCP-47 forms** (`zh-Hant` / `zh-Hans`), not region forms (`zh-TW` / `zh-CN`) — matches the committed catalogs and, if the repo has one (recommended, see Step 5), its L10n completeness gate. - **Minimum set**: zh-Hant + en (every project includes at least these two). - Per-project locale lists can be adjusted, but **English and zh-Hant are always included**. ### Translation flow - **Handled by an AI agent**, recorded as a step in `plan.md`: "author `en` + `zh-Hant` by hand, fan out the other 5 locales from `en`, write into `Localizable.xcstrings`" (see Execution playbook Step 1). - Covers: - In-app strings - Game Center leaderboard / achievement names (for games) - App Store metadata (title / description / keywords / what's new) - Info.plist purpose strings (`NS…UsageDescription`, incl. `NSUserTrackingUsageDescription`) in `InfoPlist.xcstrings` — the privacy manifest itself has no localizable text - Every time strings are added / modified, run another round of the AI translation flow; the diff lands in a PR. ### Catalog format - Use Xcode's **`Localizable.xcstrings`** (String Catalog, introduced in Xcode 15+). - Stop using legacy `.strings` / `.stringsdict` (unless an external tool forces it). - xcstrings is JSON-structured: PR-friendly diffs and easy for AI to manipulate. ## Rationale - 7 locales cover most of the global market while remaining a polish scope a solo developer can sustain. - AI translation quality for App UI strings (short, clear context) is at commercial level; long marketing copy is still recommended for human review. - xcstrings JSON structure is naturally friendly to AI / diff / version control. - Primary = the author's native locale (`zh-Hant` in the origin project); `en` is the fan-out source because translator competence is broader from English (see Field notes). ## Deviation considerations - **Focused target market**: shrink to zh-Hant + en + one target-market locale. - **No budget / no time**: ship zh-Hant + en first; leave the other locales absent, or set them to `stringUnit.state: new`, for later — not `extractionState: stale`, which marks a key no longer used in code, not a not-yet-translated locale. - **Regulated / sensitive content** (medical / financial / kids): **mandatory human review** after AI translation; add a review step to `plan.md`. - **Special scripts / RTL** (Arabic / Hebrew): UI needs additional layout verification, not just translation. ## Execution playbook Use when actually performing a translation pass. The flow is **source-pair seed → AI fan-out → tricky-case review → completeness check**. ### Step 1 — Seed source pair (en + zh-Hant) - Source language for translation **must be English** (broader translator competence across all target locales than zh-Hant). - Authors write English first; the primary locale (the author's native locale — `zh-Hant` in the origin project) is hand-written in parallel — *not* AI-translated from English. Both are written by the author with intent; the other 5 locales fan out from `en`. - Each new key lands in xcstrings with **at minimum** `en` + `zh-Hant` populated and `extractionState: manual`. - Other 5 locales (`ja`, `zh-Hans`, `es`, `th`, `ko`) start either absent or with the placeholder string `<TRANSLATE>` so the fan-out pass can find them with a single grep. ### Step 2 — AI fan-out pass For each target locale, translate each key from `en` (source-of-truth) into the target locale. The AI doing the translation should: - **Preserve substitutions**: `%@`, `%lld`, `%1$@`, `\n`, markdown markers (`**`, `_`), and any `${...}` interpolation tokens are copied verbatim. - **Match plural/variation forms**: if xcstrings declares plural variations for `en`, all forms must exist in the target locale (`zero` / `one` / `two` / `few` / `many` / `other`) per CLDR plural categories for that locale — don't blindly copy English's two forms. - **Match length budget**: if source is a button label (≤ 12 chars typical), keep target short. Don't let "OK" become a 6-word phrase. UI labels lose to long translations. - **Match register / tone**: derive from `en`'s tone. Buttons are imperative, descriptions are neutral, error messages are direct. Don't add politeness markers absent in source (see locale gotchas below). - **Preserve product nouns**: app name, brand terms, mode names that share visual identity across locales (e.g., "Practice" → 練習 in both zh-Hant and ja for visual consistency). Maintain a small **glossary** captured per-project to enforce this. After translation, set that locale's `stringUnit.state` to `translated` — `translated` is a value of `stringUnit.state`, not of `extractionState`; leave `extractionState` at `manual` or `extracted_with_value` — so Xcode no longer flags the locale as needing attention. ### Step 3 — Tricky-case review (locale-specific gotchas) Lessons captured from real translation passes, to apply as a second-pass review after the bulk AI fan-out — per-locale gotchas for `ja`, `th`, `ko`, `es`, `zh-Hans`, and `en` are in `references/locale-gotchas.md`. ### Step 4 — Visual / glossary consistency Beyond per-string correctness, enforce a project-level glossary so the same concept renders identically across locales and screens: - **Mode names** (e.g., "Daily" / "Practice") — pick one term per locale and use it everywhere (HomeView card, hub header, navigation title, Settings labels). - **Product nouns** (app name, branded features) — don't translate. - **Difficulty labels** (Easy / Medium / Hard) — short, consistent. - **Action verbs** (Submit / Confirm / Cancel) — match iOS native terminology in each locale (Apple has localized Human Interface Guidelines for major locales). Maintain the glossary as either inline comments in xcstrings, or a sidecar `Localization-Glossary.md` in `docs/` if it grows. ### Step 5 — Completeness check (before PR) Verification gates before merging a translation pass: - `<TRANSLATE>` count across xcstrings = 0 (none shipped). - `stringUnit.state: needs_review` count = 0 across all locales (source text changed since last translation, now resolved). - `stringUnit.state: new` count = 0 across all locales (nothing left untranslated). - `extractionState: stale` count = 0 (no orphaned keys left in the catalog that the source code no longer references). - Per-key locale coverage = 100% (parse xcstrings JSON; every `localizations` dict has every declared locale). - Same coverage for `InfoPlist.xcstrings`: every purpose string (`NS…UsageDescription`, incl. `NSUserTrackingUsageDescription`) has every shipped locale — the purpose-string rules themselves are in `app-store-review-rejections`'s privacy-manifest reference. - For plural keys: every locale has every plural form required by CLDR for that locale. - Substitution token parity per key: `en` has N `%@` → all locales have N `%@` (or locale-specific reordering via `%1$@` / `%2$@`). - Spot-check 3-5 keys per locale visually in the simulator with `Scheme → Run → Options → App Language`. ### L10n gate scope — and its two blind spots If the repo has a per-key completeness gate (recommended, see Step 5), it checks **per-key locale completeness**: every key present in a catalog has all declared locales, no `<TRANSLATE>`. For the two blind spots it does **NOT** catch (a required key being absent entirely, and multi-app repos sharing UI modules only) — both have shipped English-fallback bugs in real projects — read `references/l10n-gates.md`. ### xcstrings editing footgun When adding keys to a `Localizable.xcstrings`, **text-splice** the new entries into the file — do **not** round-trip the whole catalog through a Python/JSON `load → dump`. The round-trip reformats Xcode's style (`"k" : v` space-before- colon + Xcode key order) and produces a multi-thousand-line noise diff that buries the real change. Splicing keeps a clean, reviewable diff (real example: 4400-line noise diff collapsed to 192 lines after switching to splice approach). ### Tooling notes For the xcstrings JSON schema (including the `"version"` field and plural variations) and LLM fan-out / ASC-metadata tooling notes, read `references/xcstrings-format.md`. ## Verification checklist - `Localizable.xcstrings` exists and each key has an entry for every declared locale (2 for the minimum set). - App Store Connect metadata is complete per locale (including screenshot captions). - Game Center / achievement display names are complete per locale. - `PrivacyInfo.xcprivacy` has no localizable text; the Info.plist purpose strings in `InfoPlist.xcstrings` and the App Store privacy policy page do need every shipped locale. ## Related skills - `apple-platform-targets`: xcstrings requires Xcode 15+; aligns with the deployment target's toolchain. - `collaboration-skills:spec-phase-orchestration`: "translation" should be an explicit step in `plan.md`. - `asc-api-automation`: uploading localized App Store metadata once the strings are final. - Official sources: when verifying or updating a factual or version-sensitive claim, read `references/official-docs.md`. ## Field notes Real translation passes have surfaced these recurring decisions: - **Source = `en`, primary = the author's native locale (`zh-Hant` in the origin project)** — both written by author. The other 5 fan out from `en` because translator competence is broader from English than from Chinese for `th` / `ko` / `es`. - **Glossary beats per-string correctness** — visual consistency across screens matters more than the perfect translation of any single string. - **5 locales in one pass is the sweet spot** — fewer wastes the per-pass setup; more risks AI fatigue degradation on the last locales. 1 commit per locale keeps PR review tractable.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.