iOS Internationalization Workflow
Complete workflow for managing iOS app internationalization (i18n) using SwiftGen L10n code generation. Supports any number of target languages. Covers three main scenarios: internationalizing code, verifying translation completeness, and cleaning unused entries.
When to Use
digraph when_to_use {
rankdir=TB;
"User provides code/folder for i18n?" [shape=diamond, style=filled, fillcolor=lightblue];
"User asks to check missing translations?" [shape=diamond, style=filled, fillcolor=lightgreen];
"User asks to clean unused i18n?" [shape=diamond, style=filled, fillcolor=lightyellow];
"Standardize" [shape=box, style=filled, fillcolor=lightcoral];
"CheckMissing" [shape=box, style=filled, fillcolor=lightcoral];
"CleanUp" [shape=box, style=filled, fillcolor=lightcoral];
"User provides code/folder for i18n?" -> "Standardize" [label="Yes\n(Full workflow)"];
"User asks to check missing translations?" -> "CheckMissing" [label="Yes\n(Verify only)"];
"User asks to clean unused i18n?" -> "CleanUp" [label="Yes\n(Cleanup only)"];
}
Three main scenarios:
Internationalize code - User provides code or folder: "Here's my view controller, add i18n" → Run full workflow (baseline → ALL target languages → verify → SwiftGen)
Check missing translations - User explicitly asks: "Check if translations are complete" or "Find missing i18n keys" → Run check_missing_localizations.py for each target language and fill gaps
Clean unused entries - User explicitly asks: "Clean unused localizations" or "Remove unused i18n keys" → Run clean_unused_localizations.py with dry-run first
Key principle: Complete translations for ALL target languages before verification and SwiftGen generation.
Do NOT use when:
- Projects using raw NSLocalizedString without SwiftGen
- Non-iOS platforms (Android, Web)
Identifying User Intent
Determining which scenario applies:
Scenario 1: Full Workflow
User provides code files or folder paths, says things like "internationalize this" or "add i18n to this code"
Scenario 2: Check Missing
User asks to verify translation completeness with phrases like "check if translations complete" or "find missing i18n keys"
Scenario 3: Cleanup
User requests removal of unused entries with phrases like "clean unused localizations" or "remove unused i18n keys"
Quick decision guide:
- Provides code/folder → Scenario 1 (Full workflow)
- Asks to check/verify → Scenario 2 (Check missing)
- Asks to clean/remove → Scenario 3 (Cleanup)
Red Flags - STOP and Re-read
For Scenario 1 (Full internationalization):
- "I'll just run swiftgen real quick"
- "The translations can wait"
- "I'll add the keys manually"
- "This phase doesn't apply to my case"
- "I can skip phase 4 and run it later"
- "No need to verify with scripts, trust me"
For Scenario 2 (Check missing):
- "I can just eyeball the files"
- "Checking one target language is enough"
- "I'll add keys later"
- "The other languages look fine"
For Scenario 3 (Cleanup):
- "Just delete everything without checking"
- "No need for --dry-run first"
All of these mean: Read the skill again and follow the correct workflow.
Configuration
Project-specific variables:
project_root: "/path/to/your/iOS/project"
localization_dir: "*/Localization"
baseline_language: "zh-Hans" # Source of truth (can be any language)
target_languages: ["zh-Hant", "en", "ja", "ko", "de"] # ALL target languages
generated_file: "Generated/Strings.swift"
Default structure:
<ProjectRoot>/path/<localization_dir>/
├── zh-Hans.lproj/Localizable.strings # Baseline (source of truth)
├── zh-Hant.lproj/Localizable.strings # Target 1
├── en.lproj/Localizable.strings # Target 2
└── ja.lproj/Localizable.strings # Target 3 (add more as needed)
Note: The baseline language can be any language (zh-Hans, en, ja, etc.). Choose the language you work with most frequently as baseline.
Scenario 1: Full Internationalization Workflow
Use when: User provides code/folder for internationalization
Flow: Sequential phases with iterative target language processing
Phase 1: Baseline → Phase 2+: Target Languages (repeat for ALL) → Verification → SwiftGen
Critical:
- Complete ALL target language translations sequentially before verification
- Verification step must confirm ALL languages aligned before SwiftGen
- If missing keys found, return to incomplete target language phase
Phase 1: Baseline Localization
Update baseline language file and replace hardcoded strings with L10n references.
- Extract hardcoded strings from Swift/ObjC files
- Add new/modified strings to baseline file following naming conventions
- Replace strings with
L10n.xxxcalls
Completion: Baseline updated and code references replaced.
Phase 2+: Target Language Translations (Repeat for ALL Target Languages)
For EACH target language in your configuration:
- Identify new keys from Phase 1 - Review the keys added to baseline file
- Translate each new key from baseline language to this target language
- Add translations to this target language's
.stringsfile following the same structure
Process:
- Complete ONE target language fully before moving to the next
- Copy new/modified keys from Phase 1 baseline
- Translate values (not keys) to target language
- Maintain same key names and structure as baseline
- Preserve placeholders like
%@,%d, etc.
Verification Step (CRITICAL)
After completing ALL target language translations, verify alignment:
This step confirms ALL translations are complete and aligned with baseline.
# Check each target language against baseline
for target_lang in "${target_languages[@]}"; do
python3 <skill_path>/scripts/check_missing_localizations.py \
<localization_dir>/<baseline_lang>.lproj/Localizable.strings \
<localization_dir>/${target_lang}.lproj/Localizable.strings
done
Example: python3 scripts/check_missing_localizations.py Localization/zh-Hans.lproj/Localizable.strings Localization/en.lproj/Localizable.strings
Expected output for EACH language: ✅ [target] 文件没有缺失的键。
If missing keys found: Return to the incomplete target language phase and add missing translations.
Only proceed to Final Phase after verification confirms ALL languages aligned.
Final Phase: Generate Swift Code
Critical: Run after ALL changes and verification.
cd <project_root>
swiftgen
Completion: Code generated with all L10n references available for ALL languages.
Scenario 2: Check Missing Translations
Use when: User explicitly asks "Check if translations are complete" or "Find missing i18n keys"
Workflow
- Identify baseline language (usually zh-Hans or en)
- Check EACH target language against baseline:
python3 <skill_path>/scripts/check_missing_localizations.py \
<localization_dir>/<baseline_lang>.lproj/Localizable.strings \
<localization_dir>/<target_lang>.lproj/Localizable.strings
Repeat for ALL target languages.
- Review reports - Each check shows missing keys for that target language
- Add missing translations to each target language file
- Re-run verification for all modified languages to confirm all aligned
- Run SwiftGen if any translations were added
Completion: ALL target languages have complete translations matching baseline.
Scenario 3: Clean Unused Localizations
Use when: User explicitly asks "Clean unused localizations" or "Remove unused i18n keys"
Workflow
- Dry-run first (see what would be deleted):
cd <project_root>
python3 <skill_path>/scripts/clean_unused_localizations.py --dry-run
- Review the report - Shows unused entries and usage statistics
- Run actual cleanup (after confirmation):
python3 <skill_path>/scripts/clean_unused_localizations.py
- Run SwiftGen to regenerate code:
swiftgen
Safety:
- Script creates
.backupfile before deletion - Prompts for confirmation before deleting
- Requires explicit user confirmation
Completion: Unused entries removed, SwiftGen regenerated.
Quick Reference
Scenario 1: Full Internationalization
| Task | Command/Approach | Phase |
|---|---|---|
| Extract hardcoded strings | Manual review of provided code | 1 (Baseline) |
| Add keys to baseline | Edit baseline .strings file |
1 |
| Replace with L10n | L10n.Module.key syntax |
1 |
| Translate to ALL targets | Copy keys from baseline, translate values sequentially | 2+ |
| Verify alignment | python3 scripts/check_missing_localizations.py <baseline> <target> for EACH target |
After ALL translations |
| Generate Swift code | swiftgen |
Final |
Scenario 2: Check Missing Only
| Task | Command |
|---|---|
| Check each target | python3 scripts/check_missing_localizations.py <baseline> <target> for ALL targets |
| Add missing keys | Edit target .strings files |
| Verify fixed | Re-run check script for all modified targets |
| Generate (if changed) | swiftgen |
Scenario 3: Cleanup Only
| Task | Command |
|---|---|
| Preview cleanup | python3 scripts/clean_unused_localizations.py --dry-run |
| Execute cleanup | python3 scripts/clean_unused_localizations.py |
| Regenerate | swiftgen |
Common Mistakes
| Mistake | Fix | Scenario |
|---|---|---|
| Running swiftgen before syncing ALL translations | Always complete ALL target language translations before SwiftGen | 1 |
| Skipping verification step | Always run check script after ALL target translations to confirm ALL languages aligned | 1 |
| Only verifying some target languages | Check EVERY target language against baseline before SwiftGen | 1, 2 |
| Using string interpolation on format functions | Call as function: L10n.Xxx.func(arg1, arg2) |
1 |
| Translating placeholders like %@ | Preserve placeholders exactly: %@, %d, etc. |
1, 2, 3, 4 |
| Naming keys starting with numbers | Keys must start with letters: secure3d not 3dsecure |
1, 4 |
| Skipping final phase for "later" | SwiftGen MUST run after all changes or code won't compile | 1, 2, 3 |
| Cleanup without --dry-run first | Always preview what will be deleted before actual cleanup | 3 |
| Forgetting SwiftGen after cleanup | Must regenerate code after removing unused keys | 3 |
Key Rules
0. Multi-Language Workflow (CRITICAL)
For projects with multiple target languages:
- Sequential completion: Finish ONE target language completely before starting the next
- Baseline as source: Always copy keys from baseline, never between target languages
- Verify ALL languages: Every target language must be checked against baseline before SwiftGen
Workflow: Baseline → Target1 → Target2 → ... → TargetN → Verify ALL → SwiftGen (once)
1. Localization Key Naming
Core requirements:
- Domain-like naming with hierarchical structure
- All lowercase letters, numbers (after first character), and dots (.)
- CRITICAL: Each segment MUST start with a letter (a-z), NOT a number
- Use descriptive, domain-based names for clarity
Examples:
✅ common.ok
✅ market.header.name
✅ trade.confirm.title
✅ futuresrecords.header.amount_usdt
❌ symboldetail.24h.high (number in middle) → Use symboldetail.daily.high
❌ 2fa.enabled (starts with number) → Use twofa.enabled
2. L10n Code Reference Transformation
SwiftGen auto-transforms keys to code references:
- All levels except last: Title Case (capitalize first letter only)
- Last level:
- Contains underscores (
snake_case) → convert tocamelCase - Single lowercase word → keep as-is
- Compound words without underscores → stay lowercase
- Contains underscores (
Examples:
| Localization Key | L10n Reference | Notes |
|---|---|---|
common.ok |
L10n.Common.ok |
Single level |
market.header.name |
L10n.Market.Header.name |
All Title Case |
futuresrecords.header.amount_usdt |
L10n.Futuresrecords.Header.amountUsdt |
Last: snake_case → camelCase |
paybridge.error.missingproductid |
L10n.Paybridge.Error.missingproductid |
Compound stays lowercase |
challenge.status.inprogress.full |
L10n.Challenge.Status.Inprogress.full |
inprogress → Inprogress |
3. Format Strings (with Parameters)
When localization strings contain format specifiers (%@, %d, %f, etc.), SwiftGen generates a FUNCTION, not a static property.
Format specifier examples:
%@- Any object (String, etc.)%d/%i- Integer%f- Float/Double%.2f- Formatted float%%- Literal percent (does NOT create parameter)
Usage examples:
// Static property (no format specifiers)
button.setTitle(L10n.Common.ok, for: .normal)
// Function call (with format specifiers)
label.text = L10n.Challenge.Progress.stage(phaseText, statusText)
label.text = L10n.Futurestrading.Orderbook.fundingrate(8)
For detailed examples, troubleshooting, and migration guide: See references/naming-conventions.md
Helper Scripts
Located in scripts/ directory. For detailed usage, see scripts/README.md.
| Script | Purpose | Command |
|---|---|---|
check_missing_localizations.py |
Compare files for missing keys | python3 scripts/check_missing_localizations.py <baseline> <target> |
clean_unused_localizations.py |
Find/remove unused entries | python3 scripts/clean_unused_localizations.py --dry-run |
Important Constraints
For ALL phases:
- Only add/modify missing or outdated keys identified by scripts or requirements
- Do NOT edit existing, correct keys and values
- Preserve file structure, MARK comments, and formatting
- Maintain alphabetical or logical grouping
- Only modify the specific language file for that phase
Best Practices
For All Scenarios
- Always verify with scripts before assuming completeness
- Run SwiftGen after any localization file changes
- Test translations in actual app context
For Scenario 1 (Full Workflow)
- Start with baseline phase first
- Complete ALL target language translations before running verification
- Verify EVERY target language against baseline before SwiftGen
- Complete each phase fully before next
For Scenario 2 (Check Missing)
- Check ALL target languages, not just one
- Re-run verification after adding missing keys
- Run SwiftGen if any keys were added
For Scenario 3 (Cleanup)
- Always use --dry-run first
- Review the cleanup report before confirming
- Clean periodically (e.g., before releases)
- Keep backup files until verified in app
Requirements
- Python 3.x
- SwiftGen (
brew install swiftgen) - iOS project using SwiftGen for localization
- Access to
*.lproj/Localizable.stringsfiles
Advanced Usage
For CI/CD integration, automated translation, and performance optimization, see references/advanced-usage.md.