多国语言 i18n 实现

Eagle 插件内置了 i18next 模块,所以多语言不需要引入任何第三方库。这让国际化变成了一件配置性的工作:建目录、写 JSON、改 manifest、换调用方式。

五步实现

第一步:建立 _locales 文件夹

在插件根目录建一个 _locales 文件夹,文件夹名必须是下划线开头的 _locales,不能写成 locales 或 i18n。

第二步:为每种语言建一个 JSON 文件

文件名就是语言代码。官方列出的支持语系是:en、ja_JP、es_ES、de_DE、zh_TW、zh_CN、ko_KR、ru_RU。

{
    "manifest": {
        "app": {
            "name": "i18n example"
        }
    },
    "contextMenu": {
        "copy": "Copy",
        "paste": "Paste"
    }
}
{
    "manifest": {
        "app": {
            "name": "多国语言范例"
        }
    },
    "contextMenu": {
        "copy": "复制",
        "paste": "粘贴"
    }
}

繁体中文文件把「复制」写成「複製」、「粘贴」写成「貼上」——繁体不能只是简体文件的复制,审核标准里明确说了「混杂语言到无法正常阅读」会让文案看起来尚未完成。

目录结构:

my-plugin
├─ manifest.json
├─ index.html
├─ js
│  └─ plugin.js
└─ _locales
   ├─ en.json
   ├─ zh_CN.json
   └─ zh_TW.json

第三步:调整 manifest.json

两处改动:把 name 换成插值表达式,再声明支持的语言和默认语言。

{
    "id": "LE564883T24ZR",
    "version": "1.0.0",
    "name": "`manifest.app.name`",
    "logo": "/logo.png",
    "keywords": [],

    "fallbackLanguage": "zh_CN",
    "languages": ["en", "zh_TW", "zh_CN", "ja_JP"],

    "main": {
        "url": "index.html",
        "width": 640,
        "height": 480
    }
}
  • name 写成 manifest.app.name,Eagle 会按当前语系去 _locales 里取对应值。
  • fallbackLanguage 指定某个语言缺翻译时回退到哪份。
  • languages 声明支持哪些,用户在 Eagle 里切换语言时,只有列在这里的才会被使用。

⚠️ 一个容易忽略的点:manifest 里的 name 也要翻译。商店列表显示的名称会随 Eagle 的界面语言变化,如果只有中文用户看到中文名、英文用户看到拼音,那就失去了做 i18n 的意义。

第四步:在代码里换成 i18next.t()

eagle.onPluginCreate((plugin) => {

    // 取得多国语言字段
    let copyText = i18next.t('contextMenu.copy');
    let pasteText = i18next.t('contextMenu.paste');

    document.querySelector('#message').innerHTML = `
    <ul>
        <li>Language: ${eagle.app.locale}</li>
        <li>Copy: ${copyText}</li>
        <li>Paste: ${pasteText}</li>
    </ul>
    `;
});

i18next 是全局对象,不用 require。键名用点号分层,和 JSON 里的嵌套结构一一对应。

第五步:切换语言验证

在 Eagle 里:点「Eagle」按钮 → 「偏好配置」→ 「常用」→ 修改「语言」设置,然后重新打开插件,确认文案跟着变。

覆盖到界面和通知

不只是 manifest.name 和右键菜单,所有用户可见的文案都应该走 i18next:

eagle.onPluginCreate(async () => {
    const t = (key) => i18next.t(key);

    document.querySelector('#btn-run').textContent = t('actions.run');
    document.querySelector('#btn-cancel').textContent = t('actions.cancel');
    document.querySelector('#status').textContent = t('status.ready');

    // 系统对话框也要翻译
    const result = await eagle.dialog.showMessageBox({
        title: t('confirm.title'),
        message: t('confirm.message'),
        buttons: [t('confirm.yes'), t('confirm.no')],
        type: 'question'
    });

    // 原生右键菜单
    eagle.contextMenu.open([
        { id: 'rename', label: t('menu.rename'), click: () => { /* ... */ } },
        { id: 'export', label: t('menu.export'), click: () => { /* ... */ } }
    ]);
});

// 通知同样
await eagle.notification.show({
    title: i18next.t('notify.done.title'),
    body: i18next.t('notify.done.body')
});

对应的语言文件:

{
    "actions": {
        "run": "开始处理",
        "cancel": "取消"
    },
    "status": {
        "ready": "就绪"
    },
    "confirm": {
        "title": "确认",
        "message": "即将处理选中的素材",
        "yes": "确定",
        "no": "取消"
    },
    "menu": {
        "rename": "重命名",
        "export": "导出"
    },
    "notify": {
        "done": {
            "title": "处理完成",
            "body": "所有素材已处理完毕"
        }
    }
}

读取当前语系

console.log(eagle.app.locale);   // en / zh_CN / zh_TW / ja_JP / ko_KR / es_ES / de_DE / ru_RU

需要做「按语言显示不同排版」这类事时(比如日文韩文需要不同的字体栈),可以读 locale 来分支。

const CJK_FONTS = '"PingFang SC", "Microsoft YaHei", "Hiragino Sans", "Malgun Gothic"';
const LATIN_FONTS = '-apple-system, "Segoe UI", Roboto, sans-serif';

document.body.style.fontFamily = ['zh_CN', 'zh_TW', 'ja_JP', 'ko_KR']
    .includes(eagle.app.locale) ? CJK_FONTS : LATIN_FONTS;

建议的落地策略

  1. 先设计键名,再写代码。 用 模块.动作 的分层命名,比如 export.dialog.title。事后补翻译最痛苦。
  2. 默认语言用最全的那份。 fallbackLanguage 指向你写得最完整的语言,其他语言缺的键会回退到这里。
  3. 只发布你真的翻完的语言。 languages 里列了但文件里没内容的语言,用户看到的是 contextMenu.copy 这样的原始键名——这在审核里会被当成「保留翻译键」。
  4. 日期、数字也要本地化。 toLocaleString() 会按语系输出,模板字符串拼接不会。
  5. 商店文案也逐语言检查。 审核会分别检查每个投稿语言版本,一个语言写得完整不能弥补另一个语言的问题。

常见坑

文件夹名写成 locales 或 i18n。 必须是 _locales,带前导下划线。写错了 Eagle 找不到翻译,manifest.app.name 会原样显示成一串花括号。

语言文件不完整但仍在 languages 里声明了。 缺失的键会显示成键名本身。审核标准把「保留翻译键」列为看起来尚未完成的典型表现。

延伸注意: * 文件夹名写成 locales 或 i18n。 必须是 _locales,带前导下划线。写错了 Eagle 找不到翻译,manifest.app.name 会原样显示成一串花括号。 * 语言文件不完整但仍在 languages 里声明了。 缺失的键会显示成键名本身。审核标准把「保留翻译键」列为看起来尚未完成的典型表现。

整理人:WorkBuddy