UNPKG

@voerkai18n/runtime

Version:
159 lines (149 loc) 6.9 kB
/** * * * 加载语言包 * */ import { isFunction } from "flex-tools/typecheck/isFunction"; import { isPlainObject } from "flex-tools/typecheck/isPlainObject"; import type { VoerkaI18nScope } from ".."; import type { VoerkaI18nDynamicLanguageMessages, VoerkaI18nLanguageMessages } from "@/types"; import { IAsyncSignal,asyncSignal } from "asyncsignal" import { VoerkaI18nError, VoerkaI18nLoadLanguageError } from "@/errors"; import { loadAsyncModule } from "@/utils/loadAsyncModule"; export class ChangeLanguageMixin{ protected _refreshSignal? :IAsyncSignal /** * 刷新语言包 * @param language */ async refresh(this:VoerkaI18nScope,language?:string,options?:{fallback?:boolean,patch:boolean}):Promise<string>{ if(!this._refreshSignal) this._refreshSignal = asyncSignal() if (!language) language = this.activeLanguage; let finalLanguage : string = language; let finalMessages : VoerkaI18nLanguageMessages | undefined = undefined const { patch,fallback } = Object.assign({ fallback:false,patch:true },options) try{ finalMessages = await this._loadLanguageMessages(language) this._activeMessages = finalMessages as VoerkaI18nLanguageMessages // 打语言包补丁, 如果是从远程加载语言包则不需要再打补丁了,因为远程加载的语言包已经是补丁过的了 if(finalMessages && !finalMessages.$remote && patch) { await this._patch(language); } }catch(e:any){ // 切换语言失败,回退到默认语言, 注意:回退语言是不可能出错的,无论回退到了何种语言,默认语言总是可以兜底的回退语言 if(e && e instanceof VoerkaI18nError){ const fallbackLanguage = this.getFallbackLanguage(language) if(fallbackLanguage && fallbackLanguage!==language){ finalLanguage = await this.refresh(fallbackLanguage,{ patch,fallback:true }) } } }finally{ if(!fallback){ this._activeLanguage = finalLanguage if(typeof(this.messages[finalLanguage])==='function' || !(finalLanguage in this.messages)) this.messages[finalLanguage] = this._activeMessages this._activeParagraphs = this.paragraphs[finalLanguage] this._refreshSignal.resolve() this._refreshSignal = undefined await this.emit('scope/change',finalLanguage,true) } } this._setLanguageAttr() return finalLanguage } /** * * 通过加载器从远程加载指定语言的语言包 * * - 简单的对象{} * - 或者是一个返回Promise<VoerkaI18nLanguageMessages>的异步函数 * - 或者是全局的默认加载器 * * @param language 语言名称 * @returns */ private async _loadLanguageMessages(this:VoerkaI18nScope,language:string):Promise<VoerkaI18nLanguageMessages | undefined>{ this.logger.debug(`准备加载语言包:${language}`) // 非默认语言可以是:语言包对象,也可以是一个异步加载语言包文件,加载器是一个异步函数 // 如果没有加载器,则无法加载语言包,因此回退到默认语言 const loader = this.messages[language]; let messages:VoerkaI18nLanguageMessages | undefined = undefined; if (isPlainObject(loader)) { // 静态语言包 messages = loader as unknown as VoerkaI18nLanguageMessages; } else if(isFunction(loader)) { // 异步chunk语言包 try{ messages = await loadAsyncModule.call(this,loader) }catch(e:any){ this.logger.error(`加载异步语言包<${language}>失败:${e.message}`) messages = undefined } } // 使用全局默认加载器从服务器加载语言包 if (!messages && isFunction(this.loader)) { // 从远程加载语言包:如果该语言没有指定加载器,则使用全局配置的默认加载器 try{ const remoteMessages = (await this._loadMessagesFromLoader(language)) as unknown as VoerkaI18nDynamicLanguageMessages; if(isPlainObject(remoteMessages)){ messages = Object.assign( { $remote : true }, // 添加一个标识,表示该语言包是从远程加载的 this.messages[this.defaultLanguage], remoteMessages ) as VoerkaI18nLanguageMessages; // 合并默认语言包和动态语言包,这样就可以局部覆盖默认语言包 }else{ this.logger.error(`错误的语言包<${language}>数据:${remoteMessages}`) } }catch(e:any){ throw new VoerkaI18nLoadLanguageError(e.message) } } if(!isPlainObject(messages)) throw new VoerkaI18nLoadLanguageError(language) return messages } /** * * 从远程加载信息包 * * @param this * @param language */ protected async _loadMessagesFromLoader(this:VoerkaI18nScope,language:string){ if(isFunction(this.loader)){ return await this.loader.call(this,language,this) } } /** * * - 如果正在刷新语言包,则等待刷新完成 * * i18nScope.ready(callback,timeout) * * @param this * @returns */ ready(this:VoerkaI18nScope,timeout?:number):Promise<void> ready(this:VoerkaI18nScope,callback:(activeLanguage:string)=>void,timeout?:number):void ready(this:VoerkaI18nScope):any{ const callback = typeof arguments[0] === 'function' ? arguments[0] : undefined const timeout = typeof arguments[0] === 'number' ? arguments[0] : arguments[1] if(typeof(callback)==='function'){ this.manager.ready(callback,timeout) }else{ return new Promise(resolve=>{ this.manager.ready(resolve,timeout) }) } } /** * await changing() * * @param this * @param timeout * @returns */ async changing(this:VoerkaI18nScope,timeout?:number){ if(!this._refreshSignal && !this._patching) { return } await Promise.all([this._refreshSignal?.(timeout), this._patching?.(timeout)]) } }