iOS / Android アプリの多言語対応をサーバー側で管理する方法を検討してみた
はじめに
こんにちは!🦙
最近、アプリの多言語対応で「アプリ内のテキストをサーバー側で管理したい」という要望を受けることがありました。
確かにサーバー側で管理できれば、アプリのリリースを待たずにテキストの修正や新しい言語の追加ができて便利そうですよね。
この要件を実現するために色々と思考したので、その過程を後々のために残しておこうと思います。
結論に至った考え方を記録することで、今後同じような判断をする際の参考にしたり、もっと良い方法が見つかった時に比較検討できると考えています!
先に結論
結論から言うと、実装コストと運用の安定性を総合的に考慮すると、現状では型安全なアプリ内バンドル方式が最も適切 という考えに至りました。
サーバー側での管理は魅力的ですが、実際に検討してみると以下のような課題が存在します。
- 🚫 変数代入や複数形の処理が複雑
- 🚫 バンドルされたテキスト(アプリ名など)は更新不可
- 🚫 新言語追加時にアプリ側の対応が必要
- 🚫 セキュリティやバージョン管理の複雑さ
- 🚫 パフォーマンスとオフライン対応の課題
一方で、アプリ内バンドルだとサーバー管理のメリットは享受できませんが、以下のような利点があります。
- ✅ 型安全性が保証される
- ✅ シンプルな実装
今回は、その検討過程で感じた課題や気づきを詳しくまとめてみました。
最初に考えたアプローチ
最初にお話を聞いたときは、単純に「翻訳データをDB等でレコードやオブジェクトで管理すればできそう」と考えていました。
例えば、以下のようなテーブル構造です。
CREATE TABLE translations (
id VARCHAR PRIMARY KEY,
language_code VARCHAR,
text TEXT
);
これを元にAPIから翻訳データを取得して、アプリ内で表示するという流れを想定していました。
直面した課題
1. 変数代入や複数形の対応が大変
しかし、実際の多言語対応では以下のような複雑なケースがあることに気づきます。
変数代入の例:
"Hello {userName}, you have {count} messages"
複数形の例
// 英語では以下のように複数形が変わる
"You have 1 message" // 単数
"You have 2 messages" // 複数
// 日本語では複数形の概念が異なる
"メッセージが1件あります"
上記の他にも、リッチテキストや男性/女性名詞など、様々な要素が絡んできます。
これらをDBで管理するとなると、変数の置換処理や複数形のルール管理がかなり複雑になることが想像できました。
2. 既存の多言語対応ライブラリを活用しよう → override 機能を発見
独自実装は大変そうなので、既存の多言語対応ライブラリを活用する方向で検討を進めることにしました。
私のプロジェクトでは Flutter を使っているため、最近多言語対応時によく使用している slang をベースに検討することにしました。
slang の特徴:
- YAML/JSON ファイルから Dart コードを生成
- 型安全な翻訳キーの参照
- 変数代入や複数形の対応
- 階層構造のサポート
README を調べているうちに、slang の translation_overrides
を使えば、アプリにバンドルされた翻訳ファイルを実行時に上書きできることがわかりました。
これを使えば、サーバー側で翻訳ファイルを管理し、アプリ起動時にダウンロードして上書きすることが可能になります。
3. ストレージを使ったファイル配信方式
slang の override 機能を活用して、以下のような仕組みを設計しました。
- Firebase Storage などに翻訳ファイル(JSON/YAML)を保存
- アプリ起動時にファイルをダウンロード
- slang の override 機能で翻訳を上書き
これなら変数代入や複数形、その他要素も slang パッケージの自動生成コードで型安全に処理できるようになるので良さそうです!
実際に簡単なサンプルアプリを作成して検証したところ、期待通りに動作することを確認できました。
さらなる課題の発見
4. バンドルされていて外から更新できないテキスト
しかし、よく考えてみると以下のようなテキストは外部から更新できません。
-
アプリ名:
Info.plist
やAndroidManifest.xml
で定義 -
許諾ダイアログの説明文(iOS):
InfoPlist.xcstrings
で定義
これらのテキストは、アプリのビルド時に決定されるため、サーバー側で管理してもアプリの再ビルドが必要になります。
つまり、全てのテキストをサーバー側で管理することはできないという制約があることが明らかになりました。
5. 新しい言語の追加対応
また、サーバー側で新しい言語を追加したい場合はどうすればよいでしょうか?
型安全な多言語ライブラリでは、翻訳ファイルから自動生成されたコードを使って翻訳処理を行います。
しかし、サーバー側で新しい言語を追加した場合、その言語に対応する自動生成コードがアプリ側に存在しないため、そもそも翻訳処理自体ができません。
対策として考えられるのは、あらかじめ追加しそうな言語をダミー訳でアプリ側に含めておくことです。
将来的に追加を検討している言語をダミー訳(デフォルト言語など)で登録しておけば、正式な翻訳データが用意でき次第、サーバー側でファイルを更新するだけで反映できます。
しかし、この方法にも以下の課題があります。
- 想定外の言語を追加したい場合は、結局アプリの再ビルドが必要
- アプリ内で使用可能な言語の一覧と実際に翻訳済みの言語一覧に差異が生じる
- 対応言語一覧をサーバー側で管理する必要があり、二重管理になる
6. 翻訳データ構造の変更は不可
さらに、以下のような変更は単純なファイル差し替えでは対応できません。
- 変数代入や複数形の種類を変更する
- キー名や構造を大幅に変更する
これらの変更は画面描画ロジックや型定義/メソッド呼び出し箇所にも影響するため、アプリ側ソース修正が必要です。
また、コードに参照されていないキーの動的追加も意味がありません。
例えば、画面コードで home.title
のみを参照している状態で、サーバーに home.subtitle
を追加しても、該当コードがそのキーを参照していなければ表示されないためです。
7. セキュリティとバージョン管理の課題
サーバー側で翻訳を管理する場合、以下の課題も考慮する必要があります。
セキュリティの問題:
- 翻訳ファイルへのアクセス制御
- 不正な翻訳データのダウンロード/アップロード防止
バージョン管理の問題:
- アプリのバージョンと翻訳の整合性確保
- 新しい機能に対応した翻訳キーの管理
- ロールバック時の対応
パフォーマンスの問題:
- アプリ起動時のダウンロードタイミング
- オフライン時の対応
- ダウンロード頻度の最適化
検討結果
ここまで様々な課題を検討した結果、「テキスト修正が頻繁に行われないのであれば、翻訳テキストを更新・追加したい場合はアプリをアップデートする運用がシンプルで安全」という結論に至りました。
サーバー管理を導入すると以下のようなデメリットが多く、実装も複雑になるためです。
- セキュリティ、バージョン管理やキャッシュ設計の複雑化
- オフライン対応やパフォーマンスの考慮が必要
- 言語設定をアプリ側とサーバー側で二重管理することになる
- 多言語対応は本来ローカルでの利用を前提に設計されているため相性が良くない
これらの要因を総合的に考慮すると、開発工数の大幅な削減にもつながるため、シンプルで確実な方法を選択する方が良いと判断し、そのように提案しました。
結論:実装コストと運用の安定性を重視するならアプリ内バンドルが適切
検討の結果、以下の理由から 現状では型安全なアプリ内バンドル方式が最も適切 だと判断しています。
アプリ内バンドルのメリット
✅ 型安全性: 翻訳キーの存在がコンパイル時に保証される
✅ パフォーマンス: アプリ起動時に即座に翻訳が利用可能
✅ オフライン対応: ネットワークがなくても翻訳が表示される
✅ シンプルな実装: 複雑なキャッシュやバージョン管理が不要
サーバー側管理が有効なケース
一方で、以下のようなケースでは独自実装をしてでもサーバー側で管理するメリットがあるかもしれません。
マーケティングメッセージの頻繁な更新
- キャンペーン告知やプロモーション文言
- A/Bテストでのメッセージ最適化
緊急時の対応
- 障害やメンテナンス情報の即座な配信
- 重要な告知やアップデート案内
地域限定コンテンツ
- 地域イベントや現地限定情報
- 法規制に応じた表記の変更
大規模チームでの開発
- 非エンジニアの翻訳者が直接更新したい場合
- 複数の地域チームが独立して管理したい場合
これらのケースでは、アプリストアの審査を待たずに即座にテキストを更新できるメリットが実装コストを上回る可能性があります。
さいごに
多言語対応は、単純にテキストを翻訳するだけでなく、変数代入、複数形、文化的な違いなど、様々な要素を考慮する必要があります。
「サーバー側で管理すれば便利」という発想は自然ですが、実装の複雑さ、パフォーマンス、セキュリティなどを総合的に考えると、現状では型安全なアプリ内バンドル方式が最もバランスの取れた選択肢と言えると思います。
ただし、プロジェクトの要件や規模によって最適な方法は変わるので、それぞれのメリット・デメリットを理解して選択することが大切ですね。
もし「こんな方法があるよ!」や「こういうケースではサーバー側管理が有効だった!」という経験がある方がいらっしゃいましたら、ぜひコメントで教えてください🙏
Discussion