I18n Swift

Other

Swift internationalization patterns, String(localized:) usage, and semantic key naming for macOS/iOS apps

Install

openclaw skills install i18n-swift

Swift Internationalization

Expert-level Swift i18n patterns for macOS/iOS applications. Covers string localization, semantic key naming, and best practices.

When: to Use

Use this skill when:

  • Adding localized strings to SwiftUI views
  • Creating new localization keys
  • Writing String(localized:) calls
  • Organizing Localizable.strings files
  • Reviewing i18n implementation in Swift projects

Core Principles

1. String(localized:) Pattern

// Always use String(localized:) for user-facing text
Text(String(localized: "common.save"))
Button(String(localized: "sidebar.today"))

// Never hardcode strings
Text("Save")  // ❌ Wrong

2. Semantic Key Naming

Use domain.feature.key pattern:

// Good: Semantic keys
"app.name" = "TimeFlow"
"common.save" = "Save"
"sidebar.today" = "Today"
"settings.theme.title" = "Theme"

// Bad: Non-semantic keys
"saveButton" = "Save"
"viewTitle1" = "Today"
"themeTitle" = "Theme"

3. Localizable.strings Organization

Group by domain with MARK comments:

/*
 Localizable.strings
 TimeFlow
*/

// MARK: - App
"app.name" = "TimeFlow";

// MARK: - Common
"common.delete" = "Delete";
"common.save" = "Save";
"common.cancel" = "Cancel";

// MARK: - Sidebar
"sidebar.today" = "Today";
"sidebar.settings" = "Settings";
"sidebar.daily_note" = "Daily Note";

// MARK: - GTD Navigation
"gtd.inbox" = "Inbox";
"gtd.today" = "Today";
"gtd.projects" = "Projects";

Key Naming Conventions

DomainFeatureExamples
app-app.name
commondelete, save, cancelcommon.delete, common.save
sidebartoday, settings, searchsidebar.today, sidebar.settings
`home`` today, progress, timelinehome.today, home.progress
gtdinbox, next, projectsgtd.inbox, gtd.next
settingstheme, language, syncsettings.theme.title
syncidle, syncing, failedsync.idle, sync.syncing

SwiftUI Usage Patterns

Text Component

struct TodayView: View {
    var body: some View {
        VStack {
            Text(String(localized: "home.today"))
                .font(.title)

            Text(String(localized: "home.no_events"))
                .foregroundStyle(.secondary)
        }
    }
}

Button with Localized String

Button(String(localized: "common.save")) {
    // Save action
}
.disabled(isSaving)

Navigation Titles

NavigationLink(destination: SettingsView()) {
    Label(String(localized: "sidebar.settings"), systemImage: .gear)
}

Best Practices

PracticeReason
Use String(localized:)Prevents hardcoding, enables i18n
Semantic key namesSelf-documenting, easier maintenance
Group with // MARK:Organized, searchable strings files
Use domain.feature.keyClear ownership, prevents collisions
Update all languages togetherPrevents missing translations
Avoid format strings in keysUse Swift interpolation instead

String Interpolation

// Good: Use Swift interpolation
let message = String(localized: "sync.completed.count")
    .replacingOccurrences(of: "{count}", with: "\(count)")

// Alternative: Use String(format:) for localized format strings
let formatted = String(
    localized: "sync.completed.count",
    comment: "Number of items synced"
)

// Localizable.strings entry:
// "sync.completed.count" = "Synced %d items";

Common Pitfalls

PitfallConsequencePrevention
Hardcoded stringsCan't localizeAlways use String(localized:)
Non-semantic keysDifficult to maintainUse domain.feature.key pattern
Missing translationsShows key nameAdd entries to all .strings files
Format strings in keysAmbiguous valuesUse Swift interpolation
Duplicate keysConfusing maintenanceSearch before adding

Testing Patterns

func testLocalizationKeys() {
    // Verify all keys exist in Localizable.strings
    let keys = ["app.name", "common.save", "sidebar.today"]
    for key in keys {
        let localized = String(localized: key)
        XCTAssertNotEqual(localized, key, "Key not found: \(key)")
    }
}

Running Tests

# Test localization
xcodebuild test -scheme YourApp \
  -destination 'platform=macOS' \
  -only-testing:'YourAppTests/LocalizationTests/testKeysExist'