update formatters

This commit is contained in:
wxzhang 2022-08-11 22:06:31 +08:00
parent ab9e150470
commit 3c31d3fb67
3 changed files with 388 additions and 308 deletions

View File

@ -250,49 +250,40 @@ function getDataTypeDefaultFormatter(scope,activeLanguage,dataType){
} }
} }
/** /**
* 获取指定名称的格式化器函数 * 获取指定名称的格式化器函数
* *
* 查找逻辑 * 查找逻辑
* - 在当前作用域中查找 * - 在当前作用域中查找
*
* - 在全局作用域中查找 * - 在全局作用域中查找
* *
* 全局作用域的格式化器优先
*
* @param {*} scope * @param {*} scope
* @param {*} activeLanguage 当前激活语言名称 * @param {*} activeLanguage 当前激活语言名称
* @param {*} name 格式化器名称 * @param {*} name 格式化器名称
* @returns {Function} 格式化函数 * @returns {Function} 格式化函数
*/ */
function getFormatter(scope,activeLanguage,name){ function getFormatter(scope,activeLanguage,name){
// 缓存格式化器引用,避免重复检索 // 1. 从缓存中直接读取: 缓存格式化器引用,避免重复检索
if(!scope.$cache) resetScopeCache(scope) if(!scope.$cache) resetScopeCache(scope)
if(scope.$cache.activeLanguage === activeLanguage) { if(scope.$cache.activeLanguage === activeLanguage) {
if(name in scope.$cache.formatters) return scope.$cache.formatters[name] if(name in scope.$cache.formatters) return scope.$cache.formatters[name]
}else{// 当语言切换时清空缓存 }else{// 当语言切换时清空缓存
resetScopeCache(scope,activeLanguage) resetScopeCache(scope,activeLanguage)
} }
// 先在当前作用域中查找,再在全局查找 const fallbackLanguage = scope.getLanguage(activeLanguage).fallback
const targets = [scope.global.formatters,scope.formatters] // 2. 先在当前作用域中查找,再在全局查找 formatters={$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}
for(const target of targets){ const range = [
// 1. 优先在当前语言查找 scope.activeFormatters,
if(activeLanguage in target){ scope.formatters[fallbackLanguage], // 如果指定了回退语言时,也在该回退语言中查找
let formatters = target[activeLanguage] || {} scope.global.formatters[activeLanguage], // 适用于activeLanguage全局格式化器
if((name in formatters) && isFunction(formatters[name])) { scope.global.formatters["*"], // 适用于所有语言的格式化器
]
for(const formatters of range){
if(!formatters) continue
if(isFunction(formatters[name])) {
return scope.$cache.formatters[name] = formatters[name] return scope.$cache.formatters[name] = formatters[name]
}else{ // 如果语言指定了fallback,则在其回退语言中查找
let fallbackLangName = scope.getFallbackLanguage(activeLanguage)
if((fallbackLangName in formatters) && isFunction(formatters[fallbackLangName])) {
return scope.$cache.formatters[name] = formatters[fallbackLangName]
} }
} }
}
// 2. 全局作用域中查找
let formatters = target["*"] || {}
if((name in formatters) && isFunction(formatters[name])) return scope.$cache.formatters[name] = formatters[name]
}
} }
/** /**
@ -317,8 +308,6 @@ function executeFormatter(value,formatters,scope){
return result return result
} }
/** /**
*
*
* *
* [[格式化器名称,[参数,参数,...]][格式化器名称,[参数,参数,...]]]格式化器转化为 * [[格式化器名称,[参数,参数,...]][格式化器名称,[参数,参数,...]]]格式化器转化为
* 格式化器的调用函数链 * 格式化器的调用函数链
@ -328,7 +317,6 @@ function executeFormatter(value,formatters,scope){
* @param {*} formatters * @param {*} formatters
* @returns {Array} [(v)=>{...},(v)=>{...},(v)=>{...}] * @returns {Array} [(v)=>{...},(v)=>{...},(v)=>{...}]
* *
*
*/ */
function buildFormatters(scope,activeLanguage,formatters){ function buildFormatters(scope,activeLanguage,formatters){
let results = [] let results = []
@ -356,7 +344,7 @@ function buildFormatters(scope,activeLanguage,formatters){
} }
/** /**
* 将value经过格式化器处理后返回 * 将value经过格式化器处理后返回的结果
* @param {*} scope * @param {*} scope
* @param {*} activeLanguage * @param {*} activeLanguage
* @param {*} formatters * @param {*} formatters
@ -578,9 +566,8 @@ function translate(message) {
} }
I18nManager.instance = this; I18nManager.instance = this;
this._settings = deepMerge(defaultLanguageSettings,settings) this._settings = deepMerge(defaultLanguageSettings,settings)
this._scopes=[] this._scopes=[] // 保存i18nScope实例
this._defaultMessageLoader = null // 默认文本加载器 this._defaultMessageLoader = null // 默认文本加载器
return I18nManager.instance;
} }
get settings(){ return this._settings } get settings(){ return this._settings }
get scopes(){ return this._scopes } get scopes(){ return this._scopes }
@ -692,7 +679,9 @@ function translate(message) {
}else{ }else{
await Promise.all(requests) await Promise.all(requests)
} }
}catch{} }catch(e){
if(this._debug) console.error(`Error while refresh voerkai18n scopes:${e.message}`)
}
} }
} }

View File

@ -1,76 +1,112 @@
const { isPlainObject,isFunction } = require("./utils") const { isPlainObject, isFunction, getByPath, deepMixin } = require("./utils");
const DataTypes = ["String","Number","Boolean","Object","Array","Function","Null","Undefined","Symbol","Date","RegExp","Error"]; const DataTypes = [
"String",
"Number",
"Boolean",
"Object",
"Array",
"Function",
"Null",
"Undefined",
"Symbol",
"Date",
"RegExp",
"Error",
];
module.exports = class i18nScope { module.exports = class i18nScope {
constructor(options={},callback){ constructor(options = {}, callback) {
this._id = options.id || (new Date().getTime().toString()+parseInt(Math.random()*1000)) this._id = options.id || Date.now().toString() + parseInt(Math.random() * 1000);
this._debug = options.debug // 当出错时是否在控制台台输出错误信息 // 当出错时是否在控制台台输出错误信息
this._languages = options.languages // 当前作用域的语言列表 this._debug = options.debug == undefined ? process && process.env && process.env.NODE_ENV === "development" : options.debug;
this._defaultLanguage = options.defaultLanguage || "zh" // 默认语言名称 this._languages = options.languages; // 当前作用域的语言列表
this._activeLanguage = options.activeLanguage // 当前语言名称 this._defaultLanguage = options.defaultLanguage || "zh"; // 默认语言名称
this._default = options.default // 默认语言包 this._activeLanguage = options.activeLanguage; // 当前语言名称
this._messages = options.messages // 当前语言包 this._default = options.default; // 默认语言包
this._idMap = options.idMap // 消息id映射列表 this._messages = options.messages; // 当前语言包
this._formatters = options.formatters // 当前作用域的格式化函数列表{<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} this._idMap = options.idMap; // 消息id映射列表
this._loaders = options.loaders // 异步加载语言文件的函数列表 this._formatters = options.formatters; // 当前作用域的格式化函数列表{<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}}
this._global = null // 引用全局VoerkaI18n配置注册后自动引用 this._loaders = options.loaders; // 异步加载语言文件的函数列表
this._activeFormatters= options.formatters[options.activeLanguage] // 激活使用的格式化器,查找格式化器时在此查找 this._global = null; // 引用全局VoerkaI18n配置注册后自动引用
this._patchMessages = {} // 语言包补丁信息 {<language>:{....},<language>:{....}} this._activeFormatters = options.formatters[options.activeLanguage]; // 激活使用的格式化器,查找格式化器时在此查找
this._patchMessages = {}; // 语言包补丁信息 {<language>:{....},<language>:{....}}
// 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历 // 用来缓存格式化器的引用,当使用格式化器时可以直接引用,减少检索遍历
this.$cache={ this.$cache = {
activeLanguage : null, activeLanguage: null,
typedFormatters: {}, typedFormatters: {},
formatters : {}, formatters: {},
} };
// 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域则自动创建全局VoerkaI18n实例 // 如果不存在全局VoerkaI18n实例说明当前Scope是唯一或第一个加载的作用域则自动创建全局VoerkaI18n实例
if(!globalThis.VoerkaI18n){ if (!globalThis.VoerkaI18n) {
const { I18nManager } = require("./") const { I18nManager } = require("./");
globalThis.VoerkaI18n = new I18nManager({ globalThis.VoerkaI18n = new I18nManager({
debug : this._debug, debug: this._debug,
defaultLanguage: this.defaultLanguage, defaultLanguage: this.defaultLanguage,
activeLanguage : this.activeLanguage, activeLanguage: this.activeLanguage,
languages : options.languages, languages: options.languages,
}) });
} }
this._global = globalThis.VoerkaI18n this._global = globalThis.VoerkaI18n;
// 合并补丁语言包 // 合并补丁语言包
this._mergePatchedMessages() this._mergePatchedMessages();
this._patch(this._messages,this.activeLanguage) this._patch(this._messages, this.activeLanguage);
// 正在加载语言包标识 // 正在加载语言包标识
this._refreshing=false this._refreshing = false;
// 在全局注册作用域 // 在全局注册作用域
this.register(callback) 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映射列表 // 消息id映射列表
get idMap(){return this._idMap} get idMap() {
return this._idMap;
}
// 当前作用域的格式化器 {<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}} // 当前作用域的格式化器 {<lang>:{$types,$options,[格式化器名称]:()=>{},[格式化器名称]:()=>{}}}
get formatters(){return this._formatters} get formatters() {
return this._formatters;
}
// 当前作用域支持的语言列表[{name,title,fallback}] // 当前作用域支持的语言列表[{name,title,fallback}]
get languages(){return this._languages} get languages() {
return this._languages;
}
// 异步加载语言文件的函数列表 // 异步加载语言文件的函数列表
get loaders(){return this._loaders} get loaders() {
return this._loaders;
}
// 引用全局VoerkaI18n配置注册后自动引用 // 引用全局VoerkaI18n配置注册后自动引用
get global(){return this._global} get global() {
return this._global;
}
/** /**
* 在全局注册作用域 * 在全局注册作用域
* @param {*} callback 注册成功后的回调 * @param {*} callback 注册成功后的回调
*/ */
register(callback){ register(callback) {
if(!isFunction(callback)) callback = ()=>{} if (!isFunction(callback)) callback = () => {};
this.global.register(this).then(callback).catch(callback) this.global.register(this).then(callback).catch(callback);
} }
/** /**
* 注册格式化器 * 注册格式化器
@ -87,139 +123,145 @@ module.exports = class i18nScope {
language : 声明该格式化器适用语言 language : 声明该格式化器适用语言
asGlobal : 注册到全局 asGlobal : 注册到全局
*/ */
registerFormatter(name,formatter,{language="*",asGlobal}={}){ registerFormatter(name, formatter, { language = "*", asGlobal } = {}) {
if(!isFunction(formatter) || typeof(name)!=="string"){ if (!isFunction(formatter) || typeof name !== "string") {
throw new TypeError("Formatter must be a function") throw new TypeError("Formatter must be a function");
} }
language = Array.isArray(language) ? language : (language ? language.split(",") : []) language = Array.isArray(language)
if(asGlobal){ ? language
this.global.registerFormatter(name,formatter,{language}) : language
}else{ ? language.split(",")
language.forEach(lng=>{ : [];
if(DataTypes.includes(name)){ if (asGlobal) {
this._formatters[lng].$types[name] = formatter this.global.registerFormatter(name, formatter, { language });
}else{ } else {
this._formatters[lng][name] = formatter language.forEach((lng) => {
if (DataTypes.includes(name)) {
this._formatters[lng].$types[name] = formatter;
} else {
this._formatters[lng][name] = formatter;
} }
}) });
} }
} }
/** /**
* 注册默认文本信息加载器 * 注册默认文本信息加载器
* @param {Function} 必须是异步函数或者是返回Promise * @param {Function} 必须是异步函数或者是返回Promise
*/ */
registerDefaultLoader(fn){ registerDefaultLoader(fn) {
this.global.registerDefaultLoader(fn) this.global.registerDefaultLoader(fn);
} }
/** /**
* 获取指定语言信息 * 获取指定语言信息
* @param {*} language * @param {*} language
* @returns * @returns
*/ */
_getLanguage(language){ getLanguage(language) {
let index = this._languages.findIndex(lng=>lng.name==language) let index = this._languages.findIndex((lng) => lng.name == language);
if(index!==-1) return this._languages[index] if (index !== -1) return this._languages[index];
} }
/** /**
* 返回是否存在指定的语言 * 返回是否存在指定的语言
* @param {*} language 语言名称 * @param {*} language 语言名称
* @returns * @returns
*/ */
hasLanguage(language){ hasLanguage(language) {
return this._languages.indexOf(lang=>lang.name==language)!==-1 return this._languages.indexOf((lang) => lang.name == language) !== -1;
}
/**
* 获取指定语言的回退语言名称
*
* 如果没有指定则总是回退到到默认语言
*
* 但是不支持回退链
*
* 可以在配置中指定回退语言
* // settings.json
* {
* languages:[
* {name:"zh"},
* {name:"cht",fallback:"zh"}, //繁体中文可以回退到简体中文,
* {name:"en"},
* ]
* }
*
* @param {*} language
* @returns {Object} 语言信息数据 {name,title,fallback,...}
*/
getFallbackLanguage(language){
let lang = this._getLanguage(language)
if(lang){
return this.hasLanguage(lang.fallback) ? lang.fallback : this.defaultLanguage
}
} }
/** /**
* 回退到默认语言 * 回退到默认语言
*/ */
_fallback(){ _fallback() {
this._messages = this._default this._messages = this._default;
this._activeLanguage = this.defaultLanguage this._activeLanguage = this.defaultLanguage;
} }
/** /**
* 当切换语言时格式化器应该切换到对应语言的格式化器 * 当切换语言时格式化器应该切换到对应语言的格式化器
*
* 重要需要处理
* $options参数采用合并继承机制
*
*
* @param {*} language * @param {*} language
*/ */
async _loadFormatters(newLanguage){ async _changeFormatters(newLanguage) {
try{ try {
if(newLanguage in this._formatters){ if (newLanguage in this._formatters) {
let loader = this._formatters[newLanguage] let loader = this._formatters[newLanguage];
if(isPlainObject(loader)){ if (isPlainObject(loader)) {
this._activeFormatters = loader this._activeFormatters = loader;
}else if(isFunction(loader)){ } else if (isFunction(loader)) {
this._activeFormatters = (await loader()).default this._activeFormatters = (await loader()).default;
} }
}else{ // 合并生成格式化器的配置参数,当执行格式化器时该参数将被传递给格式化器
if(this._debug) console.warn(`Not configured <${newLanguage}> formatters.`) this._generateFormatterOptions(newLanguage)
} else {
if (this._debug) console.warn(`Not configured <${newLanguage}> formatters.`);
} }
}catch(e){ } catch (e) {
if(this._debug) console.error(`Error loading ${newLanguage} formatters: ${e.message}`) if (this._debug) console.error(`Error loading ${newLanguage} formatters: ${e.message}`);
} }
} }
/**
* 生成格式化器的配置参数该参数由以下合并而成
* - global.formatters[*].$options
* - global.formatters[language].$options
* - scope.activeFormattes.$options 优先
*/
_generateFormatterOptions(language){
let options = Object.assign({},getByPath(global.formatters,`*.$options`,{}))
deepMixin(options,getByPath(global.formatters,`${language}.$options`,{}))
deepMixin(options,getByPath(scope.activeFormattes,"$options",{}))
return this._activeFormatterOptions = options
}
/** /**
* 刷新当前语言包 * 刷新当前语言包
* @param {*} newLanguage * @param {*} newLanguage
*/ */
async refresh(newLanguage){ async refresh(newLanguage) {
this._refreshing = true this._refreshing = true;
if(!newLanguage) newLanguage = this.activeLanguage if (!newLanguage) newLanguage = this.activeLanguage;
// 默认语言:由于默认语言采用静态加载方式而不是异步块,因此只需要简单的替换即可 // 默认语言:由于默认语言采用静态加载方式而不是异步块,因此只需要简单的替换即可
if(newLanguage === this.defaultLanguage){ if (newLanguage === this.defaultLanguage) {
this._messages = this._default this._messages = this._default;
await this._patch(this._messages,newLanguage) // 异步补丁 await this._patch(this._messages, newLanguage); // 异步补丁
this._loadFormatters(newLanguage) this._changeFormatters(newLanguage);
return return;
} }
// 非默认语言需要异步加载语言包文件,加载器是一个异步函数 // 非默认语言需要异步加载语言包文件,加载器是一个异步函数
// 如果没有加载器,则无法加载语言包,因此回退到默认语言 // 如果没有加载器,则无法加载语言包,因此回退到默认语言
let loader = this.loaders[newLanguage] let loader = this.loaders[newLanguage];
try{ try {
if(isPlainObject(loader)){ if (isPlainObject(loader)) {
this._messages = loader this._messages = loader;
await this._patch(this._messages,newLanguage) await this._patch(this._messages, newLanguage);
}else if(isFunction(loader)){ } else if (isFunction(loader)) {
this._messages = (await loader()).default this._messages = (await loader()).default;
this._activeLanguage = newLanguage this._activeLanguage = newLanguage;
await this._patch(this._messages,newLanguage) await this._patch(this._messages, newLanguage);
}else if(isFunction(this.global.defaultMessageLoader)){// 如果该语言没有指定加载器,则使用全局配置的默认加载器 } else if (isFunction(this.global.defaultMessageLoader)) {
const loadedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this) // 如果该语言没有指定加载器,则使用全局配置的默认加载器
this._messages = Object.assign({},this._default,loadedMessages) const loadedMessages =
this._activeLanguage = newLanguage await this.global.loadMessagesFromDefaultLoader(
}else{ newLanguage,
this._fallback() this
);
this._messages = Object.assign(
{},
this._default,
loadedMessages
);
this._activeLanguage = newLanguage;
} else {
this._fallback();
} }
// 应该切换到对应语言的格式化器 // 应该切换到对应语言的格式化器
this._loadFormatters(newLanguage) this._changeFormatters(newLanguage);
}catch(e){ } catch (e) {
if(this._debug) console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`) if (this._debug) console.warn(`Error while loading language <${newLanguage}> on i18nScope(${this.id}): ${e.message}`);
this._fallback() this._fallback();
}finally{ } finally {
this._refreshing = false this._refreshing = false;
} }
} }
/** /**
@ -231,25 +273,29 @@ module.exports = class i18nScope {
* @param {*} newLanguage * @param {*} newLanguage
* @returns * @returns
*/ */
async _patch(messages,newLanguage){ async _patch(messages, newLanguage) {
if(!isFunction(this.global.loadMessagesFromDefaultLoader)) return if (!isFunction(this.global.loadMessagesFromDefaultLoader)) return;
try{ try {
let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this) let pachedMessages =
if(isPlainObject(pachedMessages)){ await this.global.loadMessagesFromDefaultLoader(
Object.assign(messages,pachedMessages) newLanguage,
this._savePatchedMessages(pachedMessages,newLanguage) this
);
if (isPlainObject(pachedMessages)) {
Object.assign(messages, pachedMessages);
this._savePatchedMessages(pachedMessages, newLanguage);
} }
}catch(e){ } 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}> messages from remote:${error.message}`);
} }
} }
/** /**
* 从本地存储中读取语言包补丁合并到当前语言包中 * 从本地存储中读取语言包补丁合并到当前语言包中
*/ */
_mergePatchedMessages(){ _mergePatchedMessages() {
let patchedMessages= this._getPatchedMessages(this.activeLanguage) let patchedMessages = this._getPatchedMessages(this.activeLanguage);
if(isPlainObject(patchedMessages)){ if (isPlainObject(patchedMessages)) {
Object.assign(this._messages,patchedMessages) Object.assign(this._messages, patchedMessages);
} }
} }
/** /**
@ -270,13 +316,20 @@ module.exports = class i18nScope {
* *
* @param {*} messages * @param {*} messages
*/ */
_savePatchedMessages(messages,language){ _savePatchedMessages(messages, language) {
try{ try {
if(globalThis.localStorage){ 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){ } 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.message
);
} }
} }
/** /**
@ -284,16 +337,28 @@ module.exports = class i18nScope {
* @param {*} language * @param {*} language
* @returns * @returns
*/ */
_getPatchedMessages(language){ _getPatchedMessages(language) {
try{ try {
return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`)) return JSON.parse(
}catch(e){ localStorage.getItem(
return {} `voerkai18n_${this.id}_${language}_patched_messages`
)
);
} catch (e) {
return {};
} }
} }
// 以下方法引用全局VoerkaI18n实例的方法 // 以下方法引用全局VoerkaI18n实例的方法
get on(){return this._global.on.bind(this._global)} get on() {
get off(){return this._global.off.bind(this._global)} return this._global.on.bind(this._global);
get offAll(){return this._global.offAll.bind(this._global)} }
get change(){return this._global.change.bind(this._global)} get off() {
} return this._global.off.bind(this._global);
}
get offAll() {
return this._global.offAll.bind(this._global);
}
get change() {
return this._global.change.bind(this._global);
}
};

View File

@ -172,6 +172,31 @@ function toNumber(value,defualt=0) {
return prefix + result.join("") + suffix return prefix + result.join("") + suffix
} }
/**
* 根据路径获取指定值
*
* getByPath({a:{b:1}},"a.b") == 1
* getByPath({a:{b:1}},"a.c",2) == 2
*
* @param {*} obj
* @param {*} path 使用.分割的路径
* @param {*} defaultValue 默认值
* @returns
*/
function getByPath(obj,path,defaultValue){
if(typeof(obj)!="object") return defaultValue
let paths = path.split(".")
let cur = obj
for(let key of paths){
if(typeof(cur)=="object" && key in cur ){
cur = cur[key]
}else{
return defaultValue
}
}
return cur
}
/** /**
* 返回value相对rel的相对时间 * 返回value相对rel的相对时间
* *
@ -191,6 +216,7 @@ module.exports ={
isNothing, isNothing,
deepMerge, deepMerge,
deepMixin, deepMixin,
getByPath,
getDataTypeName, getDataTypeName,
toDate, toDate,
toNumber, toNumber,