Localization basics: static strings and process

In 2016, 50% of facebook's user spoke language other than English. Localization is the way to reach wider audience. But how do we implement it? Let's start with the most basic thing to localize - static strings, which are mainly visible in labels, buttons and pop-ups.

Localization of static strings

For the sake of example let's image we have an app with base localization (development language) set to English and we'll be adding a second one, Polish. The only screen this app has is displaying "Welcome". First, we need to declare supporting Polish language. To do that select project in Xcode's navigator panel and make sure to select project in Xcode's main window. Then select Editor > Add Localization... > Polish (pl) in menu bar. Once that's done, in our View Model, we should replace string literal "Welcome" with a call to NSLocalizedKey, that's going to return the most suitable localized version of phrase.

// var greetingText: String {
//     "Welcome"
// }

var greetingText: String {
    NSLocalizedKey(
        "Welcome", 
        comment: "big welcoming text visible in first screen during onboarding"
    )
}

To store numerous phrases in different languages we can't simply keep them in the view model's source file. Instead we keep them separately in dedicated files called Strings Files. We're going to need one so, create it by choosing File > New > File... in menu bar, then choose Strings File template and name the file Localizable. Inside this file we are going to store the only phrase we have, following "key" = "phrase"; syntax.

"Welcome" = "Welcome";

To indicate that this version of string file is valid for English localization, select the file in Xcode's navigator, then in file inspector menu click Localize... button. Xcode will ask you to select language in which this file is localized, in our case that's English. To add Polish translation for phrases from this file check Polish in file inspector menu under Localization section. Xcode copied the Localizable.string file and marked new file as suitable for Polish localization. Select the polish version and replace English entry

"Welcome" = "Witaj!";

That's it! You can check in in simulator, by setting Polish as first preferred language in iPhone's settings. Now, let's decompose what happened, to get a better understanding of it.

NSLocalizedString

Surprisingly, NSLocalizedString isn't a type as the syntax may suggest, instead it's Fundation's top level function, that returns a localized string.

func NSLocalizedString(
    _ key: String,
    tableName: String? = nil,
    bundle: Bundle = Bundle.main,
    value: String = "",
    comment: String
) -> String

Second non optional parameter is comment that describes the context in which the phrase is used, in order to make translation process spotless. For example, initial translation of set along with key might not carry enough context to properly translate it — not only set is a verb and a noun, but also as a verb, it can be translated differently depending on a context. In polish language set as in Hey Siri, set timer for 5 minutes would be translated to ustwaić whereas in Hey Siri, how do I set the table it'd be nakryć. Those comments, depending on the export process, get exported into .xliff or .strings files, giving translators extra context.

This function forwards table, key and value to a method on specified Bundle (or on Bundle.main if nothing is specified, which is usually the case) to make signature shorter.

Bundle.main.localizedString(forKey: String, value: String?, table: String?)

This method does some of the heavy lifting for us. It smartly determines, which language to use, taking into consideration user preferences. It honours the user's preferred language order and graciously finds fallback if needed. For example, if the app supports English and Spanish, while the user's first preferred language is Spanish (Latin America), system will choose Spanish. Then it will find localized resource and look for the translation for specified key.

table, key and value

table argument, although sounds vague, is nothing more than the name of a .strings file containing the translation for specified key. If nil is specified method defaults to Localizable.string. By no coincidence that's the default name of strings file. This parameter might become useful if you decide to split translations to separate files ie. based on module of the app in which they are used.

key is a unique, in the scope of a specified table, identifier for the translation we're referencing.

The value parameter comes into play only when there's no mach for the key in table. Then, if specified, value would be returned, so we might think of it as a fallback. If you omit this parameter and a key mismatch occurs, the key will be returned.

SwiftUI

Support for localization in SwiftUI is built-in. If you ever used it, there's a good chance you wrote views in localizable fashion, without even knowing about it. Consider following view:

struct LocalizableView: View {

    var body: some View {
        Text("Welcome")
    }
}

Nothing fancy about it, just a piece of text. However, after inspecting definition of Text's initializer, we see that it's similar to NSLocalizedString's.

init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)

This piece of code would actually show translated phrase, if it was part of our app. If you'd put any other phrase as key, it would show that phrase, since there's no value parameter and the key will be return on look up failure.

Localization process: exporting and importing

If you're working in a team, most likely you aren't going to translate stuff. Once you add a new supported localization and replace all static strings with calls to NSLocalizedString, it's time to export resources for localization and hand them to localizator or localization team. To export all resources marked as localizable, select a project in Xcode's navigator and choose Editor > Export for Localization... from menu bar.

Xcode will create a separate Xcode Localization Catalog (.xcloc) for every supported localization. Inside this catalog, in Localized Contents folder you can find .xliff file. This format is a common language between different localization tools ie. lokalise. It contains only string resources and should allow easy import.

Once localisation is done and you received .xcloc back, import it by selecting a project in Xcode's navigator window and choose Editor > Import Localisations... from menu bar. Navigate to and select a single .xcloc. During import process Xcode will convert data in .xliff to .strings and .stringsdict files.

Linting

Although, if everything seems good — the key is in the correct table, table is in correct Bundle, appropriate localisation was added to the project, but still key (or value) is returned instead of translations, it's always a good idea to lint the .strings file, especially if the were edited by hand.

To do it we take advantage of the fact that .strings files are property list files with exotic formatting. That fact allows us to use the plutil command line utility.

plutil -lint Localizable.strings

or if we want to parse more than one file at a time:

plutil -lint -- First-table.strings Second-table.strings

Conclusion

In this article I've presented localization of static strings, but that's not enough to truly localize an app. We did not cover pluralisation, variable width translations, or platform dependent localization. Thankfully iOS has feature reach localization APIs, that come in handy. If you'd like to learn about more about localization check out other articles from this series.


twitter logoDo you find this article insightful? Let others know!