Storyboard/XIB and localization best practice

2020-02-03 05:23发布

The officially recommended method for XIB/Storyboard localization is to create .xib and .storyboard files inside xx.lproj (where xx is the two letter language ID) for each localization you want to support.

This creates a problem because you have multiple files that in many cases share the same UI, that are prone to change. If you wanted to re-design the UI for one view, you'll have to do it multiple times (worse if you entered the localizable string values in the xib itself). This goes against the DRY principle.

It seems way more efficient to call NSLocalizedString() where you need it, and just use one XIB or Storyboard for one base localization.

So, why should(n't) I create localized XIB/Storyboard files?

11条回答
狗以群分
2楼-- · 2020-02-03 06:08

As explained by Leszek S you can create a category.

Here I'll give you an example in swift 3 with extension for UILabel and UIButton:

  1. First of all create a "StringExtension.swift" file
  2. Add on it this code:

    extension String { func localized() -> String { let bundle = Bundle.main return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "") } }

  3. Then create another new file with the name you want (for example) "LocalizableObjectsExtensions.swift"

  4. Add on it an extension for UIButton and one for UILabel like this (of course you can create extension for what you want, UITextField...):

    extension UIButton { var localizedText: String { set (key) { setTitle(key.localized(), for: .normal) } get { return titleLabel!.text! } } }

    extension UILabel { var localizedText: String { set (key) { text = key.localized() } get { return text! } } }

  5. Now go in your Storyboard and for your button and/or you label that you want localize just add in the identity inspector of you object this:

enter image description here

FYI: here Key Path it's the name of the function you added in your extensions (UIlabel and UIButton) and Value is the name of the key that you want translate automatically which is in your Localizable.strings file. For example in your Localizable.strings (French) you have the key/value "ourOffers" = "NOS OFFRES";

Now build & Run. Your Object will be translated in the language of your device if you have the key/value in your Localizable.string. Enjoy :)

查看更多
走好不送
3楼-- · 2020-02-03 06:08

IMHO Xcode has one among the worst localization features available around...

I really don't like developing for Android but I must admit Android Studio has a better localization system.

