1. 为什么在 ChatGPT App 中要重视本地化
如果你做过普通的 Web 应用,本地化多半让你想到经典的 i18n:界面字符串、少量日期与数字格式、词典——然后你把它们逐一翻译好。在 ChatGPT App 中事情更有意思:第三位参与者是 LLM 模型本身。它会阅读你的工具描述、提示词、结果,进行推断并做出决策。
也就是说,语言不仅仅是“如何把文本优雅地展示给用户”,还关系到“模型如何理解你的工具在做什么、何时调用、要填写哪些参数”。按钮“购买”翻得一般用户也能看懂;但如果你用俄英混杂、表述含糊的方式去描述一个执行支付的 tool,模型可能要么永远不调用它,要么调用方式与预期完全不同。
还有一点:ChatGPT 已经会把用户的语言与所在地区提示传给你的 MCP 服务器——_meta["openai/locale"] 和 _meta["openai/userLocation"]。这些发生在对工具的 MCP 请求层面,目的是让你可以按用户的语言和区域来适配文本与数据。也就是说平台已经把上下文“递给”你了,而开发者的任务是有策略地利用它。
因此,在本模块中我们将本地化视为 ChatGPT App 的一个架构层面,而不是“翻译一下 UI 就完事”的工作。
2. 需要本地化的各层
把 App 看成一块分层的“蛋糕”。每一层都可以(且通常应该)本地化。为避免迷失,先画一张地图。
各层概览
先给出总表,然后再逐一拆解。
| 层 | 是什么 | 示例 | 影响 |
|---|---|---|---|
| Widget UI | 小部件中所有可见的前端 | 标题、按钮、错误、占位符 | 用户的 UX |
| 模型文本与提示词 | System prompt 与预置短语 | 指令、回复模板 | ChatGPT 的行为 |
| 数据与内容 | App 展示与处理的文本 | 商品目录、描述、日期、价格 | 既影响 UX 又影响准确性 |
| tools/模式的描述 | 工具与 JSON Schema 字段的元数据 | description、类型提示 | 模型如何调用你的 tools |
| Commerce 与法律 | 一切与购买与政策相关的内容 | SKU 名称、Terms、Privacy、邮件 | 法律合规与信任 |
这实际上是我们的第一层本地化地图:接下来会加上“深度”(装饰/语义)、语言以及具体元素。
现在按层逐一看。
Widget UI
最直观的一层是小部件的界面。在 GiftGenius 中包括:
- 区块标题;
- 字段标签(“收礼人”“预算”“兴趣”);
- 按钮(“挑选礼物”“重置筛选”);
- 输入框占位符(“例如,同事、妈妈……”);
- 错误与空状态消息(“未找到礼物”)。
在普通的 React 应用里,这些是最先被抽到词典的候选。这里也是一样,只不过要记住 UI 不是整个 App,它只是其中一个面孔。
稍后在本模块里我们会为小部件做一个正规的 i18n 架构,但现在就需要明确: JSX 里不该直接写字符串。 即便你暂时只支持一种语言,也应尽早把 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)本地化工具的讲解中,我们会看看如何优雅地处理多语言的 system prompts。这里要强调的是: 提示词和 UI 一样,也是可本地化的一层。
数据与内容
接下来是你的数据。对于 GiftGenius 来说是礼物目录:名称、描述、类别,有时还有如何使用礼物的提示。对商业 App 来说,还有价格、货币、计量单位、日期格式等。在面向 ChatGPT 的 product feed 规范中(即你向平台描述商品与服务的格式),这些文本字段(title、description)与价格被明确标注,以便平台在 ChatGPT 内正确展示给用户。
如果你想做全球化的 App,礼物目录至少会涉及这些问题:
- 是否以多语言存储名称/描述;
- 如何选择向用户返回哪种语言;
- 如果翻译尚未就绪该如何回退(fallback);
- 不同地区如何展示货币与日期/价格格式。
给目录的一个小型类型化示例:
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 的描述
第四层是工具及其参数的描述。模型正是依赖这些信息来判断何时调用你的 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 }) => { /* ... */ }
);
现在一切都是英文,模型能很好地理解。但如果用户用中文交流呢?模型依然可以把“妈妈”之类的词与 recipient 对应起来,但当字段复杂、领域术语较多时,出错概率会变高。我们会在“描述本地化策略”的讲解中专门讨论:统一使用英文描述 vs 本地化描述。
在本地化地图中,此时只需记下: tools 与 JSON Schema 的描述同样可以本地化,并且会影响模型行为。
Commerce 与法律
最后是经常被放到最后才想起来的一层——与金钱和法律文本有关的所有内容:
- SKU 与订阅计划的名称;
- commerce feed 中的 title/description 字段(商品、服务、订阅);
- Terms of Service、Privacy Policy、Refund Policy;
- 邮件与通知(email、推送),如果 App 会在 ChatGPT 之外发送;
- 你展示给用户的订单状态与支付错误(“支付被拒”“在你所在地区不可用”)。
这里有两个方面:UX 与法律。用户需要用自己的语言理解他同意了什么、为哪些内容付费。同时翻译必须在法律上准确:有时法务会要求只有某一种语言(例如英文)的文本具有法律效力,其他语言仅作“参考”。
因此在本地化地图中,我们要把 commerce 与法律文本单列出来,因为它们往往需要不同的流程(法务、合规、与市场的文本协同)。
3. 本地化的深度:“装饰性”与“语义性”
当我们说“本地化 App”时,区分两个层级很有用:装饰性与语义性。
装饰性本地化
装饰性指改变外观与可读性,但几乎不改变系统行为的内容。例如:
- 翻译后的标题与按钮标签;
- 翻译后的输入框占位符;
- UI 中“人性化”的错误消息;
- 小部件中的本地化营销横幅文案。
在传统 Web 应用中,很多团队到这里就停了。但在 ChatGPT App 中,这只是冰山上部的一层。
语义性本地化
语义性是会改变模型行为与 App 逻辑的因素。语言在这里会影响:
- 模型会选用哪个工具;
- 模型如何填写工具参数;
- 模型认定对该用户“正确”的数据是什么。
语义性本地化的例子:
- 使用用户语言的 system prompt,设定交流风格与规则;
- 用用户语言书写的 tools 及其字段 descriptions;
- 根据文化语境调整的提示/说明文本;
- 影响解析与生成的日期/货币格式设置(31.12.2025 vs 12/31/2025)。
如果你只做了装饰性、没做语义性,你的 App 看起来像是本地化了,但“内在”依然像是“英文应用”。在本地化地图中,最好明确标注哪些元素对模型行为至关重要。
以 GiftGenius 为例:
- JSON Schema 中 budget 字段的描述(“Budget in the user’s currency”)——语义性;
- “挑选礼物”按钮标签——装饰性(对 UX 重要,但模型看不到)。
有了装饰 vs 语义的区分之后,下一个顺理成章的问题是:你到底想让 App 支持多少种语言。
4. 单语 vs 多语的 ChatGPT App
在画地图之前,先明确目标:你要做严格单语的 App,还是面向多语言用户。
单语 App
单语 App 指你明确只支持一种语言。比如只支持英文。
UI 小部件、提示词、工具描述与数据——全部使用一种语言。这样会极大简化工作:
- 单一代码库,无需按语言分叉;
- 单一的目录模式(不需要 title_en、title_ru 之类);
- 维护与测试更容易。
但显然,用户覆盖面受限。对 ChatGPT App 来说,这还意味着:当用户使用其他本地化设置进入时,ChatGPT 仍然可能展示你的 App,但它需要持续把用户语言“转换”为 App 内部语言。在一些垂直领域这可以接受,但对面向大众的礼物推荐服务而言——大概率不行。
多语 App
多语 App 则是一个架构决策。此时:
- UI 与文本会基于用户 locale 正确展示;
- 数据(目录、商品描述)也与语言/地区绑定;
- tools 的 descriptions 与 system prompts 可以按语言变化;
- commerce 场景会考虑本地货币、税费与限制。
在这种情况下,全局散落一个 if (locale === "ru") 显然远远不够。你需要的是架构:词典、可本地化资源、集中存储与处理 locale 和 userLocation 的位置,以及小部件与 MCP 服务器间的约定。
Apps SDK 文档明确强调:当调用你的工具时,ChatGPT 会把 locale 与 userLocation 放在 _meta 里传给你,使你能在服务器端选择正确的语言与数据格式。这正是多语 App 的“燃料”。
小对比
为了直观起见——一个迷你对比:
| 特性 | 单语 App | 多语 App |
|---|---|---|
| 代码量 | 更少 | 更多(词典、选择逻辑) |
| 受众覆盖 | 受限 | 全球 |
| 测试复杂度 | 更低 | 更高 |
| 与 commerce/法律的协作 | 更简单 | 需要流程与法务介入 |
| 与 GPT 行为的协作 | 单语 prompt | 多语言 prompts/descriptions |
在课程层面,我们会假定 GiftGenius 变为多语(至少 EN/RU),以展示“成体系”的方案。但许多技巧对想要随时具备扩展能力的精致单语 App 也同样适用。
5. 语言在哪些地方会真正影响模型
现在来标出语言会直接影响 ChatGPT 行为的关键点。
用户输入语言 vs tools 描述语言
设想:
- 用户写道:“为同事挑一份50 欧元的礼物”;
- 你的 tool suggest_gifts 只用英文描述;
- Schema 字段:recipient、budget、currency、interests。
模型需要:
- 决定是否该调用 suggest_gifts;
- 抽取 recipient = "colleague",budget = 50,currency = "EUR";
- 把这些正确序列化为 JSON 参数。
如果 descriptions 很简短又是另一种语言,模型仍可完成任务,但填错字段的概率更高。比如把 budget 与 price_limit 搞混,或者因为字段描述太模糊(比如 “Any extra info about the gift”)而把文本塞进 interests。
当用户文本是中文、描述是英文时,模型还得在两种语言之间不断“切换”。
使用本地化 schema 的一种做法:
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 返回不同的 descriptions,让模型更容易理解用户。
数据语言 vs 查询语言
如果你的礼物目录只有英文,而用户用中文交流,模型会选择英文的名称与描述。有时这样也行,有时则不行。但更重要的是输出的格式化方式:
- 你是否把服务器的原始 title/description 直接展示给用户;
- 还是让模型在自己的文本中用用户语言转述它们;
- 抑或你的 tool 就根据 locale 返回已本地化的文本。
在 Apps SDK 中,structured content(你从 tools 返回的结构化数据)与文本回复可以分别存在。你可以返回结构化数据(例如包含商品字段的 JSON)与单独的用户文本,模型会决定如何渲染或转述。
本地化可以发生在服务器层(数据),也可以发生在模型层(按需重述)。在制定地图时要决定你希望把“最终权威”放在哪里。
System prompt 与 follow-ups
如果你的 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 | feed 中的 SKU 名称 | “Premium subscription – 1 year” | “高级订阅 — 1 年” | 兼具 UX 与法律 |
| Commerce | Terms 页面 | Terms of Service (EN only) | 提示:具有法律效力的仅为英文文本 | 语义/法律 |
| 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、commerce 与法律、错误);
- 元素(具体按钮、具体字段描述、带文本的具体 endpoint);
- EN 示例;
- 第二语言示例(如果已有或至少草稿);
- 标注“装饰/语义/法律重要”;
- 负责人(谁负责修改:前端、MCP 服务器、产品、法务)。
然后走查你的 App,认真把所有含文本或受语言影响的数据格式记录下来。
对 GiftGenius 来说,会得到类似上文表格的扩展版。在此过程中,你几乎肯定会发现一些“隐蔽”之处:
- MCP 服务器代码中的文本常量(例如错误消息);
- structuredContent 里的默认值(例如你没展示在 UI 中的类别);
- tools 的旧描述,已经与其实际行为不匹配。
这个练习在你把本地化与真实业务流程(支付、订阅激活、法律文档)打通之前尤其有用。在有 ACP 与法律文本的多语言系统里,事后再去重命名 tool charge_user 会痛苦许多。
总体而言,如果你提前绘制本地化地图,并如实标注你打算支持的对象与语言,那么当 MCP、Gateway、commerce 与 Store 加入进来时,你能省下大量的精力与麻烦。
8. 规划本地化时的常见错误
错误 1:认为本地化 = “把按钮翻译了”。
很常见的场景:团队把所有 UI 字符串仔细抽到词典并翻译,然后就很开心。但 system prompt 仍然只有英文,tools 的 descriptions 也只是英文,商品目录也仅有英文名称。结果是 App 看起来本地化了,但模型内部依旧“活在自己的世界里”,行为仍然偏向英文。实践中的表现就是奇怪的推荐与 tools 参数填写错误。
错误 2:不区分装饰与语义。
有时产品会说“把这句描述稍微改一改”,于是开发像改 UI 标签那样去改工具描述或 system prompt。但 JSON Schema 字段的 description 或 system prompt 里的某句话,都是与模型的“契约”一部分。这些改动可能会显著改变 GPT 调用你工具的方式。如果不在地图中提前标注语义性元素,就很容易在无意间破坏 App 的行为。
错误 3:没有架构就一头扎进多语言,导致混乱。
一开始非常容易在代码里随处写 if (locale === "ru") 来插入中文字符串。几周后,应用就变成“本地化地狱”:有些组件用词典,有些组件把字符串写在 JSX 里,服务器又是另一套键名风格。等到你要引入正规的 i18n 系统并统一风格时,会困难得多。
错误 4:忽视数据与金钱。
即便是经验丰富的团队,常常从翻译 UI 与提示词开始,但忽略了商品目录、价格、货币与法律文本也必须考虑 locale 与 userLocation。面向 ChatGPT 的 product feed 规范会严格要求哪些文本字段与价格对用户的正确展示是必须的。如果你不在数据层提前设计多语言,后面要么得复制 feed,要么得做痛苦的迁移。
错误 5:忽略平台提供的语言与位置信号。
在 MCP 调用中,ChatGPT 已经传递 _meta["openai/locale"] 和 _meta["openai/userLocation"], 让你知晓对话使用的语言与地区。但一些开发者仍然在首次启动时让用户“选择界面语言”,且完全不利用这些信号来选择资源与价格。结果是 UX 受损,架构也比需要的更复杂。
错误 6:没有明确“负责人”。
一旦上线,你会发现翻译分散在不同的人手里:前端改 UI 文本,后端改 tools 的 descriptions,ML 同事改 system prompt,而法务会给出修订后的 Terms。如果在本地化地图中不标注每个层的负责人,改动就会相互冲突,有些文本在一个地方更新了而另一个地方却没更新。
GO TO FULL VERSION