首页 / 浏览器扩展开发入门教程 / i18n 国际化

浏览器扩展开发入门教程

i18n 国际化

本教程共 56 篇 · 第 43 篇 · 更新于 2026-08-13 · 约 6 分钟阅读

i18n国际化_localesgetMessagemessages.json多语言Manifest V3

本节目标:学完你能建立 _locales 多语言目录、写对 messages.json、在 JS 和 manifest 里用 getMessage / _MSG 取文案,并用占位符处理动态内容。

想让扩展被不同地区用户使用,最直接但最笨的办法是给每种语言复制一份代码。Chrome 提供了 chrome.i18n 来解决这个问题:把所有用户可见的文字抽成“消息”,按语言放在 _locales 目录里。代码只管按名字取,浏览器会根据当前语言自动选对应的那一份。

43-1 目录与文件结构

在扩展根目录下建 _locales,每个语言一个子目录,目录名用语言代码(如 enzh_CNzh_TWfr)。每个子目录里放一个 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": "点击打开" }
}

注意:namedescription 一旦用了 __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 语言选定与实践清单

浏览器按”用户语言列表”和扩展提供的语言逐个匹配,规则大致是:

  1. 优先精确匹配,比如用户设了 zh_CN,你也有 zh_CN,就它了。
  2. 退一步按主语言匹配,zh_CN 没有就找 zh
  3. 都找不到,用 default_locale
  4. 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 和”禁止远程代码”这两条发布红线。