iOSアプリをサーバサイド（主にPush通知）も含めて多言語化した話

みなさんこんにちは。
[筋トレ記録アプリ](https://itunes.apple.com/jp/app/id1271132004?mt=8)を開発しているvvvvkoyoです。海外の方がトレーニング人口が多いこともあり、この度iOSアプリの多言語化対応を行いました。

このアプリでは、単純な筋トレの記録だけでなく、SNSとしての機能も提供しています。そのため、他のユーザの記録を見るための仕組みを提供する必要があり、iOS側だけでなくサーバサイドにも各種データを保存しています。

サーバサイドの処理としては、通信用のAPIの提供はもちろんのこと、ユーザによって出し分けているPush通知や、Webブラウザで筋トレ記録を閲覧するための仕組みなどが存在しています。

このエントリでは、サーバサイド（主にPush通知部分）を含めてiOSアプリの多言語化を行う場合の対応をまとめます。

# 前提
* iOSアプリ＋サーバサイドで処理をしているアプリでの多言語対応の話
* 元々は日本語のみのアプリを英語に対応させた話
* 個人開発アプリ（not 企業）
* 翻訳者＝開発者

# 対応方針
個人開発アプリのため、下記の対応方針をとりました。

* 可能な限り省コストで対応できること
* 細かいことは気にしない

多少荒い部分があったとしても死ぬわけでは無いので、細かいことは気にせずに前に進めることを優先しています。企業でお堅いアプリを多言語化する際は、もう少し考慮点が多くなると思われます。

# 多言語化対応の内容
今回の対応では、大きく下記の3つの対応を行いました。

* iOSアプリ側の対応
    * iOSアプリ内の文言の多言語化
    * iOSアプリ内の、言語に合わせた処理の分岐
* サーバサイドの対応
    * サーバサイドとiOS側の言語設定の同期
    * Push通知の多言語化

# iOSアプリ側の対応
## iOSアプリ内の文言の多言語化
Webサイトの多言語化と同じような文言の多言語化については、[こちら](https://qiita.com/Sim_progra/items/c9b04377dfa1425085c3)の記事を参考に行いました。非常によくまとまっていると思いますんので、上記の記事を参考にいただければ大丈夫かと思います。

## iOSアプリ内の、言語に合わせた処理の分岐
日本語とそれ以外の言語で処理の内容を分岐させたい部分が幾つかあったため、簡単な判定処理を作成し、それぞれの処理に挟み込みました。

なお、`NSLocale.preferredLanguages`を利用してiOSで利用している言語を取り出すと、`ja-JP`, `en-JP`といった「言語コード-国コード」といった形で取得されるので、言語コードだけを取り出す処理も簡単に作成しました。

```swift
// iOS内で利用されている言語を取得する処理
func getLanguage() -> String {
    let languages = NSLocale.preferredLanguages
    if let type = languages.first {
        return type
    }
    return ""
}

// 言語コードのみを取得する処理
func getLanguageCode() -> String {
    let lang = self.getLanguage()
    let split = lang.components(separatedBy: "-")
    return split[0]
}

// 日本語の判定処理
// 「言語コード-国コード」の形で取得されるため、jaが含まれるかどうかで判定
func isJapanese() -> Bool {
    return self.getLanguage().contains("ja") ? true : false
}
```

# サーバサイドの対応
## サーバサイドとiOS側の言語設定の同期
今回のアプリでは、Push通知の文章の生成をサーバサイドで行なっています。そのため、サーバサイドにもiOS側の言語設定と同じ言語設定を保持しておく必要があります。

### 同期のタイミング
iOSの言語設定を変更する人はごく少数と思われるため、アプリ利用開始時（正確にはサインアップ時）にアプリ内の言語設定を取得し、サインアップ時にサーバサイドに登録させる形をとりました。

この辺りをもっと正確に行うならば、起動のタイミングで言語設定のチェックを行い、過去に行ったチェック結果と異なる場合はサーバサイドの言語設定も変更する、、、といった処理を書く必要があるでしょうが、省略しました。

### 同期方法
上述のように、サインアップのタイミングで、サインアップ時に利用するAPIにiOS側の言語情報も付加するだけという一番簡単な方法をとりました。

なお、これまでに登録されたユーザについては、すべて`ja`であるとみなして、一括で対応しています。

「Push通知の言語をユーザ側にて変更したい」という要望が出る可能性がありますが、それはその要望が発生したときに考えます。

## Push通知内容の多言語化
サーバサイドとiOS側で言語情報が同期できているならば、あとは簡単です。
サーバサイドで利用しているフレームワーク等々の多言語化対応っぽいものや、IF文などでそれっぽい通知を出し分けましょう。（この辺りは、利用しているフレームワークによって最適解が異なるため、詳細は割愛します）

### Push通知の配信時間の考慮
Push通知の送信タイミングは、本来ならば考慮すべき事項です。
というのも、Push通知の送信タイミング次第でPush通知の効果は大きく異なるからです。

基本的には日本用に設定してある通知タイミングと配信対象のユーザの国とJSTとの差分を考慮して、配信時間を決める形が良さそうです。

が、なかなかに面倒なので、今回は対応しませんでした。

# まとめと振り返り
今回の対応を、簡単に振り返ってまとめます。

## 多言語化にかかった時間
今回のアプリの場合、`Localizable.strings（多言語化の設定ファイル）`の行数が約300行程度でした。つまり、300単語・文を多言語化した形になります。 

対応は下記の流れになるかと思いますが、合わせて20時間程度を要しました。（翻訳時間が大半だったような気はしますが、、）

* 対応箇所の洗い出し
* 翻訳
* 実装
* テスト

一人で行っているので、「対応箇所の洗い出し〜実装」は同じタイミングで行いました。そのため、全体の工数はかなり少なくできたとは思います。実装者と翻訳者が異なる場合などは、諸々のコミュニケーションコストが追加されるので、最低でも倍程度の時間はかかると思われます。

また、テストについてもかなり簡略化した形をとったため、こちらも通常であればもっと多くの時間が割かれるべきでしょう。

とすると、この規模のアプリであったとしても、丁寧な対応を行なった場合は延べ50-100時間程度は要すると考えられます。個人開発で細かいことを気にせずに軽いノリで行う場合にはそこまで重たくはない対応ですが、企業で開発しているアプリを多言語化する場合は、それなりの覚悟がいるなと感じました。

## 多言語化はいつ行うべきか？
多言語化はどのタイミングで行うべきかという議論については、ケースバイケースだと改めて感じました。

多言語化が初期の開発スコープに入っていないのならば、一旦は日本語のみで作り切ってしまって、後から気合いを入れて一気に片付けても、そこまで重い対応にはならないかと思います。それよりは、スピード優先で作り切ってしまう方が良い結果が生まれると思います。

逆に、開発スコープにすでに入っている場合は、最初から準備しておく以外の手はないと思います。

もし、「将来的に多言語化するかもしれないが、現状はよくわからない」といったケースに該当する場合は、「日本語の文字列を全て定数にしておく」という対応が折衷案として良いと感じました。

対応箇所の洗い出しが非常に面倒なので、その部分が定数ファイルにまとまっているだけで、かなり効率よく作業ができるかと思います。