iOSDC_matsuoka

@iOSDC Japan

9月5日(木) 〜 9月7日(土)にかけて開催されたiOSDC Japan 2019において、サイバーエージェントは今年もダイアモンドスポンサーを務め、 5名のエンジニアが登壇しました。

この記事では松岡(@matsuokah_)の「多言語対応と戦う2019年版」のセッションについて高橋(@tomosan_dev)がご紹介します。

多言語対応って本当に必要?

多言語対応は1つの国で利用されるアプリならば極論多言語対応は必要ではないですが、世界各地で利用されるアプリを開発するには必要です。
また、アプリのソースコードに対象言語を直打ちするのではなくコードからラベルなどに注入したい場合に有効と言えるでしょう。

基本的なローカライズ方法

.stringsファイルを作る

ローカライズの基本として言語対応するためにそれぞれの言語で.stringsファイルを作成します。以下の例は日本語と英語に対応するために作成するファイルたちです。
Localizable.strings(Base)

"app.hello" = "hello";
"app.world" = "world";

Localizable.strings(Japanese)

"app.hello" = "こんにちは";
"app.world" = "世界";

Localizable.strings(English)

"app.hello" = "hello";
"app.world" = "world";

Swiftでローカライズされた文字を読む

// 日本語設定では「こんにちは」、英語設定では「hello」
let hello = NSLocalizedString("hello", comment: "") 
// 日本語設定では「世界」、英語設定では「world」
let world = NSLocalizedString("world", comment: "") 

このときのポイントとして「app.world」のようにネームスペースを利用することで単語の整理に役立ちます。
また、共通の単語についてもまとめておくことで複数定義しなくて済むと思います。

ローカライズをタイプセーフに行おう

上記のようにしてローカライズを行うことができます。しかし、このコードはNSLocalizedStringのkey名をわざわざ.stringsファイルから確認しなければならない上にタイプセーフではありません。
そこでSwiftGenR.swiftなどのライブラリを使うことで解決できます。
松岡のセッションではSwiftGenを用いた方法が紹介されています。

SwiftGenのコードテンプレートはデフォルトだと以下のようなコード生成になります。

internal enum L10n {
  internal enum App {
    internal static let hoge = L10n.tr("ファイル名", "key名")
  }
}

let hoge = L10n.App.hoge // 簡単にアクセスでき、タイプセーフ

ただしデバッグ用の文言や、文言整理の観点からファイルを分けたい場合、L10nのenumが競合してしまいます。
そこでSwiftGenのstencilをカスタムすることでL10nをextensionとして定義し、競合を防ぐことができます。

デバッグのときに言語変更したい..!

iOS12以前ではUserDefaults.standard.array(forKey: “AppleLanguages”)を利用して言語変更機能を実装しておく必要があります。このプロパティは配列で返却されるので一番先頭の要素の言語がアプリに適用されます。

複数形の単語のローカライズ

Xcodeのファイル生成からStringsdictファイルを生成します。StringsファイルとStringsdictファイルは同じkey名が存在するとStringsdictの方から読まれるのでStringsdictの方には複数形に対する分岐を記述します。

スクリーンショット 2019-10-01 19.56.50

SwiftGenではとりあえず、keyとなるapp.apple.sentenceをつくっておきます。その後Stringdictファイルを構成したのが上記です。(発表資料でも言及されています)

NSStringLocalizedFormatKeyに指定されたものが最初に読み込まれます。この場合はsentenceです。sentenceの中にはotherの場合にさらにもう一階層下にapplesがあるのでapplesのなかでone, otherのときに複数形を分岐することができます。

装飾する際の範囲をNSRangeの直指定に気をつける

ローカライズを進めていくと必ず文章の長さは変わっていきます。文字の装飾にはNSAttributedStringを利用しますがrangeに直接数値をいれてしまうと装飾範囲にずれが出てくるので気をつけなければなりません。

そこで、range(of:)を利用してローカライズされた文字を引数に渡して範囲すると良いでしょう。

翻訳SaaSを利用して多言語対応をスムーズに

Crowdinという翻訳SaaSを利用することで多言語対応がスムーズに行えます。Github連携しておくことで翻訳者さんにPRを出してもらい、本体コードにマージすることができて便利です。しかしながら翻訳がすべてとは限らず国によってその国の意訳などもあるので注意は必要です。

Assetもローカライズする

WWDC2019でも発表されましたがXcode11で画像のローカライズができるようになりました(13:00あたり)。

Q&A

Localize.stringsが整理できなくなってしまうのではないですか?
→ Localize.stringsファイルを分けたり、ネームスペースをしっかり区切ることで解決しています。

Storyboardやxibについてはどうローカライズしていますか?
→ 対象プロパティのdidSetでコードから注入しています。

サーバーから取得した文言についてはどういう対応をしていますか?
→サーバー側でローカライズされているが将来的にはhttpHeaderのaccepted-languageを参照してクライアントから注入していきたいと考えています。

多言語対応でUIが崩れてしまうのではないですか?
→実際に困っていますが、現地の方に様々な表現方法を聞いてUI対応しています。

ネームスペースの判断基準はなにかありますか?
→予期せぬ変更で他のコンポーネントの文言が変わってしまう可能性があるので文言はまとめすぎないことは意識しています。

まとめ

今回のセッションを聞いて自分自身が知らなかったStringdictや多言語対応するためにいまからしておくべきことなどが明確になったと感じました。みなさんも多言語対応を意識してまずは第一言語からでも実践してみませんか?ぜひ参考にしてみてください。