From c6a9b741663af07c7bc4f5a4111fef8e40e2bbd6 Mon Sep 17 00:00:00 2001 From: wxzhang Date: Tue, 23 Aug 2022 18:29:53 +0800 Subject: [PATCH] update formatters --- package.json | 2 +- packages/runtime/cnutils.js | 1 - packages/runtime/datatypes/chinese.js | 105 ++++++++++ packages/runtime/datatypes/currency.js | 80 ++++++++ packages/runtime/datatypes/datetime.js | 164 ++++++++++++++++ packages/runtime/datatypes/numeric.js | 33 ++++ packages/runtime/formatter.js | 83 +++++++- packages/runtime/formatters/default.js | 87 ++++---- packages/runtime/formatters/en.js | 178 +++-------------- packages/runtime/formatters/zh.js | 17 +- packages/runtime/index.js | 21 +- packages/runtime/interpolate.js | 43 ++-- packages/runtime/utils.js | 207 +------------------- test/{translate.test.js => runtime.test.js} | 24 ++- 14 files changed, 581 insertions(+), 464 deletions(-) create mode 100644 packages/runtime/datatypes/chinese.js create mode 100644 packages/runtime/datatypes/currency.js create mode 100644 packages/runtime/datatypes/datetime.js create mode 100644 packages/runtime/datatypes/numeric.js rename test/{translate.test.js => runtime.test.js} (95%) diff --git a/package.json b/package.json index ea19613..f3fd93d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "cross-env NODE_OPTIONS=--experimental-vm-modules node node_modules/jest/bin/jest.js ", "test:app": "cross-env NODE_OPTIONS=--experimental-vm-modules node node_modules/jest/bin/jest.js -- app", - "test:runtime": "cross-env NODE_OPTIONS=--experimental-vm-modules jest translate --coverage --collectCoverageFrom packages/runtime/dist/runtime.cjs", + "test:runtime": "cross-env NODE_OPTIONS=--experimental-vm-modules jest runtime --coverage --collectCoverageFrom packages/runtime/dist/runtime.cjs", "list:package": "node ./packages/autopublish/index.js list", "autopublish": "node ./packages/autopublish/index.js -a --no-ask", "docs:build": "cross-env NODE_OPTIONS=--openssl-legacy-provider dumi build", diff --git a/packages/runtime/cnutils.js b/packages/runtime/cnutils.js index 18a92cf..4c99768 100644 --- a/packages/runtime/cnutils.js +++ b/packages/runtime/cnutils.js @@ -16,7 +16,6 @@ const CN_SHORT_MONTH_NAMES = ["1月","2月","3月","4月","5月","6月","7月"," const CN_NUMBER_BIG_DIGITS = ["零", '壹', '貳', '參', '肆', '伍', '陸', '柒', '捌', '玖'] const CN_NUMBER_BIG_UNITS = ['', '拾', '佰', '仟', '萬', '拾', '佰', '仟', '億', '拾', '佰', '仟', '兆', '拾', '佰', '仟', '京', '拾', '佰', '仟', '垓'] - /** * * 将数字转换为中文数字 diff --git a/packages/runtime/datatypes/chinese.js b/packages/runtime/datatypes/chinese.js new file mode 100644 index 0000000..62d5425 --- /dev/null +++ b/packages/runtime/datatypes/chinese.js @@ -0,0 +1,105 @@ +/** + * + * 处理中文数字和货币相关 + * + */ + const { isNumber } = require('./utils') + + const CN_DATETIME_UNITS = ["年","季度","月","周","日","小时","分钟","秒","毫秒","微秒"] + const CN_WEEK_DAYS = ["星期日","星期一","星期二","星期三","星期四","星期五","星期六"] + const CN_SHORT_WEEK_DAYS =["周日","周一","周二","周三","周四","周五","周六"] + const CN_MONTH_NAMES= ["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"] + const CN_SHORT_MONTH_NAMES = ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"] + + const CN_NUMBER_DIGITS = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"] + const CN_NUMBER_UNITS = ['', '十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '兆', '十', '百', '千', '京', '十', '百', '千', '垓'] + const CN_NUMBER_BIG_DIGITS = ["零", '壹', '貳', '參', '肆', '伍', '陸', '柒', '捌', '玖'] + const CN_NUMBER_BIG_UNITS = ['', '拾', '佰', '仟', '萬', '拾', '佰', '仟', '億', '拾', '佰', '仟', '兆', '拾', '佰', '仟', '京', '拾', '佰', '仟', '垓'] + + /** + * + * 将数字转换为中文数字 + * + * 注意会忽略掉小数点后面的数字 + * + * @param {*} value 数字 + * @param {*} isBig 是否大写数字 + * @returns + */ + function toChineseNumber(value,isBig) { + if(!isNumber(value)) return value; + let [wholeValue,decimalValue] = String(value).split(".") // 处理小数点 + const DIGITS = isBig ? CN_NUMBER_BIG_DIGITS : CN_NUMBER_DIGITS + const UNITS = isBig ? CN_NUMBER_BIG_UNITS : CN_NUMBER_UNITS + let result = '' + if(wholeValue.length==1) return DIGITS[parseInt(wholeValue)] + for(let i=wholeValue.length-1; i>=0; i--){ + let bit = parseInt(wholeValue[i]) + let digit = DIGITS[bit] + let unit = UNITS[wholeValue.length-i-1] + if(bit==0){ + let preBit =i< wholeValue.length ? parseInt(wholeValue[i+1]) : null// 上一位 + let isKeyBits = ((wholeValue.length-i-1) % 4)==0 + if(preBit && preBit!=0 && !isKeyBits) result = "零" + result + if(isKeyBits) result = UNITS[wholeValue.length-i-1] + result + }else{ + result=`${digit}${unit}` + result + } + } + if(isBig){ + result = result.replace("垓京","垓") + .replace("京兆","京") + .replace("兆億","兆") + .replace("億萬","億") + .replace("萬仟","萬") + }else{ + result = result.replace("垓京","垓") + .replace("京兆","京") + .replace("兆亿","兆") + .replace("亿万","亿") + .replace("万千","万") + if(result.startsWith("一十")) result=result.substring(1) + } + return result // 中文数字忽略小数部分 + } + + function toChineseBigNumber(value) { + return toChineseNumber(value,true) + } + /** + * 转换为中文大写货币 + * @param {*} value + * @param {*} division 分割符号位数,3代表每3个数字添加一个,号 + * @param {*} prefix 前缀 + * @param {*} suffix 后缀 + * @param {*} precision 小数点精确到几位 + */ + function toChineseCurrency(value,{big=false,prefix="",unit="元",suffix=""}={}){ + let [wholeValue,decimalValue] = String(value).split(".") + let result + if(big){ + result = toChineseBigNumber(wholeValue)+unit + }else{ + result = toChineseNumber(wholeValue)+unit + } + if(decimalValue){ + if(decimalValue[0]) result =result+ CN_NUMBER_DIGITS[parseInt(decimalValue[0])]+"角" + if(decimalValue[1]) result =result+ CN_NUMBER_DIGITS[parseInt(decimalValue[1])]+"分" + } + return prefix+result+suffix + } + + module.exports ={ + toChineseCurrency, + toChineseNumber, + toChineseBigNumber, + CN_DATETIME_UNITS, + CN_WEEK_DAYS, + CN_SHORT_WEEK_DAYS, + CN_MONTH_NAMES, + CN_SHORT_MONTH_NAMES, + CN_NUMBER_DIGITS, + CN_NUMBER_UNITS, + CN_NUMBER_BIG_DIGITS, + CN_NUMBER_BIG_UNITS + } \ No newline at end of file diff --git a/packages/runtime/datatypes/currency.js b/packages/runtime/datatypes/currency.js new file mode 100644 index 0000000..152c4b2 --- /dev/null +++ b/packages/runtime/datatypes/currency.js @@ -0,0 +1,80 @@ +/*** + * + * 处理货币相关 + * + */ + +const { isNumber } = require('../utils') + + +/** + * 为字符串按bits位添加一个, + * @param {*} str + * @param {*} bits + * @returns + */ +function addSplitChars(str,bits=3){ + let regexp = new RegExp(String.raw`(?!^)(?=(\d{${bits}})+$)`,"g") + let r = str.replace(regexp,",") + if(r.startsWith(",")) r = r.substring(1) + if(r.endsWith(",")) r = r.substring(0,r.length-2) + return r +} + + +/** + * 转换为货币格式 + * + * @param {*} value 可以是数字也可以是字符串 + * @param {*} division 分割符号位数,3代表每3个数字添加一个,号 + * @param {*} prefix 前缀 + * @param {*} suffix 后缀 + * @param {*} precision 小数点精确到几位,0-自动 + * @param {*} format 格式模块板字符串 + * @returns + */ + function toCurrency(value,params={}){ + let {symbol="",division=3,prefix="",precision=2,suffix="",unit=0,unitName="",radix=3,format="{symbol}{value}{unit}"} = params + + // 1. 分离出整数和小数部分 + let [wholeDigits,decimalDigits] = String(value).split(".") + // 2. 转换数制单位 比如将元转换到万元单位 + // 如果指定了unit单位,0-代表默认,1-N代表将小数点字向后移动radix*unit位 + // 比如 123456789.88 + // 当unit=1,radix=3时, == [123456,78988] // [整数,小数] + // 当unit=2,radix=3时, == [123,45678988] // [整数,小数] + if(unit>0 && radix>0){ + // 不足位数时补零 + if(wholeDigits.length0){// 四舍五入处理 + let finalBits = decimalDigits.length // 四舍五入前的位数 + decimalDigits = String(parseFloat(`0.${decimalDigits}`).toFixed(precision)).split(".")[1] + //如果经过四舍五入处理后的位数小于,代表精度进行舍去,则未尾显示+符号 + if(finalBits > decimalDigits.length) decimalDigits+="+" + } + result.push(`.${decimalDigits}`) + } + // 5. 模板替换 + return format.replace("{value}",result.join("")) + .replace("{symbol}",symbol) + .replace("{prefix}",prefix) + .replace("{suffix}",suffix) + .replace("{unit}",unitName) +} + + module.exports = { + toCurrency +} \ No newline at end of file diff --git a/packages/runtime/datatypes/datetime.js b/packages/runtime/datatypes/datetime.js new file mode 100644 index 0000000..e22acf9 --- /dev/null +++ b/packages/runtime/datatypes/datetime.js @@ -0,0 +1,164 @@ +/** + +处理日期时间相关 + +*/ +const { isFunction,replaceAll } = require('../utils') +const { Formatter } = require('../formatter'); + +/** + * 格式化日期 + * 将值转换为Date类型 + * @param {*} value + */ + function toDate(value) { + try { + return value instanceof Date ? value : new Date(value) + } catch { + return parseInt(value) + } +} + +/** + * 获取一天中的时间段 + * @param {*} hour 小时,取值0-23 + * @param {*} options + * @returns + */ +function getTimeSlot(hour,caseStyle = 0){ + if(hour<0 && hour>23) hour = 0 + const timeSlots = this.timeSlots + const slots = [0,...timeSlots.slots,24] + let slotIndex = slots.findIndex(v=>v>hour) - 1 + return caseStyle == 0 ? timeSlots.lowerCases[slotIndex] : timeSlots.upperCases[slotIndex] +} + +/** + * 根据模板格式化日期时间 + * + + YY 18 年,两位数 + YYYY 2018 年,四位数 + M 1-12 月,从1开始 + MM 01-12 月,两位数字 + MMM Jan-Dec 月,英文缩写 + D 1-31 日 + DD 01-31 日,两位数 + H 0-23 24小时 + HH 00-23 24小时,两位数 + h 1-12 12小时 + hh 01-12 12小时,两位数 + m 0-59 分钟 + mm 00-59 分钟,两位数 + s 0-59 秒 + ss 00-59 秒,两位数 + S 0-9 毫秒(百),一位数 + SS 00-99 毫秒(十),两位数 + SSS 000-999 毫秒,三位数 + Z -05:00 UTC偏移 + ZZ -0500 UTC偏移,两位数 + A AM / PM 上/下午,大写 + a am / pm 上/下午,小写 + Do 1st... 31st 月份的日期与序号 + t 小写时间段,如am,pm + T 大写时间段段,如上午、中午、下午 + + + * 重点: this-->当前格化器的配置参数 + * + * 因此可以通过this.month.long来读取长格式模板 + * + * + * @param {*} value + * @param {*} template + * @param {Boolean} onlyTime 仅格式化时间 + * @param {*} options = {month:[<短月份名称>,<短月份名称>],timeSlots:{}} + * @returns + */ +function formatDatetime(value,template="YYYY/MM/DD HH:mm:ss",onlyTime=false){ + const $config = this.datetime// this->指向的是当前语言的格式化化器配置 + const v = toDate(value) + const hour = v.getHours(),Hour = String(hour).padStart(2, "0") + const hour12 = hour > 12 ? hour - 12 : hour ,Hour12 = String(hour12).padStart(2, "0") + const minute = String(v.getMinutes()),second = String(v.getSeconds()),millisecond=String(v.getMilliseconds()) + let vars = [ + ["HH", Hour], // 00-23 24小时,两位数 + ["H", hour], // 0-23 24小时 + ["hh", Hour12], // 01-12 12小时,两位数 + ["h", hour12], // 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 毫秒,三位数 + ["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写 + ["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写 + ["t", getTimeSlot.call($config,hour,0)], // 小写时间段,如上午、中午、下午 + ["T", getTimeSlot.call($config,hour,1)], // 大写时间段,如上午、中午、下午 + ] + if(!onlyTime){ + const year =String(v.getFullYear()),month = v.getMonth(),weekday=v.getDay(),day=String(v.getDate()) + vars.push(...[ + ["YYYY", year], // 2018 年,四位数 + ["YY", year.substring(2)], // 18年,两位数 + ["MMM", $config.month.short[month]], // Jan-Dec月,缩写 + ["MM", String(month+1).padStart(2, "0")], // 01-12月,两位数字 + ["M", month+1], // 1-12 月,从1开始 + ["DD", day.padStart(2, "0")], // 01-31 日,两位数 + ["D", day], // 1-31 日 + ["d",weekday], // 0-6 一周中的一天,星期天是 0 + ["dd",$config.weekday.short[weekday]], // Su-Sa 最简写的星期几 + ["ddd",$config.weekday.short[weekday]], // Sun-Sat 简写的星期几 + ["dddd",$config.weekday.long[weekday]], // Sunday-Saturday 星期几,英文全称 + ]) + } + let result = template + vars.forEach(([k,v])=>result = replaceAll(result,k,v)) + return result +} +/** + * 只格式化时间 + * @param {*} value + * @param {*} template + * @returns + */ +function formatTime(value,template="HH:mm:ss"){ + return formatDatetime.call(this,value,template,true) +} + + +/** + * 该类型的格式化器具有以下特点: + * + * 1. 接受一个format参数, + * 2. format参数取值可以是若干预设的值,如long,short等,也可能是一个模板字符串 + * 3. 当format值时,如果定义在$config[configKey]里面,代表了$config[configKey][format]是一个模板字符串 + * 4. 如果!(format in $config[configKey]),则代表format值是一个模板字符串 + * 5. 如果format in presets, 则要求presets[format ]是一个(value)=>{....},直接返回 + * + **/ +function createDateTimeFormatter(options={},transformer){ + let opts = Object.assign({presets:{}},options) + return Formatter(function(value,format,$config){ + if((format in opts.presets) && isFunction(opts.presets[format])){ + return opts.presets[format](value) + }else if((format in $config)){ + format = $config[format] + }else if(format == "number"){ + return value + } + try{// this指向的是activeFormatter.$config + return format==null ? value : transformer.call(this,value,format) + }catch(e){ + return value + } + },opts) +} + + +module.exports = { + toDate, + formatTime, + formatDatetime, + createDateTimeFormatter +} \ No newline at end of file diff --git a/packages/runtime/datatypes/numeric.js b/packages/runtime/datatypes/numeric.js new file mode 100644 index 0000000..65224af --- /dev/null +++ b/packages/runtime/datatypes/numeric.js @@ -0,0 +1,33 @@ +/*** + * + * 处理数字相关 + * + */ + const { Formatter } = require("../formatter") +/** + * 转换为数字类型 + */ + function toNumber(value,defualt=0) { + try { + if (isNumber(value)) { + return parseFloat(value) + } else { + return defualt + } + } catch { + return value + } +} + +const numberFormartter = Formatter(function(value,precision,division,$config){ + return toCurrency(value, { division, precision}) +},{ + normalize: toNumber, + params:["precision","division"], + configKey: "number" +}) + +module.exports = { + toNumber, + numberFormartter +} \ No newline at end of file diff --git a/packages/runtime/formatter.js b/packages/runtime/formatter.js index 87dd48c..c42d877 100644 --- a/packages/runtime/formatter.js +++ b/packages/runtime/formatter.js @@ -9,7 +9,8 @@ */ - const { getByPath,isNumber,isFunction,isPlainObject,escapeRegexpStr,safeParseJson } = require("./utils") +const { getByPath,isNumber,isFunction,isPlainObject,escapeRegexpStr,safeParseJson } = require("./utils") +const { toNumber } = require("./datatypes/numeric"); /** @@ -268,6 +269,7 @@ const formatterNestingParamsRegex = String.raw`((([\'\"])(.*?)\3))|__TAG_REGEXP_ },options) // 最后一个参数是传入activeFormatterConfig参数 + // 并且格式化器的this指向的是activeFormatterConfig const $formatter = function(value,...args){ let finalValue = value // 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性 @@ -285,15 +287,17 @@ const formatterNestingParamsRegex = String.raw`((([\'\"])(.*?)\3))|__TAG_REGEXP_ if(opts.params==null){// 如果格式化器支持变参,则需要指定params=null finalArgs = args.slice(0,args.length-1) }else{ // 具有固定的参数个数 - finalArgs = opts.params.map(param=>getByPath(formatterConfig,param,undefined)) + finalArgs = opts.params.map(param=>{ + let paramValue = getByPath(formatterConfig,param,undefined) + return isFunction(paramValue) ? paramValue(finalValue) : paramValue + }) // 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数 for(let i =0; i}) } + * + * 请参阅currencyFormatter用法 + * + * @param {*} fn + * @param {*} options + * @param {*} defaultParams + */ +const createFlexFormatter = function(fn,options={},defaultParams={}){ + const $formatter = Formatter(function(value,...args){ + // 1. 最后一个参数是格式化器的参数,不同语言不一样 + let $config = args[args.length-1] + // 2. 从语言配置中读取默认参数 + let finalParams = options.params.reduce((r,name) =>{ + r[name] = $config[name] || defaultParams[name] + return r + } ,{}) + // 3. 从格式化器中传入的参数具有最高优先级,覆盖默认参数 + if(args.length==1) { // 无参调用 + Object.assign(params,{format:'default'}) + }else if(args.length==2 && isPlainObject(args[0])){ // 一个参数且是{} + Object.assign(params,args[0]) + }else{ // 位置参数,如果是空则代表 + for(let i=0; i{ + params.unit = parseInt(params.unit) || 0 + if(params.unit>4) params.unit = 4 + if(params.unit<0) params.unit = 0 + // 当指定unit大于0时取消小数点精度控制 + // 例 value = 12345678.99 默认情况下精度是2,如果unit=1,则显示1234.47+, + // 将params.precision=0取消精度限制就可以显示1234.567899万,从而保证完整的精度 + // 除非显式将precision设置为>2的值 + if(params.unit>0 && params.precision==2){ + params.precision = 0 + } + return toCurrency(value,params) +},{ + normalize: toNumber, + params : ["format","unit","precision","prefix","suffix","division","radix"], + configKey: "currency" +},{ + format:"default", + unit:0 // 小数点精度控制,0代表不控制 +}) + +const FlexFormatter =createFlexFormatter + module.exports = { createFormatter, + createFlexFormatter, Formatter, + FlexFormatter, parseFormatters + } \ No newline at end of file diff --git a/packages/runtime/formatters/default.js b/packages/runtime/formatters/default.js index f88493d..6e08f32 100644 --- a/packages/runtime/formatters/default.js +++ b/packages/runtime/formatters/default.js @@ -1,42 +1,22 @@ const { toNumber,isFunction } = require("../utils") - +const { Formatter } = require("../formatter") /** - * 字典格式化器 - * 根据输入data的值,返回后续参数匹配的结果 - * dict(data,,,,,,,...) - * - * - * dict(1,1,"one",2,"two",3,"three",4,"four") == "one" - * dict(2,1,"one",2,"two",3,"three",4,"four") == "two" - * dict(3,1,"one",2,"two",3,"three",4,"four") == "three" - * dict(4,1,"one",2,"two",3,"three",4,"four") == "four" - * // 无匹配时返回原始值 - * dict(5,1,"one",2,"two",3,"three",4,"four") == 5 - * // 无匹配时并且后续参数个数是奇数,则返回最后一个参数 - * dict(5,1,"one",2,"two",3,"three",4,"four","more") == "more" - * - * 在翻译中使用 - * I have { value | dict(1,"one",2,"two",3,"three",4,"four")} apples - * - * 为什么不使用 {value | dict({1:"one",2:"two",3:"three",4:"four"})}的形式更加自然? - * - * 因为我们是采用正则表达式来对格式化器的语法进行解释的,目前无法支持复杂的数据类型,只能支持简单的形式 - * + * 字典格式化器 * + * {value | dict({1:"one",2:"two",3:"three",4:"four"})} + * @param {*} value * @param {...any} args * @returns */ - function dict(value, ...args) { - for (let i = 0; i < args.length; i += 2) { - if (args[i] === value) { - return args[i + 1] - } - } - if (args.length > 0 && (args.length % 2 !== 0)) return args[args.length - 1] - return value - } + function dict(key, values) { + if(key in values){ + return values[key] + }else{ + return ("default" in values) ? values[key] : value + } +} /** * @@ -57,10 +37,10 @@ const { toNumber,isFunction } = require("../utils") * @paran {String} next 下一步行为,取值true/false,break,skip,默认是break * @param {*} config */ - function empty(value,escapeValue,next,config) { +const empty = Formatter(function(value,escapeValue,next,$config){ if(next===false) next = 'break' if(next===true) next = 'skip' - let opts = Object.assign({escape:"",next:'break',values:[]},config.empty || {}) + let opts = Object.assign({escape:"",next:'break',values:[]},$config) if(escapeValue!=undefined) opts.escape = escapeValue let emptyValues = [undefined,null] if(Array.isArray(opts.values)) emptyValues.push(...opts.values) @@ -69,8 +49,11 @@ const { toNumber,isFunction } = require("../utils") }else{ return value } -} -empty.paramCount = 2 +},{ + params:["escape","next"], + configKey: "empty" +}) + /** * 当执行格式化器出错时的显示内容. @@ -91,12 +74,10 @@ empty.paramCount = 2 * @param {*} config 格式化器的全局配置参数 * @returns */ -function error(value,escapeValue,next,config) { +const error = Formatter(function(value,escapeValue,next,$config){ if(value instanceof Error){ - if(scope.debug) console.error(`Error while execute formatter<${value.formatter}>:`,e) - const scope = this try{ - let opts = Object.assign({escape:null,next:'break'},config.error || {}) + let opts = Object.assign({escape:null,next:'break'},$config) if(escapeValue!=undefined) opts.escape = escapeValue if(next!=undefined) opts.next = next return { @@ -104,14 +85,16 @@ function error(value,escapeValue,next,config) { next : opts.next } }catch(e){ - if(scope.debug) console.error(`Error while execute formatter:`,e.message) + } return value }else{ return value } -} -error.paramCount = 2 // 声明该格式化器支持两个参数 +},{ + params:["escape","next"], + configKey: "error" +}) /** * 添加前缀 @@ -157,15 +140,10 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", " * * @param {*} value * @param {*} unit 单位,未指定时采用自动方式,即<1024用字节,1024{ let v = toNumber(value) let unitIndex if(unit==undefined || unit=="auto"){ @@ -175,16 +153,17 @@ function filesize(value,unit,brief=true,options={}){ unitIndex =["B","BYTE","BYTES"].includes(unit) ? 0 : FILE_SIZE_BRIEF_UNITS.indexOf(unit) } if(unitIndex<0 || unitIndex>=FILE_SIZE_BRIEF_UNITS.length) unitIndex= 0 - let result = (unitIndex == 0 ? v : v / FILE_SIZE_SECTIONS[unitIndex]).toFixed(opts.precision) + let result = (unitIndex == 0 ? v : v / FILE_SIZE_SECTIONS[unitIndex]).toFixed($config.precision) if( unitIndex>0 && (v % FILE_SIZE_SECTIONS[unitIndex])!==0) result = result+"+" // 去除尾部的0 while(["0","."].includes(result[result.length-1])){ result = result.substring(0, result.length-2) } - return brief ? `${result} ${opts.brief[unitIndex]}` : `${result} ${opts.brief[whole]}` -} -filesize.paramCount = 2 - + return brief ? `${result} ${$config.brief[unitIndex]}` : `${result} ${$config.whole[unitIndex]}` +},{ + params:["unit","brief"], + configKey:"fileSize" +}) diff --git a/packages/runtime/formatters/en.js b/packages/runtime/formatters/en.js index 1d8ec33..250513b 100644 --- a/packages/runtime/formatters/en.js +++ b/packages/runtime/formatters/en.js @@ -3,37 +3,15 @@ * */ - const { toDate,toCurrency,toNumber,isFunction,isPlainObject,formatDatetime,formatTime } = require("../utils") - const { Formatter } = require("../formatter") +const { isFunction,isPlainObject} = require("../utils") +const { toDate,formatDatetime,formatTime,createDateTimeFormatter } = require("../datatypes/datetime") +const { Formatter } = require("../formatter") + +const { toCurrency } = require("../datatypes/currency") +const { toNumber,numberFormartter } = require("../datatypes/numeric") + -/** - * 该类型的格式化器具有以下特点: - * - * 1. 接受一个format参数, - * 2. format参数取值可以是若干预设的值,如long,short等,也可能是一个模板字符串 - * 3. 当format值时,如果定义在$config[configKey]里面,代表了$config[configKey][format]是一个模板字符串 - * 4. 如果!(format in $config[configKey]),则代表format值是一个模板字符串 - * 5. 如果format in presets, 则要求presets[format ]是一个(value)=>{....},直接返回 - * - **/ -function createDateTimeFormatter(options={},transformer){ - let opts = Object.assign({presets:{}},options) - return Formatter((value,format,$config)=>{ - if((format in opts.presets) && isFunction(opts.presets[format])){ - return opts.presets[format](value) - }else if((format in $config)){ - format = $config[format] - }else if(format == "number"){ - return value - } - try{ - return format==null ? value : transformer(value,format) - }catch(e){ - return value - } - },opts) -} /** @@ -106,119 +84,6 @@ const weekdayFormatter = createDateTimeFormatter({ },formatTime) -// const dateFormatter = Formatter((value,format,$config)=>{ -// const optionals = ["local","long","short","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 -// } -// }) -// // format名称不是optionals中的一个,并且被配置在$config,则视为扩展预设值 -// if(optionIndex==-1 && typeof(format)=="string" && (format in $config)){ -// format = $config[format] -// } - -// switch(optionIndex){ -// case 0: // local -// return value.toLocaleString() -// case 1: // long -// return formatDatetime(value,$config.long) -// case 2: // short -// return formatDatetime(value,$config.short) -// 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=长格式 1=数字 -// const quarterFormatter = Formatter((value,format,$config)=>{ -// const month = value.getMonth() + 1 -// const quarter = Math.floor( ( month % 3 == 0 ? ( month / 3 ) : (month / 3 + 1 ) )) -// if(typeof(format)==='string'){ -// format = ['short','long','number'].indexOf(format) -// } -// if(format<0 && format>2) format = 0 -// return format==0 ? $config.short[quarter] : (format==1 ? $config.long[quarter] : quarter) -// },{ -// normalize: toDate, -// params : ['format'], -// configKey: "datetime.quarter" -// }) - -// // 月份格式化器 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.long[month] : (format==1 ? $config.short[month] : month+1) -// },{ -// normalize: toDate, -// params : ['format'], -// configKey: "datetime.month" -// }) - -// // 星期x格式化器 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.long[day] : (format==1 ? $config.short[day] : day) -// },{ -// normalize: toDate, -// params : ['format'], -// configKey: "datetime.weekday" -// }) - - -// // 时间格式化器 format可以取值0-local(默认),1-long,2-short,3-timestamp,也可以是一个插值表达式 -// const timeFormatter = Formatter((value,format,$config)=>{ -// const optionals = ['local','long','short','timestamp'] -// const optionIndex = optionals.findIndex((v,i)=>{ -// if(typeof(format)=="string"){ -// return v==format || v== format.toUpperCase() -// }else if(typeof(format)=="number"){ -// return format === i -// } -// }) -// // format名称不是optionals中的一个,并且被配置在$config,则视为扩展预设值 -// if(optionIndex==-1 && typeof(format)=="string" && (format in $config)){ -// format = $config[format] -// } - -// switch(optionIndex){ -// case 0: // local : toLocaleTimeString -// return value.toLocaleTimeString() -// case 1: // long -// return formatTime(value,$config.long) -// case 2: // short -// return formatTime(value,$config.short) -// case 3: // timestamp -// return value.getTime() -// default: -// return formatTime(value,format) -// } -// },{ -// normalize: toDate, -// params : ['format'], -// configKey: "datetime.time" -// }) - // 货币格式化器, CNY $13,456.00 /** * { value | currency } @@ -251,7 +116,7 @@ const currencyFormatter = Formatter((value,...args) =>{ Object.assign(params,{format:args[0]}) }else if(args.length==3){// 2个参数,分别是format,unit Object.assign(params,{format:args[0],unit:args[1]}) - }else if(args.length==4){// 2个参数,分别是format,unit,precision + }else if(args.length==4){// 3个参数,分别是format,unit,precision Object.assign(params,{format:args[0],unit:args[1],precision:args[2]}) } // 4. 检查参数正确性 @@ -308,10 +173,10 @@ module.exports = { short : "HH:mm:ss", format : 'local' }, - timeslots : { + timeSlots : { slots : [12], - lowercase : ["am","pm"], - uppercase : ["AM","PM"] + lowerCases : ["am","pm"], + upperCases : ["AM","PM"] } }, currency : { @@ -319,19 +184,23 @@ module.exports = { long : "{prefix} {symbol}{value}{unit}{suffix}", short : "{symbol}{value}{unit}", custom : "{prefix} {symbol}{value}{unit}{suffix}", + format : "default", //-- units : [""," thousands"," millions"," billions"," trillions"], //千,百万,十亿,万亿 - radix : 3, // 进制,即三位一进制,中文是是4位一进 + radix : 3, // 进制,即三位一进,中文是4位一进 symbol : "$", // 符号 prefix : "USD", // 前缀 suffix : "", // 后缀 division : 3, // ,分割位 - precision : 2, // 精度 + precision : 2, // 精度 }, number : { - division : 3, - precision : 2 + division : 3, // , 分割位,3代表每3位添加一个, + precision : 0, // 精度,即保留小数点位置,0代表不限 + default : null, // 默认数字写法 + regular : null, // 正规数字,不同的语言可能理解不一样,在中文中对应一、二、三 + big : null // 正则数字,在中文中对应的是大写壹、貳、參 }, empty:{ //values : [], // 可选,定义空值,如果想让0,''也为空值,可以指定values=[0,''] @@ -344,15 +213,14 @@ module.exports = { next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略 }, fileSize:{ - //brief: ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"], - //whole:["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"], - //precision: 2 // 小数精度 + brief: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"], + whole:["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"], + precision: 2 // 小数精度 } }, // 默认数据类型的格式化器 $types: { Date : dateFormatter, - //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", @@ -370,5 +238,5 @@ module.exports = { // ******************* 货币 ******************* currency : currencyFormatter, // 数字,如,使用分割符 - number : (value, division = 3,precision = 0) => toCurrency(value, { division, precision}) + number : numberFormartter } \ No newline at end of file diff --git a/packages/runtime/formatters/zh.js b/packages/runtime/formatters/zh.js index 04e6a3d..9b44e28 100644 --- a/packages/runtime/formatters/zh.js +++ b/packages/runtime/formatters/zh.js @@ -3,9 +3,10 @@ * */ - 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 { toChineseCurrency,toChineseNumber,CN_DATETIME_UNITS,CN_WEEK_DAYS,CN_SHORT_WEEK_DAYS, CN_MONTH_NAMES, CN_SHORT_MONTH_NAMES} = require("../cnutils") +const { toDate } = require("../datatypes/datetime") +const { toCurrency } = require("../datatypes/currency") + module.exports = { // 配置参数: 格式化器函数的最后一个参数就是该配置参数 $config:{ @@ -36,13 +37,12 @@ module.exports = { short : "HH:mm:ss", format : 'local' }, - timeslots : { + timeSlots : { slots : [6,9,11,13,18], lowerCases : ["凌晨","早上","上午","中午","下午","晚上"], upperCases : ["凌晨","早上","上午","中午","下午","晚上"] } }, - currency : { units : ["","万","亿","万亿","万万亿"], radix : 4, // 进制,即三位一进制,中文是是4位一进 @@ -53,8 +53,11 @@ module.exports = { precision : 2 }, number : { - division : 3, - precision : 2 + division : 4, + precision : 0, + default : null, // 默认数字写法 + short : null, // 正规数字,不同的语言可能理解不一样,在中文中对应一、二、三 + long : null // 正则数字,在中文中对应的是大写壹、貳、參 } }, $types: { diff --git a/packages/runtime/index.js b/packages/runtime/index.js index 4e21537..121035f 100644 --- a/packages/runtime/index.js +++ b/packages/runtime/index.js @@ -1,12 +1,13 @@ -const {DataTypes,getDataTypeName,isNumber,isPlainObject,isFunction,isNothing,deepMerge,deepMixin} = require("./utils") +const {DataTypes,getDataTypeName,isPlainObject,isFunction,isNumber,isNothing,deepMerge,deepMixin} = require("./utils") const {getInterpolatedVars,replaceInterpolatedVars} = require("./interpolate") const {createFormatter,Formatter} = require("./formatter") +const { toDate } = require("./datatypes/datetime") +const { toNumber } = require("./datatypes/numeric") const EventEmitter = require("./eventemitter") const inlineFormatters = require("./formatters") const i18nScope = require("./scope") const { translate } = require("./translate") - // 默认语言配置 const defaultLanguageSettings = { debug : true, @@ -157,6 +158,14 @@ const defaultLanguageSettings = { } module.exports ={ + toDate, + toNumber, + isNumber, + isNothing, + isPlainObject, + isFunction, + deepMerge, + deepMixin, getInterpolatedVars, replaceInterpolatedVars, I18nManager, @@ -164,11 +173,5 @@ module.exports ={ i18nScope, createFormatter, Formatter, - getDataTypeName, - isNumber, - isNothing, - isPlainObject, - isFunction, - deepMerge, - deepMixin + getDataTypeName } \ No newline at end of file diff --git a/packages/runtime/interpolate.js b/packages/runtime/interpolate.js index 73d9a14..12d8ccc 100644 --- a/packages/runtime/interpolate.js +++ b/packages/runtime/interpolate.js @@ -175,10 +175,7 @@ function getDataTypeDefaultFormatter(scope, activeLanguage, dataType) { ]; for (const target of targets) { if (!target) continue; - if ( - isPlainObject(target.$types) && - isFunction(target.$types[dataType]) - ) { + if (isPlainObject(target.$types) &&isFunction(target.$types[dataType])) { return (scope.$cache.typedFormatters[dataType] = target.$types[dataType]); } @@ -203,8 +200,7 @@ function getFormatter(scope, activeLanguage, name) { if (scope.$cache.activeLanguage === activeLanguage) { if (name in scope.$cache.formatters) return scope.$cache.formatters[name]; - } else { - // 当语言切换时清空缓存 + } else { // 当语言切换时清空缓存 resetScopeCache(scope, activeLanguage); } const fallbackLanguage = scope.getLanguage(activeLanguage).fallback; @@ -232,18 +228,20 @@ function getFormatter(scope, activeLanguage, name) { * @param {*} value * @returns */ -function executeChecker(checker, value) { +function executeChecker(checker, value,scope) { let result = { value, next: "skip" }; if (!isFunction(checker)) return result; try { - const r = checker(value); - if (isPlainObject(r)) { + const r = checker(value,scope.activeFormatterConfig); + if (isPlainObject(r) && ("next" in r) && ("value" in r)) { Object.assign(result, r); } else { result.value = r; } if (!["break", "skip"].includes(result.next)) result.next = "break"; - } catch (e) {} + } catch (e) { + if(scope.debug) console.error("Error while execute VoerkaI18n checker :"+e.message) + } return result; } /** @@ -265,7 +263,7 @@ function executeFormatter(value, formatters, scope, template) { ); if (emptyCheckerIndex != -1) { const emptyChecker = formatters.splice(emptyCheckerIndex, 1)[0]; - const { value, next } = executeChecker(emptyChecker, result); + const { value, next } = executeChecker(emptyChecker, result,scope); if (next == "break") { return value; } else { @@ -273,15 +271,13 @@ function executeFormatter(value, formatters, scope, template) { } } // 2. 错误检查 - const errorCheckerIndex = formatters.findIndex( - (func) => func.$name === "error" - ); + const errorCheckerIndex = formatters.findIndex((func) => func.$name === "error" ); let errorChecker; if (errorCheckerIndex != -1) { errorChecker = formatters.splice(errorCheckerIndex, 1)[0]; if (result instanceof Error) { result.formatter = formatter.$name; - const { value, next } = executeChecker(errorChecker, result); + const { value, next } = executeChecker(errorChecker, result,scope); if (next == "break") { return value; } else { @@ -293,13 +289,11 @@ function executeFormatter(value, formatters, scope, template) { // 3. 分别执行格式化器函数 for (let formatter of formatters) { try { - result = formatter(result, scope.activeFormatterConfig); + result = formatter(result, scope.activeFormatterConfig); } catch (e) { e.formatter = formatter.$name; if (scope.debug) - console.error( - `Error while execute i18n formatter<${formatter.$name}> for ${template}: ${e.message} ` - ); + console.error(`Error while execute i18n formatter<${formatter.$name}> for ${template}: ${e.message} `); if (isFunction(errorChecker)) { const { value, next } = executeChecker(errorChecker, result); if (next == "break") { @@ -340,7 +334,7 @@ function addDefaultFormatters(formatters) { * 本函数将之传换为转化为调用函数链,形式如下: * [(v)=>{...},(v)=>{...},(v)=>{...}] * - * 并且会自动将当前激活语言的格式化器配置作为最后一个参数配置传入,这样格式化器函数就可以读取 + * 并且会自动将当前激活语言的格式化器配置作为最后一个参数配置传入,这样格式化器函数就可以读取其配置参数 * * @param {*} scope * @param {*} activeLanguage @@ -353,13 +347,12 @@ function wrapperFormatters(scope, activeLanguage, formatters) { addDefaultFormatters(formatters); for (let [name, args] of formatters) { let fn = getFormatter(scope, activeLanguage, name); - let formatter; - // 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用 - // 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用 + let formatter; if (isFunction(fn)) { - formatter = (value, config) => - fn.call(scope, value, ...args, config); + formatter = (value, config) =>fn.call(scope.activeFormatterConfig, value, ...args, config); } else { + // 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用 + // 比如padStart格式化器是String的原型方法,不需要配置就可以直接作为格式化器调用 formatter = (value) => { if (isFunction(value[name])) { return value[name](...args); diff --git a/packages/runtime/utils.js b/packages/runtime/utils.js index b173c3a..00e9afc 100644 --- a/packages/runtime/utils.js +++ b/packages/runtime/utils.js @@ -119,88 +119,8 @@ function deepMixin(toObj,formObj,options={}){ if(typeof(v)==="function") return "Function" return v.constructor && v.constructor.name; }; -/** - * 格式化日期 - * 将值转换为Date类型 - * @param {*} value - */ -function toDate(value) { - try { - return value instanceof Date ? value : new Date(value) - } catch { - return parseInt(value) - } -} -/** - * 转换为数字类型 - */ -function toNumber(value,defualt=0) { - try { - if (isNumber(value)) { - return parseFloat(value) - } else { - return defualt - } - } catch { - return value - } -} -/** - * 转换为货币格式 - * - * @param {*} value 可以是数字也可以是字符串 - * @param {*} division 分割符号位数,3代表每3个数字添加一个,号 - * @param {*} prefix 前缀 - * @param {*} suffix 后缀 - * @param {*} precision 小数点精确到几位,0-自动 - * @param {*} format 格式模块板字符串 - * @returns - */ - function toCurrency(value,params={}){ - let {symbol="",division=3,prefix="",precision=2,suffix="",unit=0,unitName="",radix=3,format="{symbol}{value}{unit}"} = params - // 1. 分离出整数和小数部分 - let [wholeDigits,decimalDigits] = String(value).split(".") - // 2. 转换数制单位 比如将元转换到万元单位 - // 如果指定了unit单位,0-代表默认,1-N代表将小数点字向后移动radix*unit位 - // 比如 123456789.88 - // 当unit=1,radix=3时, == [123456,78988] // [整数,小数] - // 当unit=2,radix=3时, == [123,45678988] // [整数,小数] - if(unit>0 && radix>0){ - // 不足位数时补零 - if(wholeDigits.length0) result.push(",") - result.push(wholeDigits[i]) - } - // 4. 处理保留小数位数,即精度 - if(decimalDigits){ - // 如果precision是一个数字,则进行四舍五入处理 - if(isNumber(precision) && precision>0){// 四舍五入处理 - let finalBits = decimalDigits.length // 四舍五入前的位数 - decimalDigits = String(parseFloat(`0.${decimalDigits}`).toFixed(precision)).split(".")[1] - //如果经过四舍五入处理后的位数小于,代表精度进行舍去,则未尾显示+符号 - if(finalBits > decimalDigits.length) decimalDigits+="+" - } - result.push(`.${decimalDigits}`) - } - result = result.join("") - // 5. 模板替换 - result = format.replace("{value}",result) - .replace("{symbol}",symbol) - .replace("{prefix}",prefix) - .replace("{suffix}",suffix) - .replace("{unit}",unitName) - return result -} /** * 根据路径获取指定值 @@ -244,126 +164,6 @@ function deepClone(obj){ } -// YY 18 年,两位数 -// YYYY 2018 年,四位数 -// M 1-12 月,从1开始 -// MM 01-12 月,两位数字 -// MMM Jan-Dec 月,英文缩写 -// D 1-31 日 -// DD 01-31 日,两位数 -// H 0-23 24小时 -// HH 00-23 24小时,两位数 -// h 1-12 12小时 -// hh 01-12 12小时,两位数 -// m 0-59 分钟 -// mm 00-59 分钟,两位数 -// s 0-59 秒 -// ss 00-59 秒,两位数 -// S 0-9 毫秒(百),一位数 -// SS 00-99 毫秒(十),两位数 -// SSS 000-999 毫秒,三位数 -// Z -05:00 UTC偏移 -// ZZ -0500 UTC偏移,两位数 -// A AM / PM 上/下午,大写 -// a am / pm 上/下午,小写 -// Do 1st... 31st 月份的日期与序号 - - -function getTimeSlot(hour,options={}){ - if(hour<0 && hour>23) hour = 0 - const opts = Object.assign({ - slots : [12], - lowerCases : ["am","pm"], - upperCases : ["AM","PM"], - caseType : 0 - },options) - opts.slots.splice(0,0,0) // slots = [0,....] - opts.slots.push(24) - let slotIndex = opts.slots.findIndex(v=>v>hour) - 1 - return caseType == 0 ? opts.lowerCases[slotIndex] : opts.upperCases[slotIndex] -} - - -/** - * 根据模板格式化日期时间 - * @param {*} value - * @param {*} templ - * @param {*} options = {month:[<短月份名称>,<短月份名称>],timeSlots:{}} - * @returns - */ -function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss",options={}){ - const opts = Object.assign({ - month:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"], - timeSlots:{ - slots : [12], - lowercase : ["am","pm"], - uppercase : ["AM","PM"], - caseType : 0 - } - },options) - const v = toDate(value) - const year =String(v.getFullYear()),month = String(v.getMonth()+1),weekday=String(v.getDay()),day=String(v.getDate()) - const hourValue = v.getHours() - const hour = String(hourValue), minute = String(v.getMinutes()),second = String(v.getSeconds()),millisecond=String(v.getMilliseconds()) - const timeSlot = getTimeSlot(hourValue,opts.timeSlots) - const vars = [ - ["YYYY", year], // 2018 年,四位数 - ["YY", year.substring(year.length - 2, year.length)], // 18 年,两位数 - ["MMM", ""], // Jan-Dec 月,英文缩写 - ["MM", month.padStart(2, "0")], // 01-12 月,两位数字 - ["M", month], // 1-12 月,从1开始 - ["DD", day.padStart(2, "0")], // 01-31 日,两位数 - ["D", day], // 1-31 日 - ["HH", hour.padStart(2, "0")], // 00-23 24小时,两位数 - ["H", hour], // 0-23 24小时 - ["hh", String(hourValue > 12 ? hourValue - 12 : hourValue).padStart(2, "0")], // 01-12 12小时,两位数 - ["h", String(hourValue > 12 ? hourValue - 12 : hourValue)], // 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 毫秒,三位数 - ["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写 - ["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写 - ["t", getTimeSlot(hourValue,{...opts.timeSlots,caseType:0})], // 小写时间段,如上午、中午、下午 - ["T", getTimeSlot(hourValue,{...opts.timeSlots,caseType:1})], // 大写时间段,如上午、中午、下午 - ] - let result = templ - vars.forEach(([k,v])=>result = replaceAll(result,k,v)) - return result -} - -function formatTime(value,templ="HH:mm:ss",options={}){ - const opts = Object.assign({ - timeSlots:{ - slots : [12], - lowercase : ["am","pm"], - uppercase : ["AM","PM"], - caseType : 0 - } - },options) - const v = toDate(value) - const hourValue = v.getHours() - const hour = String(hourValue),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", 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 秒,两位数 - ["s", second], // 0-59 秒 - ["SSS", millisecond], // 000-999 毫秒,三位数 - ["A", hour > 12 ? "PM" : "AM"], // AM / PM 上/下午,大写 - ["a", hour > 12 ? "pm" : "am"], // am / pm 上/下午,小写 - ["t", getTimeSlot(hourValue,{...opts.timeSlots,caseType:0})], // 小写时间段,如上午、中午、下午 - ["T", getTimeSlot(hourValue,{...opts.timeSlots,caseType:1})], // 大写时间段,如上午、中午、下午 - ] - vars.forEach(([k,v])=>result = replaceAll(result,k,v)) - return result -} /** * 替换所有字符串 * 低版本ES未提供replaceAll,此函数用来替代 @@ -440,12 +240,7 @@ module.exports ={ deepMixin, replaceAll, getByPath, - getDataTypeName, - formatDatetime, - formatTime, - toDate, - toNumber, - toCurrency, + getDataTypeName, escapeRegexpStr, safeParseJson } \ No newline at end of file diff --git a/test/translate.test.js b/test/runtime.test.js similarity index 95% rename from test/translate.test.js rename to test/runtime.test.js index 57b5fd6..8d932c1 100644 --- a/test/translate.test.js +++ b/test/runtime.test.js @@ -482,7 +482,8 @@ test("翻译复数支持",async ()=>{ expect(t("我有{}个朋友",4)).toBe("I have 4 friends"); }) test("日期时间格式化器",async ()=>{ - let zhTranslatedResults = zhDatetimes.map(v=>t(v,NOW)) + let zhTranslatedResults = zhDatetimes.map(v=>t(v,NOW)) + let p = diffArray(zhTranslatedResults,expectZhDatetimes) expect(zhTranslatedResults).toStrictEqual(expectZhDatetimes) await scope.change("en") let enTranslatedResults = zhDatetimes.map(v=>t(v,NOW)) @@ -497,5 +498,26 @@ test("日期时间格式化器",async ()=>{ let enMoneysResults = enMoneys.map(v=>t(v,MONEY)) expect(enMoneysResults).toStrictEqual(expectEnMoneys) }) + + test("数字格式化器",async ()=>{ + + expect(t("{value}",123)).toBe("123") + expect(t("{value | number}",123)).toBe("123") + expect(t("{value | number}",123456789)).toBe("1,2345,6789") + expect(t("{value | number}",123456789.8888)).toBe("1,2345,6789.8888") + expect(t("{value | number(3)}",123456789.8888)).toBe("1,2345,6789.889") + expect(t("{value | number(3,2)}",123456789.8888)).toBe("12,34,567,89.889") + + await scope.change("en") + + expect(t("{value}",123)).toBe("123") + expect(t("{value | number}",123)).toBe("123") + expect(t("{value | number}",123456789)).toBe("123,456,789") + expect(t("{value | number}",123456789.8888)).toBe("123,456,789.8888") + expect(t("{value | number(3)}",123456789.8888)).toBe("1,2345,6789.889") + expect(t("{value | number(3,2)}",123456789.8888)).toBe("12,34,567,89.889") + + }) + })