@yumerijs/core 3.0.4 → 3.0.6

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 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/core.d.ts CHANGED
@@ -15,6 +15,7 @@ export interface Plugin {
15
15
  apply?: (ctx: Context, config: Config) => Promise<void> | void;
16
16
  disable?: (ctx: Context) => Promise<void> | void;
17
17
  depend?: Array<string>;
18
+ optional?: Array<string>;
18
19
  provide?: Array<string>;
19
20
  render?: string;
20
21
  config?: Schema<any>;
@@ -25,6 +26,7 @@ type PluginModuleLike = Plugin | PluginConstructor | ((ctx: Context, config: Con
25
26
  apply?: ((ctx: Context, config: Config) => Promise<void> | void);
26
27
  disable?: (ctx: Context) => Promise<void> | void;
27
28
  depend?: Array<string>;
29
+ optional?: Array<string>;
28
30
  provide?: Array<string>;
29
31
  render?: string;
30
32
  config?: Schema<any>;
@@ -39,6 +41,33 @@ export interface CoreOptions {
39
41
  skipcheckUpdates?: boolean;
40
42
  }
41
43
  export declare const coreConfigSchema: Schema<CoreOptions>;
44
+ /** 核心配置项的内置文案,随 i18n 实例初始化自动注册 */
45
+ export declare const coreI18n: {
46
+ 'core.config.port': {
47
+ zh: string;
48
+ en: string;
49
+ };
50
+ 'core.config.host': {
51
+ zh: string;
52
+ en: string;
53
+ };
54
+ 'core.config.enableCors': {
55
+ zh: string;
56
+ en: string;
57
+ };
58
+ 'core.config.enableWs': {
59
+ zh: string;
60
+ en: string;
61
+ };
62
+ 'core.config.lang': {
63
+ zh: string;
64
+ en: string;
65
+ };
66
+ 'core.config.skipcheckUpdates': {
67
+ zh: string;
68
+ en: string;
69
+ };
70
+ };
42
71
  export declare const enum PluginStatus {
43
72
  ENABLED = "enabled",
44
73
  DISABLED = "disabled",
@@ -58,11 +87,22 @@ export declare class Core {
58
87
  hooks: Record<string, Hook>;
59
88
  coreConfig: CoreOptions;
60
89
  server: CoreServer;
61
- i18n: I18n;
90
+ private _i18n?;
62
91
  loader: any;
63
92
  storage: SessionStorageProcessor;
64
93
  renderers: Map<string, IRenderer>;
65
94
  pluginRenderers: Map<string, string>;
95
+ /**
96
+ * i18n 实例
97
+ *
98
+ * 未赋值时按 core 配置的 lang 惰性创建,赋值时自动注册核心内置文案,
99
+ * 这样无论 i18n 是被 loader 创建还是被直接使用 core 的场景创建,
100
+ * coreConfigSchema 的说明文字都能被翻译到。
101
+ */
102
+ get i18n(): I18n;
103
+ set i18n(value: I18n);
104
+ /** 设置 i18n 实例并注册核心内置文案 */
105
+ setI18n(i18n: I18n): void;
66
106
  constructor(loader?: any, coreConfig?: CoreOptions, loggersetCore?: boolean, splash?: boolean);
67
107
  private checkUpdate;
68
108
  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';
@@ -19,6 +20,8 @@ function mergePluginMeta(target, source) {
19
20
  return target;
20
21
  if (target.depend == null && Array.isArray(source.depend))
21
22
  target.depend = source.depend;
23
+ if (target.optional == null && Array.isArray(source.optional))
24
+ target.optional = source.optional;
22
25
  if (target.provide == null && Array.isArray(source.provide))
23
26
  target.provide = source.provide;
24
27
  if (target.render == null && typeof source.render === 'string')
@@ -51,13 +54,22 @@ export function resolvePluginModule(module, context, config) {
51
54
  return plugin;
52
55
  }
53
56
  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)
57
+ port: Schema.number('监听端口').key('core.config.port').default(14510),
58
+ host: Schema.string('监听地址').key('core.config.host').default('0.0.0.0'),
59
+ enableCors: Schema.boolean('启用跨域').key('core.config.enableCors').default(false),
60
+ enableWs: Schema.boolean('启用 WebSocket').key('core.config.enableWs').default(false),
61
+ lang: Schema.array(Schema.string(), '语言列表').key('core.config.lang').default(['zh', 'en']),
62
+ skipcheckUpdates: Schema.boolean('启动时检查更新').key('core.config.skipcheckUpdates').default(false)
60
63
  });
64
+ /** 核心配置项的内置文案,随 i18n 实例初始化自动注册 */
65
+ export const coreI18n = {
66
+ 'core.config.port': { zh: '监听端口', en: 'Listening port' },
67
+ 'core.config.host': { zh: '监听地址', en: 'Listening address' },
68
+ 'core.config.enableCors': { zh: '启用跨域', en: 'Enable CORS' },
69
+ 'core.config.enableWs': { zh: '启用 WebSocket', en: 'Enable WebSocket' },
70
+ 'core.config.lang': { zh: '语言列表', en: 'Language list' },
71
+ 'core.config.skipcheckUpdates': { zh: '启动时检查更新', en: 'Check for updates on startup' }
72
+ };
61
73
  export class Core {
62
74
  emitter = new EventEmitter();
63
75
  components = {};
@@ -68,11 +80,32 @@ export class Core {
68
80
  hooks = {};
69
81
  coreConfig;
70
82
  server;
71
- i18n;
83
+ _i18n;
72
84
  loader;
73
85
  storage = new SessionStorageProcessor();
74
86
  renderers = new Map();
75
87
  pluginRenderers = new Map(); // Stores which plugin uses which renderer
88
+ /**
89
+ * i18n 实例
90
+ *
91
+ * 未赋值时按 core 配置的 lang 惰性创建,赋值时自动注册核心内置文案,
92
+ * 这样无论 i18n 是被 loader 创建还是被直接使用 core 的场景创建,
93
+ * coreConfigSchema 的说明文字都能被翻译到。
94
+ */
95
+ get i18n() {
96
+ if (!this._i18n) {
97
+ this.setI18n(new I18n(this.coreConfig?.lang || ['zh', 'en']));
98
+ }
99
+ return this._i18n;
100
+ }
101
+ set i18n(value) {
102
+ this.setI18n(value);
103
+ }
104
+ /** 设置 i18n 实例并注册核心内置文案 */
105
+ setI18n(i18n) {
106
+ this._i18n = i18n;
107
+ i18n.register(coreI18n);
108
+ }
76
109
  constructor(loader, coreConfig = {}, loggersetCore = true, splash = true) {
77
110
  this.coreConfig = fallback(coreConfigSchema, coreConfig);
78
111
  this.loader = loader;
@@ -144,9 +177,9 @@ export class Core {
144
177
  const plugin = resolvePluginModule(module, context, config);
145
178
  const shortName = this.getShortPluginName(context.pluginname);
146
179
  context.module = plugin;
147
- // 自动依赖注入
148
- const depend = plugin.depend || [];
149
- for (const name of depend) {
180
+ // 自动依赖注入:必需依赖始终由 loader 保证,optional 仅在已提供时注入。
181
+ const dependencies = [...(plugin.depend || []), ...(plugin.optional || [])];
182
+ for (const name of dependencies) {
150
183
  const component = this.getComponent(name);
151
184
  if (component) {
152
185
  context.inject(name, component);
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 (typeof v === 'object' && !('zh' in v || 'en' in v)) {
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yumerijs/core",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
4
4
  "description": "Core module for yumeri",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",