
Not so long ago, I published detailed
instructions for using LocoLaser , a utility for localizing Android and iOS applications in Google Sheets. I would like to continue the topic of localization and pay more attention to iOS applications. Unlike Android, in iOS development there are a number of small but unpleasant moments, which, in sum, can lead to problems that are far from minor.
Today I want to pay special attention to Interface Builder. We all know, it is not perfect. But this is the only thing we have and we have to put up with it. In this article, I will talk about the main problem that you may encounter when localizing applications in Interface Builder, and will also tell you how to deal with it.
The essence of the problem
When you translate a Storyboard or XIB file, in addition to the main file with markup, additional files are created with resource lines. These resource files are usually uploaded to special tables and given to translators. The trouble is that the keys for the lines in this file are based on the Object ID, which are generated automatically and there is no way to influence them. If you managed to copy or cut and then paste a View, Interface Builder will generate new identifiers and the translation will be lost.
')
It turns out that you can not fix the translation for any particular View. If you move it, the transfer will not move. For example, you have a ViewController on the storyboard which you decide to store separately. You create a new XIB file, move the ViewController there, but the translations do not move. Moreover, for each View a new identifier will be created and you will have to adjust the row identifiers manually. Just copy the translations will not work.
In addition, the inability to influence row identifiers does not allow having a common database of rows on several platforms at once. That, in turn, will lead to the inability to use utilities that generate resource files for different platforms.
Everyone solves this problem as they can. I remember 2 years ago, on a mobile conference, I asked one of the leading iOS developers of one of the leading companies how they solve the problem of interface localization. At that time I was just starting to learn iOS, but I already had quite a lot of experience in the field of Android and I had something to compare with. Honestly, I was stunned by the answer. In ViewController via IBOutlet, they received links to Label and other View and translated them programmatically. In code, it looks like this:
class MainViewController: UIViewController { @IBOutlet var labelToTranslate: UILabel! override func viewDidLoad() { super.viewDidLoad() self.labelToTranslate.text = NSLocalizedString("scr_main_txt_example", comment: "Some Example text") ... } ... }
In this case, all strings are in the same Localizable.strings file. This method is currently the most common and is used almost everywhere. Admit, it turns out not too elegant. Not only do you litter the ViewController with an extra code that doesn’t belong here, but you still don’t solve the problem of copying or moving the View. It's time to find something better.
Decision
And here I have something that I can offer you. The fact is that in Interface Builder, in the properties of the View, you can write the so-called "User Defined Runtime Attributes". We will use them then. But first you need to create an Extension for UILabel.
extension UILabel { public var lzText : String? { set { if newValue != nil { self.text = NSLocalizedString(newValue, comment: “”) } else { self.text = nil } } get { return self.text } } }
Now all UILabels have the lzText property. If this happens, a localized string is written to the text property. We use this property in Interface Builder.

- Select UILabel and go to the tab "Identity Inspector";
- Click the add attribute button in the “User Defined Runtime Attributes”;
- Specify the attribute key “lzText”, type: “String”, value: “scr_main_txt_example”
And it's all. No more cluttering up the code. You can not be afraid that the translation or link to View will be lost when copying or moving to another container. Attributes are copied with View. The only thing that remains unmeasured is storing all the strings in a single Localized.strings file.
UPD:But that's not all.
DjPhoeniX suggested doing even better. And we do not need to change almost anything. Just add
@IBInspectable
before declaring the property.
Modified Extensions.swift extension UILabel { @IBInspectable public var lzText : String? { set { if newValue != nil { self.text = NSLocalizedString(newValue, comment: “”) } else { self.text = nil } } get { return self.text } } }
Now this property is also available on the Attributes Inspector tab.

For even more convenience, I have prepared a file that contains a sufficiently large number of extensions for frequently used View, where each text property has a twin with the prefix “lz” (abbreviated to “localized”). You can find this file in the example for using LocoLaser:
LocalizationExtensions.swift . The entire project is published under the Apache 2.0 license, you can safely copy this file to yourself and start using.
In addition to the extensions for View, in LocalizationExtensions.swift, an extension has been added to the String class. It adds a computed localized property that returns a localized string. If the translation cannot be found, an alert is sent through NotificationCenter. You can subscribe to these alerts and handle them as you please. In the Debug build, you can write to the log or show notifications; in the Release build, send a report to the analytics system.
As a result, after applying the above method, all work with strings remains in Interface Builder. Plus, you get an additional mechanism for catching "broken" lines.
On this finish. Thanks for attention. Use on health!