Localization in SwiftUI

Let’s create a simple little Swift UI application (mine’s a Mac app) to try out the Swift UI localization options.

Once you’ve created an application, select the project in the Xcode project navigator. So for example my application’s name LocalizationApp, select this item in the project navigator. From the resultant view select the project (not the target) and this will display a section labelled Localizations. This will show your Development Language, in my case this is English. Beneath this you’ll see a + button which we can use to add further languages.

Click on the + as many times as you like and add the languages you want to support. I’ve just added French (Fr) for my example.

Adding localizable strings

Add a new file to the project, select a Strings file and name it Localizable (hence it will be Localizable.strings). This file will have key value pairs, where the key will be used as the key to the localised string and, as you probably guessed, the value will be the actual localised string, for example

"hello-world" = "Hello World";

Note: if you forget the semi-colon, you’ll get an error such as “validation failed: Couldn’t parse property list because the input data was in an invalid format”.

Now if you’ve created the default Mac application using Swift UI, go to the ContentView and replace the following

Text("Hello, world!")

with

Text("hello-world")

Wait a minute, we seemed to have replaced one string with another string, why isn’t the Text displaying “hello-world”?

The Swift UI Text control (and other controls) support LocalizedStringKey. This essentially means that the code above is an implicit version of this

Text(LocalizedStringKey("hello-world"))

So basically, we can think of this (at least in its implicit form) as first looking for the string within the .strings file and if it exists, replacing it with the LocalizedStringKey. If the string does not exist in the .strings file then use that string as it is.

We can also use string interpolation within the .strings file, so for example we might have

"my-name %@" = "My name is %@";

and we can use this in this way

Text("my-name \(name)")

The %@ is a string formatter and in this instance means we can display a string, but there are other formatters for int and other types, see String Format Specifiers.

What about variables and localization?

We’ve seen that Text and the controls allow an implicit use of LocalizedStringKey but variable assignment has no such implicit capability, so for example if we declared a variable like this

let variable = "hello-world"

Now if we use the variable like this (below) you’ll simply see the string “hello-world” displayed, which is predictable based upon what we know

Text(variable)

Ofcourse we can simply replace the variable initialization with

let variable = LocalizedStringKey("hello-world")

Adding other languages

Let’s now create a new language for our application by clicking on the Localizable.strings file and in the file inspector you’ll see a Localize button. As we’d already added a language view the project navigator (at the start of this post). You’ll now see both English and French listed. The Localizable.strings file now appears a parent to two Localizable files, one named Localizable (English) and one named Localizable (French).

In the French file we’ve added

"hello-world" = "Bonjour le monde";
"my-name %@" = "Mon nom est %@";

Note: What you’ll actually find is the within the application directory there will be two folders, one named en.lproj and fr.lproj each will have a Localizable.strings file.

Testing our localizations

Ofcourse if we now run our application we’ll still see the default language,, in my case English as that’s the locale on my Mac. So how do we test our French translation?

We can actually view the translations side by side (as it were) by amending our code like this

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .environment(\.locale, .init(identifier: "en"))
            ContentView()
                .environment(\.locale, .init(identifier: "fr"))
        }
    }
}

Or (much more likely) we can go to the application code and replace the WindowGroup with the following

var body: some Scene {
   WindowGroup {
      ContentView()
         .environment(\.locale, .init(identifier: "fr"))
   }
}

Actually, there’s also another way of change the locale.

Instead of coding the change, select the application name in Xcode’s top bar and a drop down will show “Edit Scheme”. Select the “Run” option on the left and then the tab “Options”. Locate the “App Language” picker and select French (or whichever language you added as non-default). Now run the application again and you’ll see the application using the selected language localization file.

Exporting and Importing localizations

Whilst we can use google translate or other online translation services, we might prefer to export the localization files so we can send them to a professional translation service.

Select the application within the project navigator, then select Product | Export Localizations… this will create a folder, by default named “<Your application name> Localizations” (obviously where Your application name is replaced by your actual app name). This folder contains en.xcloc and fr.xcloc files in my case.

After your translation service/department/friends complete the translations, we can now select the root in the project navigator (i.e. our application) then select Product | Import Localizations… from here select the folder and files you want to import. Click the “Import” button.