extract-i18n-plugin Svelte Themes

Extract I18n Plugin

A set of plugins for extracting i18n messages from source code and translate/compile them. Both supports for React/Preact/Vue/Svelte/Solid/Qwik/Lit/Marko based apps

extract-i18n-plugin

中文 | English

extract-i18n-plugin是一个集extract、compile、rewrite、translate于一身的vite/rollup/webpack/babel/cli插件,支持基于React、Preact、Vue(包括uni-app)、Svelte5、Solid、Qwik、Lit、Marko框架的项目。查看示例获取更多信息. 有了该插件的加持,多语言工作将变得不再繁琐和痛苦,它将为你一站式搞定。

USAGE

Install

# npm
npm install extract-i18n-plugin -D
# yarn
yarn add extract-i18n-plugin -D
# pnpm
pnpm add extract-i18n-plugin -D

CLI

extract-i18n是一个命令行工具,主要功能:提取、编译、重写、翻译.

例如:

extract-i18n --includePath=src --rewrite

这会提取src目录下的所有allowedExtensions文件的fromLang,生成JSON语言包自动翻译并生成对应的翻译JSON文件.

Programming API

const { extractI18n } = require("extract-i18n-plugin");

extractI18n(options)
  .then(() => {
    console.log("extract done!");
  })
  .catch(err => {
    console.error("extract error:", err);
  });

Options

const defaultOptions = {
  translateKey: "$t", // 提取的函数的名称
  JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" msg="xxx" />
  hooksIdentifier: "useTranslation", // 注入到组件的hook名称, 会注入const { $t } = useTranslation(),其中$t为translateKey的引用值
  injectHooks: false, // 是否将useTranslation自动注入到组件中
  jsx: false, // 是否启用 JSX 语法转换,开启后JSX里纯文本将转换为 <Trans id="aaa" msg="xxx" />而不是 $t("aaa")
  rewrite: false, // 是否将提取到的内容转换为id后重写入源文件
  extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
  autoImportI18n: true, // 是否自动导入 i18n 模块
  autoTranslate: true, // 提取完成后是否自动翻译
  cleanTranslate: true, // 是否清理无用的翻译内容
  keepRaw: false, // 开启后只做转换不生成hash值,即:"测试" -> $t("测试"), 开启rewrite时生效
  keepDefaultMsg: false, // 保留默认消息,即:"测试" -> $t("hashedKey", "测试")
  defaultMsgPos: 1, // 默认消息参数位置,0表示第一个参数,1表示第二个参数,开启keepDefaultMsg时生效
  enableCombinedSourcemap: false, // 是否开启获取组合的源映射
  enabled: true, // 是否启用插件
  debug: true, // 是否打印日志
  translateInterval: 1000, // 翻译不同语种的间隔时间, 时间过短时可能会被限流
  excludedCall: [], // 排除的调用函数名称数组,目前已内置的函数请参阅:https://github.com/semdy/extract-i18n-plugin/blob/main/lib/utils.js#L244
  includePath: ['src/'], // 包含路径的数组
  excludedPath: ['**/node_modules/**'], // 排除路径的数组 refer to https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#how-to-exclude-directory-from-reading
  allowedExtensions: [".vue", ".nvue", ".uvue", ".svelte", ".tsx", ".ts", ".jsx", ".js", ".uts", ".marko"], // 允许提取的文件扩展名
  fromLang: 'zh-cn', // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru,其它语言请使用shouldExtract判断是否要被提取
  translateLangKeys: ["zh-tw", "en"], // 定义要翻译成哪些语言
  i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
  outputPath: "src/i18n", // 提取的语言包输出文件路径
  generateId: null, // 自定义生成 key 的函数
  shouldExtract: null, // 自定义是否提取文本的判断函数
  customGenLangFileName: langKey => langKey, // 自定义生成语言文件名
  // 翻译后的文本处理函数,方便对翻译后的文本进行二次加工,如每个单词首字母大写, params: text: 翻译后的文本, toLang: 翻译后的目标语言,translateLangKeys的枚举成员
  customTranslatedText: (text, toLang) => text,
  /* 翻译器,默认使用GoogleTranslator,也可以自定义实现Translator接口 */
  translator: new GoogleTranslator()
  /** 如开启了端口代理,请配置port,如:7890 */
  translator: new GoogleTranslator({
    proxyOption: {
        port: 7890,
        host: '127.0.0.1',
        headers: {
            'User-Agent': 'Node'
        }
    }
  })
};

Configuration file

在项目根目录创建extract-i18n.config.js,用于cli和vite/webpack/babel插件的参数配置. 示例:

import { YoudaoTranslator } from "extract-i18n-plugin/translators";

export default {
  rewrite: false,
  translator: new YoudaoTranslator({
    appId: "youdao appId",
    appKey: "youdao appKey"
  }),
  ...
};

// ts支持
// extract-i18n.config.ts
import { defineConfig } from "extract-i18n-plugin";

export default defineConfig({
  rewrite: false,
  translator: new YoudaoTranslator({
    appId: "youdao appId",
    appKey: "youdao appKey"
  }),
  ...
});

