RESTful APIにおける国際化対応(i18n)データ設計:多言語文字列とモデルの表現
はじめに
現代のウェブアプリケーションやモバイルアプリケーションは、世界中のユーザーに利用されることが増えています。そのため、APIが提供する情報も多言語に対応している必要があります。RESTful APIのデータモデリングにおいて、多言語対応(国際化、i18n)は特に文字列データをどのように表現し、クライアントに提供するかが重要な課題となります。
この記事では、RESTful APIで多言語データを扱う際のデータモデリングについて、いくつかの具体的なパターンとそれぞれのメリット・デメリット、そして設計上の考慮事項を解説します。
なぜ多言語対応のデータモデリングが課題となるのか
APIが返すデータの中に、ユーザーインターフェースに表示されるようなテキスト(例: 商品名、商品説明、エラーメッセージなど)が含まれる場合、これらのテキストはユーザーの言語に合わせて表示されるべきです。しかし、単一のフィールドに一つの言語の文字列だけを持たせる設計では、多言語に対応できません。
例えば、Product
リソースが name
というフィールドを持つ場合を考えます。
{
"id": 123,
"name": "Apple",
"price": 100
}
この設計では、「Apple」という商品名を日本語ユーザーには「リンゴ」と表示させたい、といった要求に応えられません。APIは、クライアントが必要とする言語の文字列を提供できるようなデータ構造である必要があります。
クライアントは通常、HTTPの Accept-Language
ヘッダーなどを使って希望する言語を示します。APIは、その言語に応じたデータをレスポンスとして返す必要があります。これを実現するためには、サーバー側のデータ構造およびAPIレスポンスのデータモデリングが適切に行われていることが不可欠です。
多言語データの表現パターン
多言語文字列データをAPIレスポンスの中でどのように構造化するか、いくつかの代表的なパターンがあります。ここでは、主に以下の3つのパターンについて説明します。
- フィールドごとに言語コードをサフィックスとして付与
- 言語コードをキーとするMap/オブジェクト構造
- 別のリソース(関連リソース)として管理
それぞれのパターンについて、具体的なJSON構造の例と、メリット・デメリットを見ていきましょう。ここでは、商品名 (name
) と商品説明 (description
) が多言語対応が必要なフィールドであると仮定します。対応する言語は英語 (en
) と日本語 (ja
) とします。
パターン1: フィールドごとに言語コードをサフィックスとして付与
多言語化が必要なフィールド名の末尾に、言語コードをアンダースコアなどで区切って付与するパターンです。
JSON構造例:
{
"id": 123,
"name_en": "Apple",
"description_en": "A sweet red fruit.",
"name_ja": "リンゴ",
"description_ja": "甘くて赤い果物です。",
"price": 100
}
メリット:
- データ構造が比較的フラットでシンプルです。
- 特定のフィールドに対してどの言語が存在するかが分かりやすいです。
- 単一のリソース内で全ての言語データを含むため、1回のAPI呼び出しで必要な言語データをまとめて取得できます。
デメリット:
- 対応言語が増えるたびに、フィールド数がその言語数倍に比例して増加します。これはスキーマの肥大化や管理の複雑化を招く可能性があります。
- 新しい言語を追加する際に、既存のフィールドに加えて新しい言語のフィールドを追加する必要があり、APIの互換性に影響を与える可能性があります(特に、後方互換性を保つためにはフィールド追加は可能ですが、管理が煩雑になります)。
- 一部のフィールドだけが多言語対応している場合に、対応していないフィールドと混在して分かりにくくなることがあります。
考慮事項:
- このパターンは、対応言語数が少なく、多言語化が必要なフィールドも限定的である場合に適しているかもしれません。
- フィールド名の命名規則を厳密に定める必要があります(例:
[フィールド名]_[言語コード]
)。
パターン2: 言語コードをキーとするMap/オブジェクト構造
多言語化が必要なフィールドを、言語コードをキー、文字列を値とするJSONオブジェクト(Map)として表現するパターンです。
JSON構造例:
{
"id": 123,
"name": {
"en": "Apple",
"ja": "リンゴ"
},
"description": {
"en": "A sweet red fruit.",
"ja": "甘くて赤い果物です。"
},
"price": 100
}
メリット:
- 対応言語が増えても、フィールド数は変わりません。フィールド内のネストされたオブジェクトに言語データが追加されるだけです。
- 新しい言語の追加が比較的容易です(既存のフィールドの構造を変えずに、オブジェクト内に新しいキーを追加するだけです)。
- どのフィールドが多言語対応しているかが一目で分かります。
- フィールド内の構造を見れば、どの言語データが存在するかが明確です。
デメリット:
- パターン1よりデータ構造がネストされます。
- クライアントが特定の言語のデータを取得する際に、ネストされた構造をパースする必要があります。
- APIのレスポンスとして、常に全ての言語のデータを返す設計にした場合、不要な言語データも含まれることになり、レスポンスサイズが大きくなる可能性があります。
考慮事項:
- API設計として、クライアントからの要求言語に応じてサーバー側でデータをフィルタリングし、特定の言語のデータのみを返すようにするか(例:
GET /products/123?lang=ja
またはAccept-Language: ja
に応じて、name.ja
だけを返す)、あるいは全ての言語を常に返す設計にするかを決定する必要があります。常に全ての言語を返す場合は、クライアント側で必要な言語を選択して利用します。 -
一般的には、クライアントが必要とする言語のみを返す設計が、レスポンスサイズの最適化とパフォーマンスの観点から推奨されます。この場合、サーバー側でリクエストヘッダーやクエリパラメータから言語を判定し、レスポンスボディの構造を動的に生成する必要があります。例:
Accept-Language: ja
の場合、レスポンスは以下のようになるかもしれません(ただし、これはパターン2の変種と考えるべきです。元のパターン2は全ての言語を含む構造です)。json { "id": 123, "name": "リンゴ", // ja の値 "description": "甘くて赤い果物です。", // ja の値 "price": 100 }
この変種の場合、APIの設計(エンドポイントやレスポンス構造)は言語によって変化しないようにし、返す値だけを変えるのが一般的です。
パターン3: 別のリソース(関連リソース)として管理
多言語化が必要な文字列データを、元のリソースとは別のリソースとして管理するパターンです。元のリソースからは、この関連リソースへの参照を持つか、あるいは関連リソースをネストして含める形を取ります。
例えば、Product
リソースに対して、その翻訳情報を表す ProductTranslation
リソースを用意するイメージです。
データ構造のイメージ (テキストによるER図的な表現):
+------------+ 1:N +----------------------+
| Product |------------------| ProductTranslation |
+------------+ +----------------------+
| id | | id |
| price | | product_id (FK) |
| ... | | lang_code |
+------------+ | name |
| description |
+----------------------+
API表現のパターン:
-
ネストされたリソースとして表現: 元のリソース取得時に、関連する翻訳情報もネストして含める。
JSON構造例:
json { "id": 123, "price": 100, "translations": [ { "lang_code": "en", "name": "Apple", "description": "A sweet red fruit." }, { "lang_code": "ja", "name": "リンゴ", "description": "甘くて赤い果物です。" } ] }
メリット: * 元のリソースと翻訳データが論理的に分離されます。 * 新しい言語の追加や翻訳フィールドの追加・変更が、元のリソースのスキーマに影響を与えません。 * ネストされた構造のため、どの言語が存在するか、関連する翻訳データは何かが見やすいです。
デメリット: * データ構造がネストされます。 * APIのレスポンスとして、常に全ての言語の翻訳リストを返す場合、レスポンスサイズが大きくなります。
考慮事項: * このパターンでも、クライアントからの要求言語に応じて、サーバー側で
translations
リストから該当する言語のエントリのみをフィルタリングして返す、あるいはリストではなく単一の翻訳オブジェクトを返す、といった最適化が可能です。 -
関連リソースとして参照: 元のリソースからは翻訳データへの直接的な参照を持たず、別途翻訳リソースを取得させる。
APIエンドポイントのイメージ: *
GET /products/{id}
-> 商品の基本情報を返す(翻訳データは含まない) *GET /products/{product_id}/translations
-> 特定商品の全言語の翻訳リストを返す *GET /products/{product_id}/translations/{lang_code}
-> 特定商品の特定言語の翻訳データを返すGET /products/123
のJSON構造例:json { "id": 123, "price": 100, // 翻訳データは含まない、あるいは翻訳リストへのURIを示す // "translations_url": "/products/123/translations" }
GET /products/123/translations
のJSON構造例:json [ { "lang_code": "en", "name": "Apple", "description": "A sweet red fruit." }, { "lang_code": "ja", "name": "リンゴ", "description": "甘くて赤い果物です。" } ]
メリット: * リソースが明確に分離され、責務が分割されます。 * 元のリソース取得時のレスポンスサイズを小さく保てます。 * 必要な言語の翻訳データだけをピンポイントで取得できます(例:
GET /products/123/translations/ja
)。デメリット: * 元のリソースと翻訳データを同時に表示する場合など、クライアントは複数のAPI呼び出しが必要になることがあります(いわゆるN+1問題に似た状況)。
考慮事項: * このパターンは、翻訳データが非常に多くなる可能性がある場合や、翻訳データの更新頻度が高いが元のリソースの更新頻度は低い場合などに適しています。また、必要に応じて関連リソースを埋め込む(Embedding)オプションを提供するなど、柔軟なデータ取得方法を検討すると良いでしょう。
パターン選択の指針
どのパターンを選択するかは、アプリケーションの要件、データの特性、開発・運用コストなど、様々な要素を考慮して決定する必要があります。
- 対応言語数: 対応言語が少ない(数言語程度)かつ、今後も大きく増える予定がない場合は、パターン1やパターン2でも管理可能かもしれません。多数の言語に対応する場合や、今後も言語追加が見込まれる場合は、パターン2(全言語含む)やパターン3の方がスケーラビリティが高いと言えます。
- 多言語化が必要なフィールド数: 多言語化が必要なフィールドが少ない場合は、パターン1でも良いかもしれません。多くのフィールドが多言語化される場合は、パターン2やパターン3の方が構造が整理されます。
- データの取得頻度とパフォーマンス: 元のリソースと特定の言語の翻訳データが常に一緒に必要とされる場合は、パターン1、パターン2(全言語含む/フィルタリング)、またはパターン3(ネスト)が適しています。特定の言語の翻訳データだけを個別に取得したい場合や、翻訳データが必要ない場面が多い場合は、パターン3(関連リソース)が有利です。
- データの更新頻度: 翻訳データが頻繁に更新される場合、元のリソースとは分離されているパターン3の方が影響範囲を限定できます。
- 後方互換性: 新しい言語を追加する際のAPIスキーマ変更の影響を最小限に抑えたい場合は、パターン2やパターン3が有利です。パターン1は新しいフィールドの追加が必要になります。
- クライアント側の実装: クライアント側でのデータの取り扱いやすさも考慮します。ネストされた構造や別リソースからの取得は、クライアント側のロジックを複雑にする可能性があります。
多くの場合、多言語対応が必要なフィールド数がそれなりにあり、将来的な言語追加も考慮すると、パターン2(言語コードをキーとするMap/オブジェクト構造) を採用し、API側でクライアントの要求言語に応じてフィルタリングして返す、というアプローチがバランスが良いと言えるでしょう。ただし、非常に大量の翻訳データを持つ場合や、翻訳データのみを独立して管理・更新したい場合は、パターン3(別のリソースとして管理) も有力な選択肢となります。
パターン1は、対応言語数が非常に限定的で、フィールド数も少なく、シンプルさを最優先する場合に検討する価値がありますが、将来的な拡張性には注意が必要です。
まとめ
RESTful APIにおける多言語対応データのモデリングは、文字列データをどのように表現し、クライアントに提供するかが鍵となります。
- パターン1(フィールドに言語サフィックス): シンプルだが拡張性に課題。
- パターン2(言語コードをキーとするMap): 拡張性が高く管理しやすい。API側でのフィルタリングが一般的。
- パターン3(別のリソース): リソース分離による管理性向上、柔軟な取得が可能だが、クライアント側で複数回API呼び出しが必要になる場合がある。
これらのパターンにはそれぞれメリット・デメリットがあり、アプリケーションの要件に応じて最適なアプローチを選択することが重要です。特に、データ量、取得頻度、更新頻度、そして将来的な拡張性を考慮して、保守性の高いデータモデリングを目指してください。多言語対応を適切に設計することで、より多くのユーザーに利用される使いやすいAPIを提供できるでしょう。