增加动态增加语种和语言包补丁功能
This commit is contained in:
parent
4f8daebf0c
commit
8fbb3f52bb
@ -9,7 +9,7 @@ export default defineUserConfig({
|
|||||||
locales : {
|
locales : {
|
||||||
"/" : {
|
"/" : {
|
||||||
lang : "zh-CN",
|
lang : "zh-CN",
|
||||||
title : "中文"
|
title : "VoerkaI18n"
|
||||||
},
|
},
|
||||||
"/en/" : {
|
"/en/" : {
|
||||||
lang : "en-US",
|
lang : "en-US",
|
||||||
@ -22,6 +22,7 @@ export default defineUserConfig({
|
|||||||
name : "wxzhang",
|
name : "wxzhang",
|
||||||
url : "https://gitee.com/zhangfisher/voerka-i18n",
|
url : "https://gitee.com/zhangfisher/voerka-i18n",
|
||||||
},
|
},
|
||||||
|
iconAssets : "iconfont",
|
||||||
iconPrefix : "iconfont icon-",
|
iconPrefix : "iconfont icon-",
|
||||||
logo : "/logo.svg",
|
logo : "/logo.svg",
|
||||||
home : "/zh/home",
|
home : "/zh/home",
|
||||||
|
BIN
docs/.vuepress/public/images/VoerkaI18n群二维码.png
Normal file
BIN
docs/.vuepress/public/images/VoerkaI18n群二维码.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -8,6 +8,9 @@ export const zh = {
|
|||||||
"",
|
"",
|
||||||
"install.md",
|
"install.md",
|
||||||
"get-started.md",
|
"get-started.md",
|
||||||
|
"versions.md",
|
||||||
|
"support.md",
|
||||||
|
"history.md"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,7 +40,10 @@ export const zh = {
|
|||||||
"customformatter",
|
"customformatter",
|
||||||
"langpack",
|
"langpack",
|
||||||
"autotranslate",
|
"autotranslate",
|
||||||
"framework"
|
"framework",
|
||||||
|
"remoteLoad",
|
||||||
|
"lngpatch",
|
||||||
|
"langedit"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -6,13 +6,12 @@ heroImage: /logo.svg
|
|||||||
heroText: VoerkaI18n
|
heroText: VoerkaI18n
|
||||||
tagline: 适用于Nodejs/Vue/React的国际化解决方案
|
tagline: 适用于Nodejs/Vue/React的国际化解决方案
|
||||||
actions:
|
actions:
|
||||||
|
- text: 安装
|
||||||
|
link: /zh/guide/intro/install
|
||||||
|
type: primary
|
||||||
- text: 快速入门
|
- text: 快速入门
|
||||||
link: /zh/guide/intro/get-started
|
link: /zh/guide/intro/get-started
|
||||||
|
|
||||||
- text: 源码
|
|
||||||
link: https://gitee.com/zhangfisher/voerka-i18n
|
|
||||||
type: secondary
|
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: 工程化支持
|
- title: 工程化支持
|
||||||
icon: markdown
|
icon: markdown
|
||||||
@ -20,7 +19,7 @@ features:
|
|||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 集成自动翻译
|
- title: 集成自动翻译
|
||||||
icon: slides
|
icon: tab
|
||||||
details: 调用在线翻译服务API支持对提取的文本进行自动翻译,大幅度提高工程效率
|
details: 调用在线翻译服务API支持对提取的文本进行自动翻译,大幅度提高工程效率
|
||||||
link:
|
link:
|
||||||
|
|
||||||
@ -44,14 +43,35 @@ features:
|
|||||||
details: 支持monorepo工程下多库进行语言切换的联动机制
|
details: 支持monorepo工程下多库进行语言切换的联动机制
|
||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 自动扩展工具
|
- title: 工具链
|
||||||
icon: palette
|
icon: palette
|
||||||
details: 提供Vue/React/Babel等扩展插件,简化各种应用下
|
details: 提供Vue/React/Babel等扩展插件,简化各种应用开发
|
||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 扩展特性
|
- title: 插值变量
|
||||||
icon: contrast
|
icon: contrast
|
||||||
details: 强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言机制
|
details: 强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言特性
|
||||||
|
link:
|
||||||
|
|
||||||
|
|
||||||
|
- title: 语言补丁
|
||||||
|
icon: palette
|
||||||
|
details: 在应用上线后发现错误时可以在线修复
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 动态增加语种
|
||||||
|
icon: blog
|
||||||
|
details: 可以在应用上线后动态增加语种支持
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 扩展在线编辑
|
||||||
|
icon: info
|
||||||
|
details: 很容易扩展支持在线语言包编辑
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 复数支持
|
||||||
|
icon: contrast
|
||||||
|
details: 灵活而强大的复数机制
|
||||||
link:
|
link:
|
||||||
|
|
||||||
|
|
||||||
|
16
docs/zh/guide/advanced/langedit.md
Normal file
16
docs/zh/guide/advanced/langedit.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 在线编辑语言包
|
||||||
|
|
||||||
|
利用**动态加载语言包**的机制,开发者可以非常容易就开发出`让用户自行编辑界面语言`的功能。
|
||||||
|
|
||||||
|
请详细阅读[`远程加载语言包`](./remoteLoad)的实现过程,其基本思路如下:
|
||||||
|
- 后端采用数据库来保存语言包,每一个语言包可以用一个表或多个表存储。
|
||||||
|
- 编写对应的编辑语言包的Web API,实现通过API修改语言包功能
|
||||||
|
- 前端代码重写语言包加载器函数,将原来的读取静态语言包的方式,修改为采用API读取
|
||||||
|
- 前端增加编辑语言包的界面
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
6
docs/zh/guide/advanced/lngpatch.md
Normal file
6
docs/zh/guide/advanced/lngpatch.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 语言包补丁
|
||||||
|
|
||||||
|
在实际应用中,我们经常会在应用上线后,发现应用中的语言翻译错误,此时就可以利用`voerkai18n`的语言包补丁特性来解决此问题。
|
||||||
|
利用`voerkai18n`的语言包补丁特性,您就可以随时修复翻译错误,而不需要重新打包应用。
|
||||||
|
|
||||||
|
启用语言包补丁功能需要注册默认的语言包加载器,使用方法详见`动态加载语言包`介绍。
|
246
docs/zh/guide/advanced/remoteLoad.md
Normal file
246
docs/zh/guide/advanced/remoteLoad.md
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
# 远程加载语言包
|
||||||
|
|
||||||
|
## 前言
|
||||||
|
`voerkai18n`默认将要翻译的文本内容经编译后保存在当`languages`文件夹下,当打包应用时会与工程一起进行打包进工程源码中。这会带来以下问题:
|
||||||
|
- 翻译语言包是源码工程的一部分,当要翻译的语种较多时,会增加源码包大小。
|
||||||
|
- 如果产品上线后发现翻译问题,则需要重新进行整个工程的打包
|
||||||
|
- 上线后要增加一种语言,同样需要再次进行走一次打包流程
|
||||||
|
|
||||||
|
`voerkai18n`针对这些问题,支持了远程加载语言包的功能,可以支持线上`动态增加支持的语言`,`在线翻译补丁`等特性。
|
||||||
|
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 准备
|
||||||
|
|
||||||
|
为说明如何从远程加载语言包,我们将假设以下的应用:
|
||||||
|
应用`chat`,依赖于`user`、`manager`、`log`等三个库,均使用了`voerkiai18n`作为多语言解决方案
|
||||||
|
当执行完`voerkai18n compile`后,项目结构大概如下:
|
||||||
|
```javascript
|
||||||
|
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
|
||||||
|
// ....
|
||||||
|
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
|
||||||
|
|
||||||
|
// 从当前工程导入`scope`实例
|
||||||
|
import { i18nScope } from "./languages"
|
||||||
|
|
||||||
|
// 注册默认的语言加载器
|
||||||
|
i18nScope.registerDefaultLoader(async (language,scope)=>{
|
||||||
|
// language: 要切换到此语言
|
||||||
|
// scope: 语言作用域实例
|
||||||
|
// 在此向服务器发起请求,请返回翻译后的其他语言文本
|
||||||
|
return {.....}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:编写语言包加载器
|
||||||
|
|
||||||
|
然后,我们就可以在此向服务器发起异步请求来读取语言包文件。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
// 从当前工程导入`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
|
||||||
|
webroot
|
||||||
|
|-- languages
|
||||||
|
<chat>
|
||||||
|
|-- de.json
|
||||||
|
<user>
|
||||||
|
|-- de.json
|
||||||
|
<manager>
|
||||||
|
|-- de.json
|
||||||
|
<log>
|
||||||
|
|-- de.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`VoerkaI18n`将编写**如何语言加载器**和**如何在服务器上组织语言包**交由开发者自行决定,您完全可以根据自己的喜好来决定如何加载。
|
||||||
|
|
||||||
|
|
||||||
|
### 第四步:生成语言包文件
|
||||||
|
|
||||||
|
在本例中,我们要增加`de`语言,这就需要在服务器上生成一个对应的`de`语言包文件。
|
||||||
|
方法很简单,打开`languages/cn.js`文件,该文件大概如下:
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
"1": "支持的语言",
|
||||||
|
"2": "默认语言",
|
||||||
|
"3": "激活语言",
|
||||||
|
"4": "名称空间s",
|
||||||
|
....
|
||||||
|
}
|
||||||
|
```
|
||||||
|
复制一份修改和更名为`de.json`,内容大概概如下:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
webroot
|
||||||
|
|-- languages
|
||||||
|
<chat>
|
||||||
|
|-- cn.json
|
||||||
|
|
||||||
|
```
|
||||||
|
按上例说明的方式,在服务器上编辑一个`cn.json`文件,保存到`languages/char/cn.json`,里面内容只需要包括出错的内容即可。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"4": "名称空间"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
然后,当应用切换到指定`cn`语言时,就会下载该语言包合并到源码中的语言包,从而实现为语言包打补丁的功能,修复翻译错误。此功能简单而实用,强烈推荐。
|
||||||
|
|
||||||
|
### 小结
|
||||||
|
|
||||||
|
- 当注册了一个默认的语言加载器后,当切换到未配置过的语言时,会调用默认的文本加载器来从服务器加载语言文本。
|
||||||
|
- 对于已配置的语言,会在注册时从服务器加载进行合并,从而实现为语言包打补丁的功能。
|
||||||
|
- 您需要自己在服务器上组织存放配套的语言包文件,然后编写通过`fetch/axios`等从服务器加载
|
||||||
|
|
||||||
|
|
||||||
|
## 指南
|
||||||
|
|
||||||
|
### 语言包加载器
|
||||||
|
|
||||||
|
语言加载器是一个普通`异步函数`或者`返回Promise`的函数,可以用来从远程加载语言包文件。
|
||||||
|
|
||||||
|
语言加载器时会传入两个参数:
|
||||||
|
| 参数 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| language | 要切换的此语言|
|
||||||
|
| scope |语言作用域实例,其中`scope.id`值默认等于`package.json`中的`name`字段。详见[参考](../../reference/i18nscope)。 |
|
||||||
|
|
||||||
|
- 典型的语言加载器非常简单,如下:
|
||||||
|
```javascript
|
||||||
|
import { i18nScope } from "./languages"
|
||||||
|
i18nScope.registerDefaultLoader(async (language,scope)=>{
|
||||||
|
return await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- 为什么要应用自己编写语言加载器,而不是提供约定开箱即用?
|
||||||
|
主要原因是编写语言加载器很简单,但是如何组织在服务器上的保存,想让应用开发者自行决定。比如,开发者完全可以将语言包保存在数据库中等。 另外考虑安全、兼容性等原因,因此`voerkai18n`就将此交由开发者自行编写。
|
||||||
|
|
||||||
|
|
||||||
|
### 编写语言切换界面
|
||||||
|
|
||||||
|
当编写语言切换界面时,对未注册的语言是无法枚举出来的,需要应用自行处理逻辑。例如在Vue应用中
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
@click="i18n.activeLanguage=lng.name"
|
||||||
|
v-for="lng of i18n.langauges">
|
||||||
|
{{ lng.title }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
还是以本例来说明,上面的Vue应用是无法枚举出来`de`语言的,这就需要应用自己处理,比如:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
@click="i18n.activeLanguage=lng.name"
|
||||||
|
v-for="lng of i18n.langauges">
|
||||||
|
{{ lng.title }}
|
||||||
|
</button>
|
||||||
|
<!-- 预期要支持的语言 -->
|
||||||
|
<button @click="i18n.activeLanguage=lng.name"
|
||||||
|
v-for="lng of ['de','jp',.......]">
|
||||||
|
{{ lng }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
通过编写合适的语言切换界面,您可以在后期随时在线增加语种支持。
|
||||||
|
|
||||||
|
### 关于语言包补丁
|
||||||
|
语言包补丁仅对在`settings.json`配置的语言起作用,而动态增加的语种因为其语言包本身就保存在服务器,因此就不存在补丁的问题。
|
||||||
|
语言包补丁会在加载时自动合并到源码中的语言包,并且会自动在本地`localStorage`中缓存。
|
||||||
|
|
@ -221,3 +221,31 @@ VoerkaI18n.on((newLanguage)=>{
|
|||||||
...
|
...
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 第七步:语言包补丁
|
||||||
|
|
||||||
|
一般情况下,多语言的工程化过程就结束了,`voerkai18n`在多语言实践考虑得更加人性化。有没有经常发现这样的情况,当项目上线后,才发现:
|
||||||
|
- 翻译有误
|
||||||
|
- 客户对某些用语有个人喜好,要求你更改。
|
||||||
|
- 临时要增加支持一种语言
|
||||||
|
|
||||||
|
一般碰到这种情况,只好重新打包构建工程,重新发布,整个过程繁琐而麻烦。
|
||||||
|
现在`voerkai18n`针对此问题提供了完美的解决方案,可以通过服务器来为应用打语言包补丁和增加语言支持,而不需要重新打包应用和修改应用。
|
||||||
|
方法如下:
|
||||||
|
|
||||||
|
1. 注册一个默认的语言包加载器函数,用来从服务器加载语言包文件
|
||||||
|
```javascript
|
||||||
|
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)功能介绍。
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: 版本历史
|
title: 更新历史
|
||||||
---
|
---
|
||||||
|
# 更新历史
|
||||||
|
|
||||||
|
## 2022/8/5
|
||||||
|
|
||||||
|
- 增加语言包补丁功能,可以在应用上线后动态更新修复翻译错误
|
||||||
|
- 增加动态加载语言包机制,可以在应用上线后动态添加语言支持
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
基于`javascript`的国际化方案很多,比较有名的有`fbt`、`i18next`、`react-i18next`、`vue-i18n`、`react-intl`等等,每一种解决方案均有大量的用户。为什么还要再造一个轮子?好吧,再造轮子的理由不外乎不满足于现有方案,总想着现有方案的种种不足之处,然后就撸起袖子想造一个轮子,也不想想自己什么水平。
|
基于`javascript`的国际化方案很多,比较有名的有`fbt`、`i18next`、`react-i18next`、`vue-i18n`、`react-intl`等等,每一种解决方案均有大量的用户。为什么还要再造一个轮子?好吧,再造轮子的理由不外乎不满足于现有方案,总想着现有方案的种种不足之处,然后就撸起袖子想造一个轮子,也不想想自己什么水平。
|
||||||
|
|
||||||
哪么到底是对现有解决方案有什么不满?最主要有三点:
|
那么到底是对现有解决方案有什么不满?最主要有三点:
|
||||||
|
|
||||||
- 大部份均为要翻译的文本信息指定一个`key`,然后在源码文件中使用形如`$t("message.login")`之类的方式,然后在翻译时将之转换成最终的文本信息。此方式最大的问题是,在源码中必须人为地指定每一个`key`,在中文语境中,想为每一句中文均配套想一句符合语义的`英文key`是比较麻烦的,也很不直观不符合直觉。我希望在源文件中就直接使用中文,如`t("中华人民共和国万岁")`,然后国际化框架应该能自动处理后续的一系列麻烦。
|
- 大部份均为要翻译的文本信息指定一个`key`,然后在源码文件中使用形如`$t("message.login")`之类的方式,然后在翻译时将之转换成最终的文本信息。此方式最大的问题是,在源码中必须人为地指定每一个`key`,在中文语境中,想为每一句中文均配套想一句符合语义的`英文key`是比较麻烦的,也很不直观不符合直觉。我希望在源文件中就直接使用中文,如`t("中华人民共和国万岁")`,然后国际化框架应该能自动处理后续的一系列麻烦。
|
||||||
|
|
||||||
@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
- 翻译过程内,提取文本可以自动进行同步,并保留已翻译的内容。
|
- 翻译过程内,提取文本可以自动进行同步,并保留已翻译的内容。
|
||||||
|
|
||||||
- 可以随时添加支持的语言
|
- 支持远程加载语言包,并且可以在线打语言补丁包
|
||||||
|
|
||||||
- 支持调用在线自动翻译对提取文本进行翻译。
|
- 支持调用在线自动翻译对提取文本进行翻译。
|
||||||
|
|
||||||
|
4
docs/zh/guide/intro/support.md
Normal file
4
docs/zh/guide/intro/support.md
Normal file
@ -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).
|
11
docs/zh/guide/intro/versions.md
Normal file
11
docs/zh/guide/intro/versions.md
Normal file
@ -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插件,提供自动插件翻译函数和语言切换功能
|
@ -9,18 +9,18 @@ actions:
|
|||||||
- text: 快速入门
|
- text: 快速入门
|
||||||
link: /guide/intro/get-started
|
link: /guide/intro/get-started
|
||||||
|
|
||||||
- text: 源码
|
- text: 安装
|
||||||
link: /
|
link: /guide/intro/install
|
||||||
type: secondary
|
type: secondary
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- title: 工程化支持
|
- title: 工程化支持1
|
||||||
icon: markdown
|
icon: markdown
|
||||||
details: 从文本提取/自动翻译/编译/动态切换的全流程工程化支持,适用于大型项目
|
details: 从文本提取/自动翻译/编译/动态切换的全流程工程化支持,适用于大型项目
|
||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 集成自动翻译
|
- title: 集成自动翻译
|
||||||
icon: slides
|
icon: tab
|
||||||
details: 调用在线翻译服务API支持对提取的文本进行自动翻译,大幅度提高工程效率
|
details: 调用在线翻译服务API支持对提取的文本进行自动翻译,大幅度提高工程效率
|
||||||
link:
|
link:
|
||||||
|
|
||||||
@ -44,18 +44,39 @@ features:
|
|||||||
details: 支持monorepo工程下多库进行语言切换的联动机制
|
details: 支持monorepo工程下多库进行语言切换的联动机制
|
||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 自动扩展工具
|
- title: 工具链
|
||||||
icon: palette
|
icon: palette
|
||||||
details: 提供Vue/React/Babel等扩展插件,简化各种应用下
|
details: 提供Vue/React/Babel等扩展插件,简化各种应用开发
|
||||||
link:
|
link:
|
||||||
|
|
||||||
- title: 扩展特性
|
- title: 插值变量
|
||||||
icon: contrast
|
icon: contrast
|
||||||
details: 强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言机制
|
details: 强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言特性
|
||||||
link:
|
link:
|
||||||
|
|
||||||
|
|
||||||
|
- title: 语言补丁
|
||||||
|
icon: info
|
||||||
|
details: 在应用上线后发现错误时可以在线修复
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 动态语种
|
||||||
|
icon: blog
|
||||||
|
details: 可以在应用上线上动态增加语种支持
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 扩展在线编辑
|
||||||
|
icon: palette
|
||||||
|
details: 很容易扩展支持在线语言包编辑
|
||||||
|
link:
|
||||||
|
|
||||||
|
- title: 复数支持
|
||||||
|
icon: contrast
|
||||||
|
details: 灵活而强大的复数机制
|
||||||
|
link:
|
||||||
|
|
||||||
copyright: true
|
copyright: true
|
||||||
footer: MIT Licensed | Copyright © 2022-present wxzhang
|
footer: MIT Licensed | Copyright © 2022-present wxzhang
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,3 +1,35 @@
|
|||||||
# 语言代码
|
# 语言代码
|
||||||
|
|
||||||
请参阅[这里](https://fanyi-api.baidu.com/doc/21)
|
常见的语言代码如下:
|
||||||
|
|
||||||
|
| 名称 | 代码 |
|
||||||
|
|---|:---:|
|
||||||
|
|中文| zh |
|
||||||
|
|繁体中文| cht |
|
||||||
|
|英语 |en|
|
||||||
|
|日语 |jp|
|
||||||
|
|韩语 |kor |
|
||||||
|
|法语 |fra |
|
||||||
|
|西班牙语| spa|
|
||||||
|
|泰语 |th |
|
||||||
|
|阿拉伯语| ara |
|
||||||
|
|俄语| ru|
|
||||||
|
|葡萄牙语 |pt |
|
||||||
|
|德语 |de |
|
||||||
|
|意大利语| it|
|
||||||
|
|希腊语 |el |
|
||||||
|
|荷兰语 |nl |
|
||||||
|
|波兰语 |pl|
|
||||||
|
|保加利亚语 |bul |
|
||||||
|
|爱沙尼亚语 |est |
|
||||||
|
|丹麦语 |dan|
|
||||||
|
|芬兰语 |fin |
|
||||||
|
|捷克语 |cs |
|
||||||
|
|罗马尼亚语 |rom|
|
||||||
|
|斯洛文尼亚语| slo |
|
||||||
|
|瑞典语 |swe |
|
||||||
|
|匈牙利语 |hu|
|
||||||
|
|越南语 |vie |
|
||||||
|
|
||||||
|
|
||||||
|
更完整的请参阅[这里](https://fanyi-api.baidu.com/doc/21)
|
8
packages/apps/vueapp/public/languages/vueapp/de.json
Normal file
8
packages/apps/vueapp/public/languages/vueapp/de.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"1": "DE: Hello world!",
|
||||||
|
"2": "DE: 中华人民共和国",
|
||||||
|
"3": "DE: 迎接中华民族的伟大复兴",
|
||||||
|
"4": "DE: 成立于{}年",
|
||||||
|
"5": "DE: 首都:北京",
|
||||||
|
"6": "DE: VoerkaI18n多语言解决方案 "
|
||||||
|
}
|
3
packages/apps/vueapp/public/languages/vueapp/en.json
Normal file
3
packages/apps/vueapp/public/languages/vueapp/en.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"2": "The People's Republic of China!!!"
|
||||||
|
}
|
4
packages/apps/vueapp/public/languages/vueapp/zh.json
Normal file
4
packages/apps/vueapp/public/languages/vueapp/zh.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"3": "全世界迎接中华民族的伟大复兴!"
|
||||||
|
|
||||||
|
}
|
@ -46,8 +46,11 @@ export default {
|
|||||||
<h5>默认语言:{{ i18n.defaultLanguage }}</h5>
|
<h5>默认语言:{{ i18n.defaultLanguage }}</h5>
|
||||||
<h5>当前语言:{{ i18n.activeLanguage.value }}</h5>
|
<h5>当前语言:{{ i18n.activeLanguage.value }}</h5>
|
||||||
<button v-for="lng of i18n.languages" @click="i18n.activeLanguage = lng.name" style="padding:8px;margin:8px;cursor:pointer" :style="{'outline' : lng.name === i18n.activeLanguage.value ? '2px red solid' : ''}" >{{ lng.title }}</button>
|
<button v-for="lng of i18n.languages" @click="i18n.activeLanguage = lng.name" style="padding:8px;margin:8px;cursor:pointer" :style="{'outline' : lng.name === i18n.activeLanguage.value ? '2px red solid' : ''}" >{{ lng.title }}</button>
|
||||||
|
<button @click="i18n.activeLanguage = 'de'" style="padding:8px;margin:8px;cursor:pointer" :style="{'outline' : i18n.activeLanguage.value === 'de' ? '2px red solid' : ''}" >德语</button>
|
||||||
|
<button @click="i18n.activeLanguage = 'jp'" style="padding:8px;margin:8px;cursor:pointer" :style="{'outline' : i18n.activeLanguage.value === 'jp' ? '2px red solid' : ''}" >日语</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export default {
|
|
||||||
"1": "Hello world!",
|
|
||||||
"2": "中华人民共和国",
|
|
||||||
"3": "迎接中华民族的伟大复兴",
|
|
||||||
"4": "成立于{}年",
|
|
||||||
"5": "首都:北京",
|
|
||||||
"6": "VoerkaI18n多语言解决方案 "
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import runtime from "./runtime.js"
|
|||||||
const { translate,i18nScope } = runtime
|
const { translate,i18nScope } = runtime
|
||||||
|
|
||||||
import formatters from "./formatters.js"
|
import formatters from "./formatters.js"
|
||||||
import defaultMessages from "./cn.js"
|
import defaultMessages from "./zh.js"
|
||||||
const activeMessages = defaultMessages
|
const activeMessages = defaultMessages
|
||||||
|
|
||||||
|
|
||||||
@ -12,16 +12,16 @@ const activeMessages = defaultMessages
|
|||||||
const scopeSettings = {
|
const scopeSettings = {
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"name": "cn",
|
"name": "zh",
|
||||||
"title": "cn"
|
"title": "zh"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "en",
|
"name": "en",
|
||||||
"title": "en"
|
"title": "en"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultLanguage": "cn",
|
"defaultLanguage": "zh",
|
||||||
"activeLanguage": "cn",
|
"activeLanguage": "zh",
|
||||||
"namespaces": {}
|
"namespaces": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"name": "cn",
|
"name": "zh",
|
||||||
"title": "cn"
|
"title": "zh"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "en",
|
"name": "en",
|
||||||
"title": "en"
|
"title": "en"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultLanguage": "cn",
|
"defaultLanguage": "zh",
|
||||||
"activeLanguage": "cn",
|
"activeLanguage": "zh",
|
||||||
"namespaces": {}
|
"namespaces": {}
|
||||||
}
|
}
|
@ -2,6 +2,12 @@ import { createApp } from 'vue'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import i18nPlugin from '@voerkai18n/vue'
|
import i18nPlugin from '@voerkai18n/vue'
|
||||||
import { t,i18nScope } from './languages'
|
import { t,i18nScope } from './languages'
|
||||||
|
|
||||||
|
i18nScope.registerDefaultLoader(async (language,scope)=>{
|
||||||
|
return await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(i18nPlugin,{ t,i18nScope })
|
app.use(i18nPlugin,{ t,i18nScope })
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
@ -33,6 +33,7 @@ const dayjs = require("dayjs");
|
|||||||
const relativeTime = require("dayjs/plugin/relativeTime");
|
const relativeTime = require("dayjs/plugin/relativeTime");
|
||||||
const { rejects } = require("assert");
|
const { rejects } = require("assert");
|
||||||
const { Console } = require("console");
|
const { Console } = require("console");
|
||||||
|
const { resourceLimits } = require("worker_threads");
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
require('dayjs/locale/zh-CN')
|
require('dayjs/locale/zh-CN')
|
||||||
dayjs.locale("zh-CN");
|
dayjs.locale("zh-CN");
|
||||||
@ -57,7 +58,7 @@ const program =new Command()
|
|||||||
const packageFolder = path.join(workspaceRoot,"packages",packageName)
|
const packageFolder = path.join(workspaceRoot,"packages",packageName)
|
||||||
const pkgFile = path.join(workspaceRoot,"packages",packageName,"package.json")
|
const pkgFile = path.join(workspaceRoot,"packages",packageName,"package.json")
|
||||||
if(fs.existsSync(pkgFile)){
|
if(fs.existsSync(pkgFile)){
|
||||||
const { name, version,lastPublish,dependencies,devDependencies }= fs.readJSONSync(pkgFile)
|
const { name, version,lastPublish,dependencies,devDependencies,description }= fs.readJSONSync(pkgFile)
|
||||||
// 读取工作区包依赖
|
// 读取工作区包依赖
|
||||||
let packageDependencies =[]
|
let packageDependencies =[]
|
||||||
Object.entries({...dependencies,...devDependencies}).forEach(([name,version])=>{
|
Object.entries({...dependencies,...devDependencies}).forEach(([name,version])=>{
|
||||||
@ -67,6 +68,7 @@ const program =new Command()
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
name, // 完整包名
|
name, // 完整包名
|
||||||
|
description,
|
||||||
value:packageName, // 文件夹名称
|
value:packageName, // 文件夹名称
|
||||||
version,
|
version,
|
||||||
lastPublish,
|
lastPublish,
|
||||||
@ -349,9 +351,23 @@ program
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
table.render()
|
table.render()
|
||||||
|
generatePackageVersionDoc()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 生成包版本列表文件到文档中
|
||||||
|
function generatePackageVersionDoc(){
|
||||||
|
let workspaceRoot = path.join(__dirname,"../../")
|
||||||
|
shelljs.cd(workspaceRoot)
|
||||||
|
let results = []
|
||||||
|
results.push("# 版本信息")
|
||||||
|
results.push("| 包| 版本号| 最后更新|说明|")
|
||||||
|
results.push("| --- | :---:| --- |---|")
|
||||||
|
getPackages().forEach(package => {
|
||||||
|
const lastPublish = package.lastPublish ? dayjs(package.lastPublish).format("YYYY/MM/DD") : "None"
|
||||||
|
results.push(`|**${package.name}**|${package.version}|${lastPublish}|${package.description}`)
|
||||||
|
})
|
||||||
|
fs.writeFileSync(path.join(workspaceRoot,"docs/zh/guide/intro/versions.md"), results.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
async function answerForSelectPackages(packages,options){
|
async function answerForSelectPackages(packages,options){
|
||||||
const workspaceRoot = process.cwd()
|
const workspaceRoot = process.cwd()
|
||||||
@ -432,6 +448,8 @@ program
|
|||||||
}else{// 只发布指定的包
|
}else{// 只发布指定的包
|
||||||
await publishPackage(options)
|
await publishPackage(options)
|
||||||
}
|
}
|
||||||
|
// 在文档中输出各包的版本信息
|
||||||
|
generatePackageVersionDoc()
|
||||||
})
|
})
|
||||||
|
|
||||||
program.parseAsync(process.argv);
|
program.parseAsync(process.argv);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/babel",
|
"name": "@voerkai18n/babel",
|
||||||
"version": "1.0.22",
|
"version": "1.0.23",
|
||||||
"description": "VoerkaI18n babel plugin",
|
"description": "Babel插件,实现自动导入t函数和自动文本映射",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -20,5 +20,5 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||||
},
|
},
|
||||||
"lastPublish": "2022-04-28T09:03:42+08:00"
|
"lastPublish": "2022-08-05T16:45:56+08:00"
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
* @param {*} obj
|
* @param {*} obj
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function isPlainObject$1(obj){
|
function isPlainObject$2(obj){
|
||||||
if (typeof obj !== 'object' || obj === null) return false;
|
if (typeof obj !== 'object' || obj === null) return false;
|
||||||
var proto = Object.getPrototypeOf(obj);
|
var proto = Object.getPrototypeOf(obj);
|
||||||
if (proto === null) return true;
|
if (proto === null) return true;
|
||||||
@ -82,7 +82,7 @@ function deepMerge$1(toObj,formObj,options={}){
|
|||||||
|
|
||||||
|
|
||||||
var utils ={
|
var utils ={
|
||||||
isPlainObject: isPlainObject$1,
|
isPlainObject: isPlainObject$2,
|
||||||
isNumber: isNumber$1,
|
isNumber: isNumber$1,
|
||||||
deepMerge: deepMerge$1,
|
deepMerge: deepMerge$1,
|
||||||
getDataTypeName: getDataTypeName$1
|
getDataTypeName: getDataTypeName$1
|
||||||
@ -121,6 +121,8 @@ var eventemitter = class EventEmitter{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { isPlainObject: isPlainObject$1 } = utils;
|
||||||
|
|
||||||
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
||||||
|
|
||||||
var scope = class i18nScope {
|
var scope = class i18nScope {
|
||||||
@ -136,6 +138,7 @@ var scope = class i18nScope {
|
|||||||
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
||||||
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
||||||
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
|
this._patchMessages = {}; // 语言包补丁信息 {<language>:{....},<language>:{....}}
|
||||||
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
||||||
this.$cache={
|
this.$cache={
|
||||||
activeLanguage : null,
|
activeLanguage : null,
|
||||||
@ -153,6 +156,8 @@ var scope = class i18nScope {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.global = globalThis.VoerkaI18n;
|
this.global = globalThis.VoerkaI18n;
|
||||||
|
this._mergePatchedMessages();
|
||||||
|
this._patch(this._messages,newLanguage);
|
||||||
// 正在加载语言包标识
|
// 正在加载语言包标识
|
||||||
this._loading=false;
|
this._loading=false;
|
||||||
// 在全局注册作用域
|
// 在全局注册作用域
|
||||||
@ -172,6 +177,8 @@ var scope = class i18nScope {
|
|||||||
get idMap(){return this._idMap}
|
get idMap(){return this._idMap}
|
||||||
// 当前作用域的格式化函数列表
|
// 当前作用域的格式化函数列表
|
||||||
get formatters(){return this._formatters}
|
get formatters(){return this._formatters}
|
||||||
|
// 当前作用域支持的语言
|
||||||
|
get languages(){return this._languages}
|
||||||
// 异步加载语言文件的函数列表
|
// 异步加载语言文件的函数列表
|
||||||
get loaders(){return this._loaders}
|
get loaders(){return this._loaders}
|
||||||
// 引用全局VoerkaI18n配置,注册后自动引用
|
// 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
@ -195,6 +202,13 @@ var scope = class i18nScope {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
* @param {Function} 必须是异步函数或者是返回Promise
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
this.global.registerDefaultLoader(fn);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 回退到默认语言
|
* 回退到默认语言
|
||||||
*/
|
*/
|
||||||
@ -212,21 +226,89 @@ var scope = class i18nScope {
|
|||||||
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
||||||
if(newLanguage === this.defaultLanguage){
|
if(newLanguage === this.defaultLanguage){
|
||||||
this._messages = this._default;
|
this._messages = this._default;
|
||||||
|
await this._patch(this._messages,newLanguage); // 异步补丁
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
||||||
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
||||||
const loader = this.loaders[newLanguage];
|
let loader = this.loaders[newLanguage];
|
||||||
if(typeof(loader) === "function"){
|
|
||||||
try{
|
try{
|
||||||
|
if(typeof(loader) === "function"){
|
||||||
this._messages = (await loader()).default;
|
this._messages = (await loader()).default;
|
||||||
this._activeLanguage = newLanguage;
|
this._activeLanguage = newLanguage;
|
||||||
|
await this._patch(this._messages,newLanguage);
|
||||||
|
}else if(typeof(this.global.defaultMessageLoader) === "function"){// 如果该语言没有指定加载器,则使用全局配置的默认加载器
|
||||||
|
this._messages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
this._activeLanguage = newLanguage;
|
||||||
|
}else {
|
||||||
|
this._fallback();
|
||||||
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
||||||
this._fallback();
|
this._fallback();
|
||||||
}
|
}
|
||||||
}else {
|
}
|
||||||
this._fallback();
|
/**
|
||||||
|
* 当指定了默认语言包加载器后,会从服务加载语言补丁包来更新本地的语言包
|
||||||
|
*
|
||||||
|
* 补丁包会自动存储到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
* @param {*} newLanguage
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async _patch(messages,newLanguage){
|
||||||
|
if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return
|
||||||
|
try{
|
||||||
|
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
if(isPlainObject$1(pachedMessages)){
|
||||||
|
Object.assign(messages,pachedMessages);
|
||||||
|
this._savePatchedMessages(pachedMessages,newLanguage);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 从本地存储中读取语言包补丁合并到当前语言包中
|
||||||
|
*/
|
||||||
|
_mergePatchedMessages(){
|
||||||
|
let patchedMessages= this._getPatchedMessages(this.activeLanguage);
|
||||||
|
if(isPlainObject$1(patchedMessages)){
|
||||||
|
Object.assign(this._messages,patchedMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将读取的补丁包保存到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* 为什么要保存到本地的LocalStorage中?
|
||||||
|
*
|
||||||
|
* 因为默认语言是静态嵌入到源码中的,而加载语言包补丁是延后异步的,
|
||||||
|
* 当应用启动第一次就会渲染出来的是没有打过补丁的内容。
|
||||||
|
*
|
||||||
|
* - 如果还需要等待从服务器加载语言补丁合并后再渲染会影响速度
|
||||||
|
* - 如果不等待从服务器加载语言补丁就渲染,则会先显示未打补丁的内容,然后在打完补丁后再对应用进行重新渲染生效
|
||||||
|
* 这明显不是个好的方式
|
||||||
|
*
|
||||||
|
* 因此,采用的方式是:
|
||||||
|
* - 加载语言包补丁后,将之保存到到本地的LocalStorage中
|
||||||
|
* - 当应用加载时会查询是否存在补丁,如果存在就会合并渲染
|
||||||
|
* -
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
*/
|
||||||
|
_savePatchedMessages(messages,language){
|
||||||
|
try{
|
||||||
|
if(globalThis.localStorage){
|
||||||
|
globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages));
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
console.error("Error while save voerkai18n patched messages:",e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_getPatchedMessages(language){
|
||||||
|
try{
|
||||||
|
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`))
|
||||||
|
}catch(e){
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 以下方法引用全局VoerkaI18n实例的方法
|
// 以下方法引用全局VoerkaI18n实例的方法
|
||||||
@ -841,6 +923,7 @@ function translate(message) {
|
|||||||
I18nManager.instance = this;
|
I18nManager.instance = this;
|
||||||
this._settings = deepMerge(defaultLanguageSettings,settings);
|
this._settings = deepMerge(defaultLanguageSettings,settings);
|
||||||
this._scopes=[];
|
this._scopes=[];
|
||||||
|
this._defaultMessageLoader = null; // 默认文本加载器
|
||||||
return I18nManager.instance;
|
return I18nManager.instance;
|
||||||
}
|
}
|
||||||
get settings(){ return this._settings }
|
get settings(){ return this._settings }
|
||||||
@ -853,17 +936,22 @@ function translate(message) {
|
|||||||
get languages(){ return this._settings.languages}
|
get languages(){ return this._settings.languages}
|
||||||
// 内置格式化器
|
// 内置格式化器
|
||||||
get formatters(){ return inlineFormatters }
|
get formatters(){ return inlineFormatters }
|
||||||
|
get defaultMessageLoader(){ return this._defaultMessageLoader}
|
||||||
|
// 通过默认加载器加载文件
|
||||||
|
async loadMessagesFromDefaultLoader(newLanguage,scope){
|
||||||
|
if(typeof(this._defaultMessageLoader) != "function") return //throw new Error("No default message loader specified")
|
||||||
|
return await this._defaultMessageLoader.call(scope,newLanguage,scope)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 切换语言
|
* 切换语言
|
||||||
*/
|
*/
|
||||||
async change(value){
|
async change(value){
|
||||||
value=value.trim();
|
value=value.trim();
|
||||||
if(this.languages.findIndex(lang=>lang.name === value)!==-1){
|
if(this.languages.findIndex(lang=>lang.name === value)!==-1 || typeof(this._defaultMessageLoader)==="function"){
|
||||||
// 通知所有作用域刷新到对应的语言包
|
// 通知所有作用域刷新到对应的语言包
|
||||||
await this._refreshScopes(value);
|
await this._refreshScopes(value);
|
||||||
this._settings.activeLanguage = value;
|
this._settings.activeLanguage = value;
|
||||||
/// 触发语言切换事件
|
await this.emit(value); /// 触发语言切换事件
|
||||||
await this.emit(value);
|
|
||||||
}else {
|
}else {
|
||||||
throw new Error("Not supported language:"+value)
|
throw new Error("Not supported language:"+value)
|
||||||
}
|
}
|
||||||
@ -923,6 +1011,25 @@ function translate(message) {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
if(typeof(fn) !== 'function') throw new Error("The default loader must be a async function or promise returned")
|
||||||
|
this._defaultMessageLoader = fn;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
async refresh(){
|
||||||
|
try{
|
||||||
|
let requests = this._scopes.map(scope=>scope.refresh());
|
||||||
|
if(Promise.allSettled){
|
||||||
|
await Promise.allSettled(requests);
|
||||||
|
}else {
|
||||||
|
await Promise.all(requests);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtime ={
|
var runtime ={
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/cli",
|
"name": "@voerkai18n/cli",
|
||||||
"version": "1.0.30",
|
"version": "1.0.32",
|
||||||
"description": "VoerkaI18n command line interactive tools",
|
"description": "命令行工具,用来初始化/提取/编译/自动翻译等工具链",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -50,5 +50,5 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||||
},
|
},
|
||||||
"lastPublish": "2022-04-28T09:03:32+08:00"
|
"lastPublish": "2022-08-05T16:45:44+08:00"
|
||||||
}
|
}
|
@ -1,27 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"1": "请输入旧密码:",
|
|
||||||
"2": "请再次输入旧密码:",
|
|
||||||
"3": "请输入新密码:",
|
|
||||||
"4": "请再次输入新密码:",
|
|
||||||
"5": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"6": "密码强度: {strength}",
|
|
||||||
"7": "登录",
|
|
||||||
"8": "请输入用户名:",
|
|
||||||
"9": "请输入密码:",
|
|
||||||
"10": "头像",
|
|
||||||
"11": "相片",
|
|
||||||
"12": "用户名或密码错误",
|
|
||||||
"13": "请输入密码:",
|
|
||||||
"14": "欢迎您: {}",
|
|
||||||
"15": "数据库类型:{}、{}、{}",
|
|
||||||
"16": "数据库密码:{pwd}",
|
|
||||||
"17": "数据库地址:{url}",
|
|
||||||
"18": "编码:{encode}",
|
|
||||||
"19": "编码",
|
|
||||||
"20": "名称",
|
|
||||||
"21": "描述",
|
|
||||||
"22": "文件名称",
|
|
||||||
"23": "您有{}条未读消息",
|
|
||||||
"24": "消息总数:{$count}",
|
|
||||||
"25": "消息类型:{type}"
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"1": "请输入旧密码:",
|
|
||||||
"2": "请再次输入旧密码:",
|
|
||||||
"3": "请输入新密码:",
|
|
||||||
"4": "请再次输入新密码:",
|
|
||||||
"5": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"6": "密码强度: {strength}",
|
|
||||||
"7": "登录",
|
|
||||||
"8": "Please enter username:",
|
|
||||||
"9": "请输入密码:",
|
|
||||||
"10": "头像",
|
|
||||||
"11": "相片",
|
|
||||||
"12": "username and password error",
|
|
||||||
"13": "Please enter password:",
|
|
||||||
"14": "Welcome to: {}",
|
|
||||||
"15": "数据库类型:{}、{}、{}",
|
|
||||||
"16": "数据库密码:{pwd}",
|
|
||||||
"17": "数据库地址:{url}",
|
|
||||||
"18": "编码:{encode}",
|
|
||||||
"19": "编码",
|
|
||||||
"20": "名称",
|
|
||||||
"21": "描述",
|
|
||||||
"22": "文件名称",
|
|
||||||
"23": "您有{}条未读消息",
|
|
||||||
"24": "消息总数:{$count}",
|
|
||||||
"25": "消息类型:{type}"
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
/**
|
|
||||||
|
|
||||||
格式化器用来对翻译文本内容中的插值变量进行格式化,
|
|
||||||
比如将一个数字格式化为货币格式,或者将一个日期格式化为友好的日期格式。
|
|
||||||
|
|
||||||
- 以下定义了一些格式化器,在中文场景下,会启用这些格式化器。
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
const formatters = {
|
|
||||||
"*":{ // 在所有语言下生效的格式化器
|
|
||||||
$types:{...}, // 只作用于特定数据类型的默认格式化器
|
|
||||||
.... // 全局格式化器
|
|
||||||
},
|
|
||||||
cn:{
|
|
||||||
// 只作用于特定数据类型的格式化器
|
|
||||||
$types:{
|
|
||||||
Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
|
|
||||||
},
|
|
||||||
date:(value)=>dayjs(value).format("YYYY年MM月DD日")
|
|
||||||
bjTime:(value)=>"北京时间"+ value,
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
},
|
|
||||||
en:{
|
|
||||||
$types:{
|
|
||||||
Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器
|
|
||||||
},
|
|
||||||
date:(value)=>dayjs(value).format("YYYY/MM/DD")
|
|
||||||
bjTime:(value)=>"BeiJing "+ value,
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
[格式化器名称]:(value)=>{...},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
- 在翻译函数中使用格式化器的方法,示例如下:
|
|
||||||
|
|
||||||
t("Now is { value | date | bjTime }",{value: new Date()})
|
|
||||||
其等效于:
|
|
||||||
t(`Now is ${bjTime(date(value))",{value: new Date()})
|
|
||||||
由于value分别经过两个管道符转换,上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符。
|
|
||||||
|
|
||||||
最终的输出结果:
|
|
||||||
中文: "现在是北京时间2022年3月1日"
|
|
||||||
英文: "Now is BeiJing 2022/03/01"
|
|
||||||
|
|
||||||
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const formatters = {
|
|
||||||
"*":{ }, // 在所有语言下生效的格式化器
|
|
||||||
$types:{ } // 在所有语言下只作用于特定数据类型的格式化器
|
|
||||||
|
|
||||||
cn:{
|
|
||||||
$types:{
|
|
||||||
"*":{
|
|
||||||
|
|
||||||
},
|
|
||||||
Date:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Number:{
|
|
||||||
|
|
||||||
},
|
|
||||||
String:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Array:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Object:{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
en:{
|
|
||||||
$types:{
|
|
||||||
"*":{
|
|
||||||
|
|
||||||
},
|
|
||||||
Date:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Number:{
|
|
||||||
|
|
||||||
},
|
|
||||||
String:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Array:{
|
|
||||||
|
|
||||||
},
|
|
||||||
Object:{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.module.module.module.module.module.module.module.exports.s.s.s.s.s.s.s = formatters
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"请输入旧密码:": 1,
|
|
||||||
"请再次输入旧密码:": 2,
|
|
||||||
"请输入新密码:": 3,
|
|
||||||
"请再次输入新密码:": 4,
|
|
||||||
"密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种": 5,
|
|
||||||
"密码强度: {strength}": 6,
|
|
||||||
"登录": 7,
|
|
||||||
"请输入用户名:": 8,
|
|
||||||
"请输入密码:": 9,
|
|
||||||
"头像": 10,
|
|
||||||
"相片": 11,
|
|
||||||
"用户名或密码错误": 12,
|
|
||||||
"请输入密码:": 13,
|
|
||||||
"欢迎您: {}": 14,
|
|
||||||
"数据库类型:{}、{}、{}": 15,
|
|
||||||
"数据库密码:{pwd}": 16,
|
|
||||||
"数据库地址:{url}": 17,
|
|
||||||
"编码:{encode}": 18,
|
|
||||||
"编码": 19,
|
|
||||||
"名称": 20,
|
|
||||||
"描述": 21,
|
|
||||||
"文件名称": 22,
|
|
||||||
"您有{}条未读消息": 23,
|
|
||||||
"消息总数:{$count}": 24,
|
|
||||||
"消息类型:{type}": 25
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
|
|
||||||
const messageIds = require("./idMap")
|
|
||||||
const { translate,I18nManager } = require("@voerkai18n/runtime")
|
|
||||||
const defaultMessages = require("./cn.js")
|
|
||||||
const scopeSettings = require("./settings.js")
|
|
||||||
|
|
||||||
|
|
||||||
// 自动创建全局VoerkaI18n实例
|
|
||||||
if(!globalThis.VoerkaI18n){
|
|
||||||
globalThis.VoerkaI18n = new I18nManager(scopeSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
let scope = {
|
|
||||||
defaultLanguage: "cn", // 默认语言名称
|
|
||||||
default: defaultMessages, // 默认语言包
|
|
||||||
messages : defaultMessages, // 当前语言包
|
|
||||||
idMap:messageIds, // 消息id映射列表
|
|
||||||
formatters:{}, // 当前作用域的格式化函数列表
|
|
||||||
loaders:{}, // 异步加载语言文件的函数列表
|
|
||||||
global:{}, // 引用全局VoerkaI18n配置,注册后自动引用
|
|
||||||
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
|
||||||
$cache:{
|
|
||||||
activeLanguage:null,
|
|
||||||
typedFormatters:{},
|
|
||||||
formatters:{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let supportedlanguages = {}
|
|
||||||
|
|
||||||
|
|
||||||
scope.loaders["en"] = ()=>import("./en.js")
|
|
||||||
|
|
||||||
|
|
||||||
const t = translate.bind(scope)
|
|
||||||
const languages = [
|
|
||||||
{
|
|
||||||
"name": "cn",
|
|
||||||
"title": "cn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "en",
|
|
||||||
"title": "en"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
// 注册当前作用域到全局VoerkaI18n实例
|
|
||||||
VoerkaI18n.register(scope)
|
|
||||||
|
|
||||||
|
|
||||||
module.exports.languages = languages
|
|
||||||
module.exports.scope = scope
|
|
||||||
module.exports.t = t
|
|
||||||
module.exports.changeLanguage = VoerkaI18n.change.bind(VoerkaI18n)
|
|
||||||
module.exports.addLanguageListener = VoerkaI18n.on.bind(VoerkaI18n)
|
|
||||||
module.exports.removeLanguageListener = VoerkaI18n.off.bind(VoerkaI18n)
|
|
||||||
module.exports.i18nManager = VoerkaI18n
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"license": "MIT"
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"languages": [
|
|
||||||
{
|
|
||||||
"name": "cn",
|
|
||||||
"title": "cn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "en",
|
|
||||||
"title": "en"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"defaultLanguage": "cn",
|
|
||||||
"activeLanguage": "cn",
|
|
||||||
"namespaces": {}
|
|
||||||
}
|
|
@ -1,278 +0,0 @@
|
|||||||
{
|
|
||||||
"请输入旧密码:": {
|
|
||||||
"en": "请输入旧密码:",
|
|
||||||
"de": "请输入旧密码:",
|
|
||||||
"fr": "请输入旧密码:",
|
|
||||||
"es": "请输入旧密码:",
|
|
||||||
"it": "请输入旧密码:",
|
|
||||||
"jp": "请输入旧密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请再次输入旧密码:": {
|
|
||||||
"en": "请再次输入旧密码:",
|
|
||||||
"de": "请再次输入旧密码:",
|
|
||||||
"fr": "请再次输入旧密码:",
|
|
||||||
"es": "请再次输入旧密码:",
|
|
||||||
"it": "请再次输入旧密码:",
|
|
||||||
"jp": "请再次输入旧密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请输入新密码:": {
|
|
||||||
"en": "请输入新密码:",
|
|
||||||
"de": "请输入新密码:",
|
|
||||||
"fr": "请输入新密码:",
|
|
||||||
"es": "请输入新密码:",
|
|
||||||
"it": "请输入新密码:",
|
|
||||||
"jp": "请输入新密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请再次输入新密码:": {
|
|
||||||
"en": "请再次输入新密码:",
|
|
||||||
"de": "请再次输入新密码:",
|
|
||||||
"fr": "请再次输入新密码:",
|
|
||||||
"es": "请再次输入新密码:",
|
|
||||||
"it": "请再次输入新密码:",
|
|
||||||
"jp": "请再次输入新密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种": {
|
|
||||||
"en": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"de": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"fr": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"es": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"it": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"jp": "密码至少需要6位,并且至少包含数字、字符或特殊符号中的两种",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"密码强度: {strength}": {
|
|
||||||
"en": "密码强度: {strength}",
|
|
||||||
"de": "密码强度: {strength}",
|
|
||||||
"fr": "密码强度: {strength}",
|
|
||||||
"es": "密码强度: {strength}",
|
|
||||||
"it": "密码强度: {strength}",
|
|
||||||
"jp": "密码强度: {strength}",
|
|
||||||
"$file": [
|
|
||||||
"auth\\changepassword.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"登录": {
|
|
||||||
"en": "登录",
|
|
||||||
"de": "登录",
|
|
||||||
"fr": "登录",
|
|
||||||
"es": "登录",
|
|
||||||
"it": "登录",
|
|
||||||
"jp": "登录",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.html"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请输入用户名:": {
|
|
||||||
"en": "Please enter username:",
|
|
||||||
"de": "请输入用户名:",
|
|
||||||
"fr": "请输入用户名:",
|
|
||||||
"es": "请输入用户名:",
|
|
||||||
"it": "请输入用户名:",
|
|
||||||
"jp": "请输入用户名:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.html",
|
|
||||||
"auth\\login.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请输入密码:": {
|
|
||||||
"en": "请输入密码:",
|
|
||||||
"de": "请输入密码:",
|
|
||||||
"fr": "请输入密码:",
|
|
||||||
"es": "请输入密码:",
|
|
||||||
"it": "请输入密码:",
|
|
||||||
"jp": "请输入密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.html"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"头像": {
|
|
||||||
"en": "头像",
|
|
||||||
"de": "头像",
|
|
||||||
"fr": "头像",
|
|
||||||
"es": "头像",
|
|
||||||
"it": "头像",
|
|
||||||
"jp": "头像",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.html"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"相片": {
|
|
||||||
"en": "相片",
|
|
||||||
"de": "相片",
|
|
||||||
"fr": "相片",
|
|
||||||
"es": "相片",
|
|
||||||
"it": "相片",
|
|
||||||
"jp": "相片",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.html"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"用户名或密码错误": {
|
|
||||||
"en": "username and password error",
|
|
||||||
"de": "用户名或密码错误",
|
|
||||||
"fr": "用户名或密码错误",
|
|
||||||
"es": "用户名或密码错误",
|
|
||||||
"it": "用户名或密码错误",
|
|
||||||
"jp": "用户名或密码错误",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"请输入密码:": {
|
|
||||||
"en": "Please enter password:",
|
|
||||||
"de": "请输入密码:",
|
|
||||||
"fr": "请输入密码:",
|
|
||||||
"es": "请输入密码:",
|
|
||||||
"it": "请输入密码:",
|
|
||||||
"jp": "请输入密码:",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"欢迎您: {}": {
|
|
||||||
"en": "Welcome to: {}",
|
|
||||||
"de": "欢迎您: {}",
|
|
||||||
"fr": "欢迎您: {}",
|
|
||||||
"es": "欢迎您: {}",
|
|
||||||
"it": "欢迎您: {}",
|
|
||||||
"jp": "欢迎您: {}",
|
|
||||||
"$file": [
|
|
||||||
"auth\\login.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"数据库类型:{}、{}、{}": {
|
|
||||||
"en": "数据库类型:{}、{}、{}",
|
|
||||||
"de": "数据库类型:{}、{}、{}",
|
|
||||||
"fr": "数据库类型:{}、{}、{}",
|
|
||||||
"es": "数据库类型:{}、{}、{}",
|
|
||||||
"it": "数据库类型:{}、{}、{}",
|
|
||||||
"jp": "数据库类型:{}、{}、{}",
|
|
||||||
"$file": [
|
|
||||||
"db\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"数据库密码:{pwd}": {
|
|
||||||
"en": "数据库密码:{pwd}",
|
|
||||||
"de": "数据库密码:{pwd}",
|
|
||||||
"fr": "数据库密码:{pwd}",
|
|
||||||
"es": "数据库密码:{pwd}",
|
|
||||||
"it": "数据库密码:{pwd}",
|
|
||||||
"jp": "数据库密码:{pwd}",
|
|
||||||
"$file": [
|
|
||||||
"db\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"数据库地址:{url}": {
|
|
||||||
"en": "数据库地址:{url}",
|
|
||||||
"de": "数据库地址:{url}",
|
|
||||||
"fr": "数据库地址:{url}",
|
|
||||||
"es": "数据库地址:{url}",
|
|
||||||
"it": "数据库地址:{url}",
|
|
||||||
"jp": "数据库地址:{url}",
|
|
||||||
"$file": [
|
|
||||||
"db\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"编码:{encode}": {
|
|
||||||
"en": "编码:{encode}",
|
|
||||||
"de": "编码:{encode}",
|
|
||||||
"fr": "编码:{encode}",
|
|
||||||
"es": "编码:{encode}",
|
|
||||||
"it": "编码:{encode}",
|
|
||||||
"jp": "编码:{encode}",
|
|
||||||
"$file": [
|
|
||||||
"db\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"编码": {
|
|
||||||
"en": "编码",
|
|
||||||
"de": "编码",
|
|
||||||
"fr": "编码",
|
|
||||||
"es": "编码",
|
|
||||||
"it": "编码",
|
|
||||||
"jp": "编码",
|
|
||||||
"$file": [
|
|
||||||
"db\\models.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"名称": {
|
|
||||||
"en": "名称",
|
|
||||||
"de": "名称",
|
|
||||||
"fr": "名称",
|
|
||||||
"es": "名称",
|
|
||||||
"it": "名称",
|
|
||||||
"jp": "名称",
|
|
||||||
"$file": [
|
|
||||||
"db\\models.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"描述": {
|
|
||||||
"en": "描述",
|
|
||||||
"de": "描述",
|
|
||||||
"fr": "描述",
|
|
||||||
"es": "描述",
|
|
||||||
"it": "描述",
|
|
||||||
"jp": "描述",
|
|
||||||
"$file": [
|
|
||||||
"db\\models.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"文件名称": {
|
|
||||||
"en": "文件名称",
|
|
||||||
"de": "文件名称",
|
|
||||||
"fr": "文件名称",
|
|
||||||
"es": "文件名称",
|
|
||||||
"it": "文件名称",
|
|
||||||
"jp": "文件名称",
|
|
||||||
"$file": [
|
|
||||||
"db\\models.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"您有{}条未读消息": {
|
|
||||||
"en": "您有{}条未读消息",
|
|
||||||
"de": "您有{}条未读消息",
|
|
||||||
"fr": "您有{}条未读消息",
|
|
||||||
"es": "您有{}条未读消息",
|
|
||||||
"it": "您有{}条未读消息",
|
|
||||||
"jp": "您有{}条未读消息",
|
|
||||||
"$file": [
|
|
||||||
"messages\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"消息总数:{$count}": {
|
|
||||||
"en": "消息总数:{$count}",
|
|
||||||
"de": "消息总数:{$count}",
|
|
||||||
"fr": "消息总数:{$count}",
|
|
||||||
"es": "消息总数:{$count}",
|
|
||||||
"it": "消息总数:{$count}",
|
|
||||||
"jp": "消息总数:{$count}",
|
|
||||||
"$file": [
|
|
||||||
"messages\\index.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"消息类型:{type}": {
|
|
||||||
"en": "消息类型:{type}",
|
|
||||||
"de": "消息类型:{type}",
|
|
||||||
"fr": "消息类型:{type}",
|
|
||||||
"es": "消息类型:{type}",
|
|
||||||
"it": "消息类型:{type}",
|
|
||||||
"jp": "消息类型:{type}",
|
|
||||||
"$file": [
|
|
||||||
"messages\\index.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
export default {
|
|
||||||
"a":1,
|
|
||||||
"b":2,
|
|
||||||
"c{}{}":3,
|
|
||||||
"d{a}{b}":4,
|
|
||||||
"e":5
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"a":1,
|
|
||||||
"b":2,
|
|
||||||
"c{}{}":3,
|
|
||||||
"d{a}{b}":4,
|
|
||||||
"e":5
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/formatters",
|
"name": "@voerkai18n/formatters",
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"description": "",
|
"description": "格式化器,提供对要翻译文本的转换功能",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/react",
|
"name": "@voerkai18n/react",
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"description": "",
|
"description": "React支持,提供语言切换等功能",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
2
packages/runtime/dist/index.cjs
vendored
2
packages/runtime/dist/index.cjs
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.cjs.map
vendored
2
packages/runtime/dist/index.cjs.map
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.esm.js
vendored
2
packages/runtime/dist/index.esm.js
vendored
File diff suppressed because one or more lines are too long
2
packages/runtime/dist/index.esm.js.map
vendored
2
packages/runtime/dist/index.esm.js.map
vendored
File diff suppressed because one or more lines are too long
125
packages/runtime/dist/runtime.cjs
vendored
125
packages/runtime/dist/runtime.cjs
vendored
@ -5,7 +5,7 @@
|
|||||||
* @param {*} obj
|
* @param {*} obj
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function isPlainObject$1(obj){
|
function isPlainObject$2(obj){
|
||||||
if (typeof obj !== 'object' || obj === null) return false;
|
if (typeof obj !== 'object' || obj === null) return false;
|
||||||
var proto = Object.getPrototypeOf(obj);
|
var proto = Object.getPrototypeOf(obj);
|
||||||
if (proto === null) return true;
|
if (proto === null) return true;
|
||||||
@ -82,7 +82,7 @@ function deepMerge$1(toObj,formObj,options={}){
|
|||||||
|
|
||||||
|
|
||||||
var utils ={
|
var utils ={
|
||||||
isPlainObject: isPlainObject$1,
|
isPlainObject: isPlainObject$2,
|
||||||
isNumber: isNumber$1,
|
isNumber: isNumber$1,
|
||||||
deepMerge: deepMerge$1,
|
deepMerge: deepMerge$1,
|
||||||
getDataTypeName: getDataTypeName$1
|
getDataTypeName: getDataTypeName$1
|
||||||
@ -121,6 +121,8 @@ var eventemitter = class EventEmitter{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { isPlainObject: isPlainObject$1 } = utils;
|
||||||
|
|
||||||
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
||||||
|
|
||||||
var scope = class i18nScope {
|
var scope = class i18nScope {
|
||||||
@ -136,6 +138,7 @@ var scope = class i18nScope {
|
|||||||
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
||||||
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
||||||
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
|
this._patchMessages = {}; // 语言包补丁信息 {<language>:{....},<language>:{....}}
|
||||||
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
||||||
this.$cache={
|
this.$cache={
|
||||||
activeLanguage : null,
|
activeLanguage : null,
|
||||||
@ -153,6 +156,8 @@ var scope = class i18nScope {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.global = globalThis.VoerkaI18n;
|
this.global = globalThis.VoerkaI18n;
|
||||||
|
this._mergePatchedMessages();
|
||||||
|
this._patch(this._messages,newLanguage);
|
||||||
// 正在加载语言包标识
|
// 正在加载语言包标识
|
||||||
this._loading=false;
|
this._loading=false;
|
||||||
// 在全局注册作用域
|
// 在全局注册作用域
|
||||||
@ -172,6 +177,8 @@ var scope = class i18nScope {
|
|||||||
get idMap(){return this._idMap}
|
get idMap(){return this._idMap}
|
||||||
// 当前作用域的格式化函数列表
|
// 当前作用域的格式化函数列表
|
||||||
get formatters(){return this._formatters}
|
get formatters(){return this._formatters}
|
||||||
|
// 当前作用域支持的语言
|
||||||
|
get languages(){return this._languages}
|
||||||
// 异步加载语言文件的函数列表
|
// 异步加载语言文件的函数列表
|
||||||
get loaders(){return this._loaders}
|
get loaders(){return this._loaders}
|
||||||
// 引用全局VoerkaI18n配置,注册后自动引用
|
// 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
@ -195,6 +202,13 @@ var scope = class i18nScope {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
* @param {Function} 必须是异步函数或者是返回Promise
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
this.global.registerDefaultLoader(fn);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 回退到默认语言
|
* 回退到默认语言
|
||||||
*/
|
*/
|
||||||
@ -212,21 +226,89 @@ var scope = class i18nScope {
|
|||||||
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
||||||
if(newLanguage === this.defaultLanguage){
|
if(newLanguage === this.defaultLanguage){
|
||||||
this._messages = this._default;
|
this._messages = this._default;
|
||||||
|
await this._patch(this._messages,newLanguage); // 异步补丁
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
||||||
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
||||||
const loader = this.loaders[newLanguage];
|
let loader = this.loaders[newLanguage];
|
||||||
if(typeof(loader) === "function"){
|
|
||||||
try{
|
try{
|
||||||
|
if(typeof(loader) === "function"){
|
||||||
this._messages = (await loader()).default;
|
this._messages = (await loader()).default;
|
||||||
this._activeLanguage = newLanguage;
|
this._activeLanguage = newLanguage;
|
||||||
|
await this._patch(this._messages,newLanguage);
|
||||||
|
}else if(typeof(this.global.defaultMessageLoader) === "function"){// 如果该语言没有指定加载器,则使用全局配置的默认加载器
|
||||||
|
this._messages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
this._activeLanguage = newLanguage;
|
||||||
|
}else {
|
||||||
|
this._fallback();
|
||||||
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
||||||
this._fallback();
|
this._fallback();
|
||||||
}
|
}
|
||||||
}else {
|
}
|
||||||
this._fallback();
|
/**
|
||||||
|
* 当指定了默认语言包加载器后,会从服务加载语言补丁包来更新本地的语言包
|
||||||
|
*
|
||||||
|
* 补丁包会自动存储到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
* @param {*} newLanguage
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async _patch(messages,newLanguage){
|
||||||
|
if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return
|
||||||
|
try{
|
||||||
|
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
if(isPlainObject$1(pachedMessages)){
|
||||||
|
Object.assign(messages,pachedMessages);
|
||||||
|
this._savePatchedMessages(pachedMessages,newLanguage);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 从本地存储中读取语言包补丁合并到当前语言包中
|
||||||
|
*/
|
||||||
|
_mergePatchedMessages(){
|
||||||
|
let patchedMessages= this._getPatchedMessages(this.activeLanguage);
|
||||||
|
if(isPlainObject$1(patchedMessages)){
|
||||||
|
Object.assign(this._messages,patchedMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将读取的补丁包保存到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* 为什么要保存到本地的LocalStorage中?
|
||||||
|
*
|
||||||
|
* 因为默认语言是静态嵌入到源码中的,而加载语言包补丁是延后异步的,
|
||||||
|
* 当应用启动第一次就会渲染出来的是没有打过补丁的内容。
|
||||||
|
*
|
||||||
|
* - 如果还需要等待从服务器加载语言补丁合并后再渲染会影响速度
|
||||||
|
* - 如果不等待从服务器加载语言补丁就渲染,则会先显示未打补丁的内容,然后在打完补丁后再对应用进行重新渲染生效
|
||||||
|
* 这明显不是个好的方式
|
||||||
|
*
|
||||||
|
* 因此,采用的方式是:
|
||||||
|
* - 加载语言包补丁后,将之保存到到本地的LocalStorage中
|
||||||
|
* - 当应用加载时会查询是否存在补丁,如果存在就会合并渲染
|
||||||
|
* -
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
*/
|
||||||
|
_savePatchedMessages(messages,language){
|
||||||
|
try{
|
||||||
|
if(globalThis.localStorage){
|
||||||
|
globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages));
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
console.error("Error while save voerkai18n patched messages:",e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_getPatchedMessages(language){
|
||||||
|
try{
|
||||||
|
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`))
|
||||||
|
}catch(e){
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 以下方法引用全局VoerkaI18n实例的方法
|
// 以下方法引用全局VoerkaI18n实例的方法
|
||||||
@ -841,6 +923,7 @@ function translate(message) {
|
|||||||
I18nManager.instance = this;
|
I18nManager.instance = this;
|
||||||
this._settings = deepMerge(defaultLanguageSettings,settings);
|
this._settings = deepMerge(defaultLanguageSettings,settings);
|
||||||
this._scopes=[];
|
this._scopes=[];
|
||||||
|
this._defaultMessageLoader = null; // 默认文本加载器
|
||||||
return I18nManager.instance;
|
return I18nManager.instance;
|
||||||
}
|
}
|
||||||
get settings(){ return this._settings }
|
get settings(){ return this._settings }
|
||||||
@ -853,17 +936,22 @@ function translate(message) {
|
|||||||
get languages(){ return this._settings.languages}
|
get languages(){ return this._settings.languages}
|
||||||
// 内置格式化器
|
// 内置格式化器
|
||||||
get formatters(){ return inlineFormatters }
|
get formatters(){ return inlineFormatters }
|
||||||
|
get defaultMessageLoader(){ return this._defaultMessageLoader}
|
||||||
|
// 通过默认加载器加载文件
|
||||||
|
async loadMessagesFromDefaultLoader(newLanguage,scope){
|
||||||
|
if(typeof(this._defaultMessageLoader) != "function") return //throw new Error("No default message loader specified")
|
||||||
|
return await this._defaultMessageLoader.call(scope,newLanguage,scope)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 切换语言
|
* 切换语言
|
||||||
*/
|
*/
|
||||||
async change(value){
|
async change(value){
|
||||||
value=value.trim();
|
value=value.trim();
|
||||||
if(this.languages.findIndex(lang=>lang.name === value)!==-1){
|
if(this.languages.findIndex(lang=>lang.name === value)!==-1 || typeof(this._defaultMessageLoader)==="function"){
|
||||||
// 通知所有作用域刷新到对应的语言包
|
// 通知所有作用域刷新到对应的语言包
|
||||||
await this._refreshScopes(value);
|
await this._refreshScopes(value);
|
||||||
this._settings.activeLanguage = value;
|
this._settings.activeLanguage = value;
|
||||||
/// 触发语言切换事件
|
await this.emit(value); /// 触发语言切换事件
|
||||||
await this.emit(value);
|
|
||||||
}else {
|
}else {
|
||||||
throw new Error("Not supported language:"+value)
|
throw new Error("Not supported language:"+value)
|
||||||
}
|
}
|
||||||
@ -923,6 +1011,25 @@ function translate(message) {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
if(typeof(fn) !== 'function') throw new Error("The default loader must be a async function or promise returned")
|
||||||
|
this._defaultMessageLoader = fn;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
async refresh(){
|
||||||
|
try{
|
||||||
|
let requests = this._scopes.map(scope=>scope.refresh());
|
||||||
|
if(Promise.allSettled){
|
||||||
|
await Promise.allSettled(requests);
|
||||||
|
}else {
|
||||||
|
await Promise.all(requests);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtime ={
|
var runtime ={
|
||||||
|
125
packages/runtime/dist/runtime.mjs
vendored
125
packages/runtime/dist/runtime.mjs
vendored
@ -3,7 +3,7 @@
|
|||||||
* @param {*} obj
|
* @param {*} obj
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function isPlainObject$1(obj){
|
function isPlainObject$2(obj){
|
||||||
if (typeof obj !== 'object' || obj === null) return false;
|
if (typeof obj !== 'object' || obj === null) return false;
|
||||||
var proto = Object.getPrototypeOf(obj);
|
var proto = Object.getPrototypeOf(obj);
|
||||||
if (proto === null) return true;
|
if (proto === null) return true;
|
||||||
@ -80,7 +80,7 @@ function deepMerge$1(toObj,formObj,options={}){
|
|||||||
|
|
||||||
|
|
||||||
var utils ={
|
var utils ={
|
||||||
isPlainObject: isPlainObject$1,
|
isPlainObject: isPlainObject$2,
|
||||||
isNumber: isNumber$1,
|
isNumber: isNumber$1,
|
||||||
deepMerge: deepMerge$1,
|
deepMerge: deepMerge$1,
|
||||||
getDataTypeName: getDataTypeName$1
|
getDataTypeName: getDataTypeName$1
|
||||||
@ -119,6 +119,8 @@ var eventemitter = class EventEmitter{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { isPlainObject: isPlainObject$1 } = utils;
|
||||||
|
|
||||||
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
const DataTypes$1 = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
||||||
|
|
||||||
var scope = class i18nScope {
|
var scope = class i18nScope {
|
||||||
@ -134,6 +136,7 @@ var scope = class i18nScope {
|
|||||||
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
this._formatters = options.formatters; // 当前作用域的格式化函数列表
|
||||||
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
this._loaders = options.loaders; // 异步加载语言文件的函数列表
|
||||||
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
|
this._patchMessages = {}; // 语言包补丁信息 {<language>:{....},<language>:{....}}
|
||||||
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
||||||
this.$cache={
|
this.$cache={
|
||||||
activeLanguage : null,
|
activeLanguage : null,
|
||||||
@ -151,6 +154,8 @@ var scope = class i18nScope {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.global = globalThis.VoerkaI18n;
|
this.global = globalThis.VoerkaI18n;
|
||||||
|
this._mergePatchedMessages();
|
||||||
|
this._patch(this._messages,newLanguage);
|
||||||
// 正在加载语言包标识
|
// 正在加载语言包标识
|
||||||
this._loading=false;
|
this._loading=false;
|
||||||
// 在全局注册作用域
|
// 在全局注册作用域
|
||||||
@ -170,6 +175,8 @@ var scope = class i18nScope {
|
|||||||
get idMap(){return this._idMap}
|
get idMap(){return this._idMap}
|
||||||
// 当前作用域的格式化函数列表
|
// 当前作用域的格式化函数列表
|
||||||
get formatters(){return this._formatters}
|
get formatters(){return this._formatters}
|
||||||
|
// 当前作用域支持的语言
|
||||||
|
get languages(){return this._languages}
|
||||||
// 异步加载语言文件的函数列表
|
// 异步加载语言文件的函数列表
|
||||||
get loaders(){return this._loaders}
|
get loaders(){return this._loaders}
|
||||||
// 引用全局VoerkaI18n配置,注册后自动引用
|
// 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
@ -193,6 +200,13 @@ var scope = class i18nScope {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
* @param {Function} 必须是异步函数或者是返回Promise
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
this.global.registerDefaultLoader(fn);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 回退到默认语言
|
* 回退到默认语言
|
||||||
*/
|
*/
|
||||||
@ -210,21 +224,89 @@ var scope = class i18nScope {
|
|||||||
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
||||||
if(newLanguage === this.defaultLanguage){
|
if(newLanguage === this.defaultLanguage){
|
||||||
this._messages = this._default;
|
this._messages = this._default;
|
||||||
|
await this._patch(this._messages,newLanguage); // 异步补丁
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
||||||
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
||||||
const loader = this.loaders[newLanguage];
|
let loader = this.loaders[newLanguage];
|
||||||
if(typeof(loader) === "function"){
|
|
||||||
try{
|
try{
|
||||||
|
if(typeof(loader) === "function"){
|
||||||
this._messages = (await loader()).default;
|
this._messages = (await loader()).default;
|
||||||
this._activeLanguage = newLanguage;
|
this._activeLanguage = newLanguage;
|
||||||
|
await this._patch(this._messages,newLanguage);
|
||||||
|
}else if(typeof(this.global.defaultMessageLoader) === "function"){// 如果该语言没有指定加载器,则使用全局配置的默认加载器
|
||||||
|
this._messages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
this._activeLanguage = newLanguage;
|
||||||
|
}else {
|
||||||
|
this._fallback();
|
||||||
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
|
||||||
this._fallback();
|
this._fallback();
|
||||||
}
|
}
|
||||||
}else {
|
}
|
||||||
this._fallback();
|
/**
|
||||||
|
* 当指定了默认语言包加载器后,会从服务加载语言补丁包来更新本地的语言包
|
||||||
|
*
|
||||||
|
* 补丁包会自动存储到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
* @param {*} newLanguage
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async _patch(messages,newLanguage){
|
||||||
|
if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return
|
||||||
|
try{
|
||||||
|
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this);
|
||||||
|
if(isPlainObject$1(pachedMessages)){
|
||||||
|
Object.assign(messages,pachedMessages);
|
||||||
|
this._savePatchedMessages(pachedMessages,newLanguage);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 从本地存储中读取语言包补丁合并到当前语言包中
|
||||||
|
*/
|
||||||
|
_mergePatchedMessages(){
|
||||||
|
let patchedMessages= this._getPatchedMessages(this.activeLanguage);
|
||||||
|
if(isPlainObject$1(patchedMessages)){
|
||||||
|
Object.assign(this._messages,patchedMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将读取的补丁包保存到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* 为什么要保存到本地的LocalStorage中?
|
||||||
|
*
|
||||||
|
* 因为默认语言是静态嵌入到源码中的,而加载语言包补丁是延后异步的,
|
||||||
|
* 当应用启动第一次就会渲染出来的是没有打过补丁的内容。
|
||||||
|
*
|
||||||
|
* - 如果还需要等待从服务器加载语言补丁合并后再渲染会影响速度
|
||||||
|
* - 如果不等待从服务器加载语言补丁就渲染,则会先显示未打补丁的内容,然后在打完补丁后再对应用进行重新渲染生效
|
||||||
|
* 这明显不是个好的方式
|
||||||
|
*
|
||||||
|
* 因此,采用的方式是:
|
||||||
|
* - 加载语言包补丁后,将之保存到到本地的LocalStorage中
|
||||||
|
* - 当应用加载时会查询是否存在补丁,如果存在就会合并渲染
|
||||||
|
* -
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
*/
|
||||||
|
_savePatchedMessages(messages,language){
|
||||||
|
try{
|
||||||
|
if(globalThis.localStorage){
|
||||||
|
globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages));
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
console.error("Error while save voerkai18n patched messages:",e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_getPatchedMessages(language){
|
||||||
|
try{
|
||||||
|
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`))
|
||||||
|
}catch(e){
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 以下方法引用全局VoerkaI18n实例的方法
|
// 以下方法引用全局VoerkaI18n实例的方法
|
||||||
@ -839,6 +921,7 @@ function translate(message) {
|
|||||||
I18nManager.instance = this;
|
I18nManager.instance = this;
|
||||||
this._settings = deepMerge(defaultLanguageSettings,settings);
|
this._settings = deepMerge(defaultLanguageSettings,settings);
|
||||||
this._scopes=[];
|
this._scopes=[];
|
||||||
|
this._defaultMessageLoader = null; // 默认文本加载器
|
||||||
return I18nManager.instance;
|
return I18nManager.instance;
|
||||||
}
|
}
|
||||||
get settings(){ return this._settings }
|
get settings(){ return this._settings }
|
||||||
@ -851,17 +934,22 @@ function translate(message) {
|
|||||||
get languages(){ return this._settings.languages}
|
get languages(){ return this._settings.languages}
|
||||||
// 内置格式化器
|
// 内置格式化器
|
||||||
get formatters(){ return inlineFormatters }
|
get formatters(){ return inlineFormatters }
|
||||||
|
get defaultMessageLoader(){ return this._defaultMessageLoader}
|
||||||
|
// 通过默认加载器加载文件
|
||||||
|
async loadMessagesFromDefaultLoader(newLanguage,scope){
|
||||||
|
if(typeof(this._defaultMessageLoader) != "function") return //throw new Error("No default message loader specified")
|
||||||
|
return await this._defaultMessageLoader.call(scope,newLanguage,scope)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 切换语言
|
* 切换语言
|
||||||
*/
|
*/
|
||||||
async change(value){
|
async change(value){
|
||||||
value=value.trim();
|
value=value.trim();
|
||||||
if(this.languages.findIndex(lang=>lang.name === value)!==-1){
|
if(this.languages.findIndex(lang=>lang.name === value)!==-1 || typeof(this._defaultMessageLoader)==="function"){
|
||||||
// 通知所有作用域刷新到对应的语言包
|
// 通知所有作用域刷新到对应的语言包
|
||||||
await this._refreshScopes(value);
|
await this._refreshScopes(value);
|
||||||
this._settings.activeLanguage = value;
|
this._settings.activeLanguage = value;
|
||||||
/// 触发语言切换事件
|
await this.emit(value); /// 触发语言切换事件
|
||||||
await this.emit(value);
|
|
||||||
}else {
|
}else {
|
||||||
throw new Error("Not supported language:"+value)
|
throw new Error("Not supported language:"+value)
|
||||||
}
|
}
|
||||||
@ -921,6 +1009,25 @@ function translate(message) {
|
|||||||
this.formatters[language][name] = formatter;
|
this.formatters[language][name] = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
if(typeof(fn) !== 'function') throw new Error("The default loader must be a async function or promise returned")
|
||||||
|
this._defaultMessageLoader = fn;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
async refresh(){
|
||||||
|
try{
|
||||||
|
let requests = this._scopes.map(scope=>scope.refresh());
|
||||||
|
if(Promise.allSettled){
|
||||||
|
await Promise.allSettled(requests);
|
||||||
|
}else {
|
||||||
|
await Promise.all(requests);
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtime ={
|
var runtime ={
|
||||||
|
@ -567,6 +567,7 @@ function translate(message) {
|
|||||||
I18nManager.instance = this;
|
I18nManager.instance = this;
|
||||||
this._settings = deepMerge(defaultLanguageSettings,settings)
|
this._settings = deepMerge(defaultLanguageSettings,settings)
|
||||||
this._scopes=[]
|
this._scopes=[]
|
||||||
|
this._defaultMessageLoader = null // 默认文本加载器
|
||||||
return I18nManager.instance;
|
return I18nManager.instance;
|
||||||
}
|
}
|
||||||
get settings(){ return this._settings }
|
get settings(){ return this._settings }
|
||||||
@ -579,17 +580,22 @@ function translate(message) {
|
|||||||
get languages(){ return this._settings.languages}
|
get languages(){ return this._settings.languages}
|
||||||
// 内置格式化器
|
// 内置格式化器
|
||||||
get formatters(){ return inlineFormatters }
|
get formatters(){ return inlineFormatters }
|
||||||
|
get defaultMessageLoader(){ return this._defaultMessageLoader}
|
||||||
|
// 通过默认加载器加载文件
|
||||||
|
async loadMessagesFromDefaultLoader(newLanguage,scope){
|
||||||
|
if(typeof(this._defaultMessageLoader) != "function") return //throw new Error("No default message loader specified")
|
||||||
|
return await this._defaultMessageLoader.call(scope,newLanguage,scope)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 切换语言
|
* 切换语言
|
||||||
*/
|
*/
|
||||||
async change(value){
|
async change(value){
|
||||||
value=value.trim()
|
value=value.trim()
|
||||||
if(this.languages.findIndex(lang=>lang.name === value)!==-1){
|
if(this.languages.findIndex(lang=>lang.name === value)!==-1 || typeof(this._defaultMessageLoader)==="function"){
|
||||||
// 通知所有作用域刷新到对应的语言包
|
// 通知所有作用域刷新到对应的语言包
|
||||||
await this._refreshScopes(value)
|
await this._refreshScopes(value)
|
||||||
this._settings.activeLanguage = value
|
this._settings.activeLanguage = value
|
||||||
/// 触发语言切换事件
|
await this.emit(value) /// 触发语言切换事件
|
||||||
await this.emit(value)
|
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Not supported language:"+value)
|
throw new Error("Not supported language:"+value)
|
||||||
}
|
}
|
||||||
@ -649,6 +655,25 @@ function translate(message) {
|
|||||||
this.formatters[language][name] = formatter
|
this.formatters[language][name] = formatter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
if(typeof(fn) !== 'function') throw new Error("The default loader must be a async function or promise returned")
|
||||||
|
this._defaultMessageLoader = fn
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
async refresh(){
|
||||||
|
try{
|
||||||
|
let requests = this._scopes.map(scope=>scope.refresh())
|
||||||
|
if(Promise.allSettled){
|
||||||
|
await Promise.allSettled(requests)
|
||||||
|
}else{
|
||||||
|
await Promise.all(requests)
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports ={
|
module.exports ={
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/runtime",
|
"name": "@voerkai18n/runtime",
|
||||||
"version": "1.0.25",
|
"version": "1.0.27",
|
||||||
"description": "Voerkai18n Runtime",
|
"description": "核心运行时",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "dist/index.esm.js",
|
"module": "dist/index.esm.js",
|
||||||
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
"homepage": "https://gitee.com/zhangfisher/voerka-i18n",
|
||||||
@ -35,5 +35,5 @@
|
|||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||||
},
|
},
|
||||||
"lastPublish": "2022-04-28T09:03:17+08:00"
|
"lastPublish": "2022-08-05T16:36:41+08:00"
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
|
const { isPlainObject } = require("./utils")
|
||||||
|
|
||||||
const DataTypes = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
const DataTypes = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"];
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ module.exports = class i18nScope {
|
|||||||
this._formatters = options.formatters // 当前作用域的格式化函数列表
|
this._formatters = options.formatters // 当前作用域的格式化函数列表
|
||||||
this._loaders = options.loaders // 异步加载语言文件的函数列表
|
this._loaders = options.loaders // 异步加载语言文件的函数列表
|
||||||
this._global = null // 引用全局VoerkaI18n配置,注册后自动引用
|
this._global = null // 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
|
this._patchMessages = {} // 语言包补丁信息 {<language>:{....},<language>:{....}}
|
||||||
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索
|
||||||
this.$cache={
|
this.$cache={
|
||||||
activeLanguage : null,
|
activeLanguage : null,
|
||||||
@ -32,6 +33,8 @@ module.exports = class i18nScope {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.global = globalThis.VoerkaI18n
|
this.global = globalThis.VoerkaI18n
|
||||||
|
this._mergePatchedMessages()
|
||||||
|
this._patch(this._messages,newLanguage)
|
||||||
// 正在加载语言包标识
|
// 正在加载语言包标识
|
||||||
this._loading=false
|
this._loading=false
|
||||||
// 在全局注册作用域
|
// 在全局注册作用域
|
||||||
@ -51,6 +54,8 @@ module.exports = class i18nScope {
|
|||||||
get idMap(){return this._idMap}
|
get idMap(){return this._idMap}
|
||||||
// 当前作用域的格式化函数列表
|
// 当前作用域的格式化函数列表
|
||||||
get formatters(){return this._formatters}
|
get formatters(){return this._formatters}
|
||||||
|
// 当前作用域支持的语言
|
||||||
|
get languages(){return this._languages}
|
||||||
// 异步加载语言文件的函数列表
|
// 异步加载语言文件的函数列表
|
||||||
get loaders(){return this._loaders}
|
get loaders(){return this._loaders}
|
||||||
// 引用全局VoerkaI18n配置,注册后自动引用
|
// 引用全局VoerkaI18n配置,注册后自动引用
|
||||||
@ -74,6 +79,13 @@ module.exports = class i18nScope {
|
|||||||
this.formatters[language][name] = formatter
|
this.formatters[language][name] = formatter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 注册默认文本信息加载器
|
||||||
|
* @param {Function} 必须是异步函数或者是返回Promise
|
||||||
|
*/
|
||||||
|
registerDefaultLoader(fn){
|
||||||
|
this.global.registerDefaultLoader(fn)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 回退到默认语言
|
* 回退到默认语言
|
||||||
*/
|
*/
|
||||||
@ -91,21 +103,89 @@ module.exports = class i18nScope {
|
|||||||
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可
|
||||||
if(newLanguage === this.defaultLanguage){
|
if(newLanguage === this.defaultLanguage){
|
||||||
this._messages = this._default
|
this._messages = this._default
|
||||||
|
await this._patch(this._messages,newLanguage) // 异步补丁
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数
|
||||||
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
// 如果没有加载器,则无法加载语言包,因此回退到默认语言
|
||||||
const loader = this.loaders[newLanguage]
|
let loader = this.loaders[newLanguage]
|
||||||
if(typeof(loader) === "function"){
|
|
||||||
try{
|
try{
|
||||||
|
if(typeof(loader) === "function"){
|
||||||
this._messages = (await loader()).default
|
this._messages = (await loader()).default
|
||||||
this._activeLanguage = newLanguage
|
this._activeLanguage = newLanguage
|
||||||
|
await this._patch(this._messages,newLanguage)
|
||||||
|
}else if(typeof(this.global.defaultMessageLoader) === "function"){// 如果该语言没有指定加载器,则使用全局配置的默认加载器
|
||||||
|
this._messages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this)
|
||||||
|
this._activeLanguage = newLanguage
|
||||||
|
}else{
|
||||||
|
this._fallback()
|
||||||
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`)
|
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`)
|
||||||
this._fallback()
|
this._fallback()
|
||||||
}
|
}
|
||||||
}else{
|
}
|
||||||
this._fallback()
|
/**
|
||||||
|
* 当指定了默认语言包加载器后,会从服务加载语言补丁包来更新本地的语言包
|
||||||
|
*
|
||||||
|
* 补丁包会自动存储到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
* @param {*} newLanguage
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async _patch(messages,newLanguage){
|
||||||
|
if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return
|
||||||
|
try{
|
||||||
|
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this)
|
||||||
|
if(isPlainObject(pachedMessages)){
|
||||||
|
Object.assign(messages,pachedMessages)
|
||||||
|
this._savePatchedMessages(pachedMessages,newLanguage)
|
||||||
|
}
|
||||||
|
}catch{}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 从本地存储中读取语言包补丁合并到当前语言包中
|
||||||
|
*/
|
||||||
|
_mergePatchedMessages(){
|
||||||
|
let patchedMessages= this._getPatchedMessages(this.activeLanguage)
|
||||||
|
if(isPlainObject(patchedMessages)){
|
||||||
|
Object.assign(this._messages,patchedMessages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将读取的补丁包保存到本地的LocalStorage中
|
||||||
|
*
|
||||||
|
* 为什么要保存到本地的LocalStorage中?
|
||||||
|
*
|
||||||
|
* 因为默认语言是静态嵌入到源码中的,而加载语言包补丁是延后异步的,
|
||||||
|
* 当应用启动第一次就会渲染出来的是没有打过补丁的内容。
|
||||||
|
*
|
||||||
|
* - 如果还需要等待从服务器加载语言补丁合并后再渲染会影响速度
|
||||||
|
* - 如果不等待从服务器加载语言补丁就渲染,则会先显示未打补丁的内容,然后在打完补丁后再对应用进行重新渲染生效
|
||||||
|
* 这明显不是个好的方式
|
||||||
|
*
|
||||||
|
* 因此,采用的方式是:
|
||||||
|
* - 加载语言包补丁后,将之保存到到本地的LocalStorage中
|
||||||
|
* - 当应用加载时会查询是否存在补丁,如果存在就会合并渲染
|
||||||
|
* -
|
||||||
|
*
|
||||||
|
* @param {*} messages
|
||||||
|
*/
|
||||||
|
_savePatchedMessages(messages,language){
|
||||||
|
try{
|
||||||
|
if(globalThis.localStorage){
|
||||||
|
globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages));
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
console.error("Error while save voerkai18n patched messages:",e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_getPatchedMessages(language){
|
||||||
|
try{
|
||||||
|
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`))
|
||||||
|
}catch(e){
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 以下方法引用全局VoerkaI18n实例的方法
|
// 以下方法引用全局VoerkaI18n实例的方法
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/utils",
|
"name": "@voerkai18n/utils",
|
||||||
"version": "1.0.11",
|
"version": "1.0.12",
|
||||||
"description": "",
|
"description": "公共工具库",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
@ -16,5 +16,5 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||||
},
|
},
|
||||||
"lastPublish": "2022-04-28T09:02:51+08:00"
|
"lastPublish": "2022-08-05T16:45:29+08:00"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/vite",
|
"name": "@voerkai18n/vite",
|
||||||
"version": "1.0.11",
|
"version": "1.0.12",
|
||||||
"description": "VoerkaI18n plugin for Vite",
|
"description": "Vite插件,提供自动插入翻译函数和文本映射等功能",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
@ -15,5 +15,5 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
"@voerkai18n/autopublish": "workspace:^1.0.2"
|
||||||
},
|
},
|
||||||
"lastPublish": "2022-04-28T09:03:52+08:00"
|
"lastPublish": "2022-08-05T16:46:10+08:00"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@voerkai18n/vue",
|
"name": "@voerkai18n/vue",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"description": "",
|
"description": "Vue3插件,提供自动插件翻译函数和语言切换功能",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user