From 64f9eaa93a0907dd99f86d51460755b972c07d40 Mon Sep 17 00:00:00 2001 From: wxzhang Date: Sun, 2 Apr 2023 22:01:20 +0800 Subject: [PATCH] update `typescript` runtime --- docs/src/guide/advanced/customformatter.md | 2 + packages/runtime/src/formatterRegistry.ts | 154 +++++++++++++++ packages/runtime/src/formatters/zh.ts | 9 +- packages/runtime/src/interpolate.ts | 10 +- packages/runtime/src/manager.ts | 34 ++-- packages/runtime/src/scope.ts | 213 +++++++++++---------- packages/runtime/src/types.ts | 32 ++-- 7 files changed, 316 insertions(+), 138 deletions(-) create mode 100644 packages/runtime/src/formatterRegistry.ts diff --git a/docs/src/guide/advanced/customformatter.md b/docs/src/guide/advanced/customformatter.md index c1ac2c7..058ebea 100644 --- a/docs/src/guide/advanced/customformatter.md +++ b/docs/src/guide/advanced/customformatter.md @@ -530,6 +530,7 @@ export default { - 指定`global=true`将该文件声明的所有格式化器均注册到全局中 ```javascript | pure export default { + global:true, $config:{...}, $types:{... }, [格式化名称]:(value,...args,$config)=>{.....}, @@ -544,6 +545,7 @@ export default { $config:{...}, $types:{... }, [格式化名称]:(value,...args,$config)=>{.....}, + // 以下全部注册到全局`VoerkaI18n`实例中 global:{ $config:{...}, $types:{... }, diff --git a/packages/runtime/src/formatterRegistry.ts b/packages/runtime/src/formatterRegistry.ts new file mode 100644 index 0000000..629f657 --- /dev/null +++ b/packages/runtime/src/formatterRegistry.ts @@ -0,0 +1,154 @@ +/** + * + * 保存所有格式化器数据 * + * + * + */ +import { VoerkaI18nFormatter, VoerkaI18nFormatters, VoerkaI18nFormattersLoader, VoerkaI18nLanguageFormatters, SupportedDateTypes, VoerkaI18nFormatterConfigs, Formatter } from './types'; +import { DataTypes } from './utils'; +import { get as getByPath } from "flex-tools/object/get" + + +export interface VoerkaI18nScopeCache{ + activeLanguage :string | null, + typedFormatters: VoerkaI18nLanguageFormatters, + formatters : VoerkaI18nLanguageFormatters, +} + +export class FormattersNotLoadedError extends Error{ + constructor(language:string){ + super(`Formatters of language<${language}> is not loaded,try to call load()`) + } +} +const EmptyFormatters:any = { + $config:{}, + $types:{} +} +export class VoerkaI18nFormatterRegistry{ + // 由于语言的格式化器集合允许是一个异步加载块,所以需要一个ready标志 + // 当语言格式化器集合加载完成后,ready标志才会变为true + #ready:boolean = false + #formatters:VoerkaI18nLanguageFormatters = {} + #language:string = 'zh' + constructor(formatters?:VoerkaI18nLanguageFormatters){ + this.#formatters = formatters || {} + } + /** + * 当前语言 + */ + get language(){ return this.#language } + set language(language:string){ + this.#language = language + if(!(language in this.formatters)){ + (this.formatters[language] as any) = Object.assign({},EmptyFormatters) + } + this.#ready = typeof(this.#formatters[language]) != 'function' + } + + /** + * 注册格式化器 + + 格式化器是一个简单的同步函数value=>{...},用来对输入进行格式化后返回结果 + + register(name,value=>{...}) // 注册到所有语言 + register(name,value=>{...},{langauge:"zh"}) // 注册到zh语言 + register(name,value=>{...},{langauge:"en"}) // 注册到en语言 + register("Date",value=>{...},{langauge:"en"}) // 注册到en语言的默认数据类型格式化器 + register(name,value=>{...},{langauge:["zh","cht"]}) // 注册到zh和cht语言 + register(name,value=>{...},{langauge:"zh,cht"}) + @param {*} formatter 格式化器 + @param language : 字符串或数组,声明该格式化器适用语言 + + *代表适用于所有语言 + 语言名称,语言名称数组,或者使用,分割的语言名称字符串 + */ + register(name:string, formatter:VoerkaI18nFormatter, {language = "*"}:{ language: string | string[] } ) { + if (!isFunction(formatter) || typeof name !== "string") { + throw new TypeError("Formatter must be a function"); + } + const languages = Array.isArray(language) ? language: language ? language.split(","): []; + languages.forEach((lngName:string) => { + if(!(lngName in this.#formatters)) { + this.#formatters[lngName] = { } + } + if(typeof(this.#formatters[lngName])!="function"){ + let lngFormatters = this.#formatters[lngName] as VoerkaI18nFormatters + if (DataTypes.includes(name)) { + if(!lngFormatters.$types){ + lngFormatters.$types = {} + } + lngFormatters.$types![name] = formatter + } else { + lngFormatters[name] = formatter; + } + } + }); + } + registerLanguageFormatters(language:string,formatters:VoerkaI18nFormatters | VoerkaI18nFormattersLoader){ + this.#formatters[language] = formatters + } + /** + * 加载所有格式化器 + * @param formatters + */ + loadInitials(formatters:VoerkaI18nLanguageFormatters){ + this.#formatters=formatters + } + /** + * 获取指定语言的格式化器配置 + * @param language + */ + getConfig(language?:string){ + return language ? getByPath(this.#formatters,`${language}.$config`,{defaultValue:{}}) : {} + } + /** + * 返回指定语言是否具有格式化器集合 + * @param language + */ + hasLanguage(language:string){ + return language in this.#formatters + } + + //****************** 以下方法和属性只作用于当前语言 *********************** */ + + /** + * 当格式化器是一个()=>import(...)或一个返回格式化器集合的Promise时,需要调用load()方法加载格式化器 + * + * 这是由于不同语言的格式化器允许声明为异步加载块,因此需要在切换语言使用时才加载 + * + */ + async load(){ + let loader = this.#formatters[this.#language] as VoerkaI18nFormattersLoader + if(typeof(loader) === 'function'){ + this.#formatters[this.#language] = await loader() + this.#ready = true + }// 如果是一个对象,则直接返回,不需要调用 + } + /** + * 当前语言的格式化器集合 + */ + get formatters(){ + if(!this.#ready) throw new FormattersNotLoadedError(this.#language) + return this.#formatters[this.#language] as VoerkaI18nFormatters + } + /** + * 返回当前语言的格式化器 + * @param name 格式化名称 + */ + get(name:string,dataType?:SupportedDateTypes):VoerkaI18nFormatter | undefined{ + if(!this.#ready) throw new FormattersNotLoadedError(this.#language) + const lngFormatters = this.#formatters[this.#language] as VoerkaI18nFormatters + if(dataType && (dataType in lngFormatters.$types!)){ + return lngFormatters.$types![dataType] + }else if(name in lngFormatters){ + return lngFormatters[name] + } + } + /** + * 当前语言的格式化器配置 + */ + get config(){ + if(!this.#ready) throw new FormattersNotLoadedError(this.#language) + return (this.#formatters[this.#language] as VoerkaI18nFormatters).$config as VoerkaI18nFormatterConfigs + } +} \ No newline at end of file diff --git a/packages/runtime/src/formatters/zh.ts b/packages/runtime/src/formatters/zh.ts index 41cd108..20bebf8 100644 --- a/packages/runtime/src/formatters/zh.ts +++ b/packages/runtime/src/formatters/zh.ts @@ -3,10 +3,9 @@ * */ -const { - CN_DATETIME_UNITS,CN_WEEK_DAYS,CN_SHORT_WEEK_DAYS, CN_MONTH_NAMES, CN_SHORT_MONTH_NAMES, - chineseNumberFormatter,rmbFormater -} = require("../datatypes/chinese") +import { chineseNumberFormatter,rmbFormater } from "../datatypes/chinese" + + module.exports = { // 配置参数: 格式化器函数的最后一个参数就是该配置参数 @@ -71,7 +70,7 @@ module.exports = { } }, $types: { - Boolean : value =>value ? "是":"否" + Boolean : (value:any) =>value ? "是":"否" }, // 中文货币,big=true代表大写形式 rmb : rmbFormater, diff --git a/packages/runtime/src/interpolate.ts b/packages/runtime/src/interpolate.ts index 3ef0ecc..fbd8f44 100644 --- a/packages/runtime/src/interpolate.ts +++ b/packages/runtime/src/interpolate.ts @@ -306,7 +306,7 @@ function executeFormatter(value, formatters, scope, template) { for (let formatter of formatters) { try { result = formatter(result, scope.activeFormatterConfig); - } catch (e) { + } catch (e:any) { e.formatter = formatter.$name; if (scope.debug) console.error(`Error while execute i18n formatter<${formatter.$name}> for ${template}: ${e.message} `); @@ -462,10 +462,4 @@ export function replaceInterpolatedVars(this:VoerkaI18nScope,template:string, .. { replaceAll: false } ); } -} - -module.exports = { - forEachInterpolatedVars, // 遍历插值变量并替换 - getInterpolatedVars, // 获取指定字符串中的插件值变量列表 - replaceInterpolatedVars // 替换插值变量 -}; +} \ No newline at end of file diff --git a/packages/runtime/src/manager.ts b/packages/runtime/src/manager.ts index f5ef9bf..e454546 100644 --- a/packages/runtime/src/manager.ts +++ b/packages/runtime/src/manager.ts @@ -4,8 +4,9 @@ import {DataTypes} from "./utils" import { EventEmitter } from "./eventemitter" import inlineFormatters from "./formatters" import { VoerkaI18nScope } from "./scope" -import type { VoerkaI18nLanguage, VoerkaI18nFormatters, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, VoerkaI18nTypesFormatters } from "./types" +import type { VoerkaI18nLanguageDefine, VoerkaI18nLanguageFormatters, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, VoerkaI18nTypesFormatters } from "./types" import { SupportedDateTypes } from './types'; +import { VoerkaI18nFormatterRegistry } from "./formatterRegistry" // 默认语言配置 const defaultLanguageSettings = { @@ -23,8 +24,8 @@ export interface VoerkaI18nManagerOptions { debug?: boolean defaultLanguage: string activeLanguage: string - formatters: VoerkaI18nFormatters - languages: VoerkaI18nLanguage[] + formatters: VoerkaI18nLanguageFormatters + languages: VoerkaI18nLanguageDefine[] } /** * 多语言管理类 @@ -47,6 +48,7 @@ export class VoerkaI18nManager extends EventEmitter{ #options?:Required #scopes:VoerkaI18nScope[] = [] #defaultMessageLoader?:VoerkaI18nDefaultMessageLoader + #formatterRegistry!:VoerkaI18nFormatterRegistry constructor(options?:VoerkaI18nManagerOptions){ super() if(VoerkaI18nManager.instance){ @@ -54,6 +56,8 @@ export class VoerkaI18nManager extends EventEmitter{ } VoerkaI18nManager.instance = this; this.#options = deepMerge(defaultLanguageSettings,options) as Required + this.#formatterRegistry = new VoerkaI18nFormatterRegistry() + this.loadInitialFormatters() // 加载初始格式化器 this.#scopes=[] // 保存VoerkaI18nScope实例 } get debug(){return this.#options!.debug} @@ -62,8 +66,18 @@ export class VoerkaI18nManager extends EventEmitter{ get activeLanguage(){ return this.#options!.activeLanguage} // 当前激活语言 名称 get defaultLanguage(){ return this.#options!.defaultLanguage} // 默认语言名称 get languages(){ return this.#options!.languages} // 支持的语言列表 - get formatters(){ return this.#options!.formatters } // 内置格式化器{*:{$config,$types,...},zh:{$config,$types,...},en:{$config,$types,...}} get defaultMessageLoader(){ return this.#defaultMessageLoader} // 默认语言包加载器 + get formatters(){return this.#formatterRegistry!} + + /** + * 初始加载格式化器 + */ + private loadInitialFormatters(){ + if(this.#options?.formatters){ + this.#formatterRegistry.loadInitials(this.#options!.formatters) + delete (this.#options as any).formatters + } + } // 通过默认加载器加载文件 async loadMessagesFromDefaultLoader(newLanguage:string,scope:VoerkaI18nScope){ @@ -134,17 +148,7 @@ export class VoerkaI18nManager extends EventEmitter{ language : 声明该格式化器适用语言 */ registerFormatter(name:string,formatter:VoerkaI18nFormatter,{language="*"}:{language:string | string[] | '*'}){ - if(!isFunction(formatter) || typeof(name)!=="string"){ - throw new TypeError("Formatter must be a function") - } - const languages = Array.isArray(language) ? language : (language ? language.split(",") : []) - languages.forEach((lng:string)=>{ - if(DataTypes.includes(name)){ - (this.formatters[lng].$types as VoerkaI18nTypesFormatters)[name] = formatter - }else{ - this.formatters[lng][name] = formatter - } - }) + this.#formatterRegistry.register(name,formatter,{language}) } /** * 注册默认文本信息加载器 diff --git a/packages/runtime/src/scope.ts b/packages/runtime/src/scope.ts index af1cd15..7b61fd6 100644 --- a/packages/runtime/src/scope.ts +++ b/packages/runtime/src/scope.ts @@ -11,28 +11,31 @@ import type { VoerkaI18nFormatterConfigs, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, - VoerkaI18nFormatters, + VoerkaI18nLanguageFormatters, Voerkai18nIdMap, - VoerkaI18nLanguage, + VoerkaI18nLanguageDefine, VoerkaI18nLanguageMessages, VoerkaI18nLanguagePack, VoerkaI18nScopeCache, VoerkaI18nTranslate, VoerkaI18nLoaders, VoerkaI18nTypesFormatters, -VoerkaI18nLanguageFormatters -} from "./types" +VoerkaI18nFormatters, +VoerkaI18nDynamicLanguageMessages, +VoerkaI18nTypesFormatterConfigs +} from "./types" +import { VoerkaI18nFormatterRegistry } from './formatterRegistry'; export interface VoerkaI18nScopeOptions { id?: string debug?: boolean - languages: VoerkaI18nLanguage[] // 当前作用域支持的语言列表 + languages: VoerkaI18nLanguageDefine[] // 当前作用域支持的语言列表 defaultLanguage: string // 默认语言名称 activeLanguage: string // 当前语言名称 default: VoerkaI18nLanguageMessages // 默认语言包 messages: VoerkaI18nLanguageMessages // 当前语言包 idMap: Voerkai18nIdMap // 消息id映射列表 - formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}} + formatters: VoerkaI18nLanguageFormatters // 当前作用域的格式化函数列表{: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}} loaders: VoerkaI18nLoaders; // 异步加载语言文件的函数列表 } @@ -42,10 +45,11 @@ export class VoerkaI18nScope { #refreshing:boolean = false #patchMessages:VoerkaI18nLanguagePack = {} #t:VoerkaI18nTranslate - #activeFormatters:VoerkaI18nLanguageFormatters = {} + #activeFormatters:VoerkaI18nFormatters = {} #activeFormatterConfig: VoerkaI18nFormatterConfigs={} #cache:VoerkaI18nScopeCache #messages:VoerkaI18nLanguageMessages = {} + #formatterRegistry:VoerkaI18nFormatterRegistry constructor(options:VoerkaI18nScopeOptions, callback:(e?:Error)=>void) { this.#options = assignObject({ id : Date.now().toString() + parseInt(String(Math.random() * 1000)), @@ -80,8 +84,9 @@ export class VoerkaI18nScope { }); } this.#t = translate.bind(this) - this.#global = globalThis.VoerkaI18n as unknown as VoerkaI18nManager; - this._initFormatters(this.activeLanguage) // 初始化活动的格式化器 + this.#global = globalThis.VoerkaI18n as unknown as VoerkaI18nManager; + this.#formatterRegistry = new VoerkaI18nFormatterRegistry() + this.loadFormatters(this.activeLanguage) // 初始化活动的格式化器 this._mergePatchedMessages(); // 从本地缓存中读取并合并补丁语言包 this._patch(this.messages, this.activeLanguage); // 延后执行补丁命令,该命令会向远程下载补丁包 this.register(callback); // 在全局注册作用域 @@ -96,8 +101,8 @@ export class VoerkaI18nScope { get languages() {return this.#options.languages;} // 当前作用域支持的语言列表[{name,title,fallback}] get loaders() { return this.#options.loaders;} // 异步加载语言文件的函数列表 get global() { return this.#global;} // 引用全局VoerkaI18n配置,注册后自动引用 - get formatters() { return this.#options.formatters;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}} - get activeFormatters() {return this.#activeFormatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}} + get formatters() { return this.#formatterRegistry;} // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}} + get activeFormatters() {return this.#formatterRegistry.formatters} // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}} get activeFormatterConfig(){return this.#activeFormatterConfig} // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数 get cache(){return this.#cache } set cache(value:VoerkaI18nScopeCache){ this.#cache=value } @@ -110,7 +115,7 @@ export class VoerkaI18nScope { */ _initiLanguages(){ if(!Array.isArray(this.languages)){ - console.warn("[VoerkaI18n] 无效的语言配置") + console.warn("[VoerkaI18n] invalid languages config,use default languages config instead.") this.#options.languages = [ {name: "zh",title: "中文"}, {name: "en",title: "英文"} @@ -148,59 +153,68 @@ export class VoerkaI18nScope { asGlobal : 注册到全局 */ registerFormatter(name:string, formatter:VoerkaI18nFormatter, {language = "*", asGlobal= true}:{ language: string | string[] | "*", asGlobal :boolean } ) { - if (!isFunction(formatter) || typeof name !== "string") { - throw new TypeError("Formatter must be a function"); - } - const languages = Array.isArray(language) ? language: language ? language.split(","): []; - if (asGlobal) { - this.global.registerFormatter(name, formatter, { language }); - } else { - languages.forEach((lng) => { - if(!(lng in this.formatters)) this.formatters[lng] = {} - if (DataTypes.includes(name)) { - (this.formatters[lng].$types as VoerkaI18nTypesFormatters)[name] = formatter - } else { - this.formatters[lng][name] = formatter; - } - }); - } + if(asGlobal){ + this.global.registerFormatter(name, formatter, {language}); + }else{ + this.#formatterRegistry.register(name, formatter, {language}); + } } - /** - * 注册多种格式化器 - * registerFormatters({"*":{...},zh:{...},en:{...}}) - * registerFormatters({"*":{...},zh:{...},en:{...}},true) 在全局注册 - * @param {*} formatters ={"*":{...},zh:{...},en:{...}} - * @returns - */ - registerFormatters(formatters:VoerkaI18nFormatters,asGlobal=false) { - Object.entries(formatters).forEach(([language,fns])=>{ - Object.entries(fns).forEach(([name,formatter])=>{ - this.registerFormatter(name,formatter,{language,asGlobal}) - }) - }) - } + // /** + // * 注册多种语言的格式化器 + // * '*': 代表适用于所有语言 + // * registerFormatters({"*":{...},zh:{...},en:VoerkaI18nFormatters}) + // * registerFormatters({"*":{...},zh:{...},en:{...}},true) 在全局注册 + // * @param {*} langformatters ={"*":{...},zh:{...},en:{...}} + // * @returns + // */ + // registerFormatters(langformatters:VoerkaI18nLanguageFormatters,asGlobal=false) { + // Object.entries(langformatters).forEach(([langName,formatters])=>{ + // if(isPlainObject(formatters)){ + // this.#options.formatters[langName] = formatters + // } + // }) + // } /** * 初始化格式化器 * 激活和默认语言的格式化器采用静态导入的形式,而没有采用异步块的形式,这是为了确保首次加载时的能马上读取,而不能采用延迟加载方式 - * #activeFormatters={$config:{...},$types:{...},[格式化器名称]:()=>{...},[格式化器名称]:()=>{...},...}} + * #activeFormatters={ + * global:{...} // 或true代表注册到全局 + * $config:{...}, + * $types:{...}, + * [格式化器名称]:()=>{...}, + * [格式化器名称]:()=>{...}, + * ... + * } */ - private _initFormatters(newLanguage:string){ - // 全局格式化器,用来注册到全局 - Object.entries(this.formatters).forEach(([langName,formatters])=>{ - if(formatters.global===true){ - this.registerFormatters({[langName]:formatters},true) + private loadFormatters(newLanguage:string){ + this.#formatterRegistry.language = newLanguage + // 初始化格式化器 + this.formatters.loadInitials(this.#options.formatters) + if(this.#options.formatters) + // 将配置中的指定为全局的格式化器注册到全局 + Object.entries(this.#options.formatters).forEach(([langName,formatters])=>{ + if(typeof(formatters)=='function'){ + this.#formatterRegistry.registerLanguageFormatters(langName,formatters) }else if(isPlainObject(formatters.global)){ - this.registerFormatters({[langName]:formatters.global as any},true) - } - }) + if(formatters.global===true){ + this.#formatterRegistry.registerLanguageFormatters(langName,formatters) + }else if(isPlainObject(formatters.global)){ + this.global.formatters.registerLanguageFormatters(langName,formatters.global as VoerkaI18nFormatters) + } + delete formatters.global + } + }) + // 保存到Registry中,就可以从options中删除了 + delete (this.#options as any).formatters + // 初始化格式化器 try { - if (newLanguage in this.formatters) { - this.#activeFormatters = this.formatters[newLanguage]; + if (this.formatters.hasLanguage(newLanguage)) { + } else { if (this.debug) console.warn(`Not initialize <${newLanguage}> formatters.`); } this._generateFormatterConfig(newLanguage) - } catch (e) { + } catch (e:any) { if (this.debug) console.error(`Error while initialize ${newLanguage} formatters: ${e.message}`); } } @@ -218,14 +232,11 @@ export class VoerkaI18nScope { * @param {*} language */ async _changeFormatters(newLanguage:string) { - try { - if (newLanguage in this.formatters) { - let loader = this.formatters[newLanguage]; - if (isPlainObject(loader)) { - this.#activeFormatters = loader as unknown as VoerkaI18nFormatters; - } else if (isFunction(loader)) { - this.#activeFormatters = (await (loader as Function).call(this)).default; - } + try { + if (this.formatters.hasLanguage(newLanguage)) { + this.formatters.language = newLanguage + // 如果该语言的格式化器集合是异步加载,需要等待加载完成 + await this.formatters.load() // 合并生成格式化器的配置参数,当执行格式化器时该参数将被传递给格式化器 this._generateFormatterConfig(newLanguage) } else { @@ -236,26 +247,32 @@ export class VoerkaI18nScope { } } /** - * 生成格式化器的配置参数,该参数由以下合并而成: + * 生成格式化器的配置参数,该参数由以下合并而成: + * * - global.formatters[*].$config + * - global.formatters[fallbackLanguage].$config * - global.formatters[language].$config - * - scope.activeFormatters.$config 当前优先 + * - this.formatters[*].$config + * - this.formatters[fallbackLanguage].$config + * - this.formatters[language].$config */ _generateFormatterConfig(language:string){ try{ - const fallbackLanguage = this.getLanguage(language).fallback; - let configSources = [ - getByPath(this.#global.formatters,`${fallbackLanguage}.$config`,{}), - getByPath(this.#global.formatters,"*.$config",{}), - getByPath(this.formatters,`${fallbackLanguage}.$config`,{}), - getByPath(this._formatter,"*.$config",{}), - getByPath(this.#global.formatters,`${language}.$config`,{}), - getByPath(this.#activeFormatters,"$config",{}) + const fallbackLanguage = this.getLanguage(language)?.fallback; + let configSources = [ + // 从全局读取 + this.#global.formatters.getConfig('*'), + this.#global.formatters.getConfig(fallbackLanguage), + this.#global.formatters.config, + // 从当前Scope读取 + this.formatters.getConfig('*'), + this.formatters.getConfig(fallbackLanguage), + this.formatters.config ] - return this.#activeFormatterConfig = configSources.reduce((finalConfig, config)=>{ - if(isPlainObject(config)) deepMerge(finalConfig,config,{newObject:false}) + return this.#activeFormatterConfig = configSources.reduce((finalConfig, curConfig)=>{ + if(isPlainObject(curConfig)) deepMerge(finalConfig,curConfig,{newObject:false,array:'replace'}) return finalConfig - },deepClone(getByPath(this.#global.formatters,`*.$config`,{}))) + },{}) }catch(e){ if(this.debug) console.error(`Error while generate <${language}> formatter options: `,e) @@ -275,7 +292,7 @@ export class VoerkaI18nScope { * @param {*} language * @returns */ - getLanguage(language:string):VoerkaI18nLanguage | undefined{ + private getLanguage(language:string):VoerkaI18nLanguageDefine | undefined{ let index = this.languages.findIndex((lng) => lng.name == language); if (index !== -1) return this.languages[index]; } @@ -285,12 +302,12 @@ export class VoerkaI18nScope { * @returns */ hasLanguage(language:string) { - return this.languages.indexOf((lang:VoerkaI18nLanguage) => lang.name == language) !== -1; + return this.languages.indexOf((lang:VoerkaI18nLanguageDefine) => lang.name == language) !== -1; } /** * 回退到默认语言 */ - _fallback() { + private _fallback() { this.#options.messages = this.default; this.#options.activeLanguage = this.defaultLanguage; } @@ -319,25 +336,25 @@ export class VoerkaI18nScope { } else if (isFunction(loader)) { // 语言包异步chunk newMessages = (await loader()).default; } else if (isFunction(this.global.defaultMessageLoader)) { // 从远程加载语言包:如果该语言没有指定加载器,则使用全局配置的默认加载器 - const loadedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this); + const loadedMessages = (await this.global.loadMessagesFromDefaultLoader(newLanguage,this)) as unknown as VoerkaI18nDynamicLanguageMessages; if(isPlainObject(loadedMessages)){ useRemote = true // 需要保存动态语言包中的$config,合并到对应语言的格式化器配置 if(isPlainObject(loadedMessages.$config)){ - this._formatters[newLanguage] = { - $config : loadedMessages.$config + this.formatters[newLanguage] = { + $config : loadedMessages.$config as any } delete loadedMessages.$config } - newMessages = Object.assign({},this._default,loadedMessages); + newMessages = Object.assign({},this.default,loadedMessages); } } if(newMessages){ - this._messages = newMessages - this._activeLanguage = newLanguage; + this.#options.messages = newMessages + this.#options.activeLanguage = newLanguage; // 打语言包补丁, 如果是从远程加载语言包则不需要再打补丁了 if(!useRemote) { - await this._patch(this._messages, newLanguage); + await this._patch(this.#options.messages, newLanguage); } // 切换到对应语言的格式化器 await this._changeFormatters(newLanguage); @@ -361,10 +378,10 @@ export class VoerkaI18nScope { * @param {*} newLanguage * @returns */ - async _patch(messages, newLanguage) { + private async _patch(messages:VoerkaI18nLanguageMessages, newLanguage:string) { if (!isFunction(this.global.loadMessagesFromDefaultLoader)) return; try { - let pachedMessages = await this.global.loadMessagesFromDefaultLoader(newLanguage,this); + let pachedMessages = (await this.global.loadMessagesFromDefaultLoader(newLanguage,this)) as unknown as VoerkaI18nLanguageMessages; if (isPlainObject(pachedMessages)) { Object.assign(messages, pachedMessages); this._savePatchedMessages(pachedMessages, newLanguage); @@ -376,10 +393,10 @@ export class VoerkaI18nScope { /** * 从本地存储中读取语言包补丁合并到当前语言包中 */ - _mergePatchedMessages() { + private _mergePatchedMessages() { let patchedMessages = this._getPatchedMessages(this.activeLanguage); if (isPlainObject(patchedMessages)) { - Object.assign(this._messages, patchedMessages); + Object.assign(this.#options.messages, patchedMessages); } } /** @@ -400,13 +417,13 @@ export class VoerkaI18nScope { * * @param {*} messages */ - _savePatchedMessages(messages, language) { + private _savePatchedMessages(messages:VoerkaI18nLanguageMessages, language:string) { try { if (globalThis.localStorage) { 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); + } catch (e:any) { + if (this.debug) console.error("Error while save voerkai18n patched messages:",e); } } /** @@ -414,18 +431,18 @@ export class VoerkaI18nScope { * @param {*} language * @returns */ - _getPatchedMessages(language) { + private _getPatchedMessages(language:string) { try { - return JSON.parse(localStorage.getItem(`voerkai18n_${this.id}_${language}_patched_messages`)); + return JSON.parse((localStorage as any).getItem(`voerkai18n_${this.id}_${language}_patched_messages`)); } catch (e) { return {}; } } // 以下方法引用全局VoerkaI18n实例的方法 - on() {return this.#global.on(...arguments); } - off() {return this.#global.off(...arguments); } - offAll() {return this.#global.offAll(...arguments);} - async change(language) { + on(callback:Function) {return this.#global.on(callback); } + off(callback:Function) {return this.#global.off(callback); } + offAll() {return this.#global.offAll();} + async change(language:string) { await this.#global.change(language); } }; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 544a5ec..a5fec7d 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -8,14 +8,19 @@ declare global { export type SupportedDateTypes = "String" | "Number" | "Boolean" | "Object" | "Array" | "Function" | "Error" | "Symbol" | "RegExp" | "Date" | "Null" | "Undefined" | "Set" | "Map" | "WeakSet" | "WeakMap" -export type VoerkaI18nLanguageMessages = Record +export type VoerkaI18nLanguageMessages = Record & { + $config?: VoerkaI18nTypesFormatterConfigs +} +export type VoerkaI18nDynamicLanguageMessages = Record & { + $config?: VoerkaI18nTypesFormatterConfigs +} export interface VoerkaI18nLanguagePack { [language: string]: VoerkaI18nLanguageMessages } export type Voerkai18nIdMap = Record -export interface VoerkaI18nLanguage { +export interface VoerkaI18nLanguageDefine { name: string title?: string default?: boolean @@ -24,22 +29,25 @@ export interface VoerkaI18nLanguage { export type VoerkaI18nFormatterConfigs = Record -export type VoerkaI18nFormatter = (value: string, ...args: any[]) => string +export type VoerkaI18nFormatter = (value: string,args: any[],$config:VoerkI18nFormatterConfigs) => string export type VoerkaI18nTypesFormatters=Record export type VoerkaI18nTypesFormatterConfigs= Record> -// -export type VoerkaI18nLanguageFormatters = { - global?: true | Exclude // 是否全局格式化器 - $types?: VoerkaI18nTypesFormatters +export type VoerkaI18nFormattersLoader = (()=>Promise) +// 每一个语言的格式化器定义={$types:{...},$config:{...},[格式化器名称]: () => {},[格式化器名称]: () => {} +// 在formatters/xxxx.ts里面进行配置 +export type VoerkaI18nFormatters = ({ + global?: boolean | Omit // 是否全局格式化器 + $types?:VoerkaI18nTypesFormatters $config?: VoerkaI18nTypesFormatterConfigs } & { [DataTypeName in SupportedDateTypes]?: VoerkaI18nFormatter } & { [key: string]: VoerkaI18nFormatter -} +}) + // 包括语言的{"*":{...},zh:{...},en:{...}} // 声明格式化器 -export type VoerkaI18nFormatters = Record +export type VoerkaI18nLanguageFormatters = Record @@ -53,8 +61,8 @@ export type VoerkaI18nDefaultMessageLoader = (this:VoerkaI18nScope,newLanguage:s export interface VoerkaI18nScopeCache{ activeLanguage :string | null, - typedFormatters: VoerkaI18nFormatters, - formatters : VoerkaI18nFormatters, + typedFormatters: VoerkaI18nLanguageFormatters, + formatters : VoerkaI18nLanguageFormatters, } export type TranslateMessageVars = number | boolean | string | Function | Date @@ -63,7 +71,7 @@ export interface VoerkaI18nTranslate { (message: string, vars?: Record): string } export interface VoerkaI18nSupportedLanguages { - [key: string]: VoerkaI18nLanguage + [key: string]: VoerkaI18nLanguageDefine }