All skills
johnrogers avatar

/localization

@d62eb71

Use when implementing internationalization (i18n), String Catalogs, pluralization, or right-to-left layout support. Covers modern localization workflows with Xcode String Catalogs and LocalizedStringKey patterns.

Use this Skill: https://skilld.dev/gh/johnrogers/claude-swift-engineering/localization

This session only. Nothing lands on disk.

referencesformatting.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Locale-Aware Formatting

Proper formatting of dates, numbers, and currencies adapts automatically to the user's locale. Never hardcode format patterns.

Date Formatting

DateFormatter (Foundation)

let formatter = DateFormatter()
formatter.locale = Locale.current  // Always use current locale
formatter.dateStyle = .long
formatter.timeStyle = .short

let dateString = formatter.string(from: Date())

// US: "January 15, 2024 at 3:30 PM"
// France: "15 janvier 2024 a 15:30"
// Japan: "2024/1/15 15:30"
// Germany: "15. Januar 2024 um 15:30"

FormatStyle (iOS 15+)

Modern, type-safe formatting:

let date = Date()

// Date styles
date.formatted(date: .long, time: .shortened)
// US: "January 15, 2024 at 3:30 PM"

date.formatted(date: .abbreviated, time: .omitted)
// US: "Jan 15, 2024"

// Custom components
date.formatted(.dateTime.month().day().year())
// US: "Jan 15, 2024"

// Relative dates
date.formatted(.relative(presentation: .named))
// "yesterday", "tomorrow", "in 2 days"

Common Mistakes

// WRONG - US-only format, breaks in other locales
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"

// CORRECT - adapts to locale
formatter.dateStyle = .short

// WRONG - hardcoded separator
let dateStr = "\(month)/\(day)/\(year)"

// CORRECT - use formatter
let dateStr = date.formatted(date: .numeric, time: .omitted)

Number Formatting

NumberFormatter

let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .decimal

let number = 1234567.89
formatter.string(from: NSNumber(value: number))

// US: "1,234,567.89"
// Germany: "1.234.567,89"
// France: "1 234 567,89"

FormatStyle (iOS 15+)

let value = 1234567.89

// Decimal
value.formatted(.number)
// US: "1,234,567.89"

// With precision
value.formatted(.number.precision(.fractionLength(0...2)))

// Percentage
0.856.formatted(.percent)
// US: "85.6%"
// Arabic: "86%"

Currency Formatting

NumberFormatter

let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency

let price = 29.99
formatter.string(from: NSNumber(value: price))

// US: "$29.99"
// UK: "GBP29.99" or "PS29.99"
// Japan: "JPY30" (rounded, no decimals)
// France: "29,99 EUR" (comma decimal, space before symbol)

FormatStyle (iOS 15+)

let price = 29.99

// User's currency
price.formatted(.currency(code: "USD"))
// US: "$29.99"

// Specific currency
price.formatted(.currency(code: "EUR"))
// "$29.99" displayed as "EUR29.99"

// Narrow symbol
price.formatted(.currency(code: "USD").presentation(.narrow))
// "$29.99" instead of "US$29.99"

Decimal vs Currency

// Currency amounts should use Decimal, not Double
let price = Decimal(string: "29.99")!

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.string(from: price as NSDecimalNumber)

Measurement Formatting

MeasurementFormatter

let distance = Measurement(value: 100, unit: UnitLength.meters)

let formatter = MeasurementFormatter()
formatter.locale = Locale.current

formatter.string(from: distance)

// US: "328 ft" (converts to imperial)
// Metric countries: "100 m"

FormatStyle (iOS 15+)

let distance = Measurement(value: 5, unit: UnitLength.kilometers)

// Natural units for locale
distance.formatted(.measurement(width: .abbreviated))
// US: "3.1 mi"
// Metric: "5 km"

// Force specific unit
distance.formatted(.measurement(width: .abbreviated, usage: .asProvided))
// Always: "5 km"

List Formatting

ListFormatter

let items = ["apples", "oranges", "bananas"]

let formatter = ListFormatter()
formatter.locale = Locale.current
formatter.string(from: items)

// English: "apples, oranges, and bananas"
// Spanish: "manzanas, naranjas y platanos"
// Chinese: "apples, oranges and bananas"

FormatStyle (iOS 15+)

let names = ["Alice", "Bob", "Charlie"]

names.formatted(.list(type: .and))
// "Alice, Bob, and Charlie"

names.formatted(.list(type: .or))
// "Alice, Bob, or Charlie"

Person Name Formatting

var components = PersonNameComponents()
components.givenName = "John"
components.familyName = "Smith"

let formatter = PersonNameComponentsFormatter()
formatter.style = .default
formatter.string(from: components)

// Western: "John Smith"
// Chinese/Japanese/Korean: "Smith John" (family name first)

Locale-Specific Sorting

let names = ["Angstrom", "Zebra", "Apple", "aardvark"]

// Locale-aware sort
let sorted = names.sorted { (lhs, rhs) in
    lhs.localizedStandardCompare(rhs) == .orderedAscending
}

// Case-insensitive, locale-aware
let sorted2 = names.sorted {
    $0.localizedCaseInsensitiveCompare($1) == .orderedAscending
}

SwiftUI Integration

Text with Formatting

struct PriceView: View {
    let price: Decimal

    var body: some View {
        Text(price, format: .currency(code: "USD"))
    }
}

struct DateView: View {
    let date: Date

    var body: some View {
        Text(date, format: .dateTime.month().day().year())
    }
}

struct CountView: View {
    let count: Int

    var body: some View {
        Text(count, format: .number)
    }
}

Environment Locale

struct FormattedView: View {
    @Environment(\.locale) var locale

    var body: some View {
        // Format using environment locale
        Text(Date(), format: .dateTime.locale(locale))
    }
}

// Preview with specific locale
struct FormattedView_Previews: PreviewProvider {
    static var previews: some View {
        FormattedView()
            .environment(\.locale, Locale(identifier: "fr_FR"))
    }
}

Best Practices

  1. Never hardcode format strings - Use style-based formatting
  2. Always use Locale.current - Unless explicitly overriding
  3. Use Decimal for money - Avoids floating-point errors
  4. Test multiple locales - US, Germany, Japan, Arabic cover most edge cases
  5. Prefer FormatStyle - Modern, type-safe, SwiftUI-friendly

Testing Locales

// Unit test with specific locale
func testGermanCurrency() {
    let formatter = NumberFormatter()
    formatter.locale = Locale(identifier: "de_DE")
    formatter.numberStyle = .currency

    let result = formatter.string(from: 1234.56)
    XCTAssertEqual(result, "1.234,56 EUR")
}

Scheme testing:

  1. Edit Scheme > Run > Options
  2. Application Region > Choose region
  3. Run and verify formatting

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides a set of documentation guides for modern iOS localization workflows, including Xcode String Catalogs, locale-aware formatting, pluralization rules, and Right-to-Left (RTL) layout support. No executable files, dependencies, external downloads, or suspicious behaviors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    5 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d62eb71. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 9 months ago

README badge

README badge for johnrogers/claude-swift-engineering/localization