iOS localization tricks for Storyboard and NIB files

01 Nov, 2014
Xebia Background Header Wave

Localization in iOS from Interface Builder designed UI has never been without any problems. The right way of doing localization is by having multiple Strings files. Duplicating Nib or Storyboard files and then changing the language is not an acceptable method. Luckily Xcode 5 has improved this for Storyboards by introducing Base Localization, but I've personally come across several situations where this didn't work at all or when it seemed buggy. Also Nib (Xib) files without ViewController don't support it. In this post I'll show a couple of tricks that can help with the Localization of Storyboard and Nib files.

Localized subclasses

When you use this method, you create specialized subclasses of view classes that handle the localization in the awakeFromNib() method. This method is called for each view that is loaded from a Storyboard or Nib and all properties that you've set in Interface Builder will be set already. For UILabels, this means getting the text property, localizing it and setting the text property again. Using Swift, you can create a single file (e.g. LocalizationView.swift) in your project and put all your subclasses there. Then add the following code for the UILabel subclass: [objc] class LocalizedLabel : UILabel { override func awakeFromNib() { if let text = text { self.text = NSLocalizedString(text, comment: "") } } } [/objc] Now you can drag a label onto your Storyboard and fill in the text in your base language as you would normally. Then change the Class to LocalizedLabel and it will get the actual label from you Localizable.strings file. Screen Shot 2014-10-31 at 22.45.46 Screen Shot 2014-10-31 at 22.46.56 No need to make any outlets or write any code to change it! You can do something similar for UIButtons, even though they don't have a single property for the text on a button. [objc] class LocalizedButton : UIButton { override func awakeFromNib() { for state in [UIControlState.Normal, UIControlState.Highlighted, UIControlState.Selected, UIControlState.Disabled] { if let title = titleForState(state) { setTitle(NSLocalizedString(title, comment: ""), forState: state) } } } } [/objc] This will even allow you to set different labels for the different states like Normal and Highlighted.

User Defined Runtime Attributes

Another way is to use the User Defined Runtime Attributes. This method requires slightly more work, but has two small advantages:

  1. You don't need to use subclasses. This is nice when you already use another custom subclass for your labels, buttons and other view classes.
  2. Your keys in the Strings file and texts that show up in the Storyboard don't need to be the same. This works well when you use localization keys such as myCoolTableViewController.header.subtitle. It doesn't look very nice to see those everywhere in your Interface Builder labels and buttons.

So how does this work? Instead of creating a subclass, you instead add a computed property to an existing view class. For UILabels you use the following code: [objc] extension UILabel { var localizedText: String { set (key) { text = NSLocalizedString(key, comment: "") } get { return text! } } } [/objc] Now you can add a User Defined Runtime Attribute with the key localizedText to your UILabel and have the Localization key as its value. Screen Shot 2014-10-31 at 23.05.18 Screen Shot 2014-10-31 at 23.06.16 Also here if you want to make this work for buttons, it becomes slightly more complicated. You will have to add a property for each state that needs a label. [objc] extension UIButton { var localizedTitleForNormal: String { set (key) { setTitle(NSLocalizedString(key, comment: ""), forState: .Normal) } get { return titleForState(.Normal)! } } var localizedTitleForHighlighted: String { set (key) { setTitle(NSLocalizedString(key, comment: ""), forState: .Highlighted) } get { return titleForState(.Highlighted)! } } } [/objc]


Always try and pick the best solution for your problem. Use Storyboard Base Localization if that works well for you. If it doesn't, use the approach with subclasses if you don't need to use another subclass and if you don't care about using your base location strings as localization keys. Else, use the last approach with User Defined Runtime Attributes.

Explore related posts