强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Chrome 扩展开发完全指南 / 第 14 章:国际化(i18n)

第 14 章:国际化(i18n)

国际化(Internationalization,简称 i18n)让扩展能够支持多种语言和地区。通过 Chrome 的 i18n API,你可以轻松实现界面多语言切换,让全球用户都能舒适地使用你的扩展。


14.1 i18n 基础

14.1.1 manifest.json 配置

{
  "name": "__MSG_appName__",
  "description": "__MSG_appDescription__",
  "default_locale": "en"
}
  • __MSG_keyName__:引用 _locales 中的消息键
  • default_locale:默认语言代码,当用户语言不可用时回退

14.1.2 目录结构

_locales/
├── en/                    # 英语(默认)
│   └── messages.json
├── zh_CN/                 # 简体中文
│   └── messages.json
├── zh_TW/                 # 繁体中文
│   └── messages.json
├── ja/                    # 日语
│   └── messages.json
└── ko/                    # 韩语
    └── messages.json

⚠️ 注意:目录名使用下划线(zh_CN),而非连字符(zh-CN)。


14.2 消息文件

14.2.1 消息格式

{
  "appName": {
    "message": "My Extension",
    "description": "The display name of the extension"
  },
  "appDescription": {
    "message": "A powerful browser extension for productivity",
    "description": "The description of the extension in the Web Store"
  },
  "welcomeMessage": {
    "message": "Hello, $USERNAME$! Welcome back.",
    "description": "Greeting message with user name",
    "placeholders": {
      "username": {
        "content": "$1",
        "example": "John"
      }
    }
  },
  "itemCount": {
    "message": "You have $COUNT$ items",
    "description": "Shows the number of items",
    "placeholders": {
      "count": {
        "content": "$1"
      }
    }
  },
  "openSettings": {
    "message": "Open Settings",
    "description": "Button text to open settings page"
  }
}

消息字段说明

字段必需说明
message消息文本
description对翻译者的说明
placeholders占位符定义

14.2.2 中文消息文件

{
  "appName": {
    "message": "我的扩展",
    "description": "扩展的显示名称"
  },
  "appDescription": {
    "message": "一款强大的浏览器效率工具",
    "description": "Chrome Web Store 中的扩展描述"
  },
  "welcomeMessage": {
    "message": "你好,$USERNAME$!欢迎回来。",
    "description": "带用户名的问候语",
    "placeholders": {
      "username": {
        "content": "$1",
        "example": "小明"
      }
    }
  },
  "itemCount": {
    "message": "您有 $COUNT$ 个项目",
    "description": "显示项目数量",
    "placeholders": {
      "count": {
        "content": "$1"
      }
    }
  },
  "openSettings": {
    "message": "打开设置",
    "description": "打开设置页面的按钮文本"
  }
}

14.3 使用 i18n API

14.3.1 JavaScript 中获取消息

// 获取消息
const appName = chrome.i18n.getMessage('appName');
console.log(appName); // "我的扩展" 或 "My Extension"

// 带占位符的消息
const welcome = chrome.i18n.getMessage('welcomeMessage', ['小明']);
console.log(welcome); // "你好,小明!欢迎回来。"

// 多个占位符
const message = chrome.i18n.getMessage('itemCount', ['5']);
console.log(message); // "您有 5 个项目"

// 获取当前语言
const lang = chrome.i18n.getUILanguage();
console.log(lang); // "zh-CN"

14.3.2 HTML 中使用

<!-- 在 manifest.json 中使用 -->
<!-- "name": "__MSG_appName__" -->

<!-- 在 HTML 中使用(需手动替换或使用 JS) -->
<h1 data-i18n="appName"></h1>
<p data-i18n="appDescription"></p>
<button data-i18n="openSettings"></button>

<script>
// 自动翻译页面中所有带 data-i18n 属性的元素
document.querySelectorAll('[data-i18n]').forEach(element => {
  const key = element.getAttribute('data-i18n');
  const message = chrome.i18n.getMessage(key);
  if (message) {
    element.textContent = message;
  }
});

// 翻译 input placeholder
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
  const key = element.getAttribute('data-i18n-placeholder');
  const message = chrome.i18n.getMessage(key);
  if (message) {
    element.placeholder = message;
  }
});

