Localization is a key part of an application. In order to make it easy, maintainable and extensible it deserves a bit of effort and care. But for iOS graphic interfaces made with Interface Builder, it becomes a bit tricky to accomplish those goals.
We have a Storyboard with an interface like this:
There are two controls suitable to be localized: Navigation title label and the description label. We want to localize the UI in English and Russian.
The classic strategy is to have two Main.storyboard files, one in a en.lproj folder and the other in a ru.lproj folder. That sounds simple but it leads to a huge problem of maintenance as some change in the UI requires changing all the Storyboards. Imagine an app localized in more than two languages.
The second option is to tick “English — Localizable Strings” in the localization pane in the File inspector of the Storyboard; it creates a strings file called Main.strings located in the folder en.lproj. This file looks like this:
It has the usual syntax of a strings file, but the first thing you may notice is how the controls are identified. We have names like kCO-9u-5kw for the Navigation Title label, or ju9-fj-dox for the description Label.
Well, it is a bit confusing, but we can move on and localize the UI to Russian. We create the new Russian Localization in the Configuration pane of the project and automatically Xcode creates a new Main.strings file inside the ru.lproj folder.
It sounds pretty straightforward, but this workflow has a bunch of problems:
If you change something in IB, the strings files are not updated. You have to use ibtool or AppleGlot to get it done. More about this here.
The names of the controls are not in a human readable manner, not even if we have filled the “Label” property of the control (Identity Inspector > Document).
We have localizations dispersed in more than one file: one for the localizations in code (Localizable.strings) and another to localize IB files (Main.strings).
Probably, we’ll have translations repeated in both files if we need to use it in code and IB.
So I gave a thought to this, and after a couple of searches, I arrived to a Stack Overflow answer that pointed to a perfect solution to this: to use the User Defined Runtime Attributes that we can find at the Identity Inspector. Let’s see how to achieve it.
The first step is to create a couple of very simple protocols. Localizable, that we will use to get a localized string from another string used as the key:
protocol Localizable {
var localized: String { get }
}
extension String: Localizable {
var localized: String {
return NSLocalizedString(self, comment: "")
}
}
And StbIBLocalizable, that we will use to localize controls from an IB file:
protocol StbIBLocalizable {
var stbLocKey: String? { get set }
}
Now we have to implement this interface in those controls suitable to be localized:
extension UILabel: StbIBLocalizable {
@IBInspectable var stbLocKey: String? {
get { return nil }
set(key) {
text = key?.localized
}
}
}
extension UIButton: StbIBLocalizable {
@IBInspectable var stbLocKey: String? {
get { return nil }
set(key) {
setTitle(key?.localized, for: .normal)
}
}
}
NOTE: get is irrelevant in this case, we are not going to need the name of the key used in the Localizable.strings file anywhere.
NOTE 2: in order to make this run on Swift 4 you have to add @IBInspectable to the xibLocKey implementation, else it will not work:
The second step is to set, for each control, its associated localization key. We have two Localizable.strings files.
en.lproj file:
"fill_form" = "Fill in the form";
"name" = "n a m e";
"sex" = "s e x";
ru.lproj file:
"fill_form" = "Заполните анкету";
"name" = "и м я";
"sex" = "п о л";
Now it is time to set these keys in IB. To do that we have to:
As our properties are @IBInspectable you can also fill the stbLocKey in the Attributes Inspector:
This is a very clever and simple solution, and it avoids two important flaws we will encounter in other solutions:
Thanks for reading!