更新格式化器机制

This commit is contained in:
wxzhang 2022-08-11 18:33:32 +08:00
parent baa7bc569c
commit ab9e150470
13 changed files with 477 additions and 279 deletions

View File

@ -1,9 +1,9 @@
import messageIds from "./idMap.js" import messageIds from "./idMap.js" // 语言ID映射文件
import runtime from "./runtime.js" import { translate,i18nScope } from "./runtime.js" // 运行时
const { translate,i18nScope } = runtime import defaultFormatters from "./formatters/zh.js" // 默认语言格式化器
const activeFormatters = defaultFormatters // 激活语言格式化器
import formatters from "./formatters.js"
import defaultMessages from "./zh.js" import defaultMessages from "./zh.js"
const activeMessages = defaultMessages const activeMessages = defaultMessages
@ -18,24 +18,37 @@ const scopeSettings = {
{ {
"name": "en", "name": "en",
"title": "英文" "title": "英文"
},
{
"name": "de",
"title": "德语"
} }
], ],
"defaultLanguage": "zh", "defaultLanguage": "zh",
"activeLanguage": "zh", "activeLanguage": "zh",
"namespaces": {} "namespaces": {}
} }
// 格式化器
const formatters = {
'zh' : defaultFormatters,
'en' : ()=>import("./formatters/en.js"),
'de' : ()=>import("./formatters/de.js")
}
// 语言包加载器
const loaders = {
"en" : ()=>import("./en.js"),
"de" : ()=>import("./de.js")
}
// 语言作用域 // 语言作用域
const scope = new i18nScope({ const scope = new i18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id: "vueapp", // 当前作用域的id自动取当前工程的package.json的name id : "vueapp", // 当前作用域的id自动取当前工程的package.json的name
default: defaultMessages, // 默认语言包 default : defaultMessages, // 默认语言包
messages : activeMessages, // 当前语言包 messages : activeMessages, // 当前语言包
idMap:messageIds, // 消息id映射列表 idMap : messageIds, // 消息id映射列表
formatters, // 当前作用域的格式化函数列表 formatters, // 扩展自定义格式化器
loaders:{ loaders // 语言包加载器
"en" : ()=>import("./en.js")
}
}) })
// 翻译函数 // 翻译函数
const scopedTtranslate = translate.bind(scope) const scopedTtranslate = translate.bind(scope)

View File

@ -7,6 +7,10 @@
{ {
"name": "en", "name": "en",
"title": "英文" "title": "英文"
},
{
"name": "de",
"title": "德语"
} }
], ],
"defaultLanguage": "zh", "defaultLanguage": "zh",

View File

@ -3,180 +3,210 @@
"en": "Hello world!", "en": "Hello world!",
"$file": [ "$file": [
"App.vue" "App.vue"
] ],
"de": "Hello world!"
}, },
"中华人民共和国": { "中华人民共和国": {
"en": "The People's Republic of China", "en": "The People's Republic of China",
"$file": [ "$file": [
"App.vue" "App.vue"
] ],
"de": "中华人民共和国"
}, },
"迎接中华民族的伟大复兴": { "迎接中华民族的伟大复兴": {
"en": "Welcome the great rejuvenation of the Chinese nation", "en": "Welcome the great rejuvenation of the Chinese nation",
"$file": [ "$file": [
"App.vue" "App.vue"
] ],
"de": "迎接中华民族的伟大复兴"
}, },
"成立于{}年": { "成立于{}年": {
"en": "Founded in {}", "en": "Founded in {}",
"$file": [ "$file": [
"components\\china.vue" "components\\china.vue"
] ],
"de": "成立于{}年"
}, },
"首都:北京": { "首都:北京": {
"en": "Capital: Beijing", "en": "Capital: Beijing",
"$file": [ "$file": [
"components\\china.vue" "components\\china.vue"
] ],
"de": "首都:北京"
}, },
"VoerkaI18n多语言解决方案 ": { "VoerkaI18n多语言解决方案 ": {
"en": "VoerkaI18n多语言解决方案 ", "en": "VoerkaI18n多语言解决方案 ",
"$file": [ "$file": [
"App.vue" "App.vue"
] ],
"de": "VoerkaI18n多语言解决方案 "
}, },
"现在是{ value | date }": { "现在是{ value | date }": {
"en": "Now is { value | date }", "en": "Now is { value | date }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | date }"
}, },
"现在是{ value | shortdate }": { "现在是{ value | shortdate }": {
"en": "Now is { value | shortdate }", "en": "Now is { value | shortdate }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | shortdate }"
}, },
"现在是{ value | time }": { "现在是{ value | time }": {
"en": "Now is { value | time }", "en": "Now is { value | time }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | time }"
}, },
"现在是{ value | shorttime }": { "现在是{ value | shorttime }": {
"en": "Now is { value | shorttime }", "en": "Now is { value | shorttime }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | shorttime }"
}, },
"现在是{ value | year }": { "现在是{ value | year }": {
"en": "Now is { value | year }", "en": "Now is { value | year }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | year }"
}, },
"现在是{ value | month }": { "现在是{ value | month }": {
"en": "Now is { value | month }", "en": "Now is { value | month }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | month }"
}, },
"现在是{ value | day }": { "现在是{ value | day }": {
"en": "Now is { value | day }", "en": "Now is { value | day }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | day }"
}, },
"现在是{ value | weekdayValue }": { "现在是{ value | weekdayValue }": {
"en": "Now is { value | weekdayValue }", "en": "Now is { value | weekdayValue }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | weekdayValue }"
}, },
"现在是{ value | weekday }": { "现在是{ value | weekday }": {
"en": "Now is { value | weekday }", "en": "Now is { value | weekday }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | weekday }"
}, },
"现在是{ value | shortWeekday }": { "现在是{ value | shortWeekday }": {
"en": "Now is { value | shortWeekday }", "en": "Now is { value | shortWeekday }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | shortWeekday }"
}, },
"现在是{ value | monthName }": { "现在是{ value | monthName }": {
"en": "Now is { value | monthName }", "en": "Now is { value | monthName }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | monthName }"
}, },
"现在是{ value | shorMonthName }": { "现在是{ value | shorMonthName }": {
"en": "Now is { value | shorMonthName }", "en": "Now is { value | shorMonthName }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | shorMonthName }"
}, },
"现在是{ value | hour }": { "现在是{ value | hour }": {
"en": "Now is { value | hour }", "en": "Now is { value | hour }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | hour }"
}, },
"现在是{ value | hour12 }": { "现在是{ value | hour12 }": {
"en": "Now is { value | hour12 }", "en": "Now is { value | hour12 }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | hour12 }"
}, },
"现在是{ value | minute }": { "现在是{ value | minute }": {
"en": "Now is { value | minute }", "en": "Now is { value | minute }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | minute }"
}, },
"现在是{ value | second }": { "现在是{ value | second }": {
"en": "Now is { value | second }", "en": "Now is { value | second }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | second }"
}, },
"现在是{ value | millisecond }": { "现在是{ value | millisecond }": {
"en": "Now is { value | millisecond }", "en": "Now is { value | millisecond }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | millisecond }"
}, },
"现在是{ value | timestamp }": { "现在是{ value | timestamp }": {
"en": "Now is { value | timestamp }", "en": "Now is { value | timestamp }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value | timestamp }"
}, },
"商品价格:{ value | currency }": { "商品价格:{ value | currency }": {
"en": "Price: { value | currency }", "en": "Price: { value | currency }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "商品价格:{ value | currency }"
}, },
"商品价格:{ value | capitalizeCurrency }": { "商品价格:{ value | capitalizeCurrency }": {
"en": "Price: { value | capitalizeCurrency }", "en": "Price: { value | capitalizeCurrency }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "商品价格:{ value | capitalizeCurrency }"
}, },
"现在是{ value }": { "现在是{ value }": {
"en": "Now is { value }", "en": "Now is { value }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "现在是{ value }"
}, },
"商品价格:{ value | currency('CNY','元整',3)}": { "商品价格:{ value | currency('CNY','元整',3)}": {
"en": "Price: { value | currency('CNY','元整',3)}", "en": "Price: { value | currency('CNY','元整',3)}",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "商品价格:{ value | currency('CNY','元整',3)}"
}, },
"商品价格:{ value | capitalizeCurrency(true) }": { "商品价格:{ value | capitalizeCurrency(true) }": {
"en": "Price:{ value | capitalizeCurrency(true) }", "en": "Price:{ value | capitalizeCurrency(true) }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "商品价格:{ value | capitalizeCurrency(true) }"
}, },
"商品数量:{ value | number }": { "商品数量:{ value | number }": {
"en": "Count: { value | number }", "en": "Count: { value | number }",
"$file": [ "$file": [
"components\\formatters.vue" "components\\formatters.vue"
] ],
"de": "商品数量:{ value | number }"
} }
} }

