@finesoft/front 0.1.56 → 0.1.58

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
@@ -231,6 +231,61 @@ function resolveLocaleFromUrl(url, supportedLocales) {
231
231
  };
232
232
  }
233
233
  //#endregion
234
+ //#region ../core/src/i18n/generated-loader.ts
235
+ async function resolveGeneratedMessages(locale, context) {
236
+ if (!locale || !context) return;
237
+ const loader = await getGeneratedMessagesLoader();
238
+ if (!loader) return;
239
+ return loader(locale, context);
240
+ }
241
+ async function getGeneratedMessagesLoader() {
242
+ if (typeof globalThis.__FINESOFT_I18N_LOADER__ === "function") return globalThis.__FINESOFT_I18N_LOADER__;
243
+ if (typeof __FINESOFT_I18N_LOADER_IMPORTER__ !== "function") return;
244
+ try {
245
+ const imported = await __FINESOFT_I18N_LOADER_IMPORTER__();
246
+ const loader = typeof imported === "function" ? imported : typeof imported?.loadMessages === "function" ? imported.loadMessages : void 0;
247
+ if (!loader) return;
248
+ globalThis.__FINESOFT_I18N_LOADER__ = loader;
249
+ return loader;
250
+ } catch {
251
+ return;
252
+ }
253
+ }
254
+ //#endregion
255
+ //#region ../core/src/i18n/messages.ts
256
+ /**
257
+ * i18n message helpers shared by SSR and browser startup.
258
+ */
259
+ /**
260
+ * Resolve the effective translation source for a locale.
261
+ */
262
+ async function resolveConfiguredMessages(options) {
263
+ const { locale, loadMessages, context } = options;
264
+ if (!locale || !context) return;
265
+ if (loadMessages) return loadMessages(locale, context);
266
+ return resolveGeneratedMessages(locale, context);
267
+ }
268
+ /**
269
+ * Resolve `TranslationMessages` into the flat map consumed by
270
+ * `SimpleTranslator`.
271
+ */
272
+ function resolveMessages(messages, locale) {
273
+ const entries = Object.entries(messages);
274
+ if (entries.length === 0) return void 0;
275
+ if (typeof entries[0][1] === "string") return messages;
276
+ const localeMessages = messages[locale];
277
+ if (!localeMessages) return void 0;
278
+ const flat = {};
279
+ for (const [key, value] of Object.entries(localeMessages)) {
280
+ if (typeof value === "string") {
281
+ flat[key] = value;
282
+ continue;
283
+ }
284
+ for (const [suffix, text] of Object.entries(value)) flat[`${key}.${suffix}`] = text;
285
+ }
286
+ return flat;
287
+ }
288
+ //#endregion
234
289
  //#region ../core/src/i18n/interpolate.ts
235
290
  /**
236
291
  * ICU 消息格式插值
@@ -594,25 +649,9 @@ var ConsoleMetrics = class {
594
649
  });
595
650
  }
596
651
  };
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
- }
614
652
  function makeDependencies(container, options = {}) {
615
- const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {}, featureFlagsProviders = [], reportCallback, eventRecorder, locale, platform, messages } = options;
653
+ const { _resolvedMessages: messages } = options;
654
+ const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {}, featureFlagsProviders = [], reportCallback, eventRecorder, locale, platform } = options;
616
655
  const consoleFactory = new ConsoleLoggerFactory();
617
656
  const loggerFactory = reportCallback ? new CompositeLoggerFactory([consoleFactory, new ReportingLoggerFactory({ report: reportCallback })]) : consoleFactory;
618
657
  container.register(DEP_KEYS.LOGGER_FACTORY, () => loggerFactory);
@@ -875,7 +914,7 @@ var Framework = class Framework {
875
914
  getLocale() {
876
915
  return this.container.has(DEP_KEYS.LOCALE) ? this.container.resolve(DEP_KEYS.LOCALE) : void 0;
877
916
  }
878
- /** 获取翻译器(如果已通过 messages + locale 配置) */
917
+ /** 获取翻译器(如果当前 locale 已经初始化了翻译字典) */
879
918
  getTranslator() {
880
919
  return this.container.has(DEP_KEYS.TRANSLATOR) ? this.container.resolve(DEP_KEYS.TRANSLATOR) : void 0;
881
920
  }
@@ -1110,6 +1149,17 @@ function mapEach(mapper) {
1110
1149
  return (items) => items.map(mapper);
1111
1150
  }
1112
1151
  //#endregion
