Localizing your SwiftUI App
“Localization” is the process of making your app usable in multiple languages. This is different from “internationalization” which is taking localization to the next level where you handle colors and icons in a meaningful way to the locale of the app’s user. This also includes being sensitive to the format of currency, numbers, and dates and times. Fortunately, iOS does some of this for you - if you have used the right APIs. For example, if you create a Date()
and then display it using DateFormatter
with DateStyle.short
, iOS will see to it that the date reads correctly in the device locale (eg, 7/28/2020 in the United States and 28/7/2020 anywhere else in the world).
Xcode isn’t exactly as friendly as Android Studio when it comes to making your English-only (hard-coded strings) code ready for localization, so my post here shows you one way to handle this. There is a program called genstrings
that can help, but I am not covering that here.
It is helpful to pick a second language to start with. You can use British English but the differences aren’t as obvious as say using Dutch, so I will go with that.
Project Step
You will need a file that contains the original - English for myself - strings you want to be available in other languages.
Use Xcode’s File->New->File
menu command and in the template selector, pick Strings File
in the Resources
section. Name the file Localizable.strings
for this example (it can be any name you want). The file will be empty; you will add content to it below.
String Extension
Next make an extension to the String class to make some usages easier, which will make more sense a bit later in this post. For now, create a new Swift file called Strings+Extensions.swift
and put this into it:
extension String {
func localized() -> String {
return NSLocalizedString(self, comment: self)
}}
Extraction Step
Open the English version of Localizable.strings
and put it into is own Window (or vertically split the Xcode editor so that the strings file is in one half the other half you can use for source code). Open one of your files that contains hard-coded strings. For example:
Button(“Close”) { … }
In the Localizable.strings
file, add the line:
”close-button” = “Close”;
Note that both sides of the equal sign (=) are in double-quotes and the line ends with a semi-colon (;). This format is very important and Xcode will flag it as an error if the formatting is not correct.
Back in the source code, replace ”Close”
with ”close-button”
which is what is on the left side of the equal sign in the Localizable.strings
file.
Button(“close-button”) { … }
I use identifiers for the string key codes (the left side) rather than the English text, but you could also have used
”Close” = “Close”
in the localization strings file. Some people prefer the source code to read with the original strings. I find using codes makes it easier to spot missing translations.
Let’s say you have this in your code:
var defaultAddress = “Unknown Street”
You add ”default-address” = “Unknown Street”
to the Localizable.strings
file and then replace the code:
var defaultAddress = “unknown-street”
When you run the app, the default address will be “unknown-street”! What happened to the localization? The Button shows up as “Close” and not “close-button”.
This is because most SwiftUI components, like Button
and Text
not only take String
arguments but LocalizedStringKey
arguments as well. In other words, when you pass ”close-button”
to the Button
initializer, SwiftUI looks to see if that string is a localization key and if so, uses what it finds in the Localizable.strings
file; if not, it uses the string as passed.
In the case of defaultAddress
, you want to initialize it like this:
var defaultAddress = “unknown-street”.localized()
That String extension I added above comes in handy to quickly add code to programmatically switch to the localized version. The down-side is that if you use a code like I do, if you forget to put that code (or misspell it) into the Localizable.strings
file, the app will display the code at runtime. Which is why a lot of people use the English string as their code and not the way I do it. But that’s just my preference.
Localize
Now that you have all of the English code/value pairs in the Localizable.strings
file, it’s time to prepare it for translation. In the Project Navigator, select the Localizable.strings
file, then open the File Inspector (⌥+⌘+1). Tap the Localize
button.
This will create the English localization for this file and create a directory in your project called en.lproj
(you can see it with Finder).
Now open your project file from the Project Navigator (make sure your project is selected and not the target) and open the Localizations
section of the Info
tab. You will see English - Development Language
.
Tap on the Plus (+)
button and pick your languages. I picked Dutch.
A dialog opens for you to select the resources to localize. For SwiftUI you can uncheck LaunchScreen.storyboard
and just localize Localizable.strings
.
Once you have done that, look at the Project Navigator and you will see the Localizable.strings
entry is now folder with the different language files in them.
Translation Step
Unless you know people who speak the languages you are going to use (or you a native speaker - yeah for you!), you will find Google Translate to be your new best friend.
What I did was open Google Translate in new browser window and set it aside. Then I open the Localizable.strings
file for the language I’m working on. Let’s say Dutch in this case.
If you did the
Localizable.strings
file for English before creating new localizations like I suggest in the steps above, Xcode will copy all of the English strings into the Dutch file for you - saving you a lot of time.
Now all you need to do is copy the English phrase to the Google Translate window where it will automatically get translated, then copy it back and paste it into your Localizable.strings
file for that language.
Go ahead and translate all of the strings.
As good as Google Translate it, it may not be perfect. It is best to get someone who knows the target language well to proof read your app. Sometimes the context will change which word to use and you do not want to offend anyone!
Testing it Out
First, run your app in a simulator to make sure you do not see any string code keys (eg close-button
) showing up. If you do, make sure you put the correct key in the Swift code and it is spelled correctly (“closed-button” is different from “close-button” as is “Close-Button”).
Now you want to make sure the localization works and it’s pretty easy to do that.
In Xcode, edit the current schema (you can also create a new one, even one per language):
In the schema editor, change the Application Language
and Application Region
drop-downs. Here I’ve changed them to “Dutch” and “Netherlands”.
Now run your app. When it appears in the Simulator, it will behave as if it in the Netherlands and you should see all of the strings you changed now in Dutch!
One More Thing
Let’s say you are writing a SwiftUI view. For example, something that displays a product title. You might write it like this:
struct ProductTitle: View {
let label: String
let title: String
var body: some View {
HStack {
Text(label)
Spacer()
Text(title)
}
}
}
Now you want to use this new view and pass it different labels which you have put into the Localizable.strings
files:
VStack {
ProductTitle(label: “primary-label”, title: product.primary)
ProductTitle(label: “secondary-label”, title: product.subtitle)
}
When you run this, you do not get your localized text, but instead you see the code words (eg, “primary-label”). That’s weird, because the string you are just passing directly to a SwiftUI Text
view so it should work, right?
No, because the Text
view has multiple initializers. It has one which takes a String
and one which takes a LocalizedStringKey
. You passed it a String
.
So you could simply do:
ProductTitle(label: “primary-label”.localized(), title: product.primary)
but that’s kind of messy. A better approach would be to change the data type of label
inside of ProductTitle
to:
let label: LocalizedStringKey
and now the data types match what you want. When you run your app, you will see the localized strings as you expect. This is a little more versatile in that you can also pass it a string that is not a localization key.
Summary
So there you have it. Create a Localizable.strings
file with ”key-code”=“value”;
pairs (do not forget the semi-colon), use Google Translate to do the translations for you, and test it out by editing the runtime schema (or create new ones).
Een fijne dag verder and have fun and take the world by storm!