add formatters unit test
This commit is contained in:
parent
73794587c3
commit
139e2e2282
@ -70,7 +70,7 @@ export default defineConfig({
|
|||||||
"/guide/advanced/langpack.md",
|
"/guide/advanced/langpack.md",
|
||||||
"/guide/advanced/autotranslate.md",
|
"/guide/advanced/autotranslate.md",
|
||||||
"/guide/advanced/framework.md",
|
"/guide/advanced/framework.md",
|
||||||
"/guide/advanced/remoteLoad.md",
|
"/guide/advanced/dynamic-add.md",
|
||||||
"/guide/advanced/lngpatch.md",
|
"/guide/advanced/lngpatch.md",
|
||||||
"/guide/advanced/langedit.md"
|
"/guide/advanced/langedit.md"
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# 远程加载语言包
|
# 动态增加语言支持
|
||||||
|
|
||||||
## 前言
|
## 前言
|
||||||
`voerkaI18n`默认将要翻译的文本内容经编译后保存在当`languages`文件夹下,当打包应用时会与工程一起进行打包进工程源码中。这会带来以下问题:
|
`voerkaI18n`默认将要翻译的文本内容经编译后保存在当`languages`文件夹下,当打包应用时会与工程一起进行打包进工程源码中。这会带来以下问题:
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
### 准备
|
### 准备
|
||||||
|
|
||||||
为说明如何从远程加载语言包,我们将假设以下的应用:
|
为说明如何利用远程加载语言包的机制为应用动态增加语言支持,我们将假设以下的应用:
|
||||||
应用`chat`,依赖于`user`、`manager`、`log`等三个库,均使用了`voerkiai18n`作为多语言解决方案
|
应用`chat`,依赖于`user`、`manager`、`log`等三个库,均使用了`voerkiai18n`作为多语言解决方案
|
||||||
当执行完`voerkai18n compile`后,项目结构大概如下:
|
当执行完`voerkai18n compile`后,项目结构大概如下:
|
||||||
```javascript | pure
|
```javascript | pure
|
||||||
@ -95,14 +95,6 @@ i18nScope.registerDefaultLoader(async (language,scope)=>{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**重点:为什么要向服务器传递`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`、`en`两种语言,当应用要切换到`de`语言时,那么不仅是`A`应用本身需要切换到`de`语言,所依赖的库也需要切换到`de`语言。但是库`X`、`Y`、`Z`本身可能支持`de`语言,也可能不支持。如果不支持,则同样需要向服务器请求该库的翻译语言。因此,在向服务器请求时就需要带上`scope.id`,这样服务器就可以分别为应用`A`和依赖库`X`、`Y`、`Z`均准备对应的语言包了。
|
|
||||||
|
|
||||||
|
|
||||||
### 第三步:将语言包文件保存在服务器
|
### 第三步:将语言包文件保存在服务器
|
||||||
|
|
||||||
在上一步中,我们通过`fetch(/languages/${scope.id}/${language}.json)`来传递读取语言包(您可以使用任意您喜欢的方式,如`axios`),这意味着我们需要在web服务器上根据此`URL`来组织语言包,以便可以下载到语言包。比如可以这样组织:
|
在上一步中,我们通过`fetch(/languages/${scope.id}/${language}.json)`来传递读取语言包(您可以使用任意您喜欢的方式,如`axios`),这意味着我们需要在web服务器上根据此`URL`来组织语言包,以便可以下载到语言包。比如可以这样组织:
|
||||||
@ -192,8 +184,8 @@ webroot
|
|||||||
语言加载器时会传入两个参数:
|
语言加载器时会传入两个参数:
|
||||||
| 参数 | 说明 |
|
| 参数 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| language | 要切换的此语言|
|
| **language** | 要切换的此语言|
|
||||||
| scope |语言作用域实例,其中`scope.id`值默认等于`package.json`中的`name`字段。详见[参考](../../reference/i18nscope)。 |
|
| **scope** |语言作用域实例,其中`scope.id`值默认等于`package.json`中的`name`字段。详见[参考](../../reference/i18nscope)。 |
|
||||||
|
|
||||||
- 典型的语言加载器非常简单,如下:
|
- 典型的语言加载器非常简单,如下:
|
||||||
```javascript | pure
|
```javascript | pure
|
||||||
@ -202,8 +194,9 @@ i18nScope.registerDefaultLoader(async (language,scope)=>{
|
|||||||
return await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
|
return await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
- 为什么要应用自己编写语言加载器,而不是提供约定开箱即用?
|
- 为什么要应用自己编写语言加载器,而不是提供开箱即用的功能?
|
||||||
主要原因是编写语言加载器很简单,但是如何组织在服务器上的保存,想让应用开发者自行决定。比如,开发者完全可以将语言包保存在数据库中等。 另外考虑安全、兼容性等原因,因此`voerkaI18n`就将此交由开发者自行编写。
|
主要原因是编写语言加载器很简单,只是简单地使用HTTP从服务器上读取JSON语言包文件,不存在任何难度,甚至您可以直接使用上面的例子即可。
|
||||||
|
而关键是语言包在服务器上的如何组织与保存,可以让应用开发者自行决定。比如,开发者完全可以将语言包保存在数据库表中,以便能扩展其他功能。另外考虑安全、兼容性等原因,因此`voerkaI18n`就将此交由开发者自行编写。
|
||||||
|
|
||||||
|
|
||||||
### 编写语言切换界面
|
### 编写语言切换界面
|
||||||
@ -242,7 +235,46 @@ i18nScope.registerDefaultLoader(async (language,scope)=>{
|
|||||||
```
|
```
|
||||||
通过编写合适的语言切换界面,您可以在后期随时在线增加语种支持。
|
通过编写合适的语言切换界面,您可以在后期随时在线增加语种支持。
|
||||||
|
|
||||||
### 关于语言包补丁
|
### `scope.id`参数
|
||||||
语言包补丁仅对在`settings.json`配置的语言起作用,而动态增加的语种因为其语言包本身就保存在服务器,因此就不存在补丁的问题。
|
|
||||||
语言包补丁会在加载时自动合并到源码中的语言包,并且会自动在本地`localStorage`中缓存。
|
**重点:为什么要向服务器传递`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`、`en`两种语言,当应用要切换到`de`语言时,那么不仅是`A`应用本身需要切换到`de`语言,所依赖的库也需要切换到`de`语言。但是库`X`、`Y`、`Z`本身可能支持`de`语言,也可能不支持。如果不支持,则同样需要向服务器请求该库的翻译语言。因此,在向服务器请求时就需要带上`scope.id`,这样服务器就可以分别为应用`A`和依赖库`X`、`Y`、`Z`均准备对应的语言包了。
|
||||||
|
|
||||||
|
**按此机制,如果您的应用使用了任何第三方库,只要第三方库也是使用voerkai18n作为多语言解决方案,那么不需要原开发者支持,您自已就可以为之`增加语言支持`或者`打语言包补丁`。**
|
||||||
|
|
||||||
|
|
||||||
|
### 缓存语言包
|
||||||
|
|
||||||
|
当切换到动态增加的语言时会从远程服务器加载语言包,取决于语言包的大小,可能会产生延迟,这可能对用户体验造成不良影响。因此,您可以在客户端对语言包进行缓存。
|
||||||
|
|
||||||
|
```javascript | pure
|
||||||
|
import { i18nScope } from "./languages"
|
||||||
|
|
||||||
|
async function loadLanguageMessages(language,scope){
|
||||||
|
let messages = await (await fetch(`/languages/${scope.id}/${language}.json`)).json()
|
||||||
|
localStorage.setItem(`voerkai18n_${scope.id}_${language}_messages`,JSON.stringify(messages));
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
i18nScope.registerDefaultLoader(async (language,scope)=>{
|
||||||
|
let message = localStorage.getItem(`voerkai18n_${scope.id}_${language}_messages`);
|
||||||
|
if(messages){
|
||||||
|
setTimeout(async ()=>{
|
||||||
|
const messages = loadLanguageMessages(language,scope)
|
||||||
|
scope.refresh()
|
||||||
|
},0)
|
||||||
|
}else{
|
||||||
|
messages = loadLanguageMessages(language,scope)
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5,4 +5,9 @@
|
|||||||
|
|
||||||
**基本思路是,应用上线后发现翻译错误时,可以在服务器上约定位置放置语言包补丁,应用会自动进行更新修复,很实用的一个特性。**
|
**基本思路是,应用上线后发现翻译错误时,可以在服务器上约定位置放置语言包补丁,应用会自动进行更新修复,很实用的一个特性。**
|
||||||
|
|
||||||
|
### 关于语言包补丁
|
||||||
|
语言包补丁仅对在`settings.json`配置的语言起作用,而动态增加的语种因为其语言包本身就保存在服务器,因此就不存在补丁的问题。
|
||||||
|
语言包补丁会在加载时自动合并到源码中的语言包,并且会自动在本地`localStorage`中缓存。
|
||||||
|
|
||||||
|
|
||||||
使用方法详见`动态加载语言包`介绍。
|
使用方法详见`动态加载语言包`介绍。
|
||||||
|
@ -25,11 +25,11 @@ console.log(t("中华人民共和国成立于{}",1949))
|
|||||||
`t`翻译函数是从`myapp/languages/index.js`文件导出的翻译函数,但是现在`myapp/languages`还不存在,后续会使用工具自动生成。`voerkai18n`后续会使用正则表达式对提取要翻译的文本。
|
`t`翻译函数是从`myapp/languages/index.js`文件导出的翻译函数,但是现在`myapp/languages`还不存在,后续会使用工具自动生成。`voerkai18n`后续会使用正则表达式对提取要翻译的文本。
|
||||||
|
|
||||||
## 第一步:安装命令行工具
|
## 第一步:安装命令行工具
|
||||||
|
安装`@voerkai18n/cli`到全局。
|
||||||
```shell
|
```shell
|
||||||
> npm install -g @voerkai18n/cli
|
> npm install -g @voerkai18n/cli
|
||||||
> yarn global add @voerkai18n/cli
|
> yarn global add @voerkai18n/cli
|
||||||
>pnpm add -g @voerkai18/cli
|
> pnpm add -g @voerkai18/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
## 第二步:初始化工程
|
## 第二步:初始化工程
|
||||||
@ -72,10 +72,25 @@ console.log(t("中华人民共和国成立于{}",1949))
|
|||||||
|
|
||||||
- `voerkai18n init`是可选的,`voerkai18n extract`也可以实现相同的功能。
|
- `voerkai18n init`是可选的,`voerkai18n extract`也可以实现相同的功能。
|
||||||
- 一般情况下,您可以手工修改`settings.json`,如定义名称空间。
|
- 一般情况下,您可以手工修改`settings.json`,如定义名称空间。
|
||||||
|
- `voerkai18n init`仅仅是创建`languages`文件,并且生成`settings.json`,因此您也可以自己手工创建。
|
||||||
|
|
||||||
## 第三步:提取文本
|
## 第三步:标识翻译内容
|
||||||
|
|
||||||
|
接下来在源码文件中,将所有需要翻译的内容使用`t`翻译函数进行包装,例如下:
|
||||||
|
```javascript | pure
|
||||||
|
import { t } from "<myapp>/languages"
|
||||||
|
// 不含插值变量
|
||||||
|
t("中华人民共和国")
|
||||||
|
// 位置插值变量
|
||||||
|
t("中华人民共和国{}","万岁")
|
||||||
|
t("中华人民共和国成立于{}年,首都{}",1949,"北京")
|
||||||
|
```
|
||||||
|
`t`翻译函数只是一个普通函数,您需要为之提供执行环境,关于`t`翻译函数的更多用法见[这里](../use/t.md)
|
||||||
|
|
||||||
|
## 第四步:提取文本
|
||||||
|
|
||||||
接下来我们使用`voerkai18n extract`命令来自动扫描工程源码文件中的需要的翻译的文本信息。
|
接下来我们使用`voerkai18n extract`命令来自动扫描工程源码文件中的需要的翻译的文本信息。
|
||||||
|
`voerkai18n extract`命令会使用正则表达式来提取`t("提取文本")`包装的文本。
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
myapp>voerkai18n extract
|
myapp>voerkai18n extract
|
||||||
@ -83,7 +98,7 @@ myapp>voerkai18n extract
|
|||||||
|
|
||||||
执行`voerkai18n extract`命令后,就会在`myapp/languages`通过生成`translates/default.json`、`settings.json`等相关文件。
|
执行`voerkai18n extract`命令后,就会在`myapp/languages`通过生成`translates/default.json`、`settings.json`等相关文件。
|
||||||
|
|
||||||
- **translates/default.json** : 该文件就是需要进行翻译的文本信息。
|
- **translates/default.json** : 该文件就是从当前工程扫描提取出来的需要进行翻译的文本信息。
|
||||||
|
|
||||||
- **settings.json**: 语言环境的基本配置信息,可以进行修改。
|
- **settings.json**: 语言环境的基本配置信息,可以进行修改。
|
||||||
|
|
||||||
@ -100,7 +115,7 @@ myapp
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**如果略过第一步中的`voerkai18n init`,也可以使用以下命令来为创建和更新`settinbgs.json`**
|
**如果略过第一步中的`voerkai18n init`,也可以使用以下命令来为创建和更新`settings.json`**
|
||||||
|
|
||||||
```javascript | pure
|
```javascript | pure
|
||||||
myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
||||||
@ -112,9 +127,9 @@ myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
|||||||
- 计划支持`zh`、`en`、`de`、`jp`四种语言
|
- 计划支持`zh`、`en`、`de`、`jp`四种语言
|
||||||
- 默认语言是中文。(指在源码文件中我们直接使用中文即可)
|
- 默认语言是中文。(指在源码文件中我们直接使用中文即可)
|
||||||
- 激活语言是中文(即默认切换到中文)
|
- 激活语言是中文(即默认切换到中文)
|
||||||
- `-D`代表显示扫描调试信息
|
- `-D`代表显示扫描调试信息,可以显示从哪些文件提供哪些文本
|
||||||
|
|
||||||
## 第四步:翻译文本
|
## 第五步:翻译文本
|
||||||
|
|
||||||
接下来就可以分别对`language/translates`文件夹下的所有`JSON`文件进行翻译了。每个`JSON`文件大概如下:
|
接下来就可以分别对`language/translates`文件夹下的所有`JSON`文件进行翻译了。每个`JSON`文件大概如下:
|
||||||
|
|
||||||
@ -122,13 +137,13 @@ myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
|||||||
{
|
{
|
||||||
"中华人民共和国万岁":{
|
"中华人民共和国万岁":{
|
||||||
"en":"<在此编写对应的英文翻译内容>",
|
"en":"<在此编写对应的英文翻译内容>",
|
||||||
"de":"<在此编写对应的德文翻译内容>"
|
"de":"<在此编写对应的德文翻译内容>",
|
||||||
"jp":"<在此编写对应的日文翻译内容>",
|
"jp":"<在此编写对应的日文翻译内容>",
|
||||||
"$files":["index.js"] // 记录了该信息是从哪几个文件中提取的
|
"$files":["index.js"] // 记录了该信息是从哪几个文件中提取的
|
||||||
},
|
},
|
||||||
"中华人民共和国成立于{}":{
|
"中华人民共和国成立于{}":{
|
||||||
"en":"<在此编写对应的英文翻译内容>",
|
"en":"<在此编写对应的英文翻译内容>",
|
||||||
"de":"<在此编写对应的德文翻译内容>"
|
"de":"<在此编写对应的德文翻译内容>",
|
||||||
"jp":"<在此编写对应的日文翻译内容>",
|
"jp":"<在此编写对应的日文翻译内容>",
|
||||||
"$files":["index.js"]
|
"$files":["index.js"]
|
||||||
}
|
}
|
||||||
@ -143,7 +158,7 @@ myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
|||||||
- 如果文本内容在源代码中已修改了,则会视为新增加的内容。
|
- 如果文本内容在源代码中已修改了,则会视为新增加的内容。
|
||||||
- 如果文本内容已经翻译了一部份了,则会保留已翻译的内容。
|
- 如果文本内容已经翻译了一部份了,则会保留已翻译的内容。
|
||||||
|
|
||||||
因此,反复执行`voerkai18n extract`命令是安全的,不会导致进行了一半的翻译内容丢失,可以放心执行。
|
总之,反复执行`voerkai18n extract`命令是安全的,不会导致进行了一半的翻译内容丢失,可以放心执行。
|
||||||
|
|
||||||
大部分国际化解决方案至此就需要交给人工进行翻译了,但是`voerkai18n`除了手动翻译外,通过`voerkai18n translate`命令来实现**调用在线翻译服务**进行自动翻译。
|
大部分国际化解决方案至此就需要交给人工进行翻译了,但是`voerkai18n`除了手动翻译外,通过`voerkai18n translate`命令来实现**调用在线翻译服务**进行自动翻译。
|
||||||
|
|
||||||
@ -151,9 +166,9 @@ myapp>voerkai18n extract -D -lngs zh en de jp -d zh -a zh
|
|||||||
>voerkai18n translate --provider baidu --appkey <在百度翻译上申请的密钥> --appid <在百度翻译上申请的appid>
|
>voerkai18n translate --provider baidu --appkey <在百度翻译上申请的密钥> --appid <在百度翻译上申请的appid>
|
||||||
```
|
```
|
||||||
|
|
||||||
在项目文件夹下执行上面的语句,将会自动调用百度的在线翻译API进行翻译,以现在的翻译水平而言,您只需要进行少量的微调即可。关于`voerkai18n translate`命令的使用请查阅后续介绍。
|
在项目文件夹下执行上面的语句,将会自动调用`百度的在线翻译API`进行翻译,以现在的翻译水平而言,您只需要进行少量的微调即可。关于`voerkai18n translate`命令的使用请查阅后续介绍。
|
||||||
|
|
||||||
## 第五步:编译语言包
|
## 第六步:编译语言包
|
||||||
|
|
||||||
当我们完成`myapp/languages/translates`下的所有`JSON语言文件`的翻译后(如果配置了名称空间后,每一个名称空间会对应生成一个文件,详见后续`名称空间`介绍),接下来需要对翻译后的文件进行编译。
|
当我们完成`myapp/languages/translates`下的所有`JSON语言文件`的翻译后(如果配置了名称空间后,每一个名称空间会对应生成一个文件,详见后续`名称空间`介绍),接下来需要对翻译后的文件进行编译。
|
||||||
|
|
||||||
@ -180,7 +195,7 @@ myapp> voerkai18n compile
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 第六步:导入翻译函数
|
## 第七步:导入翻译函数
|
||||||
|
|
||||||
第一步中我们在源文件中直接使用了`t`翻译函数包装要翻译的文本信息,该`t`翻译函数就是在编译环节自动生成并声明在`myapp/languages/index.js`中的。
|
第一步中我们在源文件中直接使用了`t`翻译函数包装要翻译的文本信息,该`t`翻译函数就是在编译环节自动生成并声明在`myapp/languages/index.js`中的。
|
||||||
|
|
||||||
@ -192,7 +207,7 @@ import { t } from "./languages"
|
|||||||
|
|
||||||
但是如果源码文件很多,重次重复导入`t`函数也是比较麻烦的,所以我们也提供了一个`babel/vite`等插件来自动导入`t`函数。
|
但是如果源码文件很多,重次重复导入`t`函数也是比较麻烦的,所以我们也提供了一个`babel/vite`等插件来自动导入`t`函数。
|
||||||
|
|
||||||
## 第六步:切换语言
|
## 第八步:切换语言
|
||||||
|
|
||||||
当需要切换语言时,可以通过调用`change`方法来切换语言。
|
当需要切换语言时,可以通过调用`change`方法来切换语言。
|
||||||
|
|
||||||
@ -201,7 +216,7 @@ import { i18nScope } from "./languages"
|
|||||||
|
|
||||||
// 切换到英文
|
// 切换到英文
|
||||||
await i18nScope.change("en")
|
await i18nScope.change("en")
|
||||||
// VoerkaI18n是一个全局单例,可以直接访问
|
// 或者VoerkaI18n是一个全局单例,可以直接访问
|
||||||
await VoerkaI18n.change("en")
|
await VoerkaI18n.change("en")
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -214,15 +229,19 @@ import { i18nScope } from "./languages"
|
|||||||
|
|
||||||
// 切换到英文
|
// 切换到英文
|
||||||
i18nScope.on((newLanguage)=>{
|
i18nScope.on((newLanguage)=>{
|
||||||
|
// 在此重新渲染界面
|
||||||
...
|
...
|
||||||
|
|
||||||
})
|
})
|
||||||
//
|
//
|
||||||
VoerkaI18n.on((newLanguage)=>{
|
VoerkaI18n.on((newLanguage)=>{
|
||||||
|
// 在此重新渲染界面
|
||||||
...
|
...
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
[@voerkai18n/vue](../tools/vue.md)和[@voerkai18n/react](../use/react.md)提供了相对应的插件和库来简化重新界面更新渲染。
|
||||||
|
|
||||||
## 第七步:语言包补丁
|
## 第九步:语言包补丁
|
||||||
|
|
||||||
一般情况下,多语言的工程化过程就结束了,`voerkai18n`在多语言实践考虑得更加人性化。有没有经常发现这样的情况,当项目上线后,才发现:
|
一般情况下,多语言的工程化过程就结束了,`voerkai18n`在多语言实践考虑得更加人性化。有没有经常发现这样的情况,当项目上线后,才发现:
|
||||||
- 翻译有误
|
- 翻译有误
|
||||||
@ -230,10 +249,11 @@ VoerkaI18n.on((newLanguage)=>{
|
|||||||
- 临时要增加支持一种语言
|
- 临时要增加支持一种语言
|
||||||
|
|
||||||
一般碰到这种情况,只好重新打包构建工程,重新发布,整个过程繁琐而麻烦。
|
一般碰到这种情况,只好重新打包构建工程,重新发布,整个过程繁琐而麻烦。
|
||||||
现在`voerkai18n`针对此问题提供了完美的解决方案,可以通过服务器来为应用打语言包补丁和增加语言支持,而不需要重新打包应用和修改应用。
|
现在`voerkai18n`针对此问题提供了完美的解决方案,可以通过服务器来为应用`打语言包补丁`和`动态增加语言`支持,而不需要重新打包应用和修改应用。
|
||||||
方法如下:
|
|
||||||
|
|
||||||
1. 注册一个默认的语言包加载器函数,用来从服务器加载语言包文件
|
**方法如下:**
|
||||||
|
|
||||||
|
1. 注册一个默认的语言包加载器函数,用来从服务器加载语言包文件。
|
||||||
```javascript | pure
|
```javascript | pure
|
||||||
import { i18nScope } from "./languages"
|
import { i18nScope } from "./languages"
|
||||||
|
|
||||||
@ -243,7 +263,7 @@ i18nScope.registerDefaultLoader(async (language,scope)=>{
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. 将语言包补丁文件保存在服务器上指定的位置`/languages/<应用名称>/<语言名称>.json`即可。
|
2. 将语言包补丁文件保存在服务器上指定的位置`/languages/<应用名称>/<语言名称>.json`即可。
|
||||||
3. 当应用启动后会自动从服务器上加载语言补丁包,从而实现动为语言包打补丁的功能。也可以实现动态增加临时支持一种语言的功能
|
3. 当应用启动后会自动从服务器上加载语言补丁包合并,从而实现动为语言包打补丁的功能。也可以实现动态增加临时支持一种语言的功能
|
||||||
|
|
||||||
更完整的说明详见[`动态加载语言包`](../advanced/remote-load.md)和[`语言包补丁`](../advanced/lngpatch.md)功能介绍。
|
更完整的说明详见[`动态加载语言包`](../advanced/remote-load.md)和[`语言包补丁`](../advanced/lngpatch.md)功能介绍。
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ hero:
|
|||||||
actions:
|
actions:
|
||||||
- text: 快速入门
|
- text: 快速入门
|
||||||
link: https://zhangfisher.github.io/voerka-i18n/guide/intro/get-started
|
link: https://zhangfisher.github.io/voerka-i18n/guide/intro/get-started
|
||||||
|
- text: 交流QQ群
|
||||||
|
link: https://qm.qq.com/cgi-bin/qm/qr?k=jKyZR9KupT9Ith5ZsulB-i04OaJDkCwe&jump_from=webapi
|
||||||
features:
|
features:
|
||||||
- title: 全流程支持
|
- title: 全流程支持
|
||||||
icon: images/flow.png
|
icon: images/flow.png
|
||||||
|
@ -5,17 +5,18 @@
|
|||||||
|
|
||||||
const { toDate,toCurrency,toNumber,formatDatetime,formatTime,Formatter } = require("../utils")
|
const { toDate,toCurrency,toNumber,formatDatetime,formatTime,Formatter } = require("../utils")
|
||||||
|
|
||||||
// 日期格式化器
|
|
||||||
// format取字符串"long","short","local","iso","gmt","utc"或者日期模块字符串
|
|
||||||
// { value | date } == '2022/8/15' 默认
|
/**
|
||||||
// { value | date('long') } == '2022/8/15 12:08:32'
|
* 日期格式化器
|
||||||
// { value | date('short') } == '8/15'
|
* format取值:
|
||||||
// { value | date('GMT') } == 'Mon, 15 Aug 2022 06:39:38 GMT'
|
* 0-local,1-long,2-short,3-iso,4-gmt,5-UTC
|
||||||
// { value | date('ISO') } == 'Mon, 15 Aug 2022 06:39:38 ISO'
|
* 或者日期模板字符串
|
||||||
// { value | date('YYYY-MM-DD HH:mm:ss') } == '2022-8-15 12:08:32'
|
* 默认值是local
|
||||||
|
*/
|
||||||
const dateFormatter = Formatter((value,format,$config)=>{
|
const dateFormatter = Formatter((value,format,$config)=>{
|
||||||
const optionals = ["long","short","local","iso","gmt","utc"]
|
const optionals = ["local","long","short","iso","gmt","utc"]
|
||||||
// 处理参数:支持大小写和数字0-long,1-short,2-local,3-iso,4-gmt,5-utc
|
// 处理参数:同时支持大小写名称和数字
|
||||||
const optionIndex = optionals.findIndex((v,i)=>{
|
const optionIndex = optionals.findIndex((v,i)=>{
|
||||||
if(typeof(format)=="string"){
|
if(typeof(format)=="string"){
|
||||||
return v==format || v== format.toUpperCase()
|
return v==format || v== format.toUpperCase()
|
||||||
@ -24,12 +25,12 @@ const dateFormatter = Formatter((value,format,$config)=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
switch(optionIndex){
|
switch(optionIndex){
|
||||||
case 0: // long
|
case 0: // local
|
||||||
return formatDatetime(value,$config.long)
|
|
||||||
case 1: // short
|
|
||||||
return formatDatetime(value,$config.short)
|
|
||||||
case 2: // local
|
|
||||||
return value.toLocaleString()
|
return value.toLocaleString()
|
||||||
|
case 1: // long
|
||||||
|
return formatDatetime(value,$config.long)
|
||||||
|
case 2: // short
|
||||||
|
return formatDatetime(value,$config.short)
|
||||||
case 3: // ISO
|
case 3: // ISO
|
||||||
return value.toISOString()
|
return value.toISOString()
|
||||||
case 4: // GMT
|
case 4: // GMT
|
||||||
@ -45,7 +46,7 @@ const dateFormatter = Formatter((value,format,$config)=>{
|
|||||||
configKey: "datetime.date"
|
configKey: "datetime.date"
|
||||||
})
|
})
|
||||||
// 季度格式化器 format= 0=短格式 1=长格式
|
// 季度格式化器 format= 0=短格式 1=长格式
|
||||||
const mquarterFormatter = Formatter((value,format,$config)=>{
|
const quarterFormatter = Formatter((value,format,$config)=>{
|
||||||
const month = value.getMonth() + 1
|
const month = value.getMonth() + 1
|
||||||
const quarter = Math.floor( ( month % 3 == 0 ? ( month / 3 ) : (month / 3 + 1 ) ))
|
const quarter = Math.floor( ( month % 3 == 0 ? ( month / 3 ) : (month / 3 + 1 ) ))
|
||||||
if(format<0 && format>1) format = 0
|
if(format<0 && format>1) format = 0
|
||||||
@ -70,7 +71,7 @@ const monthFormatter = Formatter((value,format,$config)=>{
|
|||||||
configKey: "datetime.month"
|
configKey: "datetime.month"
|
||||||
})
|
})
|
||||||
|
|
||||||
// 周格式化器 format可以取值0,1,2,也可以取字符串long,short,number
|
// 星期x格式化器 format可以取值0,1,2,也可以取字符串long,short,number
|
||||||
const weekdayFormatter = Formatter((value,format,$config)=>{
|
const weekdayFormatter = Formatter((value,format,$config)=>{
|
||||||
const day = value.getDay()
|
const day = value.getDay()
|
||||||
if(typeof(format)==='string'){
|
if(typeof(format)==='string'){
|
||||||
@ -85,10 +86,9 @@ const weekdayFormatter = Formatter((value,format,$config)=>{
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// 时间格式化器 format可以取值0,1,2,也可以取字符串long,short,timestamp,local
|
// 时间格式化器 format可以取值0-local(默认),1-long,2-short,3-timestamp,也可以是一个插值表达式
|
||||||
const timeFormatter = Formatter((value,format,$config)=>{
|
const timeFormatter = Formatter((value,format,$config)=>{
|
||||||
const month = value.getMonth()
|
const optionals = ['local','long','short','timestamp']
|
||||||
const optionals = ['long','short','timestamp','local'] //toLocaleTimeString
|
|
||||||
const optionIndex = optionals.findIndex((v,i)=>{
|
const optionIndex = optionals.findIndex((v,i)=>{
|
||||||
if(typeof(format)=="string"){
|
if(typeof(format)=="string"){
|
||||||
return v==format || v== format.toUpperCase()
|
return v==format || v== format.toUpperCase()
|
||||||
@ -97,21 +97,21 @@ const timeFormatter = Formatter((value,format,$config)=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
switch(optionIndex){
|
switch(optionIndex){
|
||||||
case 0: // long
|
case 0: // local : toLocaleTimeString
|
||||||
return formatTime(value,$config.long)
|
|
||||||
case 1: // short
|
|
||||||
return formatTime(value,$config.short)
|
|
||||||
case 2: // timestamp
|
|
||||||
return value.getTime()
|
|
||||||
case 3: // local
|
|
||||||
return value.toLocaleTimeString()
|
return value.toLocaleTimeString()
|
||||||
|
case 1: // long
|
||||||
|
return formatTime(value,$config.long)
|
||||||
|
case 2: // short
|
||||||
|
return formatTime(value,$config.short)
|
||||||
|
case 3: // timestamp
|
||||||
|
return value.getTime()
|
||||||
default:
|
default:
|
||||||
return formatTime(value,format)
|
return formatTime(value,format)
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
normalize: toDate,
|
normalize: toDate,
|
||||||
params : ['format'],
|
params : ['format'],
|
||||||
configKey: "datetime.month"
|
configKey: "datetime.time"
|
||||||
})
|
})
|
||||||
|
|
||||||
// 货币格式化器, CNY $13,456.00
|
// 货币格式化器, CNY $13,456.00
|
||||||
@ -132,8 +132,8 @@ module.exports = {
|
|||||||
units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
|
units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
|
||||||
date :{
|
date :{
|
||||||
long : 'YYYY/MM/DD HH:mm:ss',
|
long : 'YYYY/MM/DD HH:mm:ss',
|
||||||
short : "MM/DD",
|
short : "YYYY/MM/DD",
|
||||||
format : "long"
|
format : "local"
|
||||||
},
|
},
|
||||||
quarter : {
|
quarter : {
|
||||||
names : ["Q1","Q2","Q3","Q4"],
|
names : ["Q1","Q2","Q3","Q4"],
|
||||||
@ -156,11 +156,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
currency : {
|
currency : {
|
||||||
|
units : ["Thousands","Millions","Billions","Trillions"], //千,百万,十亿,万亿
|
||||||
|
default : "{symbol}{value}",
|
||||||
|
long : "{prefix} {symbol}{value}{suffix}",
|
||||||
|
short : "{symbol}{value}",
|
||||||
symbol : "$", // 符号
|
symbol : "$", // 符号
|
||||||
prefix : "", // 前缀
|
prefix : "", // 前缀
|
||||||
suffix : "", // 后缀
|
suffix : "", // 后缀
|
||||||
division : 3, // ,分割位
|
division : 3, // ,分割位
|
||||||
precision : 2, // 精度
|
precision : 2, // 精度
|
||||||
|
|
||||||
},
|
},
|
||||||
number : {
|
number : {
|
||||||
division : 3,
|
division : 3,
|
||||||
|
@ -13,8 +13,8 @@ module.exports = {
|
|||||||
units : CN_DATETIME_UNITS,
|
units : CN_DATETIME_UNITS,
|
||||||
date :{
|
date :{
|
||||||
long : 'YYYY年MM月DD日 HH点mm分ss秒',
|
long : 'YYYY年MM月DD日 HH点mm分ss秒',
|
||||||
short : "MM/DD",
|
short : "YYYY/MM/DD",
|
||||||
format : 'long'
|
format : 'local'
|
||||||
},
|
},
|
||||||
quarter : {
|
quarter : {
|
||||||
names : ["一季度","二季度","三季度","四季度"],
|
names : ["一季度","二季度","三季度","四季度"],
|
||||||
@ -34,10 +34,12 @@ module.exports = {
|
|||||||
time:{
|
time:{
|
||||||
long : "HH点mm分ss秒",
|
long : "HH点mm分ss秒",
|
||||||
short : "HH:mm:ss",
|
short : "HH:mm:ss",
|
||||||
|
format : 'local'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
currency : {
|
currency : {
|
||||||
|
units : ["万","亿","万亿","万万亿"]
|
||||||
symbol : "¥",
|
symbol : "¥",
|
||||||
prefix : "",
|
prefix : "",
|
||||||
suffix : "元",
|
suffix : "元",
|
||||||
|
@ -407,12 +407,14 @@ function wrapperFormatters(scope,activeLanguage,formatters){
|
|||||||
let wrappedFormatters = []
|
let wrappedFormatters = []
|
||||||
addDefaultFormatters(formatters)
|
addDefaultFormatters(formatters)
|
||||||
for(let [name,args] of formatters){
|
for(let [name,args] of formatters){
|
||||||
if(name){
|
|
||||||
let fn = getFormatter(scope,activeLanguage,name)
|
let fn = getFormatter(scope,activeLanguage,name)
|
||||||
|
let formatter
|
||||||
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
|
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
|
||||||
// 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用
|
// 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用
|
||||||
if(!isFunction(fn)){
|
if(isFunction(fn)){
|
||||||
fn = (value) =>{
|
formatter = (value,config) => fn.call(scope,value,...args,config)
|
||||||
|
}else{
|
||||||
|
formatter = (value) =>{
|
||||||
if(isFunction(value[name])){
|
if(isFunction(value[name])){
|
||||||
return value[name](...args)
|
return value[name](...args)
|
||||||
}else{
|
}else{
|
||||||
@ -420,9 +422,8 @@ function wrapperFormatters(scope,activeLanguage,formatters){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn.$name = name
|
formatter.$name = name
|
||||||
wrappedFormatters.push(fn)
|
wrappedFormatters.push(formatter)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return wrappedFormatters
|
return wrappedFormatters
|
||||||
}
|
}
|
||||||
|
@ -77,12 +77,12 @@ function deepMerge(toObj,formObj,options={}){
|
|||||||
if(key in results){
|
if(key in results){
|
||||||
if(typeof value === "object" && value !== null){
|
if(typeof value === "object" && value !== null){
|
||||||
if(Array.isArray(value)){
|
if(Array.isArray(value)){
|
||||||
if(options.array === 0){//替换
|
if(options.array === 1){//合并
|
||||||
results[key] = value
|
|
||||||
}else if(options.array === 1){//合并
|
|
||||||
results[key] = [...results[key],...value]
|
results[key] = [...results[key],...value]
|
||||||
}else if(options.array === 2){//去重合并
|
}else if(options.array === 2){//去重合并
|
||||||
results[key] = [...new Set([...results[key],...value])]
|
results[key] = [...new Set([...results[key],...value])]
|
||||||
|
}else{ //默认: 替换
|
||||||
|
results[key] = value
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
results[key] = deepMerge(results[key],value,options)
|
results[key] = deepMerge(results[key],value,options)
|
||||||
@ -259,8 +259,6 @@ function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss"){
|
|||||||
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
|
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
|
||||||
["s", second], // 0-59 秒
|
["s", second], // 0-59 秒
|
||||||
["SSS", millisecond], // 000-999 毫秒,三位数
|
["SSS", millisecond], // 000-999 毫秒,三位数
|
||||||
["SS", millisecond.substring(year.length - 2, year.length)], // 00-99 毫秒(十),两位数
|
|
||||||
["S",millisecond[millisecond.length - 1]], // 0-9 毫秒(百),一位数
|
|
||||||
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
|
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
|
||||||
["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写
|
["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写
|
||||||
]
|
]
|
||||||
@ -284,8 +282,6 @@ function formatTime(value,templ="HH:mm:ss"){
|
|||||||
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
|
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
|
||||||
["s", second], // 0-59 秒
|
["s", second], // 0-59 秒
|
||||||
["SSS", millisecond], // 000-999 毫秒,三位数
|
["SSS", millisecond], // 000-999 毫秒,三位数
|
||||||
["SS", millisecond.substring(year.length - 2, year.length)], // 00-99 毫秒(十),两位数
|
|
||||||
["S",millisecond[millisecond.length - 1]], // 0-9 毫秒(百),一位数
|
|
||||||
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
|
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
|
||||||
["a", hour > 12 ? "pm" : "am"] // am / pm 上/下午,小写
|
["a", hour > 12 ? "pm" : "am"] // am / pm 上/下午,小写
|
||||||
]
|
]
|
||||||
@ -381,8 +377,8 @@ function replaceAll(str,findValue,replaceValue){
|
|||||||
const formatterConfig =Object.assign({},defaultParams,getByPath(activeFormatterConfigs,opts.configKey,{}))
|
const formatterConfig =Object.assign({},defaultParams,getByPath(activeFormatterConfigs,opts.configKey,{}))
|
||||||
let finalArgs = opts.params.map(param=>getByPath(formatterConfig,param,undefined))
|
let finalArgs = opts.params.map(param=>getByPath(formatterConfig,param,undefined))
|
||||||
// 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数
|
// 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数
|
||||||
for(let i =0; i<finalArgs.length-1;i++){
|
for(let i =0; i<finalArgs.length;i++){
|
||||||
if(i>=args.length-1) break // 最后一参数是配置
|
if(i==args.length-1) break // 最后一参数是配置
|
||||||
if(args[i]!==undefined) finalArgs[i] = args[i]
|
if(args[i]!==undefined) finalArgs[i] = args[i]
|
||||||
}
|
}
|
||||||
return fn(finalValue,...finalArgs,formatterConfig)
|
return fn(finalValue,...finalArgs,formatterConfig)
|
||||||
|
@ -1,18 +1,292 @@
|
|||||||
const {i18nScope, translate, getInterpolatedVars } = require('../packages/runtime/dist/runtime.cjs')
|
const {i18nScope, translate, getInterpolatedVars } = require('../packages/runtime/dist/runtime.cjs')
|
||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
|
|
||||||
|
function toLanguageDict(values,startIndex=0){
|
||||||
|
return values.reduce((result,curValue,i)=>{
|
||||||
|
result[i+startIndex] = curValue;
|
||||||
|
return result
|
||||||
|
},{})
|
||||||
|
}
|
||||||
|
function toLanguageIdMap(values,startIndex=0){
|
||||||
|
return values.reduce((result,curValue,i)=>{
|
||||||
|
result[curValue] = i+startIndex
|
||||||
|
return result
|
||||||
|
},{})
|
||||||
|
}
|
||||||
|
function diffArray(arr1,arr2){
|
||||||
|
let diffs = []
|
||||||
|
arr1.forEach((v,i)=>{
|
||||||
|
if(v!=arr2[i]) diffs.push([i,[v,arr2[i]]])
|
||||||
|
})
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const NOW = new Date("2022/08/12 10:12:36")
|
||||||
|
|
||||||
|
const zhDatetimes =[
|
||||||
|
"现在是{ value }",
|
||||||
|
"现在是{ value | date }",
|
||||||
|
"现在是{ value | date('local') }",
|
||||||
|
"现在是{ value | date('long') }",
|
||||||
|
"现在是{ value | date('short') }",
|
||||||
|
"现在是{ value | date('iso') }",
|
||||||
|
"现在是{ value | date('gmt') }",
|
||||||
|
"现在是{ value | date('utc') }",
|
||||||
|
"现在是{ value | date(0) }", // local
|
||||||
|
"现在是{ value | date(1) }", // long
|
||||||
|
"现在是{ value | date(2) }", // short
|
||||||
|
"现在是{ value | date(3) }", // iso
|
||||||
|
"现在是{ value | date(4) }", // gmt
|
||||||
|
"现在是{ value | date(5) }", // utc
|
||||||
|
"现在是{ value | date('YYYY-MM-DD HH:mm:ss')}",
|
||||||
|
"现在是{ value | date('YYYY-MM-DD')}",
|
||||||
|
"现在是{ value | date('HH:mm:ss')}",
|
||||||
|
"现在是{ value | month }",
|
||||||
|
"现在是{ value | month('long')}",
|
||||||
|
"现在是{ value | month('short')}",
|
||||||
|
"现在是{ value | month('number')}",
|
||||||
|
"现在是{ value | month(0)}",
|
||||||
|
"现在是{ value | month(1)}",
|
||||||
|
"现在是{ value | month(2)}",
|
||||||
|
"现在是{ value | weekday }",
|
||||||
|
"现在是{ value | weekday('long')}",
|
||||||
|
"现在是{ value | weekday('short')}",
|
||||||
|
"现在是{ value | weekday('number')}",
|
||||||
|
"现在是{ value | weekday(0)}",
|
||||||
|
"现在是{ value | weekday(1)}",
|
||||||
|
"现在是{ value | weekday(2)}",
|
||||||
|
// 时间
|
||||||
|
"现在时间 - { value | time }",
|
||||||
|
"现在时间 - { value | time('local') }",
|
||||||
|
"现在时间 - { value | time('long') }",
|
||||||
|
"现在时间 - { value | time('short') }",
|
||||||
|
"现在时间 - { value | time('timestamp') }",
|
||||||
|
"现在时间 - { value | time(0) }",
|
||||||
|
"现在时间 - { value | time(1) }",
|
||||||
|
"现在时间 - { value | time(2) }",
|
||||||
|
"现在时间 - { value | time(3) }",
|
||||||
|
"现在时间 - { value | time('HH:mm:ss') }",
|
||||||
|
"现在时间 - { value | time('mm:ss') }",
|
||||||
|
"现在时间 - { value | time('ss') }"
|
||||||
|
]
|
||||||
|
//
|
||||||
|
|
||||||
|
const expectZhDatetimes =[
|
||||||
|
"现在是2022/8/12 10:12:36", // { value }
|
||||||
|
"现在是2022/8/12 10:12:36", // { value | date }
|
||||||
|
`现在是${NOW.toLocaleString()}`, // { value | date('local') }
|
||||||
|
"现在是2022年08月12日 10点12分36秒", // { value | date('long') }
|
||||||
|
"现在是2022/08/12", // { value | date('short') }
|
||||||
|
`现在是${NOW.toISOString()}`, // { value | date('iso') }
|
||||||
|
`现在是${NOW.toGMTString()}`, // { value | date('gmt') }
|
||||||
|
`现在是${NOW.toUTCString()}`, // { value | date('utc') }
|
||||||
|
`现在是${NOW.toLocaleString()}`, // { value | date(0) } // local
|
||||||
|
"现在是2022年08月12日 10点12分36秒", // { value | date(1) } // long
|
||||||
|
"现在是2022/08/12", // { value | date(2) } // short
|
||||||
|
`现在是${NOW.toISOString()}`, // { value | date(3) } // iso
|
||||||
|
`现在是${NOW.toGMTString()}`, // { value | date(4) } // gmt
|
||||||
|
`现在是${NOW.toUTCString()}`, // { value | date(5) } // utc
|
||||||
|
"现在是2022-08-12 10:12:36", // { value | date('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
"现在是2022-08-12", // { value | date('YYYY-MM-DD')}
|
||||||
|
"现在是10:12:36", // { value | date('HH:mm:ss')}
|
||||||
|
"现在是八月", // { value | month }
|
||||||
|
"现在是八月", // { value | month('long')}
|
||||||
|
"现在是八", // { value | month('short')}
|
||||||
|
"现在是8", // { value | month('number')}
|
||||||
|
"现在是八月", // { value | month(0)}
|
||||||
|
"现在是八", // { value | month(1)}
|
||||||
|
"现在是8", // { value | month(2)}
|
||||||
|
"现在是星期五", // { value | weekday }
|
||||||
|
"现在是星期五", // { value | weekday('long')}
|
||||||
|
"现在是五", // { value | weekday('short')}
|
||||||
|
"现在是5", // { value | weekday('number')}
|
||||||
|
"现在是星期五", // { value | weekday(0)}
|
||||||
|
"现在是五", // { value | weekday(1)}
|
||||||
|
"现在是5", // { value | weekday(2)}
|
||||||
|
// 时间
|
||||||
|
`现在时间 - ${NOW.toLocaleTimeString()}`, // { value | time }
|
||||||
|
`现在时间 - ${NOW.toLocaleTimeString()}`, // { value | time('local') }
|
||||||
|
"现在时间 - 10点12分36秒", // { value | time('long') }
|
||||||
|
"现在时间 - 10:12:36", // { value | time('short') }
|
||||||
|
"现在时间 - 1660270356000", // { value | time('timestamp') }
|
||||||
|
`现在时间 - ${NOW.toLocaleTimeString()}`, // { value | time(0) }
|
||||||
|
"现在时间 - 10点12分36秒", // { value | time(1) }
|
||||||
|
"现在时间 - 10:12:36", // { value | time(2) }
|
||||||
|
"现在时间 - 1660270356000", // { value | time(3) }
|
||||||
|
"现在时间 - 10:12:36", // { value | time('HH:mm:ss') }
|
||||||
|
"现在时间 - 12:36", // { value | time('mm:ss') }
|
||||||
|
"现在时间 - 36", // { value | time('ss') }"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const enDatetimes =[
|
||||||
|
"Now is { value }",
|
||||||
|
"Now is { value | date }",
|
||||||
|
"Now is { value | date('local') }",
|
||||||
|
"Now is { value | date('long') }",
|
||||||
|
"Now is { value | date('short') }",
|
||||||
|
"Now is { value | date('iso') }",
|
||||||
|
"Now is { value | date('gmt') }",
|
||||||
|
"Now is { value | date('utc') }",
|
||||||
|
"Now is { value | date(0) }", // local
|
||||||
|
"Now is { value | date(1) }", // long
|
||||||
|
"Now is { value | date(2) }", // short
|
||||||
|
"Now is { value | date(3) }", // iso
|
||||||
|
"Now is { value | date(4) }", // gmt
|
||||||
|
"Now is { value | date(5) }", // utc
|
||||||
|
"Now is { value | date('YYYY-MM-DD HH:mm:ss')}",
|
||||||
|
"Now is { value | date('YYYY-MM-DD')}",
|
||||||
|
"Now is { value | date('HH:mm:ss')}",
|
||||||
|
"Now is { value | month }",
|
||||||
|
"Now is { value | month('long')}",
|
||||||
|
"Now is { value | month('short')}",
|
||||||
|
"Now is { value | month('number')}",
|
||||||
|
"Now is { value | month(0)}",
|
||||||
|
"Now is { value | month(1)}",
|
||||||
|
"Now is { value | month(2)}",
|
||||||
|
"Now is { value | weekday }",
|
||||||
|
"Now is { value | weekday('long')}",
|
||||||
|
"Now is { value | weekday('short')}",
|
||||||
|
"Now is { value | weekday('number')}",
|
||||||
|
"Now is { value | weekday(0)}",
|
||||||
|
"Now is { value | weekday(1)}",
|
||||||
|
"Now is { value | weekday(2)}",
|
||||||
|
// 时间
|
||||||
|
"Now time: { value | time }",
|
||||||
|
"Now time: { value | time('local') }",
|
||||||
|
"Now time: { value | time('long') }",
|
||||||
|
"Now time: { value | time('short') }",
|
||||||
|
"Now time: { value | time('timestamp') }",
|
||||||
|
"Now time: { value | time(0) }",
|
||||||
|
"Now time: { value | time(1) }",
|
||||||
|
"Now time: { value | time(2) }",
|
||||||
|
"Now time: { value | time(3) }",
|
||||||
|
"Now time: { value | time('HH:mm:ss') }",
|
||||||
|
"Now time: { value | time('mm:ss') }",
|
||||||
|
"Now time: { value | time('ss') }"
|
||||||
|
]
|
||||||
|
|
||||||
|
const expectEnDatetimes =[
|
||||||
|
"Now is 2022/8/12 10:12:36", // { value }
|
||||||
|
"Now is 2022/8/12 10:12:36", // { value | date }
|
||||||
|
`Now is ${NOW.toLocaleString()}`, // { value | date('local') }
|
||||||
|
"Now is 2022/08/12 10:12:36", // { value | date('long') }
|
||||||
|
"Now is 2022/08/12", // { value | date('short') }
|
||||||
|
`Now is ${NOW.toISOString()}`, // { value | date('iso') }
|
||||||
|
`Now is ${NOW.toGMTString()}`, // { value | date('gmt') }
|
||||||
|
`Now is ${NOW.toUTCString()}`, // { value | date('utc') }
|
||||||
|
`Now is ${NOW.toLocaleString()}`, // { value | date(0) } // local
|
||||||
|
"Now is 2022/08/12 10:12:36", // { value | date(1) } // long
|
||||||
|
"Now is 2022/08/12", // { value | date(2) } // short
|
||||||
|
`Now is ${NOW.toISOString()}`, // { value | date(3) } // iso
|
||||||
|
`Now is ${NOW.toGMTString()}`, // { value | date(4) } // gmt
|
||||||
|
`Now is ${NOW.toUTCString()}`, // { value | date(5) } // utc
|
||||||
|
"Now is 2022-08-12 10:12:36", // { value | date('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
"Now is 2022-08-12", // { value | date('YYYY-MM-DD')}
|
||||||
|
"Now is 10:12:36", // { value | date('HH:mm:ss')}
|
||||||
|
"Now is August", // { value | month }
|
||||||
|
"Now is August", // { value | month('long')}
|
||||||
|
"Now is Aug", // { value | month('short')}
|
||||||
|
"Now is 8", // { value | month('number')}
|
||||||
|
"Now is August", // { value | month(0)}
|
||||||
|
"Now is Aug", // { value | month(1)}
|
||||||
|
"Now is 8", // { value | month(2)}
|
||||||
|
"Now is Friday", // { value | weekday }
|
||||||
|
"Now is Friday", // { value | weekday('long')}
|
||||||
|
"Now is Fri", // { value | weekday('short')}
|
||||||
|
"Now is 5", // { value | weekday('number')}
|
||||||
|
"Now is Friday", // { value | weekday(0)}
|
||||||
|
"Now is Fri", // { value | weekday(1)}
|
||||||
|
"Now is 5", // { value | weekday(2)}
|
||||||
|
// 时间
|
||||||
|
`Now time: ${NOW.toLocaleTimeString()}`, // { value | time }
|
||||||
|
`Now time: ${NOW.toLocaleTimeString()}`, // { value | time('local') }
|
||||||
|
"Now time: 10:12:36", // { value | time('long') }
|
||||||
|
"Now time: 10:12:36", // { value | time('short') }
|
||||||
|
"Now time: 1660270356000", // { value | time('timestamp') }
|
||||||
|
`Now time: ${NOW.toLocaleTimeString()}`, // { value | time(0) }
|
||||||
|
"Now time: 10:12:36", // { value | time(1) }
|
||||||
|
"Now time: 10:12:36", // { value | time(2) }
|
||||||
|
"Now time: 1660270356000", // { value | time(3) }
|
||||||
|
"Now time: 10:12:36", // { value | time('HH:mm:ss') }
|
||||||
|
"Now time: 12:36", // { value | time('mm:ss') }
|
||||||
|
"Now time: 36", // { value | time('ss') }"
|
||||||
|
]
|
||||||
|
|
||||||
|
const MONEY = 123456789.8848
|
||||||
|
const zhMoneys = [
|
||||||
|
"商品价格: { value | currency }", // 默认格式,由语言配置指定,不同的语言不一样
|
||||||
|
"商品价格: { value | currency('long')}", // 长格式
|
||||||
|
"商品价格: { value | currency('short')}", // 短格式 == short(0)
|
||||||
|
"商品价格: { value | currency('long',1)}", // 长格式: 万元
|
||||||
|
"商品价格: { value | currency('long',2)}", // 长格式: 亿
|
||||||
|
"商品价格: { value | currency('long',3)}", // 长格式: 万亿
|
||||||
|
"商品价格: { value | currency('long',4)}", // 长格式: 万万亿
|
||||||
|
|
||||||
|
"商品价格: { value | currency('short')}", // 短格式
|
||||||
|
"商品价格: { value | currency('short',1)}", // 短格式
|
||||||
|
"商品价格: { value | currency('short',2)}", // 短格式
|
||||||
|
"商品价格: { value | currency('short',3)}", // 短格式
|
||||||
|
"商品价格: { value | currency('short',4)}", // 短格式
|
||||||
|
"商品价格: { value | currency('short',5)}", // 短格式
|
||||||
|
|
||||||
|
"商品价格: { value | currency({symbol,prefix ,suffix, division,precision,unit})}", // 自定义货币格式
|
||||||
|
|
||||||
|
"商品价格: { value | currency('¥')}", // 指定货币符号
|
||||||
|
"商品价格: { value | currency('¥','CNY')}", // 指定货币符号+前缀
|
||||||
|
"商品价格: { value | currency('¥','CNY','元')}", // 指定货币符号+前缀+后缀
|
||||||
|
"商品价格: { value | currency('¥','CNY','元',3)}", // 指定货币符号+前缀+后缀+分割位
|
||||||
|
"商品价格: { value | currency('¥','CNY','元',3,3)}", // 指定货币符号+前缀+后缀+分割位+精度
|
||||||
|
]
|
||||||
|
const expectZhMoneys =[
|
||||||
|
"商品价格: ¥1,2345,6789.88", // { value | currency }
|
||||||
|
"商品价格: ¥1,2345,6789.88元", // { value | currency('long')}
|
||||||
|
"商品价格: ¥1,2345,6789.88", // { value | currency('short')}
|
||||||
|
"商品价格: ¥1,2345,6789.88", // { value | currency('¥')}
|
||||||
|
"商品价格: CNY¥1,2345,6789.88", // { value | currency('¥','CNY')}
|
||||||
|
"商品价格: CNY¥1,2345,6789.88元", // { value | currency('¥','CNY','元')}
|
||||||
|
"商品价格: CNY¥123,456,789.885元", // { value | currency('¥','CNY','元',3)}
|
||||||
|
"商品价格: CNY¥123,456,789.885元", //{ value | currency('¥','CNY','元',3,3)}
|
||||||
|
]
|
||||||
|
const enMoneys = [
|
||||||
|
"Price: { value | currency }", // 默认格式,由语言配置指定,不同的语言不一样
|
||||||
|
"Price: { value | currency('long')}", // 长格式
|
||||||
|
"Price: { value | currency('short')}", // 短格式
|
||||||
|
"Price: { value | currency('$')}", // 指定货币符号
|
||||||
|
"Price: { value | currency('$','USD')}", // 指定货币符号+前缀
|
||||||
|
"Price: { value | currency('$','USD','')}", // 指定货币符号+前缀+后缀
|
||||||
|
"Price: { value | currency('$','USD','',3)}", // 指定货币符号+前缀+后缀+分割位
|
||||||
|
"Price: { value | currency('$','USD','',3,3)}", // 指定货币符号+前缀+后缀+分割位+精度
|
||||||
|
]
|
||||||
|
const expectEnMoneys =[
|
||||||
|
"Price: $123,456,789.88", // { value | currency }
|
||||||
|
"Price: $123,456,789.88", // { value | currency('long')}
|
||||||
|
"Price: $123,456,789.88", // { value | currency('short')}
|
||||||
|
"Price: $123,456,789.88", // { value | currency('$')}
|
||||||
|
"Price: USD$1,2345,6789.88", // { value | currency('$','USD')}
|
||||||
|
"Price: USD$1,2345,6789.88", // { value | currency('$','USD','元')}
|
||||||
|
"Price: USD$123,456,789.885", // { value | currency('$','USD','元',3)}
|
||||||
|
"Price: USD$123,456,789.885", //{ value | currency('$','USD','元',3,3)}
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loaders = {
|
const loaders = {
|
||||||
zh:{
|
zh:{
|
||||||
1:"你好",
|
1:"你好",
|
||||||
2:"现在是{}",
|
2:"现在是{ value | }",
|
||||||
3:"我出生于{year}年,今年{age}岁",
|
3:"我出生于{year}年,今年{age}岁",
|
||||||
4:"我有{}个朋友",
|
4:"我有{}个朋友",
|
||||||
|
...toLanguageDict(zhDatetimes,5),
|
||||||
},
|
},
|
||||||
en :{
|
en :{
|
||||||
1:"hello",
|
1:"hello",
|
||||||
2:"Now is {}",
|
2:"Now is {}",
|
||||||
3:"I was born in {year}, now is {age} years old",
|
3:"I was born in {year}, now is {age} years old",
|
||||||
4:["I have no friends","I have one friends","I have two friends","I have {} friends"]
|
4:["I have no friends","I have one friends","I have two friends","I have {} friends"],
|
||||||
|
...toLanguageDict(enDatetimes,5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,9 +306,10 @@ const formatters = {
|
|||||||
|
|
||||||
const idMap = {
|
const idMap = {
|
||||||
"你好":1,
|
"你好":1,
|
||||||
"现在是{}":2,
|
"现在是{ value | }":2,
|
||||||
"我出生于{year}年,今年{age}岁":3,
|
"我出生于{year}年,今年{age}岁":3,
|
||||||
"我有{}个朋友":4
|
"我有{}个朋友":4,
|
||||||
|
...toLanguageIdMap(zhDatetimes,5)
|
||||||
}
|
}
|
||||||
const languages = [
|
const languages = [
|
||||||
{ name: "zh", title: "中文" },
|
{ name: "zh", title: "中文" },
|
||||||
@ -173,7 +448,7 @@ test("切换到其他语言时的自动匹配同名格式化器",async ()=>{
|
|||||||
test("位置插值翻译文本内容",async ()=>{
|
test("位置插值翻译文本内容",async ()=>{
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
expect(t("你好")).toBe("你好");
|
expect(t("你好")).toBe("你好");
|
||||||
expect(t("现在是{}",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
||||||
|
|
||||||
// 经babel自动码换后,文本内容会根据idMap自动转为id
|
// 经babel自动码换后,文本内容会根据idMap自动转为id
|
||||||
expect(t("1")).toBe("你好");
|
expect(t("1")).toBe("你好");
|
||||||
@ -182,7 +457,7 @@ test("位置插值翻译文本内容",async ()=>{
|
|||||||
await scope.change("en")
|
await scope.change("en")
|
||||||
|
|
||||||
expect(t("你好")).toBe("hello");
|
expect(t("你好")).toBe("hello");
|
||||||
expect(t("现在是{}",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
||||||
expect(t("1")).toBe("hello");
|
expect(t("1")).toBe("hello");
|
||||||
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
||||||
})
|
})
|
||||||
@ -190,11 +465,11 @@ test("位置插值翻译文本内容",async ()=>{
|
|||||||
test("命名插值翻译文本内容",async ()=>{
|
test("命名插值翻译文本内容",async ()=>{
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
expect(t("你好")).toBe("你好");
|
expect(t("你好")).toBe("你好");
|
||||||
expect(t("现在是{}",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
|
||||||
|
|
||||||
await scope.change("en")
|
await scope.change("en")
|
||||||
expect(t("你好")).toBe("hello");
|
expect(t("你好")).toBe("hello");
|
||||||
expect(t("现在是{}",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
||||||
expect(t("1")).toBe("hello");
|
expect(t("1")).toBe("hello");
|
||||||
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
|
||||||
})
|
})
|
||||||
@ -222,4 +497,22 @@ test("翻译复数支持",async ()=>{
|
|||||||
expect(t("我有{}个朋友",3)).toBe("I have 3 friends");
|
expect(t("我有{}个朋友",3)).toBe("I have 3 friends");
|
||||||
expect(t("我有{}个朋友",4)).toBe("I have 4 friends");
|
expect(t("我有{}个朋友",4)).toBe("I have 4 friends");
|
||||||
})
|
})
|
||||||
|
test("日期时间格式化器",async ()=>{
|
||||||
|
|
||||||
|
let zhTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
|
||||||
|
expect(zhTranslatedResults).toStrictEqual(expectZhDatetimes)
|
||||||
|
await scope.change("en")
|
||||||
|
let enTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
|
||||||
|
expect(enTranslatedResults).toStrictEqual(expectEnDatetimes)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test("货币格式化器",async ()=>{
|
||||||
|
|
||||||
|
|
||||||
|
let zhTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
|
||||||
|
expect(zhTranslatedResults).toStrictEqual(expectZhDatetimes)
|
||||||
|
await scope.change("en")
|
||||||
|
let enTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
|
||||||
|
expect(enTranslatedResults).toStrictEqual(expectEnDatetimes)
|
||||||
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user