@voerkai18n/runtime
Version:
runtime of voerkai18n
180 lines (174 loc) • 6.72 kB
text/typescript
import { isMessageId } from "@/utils/isMessageId";
import { isFunction } from "flex-tools/typecheck/isFunction";
import { isNumber } from "flex-tools/typecheck/isNumber";
import { isPlainObject } from "flex-tools/typecheck/isPlainObject";
import type { VoerkaI18nScope } from "..";
import type {
VoerkaI18nTranslateVars,
VoerkaI18nTranslateOptions,
VoerkaI18nLanguageMessages,
} from "@/types";
export class TranslateMixin {
/**
* 根据值的单数和复数形式,从messages中取得相应的消息
*
* @param {*} messages 复数形式的文本内容 = [<=0时的内容>,<=1时的内容>,<=2时的内容>,...,<>=N的内容>]
* @param {*} value
*/
private _getPluraMessage(
this: VoerkaI18nScope,
messages: string | string[],
value: number
) {
try {
if (Array.isArray(messages)) {
return messages.length > value
? messages[value]
: messages[messages.length - 1];
} else {
return messages;
}
} catch {
return Array.isArray(messages) ? messages[0] : messages;
}
}
private _getPluraValue(args: any): [number | null, any[]] {
let pluraValue: number | null = null; // 复数值
let vars: any[] = []; // 插值变量列表
// 1. 预处理变量: 复数变量保存至pluralVars中 , 变量如果是Function则调用
if (isPlainObject(args)) {
// 字典插值
const dictVars: Record<string, any> = args;
for (const [name, value] of Object.entries(dictVars)) {
if (isFunction(value)) {
try {
dictVars[name] = value();
} catch {
dictVars[name] = value;
}
}
const isNum: boolean = typeof dictVars[name] === "number"; // 以$开头的视为复数变量,记录下来
if ((pluraValue == null && isNum) || (name.startsWith("$") && isNum)) {
pluraValue = dictVars[name];
}
}
vars = [dictVars];
} else if (Array.isArray(args)) {
// 位置插值
vars = args.map((arg) => {
try {
arg = isFunction(arg) ? arg() : arg;
if (isNumber(arg) && !pluraValue) pluraValue = parseInt(arg); // 约定:位置参数中以第一个数值变量作为指示复数变量
} catch {
return String(arg);
}
return arg;
});
} else if (args !== undefined) {
// 单个插值
pluraValue = isNumber(args) ? parseInt(args) : 0;
vars = [args];
}
return [pluraValue, vars];
}
/**
* 翻译组件
*
*/
protected _getTranslateComponent(this: VoerkaI18nScope): any {
if (!this._translateComponent) {
const builder = this.options.component || this.appScope.options.component;
if (typeof builder === "function") {
this._translateComponent = builder.call(this, this);
} else {
this._translateComponent = () => {};
this.logger.warn("No translate component builder configured");
}
}
return this._translateComponent;
}
protected _getTranslateTransformer(this: VoerkaI18nScope): any {
if (!this._translateTransformer) {
const builder = this.options.transform || this.appScope.options.transform;
if (typeof builder === "function") {
this._translateTransformer = builder.call(this, this);
}
}
return this._translateTransformer;
}
private _getActiveMessages(
this: VoerkaI18nScope,
language: string
): VoerkaI18nLanguageMessages {
const messages = this.messages[language];
if (typeof messages === "function") {
this.logger.warn(
`When the t function specifies the language <${language}> , only synchronized language packs can be used`
);
return this.activeMessages;
}
return (this.messages as any)[language] as VoerkaI18nLanguageMessages;
}
translate<R = string>(
this: VoerkaI18nScope,
message: string,
vars?: VoerkaI18nTranslateVars,
options?: VoerkaI18nTranslateOptions
): R {
if (typeof message !== "string") {
this.logger.debug(
`failed to translate message:${message},it is not a string`
);
return "" as R;
}
const activeLanguage = options?.language || this.activeLanguage;
const activeMessages = this._getActiveMessages(activeLanguage);
// 为什么样要转义换行符?因为在translates/*.json中key不允许换行符存在,需要转义为\\n,这里需要转回来
message = message.replace(/\n/g, "\\n");
// 如果内容是复数,则其值是一个数组,数组中的每个元素是从1-N数量形式的文本内容
let result: any = message;
if (!(typeof message === "string")) return message;
const finalArgs =
vars === undefined ? [] : isFunction(vars) ? vars() : vars;
try {
if (isMessageId(message)) {
// 如果是数字id,
result = (activeMessages as any)[message] || message;
} else {
const msgId = this.idMap[message];
// 语言包可能是使用idMap映射过的,则需要转换
result = (activeMessages[msgId] ||
activeMessages[message] ||
message) as string | string[];
}
const [pluraValue, vars] = this._getPluraValue(finalArgs);
// 2. 处理复数
// 经过上面的处理,content可能是字符串或者数组
// content = "原始文本内容" || 复数形式["原始文本内容","原始文本内容"....]
// 如果是数组说明要启用复数机制,需要根据插值变量中的某个变量来判断复数形式
if (Array.isArray(result) && result.length > 0) {
// 如果存在复数命名变量,只取第一个复数变量
if (pluraValue !== null) {
// 启用的是位置插值
result = this._getPluraMessage(result, pluraValue!);
} else {
// 如果找不到复数变量,则使用第一个内容
result = result[0];
}
}
// 如果没有传入插值变量,则直接返回
if (finalArgs.length === 0) result as string;
// 进行插值处理
result = this.interpolator.replace(result as string, ...vars);
if (isFunction(this.options.onTranslated)) {
result = this.options.onTranslated(result);
}
if (this._translateTransformer && options?.transform) {
result = this._translateTransformer(result, vars, options);
}
} catch (e: any) {
this.logger.error(`翻译失败:${e.stack}`);
}
return result as R;
}
}