
Localization Setup
- 2 installs
- 591 repo stars
- Updated July 24, 2026
- rshankras/claude-code-apple-skills
Generates internationalization (i18n) infrastructure for iOS/macOS apps, including String Catalogs (xcstrings), multi-language support, and RTL handling.
About
Generates i18n infrastructure for multi-language iOS/macOS apps, adopting String Catalogs and supporting RTL languages. A developer uses it when localizing an app or migrating to xcstrings.
- Adopts String Catalogs (xcstrings)
- Supports right-to-left language layouts
Localization Setup by the numbers
- 2 all-time installs (skills.sh)
- Ranked #888 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill localization-setupAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 591 |
| Last updated | July 24, 2026 |
| Repository | rshankras/claude-code-apple-skills ↗ |
What it does
Generates internationalization (i18n) infrastructure for iOS/macOS apps, including String Catalogs (xcstrings), multi-language support, and RTL handling.
Files
Localization Setup Generator
Generate internationalization (i18n) infrastructure for multi-language support in iOS/macOS apps.
When This Skill Activates
- User wants to localize their app for multiple languages
- User mentions i18n, internationalization, or localization
- User asks about String Catalogs or .strings files
- User wants to support RTL (right-to-left) languages
Pre-Generation Checks
Before generating, verify:
1. Existing Localization
# Check for existing localization files
find . -name "*.xcstrings" -o -name "Localizable.strings" 2>/dev/null | head -5
find . -name "*.lproj" -type d 2>/dev/null | head -52. Deployment Target
# String Catalogs require iOS 16+ / macOS 13+
grep -r "IPHONEOS_DEPLOYMENT_TARGET\|MACOSX_DEPLOYMENT_TARGET" *.xcodeproj 2>/dev/null3. Project Structure
# Find project for adding localization
find . -name "*.xcodeproj" | head -1Configuration Questions
1. Localization Approach
- String Catalogs (Recommended, iOS 16+) - Modern, visual editor in Xcode
- Legacy .strings - Traditional approach, all iOS versions
2. Initial Languages
- English (en) - default
- Which additional languages? (e.g., es, de, fr, ja, zh-Hans)
3. Features
- Pluralization - Handle "1 item" vs "2 items"
- Device-specific - Different strings for iPhone/iPad/Mac
- SwiftUI Preview - Preview in different locales
Generated Files
String Catalogs (Recommended)
Resources/
└── Localizable.xcstrings # String catalog with all translationsSupporting Code
Sources/Localization/
├── LocalizedStrings.swift # Type-safe string access
├── LocalizationManager.swift # Runtime language switching
└── LocalizedPreview.swift # SwiftUI preview helpersKey Features
Type-Safe String Access
// Generated enum for type-safe access
enum L10n {
static let appName = String(localized: "app_name")
static let welcomeMessage = String(localized: "welcome_message")
enum Settings {
static let title = String(localized: "settings_title")
static let language = String(localized: "settings_language")
}
}
// Usage
Text(L10n.appName)
Text(L10n.Settings.title)Pluralization
// In String Catalog, define plural rules
// key: "items_count"
// variations:
// - zero: "No items"
// - one: "1 item"
// - other: "%lld items"
Text(String(localized: "items_count \(count)",
defaultValue: "\(count) items"))String Interpolation
// In String Catalog:
// key: "greeting"
// value: "Hello, %@!"
let name = "Alice"
Text(String(localized: "greeting \(name)"))Runtime Language Switching
// Preview in different locale
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.locale, Locale(identifier: "es"))
}
}Integration Steps
1. Add String Catalog
1. In Xcode: File > New > File 2. Choose "String Catalog" 3. Name it "Localizable.xcstrings" 4. Add to your app target
2. Add Supported Languages
1. Select project in navigator 2. Info tab > Localizations 3. Click + to add languages
3. Migrate Existing Strings
If migrating from .strings files: 1. Right-click .strings file 2. "Migrate to String Catalog..."
4. Use in SwiftUI
// Automatic localization
Text("Hello, World!") // Uses String Catalog automatically
// Explicit localized string
Text(String(localized: "custom_key"))
// With type-safe enum (generated)
Text(L10n.welcomeMessage)5. Use in UIKit
label.text = String(localized: "hello_world")
// or
label.text = NSLocalizedString("hello_world", comment: "Greeting")Best Practices
Key Naming Conventions
// Good: Descriptive, hierarchical
"settings.appearance.theme"
"onboarding.step1.title"
"error.network.connection_failed"
// Avoid: Vague or hardcoded text as key
"button1"
"Hello, World!"Comments for Translators
String(localized: "delete_confirmation",
comment: "Alert message asking user to confirm deletion")Formatting
// Numbers - Use FormatStyle
Text(price, format: .currency(code: "USD"))
// Dates - Use FormatStyle
Text(date, format: .dateTime.month().day())
// Lists - Use ListFormatStyle
Text(items, format: .list(type: .and))RTL Support
// Automatic with SwiftUI
// For manual layout adjustments:
.environment(\.layoutDirection, .rightToLeft)Testing Localization
In Xcode
1. Edit Scheme > Run > Options 2. Set "App Language" to test language 3. Set "App Region" for number/date formatting
In SwiftUI Previews
#Preview {
ContentView()
.environment(\.locale, Locale(identifier: "ja"))
}Export for Translation
1. Product > Export Localizations... 2. Share .xliff files with translators 3. Import translated .xliff files
References
Localization Patterns
Best practices for internationalization (i18n) in iOS/macOS apps using String Catalogs and modern Swift APIs.
String Catalogs (iOS 16+)
Structure
String Catalogs (.xcstrings) are JSON files with a visual editor in Xcode:
{
"sourceLanguage" : "en",
"strings" : {
"welcome_message" : {
"localizations" : {
"en" : { "stringUnit" : { "value" : "Welcome!" } },
"es" : { "stringUnit" : { "value" : "¡Bienvenido!" } },
"ja" : { "stringUnit" : { "value" : "ようこそ!" } }
}
}
}
}Automatic Extraction
Xcode automatically extracts localizable strings:
// These are automatically added to String Catalog
Text("Hello, World!")
Button("Save") { }
Label("Settings", systemImage: "gear")Manual Keys
For non-UI strings or custom keys:
let message = String(localized: "custom_key")
let greeting = String(localized: "greeting \(name)")Type-Safe String Access
Basic Enum
enum L10n {
// MARK: - General
static let appName = String(localized: "app_name")
static let ok = String(localized: "ok")
static let cancel = String(localized: "cancel")
static let done = String(localized: "done")
static let error = String(localized: "error")
// MARK: - Onboarding
enum Onboarding {
static let welcomeTitle = String(localized: "onboarding.welcome.title")
static let welcomeMessage = String(localized: "onboarding.welcome.message")
static let getStarted = String(localized: "onboarding.get_started")
}
// MARK: - Settings
enum Settings {
static let title = String(localized: "settings.title")
static let appearance = String(localized: "settings.appearance")
static let notifications = String(localized: "settings.notifications")
static let about = String(localized: "settings.about")
}
// MARK: - Errors
enum Error {
static let networkFailed = String(localized: "error.network_failed")
static let saveFailed = String(localized: "error.save_failed")
static func custom(_ message: String) -> String {
String(localized: "error.custom \(message)")
}
}
}With Interpolation
enum L10n {
// "greeting" = "Hello, %@!"
static func greeting(_ name: String) -> String {
String(localized: "greeting \(name)")
}
// "items_selected" = "%lld items selected"
static func itemsSelected(_ count: Int) -> String {
String(localized: "items_selected \(count)")
}
// "file_size" = "File size: %@ MB"
static func fileSize(_ size: Double) -> String {
String(localized: "file_size \(size, format: .number.precision(.fractionLength(1)))")
}
}Pluralization
In String Catalog
Define plural variations in Xcode's String Catalog editor:
| Key | Plural Category | Value |
|---|---|---|
| items_count | zero | No items |
| items_count | one | 1 item |
| items_count | other | %lld items |
Usage
// Automatic plural selection based on count
let count = 5
Text(String(localized: "items_count \(count)")) // "5 items"
// In L10n enum
enum L10n {
static func itemsCount(_ count: Int) -> String {
String(localized: "items_count \(count)")
}
}Complex Plurals
Some languages have multiple plural categories:
| Category | Languages | Example (items) |
|---|---|---|
| zero | Arabic, Latvian, Welsh | 0 items |
| one | English, German, Spanish | 1 item |
| two | Arabic, Welsh | 2 items |
| few | Russian, Polish, Czech | 2-4 items |
| many | Russian, Polish, Arabic | 5-20 items |
| other | All | Default |
Device Variations
In String Catalog
Set device-specific variations:
| Key | Device | Value |
|---|---|---|
| tap_to_continue | iPhone | Tap to continue |
| tap_to_continue | iPad | Tap or use keyboard |
| tap_to_continue | Mac | Click to continue |
Programmatic Check
#if os(iOS)
if UIDevice.current.userInterfaceIdiom == .pad {
// iPad-specific
} else {
// iPhone-specific
}
#elseif os(macOS)
// Mac-specific
#endifRuntime Language Switching
Language Manager
import SwiftUI
@Observable
final class LocalizationManager {
static let shared = LocalizationManager()
var currentLanguage: String {
didSet {
UserDefaults.standard.set([currentLanguage], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
}
}
var currentLocale: Locale {
Locale(identifier: currentLanguage)
}
private init() {
currentLanguage = Locale.preferredLanguages.first ?? "en"
}
var supportedLanguages: [String] {
Bundle.main.localizations.filter { $0 != "Base" }
}
func displayName(for languageCode: String) -> String {
Locale.current.localizedString(forLanguageCode: languageCode) ?? languageCode
}
}Language Picker
struct LanguagePicker: View {
@Bindable private var manager = LocalizationManager.shared
var body: some View {
Picker("Language", selection: $manager.currentLanguage) {
ForEach(manager.supportedLanguages, id: \.self) { code in
Text(manager.displayName(for: code))
.tag(code)
}
}
}
}Note on Runtime Switching
Full runtime language switching requires app restart. For in-app switching without restart:
// Environment-based approach (limited to SwiftUI)
struct ContentView: View {
@State private var locale = Locale.current
var body: some View {
MainContent()
.environment(\.locale, locale)
}
}Formatting
Numbers
// Currency
Text(price, format: .currency(code: "USD"))
// Percentage
Text(0.75, format: .percent)
// Custom number format
Text(1234.5, format: .number.precision(.fractionLength(2)))Dates
// Relative (e.g., "2 days ago")
Text(date, format: .relative(presentation: .named))
// Specific format
Text(date, format: .dateTime.month(.wide).day().year())
// Range
Text(startDate...endDate)Lists
let names = ["Alice", "Bob", "Charlie"]
// "Alice, Bob, and Charlie"
Text(names, format: .list(type: .and))
// "Alice, Bob, or Charlie"
Text(names, format: .list(type: .or))Measurements
let distance = Measurement(value: 5, unit: UnitLength.kilometers)
Text(distance, format: .measurement(width: .abbreviated)) // "5 km"RTL (Right-to-Left) Support
Automatic Layout
SwiftUI handles RTL automatically. Use semantic modifiers:
// Good - Semantic
HStack {
Text("Label")
Spacer()
Text("Value")
}
// Avoid - Absolute positioning for text flow
.padding(.leading, 16) // Use this, not .left
.padding(.trailing, 16) // Use this, not .rightImages
// Flip image for RTL
Image(systemName: "arrow.right")
.flipsForRightToLeftLayoutDirection(true)Manual RTL Detection
struct RTLAwareView: View {
@Environment(\.layoutDirection) private var layoutDirection
var body: some View {
if layoutDirection == .rightToLeft {
// RTL-specific layout
} else {
// LTR layout
}
}
}SwiftUI Preview Helpers
import SwiftUI
// Preview in multiple locales
#Preview("English") {
ContentView()
.environment(\.locale, Locale(identifier: "en"))
}
#Preview("Spanish") {
ContentView()
.environment(\.locale, Locale(identifier: "es"))
}
#Preview("Japanese") {
ContentView()
.environment(\.locale, Locale(identifier: "ja"))
}
#Preview("Arabic (RTL)") {
ContentView()
.environment(\.locale, Locale(identifier: "ar"))
.environment(\.layoutDirection, .rightToLeft)
}Locale Preview Modifier
extension View {
func previewLocales(_ locales: [String] = ["en", "es", "ja", "ar"]) -> some View {
ForEach(locales, id: \.self) { locale in
self
.environment(\.locale, Locale(identifier: locale))
.previewDisplayName(Locale.current.localizedString(forIdentifier: locale) ?? locale)
}
}
}
// Usage
#Preview {
ContentView()
.previewLocales()
}Accessibility
VoiceOver Localization
Button(action: { }) {
Image(systemName: "heart.fill")
}
.accessibilityLabel(String(localized: "accessibility.favorite"))
.accessibilityHint(String(localized: "accessibility.favorite_hint"))Dynamic Type with Localization
Text(L10n.longDescription)
.font(.body)
.minimumScaleFactor(0.5) // Allow text to scale down if neededTesting
Unit Tests
import XCTest
final class LocalizationTests: XCTestCase {
func testAllKeysHaveTranslations() {
let bundle = Bundle.main
let localizations = bundle.localizations.filter { $0 != "Base" }
// Get all keys from base localization
let baseKeys = getAllKeys(for: "en", in: bundle)
for language in localizations {
let keys = getAllKeys(for: language, in: bundle)
XCTAssertEqual(baseKeys, keys, "Missing translations in \(language)")
}
}
}Pseudo-Localization
Test with pseudo-localized strings to catch layout issues:
#if DEBUG
extension String {
var pseudoLocalized: String {
// Add accents and padding to reveal truncation/layout issues
"[[ \(self.map { "̈\($0)" }.joined()) ]]"
}
}
#endifMigration from .strings
Automatic Migration
1. Right-click existing .strings file 2. Select "Migrate to String Catalog..." 3. Xcode creates .xcstrings with all translations
Manual Considerations
- InfoPlist.strings → InfoPlist.xcstrings
- Localizable.strings → Localizable.xcstrings
- Storyboard/XIB strings migrate automatically
Export/Import for Translation
Export
# Command line
xcodebuild -exportLocalizations -project YourApp.xcodeproj -localizationPath ./Localizations
# Or in Xcode: Product > Export Localizations...Import
xcodebuild -importLocalizations -project YourApp.xcodeproj -localizationPath ./Localizations/es.xcloc
# Or in Xcode: Product > Import Localizations...XLIFF Format
Exported .xcloc packages contain XLIFF files that translators can edit with standard translation tools.
import Foundation
import SwiftUI
/// Manages app localization and language preferences.
///
/// Usage:
/// ```swift
/// // Access current language
/// let manager = LocalizationManager.shared
/// print(manager.currentLanguageCode) // "en"
///
/// // Change language (requires app restart for full effect)
/// manager.setLanguage("es")
///
/// // Get display name
/// manager.displayName(for: "ja") // "Japanese"
/// ```
@MainActor
@Observable
final class LocalizationManager {
// MARK: - Singleton
static let shared = LocalizationManager()
// MARK: - Properties
/// Current language code (e.g., "en", "es", "ja")
private(set) var currentLanguageCode: String
/// Current locale based on selected language
var currentLocale: Locale {
Locale(identifier: currentLanguageCode)
}
/// Languages supported by the app (from bundle localizations)
var supportedLanguages: [String] {
Bundle.main.localizations
.filter { $0 != "Base" }
.sorted()
}
/// Language codes with their display names
var languageOptions: [(code: String, name: String)] {
supportedLanguages.map { code in
(code: code, name: displayName(for: code))
}
}
// MARK: - Initialization
private init() {
// Get preferred language or default to English
currentLanguageCode = Locale.preferredLanguages.first?.components(separatedBy: "-").first ?? "en"
}
// MARK: - Language Management
/// Set the app language.
///
/// - Parameter code: Language code (e.g., "en", "es", "ja")
/// - Note: Full language change requires app restart.
/// For SwiftUI views, use `.environment(\.locale, locale)` modifier.
func setLanguage(_ code: String) {
guard supportedLanguages.contains(code) else {
print("⚠️ Language '\(code)' not supported. Available: \(supportedLanguages)")
return
}
currentLanguageCode = code
// Update system preference (takes effect on next launch)
UserDefaults.standard.set([code], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
}
/// Get localized display name for a language code.
///
/// - Parameter code: Language code (e.g., "en", "es")
/// - Returns: Display name in current locale (e.g., "English", "Spanish")
func displayName(for code: String) -> String {
Locale.current.localizedString(forLanguageCode: code) ?? code
}
/// Get native display name for a language code.
///
/// - Parameter code: Language code (e.g., "en", "es")
/// - Returns: Display name in that language (e.g., "English", "Español")
func nativeDisplayName(for code: String) -> String {
Locale(identifier: code).localizedString(forLanguageCode: code) ?? code
}
/// Check if current language is RTL (right-to-left).
var isRightToLeft: Bool {
Locale.characterDirection(forLanguage: currentLanguageCode) == .rightToLeft
}
/// Get the layout direction for current language.
var layoutDirection: LayoutDirection {
isRightToLeft ? .rightToLeft : .leftToRight
}
}
// MARK: - SwiftUI Environment Integration
/// Environment key for LocalizationManager
private struct LocalizationManagerKey: EnvironmentKey {
@MainActor static let defaultValue = LocalizationManager.shared
}
extension EnvironmentValues {
var localizationManager: LocalizationManager {
get { self[LocalizationManagerKey.self] }
set { self[LocalizationManagerKey.self] = newValue }
}
}
// MARK: - Language Picker View
/// A picker for selecting the app language.
///
/// Usage:
/// ```swift
/// LanguagePickerView()
/// ```
struct LanguagePickerView: View {
@Environment(\.localizationManager) private var manager
@State private var selectedLanguage: String = ""
var body: some View {
Picker("Language", selection: $selectedLanguage) {
ForEach(manager.languageOptions, id: \.code) { option in
HStack {
Text(option.name)
if option.code != manager.currentLanguageCode {
Text("(\(manager.nativeDisplayName(for: option.code)))")
.foregroundStyle(.secondary)
}
}
.tag(option.code)
}
}
.onAppear {
selectedLanguage = manager.currentLanguageCode
}
.onChange(of: selectedLanguage) { _, newValue in
if newValue != manager.currentLanguageCode {
manager.setLanguage(newValue)
showRestartAlert()
}
}
}
private func showRestartAlert() {
// Note: In production, show an alert explaining restart is needed
print("Language changed. Restart app to apply changes.")
}
}
// MARK: - Locale Environment Modifier
extension View {
/// Apply localization manager's locale to the view hierarchy.
func withLocalization() -> some View {
self.environment(\.locale, LocalizationManager.shared.currentLocale)
.environment(\.layoutDirection, LocalizationManager.shared.layoutDirection)
}
}
// MARK: - Preview Helpers
#if DEBUG
extension View {
/// Preview this view in multiple locales.
func previewInLocales(_ locales: [String] = ["en", "es", "ja", "ar"]) -> some View {
ForEach(locales, id: \.self) { locale in
self
.environment(\.locale, Locale(identifier: locale))
.environment(
\.layoutDirection,
Locale.characterDirection(forLanguage: locale) == .rightToLeft
? .rightToLeft : .leftToRight
)
.previewDisplayName(Locale.current.localizedString(forIdentifier: locale) ?? locale)
}
}
}
#endif
import SwiftUI
// MARK: - Locale Preview Modifiers
#if DEBUG
/// Preview modifier for testing localization.
///
/// Usage:
/// ```swift
/// #Preview {
/// ContentView()
/// .localePreview("es")
/// }
/// ```
extension View {
/// Preview in a specific locale.
func localePreview(_ identifier: String) -> some View {
let locale = Locale(identifier: identifier)
let isRTL = Locale.characterDirection(forLanguage: identifier) == .rightToLeft
return self
.environment(\.locale, locale)
.environment(\.layoutDirection, isRTL ? .rightToLeft : .leftToRight)
}
/// Preview in multiple locales at once.
func multiLocalePreview(
_ identifiers: [String] = ["en", "es", "de", "ja", "ar"]
) -> some View {
ForEach(identifiers, id: \.self) { identifier in
self.localePreview(identifier)
.previewDisplayName(
Locale.current.localizedString(forIdentifier: identifier) ?? identifier
)
}
}
}
// MARK: - Preview Providers
/// Preview wrapper for testing localization in different locales.
///
/// Usage:
/// ```swift
/// #Preview {
/// LocalizedPreview {
/// ContentView()
/// }
/// }
/// ```
struct LocalizedPreview<Content: View>: View {
let content: Content
let locales: [String]
init(
locales: [String] = ["en", "es", "ja", "ar"],
@ViewBuilder content: () -> Content
) {
self.locales = locales
self.content = content()
}
var body: some View {
NavigationStack {
List {
ForEach(locales, id: \.self) { locale in
Section(header: Text(localeName(for: locale))) {
content
.environment(\.locale, Locale(identifier: locale))
.environment(
\.layoutDirection,
isRTL(locale) ? .rightToLeft : .leftToRight
)
}
}
}
}
}
private func localeName(for identifier: String) -> String {
let native = Locale(identifier: identifier)
.localizedString(forLanguageCode: identifier) ?? identifier
let english = Locale(identifier: "en")
.localizedString(forLanguageCode: identifier) ?? identifier
return "\(native) (\(english))"
}
private func isRTL(_ identifier: String) -> Bool {
Locale.characterDirection(forLanguage: identifier) == .rightToLeft
}
}
// MARK: - RTL Preview
/// Preview specifically for testing RTL layout.
///
/// Usage:
/// ```swift
/// #Preview {
/// RTLPreview {
/// MyView()
/// }
/// }
/// ```
struct RTLPreview<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
HStack(spacing: 0) {
VStack {
Text("LTR (English)")
.font(.caption)
.padding(.bottom, 4)
content
.environment(\.locale, Locale(identifier: "en"))
.environment(\.layoutDirection, .leftToRight)
}
.frame(maxWidth: .infinity)
Divider()
VStack {
Text("RTL (Arabic)")
.font(.caption)
.padding(.bottom, 4)
content
.environment(\.locale, Locale(identifier: "ar"))
.environment(\.layoutDirection, .rightToLeft)
}
.frame(maxWidth: .infinity)
}
}
}
// MARK: - Dynamic Type + Localization Preview
/// Preview for testing localization with different Dynamic Type sizes.
///
/// Usage:
/// ```swift
/// #Preview {
/// DynamicTypeLocalePreview(locale: "de") {
/// MyView()
/// }
/// }
/// ```
struct DynamicTypeLocalePreview<Content: View>: View {
let content: Content
let locale: String
let sizes: [DynamicTypeSize]
init(
locale: String = "en",
sizes: [DynamicTypeSize] = [.small, .large, .xxxLarge],
@ViewBuilder content: () -> Content
) {
self.content = content()
self.locale = locale
self.sizes = sizes
}
var body: some View {
ForEach(sizes, id: \.self) { size in
content
.environment(\.locale, Locale(identifier: locale))
.environment(\.dynamicTypeSize, size)
.previewDisplayName("\(locale) - \(String(describing: size))")
}
}
}
// MARK: - Common Locale Sets
/// Predefined sets of locales for different testing scenarios.
enum LocalePresets {
/// Common Western languages
static let western = ["en", "es", "fr", "de", "it", "pt"]
/// Asian languages
static let asian = ["ja", "zh-Hans", "zh-Hant", "ko"]
/// RTL languages
static let rtl = ["ar", "he", "fa"]
/// Comprehensive set
static let comprehensive = western + asian + rtl
/// Quick test set (one from each category)
static let quick = ["en", "es", "ja", "ar"]
/// Languages with long words (for layout testing)
static let longWords = ["de", "fi", "hu"]
}
// MARK: - Example Previews
#Preview("Quick Locale Test") {
VStack(spacing: 20) {
Text("Hello, World!")
Text(Date(), format: .dateTime)
Text(1234.56, format: .currency(code: "USD"))
}
.multiLocalePreview(LocalePresets.quick)
}
#Preview("RTL Comparison") {
RTLPreview {
HStack {
Image(systemName: "arrow.right")
Text("Direction Test")
Spacer()
Image(systemName: "chevron.right")
}
.padding()
}
}
#endif
import Foundation
/// Type-safe localized strings.
///
/// Usage:
/// ```swift
/// Text(L10n.appName)
/// Text(L10n.Onboarding.welcomeTitle)
/// Text(L10n.itemsCount(5))
/// ```
///
/// Generated structure mirrors String Catalog organization.
/// Add new strings to String Catalog and update this file.
enum L10n {
// MARK: - General
/// App name
static let appName = String(localized: "app_name", defaultValue: "MyApp")
/// Common button titles
static let ok = String(localized: "ok", defaultValue: "OK")
static let cancel = String(localized: "cancel", defaultValue: "Cancel")
static let done = String(localized: "done", defaultValue: "Done")
static let save = String(localized: "save", defaultValue: "Save")
static let delete = String(localized: "delete", defaultValue: "Delete")
static let edit = String(localized: "edit", defaultValue: "Edit")
static let close = String(localized: "close", defaultValue: "Close")
static let retry = String(localized: "retry", defaultValue: "Retry")
// MARK: - Onboarding
enum Onboarding {
static let welcomeTitle = String(localized: "onboarding.welcome.title",
defaultValue: "Welcome")
static let welcomeMessage = String(localized: "onboarding.welcome.message",
defaultValue: "Thanks for downloading our app!")
static let getStarted = String(localized: "onboarding.get_started",
defaultValue: "Get Started")
static let skip = String(localized: "onboarding.skip",
defaultValue: "Skip")
static let next = String(localized: "onboarding.next",
defaultValue: "Next")
}
// MARK: - Settings
enum Settings {
static let title = String(localized: "settings.title",
defaultValue: "Settings")
static let appearance = String(localized: "settings.appearance",
defaultValue: "Appearance")
static let notifications = String(localized: "settings.notifications",
defaultValue: "Notifications")
static let privacy = String(localized: "settings.privacy",
defaultValue: "Privacy")
static let about = String(localized: "settings.about",
defaultValue: "About")
static let language = String(localized: "settings.language",
defaultValue: "Language")
static let version = String(localized: "settings.version",
defaultValue: "Version")
}
// MARK: - Errors
enum Error {
static let genericTitle = String(localized: "error.generic.title",
defaultValue: "Error")
static let genericMessage = String(localized: "error.generic.message",
defaultValue: "Something went wrong. Please try again.")
static let networkTitle = String(localized: "error.network.title",
defaultValue: "Network Error")
static let networkMessage = String(localized: "error.network.message",
defaultValue: "Please check your internet connection.")
static let saveFailed = String(localized: "error.save_failed",
defaultValue: "Failed to save. Please try again.")
static let loadFailed = String(localized: "error.load_failed",
defaultValue: "Failed to load data.")
/// Custom error message with interpolation
static func custom(_ message: String) -> String {
String(localized: "error.custom \(message)",
defaultValue: "Error: \(message)")
}
}
// MARK: - Plurals
/// Plural-aware item count
/// Requires plural variations in String Catalog:
/// - zero: "No items"
/// - one: "1 item"
/// - other: "%lld items"
static func itemsCount(_ count: Int) -> String {
String(localized: "items.count \(count)",
defaultValue: "\(count) items")
}
/// Plural-aware file count
static func filesCount(_ count: Int) -> String {
String(localized: "files.count \(count)",
defaultValue: "\(count) files")
}
/// Days remaining
static func daysRemaining(_ count: Int) -> String {
String(localized: "days.remaining \(count)",
defaultValue: "\(count) days remaining")
}
// MARK: - Interpolation Examples
/// Greeting with name
static func greeting(_ name: String) -> String {
String(localized: "greeting \(name)",
defaultValue: "Hello, \(name)!")
}
/// Welcome back message
static func welcomeBack(_ name: String) -> String {
String(localized: "welcome_back \(name)",
defaultValue: "Welcome back, \(name)")
}
/// Last updated timestamp
static func lastUpdated(_ date: Date) -> String {
String(localized: "last_updated \(date, format: .dateTime)",
defaultValue: "Last updated: \(date.formatted())")
}
// MARK: - Accessibility
enum Accessibility {
static let closeButton = String(localized: "accessibility.close_button",
defaultValue: "Close")
static let backButton = String(localized: "accessibility.back_button",
defaultValue: "Go back")
static let menuButton = String(localized: "accessibility.menu_button",
defaultValue: "Open menu")
static let favoriteButton = String(localized: "accessibility.favorite_button",
defaultValue: "Add to favorites")
static let shareButton = String(localized: "accessibility.share_button",
defaultValue: "Share")
static func selected(_ isSelected: Bool) -> String {
isSelected
? String(localized: "accessibility.selected", defaultValue: "Selected")
: String(localized: "accessibility.not_selected", defaultValue: "Not selected")
}
}
}
// MARK: - String Catalog Key Reference
/*
Add these keys to your Localizable.xcstrings file:
General:
- app_name
- ok
- cancel
- done
- save
- delete
- edit
- close
- retry
Onboarding:
- onboarding.welcome.title
- onboarding.welcome.message
- onboarding.get_started
- onboarding.skip
- onboarding.next
Settings:
- settings.title
- settings.appearance
- settings.notifications
- settings.privacy
- settings.about
- settings.language
- settings.version
Errors:
- error.generic.title
- error.generic.message
- error.network.title
- error.network.message
- error.save_failed
- error.load_failed
- error.custom %@
Plurals (with variations):
- items.count %lld
- files.count %lld
- days.remaining %lld
Interpolation:
- greeting %@
- welcome_back %@
- last_updated %@
Accessibility:
- accessibility.close_button
- accessibility.back_button
- accessibility.menu_button
- accessibility.favorite_button
- accessibility.share_button
- accessibility.selected
- accessibility.not_selected
*/