Interface Builderで行うローカライゼーション (DRY String Localization with Interface Builder)

[Original Link](https://medium.com/flawless-app-stories/dry-string-localization-with-interface-builder-665496eb0270)
by [Victor Pavlychko](https://medium.com/@victor.pavlychko)
translated Korean by [pilgwon](https://pilgwon.github.io)

![image1](https://pilgwon.github.io/blog/site_media/img/20170829_DRY_Localization/image1.png)

格好いいアプリには格好いいローカライゼーションが必要であります。そしてユーザーは自分が好きなアプリが自分の言語で利用できるオプションがあるのに喜ぶでしょう。開発初期段階だとしてもインターフェースのローカライゼーションをサポートすることにおいて開発者は異論がないでしょう。特にそれがやりやすいのであれば最もやるべき作業ではないでしょうか。

私はデザインをInterface Builderでやることが好きです。この記事にはローカライゼーションをどのようにアクセスするかを共有するつもりです。

普通、XIBファイルかStoryBoardをローカライゼーションする時、 ファイルかStoryBoardをローカライゼーションする時、Xcodeは気持ちよくリソースをコピーするし、貴方は重複されたView Layoutに打つかるでしょう。orz..DRY方法論をやろうとするとそれはいい選択と言えないでしょう。 😕

そうする代わりにInterface Builderのローカライゼーション用語に “@”-prefixを付けておいて ```viewDidLoad```か ```awakeFromNib```のようなメソッドで各自に合うローカライゼーションされた値で変更してあげることを提案します。以下がInterface Builderで見た時どう見られるのかのサンプルです:

![image2](https://pilgwon.github.io/blog/site_media/img/20170829_DRY_Localization/image2.png)

ローカライジェーションの対象に ```@``` prefixを利用して必要であれば他の実際の値と混ぜ合わせるようにします。追加で、```@@``` prefixはローカライジェーションしなくてもいいのに```@```が含まれている場合に```@```の代わりに利用します。

Swiftによくあるように先にプロトコルから始めます:

```　https://gist.github.com/victor-pavlychko/b6a1b37899485e5d24a8938509e018d3#file-better_localization_localizable-swift
public protocol Localizable {
    func localize()
}
```

次は文字列をローカライジェーションして属性にローカライジェーションされた値を適用するのに役に立つヘルパー達を含めているExtensionです:

```https://gist.github.com/victor-pavlychko/56da390872c9044b913e5c88f2ad401d#file-better_localization_localizableextension-swift
public extension Localizable {
    
    public func localize(_ string: String?) -> String? {
        guard let term = string, term.hasPrefix("@") else {
            return string
        }
        guard !term.hasPrefix("@@") else {
            return term.substring(from: term.index(after: term.startIndex))
        }
        return NSLocalizedString(term.substring(from: term.index(after: term.startIndex)), comment: "")
    }
    
    public func localize(_ string: String?, _ setter: (String?) -> Void) {
        setter(localize(string))
    }

    public func localize(_ getter: (UIControlState) -> String?, _ setter: (String?, UIControlState) -> Void) {
        setter(localize(getter(.normal)), .normal)
        setter(localize(getter(.selected)), .selected)
        setter(localize(getter(.highlighted)), .highlighted)
        setter(localize(getter(.disabled)), .disabled)
    }
}
```

```
参考: ２番目のローカライジェーションヘルパーはSwift 4では KeyPath Syntaxアップグレードが必要になります。
```

今まではいいです。これからいくつかのローカライジェーションを実装してみましょう。その過程自体はコンテナーがその子供に自分をローカライジェーションさせる作業のため明白的に再帰的です:

```https://gist.github.com/victor-pavlychko/7561f44595381a91c456b06097e30d65#file-better_localization_uiviewextensions-swift
extension UIView: Localizable {
    public func localize() {
        subviews.forEach { $0.localize() }
    }
}
```

この実現には大体のアプリで必要にする共通コントロールに関するローカライジェーションサポートを追加してみます。実装方法は簡単です:

```https://gist.github.com/victor-pavlychko/a92451e923a5c11e5a242d3fe4c8f131#file-better_localization_uiviewextensions2-swift
public extension UILabel {
    public override func localize() {
        super.localize()
        localize(text) { text = $0 }
    }
}

public extension UIButton {
    public override func localize() {
        super.localize()
        localize(title(for:), setTitle(_:for:))
    }
}
```

UIButtonのタイトルを設定することは可能な限り全てのコントロール状態に対するローカライジェーションを適用する他のヘルパーを利用することを覚えておきましょう。

```View```のみが```Interface Builder```で設定できるオブジェクトではありません。
次に出るオブジェクトも設定が可能なので覚えておきましょう:
 
1.  ```UIBarItem```とそのサブクラス :  ```UIBarButtonItem``` そして ```UITabBarItem```
2.  ```UINavigationItem```
3.  あなたのアプリで利用すると決めた全てのオブジェクト

```https://gist.github.com/victor-pavlychko/a0fc51221de4d287891ddcbf3b00f7b1#file-better_localization_uinavigationitemextensions-swift
extension UIBarItem: Localizable {
    public func localize() {
        localize(title) { title = $0 }
    }
}

public extension UIBarButtonItem {
    public override func localize() {
        super.localize()
        customView?.localize()
    }
}

extension UINavigationItem: Localizable {
    public func localize() {
        localize(title) { title = $0 }
        localize(prompt) { prompt = $0 }
        titleView?.localize()
        leftBarButtonItems?.forEach { $0.localize() }
        rightBarButtonItems?.forEach { $0.localize() }
    }
}
```

最後に、ローカライジェーションフローをどこかでは始めないといけません。そのためには主に次のイベントをよく使います:
1. ```UIViewController```のサブクラス ```viewDidLoad```メソッドの中で```title```,```navigationItem```,```tabBarItem```そして```view```をローカライジェーションします。
2. ```awakeFromNib```メソッドの中で```UITableViewCell```のコンテンツと```UICollectionViewCell```のサブクラスをローカライジェーションします。 

コンテンツ、ナビゲーションそしてタブバーをローカライジェーションする時に使う```UIViewController```のためのヘルパーは以下となります:

```https://gist.github.com/victor-pavlychko/25b8fbdaf02d9af90d5de7a9e391f90c#file-better_localization_uiviewcontrollerexensions-swift
extension UIViewController: Localizable {
    public func localize() {
        localize(title) { title = $0 }
        navigationItem.localize()
        tabBarItem?.localize()
        view.localize()
    }
}
```

最後まで読んでいただきましてありがとうございました。これからあなたのプロジェクトに適用してみましょう。 😉. 記事の内容が良かったり、直接プロジェクトに適用してみたとしたら 👏 の押下のこと宜しくお願い致しますね。(本文記事にあります。)