update formatters

This commit is contained in:
wxzhang 2022-08-15 18:24:27 +08:00
parent 6be922d6fe
commit af148a30cd
7 changed files with 339 additions and 190 deletions

View File

@ -7,7 +7,7 @@
- 以下定义了一些格式化器在中文场景下会启用这些格式化器
import dayjs from "dayjs";
export default {
$options:{...},
$config:{...},
$types:{
Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
},
@ -32,7 +32,7 @@
{{if moduleType === "esm"}}
export default{{else}}module.exports = {{/if}}{
// 格式化器参数
$options:{
$config:{
},
// 指定数据类型的默认格式化器

View File

@ -3,27 +3,147 @@
*
*/
const { toDate,toCurrency } = require("../utils")
const { toDate,toCurrency,formatDatetime,formatTime,Formatter } = require("../utils")
// 日期格式化器
// format取字符串"long","short","local","iso","gmt","utc"或者日期模块字符串
// { value | date } == '2022/8/15'
// { value | date('long') } == '2022/8/15 12:08:32'
// { value | date('short') } == '8/15'
// { value | date('GMT') } == 'Mon, 15 Aug 2022 06:39:38 GMT'
// { value | date('ISO') } == 'Mon, 15 Aug 2022 06:39:38 ISO'
// { value | date('YYYY-MM-DD HH:mm:ss') } == '2022-8-15 12:08:32'
const dateFormatter = Formatter((value,format,$config)=>{
const optionals = ["long","short","local","iso","gmt","utc"]
const optionIndex = optionals.findIndex((v,i)=>{
if(typeof(format)=="string"){
return v==format || v== format.toUpperCase()
}else if(typeof(format)=="number"){
return format === i
}
})
switch(optionIndex){
case 0: // long
return formatDatetime(value,$config.long)
case 1: // short
return formatDatetime(value,$config.short)
case 2: // local
return value.toLocaleString()
case 3: // ISO
return value.toISOString()
case 4: // GMT
return value.toGMTString()
case 5: // UTC
return value.toUTCString()
default:
return formatDatetime(value,format)
}
},{
normalize: toDate, // 转换输入为Date类型
params : ['format'],
configKey: "datetime.date"
})
// 月份格式化器 format可以取值0,1,2也可以取字符串long,short,number
const monthFormatter = Formatter((value,format,$config)=>{
const month = value.getMonth()
if(typeof(format)==='string'){
format = ['long','short','number'].indexOf(format)
}
if(format<0 && format>2) format = 0
return format==0 ? $config.names[month] : (format==1 ? $config.shortNames[month] : month+1)
},{
normalize: toDate,
params : ['format'],
configKey: "datetime.month"
})
// 周格式化器 format可以取值0,1,2也可以取字符串long,short,number
const weekdayFormatter = Formatter((value,format,$config)=>{
const day = value.getDay()
if(typeof(format)==='string'){
format = ['long','short','number'].indexOf(format)
}
if(format<0 && format>2) format = 0
return format==0 ? $config.names[day] : (format==1 ? $config.shortNames[day] : day)
},{
normalize: toDate,
params : ['format'],
configKey: "datetime.weekday"
})
// 时间格式化器 format可以取值0,1,2也可以取字符串long,short,timestamp,local
const timeFormatter = Formatter((value,format,$config)=>{
const month = value.getMonth()
const optionals = ['long','short','timestamp','local'] //toLocaleTimeString
const optionIndex = optionals.findIndex((v,i)=>{
if(typeof(format)=="string"){
return v==format || v== format.toUpperCase()
}else if(typeof(format)=="number"){
return format === i
}
})
switch(optionIndex){
case 0: // long
return formatTime(value,$config.long)
case 1: // short
return formatTime(value,$config.short)
case 2: // timestamp
return value.getTime()
case 3: // local
return value.toLocaleTimeString()
default:
return formatTime(value,format)
}
},{
normalize: toDate,
params : ['format'],
configKey: "datetime.month"
})
// 货币格式化器, CNY $13,456.00
const currencyFormatter = Formatter((value, unit,prefix ,suffix, division,precision) =>{
return toCurrency(value, { unit,division, prefix, precision,suffix })
},{
normalize: toNumber,
params:["prefix","suffix", "division","precision"],
configKey: "currency"
})
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"]
},
// 配置参数
$config:{
datetime : {
units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
date :{
long : 'YYYY/MM/DD HH:mm:ss',
short : "MM/DD",
format : "local"
},
month:{
names : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
shortNames : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
format : 0 // 0-长名称1-短名称2-数字
},
weekday:{
names :["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
shortNames : ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"],
format : 0, // 0-长名称1-短名称2-数字
},
time : {
long : "HH:mm:ss",
short : "HH:mm:ss",
format : 'local'
},
}
currency : {
unit : "$",
prefix : "",
suffix : "",
division : 3,
precision : 2
unit : "$", // 单位
prefix : "", // 前缀
suffix : "", // 后缀
division : 3, // ,分割位
precision : 2, // 精度
},
number : {
division : 3,
@ -50,32 +170,27 @@
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"
Error : value => "ERROR",
Boolean : value =>value ? "True":"False"
},
// 以下是格式化定义
// 日期
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()}` },
time : value => { const d = toDate(value); return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` },
shorttime : value => { const d = toDate(value); return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}` },
// ******************* 日期 *******************
date : dateFormatter,
time : timeFormatter,
year : value => toDate(value).getFullYear(),
month : value => toDate(value).getMonth() + 1,
day : value => toDate(value).getDate(),
weekdayValue : value => toDate(value).getDay(),
weekday : value => ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][toDate(value).getDay()],
shortWeekday : value => ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"][toDate(value).getDay()],
monthName : value => ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][toDate(value).getMonth()],
shorMonthName : value => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"][toDate(value).getMonth()],
// 时间
weekday : weekdayFormatter,
month : monthFormatter,
// ******************* 时间 *******************
hour : value => toDate(value).getHours(),
hour12 : value => {const hour = toDate(value).getHours(); return hour > 12 ? hour - 12 : thour},
minute : value => toDate(value).getMinutes(),
second : value => toDate(value).getSeconds(),
millisecond : value => toDate(value).getMilliseconds(),
timestamp : value => toDate(value).getTime(),
// 货币
// 常规货币形式 $111,233.33
currency : (value, prefix = "$",suffix="", division = 3,precision = 2) => toCurrency(value, { division, prefix, precision,suffix }),
// ******************* 货币 *******************
currency : currencyFormatter,
// 数字,如,使用分割符
number : (value, division = 3,precision = 0) => toCurrency(value, { division, precision})
}

View File

@ -8,19 +8,35 @@
module.exports = {
// 配置参数: 格式化器函数的最后一个参数就是该配置参数
$options:{
$config:{
datetime : {
units : CN_DATETIME_UNITS,
weekdays : CN_WEEK_DAYS,
shortWeekdays : CN_SHORT_WEEK_DAYS,
monthNames : CN_MONTH_NAMES,
shorMonthNames: CN_SHORT_MONTH_NAMES
date :{
long : 'YYYY年MM月DD日 HH点mm分ss秒',
short : "MM/DD",
format : 'YYYY年MM月DD日 HH点mm分ss秒'
},
month:{
names : CN_MONTH_NAMES,
shortNames : CN_SHORT_MONTH_NAMES,
format : 0, // 0-长名称1-短名称2-数字
},
weekday:{
names : CN_WEEK_DAYS,
shortNames : CN_SHORT_WEEK_DAYS,
format : 0, // 0-长名称1-短名称2-数字
},
time:{
long : "HH点mm分ss秒",
short : "HH:mm:ss",
}
},
currency : {
unit : "¥",
prefix : "",
suffix : "",
division : 3,
suffix : "",
division : 4,
precision : 2
},
number : {
@ -29,16 +45,10 @@ module.exports = {
}
},
$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()}`},
Boolean : value =>value ? "是":"否"
},
// 日期
date : value => { const d = toDate(value); return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}` },
weekday : value => CN_WEEK_DAYS[toDate(value).getDay()],
shortWeekday : value => CN_SHORT_WEEK_DAYS[toDate(value).getDay()],
monthName : value => CN_MONTH_NAMES[toDate(value).getMonth()],
shorMonthName: value => CN_SHORT_MONTH_NAMES[toDate(value).getMonth()],
// 时间
time : value =>{const d = toDate(value);return `${d.getHours()}${d.getMinutes()}${d.getSeconds()}`},
// 货币
currency : (value,prefix = "¥",suffix="", division = 4, precision = 2) => toCurrency(value, { division, prefix, precision,suffix }),
// 中文货币big=true代表大写形式

View File

@ -271,7 +271,7 @@ function getFormatter(scope,activeLanguage,name){
resetScopeCache(scope,activeLanguage)
}
const fallbackLanguage = scope.getLanguage(activeLanguage).fallback
// 2. 先在当前作用域中查找,再在全局查找 formatters={$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}
// 2. 先在当前作用域中查找,再在全局查找 formatters={$types,$config,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}
const range = [
scope.activeFormatters,
scope.formatters[fallbackLanguage], // 如果指定了回退语言时,也在该回退语言中查找
@ -305,9 +305,9 @@ function executeChecker(checker,value){
/**
* 执行格式化器并返回结果
*
* 格式化器this指向当前scope并且最后一个参数是当前scope格式化器的$options
* 格式化器this指向当前scope并且最后一个参数是当前scope格式化器的$config
*
* 这样格式化器可以读取$options
* 这样格式化器可以读取$config
*
* @param {*} value
* @param {Array[Function]} formatters 多个格式化器函数(经过包装过的)顺序执行前一个输出作为下一个格式化器的输入
@ -655,7 +655,7 @@ function translate(message) {
get activeLanguage(){ return this._settings.activeLanguage} // 当前激活语言 名称
get defaultLanguage(){ return this._settings.defaultLanguage} // 默认语言名称
get languages(){ return this._settings.languages} // 支持的语言列表
get formatters(){ return this._settings.formatters } // 内置格式化器{*:{$options,$types,...},zh:{$options,$types,...},en:{$options,$types,...}}
get formatters(){ return this._settings.formatters } // 内置格式化器{*:{$config,$types,...},zh:{$config,$types,...},en:{$config,$types,...}}
get defaultMessageLoader(){ return this._defaultMessageLoader} // 默认语言包加载器
// 通过默认加载器加载文件

View File

@ -25,7 +25,7 @@ module.exports = class i18nScope {
this._default = options.default; // 默认语言包
this._messages = options.messages; // 当前语言包
this._idMap = options.idMap; // 消息id映射列表
this._formatters = options.formatters; // 当前作用域的格式化函数列表{<lang>: {$types,$options,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
this._formatters = options.formatters; // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
this._loaders = options.loaders; // 异步加载语言文件的函数列表
this._global = null; // 引用全局VoerkaI18n配置注册后自动引用
this._patchMessages = {}; // 语言包补丁信息{<language>: {....},<language>:{....}}
@ -62,8 +62,8 @@ module.exports = class i18nScope {
get languages() {return this._languages;} // 当前作用域支持的语言列表[{name,title,fallback}]
get loaders() { return this._loaders;} // 异步加载语言文件的函数列表
get global() { return this._global;} // 引用全局VoerkaI18n配置注册后自动引用
get formatters() { return this._formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$options,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters() {return this._activeFormatters} // 当前作用域激活的格式化器定义 {$types,$options,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get formatters() { return this._formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters() {return this._activeFormatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get activeFormatterOptions(){return this._activeFormatterOptions} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数
/**
@ -86,10 +86,12 @@ module.exports = class i18nScope {
registerFormatter(name,value=>{...},{langauge:["zh","cht"]}) // 注册到zh和cht语言
registerFormatter(name,value=>{...},{langauge:"zh,cht"})
* @param {*} formatter 格式化器
language : 声明该格式化器适用语言
language : 字符串或数组声明该格式化器适用语言
*代表适用于所有语言
语言名称语言名称数组或者使用,分割的语言名称字符串
asGlobal : 注册到全局
*/
registerFormatter(name, formatter, { language = "*", asGlobal } = {}) {
registerFormatter(name, formatter, { language = "*", global : asGlobal } = {}) {
if (!isFunction(formatter) || typeof name !== "string") {
throw new TypeError("Formatter must be a function");
}
@ -110,6 +112,20 @@ module.exports = class i18nScope {
});
}
}
/**
* 注册多种格式化器
* registerFormatters(={"*",zh:{...},en:{...}})
* registerFormatters(={"*",zh:{...},en:{...}},true) 在全局注册
* @param {*} formatters ={"*",zh:{...},en:{...}}
* @returns
*/
registerFormatters(formatters,isGlobal=false) {
Object.entries(formatters).forEach(([language,fns]=>{
Object.entries(fns).forEach(([name,formatter])=>{
this.registerFormatter(name,formatter,{language})
})
}))
}
/**
* 注册默认文本信息加载器
* @param {Function} 必须是异步函数或者是返回Promise
@ -144,7 +160,7 @@ module.exports = class i18nScope {
/**
* 初始化格式化器
* 激活和默认语言的格式化器采用静态导入的形式而没有采用异步块的形式这是为了确保首次加载时的能马上读取而减少延迟加载
* _activeFormatters={$options:{...},$types:{...},[格式化器名称]:()=>{...},[格式化器名称]:()=>{...},...}}
* _activeFormatters={$config:{...},$types:{...},[格式化器名称]:()=>{...},[格式化器名称]:()=>{...},...}}
*/
_initFormatters(newLanguage){
this._activeFormatters = {}
@ -167,7 +183,7 @@ module.exports = class i18nScope {
* 当切换语言时格式化器应该切换到对应语言的格式化器
*
* 重要需要处理
* $options参数采用合并继承机制
* $config参数采用合并继承机制
*
*
* @param {*} language
@ -192,19 +208,19 @@ module.exports = class i18nScope {
}
/**
* 生成格式化器的配置参数该参数由以下合并而成
* - global.formatters[*].$options
* - global.formatters[language].$options
* - scope.activeFormatters.$options 当前优先
* - global.formatters[*].$config
* - global.formatters[language].$config
* - scope.activeFormatters.$config 当前优先
*/
_generateFormatterOptions(language){
let options
try{
options = Object.assign({},getByPath(this._global.formatters,`*.$options`,{}))
deepMixin(options,getByPath(this._global.formatters,`${language}.$options`,{}))
deepMixin(options,getByPath(this._activeFormatters,"$options",{}))
options = Object.assign({},getByPath(this._global.formatters,`*.$config`,{}))
deepMixin(options,getByPath(this._global.formatters,`${language}.$config`,{}))
deepMixin(options,getByPath(this._activeFormatters,"$config",{}))
}catch(e){
if(this.debug) console.error(`Error while generate <${language}> formatter options: `,e)
if(!options) options = this._activeFormatters.$options || {}
if(!options) options = this._activeFormatters.$config || {}
}
return this._activeFormatterOptions = options
}

View File

@ -151,12 +151,12 @@ function toNumber(value,defualt=0) {
*
* @param {*} value 可以是数字也可以是字符串
* @param {*} division 分割符号位数,3代表每3个数字添加一个,
* @param {*} prefix 前缀,货币单位
* @param {*} suffix 前缀,货币单位
* @param {*} prefix 前缀
* @param {*} suffix 后缀
* @param {*} precision 小数点精确到几位0-自动
* @returns
*/
function toCurrency(value,{division=3,prefix="",precision=0,suffix=""}={}){
function toCurrency(value,{unit="",division=3,prefix="",precision=0,suffix=""}={}){
let [wholeValue,decimalValue] = String(value).split(".")
let result = []
for(let i=0;i<wholeValue.length;i++){
@ -169,7 +169,7 @@ function toNumber(value,defualt=0) {
}
result.push(`.${decimalValue}`)
}
return prefix + result.join("") + suffix
return `${prefix}${unit}${result.join("")}${suffix}`
}
/**
@ -225,9 +225,9 @@ function getByPath(obj,path,defaultValue){
function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss"){
const v = toDate(value)
const year =String(v.getFullYear()),month = String(v.getMonth()+1),weekday=String(v.getDay()),day=String(v.getDate())
const minute = String(v.getMinutes()),second = String(v.getSeconds()),millisecond=String(v.getMilliseconds()),hour = String(v.getHours())
const time = String(v.getTime())
let result = templ
const hourNum = v.getHours()
const hour = String(hourNum).minute = String(v.getMinutes()),second = String(v.getSeconds()),millisecond=String(v.getMilliseconds())
const timezone = v.getTimezoneOffset()
const vars = [
["YYYY", year], // 2018 年,四位数
["YY", year.substring(year.length - 2, year.length)], // 18 年,两位数
@ -236,10 +236,33 @@ function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss"){
["M", month], // 1-12 月从1开始
["DD", day.padStart(2, "0")], // 01-31 日,两位数
["D", day], // 1-31 日
["HH", String(hour).padStart(2, "0")], // 00-23 24小时两位数
["H", String(hour)], // 0-23 24小时
["HH", hour.padStart(2, "0")], // 00-23 24小时两位数
["H", hour], // 0-23 24小时
["hh", String(hourNum > 12 ? hourNum - 12 : hourNum).padStart(2, "0")], // 01-12 12小时两位数
["h", String(hourNum > 12 ? hourNum - 12 : hourNum)], // 1-12 12小时
["mm", minute.padStart(2, "0")], // 00-59 分钟,两位数
["m", minute], // 0-59 分钟
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
["s", second], // 0-59 秒
["SSS", millisecond], // 000-999 毫秒,三位数
["SS", millisecond.substring(year.length - 2, year.length)], // 00-99 毫秒(十),两位数
["S",millisecond[millisecond.length - 1]], // 0-9 毫秒(百),一位数
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写
]
vars.forEach(([key,value])=>result = replaceAll(result,key,value))
return result
}
function formatTime(value,templ="HH:mm:ss"){
const v = toDate(value)
const hourNum = v.getHours()
const hour = String(hourNum).minute = String(v.getMinutes()),second = String(v.getSeconds()),millisecond=String(v.getMilliseconds())
let result = templ
const vars = [
["HH", hour.padStart(2, "0")], // 00-23 24小时两位数
["H", hour], // 0-23 24小时
["hh", String(hour > 12 ? hour - 12 : hour).padStart(2, "0")], // 01-12 12小时两位数
["h", hour > 12 ? hour - 12 : hour], // 1-12 12小时
["h", String(hour > 12 ? hour - 12 : hour)], // 1-12 12小时
["mm", minute.padStart(2, "0")], // 00-59 分钟,两位数
["m", minute], // 0-59 分钟
["ss", second.padStart(2, "0")], // 00-59 秒,两位数
@ -250,10 +273,9 @@ function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss"){
["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写
["a", hour > 12 ? "pm" : "am"] // am / pm 上/下午,小写
]
vars.forEach(([key,value])=>result = result.replace(key,value))
vars.forEach(([key,value])=>result = replaceAll(result,key,value))
return result
}
/**
* 替换所有字符串
* 低版本ES未提供replaceAll,此函数用来替代
@ -282,10 +304,23 @@ function replaceAll(str,findValue,replaceValue){
*
* - 函数第一个参数是上一上格式化器的输出
* - 支持0-N个简单类型的入参
* - 格式化器可以在格式化器的$options参数指定一个键值来配置不同语言时的参数
* - 格式化器可以在格式化器的$config参数指定一个键值来配置不同语言时的参数
*
* createFormatter((value,prefix,suffix, division ,precision,options)=>{
*
* "currency":createFormatter((value,prefix,suffix, division ,precision,options)=>{
* // 无论在格式化入参数是多少个经过处理后在此得到prefix,suffix, division ,precision参数已经是经过处理后的参数
* 依次读取格式化器的参数合并
* - 从当前激活格式化器的$config中读取配置参数
* - 在t函数后传入参数
* 比如currency格式化器支持4参数其入参顺序是prefix,suffix, division ,precision
* 那么在t函数中可以使用以下五种入参数方式
* {value | currency } //prefix=undefined,suffix=undefined, division=undefined ,precision=undefined
* {value | currency(prefix) }
* {value | currency(prefix,suffix) }
* {value | currency(prefix,suffix,division) }
* {value | currency(prefix,suffix,division,precision)}
*
* 经过createFormatter处理后会从当前激活格式化器的$config中读取prefix,suffix, division ,precision参数作为默认参数
* 然后t函数中的参数会覆盖默认参数优先级更高
* },
* {
* unit:"$",
@ -295,8 +330,9 @@ function replaceAll(str,findValue,replaceValue){
* precision
* },
* {
* normalize:value=>{...},
* params:["prefix","suffix", "division" ,"precision"] // 声明参数顺序
* optionKey:"currency" // 声明特定语言下的配置在$options.currency
* configKey:"currency" // 声明特定语言下的配置在$config.currency
* }
* )
*
@ -309,13 +345,13 @@ function replaceAll(str,findValue,replaceValue){
let opts = Object.assign({
normalize : null, // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等
params : [], // 声明参数顺序
optionKeyPath: null // 声明该格式化器在$options中的路径,支持简单的使用.的路径语法
configKey : null // 声明该格式化器在$config中的路径,支持简单的使用.的路径语法
})
// 最后一个参数是传入activeFormatterOptions参数
const wrappedFn = function(value,...args){
let finalValue = value
// 1. 输入值规范处理,主要的类型转换等
// 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性
if(isFunction(opts.normalize)){
try{
finalValue = opts.normalize(finalValue)
@ -325,16 +361,16 @@ function replaceAll(str,findValue,replaceValue){
let activeFormatterOpts = args.length>0 ? args[args.length-1] : {}
if(!isPlainObject( activeFormatterOpts)) activeFormatterOpts ={}
// 3. 从当前语言的激活语言中读取配置参数
const activeOptions =Object.assign({},defaultParams,getByPath(activeFormatterOpts,opts.optionKey,{}))
let finalArgs = opts.params.map(param=>getByPath(activeOptions,param,undefined))
// 4. 将翻译函数执行格式化器时传入的参数具有高优先级
const activeConfig =Object.assign({},defaultParams,getByPath(activeFormatterOpts,opts.configKey,{}))
let finalArgs = opts.params.map(param=>getByPath(activeConfig,param,undefined))
// 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数
for(let i =0; i<finalArgs.length-1;i++){
if(i>=args.length-1) break // 最后一参数是配置
if(args[i]!==undefined) finalArgs[i] = args[i]
}
return fn(finalValue,...finalArgs,activeFormatterOpts)
}
fn.paramCount = opts.paramCount
wrappedFn.configurable = true // 当函数是可配置时才在最后一个参数中传入$config
return wrappedFn
}
@ -352,6 +388,8 @@ module.exports ={
replaceAll,
getByPath,
getDataTypeName,
formatDatetime,
formatTime,
toDate,
toNumber,
toCurrency,

View File

@ -1,7 +1,7 @@
const dayjs = require('dayjs');
const { getInterpolatedVars, replaceInterpolatedVars , translate} = require('../packages/runtime/index.js')
const messages = {
const { i18nScope, translate, getInterpolatedVars } = require('../packages/runtime/dist/runtime.cjs')
const loaders = {
zh:{
1:"你好",
2:"现在是{}",
@ -16,88 +16,60 @@ const messages = {
}
}
const formatters = {
zh:{
$config:{},
$types:{},
book:(v)=>`${v}`,
},
en:{
$config:{},
$types:{ },
book:(v)=>`<${v}>`,
},
}
const idMap = {
"你好":1,
"现在是{}":2,
"我出生于{year}年,今年{age}岁":3,
"我有{}个朋友":4
1:"你好",
2:"现在是{}",
3:"我出生于{year}年,今年{age}岁"
4:"我有{}个朋友"
}
const languages = [
{ name: "zh", title: "中文" },
{ name: "en", title: "英文" },
{ name: "de", title: "德语" },
{ name: "jp", title: "日语" }
]
const scope = new i18nScope({
id : "test",
defaultLanguage: "zh",
activeLanguage : "zh",
namespaces : {},
default : loaders.zh, // 默认语言包
messages : loaders.zh, // 当前语言包
languages, // 语言配置
idMap, // 消息id映射列表
formatters, // 扩展自定义格式化器
loaders // 语言包加载器
})
let scope1 ={
defaultLanguage: "zh", // 默认语言名称
default: messages.zh,
messages :messages.zh,
idMap,
formatters:{ // 当前作用域的格式化函数列表
"*":{
$types:{
Date:(v)=>dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
Boolean:(v)=>v?"True":"False",
},
sum:(v,n=1)=>v+n,
double:(v)=>v*2,
upper:(v)=>v.toUpperCase(),
lower:(v)=>v.toLowerCase()
},
zh:{
$types:{
Date:(v)=>dayjs(v).format('YYYY年MM月DD日 HH点mm分ss秒'),
Boolean:(v)=>v?"是":"否",
},
book:(v)=>`${v}`,
},
en:{
$types:{
const t = translate.bind(scope)
},
book:(v)=>`<${v}>`,
},
},
loaders:{}, // 异步加载语言文件的函数列表
global:{// 引用全局VoerkaI18n配置
defaultLanguage: "zh",
activeLanguage: "zh",
languages:[
{name:"zh",title:"中文",default:true},
{name:"en",title:"英文"},
{name:"de",title:"德语"},
{name:"jp",title:"日语"}
],
formatters:{ // 当前作用域的格式化函数列表
"*":{
$types:{
}
},
zh:{
$types:{
}
},
en:{
$types:{
}
},
}
}
}
const replaceVars = replaceInterpolatedVars.bind(scope1)
const t = translate.bind(scope1)
function changeLanguage(language){
scope1.global.activeLanguage = language
scope1.messages = messages[language]
}
// 适用于所有语言的格式化器,并且注册到全局
scope.registerFormatters({
"*":{
sum : (v,n=1)=>v+n,
double: (v)=>v*2,
upper : (v)=>v.toUpperCase(),
}
},true)
beforeEach(() => {
scope1.global.activeLanguage = "zh" // 切换到中文
scope.change("zh")
});
@ -121,7 +93,6 @@ test("获取翻译内容中的插值变量",done=>{
expect(results[1].name).toEqual("city");
expect(results[1].formatters.length).toEqual(0);
done()
})
@ -136,7 +107,6 @@ test("获取翻译内容中定义了重复的插值变量",done=>{
expect(results[1].formatters[0].name).toEqual("x");
expect(results[1].formatters[0].args).toEqual([]);
expect(results[2].name).toEqual("a");
expect(results[2].formatters.length).toEqual(2);
expect(results[2].formatters[0].name).toEqual("x");
@ -148,45 +118,45 @@ test("获取翻译内容中定义了重复的插值变量",done=>{
})
test("替换翻译内容的位置插值变量",done=>{
expect(replaceVars("{}{}{}",1,2,3)).toBe("123");
expect(replaceVars("{a}{b}{c}",1,2,3)).toBe("123");
expect(t("{}{}{}",1,2,3)).toBe("123");
expect(t("{a}{b}{c}",1,2,3)).toBe("123");
// 定义了一些无效的格式化器,直接忽略
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3)).toBe("123");
expect(replaceVars("{a|xxx}{b|dd}{c|}",1,2,3,4,5,6)).toBe("123");
expect(replaceVars("{ a|}{b|dd}{c|}{}",1,2,3)).toBe("123{}");
expect(t("{a|xxx}{b|dd}{c|}",1,2,3)).toBe("123");
expect(t("{a|xxx}{b|dd}{c|}",1,2,3,4,5,6)).toBe("123");
expect(t("{ a|}{b|dd}{c|}{}",1,2,3)).toBe("123{}");
// 中文状态下true和false被转换成中文的"是"和"否"
expect(replaceVars("{}{}{}",1,"2",true)).toBe("12是");
expect(replaceVars("{|double}{}{}",1,"2",true)).toBe("22是");
expect(t("{}{}{}",1,"2",true)).toBe("12是");
expect(trim("{|double}{}{}",1,"2",true)).toBe("22是");
done()
})
test("替换翻译内容的命名插值变量",done=>{
expect(replaceVars("{a}{b}{c}",{a:11,b:22,c:33})).toBe("112233");
expect(replaceVars("{a}{b}{c}{a}{b}{c}",{a:1,b:"2",c:3})).toBe("123123");
expect(t("{a}{b}{c}",{a:11,b:22,c:33})).toBe("112233");
expect(t("{a}{b}{c}{a}{b}{c}",{a:1,b:"2",c:3})).toBe("123123");
done()
})
test("命名插值变量使用格式化器",done=>{
// 提供无效的格式化器,直接忽略
expect(replaceVars("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
expect(t("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
expect(t("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
// 默认的字符串格式化器,不需要定义使用字符串方法
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
expect(t("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
// padStart格式化器是字符串的方法不需要额外定义可以直接使用
expect(replaceVars("{a|padStart(10)}",{a:"123"})).toBe(" 123");
expect(replaceVars("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
expect(t("{a|padStart(10)}",{a:"123"})).toBe(" 123");
expect(t("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
done()
})
test("命名插值变量使用格式化器",done=>{
// 提供无效的格式化器,直接忽略
expect(replaceVars("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
expect(t("{a|x}{b|x|y}{c|}",{a:1,b:2,c:3})).toBe("123");
expect(t("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
// 默认的字符串格式化器,不需要定义使用字符串方法
expect(replaceVars("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
expect(replaceVars("{a|padStart(10)}",{a:"123"})).toBe(" 123");
expect(replaceVars("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
expect(t("{a|x}{b|x|y}{c|double}",{a:1,b:2,c:3})).toBe("126");
expect(t("{a|padStart(10)}",{a:"123"})).toBe(" 123");
expect(t("{a|padStart(10)|trim}",{a:"123"})).toBe("123");
done()
})
@ -194,11 +164,11 @@ test("命名插值变量使用格式化器",done=>{
test("切换到其他语言时的自动匹配同名格式化器",done=>{
// 默认的字符串类型的格式化器
expect(replaceVars("{a}",{a:true})).toBe("是");
expect(replaceVars("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("《毛泽东选集》是毛泽东思想的重要载体");
expect(t("{a}",{a:true})).toBe("是");
expect(t("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("《毛泽东选集》是毛泽东思想的重要载体");
changeLanguage("en")
expect(replaceVars("{a}",{a:false})).toBe("False");
expect(replaceVars("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("<毛泽东选集>是毛泽东思想的重要载体");
expect(t("{a}",{a:false})).toBe("False");
expect(t("{name|book}是毛泽东思想的重要载体","毛泽东选集")).toBe("<毛泽东选集>是毛泽东思想的重要载体");
done()
})