This commit is contained in:
wxzhang 2023-04-06 20:41:15 +08:00
parent 105a75bc59
commit e7318e2de4
6 changed files with 97 additions and 76 deletions

View File

@ -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', () => {})

View File

@ -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
}
}

View File

@ -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<VoerkaI18nManagerOptions>
#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<VoerkaI18nManagerOptions>
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;
}
}
});
}
/**
*

View File

@ -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<VoerkaI18nScopeOptions>
#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 <defaultLanguage> 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();
}

View File

@ -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<string, string | string[]> & {
export type VoerkaI18nLanguageMessages = Record<string, string | string[]> | {
$config?: VoerkaI18nTypesFormatterConfigs
$remote?: boolean
}
export type VoerkaI18nLanguageMessagePack = Record<string, VoerkaI18nLanguageMessages | VoerkaI18nMessageLoader>

View File

@ -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))
}