Jacob’s Tech Tavern

Jacob’s Tech Tavern

Localization in Xcode 15

Utilise new String Catalogs to internationalize your Swift apps

Jacob Bartlett's avatar
Jacob Bartlett
Dec 11, 2023

String Catalogs are a neat new feature introduced in WWDC 2023.

.strings files (and, to an even greater extent, .stringsdict files) were one of the final bastions of Objective-C-era cruft in the Apple development environment.

But no more.

I’ve been upgrading Bev, my trusty, boozy side project, to iOS 17. Along the way, I’ve been implementing the teachings from WWDC23’s Discover String Catalogs.

Join me on the journey!

The Bad Old World™

Prior to Xcode 15, most of your strings will be defined in a .strings file:

// Home screen
"home_screen_title"="Home";
"home_screen_button_add_friend"="Add friend";
"home_screen_button_settings"="Settings";
"home_screen_friends_list"="Your friends list";

You missed one semicolon?

Too bad, every string in your module is now broken, displaying home_screen_title or home_screen_button_add_friend in place of your actual copy.

.stringsdict files are even tougher to grasp. Because different languages have different grammatical rules for handling pluralisation, we need a far more complex beast to define a single string, even to say something as simple as “3 friends”:

<dict>
    <key>home_screen_friends_count</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@friends_count@</string>
        <key>friends_count</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>zero</key>
            <string>No friends</string>
            <key>one</key>
            <string>1 friend</string>
            <key>other</key>
            <string>%d friends</string>
        </dict>
    </dict>
</dict>

Creating a String Catalog

When I first wrote Bev, I didn't bother internationalising the strings.

😱

We could argue all day that the API only returns data in English, or that I was never intending to publish the app to the 175 countries reachable on the App Store.

But frankly, it was just sloppy work.

Shame on me.

On the plus side, however, this gives us a clean slate to start from.

What sort of text exists in the app?

The Bev app contains 2 screens:

  1. The list of beers

  2. The details for a specific beer.

There are a few types of UI components — and a few types of strings — which we will want to internationalize.

// Simple standalone strings
//
NavigationStack {
    beerListView
        .navigationTitle("Bev")
}
Alert(title: Text("Error"), /* ... */ )
Section("Drink this with") { /* ... */ }

// Strings with variable text inside  
//
Text("First brewed \(beer.firstBrewed)") // e.g. "First brewed 2020"

// Strings with formatted numbers 
//
Text(String(format: "%.2f", beer.abv) + "% abv") // e.g."4.05% abv"

// Pluralised strings 
//
Text("\(viewModel.beers.count) beers") // e.g. "100 beers"

The String Catalog

First things first.

Let’s create a new file in our Resources/ folder, and select the new String Catalog file type to add to our app target.

Creating a new String Catalog file

Initially, however, the String Catalog is fairly nondescript. What’s all the fuss about?

The empty String Catalog

The magic happens when we run our code, and Xcode populates this file at compile-time. In an older project, this might not happen automatically — make sure to set Use Compiler to Extract Swift Strings in Xcode Build Settings.

The newly-populated String Catalog

Wow! 🤩

Suddenly, all my raw strings are vacuumed up into our String Catalog, the Localizable file.

For the interested — this .xcstrings file is actually implemented as JSON if you simply open the file in a text editor. At build-time, Xcode actually compiles the String Catalog down into .strings and .stringsdict files in order to maintain full backwards compatibility.

Implicit LocalizedStringKeys

The reason this “magic” happens is, SwiftUI primitives implicitly look up a LocalizedStringKey whenever they are provided a string literal. These implicit LocalizedStringKeys are extracted by the compiler to populate our String Catalog at Localizable.xcstrings.

From Apple’s docs: This implicit lookup actually works because LocalizedStringKey conforms to the ExpressibleByStringLiteral protocol.

Internationalising Bev

Now we’ve learned some theory, and populated our initial String Catalog, we can finally internationalise the various strings in the app.

Simple standalone strings

These are our simplest case; where there is a simple piece of text in a SwiftUI primitive, such as:

Section("Yeast")

The LocalizedStringKey is implicitly generated, so our String Catalog already works:

Yeast!

However, it’s good practice to organise our string keys by screen and feature, so I namespace them like Section.FeatureName.Screen.String:

Section("Feature.Beer.DetailView.Yeast")

It’s also very polite to add context to the String Catalog for our future translators:

A more well-structured localized string in the table

Strings with variable text inside

Very often, we will want to display text which involves string interpolation based on a variable or a property.

Text("First brewed \(beer.firstBrewed)")

It’s pretty clear that this might not work in other languages, even if the text was translated — in the grammar of many languages, we may want to put the year first.

The String Catalog cleverly accounts for this.

We first re-write our implicit LocalizedStringKey to this more exact form, along with an argument that we inline via string interpolation:

Text("Feature.Beer.DetailView.FirstBrewed \(beer.firstBrewed)")

The String Catalog generates the string including the interpolated value, using %@ as the placeholder:

“First brewed %@” with built-in string interpolation

Subscribe for free to continue reading

Enter your email and get every post from Jacob’s Tech Tavern.
Already have an account? Sign in
© 2026 Jacob Bartlett · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture