From ab9e1504706e7467395004ed13754e4fa87b0f95 Mon Sep 17 00:00:00 2001 From: wxzhang Date: Thu, 11 Aug 2022 18:33:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E5=99=A8=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/apps/vueapp/src/languages/index.js | 39 ++-- .../apps/vueapp/src/languages/settings.json | 4 + .../src/languages/translates/default.json | 90 ++++++--- packages/cli/compile.command.js | 56 ++++-- packages/cli/templates/entry.js | 57 +++--- packages/cli/templates/formatters.js | 109 +++++------ packages/runtime/cnutils.js | 2 + packages/runtime/formatters/en.js | 23 +++ packages/runtime/formatters/index.js | 1 + packages/runtime/formatters/zh.js | 26 ++- packages/runtime/index.js | 181 +++++++++--------- packages/runtime/scope.js | 161 ++++++++++++---- packages/runtime/utils.js | 7 +- 13 files changed, 477 insertions(+), 279 deletions(-) diff --git a/packages/apps/vueapp/src/languages/index.js b/packages/apps/vueapp/src/languages/index.js index f05eeb9..4ee9f2f 100644 --- a/packages/apps/vueapp/src/languages/index.js +++ b/packages/apps/vueapp/src/languages/index.js @@ -1,9 +1,9 @@ -import messageIds from "./idMap.js" -import runtime from "./runtime.js" -const { translate,i18nScope } = runtime +import messageIds from "./idMap.js" // 语言ID映射文件 +import { translate,i18nScope } from "./runtime.js" // 运行时 +import defaultFormatters from "./formatters/zh.js" // 默认语言格式化器 +const activeFormatters = defaultFormatters // 激活语言格式化器 -import formatters from "./formatters.js" import defaultMessages from "./zh.js" const activeMessages = defaultMessages @@ -18,24 +18,37 @@ const scopeSettings = { { "name": "en", "title": "英文" + }, + { + "name": "de", + "title": "德语" } ], "defaultLanguage": "zh", "activeLanguage": "zh", "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({ - ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters - id: "vueapp", // 当前作用域的id,自动取当前工程的package.json的name - default: defaultMessages, // 默认语言包 - messages : activeMessages, // 当前语言包 - idMap:messageIds, // 消息id映射列表 - formatters, // 当前作用域的格式化函数列表 - loaders:{ - "en" : ()=>import("./en.js") - } + ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters + id : "vueapp", // 当前作用域的id,自动取当前工程的package.json的name + default : defaultMessages, // 默认语言包 + messages : activeMessages, // 当前语言包 + idMap : messageIds, // 消息id映射列表 + formatters, // 扩展自定义格式化器 + loaders // 语言包加载器 }) // 翻译函数 const scopedTtranslate = translate.bind(scope) diff --git a/packages/apps/vueapp/src/languages/settings.json b/packages/apps/vueapp/src/languages/settings.json index 3984231..ecdb463 100644 --- a/packages/apps/vueapp/src/languages/settings.json +++ b/packages/apps/vueapp/src/languages/settings.json @@ -7,6 +7,10 @@ { "name": "en", "title": "英文" + }, + { + "name": "de", + "title": "德语" } ], "defaultLanguage": "zh", diff --git a/packages/apps/vueapp/src/languages/translates/default.json b/packages/apps/vueapp/src/languages/translates/default.json index ef29d94..79e2340 100644 --- a/packages/apps/vueapp/src/languages/translates/default.json +++ b/packages/apps/vueapp/src/languages/translates/default.json @@ -3,180 +3,210 @@ "en": "Hello world!", "$file": [ "App.vue" - ] + ], + "de": "Hello world!" }, "中华人民共和国": { "en": "The People's Republic of China", "$file": [ "App.vue" - ] + ], + "de": "中华人民共和国" }, "迎接中华民族的伟大复兴": { "en": "Welcome the great rejuvenation of the Chinese nation", "$file": [ "App.vue" - ] + ], + "de": "迎接中华民族的伟大复兴" }, "成立于{}年": { "en": "Founded in {}", "$file": [ "components\\china.vue" - ] + ], + "de": "成立于{}年" }, "首都:北京": { "en": "Capital: Beijing", "$file": [ "components\\china.vue" - ] + ], + "de": "首都:北京" }, "VoerkaI18n多语言解决方案 ": { "en": "VoerkaI18n多语言解决方案 ", "$file": [ "App.vue" - ] + ], + "de": "VoerkaI18n多语言解决方案 " }, "现在是{ value | date }": { "en": "Now is { value | date }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | date }" }, "现在是{ value | shortdate }": { "en": "Now is { value | shortdate }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | shortdate }" }, "现在是{ value | time }": { "en": "Now is { value | time }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | time }" }, "现在是{ value | shorttime }": { "en": "Now is { value | shorttime }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | shorttime }" }, "现在是{ value | year }": { "en": "Now is { value | year }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | year }" }, "现在是{ value | month }": { "en": "Now is { value | month }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | month }" }, "现在是{ value | day }": { "en": "Now is { value | day }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | day }" }, "现在是{ value | weekdayValue }": { "en": "Now is { value | weekdayValue }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | weekdayValue }" }, "现在是{ value | weekday }": { "en": "Now is { value | weekday }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | weekday }" }, "现在是{ value | shortWeekday }": { "en": "Now is { value | shortWeekday }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | shortWeekday }" }, "现在是{ value | monthName }": { "en": "Now is { value | monthName }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | monthName }" }, "现在是{ value | shorMonthName }": { "en": "Now is { value | shorMonthName }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | shorMonthName }" }, "现在是{ value | hour }": { "en": "Now is { value | hour }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | hour }" }, "现在是{ value | hour12 }": { "en": "Now is { value | hour12 }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | hour12 }" }, "现在是{ value | minute }": { "en": "Now is { value | minute }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | minute }" }, "现在是{ value | second }": { "en": "Now is { value | second }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | second }" }, "现在是{ value | millisecond }": { "en": "Now is { value | millisecond }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | millisecond }" }, "现在是{ value | timestamp }": { "en": "Now is { value | timestamp }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value | timestamp }" }, "商品价格:{ value | currency }": { "en": "Price: { value | currency }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "商品价格:{ value | currency }" }, "商品价格:{ value | capitalizeCurrency }": { "en": "Price: { value | capitalizeCurrency }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "商品价格:{ value | capitalizeCurrency }" }, "现在是{ value }": { "en": "Now is { value }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "现在是{ value }" }, "商品价格:{ value | currency('CNY','元整',3)}": { "en": "Price: { value | currency('CNY','元整',3)}", "$file": [ "components\\formatters.vue" - ] + ], + "de": "商品价格:{ value | currency('CNY','元整',3)}" }, "商品价格:{ value | capitalizeCurrency(true) }": { "en": "Price:{ value | capitalizeCurrency(true) }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "商品价格:{ value | capitalizeCurrency(true) }" }, "商品数量:{ value | number }": { "en": "Count: { value | number }", "$file": [ "components\\formatters.vue" - ] + ], + "de": "商品数量:{ value | number }" } } \ No newline at end of file diff --git a/packages/cli/compile.command.js b/packages/cli/compile.command.js index 1307129..dd08549 100644 --- a/packages/cli/compile.command.js +++ b/packages/cli/compile.command.js @@ -146,23 +146,47 @@ module.exports =async function compile(langFolder,opts={}){ } // 5 . 生成编译后的格式化函数文件 - const formattersFile = path.join(langFolder,"formatters.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.") + // const formattersFile = path.join(langFolder,"formatters.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)) + // } + + 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. 生成编译后的访问入口文件 const entryFile = path.join(langFolder,"index.js") diff --git a/packages/cli/templates/entry.js b/packages/cli/templates/entry.js index f059c3c..6a1af6b 100644 --- a/packages/cli/templates/entry.js +++ b/packages/cli/templates/entry.js @@ -1,35 +1,48 @@ {{if moduleType === "esm"}} -import messageIds from "./idMap.js" -{{if inlineRuntime }}import runtime from "./runtime.js" -const { translate,i18nScope } = runtime -{{else}}import { translate,i18nScope } from "@voerkai18n/runtime"{{/if}} -import formatters from "./formatters.js" +import messageIds from "./idMap.js" // 语言ID映射文件 +{{if inlineRuntime }}import { translate,i18nScope } from "./runtime.js" // 运行时 +import defaultFormatters from "./formatters/{{defaultLanguage}}.js" // 默认语言格式化器 +{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "./formatters/{{activeLanguage}}.js"{{/if}} // 激活语言格式化器 +{{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" -{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages -{{else}}import activeMessages from "./{{activeLanguage}}.js"{{/if}} +{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages{{else}}import activeMessages from "./{{activeLanguage}}.js"{{/if}} {{else}} const messageIds = require("./idMap") {{if inlineRuntime }}const { translate,i18nScope } = require("./runtime.js") -{{else}}const { translate,i18nScope } = require("@voerkai18n/runtime"){{/if}} -const formatters = require("./formatters.js") -const defaultMessages = require("./{{defaultLanguage}}.js") -{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages -{{else}}const activeMessages = require("./{{activeLanguage}}.js"){{/if}} -{{/if}} +const defaultFormatters = require("./formatters/{{defaultLanguage}}.js") +{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}const activeFormatters = require("./formatters/{{activeLanguage}}.js"){{/if}} +{{else}}const { translate,i18nScope } = require("@voerkai18n/runtime") +const defaultFormatters = require("@voerkai18n/runtime/formatters/{{defaultLanguage}}.js") +{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}const activeFormatters = require("@voerkai18n/runtime/formatters/{{activeLanguage}}.js"){{/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 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({ - ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters - id: "{{scopeId}}", // 当前作用域的id,自动取当前工程的package.json的name - default: defaultMessages, // 默认语言包 - messages : activeMessages, // 当前语言包 - idMap:messageIds, // 消息id映射列表 - formatters, // 当前作用域的格式化函数列表 - 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}} - } + ...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters + id : "{{scopeId}}", // 当前作用域的id,自动取当前工程的package.json的name + debug : false, // 是否在控制台输出高度信息 + default : defaultMessages, // 默认语言包 + messages : activeMessages, // 当前语言包 + idMap : messageIds, // 消息id映射列表 + formatters, // 扩展自定义格式化器 + loaders // 语言包加载器 }) // 翻译函数 const scopedTtranslate = translate.bind(scope) diff --git a/packages/cli/templates/formatters.js b/packages/cli/templates/formatters.js index 1793dde..3cd6df5 100644 --- a/packages/cli/templates/formatters.js +++ b/packages/cli/templates/formatters.js @@ -1,35 +1,21 @@ /** - 格式化器用来对翻译文本内容中的插值变量进行格式化, + 格式化器用来对翻译文本内容中的插值变量进行处理 + 比如将一个数字格式化为货币格式,或者将一个日期格式化为友好的日期格式。 - 以下定义了一些格式化器,在中文场景下,会启用这些格式化器。 import dayjs from "dayjs"; - const formatters = { - "*":{ // 在所有语言下生效的格式化器 - $types:{...}, // 只作用于特定数据类型的默认格式化器 - .... // 全局格式化器 - }, - 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)=>{...}, + export default { + $options:{...}, + $types:{ + Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"), }, - en:{ - $types:{ - Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器 - }, - date:(value)=>dayjs(value).format("YYYY/MM/DD") - bjTime:(value)=>"BeiJing "+ value, - [格式化器名称]:(value)=>{...}, - [格式化器名称]:(value)=>{...}, - [格式化器名称]:(value)=>{...}, + date:(value)=>dayjs(value).format("YYYY年MM月DD日") + bjTime:(value)=>"北京时间"+ value, + [格式化器名称]:(value)=>{...}, + [格式化器名称]:(value)=>{...}, + [格式化器名称]:(value)=>{...}, } } - 在翻译函数中使用格式化器的方法,示例如下: @@ -38,48 +24,47 @@ 其等效于: t(`Now is ${bjTime(date(value))",{value: new Date()}) 由于value分别经过两个管道符转换,上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符。 - 最终的输出结果: 中文: "现在是北京时间2022年3月1日" 英文: "Now is BeiJing 2022/03/01" - - * */ - {{if moduleType === "esm"}} - export default{{else}}module.exports = {{/if}}{ - // 在所有语言下生效的格式化器 - "*":{ - //[格式化名称]:(value)=>{...}, - //[格式化名称]:(value,arg)=>{...}, - }, - // 在所有语言下只作用于特定数据类型的格式化器 - $types:{ + // 格式化器参数 + $options:{ - }, -{{each languages}} {{$value.name}}:{ - $types:{ - // 所有类型的默认格式化器 - // "*":{ - - // }, - // Date:{ - - // }, - // Number:{ - - // }, - // String:{ - - // }, - // Array:{ - - // }, - // Object:{ - - // } - } - }{{if $index !== languages.length - 1}},{{/if}} -{{/each}}} \ No newline at end of file + }, + // 指定数据类型的默认格式化器 + $types:{ + // "*" : { }, + // Date : { }, + // Number: { }, + // String: { }, + // Array : { }, + // Object: { } + } + // 允许重载内置的格式化器 + // --- 日期 ------ + // date : value => { ... }, + // shortdate : value => { ... }, + // time : value => { ... }, + // shorttime : value => { ... }, + // year : value => { ... }, + // month : value => { ... }, + // day : value => { ... }, + // weekdayValue : value => { ... }, + // weekday : value => { ... }, + // shortWeekday : value => { ... }, + // monthName : value => { ... }, + // shorMonthName : value => { ... }, + // --- 时间 ------ + // hour : value => { ... }, + // hour12 : value => { ... }, + // minute : value => { ... }, + // second : value => { ... }, + // millisecond : value => { ... }, + // timestamp : value => { ... }, + // currency : value => { ... }, + // number : value => { ... }, +} diff --git a/packages/runtime/cnutils.js b/packages/runtime/cnutils.js index 0305c5a..f6816ad 100644 --- a/packages/runtime/cnutils.js +++ b/packages/runtime/cnutils.js @@ -5,6 +5,7 @@ */ const { isNumber } = require('./utils') +const CN_DATETIME_UNITS = ["年","季度","月","周","日","小时","分钟","秒","毫秒","微秒"] const CN_WEEK_DAYS = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"] const CN_SHORT_WEEK_DAYS =["日","一","二","三","四","五","六"] const CN_MONTH_NAMES= ["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"] @@ -93,6 +94,7 @@ function toChineseCurrency(value,{big=false,prefix="",unit="元",suffix=""}={}){ toChineseCurrency, toChineseNumber, toChineseBigNumber, + CN_DATETIME_UNITS, CN_WEEK_DAYS, CN_SHORT_WEEK_DAYS, CN_MONTH_NAMES, diff --git a/packages/runtime/formatters/en.js b/packages/runtime/formatters/en.js index 9a5a775..c4558d2 100644 --- a/packages/runtime/formatters/en.js +++ b/packages/runtime/formatters/en.js @@ -6,12 +6,35 @@ const { toDate,toCurrency } = require("../utils") 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: { Date : value => { const d = toDate(value); return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` }, Null : value =>"", Undefined: value =>"", Error : value => "ERROR" }, + // 以下是格式化定义 // 日期 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()}` }, diff --git a/packages/runtime/formatters/index.js b/packages/runtime/formatters/index.js index 024c658..e1fed09 100644 --- a/packages/runtime/formatters/index.js +++ b/packages/runtime/formatters/index.js @@ -1,5 +1,6 @@ /** * 内置的格式化器 + * 被注册到全局语言管理器 */ const enFormatters = require("./en") diff --git a/packages/runtime/formatters/zh.js b/packages/runtime/formatters/zh.js index 771897e..412f2b2 100644 --- a/packages/runtime/formatters/zh.js +++ b/packages/runtime/formatters/zh.js @@ -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") - -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: { Date: value => {const d = toDate(value);return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 ${d.getHours()}点${d.getMinutes()}分${d.getSeconds()}秒`} }, diff --git a/packages/runtime/index.js b/packages/runtime/index.js index a70611e..b16e6a8 100644 --- a/packages/runtime/index.js +++ b/packages/runtime/index.js @@ -1,7 +1,7 @@ const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require("./utils") const EventEmitter = require("./eventemitter") 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 " - 需要统一解析为 + 统一解析为 [ [aaa,[1,1]], // [formatter'name,[args,...]] - [bbb,[]], + [<格式化器名称>,[<参数>,<参数>,...]] ] formatters="| aaa(1,1,"dddd") | bbb " - 目前对参数采用简单的split(",")来解析,因此如果参数中包括了,则无法正确解析,例如aaa(1,1,"dd,,dd")形式的参数 + 特别注意: + - 目前对参数采用简单的split(",")来解析,因此如果参数中包括了逗号等会影响解析的字符时,可能导致错误 + 例如aaa(1,1,"dd,,dd")形式的参数 在此场景下基本够用了,如果需要支持更复杂的参数解析,可以后续考虑使用正则表达式来解析 + - 如果参数是{},[],则尝试解决为对象和数组,但是里面的内容无法支持复杂和嵌套数据类型 - @returns [[,[,,...]]] + @returns [ [<格式化器名称>,[<参数>,<参数>,...],[<格式化器名称>,[<参数>,<参数>,...]],...] */ function parseFormatters(formatters){ if(!formatters) return [] // 1. 先解析为 ["aaa()","bbb"]形式 let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim()) - // 2. 解析格式化器参数 return result.map(formatter=>{ let firstIndex = formatter.indexOf("(") @@ -64,11 +67,11 @@ function parseFormatters(formatters){ const argsContent = formatter.substr(firstIndex+1,lastIndex-firstIndex-1).trim() let args = argsContent=="" ? [] : argsContent.split(",").map(arg=>{ arg = arg.trim() - if(!isNaN(parseInt(arg))){ - return parseInt(arg) // 数字 + if(isNumber(arg)){ // 数字 + return parseFloat(arg) }else if((arg.startsWith('\"') && arg.endsWith('\"')) || (arg.startsWith('\'') && arg.endsWith('\'')) ){ - return arg.substr(1,arg.length-2) // 字符串 - }else if(arg.toLowerCase()==="true" || arg.toLowerCase()==="false"){ + return arg.substr(1,arg.length-2) // 字符串 + }else if(["true" ,"false"].includes(arg.toLowerCase())){ return arg.toLowerCase()==="true" // 布尔值 }else if((arg.startsWith('{') && arg.endsWith('}')) || (arg.startsWith('[') && arg.endsWith(']'))){ try{ @@ -89,7 +92,7 @@ function parseFormatters(formatters){ /** * 提取字符串中的插值变量 - * // [ + * [ // { name:<变量名称>,formatters:[{name:<格式化器名称>,args:[<参数>,<参数>,....]]}],<匹配字符串>], // .... @@ -145,12 +148,12 @@ function forEachInterpolatedVars(str,callback,options={}){ const varname = match.groups.varname || "" // 解析格式化器和参数 = [,[,[,,...]]] const formatters = parseFormatters(match.groups.formatters) - if(typeof(callback)==="function"){ + if(isFunction(callback)){ try{ - if(opts.replaceAll){ + if(opts.replaceAll){ // 在某此版本上可能没有 result=result.replaceAll(match[0],callback(varname,formatters,match[0])) }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函数可能会抛出异常,如果抛出异常,则中断匹配过程 break @@ -161,7 +164,8 @@ function forEachInterpolatedVars(str,callback,options={}){ return result } /** - * 将要翻译内容提供了一个非文本内容时进行默认的转换 + * 当传入的翻译内容不是一个字符串时,进行默认的转换 + * * - 对函数则执行并取返回结果() * - 对Array和Object使用JSON.stringify * - 其他类型使用toString @@ -171,17 +175,25 @@ function forEachInterpolatedVars(str,callback,options={}){ */ function transformToString(value){ let result = value - if(typeof(result)==="function") result = value() - if(!(typeof(result)==="string")){ - if(Array.isArray(result) || isPlainObject(result)){ - result = JSON.stringify(result) - }else{ - result = result.toString() + try{ + if(isFunction(result)) result = value() + if(!(typeof(result)==="string")){ + if(Array.isArray(result) || isPlainObject(result)){ + result = JSON.stringify(result) + }else{ + result = result.toString() + } } + }catch{ + result = result.toString() } return result } - +/** + * 清空指定语言的缓存 + * @param {*} scope + * @param {*} activeLanguage + */ function resetScopeCache(scope,activeLanguage=null){ scope.$cache = {activeLanguage,typedFormatters:{},formatters:{}} } @@ -189,26 +201,20 @@ function resetScopeCache(scope,activeLanguage=null){ * 取得指定数据类型的默认格式化器 * * 可以为每一个数据类型指定一个默认的格式化器,当传入插值变量时, - * 会自动调用该格式化器来对值进行格式化转换 - * - * - * - + * 会自动调用该格式化器来对值进行格式化转换 const formatters = { "*":{ $types:{...} // 在所有语言下只作用于特定数据类型的格式化器 }, // 在所有语言下生效的格式化器 zh:{ $types:{ - [数据类型]:{ - default:(value)=>{...} // 默认 - }, - + [数据类型]:(value)=>{...} // 默认 }, [格式化器名称]:(value)=>{...}, [格式化器名称]:(value)=>{...}, [格式化器名称]:(value)=>{...}, }, + en:{.....} } * @param {*} scope * @param {*} activeLanguage @@ -216,6 +222,7 @@ function resetScopeCache(scope,activeLanguage=null){ * @returns {Function} 格式化函数 */ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){ + // 当指定数据类型的的默认格式化器的缓存处理 if(!scope.$cache) resetScopeCache(scope) if(scope.$cache.activeLanguage === activeLanguage) { if(dataType in scope.$cache.typedFormatters) return scope.$cache.typedFormatters[dataType] @@ -229,20 +236,21 @@ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){ // 1. 在全局$types中查找 if(("*" in target) && isPlainObject(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] } } // 2. 当前语言的$types中查找 if((activeLanguage in target) && isPlainObject(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] } } - } + } } + /** * 获取指定名称的格式化器函数 * @@ -254,7 +262,7 @@ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){ * 全局作用域的格式化器优先 * * @param {*} scope - * @param {*} activeLanguage + * @param {*} activeLanguage 当前激活语言名称 * @param {*} name 格式化器名称 * @returns {Function} 格式化函数 */ @@ -272,11 +280,18 @@ function getFormatter(scope,activeLanguage,name){ // 1. 优先在当前语言查找 if(activeLanguage in target){ 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. 全局作用域中查找 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 try{ for(let formatter of formatters){ - if(typeof(formatter) === "function") { + if(isFunction(formatter)) { result = formatter.call(scope,result) }else{// 如果碰到无效的格式化器,则跳过过续的格式化器 return result @@ -320,7 +335,7 @@ function buildFormatters(scope,activeLanguage,formatters){ for(let formatter of formatters){ if(formatter[0]){ const func = getFormatter(scope,activeLanguage,formatter[0]) - if(typeof(func)==="function"){ + if(isFunction(func)){ results.push((v)=>{ return func(v,...formatter[1]) }) @@ -328,7 +343,7 @@ function buildFormatters(scope,activeLanguage,formatters){ // 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用 // 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用 results.push((v)=>{ - if(typeof(v[formatter[0]])==="function"){ + if(isFunction(v[formatter[0]])){ return v[formatter[0]].call(v,...formatter[1]) }else{ return v @@ -417,23 +432,23 @@ function replaceInterpolatedVars(template,...args) { // 默认语言配置 const defaultLanguageSettings = { + debug : true, defaultLanguage: "zh", - activeLanguage: "zh", - languages:[ + activeLanguage : "zh", + formatters : inlineFormatters, + languages : [ {name:"zh",title:"中文",default:true}, {name:"en",title:"英文"} - ], - formatters:inlineFormatters, - datetime:{ - - }, - currency:{ - - } + ] } +/** + * 文本id必须是一个数字 + * @param {*} content + * @returns + */ function isMessageId(content){ - return parseInt(content)>0 + return isNumber(content) } /** * 根据值的单数和复数形式,从messages中取得相应的消息 @@ -477,7 +492,7 @@ function translate(message) { // 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用 if(arguments.length === 2 && isPlainObject(arguments[1])){ Object.entries(arguments[1]).forEach(([name,value])=>{ - if(typeof(value)==="function"){ + if(isFunction(value)){ try{ vars[name] = value() }catch(e){ @@ -491,7 +506,7 @@ function translate(message) { }else if(arguments.length >= 2){ vars = [...arguments].splice(1).map((arg,index)=>{ try{ - arg = typeof(arg)==="function" ? arg() : arg + arg = isFunction(arg) ? arg() : arg // 位置参数中以第一个数值变量为复数变量 if(isNumber(arg)) pluraValue = parseInt(arg) }catch(e){ } @@ -580,7 +595,7 @@ function translate(message) { get defaultMessageLoader(){ return this._defaultMessageLoader} // 通过默认加载器加载文件 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) } /** @@ -588,7 +603,7 @@ function translate(message) { */ async change(value){ 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) this._settings.activeLanguage = value @@ -636,29 +651,36 @@ function translate(message) { * 注册全局格式化器 * 格式化器是一个简单的同步函数value=>{...},用来对输入进行格式化后返回结果 * - * registerFormatters(name,value=>{...}) // 适用于所有语言 - * registerFormatters(name,value=>{...},{langauge:"zh"}) // 适用于cn语言 - * registerFormatters(name,value=>{...},{langauge:"en"}) // 适用于en语言 + * registerFormatter(name,value=>{...}) // 注册到所有语言 + * registerFormatter(name,value=>{...},{langauge:"zh"}) // 注册到zh语言 + * 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 : 声明该格式化器适用语言 isGlobal : 注册到全局 */ - registerFormatter(name,formatter,{language="*",isGlobal}={}){ - if(!typeof(formatter)==="function" || typeof(name)!=="string"){ + registerFormatter(name,formatter,{language="*"}={}){ + if(!isFunction(formatter) || typeof(name)!=="string"){ throw new TypeError("Formatter must be a function") } - if(DataTypes.includes(name)){ - this.formatters[language].$types[name] = formatter - }else{ - this.formatters[language][name] = formatter - } + language = Array.isArray(language) ? language : (language ? language.split(",") : []) + language.forEach(lng=>{ + if(DataTypes.includes(name)){ + this.formatters[lng].$types[name] = formatter + }else{ + this.formatters[lng][name] = formatter + } + }) } /** * 注册默认文本信息加载器 */ 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.refresh() } @@ -673,27 +695,6 @@ function translate(message) { }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 ={ diff --git a/packages/runtime/scope.js b/packages/runtime/scope.js index 1b90ee1..d7869e2 100644 --- a/packages/runtime/scope.js +++ b/packages/runtime/scope.js @@ -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"]; module.exports = class i18nScope { constructor(options={},callback){ - // 每个作用域都有一个唯一的id 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._activeLanguage = options.activeLanguage // 当前语言名称 this._default = options.default // 默认语言包 this._messages = options.messages // 当前语言包 this._idMap = options.idMap // 消息id映射列表 - this._formatters = options.formatters // 当前作用域的格式化函数列表 + this._formatters = options.formatters // 当前作用域的格式化函数列表{:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} this._loaders = options.loaders // 异步加载语言文件的函数列表 this._global = null // 引用全局VoerkaI18n配置,注册后自动引用 + this._activeFormatters= options.formatters[options.activeLanguage] // 激活使用的格式化器,查找格式化器时在此查找 this._patchMessages = {} // 语言包补丁信息 {:{....},:{....}} - // 主要用来缓存格式化器的引用,当使用格式化器时可以直接引用,避免检索 + // 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历 this.$cache={ activeLanguage : null, typedFormatters: {}, formatters : {}, } - // 如果不存在全局VoerkaI18n实例,说明当前Scope是唯一或第一个加载的作用域, - // 则使用当前作用域来初始化全局VoerkaI18n实例 + // 如果不存在全局VoerkaI18n实例,说明当前Scope是唯一或第一个加载的作用域,则自动创建全局VoerkaI18n实例 if(!globalThis.VoerkaI18n){ const { I18nManager } = require("./") globalThis.VoerkaI18n = new I18nManager({ + debug : this._debug, defaultLanguage: this.defaultLanguage, activeLanguage : this.activeLanguage, - languages: options.languages, + languages : options.languages, }) } this._global = globalThis.VoerkaI18n + // 合并补丁语言包 this._mergePatchedMessages() this._patch(this._messages,this.activeLanguage) // 正在加载语言包标识 @@ -42,6 +44,8 @@ module.exports = class i18nScope { } // 作用域 get id(){return this._id} + // 调试开关 + get debug(){return this._debug} // 默认语言名称 get defaultLanguage(){return this._defaultLanguage} // 默认语言名称 @@ -52,7 +56,7 @@ module.exports = class i18nScope { get messages(){return this._messages} // 消息id映射列表 get idMap(){return this._idMap} - // 当前作用域的格式化函数列表 + // 当前作用域的格式化器 {:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} get formatters(){return this._formatters} // 当前作用域支持的语言列表[{name,title,fallback}] get languages(){return this._languages} @@ -65,33 +69,39 @@ module.exports = class i18nScope { * @param {*} callback 注册成功后的回调 */ register(callback){ - if(!typeof(callback)==="function") callback = ()=>{} + if(!isFunction(callback)) callback = ()=>{} this.global.register(this).then(callback).catch(callback) } /** * 注册格式化器 + * * 格式化器是一个简单的同步函数value=>{...},用来对输入进行格式化后返回结果 * - * registerFormatters(name,value=>{...}) // 适用于所有语言 - * registerFormatters(name,value=>{...},{langauge:"zh"}) // 适用于cn语言 - * registerFormatters(name,value=>{...},{langauge:"en"}) // 适用于en语言 - - * @param {*} formatters + * registerFormatter(name,value=>{...}) // 注册到所有语言 + * registerFormatter(name,value=>{...},{langauge:"zh"}) // 注册到zh语言 + * 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 {*} formatter 格式化器 language : 声明该格式化器适用语言 - isGlobal : 注册到全局 + asGlobal : 注册到全局 */ - registerFormatter(name,formatter,{language="*",isGlobal}={}){ - if(!typeof(formatter)==="function" || typeof(name)!=="string"){ + registerFormatter(name,formatter,{language="*",asGlobal}={}){ + if(!isFunction(formatter) || typeof(name)!=="string"){ 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}) }else{ - if(DataTypes.includes(name)){ - this.formatters[language].$types[name] = formatter - }else{ - this.formatters[language][name] = formatter - } + language.forEach(lng=>{ + if(DataTypes.includes(name)){ + this._formatters[lng].$types[name] = formatter + }else{ + this._formatters[lng][name] = formatter + } + }) } } /** @@ -101,17 +111,76 @@ module.exports = class i18nScope { 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] } + /** + * 返回是否存在指定的语言 + * @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._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 @@ -119,29 +188,35 @@ module.exports = class i18nScope { async refresh(newLanguage){ this._refreshing = true if(!newLanguage) newLanguage = this.activeLanguage - // 默认语言,默认语言采用静态加载方式,只需要简单的替换即可 + // 默认语言:由于默认语言采用静态加载方式而不是异步块,因此只需要简单的替换即可 if(newLanguage === this.defaultLanguage){ this._messages = this._default await this._patch(this._messages,newLanguage) // 异步补丁 + this._loadFormatters(newLanguage) return } // 非默认语言需要异步加载语言包文件,加载器是一个异步函数 // 如果没有加载器,则无法加载语言包,因此回退到默认语言 let loader = this.loaders[newLanguage] 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._activeLanguage = 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) this._messages = Object.assign({},this._default,loadedMessages) this._activeLanguage = newLanguage }else{ this._fallback() } + // 应该切换到对应语言的格式化器 + this._loadFormatters(newLanguage) }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() }finally{ this._refreshing = false @@ -157,14 +232,16 @@ module.exports = class i18nScope { * @returns */ async _patch(messages,newLanguage){ - if(typeof(this.global.loadMessagesFromDefaultLoader) !== 'function') return + if(!isFunction(this.global.loadMessagesFromDefaultLoader)) return try{ let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this) if(isPlainObject(pachedMessages)){ Object.assign(messages,pachedMessages) 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中 * - 当应用加载时会查询是否存在补丁,如果存在就会合并渲染 - * - * * @param {*} messages */ @@ -200,9 +276,14 @@ module.exports = class i18nScope { globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`, JSON.stringify(messages)); } }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){ try{ return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`)) @@ -211,10 +292,8 @@ module.exports = class i18nScope { } } // 以下方法引用全局VoerkaI18n实例的方法 - get on(){return this.global.on.bind(this.global)} - get off(){return this.global.off.bind(this.global)} - get offAll(){return this.global.offAll.bind(this.global)} - get change(){ - return this.global.change.bind(this.global) - } + get on(){return this._global.on.bind(this._global)} + get off(){return this._global.off.bind(this._global)} + get offAll(){return this._global.offAll.bind(this._global)} + get change(){return this._global.change.bind(this._global)} } \ No newline at end of file diff --git a/packages/runtime/utils.js b/packages/runtime/utils.js index f0f6651..bc81672 100644 --- a/packages/runtime/utils.js +++ b/packages/runtime/utils.js @@ -41,6 +41,9 @@ return false } } +function isFunction(fn){ + return typeof fn === "function" +} /** * 当value= null || undefined || "" || [] || {} 时返回true * @param {*} value @@ -142,8 +145,7 @@ function toNumber(value,defualt=0) { } catch { return value } -} - +} /** * 转换为货币格式 * @@ -184,6 +186,7 @@ function relativeTime(value, rel){ module.exports ={ isPlainObject, + isFunction, isNumber, isNothing, deepMerge,