From a3b42aa7c361d9835b23512dbbcac41db8767ef3 Mon Sep 17 00:00:00 2001 From: wxzhang Date: Sun, 7 Aug 2022 11:16:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=96=87=E6=A1=A3=E7=BD=91?= =?UTF-8?q?=E7=AB=99=E5=88=B0dumi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .umirc.ts | 100 ++++++++ docs/src/contribute/readme.md | 73 ++++++ docs/src/guide/advanced/arch.png | Bin 0 -> 27785 bytes docs/src/guide/advanced/autoimport.md | 25 ++ docs/src/guide/advanced/autotranslate.md | 9 + docs/src/guide/advanced/customformatter.md | 197 ++++++++++++++++ docs/src/guide/advanced/framework.md | 68 ++++++ docs/src/guide/advanced/langedit.md | 16 ++ docs/src/guide/advanced/langpack.md | 22 ++ docs/src/guide/advanced/lngpatch.md | 8 + docs/src/guide/advanced/multi-libs.md | 16 ++ docs/src/guide/advanced/remoteLoad.md | 246 ++++++++++++++++++++ docs/src/guide/advanced/runtime.md | 37 +++ docs/src/guide/advanced/textMap.md | 45 ++++ docs/src/guide/intro/get-started.md | 251 +++++++++++++++++++++ docs/src/guide/intro/history.md | 9 + docs/src/guide/intro/install.md | 47 ++++ docs/src/guide/intro/question.md | 3 + docs/src/guide/intro/readme.md | 38 ++++ docs/src/guide/intro/support.md | 4 + docs/src/guide/intro/versions.md | 11 + docs/src/guide/tools/babel.md | 73 ++++++ docs/src/guide/tools/cli.md | 251 +++++++++++++++++++++ docs/src/guide/tools/vite.md | 97 ++++++++ docs/src/guide/tools/vue.md | 92 ++++++++ docs/src/guide/use/change-langeuage.md | 32 +++ docs/src/guide/use/currency.md | 5 + docs/src/guide/use/datetime.md | 19 ++ docs/src/guide/use/interpolation.md | 84 +++++++ docs/src/guide/use/namespace.md | 47 ++++ docs/src/guide/use/plural.md | 112 +++++++++ docs/src/guide/use/react.md | 74 ++++++ docs/src/guide/use/t.md | 40 ++++ docs/src/guide/use/vue.md | 135 +++++++++++ docs/src/index.md | 74 ++++++ docs/src/reference/formatters.md | 3 + docs/src/reference/i18nscope.md | 40 ++++ docs/src/reference/lang-code.md | 35 +++ docs/src/reference/readme.md | 12 + docs/src/reference/voerkaI18n.md | 27 +++ tsconfig.json | 29 +++ typings.d.ts | 2 + 42 files changed, 2508 insertions(+) create mode 100644 .umirc.ts create mode 100644 docs/src/contribute/readme.md create mode 100644 docs/src/guide/advanced/arch.png create mode 100644 docs/src/guide/advanced/autoimport.md create mode 100644 docs/src/guide/advanced/autotranslate.md create mode 100644 docs/src/guide/advanced/customformatter.md create mode 100644 docs/src/guide/advanced/framework.md create mode 100644 docs/src/guide/advanced/langedit.md create mode 100644 docs/src/guide/advanced/langpack.md create mode 100644 docs/src/guide/advanced/lngpatch.md create mode 100644 docs/src/guide/advanced/multi-libs.md create mode 100644 docs/src/guide/advanced/remoteLoad.md create mode 100644 docs/src/guide/advanced/runtime.md create mode 100644 docs/src/guide/advanced/textMap.md create mode 100644 docs/src/guide/intro/get-started.md create mode 100644 docs/src/guide/intro/history.md create mode 100644 docs/src/guide/intro/install.md create mode 100644 docs/src/guide/intro/question.md create mode 100644 docs/src/guide/intro/readme.md create mode 100644 docs/src/guide/intro/support.md create mode 100644 docs/src/guide/intro/versions.md create mode 100644 docs/src/guide/tools/babel.md create mode 100644 docs/src/guide/tools/cli.md create mode 100644 docs/src/guide/tools/vite.md create mode 100644 docs/src/guide/tools/vue.md create mode 100644 docs/src/guide/use/change-langeuage.md create mode 100644 docs/src/guide/use/currency.md create mode 100644 docs/src/guide/use/datetime.md create mode 100644 docs/src/guide/use/interpolation.md create mode 100644 docs/src/guide/use/namespace.md create mode 100644 docs/src/guide/use/plural.md create mode 100644 docs/src/guide/use/react.md create mode 100644 docs/src/guide/use/t.md create mode 100644 docs/src/guide/use/vue.md create mode 100644 docs/src/index.md create mode 100644 docs/src/reference/formatters.md create mode 100644 docs/src/reference/i18nscope.md create mode 100644 docs/src/reference/lang-code.md create mode 100644 docs/src/reference/readme.md create mode 100644 docs/src/reference/voerkaI18n.md create mode 100644 tsconfig.json create mode 100644 typings.d.ts diff --git a/.umirc.ts b/.umirc.ts new file mode 100644 index 0000000..5a30b67 --- /dev/null +++ b/.umirc.ts @@ -0,0 +1,100 @@ +import { defineConfig } from 'dumi'; + +// more config: https://d.umijs.org/config + +export default defineConfig({ + title: 'VoerkaI18n', + base:"voerkao18n", + mode: 'site', + logo: "/images/i18n.png", + outputPath:"docs/dist", + resolve:{ + includes:["docs/src"] + }, + locales: [['zh-CN', '中文']], + navs:[ + { + title:"指南", + path: "/guide" + }, + { + title:"参考", + path: "/reference" + }, + { + title:"贡献源码", + path: "/contribute" + }, + { + title:"源代码", + path: "https://gitee.com/zhangfisher/voerka-i18n" + } + ], + menus: { + '/guide': [ + { + title: '开始', + children: [ + "/guide/intro/readme.md", + "/guide/intro/install.md", + "/guide/intro/get-started.md", + "/guide/intro/versions.md", + "/guide/intro/support.md", + "/guide/intro/question.md" + ], + }, + { + title: '指南', + children: [ + "/guide/use/t.md", + "/guide/use/interpolation.md", + "/guide/use/datetime.md", + "/guide/use/plural.md", + "/guide/use/currency.md", + "/guide/use/namespace.md", + "/guide/use/change-langeuage.md", + "/guide/use/vue.md", + "/guide/use/react.md" + ], + }, + { + title: '高级特性', + children: [ + "/guide/advanced/runtime.md", + "/guide/advanced/textMap.md", + "/guide/advanced/multi-libs.md", + "/guide/advanced/autoimport.md", + "/guide/advanced/customformatter.md", + "/guide/advanced/langpack.md", + "/guide/advanced/autotranslate.md", + "/guide/advanced/framework.md", + "/guide/advanced/remoteLoad.md", + "/guide/advanced/lngpatch.md", + "/guide/advanced/langedit.md" + ], + }, + { + title: '工具', + children: [ + "/guide/tools/cli.md", + "/guide/tools/babel.md", + "/guide/tools/vite.md", + "/guide/tools/vue.md" + ], + }, + ], + '/reference': [ + { + title: '参考', + children:[ + "/reference/voerkaI18n.md", + "/reference/i18nscope.md", + "/reference/formatters.md", + "/reference/lang-code.md", + + ] + } + ] + + } +}); diff --git a/docs/src/contribute/readme.md b/docs/src/contribute/readme.md new file mode 100644 index 0000000..a603149 --- /dev/null +++ b/docs/src/contribute/readme.md @@ -0,0 +1,73 @@ +--- +sidebar: heading +--- + +# 源码贡献 + +`voerkai18n`是开源项目,欢迎大家贡献源码。 + +## 获取源码 + +`voerkai18n`在Github和[Gitee](https://gitee.com/zhangfisher/voerka-i18n)上面开源。 + +### 拉取源码 + +```shell +git clone https://gitee.com/zhangfisher/voerka-i18n +``` + +### 安装依赖 + +`voerkai18n`是一个`monorepo`多包工程,使用`pnpm`作为包管理器。所以首先需要安装`pnpm`。 + +```javascript | pure +> npm install -g pnpm +``` + +然后再使用`pnpm install` + +## 源码结构 + +```javascript | pure +voerkai18n + packages + autopublish // 自动发布工具,仅用于开发阶段 + babel // @voerkai18n/babel插件 + cli // @voerkai18n/cli命令行工具 + formatters // @voerkai18n/formatters通用的格式化器 + react // @voerkai18n/react + runtime // @voerkai18n/runtime + utils // @voerkai18n/utils工具库 + vite // @voerkai18n/vite插件 + vue // @voerkai18n/vue插件 + apps // 测试应用 + test // 单元测试用例 + docs // 文档网站Vuepress + readme.md +``` + +## 工作原理 + +![](/images/arch.png) + +## 开发格式化器 + + + +## 单元测试 + + + +## 文档 + + + + +## 发布 + + + + + + + diff --git a/docs/src/guide/advanced/arch.png b/docs/src/guide/advanced/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..c474dfcfb4f847d0e9f0291fb0a0eb1ea2227048 GIT binary patch literal 27785 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...}, + //[格式化名称]:(value,arg)=>{...}, + }, + // 在所有语言下只作用于特定数据类型的格式化器 + $types:{ + // [数据类型名称]:(value)=>{...}, + // [数据类型名称]:(value)=>{...}, + }, + zh:{ + $types:{ + // 所有类型的默认格式化器 + "*":{ + }, + Date:{}, + Number:{}, + Boolean:{ }, + String:{}, + Array:{ + + }, + Object:{ + + } + }, + [格式化名称]:(value)=>{.....}, + //..... + }, + en:{ + $types:{ + // [数据类型名称]:(value)=>{...}, + }, + [格式化名称]:(value)=>{.....}, + //.....更多的格式化器..... + } +} +``` + +## 格式化器函数 + +**每一个格式化器就是一个普通的同步函数**,不支持异步函数,格式化器函数可以支持无参数或有参数。 + +- 无参数的格式化器:`(value)=>{....返回格式化的结果...}`。 + +- 带参数的格式化器:`(value,arg1,...)=>{....返回格式化的结果...}`,其中`value`是上一个格式化器的输出结果。 + +## 类型格式化器 + +可以为每一种数据类型指定一个默认的格式化器,支持对`String`、`Date`、`Error`、`Object`、`Array`、`Boolean`、`Number`等数据类型的格式化。 + +当插值变量传入时,如果有定义了对应的的类型格式化器,会默认调用该格式化器对数据进行转换。 + +比如我们定义对`Boolean`类型格式化器, + +```javascript | pure +//formatters.js + +module.exports = { + // 在所有语言下只作用于特定数据类型的格式化器 + $types:{ + Boolean:(value)=> value ? "ON" : "OFF" + } +} +t("灯状态:{status}",true) // === 灯状态:ON +t("灯状态:{status}",false) // === 灯状态:OFF +``` + +在上例中,如果我们想在不同的语言环境下,翻译为不同的显示文本,则可以为不同的语言指定类型格式化器 + +```javascript | pure +//formatters.js +module.exports = { + zh:{ + $types:{ + Boolean:(value)=> value ? "开" : "关" + } + }, + en:{ + $types:{ + Boolean:(value)=> value ? "ON" : "OFF" + } + } +} +// 当切换到中文时 +t("灯状态:{status}",true) // === 灯状态:开 +t("灯状态:{status}",false) // === 灯状态:关 +// 当切换到英文时 +t("灯状态:{status}",true) // === 灯状态:ON +t("灯状态:{status}",false) // === 灯状态:OFF +``` + +**说明:** + +- 完整的类型格式化器定义形式 + + ```javascript | pure + module.exports = { + "*":{ + $types:{...} + }, + zh:{ + $types:{...} + }, + en:{ + $types:{....} + } + } + ``` + + 在匹配应用格式化时会先在当前语言的`$types`中查找匹配的格式化器,如果找不到再上`*.$types`中查找。 + +- `*.$types`代表当所有语言中均没有定义时才匹配的类型格式化。 + +- 类型格式化器是**默认执行的,不需要指定名称**。 + +- 当前作用域的格式化器优先于全局的格式化器。 + +## 通用的格式化器 + +类型格式化器只针对特定数据类型,并且会默认调用。而通用的格式化器需要使用`|`管道符进行显式调用。 + +同样的,通用的格式化器定义在`languages/formatters.js`中。 + +```javascript | pure +module.exports = { + "*":{ + $types:{...}, + [格式化名称]:(value)=>{.....}, + }, + zh:{ + $types:{...}, + [格式化名称]:(value)=>{.....}, + }, + en:{ + $types:{....}, + [格式化名称]:(value)=>{.....}, + [格式化名称]:(value,arg)=>{.....}, + } +} +``` + +每一个格式化器均需要指定一个名称,在进行插值替换时会优先依据当前语言来匹配查找格式化器,如果找不到,再到键名为`*`中查找。 + +```javascript | pure +module.exports = { + "*":{ + uppercase:(value)=>value + }, + zh:{ + uppercase:(value)=>["一","二","三","四","五","六","七","八","九","十"][value-1] + }, + en:{ + uppercase:(value)=>["One","Two","Three","Four","Five","Six","seven","eight","nine","ten"][value-1] + }, + jp:{ + + } +} +// 当切换到中文时 +t("{value | uppercase}",1) // == 一 +t("{value | uppercase}",2) // == 二 +t("{value | uppercase}",3) // == 三 +// 当切换到英文时 +t("{value | uppercase}",1) // == One +t("{value | uppercase}",2) // == Two +t("{value | uppercase}",3) // == Three +// 当切换到日文时,由于在该语言下没有定义uppercase格式式,因此到*中查找 +t("{value | uppercase}",1) // == 1 +t("{value | uppercase}",2) // == 2 +t("{value | uppercase}",3) // == 3 +``` + +## 作用域格式化器 + +定义在`languages/formatters.js`里面的格式化器仅在当前工程生效,也就是仅在当前作用域生效。一般由应用开发者自行扩展。 + +## 全局格式化器 + +定义在`@voerkai18n/runtime`里面的格式化器则全局有效,在所有场合均可以使用,但是其优先级低于作用域内的同名格式化器。 + +目前内置的全局格式化器请参阅API参考 + +## 扩展格式化器 + +除了可以在当前项目`languages/formatters.js`自定义格式化器和`@voerkai18n/runtime`里面的全局格式化器外,单列了`@voerkai18n/formatters`项目用来包含了更多的格式化器。 + +作为开源项目,欢迎大家提交贡献更多的格式化器。 diff --git a/docs/src/guide/advanced/framework.md b/docs/src/guide/advanced/framework.md new file mode 100644 index 0000000..906a1f0 --- /dev/null +++ b/docs/src/guide/advanced/framework.md @@ -0,0 +1,68 @@ +# 框架集成 + +`voerkai18n`可用应用于绝大多数框架,包括但不限于`Vue`、`React`、`Angular`、`Svelte`等等。要为某个框架或库集成VoerkaI18n的功能,一般需要完成以下几件事: + +## 执行`t`翻译函数 + +翻译本质上是非常简单的查表功能,原则上只需要将所有需要翻译的字符串使用`t`函数包装起来即可。因此,只需要具备执行`t`函数的条件即可。而这是比较容易做到的,没有任何难度。 + +一般只需要`import { t } from "./languages"`即可直接使用`t`函数,不需要任何配置。`./languages`里面的文件本身就是您项目源码的一部分。 + +至此,您的应用就具备翻译功能了。 + +## 自动导入`t`翻译函数 + +由于需要在所以需要使用`t`翻译函数的文件均需要导入,比较麻烦。为了简化导入工作,`voerkai18n`提供了`babel`、`vite`、`webpack`插件,配置好以后,可以扫描发现在源码中使用了`t`函数,就自动进行导入。这几个插件基本上涵盖了大部份Javascrip工程场景,应用这些插件后,`t`函数就相当于是一个全局函数,可以在项目中直接使用而不必进行导入。 + +自动导入`t`翻译函数仅是改善编程体验,并不是必须的。 + +## 动态切换语言 + +动态切换语言指的是用户在界面上选择其他语言,整个界面自动更新为新的语言,即重新渲染。基本步骤如下: + +- **切换语言** + + ```javascript | pure + import { i18nScope } from "./languages" + await i18nScope.change("<新语言>") + // 或者调用全局 + VoerkaI18n.change("<新语言>") + ``` + +- **响应语言切换事件** + + ```javascript | pure + import { i18nScope } from "./languages" + i18nScope.on((newLanguage)=>{...}) + // 或者调用全局 + VoerkaI18n.on((newLanguage)=>{...}) + ``` + +- **重新渲染界面** + + 应用程序可以在侦听到语言切换事件后对整个应用程序进行重新渲染。而这种全局重新渲染各个框架的实现就有所差别,但是总体上并不难。比如在Vue中,可以这样: + + ```javascript | pure + import { i18nScope } from "./languages" + i18nScope.on((newLanguage)=>{ + app._instance.update() // 强制重新渲染 + activeLanguage.value = newLanguage + }) + ``` + +## 小结 + +了解了上述基本原理,为`Vue`、`React`、`Angular`、`Svelte`、`uniapp`、`ReactNative`等应用程序集成`voerkai18n`就非常容易。 + + + + + + + + + + + + + diff --git a/docs/src/guide/advanced/langedit.md b/docs/src/guide/advanced/langedit.md new file mode 100644 index 0000000..81cc48a --- /dev/null +++ b/docs/src/guide/advanced/langedit.md @@ -0,0 +1,16 @@ +# 在线编辑语言包 + +利用**动态加载语言包**的机制,开发者可以非常容易就开发出`让用户自行编辑界面语言`的功能。 + +请详细阅读[`远程加载语言包`](./remote-load)的实现过程,其基本思路如下: +- 后端采用数据库来保存语言包,每一个语言包可以用一个表或多个表存储。 +- 编写对应的编辑语言包的Web API,实现通过API修改语言包功能 +- 前端代码重写语言包加载器函数,将原来的读取静态语言包的方式,修改为采用API读取 +- 前端增加编辑语言包的界面 + + + + + + + diff --git a/docs/src/guide/advanced/langpack.md b/docs/src/guide/advanced/langpack.md new file mode 100644 index 0000000..4e37524 --- /dev/null +++ b/docs/src/guide/advanced/langpack.md @@ -0,0 +1,22 @@ +# 语言包 + +当使用`webpack`、`rollup`、`esbuild`进行项目打包时,默认语言包采用静态加载,会被打包进行源码中,而其他语言则采用异步打包方式。在`languages/index.js`中。 + +```javascript | pure +const defaultMessages = require("./zh.js") +const activeMessages = defaultMessages + +// 语言作用域 +const scope = new i18nScope({ + default: defaultMessages, // 默认语言包 + messages : activeMessages, // 当前语言包 + .... + // 以下为每一种语言生成一个异步打包语句 + loaders:{ + "en" : ()=>import("./en.js") + "de" : ()=>import("./de.js") + "jp" : ()=>import("./jp.js") + }) +``` + +利用异步打包机制,从而避免将多个语言静态打包到源码包。 \ No newline at end of file diff --git a/docs/src/guide/advanced/lngpatch.md b/docs/src/guide/advanced/lngpatch.md new file mode 100644 index 0000000..959a3b6 --- /dev/null +++ b/docs/src/guide/advanced/lngpatch.md @@ -0,0 +1,8 @@ +# 语言包补丁 + +在实际应用中,我们经常会在应用上线后,发现应用中的语言翻译错误,此时就可以利用`voerkai18n`的语言包补丁特性来解决此问题。 +利用`voerkai18n`的语言包补丁特性,您就可以随时修复翻译错误,而不需要重新打包应用。 + +**基本思路是,应用上线后发现翻译错误时,可以在服务器上约定位置放置语言包补丁,应用会自动进行更新修复,很实用的一个特性。** + +使用方法详见`动态加载语言包`介绍。 diff --git a/docs/src/guide/advanced/multi-libs.md b/docs/src/guide/advanced/multi-libs.md new file mode 100644 index 0000000..bd80f19 --- /dev/null +++ b/docs/src/guide/advanced/multi-libs.md @@ -0,0 +1,16 @@ +# 多库联动 + +`voerkai18n `支持多个库国际化的联动和协作,即**当主程序切换语言时,所有引用依赖库也会跟随主程序进行语言切换**,整个切换过程对所有库开发都是透明的。 +库开发者不需要特殊配置,只需要像普通应用一样进行开发即可。 +整体原理框架如下: + +![结构图](./arch.png) + +当我们在开发一个应用或者库并`import "./languages"`时,在`langauges/index.js`进行了如下处理: + +- 创建一个`i18nScope`作用域实例 +- 检测当前应用环境下是否具有全局单例`VoerkaI18n` + - 如果存在`VoerkaI18n`全局单例,则会将当前`i18nScope`实例注册到`VoerkaI18n.scopes`中 + - 如果不存在`VoerkaI18n`全局单例,则使用当前`i18nScope`实例的参数来创建一个`VoerkaI18n`全局单例。 +- 在每个应用与库中均可以使用`import { t } from ".langauges`导入本工程的`t`翻译函数,该`t`翻译函数被绑定当前`i18nScope`作用域实例,因此翻译时就只会使用到本工程的文本。这样就割离了不同工程和库之间的翻译。 +- 由于所有引用的`i18nScope`均注册到了全局单例`VoerkaI18n`,当切换语言时,`VoerkaI18n`会刷新切换所有注册的`i18nScope`,这样就实现了各个`i18nScope`即独立,又可以联动语言切换。 diff --git a/docs/src/guide/advanced/remoteLoad.md b/docs/src/guide/advanced/remoteLoad.md new file mode 100644 index 0000000..0303dec --- /dev/null +++ b/docs/src/guide/advanced/remoteLoad.md @@ -0,0 +1,246 @@ +# 远程加载语言包 + +## 前言 +`voerkai18n`默认将要翻译的文本内容经编译后保存在当`languages`文件夹下,当打包应用时会与工程一起进行打包进工程源码中。这会带来以下问题: +- 翻译语言包是源码工程的一部分,当要翻译的语种较多时,会增加源码包大小。 +- 如果产品上线后发现翻译问题,则需要重新进行整个工程的打包 +- 上线后要增加一种语言,同样需要再次进行走一次打包流程 + +`voerkai18n`针对这些问题,支持了远程加载语言包的功能,可以支持线上`动态增加支持的语言`,`在线翻译补丁`等特性。 + + +## 使用方法 + +### 准备 + +为说明如何从远程加载语言包,我们将假设以下的应用: +应用`chat`,依赖于`user`、`manager`、`log`等三个库,均使用了`voerkiai18n`作为多语言解决方案 +当执行完`voerkai18n compile`后,项目结构大概如下: +```javascript | pure +chat + |-- languages + | |-- index.js + | |-- idMap.js + | |-- runtime.js + | |-- settings.json + | |-- cn.js + | |-- en.js + | |-- translates + | |-- default.json + |-- index.js + |-- package.json //name=chat + +``` +打开`languages/index.js`,大概如下: +```javascript | pure +// .... +const scope = new i18nScope({ + id: "chat", // 当前作用域的id,自动取当前工程的package.json的name + loaders:{ + "en" : ()=>import("./en.js") + }, + //..... +}) +/// .... +``` +- 可以看到在`languages/index.js`中创建了一个以当前工程`package.json`的`name`为`id`的`i18nScope`实例,然后注册到全局`VoerkaI18n`实例中。 +- 为`en`语言创建了一个异步加载器,用来异步加载`en`语言包。 +- 当打包`chat`应用时,`cn.js`、`en.js`等语言包均作为源码的一部分打包,差别在于非默认语言`en.js`单独作为一个`chunk`打包以便能异步加载。 + +下面假设,当应用上线后,客户要求增加`de`语言,但是我们的源码包中并没有包含`de`语言,利用`voerkiai18n`语言加载器功能,可以比较方便地实现动态增加支持语言的功能。 + +### 第一步:注册默认的语言加载器 + + `voerkiai18n`是采用语言加载器来加载语言包的,默认语言包以静态方法打包到源码中,而非默认语言则采用异步加载方式进行加载。 +当注册了一个默认的语言包加载器后,如果切换到一个未注册的语言时,会调用默认的语言包加载器来获取语言包。 +利用此特性就可以实现随时动态为应用增加语言支持的特性。 + +首先需要在应用中导入`i18nScope`实例,然后注册一个默认的语言加载器。 + +```javascript | pure + +// 从当前工程导入`scope`实例 +import { i18nScope } from "./languages" + +// 注册默认的语言加载器 +i18nScope.registerDefaultLoader(async (language,scope)=>{ + // language: 要切换到此语言 + // scope: 语言作用域实例 + // 在此向服务器发起请求,请返回翻译后的其他语言文本 + return {.....} +}) +``` + +### 第二步:编写语言包加载器 + +然后,我们就可以在此向服务器发起异步请求来读取语言包文件。 + +```javascript | pure + +// 从当前工程导入`scope`实例 +import { i18nScope } from "./languages" + +i18nScope.registerDefaultLoader(async (language,scope)=>{ + return await (await fetch(`/languages/${scope.id}/${language}.json`)).json() +}) +``` + +语言加载器函数需要返回JSON格式的语言包,大概如下: +```json +{ + "1":"xxxxx", + "2":"xxxxx", + "3":"xxxxx", + //.... +} +``` + +**重点:为什么要向服务器传递`scope.id`参数?** +在多包环境下,按照多包/库开发的规范,每一个库或包均具有一个**唯一的id**,默认会使用`package.json`中的`name`字段。 + **例如**: +- 应用`A`,依赖于包/库`X`、`Y`、`Z`,并且`A/X/Y/Z`均使用了`voerkiai18n`作为多语言解决方案 +- 当应用启动时,`A/X/Y/Z`均会创建一个`i18nScope`实例,其`id`分别是`A/X/Y/Z`,然后这些`i18nScope`实例会注册到全局的`VoerkaI18n`实例中(详见多库联动介绍)。 +- 假如应用`A`配置支持`zh`、`cn`两种语言,当应用要切换到`de`语言时,那么不仅是`A`应用本身需要切换到`de`语言,所依赖的库也需要切换到`de`语言。但是库`X`、`Y`、`Z`本身可能支持`de`语言,也可以不支持。如果不支持,则同样需要向服务器请求该库的翻译语言。因此,在向服务器请求时就需要带上`scope.id`。 + + +### 第三步:将语言包文件保存在服务器 + +在上一步中,我们通过`fetch(/languages/${scope.id}/${language}.json)`来传递读取语言包(您可以使用任意您喜欢的方式),这意味着我们需要在web服务器上根据此URL来组织语言包,以便可以下载到语言包。比如可以使用如下: +```javascript | pure +webroot + |-- languages + + |-- de.json + + |-- de.json + + |-- de.json + + |-- de.json +``` + +`VoerkaI18n`将编写**如何语言加载器**和**如何在服务器上组织语言包**交由开发者自行决定,您完全可以根据自己的喜好来决定如何加载。 + + +### 第四步:生成语言包文件 + +在本例中,我们要增加`de`语言,这就需要在服务器上生成一个对应的`de`语言包文件。 +方法很简单,打开`languages/cn.js`文件,该文件大概如下: +```javascript | pure +module.exports = { + "1": "支持的语言", + "2": "默认语言", + "3": "激活语言", + "4": "名称空间s", + .... +} +``` +复制一份修改和更名为`de.json`,内容大概概如下: +```javascript | pure +{ + "1": "支持的语言", + "2": "默认语言", + "3": "激活语言", + "4": "名称空间s", + .... +} +``` +然后将`de.json`复制到`languages/chat/de.json`即可。 +同样地,我们也需要对`user`、`manager`、`log`等三个库的语言文件如法泡制,生成语言包文件`languages/user/de.json`,`languages/manager/de.json`,`languages/log/de.json`,这样这三个库也能实现支持`de`语言。 + +### 第五步:编写语言包补丁 + +至此,我们已经实现了可以为应用动态添加语言支持的功能。但是默认语言加载器只是针对的未知的语言起作用,而对内置的语言是不起作用的。也就是说上例中的内置语言`cn`和`en`不能通过此方法来加载。 + +在实际应用中,我们经常会在应用上线的,发现应用中的某此语言翻译错误,此时就可以利用`voerkai18n`的语言包补丁特性来解决此问题。 +利用`voerkai18n`的语言包补丁特性,您就可以随时修复翻译错误,而不需要重新打包应用。 + +`voerkai18n`的语言包补丁特性的工作机制同样也是利用了默认语言加载器来加载语言包补丁。其工作原理很简单,如下: +- 按上例中的方式注册默认语言加载器 +- 当i18nScope注册到全局VoerkaI18n时,会调用默认的语言加载器,从服务器加载语言包,然后合并到本地语言包中,这样就很轻松地实现了动态言包的特性。 + +在本例中,我们假设chat应用的中文语言发现翻译错误,需要一个语言包补丁来修复,方法如下: +```javascript | pure +webroot + |-- languages + + |-- cn.json + +``` +按上例说明的方式,在服务器上编辑一个`cn.json`文件,保存到`languages/char/cn.json`,里面内容只需要包括出错的内容即可。 + +```javascript | pure +{ + "4": "名称空间" +} +``` +然后,当应用切换到指定`cn`语言时,就会下载该语言包合并到源码中的语言包,从而实现为语言包打补丁的功能,修复翻译错误。此功能简单而实用,强烈推荐。 + +### 小结 + + - 当注册了一个默认的语言加载器后,当切换到未配置过的语言时,会调用默认的文本加载器来从服务器加载语言文本。 + - 对于已配置的语言,会在注册时从服务器加载进行合并,从而实现为语言包打补丁的功能。 + - 您需要自己在服务器上组织存放配套的语言包文件,然后编写通过`fetch/axios`等从服务器加载 + + +## 指南 + +### 语言包加载器 + +语言加载器是一个普通`异步函数`或者`返回Promise`的函数,可以用来从远程加载语言包文件。 + +语言加载器时会传入两个参数: +| 参数 | 说明 | +| --- | --- | +| language | 要切换的此语言| +| scope |语言作用域实例,其中`scope.id`值默认等于`package.json`中的`name`字段。详见[参考](../../reference/i18nscope)。 | + +- 典型的语言加载器非常简单,如下: +```javascript | pure +import { i18nScope } from "./languages" +i18nScope.registerDefaultLoader(async (language,scope)=>{ + return await (await fetch(`/languages/${scope.id}/${language}.json`)).json() +}) +``` +- 为什么要应用自己编写语言加载器,而不是提供约定开箱即用? + 主要原因是编写语言加载器很简单,但是如何组织在服务器上的保存,想让应用开发者自行决定。比如,开发者完全可以将语言包保存在数据库中等。 另外考虑安全、兼容性等原因,因此`voerkai18n`就将此交由开发者自行编写。 + + +### 编写语言切换界面 + + 当编写语言切换界面时,对未注册的语言是无法枚举出来的,需要应用自行处理逻辑。例如在Vue应用中 + +```javascript | pure +
+ +
+``` + +还是以本例来说明,上面的Vue应用是无法枚举出来`de`语言的,这就需要应用自己处理,比如: + +```html + +``` +通过编写合适的语言切换界面,您可以在后期随时在线增加语种支持。 + +### 关于语言包补丁 +语言包补丁仅对在`settings.json`配置的语言起作用,而动态增加的语种因为其语言包本身就保存在服务器,因此就不存在补丁的问题。 +语言包补丁会在加载时自动合并到源码中的语言包,并且会自动在本地`localStorage`中缓存。 + diff --git a/docs/src/guide/advanced/runtime.md b/docs/src/guide/advanced/runtime.md new file mode 100644 index 0000000..a0fb5b4 --- /dev/null +++ b/docs/src/guide/advanced/runtime.md @@ -0,0 +1,37 @@ +# 运行时 + +`@voerkai18n/runtime`是`voerkai18n`的运行时依赖,支持两种依赖方式。 + +## 源码依赖 + + 默认情况下,运行`voerkai18n compile`时会在`languages`文件下生成运行时文件`runtime.js`,该文件被`languages/index.js`引入,里面是核心运行时`ES6`源代码(`@voerkai18n/runtime`源码),也就是在您的工程中是直接引入的运行时代码,因此就不需要额外安装`@voerkai18n/runtime`了。 + + 此时,`@voerkai18n/runtime`源码就成为您工程是一部分。 + +## 库依赖 + + 当运行`voerkai18n compile --no-inline-runtime`时,就不会生成运行时文件`runtime.js`,而是采用`import "@voerkai18n/runtime`的方式导入运行时,此时会自动/手动安装`@voerkai18n/runtime`到运行依赖中。 + + +## 如何选择 + +**那么应该选择`源码依赖`还是`库依赖`呢?** + +问题的重点在于,在`monorepo`工程或者`开发库`时,`源码依赖`会导致存在重复的运行时源码。而采用`库依赖`,则不存在此问题。因此: + +- 普通应用采用`源码依赖`方式,运行`voerkai18n compile `来编译语言包。 +- `monorepo`工程或者`开发库`采用`库依赖`,`voerkai18n compile --no-inline-runtime`来编译语言包。 + + + +## 注意 + +- `@voerkai18n/runtime`发布了`commonjs`和`esm`两个经过`babel/rollup`转码后的`ES5`版本。 + +- 每次运行`voerkai18n compile`时均会重新生成`runtime.js`源码文件,为了确保最新的运行时,请及时更新`@voerkai18n/cli` + +- 当升级了`@voerkai18n/runtime`后,需要重新运行`voerkai18n compile`以重新生成`runtime.js`文件。 + + + + diff --git a/docs/src/guide/advanced/textMap.md b/docs/src/guide/advanced/textMap.md new file mode 100644 index 0000000..75f65d2 --- /dev/null +++ b/docs/src/guide/advanced/textMap.md @@ -0,0 +1,45 @@ +# 文本映射 + +虽然`VoerkaI18n`推荐采用`t("中华人民共和国万岁")`形式的符合直觉的翻译形式,而不是采用`t("xxxx.xxx")`这样不符合直觉的形式,但是为什么大部份的国际化方案均采用`t("xxxx.xxx")`形式? + +在我们的方案中,t("中华人民共和国万岁")形式相当于采用原始文本进行查表,语言名形式如下: + +```javascript | pure +// en.js +{ + "中华人民共和国":"the people's Republic of China" +} +// jp.js +{ + "中华人民共和国":"中華人民共和国" +} +``` + +很显然,直接使用文本内容作为`key`,虽然符合直觉,但是会造成大量的冗余信息。因此,`voerkai18n compile`会将之编译成如下: + +```javascript | pure +//idMap.js +{ + "1":"中华人民共和国万岁" +} +// en.js +{ + "1":"Long live the people's Republic of China" +} +// jp.js +{ + "2":"中華人民共和国" +} +``` + +如此,就消除了在`en.js`、`jp.js`文件中的冗余。但是在源代码文件中还存在`t("中华人民共和国万岁")`,整个运行环境中存在两份副本,一份在源代码文件中,一份在`idMap.js`中。 + +为了进一步减少重复内容,因此,我们需要将源代码文件中的`t("中华人民共和国万岁")`更改为`t("1")`,这样就能确保无重复冗余。但是,很显然,我们不可能手动来更改源代码文件,这就需要由`voerkai18n`提供的一个编译区插件来做这一件事了。 + +以`babel-plugin-voerkai18n`插件为例,该插件同时还完成一份任务,就是自动读取`voerkai18n compile`生成的`idMap.js`文件,然后将`t("中华人民共和国万岁")`自动更改为`t("1")`,这样就完全消除了重复冗余信息。 + +所以,在最终形成的代码中,实际上每一个t函数均是`t("1")`、`t("2")`、`t("3")`、`...`、`t("n")`的形式,最终代码还是采用了用`key`来进行转换,只不过这个过程是自动完成的而已。 + +**注意:** + +- 如果没有启用`babel-plugin-voerkai18n`或`vite`等编译区插件,还是可以正常工作,但是会有一份默认语言的冗余信息存在。 \ No newline at end of file diff --git a/docs/src/guide/intro/get-started.md b/docs/src/guide/intro/get-started.md new file mode 100644 index 0000000..ab5a3ef --- /dev/null +++ b/docs/src/guide/intro/get-started.md @@ -0,0 +1,251 @@ +--- +title: 快速入门 +--- + + +# 快速入门 + + +本节以标准的`Nodejs`应用程序为例,简要介绍`VoerkaI18n`国际化框架的基本使用。其他`vue`或`react`应用的使用也基本相同。 + +```shell +myapp + |--package.json + |--index.js +``` + +在本项目的所有支持的源码文件中均可以使用`t`函数对要翻译的文本进行包装,简单而粗暴。 + +```javascript | pure +// index.js +console.log(t("中华人民共和国万岁")) +console.log(t("中华人民共和国成立于{}",1949)) +``` + +`t`翻译函数是从`myapp/languages/index.js`文件导出的翻译函数,但是现在`myapp/languages`还不存在,后续会使用工具自动生成。`voerkai18n`后续会使用正则表达式对提取要翻译的文本。 + +## 第一步:安装命令行工具 + +```shell +> npm install -g @voerkai18n/cli +> yarn global add @voerkai18n/cli +>pnpm add -g @voerkai18/cli +``` + +## 第二步:初始化工程 + +在工程目录中运行`voerkai18n init`命令进行初始化。 + +```javascript | pure +> voerkai18n init +``` + +上述命令会在当前工程目录下创建`languages/settings.json`文件。如果您的源代码在`src`子文件夹中,则会创建在`src/languages/settings.json` + +`settings.json`内容如下: + +```json +{ + "languages": [ + { + "name": "zh", + "title": "zh" + }, + { + "name": "en", + "title": "en" + } + ], + "defaultLanguage": "zh", + "activeLanguage": "zh", + "namespaces": {} +} +``` + +上述命令代表了: + +- 本项目拟支持`中文`和`英文`两种语言。 +- 默认语言是`中文`(即在源代码中直接使用中文) +- 激活语言是`中文` + +**注意:** + +- `voerkai18n init`是可选的,`voerkai18n extract`也可以实现相同的功能。 +- 一般情况下,您可以手工修改`settings.json`,如定义名称空间。 + +## 第三步:提取文本 + +接下来我们使用`voerkai18n extract`命令来自动扫描工程源码文件中的需要的翻译的文本信息。 + +```shell +myapp>voerkai18n extract +``` + +执行`voerkai18n extract`命令后,就会在`myapp/languages`通过生成`translates/default.json`、`settings.json`等相关文件。 + +- **translates/default.json** : 该文件就是需要进行翻译的文本信息。 + +- **settings.json**: 语言环境的基本配置信息,可以进行修改。 + +最后文件结构如下: + +```shell +myapp + |-- languages + |-- settings.json // 语言配置文件 + |-- translates // 此文件夹是所有需要翻译的内容 + |-- default.json // 默认名称空间内容 + |-- package.json + |-- index.js + +``` + +**如果略过第一步中的`voerkai18n init`,也可以使用以下命令来为创建和更新`settinbgs.json`** + +```javascript | pure +myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh +``` + +以上命令代表: + +- 扫描当前文件夹下所有源码文件,默认是`js`、`jsx`、`html`、`vue`文件类型。 +- 计划支持`zh`、`en`、`de`、`jp`四种语言 +- 默认语言是中文。(指在源码文件中我们直接使用中文即可) +- 激活语言是中文(即默认切换到中文) +- `-D`代表显示扫描调试信息 + +## 第四步:翻译文本 + +接下来就可以分别对`language/translates`文件夹下的所有`JSON`文件进行翻译了。每个`JSON`文件大概如下: + +```json +{ + "中华人民共和国万岁":{ + "en":"<在此编写对应的英文翻译内容>", + "de":"<在此编写对应的德文翻译内容>" + "jp":"<在此编写对应的日文翻译内容>", + "$files":["index.js"] // 记录了该信息是从哪几个文件中提取的 + }, + "中华人民共和国成立于{}":{ + "en":"<在此编写对应的英文翻译内容>", + "de":"<在此编写对应的德文翻译内容>" + "jp":"<在此编写对应的日文翻译内容>", + "$files":["index.js"] + } +} +``` + +我们只需要修改该文件翻译对应的语言即可。 + +**重点:如果翻译期间对源文件进行了修改,则只需要重新执行一下`voerkai18n extract`命令,该命令会进行以下操作:** + +- 如果文本内容在源代码中已经删除了,则会自动从翻译清单中删除。 +- 如果文本内容在源代码中已修改了,则会视为新增加的内容。 +- 如果文本内容已经翻译了一部份了,则会保留已翻译的内容。 + +因此,反复执行`voerkai18n extract`命令是安全的,不会导致进行了一半的翻译内容丢失,可以放心执行。 + +大部分国际化解决方案至此就需要交给人工进行翻译了,但是`voerkai18n`除了手动翻译外,通过`voerkai18n translate`命令来实现**调用在线翻译服务**进行自动翻译。 + +```javascript | pure +>voerkai18n translate --provider baidu --appkey <在百度翻译上申请的密钥> --appid <在百度翻译上申请的appid> +``` + + 在项目文件夹下执行上面的语句,将会自动调用百度的在线翻译API进行翻译,以现在的翻译水平而言,您只需要进行少量的微调即可。关于`voerkai18n translate`命令的使用请查阅后续介绍。 + +## 第五步:编译语言包 + +当我们完成`myapp/languages/translates`下的所有`JSON语言文件`的翻译后(如果配置了名称空间后,每一个名称空间会对应生成一个文件,详见后续`名称空间`介绍),接下来需要对翻译后的文件进行编译。 + +```shell +myapp> voerkai18n compile +``` + +`compile`命令根据`myapp/languages/translates/*.json`和`myapp/languages/settings.json`文件编译生成以下文件: + +```javascript | pure + |-- languages + |-- settings.json // 语言配置文件 + |-- idMap.js // 文本信息id映射表 + |-- runtime.js // 运行时源码 + |-- index.js // 包含该应用作用域下的翻译函数等 + |-- zh.js // 语言包 + |-- en.js + |-- jp.js + |-- de.js + |-- translates // 此文件夹包含了所有需要翻译的内容 + |-- default.json + |-- package.json + |-- index.js + +``` + +## 第六步:导入翻译函数 + +第一步中我们在源文件中直接使用了`t`翻译函数包装要翻译的文本信息,该`t`翻译函数就是在编译环节自动生成并声明在`myapp/languages/index.js`中的。 + +```javascript | pure +import { t } from "./languages" +``` + +因此,我们需要在需要进行翻译时导入该函数即可。 + +但是如果源码文件很多,重次重复导入`t`函数也是比较麻烦的,所以我们也提供了一个`babel/vite`等插件来自动导入`t`函数。 + +## 第六步:切换语言 + +当需要切换语言时,可以通过调用`change`方法来切换语言。 + +```javascript | pure +import { i18nScope } from "./languages" + +// 切换到英文 +await i18nScope.change("en") +// VoerkaI18n是一个全局单例,可以直接访问 +await VoerkaI18n.change("en") +``` + +`i18nScope.change`与`VoerkaI18n.change`两者是等价的。 + +一般可能也需要在语言切换后进行界面更新渲染,可以订阅事件来响应语言切换。 + +```javascript | pure +import { i18nScope } from "./languages" + +// 切换到英文 +i18nScope.on((newLanguage)=>{ + ... +}) +// +VoerkaI18n.on((newLanguage)=>{ + ... +}) +``` + +## 第七步:语言包补丁 + +一般情况下,多语言的工程化过程就结束了,`voerkai18n`在多语言实践考虑得更加人性化。有没有经常发现这样的情况,当项目上线后,才发现: +- 翻译有误 +- 客户对某些用语有个人喜好,要求你更改。 +- 临时要增加支持一种语言 + +一般碰到这种情况,只好重新打包构建工程,重新发布,整个过程繁琐而麻烦。 +现在`voerkai18n`针对此问题提供了完美的解决方案,可以通过服务器来为应用打语言包补丁和增加语言支持,而不需要重新打包应用和修改应用。 +方法如下: + +1. 注册一个默认的语言包加载器函数,用来从服务器加载语言包文件 +```javascript | pure +import { i18nScope } from "./languages" + +i18nScope.registerDefaultLoader(async (language,scope)=>{ + return await (await fetch(`/languages/${scope.id}/${language}.json`)).json() +}) +``` + +2. 将语言包补丁文件保存在服务器上指定的位置`/languages/<应用名称>/<语言名称>.json`即可。 +3. 当应用启动后会自动从服务器上加载语言补丁包,从而实现动为语言包打补丁的功能。也可以实现动态增加临时支持一种语言的功能 + +更完整的说明详见[`动态加载语言包`](../advanced/remoteLoad.md)和[`语言包补丁`](../advanced/lngpatch.md)功能介绍。 + + + \ No newline at end of file diff --git a/docs/src/guide/intro/history.md b/docs/src/guide/intro/history.md new file mode 100644 index 0000000..78fc32c --- /dev/null +++ b/docs/src/guide/intro/history.md @@ -0,0 +1,9 @@ +--- +title: 更新历史 +--- +# 更新历史 + +## 2022/8/5 + +- 增加语言包补丁功能,可以在应用上线后动态更新修复翻译错误 +- 增加动态加载语言包机制,可以在应用上线后动态添加语言支持 \ No newline at end of file diff --git a/docs/src/guide/intro/install.md b/docs/src/guide/intro/install.md new file mode 100644 index 0000000..8c1de30 --- /dev/null +++ b/docs/src/guide/intro/install.md @@ -0,0 +1,47 @@ +--- +title: 安装 +--- + +# 安装 + +`VoerkaI18n`国际化框架是一个开源多包工程,主要由以下几个包组成: + +## **@voerkai18/cli** + +包含文本提取/编译等命令行工具,一般应该安装到全局。 + +```javascript | pure +npm install --g @voerkai18n/cli +yarn global add @voerkai18n/cli +pnpm add -g @voerkai18n/cli +``` + +## **@voerkai18/runtime** + +**可选的**,运行时,`@voerkai18/cli`的依赖。大部分情况下不需要手动安装,一般仅在开发库项目时采用独立的运行时依赖。 + +```javascript | pure +npm install --save @voerkai18n/runtime +yarn add @voerkai18n/runtime +pnpm add @voerkai18n/runtime +``` + +## **@voerkai18/formatters** + +**可选的**,一些额外的格式化器,可以按需进行安装到`dependencies`中,用来扩展翻译时对插值变量的额外处理。 + +## **@voerkai18/babel** + +可选的`babel`插件,用来实现自动导入翻译函数和翻译文本映射自动替换。 + +## **@voerkai18/vue** + +可选的`vue`插件,用来为Vue应用提供语言动态切换功能。 + +## **@voerkai18/react** + +可选的,用来为React应用提供语言动态切换功能。 + +## **@voerkai18/vite** + +可选的`vite`插件,用来为`vite`应用提供自动导入翻译函数和翻译文本映射自动替换。 diff --git a/docs/src/guide/intro/question.md b/docs/src/guide/intro/question.md new file mode 100644 index 0000000..4737654 --- /dev/null +++ b/docs/src/guide/intro/question.md @@ -0,0 +1,3 @@ +--- +title: 常见问题 +--- \ No newline at end of file diff --git a/docs/src/guide/intro/readme.md b/docs/src/guide/intro/readme.md new file mode 100644 index 0000000..41d2da7 --- /dev/null +++ b/docs/src/guide/intro/readme.md @@ -0,0 +1,38 @@ +# 概述 + +基于`javascript`的国际化方案很多,比较有名的有`fbt`、`i18next`、`react-i18next`、`vue-i18n`、`react-intl`等等,每一种解决方案均有大量的用户。为什么还要再造一个轮子?好吧,再造轮子的理由不外乎不满足于现有方案,总想着现有方案的种种不足之处,然后就撸起袖子想造一个轮子,也不想想自己什么水平。 + +那么到底是对现有解决方案有什么不满?最主要有三点: + +- 大部份均为要翻译的文本信息指定一个`key`,然后在源码文件中使用形如`$t("message.login")`之类的方式,然后在翻译时将之转换成最终的文本信息。此方式最大的问题是,在源码中必须人为地指定每一个`key`,在中文语境中,想为每一句中文均配套想一句符合语义的`英文key`是比较麻烦的,也很不直观不符合直觉。我希望在源文件中就直接使用中文,如`t("中华人民共和国万岁")`,然后国际化框架应该能自动处理后续的一系列麻烦。 + +- 要能够比较友好地支持多库多包`monorepo`场景下的国际化协作,当主程序切换语言时,其他包或库也可以自动切换,并且在开发上每个包或库均可以独立地进行开发,集成到主程序时能无缝集成。这点在现有方案上没有找到比较理想的解决方案。 + +- 大部份国际化框架均将中文视为二等公民,大部份情况下您应该采用英文作为第一语言,虽然这不是太大的问题,但是既然要再造一个轮子,为什么不将中文提升到一等公民呢。 + + + + 基于此就开始造出`VoerkaI18n`这个**全新的国际化多语言解决方案**,主要特性包括: + + + +- 全面工程化解决方案,提供初始化、提取文本、自动翻译、编译等工具链支持。 + +- 符合直觉,不需要手动定义文本`Key`映射。 + +- 强大的插值变量`格式化器`机制,可以扩展出强大的多语言特性。 + +- 支持`babel`插件自动导入`t`翻译函数。 + +- 支持`nodejs`、浏览器(`vue`/`react`)前端环境。 + +- 采用`工具链`与`运行时`分开设计,发布时只需要集成很小的运行时。 + +- 高度可扩展的`复数`、`货币`、`数字`等常用的多语言处理机制。 + +- 翻译过程内,提取文本可以自动进行同步,并保留已翻译的内容。 + +- 支持远程加载语言包,并且可以在线打语言补丁包 + +- 支持调用在线自动翻译对提取文本进行翻译。 + diff --git a/docs/src/guide/intro/support.md b/docs/src/guide/intro/support.md new file mode 100644 index 0000000..c097721 --- /dev/null +++ b/docs/src/guide/intro/support.md @@ -0,0 +1,4 @@ +# 获取支持 + +- 通过Gitgee或Github提交[issues](https://github.com/zhangfisher/voerka-i18n/issues) +- 国内用户可以加[QQ群](https://qm.qq.com/cgi-bin/qm/qr?k=jKyZR9KupT9Ith5ZsulB-i04OaJDkCwe&jump_from=webapi). diff --git a/docs/src/guide/intro/versions.md b/docs/src/guide/intro/versions.md new file mode 100644 index 0000000..d361f7d --- /dev/null +++ b/docs/src/guide/intro/versions.md @@ -0,0 +1,11 @@ +# 版本信息 +| 包| 版本号| 最后更新|说明| +| --- | :---:| --- |---| +|**@voerkai18n/utils**|1.0.12|08/05|公共工具库 +|**@voerkai18n/runtime**|1.0.27|08/05|核心运行时 +|**@voerkai18n/formatters**|1.0.6|04/15|格式化器,提供对要翻译文本的转换功能 +|**@voerkai18n/react**|1.0.4|04/16|React支持,提供语言切换等功能 +|**@voerkai18n/cli**|1.0.32|08/05|命令行工具,用来初始化/提取/编译/自动翻译等工具链 +|**@voerkai18n/babel**|1.0.23|08/05|Babel插件,实现自动导入t函数和自动文本映射 +|**@voerkai18n/vite**|1.0.12|08/05|Vite插件,提供自动插入翻译函数和文本映射等功能 +|**@voerkai18n/vue**|1.0.5|04/15|Vue3插件,提供自动插件翻译函数和语言切换功能 \ No newline at end of file diff --git a/docs/src/guide/tools/babel.md b/docs/src/guide/tools/babel.md new file mode 100644 index 0000000..b4fb1b2 --- /dev/null +++ b/docs/src/guide/tools/babel.md @@ -0,0 +1,73 @@ +# Babel插件 + +全局安装`@voerkai18n/babel`插件用来进行自动导入`t`函数和自动文本映射。 + +## 安装 + +```javascript | pure +> npm install -g @voerkai18n/babel +> yarn global add @voerkai18n/babel +> pnpm add -g @voerkai18n/babel +``` + +## 启用插件 + +使用方法如下: + +- 在`babel.config.js`中配置插件 + +```javascript | pure +const i18nPlugin = require("@voerkai18n/babel") +module.expors = { + plugins: [ + [ + i18nPlugin, + { + // 可选,指定语言文件存放的目录,即保存编译后的语言文件的文件夹 + // 可以指定相对路径,也可以指定绝对路径 + // location:"", + autoImport:"#/languages" + } + ] + ] +} +``` + +这样,当在进行`babel`转码时,就会自动在`js`源码文件中导入`t`翻译函数。 + +## 插件参数 + +插件支持以下参数: + +- **location** + + 配置`langauges`文件夹位置,默认会使用当前文件夹下的`languages`文件。 + + 因此,如果你的`babel.config.js`在项目根文件夹,而`languages`文件夹位于`src/languages`,则可以将`location="src/languages"`,这样插件会自动从该文件夹读取需要的数据。 + +- **autoImport** + + 用来配置导入的路径。比如 `autoImport="#/languages" `,则当在babel转码时,如果插件检测到t函数的存在并没有导入,就会自动在该源码中自动导入`import { t } from "#/languages"` + + 配置`autoImport`时需要注意的是,为了提供一致的导入路径,视所使用的打包工具或转码插件,如`webpack`、`rollup`等。比如使用`babel-plugin-module-resolver` + + ```javascript | pure + module.expors = { + plugins: [ + [ + "module-resolver", + { + root:"./", + alias:{ + "languages":"./src/languages" + } + } + ] + ] + } + ``` + + 这样配置`autoImport="languages"`,则自动导入`import { t } from "languages"`。 + + 如`webpack`、`rollup`等打包工具也有类似的插件可以实现别名等转换,其目的就是让`@voerkai18n/babel`插件能自动导入固定路径,而不是各种复杂的相对路径。 + \ No newline at end of file diff --git a/docs/src/guide/tools/cli.md b/docs/src/guide/tools/cli.md new file mode 100644 index 0000000..0cc609b --- /dev/null +++ b/docs/src/guide/tools/cli.md @@ -0,0 +1,251 @@ +# 命令行工具 + +`@voerkai18n/cli`命令行工具用来实现工程初始化、扫描提取文本、自动翻译和编译语言等功能。 + +::: info +建议将`@voerkai18n/cli`命令行工具安装在全局 +::: + +## 安装 + +全局安装`@voerkai18n/cli`工具。 + +```javascript | pure +> npm install -g @voerkai18n/cli +> yarn global add @voerkai18n/cli +> pnpm add -g @voerkai18n/cli +``` + +然后就可以执行: + +```javascript | pure +> voerkai18n init +> voerkai18n extract +> voerkai18n compile +``` + +如果没有全局安装,则需要: + +```javascript | pure +> yarn voerkai18n init +> yarn voerkai18n extract +> yarn voerkai18n compile +--- +> pnpm voerkai18n init +> pnpm voerkai18n extract +> pnpm voerkai18n compile +``` + +## 初始化 - init + +用于在指定项目创建`voerkai18n`国际化配置文件。 + +```shell +> voerkai18n init --help +初始化项目国际化配置 +Arguments: + location 工程项目所在目录 +Options: + -D, --debug 输出调试信息 + -r, --reset 重新生成当前项目的语言配置 + -lngs, --languages 支持的语言列表 (default: ["zh","en"]) + -d, --defaultLanguage 默认语言 + -a, --activeLanguage 激活语言 + -h, --help display help for command +``` + +**使用方法如下:** + +首先需要在工程文件下运行`voerkai18n init`命令对当前工程进行初始化。 + +```javascript | pure +//- `lngs`参数用来指定拟支持的语言名称列表 +> voerkai18n init . -lngs zh en jp de -d zh +``` + +运行`voerkai18n init`命令后,会在当前工程中创建相应配置文件。 + +```javascript | pure +myapp + |-- languages + |-- settings.json // 语言配置文件 + |-- package.json + |-- index.js +``` + +`settings.json`文件很简单,主要是用来配置要支持的语言等基本信息。 + +```javascript | pure +module.exports = { + // 拟支持的语言列表 + "languages": [ + { + "name": "zh", + "title": "中文" + }, + { + "name": "en", + "title": "英文" + } + ], + // 默认语言,即准备在源码中写的语言,一般我们可以直接使用中文 + "defaultLanguage": "zh", + // 激活语言,即默认要启用的语言,一般等于defaultLanguage + "activeLanguage": "zh", + // 翻译名称空间定义,详见后续介绍。 + "namespaces": {} +} +``` + +**说明:** + +- 您也可以手动自行创建`languages/settings.json`文件。这样就不需运行`voerkai18n init`命令了。 + +- 如果你的源码放在`src`文件夹,则`init`命令会自动在在`src`文件夹下创建`languages`文件夹。 + +- `voerkai18n init`是可选的,直接使用`extract`时也会自动创建相应的文件。 + +- `-m`参数用来指定生成的`settings.json`的模块类型: + - 当`-m=auto`时,会自动读取前工程`package.json`中的`type`字段 + - 当`-m=esm`时,会生成`ESM`模块类型的`settings.json`。 + - 当`-m=cjs`时,会生成`commonjs`模块类型的`settings.json`。 + +- `location`参数是可选的,如果没有指定则采用当前目录。 + + 如果你想将`languages`安装在`src/languages`下,则可以指定`voerkai18n init ./src` + +## 提取文本 - extract + +扫描提取当前项目中的所有源码,提取出所有需要翻译的文本内容并保存在到`<工程源码目录>/languages/translates/*.json`。 + +```shell +> voerkai18n extract --help +扫描并提取所有待翻译的字符串到文件夹中 + +Arguments: + location 工程项目所在目录 (default: "./") + +Options: + -D, --debug 输出调试信息 + -lngs, --languages 支持的语言 + -d, --defaultLanguage 默认语言 + -a, --activeLanguage 激活语言 + -ns, --namespaces 翻译名称空间 + -e, --exclude 排除要扫描的文件夹,多个用逗号分隔 + -u, --updateMode 本次提取内容与已存在内容的数据合并策略,默认取值sync=同步,overwrite=覆盖,merge=合并 + -f, --filetypes 要扫描的文件类型 + -h, --help display help for command +``` + +**说明:** + +- 启用`-d`参数时会输出提取过程,显示从哪些文件提取了几条信息。 +- 如果已手动创建或通过`init`命令创建了`languages/settings.json`文件,则可以不指定`-ns`,`-lngs`,`-d`,`-a`参数。`extract`会优先使用`languages/settings.json`文件中的参数来进行提取。 +- `-u`参数用来指定如何将提取的文本与现存的文件进行合并。因为在国际化流程中,我们经常面临源代码变更时需要更新翻译的问题。支持三种合并策略。 + - **sync**:同步(默认值),两者自动合并,并且会删除在源码文件中不存在的文本。如果某个翻译已经翻译了一半也会保留。此值适用于大部情况,推荐。 + - **overwrite**:覆盖现存的翻译内容。这会导致已经进行了一半的翻译数据丢失,**慎用**。 + - **merge**:合并,与sync的差别在于不会删除源码中已不存在的文本。 +- `-e`参数用来排除扫描的文件夹,多个用逗号分隔。内部采用`gulp.src`来进行文件提取,请参数。如 `-e !libs,core/**/*`。默认会自动排除`node_modules`文件夹 +- `-f`参数用来指定要扫描的文件类型,默认`js,jsx,ts,tsx,vue,html` +- `extract`是基于正则表达式方式进行匹配的,而不是像`i18n-next`采用基于`AST`解析。 + +>**重点:** +> +>默认情况下,`voerkai18n extract`可以安全地反复多次执行,不会导致已经翻译一半的内容丢失。 +> +>如果想添加新的语言支持,也`voerkai18n extract`也可以如预期的正常工作。 + +## 自动翻译 - translate + +在工程文件夹下执行`voerkai18n translate`命令,该命令会读取`languages/settings.json`配置文件,并调用在线翻译服务(如百度在线翻译)对提取的文本(`languages/translates/*.json`)进行自动翻译。 + +```shell +Usage: voerkai18n translate [options] [location] + +调用在线翻译服务商的API翻译译指定项目的语言包,如使用百度云翻译服务 + +Arguments: + location 工程项目所在目录 + +Options: + -p, --provider 在线翻译服务提供者名称或翻译脚本文件 (default: "baidu") + -m, --max-package-size 将多个文本合并提交的最大包字节数 (default: 3000) + --appkey [key] API密钥 + --appid [id] API ID + --no-backup 备份原始文件 + --mode 翻译模式,取值auto=仅翻译未翻译的,full=全部翻译 + -q, --qps 翻译速度限制,即每秒可调用的API次数 (default: 1) + -h, --help 显示帮助 +``` + +- 内置支持调用百度的在线翻译服务,您需要百度的网站上(http://api.fanyi.baidu.com/)申请开通服务,开通后可以得到`appid`和`appkey`(密钥)。 + +- `--provider`用来指定在线翻译服务提供者,内置支持的是百度在线翻译。也可以传入一个js脚本,如下: + + ```javascript | pure + // youdao.js + module.exports = async function(options){ + let { appkey,appid } = options + return { + translate:async (texts,from,to){ + // texts是一个Array + // from,to代表要从哪一种语言翻译到何种语言 + ..... + // 在此对texts内容调用在线翻译API + // 翻译结果应该返回与texts对应的数组 + // 如果出错则应该throw new Error() + return [...] + } + } + } + ``` + +- `qps`用来指定调用在线翻译API的速度,默认是1,代表每秒调用一次;此参数的引入是考虑到有些翻译平台的免费API有QPS限制。比如百度在线翻译免费版本限制`QPS`就是1,即每秒只能调用一次。如果您购买了服务,则可以将`QPS`调高。 + +- 默认情况下,每次运行时均会备份原始的翻译文件至`languages/translates/backup`,`--no-backup`可以禁止备份。 + +- 默认情况下,`voerkai18n translate`会在每次运行时跳过已经翻译过的内容,这样可以保留翻译成果。此特性在您对自动翻译的内容进行修改后,再多次运行`voerkai18n translate`命令时均能保留翻译内容,不会导致您修改调整过的内容丢失。`--mode full`参数可以完全覆盖翻译,请慎用。 + +- 为了提高在线翻译的速度,`voerkai18n translate`并不是一条文本调用一次API,而是将多条文本合并起来进行调用,但是单次调用也是有数据包大小的限制的,`--max-package-size`参数用来指定数据包的最大值。比如百度建议,为保证翻译质量,请将单次请求长度控制在 6000 bytes以内(汉字约为输入参数 2000 个)。 + +- 需要注意的是,自动翻译虽然准确性还不错,真实场景还是需要进行手工调整的,特别是自动翻译一般不能识别插值变量。 + +## 编译 - compile + +编译当前工程的语言包,编译结果输出在.`/langauges`文件夹。 + +```shell +Usage: voerkai18n compile [options] [location] + +编译指定项目的语言包 + +Arguments: + location 工程项目所在目录 (default: "./") + +Options: + -D, --debug 输出调试信息 + -m, --moduleType [types] 输出模块类型,取值auto,esm,cjs (default: "esm") + --no-inline-runtime 不嵌入运行时源码 + -h, --help display help for command +``` + +`voerkai18n compile`执行后会在`langauges`文件夹下输出: + +```javascript | pure +myapp + |--- langauges + |-- index.js // 当前作用域的源码 + |-- idMap.js // 翻译文本与id的映射文件 + |-- formatters.js // 自定义格式化器 + |-- zh.js // 中文语言包 + |-- en.js // 英文语言包 + |-- xx.js // 其他语言包 + |-- ... +``` + +**说明:** + +- 在当前工程目录下,一般不需要指定参数就可以反复多次进行编译。 +- 您每次修改了源码并`extract`后,均应该再次运行`compile`命令。 +- 如果您修改了`formatters.js`,执行`compile`命令不会重新生成和修改该文件。 +- `--no-inline-runtime `参数用来指示如何引用运行时。默认会将运行时代码生成保存在`languages/runtime.js`,应用以源码形式引用。当启用`--no-inline-runtime `参数时会采用`require("@voerkai18n/runtime")`的方式。 \ No newline at end of file diff --git a/docs/src/guide/tools/vite.md b/docs/src/guide/tools/vite.md new file mode 100644 index 0000000..3005b68 --- /dev/null +++ b/docs/src/guide/tools/vite.md @@ -0,0 +1,97 @@ +# Vite插件 + +`@voerkai18n/babel`插件在`vite`应用中不能正常使用,需要使用`@voerkai18n/vite`插件来完成类似的功能,包括自动文本映射和自动导入`t`函数。 + +## 安装 + +`@voerkai18n/vite`只需要作为开发依赖安装即可。 + +```javascript | pure +npm install --save-dev @voerkai18n/vite +yarn add -D @voerkai18n/vite +pnpm add -D @voerkai18n/vite +``` + +## 启用插件 + +接下来在`vite.config.js`中配置启用`@voerkai18n/vite`插件。 + +```javascript | pure +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import Inspect from 'vite-plugin-inspect'// 可选的 +import Voerkai18nPlugin from "@voerkai18n/vite" +export default defineConfig({ + plugins: [ + Inspect(), // 可选的 + Voerkai18nPlugin({debug:true}), + vue() + ] +}) + +``` + +- ` vite-plugin-inspect`是开发`vite`插件时的调试插件,启用后就可以通过`localhost:3000/__inspect/ `查看Vue源码文件经过插件处理前后的内容,一般是Vite插件开发者使用。上例中安装后,就可以查看`Voerkai18nPlugin`对`Vue`文件干了什么事,可以加深理解,**正常使用不需要安装**。 + +## 插件功能 + +`@voerkai18n/vite`插件配置启用后,`vite`在进行`dev`或`build`时,就会在``自动注入`import { t } from "languages" `,同时会扫描源代码文件(包括`vue`,`js`等),根据`idMap.js`文件里面的文本映射表,将`t('"xxxx")`转换成`t("")`的形式。 + +不同于`@voerkai18n/babel`插件,`@voerkai18n/vite`插件不需要配置`location`和`autoImport`参数,能正确地处理导入`languages`路径。 + +## 插件参数 + +`vite`插件支持以下参数: + +```javascript | pure +import Voerkai18nPlugin from "@voerkai18n/vite" +export default defineConfig({ + plugins: [ + Inspect(), // 可选的 + Voerkai18nPlugin({ + location: "./", // 可选的,指定当前工程目录 + autoImport: true, // 是否自动导入t函数 + debug:false, // 是否输出调试信息,当=true时,在控制台输出转换匹配的文件清单 + patterns:[ + "!(?/src/languages`文件夹下。 + +- `autoImport`:可选的,默认`true`,用来配置是否自动导入`t`函数。当vue文件没有指定导入时才会自动导入,并且根据当前vue文件的路径处理好导入位置。 + +- `debug`:可选的,开启后会在控制台输出一些调试信息,对一般用户没有用。 + +- `patterns`:可选的,一些正则表达式匹配规则,用来过滤匹配哪一些文件需要进行扫描和处理。默认的规则: + + ```javascript | pure + const patterns={ + "!(? +import {reactive } from 'vue' +export default { + inject: ['i18n'], // 注入i18n实例,该实例由@voerkai18n/vue插件提供 + .... +} + +``` + +声明`inject: ['i18n']`后在当前组件实例中就可以访问`this.i18n`,该实例是一个经过`reactive`封闭的响应式对象,其内容是: + +```javascript | pure +this.i18n = { + activeLanguage, // 当前激活语言,可以通过直接赋值来切换语言 + defaultLanguage, // 默认语言名称 + languages // 支持的语言列表=[{name,title},...] +} +``` + +## 应用示例 + +注入`i18n`实例后就可以在此基础上实现`激活语言`、`默认语言`、`切换语言`等功能。 + +```vue + +