@vlian/framework 1.2.55 → 1.2.57

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.
Files changed (71) hide show
  1. package/dist/analytics.umd.js +1 -1
  2. package/dist/core/initialization/initialization.cjs.map +1 -1
  3. package/dist/core/initialization/initialization.js +3 -3
  4. package/dist/core/initialization/initialization.js.map +1 -1
  5. package/dist/core/router/validation/RouterConfigValidator.d.ts +2 -2
  6. package/dist/core/router/validation/schema.d.ts +3 -3
  7. package/dist/index.umd.js +295 -229
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/kernel/kernel.cjs +1 -1
  10. package/dist/kernel/kernel.cjs.map +1 -1
  11. package/dist/kernel/kernel.js +1 -1
  12. package/dist/kernel/kernel.js.map +1 -1
  13. package/dist/kernel/manager/i18n/I18nManager.cjs +93 -0
  14. package/dist/kernel/manager/i18n/I18nManager.cjs.map +1 -0
  15. package/dist/kernel/manager/i18n/I18nManager.d.ts +15 -0
  16. package/dist/kernel/manager/i18n/I18nManager.js +83 -0
  17. package/dist/kernel/manager/i18n/I18nManager.js.map +1 -0
  18. package/dist/kernel/manager/i18n/i18n.persistence.cjs +62 -0
  19. package/dist/kernel/manager/i18n/i18n.persistence.cjs.map +1 -0
  20. package/dist/kernel/manager/i18n/i18n.persistence.d.ts +5 -0
  21. package/dist/kernel/manager/i18n/i18n.persistence.js +41 -0
  22. package/dist/kernel/manager/i18n/i18n.persistence.js.map +1 -0
  23. package/dist/kernel/manager/i18n/i18n.schema.cjs +88 -0
  24. package/dist/kernel/manager/i18n/i18n.schema.cjs.map +1 -0
  25. package/dist/kernel/manager/i18n/i18n.schema.d.ts +6 -0
  26. package/dist/kernel/manager/i18n/i18n.schema.js +64 -0
  27. package/dist/kernel/manager/i18n/i18n.schema.js.map +1 -0
  28. package/dist/kernel/manager/i18n/index.cjs +13 -0
  29. package/dist/kernel/manager/i18n/index.cjs.map +1 -0
  30. package/dist/kernel/manager/i18n/index.d.ts +1 -0
  31. package/dist/kernel/manager/i18n/index.js +3 -0
  32. package/dist/kernel/manager/i18n/index.js.map +1 -0
  33. package/dist/kernel/manager/i18nManager.cjs +2 -77
  34. package/dist/kernel/manager/i18nManager.cjs.map +1 -1
  35. package/dist/kernel/manager/i18nManager.d.ts +1 -13
  36. package/dist/kernel/manager/i18nManager.js +1 -76
  37. package/dist/kernel/manager/i18nManager.js.map +1 -1
  38. package/dist/kernel/manager/logger/LoggerManager.cjs +109 -0
  39. package/dist/kernel/manager/logger/LoggerManager.cjs.map +1 -0
  40. package/dist/kernel/manager/logger/LoggerManager.d.ts +21 -0
  41. package/dist/kernel/manager/logger/LoggerManager.js +99 -0
  42. package/dist/kernel/manager/logger/LoggerManager.js.map +1 -0
  43. package/dist/kernel/manager/logger/index.cjs +13 -0
  44. package/dist/kernel/manager/logger/index.cjs.map +1 -0
  45. package/dist/kernel/manager/logger/index.d.ts +1 -0
  46. package/dist/kernel/manager/logger/index.js +3 -0
  47. package/dist/kernel/manager/logger/index.js.map +1 -0
  48. package/dist/kernel/manager/logger/logger.persistence.cjs +64 -0
  49. package/dist/kernel/manager/logger/logger.persistence.cjs.map +1 -0
  50. package/dist/kernel/manager/logger/logger.persistence.d.ts +6 -0
  51. package/dist/kernel/manager/logger/logger.persistence.js +43 -0
  52. package/dist/kernel/manager/logger/logger.persistence.js.map +1 -0
  53. package/dist/kernel/manager/logger/logger.schema.cjs +76 -0
  54. package/dist/kernel/manager/logger/logger.schema.cjs.map +1 -0
  55. package/dist/kernel/manager/logger/logger.schema.d.ts +8 -0
  56. package/dist/kernel/manager/logger/logger.schema.js +55 -0
  57. package/dist/kernel/manager/logger/logger.schema.js.map +1 -0
  58. package/dist/kernel/manager/loggerManager.cjs +2 -103
  59. package/dist/kernel/manager/loggerManager.cjs.map +1 -1
  60. package/dist/kernel/manager/loggerManager.d.ts +1 -18
  61. package/dist/kernel/manager/loggerManager.js +1 -102
  62. package/dist/kernel/manager/loggerManager.js.map +1 -1
  63. package/dist/kernel/types.d.ts +3 -3
  64. package/dist/kernel/types.js.map +1 -1
  65. package/dist/library/locale/index.cjs +8 -34
  66. package/dist/library/locale/index.cjs.map +1 -1
  67. package/dist/library/locale/index.d.ts +4 -4
  68. package/dist/library/locale/index.js +5 -34
  69. package/dist/library/locale/index.js.map +1 -1
  70. package/dist/state.umd.js +1 -1
  71. package/package.json +3 -1
