故事板/XIB和本地化最佳实践

22

官方推荐方法是为了本地化XIB/Storyboard,需要在每个你想要支持的本地化中创建.xib和.storyboard文件,放在xx.lproj文件夹中(其中xx是两个字母的语言ID)。

这样会产生一个问题,因为你有多个文件,在很多情况下共享相同的UI,并且容易更改。如果你想要重新设计一个视图的UI,你将不得不多次进行操作(如果你在xib本身中输入了本地化字符串值,则更糟)。这违反了DRY原则。

似乎更有效的方法是在需要时调用NSLocalizedString(),并只使用一个XIB或Storyboard作为基础本地化。

那么,我为什么应该(或不应该)创建本地化的XIB/Storyboard文件呢?

11个回答

53

你可以像这样在UILabel、UIButton等控件上创建一个分类:

#import "UILabel+Localization.h"

@implementation UILabel (Localization)

- (void)setLocalizeKey:(NSString*)key
{
    self.text = NSLocalizedString(key, nil);
}

@end

然后在您的xib文件上使用"用户自定义运行时属性",将UILabel(或UIButton等)链接到保存在Localizable.strings文件中的键。

用户自定义运行时属性

这样,您可以将所有字符串保存在一个文件中,而不必为每种语言创建单独的xib文件。


1
这是一个非常聪明的解决方案! - emenegro
7
根据这个答案,我写了一篇更大的教程,介绍如何进行XIB和Storyboard的本地化。https://medium.com/p/easy-xib-and-storyboard-localization-b2794c69c9db - emenegro
@emenegro:很棒的博客。 - Kaey
@leszek-szary:谢谢,伙计。 - Kaey
<3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 <3 请翻译此编程相关内容。 - OXXY
你救了我的命。 - Teofilo Israel Vizcaino Rodrig

29

只需更改文本标签,我做了这样的事情:

+(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];
        }        
    }    
}

在你的viewDidLoad:中调用此函数,如下所示:

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

当你只需要本地化标签时,它可以节省很多声明和连接IBOutlets的工作。


2
我找到了至今最好的方法!对于UILabel情况,我只是添加了 [l sizeToFit]; 并将 b.titleLabel.text = NSLocalizedString(b.titleLabel.text, nil); 替换为 [b setTitle:NSLocalizedString(b.titleLabel.text, nil) forState:UIControlStateNormal]; 以避免大小问题。此外,将此按钮放入某个全局Utils类中,而不是使每个视图控制器过于复杂,这也是有意义的。 - makaron
1
不错的快捷方式,但现在必须跟踪标签和按钮中添加/编辑的字符串,并确保已将它们包含在字符串文件中。这样做会失去在事后提取所有未翻译字符串进行翻译的能力。 - Dean Davids

4
正如Leszek S所解释的那样,您可以创建一个类别。
这里我将为您提供一个在Swift 3中使用UILabel和UIButton扩展的示例:
  1. 首先创建一个名为 "StringExtension.swift" 的文件。
  2. 在文件中添加以下代码:

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

  3. 然后创建另一个新文件,可以自定义命名(例如) "LocalizableObjectsExtensions.swift"。

  4. 在文件中添加 UIButton 和 UILabel 的扩展,就像这样(当然你也可以为其他控件创建扩展,如 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. 现在进入 Storyboard,在您想要本地化的按钮和/或标签的身份检查器中添加以下内容:

enter image description here

FYI:这里的Key Path是您在扩展(UIlabel和UIButton)中添加的函数的名称,而Value是要自动翻译的键的名称,该键在您的Localizable.strings文件中。例如,在您的Localizable.strings(法语)文件中,您有键/值“ourOffers” =“NOS OFFRES”;
现在构建并运行。如果您在Localizable.string中具有键/值,则对象将被翻译为设备语言。享受 :)

4

Flax的解决方案运行良好,有一点需要注意的是,如果你有在UICollectionViews(或类似的控件)中包含在UICollectionViewCells中的UILabelsUIButtons,并且这些集合在当前视图中频繁更改,例如由于用户行为或通过异步请求填充,则为了使标签与正确的本地化字符串保持更新,可以在viewDidLayoutSubviews而不是viewDidLoad中调用本地化函数(后者只会调用一次):

