Expert-level Swift i18n patterns for macOS/iOS applications. Covers string localization, semantic key naming, and best practices.
Use this skill when:
swift
// Always use String(localized:) for user-facing text
Text(String(localized: "common.save"))
Button(String(localized: "sidebar.today"))
// Never hardcode strings
Text("Save") // ❌ Wrong
Use domain.feature.key pattern:
swift
// 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"
Group by domain with MARK comments:
swift
/*
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";
DomainFeatureExamplesapp-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
swift
struct TodayView: View {
var body: some View {
VStack {
Text(String(localized: "home.today"))
.font(.title)
Text(String(localized: "home.no_events"))
.foregroundStyle(.secondary)
}
}
}
swift
Button(String(localized: "common.save")) {
// Save action
}
.disabled(isSaving)
swift
NavigationLink(destination: SettingsView()) {
Label(String(localized: "sidebar.settings"), systemImage: .gear)
}
PracticeReasonUse String(localized:)Prevents hardcoding, enables i18nSemantic key namesSelf-documenting, easier maintenanceGroup with // MARK:Organized, searchable strings filesUse domain.feature.keyClear ownership, prevents collisionsUpdate all languages togetherPrevents missing translationsAvoid format strings in keysUse Swift interpolation instead
swift
// 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";
PitfallConsequencePreventionHardcoded stringsCan't localizeAlways use String(localized:)Non-semantic keysDifficult to maintainUse domain.feature.key patternMissing translationsShows key nameAdd entries to all .strings filesFormat strings in keysAmbiguous valuesUse Swift interpolationDuplicate keysConfusing maintenanceSearch before adding
swift
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)")
}
}
bash
# Test localization
xcodebuild test -scheme YourApp \
-destination 'platform=macOS' \
-only-testing:'YourAppTests/LocalizationTests/testKeysExist'