@yumerijs/core 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +27 -1
- package/dist/config.js +40 -0
- package/dist/context.d.ts +54 -0
- package/dist/context.js +115 -0
- package/dist/core.d.ts +39 -1
- package/dist/core.js +38 -7
- package/dist/i18n.d.ts +5 -0
- package/dist/i18n.js +14 -4
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import type { I18n } from './i18n.js';
|
|
1
2
|
export declare function fallback<T>(schema: Schema<T>, config: T): T;
|
|
2
3
|
export declare class Schema<T = any> {
|
|
3
4
|
_type?: T;
|
|
4
5
|
type: string;
|
|
5
6
|
isRequired?: boolean;
|
|
6
7
|
description?: string;
|
|
8
|
+
/** 说明文字对应的 i18n key,由 key() 设置 */
|
|
9
|
+
i18nKey?: string;
|
|
7
10
|
defaultValue?: any;
|
|
8
11
|
properties?: Record<string, Schema<any>>;
|
|
9
12
|
items?: Schema<any>;
|
|
10
13
|
enum?: T[];
|
|
11
|
-
constructor(definition: Omit<Schema<T>, '_type' | 'required' | 'default'> & {
|
|
14
|
+
constructor(definition: Omit<Schema<T>, '_type' | 'required' | 'default' | 'key'> & {
|
|
12
15
|
enum?: T[];
|
|
13
16
|
});
|
|
14
17
|
static string(description?: string): Schema<string>;
|
|
@@ -23,8 +26,31 @@ export declare class Schema<T = any> {
|
|
|
23
26
|
}, description?: string): Schema<T & U>;
|
|
24
27
|
static enum<L extends string | number>(values: L[], description?: string): Schema<L>;
|
|
25
28
|
required(this: this): this;
|
|
29
|
+
/**
|
|
30
|
+
* 绑定说明文字的 i18n key
|
|
31
|
+
*
|
|
32
|
+
* 绑定后该配置项的说明文字会按请求语言从内置 I18n 里解析;
|
|
33
|
+
* key 未注册或没命中任何语言时,仍然显示 description 原文,
|
|
34
|
+
* 所以不接入 i18n 的插件不受影响。
|
|
35
|
+
*
|
|
36
|
+
* @param name 翻译 key,建议用 `插件名.config.配置项` 形式的命名空间避免全局撞车
|
|
37
|
+
*/
|
|
38
|
+
key(name: string): this;
|
|
26
39
|
default(this: this, value: T): this;
|
|
27
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* 解析 schema 说明文字要显示的文本
|
|
43
|
+
*
|
|
44
|
+
* 没绑定 i18n key、或 key 没有命中任何语言时一律退回 description 原文,
|
|
45
|
+
* 因此插件的显示效果在未接入 i18n 时与之前完全一致。
|
|
46
|
+
* 绑定了 key 的配置项可以不写 description,此时直接使用译文;
|
|
47
|
+
* 译文和原文都没有时返回 undefined,由调用方回落到字段名。
|
|
48
|
+
*
|
|
49
|
+
* @param schema schema 节点
|
|
50
|
+
* @param i18n i18n 实例,未初始化时按原文处理
|
|
51
|
+
* @param langs 语言优先级列表,通常是 session.request.languages
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveDescription(schema: Schema<any>, i18n?: I18n, langs?: string[]): string | undefined;
|
|
28
54
|
export { Schema as ConfigSchema };
|
|
29
55
|
export interface Config {
|
|
30
56
|
[key: string]: any;
|
package/dist/config.js
CHANGED
|
@@ -30,6 +30,8 @@ export class Schema {
|
|
|
30
30
|
type;
|
|
31
31
|
isRequired;
|
|
32
32
|
description;
|
|
33
|
+
/** 说明文字对应的 i18n key,由 key() 设置 */
|
|
34
|
+
i18nKey;
|
|
33
35
|
defaultValue;
|
|
34
36
|
properties;
|
|
35
37
|
items;
|
|
@@ -38,6 +40,7 @@ export class Schema {
|
|
|
38
40
|
this.type = definition.type;
|
|
39
41
|
this.isRequired = definition.isRequired;
|
|
40
42
|
this.description = definition.description;
|
|
43
|
+
this.i18nKey = definition.i18nKey;
|
|
41
44
|
this.defaultValue = definition.defaultValue;
|
|
42
45
|
this.properties = definition.properties;
|
|
43
46
|
this.items = definition.items;
|
|
@@ -70,9 +73,46 @@ export class Schema {
|
|
|
70
73
|
this.isRequired = true;
|
|
71
74
|
return this;
|
|
72
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* 绑定说明文字的 i18n key
|
|
78
|
+
*
|
|
79
|
+
* 绑定后该配置项的说明文字会按请求语言从内置 I18n 里解析;
|
|
80
|
+
* key 未注册或没命中任何语言时,仍然显示 description 原文,
|
|
81
|
+
* 所以不接入 i18n 的插件不受影响。
|
|
82
|
+
*
|
|
83
|
+
* @param name 翻译 key,建议用 `插件名.config.配置项` 形式的命名空间避免全局撞车
|
|
84
|
+
*/
|
|
85
|
+
key(name) {
|
|
86
|
+
this.i18nKey = name;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
73
89
|
default(value) {
|
|
74
90
|
this.defaultValue = value;
|
|
75
91
|
return this;
|
|
76
92
|
}
|
|
77
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* 解析 schema 说明文字要显示的文本
|
|
96
|
+
*
|
|
97
|
+
* 没绑定 i18n key、或 key 没有命中任何语言时一律退回 description 原文,
|
|
98
|
+
* 因此插件的显示效果在未接入 i18n 时与之前完全一致。
|
|
99
|
+
* 绑定了 key 的配置项可以不写 description,此时直接使用译文;
|
|
100
|
+
* 译文和原文都没有时返回 undefined,由调用方回落到字段名。
|
|
101
|
+
*
|
|
102
|
+
* @param schema schema 节点
|
|
103
|
+
* @param i18n i18n 实例,未初始化时按原文处理
|
|
104
|
+
* @param langs 语言优先级列表,通常是 session.request.languages
|
|
105
|
+
*/
|
|
106
|
+
export function resolveDescription(schema, i18n, langs) {
|
|
107
|
+
if (!schema)
|
|
108
|
+
return undefined;
|
|
109
|
+
const key = schema.i18nKey;
|
|
110
|
+
if (key && i18n) {
|
|
111
|
+
const translated = i18n.get(key, langs);
|
|
112
|
+
// I18n.get 在没命中任何语言时会原样返回 key,借此判断是否真的翻译到了
|
|
113
|
+
if (translated !== key)
|
|
114
|
+
return translated;
|
|
115
|
+
}
|
|
116
|
+
return schema.description;
|
|
117
|
+
}
|
|
78
118
|
export { Schema as ConfigSchema };
|
package/dist/context.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export declare class Context {
|
|
|
24
24
|
private i18ns;
|
|
25
25
|
private affects;
|
|
26
26
|
private services;
|
|
27
|
+
private timers;
|
|
28
|
+
private disposed;
|
|
27
29
|
component: Components;
|
|
28
30
|
renderer?: IRenderer;
|
|
29
31
|
module: any;
|
|
@@ -47,6 +49,58 @@ export declare class Context {
|
|
|
47
49
|
* @param callback 销毁回调
|
|
48
50
|
*/
|
|
49
51
|
affect(callback: () => void | Promise<void>): void;
|
|
52
|
+
/**
|
|
53
|
+
* 注册重复定时器(Node 原生 setInterval 的包装)
|
|
54
|
+
*
|
|
55
|
+
* 参数与行为和原生 setInterval 完全一致,额外保证插件卸载后不留副作用:
|
|
56
|
+
* - 定时器句柄会被 Context 记录,插件卸载(dispose)时统一清理,
|
|
57
|
+
* 不会留下继续运行、拖住事件循环的“幽灵定时器”
|
|
58
|
+
* - 卸载后即使还有已经排队的 tick,包装层也会拦截,不再调用插件回调
|
|
59
|
+
* - 回调中的同步异常与 async 回调的 Promise 拒绝会被捕获并写入日志,
|
|
60
|
+
* 不会变成 uncaughtException / unhandledRejection 影响整个进程
|
|
61
|
+
*
|
|
62
|
+
* @param callback 定时执行的回调
|
|
63
|
+
* @param ms 间隔毫秒数
|
|
64
|
+
* @param args 原样透传给回调的额外参数
|
|
65
|
+
* @returns 定时器句柄,可用 ctx.clearInterval 或原生 clearInterval 取消;
|
|
66
|
+
* 若 Context 已卸载则返回 undefined(此时不会创建定时器)
|
|
67
|
+
*/
|
|
68
|
+
setInterval(callback: (...args: any[]) => any, ms?: number, ...args: any[]): NodeJS.Timeout | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* 注册一次性定时器(Node 原生 setTimeout 的包装)
|
|
71
|
+
*
|
|
72
|
+
* 行为与原生 setTimeout 一致,卸载保证同 setInterval:
|
|
73
|
+
* 卸载时清理尚未触发的句柄,已排队但尚未执行的回调同样会被拦截。
|
|
74
|
+
*
|
|
75
|
+
* @param callback 延时执行的回调
|
|
76
|
+
* @param ms 延时毫秒数
|
|
77
|
+
* @param args 原样透传给回调的额外参数
|
|
78
|
+
* @returns 定时器句柄,可用 ctx.clearTimeout 或原生 clearTimeout 取消;
|
|
79
|
+
* 若 Context 已卸载则返回 undefined(此时不会创建定时器)
|
|
80
|
+
*/
|
|
81
|
+
setTimeout(callback: (...args: any[]) => any, ms?: number, ...args: any[]): NodeJS.Timeout | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* 取消由 setInterval 创建的定时器(Node 原生 clearInterval 的包装)
|
|
84
|
+
* @param timer 定时器句柄
|
|
85
|
+
*/
|
|
86
|
+
clearInterval(timer?: NodeJS.Timeout | number | null): void;
|
|
87
|
+
/**
|
|
88
|
+
* 取消由 setTimeout 创建的定时器(Node 原生 clearTimeout 的包装)
|
|
89
|
+
* @param timer 定时器句柄
|
|
90
|
+
*/
|
|
91
|
+
clearTimeout(timer?: NodeJS.Timeout | number | null): void;
|
|
92
|
+
/**
|
|
93
|
+
* 校验回调参数并确认 Context 尚未卸载
|
|
94
|
+
* @param kind 定时器类型,仅用于日志
|
|
95
|
+
* @param callback 用户回调
|
|
96
|
+
*/
|
|
97
|
+
private prepareTimer;
|
|
98
|
+
/**
|
|
99
|
+
* 调用插件定时器回调,并捕获同步异常与 async 拒绝
|
|
100
|
+
* @param callback 用户回调
|
|
101
|
+
* @param args 透传给回调的参数
|
|
102
|
+
*/
|
|
103
|
+
private invokeTimerCallback;
|
|
50
104
|
/**
|
|
51
105
|
* 注册路由
|
|
52
106
|
* @param path 路由路径
|
package/dist/context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { setInterval as nodeSetInterval, clearInterval as nodeClearInterval, setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'timers';
|
|
2
3
|
/**
|
|
3
4
|
* 插件上下文对象
|
|
4
5
|
* 每个插件一个 Context,用于管理插件注册的命令、路由、事件、组件和中间件
|
|
@@ -15,6 +16,8 @@ export class Context {
|
|
|
15
16
|
i18ns = [];
|
|
16
17
|
affects = [];
|
|
17
18
|
services = [];
|
|
19
|
+
timers = new Set();
|
|
20
|
+
disposed = false;
|
|
18
21
|
component;
|
|
19
22
|
renderer;
|
|
20
23
|
module;
|
|
@@ -50,6 +53,113 @@ export class Context {
|
|
|
50
53
|
return;
|
|
51
54
|
this.affects.push(callback);
|
|
52
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* 注册重复定时器(Node 原生 setInterval 的包装)
|
|
58
|
+
*
|
|
59
|
+
* 参数与行为和原生 setInterval 完全一致,额外保证插件卸载后不留副作用:
|
|
60
|
+
* - 定时器句柄会被 Context 记录,插件卸载(dispose)时统一清理,
|
|
61
|
+
* 不会留下继续运行、拖住事件循环的“幽灵定时器”
|
|
62
|
+
* - 卸载后即使还有已经排队的 tick,包装层也会拦截,不再调用插件回调
|
|
63
|
+
* - 回调中的同步异常与 async 回调的 Promise 拒绝会被捕获并写入日志,
|
|
64
|
+
* 不会变成 uncaughtException / unhandledRejection 影响整个进程
|
|
65
|
+
*
|
|
66
|
+
* @param callback 定时执行的回调
|
|
67
|
+
* @param ms 间隔毫秒数
|
|
68
|
+
* @param args 原样透传给回调的额外参数
|
|
69
|
+
* @returns 定时器句柄,可用 ctx.clearInterval 或原生 clearInterval 取消;
|
|
70
|
+
* 若 Context 已卸载则返回 undefined(此时不会创建定时器)
|
|
71
|
+
*/
|
|
72
|
+
setInterval(callback, ms, ...args) {
|
|
73
|
+
if (!this.prepareTimer('interval', callback))
|
|
74
|
+
return undefined;
|
|
75
|
+
const timer = nodeSetInterval(() => {
|
|
76
|
+
// 兜底:卸载后可能仍有已经排队的 tick,直接丢弃
|
|
77
|
+
if (this.disposed)
|
|
78
|
+
return;
|
|
79
|
+
this.invokeTimerCallback(callback, args);
|
|
80
|
+
}, ms);
|
|
81
|
+
this.timers.add(timer);
|
|
82
|
+
return timer;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 注册一次性定时器(Node 原生 setTimeout 的包装)
|
|
86
|
+
*
|
|
87
|
+
* 行为与原生 setTimeout 一致,卸载保证同 setInterval:
|
|
88
|
+
* 卸载时清理尚未触发的句柄,已排队但尚未执行的回调同样会被拦截。
|
|
89
|
+
*
|
|
90
|
+
* @param callback 延时执行的回调
|
|
91
|
+
* @param ms 延时毫秒数
|
|
92
|
+
* @param args 原样透传给回调的额外参数
|
|
93
|
+
* @returns 定时器句柄,可用 ctx.clearTimeout 或原生 clearTimeout 取消;
|
|
94
|
+
* 若 Context 已卸载则返回 undefined(此时不会创建定时器)
|
|
95
|
+
*/
|
|
96
|
+
setTimeout(callback, ms, ...args) {
|
|
97
|
+
if (!this.prepareTimer('timeout', callback))
|
|
98
|
+
return undefined;
|
|
99
|
+
const timer = nodeSetTimeout(() => {
|
|
100
|
+
// 触发过的句柄不必再被追踪,先摘掉再执行回调
|
|
101
|
+
this.timers.delete(timer);
|
|
102
|
+
if (this.disposed)
|
|
103
|
+
return;
|
|
104
|
+
this.invokeTimerCallback(callback, args);
|
|
105
|
+
}, ms);
|
|
106
|
+
this.timers.add(timer);
|
|
107
|
+
return timer;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 取消由 setInterval 创建的定时器(Node 原生 clearInterval 的包装)
|
|
111
|
+
* @param timer 定时器句柄
|
|
112
|
+
*/
|
|
113
|
+
clearInterval(timer) {
|
|
114
|
+
if (timer === undefined || timer === null)
|
|
115
|
+
return;
|
|
116
|
+
this.timers.delete(timer);
|
|
117
|
+
nodeClearInterval(timer);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 取消由 setTimeout 创建的定时器(Node 原生 clearTimeout 的包装)
|
|
121
|
+
* @param timer 定时器句柄
|
|
122
|
+
*/
|
|
123
|
+
clearTimeout(timer) {
|
|
124
|
+
if (timer === undefined || timer === null)
|
|
125
|
+
return;
|
|
126
|
+
this.timers.delete(timer);
|
|
127
|
+
nodeClearTimeout(timer);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 校验回调参数并确认 Context 尚未卸载
|
|
131
|
+
* @param kind 定时器类型,仅用于日志
|
|
132
|
+
* @param callback 用户回调
|
|
133
|
+
*/
|
|
134
|
+
prepareTimer(kind, callback) {
|
|
135
|
+
if (typeof callback !== 'function') {
|
|
136
|
+
throw new TypeError('The "callback" argument must be of type function.');
|
|
137
|
+
}
|
|
138
|
+
if (this.disposed) {
|
|
139
|
+
this.core.logger.warn(`Plugin "${this.pluginname}" attempt to create a ${kind} after its context was disposed, ignored.`);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 调用插件定时器回调,并捕获同步异常与 async 拒绝
|
|
146
|
+
* @param callback 用户回调
|
|
147
|
+
* @param args 透传给回调的参数
|
|
148
|
+
*/
|
|
149
|
+
invokeTimerCallback(callback, args) {
|
|
150
|
+
try {
|
|
151
|
+
const result = callback(...args);
|
|
152
|
+
// 兼容 async 回调,避免未处理的 Promise 拒绝
|
|
153
|
+
if (result && typeof result.then === 'function') {
|
|
154
|
+
Promise.resolve(result).catch((error) => {
|
|
155
|
+
this.core.logger.error(`Unhandled rejection in timer callback of plugin "${this.pluginname}":`, error);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.core.logger.error(`Unhandled error in timer callback of plugin "${this.pluginname}":`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
53
163
|
/**
|
|
54
164
|
* 注册路由
|
|
55
165
|
* @param path 路由路径
|
|
@@ -238,6 +348,11 @@ export class Context {
|
|
|
238
348
|
* 卸载插件时清理注册的所有资源
|
|
239
349
|
*/
|
|
240
350
|
async dispose() {
|
|
351
|
+
// 先把自己标记为已卸载并停掉所有定时器(Node 里 clearTimeout/clearInterval 可互换):
|
|
352
|
+
// 这样后续拆卸过程中即使有已排队的 tick 也不会再触发插件回调
|
|
353
|
+
this.disposed = true;
|
|
354
|
+
this.timers.forEach((timer) => nodeClearTimeout(timer));
|
|
355
|
+
this.timers.clear();
|
|
241
356
|
// 删除组件
|
|
242
357
|
this.components.forEach((name) => delete this.core.components[name]);
|
|
243
358
|
// 删除服务
|
package/dist/core.d.ts
CHANGED
|
@@ -39,6 +39,33 @@ export interface CoreOptions {
|
|
|
39
39
|
skipcheckUpdates?: boolean;
|
|
40
40
|
}
|
|
41
41
|
export declare const coreConfigSchema: Schema<CoreOptions>;
|
|
42
|
+
/** 核心配置项的内置文案,随 i18n 实例初始化自动注册 */
|
|
43
|
+
export declare const coreI18n: {
|
|
44
|
+
'core.config.port': {
|
|
45
|
+
zh: string;
|
|
46
|
+
en: string;
|
|
47
|
+
};
|
|
48
|
+
'core.config.host': {
|
|
49
|
+
zh: string;
|
|
50
|
+
en: string;
|
|
51
|
+
};
|
|
52
|
+
'core.config.enableCors': {
|
|
53
|
+
zh: string;
|
|
54
|
+
en: string;
|
|
55
|
+
};
|
|
56
|
+
'core.config.enableWs': {
|
|
57
|
+
zh: string;
|
|
58
|
+
en: string;
|
|
59
|
+
};
|
|
60
|
+
'core.config.lang': {
|
|
61
|
+
zh: string;
|
|
62
|
+
en: string;
|
|
63
|
+
};
|
|
64
|
+
'core.config.skipcheckUpdates': {
|
|
65
|
+
zh: string;
|
|
66
|
+
en: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
42
69
|
export declare const enum PluginStatus {
|
|
43
70
|
ENABLED = "enabled",
|
|
44
71
|
DISABLED = "disabled",
|
|
@@ -58,11 +85,22 @@ export declare class Core {
|
|
|
58
85
|
hooks: Record<string, Hook>;
|
|
59
86
|
coreConfig: CoreOptions;
|
|
60
87
|
server: CoreServer;
|
|
61
|
-
|
|
88
|
+
private _i18n?;
|
|
62
89
|
loader: any;
|
|
63
90
|
storage: SessionStorageProcessor;
|
|
64
91
|
renderers: Map<string, IRenderer>;
|
|
65
92
|
pluginRenderers: Map<string, string>;
|
|
93
|
+
/**
|
|
94
|
+
* i18n 实例
|
|
95
|
+
*
|
|
96
|
+
* 未赋值时按 core 配置的 lang 惰性创建,赋值时自动注册核心内置文案,
|
|
97
|
+
* 这样无论 i18n 是被 loader 创建还是被直接使用 core 的场景创建,
|
|
98
|
+
* coreConfigSchema 的说明文字都能被翻译到。
|
|
99
|
+
*/
|
|
100
|
+
get i18n(): I18n;
|
|
101
|
+
set i18n(value: I18n);
|
|
102
|
+
/** 设置 i18n 实例并注册核心内置文案 */
|
|
103
|
+
setI18n(i18n: I18n): void;
|
|
66
104
|
constructor(loader?: any, coreConfig?: CoreOptions, loggersetCore?: boolean, splash?: boolean);
|
|
67
105
|
private checkUpdate;
|
|
68
106
|
addRenderer(renderer: IRenderer): void;
|
package/dist/core.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Logger } from './logger.js';
|
|
|
4
4
|
import { Route } from './route.js';
|
|
5
5
|
import { Hook } from './hook.js';
|
|
6
6
|
import { Server as CoreServer } from './server.js';
|
|
7
|
+
import { I18n } from './i18n.js';
|
|
7
8
|
import { SessionStorageProcessor } from './storage.js';
|
|
8
9
|
import * as fs from 'fs';
|
|
9
10
|
import semver from 'semver';
|
|
@@ -51,13 +52,22 @@ export function resolvePluginModule(module, context, config) {
|
|
|
51
52
|
return plugin;
|
|
52
53
|
}
|
|
53
54
|
export const coreConfigSchema = Schema.object({
|
|
54
|
-
port: Schema.number('监听端口').default(14510),
|
|
55
|
-
host: Schema.string('监听地址').default('0.0.0.0'),
|
|
56
|
-
enableCors: Schema.boolean('启用跨域').default(false),
|
|
57
|
-
enableWs: Schema.boolean('启用 WebSocket').default(false),
|
|
58
|
-
lang: Schema.array(Schema.string(), '语言列表').default(['zh', 'en']),
|
|
59
|
-
skipcheckUpdates: Schema.boolean('启动时检查更新').default(false)
|
|
55
|
+
port: Schema.number('监听端口').key('core.config.port').default(14510),
|
|
56
|
+
host: Schema.string('监听地址').key('core.config.host').default('0.0.0.0'),
|
|
57
|
+
enableCors: Schema.boolean('启用跨域').key('core.config.enableCors').default(false),
|
|
58
|
+
enableWs: Schema.boolean('启用 WebSocket').key('core.config.enableWs').default(false),
|
|
59
|
+
lang: Schema.array(Schema.string(), '语言列表').key('core.config.lang').default(['zh', 'en']),
|
|
60
|
+
skipcheckUpdates: Schema.boolean('启动时检查更新').key('core.config.skipcheckUpdates').default(false)
|
|
60
61
|
});
|
|
62
|
+
/** 核心配置项的内置文案,随 i18n 实例初始化自动注册 */
|
|
63
|
+
export const coreI18n = {
|
|
64
|
+
'core.config.port': { zh: '监听端口', en: 'Listening port' },
|
|
65
|
+
'core.config.host': { zh: '监听地址', en: 'Listening address' },
|
|
66
|
+
'core.config.enableCors': { zh: '启用跨域', en: 'Enable CORS' },
|
|
67
|
+
'core.config.enableWs': { zh: '启用 WebSocket', en: 'Enable WebSocket' },
|
|
68
|
+
'core.config.lang': { zh: '语言列表', en: 'Language list' },
|
|
69
|
+
'core.config.skipcheckUpdates': { zh: '启动时检查更新', en: 'Check for updates on startup' }
|
|
70
|
+
};
|
|
61
71
|
export class Core {
|
|
62
72
|
emitter = new EventEmitter();
|
|
63
73
|
components = {};
|
|
@@ -68,11 +78,32 @@ export class Core {
|
|
|
68
78
|
hooks = {};
|
|
69
79
|
coreConfig;
|
|
70
80
|
server;
|
|
71
|
-
|
|
81
|
+
_i18n;
|
|
72
82
|
loader;
|
|
73
83
|
storage = new SessionStorageProcessor();
|
|
74
84
|
renderers = new Map();
|
|
75
85
|
pluginRenderers = new Map(); // Stores which plugin uses which renderer
|
|
86
|
+
/**
|
|
87
|
+
* i18n 实例
|
|
88
|
+
*
|
|
89
|
+
* 未赋值时按 core 配置的 lang 惰性创建,赋值时自动注册核心内置文案,
|
|
90
|
+
* 这样无论 i18n 是被 loader 创建还是被直接使用 core 的场景创建,
|
|
91
|
+
* coreConfigSchema 的说明文字都能被翻译到。
|
|
92
|
+
*/
|
|
93
|
+
get i18n() {
|
|
94
|
+
if (!this._i18n) {
|
|
95
|
+
this.setI18n(new I18n(this.coreConfig?.lang || ['zh', 'en']));
|
|
96
|
+
}
|
|
97
|
+
return this._i18n;
|
|
98
|
+
}
|
|
99
|
+
set i18n(value) {
|
|
100
|
+
this.setI18n(value);
|
|
101
|
+
}
|
|
102
|
+
/** 设置 i18n 实例并注册核心内置文案 */
|
|
103
|
+
setI18n(i18n) {
|
|
104
|
+
this._i18n = i18n;
|
|
105
|
+
i18n.register(coreI18n);
|
|
106
|
+
}
|
|
76
107
|
constructor(loader, coreConfig = {}, loggersetCore = true, splash = true) {
|
|
77
108
|
this.coreConfig = fallback(coreConfigSchema, coreConfig);
|
|
78
109
|
this.loader = loader;
|
package/dist/i18n.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export declare class I18n {
|
|
|
10
10
|
constructor(fallback?: string[]);
|
|
11
11
|
register(key: string | Record<string, any>, lang?: Record<string, string>): void;
|
|
12
12
|
setFallback(fallback: string[]): void;
|
|
13
|
+
/**
|
|
14
|
+
* 判断一个值是否是语言表,即 `{ zh: '...', en: '...' }` 这种值全为字符串的对象。
|
|
15
|
+
* 这里不硬编码具体语言码,因为 fallback 列表可由 coreConfig.lang 配置。
|
|
16
|
+
*/
|
|
17
|
+
private isLocaleMap;
|
|
13
18
|
private flattenAndRegister;
|
|
14
19
|
isRegistered(key: string): boolean;
|
|
15
20
|
delete(key: string): void;
|
package/dist/i18n.js
CHANGED
|
@@ -22,17 +22,27 @@ export class I18n {
|
|
|
22
22
|
setFallback(fallback) {
|
|
23
23
|
this.fallback = fallback;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 判断一个值是否是语言表,即 `{ zh: '...', en: '...' }` 这种值全为字符串的对象。
|
|
27
|
+
* 这里不硬编码具体语言码,因为 fallback 列表可由 coreConfig.lang 配置。
|
|
28
|
+
*/
|
|
29
|
+
isLocaleMap(value) {
|
|
30
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const values = Object.values(value);
|
|
33
|
+
return values.length > 0 && values.every(v => typeof v === 'string');
|
|
34
|
+
}
|
|
25
35
|
flattenAndRegister(obj, prefix = '') {
|
|
26
36
|
for (const [k, v] of Object.entries(obj)) {
|
|
27
37
|
const fullKey = prefix ? `${prefix}.${k}` : k;
|
|
28
|
-
if (
|
|
29
|
-
this.flattenAndRegister(v, fullKey);
|
|
30
|
-
}
|
|
31
|
-
else if (typeof v === 'object') {
|
|
38
|
+
if (this.isLocaleMap(v)) {
|
|
32
39
|
if (!this.data[fullKey])
|
|
33
40
|
this.data[fullKey] = {};
|
|
34
41
|
Object.assign(this.data[fullKey], v);
|
|
35
42
|
}
|
|
43
|
+
else if (v && typeof v === 'object') {
|
|
44
|
+
this.flattenAndRegister(v, fullKey);
|
|
45
|
+
}
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
isRegistered(key) {
|