diff --git a/packages/runtime/formatters/en.js b/packages/runtime/formatters/en.js index 192c86e..45cd862 100644 --- a/packages/runtime/formatters/en.js +++ b/packages/runtime/formatters/en.js @@ -3,7 +3,7 @@ * */ - const { toDate,toCurrency,formatDatetime,formatTime,Formatter } = require("../utils") + const { toDate,toCurrency,toNumber,formatDatetime,formatTime,Formatter } = require("../utils") // 日期格式化器 // format取字符串"long","short","local","iso","gmt","utc"或者日期模块字符串 @@ -43,7 +43,17 @@ const dateFormatter = Formatter((value,format,$config)=>{ params : ['format'], configKey: "datetime.date" }) - +// 季度格式化器 format= 0=短格式 1=长格式 +const mquarterFormatter = Formatter((value,format,$config)=>{ + const month = value.getMonth() + 1 + const quarter = Math.floor( ( month % 3 == 0 ? ( month / 3 ) : (month / 3 + 1 ) )) + if(format<0 && format>1) format = 0 + return format==0 ? $config.shortNames[month] : (format==1 ? $config.shortNames[month] : month+1) +},{ + normalize: toDate, + params : ['format'], + configKey: "datetime.quarter" +}) // 月份格式化器 format可以取值0,1,2,也可以取字符串long,short,number const monthFormatter = Formatter((value,format,$config)=>{ @@ -104,15 +114,17 @@ const timeFormatter = Formatter((value,format,$config)=>{ }) // 货币格式化器, CNY $13,456.00 -const currencyFormatter = Formatter((value, unit,prefix ,suffix, division,precision) =>{ - return toCurrency(value, { unit,division, prefix, precision,suffix }) +const currencyFormatter = Formatter((value, symbol,prefix ,suffix, division,precision) =>{ + return toCurrency(value, { symbol,division, prefix, precision,suffix }) },{ normalize: toNumber, - params:["prefix","suffix", "division","precision"], + params:["symbol","prefix","suffix", "division","precision"], configKey: "currency" }) - module.exports = { + + +module.exports = { // 配置参数 $config:{ datetime : { @@ -122,6 +134,10 @@ const currencyFormatter = Formatter((value, unit,prefix ,suffix, division,precis short : "MM/DD", format : "local" }, + quarter : { + names : ["Q1","Q2","Q3","Q4"], + shortNames : ["Q1","Q2","Q3","Q4"] + }, 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"], @@ -137,9 +153,9 @@ const currencyFormatter = Formatter((value, unit,prefix ,suffix, division,precis short : "HH:mm:ss", format : 'local' }, - } + }, currency : { - unit : "$", // 单位 + symbol : "$", // 符号 prefix : "", // 前缀 suffix : "", // 后缀 division : 3, // ,分割位 @@ -150,14 +166,14 @@ const currencyFormatter = Formatter((value, unit,prefix ,suffix, division,precis precision : 2 }, empty:{ - //values : [], // 可选,定义空值,如果想让0,''也为空值,可以指定values=[0,''] - escape : "", // 当空值时显示的备用值 - next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过 + //values : [], // 可选,定义空值,如果想让0,''也为空值,可以指定values=[0,''] + escape : "", // 当空值时显示的备用值 + next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过 }, error : { //当错误时显示的内容,支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数 escape : null, // 默认当错误时显示空内容 - next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略 + next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略 }, fileSize:{ //brief: ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"], diff --git a/packages/runtime/formatters/zh.js b/packages/runtime/formatters/zh.js index dc89596..f13bd47 100644 --- a/packages/runtime/formatters/zh.js +++ b/packages/runtime/formatters/zh.js @@ -16,6 +16,11 @@ module.exports = { short : "MM/DD", format : 'YYYY年MM月DD日 HH点mm分ss秒' }, + quarter : { + names : ["一季度","二季度","三季度","四季度"], + shortNames : ["Q1","Q2","Q3","Q4"], + format : 0 // 0-短格式,1-长格式 + }, month:{ names : CN_MONTH_NAMES, shortNames : CN_SHORT_MONTH_NAMES, @@ -33,7 +38,7 @@ module.exports = { }, currency : { - unit : "¥", + symbol : "¥", prefix : "", suffix : "元", division : 4, @@ -49,9 +54,7 @@ module.exports = { Boolean : value =>value ? "是":"否" }, - // 货币 - currency : (value,prefix = "¥",suffix="", division = 4, precision = 2) => toCurrency(value, { division, prefix, precision,suffix }), - // 中文货币,big=true代表大写形式 + // 中文货币,big=true代表大写形式 capitalizeCurrency:(value,big,unit="元",prefix,suffix)=>toChineseCurrency(value,{big,prefix,suffix,unit}), // 中文数字,如一千二百三十一 number:(value,isBig)=>toChineseNumber(value,isBig) diff --git a/packages/runtime/index.js b/packages/runtime/index.js index f6ba24d..0fc6298 100644 --- a/packages/runtime/index.js +++ b/packages/runtime/index.js @@ -386,23 +386,17 @@ function addDefaultFormatters(formatters){ * */ function wrapperFormatters(scope,activeLanguage,formatters){ - let wrappedFormatters = [] - addDefaultFormatters(formatters) + let wrappedFormatters = [] for(let [name,args] of formatters){ if(name){ - const func = getFormatter(scope,activeLanguage,name) - if(isFunction(func)){ - let fn = (value) => { - // 每一个格式式化器均支持若干输入参数,但是在使用时,可以支持可选参数 - // 比currency(value,prefix,suffix, division,precision)支持4个参数,但是在使用时支持可选参数 - // {value | currency} 或 {value | currency('元')} 或 {value | currency('元',"整")} - // 为了让格式化器能比较方便地处理最后一个配置参数,当格式化器函数指定了paramCount参数来声明其支持的参数后 - // 在此就自动初始参数个数,这样在currency函数中,就可以使用这样的currency(value,prefix,suffix, division,precision,options) - // 不管开发者使用时的输入参数数是多少均可以保证在currency函数中总是可以得到有效的options参数 - if(func.paramCount && args.length < func.paramCount ){ - args.push(...new Array(parseInt(func.paramCount)-args.length).fill(undefined)) + const func = getFormatter(scope,activeLanguage,name) + if(isFunction(func)){ + const fn = (value) => { + if(func.configurable){ // 如果格式化器函数是使用createFormatter创建的 + return func.call(scope,value,...args,scope.activeFormatterConfig) + }else{ // 不可配置的格式化器不会传入格式化器配置 + return func.call(scope,value,...args) } - return func.call(scope,value,...args,scope.activeFormatterOptions) } fn.$name = name wrappedFormatters.push(fn) @@ -441,8 +435,9 @@ function getFormattedValue(scope,activeLanguage,formatters,value,template){ if(defaultFormatter){ return executeFormatter(value,[defaultFormatter],scope,template) } - } - value = executeFormatter(value,formatterFuncs,scope,template) + }else{ + value = executeFormatter(value,formatterFuncs,scope,template) + } return value } diff --git a/packages/runtime/scope.js b/packages/runtime/scope.js index 39aef6c..639b3b1 100644 --- a/packages/runtime/scope.js +++ b/packages/runtime/scope.js @@ -40,10 +40,10 @@ module.exports = class i18nScope { if (!globalThis.VoerkaI18n) { const { I18nManager } = require("./"); globalThis.VoerkaI18n = new I18nManager({ - debug: this._debug, + debug : this._debug, defaultLanguage: this.defaultLanguage, - activeLanguage: this.activeLanguage, - languages: options.languages, + activeLanguage : this.activeLanguage, + languages : options.languages, }); } this._global = globalThis.VoerkaI18n; @@ -64,7 +64,7 @@ module.exports = class i18nScope { get global() { return this._global;} // 引用全局VoerkaI18n配置,注册后自动引用 get formatters() { return this._formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}} get activeFormatters() {return this._activeFormatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}} - get activeFormatterOptions(){return this._activeFormatterOptions} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数 + get activeFormatterConfig(){return this._activeFormatterConfig} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数 /** * 在全局注册作用域当前作用域 @@ -119,12 +119,12 @@ module.exports = class i18nScope { * @param {*} formatters ={"*",zh:{...},en:{...}} * @returns */ - registerFormatters(formatters,isGlobal=false) { - Object.entries(formatters).forEach(([language,fns]=>{ + registerFormatters(formatters,asGlobal=false) { + Object.entries(formatters).forEach(([language,fns])=>{ Object.entries(fns).forEach(([name,formatter])=>{ - this.registerFormatter(name,formatter,{language}) + this.registerFormatter(name,formatter,{language,global:asGlobal}) }) - })) + }) } /** * 注册默认文本信息加载器 @@ -222,7 +222,7 @@ module.exports = class i18nScope { if(this.debug) console.error(`Error while generate <${language}> formatter options: `,e) if(!options) options = this._activeFormatters.$config || {} } - return this._activeFormatterOptions = options + return this._activeFormatterConfig = options } /** * 刷新当前语言包 diff --git a/packages/runtime/utils.js b/packages/runtime/utils.js index 4af3e51..f7b8999 100644 --- a/packages/runtime/utils.js +++ b/packages/runtime/utils.js @@ -138,7 +138,7 @@ function toDate(value) { function toNumber(value,defualt=0) { try { if (isNumber(value)) { - return parseInt(value) + return parseFloat(value) } else { return defualt } @@ -156,7 +156,7 @@ function toNumber(value,defualt=0) { * @param {*} precision 小数点精确到几位,0-自动 * @returns */ - function toCurrency(value,{unit="",division=3,prefix="",precision=0,suffix=""}={}){ + function toCurrency(value,{symbol="",division=3,prefix="",precision=0,suffix=""}={}){ let [wholeValue,decimalValue] = String(value).split(".") let result = [] for(let i=0;iresult = replaceAll(result,key,value)) return result } + function formatTime(value,templ="HH:mm:ss"){ const v = toDate(value) const hourNum = v.getHours() @@ -348,7 +349,7 @@ function replaceAll(str,findValue,replaceValue){ configKey : null // 声明该格式化器在$config中的路径,支持简单的使用.的路径语法 }) - // 最后一个参数是传入activeFormatterOptions参数 + // 最后一个参数是传入activeFormatterConfig参数 const wrappedFn = function(value,...args){ let finalValue = value // 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性 @@ -357,18 +358,18 @@ function replaceAll(str,findValue,replaceValue){ finalValue = opts.normalize(finalValue) }catch{} } - // 2. 读取activeFormatterOptions - let activeFormatterOpts = args.length>0 ? args[args.length-1] : {} - if(!isPlainObject( activeFormatterOpts)) activeFormatterOpts ={} + // 2. 读取activeFormatterConfig + let activeFormatterConfigs = args.length>0 ? args[args.length-1] : {} + if(!isPlainObject( activeFormatterConfigs)) activeFormatterConfigs ={} // 3. 从当前语言的激活语言中读取配置参数 - const activeConfig =Object.assign({},defaultParams,getByPath(activeFormatterOpts,opts.configKey,{})) - let finalArgs = opts.params.map(param=>getByPath(activeConfig,param,undefined)) + const formatterConfig =Object.assign({},defaultParams,getByPath(activeFormatterConfigs,opts.configKey,{})) + let finalArgs = opts.params.map(param=>getByPath(formatterConfig,param,undefined)) // 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数 for(let i =0; i=args.length-1) break // 最后一参数是配置 if(args[i]!==undefined) finalArgs[i] = args[i] } - return fn(finalValue,...finalArgs,activeFormatterOpts) + return fn(finalValue,...finalArgs,activeFormatterConfigs) } wrappedFn.configurable = true // 当函数是可配置时才在最后一个参数中传入$config return wrappedFn diff --git a/test/translate.test.js b/test/translate.test.js index 3584f84..7fef2a2 100644 --- a/test/translate.test.js +++ b/test/translate.test.js @@ -1,5 +1,5 @@ +const {i18nScope, translate, getInterpolatedVars } = require('../packages/runtime/index') const dayjs = require('dayjs'); -const { i18nScope, translate, getInterpolatedVars } = require('../packages/runtime/dist/runtime.cjs') const loaders = { zh:{ @@ -12,7 +12,7 @@ const loaders = { 1:"hello", 2:"Now is {}", 3:"I was born in {year}, now is {age} years old", - 4:["I have no friends","I have one friends","I have two friends","I have {} friends"], + 4:["I have no friends","I have one friends","I have two friends","I have {} friends"] } } @@ -29,10 +29,11 @@ const formatters = { book:(v)=>`<${v}>`, }, } + const idMap = { 1:"你好", 2:"现在是{}", - 3:"我出生于{year}年,今年{age}岁" + 3:"我出生于{year}年,今年{age}岁", 4:"我有{}个朋友" } const languages = [ @@ -64,7 +65,7 @@ scope.registerFormatters({ "*":{ sum : (v,n=1)=>v+n, double: (v)=>v*2, - upper : (v)=>v.toUpperCase(), + upper : (v)=>v.toUpperCase() } },true) @@ -207,22 +208,22 @@ test("命名插值翻译文本内容",done=>{ test("当没有对应的语言翻译时",done=>{ expect(t("我是中国人")).toBe("我是中国人"); - changeLanguage("en") + scope.change("en") expect(t("我是中国人")).toBe("我是中国人"); done() }) -test("切换到未知语言",done=>{ +test("切换到未知语言时回退到默认语言",done=>{ expect(t("我是中国人")).toBe("我是中国人"); - changeLanguage("en") + scope.change("xn") expect(t("我是中国人")).toBe("我是中国人"); done() }) test("翻译复数支持",done=>{ - changeLanguage("en") + scope.change("en") expect(t("我有{}个朋友",0)).toBe("I have no friends"); expect(t("我有{}个朋友",1)).toBe("I have one friends"); expect(t("我有{}个朋友",2)).toBe("I have two friends");