// 翻译 title 属性
document.querySelectorAll('[data-i18n-title]').forEach(element => {
  const key = element.getAttribute('data-i18n-title');
  const message = chrome.i18n.getMessage(key);
  if (message) {
    element.title = message;
  }
});
</script>

14.4 国际化工具类

// lib/i18n.js

class I18nManager {
  constructor() {
    this.currentLocale = chrome.i18n.getUILanguage();
    this.messages = {};
  }

  // 获取消息,支持回退
  getMessage(key, substitutions = []) {
    const message = chrome.i18n.getMessage(key, substitutions);
    return message || key; // 回退到键名
  }

  // 批量获取消息
  getMessages(keys) {
    const result = {};
    for (const key of keys) {
      result[key] = this.getMessage(key);
    }
    return result;
  }

  // 翻译整个 DOM
  translatePage(root = document) {
    // 翻译文本内容
    root.querySelectorAll('[data-i18n]').forEach(el => {
      const key = el.getAttribute('data-i18n');
      const text = this.getMessage(key);
      if (text) el.textContent = text;
    });

    // 翻译 placeholder
    root.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
      const key = el.getAttribute('data-i18n-placeholder');
      el.placeholder = this.getMessage(key);
    });

    // 翻译 title
    root.querySelectorAll('[data-i18n-title]').forEach(el => {
      const key = el.getAttribute('data-i18n-title');
      el.title = this.getMessage(key);
    });

    // 翻译 aria-label
    root.querySelectorAll('[data-i18n-aria]').forEach(el => {
      const key = el.getAttribute('data-i18n-aria');
      el.setAttribute('aria-label', this.getMessage(key));
    });
  }

  // 格式化日期(根据地区)
  formatDate(date, options = {}) {
    const defaults = {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      ...options
    };
    return new Intl.DateTimeFormat(this.currentLocale, defaults).format(date);
  }

  // 格式化数字
  formatNumber(number, options = {}) {
    return new Intl.NumberFormat(this.currentLocale, options).format(number);
  }

  // 格式化货币
  formatCurrency(amount, currency = 'CNY') {
    return new Intl.NumberFormat(this.currentLocale, {
      style: 'currency',
      currency
    }).format(amount);
  }

  // 相对时间
  formatRelativeTime(value, unit) {
    try {
      return new Intl.RelativeTimeFormat(this.currentLocale, {
        numeric: 'auto'
      }).format(value, unit);
    } catch {
      return `${value} ${unit}s ago`;
    }
  }
}

// 单例导出
const i18n = new I18nManager();
export default i18n;

14.5 支持语言列表

常用语言代码:

语言代码目录名
英语enen
简体中文zh-CNzh_CN
繁体中文zh-TWzh_TW
日语jaja
韩语koko
法语frfr
德语dede
西班牙语eses
葡萄牙语ptpt_BR / pt_PT
俄语ruru
阿拉伯语arar

14.6 业务场景

场景一:自动语言检测与提示

async function suggestLanguage() {
  const userLang = chrome.i18n.getUILanguage();
  const { preferredLang } = await chrome.storage.sync.get('preferredLang');

  if (!preferredLang && !userLang.startsWith('zh')) {
    // 用户使用非中文系统,但仍可提示
    const available = ['en', 'zh_CN', 'ja', 'ko'];
    const bestMatch = available.find(l => userLang.startsWith(l.split('_')[0]));

    if (bestMatch && bestMatch !== 'en') {
      chrome.notifications.create('lang-suggest', {
        type: 'basic',
        iconUrl: 'icons/icon-128.png',
        title: 'Language Available',
        message: `This extension is available in your language. Check Settings.`
      });
    }
  }
}

场景二:右键菜单国际化

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'searchWeb',
    title: chrome.i18n.getMessage('contextMenuSearch'),
    contexts: ['selection']
  });

  chrome.contextMenus.create({
    id: 'saveToCollection',
    title: chrome.i18n.getMessage('contextMenuSave'),
    contexts: ['selection', 'link', 'image']
  });
});

14.7 注意事项

问题说明解决方案
消息不显示键名拼写错误或文件缺失检查 messages.json
占位符无效placeholders 配置错误确认 $1 格式正确
目录名错误使用了连字符而非下划线zh_CN 而非 zh-CN
翻译不更新缓存问题重新加载扩展
manifest 中不生效格式应为 __MSG_key__检查双下划线格式

14.8 扩展阅读