That said, because I really cannot stand anymore to recreate Storyboard.strings after each mod (you know, Xcode won't update them for you...), this is how I do :

I have a couple of extensions to loop subviews (and subviews of subviews) and I deal with each of the main objects (labels, textfield, buttons...) by localizing their main properties (text, placeholde...) through a simple helper (AltoUtil.ls) which is a "short" version for NSLocalizedString.

Then I insert texts and placeholders with underscores (for example "_first_name", "_email_address") in my storyboard/xibs and I add those strings to each Localizable.strings file.

Now I just need to call the localize() function in viewDidLoad (or whereber I need it) so that I can have the whole view controller localized. For cells I just call the localize() inside the awakeFromNib() method for example.

I'm sure this is not the fastest method (due to subviews loop) but I don't get any slowdown compared to other methods I used to use and it's pretty productive.

import UIKit

extension UIView {

    func localize()
    {
        for view in self.allSubviews()
        {
            if let label = view as? UILabel
            {
                label.text = AltoUtil.ls(label.text)
            }
            else if let textField = view as? UITextField
            {
                textField.text = AltoUtil.ls(textField.text)
                textField.placeholder = AltoUtil.ls(textField.placeholder)
            }
            else if let button = view as? UIButton
            {
                button.setTitle(AltoUtil.ls(button.title(for: UIControl.State.normal)), for: UIControl.State.normal)
            }
            else if let searchBar = view as? UISearchBar
            {
                searchBar.placeholder = AltoUtil.ls(searchBar.placeholder)
            }
        }
    }

    func allSubviews() -> [UIView]
    {
        return subviews + subviews.flatMap { $0.allSubviews() }
    }
}

The second extension is needed to localize view controllers title and tab bar items in view controllers. You can add any item you need to localize.

import UIKit

extension UIViewController {

    func localize()
    {
        self.title = AltoUtil.ls(self.navigationItem.title)
        self.tabBarItem?.title = AltoUtil.ls(self.tabBarItem?.title)

        self.view.localize()        
    }
}
查看更多
成全新的幸福
4楼-- · 2020-02-03 06:12

I came across this post and several others while trying to make xib localization easier for myself. I posted my method of including IBOutles for labels/buttons on this question, worked great for me, keeps all changes limited to the Localization.strings files.

https://stackoverflow.com/a/15485572/1449834

查看更多
再贱就再见
5楼-- · 2020-02-03 06:12

I used a similar approach as Leszek Szary described for my views in Swift.

Using a Boolean value as opposed to the localization keys, I added an "On/Off" drop down menu that determines whether the initial text values should be localized or not. This allows for the Storyboard to be kept clean without any extra upkeep.

Image1

When a value is selected, a single Runtime Attribute is added to the view and is used as a condition from within it's setter.

Image2

Image3

Here is the code from my .swift file which extends UIButton, UILabel, UITabBarItem and UITextField, including the text field placeholder and button control states:

import UIKit

extension String {
    public var localize: String {
        return NSLocalizedString(self, comment: "")
    }
}

extension UIButton {
    @IBInspectable public var Localize: Bool {
        get { return false }
        set { if (newValue) {
            setTitle( title(for:.normal)?.localize,      for:.normal)
            setTitle( title(for:.highlighted)?.localize, for:.highlighted)
            setTitle( title(for:.selected)?.localize,    for:.selected)
            setTitle( title(for:.disabled)?.localize,    for:.disabled)
        }}
    }
}

extension UILabel {
    @IBInspectable public var Localize: Bool {
        get { return false }
        set { if (newValue) { text = text?.localize }}
    }
}

extension UITabBarItem {
    @IBInspectable public var Localize: Bool {
        get { return false }
        set { if (newValue) { title = title?.localize }}
    }
}

extension UITextField {
    @IBInspectable public var Localize: Bool {
        get { return false }
        set { if (newValue) {
            placeholder = placeholder?.localize
            text = text?.localize
        }}
    }
}

You could also use the new property to easily translate values that are set while your program is running like this:

let button = UIButton()

button.setTitle("Normal Text", for: .normal)
button.setTitle("Selected Text", for: .selected)

button.Localize = true
查看更多
Emotional °昔
6楼-- · 2020-02-03 06:14

Useful post, much easier than multiple XIBs. I extended the code to handle UISegmentedControl:

if ([v isKindOfClass:[UISegmentedControl class]]) {
    UISegmentedControl* s = (UISegmentedControl*)v;
    for (int i = 0; i < s.numberOfSegments; i++) {
        [s setTitle:NSLocalizedString([s titleForSegmentAtIndex:i],nil) forSegmentAtIndex:i];
    }
}
查看更多
做个烂人
7楼-- · 2020-02-03 06:16

For just changing text labels I did something like this

+(void) replaceTextWithLocalizedTextInSubviewsForView:(UIView*)view
{
    for (UIView* v in view.subviews)
    {
        if (v.subviews.count > 0)
        {
            [self replaceTextWithLocalizedTextInSubviewsForView:v];
        }

        if ([v isKindOfClass:[UILabel class]])
        {
            UILabel* l = (UILabel*)v;
            l.text = NSLocalizedString(l.text, nil);
            [l sizeToFit];
        }        

        if ([v isKindOfClass:[UIButton class]])
        {
            UIButton* b = (UIButton*)v;
            [b setTitle:NSLocalizedString(b.titleLabel.text, nil) forState:UIControlStateNormal];
        }        
    }    
}

call this function in your viewDidLoad: like this:

[[self class] replaceTextWithLocalizedTextInSubviewsForView:self.view];

It saved me a lot of work declaring and connecting IBOutlets when all you want is localized labels.

查看更多
登录 后发表回答