Formats

.xcstrings

An Xcode string catalog holds every locale in one file. anylocale reads it whole and writes it whole, plurals and named substitutions included.

What you get

Everything the editor does, through the files you already ship.

  • Every plural form a language has

    Each locale gets exactly the CLDR categories it uses, Arabic six and Japanese one, written in the format's own plural shape.

  • Placeholders round-trip

    %@, %1$s and {count} come back as they went in, and a translation that drops or alters one is flagged before it is verified.

  • AI drafts, a person verifies

    Every language is drafted against your glossary and each key's length limit. A draft never ships until someone has verified it.

  • Pulls on every build

    A Run Script phase above Compile Sources keeps the target on the latest verified text without anyone remembering to sync.

    Xcode build phase
  • A spreadsheet for translators

    Export a locale to CSV, hand it to someone who works in Excel or Numbers, and import the result in one push.

    CSV
  • Updates over the air

    Publish the verified strings and let the running app fetch them, so a fixed translation reaches users without a store release.

    Over-the-air delivery

Set it up

From nothing to every language.

  1. Step 1: Add a String Catalog to the target

    File, New, File from Template, String Catalog, named Localizable. With Use Compiler to Extract Swift Strings on (the default since Xcode 15), every Text("key") and String(localized:) lands in the catalog on the next build. The comment becomes the note translators and the AI draft see.

    ContentView.swift
    import SwiftUI
    
    struct ContentView: View {
        let count: Int
    
        var body: some View {
            VStack {
                Text("welcome_title", comment: "Headline on the first screen")
                Text("cart.items \(count)", comment: "Items in the cart, pluralised")
            }
        }
    }
  2. Step 2: Add the languages in Xcode

    Project, Info, Localizations, and add each language the project has. A locale the target does not list is ignored by the build even when the catalog carries it.

  3. Step 3: Install the CLI and log in

    Homebrew or the one-line installer, then a token issued per project under Settings, Integrations. The read scope pulls; push writes source files and values; manage creates and deletes keys.

    shell
    brew install anylocale/tap/anylocale
    anylocale login --project my-app
  4. Step 4: Push the source file

    The push is additive: keys missing from the file stay in place, so a truncated file cannot empty a project. Descriptions in the file become translator notes.

    shell
    curl -fsS -X POST "https://anylocale.com/api/loc/push?project=my-app&format=xcstrings" \
        -H "Authorization: Bearer $ANYLOCALE_TOKEN" \
        --data-binary @Localizable.xcstrings
  5. Step 5: Pull the catalog back

    The format carries every locale, so the locale flag is required by the command but does not narrow the file. Every entry comes back with extractionState set to manual, which keeps Xcode from marking it stale.

    shell
    anylocale pull --project my-app --locale en \
        --format xcstrings --out Localizable.xcstrings
    Localizable.xcstrings
    {
      "sourceLanguage" : "en",
      "strings" : {
        "cart.items" : {
          "comment" : "Items in the cart, pluralised",
          "extractionState" : "manual",
          "localizations" : {
            "de" : {
              "variations" : {
                "plural" : {
                  "one" : { "stringUnit" : { "state" : "translated", "value" : "1 Artikel" } },
                  "other" : { "stringUnit" : { "state" : "translated", "value" : "%lld Artikel" } }
                }
              }
            }
          }
        }
      }
    }

Good to know

  • One catalog per string table: a project with Localizable and Settings tables pulls two files.
  • A pulled catalog marks every entry as manually extracted, so Xcode keeps entries whose key the compiler did not find in this build.
  • Device variations (a different text on iPad or Mac) are refused on push with the key named. Plurals and substitutions are fine.
  • Every pull carries the current text of every key, verified or not. Only over-the-air delivery is limited to verified strings.

Questions

Does the catalog keep its plurals and device variations?
Plurals and %#@name@ substitutions round-trip, with every category the locale has. Device variations do not: a catalog that carries one is refused on push with the key named, so keep such a key out of the catalog you push or drop the variation in Xcode.
Can I push one string table at a time?
Each catalog is one table, so Localizable.xcstrings and Settings.xcstrings are two pushes. A pull is one catalog covering the project's keys; the file flag that picks a table applies to .strings and .stringsdict only.
What happens to a key I removed from the catalog?
Nothing. The push is additive, so a key missing from the file stays in the project until it is deleted in the app or through the API with the manage scope. A truncated file cannot empty a project.
Which other formats can the same project be pulled in?
.xcstrings, .strings with .stringsdict, strings.xml, JSON, next-intl messages and CSV. A project is not tied to the format it was pushed in, which is how an iOS app and an Android app share one catalog..xcstrings.stringsstrings.xmlJSONnext-intlCSV

Start with the file your app already ships.

After checkout, import one strings file and read the first drafts within minutes.

Nothing to connect and nothing to install. The CLI is optional.