package/dist/index.umd.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /*!
2
- * @vlian/framework v1.2.54
2
+ * @vlian/framework v1.2.56
3
3
  * Secra Framework - 一个现代化的低代码框架
4
4
  * (c) 2026 Secra Framework Contributors
5
5
  * Licensed under Apache-2.0
6
6
  */
7
7
  (function (global, factory) {
8
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@vlian/utils'), require('@vlian/logger'), require('@vlian/monitoring'), require('react/jsx-runtime'), require('react-dom/client'), require('react'), require('react-router-dom'), require('zod'), require('i18next'), require('react-i18next'), require('@vlian/csrf'), require('@/utils')) :
9
- typeof define === 'function' && define.amd ? define(['exports', '@vlian/utils', '@vlian/logger', '@vlian/monitoring', 'react/jsx-runtime', 'react-dom/client', 'react', 'react-router-dom', 'zod', 'i18next', 'react-i18next', '@vlian/csrf', '@/utils'], factory) :
10
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vlianFramework = {}, global.vlianUtils, global.logger, global.monitoring, global.jsxRuntime, global.ReactDOMClient, global.React, global.ReactRouterDOM, global.z, global.i18next, global.ReactI18next, global.csrf, global.utils$1));
11
- })(this, (function (exports, vlianUtils, logger, monitoring, jsxRuntime, client, React, reactRouterDom, zod, i18n, reactI18next, csrf, utils$1) { 'use strict';
8
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@vlian/utils'), require('@vlian/logger'), require('@vlian/monitoring'), require('react/jsx-runtime'), require('react-dom/client'), require('react'), require('react-router-dom'), require('zod'), require('i18next'), require('react-i18next'), require('@vlian/csrf')) :
9
+ typeof define === 'function' && define.amd ? define(['exports', '@vlian/utils', '@vlian/logger', '@vlian/monitoring', 'react/jsx-runtime', 'react-dom/client', 'react', 'react-router-dom', 'zod', 'i18next', 'react-i18next', '@vlian/csrf'], factory) :
10
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vlianFramework = {}, global.vlianUtils, global.logger, global.monitoring, global.jsxRuntime, global.ReactDOMClient, global.React, global.ReactRouterDOM, global.z, global.i18next, global.ReactI18next, global.csrf));
11
+ })(this, (function (exports, vlianUtils, logger, monitoring, jsxRuntime, client, React, reactRouterDom, zod, i18n, reactI18next, csrf) { 'use strict';
12
12
 
13
13
  function _interopNamespaceDefault(e) {
14
14
  var n = Object.create(null);
@@ -29,84 +29,13 @@
29
29
 
30
30
  var vlianUtils__namespace = /*#__PURE__*/_interopNamespaceDefault(vlianUtils);
31
31
 
32
- const local$1 = {
33
- notfound: {
34
- home: "Back to Home"
35
- },
36
- defaultApp: {
37
- title: "Welcome to Secra Framework",
38
- description: "Framework has been successfully started! Please provide your app component to get started.",
39
- quickStart: {
40
- title: "Quick Start",
41
- description: "Provide the app option in your startup configuration to render your application:",
42
- code: "import { start } from '@vlian/framework';\nimport App from './App';\n\nstart({\n app: <App />,\n});"
43
- },
44
- nextSteps: {
45
- title: "Next Steps",
46
- step1: "Create your application root component (e.g., App.tsx)",
47
- step2: "Pass the app option in your startup configuration",
48
- step3: "Start developing your application features"
49
- },
50
- footer: "Secra Framework - Modern Low-Code Frontend Application Framework"
51
- }
52
- };
53
-
54
- const local = {
55
- notfound: {
56
- home: "回到首页"
57
- },
58
- defaultApp: {
59
- title: "欢迎使用 Secra Framework",
60
- description: "框架已成功启动!请提供您的应用组件以开始使用。",
61
- quickStart: {
62
- title: "快速开始",
63
- description: "在启动配置中提供 app 选项来渲染您的应用:",
64
- code: "import { start } from '@vlian/framework';\nimport App from './App';\n\nstart({\n app: <App />,\n});"
65
- },
66
- nextSteps: {
67
- title: "下一步",
68
- step1: "创建您的应用根组件(如 App.tsx)",
69
- step2: "在启动配置中传入 app 选项",
70
- step3: "开始开发您的应用功能"
71
- },
72
- footer: "Secra Framework - 现代化的低代码前端应用框架"
73
- }
74
- };
75
-
76
- let setupPromise = null;
77
- /** Setup plugin i18n */ function setupI18n(otherLocales = {
78
- 'en-US': {},
79
- 'zh-CN': {}
80
- }) {
81
- const locales = {
82
- 'en-US': {
83
- translation: Object.assign({}, local$1, otherLocales['en-US'])
84
- },
85
- 'zh-CN': {
86
- translation: Object.assign({}, local, otherLocales['zh-CN'])
87
- }
88
- };
89
- if (i18n.isInitialized) {
90
- i18n.addResourceBundle('en-US', 'translation', locales['en-US'].translation, true, true);
91
- i18n.addResourceBundle('zh-CN', 'translation', locales['zh-CN'].translation, true, true);
92
- return Promise.resolve(i18n);
93
- }
94
- if (setupPromise) {
95
- return setupPromise;
96
- }
97
- const initPromise = i18n.use(reactI18next.initReactI18next).init({
98
- interpolation: {
99
- escapeValue: false
100
- },
101
- lng: 'zh-CN',
102
- resources: locales
103
- }).then(()=>i18n);
104
- setupPromise = initPromise;
105
- return initPromise;
32
+ const reactI18nextInstance = i18n.use(reactI18next.initReactI18next);
33
+ /** Setup plugin i18n */ async function setupI18n(options, callback) {
34
+ await reactI18nextInstance.init(options, callback);
106
35
  }
107
36
  const $t = i18n.t;
108
- function setLang(locale) {
109
- i18n.changeLanguage(locale);
37
+ async function setLang(locale, callback) {
38
+ await i18n.changeLanguage(locale, callback);
110
39
  }
111
40
 
112
41
  function _define_property$K(obj, key, value) {
@@ -1269,13 +1198,13 @@
1269
1198
 
1270
1199
  /**
1271
1200
  * 初始化方法
1272
- *
1201
+ *
1273
1202
  * 这是核心的初始化逻辑,可被启动页或直接调用
1274
- *
1203
+ *
1275
1204
  * @param options - 初始化选项
1276
1205
  * @param onProgress - 进度回调函数(可选)
1277
1206
  * @returns Promise<InitializationContext>
1278
- *
1207
+ *
1279
1208
  * @example
1280
1209
  * ```typescript
1281
1210
  * const context = await initialization({
@@ -11980,7 +11909,7 @@
11980
11909
  }
11981
11910
  return window.localStorage;
11982
11911
  }
11983
- function readPersistedValue$1(options, defaultKey) {
11912
+ function readPersistedValue(options, defaultKey) {
11984
11913
  if (options.enabled === false) {
11985
11914
  return null;
11986
11915
  }
@@ -11994,7 +11923,7 @@
11994
11923
  return null;
11995
11924
  }
11996
11925
  }
11997
- function writePersistedValue$1(value, options, defaultKey) {
11926
+ function writePersistedValue(value, options, defaultKey) {
11998
11927
  if (options.enabled === false || !value) {
11999
11928
  return;
12000
11929
  }
@@ -12042,7 +11971,7 @@
12042
11971
  return;
12043
11972
  }
12044
11973
  hydratedRef.current = true;
12045
- const persistedMode = readPersistedValue$1(persistenceOptions, DEFAULT_THEME_STORAGE_KEY);
11974
+ const persistedMode = readPersistedValue(persistenceOptions, DEFAULT_THEME_STORAGE_KEY);
12046
11975
  if (!persistedMode || !isThemeMode(persistedMode) || !modes.includes(persistedMode)) {
12047
11976
  return;
12048
11977
  }
@@ -12063,7 +11992,7 @@
12063
11992
  if (!mode || lastWrittenModeRef.current === mode) {
12064
11993
  return;
12065
11994
  }
12066
- writePersistedValue$1(mode, persistenceOptions, DEFAULT_THEME_STORAGE_KEY);
11995
+ writePersistedValue(mode, persistenceOptions, DEFAULT_THEME_STORAGE_KEY);
12067
11996
  lastWrittenModeRef.current = mode;
12068
11997
  }, [
12069
11998
  persistenceOptions,
@@ -12137,7 +12066,7 @@
12137
12066
  return;
12138
12067
  }
12139
12068
  hydratedRef.current = true;
12140
- const persistedLocale = readPersistedValue$1(persistenceOptions, DEFAULT_LOCALE_STORAGE_KEY);
12069
+ const persistedLocale = readPersistedValue(persistenceOptions, DEFAULT_LOCALE_STORAGE_KEY);
12141
12070
  if (!persistedLocale || !isLocale(persistedLocale) || !locales.includes(persistedLocale)) {
12142
12071
  return;
12143
12072
  }
@@ -12154,7 +12083,7 @@
12154
12083
  if (lastWrittenLocaleRef.current === locale) {
12155
12084
  return;
12156
12085
  }
12157
- writePersistedValue$1(locale, persistenceOptions, DEFAULT_LOCALE_STORAGE_KEY);
12086
+ writePersistedValue(locale, persistenceOptions, DEFAULT_LOCALE_STORAGE_KEY);
12158
12087
  lastWrittenLocaleRef.current = locale;
12159
12088
  }, [
12160
12089
  locale,
@@ -12245,76 +12174,6 @@
12245
12174
  }
12246
12175
  };
12247
12176
 
12248
- function resolveStorageInstance(driver) {
12249
- if (driver === 'none' || typeof window === 'undefined') {
12250
- return null;
12251
- }
12252
- const instance = driver === 'localStorage' ? storage.local : driver === 'sessionStorage' ? storage.session : storage.indexedDB;
12253
- if (!instance || typeof instance.get !== 'function' || typeof instance.set !== 'function') {
12254
- return null;
12255
- }
12256
- return instance;
12257
- }
12258
- async function readPersistedValue(persistence) {
12259
- if (!persistence || persistence.enabled === false) {
12260
- return null;
12261
- }
12262
- const driver = persistence.storage ?? 'localStorage';
12263
- const key = persistence.key;
12264
- if (!key) {
12265
- return null;
12266
- }
12267
- const targetStorage = resolveStorageInstance(driver);
12268
- if (!targetStorage) {
12269
- return null;
12270
- }
12271
- try {
12272
- const value = await targetStorage.get(key);
12273
- if (value === null || value === undefined) {
12274
- return null;
12275
- }
12276
- if (typeof value === 'string') {
12277
- return value;
12278
- }
12279
- return JSON.stringify(value);
12280
- } catch {
12281
- return null;
12282
- }
12283
- }
12284
- async function writePersistedValue(persistence, value) {
12285
- if (!persistence || persistence.enabled === false) {
12286
- return;
12287
- }
12288
- const driver = persistence.storage ?? 'localStorage';
12289
- const key = persistence.key;
12290
- if (!key) {
12291
- return;
12292
- }
12293
- const targetStorage = resolveStorageInstance(driver);
12294
- if (!targetStorage) {
12295
- return;
12296
- }
12297
- try {
12298
- await targetStorage.set(key, value);
12299
- } catch (e) {
12300
- utils$1.logger.error(e);
12301
- // ignore persistence errors
12302
- }
12303
- }
12304
-
12305
- function _define_property$4(obj, key, value) {
12306
- if (key in obj) {
12307
- Object.defineProperty(obj, key, {
12308
- value: value,
12309
- enumerable: true,
12310
- configurable: true,
12311
- writable: true
12312
- });
12313
- } else {
12314
- obj[key] = value;
12315
- }
12316
- return obj;
12317
- }
12318
12177
  function normalizeLogLevel(value, fallback) {
12319
12178
  if (typeof value === 'number' && Number.isInteger(value)) {
12320
12179
  switch(value){
@@ -12353,26 +12212,93 @@
12353
12212
  return fallback;
12354
12213
  }
12355
12214
  }
12215
+ function normalizeLoggerSnapshot(value, fallback) {
12216
+ if (typeof value === 'number' || typeof value === 'string') {
12217
+ return {
12218
+ level: normalizeLogLevel(value, fallback.level)
12219
+ };
12220
+ }
12221
+ const source = value && typeof value === 'object' ? value : {};
12222
+ return {
12223
+ level: normalizeLogLevel(source.level, fallback.level)
12224
+ };
12225
+ }
12226
+ function isLoggerEqual(left, right) {
12227
+ return left.level === right.level;
12228
+ }
12229
+
12230
+ const DEFAULT_LOGGER_CACHE_KEY = 'vlian:kernel:logger-level';
12231
+ function resolvePersistenceContext$2(persistence) {
12232
+ if (persistence?.enabled !== true) {
12233
+ return null;
12234
+ }
12235
+ return {
12236
+ key: persistence.key || DEFAULT_LOGGER_CACHE_KEY
12237
+ };
12238
+ }
12239
+ async function loadLoggerFromCache(cacheManager, persistence, fallback) {
12240
+ const persistenceContext = resolvePersistenceContext$2(persistence);
12241
+ if (!persistenceContext) {
12242
+ return {
12243
+ ...fallback
12244
+ };
12245
+ }
12246
+ try {
12247
+ const cached = await cacheManager.get(persistenceContext.key, {
12248
+ defaultValue: fallback
12249
+ });
12250
+ return normalizeLoggerSnapshot(cached, fallback);
12251
+ } catch {
12252
+ return {
12253
+ ...fallback
12254
+ };
12255
+ }
12256
+ }
12257
+ async function saveLoggerToCache(cacheManager, persistence, snapshot) {
12258
+ const persistenceContext = resolvePersistenceContext$2(persistence);
12259
+ if (!persistenceContext) {
12260
+ return;
12261
+ }
12262
+ try {
12263
+ await cacheManager.set(persistenceContext.key, {
12264
+ ...snapshot
12265
+ });
12266
+ } catch {
12267
+ // Ignore persistence failures and keep logger updates in memory.
12268
+ }
12269
+ }
12270
+
12271
+ function _define_property$4(obj, key, value) {
12272
+ if (key in obj) {
12273
+ Object.defineProperty(obj, key, {
12274
+ value: value,
12275
+ enumerable: true,
12276
+ configurable: true,
12277
+ writable: true
12278
+ });
12279
+ } else {
12280
+ obj[key] = value;
12281
+ }
12282
+ return obj;
12283
+ }
12356
12284
  class LoggerManager {
12357
12285
  async initialize(context) {
12358
12286
  this.config = context.config.logger;
12359
- const hasConfiguredLevel = this.config.level !== undefined && this.config.level !== null;
12360
- const configuredLevel = normalizeLogLevel(this.config.level, logger.LogLevel.INFO);
12361
- if (hasConfiguredLevel) {
12362
- this.level = configuredLevel;
12363
- logger.logger.setLevel(this.level);
12364
- await writePersistedValue(this.config.persistence, String(this.level));
12365
- return;
12366
- }
12367
- const persisted = await readPersistedValue(this.config.persistence);
12368
- if (persisted !== null) {
12369
- this.level = normalizeLogLevel(persisted, configuredLevel);
12370
- logger.logger.setLevel(this.level);
12371
- return;
12372
- }
12373
- this.level = configuredLevel;
12374
- logger.logger.setLevel(this.level);
12375
- await writePersistedValue(this.config.persistence, String(this.level));
12287
+ this.cacheManager = context.cacheManager;
12288
+ const initialSnapshot = normalizeLoggerSnapshot({
12289
+ level: this.config.level
12290
+ }, {
12291
+ level: logger.LogLevel.INFO
12292
+ });
12293
+ this.snapshot = await loadLoggerFromCache(this.cacheManager, this.config.persistence, initialSnapshot);
12294
+ logger.logger.setLevel(this.snapshot.level);
12295
+ this.initialized = true;
12296
+ }
12297
+ subscribe(listener) {
12298
+ this.listeners.add(listener);
12299
+ return ()=>{
12300
+ this.listeners.delete(listener);
12301
+ };
12376
12302
  }
12377
12303
  debug(...args) {
12378
12304
  logger.logger.debug(...args);
@@ -12387,18 +12313,52 @@
12387
12313
  logger.logger.error(...args);
12388
12314
  }
12389
12315
  async setLevel(level) {
12390
- this.level = level;
12391
- logger.logger.setLevel(level);
12392
- await writePersistedValue(this.config.persistence, String(level));
12316
+ this.ensureInitialized();
12317
+ const prevSnapshot = this.snapshot;
12318
+ const nextSnapshot = normalizeLoggerSnapshot({
12319
+ level
12320
+ }, prevSnapshot);
12321
+ if (isLoggerEqual(prevSnapshot, nextSnapshot)) {
12322
+ return;
12323
+ }
12324
+ this.snapshot = nextSnapshot;
12325
+ logger.logger.setLevel(this.snapshot.level);
12326
+ if (this.cacheManager) {
12327
+ await saveLoggerToCache(this.cacheManager, this.config.persistence, this.snapshot);
12328
+ }
12329
+ this.emit(this.snapshot, prevSnapshot);
12393
12330
  }
12394
12331
  getSnapshot() {
12395
12332
  return {
12396
- level: this.level
12333
+ ...this.snapshot
12397
12334
  };
12398
12335
  }
12336
+ ensureInitialized() {
12337
+ if (!this.initialized) {
12338
+ throw new Error('LoggerManager must be initialized before use.');
12339
+ }
12340
+ }
12341
+ emit(next, prev) {
12342
+ for (const listener of this.listeners){
12343
+ try {
12344
+ listener({
12345
+ ...next
12346
+ }, {
12347
+ ...prev
12348
+ });
12349
+ } catch {
12350
+ // Keep notifying remaining listeners even if one fails.
12351
+ }
12352
+ }
12353
+ }
12399
12354
  constructor(){
12400
- _define_property$4(this, "level", logger.LogLevel.INFO);
12355
+ _define_property$4(this, "snapshot", {
12356
+ level: logger.LogLevel.INFO
12357
+ });
12401
12358
  _define_property$4(this, "config", DEFAULT_CONFIG.logger);
12359
+ _define_property$4(this, "listeners", new Set());
12360
+ _define_property$4(this, "cacheManager", null);
12361
+ _define_property$4(this, "initialized", false);
12402
12362
  }
12403
12363
  }
12404
12364
 
@@ -12487,7 +12447,7 @@
12487
12447
  'system'
12488
12448
  ]);
12489
12449
  const TOKEN_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
12490
- function isPlainObject(value) {
12450
+ function isPlainObject$1(value) {
12491
12451
  return Object.prototype.toString.call(value) === '[object Object]';
12492
12452
  }
12493
12453
  function hasOwnProperty(value, key) {
@@ -12504,7 +12464,7 @@
12504
12464
  };
12505
12465
  }
12506
12466
  function sanitizeTokens(tokens) {
12507
- if (!isPlainObject(tokens)) {
12467
+ if (!isPlainObject$1(tokens)) {
12508
12468
  return undefined;
12509
12469
  }
12510
12470
  const sanitizedTokens = {};
@@ -12519,7 +12479,7 @@
12519
12479
  return Object.keys(sanitizedTokens).length > 0 ? sanitizedTokens : undefined;
12520
12480
  }
12521
12481
  function normalizeTheme(value, fallback) {
12522
- const source = isPlainObject(value) ? value : {};
12482
+ const source = isPlainObject$1(value) ? value : {};
12523
12483
  const mode = VALID_THEME_MODES.has(source.mode) ? source.mode : fallback.mode;
12524
12484
  const primaryColor = typeof source.primaryColor === 'string' && source.primaryColor.trim() ? source.primaryColor : fallback.primaryColor;
12525
12485
  const fallbackTokens = sanitizeTokens(fallback.tokens);
@@ -12578,7 +12538,7 @@
12578
12538
  }
12579
12539
 
12580
12540
  const DEFAULT_THEME_CACHE_KEY = 'vlian:kernel:theme';
12581
- function resolvePersistenceContext(persistence) {
12541
+ function resolvePersistenceContext$1(persistence) {
12582
12542
  if (persistence?.enabled !== true) {
12583
12543
  return null;
12584
12544
  }
@@ -12587,7 +12547,7 @@
12587
12547
  };
12588
12548
  }
12589
12549
  async function loadThemeFromCache(cacheManager, persistence, fallback) {
12590
- const persistenceContext = resolvePersistenceContext(persistence);
12550
+ const persistenceContext = resolvePersistenceContext$1(persistence);
12591
12551
  const safeFallback = cloneTheme(fallback);
12592
12552
  if (!persistenceContext) {
12593
12553
  return safeFallback;
@@ -12602,7 +12562,7 @@
12602
12562
  }
12603
12563
  }
12604
12564
  async function saveThemeToCache(cacheManager, persistence, theme) {
12605
- const persistenceContext = resolvePersistenceContext(persistence);
12565
+ const persistenceContext = resolvePersistenceContext$1(persistence);
12606
12566
  if (!persistenceContext) {
12607
12567
  return;
12608
12568
  }
@@ -12684,6 +12644,108 @@
12684
12644
  }
12685
12645
  }
12686
12646
 
12647
+ function isPlainObject(value) {
12648
+ return Object.prototype.toString.call(value) === '[object Object]';
12649
+ }
12650
+ function cloneI18nSnapshot(snapshot) {
12651
+ const resources = snapshot.resources ? Object.fromEntries(Object.entries(snapshot.resources).map(([locale, value])=>[
12652
+ locale,
12653
+ {
12654
+ ...value
12655
+ }
12656
+ ])) : undefined;
12657
+ return {
12658
+ locale: snapshot.locale,
12659
+ ...resources ? {
12660
+ resources
12661
+ } : {}
12662
+ };
12663
+ }
12664
+ function normalizeI18nSnapshot(value, fallback) {
12665
+ const source = isPlainObject(value) ? value : {};
12666
+ const locale = typeof source.locale === 'string' && source.locale.trim() ? source.locale : fallback.locale;
12667
+ const fallbackResources = fallback.resources;
12668
+ const incomingResources = isPlainObject(source.resources) ? Object.fromEntries(Object.entries(source.resources).filter(([, resourceValue])=>isPlainObject(resourceValue)).map(([locale, resourceValue])=>[
12669
+ locale,
12670
+ {
12671
+ ...resourceValue
12672
+ }
12673
+ ])) : undefined;
12674
+ const resources = incomingResources || fallbackResources ? {
12675
+ ...fallbackResources || {},
12676
+ ...incomingResources || {}
12677
+ } : undefined;
12678
+ return {
12679
+ locale,
12680
+ ...resources ? {
12681
+ resources
12682
+ } : {}
12683
+ };
12684
+ }
12685
+ function mergeI18nSnapshot(current, next) {
12686
+ return normalizeI18nSnapshot({
12687
+ locale: next.locale ?? current.locale,
12688
+ resources: next.resources ?? current.resources
12689
+ }, current);
12690
+ }
12691
+ function isI18nEqual(left, right) {
12692
+ if (left.locale !== right.locale) {
12693
+ return false;
12694
+ }
12695
+ const leftResources = left.resources || {};
12696
+ const rightResources = right.resources || {};
12697
+ const leftLocales = Object.keys(leftResources);
12698
+ const rightLocales = Object.keys(rightResources);
12699
+ if (leftLocales.length !== rightLocales.length) {
12700
+ return false;
12701
+ }
12702
+ for (const locale of leftLocales){
12703
+ if (leftResources[locale] !== rightResources[locale]) {
12704
+ return false;
12705
+ }
12706
+ }
12707
+ return true;
12708
+ }
12709
+
12710
+ const DEFAULT_I18N_CACHE_KEY = 'vlian:kernel:i18n';
12711
+ function resolvePersistenceContext(persistence) {
12712
+ if (persistence?.enabled !== true) {
12713
+ return null;
12714
+ }
12715
+ return {
12716
+ key: persistence.key || DEFAULT_I18N_CACHE_KEY
12717
+ };
12718
+ }
12719
+ async function loadI18nFromCache(cacheManager, persistence, fallback) {
12720
+ const persistenceContext = resolvePersistenceContext(persistence);
12721
+ const safeFallback = cloneI18nSnapshot(fallback);
12722
+ if (!persistenceContext) {
12723
+ return safeFallback;
12724
+ }
12725
+ try {
12726
+ const cached = await cacheManager.get(persistenceContext.key, {
12727
+ defaultValue: safeFallback
12728
+ });
12729
+ return normalizeI18nSnapshot(cached, safeFallback);
12730
+ } catch {
12731
+ return safeFallback;
12732
+ }
12733
+ }
12734
+ async function saveI18nToCache(cacheManager, persistence, snapshot) {
12735
+ const persistenceContext = resolvePersistenceContext(persistence);
12736
+ if (!persistenceContext) {
12737
+ return;
12738
+ }
12739
+ try {
12740
+ const persistedSnapshot = {
12741
+ locale: cloneI18nSnapshot(snapshot).locale
12742
+ };
12743
+ await cacheManager.set(persistenceContext.key, persistedSnapshot);
12744
+ } catch {
12745
+ // Ignore persistence failures and keep i18n updates in memory.
12746
+ }
12747
+ }
12748
+
12687
12749
  function _define_property$1(obj, key, value) {
12688
12750
  if (key in obj) {
12689
12751
  Object.defineProperty(obj, key, {
@@ -12700,31 +12762,21 @@
12700
12762
  class I18nManager {
12701
12763
  async initialize(context) {
12702
12764
  this.config = context.config.i18n;
12703
- this.snapshot = {
12704
- ...DEFAULT_I18N,
12705
- ...this.config.initial || {}
12706
- };
12707
- const persisted = await readPersistedValue(this.config.persistence);
12708
- if (persisted !== null) {
12709
- try {
12710
- const parsed = JSON.parse(persisted);
12711
- this.snapshot = {
12712
- ...this.snapshot,
12713
- ...parsed
12714
- };
12715
- } catch {
12716
- // ignore parse errors
12717
- }
12718
- } else {
12719
- await writePersistedValue(this.config.persistence, JSON.stringify(this.snapshot));
12720
- }
12721
- await setupI18n(this.snapshot.resources || {});
12722
- setLang(this.snapshot.locale);
12765
+ this.cacheManager = context.cacheManager;
12766
+ const initialSnapshot = normalizeI18nSnapshot(this.config.initial || {}, DEFAULT_I18N);
12767
+ this.snapshot = await loadI18nFromCache(this.cacheManager, this.config.persistence, initialSnapshot);
12768
+ await setupI18n({
12769
+ interpolation: {
12770
+ escapeValue: false
12771
+ },
12772
+ lng: this.snapshot.locale || "zh-CN",
12773
+ resources: this.snapshot.resources
12774
+ });
12775
+ await setLang(this.snapshot.locale);
12776
+ this.initialized = true;
12723
12777
  }
12724
12778
  getSnapshot() {
12725
- return {
12726
- ...this.snapshot
12727
- };
12779
+ return cloneI18nSnapshot(this.snapshot);
12728
12780
  }
12729
12781
  subscribe(listener) {
12730
12782
  this.listeners.add(listener);
@@ -12732,29 +12784,43 @@
12732
12784
  this.listeners.delete(listener);
12733
12785
  };
12734
12786
  }
12735
- emit(next, prev) {
12736
- this.listeners.forEach((listener)=>{
12737
- listener({
12738
- ...next
12739
- }, {
12740
- ...prev
12741
- });
12742
- });
12743
- }
12744
12787
  async setLocale(locale) {
12745
- const prev = this.snapshot;
12746
- this.snapshot = {
12747
- ...this.snapshot,
12788
+ this.ensureInitialized();
12789
+ const prevSnapshot = this.snapshot;
12790
+ const nextSnapshot = mergeI18nSnapshot(prevSnapshot, {
12748
12791
  locale
12749
- };
12750
- setLang(locale);
12751
- await writePersistedValue(this.config.persistence, JSON.stringify(this.snapshot));
12752
- this.emit(this.snapshot, prev);
12792
+ });
12793
+ if (isI18nEqual(prevSnapshot, nextSnapshot)) {
12794
+ return;
12795
+ }
12796
+ this.snapshot = nextSnapshot;
12797
+ // set lang
12798
+ await setLang(this.snapshot.locale);
12799
+ if (this.cacheManager) {
12800
+ await saveI18nToCache(this.cacheManager, this.config.persistence, this.snapshot);
12801
+ }
12802
+ this.emit(this.snapshot, prevSnapshot);
12803
+ }
12804
+ ensureInitialized() {
12805
+ if (!this.initialized) {
12806
+ throw new Error('I18nManager must be initialized before use.');
12807
+ }
12808
+ }
12809
+ emit(next, prev) {
12810
+ for (const listener of this.listeners){
12811
+ try {
12812
+ listener(cloneI18nSnapshot(next), cloneI18nSnapshot(prev));
12813
+ } catch {
12814
+ // Keep notifying remaining listeners even if one fails.
12815
+ }
12816
+ }
12753
12817
  }
12754
12818
  constructor(){
12755
- _define_property$1(this, "snapshot", DEFAULT_I18N);
12819
+ _define_property$1(this, "snapshot", cloneI18nSnapshot(DEFAULT_I18N));
12756
12820
  _define_property$1(this, "config", DEFAULT_CONFIG.i18n);
12757
12821
  _define_property$1(this, "listeners", new Set());
12822
+ _define_property$1(this, "cacheManager", null);
12823
+ _define_property$1(this, "initialized", false);
12758
12824
  }
12759
12825
  }
12760
12826
 
@@ -12934,7 +13000,7 @@
12934
13000
  ...DEFAULT_I18N,
12935
13001
  ...loadedI18nInitial,
12936
13002
  ...options.config?.i18n?.initial || {},
12937
- resources: isRecord(optionLocale) ? optionLocale : loadedI18nInitial.resources
13003
+ resources: isRecord(optionLocale) ? optionLocale : loadedI18nInitial.resources ?? options.config?.i18n?.initial?.resources
12938
13004
  }
12939
13005
  }
12940
13006
  };