iOS開発におけるローカライジングについてちょっと考えてみた

iOS開発におけるローカライジングについてちょっと考えてみた – Morizotter Blog

追記

  • 普通に都度NSLocalizedString+genstrings or Linguanの組み合わせがやっぱいいのかなと思っていたりします。

* Githubのリンクは予告なく削除されることがあります。

動機

Objective-Cのローカライズに関して、なかなかしっくり来なくてずっと考えていました。いろいろな資料を読んでもなんかグッと来ないんです。なので、自分で書いてみることにしました。その前によくあるやり方です。

よくあるやり方

Stackoverflowや様々なブログに書かれているよくあるやりかたとして以下の様なものがあります。

  1. .pch(または.hファイル)に#defineでNSLocalizedStringのエイリアスを書く
  2. ViewをたどってUILabelなどを見つけたらtextを取り出してローカライズ。
  3. Storyboardのファイルに直接ローカライズ
  4. 個別のNSLocalizedStringをdefineでローカライズ文言分書いていく

でもなんか、ちょっとまだ来ない。#define書きまくるのもうーん。ということで、自分で書いてみました。

やりたかったこと

  1. ローカライズのためのNSLocalizedStringを一つの場所にまとめたい。
  2. .stringのファイル編集はLinguanでやりたい。

NSLocalizedStringがいろんな場所に散らかるのは別にいいかもしれないんですが、「なんかまとめてみたい」という変な欲求を解決する必要がありました。

Linguanは、プロジェクトの中のLocalizedStringを全て取り出して、言語数分のローカライズの準備をしてくれます("Localize" = ""; こういうやつです)。編集もし易いので最近よく使っています。もちろんテーブルファイルもちゃんと書き出してくれます。ただ、NSLocalizedStringの中が@"Localize"のような文字リテラルでなくてはダメです。(僕が、Linguanでやっているところを多分genstringsなどのコマンドで代用することもできると思います)。

Linguan1

Linguanは検索ボタンを押すと、こんな感じで全ての言語ファイルをスプレッドシート状に表示してくれます。もちろんそのまま編集もできますし、編集用テキストファイルに書きだして、テキストファイルで編集した後読み込ませることなども可能です。Xcodeのプロジェクトを指定すれば勝手に取り出してくれるので便利です。翻訳がない場合ワーニングを出してくれたりします。ちょっと高いのが問題ですね。

こんな感じになりました

Githubで公開してみました。簡単に説明します。公開しているのは、MZRLocalizerというクラスです。利用する際はこちらをサブクラス化して、言語一覧を作成する部分をオーバーライドして使います。

- (NSDictionary *)prepareLocalizationDictionary {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    // ViewController
    dict[ExampleLocalizerViewControllerTitle] = NSLocalizedString(@"ExampleLocalizerViewControllerTitle", nil);
    dict[ExampleLocalizerViewControllerJapan] = NSLocalizedString(@"ExampleLocalizerViewControllerJapan", nil);
    dict[ExampleLocalizerViewControllerUnitedStates] = NSLocalizedString(@"ExampleLocalizerViewControllerUnitedStates", nil);
    return [NSDictionary dictionaryWithDictionary:dict];
}

最初からローカライゼーションしながら開発するということはまずないと思いますので、最初はメソッドだけ使って適当な文字を当てておきます。(下の例で言うと、@"お魚タイトル一覧"の部分です)。stringWithKey:メソッドの中ではkeyに該当する候補がなかった場合、keyをそのまま返すように実装されています。

