update currency formatter

This commit is contained in:
wxzhang 2022-08-19 17:59:34 +08:00
parent e5faab29f0
commit 9b3b7a9035
6 changed files with 141 additions and 103 deletions

View File

@ -14,13 +14,13 @@ export default defineConfig({
},
locales: [['zh-CN', '中文']],
scripts:[`
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?06dc18a1ffc1c69ab1445ba8020ded5b";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?14df7a33942125aa14ad60ee1cdb1940";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
`],
navs:[
{

View File

@ -3,9 +3,7 @@
*
*/
const { toDate,toCurrency,toNumber,formatDatetime,formatTime,Formatter } = require("../utils")
const { toDate,toCurrency,toNumber,isPlainObject,formatDatetime,formatTime,Formatter } = require("../utils")
/**
* 日期格式化器
@ -123,26 +121,44 @@ const timeFormatter = Formatter((value,format,$config)=>{
* { value | currency({symbol,unit,prefix,precision,suffix}) }
*/
const currencyFormatter = Formatter((value,...args) =>{
// 1. 最后一个参数是格式化器的参数,不同语言不一样
let $config = args[args.length-1]
// 2. 从语言配置中读取默认参数
let params = {
unit : 0,
radix : $config.radix, // 进制即三位一进制中文是是4位一进
symbol : $config.symbol, // 符号
radix : $config.radix, // 进制,取值,0-4,
symbol : $config.symbol, // 符号,即三位一进制中文是是4位一进
prefix : $config.prefix, // 前缀
suffix : $config.suffix, // 后缀
division : $config.division, // ,分割位
precision : $config.precision, // 精度
format : $config.format, // 模板字符串
}
let $config = args[args.length-1]
if(args.length==1) {
}
// 3. 从格式化器中传入的参数具有最高优先级,覆盖默认参数
if(args.length==1) { // 无参调用
Object.assign(params,{format:'default'})
}else if(args.length==2 && isPlainObject(args[0])){
}else if(args.length==2 && isPlainObject(args[0])){ // 一个参数且是{}
Object.assign(params,args[0])
}else if(args.length==2){
}else if(args.length==2){
// 一个字符串参数只能是default,long,short, 或者是一个模板字符串,如"{symbol}{value}{unit}"
Object.assign(params,{format:args[0]})
}else{
}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
Object.assign(params,{format:args[0],unit:args[1],precision:args[2]})
}
// 4. 检查参数正确性
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
}
// 模板字符串
if(params.format in $config){
params.format = $config[params.format]
@ -188,17 +204,16 @@ module.exports = {
},
currency : {
default : "{symbol}{value}{unit}",
long : "{prefix}{symbol}{value}{unit}{suffix}",
long : "{prefix} {symbol}{value}{unit}{suffix}",
short : "{symbol}{value}{unit}",
auto : "{symbol}{value} {unit}",
//--
units : ["","Thousands","Millions","Billions","Trillions"], //千,百万,十亿,万亿
units : [""," thousands"," millions"," billions"," trillions"], //千,百万,十亿,万亿
radix : 3, // 进制即三位一进制中文是是4位一进
symbol : "$", // 符号
prefix : "", // 前缀
prefix : "USD", // 前缀
suffix : "", // 后缀
division : 3, // ,分割位
precision : 2, // 精度
precision : 2, // 精度
},
number : {

View File

@ -42,7 +42,7 @@ module.exports = {
units : ["","万","亿","万亿","万万亿"],
radix : 4, // 进制即三位一进制中文是是4位一进
symbol : "¥",
prefix : "",
prefix : "RMB",
suffix : "元",
division : 4,
precision : 2

View File

@ -41,8 +41,8 @@
// 插值变量字符串替换正则
let varReplaceRegexp =String.raw`\{\s*{varname}\s*\}`
// 提取匹配("a",1,2,'b',{..},[...])
const formaterVarsRegexp = String.raw`((([\'\"])(.*?)\3)|(\w)|(\{.*?\})|(\[.*?\]))(?<=\s*[,\)]?\s*)`
const formatterParamsRegex = /((([\'\"])(.*?)\3)|(\{.*?\})|(\[.*?\])|([\d]+\.?[\d]?)|((true|false|null)(?=[,\b\s]))|([\w\.]+)|((?<=,)\s*(?=,)))(?<=\s*[,\)]?\s*)/g;
/**
* 考虑到通过正则表达式进行插值的替换可能较慢
* 因此提供一个简单方法来过滤掉那些不需要进行插值处理的字符串
@ -84,6 +84,7 @@
let result = formatters.trim().substr(1).trim().split("|").map(r=>r.trim())
// 2. 解析格式化器参数
return result.map(formatter=>{
if(formatter=="") return null
let firstIndex = formatter.indexOf("(")
let lastIndex = formatter.lastIndexOf(")")
if(firstIndex!==-1 && lastIndex!==-1){ // 带参数的格式化器
@ -96,7 +97,7 @@
}else{// 不带参数的格式化器
return [formatter,[]]
}
})
}).filter(formatter=> Array.isArray(formatter))
}
/**
* 解析格式化器的参数
@ -186,7 +187,7 @@ function parseFormaterParams(strParams) {
* @param {Function(<变量名称>,[formatters],match[0])} callback
* @returns 返回替换后的字符串
*/
function forEachInterpolatedVars(str,callback,options={}){
function forEachInterpolatedVars(str,replacer,options={}){
let result=str, match
let opts = Object.assign({
replaceAll:true, // 是否替换所有插值变量当使用命名插值时应置为true当使用位置插值时应置为false
@ -196,9 +197,9 @@ function parseFormaterParams(strParams) {
const varname = match.groups.varname || ""
// 解析格式化器和参数 = [<formatterName>,[<formatterName>,[<arg>,<arg>,...]]]
const formatters = parseFormatters(match.groups.formatters)
if(isFunction(callback)){
if(isFunction(replacer)){
try{
const finalValue = callback(varname,formatters,match[0])
const finalValue = replacer(varname,formatters,match[0])
if(opts.replaceAll){ // 在某此版本上可能没有
result=result.replaceAll(match[0],finalValue)
}else{

View File

@ -59,8 +59,8 @@ function isNothing(value){
}
// 区配JSON字符串里面的非标的key即key没有使用"字符包起来的键
const bastardJsonKeyRegex = /(([\w\u4e00-\u9fa5])|(\'.*?\'))+(?=\s*\:)/g
// 区配JSON字符串里面的非标的key即key没有使用"字符包起来的键
const bastardJsonKeyRegex = /((?<=:\s*)(\'.*?\')+)|((([\w\u4e00-\u9fa5])|(\'.*?\'))+(?=\s*\:))/g
/**
* 格式化器中的{a:1,b:2}形式的参数由于是非标准的JSON格式采用JSON.parse会出错
* 如果使用eval转换则存在安全隐患
@ -74,12 +74,11 @@ function safeParseJson(str){
if (matched.index === bastardJsonKeyRegex.lastIndex) {
bastardJsonKeyRegex.lastIndex++;
}
str = str.replace(new RegExp(`${matched[0]}\s*:`),key=>{
key = key.substring(0,key.length-1).trim()
str = str.replace(new RegExp(matched[0]),key=>{
if(key.startsWith("'") && key.endsWith("'")){
key = key.substring(1,key.length-1)
}
return `"${key}" :`
return `"${key}"`
})
}
return JSON.parse(str)
@ -183,36 +182,40 @@ function toNumber(value,defualt=0) {
* @returns
*/
function toCurrency(value,params={}){
const {symbol="",division=3,prefix="",precision=0,suffix="",unit=0,unitName="",radix=3,format="{symbol}{value}{unit}"} = params
let {symbol="",division=3,prefix="",precision=2,suffix="",unit=0,unitName="",radix=3,format="{symbol}{value}{unit}"} = params
// 1. 分离出整数和小数部分
let [wholeValue,decimalValue] = String(value).split(".")
let [wholeDigits,decimalDigits] = String(value).split(".")
// 2. 转换数制单位 比如将元转换到万元单位
// 如果指定了unit单位0-代表默认1-N代表将小数点字向后移动radix*unit位
// 比如 123456789.88
// 当unit=1,radix=3时 == [123456,78988]
// 当unit=1,radix=3时 == [123,45678988]
// 当unit=1,radix=3时 == [123456,78988] // [整数,小数]
// 当unit=2,radix=3时 == [123,45678988] // [整数,小数]
if(unit>0 && radix>0){
// 不足位数时补零
if(wholeValue.length<radix) wholeValue = new Array(radix-wholeValue.length+1).fill(0).join("")+ wholeValue
// 移到小数部分
decimalValue+=wholeValue.substring(wholeValue,wholeValue.length-radix)
wholeValue = wholeValue.substring(0,wholeValue.length-radix)
if(wholeDigits.length<radix*unit) wholeDigits = new Array(radix*unit-wholeDigits.length+1).fill(0).join("")+ wholeDigits
// 将整数的最后radix*unit字符移到小数部分前面
wholeDigits = wholeDigits.substring(0,wholeDigits.length-radix*unit)
decimalDigits=wholeDigits.substring(wholeDigits,wholeDigits.length-radix*unit)+decimalDigits
if(wholeDigits=="") wholeDigits = "0"
}
// 3. 添加分割符号
let result = []
for(let i=0;i<wholeValue.length;i++){
if(((wholeValue.length - i) % division)==0 && i>0) result.push(",")
result.push(wholeValue[i])
for(let i=0;i<wholeDigits.length;i++){
if(((wholeDigits.length - i) % division)==0 && i>0) result.push(",")
result.push(wholeDigits[i])
}
// 4. 处理保留小数位数,即精度
if(decimalValue){
if(precision>0){
decimalValue = String(parseFloat(`0.${decimalValue}`).toFixed(precision)).split(".")[1]
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(`.${decimalValue}`)
result.push(`.${decimalDigits}`)
}
result = result.join("")
// 5. 模板替换
@ -369,9 +372,10 @@ function replaceAll(str,findValue,replaceValue){
*
* - 函数第一个参数是上一上格式化器的输出
* - 支持0-N个简单类型的入参
* - 可以是定参也可以变参
* - 格式化器可以在格式化器的$config参数指定一个键值来配置不同语言时的参数
*
* "currency":createFormatter((value,prefix,suffix, division ,precision,options)=>{
* "currency":createFormatter((value,prefix,suffix, division ,precision,$config)=>{
* // 无论在格式化入参数是多少个经过处理后在此得到prefix,suffix, division ,precision参数已经是经过处理后的参数
* 依次读取格式化器的参数合并
* - 创建格式化时的defaultParams参数

View File

@ -13,6 +13,7 @@ function toLanguageIdMap(values,startIndex=0){
return result
},{})
}
// 显示两个数组哪一行不同
function diffArray(arr1,arr2){
let diffs = []
arr1.forEach((v,i)=>{
@ -214,7 +215,7 @@ const expectEnDatetimes =[
"Now time: 36", // { value | time('ss') }"
]
const MONEY = 123456789.8848
const MONEY = 123456789.88
const zhMoneys = [
"商品价格: { value | currency}", // 默认格式
// long
@ -230,49 +231,67 @@ const zhMoneys = [
"商品价格: { value | currency('short',3)}", // 短格式 Billions
"商品价格: { value | currency('short',4)}", // 短格式 Trillions
// // 自定义货币格式
// "商品价格: { value | currency({symbol:'#¥'})}",
// "商品价格: { value | currency({symbol:'#¥',prefix:'人民币'})}",
// "商品价格: { value | currency({symbol:'#¥',prefix:'人民币',suffix:'整'})}",
// "商品价格: { value | currency({symbol:'#¥',prefix:'人民币',suffix:'整',precision:4})}",
// "商品价格: { value | currency({symbol:'#¥',suffix:'整',precision:4, unit:2})}",
// 自定义货币格式
"商品价格: { value | currency({symbol:'¥¥'})}",
"商品价格: { value | currency({symbol:'¥¥',prefix:'人民币:'})}",
"商品价格: { value | currency({symbol:'¥¥',prefix:'人民币:',suffix:'元整'})}",
"商品价格: { value | currency({symbol:'¥¥',prefix:'人民币:',suffix:'元整',unit:2})}",
"商品价格: { value | currency({symbol:'¥¥',prefix:'人民币:',suffix:'元整',unit:2,precision:4})}"
]
const expectZhMoneys =[
"商品价格: ¥1,2345,6789.88", // { value | currency }
"商品价格: ¥1,2345,6789.88", // { value | currency }
// long
"商品价格: ¥1,2345,6789.88元", // { value | currency('long')}
"商品价格: ¥1,2345,68万元", // { value | currency('long',1)}
"商品价格: ¥1.23亿元", // { value | currency('long',2)}
"商品价格: ¥0.00万亿元", // { value | currency('long',3)}
"商品价格: ¥0.00万万亿元", // { value | currency('long',4)}
"商品价格: RMB ¥1,2345,6789.88元", // { value | currency('long')}
"商品价格: RMB ¥1,2345.678988万元", // { value | currency('long',1)}
"商品价格: RMB ¥1.2345678988亿元", // { value | currency('long',2)}
"商品价格: RMB ¥0.00012345678988万亿元", // { value | currency('long',3)}
"商品价格: RMB ¥0.000000012345678988万万亿元", // { value | currency('long',4)}
// short
"商品价格: ¥1,2345,6789.88", // { value | currency('short')}
"商品价格: ¥1,2345,68万", // { value | currency('short',1)}
"商品价格: ¥1.23亿", // { value | currency('short',2)}
"商品价格: ¥0.00万亿", // { value | currency('short',3)}
"商品价格: ¥0.00万万亿", // { value | currency('short',4)}
"商品价格: ¥1,2345,6789.88", // { value | currency('short')}
"商品价格: ¥1,2345.678988万", // { value | currency('short',1)}
"商品价格: ¥1.2345678988亿", // { value | currency('short',2)}
"商品价格: ¥0.00012345678988万亿", // { value | currency('short',3)}
"商品价格: ¥0.000000012345678988万万亿", // { value | currency('short',4)}
// 自定义货币格式
"商品价格: ¥¥1,2345,6789.88",
"商品价格: 人民币:¥¥1,2345,6789.88",
"商品价格: 人民币:¥¥1,2345,6789.88元整",
"商品价格: 人民币:¥¥1,2345.678988万元",
"商品价格: 人民币:¥¥1.2346亿元整"
]
const enMoneys = [
"Price: { value | currency }", // 默认格式,由语言配置指定,不同的语言不一样
"Price: { value | currency('long')}", // 长格式
"Price: { value | currency('short')}", // 短格式
"Price: { value | currency('$')}", // 指定货币符号
"Price: { value | currency('$','USD')}", // 指定货币符号+前缀
"Price: { value | currency('$','USD','')}", // 指定货币符号+前缀+后缀
"Price: { value | currency('$','USD','',3)}", // 指定货币符号+前缀+后缀+分割位
"Price: { value | currency('$','USD','',3,3)}", // 指定货币符号+前缀+后缀+分割位+精度
"Price: { value | currency }", // 默认格式
// long
"Price: { value | currency('long') }", // 长格式
"Price: { value | currency('long',1) }", // 长格式: 万元
"Price: { value | currency('long',2) }", // 长格式: 亿
"Price: { value | currency('long',3) }", // 长格式: 万亿
"Price: { value | currency('long',4) }", // 长格式: 万万亿
// short
"Price: { value | currency('short') }", // 短格式
"Price: { value | currency('short',1) }", // 短格式 Thousands
"Price: { value | currency('short',2) }", // 短格式 Millions
"Price: { value | currency('short',3) }", // 短格式 Billions
"Price: { value | currency('short',4) }", // 短格式 Trillions
]
const expectEnMoneys =[
"Price: $123,456,789.88", // { value | currency }
"Price: $123,456,789.88", // { value | currency('long')}
"Price: $123,456,789.88", // { value | currency('short')}
"Price: $123,456,789.88", // { value | currency('$')}
"Price: USD$1,2345,6789.88", // { value | currency('$','USD')}
"Price: USD$1,2345,6789.88", // { value | currency('$','USD','元')}
"Price: USD$123,456,789.885", // { value | currency('$','USD','元',3)}
"Price: USD$123,456,789.885", //{ value | currency('$','USD','元',3,3)}
"Price: $123,456,789.88", // { value | currency }
// long
"Price: USD $123,456,789.88", // { value | currency('long')}
"Price: USD $123,456.78988 thousands", // { value | currency('long',1)}
"Price: USD $123.45678988 millions", // { value | currency('long',2)}
"Price: USD $0.12345678988 billions", // { value | currency('long',3)}
"Price: USD $0.00012345678988 trillions", // { value | currency('long',4)}
// short
"Price: $123,456,789.88", // { value | currency('short')}
"Price: $123,456.78988 thousands", // { value | currency('short',1)}
"Price: $123.45678988 millions", // { value | currency('short',2)}
"Price: $0.12345678988 billions", // { value | currency('short',3)}
"Price: $0.00012345678988 trillions", // { value | currency('short',4)}
]
@ -452,30 +471,31 @@ test("切换到其他语言时的自动匹配同名格式化器",async ()=>{
test("位置插值翻译文本内容",async ()=>{
const now = new Date()
expect(t("你好")).toBe("你好");
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
// 经babel自动码换后文本内容会根据idMap自动转为id
expect(t("1")).toBe("你好");
expect(t("2",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
expect(t("2",now)).toBe(`现在是${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
await scope.change("en")
expect(t("你好")).toBe("hello");
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
expect(t("1")).toBe("hello");
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
})
test("命名插值翻译文本内容",async ()=>{
const now = new Date()
expect(t("你好")).toBe("你好");
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY年MM月DD日 HH点mm分ss秒')}`);
expect(t("现在是{ value | }",now)).toBe(`现在是${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
await scope.change("en")
expect(t("你好")).toBe("hello");
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
expect(t("现在是{ value | }",now)).toBe(`Now is ${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
// 使用idMap
expect(t("1")).toBe("hello");
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/MM/DD HH:mm:ss')}`);
expect(t("2",now)).toBe(`Now is ${dayjs(now).format('YYYY/M/D HH:mm:ss')}`);
})
@ -511,12 +531,10 @@ test("日期时间格式化器",async ()=>{
})
// test("货币格式化器",async ()=>{
// let zhTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
// expect(zhTranslatedResults).toStrictEqual(expectZhDatetimes)
// await scope.change("en")
// let enTranslatedResults = zhDatetimes.map(v=>t(v,NOW))
// expect(enTranslatedResults).toStrictEqual(expectEnDatetimes)
// })
test("货币格式化器",async ()=>{
let zhMoneysResults = zhMoneys.map(v=>t(v,MONEY))
expect(zhMoneysResults).toStrictEqual(expectZhMoneys)
await scope.change("en")
let enMoneysResults = enMoneys.map(v=>t(v,MONEY))
expect(enMoneysResults).toStrictEqual(expectEnMoneys)
})