i18n 国际化
本教程共 56 篇 · 第 43 篇 · 更新于 2026-08-13 · 约 6 分钟阅读
本节目标:学完你能建立 _locales 多语言目录、写对 messages.json、在 JS 和 manifest 里用 getMessage / _MSG 取文案,并用占位符处理动态内容。
想让扩展被不同地区用户使用,最直接但最笨的办法是给每种语言复制一份代码。Chrome 提供了 chrome.i18n 来解决这个问题:把所有用户可见的文字抽成“消息”,按语言放在 _locales 目录里。代码只管按名字取,浏览器会根据当前语言自动选对应的那一份。
43-1 目录与文件结构
在扩展根目录下建 _locales,每个语言一个子目录,目录名用语言代码(如 en、zh_CN、zh_TW、fr)。每个子目录里放一个 messages.json:
_locales/
en/messages.json
zh_CN/messages.json
zh_TW/messages.json
messages.json 是个普通 JSON,顶层每个 key 对应一条消息,值是一个对象,至少含 message 字段:
{
"hello": {
"message": "Hello, world!",
"description": "Greeting shown on popup"
},
"bye": {
"message": "Goodbye!"
}
}
message:实际显示的文案,必填。description:给翻译者的说明,非必填但强烈建议写,能减少误译。- 语言代码规则:用 BCP 47,
zh_CN表示简体中文、zh_TW繁体。Chrome 也接受zh-CN写法,但目录名里推荐下划线形式。
43-2 getMessage 与占位符:在 JS 里取文案
代码里用 chrome.i18n.getMessage() 按名字取:
const greeting = chrome.i18n.getMessage('hello');
document.getElementById('title').textContent = greeting;
取不到时返回空字符串,不会抛错,所以取完最好判断一下。
很多文案不是死文字,比如”你好,$USER$“。这时用占位符:在 message 里写 $名字$,在 placeholders 里定义这个名字对应什么。
{
"welcome": {
"message": "Welcome, $USER$! You have $COUNT$ new messages.",
"description": "Welcome line with user name and count",
"placeholders": {
"user": {
"content": "$1",
"example": "Alice"
},
"count": {
"content": "$2",
"example": "3"
}
}
}
}
调用时按顺序传替换值:
const text = chrome.i18n.getMessage('welcome', ['Alice', '3']);
// 结果:Welcome, Alice! You have 3 new messages.
占位符还有两种高级玩法:
- 直接引用替换串:不定义
placeholders,直接在 message 里写$1、$2,靠getMessage的第二个参数位置对应。但可读性差,一周后你就忘了$2是什么。 - 硬编码替换:
content写死固定字符串,比如把品牌名Example.com当成占位符锁死不翻译。
Tip占位符名与
message里的$NAME$、placeholders里的 key 必须完全一致(大小写敏感,按名字精确匹配)。建议统一大写、见名知意,如$USER$、$COUNT$、$URL$。
43-3 manifest 本地化与预定义消息
manifest.json 里的字符串也能国际化。语法是 __MSG_消息名__(前后双下划线):
{
"manifest_version": 3,
"name": "__MSG_extName__",
"description": "__MSG_extDescription__",
"default_locale": "zh_CN",
"action": {
"default_title": "__MSG_actionTitle__"
}
}
对应的 messages.json 里要有同名消息:
{
"extName": { "message": "我的扩展" },
"extDescription": { "message": "一个演示国际化的扩展" },
"actionTitle": { "message": "点击打开" }
}
注意:name 和 description 一旦用了 __MSG_,清单必须声明 default_locale,否则加载失败。default_locale 指定“找不到对应语言时兜底用哪种”。
有些值由浏览器自动提供,直接用固定名字取,最常用的是 @@extension_id:
@@extension_id:扩展自己的 ID,适合拼内部资源链接。它不能写进 manifest,但在 JS 和 CSS 里都可以用(比如 CSS 里写chrome-extension://__MSG_@@extension_id__/...这种地址)。@@ui_locale:当前生效的语言代码,可拿来拼语言相关的外部地址。@@bidi_dir/@@bidi_start_edge/@@bidi_end_edge:文字方向,做阿拉伯语等 RTL 语言布局时有用(ltr还是rtl)。
const id = chrome.i18n.getMessage('@@extension_id');
const locale = chrome.i18n.getMessage('@@ui_locale');
43-4 语言选定与实践清单
浏览器按”用户语言列表”和扩展提供的语言逐个匹配,规则大致是:
- 优先精确匹配,比如用户设了
zh_CN,你也有zh_CN,就它了。 - 退一步按主语言匹配,
zh_CN没有就找zh。 - 都找不到,用
default_locale。 - 连
default_locale都没有(且清单用了__MSG_),直接加载报错。
所以起码要有一个 default_locale 对应的 _locales 目录兜底,别只放一种小语种。
- 所有用户可见文案(按钮、提示、通知)统一走
getMessage,别在代码里写死中文。 - 翻译文件只放
_locales,不混进代码。 - manifest 用
__MSG_时务必配default_locale。 - 带变量的文案用占位符,别用字符串拼接,否则语序在不同语言下会乱。
- 占位符的
description写给翻译者看,别偷懒空着。
43-5 完整示例与常见坑
把前面的点串起来,一个支持中英文的扩展目录长这样:
_locales/
en/messages.json
zh_CN/messages.json
manifest.json
popup.html
popup.js
zh_CN/messages.json:
{
"extName": { "message": "我的扩展" },
"greeting": {
"message": "你好,$NAME$,你有 $COUNT$ 条新消息。",
"description": "弹出页问候语",
"placeholders": {
"name": { "content": "$1", "example": "小明" },
"count": { "content": "$2", "example": "5" }
}
}
}
en/messages.json:
{
"extName": { "message": "My Extension" },
"greeting": {
"message": "Hello, $NAME$, you have $COUNT$ new messages.",
"description": "Popup greeting",
"placeholders": {
"name": { "content": "$1", "example": "Alice" },
"count": { "content": "$2", "example": "5" }
}
}
}
popup.js 里取文案:
const greeting = chrome.i18n.getMessage('greeting', ['小明', '5']);
document.getElementById('msg').textContent = greeting;
注意中英文的占位符名字保持一致($NAME$、$COUNT$),只是 message 文案不同。序号 $1、$2 也保持同一顺序,代码传参才不会因为语言切换而错位。
default_locale漏写:只要 manifest 里出现了__MSG_,就必须声明default_locale,否则整个扩展加载失败。- 占位符顺序错位:不同语言都用
$1、$2对应同一含义,别让英文的$1是名字、中文的$1却是数量。 - 在
message里硬编码变量:写"你好," + name而不是用占位符,换语言后语序就乱了。 @@extension_id写进 manifest:预定义 ID 不能写进 manifest(JS 和 CSS 里可用),manifest 里用会报错。
Tip想测试某种语言,官方做法是临时修改浏览器显示语言(chrome://settings/languages),或用
--lang=en --user-data-dir=<临时目录>启动参数带独立 profile 启动 Chrome 验证。网上流传的?locale=查询参数不是官方机制,不保证生效。
43-6 getMessage 的传参方式
getMessage 的第二个替换参数只支持单个字符串或字符串数组(最多 9 个),按 $1、$2 的位置对应:
chrome.i18n.getMessage('welcome', ['Alice', '3']);
// 只有一个替换串时,也可以直接传字符串
chrome.i18n.getMessage('welcome', 'Alice');
注意:getMessage 不支持对象传参,不存在“按占位符名传值”的写法。传对象不会被解析成替换串。占位符少、一眼能看清的时候用数组最省事;占位符一多,建议把传参顺序和 placeholders 定义放在一起维护,避免错位。
除了 getMessage,chrome.i18n 还有两个辅助方法值得知道。getUILanguage() 返回当前界面语言代码。如果你的扩展要根据语言切换某些不可本地化的行为,可以用它:
const uiLang = chrome.i18n.getUILanguage(); // 如 "zh-CN"
detectLanguage(text) 则能猜一段文字的语言,适合做”用户粘贴内容自动识别语种”的功能。不过这两个都是锦上添花,核心还是 getMessage 把文案从代码里抽出来这件事本身。
43-7 小结
i18n 的思路就一条:文案与代码分离。建 _locales/<语言>/messages.json → 用 getMessage('名') 在 JS 取、__MSG_名__ 在 manifest 取 → 动态内容用占位符 $NAME$ + placeholders → manifest 用了占位符就必须声明 default_locale → 浏览器按用户语言自动选,缺了就兜底。
到这一节,模块八”常用 API”全部讲完:从 storage 存储、tabs 操作、scripting 注入、alarms 定时、cookies、bookmarks、downloads,到本节的 declarativeNetRequest 网络请求、runtime 平台能力、i18n 多语言。下一模块进入安全与策略,讲 CSP 和”禁止远程代码”这两条发布红线。