[[ExampleLocalizer sharedInstance] stringWithKey:@"お魚タイトル一覧"];`

ある程度終盤に近づいてきたら、Localized.stringsFish.stringsなどを作ります。そして、先ほどのprepareLocalizationDictionaryに追加してゆきます。この際に、static constなどで定数を作っておきます。先ほどの@”お魚タイトル一覧”は、

[[ExampleLocalizer sharedInstance] stringWithKey:ExampleLocalizerFishManagerFishListName];

とかそんな感じになると思います。この定数の名前の付け方が多分大事なのですが、今は、「ローカライゼーションのサブクラス名+利用するクラス名+(テーブル名)+項目名」という感じが良いと思っています。上の例だと、ローカライゼーションのサブクラス名が、ExampleLocalizer、利用するクラス名がFishManager、テーブル名がFish、項目名がListNameですかね。すっごい長いです。対策考えたほうが良さそうです!でも利点はちゃんとあって、名前がかぶらないのと、コード補完の時にスムーズに対象がしぼれるってことです。

最後に、Linguanなどで翻訳を一通り作っていきます。あとは、コーディングしながらこちらの.stringsファイルも編集という感じになります。

すげー面倒。なんでわざわざ!

って思いますよね。僕もちょっと思います。でも、散らばっていて把握できなくなる気持ち悪さよりは良いかなと思います。今のところ、ローカライゼーションのやりかたは、この方法と、普通にNSLocalizedStringを都度書いていく方法の両方が良いと思っています。Linguanを使えば多分らくちんなので。

その他ローカライゼーションに関して

ローカライゼーションに関して、クラス基準でやるか言語基準でやるかということを少し悩みました。たとえば、鮭をローカライズしたいとして、NSLocalizedStringのキーをkSalmonなどにすると、kSalmonはどこでも使いまわせるし楽ちんです。ただ、これをやっていくと、増えてくると、「どっかに書いてあるはず」と思って探したり、微妙に表記がぶれる場合(大文字小文字、単数複数)などややこしくなります。また、文章をローカライズした時に、その文章を表すkLoginFailureAlertとか、わからないですけどそういうキーを付けると思いますが、これもまた増えてくると探すのが大変です。なので、重複があったらまた書けば良いということで、クラス基準。クラスAに鮭とあったら、ClassASalmonというキー、クラスBにも鮭とあったらClassBSalmonという感じでやったほうが良いかなと思いました。確かに同じものを複数書く可能性もありますが、同じものがたくさん出てくるというのは設計の問題もあるかもしれません。

これは試行錯誤の途中で出てきたものです

みなさん、ローカライズの方法についていろいろ思う所あると思うので、ご意見聞かせて下さいー。僕もこの方法を信じて自信にあふれているわけではぜんぜんないので、どんどん変なところ教えてもらえるとありがたいです。先に言っておくと、morizotter/MZRLocalizerは変な英語で書かれています。

参考資料


You may also like...

  • せむにる

    僕は英語・日本語対応のみなので、その都度 NSLocalizedString を書いちゃって、1つ目のパラメーターに翻訳する必要のない英語を直接入れてしまうパターンが多いです。Localized.strings は genstrings コマンドで生成してます。
    対応言語一つ分 Localized.strings を作らなくて良いのでメンテナンスコストを下げられますが、ソースコードはちょっと長い行が出てきたりもしますね。

    • morizotter

      おお、記事読んでくださって、更にコメントいただきましてありがとうございます。わかります。わかります。そのやり方も気楽でいいですね。シンプルな方が結局いいですね。
      今回いろいろ考えましたが、結局、普通のやり方が一番という結論に達しそうですw 今回書いた考え方だと無駄にコード量が多くなるだけのような気がしてきました。ただ、キーの命名に関しては今回考えたのを使おうかなぁと思っています。

    • morizotter

      おお、記事読んでくださって、更にコメントいただきましてありがとうございます。わかります。わかります。そのやり方も気楽でいいですね。シンプルな方が結局いいですね。
      今回いろいろ考えましたが、結局、普通のやり方が一番という結論に達しそうですw 今回書いた考え方だと無駄にコード量が多くなるだけのような気がしてきました。ただ、キーの命名に関しては今回考えたのを使おうかなぁと思っています。