View File

@ -146,23 +146,47 @@ module.exports =async function compile(langFolder,opts={}){
} }
// 5 . 生成编译后的格式化函数文件 // 5 . 生成编译后的格式化函数文件
const formattersFile = path.join(langFolder,"formatters.js") // const formattersFile = path.join(langFolder,"formatters.js")
if(!fs.existsSync(formattersFile)){ // if(!fs.existsSync(formattersFile)){
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), templateContext ) // const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), templateContext )
fs.writeFileSync(formattersFile,formattersContent) // fs.writeFileSync(formattersFile,formattersContent)
logger.log(t(" - 格式化器:{}"),path.basename(formattersFile)) // logger.log(t(" - 格式化器:{}"),path.basename(formattersFile))
}else{ // 格式化器如果存在,则需要更改对应的模块类型 // }else{ // 格式化器如果存在,则需要更改对应的模块类型
let formattersContent = fs.readFileSync(formattersFile,"utf8").toString() // let formattersContent = fs.readFileSync(formattersFile,"utf8").toString()
if(moduleType == "esm"){ // if(moduleType == "esm"){
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ") // formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ")
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ") // formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ")
}else{ // }else{
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*default\s*/gm,"module.exports = ") // formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*default\s*/gm,"module.exports = ")
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*/gm,"module.exports.") // formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*/gm,"module.exports.")
// }
// fs.writeFileSync(formattersFile,formattersContent)
// logger.log(t(" - 更新格式化器:{}"),path.basename(formattersFile))
// }
const formattersFolder = path.join(langFolder,"formatters")
if(!fs.existsSync(formattersFolder)) fs.mkdirSync(formattersFolder)
// 为每一个语言生成一个对应的式化器
languages.forEach(lang=>{
const formattersFile = path.join(formattersFolder,`${lang.name}.js`)
if(!fs.existsSync(formattersFile)){
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), templateContext )
fs.writeFileSync(formattersFile,formattersContent)
logger.log(t(" - 格式化器:{}"),path.basename(formattersFile))
}else{ // 格式化器如果存在,则需要更改对应的模块类型
let formattersContent = fs.readFileSync(formattersFile,"utf8").toString()
if(moduleType == "esm"){
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ")
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ")
}else{
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*default\s*/gm,"module.exports = ")
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*export\s*/gm,"module.exports.")
}
fs.writeFileSync(formattersFile,formattersContent)
logger.log(t(" - 更新格式化器:{}"),path.basename(formattersFile))
} }
fs.writeFileSync(formattersFile,formattersContent) })
logger.log(t(" - 更新格式化器:{}"),path.basename(formattersFile))
}
// 6. 生成编译后的访问入口文件 // 6. 生成编译后的访问入口文件
const entryFile = path.join(langFolder,"index.js") const entryFile = path.join(langFolder,"index.js")

View File

