@finesoft/front 0.1.54 → 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
@@ -203,6 +203,95 @@ function setHtmlLocaleAttributes(attrs) {
203
203
  document.documentElement.lang = attrs.lang;
204
204
  document.documentElement.dir = attrs.dir;
205
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
+ };
206
295
  //#endregion
207
296
  //#region ../core/src/logger/composite.ts
208
297
  var CompositeLoggerFactory = class {
@@ -442,7 +531,8 @@ const DEP_KEYS = {
442
531
  FETCH: "fetch",
443
532
  EVENT_RECORDER: "eventRecorder",
444
533
  LOCALE: "locale",
445
- PLATFORM: "platform"
534
+ PLATFORM: "platform",
535
+ TRANSLATOR: "translator"
446
536
  };
447
537
  var MemoryStorage = class {
448
538
  store = /* @__PURE__ */ new Map();
@@ -504,8 +594,25 @@ var ConsoleMetrics = class {
504
594
  });
505
595
  }
506
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
+ }
507
614
  function makeDependencies(container, options = {}) {
508
- const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {}, featureFlagsProviders = [], reportCallback, eventRecorder, locale, platform } = options;
615
+ const { fetch: fetchFn = globalThis.fetch?.bind(globalThis), featureFlags = {}, featureFlagsProviders = [], reportCallback, eventRecorder, locale, platform, messages } = options;
509
616
  const consoleFactory = new ConsoleLoggerFactory();
510
617
  const loggerFactory = reportCallback ? new CompositeLoggerFactory([consoleFactory, new ReportingLoggerFactory({ report: reportCallback })]) : consoleFactory;
511
618
  container.register(DEP_KEYS.LOGGER_FACTORY, () => loggerFactory);
@@ -518,6 +625,13 @@ function makeDependencies(container, options = {}) {
518
625
  container.register(DEP_KEYS.METRICS, () => new ConsoleMetrics());
519
626
  container.register(DEP_KEYS.EVENT_RECORDER, () => eventRecorder ?? new ConsoleEventRecorder());
520
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
+ }
521
635
  container.register(DEP_KEYS.PLATFORM, () => platform ?? detectPlatform(typeof navigator !== "undefined" ? navigator.userAgent : void 0));
522
636
  container.register(DEP_KEYS.FETCH, () => fetchFn);
523
637
  }