Vite plugin

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { vitePluginI18n } from "extract-i18n-plugin";

export default defineConfig({
  plugins: [
    vue(),
    // 用于运行时转换
    vitePluginI18n(userConfig)
  ]
});

参数优先级:userConfig > extract-i18n.config.js > defaultOptions

Rollup plugin

import vue from "rollup-plugin-vue";
import resolve from "@rollup/plugin-node-resolve";
import { rollupPluginI18n } from "extract-i18n-plugin";

export default {
  plugins: [
    resolve(),
    vue(),
    // 用于运行时转换
    rollupPluginI18n(userConfig)
  ]
};

参数优先级:同上

Webpack plugin

const { WebpackPluginI18n } = require("extract-i18n-plugin");

module.exports = {
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: "./public/index.html"
    }),
    new WebpackPluginI18n(userConfig)
  ]
};

参数优先级:同上

Babel plugin

// babel.config.js
const config = require("./extract-i18n.config");
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  plugins: [
    [
      "extract-i18n-plugin/babel-plugin-i18n",
      {
        ...config,
        ...userConfig
      }
    ]
  ]
};

babel插件不会自动带入extract-i18n.config.js中的配置,但会带上defaultOptions,优先级:userConfig > defaultOptions

Deprecated

仓库中的babel-plugin-i18n-importrollup-plugin-i18n-importvite-plugin-i18n-importwebpack-i18n-import-loader已弃用,因为主插件中带有自动生成导入i18n的逻辑。

How It Works(工作原理)

插件会先将源码解析为 抽象语法树(AST),然后遍历其中的节点,提取可国际化的文本内容。主要处理的节点类型包括:

  • CallExpression

  • StringLiteral

  • TemplateElement

  • JSXText

  • JSXElement

  • JSXExpressionContainer

当启用 rewrite 选项时,插件会对源码进行重写,将原始文本替换为对应的国际化函数调用,并自动生成对应的 keyvalue,同时写入语言包文件。

例如:

<p>你好</p>
<p>{$t("测试")}</p>

会被转换为:

<p>{$t("03tpnc")}</p>
<p>{$t("03nbln")}</p>

如果开启了keepDefaultMsg则会生成:

<p>{$t("03tpnc", "你好")}</p>
<p>{$t("03nbln", "测试")}</p>

同时生成语言包:

{
  "03tpnc": "你好",
  "03nbln": "测试"
}

如果开启了autoTranslate则会自动翻译成其它语言并生成语言包,具体翻译成哪些语言由translateLangKeys决定。

如果开启了autoImportI18n,当前文件有国际化内容被提取,则会自动在该文件插入导入语句,如:import { $t } from "@/i18n"

如果开启了injectHooks则会在jsx/tsx组件中自动注入hooks以适应语言动态切换,如:const { t } = useTranslation().


Vue 编译优化处理

