From 5fba9e60523e47ad6c41c4af77a760ee92ac842d Mon Sep 17 00:00:00 2001 From: Trey t Date: Sun, 11 Jan 2026 01:55:42 -0600 Subject: [PATCH] docs: add localization design String Catalogs + AI translation pipeline for 5 languages: Spanish, French, German, Japanese, Chinese (Simplified) Co-Authored-By: Claude Opus 4.5 --- docs/plans/2026-01-11-localization-design.md | 246 +++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 docs/plans/2026-01-11-localization-design.md diff --git a/docs/plans/2026-01-11-localization-design.md b/docs/plans/2026-01-11-localization-design.md new file mode 100644 index 0000000..fa372fc --- /dev/null +++ b/docs/plans/2026-01-11-localization-design.md @@ -0,0 +1,246 @@ +# App Localization Design + +## Overview + +Localize SportsTime for international markets using Xcode String Catalogs with AI-generated translations. + +## Scope + +**What's localized:** +- UI text (buttons, labels, menus, error messages) — ~200 strings +- Locale-aware formatting (dates, distances, durations, numbers) + +**What stays in English:** +- Team names (Yankees, Lakers) +- Stadium names (Fenway Park, Madison Square Garden) +- League names (MLB, NBA, NHL, NFL) + +**Target Languages:** +- Spanish (es) +- French (fr) +- German (de) +- Japanese (ja) +- Chinese Simplified (zh-Hans) + +**Translation Approach:** +- AI-generated translations initially +- Iterate based on user feedback +- Easy to swap for professional translations later + +## Architecture + +### File Structure + +``` +SportsTime/ +├── Resources/ +│ └── Localizable.xcstrings # Single String Catalog for the app +├── Scripts/ +│ └── translate.py # AI translation script +``` + +### String Key Convention + +Use descriptive keys rather than English text: + +```swift +// Good - descriptive key +Text("trip.creation.title") // "Plan Your Trip" +Text("button.save") // "Save" +Text("error.network.offline") // "No internet connection" + +// Avoid - English as key +Text("Plan Your Trip") // Fragile if English text changes +``` + +### SwiftUI Integration + +```swift +// Static text - automatic lookup +Text("trip.detail.header") + +// Dynamic text with interpolation +Text("trip.stops.count \(count)") // Uses stringsdict for pluralization + +// Programmatic access +let message = String(localized: "error.generic") +``` + +## String Extraction Process + +### Phase 1: Audit Existing Strings + +Identify hardcoded strings in: +- View files: `Text()`, `Label()`, `Button()` +- Alerts: `.alert("...", isPresented:)` +- Error messages in ViewModels +- Navigation titles: `.navigationTitle("...")` + +### Phase 2: Replace Strings + +Replace hardcoded strings with localized references: + +```swift +// Before +Text("No trips planned yet") + +// After +Text("home.empty.title") +``` + +### Phase 3: Populate String Catalog + +Add English strings to `Localizable.xcstrings`: + +```json +{ + "sourceLanguage": "en", + "strings": { + "home.empty.title": { + "localizations": { + "en": { "stringUnit": { "value": "No trips planned yet" } } + } + } + } +} +``` + +## AI Translation Pipeline + +### Translation Script Usage + +```bash +python Scripts/translate.py --target es,fr,de,ja,zh-Hans +``` + +### How It Works + +1. Parse `Localizable.xcstrings` JSON +2. For each target language, find strings missing translations +3. Batch strings (10-20 per API call) with context +4. Write translations back to the String Catalog +5. Commit the updated file to git + +### Prompt Strategy + +``` +You are translating a sports trip planning iOS app. +The app helps users plan road trips to attend MLB, NBA, NHL, NFL games. + +Translate to Spanish (Latin America, informal "tú" form). +Keep sport names (NBA, MLB) and team names in English. +Keep translations concise for UI buttons and labels. + +Strings to translate: +- home.empty.title: "No trips planned yet" +- button.create_trip: "Plan a Trip" +- trip.stops.count: "%d stops" +``` + +### Pluralization + +String Catalogs handle plurals natively: + +```json +"trip.stops.count": { + "localizations": { + "es": { + "variations": { + "plural": { + "one": { "stringUnit": { "value": "%d parada" } }, + "other": { "stringUnit": { "value": "%d paradas" } } + } + } + } + } +} +``` + +### Quality Safeguards + +- Translations committed to git — reviewable in PRs +- String Catalog shows "Needs Review" state for AI-generated strings +- Easy to mark specific strings for professional review later + +## Locale-Aware Formatting + +### Dates and Times + +```swift +// Relative dates ("Tomorrow", "In 3 days") +game.date.formatted(.relative(presentation: .named)) + +// Game date with weekday +game.date.formatted(.dateTime.weekday(.wide).month().day()) +// English: "Saturday, March 15" +// German: "Samstag, 15. März" +// Japanese: "3月15日土曜日" +``` + +### Distances + +```swift +let distance = Measurement(value: miles, unit: UnitLength.miles) +distance.formatted(.measurement(width: .abbreviated, usage: .road)) +// US: "245 mi" +// Germany: "394 km" +``` + +### Driving Duration + +```swift +let duration = Duration.seconds(driveTimeSeconds) +duration.formatted(.units(allowed: [.hours, .minutes], width: .abbreviated)) +// English: "4 hr, 15 min" +// French: "4 h 15 min" +``` + +### Numbers + +```swift +let score = 1500 +score.formatted(.number) +// US: "1,500" +// Germany: "1.500" +``` + +## Implementation Plan + +### Phases + +1. **Setup** — Create `Localizable.xcstrings`, add translation script to `Scripts/` +2. **Extract** — Audit and extract all hardcoded strings (~200) +3. **Translate** — Run AI translation for 5 target languages +4. **Format Audit** — Verify all dates/distances use Foundation formatters +5. **Test** — QA each language in Simulator +6. **Ship** — App Store metadata localization + +### Testing Strategy + +Test in Simulator with scheme environment variable: + +``` +Product → Scheme → Edit Scheme → Run → Arguments +-AppleLanguages (es) +-AppleLocale es_MX +``` + +Key test cases per language: +- All screens render without truncation +- Plurals work correctly (0, 1, 2, many items) +- Dates/distances format correctly +- Right-to-left layout works if Arabic added later + +### App Store Metadata + +Localize separately in App Store Connect: +- App name (can stay "SportsTime" globally) +- Subtitle, description, keywords, screenshots + +### Maintenance Workflow + +When adding new strings: +1. Add to Swift code with descriptive key +2. Xcode auto-adds key to String Catalog with English value +3. Run `translate.py` to generate translations +4. Commit updated String Catalog