@finesoft/front 0.1.53 → 0.1.55

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/index.mjs CHANGED
@@ -85,9 +85,10 @@ var IntentDispatcher = class {
85
85
  };
86
86
  //#endregion
87
87
  //#region ../core/src/dependencies/container.ts
88
- var Container = class {
88
+ var Container = class Container {
89
89
  registrations = /* @__PURE__ */ new Map();
90
90
  resolutionStack = /* @__PURE__ */ new Set();
91
+ parent;
91
92
  /** 注册依赖(默认单例) */
92
93
  register(key, factory, singleton = true) {
93
94
  this.registrations.set(key, {
@@ -96,10 +97,13 @@ var Container = class {
96
97
  });
97
98
  return this;
98
99
  }
99
- /** 解析依赖 */
100
+ /** 解析依赖 — 当前容器未注册时回退到 parent */
100
101
  resolve(key) {
101
102
  const reg = this.registrations.get(key);
102
- if (!reg) throw new Error(`[Container] No registration for key: "${key}"`);
103
+ if (!reg) {
104
+ if (this.parent) return this.parent.resolve(key);
105
+ throw new Error(`[Container] No registration for key: "${key}"`);
106
+ }
103
107
  if (reg.singleton) {
104
108
  if (reg.instance === void 0) {
105
109
  if (this.resolutionStack.has(key)) throw new Error(`[Container] Circular dependency detected: ${[...this.resolutionStack, key].join(" → ")}`);
@@ -114,9 +118,20 @@ var Container = class {
114
118
  }
115
119
  return reg.factory();
116
120
  }
117
- /** 检查是否已注册 */
121
+ /** 检查是否已注册(含 parent) */
118
122
  has(key) {
119
- return this.registrations.has(key);
123
+ return this.registrations.has(key) || (this.parent?.has(key) ?? false);
124
+ }
125
+ /**
126
+ * 创建子容器(请求级 scope)
127
+ *
128
+ * 子容器可覆写父容器的依赖(如每请求的 locale、user),
129
+ * 未覆写的 key 自动回退到父容器解析。
130
+ */
131
+ createScope() {
132
+ const child = new Container();
133
+ child.parent = this;
134
+ return child;
120
135
  }
121
136
  /** 销毁容器,清除所有缓存 */
122
137
  dispose() {
@@ -125,6 +140,190 @@ var Container = class {
125
140
  }
126
141
  };
127
142
  //#endregion
143
+ //#region ../core/src/i18n/locale.ts
144
+ /** RTL 语言列表 */
145
+ const RTL_LANGUAGES = new Set([
146
+ "ar",
147
+ "arc",
148
+ "dv",
149
+ "fa",
150
+ "ha",
151
+ "he",
152
+ "khw",
153
+ "ks",
154
+ "ku",
155
+ "ps",
156
+ "ur",
157
+ "yi"
158
+ ]);
159
+ /** 检测语言是否为 RTL */
160
+ function isRtl(language) {
161
+ const primary = language.split("-")[0].toLowerCase();
162
+ return RTL_LANGUAGES.has(primary);
163
+ }
164
+ /** 获取文本方向 */
165
+ function getTextDirection(language) {
166
+ return isRtl(language) ? "rtl" : "ltr";
167
+ }
168
+ /**
169
+ * 从语言代码生成 HTML lang/dir 属性
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * getLocaleAttributes("ar-SA") // { lang: "ar-SA", dir: "rtl" }
174
+ * getLocaleAttributes("en-US") // { lang: "en-US", dir: "ltr" }
175
+ * ```
176
+ */
177
+ function getLocaleAttributes(language) {
178
+ return {
179
+ lang: language,
180
+ dir: getTextDirection(language)
181
+ };
182
+ }
183
+ /**
184
+ * 构建 LocaleInfo
185
+ *
186
+ * @param language - 语言代码(如 "zh-Hans")
187
+ * @param region - 地区代码(如 "CN"),可选
188
+ */
189
+ function makeLocaleInfo(language, region) {
190
+ return {
191
+ language,
192
+ region,
193
+ bcp47: region ? `${language}-${region}` : language,
194
+ dir: getTextDirection(language)
195
+ };
196
+ }
197
+ /**
198
+ * 将 locale 属性应用到 `<html>` 元素
199
+ *
200
+ * 服务端渲染时可用于字符串拼接,浏览器端直接操作 DOM。
201
+ */
202
+ function setHtmlLocaleAttributes(attrs) {
203
+ document.documentElement.lang = attrs.lang;
204
+ document.documentElement.dir = attrs.dir;
205
+ }
206
+ /**
207
+ * 从 URL 前缀中提取 locale
208
+ *
209
+ * @param url - 请求 URL(如 "/zh/about")
210
+ * @param supportedLocales - 支持的 locale 列表(如 ["zh", "en", "ja"])
211
+ * @returns 匹配时返回 `{ locale, strippedUrl }`,不匹配返回 null
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * resolveLocaleFromUrl("/zh/about", ["zh", "en"])
216
+ * // → { locale: "zh", strippedUrl: "/about" }
217
+ *
218
+ * resolveLocaleFromUrl("/about", ["zh", "en"])
219
+ * // → null
220
+ * ```
221
+ */
222
+ function resolveLocaleFromUrl(url, supportedLocales) {
223
+ const match = url.split("?")[0].match(/^\/([^/]+)(\/.*)?$/);
224
+ if (!match) return null;
225
+ const candidate = match[1];
226
+ const found = supportedLocales.find((l) => l.toLowerCase() === candidate.toLowerCase());
227
+ if (!found) return null;
228
+ return {
229
+ locale: found,
230
+ strippedUrl: match[2] || "/"
231
+ };
232
+ }
233
+ //#endregion
234
+ //#region ../core/src/i18n/interpolate.ts
235
+ /**
236
+ * ICU 消息格式插值
237
+ *
238
+ * 支持 `{name}` 占位符替换和基础复数规则。
239
+ */
240
+ /** 将 `{key}` 占位符替换为 values 中的对应值 */
241
+ function interpolate(template, values) {
242
+ if (!values) return template;
243
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
244
+ const val = values[key];
245
+ return val !== void 0 ? String(val) : `{${key}}`;
246
+ });
247
+ }
248
+ /**
249
+ * 英语复数规则(默认)
250
+ * 0 → other, 1 → one, 2+ → other
251
+ */
252
+ function englishPlural(count) {
253
+ return count === 1 ? "one" : "other";
254
+ }
255
+ /**
256
+ * 解析带复数后缀的翻译 key
257
+ *
258
+ * 约定: `key.one`, `key.other`, `key.zero`, etc.
259
+ */
260
+ function resolvePluralKey(key, category) {
261
+ return `${key}.${category}`;
262
+ }
263
+ //#endregion
264
+ //#region ../core/src/i18n/translator.ts
265
+ /**
266
+ * SimpleTranslator — 默认翻译器实现
267
+ *
268
+ * 从扁平的 key→string 映射提供翻译,支持 ICU 插值和复数规则。
269
+ */
270
+ var SimpleTranslator = class {
271
+ locale;
272
+ messages;
273
+ pluralRule;
274
+ fallback;
275
+ constructor(options) {
276
+ this.locale = options.locale;
277
+ this.messages = options.messages;
278
+ this.pluralRule = options.pluralRule ?? englishPlural;
279
+ this.fallback = options.fallback ?? ((key) => key);
280
+ }
281
+ t(key, values) {
282
+ const template = this.messages[key];
283
+ if (template === void 0) return this.fallback(key);
284
+ return interpolate(template, values);
285
+ }
286
+ plural(key, count, values) {
287
+ const pluralKey = resolvePluralKey(key, this.pluralRule(count));
288
+ const mergedValues = {
289
+ count,
290
+ ...values
291
+ };
292
+ return this.t(pluralKey, mergedValues);
293
+ }
294
+ };
295
+ //#endregion
296
+ //#region ../core/src/logger/composite.ts
297
+ var CompositeLoggerFactory = class {
298
+ constructor(factories) {
299
+ this.factories = factories;
300
+ }
301
+ loggerFor(name) {
302
+ return new CompositeLogger(this.factories.map((f) => f.loggerFor(name)));
303
+ }
304
+ };
305
+ var CompositeLogger = class {
306
+ constructor(loggers) {
307
+ this.loggers = loggers;
308
+ }
309
+ debug(...args) {
310
+ return this.callAll("debug", args);
311
+ }
312
+ info(...args) {
313
+ return this.callAll("info", args);
314
+ }
315
+ warn(...args) {
316
+ return this.callAll("warn", args);
317
+ }
318
+ error(...args) {
319
+ return this.callAll("error", args);
320
+ }
321
+ callAll(method, args) {
322
+ for (const logger of this.loggers) logger[method](...args);
323
+ return "";
324
+ }
325
+ };
326
+ //#endregion
128
327
  //#region ../core/src/logger/base.ts
129
328
  var BaseLogger = class {
130
329
  category;
@@ -213,6 +412,111 @@ var ConsoleLoggerFactory = class {
213
412
  }
214
413
  };
215
414
  //#endregion
415
+ //#region ../core/src/logger/reporting.ts
416
+ /**
417
+ * ReportingLogger — 上报型日志实现
418
+ *
419
+ * 将 warn/error 级别日志转发到外部监控服务(如 Sentry、Datadog)。
420
+ * 用户通过 ReportCallback 注入上报逻辑,框架不直接依赖任何第三方 SDK。
421
+ */
422
+ const LEVEL_PRIORITY = {
423
+ debug: 0,
424
+ info: 1,
425
+ warn: 2,
426
+ error: 3
427
+ };
428
+ var ReportingLogger = class extends BaseLogger {
429
+ minPriority;
430
+ report;
431
+ constructor(category, options) {
432
+ super(category);
433
+ this.minPriority = LEVEL_PRIORITY[options.minLevel ?? "warn"];
434
+ this.report = options.report;
435
+ }
436
+ debug(...args) {
437
+ this.maybeReport("debug", args);
438
+ return "";
439
+ }
440
+ info(...args) {
441
+ this.maybeReport("info", args);
442
+ return "";
443
+ }
444
+ warn(...args) {
445
+ this.maybeReport("warn", args);
446
+ return "";
447
+ }
448
+ error(...args) {
449
+ this.maybeReport("error", args);
450
+ return "";
451
+ }
452
+ maybeReport(level, args) {
453
+ if (LEVEL_PRIORITY[level] >= this.minPriority) this.report(level, this.category, args);
454
+ }
455
+ };
456
+ var ReportingLoggerFactory = class {
457
+ options;
458
+ constructor(options) {
459
+ this.options = options;
460
+ }
461
+ loggerFor(category) {
462
+ return new ReportingLogger(category, this.options);
463
+ }
464
+ };
465
+ //#endregion
466
+ //#region ../core/src/metrics/console-recorder.ts
467
+ var ConsoleEventRecorder = class {
468
+ prefix;
469
+ constructor(prefix = "Metrics") {
470
+ this.prefix = prefix;
471
+ }
472
+ record(type, fields) {
473
+ console.info(`[${this.prefix}:${type}]`, fields ?? "");
474
+ }
475
+ async flush() {}
476
+ destroy() {}
477
+ };
478
+ //#endregion
479
+ //#region ../core/src/utils/platform.ts
480
+ /**
481
+ * 从 User-Agent 字符串解析平台信息
482
+ *
483
+ * @param ua - User-Agent 字符串(默认取 navigator.userAgent)
484
+ */
485
+ function detectPlatform(ua) {
486
+ const agent = ua ?? (typeof navigator !== "undefined" ? navigator.userAgent : "");
487
+ const lower = agent.toLowerCase();
488
+ return {
489
+ os: detectOS(lower),
490
+ browser: detectBrowser(lower),
491
+ engine: detectEngine(lower),
492
+ isMobile: /mobile|android|iphone|ipad|ipod/i.test(agent),
493
+ isTouch: typeof navigator !== "undefined" && "maxTouchPoints" in navigator ? navigator.maxTouchPoints > 0 : false
494
+ };
495
+ }
496
+ function detectOS(ua) {
497
+ if (/iphone|ipad|ipod/.test(ua)) return "ios";
498
+ if (/android/.test(ua)) return "android";
499
+ if (/macintosh|mac os x/.test(ua)) return "macos";
500
+ if (/windows/.test(ua)) return "windows";
501
+ if (/linux/.test(ua)) return "linux";
502
+ return "unknown";
503
+ }
504
+ function detectBrowser(ua) {
505
+ if (/edg\//.test(ua)) return "edge";
506
+ if (/opr\/|opera/.test(ua)) return "opera";
507
+ if (/samsungbrowser/.test(ua)) return "samsung";
508
+ if (/chrome|crios/.test(ua) && !/edg\//.test(ua)) return "chrome";
509
+ if (/firefox|fxios/.test(ua)) return "firefox";
510
+ if (/safari/.test(ua) && !/chrome/.test(ua)) return "safari";
511
+ return "unknown";
512
+ }
513
+ function detectEngine(ua) {
514
+ if (/applewebkit/.test(ua) && !/chrome/.test(ua)) return "webkit";
515
+ if (/applewebkit/.test(ua) && /chrome/.test(ua)) return "blink";
516
+ if (/gecko\//.test(ua)) return "gecko";
517
+ return "unknown";
518
+ }
519
+ //#endregion
216
520
  //#region ../core/src/dependencies/make-dependencies.ts
217
521
  /**
218
522
  * 依赖工厂 — 创建所有基础依赖
@@ -224,7 +528,11 @@ const DEP_KEYS = {
224
528
  STORAGE: "storage",
225
529
  FEATURE_FLAGS: "featureFlags",
226
530
  METRICS: "metrics",
227
- FETCH: "fetch"
531
+ FETCH: "fetch",
532
+ EVENT_RECORDER: "eventRecorder",
533
+ LOCALE: "locale",
534
+ PLATFORM: "platform",
535
+ TRANSLATOR: "translator"
228
536
  };
229
537
  var MemoryStorage = class {
230
538
  store = /* @__PURE__ */ new Map();
@@ -240,38 +548,91 @@ var MemoryStorage = class {
240
548
  };
241
549
  var DefaultFeatureFlags = class {
242
550
  flags;
551
+ providers = [];
243
552
  constructor(flags = {}) {
244
553
  this.flags = flags;
245
554
  }
555
+ /** 注册外部 provider(如远程配置、A/B 测试 SDK) */
556
+ addProvider(provider) {
557
+ this.providers.push(provider);
558
+ }
246
559
  isEnabled(key) {
560
+ for (let i = this.providers.length - 1; i >= 0; i--) if (this.providers[i].isEnabled(key)) return true;
247
561
  return this.flags[key] === true;
248
562
  }
249
563
  getString(key) {
564
+ for (let i = this.providers.length - 1; i >= 0; i--) {
565
+ const result = this.providers[i].getString?.(key);
566
+ if (result !== void 0) return result;
567
+ }
250
568
  const v = this.flags[key];
251
569
  return typeof v === "string" ? v : void 0;
252
570
  }
253
571
  getNumber(key) {
572
+ for (let i = this.providers.length - 1; i >= 0; i--) {
573
+ const result = this.providers[i].getNumber?.(key);
574
+ if (result !== void 0) return result;
575
+ }
254
576
  const v = this.flags[key];
255
577
  return typeof v === "number" ? v : void 0;
256
578
  }
257
579
  };
258
580
  var ConsoleMetrics = class {
581
+ record(type, fields) {
582
+ console.info(`[Metrics:${type}]`, fields ?? "");
583
+ }
259
584
  recordPageView(page, fields) {
260
- console.info(`[Metrics:PageView] ${page}`, fields ?? "");
585
+ this.record("PageView", {
586
+ page,
587
+ ...fields
588
+ });
261
589
  }
262
590
  recordEvent(name, fields) {
263
- console.info(`[Metrics:Event] ${name}`, fields ?? "");
591
+ this.record("Event", {
592
+ name,
593
+ ...fields
594
+ });
264
595
  }
265
596
  };
597
+ /**
598
+ * 从 TranslationMessages 中解析出 SimpleTranslator 所需的扁平 Record<string, string>
599
+ *
600
+ * - 扁平格式:直接返回
601
+ * - 嵌套格式:提取 locale 对应子表并展平复数 key(`{key}.{plural}` 拼接)
602
+ */
603
+ function resolveMessages(messages, locale) {
604
+ const entries = Object.entries(messages);
605
+ if (entries.length === 0) return void 0;
606
+ if (typeof entries[0][1] === "string") return messages;
607
+ const localeMsgs = messages[locale];
608
+ if (!localeMsgs) return void 0;
609
+ const flat = {};
610
+ for (const [key, value] of Object.entries(localeMsgs)) if (typeof value === "string") flat[key] = value;
611
+ else for (const [suffix, text] of Object.entries(value)) flat[`${key}.${suffix}`] = text;
612
+ return flat;
613
+ }
266
614
  function makeDependencies(container, options = {}) {
267
- const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {} } = options;
268
- const loggerFactory = new ConsoleLoggerFactory();
615
+ const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {}, featureFlagsProviders = [], reportCallback, eventRecorder, locale, platform, messages } = options;
616
+ const consoleFactory = new ConsoleLoggerFactory();
617
+ const loggerFactory = reportCallback ? new CompositeLoggerFactory([consoleFactory, new ReportingLoggerFactory({ report: reportCallback })]) : consoleFactory;
269
618
  container.register(DEP_KEYS.LOGGER_FACTORY, () => loggerFactory);
270
619
  container.register(DEP_KEYS.LOGGER, () => loggerFactory.loggerFor("framework"));
271
620
  container.register(DEP_KEYS.NET, () => ({ fetch: (url, opts) => fetchFn(url, opts) }));
272
621
  container.register(DEP_KEYS.STORAGE, () => new MemoryStorage());
273
- container.register(DEP_KEYS.FEATURE_FLAGS, () => new DefaultFeatureFlags(featureFlags));
622
+ const flags = new DefaultFeatureFlags(featureFlags);
623
+ for (const provider of featureFlagsProviders) flags.addProvider(provider);
624
+ container.register(DEP_KEYS.FEATURE_FLAGS, () => flags);
274
625
  container.register(DEP_KEYS.METRICS, () => new ConsoleMetrics());
626
+ container.register(DEP_KEYS.EVENT_RECORDER, () => eventRecorder ?? new ConsoleEventRecorder());
627
+ if (locale) container.register(DEP_KEYS.LOCALE, () => getLocaleAttributes(locale));
628
+ if (locale && messages) {
629
+ const flat = resolveMessages(messages, locale);
630
+ if (flat) container.register(DEP_KEYS.TRANSLATOR, () => new SimpleTranslator({
631
+ locale,
632
+ messages: flat
633
+ }));
634
+ }
635
+ container.register(DEP_KEYS.PLATFORM, () => platform ?? detectPlatform(typeof navigator !== "undefined" ? navigator.userAgent : void 0));
275
636
  container.register(DEP_KEYS.FETCH, () => fetchFn);
276
637
  }
277
638
  //#endregion
@@ -355,37 +716,6 @@ var Router = class {
355
716
  }
356
717
  };
357
718
  //#endregion
358
- //#region ../core/src/logger/composite.ts
359
- var CompositeLoggerFactory = class {
360
- constructor(factories) {
361
- this.factories = factories;
362
- }
363
- loggerFor(name) {
364
- return new CompositeLogger(this.factories.map((f) => f.loggerFor(name)));
365
- }
366
- };
367
- var CompositeLogger = class {
368
- constructor(loggers) {
369
- this.loggers = loggers;
370
- }
371
- debug(...args) {
372
- return this.callAll("debug", args);
373
- }
374
- info(...args) {
375
- return this.callAll("info", args);
376
- }
377
- warn(...args) {
378
- return this.callAll("warn", args);
379
- }
380
- error(...args) {
381
- return this.callAll("error", args);
382
- }
383
- callAll(method, args) {
384
- for (const logger of this.loggers) logger[method](...args);
385
- return "";
386
- }
387
- };
388
- //#endregion
389
719
  //#region ../core/src/middleware/pipeline.ts
390
720
  /** 执行 beforeLoad 守卫链 */
391
721
  async function runBeforeLoadGuards(guards, ctx) {
@@ -541,6 +871,18 @@ var Framework = class Framework {
541
871
  title: page.title
542
872
  });
543
873
  }
874
+ /** 获取 locale 信息(如果已配置) */
875
+ getLocale() {
876
+ return this.container.has(DEP_KEYS.LOCALE) ? this.container.resolve(DEP_KEYS.LOCALE) : void 0;
877
+ }
878
+ /** 获取翻译器(如果已通过 messages + locale 配置) */
879
+ getTranslator() {
880
+ return this.container.has(DEP_KEYS.TRANSLATOR) ? this.container.resolve(DEP_KEYS.TRANSLATOR) : void 0;
881
+ }
882
+ /** 获取平台信息 */
883
+ getPlatform() {
884
+ return this.container.resolve(DEP_KEYS.PLATFORM);
885
+ }
544
886
  /** 注册 Action 处理器 */
545
887
  onAction(kind, handler) {
546
888
  this.actionDispatcher.onAction(kind, handler);
@@ -606,10 +948,24 @@ var HttpClient = class {
606
948
  baseUrl;
607
949
  defaultHeaders;
608
950
  fetchFn;
951
+ requestInterceptors;
952
+ responseInterceptors;
609
953
  constructor(config) {
610
954
  this.baseUrl = config.baseUrl;
611
955
  this.defaultHeaders = config.defaultHeaders ?? {};
612
956
  this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
957
+ this.requestInterceptors = [...config.requestInterceptors ?? []];
958
+ this.responseInterceptors = [...config.responseInterceptors ?? []];
959
+ }
960
+ /** 动态添加请求拦截器 */
961
+ useRequestInterceptor(interceptor) {
962
+ this.requestInterceptors.push(interceptor);
963
+ return this;
964
+ }
965
+ /** 动态添加响应拦截器 */
966
+ useResponseInterceptor(interceptor) {
967
+ this.responseInterceptors.push(interceptor);
968
+ return this;
613
969
  }
614
970
  /** GET 请求,返回解析后的 JSON */
615
971
  async get(path, params) {
@@ -649,7 +1005,7 @@ var HttpClient = class {
649
1005
  ...this.defaultHeaders,
650
1006
  ...options?.headers
651
1007
  };
652
- const init = {
1008
+ let init = {
653
1009
  method,
654
1010
  headers
655
1011
  };
@@ -657,7 +1013,9 @@ var HttpClient = class {
657
1013
  headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
658
1014
  init.body = JSON.stringify(options.body);
659
1015
  }
660
- const response = await this.fetchFn(url, init);
1016
+ for (const interceptor of this.requestInterceptors) init = await interceptor(url, init);
1017
+ let response = await this.fetchFn(url, init);
1018
+ for (const interceptor of this.responseInterceptors) response = await interceptor(response, url);
661
1019
  if (!response.ok) {
662
1020
  const body = await response.text().catch(() => void 0);
663
1021
  throw new HttpError(response.status, response.statusText, body);
@@ -770,18 +1128,23 @@ function mapEach(mapper) {
770
1128
  * ]);
771
1129
  * ```
772
1130
  */
773
- function defineRoutes(framework, definitions) {
1131
+ function defineRoutes(framework, definitions, options) {
774
1132
  const registeredIntents = /* @__PURE__ */ new Set();
775
1133
  for (const def of definitions) {
776
1134
  if (def.controller && !registeredIntents.has(def.intentId)) {
777
1135
  framework.registerIntent(def.controller);
778
1136
  registeredIntents.add(def.intentId);
779
1137
  }
780
- framework.router.add(def.path, def.intentId, {
1138
+ const routeOpts = {
781
1139
  renderMode: def.renderMode,
782
1140
  beforeGuards: def.beforeLoad,
783
1141
  afterGuards: def.afterLoad
784
- });
1142
+ };
1143
+ framework.router.add(def.path, def.intentId, routeOpts);
1144
+ if (options?.locales?.length) {
1145
+ const localePath = def.path === "/" ? "/:locale" : `/:locale${def.path}`;
1146
+ framework.router.add(localePath, def.intentId, routeOpts);
1147
+ }
785
1148
  }
786
1149
  }
787
1150
  //#endregion
@@ -834,6 +1197,21 @@ function isNone(value) {
834
1197
  return value === null || value === void 0;
835
1198
  }
836
1199
  //#endregion
1200
+ //#region ../core/src/utils/pwa.ts
1201
+ /**
1202
+ * 检测 PWA display mode
1203
+ *
1204
+ * - `standalone`: 已安装的 PWA(通过 Add to Home Screen)
1205
+ * - `twa`: Trusted Web Activity(Android 原生壳)
1206
+ * - `browser`: 普通浏览器标签页
1207
+ */
1208
+ function getPWADisplayMode() {
1209
+ if (typeof window === "undefined") return "browser";
1210
+ if (document.referrer.startsWith("android-app://")) return "twa";
1211
+ if (window.matchMedia("(display-mode: standalone)").matches || "standalone" in window.navigator && window.navigator.standalone === true) return "standalone";
1212
+ return "browser";
1213
+ }
1214
+ //#endregion
837
1215
  //#region ../core/src/utils/url.ts
838
1216
  /**
839
1217
  * URL 工具函数
@@ -953,6 +1331,109 @@ function deny(status = 403, message = "Forbidden") {
953
1331
  };
954
1332
  }
955
1333
  //#endregion
1334
+ //#region ../core/src/metrics/composite-recorder.ts
1335
+ var CompositeEventRecorder = class {
1336
+ recorders;
1337
+ constructor(recorders) {
1338
+ this.recorders = recorders;
1339
+ }
1340
+ record(type, fields) {
1341
+ for (const recorder of this.recorders) recorder.record(type, fields);
1342
+ }
1343
+ async flush() {
1344
+ await Promise.all(this.recorders.map((r) => r.flush?.()));
1345
+ }
1346
+ destroy() {
1347
+ for (const recorder of this.recorders) recorder.destroy?.();
1348
+ }
1349
+ };
1350
+ //#endregion
1351
+ //#region ../core/src/metrics/impression-observer.ts
1352
+ var IntersectionImpressionObserver = class {
1353
+ observer;
1354
+ tracked = /* @__PURE__ */ new Map();
1355
+ captured = [];
1356
+ minDuration;
1357
+ constructor(options = {}) {
1358
+ this.minDuration = options.minVisibleDuration ?? 1e3;
1359
+ this.observer = new IntersectionObserver((entries) => {
1360
+ const now = Date.now();
1361
+ for (const entry of entries) {
1362
+ const tracked = this.tracked.get(entry.target);
1363
+ if (!tracked) continue;
1364
+ if (entry.isIntersecting) {
1365
+ if (tracked.visibleSince === null) tracked.visibleSince = now;
1366
+ } else if (tracked.visibleSince !== null) {
1367
+ if (now - tracked.visibleSince >= this.minDuration) this.captured.push({
1368
+ id: tracked.id,
1369
+ timestamp: tracked.visibleSince,
1370
+ metadata: tracked.metadata
1371
+ });
1372
+ tracked.visibleSince = null;
1373
+ }
1374
+ }
1375
+ }, { threshold: options.threshold ?? .5 });
1376
+ }
1377
+ observe(element, id, metadata) {
1378
+ this.tracked.set(element, {
1379
+ id,
1380
+ metadata,
1381
+ visibleSince: null
1382
+ });
1383
+ this.observer.observe(element);
1384
+ }
1385
+ unobserve(element) {
1386
+ this.observer.unobserve(element);
1387
+ this.tracked.delete(element);
1388
+ }
1389
+ consume() {
1390
+ const now = Date.now();
1391
+ for (const [, tracked] of this.tracked) if (tracked.visibleSince !== null) {
1392
+ if (now - tracked.visibleSince >= this.minDuration) {
1393
+ this.captured.push({
1394
+ id: tracked.id,
1395
+ timestamp: tracked.visibleSince,
1396
+ metadata: tracked.metadata
1397
+ });
1398
+ tracked.visibleSince = now;
1399
+ }
1400
+ }
1401
+ return this.captured.splice(0);
1402
+ }
1403
+ destroy() {
1404
+ this.observer.disconnect();
1405
+ this.tracked.clear();
1406
+ this.captured.length = 0;
1407
+ }
1408
+ };
1409
+ //#endregion
1410
+ //#region ../core/src/metrics/void-recorder.ts
1411
+ var VoidEventRecorder = class {
1412
+ record() {}
1413
+ async flush() {}
1414
+ destroy() {}
1415
+ };
1416
+ //#endregion
1417
+ //#region ../core/src/metrics/with-fields-recorder.ts
1418
+ var WithFieldsRecorder = class {
1419
+ constructor(inner, providers) {
1420
+ this.inner = inner;
1421
+ this.providers = providers;
1422
+ }
1423
+ record(type, fields) {
1424
+ let merged = {};
1425
+ for (const provider of this.providers) Object.assign(merged, provider.getFields());
1426
+ if (fields) Object.assign(merged, fields);
1427
+ this.inner.record(type, merged);
1428
+ }
1429
+ async flush() {
1430
+ return this.inner.flush?.();
1431
+ }
1432
+ destroy() {
1433
+ this.inner.destroy?.();
1434
+ }
1435
+ };
1436
+ //#endregion
956
1437
  //#region ../browser/src/action-handlers/external-url-action.ts
957
1438
  function registerExternalUrlHandler(deps) {
958
1439
  const { framework, log } = deps;
@@ -1395,11 +1876,20 @@ function createPrefetchedIntentsFromDom() {
1395
1876
  * 自动执行 hydration 全流程。
1396
1877
  */
1397
1878
  async function startBrowserApp(config) {
1398
- const { bootstrap, mountId = "app", mount, callbacks } = config;
1879
+ const { bootstrap, mountId = "app", mount, callbacks, onBeforeStart, onAfterStart, frameworkConfig } = config;
1399
1880
  const prefetchedIntents = createPrefetchedIntentsFromDom();
1400
- const framework = Framework.create({ prefetchedIntents });
1881
+ const framework = Framework.create({
1882
+ ...frameworkConfig,
1883
+ prefetchedIntents
1884
+ });
1401
1885
  bootstrap(framework);
1402
1886
  const log = framework.container.resolve(DEP_KEYS.LOGGER_FACTORY).loggerFor("browser");
1887
+ const locale = framework.getLocale();
1888
+ if (locale) {
1889
+ setHtmlLocaleAttributes(locale);
1890
+ log.debug("[startBrowserApp] Applied locale attributes:", locale);
1891
+ }
1892
+ await onBeforeStart?.(framework);
1403
1893
  const initialAction = framework.routeUrl(window.location.pathname + window.location.search);
1404
1894
  const target = document.getElementById(mountId);
1405
1895
  if (!target) throw new Error(`[startBrowserApp] Mount target not found: #${mountId}. Ensure your HTML has <div id="${mountId}"></div>.`);
@@ -1416,6 +1906,7 @@ async function startBrowserApp(config) {
1416
1906
  page: Promise.reject(/* @__PURE__ */ new Error("404")),
1417
1907
  isFirstPage: true
1418
1908
  });
1909
+ await onAfterStart?.(framework);
1419
1910
  }
1420
1911
  //#endregion
1421
1912
  //#region ../ssr/src/render.ts
@@ -1428,11 +1919,16 @@ async function startBrowserApp(config) {
1428
1919
  * 4. 调用应用层提供的渲染函数
1429
1920
  */
1430
1921
  async function ssrRender(options) {
1431
- const { url, frameworkConfig, bootstrap, getErrorPage, renderApp, ssrContext } = options;
1432
- const mergedConfig = ssrContext?.fetch ? {
1922
+ const { url, frameworkConfig, bootstrap, getErrorPage, renderApp, ssrContext, resolveLocale } = options;
1923
+ const resolvedLocale = resolveLocale?.(url, ssrContext?.request);
1924
+ const effectiveConfig = resolvedLocale ? {
1433
1925
  ...frameworkConfig,
1434
- fetch: ssrContext.fetch
1926
+ locale: resolvedLocale.lang
1435
1927
  } : frameworkConfig;
1928
+ const mergedConfig = ssrContext?.fetch ? {
1929
+ ...effectiveConfig,
1930
+ fetch: ssrContext.fetch
1931
+ } : effectiveConfig;
1436
1932
  const framework = Framework.create(mergedConfig);
1437
1933
  bootstrap(framework);
1438
1934
  try {
@@ -1481,13 +1977,15 @@ async function ssrRender(options) {
1481
1977
  }
1482
1978
  } else page = getErrorPage(404, "Page not found");
1483
1979
  const result = await renderApp(page, framework);
1980
+ const locale = resolvedLocale ?? framework.getLocale();
1484
1981
  return {
1485
1982
  html: result.html,
1486
1983
  head: result.head,
1487
1984
  css: result.css,
1488
1985
  serverData,
1489
1986
  renderMode: match?.renderMode,
1490
- slots: result.slots
1987
+ slots: result.slots,
1988
+ locale
1491
1989
  };
1492
1990
  } finally {
1493
1991
  framework.dispose();
@@ -1540,14 +2038,15 @@ async function handleMiddlewareResult(result, getErrorPage, renderApp, framework
1540
2038
  * @returns `render(url, ssrContext?)` — 供 @finesoft/server SSRModule 使用
1541
2039
  */
1542
2040
  function createSSRRender(config) {
1543
- const { bootstrap, getErrorPage, renderApp, frameworkConfig } = config;
2041
+ const { bootstrap, getErrorPage, renderApp, frameworkConfig, resolveLocale } = config;
1544
2042
  return (url, ssrContext) => ssrRender({
1545
2043
  url,
1546
2044
  frameworkConfig: frameworkConfig ?? {},
1547
2045
  bootstrap,
1548
2046
  getErrorPage,
1549
- renderApp: (page) => renderApp(page),
1550
- ssrContext
2047
+ renderApp: (page, framework) => renderApp(page, framework),
2048
+ ssrContext,
2049
+ resolveLocale
1551
2050
  });
1552
2051
  }
1553
2052
  //#endregion
@@ -1564,21 +2063,33 @@ const SSR_PLACEHOLDERS = {
1564
2063
  /** 匹配所有 <!--ssr-xxx--> 占位符(含内置与自定义) */
1565
2064
  const PLACEHOLDER_REGEX = /<!--ssr-([a-z][a-z0-9-]*)-->/g;
1566
2065
  function injectSSRContent(options) {
1567
- const { template, head, css, html, serializedData, slots } = options;
2066
+ const { template, head, css, html, serializedData, slots, locale } = options;
1568
2067
  const replacements = {
1569
2068
  head: `${head}\n${css ? `<style>${css}</style>` : ""}`,
1570
2069
  body: html,
1571
2070
  data: `<script id="serialized-server-data" type="application/json">${serializedData}<\/script>`,
1572
2071
  ...slots
1573
2072
  };
1574
- return template.replace(PLACEHOLDER_REGEX, (_, name) => replacements[name] ?? "");
2073
+ let result = template.replace(PLACEHOLDER_REGEX, (_, name) => replacements[name] ?? "");
2074
+ if (locale) result = applyLocaleToHtml(result, locale);
2075
+ return result;
1575
2076
  }
1576
2077
  /**
1577
2078
  * CSR 空壳注入 — 清空所有占位符
1578
2079
  * 用于 renderMode === "csr" 的路由
2080
+ *
2081
+ * @param locale - 可选的 locale 属性,注入到 `<html lang="" dir="">`
1579
2082
  */
1580
- function injectCSRShell(template) {
1581
- return template.replace(PLACEHOLDER_REGEX, () => "");
2083
+ function injectCSRShell(template, locale) {
2084
+ let result = template.replace(PLACEHOLDER_REGEX, () => "");
2085
+ if (locale) result = applyLocaleToHtml(result, locale);
2086
+ return result;
2087
+ }
2088
+ /** 将 lang/dir 注入到 <html> 标签 */
2089
+ function applyLocaleToHtml(html, locale) {
2090
+ return html.replace(/(<html)([^>]*)(>)/i, (_match, open, attrs, close) => {
2091
+ return `${open}${attrs.replace(/\s+lang="[^"]*"/gi, "").replace(/\s+dir="[^"]*"/gi, "")} lang="${locale.lang}" dir="${locale.dir}"${close}`;
2092
+ });
1582
2093
  }
1583
2094
  //#endregion
1584
2095
  //#region ../ssr/src/server-data.ts
@@ -1823,22 +2334,40 @@ ${setupImport}
1823
2334
 
1824
2335
  const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
1825
2336
  const RENDER_MODES = ${renderModes};
2337
+ const DEFAULT_LOCALE = ${JSON.stringify(ctx.defaultLocale ?? null)};
1826
2338
  ${cacheImpl}
1827
2339
 
1828
- function injectSSR(t, head, css, html, data) {
1829
- return t
2340
+ function injectSSR(t, head, css, html, data, locale) {
2341
+ const injected = t
1830
2342
  .replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, (_, name) => {
1831
2343
  const replacements = {
1832
- head: head + "\\n<style>" + css + "</style>",
2344
+ head: head + "\n<style>" + css + "</style>",
1833
2345
  body: html,
1834
2346
  data: '<script id="serialized-server-data" type="application/json">' + data + "<\/script>",
1835
2347
  };
1836
2348
  return replacements[name] ?? "";
1837
2349
  });
2350
+ return applyLocaleToHtml(injected, locale);
2351
+ }
2352
+
2353
+ function applyLocaleToHtml(html, locale) {
2354
+ if (!locale) return html;
2355
+ return html.replace(/<html([^>]*)>/, (_, attrs) => {
2356
+ let a = attrs.replace(/s*lang="[^"]*"/g, "").replace(/s*dir="[^"]*"/g, "");
2357
+ return "<html" + a + ' lang="' + locale.lang + '" dir="' + locale.dir + '">';
2358
+ });
2359
+ }
2360
+
2361
+ function getLocaleAttrs(lang) {
2362
+ if (!lang) return undefined;
2363
+ const RTL = new Set(["ar","arc","dv","fa","ha","he","khw","ks","ku","ps","ur","yi"]);
2364
+ const base = lang.split(/[-_]/)[0].toLowerCase();
2365
+ return { lang: lang, dir: RTL.has(base) ? "rtl" : "ltr" };
1838
2366
  }
1839
2367
 
1840
- function injectCSRShell(t) {
1841
- return t.replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, () => "");
2368
+ function injectCSRShell(t, locale) {
2369
+ const stripped = t.replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, () => "");
2370
+ return applyLocaleToHtml(stripped, locale);
1842
2371
  }
1843
2372
 
1844
2373
  function matchRenderMode(url) {
@@ -1887,22 +2416,23 @@ app.get("*", async (c) => {
1887
2416
  // Vite 配置级别覆盖: CSR 直接返回空壳
1888
2417
  const overrideMode = matchRenderMode(url);
1889
2418
  if (overrideMode === "csr") {
1890
- return c.html(injectCSRShell(TEMPLATE));
2419
+ return c.html(injectCSRShell(TEMPLATE, getLocaleAttrs(DEFAULT_LOCALE)));
1891
2420
  }
1892
2421
 
1893
2422
  // ISR 缓存命中
1894
2423
  const cached = await platformCacheGet(url);
1895
2424
  if (cached) return c.html(cached);
1896
2425
 
1897
- const { html: appHtml, head, css, serverData, renderMode } = await render(url, { fetch: _createInternalFetch(_ssrDepth + 1) });
2426
+ const { html: appHtml, head, css, serverData, renderMode, locale } = await render(url, { fetch: _createInternalFetch(_ssrDepth + 1) });
2427
+ const localeAttrs = getLocaleAttrs(locale || DEFAULT_LOCALE);
1898
2428
 
1899
2429
  // 路由级 CSR
1900
2430
  if (renderMode === "csr") {
1901
- return c.html(injectCSRShell(TEMPLATE));
2431
+ return c.html(injectCSRShell(TEMPLATE, localeAttrs));
1902
2432
  }
1903
2433
 
1904
2434
  const serializedData = serializeServerData(serverData);
1905
- const finalHtml = injectSSR(TEMPLATE, head, css, appHtml, serializedData);
2435
+ const finalHtml = injectSSR(TEMPLATE, head, css, appHtml, serializedData, localeAttrs);
1906
2436
 
1907
2437
  // Prerender ISR 缓存(包括 Vite 配置覆盖和路由级)
1908
2438
  if (renderMode === "prerender" || overrideMode === "prerender") {
@@ -1978,20 +2508,34 @@ async function prerenderRoutes(ctx) {
1978
2508
  if (ctx.renderModes) {
1979
2509
  for (const [pattern, mode] of Object.entries(ctx.renderModes)) if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) prerenderPaths.add(pattern);
1980
2510
  }
2511
+ if (ctx.locales?.length) {
2512
+ const basePaths = [...prerenderPaths];
2513
+ for (const locale of ctx.locales) for (const basePath of basePaths) {
2514
+ const localePath = basePath === "/" ? `/${locale}` : `/${locale}${basePath}`;
2515
+ prerenderPaths.add(localePath);
2516
+ }
2517
+ }
1981
2518
  if (prerenderPaths.size === 0) return [];
1982
2519
  const ssrPath = pathToFileURL(path.resolve(root, "dist/server/ssr.js")).href;
1983
2520
  const ssrModule = await dynamicImport(ssrPath);
1984
2521
  const results = [];
1985
2522
  for (const url of prerenderPaths) try {
1986
- const { html: appHtml, head, css, serverData } = await ssrModule.render(url);
2523
+ const { html: appHtml, head, css, serverData, locale } = await ssrModule.render(url);
1987
2524
  const serializedData = ssrModule.serializeServerData(serverData);
1988
- const finalHtml = ctx.templateHtml.replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, (_match, name) => {
2525
+ let finalHtml = ctx.templateHtml.replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, (_match, name) => {
1989
2526
  return {
1990
2527
  head: head + "\n<style>" + css + "</style>",
1991
2528
  body: appHtml,
1992
2529
  data: "<script id=\"serialized-server-data\" type=\"application/json\">" + serializedData + "<\/script>"
1993
2530
  }[name] ?? "";
1994
2531
  });
2532
+ if (locale) {
2533
+ const { getLocaleAttributes } = await dynamicImport("@finesoft/core");
2534
+ const attrs = getLocaleAttributes(locale);
2535
+ finalHtml = finalHtml.replace(/<html([^>]*)>/, (_m, a) => {
2536
+ return `<html${a.replace(/\s*lang="[^"]*"/g, "").replace(/\s*dir="[^"]*"/g, "")} lang="${attrs.lang}" dir="${attrs.dir}">`;
2537
+ });
2538
+ }
1995
2539
  results.push({
1996
2540
  url,
1997
2541
  html: finalHtml
@@ -2443,13 +2987,13 @@ function autoAdapter() {
2443
2987
  return {
2444
2988
  name: "auto",
2445
2989
  async build(ctx) {
2446
- const detected = detectPlatform();
2990
+ const detected = detectPlatform$1();
2447
2991
  console.log(` [auto] Detected platform: ${detected}\n`);
2448
2992
  return resolveAdapter(detected).build(ctx);
2449
2993
  }
2450
2994
  };
2451
2995
  }
2452
- function detectPlatform() {
2996
+ function detectPlatform$1() {
2453
2997
  if (process.env.VERCEL) return "vercel";
2454
2998
  if (process.env.CF_PAGES) return "cloudflare";
2455
2999
  if (process.env.NETLIFY) return "netlify";
@@ -2508,7 +3052,7 @@ function matchRenderModeOverride(url, renderModes) {
2508
3052
  return null;
2509
3053
  }
2510
3054
  function createSSRApp(options) {
2511
- const { root, vite, isProduction, ssrEntryPath = "/src/ssr.ts", ssrProductionModule, parentFetch, renderModes } = options;
3055
+ const { root, vite, isProduction, ssrEntryPath = "/src/ssr.ts", ssrProductionModule, parentFetch, renderModes, defaultLocale } = options;
2512
3056
  const app = new Hono();
2513
3057
  /** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */
2514
3058
  const ISR_CACHE_MAX = 1e3;
@@ -2556,22 +3100,23 @@ function createSSRApp(options) {
2556
3100
  if (typeof ssrMod.render !== "function" || typeof ssrMod.serializeServerData !== "function") throw new Error("[SSR] Module missing required exports: render, serializeServerData");
2557
3101
  const { render, serializeServerData } = ssrMod;
2558
3102
  const overrideMode = matchRenderModeOverride(url, renderModes);
2559
- if (overrideMode === "csr") return c.html(injectCSRShell(template));
3103
+ if (overrideMode === "csr") return c.html(injectCSRShell(template, defaultLocale ? getLocaleAttributes(defaultLocale) : void 0));
2560
3104
  const cached = isrCache.get(url);
2561
3105
  if (cached) return c.html(cached);
2562
3106
  const requestFetch = parentFetch ? createInternalFetch(parentFetch, ssrDepth + 1) : void 0;
2563
3107
  const ssrContext = { request: c.req.raw };
2564
3108
  if (requestFetch) ssrContext.fetch = requestFetch;
2565
- const { html: appHtml, head, css, serverData, renderMode, redirect: middlewareRedirect, slots } = await render(url, ssrContext);
3109
+ const { html: appHtml, head, css, serverData, renderMode, redirect: middlewareRedirect, slots, locale } = await render(url, ssrContext);
2566
3110
  if (middlewareRedirect) return c.redirect(middlewareRedirect.url, middlewareRedirect.status);
2567
- if (renderMode === "csr") return c.html(injectCSRShell(template));
3111
+ if (renderMode === "csr") return c.html(injectCSRShell(template, locale));
2568
3112
  const finalHtml = injectSSRContent({
2569
3113
  template,
2570
3114
  head,
2571
3115
  css,
2572
3116
  html: appHtml,
2573
3117
  serializedData: serializeServerData(serverData),
2574
- slots
3118
+ slots,
3119
+ locale
2575
3120
  });
2576
3121
  if (renderMode === "prerender" || overrideMode === "prerender") isrSet(url, finalHtml);
2577
3122
  return c.html(finalHtml);
@@ -2883,7 +3428,8 @@ function finesoftFrontViteConfig(options = {}) {
2883
3428
  isProduction: false,
2884
3429
  ssrEntryPath: "/" + ssrEntry,
2885
3430
  parentFetch: app.fetch.bind(app),
2886
- renderModes: options.renderModes
3431
+ renderModes: options.renderModes,
3432
+ defaultLocale: options.defaultLocale
2887
3433
  });
2888
3434
  app.route("/", ssrApp);
2889
3435
  const listener = getRequestListener(app.fetch);
@@ -2927,17 +3473,18 @@ function finesoftFrontViteConfig(options = {}) {
2927
3473
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
2928
3474
  try {
2929
3475
  const overrideMode = matchRenderModeConfig(url, options.renderModes);
2930
- if (overrideMode === "csr") return c.html(injectCSRShell(template));
3476
+ if (overrideMode === "csr") return c.html(injectCSRShell(template, options.defaultLocale ? getLocaleAttributes(options.defaultLocale) : void 0));
2931
3477
  const cached = isrCache.get(url);
2932
3478
  if (cached) return c.html(cached);
2933
- const { html: appHtml, head, css, serverData, renderMode } = await ssrModule.render(url, { fetch: createInternalFetch(app.fetch.bind(app), ssrDepth + 1) });
2934
- if (renderMode === "csr") return c.html(injectCSRShell(template));
3479
+ const { html: appHtml, head, css, serverData, renderMode, locale } = await ssrModule.render(url, { fetch: createInternalFetch(app.fetch.bind(app), ssrDepth + 1) });
3480
+ if (renderMode === "csr") return c.html(injectCSRShell(template, locale));
2935
3481
  const finalHtml = injectSSRContent({
2936
3482
  template,
2937
3483
  head,
2938
3484
  css,
2939
3485
  html: appHtml,
2940
- serializedData: ssrModule.serializeServerData(serverData)
3486
+ serializedData: ssrModule.serializeServerData(serverData),
3487
+ locale
2941
3488
  });
2942
3489
  if (renderMode === "prerender" || overrideMode === "prerender") isrSet(url, finalHtml);
2943
3490
  return c.html(finalHtml);
@@ -2995,6 +3542,8 @@ function finesoftFrontViteConfig(options = {}) {
2995
3542
  templateHtml,
2996
3543
  renderModes: options.renderModes,
2997
3544
  proxies: options.proxies,
3545
+ locales: options.locales,
3546
+ defaultLocale: options.defaultLocale,
2998
3547
  resolvedResolve,
2999
3548
  resolvedCss,
3000
3549
  vite,
@@ -3020,6 +3569,6 @@ function finesoftFrontViteConfig(options = {}) {
3020
3569
  };
3021
3570
  }
3022
3571
  //#endregion
3023
- export { ACTION_KINDS, ActionDispatcher, BaseController, BaseLogger, CompositeLogger, CompositeLoggerFactory, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, Framework, History, HttpClient, HttpError, IntentDispatcher, LruMap, PrefetchedIntents, Router, SSR_PLACEHOLDERS, autoAdapter, buildUrl, cloudflareAdapter, createBrowserContext, createPrefetchedIntentsFromDom, createSSRApp, createSSRRender, createServer, createServerContext, defineRoutes, deny, deserializeServerData, detectRuntime, finesoftFrontViteConfig, generateProxyCode, generateUuid, getBaseUrl, injectCSRShell, injectSSRContent, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, netlifyAdapter, next, nodeAdapter, parseAcceptLanguage, pipe, pipeAsync, redirect, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, registerProxyRoutes, removeHost, removeQueryParams, removeScheme, resetFilterCache, resolveAdapter, resolveRoot, rewrite, runAfterLoadGuards, runBeforeLoadGuards, serializeServerData, shouldLog, ssrRender, stableStringify, startBrowserApp, startServer, staticAdapter, tryScroll, vercelAdapter };
3572
+ export { ACTION_KINDS, ActionDispatcher, BaseController, BaseLogger, CompositeEventRecorder, CompositeLogger, CompositeLoggerFactory, ConsoleEventRecorder, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, Framework, History, HttpClient, HttpError, IntentDispatcher, IntersectionImpressionObserver, LruMap, PrefetchedIntents, ReportingLogger, ReportingLoggerFactory, Router, SSR_PLACEHOLDERS, SimpleTranslator, VoidEventRecorder, WithFieldsRecorder, autoAdapter, buildUrl, cloudflareAdapter, createBrowserContext, createPrefetchedIntentsFromDom, createSSRApp, createSSRRender, createServer, createServerContext, defineRoutes, deny, deserializeServerData, detectPlatform, detectRuntime, englishPlural, finesoftFrontViteConfig, generateProxyCode, generateUuid, getBaseUrl, getLocaleAttributes, getPWADisplayMode, getTextDirection, injectCSRShell, injectSSRContent, interpolate, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isRtl, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, makeLocaleInfo, mapEach, netlifyAdapter, next, nodeAdapter, parseAcceptLanguage, pipe, pipeAsync, redirect, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, registerProxyRoutes, removeHost, removeQueryParams, removeScheme, resetFilterCache, resolveAdapter, resolveLocaleFromUrl, resolvePluralKey, resolveRoot, rewrite, runAfterLoadGuards, runBeforeLoadGuards, serializeServerData, setHtmlLocaleAttributes, shouldLog, ssrRender, stableStringify, startBrowserApp, startServer, staticAdapter, tryScroll, vercelAdapter };
3024
3573
 
3025
3574
  //# sourceMappingURL=index.mjs.map