@@ -761,6 +875,10 @@ var Framework = class Framework {
761
875
  getLocale() {
762
876
  return this.container.has(DEP_KEYS.LOCALE) ? this.container.resolve(DEP_KEYS.LOCALE) : void 0;
763
877
  }
878
+ /** 获取翻译器(如果已通过 messages + locale 配置) */
879
+ getTranslator() {
880
+ return this.container.has(DEP_KEYS.TRANSLATOR) ? this.container.resolve(DEP_KEYS.TRANSLATOR) : void 0;
881
+ }
764
882
  /** 获取平台信息 */
765
883
  getPlatform() {
766
884
  return this.container.resolve(DEP_KEYS.PLATFORM);
@@ -1010,18 +1128,23 @@ function mapEach(mapper) {
1010
1128
  * ]);
1011
1129
  * ```
1012
1130
  */
1013
- function defineRoutes(framework, definitions) {
1131
+ function defineRoutes(framework, definitions, options) {
1014
1132
  const registeredIntents = /* @__PURE__ */ new Set();
1015
1133
  for (const def of definitions) {
1016
1134
  if (def.controller && !registeredIntents.has(def.intentId)) {
1017
1135
  framework.registerIntent(def.controller);
1018
1136
  registeredIntents.add(def.intentId);
1019
1137
  }
1020
- framework.router.add(def.path, def.intentId, {
1138
+ const routeOpts = {
1021
1139
  renderMode: def.renderMode,
1022
1140
  beforeGuards: def.beforeLoad,
1023
1141
  afterGuards: def.afterLoad
1024
- });
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
+ }
1025
1148
  }
1026
1149
  }
1027
1150
  //#endregion
@@ -1311,68 +1434,6 @@ var WithFieldsRecorder = class {
1311
1434
  }
1312
1435
  };
1313
1436
  //#endregion
1314
- //#region ../core/src/i18n/interpolate.ts
1315
- /**
1316
- * ICU 消息格式插值
1317
- *
1318
- * 支持 `{name}` 占位符替换和基础复数规则。
1319
- */
1320
- /** 将 `{key}` 占位符替换为 values 中的对应值 */
1321
- function interpolate(template, values) {
1322
- if (!values) return template;
1323
- return template.replace(/\{(\w+)\}/g, (_, key) => {
1324
- const val = values[key];
1325
- return val !== void 0 ? String(val) : `{${key}}`;
1326
- });
1327
- }
1328
- /**
1329
- * 英语复数规则(默认)
1330
- * 0 → other, 1 → one, 2+ → other
1331
- */
1332
- function englishPlural(count) {
1333
- return count === 1 ? "one" : "other";
1334
- }
1335
- /**
1336
- * 解析带复数后缀的翻译 key
1337
- *
1338
- * 约定: `key.one`, `key.other`, `key.zero`, etc.
1339
- */
1340
- function resolvePluralKey(key, category) {
1341
- return `${key}.${category}`;
1342
- }
1343
- //#endregion
1344
- //#region ../core/src/i18n/translator.ts
1345
- /**
1346
- * SimpleTranslator — 默认翻译器实现
1347
- *
1348
- * 从扁平的 key→string 映射提供翻译,支持 ICU 插值和复数规则。
1349
- */
1350
- var SimpleTranslator = class {
1351
- locale;
1352
- messages;
1353
- pluralRule;
1354
- fallback;
1355
- constructor(options) {
1356
- this.locale = options.locale;
1357
- this.messages = options.messages;
1358
- this.pluralRule = options.pluralRule ?? englishPlural;
1359
- this.fallback = options.fallback ?? ((key) => key);
1360
- }
1361
- t(key, values) {
1362
- const template = this.messages[key];
1363
- if (template === void 0) return this.fallback(key);
1364
- return interpolate(template, values);
1365
- }
1366
- plural(key, count, values) {
1367
- const pluralKey = resolvePluralKey(key, this.pluralRule(count));
1368
- const mergedValues = {
1369
- count,
1370
- ...values
1371
- };
1372
- return this.t(pluralKey, mergedValues);
1373
- }
1374
- };
1375
- //#endregion
1376
1437
  //#region ../browser/src/action-handlers/external-url-action.ts
1377
1438
  function registerExternalUrlHandler(deps) {
1378
1439
  const { framework, log } = deps;
@@ -1859,10 +1920,15 @@ async function startBrowserApp(config) {
1859
1920
  */
1860
1921
  async function ssrRender(options) {
1861
1922
  const { url, frameworkConfig, bootstrap, getErrorPage, renderApp, ssrContext, resolveLocale } = options;
1862
- const mergedConfig = ssrContext?.fetch ? {
1923
+ const resolvedLocale = resolveLocale?.(url, ssrContext?.request);
1924
+ const effectiveConfig = resolvedLocale ? {
1863
1925
  ...frameworkConfig,
1864
- fetch: ssrContext.fetch
1926
+ locale: resolvedLocale.lang
1865
1927
  } : frameworkConfig;
1928
+ const mergedConfig = ssrContext?.fetch ? {
1929
+ ...effectiveConfig,
1930
+ fetch: ssrContext.fetch
1931
+ } : effectiveConfig;
1866
1932
  const framework = Framework.create(mergedConfig);
1867
1933
  bootstrap(framework);
1868
1934
  try {
@@ -1911,7 +1977,7 @@ async function ssrRender(options) {
1911
1977
  }
1912
1978
  } else page = getErrorPage(404, "Page not found");
1913
1979
  const result = await renderApp(page, framework);
1914
- const locale = resolveLocale?.(url, ssrContext?.request) ?? framework.getLocale();
1980
+ const locale = resolvedLocale ?? framework.getLocale();
1915
1981
  return {
1916
1982
  html: result.html,
1917
1983
  head: result.head,
@@ -1972,14 +2038,15 @@ async function handleMiddlewareResult(result, getErrorPage, renderApp, framework
1972
2038
  * @returns `render(url, ssrContext?)` — 供 @finesoft/server SSRModule 使用
1973
2039
  */
1974
2040
  function createSSRRender(config) {
1975
- const { bootstrap, getErrorPage, renderApp, frameworkConfig } = config;
2041
+ const { bootstrap, getErrorPage, renderApp, frameworkConfig, resolveLocale } = config;
1976
2042
  return (url, ssrContext) => ssrRender({
1977
2043
  url,
1978
2044
  frameworkConfig: frameworkConfig ?? {},
1979
2045
  bootstrap,
1980
2046
  getErrorPage,
1981
- renderApp: (page) => renderApp(page),
1982
- ssrContext
2047
+ renderApp: (page, framework) => renderApp(page, framework),
2048
+ ssrContext,
2049
+ resolveLocale
1983
2050
  });
1984
2051
  }
1985
2052
  //#endregion
@@ -2004,17 +2071,25 @@ function injectSSRContent(options) {
2004
2071
  ...slots
2005
2072
  };
2006
2073
  let result = template.replace(PLACEHOLDER_REGEX, (_, name) => replacements[name] ?? "");
2007
- if (locale) result = result.replace(/(<html)([^>]*)(>)/i, (match, open, attrs, close) => {
2008
- return `${open}${attrs.replace(/\s+lang="[^"]*"/gi, "").replace(/\s+dir="[^"]*"/gi, "")} lang="${locale.lang}" dir="${locale.dir}"${close}`;
2009
- });
2074
+ if (locale) result = applyLocaleToHtml(result, locale);
2010
2075
  return result;
2011
2076
  }
2012
2077
  /**
2013
2078
  * CSR 空壳注入 — 清空所有占位符
2014
2079
  * 用于 renderMode === "csr" 的路由
2080
+ *
2081
+ * @param locale - 可选的 locale 属性,注入到 `<html lang="" dir="">`
2015
2082
  */
2016
- function injectCSRShell(template) {
2017
- 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
+ });
2018
2093
  }
2019
2094
  //#endregion
2020
2095
  //#region ../ssr/src/server-data.ts
@@ -2259,22 +2334,40 @@ ${setupImport}
2259
2334
 
2260
2335
  const TEMPLATE = ${JSON.stringify(ctx.templateHtml)};
2261
2336
  const RENDER_MODES = ${renderModes};
2337
+ const DEFAULT_LOCALE = ${JSON.stringify(ctx.defaultLocale ?? null)};
2262
2338
  ${cacheImpl}
2263
2339
 
2264
- function injectSSR(t, head, css, html, data) {
2265
- return t
2340
+ function injectSSR(t, head, css, html, data, locale) {
2341
+ const injected = t
2266
2342
  .replace(/<!--ssr-([a-z][a-z0-9-]*)-->/g, (_, name) => {
2267
2343
  const replacements = {
2268
- head: head + "\\n<style>" + css + "</style>",
2344
+ head: head + "\n<style>" + css + "</style>",
2269
2345
  body: html,
2270
2346
  data: '<script id="serialized-server-data" type="application/json">' + data + "<\/script>",
2271
2347
  };
2272
2348
  return replacements[name] ?? "";
2273
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" };
2274
2366
  }
2275
2367
 
2276
- function injectCSRShell(t) {
2277
- 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);
2278
2371
  }
2279
2372
 
2280
2373
  function matchRenderMode(url) {
@@ -2323,22 +2416,23 @@ app.get("*", async (c) => {
2323
2416
  // Vite 配置级别覆盖: CSR 直接返回空壳
2324
2417
  const overrideMode = matchRenderMode(url);
2325
2418
  if (overrideMode === "csr") {
2326
- return c.html(injectCSRShell(TEMPLATE));
2419
+ return c.html(injectCSRShell(TEMPLATE, getLocaleAttrs(DEFAULT_LOCALE)));
2327
2420
  }
2328
2421
 
2329
2422
  // ISR 缓存命中
2330
2423
  const cached = await platformCacheGet(url);
2331
2424
  if (cached) return c.html(cached);
2332
2425
 
2333
- 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);
2334
2428
 
2335
2429
  // 路由级 CSR
2336
2430
  if (renderMode === "csr") {
2337
- return c.html(injectCSRShell(TEMPLATE));
2431
+ return c.html(injectCSRShell(TEMPLATE, localeAttrs));
2338
2432
  }
2339
2433
 
2340
2434
  const serializedData = serializeServerData(serverData);
2341
- const finalHtml = injectSSR(TEMPLATE, head, css, appHtml, serializedData);
2435
+ const finalHtml = injectSSR(TEMPLATE, head, css, appHtml, serializedData, localeAttrs);
2342
2436
 
2343
2437
  // Prerender ISR 缓存(包括 Vite 配置覆盖和路由级)
2344
2438
  if (renderMode === "prerender" || overrideMode === "prerender") {
@@ -2414,20 +2508,34 @@ async function prerenderRoutes(ctx) {
2414
2508
  if (ctx.renderModes) {
2415
2509
  for (const [pattern, mode] of Object.entries(ctx.renderModes)) if (mode === "prerender" && !pattern.includes("*") && !pattern.includes(":")) prerenderPaths.add(pattern);
2416
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
+ }
2417
2518
  if (prerenderPaths.size === 0) return [];
2418
2519
  const ssrPath = pathToFileURL(path.resolve(root, "dist/server/ssr.js")).href;
2419
2520
  const ssrModule = await dynamicImport(ssrPath);
2420
2521
  const results = [];
2421
2522
  for (const url of prerenderPaths) try {
2422
- const { html: appHtml, head, css, serverData } = await ssrModule.render(url);
2523
+ const { html: appHtml, head, css, serverData, locale } = await ssrModule.render(url);
2423
2524
  const serializedData = ssrModule.serializeServerData(serverData);
2424
- 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) => {
2425
2526
  return {
2426
2527
  head: head + "\n<style>" + css + "</style>",
2427
2528
  body: appHtml,
2428
2529
  data: "<script id=\"serialized-server-data\" type=\"application/json\">" + serializedData + "<\/script>"
2429
2530
  }[name] ?? "";
2430
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
+ }
2431
2539
  results.push({
2432
2540
  url,
2433
2541
  html: finalHtml
@@ -2944,7 +3052,7 @@ function matchRenderModeOverride(url, renderModes) {
2944
3052
  return null;
2945
3053
  }
2946
3054
  function createSSRApp(options) {
2947
- 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;
2948
3056
  const app = new Hono();
2949
3057
  /** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */
2950
3058
  const ISR_CACHE_MAX = 1e3;
@@ -2992,22 +3100,23 @@ function createSSRApp(options) {
2992
3100
  if (typeof ssrMod.render !== "function" || typeof ssrMod.serializeServerData !== "function") throw new Error("[SSR] Module missing required exports: render, serializeServerData");
2993
3101
  const { render, serializeServerData } = ssrMod;
2994
3102
  const overrideMode = matchRenderModeOverride(url, renderModes);
2995
- if (overrideMode === "csr") return c.html(injectCSRShell(template));
3103
+ if (overrideMode === "csr") return c.html(injectCSRShell(template, defaultLocale ? getLocaleAttributes(defaultLocale) : void 0));
2996
3104
  const cached = isrCache.get(url);
2997
3105
  if (cached) return c.html(cached);
2998
3106
  const requestFetch = parentFetch ? createInternalFetch(parentFetch, ssrDepth + 1) : void 0;
2999
3107
  const ssrContext = { request: c.req.raw };
3000
3108
  if (requestFetch) ssrContext.fetch = requestFetch;
3001
- 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);
3002
3110
  if (middlewareRedirect) return c.redirect(middlewareRedirect.url, middlewareRedirect.status);
3003
- if (renderMode === "csr") return c.html(injectCSRShell(template));
3111
+ if (renderMode === "csr") return c.html(injectCSRShell(template, locale));
3004
3112
  const finalHtml = injectSSRContent({
3005
3113
  template,
3006
3114
  head,
3007
3115
  css,
3008
3116
  html: appHtml,
3009
3117
  serializedData: serializeServerData(serverData),
3010
- slots
3118
+ slots,
3119
+ locale
3011
3120
  });
3012
3121
  if (renderMode === "prerender" || overrideMode === "prerender") isrSet(url, finalHtml);
3013
3122
  return c.html(finalHtml);
@@ -3319,7 +3428,8 @@ function finesoftFrontViteConfig(options = {}) {
3319
3428
  isProduction: false,
3320
3429
  ssrEntryPath: "/" + ssrEntry,
3321
3430
  parentFetch: app.fetch.bind(app),
3322
- renderModes: options.renderModes
3431
+ renderModes: options.renderModes,
3432
+ defaultLocale: options.defaultLocale
3323
3433
  });
3324
3434
  app.route("/", ssrApp);
3325
3435
  const listener = getRequestListener(app.fetch);
@@ -3363,17 +3473,18 @@ function finesoftFrontViteConfig(options = {}) {
3363
3473
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
3364
3474
  try {
3365
3475
  const overrideMode = matchRenderModeConfig(url, options.renderModes);
3366
- if (overrideMode === "csr") return c.html(injectCSRShell(template));
3476
+ if (overrideMode === "csr") return c.html(injectCSRShell(template, options.defaultLocale ? getLocaleAttributes(options.defaultLocale) : void 0));
3367
3477
  const cached = isrCache.get(url);
3368
3478
  if (cached) return c.html(cached);
3369
- const { html: appHtml, head, css, serverData, renderMode } = await ssrModule.render(url, { fetch: createInternalFetch(app.fetch.bind(app), ssrDepth + 1) });
3370
- 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));
3371
3481
  const finalHtml = injectSSRContent({
3372
3482
  template,
3373
3483
  head,
3374
3484
  css,
3375
3485
  html: appHtml,
3376
- serializedData: ssrModule.serializeServerData(serverData)
3486
+ serializedData: ssrModule.serializeServerData(serverData),
3487
+ locale
3377
3488
  });
3378
3489
  if (renderMode === "prerender" || overrideMode === "prerender") isrSet(url, finalHtml);
3379
3490
  return c.html(finalHtml);
@@ -3431,6 +3542,8 @@ function finesoftFrontViteConfig(options = {}) {
3431
3542
  templateHtml,
3432
3543
  renderModes: options.renderModes,
3433
3544
  proxies: options.proxies,
3545
+ locales: options.locales,
3546
+ defaultLocale: options.defaultLocale,
3434
3547
  resolvedResolve,
3435
3548
  resolvedCss,
3436
3549
  vite,
@@ -3456,6 +3569,6 @@ function finesoftFrontViteConfig(options = {}) {
3456
3569
  };
3457
3570
  }
3458
3571
  //#endregion
3459
- 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, resolvePluralKey, resolveRoot, rewrite, runAfterLoadGuards, runBeforeLoadGuards, serializeServerData, setHtmlLocaleAttributes, 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 };
3460
3573
 
3461
3574
  //# sourceMappingURL=index.mjs.map