Vue 编译器在编译阶段会进行大量优化,例如:

  • 静态提升(Static Hoisting)

  • 缓存优化(_cache

  • 静态节点标记(PatchFlag

  • 动态属性依赖提升(hoisted dynamicProps

当插件将文本替换为 i18n 调用后,原本的 静态节点可能会变为动态节点。 为了保证运行时行为正确,插件需要执行以下处理:

  • 静态节点重新标记为动态节点

  • 移除 _cache 缓存优化

  • 更新或追加 dynamicProps 依赖

因此,在 Vue 项目中,这部分转换逻辑会相对复杂。


Qwik 编译优化处理

Qwik 编译器同样会在编译阶段对模板进行 静态节点标记。

当插件对文本节点进行 i18n 转换后,这些原本被标记为静态的节点也需要重新标记为 动态节点,以确保运行时能够正确更新内容。

重要说明

在Vue3中,vue-i18n版本大于9.0.0时,legacy须设为false,否则在开发阶段会有Uncaught TypeError: 'set' on proxy: trap returned falsish for property '$t'的代理错误. 推荐写法如下:

import { createI18n } from "vue-i18n";
import zhMessages from "@/locales/zh-cn.json";
import enMessages from "@/locales/en.json";

const i18n = createI18n({
  legacy: false,
  globalInjection: true,
  allowComposition: true,
  fallbackLocale: "en",
  locale: "zh",
  messages: {
    en: enMessages,
    zh: zhMessages
  }
});

// 导出一个$t方法
export const $t = i18n.global.t.bind(i18n.global);

export default i18n;

如果不想使用vite/webpack插件,可以手动调用extract-i18n --rewrite,这会将转换后的代码重新写入源文件(uni-app X项目可用于此模式).

Known issues & Guidelines

  • 由于svelte和solid-js编译器都有静态提升的优化策略,因此不支持纯文本提取,需要在源码中使用显式调用$t("文本")的方式。

  • Lit由于是静态模板,因此不支持纯文本提取,需要在源码中使用显式调用$t("文本")的方式。

  • vue编译器同样有静态提升以及静态节点标记(patchFlag)的优化,该插件会将它重新标记为动态节点,否则切换语言后,节点不会更新。绝大部分情况下纯文本提取没问题,有问题的地方建议使用显式调用$t("文本")的方式。

  • 基于uni-app的小程序项目的建议:开发时直接写纯文本,然后使用extract-i18n --rewrite --keepRaw转换,会将"文本"转换成$t("文本")并写入源码,不然该插件将无法正常工作,因为根据uni-app编译器策略,静态文本会保留在wxml文件中,只有动态内容才会编译到js文件中,这样才能被正常提取和转换。

  • uni-app X项目底层编译器是kotlin, 需要提前将源码进行转换。建议使用extract-i18n --rewrite --keepDefaultMsg"文本"转换成$t("id","文本"),这样既保证了i18n的功能也不影响对源码的阅读。

  • svelte4 typescript项目静态提取不受支持,因为svelte 4.0编译器的parser不支持typescript。

  • svelte项目建议添加prettier-plugin-svelte依赖,因为rewrite模式会调用prettier格式化.svelte文件,svelte文件格式化依赖这个插件.

  • 对于纯英文项目,建议在源码中使用显式调用$t("文本") + extractFromText:false的方式。因为该插件无法区分需要翻译的文本和代码中的字符串。

  • extractFromText设为false,则纯文本不会被提取,只会从$t()Trans组件中提取文本,能一定程序上提高性能。

  • 有动态插值的文本需要显式调用$t(),如:$t("{name}的余额为{balance}", {name: '张三', balance: 100})

总结:

纯文本编写有很好的便利性,但$t()有更好的稳定性和可靠性(特别是vue项目),当然也可以混用。该插件已经服务了公司内部多个涵盖React、Vue、uni-app(APP-PLUS、X、H5、小程序)的项目,不管用哪种方式,稳定性和可靠性都不错。

Translators

插件默认使用谷歌翻译(默认配置代理端口7890)。在网络不支持访问谷歌的情况下,我们推荐使用 有道翻译 ✨,其翻译效果优秀。目前插件已经内置谷歌、有道和百度翻译功能。如果需要自定义翻译器,可参考下方的示例。

Google Translate (default)

import { GoogleTranslator } from 'extract-i18n-plugin/translators'

...
translator: new GoogleTranslator({
    proxyOption: {
        host: '127.0.0.1',
        port: 7890,
        headers: {
            'User-Agent': 'Node'
        }
    }
})
...

有道Translate

需要申请api,api文档

import { YoudaoTranslator } from 'extract-i18n-plugin/translators'

...
translator: new YoudaoTranslator({
    appId: '你申请的appId',
    appKey: '你申请的appKey'
})
...

百度Translate

需要申请api,api文档

import { BaiduTranslator } from 'extract-i18n-plugin/translators'

...
translator: new BaiduTranslator({
    appId: '你申请的appId', // 百度翻译 AppId
    appKey: '你申请的appKey' // 百度翻译 AppKey
})
...

火山引擎AI Translate

支持调用 doubaodeepseek 进行翻译,AI大模型的翻译效果会比传统的API翻译更准确,但耗时较长。 火山引擎大模型介绍:https://www.volcengine.com/docs/82379/1099455。 需要开通大模型服务并申请API,api文档

import { VolcEngineTranslator } from 'extract-i18n-plugin/translators'

...
translator: new VolcEngineTranslator({
    apiKey: '你申请的apiKey',
    model: '你要调用的模型,如:`doubao-1-5-pro-32k-250115`,请确保使用前已在控制台开通了对应模型'
})
...

Empty Translate

如果只需要扫描目标语言,不进行翻译,该翻译器会生成 JSON 文件。

import { EmptyTranslator } from 'extract-i18n-plugin/translators'

...
translator: new EmptyTranslator()
...

Custom Translate

如果你有一个自用的翻译接口,可以通过以下方式自定义翻译器——

最简单的方式是使用 Translator 基类定义翻译器实例。

import { Translator } from 'extract-i18n-plugin/translators'
import axios from 'axios'

...
translator: new Translator({
    name: '我的翻译器',
    // 翻译的方法
    fetchMethod: (str, fromKey, toKey, _separator) => {
        // 实际的接口调用可能比示例更复杂,具体可参考源码中YoudaoTranslator的实现,路径:src\translators\youdao.js
        const myApi = 'https://www.my-i18n.cn/api/translate?from=${fromKey}&to=${toKey}&t={+new Date}'
        return axios.post(myApi, { str })
            .then(res => res.data)
    },
    // 接口触发间隔,有些接口频繁触发会被拉黑,请根据实际情况设置一个合理的接口触发间隔
    interval: 1000
})
...

如果需要更高阶的功能,可以使用继承,不过目前无相关场景。

import { Translator } from 'extract-i18n-plugin/translators'

class CustomTranslator extends Translator {
    constructor () {
        super({
            name: '我的翻译器',
            ...
        })
    }
}

...
translator: new CustomTranslator()
...

MIT

Top categories

Loading Svelte Themes