
swift-localization
by jamesmontemagno
A simple swift app for tracking weight
SKILL.md
name: swift-localization description: Best practices for internationalizing Swift/SwiftUI applications using LocalizedStringResource, String Catalogs (.xcstrings), and type-safe localization patterns. Use when implementing multi-language support, adding new UI strings, or refactoring hardcoded text in Swift apps. license: MIT
Swift Localization Best Practices
Comprehensive guide for implementing localization in Swift and SwiftUI applications using modern Apple frameworks and type-safe patterns.
Core Principles
1. Never Hardcode User-Facing Strings
All text visible to users must be localized, including:
- UI labels, buttons, and navigation titles
- Error messages and alerts
- Placeholder text and hints
- Accessibility labels and hints
- Status messages and notifications
Bad:
Text("Add Weight")
Button("Save") { ... }
.alert("Error", message: "Something went wrong")
Good:
Text(L10n.Common.addWeight)
Button(L10n.Common.saveButton) { ... }
.alert(L10n.Common.errorTitle, message: L10n.Errors.genericMessage)
2. Use Type-Safe Localization Keys
Create a centralized enum structure (commonly named L10n) using LocalizedStringResource for compile-time safety.
Implementation Pattern
Structure: L10n.swift
Create a hierarchical enum structure organized by feature or screen:
import Foundation
enum L10n {
enum Common {
static let saveButton = LocalizedStringResource(
"common.button.save",
defaultValue: "Save"
)
static let cancelButton = LocalizedStringResource(
"common.button.cancel",
defaultValue: "Cancel"
)
static let errorTitle = LocalizedStringResource(
"common.alert.errorTitle",
defaultValue: "Error"
)
}
enum Dashboard {
static let navigationTitle = LocalizedStringResource(
"dashboard.navigation.title",
defaultValue: "Dashboard"
)
static func greeting(_ name: String) -> LocalizedStringResource {
LocalizedStringResource(
"dashboard.greeting",
defaultValue: "Hello, \(name)!"
)
}
}
enum Settings {
static let navigationTitle = LocalizedStringResource(
"settings.navigation.title",
defaultValue: "Settings"
)
static let themeTitle = LocalizedStringResource(
"settings.personalization.theme.title",
defaultValue: "Theme"
)
}
}
Key Naming Convention
Use dot-separated namespaces that mirror your code structure:
{feature}.{component}.{element}.{property}- Examples:
dashboard.currentWeight- Simple valuecommon.button.save- Reusable buttonsettings.section.personalization.title- Nested section titleaddEntry.error.invalidWeight- Error message
Parameterized Strings
For strings with dynamic content, use functions that return LocalizedStringResource:
enum L10n {
enum Dashboard {
static func latestEntry(_ time: String) -> LocalizedStringResource {
LocalizedStringResource(
"dashboard.latestEntry",
defaultValue: "Latest: \(time)"
)
}
static func averageEntries(_ count: Int) -> LocalizedStringResource {
LocalizedStringResource(
"dashboard.averageEntries",
defaultValue: "Average of \(count) entries"
)
}
}
}
Usage in SwiftUI Views
Convert LocalizedStringResource to String using String(localized:):
struct DashboardView: View {
var body: some View {
NavigationStack {
VStack {
Text(L10n.Dashboard.navigationTitle)
Button(L10n.Common.saveButton) {
save()
}
}
.navigationTitle(String(localized: L10n.Dashboard.navigationTitle))
}
}
}
For string interpolation or concatenation:
TextField(
String(localized: L10n.AddEntry.weightPlaceholder),
text: $weightText
)
.accessibilityLabel(String(localized: L10n.Accessibility.weightValue))
.accessibilityHint(String(localized: L10n.Accessibility.weightValueHint(unitSymbol)))
Accessibility Localization
Always localize accessibility strings separately:
enum L10n {
enum Accessibility {
static let addWeightEntry = LocalizedStringResource(
"accessibility.label.addWeightEntry",
defaultValue: "Add weight entry"
)
static let addWeightEntryHint = LocalizedStringResource(
"accessibility.hint.addWeightEntry",
defaultValue: "Opens form to log a new weight"
)
static func weightValueHint(_ unitSymbol: String) -> LocalizedStringResource {
LocalizedStringResource(
"accessibility.hint.weightValue",
defaultValue: "Enter your current weight in \(unitSymbol)"
)
}
}
}
Usage:
Button {
showAddEntry = true
} label: {
Image(systemName: "plus")
}
.accessibilityLabel(String(localized: L10n.Accessibility.addWeightEntry))
.accessibilityHint(String(localized: L10n.Accessibility.addWeightEntryHint))
String Catalogs (.xcstrings)
File Structure
Modern Swift projects use .xcstrings files (String Catalogs) instead of .strings files. Xcode automatically generates entries when you use LocalizedStringResource.
Location: {ProjectName}/Localization/Localizable.xcstrings
Example structure:
{
"sourceLanguage" : "en",
"strings" : {
"common.button.save" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Save"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Guardar"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Enregistrer"
}
}
}
}
},
"version" : "1.0"
}
Managing String Catalogs in Xcode
- Build the project after adding new
LocalizedStringResourceentries - Xcode will detect them - Open
Localizable.xcstringsin Xcode - Use the String Catalog editor to add translations
- Xcode shows:
- ✓ Translated strings
- ⚠️ Untranslated strings (needs attention)
- States:
new,translated,needs_review
Adding New Languages
- In Xcode: Project Settings → Info → Localizations →
+ - Select language (e.g., Spanish, French, German)
- Xcode adds the language to all
.xcstringsfiles - Translate strings in the String Catalog editor
Common Patterns
Conditional Text
enum L10n {
enum Settings {
static let iCloudSyncEnabled = LocalizedStringResource(
"settings.data.iCloudSync.enabled",
defaultValue: "On"
)
static let iCloudSyncDisabled = LocalizedStringResource(
"settings.data.iCloudSync.disabled",
defaultValue: "Off"
)
}
}
// Usage
Text(isEnabled ? L10n.Settings.iCloudSyncEnabled : L10n.Settings.iCloudSyncDisabled)
Error Messages
enum L10n {
enum AddEntry {
static let errorInvalidWeight = LocalizedStringResource(
"addEntry.error.invalidWeight",
defaultValue: "Please enter a valid weight"
)
static func errorSaveFailure(_ message: String) -> LocalizedStringResource {
LocalizedStringResource(
"addEntry.error.saveFailure",
defaultValue: "Failed to save entry: \(message)"
)
}
}
}
// Usage
.alert(L10n.Common.errorTitle, isPresented: $showingError) {
Button(L10n.Common.okButton) { showingError = false }
} message: {
Text(L10n.AddEntry.errorInvalidWeight)
}
Pluralization
For count-dependent strings, use string interpolation:
static func days(_ count: Int) -> LocalizedStringResource {
LocalizedStringResource(
"common.value.days",
defaultValue: "\(count) days"
)
}
In .xcstrings, you can add plural rules:
"common.value.days" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld day"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld days"
}
}
}
}
}
}
}
Date Formatting
Let Foundation handle date localization:
// Good - respects user's locale
Text(date, style: .date)
Text(date, format: .dateTime.day().month().year())
// Avoid hardcoded formats
Text(dateFormatter.string(from: date)) // ❌ if format is hardcoded
Migration Strategy
Converting Hardcoded Strings
-
Audit for hardcoded strings:
# Find potential hardcoded user-facing strings grep -r 'Text("' --include="*.swift" . grep -r 'Button("' --include="*.swift" . grep -r '.alert("' --include="*.swift" . -
Create L10n entries:
- Add to appropriate enum section
- Use descriptive key name
- Provide clear default value
-
Replace in views:
// Before Text("Current Weight") // After Text(L10n.Dashboard.currentWeight) -
Build and verify:
- Build project to generate
.xcstringsentries - Open String Catalog to verify keys appear
- Add translations for all supported languages
- Build project to generate
Handling Legacy NSLocalizedString
If migrating from NSLocalizedString:
// Old pattern
NSLocalizedString("key", comment: "Description")
// New pattern
LocalizedStringResource("key", defaultValue: "Default Value")
Both work with .xcstrings, but LocalizedStringResource is preferred for SwiftUI.
Testing Localization
Pseudo-localization
Test string length and layout issues:
- Xcode → Product → Scheme → Edit Scheme
- Run → Options → App Language → "Double-Length Pseudo-language"
- Strings appear doubled to simulate longer translations
Language Testing
- Simulator: Settings → Language & Region → Preferred Languages
- Xcode scheme: Edit Scheme → Run → App Language → Select language
- Verify:
- All strings are translated
- No truncation or layout issues
- Right-to-left (RTL) languages display correctly
Automated Checks
// Unit test to ensure no hardcoded strings in views
func testNoHardcodedStrings() {
let source = try! String(contentsOfFile: "DashboardView.swift")
let pattern = #"Text\("[^L]"#
let regex = try! NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: source, range: NSRange(source.startIndex..., in: source))
XCTAssertEqual(matches.count, 0, "Found hardcoded Text strings")
}
Best Practices Summary
✅ Do:
- Organize L10n enum by feature/screen
- Use
LocalizedStringResourcefor type safety - Provide descriptive default values
- Localize accessibility strings separately
- Use functions for parameterized strings
- Let Foundation handle date/number formatting
- Test with pseudo-localization and RTL languages
❌ Don't:
- Hardcode user-facing strings directly in views
- Use string concatenation for sentences (breaks translation)
- Assume English word order works in all languages
- Skip accessibility localization
- Use generic key names like "title1", "label2"
- Forget to build after adding new keys
Tooling
Xcode String Catalog Editor
- Filter: Search/filter strings by state (new, translated, needs review)
- Bulk edit: Select multiple strings to mark as reviewed
- Export/Import: File → Export/Import Localizations for external translation
Command Line
# Extract strings from code (legacy .strings format)
genstrings -o en.lproj *.swift
# Export for translation
xcodebuild -exportLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations
# Import translations
xcodebuild -importLocalizations -project MyApp.xcodeproj -localizationPath ./Localizations/fr.xcloc
Resources
- Apple Localization Documentation
- LocalizedStringResource API
- String Catalogs
- WWDC: Discover String Catalogs
Quick Reference
| Task | Pattern |
|---|---|
| Simple string | static let title = LocalizedStringResource("key", defaultValue: "Title") |
| Parameterized | static func greeting(_ name: String) -> LocalizedStringResource { ... } |
| In SwiftUI | Text(L10n.Feature.label) |
| With String conversion | String(localized: L10n.Feature.label) |
| Navigation title | .navigationTitle(String(localized: L10n.Feature.title)) |
| Accessibility | .accessibilityLabel(String(localized: L10n.Accessibility.label)) |
| Alert title | .alert(L10n.Common.errorTitle, message: ...) |
This skill provides general best practices for Swift localization. Adapt patterns to your project's specific architecture and requirements.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