@ -1,35 +1,48 @@
{{if moduleType === "esm"}} {{if moduleType === "esm"}}
import messageIds from "./idMap.js" import messageIds from "./idMap.js" // 语言ID映射文件
{{if inlineRuntime }}import runtime from "./runtime.js" {{if inlineRuntime }}import { translate,i18nScope } from "./runtime.js" // 运行时
const { translate,i18nScope } = runtime import defaultFormatters from "./formatters/{{defaultLanguage}}.js" // 默认语言格式化器
{{else}}import { translate,i18nScope } from "@voerkai18n/runtime"{{/if}} {{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "./formatters/{{activeLanguage}}.js"{{/if}} // 激活语言格式化器
import formatters from "./formatters.js" {{else}}import { translate,i18nScope } from "@voerkai18n/runtime"
{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "@voerkai18n/runtime/formatters/{{activeLanguage}}.js"{{/if}}
{{/if}}
import defaultMessages from "./{{defaultLanguage}}.js" import defaultMessages from "./{{defaultLanguage}}.js"
{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages {{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages{{else}}import activeMessages from "./{{activeLanguage}}.js"{{/if}}
{{else}}import activeMessages from "./{{activeLanguage}}.js"{{/if}}
{{else}} {{else}}
const messageIds = require("./idMap") const messageIds = require("./idMap")
{{if inlineRuntime }}const { translate,i18nScope } = require("./runtime.js") {{if inlineRuntime }}const { translate,i18nScope } = require("./runtime.js")
{{else}}const { translate,i18nScope } = require("@voerkai18n/runtime"){{/if}} const defaultFormatters = require("./formatters/{{defaultLanguage}}.js")
const formatters = require("./formatters.js") {{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}const activeFormatters = require("./formatters/{{activeLanguage}}.js"){{/if}}
const defaultMessages = require("./{{defaultLanguage}}.js") {{else}}const { translate,i18nScope } = require("@voerkai18n/runtime")
{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages const defaultFormatters = require("@voerkai18n/runtime/formatters/{{defaultLanguage}}.js")
{{else}}const activeMessages = require("./{{activeLanguage}}.js"){{/if}} {{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}const activeFormatters = require("@voerkai18n/runtime/formatters/{{activeLanguage}}.js"){{/if}}
{{/if}} {{/if}}
const defaultMessages = require("./{{defaultLanguage}}.js") // 默认语言包
{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages{{else}}const activeMessages = require("./{{activeLanguage}}.js"){{/if}} {{/if}}
// 语言配置文件 // 语言配置文件
const scopeSettings = {{@ settings}} const scopeSettings = {{@ settings}}
// 格式化器
const formatters = {
{{each languages}}{{if $value.name == defaultLanguage}}'{{defaultLanguage}}' : defaultFormatters{{if $index !== languages.length - 1}},{{/if}}
{{else if $value.name == activeLanguage}}{{if defaultLanguage !== activeLanguage}}'{{activeLanguage}}':activeFormatters{{/if}}{{if $index !== languages.length - 1}},{{/if}}
{{else}}'{{$value.name}}' : ()=>import("./formatters/{{$value.name}}.js"){{if $index !== languages.length - 1}},{{'\n\t'}}{{/if}}{{/if}}{{/each}}
}
// 语言包加载器
const loaders = { {{each languages}}{{if $value.name !== defaultLanguage}}
{{if $value.name == activeLanguage}}"{{$value.name}}" : activeMessages{{else}}"{{$value.name}}" : ()=>import("./{{$value.name}}.js"){{/if}}{{if $index !== languages.length - 1}},{{/if}}{{/if}}{{/each}}
}
// 语言作用域 // 语言作用域
const scope = new i18nScope({ const scope = new i18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id: "{{scopeId}}", // 当前作用域的id自动取当前工程的package.json的name id : "{{scopeId}}", // 当前作用域的id自动取当前工程的package.json的name
default: defaultMessages, // 默认语言包 debug : false, // 是否在控制台输出高度信息
messages : activeMessages, // 当前语言包 default : defaultMessages, // 默认语言包
idMap:messageIds, // 消息id映射列表 messages : activeMessages, // 当前语言包
formatters, // 当前作用域的格式化函数列表 idMap : messageIds, // 消息id映射列表
loaders:{ {{each languages}}{{if $value.name !== defaultLanguage}} formatters, // 扩展自定义格式化器
{{if $value.name == activeLanguage}}"{{$value.name}}" : ()=>activeMessages{{else}}"{{$value.name}}" : ()=>import("./{{$value.name}}.js"){{/if}}{{if $index !== languages.length - 1}},{{/if}}{{/if}}{{/each}} loaders // 语言包加载器
}
}) })
// 翻译函数 // 翻译函数
const scopedTtranslate = translate.bind(scope) const scopedTtranslate = translate.bind(scope)

View File

@ -1,35 +1,21 @@
/** /**
格式化器用来对翻译文本内容中的插值变量进行格式化 格式化器用来对翻译文本内容中的插值变量进行处理
比如将一个数字格式化为货币格式或者将一个日期格式化为友好的日期格式 比如将一个数字格式化为货币格式或者将一个日期格式化为友好的日期格式
- 以下定义了一些格式化器在中文场景下会启用这些格式化器 - 以下定义了一些格式化器在中文场景下会启用这些格式化器
import dayjs from "dayjs"; import dayjs from "dayjs";
const formatters = { export default {
"*":{ // 在所有语言下生效的格式化器 $options:{...},
$types:{...}, // 只作用于特定数据类型的默认格式化器 $types:{
.... // 全局格式化器 Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
},
zh:{
// 只作用于特定数据类型的格式化器
$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:{ date:(value)=>dayjs(value).format("YYYY年MM月DD日")
$types:{ bjTime:(value)=>"北京时间"+ value,
Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器 [格式化器名称]:(value)=>{...},
}, [格式化器名称]:(value)=>{...},
date:(value)=>dayjs(value).format("YYYY/MM/DD") [格式化器名称]:(value)=>{...},
bjTime:(value)=>"BeiJing "+ value,
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
} }
} }
- 在翻译函数中使用格式化器的方法,示例如下 - 在翻译函数中使用格式化器的方法,示例如下
@ -38,48 +24,47 @@
其等效于 其等效于
t(`Now is ${bjTime(date(value))",{value: new Date()}) t(`Now is ${bjTime(date(value))",{value: new Date()})
由于value分别经过两个管道符转换上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符 由于value分别经过两个管道符转换上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符
最终的输出结果 最终的输出结果
中文: "现在是北京时间2022年3月1日" 中文: "现在是北京时间2022年3月1日"
英文: "Now is BeiJing 2022/03/01" 英文: "Now is BeiJing 2022/03/01"
*
*/ */
{{if moduleType === "esm"}} {{if moduleType === "esm"}}
export default{{else}}module.exports = {{/if}}{ export default{{else}}module.exports = {{/if}}{
// 在所有语言下生效的格式化器 // 格式化器参数
"*":{ $options:{
//[格式化名称]:(value)=>{...},
//[格式化名称]:(value,arg)=>{...},
},
// 在所有语言下只作用于特定数据类型的格式化器
$types:{
}, },
{{each languages}} {{$value.name}}:{ // 指定数据类型的默认格式化器
$types:{ $types:{
// 所有类型的默认格式化器 // "*" : { },
// "*":{ // Date : { },
// Number: { },
// }, // String: { },
// Date:{ // Array : { },
// Object: { }
// }, }
// Number:{ // 允许重载内置的格式化器
// --- 日期 ------
// }, // date : value => { ... },
// String:{ // shortdate : value => { ... },
// time : value => { ... },
// }, // shorttime : value => { ... },
// Array:{ // year : value => { ... },
// month : value => { ... },
// }, // day : value => { ... },
// Object:{ // weekdayValue : value => { ... },
// weekday : value => { ... },
// } // shortWeekday : value => { ... },
} // monthName : value => { ... },
}{{if $index !== languages.length - 1}},{{/if}} // shorMonthName : value => { ... },
{{/each}}} // --- 时间 ------
// hour : value => { ... },
// hour12 : value => { ... },
// minute : value => { ... },
// second : value => { ... },
// millisecond : value => { ... },
// timestamp : value => { ... },
// currency : value => { ... },
// number : value => { ... },
}

View File

@ -5,6 +5,7 @@
*/ */
const { isNumber } = require('./utils') const { isNumber } = require('./utils')
const CN_DATETIME_UNITS = ["年","季度","月","周","日","小时","分钟","秒","毫秒","微秒"]
const CN_WEEK_DAYS = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"] const CN_WEEK_DAYS = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"]
const CN_SHORT_WEEK_DAYS =["日","一","二","三","四","五","六"] const CN_SHORT_WEEK_DAYS =["日","一","二","三","四","五","六"]
const CN_MONTH_NAMES= ["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"] const CN_MONTH_NAMES= ["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]
@ -93,6 +94,7 @@ function toChineseCurrency(value,{big=false,prefix="",unit="元",suffix=""}={}){
toChineseCurrency, toChineseCurrency,
toChineseNumber, toChineseNumber,
toChineseBigNumber, toChineseBigNumber,
CN_DATETIME_UNITS,
CN_WEEK_DAYS, CN_WEEK_DAYS,
CN_SHORT_WEEK_DAYS, CN_SHORT_WEEK_DAYS,
CN_MONTH_NAMES, CN_MONTH_NAMES,

View File

@ -6,12 +6,35 @@
const { toDate,toCurrency } = require("../utils") const { toDate,toCurrency } = require("../utils")
module.exports = { module.exports = {
// 配置参数: 格式化器函数的最后一个参数就是该配置参数
$options:{
datetime : {
units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
weekday : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
shortWeekdays : ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"],
monthNames : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
shorMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"]
},
currency : {
unit : "$",
prefix : "",
suffix : "",
division : 3,
precision : 2
},
number : {
division : 3,
precision : 2
}
},
// 默认数据类型的格式化器
$types: { $types: {
Date : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` }, Date : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` },
Null : value =>"", Null : value =>"",
Undefined: value =>"", Undefined: value =>"",
Error : value => "ERROR" Error : value => "ERROR"
}, },
// 以下是格式化定义
// 日期 // 日期
date : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` }, date : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` },
shortdate : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` }, shortdate : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` },

View File

@ -1,5 +1,6 @@
/** /**
* 内置的格式化器 * 内置的格式化器
* 被注册到全局语言管理器
*/ */
const enFormatters = require("./en") const enFormatters = require("./en")

View File

@ -3,11 +3,31 @@
* *
*/ */
const { toChineseCurrency,toChineseNumber,CN_WEEK_DAYS,CN_SHORT_WEEK_DAYS, CN_MONTH_NAMES, CN_SHORT_MONTH_NAMES} = require("../cnutils") const { toChineseCurrency,toChineseNumber,CN_DATETIME_UNITS,CN_WEEK_DAYS,CN_SHORT_WEEK_DAYS, CN_MONTH_NAMES, CN_SHORT_MONTH_NAMES} = require("../cnutils")
const { toDate, toCurrency } = require("../utils") const { toDate, toCurrency } = require("../utils")
module.exports = {
module.exports = {// 简体中文 // 配置参数: 格式化器函数的最后一个参数就是该配置参数
$options:{
datetime : {
units : CN_DATETIME_UNITS,
weekdays : CN_WEEK_DAYS,
shortWeekdays : CN_SHORT_WEEK_DAYS,
monthNames : CN_MONTH_NAMES,
shorMonthNames: CN_SHORT_MONTH_NAMES
},
currency : {
unit : "$",
prefix : "",
suffix : "",
division : 3,
precision : 2
},
number : {
division : 3,
precision : 2
}
},
$types: { $types: {
Date: value => {const d = toDate(value);return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}${d.getHours()}${d.getMinutes()}${d.getSeconds()}`} Date: value => {const d = toDate(value);return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}${d.getHours()}${d.getMinutes()}${d.getSeconds()}`}
}, },

View File

@ -1,7 +1,7 @@
const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require("./utils") const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require("./utils")
const EventEmitter = require("./eventemitter") const EventEmitter = require("./eventemitter")
const i18nScope = require("./scope.js") const i18nScope = require("./scope.js")
let inlineFormatters = require("./formatters") // 内置格式化器 let inlineFormatters = require("./formatters")
@ -34,28 +34,31 @@ const DataTypes = ["String","Number","Boolean","Object","Array","Function","Err
/** /**
通过正则表达式对原始文本内容进行解析匹配后得到的 使用正则表达式对原始文本内容进行解析匹配后得到的便以处理的数组
formatters="| aaa(1,1) | bbb " formatters="| aaa(1,1) | bbb "
需要统一解析为 统一解析为
[ [
[aaa,[1,1]], // [formatter'name,[args,...]] [aaa,[1,1]], // [formatter'name,[args,...]]
[bbb,[]], [<格式化器名称>,[<参数>,<参数>,...]]
] ]
formatters="| aaa(1,1,"dddd") | bbb " formatters="| aaa(1,1,"dddd") | bbb "
目前对参数采用简单的split(",")来解析因此如果参数中包括了,则无法正确解析例如aaa(1,1,"dd,,dd")形式的参数 特别注意
- 目前对参数采用简单的split(",")来解析因此如果参数中包括了逗号等会影响解析的字符时可能导致错误
例如aaa(1,1,"dd,,dd")形式的参数
在此场景下基本够用了如果需要支持更复杂的参数解析可以后续考虑使用正则表达式来解析 在此场景下基本够用了如果需要支持更复杂的参数解析可以后续考虑使用正则表达式来解析
- 如果参数是{},[]则尝试解决为对象和数组但是里面的内容无法支持复杂和嵌套数据类型
@returns [[<formatterName>,[<arg>,<arg>,...]]] @returns [ [<格式化器名称>,[<参数>,<参数>,...],[<格式化器名称>,[<参数>,<参数>,...]],...]
*/ */
function parseFormatters(formatters){ function parseFormatters(formatters){
if(!formatters) return [] if(!formatters) return []
// 1. 先解析为 ["aaa()","bbb"]形式 // 1. 先解析为 ["aaa()","bbb"]形式
let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim()) let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim())
// 2. 解析格式化器参数 // 2. 解析格式化器参数
return result.map(formatter=>{ return result.map(formatter=>{
let firstIndex = formatter.indexOf("(") let firstIndex = formatter.indexOf("(")
@ -64,11 +67,11 @@ function parseFormatters(formatters){
const argsContent = formatter.substr(firstIndex+1,lastIndex-firstIndex-1).trim() const argsContent = formatter.substr(firstIndex+1,lastIndex-firstIndex-1).trim()
let args = argsContent=="" ? [] : argsContent.split(",").map(arg=>{ let args = argsContent=="" ? [] : argsContent.split(",").map(arg=>{
arg = arg.trim() arg = arg.trim()
if(!isNaN(parseInt(arg))){ if(isNumber(arg)){ // 数字
return parseInt(arg) // 数字 return parseFloat(arg)
}else if((arg.startsWith('\"') && arg.endsWith('\"')) || (arg.startsWith('\'') && arg.endsWith('\'')) ){ }else if((arg.startsWith('\"') && arg.endsWith('\"')) || (arg.startsWith('\'') && arg.endsWith('\'')) ){
return arg.substr(1,arg.length-2) // 字符串 return arg.substr(1,arg.length-2) // 字符串
}else if(arg.toLowerCase()==="true" || arg.toLowerCase()==="false"){ }else if(["true" ,"false"].includes(arg.toLowerCase())){
return arg.toLowerCase()==="true" // 布尔值 return arg.toLowerCase()==="true" // 布尔值
}else if((arg.startsWith('{') && arg.endsWith('}')) || (arg.startsWith('[') && arg.endsWith(']'))){ }else if((arg.startsWith('{') && arg.endsWith('}')) || (arg.startsWith('[') && arg.endsWith(']'))){
try{ try{
@ -89,7 +92,7 @@ function parseFormatters(formatters){
/** /**
* 提取字符串中的插值变量 * 提取字符串中的插值变量
* // [ * [
// { // {
name:<变量名称>,formatters:[{name:<格式化器名称>,args:[<参数>,<参数>,....]]],<匹配字符串>], name:<变量名称>,formatters:[{name:<格式化器名称>,args:[<参数>,<参数>,....]]],<匹配字符串>],
// .... // ....
@ -145,12 +148,12 @@ function forEachInterpolatedVars(str,callback,options={}){
const varname = match.groups.varname || "" const varname = match.groups.varname || ""
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]] // 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
const formatters = parseFormatters(match.groups.formatters) const formatters = parseFormatters(match.groups.formatters)
if(typeof(callback)==="function"){ if(isFunction(callback)){
try{ try{
if(opts.replaceAll){ if(opts.replaceAll){ // 在某此版本上可能没有
result=result.replaceAll(match[0],callback(varname,formatters,match[0])) result=result.replaceAll(match[0],callback(varname,formatters,match[0]))
}else{ }else{
result=result.replace(match[0],callback(varname,formatters,match[0])) result=result.replace(new RegExp(match[0],"gm"),callback(varname,formatters,match[0]))
} }
}catch{// callback函数可能会抛出异常如果抛出异常则中断匹配过程 }catch{// callback函数可能会抛出异常如果抛出异常则中断匹配过程
break break
@ -161,7 +164,8 @@ function forEachInterpolatedVars(str,callback,options={}){
return result return result
} }
/** /**
* 将要翻译内容提供了一个非文本内容时进行默认的转换 * 当传入的翻译内容不是一个字符串时进行默认的转换
*
* - 对函数则执行并取返回结果() * - 对函数则执行并取返回结果()
* - 对Array和Object使用JSON.stringify * - 对Array和Object使用JSON.stringify
* - 其他类型使用toString * - 其他类型使用toString
@ -171,17 +175,25 @@ function forEachInterpolatedVars(str,callback,options={}){
*/ */
function transformToString(value){ function transformToString(value){
let result = value let result = value
if(typeof(result)==="function") result = value() try{
if(!(typeof(result)==="string")){ if(isFunction(result)) result = value()
if(Array.isArray(result) || isPlainObject(result)){ if(!(typeof(result)==="string")){
result = JSON.stringify(result) if(Array.isArray(result) || isPlainObject(result)){
}else{ result = JSON.stringify(result)
result = result.toString() }else{
result = result.toString()
}
} }
}catch{
result = result.toString()
} }
return result return result
} }
/**
* 清空指定语言的缓存
* @param {*} scope
* @param {*} activeLanguage
*/
function resetScopeCache(scope,activeLanguage=null){ function resetScopeCache(scope,activeLanguage=null){
scope.$cache = {activeLanguage,typedFormatters:{},formatters:{}} scope.$cache = {activeLanguage,typedFormatters:{},formatters:{}}
} }
@ -189,26 +201,20 @@ function resetScopeCache(scope,activeLanguage=null){
* 取得指定数据类型的默认格式化器 * 取得指定数据类型的默认格式化器
* *
* 可以为每一个数据类型指定一个默认的格式化器,当传入插值变量时 * 可以为每一个数据类型指定一个默认的格式化器,当传入插值变量时
* 会自动调用该格式化器来对值进行格式化转换 * 会自动调用该格式化器来对值进行格式化转换
*
*
*
const formatters = { const formatters = {
"*":{ "*":{
$types:{...} // 在所有语言下只作用于特定数据类型的格式化器 $types:{...} // 在所有语言下只作用于特定数据类型的格式化器
}, // 在所有语言下生效的格式化器 }, // 在所有语言下生效的格式化器
zh:{ zh:{
$types:{ $types:{
[数据类型]:{ [数据类型]:(value)=>{...} // 默认
default:(value)=>{...} // 默认
},
}, },
[格式化器名称]:(value)=>{...}, [格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...}, [格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...}, [格式化器名称]:(value)=>{...},
}, },
en:{.....}
} }
* @param {*} scope * @param {*} scope
* @param {*} activeLanguage * @param {*} activeLanguage
@ -216,6 +222,7 @@ function resetScopeCache(scope,activeLanguage=null){
* @returns {Function} 格式化函数 * @returns {Function} 格式化函数
*/ */
function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
// 当指定数据类型的的默认格式化器的缓存处理
if(!scope.$cache) resetScopeCache(scope) if(!scope.$cache) resetScopeCache(scope)
if(scope.$cache.activeLanguage === activeLanguage) { if(scope.$cache.activeLanguage === activeLanguage) {
if(dataType in scope.$cache.typedFormatters) return scope.$cache.typedFormatters[dataType] if(dataType in scope.$cache.typedFormatters) return scope.$cache.typedFormatters[dataType]
@ -229,20 +236,21 @@ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
// 1. 在全局$types中查找 // 1. 在全局$types中查找
if(("*" in target) && isPlainObject(target["*"].$types)){ if(("*" in target) && isPlainObject(target["*"].$types)){
let formatters = target["*"].$types let formatters = target["*"].$types
if(dataType in formatters && typeof(formatters[dataType])==="function"){ if(dataType in formatters && isFunction(formatters[dataType])){
return scope.$cache.typedFormatters[dataType] = formatters[dataType] return scope.$cache.typedFormatters[dataType] = formatters[dataType]
} }
} }
// 2. 当前语言的$types中查找 // 2. 当前语言的$types中查找
if((activeLanguage in target) && isPlainObject(target[activeLanguage].$types)){ if((activeLanguage in target) && isPlainObject(target[activeLanguage].$types)){
let formatters = target[activeLanguage].$types let formatters = target[activeLanguage].$types
if(dataType in formatters && typeof(formatters[dataType])==="function"){ if(dataType in formatters && isFunction(formatters[dataType])){
return scope.$cache.typedFormatters[dataType] = formatters[dataType] return scope.$cache.typedFormatters[dataType] = formatters[dataType]
} }
} }
} }
} }
/** /**
* 获取指定名称的格式化器函数 * 获取指定名称的格式化器函数
* *
@ -254,7 +262,7 @@ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
* 全局作用域的格式化器优先 * 全局作用域的格式化器优先
* *
* @param {*} scope * @param {*} scope
* @param {*} activeLanguage * @param {*} activeLanguage 当前激活语言名称
* @param {*} name 格式化器名称 * @param {*} name 格式化器名称
* @returns {Function} 格式化函数 * @returns {Function} 格式化函数
*/ */
@ -272,11 +280,18 @@ function getFormatter(scope,activeLanguage,name){
// 1. 优先在当前语言查找 // 1. 优先在当前语言查找
if(activeLanguage in target){ if(activeLanguage in target){
let formatters = target[activeLanguage] || {} let formatters = target[activeLanguage] || {}
if((name in formatters) && typeof(formatters[name])==="function") return scope.$cache.formatters[name] = formatters[name] if((name in formatters) && isFunction(formatters[name])) {
} return scope.$cache.formatters[name] = formatters[name]
}else{ // 如果语言指定了fallback,则在其回退语言中查找
let fallbackLangName = scope.getFallbackLanguage(activeLanguage)
if((fallbackLangName in formatters) && isFunction(formatters[fallbackLangName])) {
return scope.$cache.formatters[name] = formatters[fallbackLangName]
}
}
}
// 2. 全局作用域中查找 // 2. 全局作用域中查找
let formatters = target["*"] || {} let formatters = target["*"] || {}
if((name in formatters) && typeof(formatters[name])==="function") return scope.$cache.formatters[name] = formatters[name] if((name in formatters) && isFunction(formatters[name])) return scope.$cache.formatters[name] = formatters[name]
} }
} }
@ -290,7 +305,7 @@ function executeFormatter(value,formatters,scope){
let result = value let result = value
try{ try{
for(let formatter of formatters){ for(let formatter of formatters){
if(typeof(formatter) === "function") { if(isFunction(formatter)) {
result = formatter.call(scope,result) result = formatter.call(scope,result)
}else{// 如果碰到无效的格式化器,则跳过过续的格式化器 }else{// 如果碰到无效的格式化器,则跳过过续的格式化器
return result return result
@ -320,7 +335,7 @@ function buildFormatters(scope,activeLanguage,formatters){
for(let formatter of formatters){ for(let formatter of formatters){
if(formatter[0]){ if(formatter[0]){
const func = getFormatter(scope,activeLanguage,formatter[0]) const func = getFormatter(scope,activeLanguage,formatter[0])
if(typeof(func)==="function"){ if(isFunction(func)){
results.push((v)=>{ results.push((v)=>{
return func(v,...formatter[1]) return func(v,...formatter[1])
}) })
@ -328,7 +343,7 @@ function buildFormatters(scope,activeLanguage,formatters){
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用 // 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
// 比如padStart格式化器是String的原型方法不需要配置就可以直接作为格式化器调用 // 比如padStart格式化器是String的原型方法不需要配置就可以直接作为格式化器调用
results.push((v)=>{ results.push((v)=>{
if(typeof(v[formatter[0]])==="function"){ if(isFunction(v[formatter[0]])){
return v[formatter[0]].call(v,...formatter[1]) return v[formatter[0]].call(v,...formatter[1])
}else{ }else{
return v return v
@ -417,23 +432,23 @@ function replaceInterpolatedVars(template,...args) {
// 默认语言配置 // 默认语言配置
const defaultLanguageSettings = { const defaultLanguageSettings = {
debug : true,
defaultLanguage: "zh", defaultLanguage: "zh",
activeLanguage: "zh", activeLanguage : "zh",
languages:[ formatters : inlineFormatters,
languages : [
{name:"zh",title:"中文",default:true}, {name:"zh",title:"中文",default:true},
{name:"en",title:"英文"} {name:"en",title:"英文"}
], ]
formatters:inlineFormatters,
datetime:{
},
currency:{
}
} }
/**
* 文本id必须是一个数字
* @param {*} content
* @returns
*/
function isMessageId(content){ function isMessageId(content){
return parseInt(content)>0 return isNumber(content)
} }
/** /**
* 根据值的单数和复数形式从messages中取得相应的消息 * 根据值的单数和复数形式从messages中取得相应的消息
@ -477,7 +492,7 @@ function translate(message) {
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用 // 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
if(arguments.length === 2 && isPlainObject(arguments[1])){ if(arguments.length === 2 && isPlainObject(arguments[1])){
Object.entries(arguments[1]).forEach(([name,value])=>{ Object.entries(arguments[1]).forEach(([name,value])=>{
if(typeof(value)==="function"){ if(isFunction(value)){
try{ try{
vars[name] = value() vars[name] = value()
}catch(e){ }catch(e){
@ -491,7 +506,7 @@ function translate(message) {
}else if(arguments.length >= 2){ }else if(arguments.length >= 2){
vars = [...arguments].splice(1).map((arg,index)=>{ vars = [...arguments].splice(1).map((arg,index)=>{
try{ try{
arg = typeof(arg)==="function" ? arg() : arg arg = isFunction(arg) ? arg() : arg
// 位置参数中以第一个数值变量为复数变量 // 位置参数中以第一个数值变量为复数变量
if(isNumber(arg)) pluraValue = parseInt(arg) if(isNumber(arg)) pluraValue = parseInt(arg)
}catch(e){ } }catch(e){ }
@ -580,7 +595,7 @@ function translate(message) {
get defaultMessageLoader(){ return this._defaultMessageLoader} get defaultMessageLoader(){ return this._defaultMessageLoader}
// 通过默认加载器加载文件 // 通过默认加载器加载文件
async loadMessagesFromDefaultLoader(newLanguage,scope){ async loadMessagesFromDefaultLoader(newLanguage,scope){
if(typeof(this._defaultMessageLoader) != "function") return //throw new Error("No default message loader specified") if(!isFunction(this._defaultMessageLoader)) return //throw new Error("No default message loader specified")
return await this._defaultMessageLoader.call(scope,newLanguage,scope) return await this._defaultMessageLoader.call(scope,newLanguage,scope)
} }
/** /**
@ -588,7 +603,7 @@ function translate(message) {
*/ */
async change(value){ async change(value){
value=value.trim() value=value.trim()
if(this.languages.findIndex(lang=>lang.name === value)!==-1 || typeof(this._defaultMessageLoader)==="function"){ if(this.languages.findIndex(lang=>lang.name === value)!==-1 || isFunction(this._defaultMessageLoader)){
// 通知所有作用域刷新到对应的语言包 // 通知所有作用域刷新到对应的语言包
await this._refreshScopes(value) await this._refreshScopes(value)
this._settings.activeLanguage = value this._settings.activeLanguage = value
@ -636,29 +651,36 @@ function translate(message) {
* 注册全局格式化器 * 注册全局格式化器
* 格式化器是一个简单的同步函数value=>{...}用来对输入进行格式化后返回结果 * 格式化器是一个简单的同步函数value=>{...}用来对输入进行格式化后返回结果
* *
* registerFormatters(name,value=>{...}) // 适用于所有语言 * registerFormatter(name,value=>{...}) // 注册到所有语言
* registerFormatters(name,value=>{...},{langauge:"zh"}) // 适用于cn语言 * registerFormatter(name,value=>{...},{langauge:"zh"}) // 注册到zh语言
* registerFormatters(name,value=>{...},{langauge:"en"}) // 适用于en语言 * registerFormatter(name,value=>{...},{langauge:"en"}) // 注册到en语言
registerFormatter("Date",value=>{...},{langauge:"en"}) // 注册到en语言的默认数据类型格式化器
registerFormatter(name,value=>{...},{langauge:["zh","cht"]}) // 注册到zh和cht语言
registerFormatter(name,value=>{...},{langauge:"zh,cht"})
* @param {*} formatters * @param {*} formatter
language : 声明该格式化器适用语言 language : 声明该格式化器适用语言
isGlobal : 注册到全局 isGlobal : 注册到全局
*/ */
registerFormatter(name,formatter,{language="*",isGlobal}={}){ registerFormatter(name,formatter,{language="*"}={}){
if(!typeof(formatter)==="function" || typeof(name)!=="string"){ if(!isFunction(formatter) || typeof(name)!=="string"){
throw new TypeError("Formatter must be a function") throw new TypeError("Formatter must be a function")
} }
if(DataTypes.includes(name)){ language = Array.isArray(language) ? language : (language ? language.split(",") : [])
this.formatters[language].$types[name] = formatter language.forEach(lng=>{
}else{ if(DataTypes.includes(name)){
this.formatters[language][name] = formatter this.formatters[lng].$types[name] = formatter
} }else{
this.formatters[lng][name] = formatter
}
})
} }
/** /**
* 注册默认文本信息加载器 * 注册默认文本信息加载器
*/ */
registerDefaultLoader(fn){ registerDefaultLoader(fn){
if(typeof(fn) !== 'function') throw new Error("The default loader must be a async function or promise returned") if(!isFunction(fn)) throw new Error("The default loader must be a async function or promise returned")
this._defaultMessageLoader = fn this._defaultMessageLoader = fn
this.refresh() this.refresh()
} }
@ -673,27 +695,6 @@ function translate(message) {
}catch{} }catch{}
} }
}
/**
* 创建格式化器
* @param {*} fn
* @param {*} options = {
* erorr:(e)=>{...} //执行出错时返回值
* empty:()=>{...} // 当空值时的返回值 *
* }
*
*/
function createFormatter(fn,options){
if(isPlainObject(options)) fn._options = options
return fn.bind(fn._options)
}
/**
* 扩展格式化器
* @param {*} fn
*/
function extendFormatter(fn){
} }
module.exports ={ module.exports ={

View File

@ -1,38 +1,40 @@
const { isPlainObject } = require("./utils") const { isPlainObject,isFunction } = 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"];
module.exports = class i18nScope { module.exports = class i18nScope {
constructor(options={},callback){ constructor(options={},callback){
// 每个作用域都有一个唯一的id
this._id = options.id || (new Date().getTime().toString()+parseInt(Math.random()*1000)) this._id = options.id || (new Date().getTime().toString()+parseInt(Math.random()*1000))
this._languages = options.languages // 当前作用域的语言列表 this._debug = options.debug // 当出错时是否在控制台台输出错误信息
this._languages = options.languages // 当前作用域的语言列表
this._defaultLanguage = options.defaultLanguage || "zh" // 默认语言名称 this._defaultLanguage = options.defaultLanguage || "zh" // 默认语言名称
this._activeLanguage = options.activeLanguage // 当前语言名称 this._activeLanguage = options.activeLanguage // 当前语言名称
this._default = options.default // 默认语言包 this._default = options.default // 默认语言包
this._messages = options.messages // 当前语言包 this._messages = options.messages // 当前语言包
this._idMap = options.idMap // 消息id映射列表 this._idMap = options.idMap // 消息id映射列表
this._formatters = options.formatters // 当前作用域的格式化函数列表 this._formatters = options.formatters // 当前作用域的格式化函数列表{<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}}
this._loaders = options.loaders // 异步加载语言文件的函数列表 this._loaders = options.loaders // 异步加载语言文件的函数列表
this._global = null // 引用全局VoerkaI18n配置注册后自动引用 this._global = null // 引用全局VoerkaI18n配置注册后自动引用
this._activeFormatters= options.formatters[options.activeLanguage] // 激活使用的格式化器,查找格式化器时在此查找
this._patchMessages = {} // 语言包补丁信息 {<language>:{....},<language>:{....}} this._patchMessages = {} // 语言包补丁信息 {<language>:{....},<language>:{....}}
// 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索 // 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历
this.$cache={ this.$cache={
activeLanguage : null, activeLanguage : null,
typedFormatters: {}, typedFormatters: {},
formatters : {}, formatters : {},
} }
// 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域 // 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域则自动创建全局VoerkaI18n实例
// 则使用当前作用域来初始化全局VoerkaI18n实例
if(!globalThis.VoerkaI18n){ if(!globalThis.VoerkaI18n){
const { I18nManager } = require("./") const { I18nManager } = require("./")
globalThis.VoerkaI18n = new I18nManager({ globalThis.VoerkaI18n = new I18nManager({
debug : this._debug,
defaultLanguage: this.defaultLanguage, defaultLanguage: this.defaultLanguage,
activeLanguage : this.activeLanguage, activeLanguage : this.activeLanguage,
languages: options.languages, languages : options.languages,
}) })
} }
this._global = globalThis.VoerkaI18n this._global = globalThis.VoerkaI18n
// 合并补丁语言包
this._mergePatchedMessages() this._mergePatchedMessages()
this._patch(this._messages,this.activeLanguage) this._patch(this._messages,this.activeLanguage)
// 正在加载语言包标识 // 正在加载语言包标识
@ -42,6 +44,8 @@ module.exports = class i18nScope {
} }
// 作用域 // 作用域
get id(){return this._id} get id(){return this._id}
// 调试开关
get debug(){return this._debug}
// 默认语言名称 // 默认语言名称
get defaultLanguage(){return this._defaultLanguage} get defaultLanguage(){return this._defaultLanguage}
// 默认语言名称 // 默认语言名称
@ -52,7 +56,7 @@ module.exports = class i18nScope {
get messages(){return this._messages} get messages(){return this._messages}
// 消息id映射列表 // 消息id映射列表
get idMap(){return this._idMap} get idMap(){return this._idMap}
// 当前作用域的格式化函数列表 // 当前作用域的格式化器 {<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}}
get formatters(){return this._formatters} get formatters(){return this._formatters}
// 当前作用域支持的语言列表[{name,title,fallback}] // 当前作用域支持的语言列表[{name,title,fallback}]
get languages(){return this._languages} get languages(){return this._languages}
@ -65,33 +69,39 @@ module.exports = class i18nScope {
* @param {*} callback 注册成功后的回调 * @param {*} callback 注册成功后的回调
*/ */
register(callback){ register(callback){
if(!typeof(callback)==="function") callback = ()=>{} if(!isFunction(callback)) callback = ()=>{}
this.global.register(this).then(callback).catch(callback) this.global.register(this).then(callback).catch(callback)
} }
/** /**
* 注册格式化器 * 注册格式化器
*
* 格式化器是一个简单的同步函数value=>{...}用来对输入进行格式化后返回结果 * 格式化器是一个简单的同步函数value=>{...}用来对输入进行格式化后返回结果
* *
* registerFormatters(name,value=>{...}) // 适用于所有语言 * registerFormatter(name,value=>{...}) // 注册到所有语言
* registerFormatters(name,value=>{...},{langauge:"zh"}) // 适用于cn语言 * registerFormatter(name,value=>{...},{langauge:"zh"}) // 注册到zh语言
* registerFormatters(name,value=>{...},{langauge:"en"}) // 适用于en语言 * registerFormatter(name,value=>{...},{langauge:"en"}) // 注册到en语言
registerFormatter("Date",value=>{...},{langauge:"en"}) // 注册到en语言的默认数据类型格式化器
* @param {*} formatters registerFormatter(name,value=>{...},{langauge:["zh","cht"]}) // 注册到zh和cht语言
registerFormatter(name,value=>{...},{langauge:"zh,cht"})
* @param {*} formatter 格式化器
language : 声明该格式化器适用语言 language : 声明该格式化器适用语言
isGlobal : 注册到全局 asGlobal : 注册到全局
*/ */
registerFormatter(name,formatter,{language="*",isGlobal}={}){ registerFormatter(name,formatter,{language="*",asGlobal}={}){
if(!typeof(formatter)==="function" || typeof(name)!=="string"){ if(!isFunction(formatter) || typeof(name)!=="string"){
throw new TypeError("Formatter must be a function") throw new TypeError("Formatter must be a function")
} }
if(isGlobal){ language = Array.isArray(language) ? language : (language ? language.split(",") : [])
if(asGlobal){
this.global.registerFormatter(name,formatter,{language}) this.global.registerFormatter(name,formatter,{language})
}else{ }else{
if(DataTypes.includes(name)){ language.forEach(lng=>{
this.formatters[language].$types[name] = formatter if(DataTypes.includes(name)){
}else{ this._formatters[lng].$types[name] = formatter
this.formatters[language][name] = formatter }else{
} this._formatters[lng][name] = formatter
}
})
} }
} }
/** /**
@ -101,17 +111,76 @@ module.exports = class i18nScope {
registerDefaultLoader(fn){ registerDefaultLoader(fn){
this.global.registerDefaultLoader(fn) this.global.registerDefaultLoader(fn)
} }
_getLanguage(lang){ /**
let index = this._languages.findIndex(lng=>lng.name==lang) * 获取指定语言信息
* @param {*} language
* @returns
*/
_getLanguage(language){
let index = this._languages.findIndex(lng=>lng.name==language)
if(index!==-1) return this._languages[index] if(index!==-1) return this._languages[index]
} }
/**
* 返回是否存在指定的语言
* @param {*} language 语言名称
* @returns
*/
hasLanguage(language){
return this._languages.indexOf(lang=>lang.name==language)!==-1
}
/**
* 获取指定语言的回退语言名称
*
* 如果没有指定则总是回退到到默认语言
*
* 但是不支持回退链
*
* 可以在配置中指定回退语言
* // settings.json
* {
* languages:[
* {name:"zh"},
* {name:"cht",fallback:"zh"}, //繁体中文可以回退到简体中文,
* {name:"en"},
* ]
* }
*
* @param {*} language
* @returns {Object} 语言信息数据 {name,title,fallback,...}
*/
getFallbackLanguage(language){
let lang = this._getLanguage(language)
if(lang){
return this.hasLanguage(lang.fallback) ? lang.fallback : this.defaultLanguage
}
}
/** /**
* 回退到默认语言 * 回退到默认语言
*/ */
_fallback(newLanguage){ _fallback(){
this._messages = this._default this._messages = this._default
this._activeLanguage = this.defaultLanguage this._activeLanguage = this.defaultLanguage
} }
/**
* 当切换语言时格式化器应该切换到对应语言的格式化器
* @param {*} language
*/
async _loadFormatters(newLanguage){
try{
if(newLanguage in this._formatters){
let loader = this._formatters[newLanguage]
if(isPlainObject(loader)){
this._activeFormatters = loader
}else if(isFunction(loader)){
this._activeFormatters = (await loader()).default
}
}else{
if(this._debug) console.warn(`Not configured <${newLanguage}> formatters.`)
}
}catch(e){
if(this._debug) console.error(`Error loading ${newLanguage} formatters: ${e.message}`)
}
}
/** /**
* 刷新当前语言包 * 刷新当前语言包
* @param {*} newLanguage * @param {*} newLanguage
@ -119,29 +188,35 @@ module.exports = class i18nScope {
async refresh(newLanguage){ async refresh(newLanguage){
this._refreshing = true this._refreshing = true
if(!newLanguage) newLanguage = this.activeLanguage if(!newLanguage) newLanguage = this.activeLanguage
// 默认语言,默认语言采用静态加载方式,只需要简单的替换即可 // 默认语言:由于默认语言采用静态加载方式而不是异步块,因此只需要简单的替换即可
if(newLanguage === this.defaultLanguage){ if(newLanguage === this.defaultLanguage){
this._messages = this._default this._messages = this._default
await this._patch(this._messages,newLanguage) // 异步补丁 await this._patch(this._messages,newLanguage) // 异步补丁
this._loadFormatters(newLanguage)
return return
} }
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数 // 非默认语言需要异步加载语言包文件,加载器是一个异步函数
// 如果没有加载器,则无法加载语言包,因此回退到默认语言 // 如果没有加载器,则无法加载语言包,因此回退到默认语言
let loader = this.loaders[newLanguage] let loader = this.loaders[newLanguage]
try{ try{
if(typeof(loader) === "function"){ if(isPlainObject(loader)){
this._messages = loader
await this._patch(this._messages,newLanguage)
}else if(isFunction(loader)){
this._messages = (await loader()).default this._messages = (await loader()).default
this._activeLanguage = newLanguage this._activeLanguage = newLanguage
await this._patch(this._messages,newLanguage) await this._patch(this._messages,newLanguage)
}else if(typeof(this.global.defaultMessageLoader) === "function"){// 如果该语言没有指定加载器,则使用全局配置的默认加载器 }else if(isFunction(this.global.defaultMessageLoader)){// 如果该语言没有指定加载器,则使用全局配置的默认加载器
const loadedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this) const loadedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this)
this._messages = Object.assign({},this._default,loadedMessages) this._messages = Object.assign({},this._default,loadedMessages)
this._activeLanguage = newLanguage this._activeLanguage = newLanguage
}else{ }else{
this._fallback() this._fallback()
} }
// 应该切换到对应语言的格式化器
this._loadFormatters(newLanguage)
}catch(e){ }catch(e){
console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`) if(this._debug) console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`)
this._fallback() this._fallback()
}finally{ }finally{
this._refreshing = false this._refreshing = false
@ -157,14 +232,16 @@ module.exports = class i18nScope {
* @returns * @returns
*/ */
async _patch(messages,newLanguage){ async _patch(messages,newLanguage){
if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return if(!isFunction(this.global.loadMessagesFromDefaultLoader)) return
try{ try{
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this) let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this)
if(isPlainObject(pachedMessages)){ if(isPlainObject(pachedMessages)){
Object.assign(messages,pachedMessages) Object.assign(messages,pachedMessages)
this._savePatchedMessages(pachedMessages,newLanguage) this._savePatchedMessages(pachedMessages,newLanguage)
} }
}catch{} }catch(e){
if(this._debug) console.error(`Error while loading <${newLanguage}> messages from remote:${error.message}`)
}
} }
/** /**
* 从本地存储中读取语言包补丁合并到当前语言包中 * 从本地存储中读取语言包补丁合并到当前语言包中
@ -190,7 +267,6 @@ module.exports = class i18nScope {
* 因此采用的方式是 * 因此采用的方式是
* - 加载语言包补丁后将之保存到到本地的LocalStorage中 * - 加载语言包补丁后将之保存到到本地的LocalStorage中
* - 当应用加载时会查询是否存在补丁如果存在就会合并渲染 * - 当应用加载时会查询是否存在补丁如果存在就会合并渲染
* -
* *
* @param {*} messages * @param {*} messages
*/ */
@ -200,9 +276,14 @@ module.exports = class i18nScope {
globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages)); globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages));
} }
}catch(e){ }catch(e){
console.error("Error while save voerkai18n patched messages:",e.message) if(this.$cache._debug) console.error("Error while save voerkai18n patched messages:",e.message)
} }
} }
/**
* 从本地缓存中读取补丁语言包
* @param {*} language
* @returns
*/
_getPatchedMessages(language){ _getPatchedMessages(language){
try{ try{
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`)) return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`))
@ -211,10 +292,8 @@ module.exports = class i18nScope {
} }
} }
// 以下方法引用全局VoerkaI18n实例的方法 // 以下方法引用全局VoerkaI18n实例的方法
get on(){return this.global.on.bind(this.global)} get on(){return this._global.on.bind(this._global)}
get off(){return this.global.off.bind(this.global)} get off(){return this._global.off.bind(this._global)}
get offAll(){return this.global.offAll.bind(this.global)} get offAll(){return this._global.offAll.bind(this._global)}
get change(){ get change(){return this._global.change.bind(this._global)}
return this.global.change.bind(this.global)
}
} }

View File

@ -41,6 +41,9 @@
return false return false
} }
} }
function isFunction(fn){
return typeof fn === "function"
}
/** /**
* 当value= null || undefined || "" || [] || {} 时返回true * 当value= null || undefined || "" || [] || {} 时返回true
* @param {*} value * @param {*} value
@ -142,8 +145,7 @@ function toNumber(value,defualt=0) {
} catch { } catch {
return value return value
} }
} }
/** /**
* 转换为货币格式 * 转换为货币格式
* *
@ -184,6 +186,7 @@ function relativeTime(value, rel){
module.exports ={ module.exports ={
isPlainObject, isPlainObject,
isFunction,
isNumber, isNumber,
isNothing, isNothing,
deepMerge, deepMerge,