localize.dev

Docs / iOS

iOS and macOS localization with String Catalogs, automated

Find hardcoded text in SwiftUI and UIKit, translate it into Localizable.xcstrings for every language, and wrap UIKit strings in String(localized:), in one command or pull request.

How it works

SwiftUI looks up the text of Text("…"), Button, Label, TextField, .navigationTitle and other views in the String Catalog by itself, so that code stays as written. UIKit takes plain strings, so label.text = "…", setTitle("…", for:) and alert titles become String(localized: "…").

A count with its word, "\(n) \(n == 1 ? "item" : "items")", becomes "\(n) items": the catalog varies %lld items by the count, with each language’s plural forms.

Every string goes into Localizable.xcstrings, keyed by its text the way Xcode writes it (Welcome back, %@!), with each language’s translation in the same file and numbered placeholders (%1$@, %2$lld) when there’s more than one. Text(verbatim:), image names and comments are left alone.

Before and after

The engine’s real output for this file. In a real run, the hosted model names the keys from context.

Shop/CartView.swift · before
import SwiftUI

struct CartView: View {
    let name: String
    var body: some View {
        VStack {
            Text("Welcome back, \(name)!")
            Button("Checkout") {}
        }
        .navigationTitle("Your cart")
    }
}

final class LoginController: UIViewController {
    let label = UILabel()
    override func viewDidLoad() {
        label.text = "Sign in to continue"
    }
}
Shop/CartView.swift · after
import SwiftUI

struct CartView: View {
    let name: String
    var body: some View {
        VStack {
            Text("Welcome back, \(name)!")
            Button("Checkout") {}
        }
        .navigationTitle("Your cart")
    }
}

final class LoginController: UIViewController {
    let label = UILabel()
    override func viewDidLoad() {
        label.text = String(localized: "Sign in to continue")
    }
}
locales/en.json
{
  "sourceLanguage" : "en",
  "strings" : {
    "Checkout" : {},
    "Sign in to continue" : {},
    "Welcome back, %@!" : {},
    "Your cart" : {}
  },
  "version" : "1.0"
}

What the setup adds

The first run sets up String Catalogs (Localizable.xcstrings) if the project doesn’t have it yet:

  • Localizable.xcstrings, found anywhere in the project or created at the root: add it to the app target in Xcode once

Run it

The CLI needs Node.js 22 or later, nothing else: npx runs it without adding a package.json to the project. No Node on your machine? The GitHub Action below runs it for you.

npx localize-dev login

# Preview, then write the changes
npx localize-dev run --dry-run -l es,fr,de
npx localize-dev run -l es,fr,de

# Or open a pull request instead
npx localize-dev pr -l es,fr,de

Or add the GitHub Action and get a pull request on every push. Try it on your own code first in the playground.