1152
+ //#region ../core/src/bootstrap/define-bootstrap.ts
1153
+ const BOOTSTRAP_CONFIG = Symbol.for("@finesoft/bootstrap-config");
1154
+ function defineBootstrap(config, bootstrap) {
1155
+ const configuredBootstrap = bootstrap;
1156
+ configuredBootstrap[BOOTSTRAP_CONFIG] = config;
1157
+ return configuredBootstrap;
1158
+ }
1159
+ function getBootstrapConfig(bootstrap) {
1160
+ return bootstrap[BOOTSTRAP_CONFIG] ?? {};
1161
+ }
1162
+ //#endregion
1113
1163
  //#region ../core/src/bootstrap/define-routes.ts
1114
1164
  /**
1115
1165
  * 声明式注册路由和 Controller
@@ -1876,21 +1926,40 @@ function createPrefetchedIntentsFromDom() {
1876
1926
  * 自动执行 hydration 全流程。
1877
1927
  */
1878
1928
  async function startBrowserApp(config) {
1879
- const { bootstrap, mountId = "app", mount, callbacks, onBeforeStart, onAfterStart, frameworkConfig } = config;
1929
+ const { bootstrap, mountId = "app", mount, callbacks, onBeforeStart, onAfterStart, frameworkConfig, loadMessages } = config;
1930
+ const bootstrapConfig = getBootstrapConfig(bootstrap);
1931
+ const resolvedFrameworkConfig = {
1932
+ ...bootstrapConfig.frameworkConfig,
1933
+ ...frameworkConfig
1934
+ };
1935
+ const resolvedLoadMessages = loadMessages ?? bootstrapConfig.loadMessages;
1880
1936
  const prefetchedIntents = createPrefetchedIntentsFromDom();
1937
+ const initialUrl = window.location.pathname + window.location.search;
1938
+ const locale = resolveBrowserLocale(resolvedFrameworkConfig.locale);
1939
+ const resolvedMessages = await resolveConfiguredMessages({
1940
+ locale,
1941
+ loadMessages: resolvedLoadMessages,
1942
+ context: locale ? {
1943
+ runtime: "browser",
1944
+ fetch: getBrowserFetch(resolvedFrameworkConfig.fetch),
1945
+ url: initialUrl
1946
+ } : void 0
1947
+ });
1881
1948
  const framework = Framework.create({
1882
- ...frameworkConfig,
1949
+ ...resolvedFrameworkConfig,
1950
+ locale,
1951
+ _resolvedMessages: resolvedMessages,
1883
1952
  prefetchedIntents
1884
1953
  });
1885
1954
  bootstrap(framework);
1886
1955
  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);
1956
+ const resolvedLocale = framework.getLocale();
1957
+ if (resolvedLocale) {
1958
+ setHtmlLocaleAttributes(resolvedLocale);
1959
+ log.debug("[startBrowserApp] Applied locale attributes:", resolvedLocale);
1891
1960
  }
1892
1961
  await onBeforeStart?.(framework);
1893
- const initialAction = framework.routeUrl(window.location.pathname + window.location.search);
1962
+ const initialAction = framework.routeUrl(initialUrl);
1894
1963
  const target = document.getElementById(mountId);
1895
1964
  if (!target) throw new Error(`[startBrowserApp] Mount target not found: #${mountId}. Ensure your HTML has <div id="${mountId}"></div>.`);
1896
1965
  const updateApp = mount(target, { framework });
@@ -1908,6 +1977,17 @@ async function startBrowserApp(config) {
1908
1977
  });
1909
1978
  await onAfterStart?.(framework);
1910
1979
  }
1980
+ function resolveBrowserLocale(locale) {
1981
+ if (locale) return locale;
1982
+ return document.documentElement.lang.trim() || void 0;
1983
+ }
1984
+ function getBrowserFetch(fetchFn) {
1985
+ const resolvedFetch = fetchFn ?? globalThis.fetch?.bind(globalThis);
1986
+ if (resolvedFetch) return resolvedFetch;
1987
+ return (() => {
1988
+ throw new Error("[startBrowserApp] loadMessages requires a fetch implementation.");
1989
+ });
1990
+ }
1911
1991
  //#endregion
1912
1992
  //#region ../ssr/src/render.ts
1913
1993
  /**
@@ -1919,21 +1999,40 @@ async function startBrowserApp(config) {
1919
1999
  * 4. 调用应用层提供的渲染函数
1920
2000
  */
