From e7318e2de4284478f66a1bd04f3efc0ae4c46bf6 Mon Sep 17 00:00:00 2001 From: wxzhang Date: Thu, 6 Apr 2023 20:41:15 +0800 Subject: [PATCH] update --- packages/runtime/src/__tests__/index.test.ts | 25 ++++- packages/runtime/src/formatterRegistry.ts | 18 +--- packages/runtime/src/manager.ts | 25 ++++- packages/runtime/src/scope.ts | 97 +++++++++----------- packages/runtime/src/types.ts | 3 +- packages/runtime/src/utils.ts | 5 + 6 files changed, 97 insertions(+), 76 deletions(-) diff --git a/packages/runtime/src/__tests__/index.test.ts b/packages/runtime/src/__tests__/index.test.ts index d56d0c8..e16680e 100644 --- a/packages/runtime/src/__tests__/index.test.ts +++ b/packages/runtime/src/__tests__/index.test.ts @@ -4,13 +4,20 @@ import {test,vi,describe,expect,afterAll,beforeAll} from 'vitest' import { VoerkaI18nScope } from '../scope' import zhFormatters from '../formatters/zh'; import enFormatters from '../formatters/en'; +import { VoerkaI18nManager } from '../manager'; +import { VoerkaI18nFormatterRegistry } from '../formatterRegistry'; +import { VoerkaI18nLanguageMessages } from '../types'; -const zhMessages={ +const zhMessages:VoerkaI18nLanguageMessages = { + $config:{ + x:{a:1}, + y:{b:1} + }, "1": "你好", "2": "你好,{name}", "3": "中国", - "4": ["我有一部车","我有很多部车"] + "4": ["我有一部车","我有很多部车"] , } const enMessages={ "1": "hello", @@ -37,7 +44,8 @@ const languages = [ const formatters ={ zh:zhFormatters, - en:enFormatters + en:enFormatters, + jp:()=>{} } describe("VoerkaI18nScope", () => { @@ -56,8 +64,19 @@ describe("VoerkaI18nScope", () => { expect(scope.default).toEqual(zhMessages) expect(scope.current).toEqual(zhMessages) expect(scope.idMap).toEqual(idMap) + // 格式化器配置 + expect(scope.formatters).toBeInstanceOf(VoerkaI18nFormatterRegistry) + expect(scope.formatters.language).toBe("zh") + expect(scope.formatters.formatters).toEqual(formatters) + expect(scope.formatters.config).toBe(zhFormatters.$config) + expect(scope.formatters.types).toBe(zhFormatters.$types) + + + // 全局管理器 + expect(scope.global).toBeInstanceOf(VoerkaI18nManager) }) + }) test('translate', () => {}) diff --git a/packages/runtime/src/formatterRegistry.ts b/packages/runtime/src/formatterRegistry.ts index 0117415..84f4492 100644 --- a/packages/runtime/src/formatterRegistry.ts +++ b/packages/runtime/src/formatterRegistry.ts @@ -32,8 +32,8 @@ export class VoerkaI18nFormatterRegistry{ #ready:boolean = false #formatters:VoerkaI18nLanguageFormatters = {} #language:string = 'zh' - constructor(formatters?:VoerkaI18nLanguageFormatters){ - this.#formatters = formatters || {} + constructor(){ + } /** * 当前语言 @@ -65,20 +65,16 @@ export class VoerkaI18nFormatterRegistry{ 语言名称,语言名称数组,或者使用,分割的语言名称字符串 */ register(name:string | SupportedDateTypes, formatter:VoerkaI18nFormatter, {language = "*"}:{ language: string | string[] } ) { - if (!isFunction(formatter) || typeof name !== "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(!(lngName in this.#formatters)) this.#formatters[lngName] = {} if(typeof(this.#formatters[lngName])!="function"){ let lngFormatters = this.#formatters[lngName] as any if (DataTypes.includes(name)) { - if(!lngFormatters.$types){ - lngFormatters.$types = {} - } + if(!lngFormatters.$types) lngFormatters.$types = {} lngFormatters.$types![name] = formatter } else { lngFormatters[name] = formatter; @@ -187,9 +183,5 @@ export class VoerkaI18nFormatterRegistry{ if(!this.#ready) throw new FormattersNotLoadedError(this.#language) return (this.#formatters[this.#language] as VoerkaI18nFormatters).$types as VoerkaI18nFormatterConfigs } - get items(){ - if(!this.#ready) throw new FormattersNotLoadedError(this.#language) - return this.#formatters[this.#language] as VoerkaI18nFormatterConfigs - } } \ No newline at end of file diff --git a/packages/runtime/src/manager.ts b/packages/runtime/src/manager.ts index 3d41ddd..8b43b25 100644 --- a/packages/runtime/src/manager.ts +++ b/packages/runtime/src/manager.ts @@ -5,6 +5,7 @@ import inlineFormatters from "./formatters" import type { VoerkaI18nScope } from "./scope" import type { VoerkaI18nLanguageDefine, VoerkaI18nLanguageFormatters, VoerkaI18nDefaultMessageLoader, VoerkaI18nFormatter, VoerkaI18nTypesFormatters } from "./types" import { VoerkaI18nFormatterRegistry } from "./formatterRegistry" +import { DataTypes } from "./utils" // 默认语言配置 const defaultLanguageSettings = { @@ -46,7 +47,7 @@ export class VoerkaI18nManager extends EventEmitter{ #options?:Required #scopes:VoerkaI18nScope[] = [] #defaultMessageLoader?:VoerkaI18nDefaultMessageLoader - #formatterRegistry!:VoerkaI18nFormatterRegistry + #formatters:VoerkaI18nLanguageFormatters = {} constructor(options?:VoerkaI18nManagerOptions){ super() if(VoerkaI18nManager.instance){ @@ -54,7 +55,6 @@ export class VoerkaI18nManager extends EventEmitter{ } VoerkaI18nManager.instance = this; this.#options = deepMerge(defaultLanguageSettings,options) as Required - this.#formatterRegistry = new VoerkaI18nFormatterRegistry() this.loadInitialFormatters() // 加载初始格式化器 this.#scopes=[] // 保存VoerkaI18nScope实例 } @@ -65,14 +65,14 @@ export class VoerkaI18nManager extends EventEmitter{ get defaultLanguage(){ return this.#options!.defaultLanguage} // 默认语言名称 get languages(){ return this.#options!.languages} // 支持的语言列表 get defaultMessageLoader(){ return this.#defaultMessageLoader} // 默认语言包加载器 - get formatters(){return this.#formatterRegistry!} + get formatters(){return this.#formatters!} /** * 初始加载格式化器 */ private loadInitialFormatters(){ if(this.#options?.formatters){ - this.#formatterRegistry.loadInitials(this.#options!.formatters) + this.#formatters = this.#options!.formatters delete (this.#options as any).formatters } } @@ -146,7 +146,22 @@ export class VoerkaI18nManager extends EventEmitter{ language : 声明该格式化器适用语言 */ registerFormatter(name:string,formatter:VoerkaI18nFormatter,{language="*"}:{language:string | string[] | '*'}){ - this.#formatterRegistry.register(name,formatter,{language}) + 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 any + if (DataTypes.includes(name)) { + if(!lngFormatters.$types) lngFormatters.$types = {} + lngFormatters.$types![name] = formatter + } else { + lngFormatters[name] = formatter; + } + } + }); } /** * 注册默认文本信息加载器 diff --git a/packages/runtime/src/scope.ts b/packages/runtime/src/scope.ts index 41f12df..360fbd8 100644 --- a/packages/runtime/src/scope.ts +++ b/packages/runtime/src/scope.ts @@ -24,6 +24,7 @@ VoerkaI18nMessageLoader, } from "./types" import { VoerkaI18nFormatterRegistry } from './formatterRegistry'; import { InvalidLanguageError } from "./errors" +import { randomId } from "./utils" export interface VoerkaI18nScopeOptions { id?: string @@ -38,20 +39,20 @@ export interface VoerkaI18nScopeOptions { export class VoerkaI18nScope { #options:Required - #global:VoerkaI18nManager // 引用全局VoerkaI18nManager配置,注册后自动引用 + #global:VoerkaI18nManager // 引用全局VoerkaI18nManager配置,注册后自动引用 #refreshing:boolean = false - #patchMessages:VoerkaI18nLanguagePack = {} #t:VoerkaI18nTranslate #activeFormatters:VoerkaI18nFormatters = {} #activeFormatterConfig: VoerkaI18nFormatterConfigs={} #cache:VoerkaI18nScopeCache - #formatterRegistry:VoerkaI18nFormatterRegistry + #formatterRegistry:VoerkaI18nFormatterRegistry = new VoerkaI18nFormatterRegistry() #defaultLanguage:string ='zh' #activeLanguage:string='zh' #currentMessages:VoerkaI18nLanguageMessages = {} // 当前语言包 + #patchedMessages:VoerkaI18nLanguagePack = {} // 补丁语言包 constructor(options:VoerkaI18nScopeOptions, callback?:(e?:Error)=>void) { this.#options = assignObject({ - id : Date.now().toString() + parseInt(String(Math.random() * 1000)), + id : randomId(), // 作用域唯一id debug : false, languages : {}, // 当前作用域支持的语言列表 messages : {}, // 所有语言包={[language]:VoerkaI18nLanguageMessages} @@ -63,24 +64,16 @@ export class VoerkaI18nScope { activeLanguage : this.#options.activeLanguage, typedFormatters: {}, formatters : {}, - }; + }; // 初始化 - this._initiLanguages() - // 如果不存在全局VoerkaI18n实例,说明当前Scope是唯一或第一个加载的作用域,则自动创建全局VoerkaI18n实例 - if (!globalThis.VoerkaI18n) { - globalThis.VoerkaI18n = new VoerkaI18nManager({ - debug : this.debug, - defaultLanguage: this.#defaultLanguage, - activeLanguage : this.#activeLanguage, - languages : options.languages, - }); - } + this.init() + // 将当前实例注册到全局单例VoerkaI18nManager中 + this.#global = this.registerToManager(callback) + // 从本地缓存中读取并合并补丁语言包 + this._mergePatchedMessages(); + // 延后执行补丁命令,该命令会向远程下载补丁包 + this._patch(this.#currentMessages, this.activeLanguage); this.#t = translate.bind(this) - this.#global = globalThis.VoerkaI18n as unknown as VoerkaI18nManager; - this.#formatterRegistry = new VoerkaI18nFormatterRegistry() - this._mergePatchedMessages(); // 从本地缓存中读取并合并补丁语言包 - this._patch(this.#currentMessages, this.activeLanguage); // 延后执行补丁命令,该命令会向远程下载补丁包 - if(callback) this.register(callback); // 在全局注册作用域 } get id() {return this.#options.id;} // 作用域唯一id get debug() {return this.#options.debug;} // 调试开关 @@ -106,7 +99,7 @@ export class VoerkaI18nScope { * - 将en配置为默认回退语言 * - 确保提供了有效的默认语言和活动语言 */ - _initiLanguages(){ + private init(){ if(!Array.isArray(this.languages)){ console.warn("[VoerkaI18n] invalid languages config,use default languages config instead.") this.#options.languages = [ @@ -138,16 +131,29 @@ export class VoerkaI18nScope { throw new Error("[VoerkaI18n] invalid config, messages must be static.") } this.#currentMessages = this.messages[this.#activeLanguage] as VoerkaI18nLanguageMessages + + // 初始化格式化器 + this.loadInitialFormatters() } - - /** - * 在全局注册作用域当前作用域 - * @param {*} callback 注册成功后的回调 - */ - register(callback:(e?:Error)=>void) { + /** + * 注册当前作用域到全局作用域 + * @param callback + */ + private registerToManager(callback?:(e?:Error)=>void){ + // 如果不存在全局VoerkaI18n实例,说明当前Scope是唯一或第一个加载的作用域,则自动创建全局VoerkaI18n实例 + if (!globalThis.VoerkaI18n) { + globalThis.VoerkaI18n = new VoerkaI18nManager({ + debug : this.debug, + defaultLanguage: this.#defaultLanguage, + activeLanguage : this.#activeLanguage, + languages : this.#options.languages, + }); + } + this.#global = globalThis.VoerkaI18n as unknown as VoerkaI18nManager; if (!isFunction(callback)) callback = () => {}; - this.global.register(this).then(()=>callback()).catch((e)=>callback(e)); - } + this.#global.register(this).then(()=>(callback as any)()).catch((e)=>(callback as any)(e)); + return this.#global + } /** * 注册格式化器 * @@ -199,10 +205,9 @@ export class VoerkaI18nScope { * ... * } */ - private loadFormatters(newLanguage:string){ + private loadInitialFormatters(){ // 初始化格式化器 this.formatters.loadInitials(this.#options.formatters) - this.#formatterRegistry.language = newLanguage if(this.#options.formatters) // 将配置中的指定为全局的格式化器注册到全局 Object.entries(this.#options.formatters).forEach(([langName,formatters])=>{ @@ -219,17 +224,8 @@ export class VoerkaI18nScope { }) // 保存到Registry中,就可以从options中删除了 delete (this.#options as any).formatters - // 初始化格式化器 - try { - if (this.formatters.hasLanguage(newLanguage)) { - - } else { - if (this.debug) console.warn(`Not initialize <${newLanguage}> formatters.`); - } - this._generateFormatterConfig(newLanguage) - } catch (e:any) { - if (this.debug) console.error(`Error while initialize ${newLanguage} formatters: ${e.message}`); - } + // 切换到当前语言的格式化器上下文 + this.changeFormatters(this.activeLanguage) } /** @@ -244,7 +240,7 @@ export class VoerkaI18nScope { * * @param {*} language */ - async _changeFormatters(newLanguage:string) { + private async changeFormatters(newLanguage:string) { try { if (this.formatters.hasLanguage(newLanguage)) { this.formatters.language = newLanguage @@ -260,16 +256,9 @@ export class VoerkaI18nScope { } } /** - * 生成格式化器的配置参数,该参数由以下合并而成: - * - * - global.formatters[*].$config - * - global.formatters[fallbackLanguage].$config - * - global.formatters[language].$config - * - this.formatters[*].$config - * - this.formatters[fallbackLanguage].$config - * - this.formatters[language].$config + * 生成格式化器的配置参数,该参数由以下合并而成: */ - _generateFormatterConfig(language:string){ + private _generateFormatterConfig(language:string){ try{ const fallbackLanguage = this.getLanguage(language)?.fallback; let configSources = [ @@ -375,7 +364,7 @@ export class VoerkaI18nScope { if (newLanguage === this.defaultLanguage) { this.#currentMessages = this.default; await this._patch(this.#currentMessages, newLanguage); // 异步补丁 - await this._changeFormatters(newLanguage); + await this.changeFormatters(newLanguage); this.#refreshing = false return; }else{ // 非默认语言可以是静态语言包也可以是异步加载语言包 @@ -390,7 +379,7 @@ export class VoerkaI18nScope { await this._patch(this.#currentMessages, newLanguage); } // 切换到对应语言的格式化器 - await this._changeFormatters(newLanguage); + await this.changeFormatters(newLanguage); }else{ this._fallback(); } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 4e43474..4e3c6fa 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -9,8 +9,9 @@ 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 + $remote?: boolean } export type VoerkaI18nLanguageMessagePack = Record diff --git a/packages/runtime/src/utils.ts b/packages/runtime/src/utils.ts index 2e404cd..83c866d 100644 --- a/packages/runtime/src/utils.ts +++ b/packages/runtime/src/utils.ts @@ -71,3 +71,8 @@ export function toDate(value:any) { export function toBoolean(value:any){ return !!value } + + +export function randomId():string{ + return Date.now().toString() + parseInt(String(Math.random() * 1000)) +} \ No newline at end of file