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