DailyRoundup

Localization and String Management

DailyRoundup uses Xcode’s .xcstrings format as the single source of truth for all user-facing strings. This document explains how the system works and how to add new strings correctly.


Overview

All user-facing strings live in:

DailyRoundup/Localizable.xcstrings

The file is automatically included in the app target because the project uses Xcode’s PBXFileSystemSynchronizedRootGroup — any file added to the DailyRoundup/ folder is picked up automatically without editing project.pbxproj.


How it works in SwiftUI

SwiftUI’s Text, Label, Button, Section, and similar initializers accept LocalizedStringKey when a string literal is passed. The system looks up the literal as a key in Localizable.xcstrings at runtime and returns the translation for the current locale.

// ✅ Already LocalizedStringKey — just make sure the key is in Localizable.xcstrings
Text("Settings")
Label("Sync now", systemImage: "arrow.clockwise")
Button("Cancel") {  }
Section("Troubleshooting") {  }

When a String variable is passed instead of a literal, SwiftUI uses the StringProtocol overload, which displays the string verbatim (no lookup). Avoid this for user-facing strings:

let title: String = "Settings"
Text(title)          // ❌ verbatim — does NOT go through Localizable.xcstrings

let key: LocalizedStringKey = "Settings"
Text(key)            // ✅ LocalizedStringKey — looked up in Localizable.xcstrings

Adding a new user-facing string

1. Static string in a SwiftUI view

Use a string literal directly. Add a matching entry to Localizable.xcstrings:

Text("My new string")

Localizable.xcstrings entry:

"My new string" : {
  "comment" : "Brief description of where this string appears"
}

2. String computed outside of SwiftUI (non-UI code)

Use String(localized:) and add a matching entry to Localizable.xcstrings:

let message = String(localized: "My new string")

This is required wherever a String value is later passed to a SwiftUI view via a variable (since the variable loses the LocalizedStringKey type), or to a system API that takes String (such as UNNotificationAction.title).

3. String with a dynamic substitution

Use String(localized:) with a String.LocalizationValue that contains an interpolation. The key stored in Localizable.xcstrings uses a printf-style specifier:

Swift interpolated type Format specifier in .xcstrings key
Int / Int64 %lld
String %@
Double / Float %f
// Code
let progress = String(localized: "Importing \(count) items…")

// Localizable.xcstrings key
"Importing %lld items…" : {
  "comment" : "Progress message; %lld is the item count"
}

4. Plural forms

For proper plural handling, add variations to the entry and use the String(localized:) interpolation form:

let title: LocalizedStringKey = count == 1
    ? "Resolve 1 Conflict…"
    : "Resolve \(count) Conflicts…"

This generates separate keys ("Resolve 1 Conflict…" and "Resolve %lld Conflicts…") that translators can vary independently per locale. Alternatively, use .xcstrings plural variations for a single key.


Struct members that accept LocalizedStringKey

Some private structs in the app are deliberately typed to accept LocalizedStringKey so that string literals passed at the call site go through the localization system automatically:

Struct Member Type
MetricRow label LocalizedStringKey
MenuBarButton title LocalizedStringKey

When creating new reusable view structs, prefer LocalizedStringKey over String for any member that will be rendered as visible text.


Adding a translation

  1. Open DailyRoundup/Localizable.xcstrings in Xcode.
  2. Select the language to add under Editor › Add Language….
  3. Fill in the translated values for each string.
  4. For strings that were added without explicit localizations, Xcode shows them with state "new" — translate those first before releasing to that locale.

Checklist for new strings