This commit is contained in:
wxzhang 2023-04-13 22:04:27 +08:00
parent e060827dc1
commit d945c2092d
8 changed files with 255 additions and 261 deletions

View File

@ -87,6 +87,8 @@ const formatters ={
let scope:VoerkaI18nScope; let scope:VoerkaI18nScope;
describe("所有测试", () => {
beforeAll(async ()=>{ beforeAll(async ()=>{
return new Promise((resolve)=>{ return new Promise((resolve)=>{
scope = new VoerkaI18nScope({ scope = new VoerkaI18nScope({
@ -103,7 +105,6 @@ beforeAll(async ()=>{
beforeEach(async ()=>{ beforeEach(async ()=>{
await scope.change("zh") await scope.change("zh")
}) })
describe("VoerkaI18nScope", () => { describe("VoerkaI18nScope", () => {
test("成功创建实例", () => { test("成功创建实例", () => {
expect(scope).toBeInstanceOf(VoerkaI18nScope) expect(scope).toBeInstanceOf(VoerkaI18nScope)
@ -112,7 +113,6 @@ describe("VoerkaI18nScope", () => {
expect(scope.messages).toEqual(messages) expect(scope.messages).toEqual(messages)
expect(scope.default).toEqual(zhMessages) expect(scope.default).toEqual(zhMessages)
expect(scope.current).toEqual(zhMessages) expect(scope.current).toEqual(zhMessages)
expect(scope.idMap).toEqual(idMap)
// 全局管理器 // 全局管理器
expect(scope.global).toBeInstanceOf(VoerkaI18nManager) expect(scope.global).toBeInstanceOf(VoerkaI18nManager)
}) })
@ -125,7 +125,6 @@ describe("VoerkaI18nScope", () => {
expect(scope.messages).toEqual(messages) expect(scope.messages).toEqual(messages)
expect(scope.default).toEqual(zhMessages) expect(scope.default).toEqual(zhMessages)
expect(scope.current).toEqual(enMessages) expect(scope.current).toEqual(enMessages)
expect(scope.idMap).toEqual(idMap)
resolve() resolve()
}) })
scope.change("en") scope.change("en")
@ -325,3 +324,4 @@ describe('插值变量格式化器', () => {
describe('内置格式化器', () => { describe('内置格式化器', () => {
}) })
})

View File

@ -5,12 +5,12 @@
*/ */
import { FlexFormatter, IFormatter } from '../formatter' import { FlexFormatter, Formatter } from '../formatter'
import { toChineseNumber } from "flex-tools/chinese/toChineseNumber" import { toChineseNumber } from "flex-tools/chinese/toChineseNumber"
import { toChineseCurrency } from "flex-tools/chinese/toChineseCurrency" import { toChineseCurrency } from "flex-tools/chinese/toChineseCurrency"
export const chineseNumberFormatter = Formatter((value: any, [isBig]:[isBig: boolean], $config) => { export const chineseNumberFormatter = Formatter<any,[isBig: boolean]>((value: any, [isBig], config: any) => {
return toChineseNumber(value, isBig) as string return toChineseNumber(value, isBig) as string
}, { }, {
params: ["isBig"] params: ["isBig"]

View File

@ -4,14 +4,14 @@
*/ */
import { toDate } from '../utils' import { toDate } from '../utils'
import { IFormatter } from '../formatter';
import { formatDateTime } from "flex-tools/misc/formatDateTime" import { formatDateTime } from "flex-tools/misc/formatDateTime"
import { relativeTime } from "flex-tools/misc/relativeTime" import { relativeTime } from "flex-tools/misc/relativeTime"
import { assignObject } from "flex-tools/object/assignObject" import { assignObject } from "flex-tools/object/assignObject"
import { isFunction } from "flex-tools/typecheck/isFunction" import { isFunction } from "flex-tools/typecheck/isFunction"
import { Formatter } from '../formatter'
function formatTime(value:number ,template="HH:mm:ss"){ function formatTime(value:number ,[template]="HH:mm:ss"){
return formatDateTime(value,template,{}) return formatDateTime(value,template,{})
} }
@ -21,8 +21,8 @@ function formatTime(value:number ,template="HH:mm:ss"){
* *
* 1. format参数 * 1. format参数
* 2. format参数取值可以是若干预设的值long,short等 * 2. format参数取值可以是若干预设的值long,short等
* 3. format值时$config[configKey]$config[configKey][format] * 3. format值时config[configKey]config[configKey][format]
* 4. !(format in $config[configKey])format值是一个模板字符串 * 4. !(format in config[configKey])format值是一个模板字符串
* 5. format in presets, presets[format ](value)=>{....} * 5. format in presets, presets[format ](value)=>{....}
* *
**/ **/
@ -30,15 +30,15 @@ export type FormatterTransformer = (value:any,format:string)=>string
export function createDateTimeFormatter(options={},transformer:FormatterTransformer){ export function createDateTimeFormatter(options={},transformer:FormatterTransformer){
let opts = assignObject({presets:{}},options) let opts = assignObject({presets:{}},options)
return Formatter(function(this:any,value:any,[format]:any[],$config:Record<string,any>){ return Formatter(function(this:any,value:any,[format]:any[],config:Record<string,any>){
if((format in opts.presets) && isFunction(opts.presets[format])){ if((format in opts.presets) && isFunction(opts.presets[format])){
return opts.presets[format](value) return opts.presets[format](value)
}else if((format in $config)){ }else if((format in config)){
format = $config[format] format = config[format]
}else if(format == "number"){ }else if(format == "number"){
return value return value
} }
try{// this指向的是activeFormatter.$config try{// this指向的是activeFormatter.config
return format==null ? value : transformer.call(this,value,format) return format==null ? value : transformer.call(this,value,format)
}catch(e){ }catch(e){
return value return value
@ -50,7 +50,7 @@ export function createDateTimeFormatter(options={},transformer:FormatterTransfor
/** /**
* *
* - format取值local,long,short,iso,gmt,utc,<模板字符串> * - format取值local,long,short,iso,gmt,utc,<模板字符串>
* - $config.datetime.date.format指定 * - config.datetime.date.format指定
*/ */
export const dateFormatter = createDateTimeFormatter({ export const dateFormatter = createDateTimeFormatter({
normalize: toDate, normalize: toDate,
@ -104,7 +104,7 @@ export const weekdayFormatter = createDateTimeFormatter({
/** /**
* *
* - format取值local,long,short,timestamp,<模板字符串> * - format取值local,long,short,timestamp,<模板字符串>
* - $config.datetime.time.format指定 * - config.datetime.time.format指定
*/ */
export const timeFormatter = createDateTimeFormatter({ export const timeFormatter = createDateTimeFormatter({
normalize : toDate, normalize : toDate,
@ -121,9 +121,9 @@ export const timeFormatter = createDateTimeFormatter({
* @param {*} value * @param {*} value
* @param {*} baseTime * @param {*} baseTime
*/ */
export const relativeTimeFormatter = Formatter((value:any,[baseTime]:[Date],$config:any)=>{ export const relativeTimeFormatter = Formatter<any,[Date]>((value:any,[baseTime],config:any)=>{
//const { units,now,before,base = Date.now() , after } = $config //const { units,now,before,base = Date.now() , after } = config
return relativeTime(value, $config) return relativeTime(value,baseTime,config)
},{ },{
normalize:toDate, normalize:toDate,
params:["base"], params:["base"],

View File

@ -9,12 +9,12 @@
* *
*/ */
import { toNumber } from "../utils" import { toNumber } from "../utils"
import { IFormatter } from "../formatter"
import { toCurrency } from "./currency" import { toCurrency } from "./currency"
import { VoerkaI18nFormatter } from '../types'; import { VoerkaI18nFormatter } from '../types';
import { Formatter } from "../formatter";
export const numberFormartter = Formatter(function(value:any,[precision,division]:[number,number],$config:any){ export const numberFormartter = Formatter<any,any[]>(function(value:any,[precision,division],config:Record<string,any>){
return toCurrency(value, { division, precision},$config) return toCurrency(value, { division, precision},config)
},{ },{
normalize: toNumber, normalize: toNumber,
params:["precision","division"], params:["precision","division"],

View File

@ -16,7 +16,7 @@ import { isFunction } from "flex-tools/typecheck/isFunction"
import { isPlainObject } from "flex-tools/typecheck/isPlainObject" import { isPlainObject } from "flex-tools/typecheck/isPlainObject"
import { safeParseJson } from "flex-tools/object/safeParseJson" import { safeParseJson } from "flex-tools/object/safeParseJson"
import { assignObject } from "flex-tools/object/assignObject" import { assignObject } from "flex-tools/object/assignObject"
import { VoerkaI18nFormatterConfigs } from './types'; import { VoerkaI18nFormatterConfigs, VoerkaI18nFormatter, Primitive } from './types';
/** /**
使便 使便
@ -271,18 +271,14 @@ function parseFormaterParams(strParams: string): any[] {
* @returns * @returns
*/ */
export type VarValueType = string | Error | undefined | null
export type IFormatter = (this: any, value: VarValueType, args: any[],config:VoerkaI18nFormatterConfigs) => string export interface CreateFormatterOptions {
export interface FormatterOptions {
normalize?: (value: string) => any // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等 normalize?: (value: string) => any // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等
params?: Record<string, any> | null, // 可选的声明参数顺序如果是变参的则需要传入null params?: Record<string, any> | null, // 可选的声明参数顺序如果是变参的则需要传入null
configKey?: string // 声明该格式化器在$config中的路径支持简单的使用.的路径语法 configKey?: string // 声明该格式化器在$config中的路径支持简单的使用.的路径语法
} }
export function createFormatter(fn: IFormatter, options?: FormatterOptions, defaultParams?: Record<string, any>) { export function createFormatter<Value=any,Args extends any[] = any[],Return=any>(fn: VoerkaI18nFormatter, options?: CreateFormatterOptions, defaultParams?: Record<string, any>) {
let opts = assignObject({ let opts = assignObject({
normalize: null, // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等 normalize: null, // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等需要对输入值进行处理如强制类型转换等
params: null, // 可选的声明参数顺序如果是变参的则需要传入null params: null, // 可选的声明参数顺序如果是变参的则需要传入null
@ -291,7 +287,7 @@ export function createFormatter(fn: IFormatter, options?: FormatterOptions, defa
// 最后一个参数是传入activeFormatterConfig参数 // 最后一个参数是传入activeFormatterConfig参数
// 并且格式化器的this指向的是activeFormatterConfig // 并且格式化器的this指向的是activeFormatterConfig
const $formatter = function (this: any, value: string, args: any[],$config:Record<string,any>) { const $formatter = function (this: any, value: Value, args: Args,config:Record<string,any>):Return {
let finalValue = value let finalValue = value
// 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性 // 1. 输入值规范处理,主要是进行类型转换,确保输入的数据类型及相关格式的正确性,提高数据容错性
if (isFunction(opts.normalize)) { if (isFunction(opts.normalize)) {
@ -312,7 +308,6 @@ export function createFormatter(fn: IFormatter, options?: FormatterOptions, defa
}) })
// 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数 // 4. 将翻译函数执行格式化器时传入的参数覆盖默认参数
for (let i = 0; i < finalArgs.length; i++) { for (let i = 0; i < finalArgs.length; i++) {
if (i == args.length - 1) break // 最后一参数是配置
if (args[i] !== undefined) finalArgs[i] = args[i] if (args[i] !== undefined) finalArgs[i] = args[i]
} }
} }
@ -339,14 +334,14 @@ export function createFormatter(fn: IFormatter, options?: FormatterOptions, defa
* @param {*} options * @param {*} options
* @param {*} defaultParams * @param {*} defaultParams
*/ */
export const createFlexFormatter = function (fn: IFormatter, options: FormatterOptions, defaultParams?: Record<string, any>) { export const createFlexFormatter = function<Value=any,Args extends any[]=any[],Return=any>(fn: VoerkaI18nFormatter, options: CreateFormatterOptions, defaultParams?: Record<string, any>) {
const opts = assignObject({ const opts = assignObject({
params: {} params: {}
}, options) }, options)
const $flexFormatter = Formatter(function (this: any, value: VarValueType,args: string[],$config:Record<string,any> ) { const $flexFormatter = Formatter<Value,Args,Return>(function (this: any, value: any,args,config:Record<string,any> ) {
// 2. 从语言配置中读取默认参数 // 2. 从语言配置中读取默认参数
let finalParams = (options.params || {}).reduce((r: Record<string, any>, name: string) => { let finalParams = (options.params || {}).reduce((r: Record<string, any>, name: string) => {
r[name] = $config[name] == undefined ? (defaultParams || {})[name] : $config[name] r[name] = config[name] == undefined ? (defaultParams || {})[name] : config[name]
return r return r
}, {}) }, {})
// 3. 从格式化器中传入的参数具有最高优先级,覆盖默认参数 // 3. 从格式化器中传入的参数具有最高优先级,覆盖默认参数
@ -357,13 +352,12 @@ export const createFlexFormatter = function (fn: IFormatter, options: FormatterO
if (args[i] !== undefined) finalParams[opts.params[i]] = args[i] if (args[i] !== undefined) finalParams[opts.params[i]] = args[i]
} }
} }
return fn.call(this, value, finalParams, $config) return fn.call(this, value, finalParams, config)
}, { ...options, params: null }) // 变参工式化器需要指定params=null }, { ...options, params: null }) // 变参工式化器需要指定params=null
return $flexFormatter return $flexFormatter
} }
export const Formatter = createFormatter export const Formatter = createFormatter
export const FlexFormatter = createFlexFormatter export const FlexFormatter = createFlexFormatter

View File

@ -1,7 +1,7 @@
import { assignObject } from 'flex-tools/object/assignObject'; import { assignObject } from 'flex-tools/object/assignObject';
import { VoerkaI18nFormatterConfigs } from '../types'; import { VoerkaI18nFormatterConfigs } from '../types';
import { toNumber } from "../utils" import { toNumber } from "../utils"
import { IFormatter, VarValueType,Formatter } from "../formatter" import { Formatter } from "../formatter"
/** /**
* *
@ -39,7 +39,7 @@ export function dict(key:string, values:any) {
* @paran {String} next true/false,break,skip,break * @paran {String} next true/false,break,skip,break
* @param {*} config * @param {*} config
*/ */
export const empty = Formatter(function(value:VarValueType,[escapeValue,next]:[escapeValue:any,next: 'break' | 'ignore'],config:VoerkaI18nFormatterConfigs){ export const empty = Formatter<any,[escapeValue:any,next: 'break' | 'ignore']>(function(value:any,[escapeValue,next],config:VoerkaI18nFormatterConfigs){
let opts = assignObject({escape:"",next:'break',values:[]},config) let opts = assignObject({escape:"",next:'break',values:[]},config)
if(escapeValue!=undefined) opts.escape = escapeValue if(escapeValue!=undefined) opts.escape = escapeValue
let emptyValues = [undefined,null] let emptyValues = [undefined,null]
@ -72,10 +72,10 @@ export const empty = Formatter(function(value:VarValueType,[escapeValue,next]:[e
* @param {*} config * @param {*} config
* @returns * @returns
*/ */
export const error = Formatter(function(value:string,escapeValue:any,next:'break' | 'ignore',$config:VoerkaI18nFormatterConfigs){ export const error = Formatter<any,[escapeValue:any,next:'break' | 'ignore']>(function(value,[escapeValue,next],config:VoerkaI18nFormatterConfigs){
if(typeof(value)=='object' && (value instanceof Error)){ if(typeof(value)=='object' && (value instanceof Error)){
try{ try{
let opts = assignObject({escape:null,next:'break'},$config) let opts = assignObject({escape:null,next:'break'},config)
if(escapeValue!=undefined) opts.escape = escapeValue if(escapeValue!=undefined) opts.escape = escapeValue
if(next!=undefined) opts.next = next if(next!=undefined) opts.next = next
return { return {
@ -141,7 +141,7 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "
* @param {*} brief * @param {*} brief
* @param {*} options * @param {*} options
*/ */
export const filesize= Formatter((value:string,unit:string,brief:boolean=true,$config:VoerkaI18nFormatterConfigs)=>{ export const filesize= Formatter<any,[unit:string,brief:boolean]>((value:string,[unit,brief],config:VoerkaI18nFormatterConfigs)=>{
let v = toNumber(value) let v = toNumber(value)
let unitIndex let unitIndex
if(unit==undefined || unit=="auto"){ if(unit==undefined || unit=="auto"){
@ -151,13 +151,13 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "
unitIndex =["B","BYTE","BYTES"].includes(unit) ? 0 : FILE_SIZE_BRIEF_UNITS.indexOf(unit) unitIndex =["B","BYTE","BYTES"].includes(unit) ? 0 : FILE_SIZE_BRIEF_UNITS.indexOf(unit)
} }
if(unitIndex<0 || unitIndex>=FILE_SIZE_BRIEF_UNITS.length) unitIndex= 0 if(unitIndex<0 || unitIndex>=FILE_SIZE_BRIEF_UNITS.length) unitIndex= 0
let result = (unitIndex == 0 ? v : v / FILE_SIZE_SECTIONS[unitIndex]).toFixed($config.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+"+" if( unitIndex>0 && (v % FILE_SIZE_SECTIONS[unitIndex])!==0) result = result+"+"
// 去除尾部的0 // 去除尾部的0
while(["0","."].includes(result[result.length-1])){ while(["0","."].includes(result[result.length-1])){
result = result.substring(0, result.length-2) result = result.substring(0, result.length-2)
} }
return brief ? `${result} ${$config.brief[unitIndex]}` : `${result} ${$config.whole[unitIndex]}` return brief ? `${result} ${config.brief[unitIndex]}` : `${result} ${config.whole[unitIndex]}`
},{ },{
params:["unit","brief"], params:["unit","brief"],
configKey:"fileSize" configKey:"fileSize"

View File

@ -5,7 +5,7 @@
import enFormatters from "./en" import enFormatters from "./en"
import zhFormatters from "./zh" import zhFormatters from "./zh"
import defaultFormatters from "./default" import * as defaultFormatters from "./default"
export default { export default {
"*":defaultFormatters, "*":defaultFormatters,

View File

@ -35,7 +35,7 @@ export interface VoerkaI18nLanguageDefine {
export type VoerkaI18nFormatterConfigs = Record<string, any> export type VoerkaI18nFormatterConfigs = Record<string, any>
export type VoerkaI18nFormatter = ((value: string,args: any[],config: VoerkI18nFormatterConfigs) => string) export type VoerkaI18nFormatter = ((value: any,args: any[],config: VoerkI18nFormatterConfigs) => any)
export type VoerkaI18nTypesFormatters=Partial<Record<SupportedDateTypes, VoerkaI18nFormatter>> export type VoerkaI18nTypesFormatters=Partial<Record<SupportedDateTypes, VoerkaI18nFormatter>>
export type VoerkaI18nTypesFormatterConfig= Partial<Record<string, any>> export type VoerkaI18nTypesFormatterConfig= Partial<Record<string, any>>
export type VoerkaI18nTypesFormatterConfigs= Partial<Record<SupportedDateTypes | string, Record<string,any>>> export type VoerkaI18nTypesFormatterConfigs= Partial<Record<SupportedDateTypes | string, Record<string,any>>>