1921
2001
  async function ssrRender(options) {
1922
- const { url, frameworkConfig, bootstrap, getErrorPage, renderApp, ssrContext, resolveLocale } = options;
2002
+ const { url, frameworkConfig, bootstrap, getErrorPage, renderApp, ssrContext, resolveLocale, loadMessages } = options;
2003
+ const bootstrapConfig = getBootstrapConfig(bootstrap);
2004
+ const resolvedFrameworkConfig = {
2005
+ ...bootstrapConfig.frameworkConfig,
2006
+ ...frameworkConfig
2007
+ };
2008
+ const resolvedLoadMessages = loadMessages ?? bootstrapConfig.loadMessages;
2009
+ const parsed = new URL(url, "http://localhost");
2010
+ const fullPath = parsed.pathname + parsed.search;
1923
2011
  const resolvedLocale = resolveLocale?.(url, ssrContext?.request);
1924
2012
  const effectiveConfig = resolvedLocale ? {
1925
- ...frameworkConfig,
2013
+ ...resolvedFrameworkConfig,
1926
2014
  locale: resolvedLocale.lang
1927
- } : frameworkConfig;
1928
- const mergedConfig = ssrContext?.fetch ? {
2015
+ } : resolvedFrameworkConfig;
2016
+ const resolvedMessages = await resolveConfiguredMessages({
2017
+ locale: effectiveConfig.locale,
2018
+ loadMessages: resolvedLoadMessages,
2019
+ context: effectiveConfig.locale ? {
2020
+ runtime: "server",
2021
+ fetch: getSSRFetch(ssrContext?.fetch ?? effectiveConfig.fetch),
2022
+ url: fullPath,
2023
+ request: ssrContext?.request
2024
+ } : void 0
2025
+ });
2026
+ const mergedConfig = {
1929
2027
  ...effectiveConfig,
1930
- fetch: ssrContext.fetch
1931
- } : effectiveConfig;
1932
- const framework = Framework.create(mergedConfig);
2028
+ fetch: ssrContext?.fetch ?? effectiveConfig.fetch
2029
+ };
2030
+ const framework = Framework.create({
2031
+ ...mergedConfig,
2032
+ _resolvedMessages: resolvedMessages
2033
+ });
1933
2034
  bootstrap(framework);
