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 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-11 01:55:42 -06:00
parent c9e5bd9909
commit 5fba9e6052

View File

@@ -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: "315"
```
### 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