update formatters

This commit is contained in:
wxzhang 2022-08-12 18:08:45 +08:00
parent 3c31d3fb67
commit b8f59c43bc
6 changed files with 325 additions and 78 deletions

View File

@ -7,7 +7,7 @@ export default defineConfig({
base:"/voerka-i18n/",
publicPath:"/voerka-i18n/",
mode: 'site',
logo: "/images/i18n.png",
logo: "/voerka-i18n/images/i18n.png",
outputPath:"docs/dist",
resolve:{
includes:["docs/src"]

View File

@ -1,4 +1,7 @@
/**
import { toNumber,isFunction } from "../utils"
/**
* 字典格式化器
* 根据输入data的值返回后续参数匹配的结果
* dict(data,<value1>,<result1>,<value2>,<result1>,<value3>,<result1>,...)
@ -34,7 +37,156 @@
if (args.length > 0 && (args.length % 2 !== 0)) return args[args.length - 1]
return value
}
/**
*
* 空值 null,undefined
*
* 当输入空值时的行为
*
* { value | empty } == 转换显示为''
* { value | empty('无') } ==
* { value | unit('KB') | empty('0') } == 0KB
*
* 有时在处理其他类型时可能希望将0或者''也视为空值
* { value | empty('没钱了') } ==
*
*
* @param {*} value
* @param {String} escapeValue
* @param {*} options
*/
function empty(value,escapeValue,next) {
if(next===true) next = 'break'
let opts = Object.assign({escape:"",next:'ignore'},options.empty || {})
if(!escapeValue) opts.escape = escapeValue
let emptyValues = [undefined,null]
if(Array.isArray(opts.values)) emptyValues.push(...opts.values)
if(emptyValues.includes(value)){
return {value:opts.escape,next: opts.next}
}else{
return value
}
}
empty.paramCount = 2
/**
* 当执行格式化器出错时的显示内容.
{ value | error } ==
{ value | error('') } == 显示空字符串
{ value | error('ERROR') } == 显示ERROR字样
{ value | error('ERROR:{ message}') } == 显示error.message
{ value | error('ERROR:{ error}') } == 显示error.constructor.name
this--> scope实例
* @param {*} value
* @param {*} escapeValue
* @param {*} next 下一步的行为取值break,ignore
* @param {*} options 格式化器的全局配置参数
* @returns
*/
function error(value,escapeValue,next,options) {
if(value instanceof Error){
try{
let opts = Object.assign({escape:"",next:'break'},options.error || {})
if(!escapeValue) opts.escape = escapeValue
if(!next) opts.next = next
return {
value : String(opts.escape).replace(/\{\s*message\s*\}/g,value.message)
.replace(/\{\s*error\s*\}/g,value.constructor.name)
next : opts.next
}
}cache(e){
if(this.debug) console.error(`Error while execute formatter: ${e.message}`)
}
}else{
return value
}
}
error.paramCount = 2 // 声明该格式化器支持两个参数
/**
* 添加前缀
* @param {*} value
* @param {*} prefix
* @returns
*/
function prefix(value,prefix="") {
return prefix ? `${prefix}${value}` : value
}
/**
* 添加后缀
* @param {*} value
* @param {*} suffix
* @returns
*/
function suffix(value,suffix="") {
return suffix ? `${value}${suffix}` : value
}
const FILE_SIZE_SECTIONS = [
0,
1024,
1048576,
1073741824,
1099511627776,
1125899906842624,
1152921504606847000,
1.1805916207174113e+21,
1.2089258196146292e+24,
1.2379400392853803e+27,
1.2676506002282294e+30
]
const FILE_SIZE_BRIEF_UNITS = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"]
const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"]
//const getSizePoint= index=>index ==0 ? 0 : new Array(index).fill(0).reduce((pv,cv)=>pv*1024,1)
/**
* 输出文件大小
*
* { value | fileSize }
* { value | fileSize('KB') }
* { value | fileSize('MB') }
*
* @param {*} value
* @param {*} unit 单位未指定时采用自动方式<1024用字节1024<v<1024*1024显示KB,...
* @param {*} brief
* @param {*} options
*/
function fileSize(value,unit,brief=true,options={}){
let opts = Object.assign({
precision: 2,
brief : FILE_SIZE_BRIEF_UNITS,
whole : FILE_SIZE_WHOLE_UNITS
},options.fileSize || {})
let v = toNumber(value)
let unitIndex
if(unit==undefined || unit=="auto"){
unitIndex = FILE_SIZE_SECTIONS.findIndex(x=>v<x) - 1
}else{
unit = unit.toUpperCase()
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)
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
fileSize.escape = 0
module.exports = {
dict
dict,
prefix,
suffix,
filesize,
error,
empty
}

View File

@ -5,6 +5,9 @@
const { toDate,toCurrency } = require("../utils")
module.exports = {
// 配置参数: 格式化器函数的最后一个参数就是该配置参数
$options:{
@ -25,6 +28,21 @@
number : {
division : 3,
precision : 2
},
empty:{
//values : [], // 可选定义空值如果想让0,''也为空值可以指定values=[0,'']
escape : "", // 当空值时显示的备用值
next : null // 当空值时下一步的行为: break=中止;ignore=忽略
},
error : {
//当错误时显示的内容支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数
escape : "", // 默认当错误时显示空内容
next : null // 当出错时下一步的行为: break=中止;ignore=忽略
},
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 // 小数精度
}
},
// 默认数据类型的格式化器

View File

@ -288,28 +288,51 @@ function getFormatter(scope,activeLanguage,name){
/**
* 执行格式化器并返回结果
*
* 格式化器this指向当前scope并且最后一个参数是当前scope格式化器的$options
*
* 这样格式化器可以读取$options
*
* @param {*} value
* @param {*} formatters 多个格式化器顺序执行前一个输出作为下一个格式化器的输入
* @param {Array[Function]} formatters 多个格式化器函数(经过包装过的)顺序执行前一个输出作为下一个格式化器的输入
*/
function executeFormatter(value,formatters,scope){
if(formatters.length===0) return value
let result = value
try{
for(let formatter of formatters){
if(isFunction(formatter)) {
result = formatter.call(scope,result)
}else{// 如果碰到无效的格式化器,则跳过过续的格式化器
return result
const emptyChecker = formatters.find(func=>func.$name==='empty')
const errorChecker = formatters.find(func=>func.$name==='error')
// 当输入是一个空值时
if(emptyChecker){
const { value,next } = emptyChecker(result)
if(next == 'break') return value
}
if(result instanceof Error && errorChecker){
const { value,next } = errorChecker(result)
if(next == 'break') return value
}
for(let formatter of formatters){
try{
result = formatter(result)
}catch(e){
if(scope.debug) console.error(`Error while execute i18n formatter<${formatter.$name}> for ${value}: ${e.message} ` )
const { value,next } = errorChecker(e)
if(next=="break"){
if(value!==undefined) result = value
break
}else if(next=="ignore"){
continue
}
}
}catch(e){
//console.error(`Error while execute i18n formatter for ${value}: ${e.message} ` )
}
if(value!==undefined) result = value
}
}
return result
}
/**
*
* [[格式化器名称,[参数,参数,...]][格式化器名称,[参数,参数,...]]]格式化器转化为
* [[格式化器名称,[参数,参数,...]][格式化器名称,[参数,参数,...]]]格式化器包装转化为
* 格式化器的调用函数链
*
* @param {*} scope
@ -318,29 +341,41 @@ function executeFormatter(value,formatters,scope){
* @returns {Array} [(v)=>{...},(v)=>{...},(v)=>{...}]
*
*/
function buildFormatters(scope,activeLanguage,formatters){
let results = []
for(let formatter of formatters){
if(formatter[0]){
const func = getFormatter(scope,activeLanguage,formatter[0])
function wrapperFormatters(scope,activeLanguage,formatters){
let wrappedFormatters = []
for(let [name,args] of formatters){
if(name){
const func = getFormatter(scope,activeLanguage,name)
if(isFunction(func)){
results.push((v)=>{
return func(v,...formatter[1])
})
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))
}
return func.call(scope,value,...args,scope.activeFormatterOptions)
}
fn.$name = name
wrappedFormatters.push(fn)
}else{
// 格式化器无效或者没有定义时,查看当前值是否具有同名的原型方法,如果有则执行调用
// 比如padStart格式化器是String的原型方法不需要配置就可以直接作为格式化器调用
results.push((v)=>{
if(isFunction(v[formatter[0]])){
return v[formatter[0]].call(v,...formatter[1])
wrappedFormatters.push((value)=>{
if(isFunction(value[name])){
// 最后一个参数是当前作用域的格式化器配置参数
return value[name](value,...args)
}else{
return v
return value
}
})
}
}
}
return results
return wrappedFormatters
}
/**
@ -353,14 +388,15 @@ function buildFormatters(scope,activeLanguage,formatters){
*/
function getFormattedValue(scope,activeLanguage,formatters,value){
// 1. 取得格式化器函数列表
const formatterFuncs = buildFormatters(scope,activeLanguage,formatters)
// 2. 查找每种数据类型默认格式化器,并添加到formatters最前面默认数据类型格式化器优先级最高
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
// 默认数据类型的格式化器仅在没有指定其他格式化器时生效
if(defaultFormatter && formatterFuncs.length==0){
formatterFuncs.splice(0,0,defaultFormatter)
}
const formatterFuncs = wrapperFormatters(scope,activeLanguage,formatters)
// 3. 执行格式化器
if(formatterFuncs.length==0){
// 当没有格式化器时,查询是否指定了默认数据类型的格式化器,如果有则执行
const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value))
if(defaultFormatter){
return executeFormatter(value,[defaultFormatter],scope)
}
}
value = executeFormatter(value,formatterFuncs,scope)
return value
}

View File

@ -57,49 +57,29 @@ module.exports = class i18nScope {
this.register(callback);
}
// 作用域
get id() {
return this._id;
}
get id() {return this._id;}
// 调试开关
get debug() {
return this._debug;
}
get debug() {return this._debug;}
// 默认语言名称
get defaultLanguage() {
return this._defaultLanguage;
}
get defaultLanguage() {return this._defaultLanguage;}
// 默认语言名称
get activeLanguage() {
return this._activeLanguage;
}
get activeLanguage() {return this._activeLanguage;}
// 默认语言包
get default() {
return this._default;
}
get default() {return this._default;}
// 当前语言包
get messages() {
return this._messages;
}
get messages() {return this._messages; }
// 消息id映射列表
get idMap() {
return this._idMap;
}
get idMap() {return this._idMap;}
// 当前作用域的格式化器 {<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}}
get formatters() {
return this._formatters;
}
get formatters() { return this._formatters;}
// 当前作用域支持的语言列表[{name,title,fallback}]
get languages() {
return this._languages;
}
get languages() {return this._languages;}
// 异步加载语言文件的函数列表
get loaders() {
return this._loaders;
}
get loaders() { return this._loaders;}
// 引用全局VoerkaI18n配置注册后自动引用
get global() {
return this._global;
}
get global() { return this._global;}
// 当前格式化器配置参数
get activeFormatterOptions(){return this._activeFormatterOptions}
/**
* 在全局注册作用域
* @param {*} callback 注册成功后的回调

View File

@ -197,16 +197,77 @@ function getByPath(obj,path,defaultValue){
return cur
}
/**
* 返回value相对rel的相对时间
*
* 12分钟前 6秒前, 1小时
*
*
*
*/
function relativeTime(value, rel){
// 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 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 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", String(hour).padStart(2, "0")], // 00-23 24小时两位数
["H", String(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小时
["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 = result.replace(key,value))
return result
}
/**
* 创建格式化器
*/
function createFormatter(fn,meta={}){
let opts = Object.assign({
checker : false, // true时代表该格式化器是作为校验器存在会在执行每一次格式化器函数后调用进行检查
paramCount : 0, // 声明该该格式化器具有几个参数,0代表未知
error : 0, // 当执行格式化化器函数出错时的行为取值0-break,1-skip,2-default
empty : false, // 当输入为空时的输出
default : null // 当出错默认输出
},meta)
Object.entries(opts).forEach(([key,value])=>fn[key] = value)
return fn
}
module.exports ={