@voerkai18n/runtime
Version:
runtime of voerkai18n
404 lines (396 loc) • 14.6 kB
text/typescript
import type {
Voerkai18nIdMap,
VoerkaI18nLanguage,
VoerkaI18nLanguageMessages,
VoerkaI18nLanguageMessagePack,
IVoerkaI18nStorage,
VoerkaI18nLanguagePack,
VoerkaI18nLanguageLoader,
VoerkaI18nTranslate,
VoerkaI18nTranslateComponentBuilder,
VoerkaI18nParagraphs,
VoerkaI18nLanguageParagraphs,
VoerkaI18nTranslateVars,
VoerkaI18nTranslateOptions,
VoerkaI18nTranslateTransformBuilder,
VoerkaI18nTranslateTransformer,
} from "@/types";
import { DefaultLanguageSettings } from "../consts";
import { Mixin } from "ts-mixer";
import { EventEmitterMixin } from "./mixins/eventEmitter";
import { PatchMessageMixin } from "./mixins/patch";
import { ChangeLanguageMixin } from "./mixins/change";
import { VoerkaI18nLogger, VoerkaI18nLoggerOutput } from "../logger";
import { VoerkaI18nFormatters } from "../formatter/types";
import { getId } from "@/utils/getId";
import { createLogger } from "@/logger";
import { VoerkaI18nFormatterManager } from "../formatter/manager";
import { isI18nManger } from "@/utils/isI18nManger";
import { LanguageMixin } from "./mixins/language";
import { TranslateMixin } from "./mixins/translate";
import { RestoreMixin } from "./mixins/restore";
import { InterpolatorMixin } from "./mixins/interpolator";
import { isFunction } from "flex-tools/typecheck/isFunction";
import { assignObject } from "flex-tools/object/assignObject";
import { VoerkaI18nManager } from "../manager";
import { LocalStorage } from "@/storage";
import { isBrowser } from "@/utils/isBrowser";
import { isMessageId } from "@/utils/isMessageId";
export interface VoerkaI18nScopeOptions<
TranslateComponent = any,
TranslateTransformResult = any
> {
id?: string; // 作用域唯一id,一般可以使用package.json中的name字段
debug?: boolean; // 是否开启调试模式,开启后会输出调试信息
library?: boolean; // 当使用在库中时应该置为true
languages: VoerkaI18nLanguage[]; // 当前作用域支持的语言列表
fallback?: string; // 默认回退语言
messages: VoerkaI18nLanguageMessagePack; // 当前语言包
paragraphs: VoerkaI18nParagraphs; // 段落
idMap?: Voerkai18nIdMap; // 消息id映射列表
storage?: IVoerkaI18nStorage; // 语言包存储器
formatters?: VoerkaI18nFormatters; // 当前作用域的格式化
log?: VoerkaI18nLoggerOutput; // 日志记录器
attached?: boolean; // 是否挂接到appScope
storageKey?: string; // 保存到Storeage时的Key
loader?: VoerkaI18nLanguageLoader; // 从远程加载语言包
cachePatch?: boolean; // 是否缓存补丁语言包
injectLangAttr?: boolean | string; // 是否注入到html元素上注入一个langauge属性指向当前活动语言
namespaces?: Record<string, string>; // 命名空间
patterns?: string[]; // 源文件匹配清单,使用fast-glob匹配文件
component?: VoerkaI18nTranslateComponentBuilder<TranslateComponent>; // 翻译组件
// 对翻译结果进行变换,比如变换为vue/ref对象,当使用$t时生效
transform?: VoerkaI18nTranslateTransformBuilder<TranslateTransformResult>;
// 当翻译完成后的回调
onTranslated?: (result: string) => string;
}
export class VoerkaI18nScope<
TranslateComponent = any,
TranslateTransformResult = any
> extends Mixin(
EventEmitterMixin,
PatchMessageMixin,
ChangeLanguageMixin,
LanguageMixin,
TranslateMixin,
InterpolatorMixin,
RestoreMixin
) {
__VoerkaI18nScope__ = true;
static idSeq: number = 0;
private _options: Required<VoerkaI18nScopeOptions<TranslateComponent>>;
private _manager!: VoerkaI18nManager; // 引用全局VoerkaI18nManager配置,注册后自动引用
private _formatterManager: VoerkaI18nFormatterManager | null = null;
private _logger!: VoerkaI18nLogger;
protected _defaultLanguage: string = "zh-CN"; // 默认语言名称
protected _activeLanguage: string = "zh-CN"; // 默认语言名称
protected _activeMessages: VoerkaI18nLanguageMessages = {}; // 当前语言包
protected _patchedMessages: VoerkaI18nLanguagePack = {}; // 补丁语言包
protected _translateComponent?: TranslateComponent;
protected _translateTransformer?: VoerkaI18nTranslateTransformer<TranslateTransformResult>;
protected _activeParagraphs: VoerkaI18nLanguageParagraphs = {}; // 当前段落
$id: number = ++VoerkaI18nScope.idSeq;
/**
*
* @param options
*/
constructor(options: VoerkaI18nScopeOptions) {
super();
this._options = assignObject(
{
id: getId(), // 作用域唯一id
library: false, // 当使用在库中时应该置为true
debug: false, // 是否开启调试模式,开启后会输出调试信息
injectLangAttr: true, // 是否注入一个langauge属性到body元素,或者指定元素选择器
languages: [], // 当前作用域支持的语言列表
messages: {}, // 所有语言包={[language]:VoerkaI18nLanguageMessages}
paragraphs: {}, // 段落
idMap: {}, // 消息id映射列表
formatters: [], // 是否挂接到appScope
attached: true, // 是否挂接到appScope
storageKey: "language", // 保存语言配置到Storage时的Key
cachePatch: true, // 是否缓存补丁语言包
},
options
) as Required<VoerkaI18nScopeOptions>;
this._init();
}
get id() {
return this._options.id;
} // 作用域唯一id
get options() {
return this._options;
} //
get attached() {
return this._options.attached;
} // 作用域唯一id
get debug() {
return this._options.debug;
} // 是否开启调试模式
get library() {
return this._options.library;
} // 是否是库
get formatters() {
return this._formatterManager!;
} // 格式化器管理器
get defaultLanguage() {
return this._defaultLanguage;
} // 默认语言名称
get defaultMessages() {
return this.messages[this.defaultLanguage];
} // 默认语言包
get messages() {
return this._options.messages;
} // 所有语言包
get paragraphs() {
return this._options.paragraphs;
} // 段落
get idMap() {
return this._options.idMap;
} // 消息id映射列表
get languages() {
return this._options.languages;
} // 当前作用域支持的语言列表[{name,title,fallback}]
get manager() {
return this._manager;
} // 引用全局VoerkaI18n配置,注册后自动引用
get appScope() {
return this._manager.scope;
} // 全局作用域
get interpolator() {
return this._flexVars!;
} // 变量插值处理器,使用flexvars
get logger() {
return this._logger!;
} // 日志记录器
get t(): VoerkaI18nTranslate {
return this.translate.bind(this) as VoerkaI18nTranslate;
}
set t(t: Function) {
this.translate = t.bind(this);
}
get Translate(): TranslateComponent {
return this._getTranslateComponent()! as TranslateComponent;
}
get activeMessages() {
return this._activeMessages;
} // 当前语言包
get activeParagraphs() {
return this._activeParagraphs;
} // 当前段落
get activeLanguage(): string {
return this._activeLanguage;
}
get storage() {
return this.getScopeOption<IVoerkaI18nStorage>("storage");
}
get loader() {
return this.getScopeOption<VoerkaI18nLanguageLoader>("loader");
}
get $t(): VoerkaI18nTranslate<TranslateTransformResult> {
return (
message: string,
vars?: VoerkaI18nTranslateVars,
options?: VoerkaI18nTranslateOptions
) => {
this._getTranslateTransformer();
if (!options) options = {};
options.transform = true;
return this.translate(message, vars, options);
};
}
/**
* 有些配置项是以appScope为准
* @param name
* @returns
*/
private getScopeOption<T>(name: string): T | undefined {
const scopeOpts = this._options as any;
// @ts-ignore
return (
this.attached
? scopeOpts[name] ||
(this.library ? (this._manager as any)[name] : undefined)
: scopeOpts[name]
) as T | undefined;
}
private _initOptions() {
// 1. 检测语言配置列表是否有效
if (!Array.isArray(this.languages)) {
this.logger.warn(
"[VoerkaI18n] invalid language settings, will use default language settings."
);
this._options.languages = Object.assign([], DefaultLanguageSettings);
} else if (this.languages.length == 0) {
throw new Error("[VoerkaI18n] must provide valid language settings.");
}
// 2.为语言配置默认回退语言,并且提取默认语言和活动语言
let activeLang: string, defaultLang: string;
this.languages.forEach((language) => {
if (language.default) defaultLang = language.name;
if (language.active) activeLang = language.name;
});
// 3. 确保提供了有效的默认语言和活动语言
const lanMessages = this._options.messages;
if (!(defaultLang! in lanMessages))
defaultLang = Object.keys(lanMessages)[0];
if (!(activeLang! in lanMessages)) activeLang = defaultLang!;
if (!(defaultLang! in lanMessages)) {
throw new Error(
"[VoerkaI18n] invalid language configuration, must provide valid default and active languages."
);
}
this._activeLanguage = activeLang!;
this._defaultLanguage = defaultLang!;
if (!this._options.library && !this._options.storage) {
this._options.storage = LocalStorage;
}
// 初始化时,默认和激活的语言包只能是静态语言包,不能是动态语言包
// 因为初始化时激活语言需要马上显示,如果是异步语言包,会导致显示延迟
if (isFunction(this.messages[this._defaultLanguage])) {
throw new Error(
"[VoerkaI18n] default language pack must be static content, can't use async load way."
);
}
this._activeMessages = this.messages[
this._activeLanguage
] as VoerkaI18nLanguageMessages;
this._activeParagraphs = this.paragraphs[
this._activeLanguage
] as VoerkaI18nLanguageParagraphs;
}
/**
* 对输入的语言配置进行处理
* - 将en配置为默认回退语言
* - 确保提供了有效的默认语言和活动语言
*/
private _init() {
this._logger = createLogger(this._options.log);
// 处理初始化参数
this._initOptions();
// appScope需要从应用中恢复保存的
if (!this.library) this.restoreLanguage();
// 初始化格式化器
this._initInterpolators();
// 将当前实例注册到全局单例VoerkaI18nManager中
this.registerToManager();
// 初始化格式化器
this._formatterManager = new VoerkaI18nFormatterManager(this);
}
/**
*
* 当scope上在全局应用scope创建之后时,会调用此方法
* 本方法在注册到全局VoerkaI18nManager时由Manager调用,
*
* 注意:本方法仅当
* scope是在全局应用scope创建之前时才会调用
*
* 如果scope是在全局应用scope创建之后时创建的,则不会调用此方法
* 因为此时scope会直接注册到全局VoerkaI18nManager中,不会保存到全局变量__VoerkaI18nScopes__中
*
* @param manager
* @returns
*/
bind(manager: VoerkaI18nManager) {
this._manager = manager;
this._manager.once("init", this._initRefresh.bind(this));
}
/**
* 第一次初始化时刷新语言
*/
private _initRefresh(getInitLanguage?: () => string) {
if (this.library) {
this.refresh(getInitLanguage && getInitLanguage());
} else {
const tasks: any[] = [];
if (
this._defaultLanguage !== this._activeLanguage ||
isFunction(this.activeMessages)
) {
tasks.push(this.refresh(undefined, { patch: false }));
}
tasks.push(this._patch());
Promise.all(tasks).then(() => {
this.emit("ready", this.activeLanguage, true);
this._setLanguageAttr();
});
}
}
/**
* 注册当前作用域到全局作用域
* @param callback
*/
private registerToManager() {
if (!this.attached) return;
const isAppScope = !this.options.library;
if (isAppScope) {
if (globalThis.VoerkaI18n && globalThis.VoerkaI18n.scope && isBrowser()) {
console.warn("Only can have one i18nScope with library=false");
}
this._manager = new VoerkaI18nManager(this);
}
// 当前作用域是库时,如果此时Manager和应用Scope还没创建就先保存到了全局变量__VoerkaI18nScopes__中
// 当应用Scope创建后,会再调用registerToManager方法注册到全局VoerkaI18nManager中
const manager = globalThis.VoerkaI18n as VoerkaI18nManager;
if (manager && isI18nManger(manager)) {
if (isAppScope) {
this._initRefresh();
} else {
manager.register(this);
}
} else {
if (!globalThis.__VoerkaI18nScopes__)
globalThis.__VoerkaI18nScopes__ = [];
globalThis.__VoerkaI18nScopes__.push(this);
}
}
async change(language: string) {
let finalLang: string = this.activeLanguage;
if (this.attached) {
finalLang = await this._manager.change(language);
} else {
finalLang = await this.refresh(language);
}
return finalLang;
}
/**
* 检查当前环境是是否是在浏览器环境中,如果是,则在body上添加language=<activeLanguage>属性
*/
protected _setLanguageAttr() {
if (this.library || !isBrowser()) return;
try {
const injectLangAttr = this._options.injectLangAttr;
if (!injectLangAttr) return;
const ele =
injectLangAttr === true
? document.body
: document.body.querySelector(injectLangAttr as string);
if (ele) {
ele.setAttribute("lang", this.activeLanguage);
}
} catch {}
}
/**
*
* @param message
* @returns
*/
getRawMessage(message: string) {
if (isMessageId(message)) {
if (message in this.defaultMessages) {
return (this.defaultMessages as any)[message];
}
} else {
return message;
}
}
getMessageId(message: any) {
if (isMessageId(message)) {
return message;
} else {
if (message in this.idMap) {
return this.idMap[message];
}
}
}
}