1934
2035
  try {
1935
- const parsed = new URL(url, "http://localhost");
1936
- const fullPath = parsed.pathname + parsed.search;
1937
2036
  const match = framework.routeUrl(fullPath);
1938
2037
  if (match?.renderMode === "csr") return {
1939
2038
  html: "",
@@ -1991,6 +2090,13 @@ async function ssrRender(options) {
1991
2090
  framework.dispose();
1992
2091
  }
1993
2092
  }
2093
+ function getSSRFetch(fetchFn) {
2094
+ const resolvedFetch = fetchFn ?? globalThis.fetch?.bind(globalThis);
2095
+ if (resolvedFetch) return resolvedFetch;
2096
+ return (() => {
2097
+ throw new Error("[ssrRender] loadMessages requires a fetch implementation.");
2098
+ });
2099
+ }
1994
2100
  /**
1995
2101
  * 将中间件结果转换为 SSRRenderResult(如果需要短路返回)。
1996
2102
  * 返回 null 表示继续正常流程。
@@ -2038,7 +2144,7 @@ async function handleMiddlewareResult(result, getErrorPage, renderApp, framework
2038
2144
  * @returns `render(url, ssrContext?)` — 供 @finesoft/server SSRModule 使用
2039
2145
  */
2040
2146
  function createSSRRender(config) {
2041
- const { bootstrap, getErrorPage, renderApp, frameworkConfig, resolveLocale } = config;
2147
+ const { bootstrap, getErrorPage, renderApp, frameworkConfig, resolveLocale, loadMessages } = config;
2042
2148
  return (url, ssrContext) => ssrRender({
2043
2149
  url,
2044
2150
  frameworkConfig: frameworkConfig ?? {},
@@ -2046,7 +2152,8 @@ function createSSRRender(config) {
2046
2152
  getErrorPage,
2047
2153
  renderApp: (page, framework) => renderApp(page, framework),
2048
2154
  ssrContext,
2049
- resolveLocale
2155
+ resolveLocale,
2156
+ loadMessages
2050
2157
  });
2051
2158
  }
2052
2159
  //#endregion
@@ -3329,6 +3436,8 @@ function parseAcceptLanguage(header, supported, fallback) {
3329
3436
  * 支持多平台 adapter: "vercel" | "cloudflare" | "netlify" | "node" | "static" | "auto"
3330
3437
  * 或自定义 Adapter 对象。
3331
3438
  */
3439
+ const GENERATED_I18N_LOADER_ID = "virtual:finesoft-front/i18n-loader";
3440
+ const RESOLVED_GENERATED_I18N_LOADER_ID = `\0${GENERATED_I18N_LOADER_ID}`;
3332
3441
  /**
3333
3442
  * 从 setup 模块中查找 setup 函数:优先 default,其次 setup 命名导出。
3334
3443
  */
@@ -3351,6 +3460,18 @@ function matchRenderModeConfig(url, renderModes) {
3351
3460
  }
3352
3461
  return null;
3353
3462
  }
3463
+ function normalizePathForGlob(pathname) {
3464
+ return pathname.replace(/\\/g, "/");
3465
+ }
3466
+ async function resolveMessagesDir(root, messagesDir) {
3467
+ const { existsSync } = await dynamicImport("node:fs");
3468
+ const { isAbsolute, relative, resolve } = await dynamicImport("node:path");
3469
+ const absoluteDir = isAbsolute(messagesDir) ? messagesDir : resolve(root, messagesDir);
3470
+ if (!existsSync(absoluteDir)) throw new Error(`[finesoftFrontViteConfig] i18n.messagesDir not found: ${messagesDir}`);
3471
+ const relativeDir = normalizePathForGlob(relative(root, absoluteDir));
3472
+ if (!relativeDir || relativeDir.startsWith("..")) throw new Error(`[finesoftFrontViteConfig] i18n.messagesDir must stay inside the project root: ${messagesDir}`);
3473
+ return relativeDir.replace(/^\.\/+/, "");
3474
+ }
3354
3475
  function finesoftFrontViteConfig(options = {}) {
3355
3476
  const ssrEntry = options.ssr?.entry ?? "src/ssr.ts";
3356
3477
  let root = process.cwd();
@@ -3361,7 +3482,10 @@ function finesoftFrontViteConfig(options = {}) {
3361
3482
  return {
3362
3483
  name: "finesoft-front",
3363
3484
  config(userConfig) {
3364
- const overrides = { appType: "custom" };
3485
+ const overrides = {
3486
+ appType: "custom",
3487
+ define: { __FINESOFT_I18N_LOADER_IMPORTER__: options.i18n?.messagesDir ? `async () => import(${JSON.stringify(GENERATED_I18N_LOADER_ID)})` : "undefined" }
3488
+ };
3365
3489
  if (!process.env.__FINESOFT_SUB_BUILD__) overrides.build = { outDir: userConfig.build?.outDir ?? "dist/client" };
3366
3490
  return overrides;
3367
3491
  },
@@ -3371,6 +3495,26 @@ function finesoftFrontViteConfig(options = {}) {
3371
3495
  resolvedCss = config.css;
3372
3496
  root = config.root;
3373
3497
  },
3498
+ resolveId(id) {
3499
+ if (id === GENERATED_I18N_LOADER_ID && options.i18n?.messagesDir) return RESOLVED_GENERATED_I18N_LOADER_ID;
3500
+ return null;
3501
+ },
3502
+ async load(id) {
3503
+ if (id !== RESOLVED_GENERATED_I18N_LOADER_ID || !options.i18n?.messagesDir) return null;
3504
+ const baseDir = `/${await resolveMessagesDir(root, options.i18n.messagesDir)}`;
3505
+ const globPattern = `${baseDir}/*.json`;
3506
+ const filePrefix = `${baseDir}/`;
3507
+ return `
3508
+ const localeModules = import.meta.glob(${JSON.stringify(globPattern)}, { import: "default" });
3509
+
3510
+ export async function loadMessages(locale) {
3511
+ const loader = localeModules[${JSON.stringify(filePrefix)} + locale + ".json"];
3512
+ if (!loader) return undefined;
3513
+ const messages = await loader();
3514
+ return messages ?? undefined;
3515
+ }
3516
+ `;
3517
+ },
3374
3518
  transformIndexHtml: {
3375
3519
  order: "pre",
3376
3520
  async handler(html, ctx) {
@@ -3569,6 +3713,6 @@ function finesoftFrontViteConfig(options = {}) {
3569
3713
  };
3570
3714
  }
3571
3715
  //#endregion
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 };
3716
+ 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, defineBootstrap, defineRoutes, deny, deserializeServerData, detectPlatform, detectRuntime, englishPlural, finesoftFrontViteConfig, generateProxyCode, generateUuid, getBaseUrl, getBootstrapConfig, 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, resolveConfiguredMessages, resolveLocaleFromUrl, resolveMessages, resolvePluralKey, resolveRoot, rewrite, runAfterLoadGuards, runBeforeLoadGuards, serializeServerData, setHtmlLocaleAttributes, shouldLog, ssrRender, stableStringify, startBrowserApp, startServer, staticAdapter, tryScroll, vercelAdapter };
3573
3717
 
3574
3718
  //# sourceMappingURL=index.mjs.map