- (void)viewDidLayoutSubviews
{
    [LocalizationHelper replaceTextWithLocalizedTextInSubviewsForView:self.view];
}

从这段代码中可以看出,我将本地化方法保留在静态辅助类中(正如其他人所建议的):

@implementation LocalizationHelper

+(void) replaceTextWithLocalizedTextInSubviewsForView:(UIView*)view
{
    for (UIView* v in view.subviews)
       ... <code from above> ...
}
@end

我本想将这个解决方案作为评论添加到上面,但我没有信誉分!


2

1
我在Swift中的视图中采用了与Leszek Szary描述类似的方法。
与使用本地化键相反,我添加了一个“开/关”下拉菜单来确定是否应本地化初始文本值。这样可以使Storyboard保持清洁,无需额外维护。

Image1

当选择一个值时,会向视图添加单个运行时属性,并将其用作其setter内的条件。

Image2

Image3

这是我 .swift 文件中的代码,它扩展了 UIButtonUILabelUITabBarItemUITextField,包括文本字段占位符和按钮控制状态:

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
        }}
    }
}

你也可以使用新属性来轻松地翻译程序运行时设置的值,例如:
let button = UIButton()

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

button.Localize = true

1

我正在寻找Flax提供的准确答案,但我需要它用Swift语言。所以我将其翻译成了Swift。感谢Flax。

    func replaceTextWithLocalizedTextInSubviewsForView(view: UIView) {

    for v in view.subviews {

        if v.subviews.count > 0 {
            self.replaceTextWithLocalizedTextInSubviewsForView(v)
        }

        if (v.isKindOfClass(UILabel)) {
            let myLabel: UILabel = v as! UILabel
            myLabel.text = NSLocalizedString(myLabel.text!, comment: "Text to translate.")
            myLabel.sizeToFit()
        }

        if (v.isKindOfClass(UIButton)) {
            let myButton: UIButton = v as! UIButton
            myButton.setTitle(NSLocalizedString((myButton.titleLabel?.text)!, comment: "Text to translate.") as String, forState: .Normal)
            myButton.sizeToFit()
        }
    }
}

这适用于Swift 2.1


1

我查看的每个地方都说,您必须为每个本地化实例复制整个xib文件,即使您只想提取文本并为每个本地化实例复制不同语言的文本。

如果有人知道一种方法可以仅复制xib文件中可见的用户文本(用不同的语言),而无需为每种语言复制整个xib文件,请告诉我们。


1

这篇文章很有用,比创建多个XIB文件要容易得多。我扩展了代码以处理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];
    }
}

0

在我看来,Xcode 的本地化功能是最糟糕的之一...

我真的不喜欢为 Android 开发,但我必须承认 Android Studio 有一个更好的本地化系统。

话虽如此,因为我真的无法忍受每次修改后都要重新创建 Storyboard.strings(你知道,Xcode 不会自动更新它们...),这就是我的做法:

我有几个扩展来循环子视图(和子视图的子视图),并通过一个简单的帮助程序(AltoUtil.ls)处理每个主对象(标签、文本框、按钮...)的本地化主要属性(文本、占位符...),它是 NSLocalizedString 的“简短”版本。

然后,我在我的 storyboard/xib 中插入带下划线的文本和占位符(例如“_first_name”、“_email_address”),并将这些字符串添加到每个 Localizable.strings 文件中。

现在,我只需要在 viewDidLoad 中调用 localize() 函数(或者我需要它的任何地方),这样我就可以将整个视图控制器本地化。对于单元格,例如,在 awakeFromNib() 方法中调用 localize()。

我相信这不是最快的方法(由于子视图循环),但与我以前使用的其他方法相比,我没有任何减速,并且非常高效。

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() }
    }
}

第二个扩展是为了本地化视图控制器的标题和选项卡栏中的项目。您可以添加任何需要本地化的项目。
import UIKit

extension UIViewController {

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

        self.view.localize()        
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接