Localization
LOCALIZATION (.strings files + RTL/LTR + Language Switching):
FORBIDDEN PATTERNS (CRITICAL — violation = broken app):
- NEVER hardcode translations with if/else or switch on language code. Example of FORBIDDEN code:
if appLanguage == "ar" { Text("الإعدادات") } else { Text("Settings") }
switch language { case "ar": return "بحث" default: return "Search" }
- NEVER build a manual translation dictionary/map in code (e.g., let translations = ["en": "Settings", "ar": "الإعدادات"]).
- NEVER use ternary operators to pick translated strings: Text(isArabic ? "الإعدادات" : "Settings").
- These patterns bypass Apple's localization system, break when new languages are added, and ignore the environment locale.
- The ONLY correct approach: use string literals in views — Text("Settings"), Button("Save"), .navigationTitle("Dashboard") — and let Localizable.strings handle translations.
- The deployment pipeline generates .strings files automatically. Your code must ONLY contain English string literals.
.strings FILE GENERATION:
- When localization is requested, generate Resources/{lang}.lproj/Localizable.strings for EACH language.
- File format: standard Apple .strings — one "key" = "translation"; per line.
- KEYS MUST BE THE ENGLISH TEXT ITSELF. Example: "Settings" = "Settings"; (en), "Settings" = "الإعدادات"; (ar). NOT snake_case like "settings_title".
- This means Text("Settings") in code auto-localizes because the key IS the English text.
- English .strings: identity mapping (key = value). Other languages: key = translated value.
- ALL user-facing strings MUST have a key: Text(), Button(), Label(), Toggle(), navigationTitle(), Section(), alert titles/messages, ContentUnavailableView labels, placeholder text.
CRITICAL — localization key usage rules:
Rule 1 — String LITERALS in views are auto-localized:
Text("settings_title"), .navigationTitle("dashboard_title"), Label("tab_workouts", systemImage: "icon")
SwiftUI treats these as LocalizedStringKey and looks them up using the environment locale.
Rule 2 — String VARIABLES are NOT auto-localized:
let key = "settings_title"; Text(key) — shows raw key text. This is the #1 localization bug.
FIX: Text(LocalizedStringKey(key))
Rule 3 — Computed properties returning keys for display:
If a switch/computed property returns a key as String (e.g. "metric_steps"), and you pass it to Text(label), it will NOT be localized.
FIX: Return LocalizedStringKey instead of String from the computed property.
Rule 4 — NEVER use String(localized:) in view parameters:
Text(String(localized: "key")) resolves against SYSTEM locale, NOT the environment locale. Runtime language switching breaks.
Rule 5 — EVERY key in code must exist in EVERY .strings file. Missing key = raw key shown to user.
- Sample data strings stay as plain String — they are demo content, not translatable.
CONFIG_CHANGES FOR LOCALIZATIONS:
- CONFIG_CHANGES MUST include "localizations": ["en", "ar", "es"] (list of all language codes).
- The client reads this to register knownRegions in the Xcode .pbxproj and create .lproj file references.
LANGUAGE SELECTION & SWITCHING:
- Root view: @AppStorage("appLanguage") private var appLanguage: String = "en"
- Root @main app MUST apply .id(appLanguage) on RootView AND set locale/layoutDirection:
private var layoutDirection: LayoutDirection {
["ar", "he", "fa", "ur"].contains(appLanguage) ? .rightToLeft : .leftToRight
}
RootView()
.id(appLanguage) // MANDATORY: forces full view rebuild on language change
.environment(.locale, Locale(identifier: appLanguage))
.environment(.layoutDirection, layoutDirection)
- .id(appLanguage) is CRITICAL — without it, changing language causes mirrored/broken layouts because SwiftUI animates the layout direction change instead of rebuilding. With .id(), the entire view tree is destroyed and recreated cleanly.
- Setting locale ALONE does NOT flip layout to RTL. You MUST set layoutDirection explicitly.
- RTL languages: Arabic (ar), Hebrew (he), Persian/Farsi (fa), Urdu (ur).
- App restart NOT needed — .id(appLanguage) forces a full view rebuild when @AppStorage changes.
- Settings screen: add a language picker (Picker or List with checkmark) that writes to @AppStorage("appLanguage").
- Display language name: Locale(identifier: code).localizedString(forLanguageCode: code) ?? code
RTL / LTR LAYOUT DIRECTION:
- .environment(.layoutDirection) MUST be set explicitly — .environment(.locale) does NOT set it automatically.
- Use .leading/.trailing (never .left/.right) for alignment, padding, edges.
- Icons that represent direction (back arrows, chevrons, progress bars) MUST call .flipsForRightToLeftLayoutDirection(true).
- Decorative/universal icons (checkmarks, stars, hearts) must NOT flip.
- Text alignment: always use .leading — SwiftUI resolves it to left or right based on layoutDirection.
- Padding/spacing: always use .leading/.trailing edges, never .left/.right (Edge.Set).
LOCALE-AWARE FORMATTING:
- Dates: use .formatted(date:time:) or Text(date, format:) — automatically adapts to user's locale/calendar.
- Numbers: use .formatted() or Text(number, format: .number) — respects locale decimal/grouping separators.
- Currency: use .formatted(.currency(code:)) — locale-aware symbol placement and formatting.
- Measurements: use Measurement + MeasurementFormatter for locale-appropriate units.
- NEVER manually format dates/numbers with hardcoded separators or patterns.
TESTING RTL IN PREVIEWS:
- Add preview with RTL locale: .environment(.locale, Locale(identifier: "ar")) and .environment(.layoutDirection, .rightToLeft)
- Verify: text alignment flips, HStack order reverses, directional icons mirror, padding sides swap.
1---2name: localization3description: Localization patterns: .strings file generation, RTL/LTR layout, language switching, locale-aware formatting. Use when implementing app features related to localization.4---5# Localization67LOCALIZATION (.strings files + RTL/LTR + Language Switching):89FORBIDDEN PATTERNS (CRITICAL — violation = broken app):10- NEVER hardcode translations with if/else or switch on language code. Example of FORBIDDEN code:11 if appLanguage == "ar" { Text("الإعدادات") } else { Text("Settings") }12 switch language { case "ar": return "بحث" default: return "Search" }13- NEVER build a manual translation dictionary/map in code (e.g., let translations = ["en": "Settings", "ar": "الإعدادات"]).14- NEVER use ternary operators to pick translated strings: Text(isArabic ? "الإعدادات" : "Settings").15- These patterns bypass Apple's localization system, break when new languages are added, and ignore the environment locale.16- The ONLY correct approach: use string literals in views — Text("Settings"), Button("Save"), .navigationTitle("Dashboard") — and let Localizable.strings handle translations.17- The deployment pipeline generates .strings files automatically. Your code must ONLY contain English string literals.1819.strings FILE GENERATION:20- When localization is requested, generate Resources/{lang}.lproj/Localizable.strings for EACH language.21- File format: standard Apple .strings — one "key" = "translation"; per line.22- KEYS MUST BE THE ENGLISH TEXT ITSELF. Example: "Settings" = "Settings"; (en), "Settings" = "الإعدادات"; (ar). NOT snake_case like "settings_title".23- This means Text("Settings") in code auto-localizes because the key IS the English text.24- English .strings: identity mapping (key = value). Other languages: key = translated value.25- ALL user-facing strings MUST have a key: Text(), Button(), Label(), Toggle(), navigationTitle(), Section(), alert titles/messages, ContentUnavailableView labels, placeholder text.2627CRITICAL — localization key usage rules:2829Rule 1 — String LITERALS in views are auto-localized:30 Text("settings_title"), .navigationTitle("dashboard_title"), Label("tab_workouts", systemImage: "icon")31 SwiftUI treats these as LocalizedStringKey and looks them up using the environment locale.3233Rule 2 — String VARIABLES are NOT auto-localized:34 let key = "settings_title"; Text(key) — shows raw key text. This is the #1 localization bug.35 FIX: Text(LocalizedStringKey(key))3637Rule 3 — Computed properties returning keys for display:38 If a switch/computed property returns a key as String (e.g. "metric_steps"), and you pass it to Text(label), it will NOT be localized.39 FIX: Return LocalizedStringKey instead of String from the computed property.4041Rule 4 — NEVER use String(localized:) in view parameters:42 Text(String(localized: "key")) resolves against SYSTEM locale, NOT the environment locale. Runtime language switching breaks.4344Rule 5 — EVERY key in code must exist in EVERY .strings file. Missing key = raw key shown to user.4546- Sample data strings stay as plain String — they are demo content, not translatable.4748CONFIG_CHANGES FOR LOCALIZATIONS:49- CONFIG_CHANGES MUST include "localizations": ["en", "ar", "es"] (list of all language codes).50- The client reads this to register knownRegions in the Xcode .pbxproj and create .lproj file references.5152LANGUAGE SELECTION & SWITCHING:53- Root view: @AppStorage("appLanguage") private var appLanguage: String = "en"54- Root @main app MUST apply .id(appLanguage) on RootView AND set locale/layoutDirection:55 private var layoutDirection: LayoutDirection {56 ["ar", "he", "fa", "ur"].contains(appLanguage) ? .rightToLeft : .leftToRight57 }58 RootView()59 .id(appLanguage) // MANDATORY: forces full view rebuild on language change60 .environment(\.locale, Locale(identifier: appLanguage))61 .environment(\.layoutDirection, layoutDirection)62- .id(appLanguage) is CRITICAL — without it, changing language causes mirrored/broken layouts because SwiftUI animates the layout direction change instead of rebuilding. With .id(), the entire view tree is destroyed and recreated cleanly.63- Setting locale ALONE does NOT flip layout to RTL. You MUST set layoutDirection explicitly.64- RTL languages: Arabic (ar), Hebrew (he), Persian/Farsi (fa), Urdu (ur).65- App restart NOT needed — .id(appLanguage) forces a full view rebuild when @AppStorage changes.66- Settings screen: add a language picker (Picker or List with checkmark) that writes to @AppStorage("appLanguage").67- Display language name: Locale(identifier: code).localizedString(forLanguageCode: code) ?? code6869RTL / LTR LAYOUT DIRECTION:70- .environment(\.layoutDirection) MUST be set explicitly — .environment(\.locale) does NOT set it automatically.71- Use .leading/.trailing (never .left/.right) for alignment, padding, edges.72- Icons that represent direction (back arrows, chevrons, progress bars) MUST call .flipsForRightToLeftLayoutDirection(true).73- Decorative/universal icons (checkmarks, stars, hearts) must NOT flip.74- Text alignment: always use .leading — SwiftUI resolves it to left or right based on layoutDirection.75- Padding/spacing: always use .leading/.trailing edges, never .left/.right (Edge.Set).7677LOCALE-AWARE FORMATTING:78- Dates: use .formatted(date:time:) or Text(date, format:) — automatically adapts to user's locale/calendar.79- Numbers: use .formatted() or Text(number, format: .number) — respects locale decimal/grouping separators.80- Currency: use .formatted(.currency(code:)) — locale-aware symbol placement and formatting.81- Measurements: use Measurement + MeasurementFormatter for locale-appropriate units.82- NEVER manually format dates/numbers with hardcoded separators or patterns.8384TESTING RTL IN PREVIEWS:85- Add preview with RTL locale: .environment(\.locale, Locale(identifier: "ar")) and .environment(\.layoutDirection, .rightToLeft)86- Verify: text alignment flips, HStack order reverses, directional icons mirror, padding sides swap.