Interpolate the variable inside String(localized:), available since iOS 15 and macOS 12, or inside a SwiftUI Text literal. The key Xcode extracts contains a format specifier in place of the value, and the translation keeps that specifier.
let name = "Ana"
let count = 3
let greeting = String(localized: "Hello, \(name)!")
let summary = String(localized: "You have \(count) new messages")
When you build, Xcode adds Hello, %@! and You have %lld new messages to the String Catalog. A String becomes %@, an Int becomes %lld and a Double becomes %lf. The translator writes the words around the placeholder: "¡Hola, %@!". The initializer is documented at String(localized:table:bundle:locale:comment:).
SwiftUI needs no extra call, because a string literal passed to Text is a LocalizedStringKey:
Text("Hello, \(name)!") // looks up the key "Hello, %@!"
Before iOS 15, use NSLocalizedString for the format and substitute the values afterwards. Never interpolate inside the NSLocalizedString key: Xcode cannot extract it, and translators receive the code expression instead of the text.
let format = NSLocalizedString("Hello, %@!", comment: "Greeting with the user's name")
let greeting = String.localizedStringWithFormat(format, name)
String.localizedStringWithFormat formats numbers for the user's locale; String(format:) substitutes them without localizing.
Pitfalls:
"%2$lld messages from %1$@" takes the same arguments as the source "%@ sent %lld messages". The numbers refer to the interpolations in source order.%@ replaced by %d can crash the app.String(localized:) searches the main bundle by default; pass bundle: for strings that live in a framework or package.Published Oct 9, 2026 · Markdown version