1. なぜ ChatGPT App でこそローカリゼーションを考えるのか
通常の Web アプリを作ってきたなら、ローカリゼーションは古典的な i18n(UI の文言、日付や数値のフォーマット、辞書)を丁寧に翻訳することだと考えがちです。ChatGPT App はもう少しややこしい存在です。ここには第3の登場人物、すなわち LLM モデルがいます。モデルはあなたのツール説明やプロンプト、結果を読み、推論し、意思決定します。
つまり言語は「ユーザーにきれいにテキストを見せる」だけでなく、「ツールが何をするのか、いつ呼ぶのか、どんな引数を渡すのかをモデルにどう理解させるか」でもあります。ボタンのラベル「購入」は多少拙くてもユーザーは理解してくれます。しかし支払いを行う tool の説明を別言語のちゃんぽんで曖昧に書けば、モデルはその tool をまったく呼ばないか、想定とまったく違う呼び方をするかもしれません。
もう1点。ChatGPT はすでに MCP サーバーへユーザーのロケールとロケーションのヒントを渡しています — _meta["openai/locale"] と _meta["openai/userLocation"]。これはツールへの MCP リクエストのレベルで行われ、ユーザーの言語や地域に合わせてテキストやデータを調整できるようにするためです。つまりプラットフォームが文脈を「差し込んで」くれているので、開発者はそれを賢く活用するだけです。
そのためこのモジュールでは、ローカリゼーションをアーキテクチャの側面として捉え、「UI を訳して終わり」にはしません。
2. ローカライズすべきレイヤー
App を層の重なり(ケーキ)として見てみましょう。各レイヤーは(多くの場合)ローカライズされるべきです。混乱しないために、まずは地図から始めます。
レイヤーの概観
まずは一覧表、その後に細かく見ていきます。
| レイヤー | 概要 | 例 | 影響 |
|---|---|---|---|
| Widget UI | ウィジェット内でユーザーに見えるフロントエンド全体 | 見出し、ボタン、エラー、プレースホルダー/ヒント | ユーザー体験(UX) |
| モデル用テキストとプロンプト | System‑prompt と定型フレーズ | 指示、回答テンプレート | ChatGPT の振る舞い |
| データとコンテンツ | App が表示・処理するテキスト | 商品カタログ、説明、日付、価格 | UX と応答精度の両方 |
| tools/スキーマの説明 | ツールと JSON Schema フィールドのメタデータ | description、型ヒント | モデルが tools をどう呼ぶか |
| コマースと法務 | 課金/購入やポリシー関連すべて | SKU 名、Terms、Privacy、通知メール | 法的適合性と信頼 |
これはローカリゼーション・マップの第1層です。この後、深さ(見た目/セマンティクス)、言語、具体的要素を重ねていきます。
では各レイヤーを順に見ていきます。
Widget UI
最もわかりやすいのがウィジェットの UI です。GiftGenius では例えば以下です。
- ブロックの見出し;
- フィールドラベル(「受取人」「予算」「関心」);
- ボタン(「ギフトを探す」「フィルターをリセット」);
- 入力欄のプレースホルダー(「例:同僚、母…」);
- エラーや空状態のメッセージ(「ギフトが見つかりません」)。
通常の React アプリでは、これらは辞書への切り出しの最初の候補です。ここでも同じですが、UI は App 全体の一つの顔にすぎません。
このモジュールの後半でウィジェット向けのちゃんとした i18n アーキテクチャを作りますが、今の時点でもう一つ大事なことを固定しましょう。 JSX に文字列を直書きしない。 たとえ今は 1 言語対応でも、UI テキストを最初から構造化しておくと楽です。
GiftGenius のミニ例(まだ本格的な i18n ライブラリなし):
type Locale = "en" | "ru";
const uiText = {
en: {
title: "GiftGenius: find a perfect present",
recipientLabel: "Recipient",
},
ru: {
title: "GiftGenius: 理想のプレゼントを見つけよう",
recipientLabel: "受取人",
},
};
function GiftForm({ locale }: { locale: Locale }) {
const t = uiText[locale];
return (
<div>
<h2>{t.title}</h2>
<label>{t.recipientLabel}</label>
{/* その他のフィールド */}
</div>
);
}
ここではまだ「本物の」ローカリゼーションではありませんが、UI テキスト層を明確に切り出せています。
GPT 用テキストとプロンプト
次はユーザーに直接見えないがモデルの挙動に強く影響するテキストです。
- App の system‑prompt(「あなたはギフト選定アシスタント…」);
- モデルに与える説明テンプレート(「選定理由の要約を作って」);
- モデル向けの用意済み follow‑up やヒント(「予算が未指定ならユーザーに確認を提案して…」)。
これらも(多くの場合)ローカライズすべきです。簡単な例として、ユーザーが日本語で入力しているのに system‑prompt がすべて英語だと、モデルは対処できるものの、その言語に合わせたスタイルや言い回しを厳密にコントロールする余地を失います。
プロンプトや説明(prompts/descriptions)のローカライズ手法は後の講義で扱います。ここで言いたいのは、 プロンプトも UI と同様にローカライズ対象の層 だということです。
データとコンテンツ
次はデータです。GiftGenius ならギフトのカタログ:名称、説明、カテゴリ、時には使い方のヒント。コマース系 App なら価格、通貨、単位、日付フォーマットなども含みます。ChatGPT 向けの product feed 仕様(プラットフォームで商品やサービスを記述するフォーマット)では、これらのテキストフィールド(title、description)や価格が明示され、ChatGPT 内で適切に表示できるようになっています。
グローバル対応を目指すなら、ギフトカタログには少なくとも次のような論点が生じます。
- 名称/説明を複数言語で保持するか;
- どの言語をユーザーへ返すかの選択;
- 翻訳が未準備のときのフォールバック;
- 地域ごとの通貨・日付/価格フォーマットの表示。
カタログの小さな型付き例:
type Locale = "en" | "ru";
interface LocalizedString {
en: string;
ru: string;
}
interface Gift {
id: string;
title: LocalizedString;
description: LocalizedString;
priceCents: number;
currency: "USD" | "EUR" | "RUB";
}
function getLocalizedTitle(gift: Gift, locale: Locale) {
return gift.title[locale] ?? gift.title.en;
}
つまりローカリゼーションはフロントエンドだけでなく、データベースや MCP リソースのデータ構造にも関わります。これは Gateway(ChatGPT と自サービス間のゲートウェイ)や MCP サーバーの回で改めて扱います。
tools の説明と JSON Schema
第4層はツールとその引数の説明です。これによってモデルは、いつ tool を呼び、どの引数を渡すかを理解します。MCP ではツールの title、description と、JSON スキーマ各フィールドの description に相当します。
Apps SDK のドキュメントは、モデルがパラメータの名称・説明・ドキュメントを使ってツール選択や引数構築を行うことを強調しています。
TypeScript 製の MCP サーバーにおける GiftGenius の仮のツール例:
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description: "Suggest 3–5 gift ideas based on recipient profile.",
inputSchema: {
type: "object",
properties: {
recipient: {
type: "string",
description: "Who is the gift for (e.g. mother, colleague)?",
},
},
required: ["recipient"],
},
},
async ({ input }) => { /* ... */ }
);
今はすべて英語で、モデルには十分明確です。しかしユーザーが日本語でやり取りする場合はどうでしょう。簡単な項目なら対応できますが、複雑なフィールドやドメイン用語ではミスの確率が上がります。ツール説明のローカライズ戦略(英語のみで統一するか、多言語説明を用意するか)は別講で扱います。
この段階でローカリゼーション・マップに明記しておくべきこと: tools と JSON Schema の説明もローカライズ可能であり、モデルの挙動に影響する という点です。
コマースと法務
最後に、往々にして最後に思い出される層 — 課金や法的テキストに関わるすべてです。
- SKU 名やサブスクリプションプラン名;
- コマースフィード内の title/description(商品、サービス、サブスク);
- Terms of Service、Privacy Policy、Refund Policy;
- App が ChatGPT の外で送るメールやプッシュ通知;
- ユーザーに見せる注文ステータスや決済エラー(「支払いが拒否されました」「お住まいの地域ではご利用いただけません」)。
ここには UX と法の二つの側面があります。ユーザーは自分の言語で、何に同意し何に対価を払うのかを理解できなければなりません。同時に翻訳は法的に正確である必要があります。場合によっては、法的効力を持つのは一つの言語(例:英語)のみで、他言語は参考訳に過ぎないと法務が定めることもあります。
ローカリゼーション・マップではコマースと法務コンテンツを必ず別レイヤーとして記載します。しばしば異なるプロセス(法務、コンプライアンス、マーケの文言承認)が必要になるためです。
3. ローカリゼーションの深さ:見た目(コスメ)かセマンティクスか
「App をローカライズする」と言うとき、深さを二つに分けると有益です。コスメ(見た目)とセマンティクスです。
見た目のローカリゼーション
コスメとは、見た目や可読性は変えるが、システムの挙動はほとんど変えないものです。例:
- ボタン上の見出しやラベルの翻訳;
- 入力欄のプレースホルダー;
- UI の「人間らしい」エラーメッセージ;
- ウィジェット内のマーケティングバナーの文言。
古典的な Web アプリではしばしばここで止まります。ChatGPT App では重要ではあるものの、これは氷山の一角に過ぎません。
セマンティックなローカリゼーション
セマンティクスとは、モデルの行動や App のロジックが変わる要素です。ここでは言語が次の点に影響します。
- モデルがどのツールを選ぶか;
- ツール引数の埋め方;
- そのユーザーにとって何が「正しい」データかの判断。
セマンティック・ローカライゼーションの例:
- ユーザーの言語での system‑prompt(口調やルールを定義);
- ユーザーの言語での tools とフィールドの descriptions;
- 文化的コンテキストに応じたヒント/指示の文言変更;
- パースや生成に影響する日付/通貨フォーマットの設定(31.12.2025 と 12/31/2025 の違い)。
見た目だけをローカライズしてセマンティクスを放置すると、見た目はローカライズされていても、内部の挙動は「英語的」なままになり得ます。ローカリゼーション・マップでは、モデルの挙動にクリティカルな要素を明示しておくとよいでしょう。
GiftGenius における例:
- JSON Schema における budget フィールドの説明(「Budget in the user’s currency」) — セマンティクス;
- 「ギフトを探す」ボタンのラベル — 見た目(UX には重要だがモデルは見ない)。
見た目とセマンティクスの区別ができたら、App を何言語で動かしたいのかを決めるのが自然です。
4. 単一言語 vs 多言語の ChatGPT App
ローカリゼーション・マップを描く前に、方針を決めましょう。厳密に単一言語の App にするのか、多言語オーディエンスを狙うのか。
単一言語の App
単一言語の App は、意図的に 1 言語だけをサポートする選択です。例えば英語のみ、など。
UI ウィジェット、プロンプト、ツール説明、データ — すべて 1 言語。これは運用を大きく簡素化します。
- 言語による分岐のない単一のコードベース;
- カタログのスキーマも 1 種(title_en や title_ru などを不要にできる);
- 保守とテストが容易。
ただし当然オーディエンスは限定されます。ChatGPT App の場合、ユーザーのロケールが別言語でも App を表示できますが、ユーザーの言語を App の内部言語へ常に「置き換える」必要が生じます。領域によってはそれで十分ですが、マス向けのギフト提案サービスなどでは厳しいでしょう。
多言語の App
多言語の App はアーキテクチャ上の意思決定です。ここでは:
- UI とテキストがユーザーの locale に基づいて正しく表示される;
- データ(カタログや商品説明)も言語/地域に紐づく;
- tools の descriptions や system‑prompts が言語ごとに変化し得る;
- コマースのシナリオが現地通貨、税、規制を考慮する。
この場合、コード中の if (locale === "ru") のような分岐をあちこちに置くやり方では明らかに足りません。辞書、ローカライズ可能なリソース、locale と userLocation を管理する単一の場所、ウィジェットと MCP サーバー間の取り決めが必要になります。
Apps SDK のドキュメントは、ChatGPT がツール呼び出し時に _meta に locale と userLocation を渡すことを明言しています。これはサーバー側で正しい言語とデータ形式を選ぶためです。これこそが多言語 App の「燃料」です。
簡単な比較
わかりやすくするため、簡易比較表:
| 特性 | 単一言語 App | 多言語 App |
|---|---|---|
| コード量 | 少ない | 多い(辞書、選択ロジック) |
| オーディエンスの到達 | 限定的 | グローバル |
| テストの複雑さ | 低い | 高い |
| コマース/法務対応 | 容易 | プロセスと法務が必要 |
| GPT の挙動管理 | 単一言語のプロンプト | 多言語の prompts/descriptions |
この講座では GiftGenius を多言語化(少なくとも EN/RU)して「成熟した」構成を示します。ただし多くのテクニックは、将来の拡張を見据えた丁寧な単一言語 App にも役立ちます。
5. 言語がモデルの挙動に直結して効く場所
次に、言語が ChatGPT の挙動に直接影響するポイントを特定しましょう。
ユーザー入力の言語 vs tools の説明の言語
状況を想定してみます。
- ユーザー入力:「同僚用に 50 ユーロのギフトを選んで」;
- あなたのツール suggest_gifts の説明は英語のみ;
- スキーマのフィールド:recipient、budget、currency、interests。
モデルが行うべきこと:
- suggest_gifts を呼ぶべきか判断する;
- recipient = "colleague"、budget = 50、currency = "EUR" を抽出する;
- これを JSON 引数に正しくシリアライズする。
説明が短く、しかも別言語だと、モデルは対応できるもののフィールドの埋め方を誤る確率は上がります。例えば budget と price_limit を取り違えたり、フィールド interests にテキストを入れてしまったり(説明が「Any extra info about the gift」のように曖昧だった場合)。
ユーザーが日本語で、説明が英語だと、モデルは常に言語を行き来する必要も出てきます。
ローカライズされたスキーマの例:
const locale = _meta?.["openai/locale"] ?? "en"; // ChatGPT から渡される
const isRu = locale.startsWith("ru");
server.registerTool(
"suggest_gifts",
{
title: isRu ? "ギフト選定" : "Suggest gifts",
description: isRu
? "受取人プロファイルに基づいて、3〜5 件のギフト案を提案します。"
: "Suggest 3–5 gift ideas based on recipient profile.",
inputSchema: { /* ... */ },
},
async ({ input }) => { /* ... */ }
);
ここでは簡略化しています。実際には descriptions はサーバー起動時に一度だけ生成する方がよいですが、要点は同じです。locale に応じて ChatGPT に異なる descriptions を返し、モデルがユーザーを理解しやすくできます。
データの言語 vs リクエストの言語
ギフトカタログが英語のみで、ユーザーは日本語で対話する場合、モデルは英語の名称と説明のアイテムを選びます。これで良い場合もありますが、より重要なのは出力のフォーマットです。
- サーバーから受け取ったオリジナルの title/description をそのまま見せるのか;
- モデルがユーザーの言語で要約し直して提示するのか;
- あるいはツール側が locale に基づいてすでにローカライズ済みのテキストを返すのか。
Apps SDK では structured content(ツールから返す構造化データ)とテキスト応答を分けて扱えます。構造化データ(例えば商品フィールドの JSON)とユーザー向けのテキストを別々に返し、モデルがそれをレンダリングしたり言い換えたりできます。
ローカライゼーションはサーバー側(データ)でもモデル側(言い換え)でも可能です。マップを作る際には、どこで「最終決定」をするかを決めるとよいでしょう。
System‑prompt と follow‑up
system‑prompt が英語だけでユーザーが日本語の場合、モデルは常に二つの言語の間でバランスを取ることになります。それで問題ないこともありますが、日本語版ではよりカジュアル、英語版ではフォーマルなど、トーンを厳密に決めたい場合もあるでしょう。
したがって、ローカリゼーション・マップには次を明記します。
- system‑prompt EN;
- system‑prompt RU;
- follow‑up テンプレート(EN/RU);
- ツール用プロンプト内の「強い」ヒント類。
6. GiftGenius のローカリゼーション・マップ
これまで扱った「レイヤー」「深さ」「言語」を GiftGenius に当てはめ、明示的なローカリゼーション・マップにまとめましょう。皆さんが自分の App で行う作業の縮図です。考え方はシンプルで、列にレイヤーとエンティティ種類、行に具体的な要素を書き出します。
マップ例
以下は GiftGenius(EN/RU)の簡略マップです。
| カテゴリ | 要素 | 例(EN) | 例(RU) | 見た目かセマンティクスか |
|---|---|---|---|---|
| UI | ウィジェットのタイトル | GiftGenius: find a perfect present | GiftGenius: 理想のプレゼントを見つけよう | 見た目 |
| UI | 受取人ラベル | Recipient | 受取人 | 見た目 |
| UI | 空リストのエラー | No gifts found | ギフトが見つかりません | 見た目 |
| Prompts | System‑prompt | You are GiftGenius, a gift assistant… | あなたは GiftGenius、ギフト選定アシスタントです… | セマンティクス |
| Prompts | 選定理由のサマリーテンプレート | Here’s why these gifts fit… | これらのギフトが合う理由は次のとおり… | セマンティクス |
| Data | ギフト名 | Smart mug | スマートマグカップ | UX とセマンティクス |
| Data | ギフト説明 | Self‑heating mug with app control… | アプリで制御できる自己加熱マグ… | UX とセマンティクス |
| Data | 通貨 | 59.99 USD | 5,499 ₽ / 59.99 € | セマンティクス(フォーマット/通貨) |
| Tools/schema | |
Suggest gift ideas based on profile… | 受取人プロファイルに基づきギフト案を提案… | セマンティクス |
| Tools/schema | |
Budget in user’s currency | ユーザーの通貨での予算 | セマンティクス |
| Commerce | フィード内の SKU 名 | “Premium subscription – 1 year” | 「プレミアムサブスクリプション — 1 年」 | UX と法務 |
| Commerce | 利用規約ページ | Terms of Service (EN only) | 注記:法的に有効なのは EN テキストのみ | セマンティクス/法務 |
| Errors (backend) | 決済エラーメッセージ | Payment failed, please try again later | 支払いに失敗しました。しばらくしてからお試しください | 見た目 + UX |
左側でレイヤーごとにグループ化し、その右に具体的な要素を並べています。最後の列は、プロンプト設計/モデル担当者の承認なしに変更できないものを見極める助けになります。セマンティクスと記したものは GPT の挙動に影響します。
小さなコードのスケッチ
マップとコードを結びつけるため、ローカライズ可能なエンティティに簡単な型を設けられます。
type LocalizedTextKey =
| "ui.title"
| "ui.recipient_label"
| "error.no_gifts"
| "prompt.summary_intro";
type Locale = "en" | "ru";
type Messages = Record<Locale, Record<LocalizedTextKey, string>>;
const messages: Messages = {
en: {
"ui.title": "GiftGenius: find a perfect present",
"ui.recipient_label": "Recipient",
"error.no_gifts": "No gifts found",
"prompt.summary_intro": "Here’s why these gifts fit:",
},
ru: {
"ui.title": "GiftGenius: 理想のプレゼントを見つけよう",
"ui.recipient_label": "受取人",
"error.no_gifts": "ギフトが見つかりません",
"prompt.summary_intro": "これらのギフトが合う理由は次のとおり:",
},
};
同じキーをウィジェットでもサーバーのプロンプト生成でも使えます(locale をスタック全体で渡す必要があります。これについては次の講義で)。こうして「ローカリゼーション・マップ」は、ばらばらの文字列ではなく型付けされた辞書へと育っていきます。
7. 演習:自分の App のローカリゼーション・マップを作る
具体的な i18n テクニックや Gateway/MCP のアーキテクチャに進む前に、地味だけどとても有用な練習をしましょう。何をローカライズするのか、正直に洗い出します。
良いアプローチは、任意のエディタ(Google Sheets や Notion など)で次の列を持つ表を作ることです。
- カテゴリ/レイヤー(UI、プロンプト、データ、tools、コマースと法務、エラー);
- 要素(具体的なボタン、フィールドの説明、テキストを返す特定のエンドポイントなど);
- 例(EN 値);
- 第2言語での例(既にあれば、またはドラフト);
- 「見た目/セマンティクス/法的に重要」の区分;
- オーナー(誰が修正に責任を持つか:フロントエンド、MCP サーバー、プロダクト、法務)。
そのうえで App 全体を見渡し、テキストがあるところ、あるいは言語がデータ形式に影響するところを正直にすべて書き出します。
GiftGenius であれば、上の表を拡張したようなものになるでしょう。その過程でおそらく次のような「隠れ箇所」が見つかります。
- MCP サーバーのコード内に埋もれた文字列定数(例:エラーメッセージ);
- structuredContent のデフォルト値(UI では出していないカテゴリなど);
- 実際の挙動と一致していない古い tools のラベル。
これは、ローカリゼーションを実ビジネスプロセス(決済、サブスク有効化、法的文書)と結びつける前にやっておくと特に有益です。多言語のシステムで、ACP や法的テキストと絡んだ charge_user のような tool 名の変更は後からだとずっと大変になります。
先にローカリゼーション・マップを描き、どの要素をどの言語でサポートするのかを正直に記しておけば、次のモジュール(MCP、Gateway、コマース、Store)で必要になる作業にだいぶ余裕を持って臨めます。
8. ローカリゼーション計画でありがちな落とし穴
誤り 1:ローカライズ=「ボタンの翻訳」だと思い込む。
よくあるのが、UI の文字列を丁寧に辞書化し、翻訳して完了、という流れです。この間に system‑prompt は英語のまま、tools の descriptions も英語のまま、商品カタログも英語名のみ。結果として App は見た目こそローカライズされていますが、内部のモデルは英語圏中心の世界観で動き続け、ツール引数のミスや不思議なレコメンドが発生します。
誤り 2:見た目とセマンティクスを区別しない。
プロダクト側から「表現をちょっと直して」と頼まれ、開発者がツールの説明や system‑prompt の文言を気軽に変えてしまうことがあります。しかし JSON Schema のフィールド description や system‑prompt の一文は、モデルとの契約の一部です。こうした変更は、GPT がツールを呼ぶ方法を大きく変え得ます。ローカリゼーション・マップでセマンティクス要素を明示しておかないと、うっかり App の挙動を壊しがちです。
誤り 3:設計なしに多言語対応を場当たりで始める。
初期はつい、 if (locale === "ru") のような条件分岐を各所に撒いて、日本語の文字列をその場で差し込んでしまいがちです。数週間後、アプリは「ローカライズ地獄」に変貌します。あるコンポーネントは辞書から、別のコンポーネントは JSX に直書き、サーバーは第三のキー体系… 後からまともな i18n を導入して統一するのが一気に難しくなります。
誤り 4:データとお金を忘れる。
経験豊富なチームでも、UI とプロンプトの翻訳から着手し、商品カタログ、価格、通貨、法的テキストも locale と userLocation を考慮すべきだという点を見落としがちです。ChatGPT の product feed 仕様では、ユーザーに正しく商品を表示するために必要なテキストフィールドや価格が厳密に定義されています。データ層での多言語性を最初に設計しておかないと、後でフィードを複製したり痛い移行を強いられます。
誤り 5:プラットフォームのロケール/ロケーション信号を無視する。
ChatGPT はツール呼び出し時の MCP で、 _meta["openai/locale"] と _meta["openai/userLocation"] を既に渡しており、どの言語・どの地域から対話しているかを理解できるようにしています。それでも初回起動で「インターフェース言語を選んでください」とだけ尋ね、これらの信号をリソースや価格の選択に使わないケースがあります。結果として UX は悪化し、アーキテクチャは必要以上に複雑になります。
誤り 6:ローカライズ対象要素の「オーナー」を決めない。
運用が始まると、翻訳の責任が人ごとに拡散しがちです。フロントエンドは UI 文言、バックエンドは tools の descriptions、ML 担当は system‑prompt、法務は修正済みの Terms を送ってくる、など。ローカリゼーション・マップで、どのレイヤーを誰が担当するかを明示しておかないと、変更同士が衝突したり、ある場所だけ更新されて別の場所が古いままになったりします。
GO TO FULL VERSION