diff --git a/packages/runtime/formatters/common.js b/packages/runtime/formatters/default.js similarity index 77% rename from packages/runtime/formatters/common.js rename to packages/runtime/formatters/default.js index 8dd3279..171d840 100644 --- a/packages/runtime/formatters/common.js +++ b/packages/runtime/formatters/default.js @@ -1,4 +1,4 @@ -import { toNumber,isFunction } from "../utils" +const { toNumber,isFunction } = require("../utils") /** @@ -42,9 +42,9 @@ import { toNumber,isFunction } from "../utils" * * 空值: null,undefined * - * 当输入空值时的行为 + * 当输入空值时的处理逻辑 * - * { value | empty } == 转换显示为'' + * { value | empty } == 转换显示为'',并且忽略 * { value | empty('无') } == 无 * { value | unit('KB') | empty('0') } == 0KB * @@ -54,14 +54,16 @@ import { toNumber,isFunction } from "../utils" * * @param {*} value * @param {String} escapeValue + * @paran {String} next 下一步行为,取值true/false,break,skip,默认是break * @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 + function empty(value,escapeValue,next,options) { + if(next===false) next = 'break' + if(next===true) next = 'skip' + let opts = Object.assign({escape:"",next:'break',values:[]},options.empty || {}) + if(escapeValue!=undefined) opts.escape = escapeValue let emptyValues = [undefined,null] - if(Array.isArray(opts.values)) emptyValues.push(...opts.values) + if(Array.isArray(opts.values)) emptyValues.push(...opts.values) if(emptyValues.includes(value)){ return {value:opts.escape,next: opts.next} }else{ @@ -73,13 +75,13 @@ empty.paramCount = 2 /** * 当执行格式化器出错时的显示内容. -{ value | error } == +{ value | error } == 默认 { value | error('') } == 显示空字符串 { value | error('ERROR') } == 显示ERROR字样 { value | error('ERROR:{ message}') } == 显示error.message { value | error('ERROR:{ error}') } == 显示error.constructor.name +{ value | error('ERROR:{ error}',) } == 显示error.constructor.name -this--> scope实例 * @param {*} value * @param {*} escapeValue @@ -87,21 +89,22 @@ this--> scope实例 * @param {*} options 格式化器的全局配置参数 * @returns */ -function error(value,escapeValue,next,options) { - +function error(value,escapeValue,next,options) { 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:"",next:'break'},options.error || {}) - if(!escapeValue) opts.escape = escapeValue - if(!next) opts.next = next + let opts = Object.assign({escape:null,next:'break'},options.error || {}) + if(escapeValue!=undefined) opts.escape = escapeValue + if(next!=undefined) opts.next = next return { - value : String(opts.escape).replace(/\{\s*message\s*\}/g,value.message) - .replace(/\{\s*error\s*\}/g,value.constructor.name) + value : opts.escape ? String(opts.escape).replace(/\{\s*message\s*\}/g,value.message).replace(/\{\s*error\s*\}/g,value.constructor.name) : null, next : opts.next } - }cache(e){ - if(this.debug) console.error(`Error while execute formatter: ${e.message}`) + }catch(e){ + if(scope.debug) console.error(`Error while execute formatter:`,e.message) } + return value }else{ return value } @@ -142,7 +145,6 @@ function suffix(value,suffix="") { ] 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) /** * 输出文件大小 @@ -156,7 +158,7 @@ const FILE_SIZE_WHOLE_UNITS = ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", " * @param {*} brief * @param {*} options */ -function fileSize(value,unit,brief=true,options={}){ +function filesize(value,unit,brief=true,options={}){ let opts = Object.assign({ precision: 2, brief : FILE_SIZE_BRIEF_UNITS, @@ -179,14 +181,17 @@ function fileSize(value,unit,brief=true,options={}){ } return brief ? `${result} ${opts.brief[unitIndex]}` : `${result} ${opts.brief[whole]}` } -fileSize.paramCount = 2 -fileSize.escape = 0 +filesize.paramCount = 2 - module.exports = { + + + +module.exports = { dict, prefix, suffix, filesize, error, empty - } \ No newline at end of file +} + diff --git a/packages/runtime/formatters/en.js b/packages/runtime/formatters/en.js index 0762aed..c591f4b 100644 --- a/packages/runtime/formatters/en.js +++ b/packages/runtime/formatters/en.js @@ -32,12 +32,12 @@ empty:{ //values : [], // 可选,定义空值,如果想让0,''也为空值,可以指定values=[0,''] escape : "", // 当空值时显示的备用值 - next : null // 当空值时下一步的行为: break=中止;ignore=忽略 + next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过 }, error : { //当错误时显示的内容,支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数 - escape : "", // 默认当错误时显示空内容 - next : null // 当出错时下一步的行为: break=中止;ignore=忽略 + escape : null, // 默认当错误时显示空内容 + next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略 }, fileSize:{ //brief: ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"], diff --git a/packages/runtime/formatters/index.js b/packages/runtime/formatters/index.js index e1fed09..e04ff83 100644 --- a/packages/runtime/formatters/index.js +++ b/packages/runtime/formatters/index.js @@ -5,12 +5,12 @@ const enFormatters = require("./en") const zhFormatters = require("./zh") -const commonFormatters = require("./common") +const defaultFormatters = require("./default") module.exports = { "*":{ ...enFormatters, - ...commonFormatters + ...defaultFormatters }, zh:zhFormatters } \ No newline at end of file diff --git a/packages/runtime/formatters/zh.js b/packages/runtime/formatters/zh.js index 412f2b2..8ed91ab 100644 --- a/packages/runtime/formatters/zh.js +++ b/packages/runtime/formatters/zh.js @@ -17,7 +17,7 @@ module.exports = { shorMonthNames: CN_SHORT_MONTH_NAMES }, currency : { - unit : "$", + unit : "¥", prefix : "", suffix : "", division : 3, @@ -32,7 +32,7 @@ module.exports = { Date: value => {const d = toDate(value);return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 ${d.getHours()}点${d.getMinutes()}分${d.getSeconds()}秒`} }, // 日期 - date : value => `${value.getFullYear()}年${value.getMonth() + 1}月${value.getDate()}日`, + date : value => { const d = toDate(value); return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日` }, weekday : value => CN_WEEK_DAYS[toDate(value).getDay()], shortWeekday : value => CN_SHORT_WEEK_DAYS[toDate(value).getDay()], monthName : value => CN_MONTH_NAMES[toDate(value).getMonth()], diff --git a/packages/runtime/index.js b/packages/runtime/index.js index b11eda7..56571ec 100644 --- a/packages/runtime/index.js +++ b/packages/runtime/index.js @@ -1,8 +1,7 @@ -const { getDataTypeName,isNumber,isPlainObject,deepMerge } = require("./utils") +const {createFormatter,getDataTypeName,isNumber,isPlainObject,deepMerge,isFunction,isNothing,deepMixin,replaceAll} = require("./utils") const EventEmitter = require("./eventemitter") -const i18nScope = require("./scope.js") -let inlineFormatters = require("./formatters") - +const inlineFormatters = require("./formatters") +const i18nScope = require("./scope") // 用来提取字符里面的插值变量参数 , 支持管道符 { var | formatter | formatter } @@ -150,10 +149,11 @@ function forEachInterpolatedVars(str,callback,options={}){ const formatters = parseFormatters(match.groups.formatters) if(isFunction(callback)){ try{ + const finalValue = callback(varname,formatters,match[0]) if(opts.replaceAll){ // 在某此版本上可能没有 - result=result.replaceAll(match[0],callback(varname,formatters,match[0])) + result=result.replaceAll(match[0],finalValue) }else{ - result=result.replace(new RegExp(match[0],"gm"),callback(varname,formatters,match[0])) + result=replaceAll(result,match[0],finalValue) } }catch{// callback函数可能会抛出异常,如果抛出异常,则中断匹配过程 break @@ -286,6 +286,22 @@ function getFormatter(scope,activeLanguage,name){ } } +function executeChecker(checker,value){ + let result ={ value, next:"skip"} + if(!isFunction(checker)) return result + try{ + const r = checker(value) + if(isPlainObject(r)) { + Object.assign(result,r) + }else{ + result.value = r + } + if(!["break","skip"].includes(result.next)) result.next="break" + }catch(e){ + + } + return result +} /** * 执行格式化器并返回结果 * @@ -296,40 +312,68 @@ function getFormatter(scope,activeLanguage,name){ * @param {*} value * @param {Array[Function]} formatters 多个格式化器函数(经过包装过的)顺序执行,前一个输出作为下一个格式化器的输入 */ -function executeFormatter(value,formatters,scope){ +function executeFormatter(value,formatters,scope,template){ if(formatters.length===0) return value let result = value const emptyChecker = formatters.find(func=>func.$name==='empty') - const errorChecker = formatters.find(func=>func.$name==='error') + const errorChecker = formatters.find(func=>func.$name==='error') - // 当输入是一个空值时 + // 1. 空值检查 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 + const { value,next } = executeChecker(emptyChecker,result) + if(next == 'break') { + return value + }else{ + result = value + } + } + // 2. 错误检查 + if((result instanceof Error) && errorChecker){ + result.formatter = formatter.$name + const { value,next } = executeChecker(emptyChecker,result) + if(next == 'break') { + return value + }else{ + result = value + } } - + // 3. 分别执行格式化器函数 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) + e.formatter = formatter.$name + if(scope.debug) console.error(`Error while execute i18n formatter<${formatter.$name}> for ${template}: ${e.message} ` ) + const { value,next } = executeChecker(errorChecker,result) if(next=="break"){ if(value!==undefined) result = value break - }else if(next=="ignore"){ + }else if(next=="skip"){ continue - } - if(value!==undefined) result = value + } } } return result } + + + +/** + * 添加默认的empty和error格式化器,用来提供默认的空值和错误处理逻辑 + * @param {*} formatters + */ +function addDefaultFormatters(formatters){ + // 默认的空值处理逻辑: 转换为"",然后继续执行接下来的逻辑 + if(formatters.findIndex(([name])=>name=="empty")===-1){ + formatters.push(["empty",[]]) + } + // 默认的错误处理逻辑: 开启DEBUG时会显示ERROR:message;关闭DEBUG时会保持最近值不变然后中止后续执行 + if(formatters.findIndex(([name])=>name=="error")===-1){ + formatters.push(["error",[]]) + } +} + /** * * 将[[格式化器名称,[参数,参数,...]],[格式化器名称,[参数,参数,...]]]格式化器包装转化为 @@ -342,7 +386,8 @@ function executeFormatter(value,formatters,scope){ * */ function wrapperFormatters(scope,activeLanguage,formatters){ - let wrappedFormatters = [] + let wrappedFormatters = [] + addDefaultFormatters(formatters) for(let [name,args] of formatters){ if(name){ const func = getFormatter(scope,activeLanguage,name) @@ -386,7 +431,7 @@ function wrapperFormatters(scope,activeLanguage,formatters){ * @param {*} value * @returns */ -function getFormattedValue(scope,activeLanguage,formatters,value){ +function getFormattedValue(scope,activeLanguage,formatters,value,template){ // 1. 取得格式化器函数列表 const formatterFuncs = wrapperFormatters(scope,activeLanguage,formatters) // 3. 执行格式化器 @@ -394,10 +439,10 @@ function getFormattedValue(scope,activeLanguage,formatters,value){ // 当没有格式化器时,查询是否指定了默认数据类型的格式化器,如果有则执行 const defaultFormatter = getDataTypeDefaultFormatter(scope,activeLanguage,getDataTypeName(value)) if(defaultFormatter){ - return executeFormatter(value,[defaultFormatter],scope) + return executeFormatter(value,[defaultFormatter],scope,template) } } - value = executeFormatter(value,formatterFuncs,scope) + value = executeFormatter(value,formatterFuncs,scope,template) return value } @@ -432,9 +477,9 @@ function replaceInterpolatedVars(template,...args) { // 读取模板字符串中的插值变量列表 // [[var1,[formatter,formatter,...],match],[var2,[formatter,formatter,...],match],...} let varValues = args[0] - return forEachInterpolatedVars(template,(varname,formatters)=>{ + return forEachInterpolatedVars(template,(varname,formatters,match)=>{ let value = (varname in varValues) ? varValues[varname] : '' - return getFormattedValue(scope,activeLanguage,formatters,value) + return getFormattedValue(scope,activeLanguage,formatters,value,template) }) }else{ // ****************************位置插值**************************** @@ -442,9 +487,9 @@ function replaceInterpolatedVars(template,...args) { const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args if(params.length===0) return template // 没有变量则不需要进行插值处理,返回原字符串 let i = 0 - return forEachInterpolatedVars(template,(varname,formatters)=>{ + return forEachInterpolatedVars(template,(varname,formatters,match)=>{ if(params.length>i){ - return getFormattedValue(scope,activeLanguage,formatters,params[i++]) + return getFormattedValue(scope,activeLanguage,formatters,params[i++],template) }else{ throw new Error() // 抛出异常,停止插值处理 } @@ -603,19 +648,16 @@ function translate(message) { I18nManager.instance = this; this._settings = deepMerge(defaultLanguageSettings,settings) this._scopes=[] // 保存i18nScope实例 - this._defaultMessageLoader = null // 默认文本加载器 + this._defaultMessageLoader = null // 默认语言包加载器 } - get settings(){ return this._settings } - get scopes(){ return this._scopes } - // 当前激活语言 - get activeLanguage(){ return this._settings.activeLanguage} - // 默认语言 - get defaultLanguage(){ return this._settings.defaultLanguage} - // 支持的语言列表 - get languages(){ return this._settings.languages} - // 内置格式化器 - get formatters(){ return inlineFormatters } - get defaultMessageLoader(){ return this._defaultMessageLoader} + get settings(){ return this._settings } // 配置参数 + get scopes(){ return this._scopes } // 注册的报有i18nScope实例q + get activeLanguage(){ return this._settings.activeLanguage} // 当前激活语言 名称 + get defaultLanguage(){ return this._settings.defaultLanguage} // 默认语言名称 + get languages(){ return this._settings.languages} // 支持的语言列表 + get formatters(){ return this._settings.formatters } // 内置格式化器{*:{$options,$types,...},zh:{$options,$types,...},en:{$options,$types,...}} + get defaultMessageLoader(){ return this._defaultMessageLoader} // 默认语言包加载器 + // 通过默认加载器加载文件 async loadMessagesFromDefaultLoader(newLanguage,scope){ if(!isFunction(this._defaultMessageLoader)) return //throw new Error("No default message loader specified") @@ -627,10 +669,9 @@ function translate(message) { async change(value){ value=value.trim() if(this.languages.findIndex(lang=>lang.name === value)!==-1 || isFunction(this._defaultMessageLoader)){ - // 通知所有作用域刷新到对应的语言包 - await this._refreshScopes(value) + await this._refreshScopes(value) // 通知所有作用域刷新到对应的语言包 this._settings.activeLanguage = value - await this.emit(value) /// 触发语言切换事件 + await this.emit(value) // 触发语言切换事件 }else{ throw new Error("Not supported language:"+value) } @@ -640,7 +681,6 @@ function translate(message) { * @param {*} newLanguage */ async _refreshScopes(newLanguage){ - // 并发执行所有作用域语言包的加载 try{ const scopeRefreshers = this._scopes.map(scope=>{ return scope.refresh(newLanguage) @@ -728,8 +768,13 @@ module.exports ={ I18nManager, translate, i18nScope, + createFormatter, defaultLanguageSettings, getDataTypeName, isNumber, - isPlainObject + isNothing, + isPlainObject, + isFunction, + deepMerge, + deepMixin } \ No newline at end of file diff --git a/packages/runtime/scope.js b/packages/runtime/scope.js index 8268712..98d10ea 100644 --- a/packages/runtime/scope.js +++ b/packages/runtime/scope.js @@ -17,25 +17,24 @@ const DataTypes = [ module.exports = class i18nScope { constructor(options = {}, callback) { - this._id = options.id || Date.now().toString() + parseInt(Math.random() * 1000); - // 当出错时是否在控制台台输出错误信息 - this._debug = options.debug == undefined ? process && process.env && process.env.NODE_ENV === "development" : options.debug; - this._languages = options.languages; // 当前作用域的语言列表 - this._defaultLanguage = options.defaultLanguage || "zh"; // 默认语言名称 - this._activeLanguage = options.activeLanguage; // 当前语言名称 - this._default = options.default; // 默认语言包 - this._messages = options.messages; // 当前语言包 - this._idMap = options.idMap; // 消息id映射列表 - this._formatters = options.formatters; // 当前作用域的格式化函数列表{:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} - this._loaders = options.loaders; // 异步加载语言文件的函数列表 - this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用 - this._activeFormatters = options.formatters[options.activeLanguage]; // 激活使用的格式化器,查找格式化器时在此查找 - this._patchMessages = {}; // 语言包补丁信息 {:{....},:{....}} - // 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历 + this._id = options.id || Date.now().toString() + parseInt(Math.random() * 1000); + this._debug = options.debug == undefined ? process && process.env && process.env.NODE_ENV === "development" : options.debug; // 当出错时是否在控制台台输出错误信息 + this._languages = options.languages; // 当前作用域支持的语言列表 + this._defaultLanguage = options.defaultLanguage || "zh"; // 默认语言名称 + this._activeLanguage = options.activeLanguage; // 当前语言名称 + this._default = options.default; // 默认语言包 + this._messages = options.messages; // 当前语言包 + this._idMap = options.idMap; // 消息id映射列表 + this._formatters = options.formatters; // 当前作用域的格式化函数列表{: {$types,$options,[格式化器名称]: () => {},[格式化器名称]: () => {}}} + this._loaders = options.loaders; // 异步加载语言文件的函数列表 + this._global = null; // 引用全局VoerkaI18n配置,注册后自动引用 + this._patchMessages = {}; // 语言包补丁信息{: {....},:{....}} + this._refreshing = false; // 正在加载语言包标识 + // 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历 this.$cache = { - activeLanguage: null, + activeLanguage : null, typedFormatters: {}, - formatters: {}, + formatters : {}, }; // 如果不存在全局VoerkaI18n实例,说明当前Scope是唯一或第一个加载的作用域,则自动创建全局VoerkaI18n实例 if (!globalThis.VoerkaI18n) { @@ -47,41 +46,28 @@ module.exports = class i18nScope { languages: options.languages, }); } - this._global = globalThis.VoerkaI18n; - // 合并补丁语言包 - this._mergePatchedMessages(); - this._patch(this._messages, this.activeLanguage); - // 正在加载语言包标识 - this._refreshing = false; - // 在全局注册作用域 - this.register(callback); - } - // 作用域 - get id() {return this._id;} - // 调试开关 - get debug() {return this._debug;} - // 默认语言名称 - get defaultLanguage() {return this._defaultLanguage;} - // 默认语言名称 - get activeLanguage() {return this._activeLanguage;} - // 默认语言包 - get default() {return this._default;} - // 当前语言包 - get messages() {return this._messages; } - // 消息id映射列表 - get idMap() {return this._idMap;} - // 当前作用域的格式化器 {:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} - get formatters() { return this._formatters;} - // 当前作用域支持的语言列表[{name,title,fallback}] - get languages() {return this._languages;} - // 异步加载语言文件的函数列表 - get loaders() { return this._loaders;} - // 引用全局VoerkaI18n配置,注册后自动引用 - get global() { return this._global;} - // 当前格式化器配置参数 - get activeFormatterOptions(){return this._activeFormatterOptions} + this._global = globalThis.VoerkaI18n; + this._initFormatters(this.activeLanguage) // 初始化活动的格式化器 + this._mergePatchedMessages(); // 从本地缓存中读取并合并补丁语言包 + this._patch(this._messages, this.activeLanguage); // 延后执行补丁命令,该命令会向远程下载补丁包 + this.register(callback); // 在全局注册作用域 + } + get id() {return this._id;} // 作用域唯一id + get debug() {return this._debug;} // 调试开关 + get defaultLanguage() {return this._defaultLanguage;} // 默认语言名称 + get activeLanguage() {return this._activeLanguage;} // 默认语言名称 + get default() {return this._default;} // 默认语言包 + get messages() {return this._messages; } // 当前语言包 + get idMap() {return this._idMap;} // 消息id映射列表 + get languages() {return this._languages;} // 当前作用域支持的语言列表[{name,title,fallback}] + get loaders() { return this._loaders;} // 异步加载语言文件的函数列表 + get global() { return this._global;} // 引用全局VoerkaI18n配置,注册后自动引用 + get formatters() { return this._formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$options,[格式化器名称]: () = >{},[格式化器名称]: () => {}}} + get activeFormatters() {return this._activeFormatters} // 当前作用域激活的格式化器定义 {$types,$options,[格式化器名称]: () = >{},[格式化器名称]: () = >{}} + get activeFormatterOptions(){return this._activeFormatterOptions} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数 + /** - * 在全局注册作用域 + * 在全局注册作用域当前作用域 * @param {*} callback 注册成功后的回调 */ register(callback) { @@ -155,8 +141,30 @@ module.exports = class i18nScope { this._messages = this._default; this._activeLanguage = this.defaultLanguage; } + /** + * 初始化格式化器 + * 激活和默认语言的格式化器采用静态导入的形式,而没有采用异步块的形式,这是为了确保首次加载时的能马上读取,而减少延迟加载 + * _activeFormatters={$options:{...},$types:{...},[格式化器名称]:()=>{...},[格式化器名称]:()=>{...},...}} + */ + _initFormatters(newLanguage){ + this._activeFormatters = {} + try { + if (newLanguage in this._formatters) { + this._activeFormatters = this._formatters[newLanguage]; + } else { + if (this._debug) console.warn(`Not initialize <${newLanguage}> formatters.`); + } + this._generateFormatterOptions(newLanguage) + } catch (e) { + if (this._debug) console.error(`Error while initialize ${newLanguage} formatters: ${e.message}`); + } + } + /** - * 当切换语言时,格式化器应该切换到对应语言的格式化器 + * + * 切换到对应语言的格式化器 + * + * 当切换语言时,格式化器应该切换到对应语言的格式化器 * * 重要需要处理: * $options参数采用合并继承机制 @@ -186,12 +194,18 @@ module.exports = class i18nScope { * 生成格式化器的配置参数,该参数由以下合并而成: * - global.formatters[*].$options * - global.formatters[language].$options - * - scope.activeFormattes.$options 优先 + * - scope.activeFormatters.$options 当前优先 */ _generateFormatterOptions(language){ - let options = Object.assign({},getByPath(global.formatters,`*.$options`,{})) - deepMixin(options,getByPath(global.formatters,`${language}.$options`,{})) - deepMixin(options,getByPath(scope.activeFormattes,"$options",{})) + let options + try{ + options = Object.assign({},getByPath(this._global.formatters,`*.$options`,{})) + deepMixin(options,getByPath(this._global.formatters,`${language}.$options`,{})) + deepMixin(options,getByPath(this._activeFormatters,"$options",{})) + }catch(e){ + if(this.debug) console.error(`Error while generate <${language}> formatter options: `,e) + if(!options) options = this._activeFormatters.$options || {} + } return this._activeFormatterOptions = options } /** @@ -256,17 +270,13 @@ module.exports = class i18nScope { async _patch(messages, newLanguage) { if (!isFunction(this.global.loadMessagesFromDefaultLoader)) return; try { - let pachedMessages = - await this.global.loadMessagesFromDefaultLoader( - newLanguage, - this - ); + let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this); if (isPlainObject(pachedMessages)) { Object.assign(messages, pachedMessages); this._savePatchedMessages(pachedMessages, newLanguage); } } catch (e) { - if (this._debug) console.error(`Error while loading <${newLanguage}> messages from remote:${error.message}`); + if (this._debug) console.error(`Error while loading <${newLanguage}> patch messages from remote:`,e); } } /** @@ -299,17 +309,10 @@ module.exports = class i18nScope { _savePatchedMessages(messages, language) { try { if (globalThis.localStorage) { - globalThis.localStorage.setItem( - `voerkai18n_${this.id}_${language}_patched_messages`, - JSON.stringify(messages) - ); + globalThis.localStorage.setItem(`voerkai18n_${this.id}_${language}_patched_messages`,JSON.stringify(messages)); } } catch (e) { - if (this.$cache._debug) - console.error( - "Error while save voerkai18n patched messages:", - e.message - ); + if (this.$cache._debug) console.error("Error while save voerkai18n patched messages:",e); } } /** @@ -319,11 +322,7 @@ module.exports = class i18nScope { */ _getPatchedMessages(language) { try { - return JSON.parse( - localStorage.getItem( - `voerkai18n_${this.id}_${language}_patched_messages` - ) - ); + return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`)); } catch (e) { return {}; } diff --git a/packages/runtime/utils.js b/packages/runtime/utils.js index 06ae20d..14e33c6 100644 --- a/packages/runtime/utils.js +++ b/packages/runtime/utils.js @@ -254,22 +254,92 @@ function formatDatetime(value,templ="YYYY/MM/DD HH:mm:ss"){ return result } +/** + * 替换所有字符串 + * 低版本ES未提供replaceAll,此函数用来替代 + * + * + * @param {*} str + * @param {*} findValue + * @param {*} replaceValue + */ +function replaceAll(str,findValue,replaceValue){ + if(typeof(str)!=="string" || findValue=="" || findValue==replaceValue) return str + let result = str + try{ + while(result.search(findValue)!=-1){ + result = result.replace(findValue,replaceValue) + } + }catch{} + return result +} + /** * 创建格式化器 + * + * 格式化器是一个普通的函数,具有以下特点: + * + * - 函数第一个参数是上一上格式化器的输出 + * - 支持0-N个简单类型的入参 + * - 格式化器可以在格式化器的$options参数指定一个键值来配置不同语言时的参数 + * + * createFormatter((value,prefix,suffix, division ,precision,options)=>{ + * + * }, + * { + * unit:"$", + * prefix, + * suffix, + * division, + * precision + * }, + * { + * params:["prefix","suffix", "division" ,"precision"] // 声明参数顺序 + * optionKey:"currency" // 声明特定语言下的配置在$options.currency + * } + * ) + * + * @param {*} fn + * @param {*} defaultParams 默认参数 + * @param {*} meta + * @returns */ -function createFormatter(fn,meta={}){ + function createFormatter(fn,defaultParams={},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 + normalize : null, // 对输入值进行规范化处理,如进行时间格式化时,为了提高更好的兼容性,支持数字时间戳/字符串/Date等,需要对输入值进行处理,如强制类型转换等 + params : [], // 声明参数顺序 + optionKeyPath: null // 声明该格式化器在$options中的路径,支持简单的使用.的路径语法 + }) + + // 最后一个参数是传入activeFormatterOptions参数 + const wrappedFn = function(value,...args){ + let finalValue = value + // 1. 输入值规范处理,主要的类型转换等 + if(isFunction(opts.normalize)){ + try{ + finalValue = opts.normalize(finalValue) + }catch{} + } + // 2. 读取activeFormatterOptions + let activeFormatterOpts = args.length>0 ? args[args.length-1] : {} + if(!isPlainObject( activeFormatterOpts)) activeFormatterOpts ={} + // 3. 从当前语言的激活语言中读取配置参数 + const activeOptions =Object.assign({},defaultParams,getByPath(activeFormatterOpts,opts.optionKey,{})) + let finalArgs = opts.params.map(param=>getByPath(activeOptions,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) + } + fn.paramCount = opts.paramCount + return wrappedFn } +const Formatter = createFormatter + module.exports ={ isPlainObject, isFunction, @@ -277,9 +347,13 @@ module.exports ={ isNothing, deepMerge, deepMixin, + Formatter, + createFormatter, + replaceAll, getByPath, getDataTypeName, toDate, toNumber, - toCurrency + toCurrency, + createFormatter } \ No newline at end of file