reroute-js 0.45.12-beta.1 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +100 -2
  2. package/_/blog/src/client/routes/blog/[layout].tsx +45 -13
  3. package/_/blog/src/client/routes/blog/content/pt-BR/1-hello-world.tsx +27 -0
  4. package/_/blog/src/client/routes/index.tsx +4 -4
  5. package/_/search/src/client/routes/docs/content/content-collections.md +18 -0
  6. package/cli/bin.d.ts +2 -2
  7. package/cli/bin.js +980 -337
  8. package/cli/bin.js.map +29 -21
  9. package/cli/index.d.ts +2 -2
  10. package/cli/index.js +5 -5
  11. package/cli/index.js.map +1 -1
  12. package/cli/src/cli.d.ts +2 -2
  13. package/cli/src/commands/analyze.d.ts +2 -2
  14. package/cli/src/commands/build.d.ts +2 -2
  15. package/cli/src/commands/dev.d.ts +2 -2
  16. package/cli/src/commands/gen.d.ts +2 -2
  17. package/cli/src/commands/gen.d.ts.map +1 -1
  18. package/cli/src/commands/index.d.ts +2 -2
  19. package/cli/src/commands/init.d.ts +2 -2
  20. package/cli/src/commands/lib/assets.d.ts +2 -2
  21. package/cli/src/commands/lib/bundler.d.ts +2 -2
  22. package/cli/src/commands/lib/command.d.ts +2 -2
  23. package/cli/src/commands/lib/env.d.ts +2 -2
  24. package/cli/src/commands/lib/index.d.ts +2 -2
  25. package/cli/src/commands/lib/log.d.ts +2 -2
  26. package/cli/src/commands/lib/markdown/availability.d.ts +2 -2
  27. package/cli/src/commands/lib/markdown/index.d.ts +2 -2
  28. package/cli/src/commands/lib/markdown/processor.d.ts +2 -2
  29. package/cli/src/commands/lib/production.d.ts +2 -2
  30. package/cli/src/commands/lib/server.d.ts +2 -2
  31. package/cli/src/commands/lib/streaming/analyzer.d.ts +2 -2
  32. package/cli/src/commands/lib/streaming/suspense.d.ts +2 -2
  33. package/cli/src/commands/lib/tailwind.d.ts +2 -2
  34. package/cli/src/commands/lib/terminal-ui.d.ts +2 -2
  35. package/cli/src/commands/lib/version.d.ts +2 -2
  36. package/cli/src/commands/og.d.ts +2 -2
  37. package/cli/src/commands/start.d.ts +2 -2
  38. package/cli/src/index.d.ts +2 -2
  39. package/core/index.d.ts +2 -2
  40. package/core/index.js +667 -117
  41. package/core/index.js.map +28 -20
  42. package/core/src/bundler/hash.d.ts +2 -2
  43. package/core/src/bundler/index.d.ts +2 -2
  44. package/core/src/config.d.ts +6 -3
  45. package/core/src/config.d.ts.map +1 -1
  46. package/core/src/content/discovery.d.ts +2 -2
  47. package/core/src/content/index.d.ts +2 -2
  48. package/core/src/content/metadata.d.ts +2 -2
  49. package/core/src/i18n/catalog.d.ts +17 -0
  50. package/core/src/i18n/catalog.d.ts.map +1 -0
  51. package/core/src/i18n/discover.d.ts +19 -0
  52. package/core/src/i18n/discover.d.ts.map +1 -0
  53. package/core/src/i18n/index.d.ts +17 -0
  54. package/core/src/i18n/index.d.ts.map +1 -0
  55. package/core/src/i18n/messages.d.ts +18 -0
  56. package/core/src/i18n/messages.d.ts.map +1 -0
  57. package/core/src/i18n/negotiate.d.ts +18 -0
  58. package/core/src/i18n/negotiate.d.ts.map +1 -0
  59. package/core/src/i18n/pathname.d.ts +34 -0
  60. package/core/src/i18n/pathname.d.ts.map +1 -0
  61. package/core/src/i18n/resolve.d.ts +22 -0
  62. package/core/src/i18n/resolve.d.ts.map +1 -0
  63. package/core/src/i18n/types.d.ts +68 -0
  64. package/core/src/i18n/types.d.ts.map +1 -0
  65. package/core/src/index.d.ts +3 -2
  66. package/core/src/index.d.ts.map +1 -1
  67. package/core/src/llms/extractor.d.ts +2 -2
  68. package/core/src/llms/formatter.d.ts +2 -2
  69. package/core/src/llms/full-generator.d.ts +2 -2
  70. package/core/src/llms/index-generator.d.ts +2 -2
  71. package/core/src/llms/index.d.ts +2 -2
  72. package/core/src/og/discovery.d.ts +2 -2
  73. package/core/src/og/index.d.ts +2 -2
  74. package/core/src/og/meta.d.ts +2 -2
  75. package/core/src/og/render.d.ts +2 -2
  76. package/core/src/og/types.d.ts +2 -2
  77. package/core/src/robots/discovery.d.ts +2 -2
  78. package/core/src/robots/generator.d.ts +2 -2
  79. package/core/src/robots/index.d.ts +2 -2
  80. package/core/src/robots/policies.d.ts +2 -2
  81. package/core/src/rss/discovery.d.ts +4 -2
  82. package/core/src/rss/discovery.d.ts.map +1 -1
  83. package/core/src/rss/generator.d.ts +2 -2
  84. package/core/src/rss/index.d.ts +2 -2
  85. package/core/src/sitemap/discovery.d.ts +7 -2
  86. package/core/src/sitemap/discovery.d.ts.map +1 -1
  87. package/core/src/sitemap/generator.d.ts +2 -2
  88. package/core/src/sitemap/generator.d.ts.map +1 -1
  89. package/core/src/sitemap/index.d.ts +2 -2
  90. package/core/src/ssr/index.d.ts +2 -2
  91. package/core/src/ssr/lib/cache.d.ts +2 -2
  92. package/core/src/ssr/lib/cleanup.d.ts +2 -2
  93. package/core/src/ssr/lib/collections.d.ts +2 -2
  94. package/core/src/ssr/lib/compression.d.ts +2 -2
  95. package/core/src/ssr/lib/compute/content.d.ts +2 -9
  96. package/core/src/ssr/lib/compute/content.d.ts.map +1 -1
  97. package/core/src/ssr/lib/compute/index.d.ts +2 -2
  98. package/core/src/ssr/lib/compute/layouts.d.ts +2 -2
  99. package/core/src/ssr/lib/compute/routes.d.ts +2 -2
  100. package/core/src/ssr/lib/compute/routes.d.ts.map +1 -1
  101. package/core/src/ssr/lib/content-path.d.ts +22 -0
  102. package/core/src/ssr/lib/content-path.d.ts.map +1 -0
  103. package/core/src/ssr/lib/data.d.ts +2 -2
  104. package/core/src/ssr/lib/data.d.ts.map +1 -1
  105. package/core/src/ssr/lib/html.d.ts +2 -2
  106. package/core/src/ssr/lib/imports.d.ts +2 -2
  107. package/core/src/ssr/lib/index.d.ts +2 -2
  108. package/core/src/ssr/lib/layouts.d.ts +2 -2
  109. package/core/src/ssr/lib/metadata.d.ts +2 -2
  110. package/core/src/ssr/lib/metadata.d.ts.map +1 -1
  111. package/core/src/ssr/lib/mime.d.ts +2 -2
  112. package/core/src/ssr/lib/modules.d.ts +4 -4
  113. package/core/src/ssr/lib/modules.d.ts.map +1 -1
  114. package/core/src/ssr/lib/path.d.ts +2 -2
  115. package/core/src/ssr/lib/preload.d.ts +2 -2
  116. package/core/src/ssr/lib/preload.d.ts.map +1 -1
  117. package/core/src/ssr/lib/route-match.d.ts +2 -2
  118. package/core/src/ssr/lib/scripts/collections.d.ts +2 -2
  119. package/core/src/ssr/lib/scripts/data.d.ts +3 -3
  120. package/core/src/ssr/lib/scripts/data.d.ts.map +1 -1
  121. package/core/src/ssr/lib/scripts/escape.d.ts +2 -2
  122. package/core/src/ssr/lib/scripts/feeds.d.ts +2 -2
  123. package/core/src/ssr/lib/scripts/index.d.ts +2 -2
  124. package/core/src/ssr/lib/seed.d.ts +2 -2
  125. package/core/src/ssr/lib/seed.d.ts.map +1 -1
  126. package/core/src/ssr/lib/serialize.d.ts +2 -2
  127. package/core/src/ssr/lib/setup.d.ts +3 -3
  128. package/core/src/ssr/lib/setup.d.ts.map +1 -1
  129. package/core/src/ssr/lib/styles.d.ts +2 -2
  130. package/core/src/ssr/lib/template.d.ts +2 -2
  131. package/core/src/ssr/lib/types.d.ts +2 -2
  132. package/core/src/ssr/render.d.ts +4 -2
  133. package/core/src/ssr/render.d.ts.map +1 -1
  134. package/core/src/ssr/stream.d.ts +4 -2
  135. package/core/src/ssr/stream.d.ts.map +1 -1
  136. package/elysia/index.d.ts +2 -2
  137. package/elysia/index.js +872 -177
  138. package/elysia/index.js.map +32 -23
  139. package/elysia/src/index.d.ts +2 -2
  140. package/elysia/src/libs/assets.d.ts +2 -2
  141. package/elysia/src/libs/cache.d.ts +2 -2
  142. package/elysia/src/libs/caching.d.ts +2 -2
  143. package/elysia/src/libs/http.d.ts +2 -2
  144. package/elysia/src/libs/i18n.d.ts +26 -0
  145. package/elysia/src/libs/i18n.d.ts.map +1 -0
  146. package/elysia/src/libs/image.d.ts +2 -2
  147. package/elysia/src/libs/index.d.ts +2 -2
  148. package/elysia/src/libs/llms.d.ts +2 -2
  149. package/elysia/src/libs/response.d.ts +2 -2
  150. package/elysia/src/libs/serving.d.ts +2 -2
  151. package/elysia/src/plugin.d.ts +2 -2
  152. package/elysia/src/routes/artifacts.d.ts +2 -2
  153. package/elysia/src/routes/content.d.ts +2 -2
  154. package/elysia/src/routes/image.d.ts +2 -2
  155. package/elysia/src/routes/index.d.ts +2 -2
  156. package/elysia/src/routes/internal.d.ts +2 -2
  157. package/elysia/src/routes/llms.d.ts +2 -2
  158. package/elysia/src/routes/og.d.ts +2 -2
  159. package/elysia/src/routes/redirects.d.ts +2 -2
  160. package/elysia/src/routes/robots.d.ts +2 -2
  161. package/elysia/src/routes/rss.d.ts +2 -2
  162. package/elysia/src/routes/rss.d.ts.map +1 -1
  163. package/elysia/src/routes/search.d.ts +2 -2
  164. package/elysia/src/routes/search.d.ts.map +1 -1
  165. package/elysia/src/routes/sitemap.d.ts +2 -2
  166. package/elysia/src/routes/ssr.d.ts +2 -2
  167. package/elysia/src/routes/ssr.d.ts.map +1 -1
  168. package/elysia/src/routes/static.d.ts +2 -2
  169. package/elysia/src/types.d.ts +2 -2
  170. package/package.json +7 -6
  171. package/react/index.d.ts +2 -2
  172. package/react/index.js +560 -170
  173. package/react/index.js.map +12 -10
  174. package/react/src/components/ClientOnly.d.ts +2 -2
  175. package/react/src/components/ContentRoute.d.ts +3 -3
  176. package/react/src/components/ContentRoute.d.ts.map +1 -1
  177. package/react/src/components/Image.d.ts +2 -2
  178. package/react/src/components/LazyRoute.d.ts +2 -2
  179. package/react/src/components/Link.d.ts +2 -2
  180. package/react/src/components/Link.d.ts.map +1 -1
  181. package/react/src/components/Markdown.d.ts +2 -2
  182. package/react/src/components/Outlet.d.ts +2 -2
  183. package/react/src/components/index.d.ts +2 -2
  184. package/react/src/hooks/index.d.ts +2 -2
  185. package/react/src/hooks/useContent.d.ts +2 -2
  186. package/react/src/hooks/useContent.d.ts.map +1 -1
  187. package/react/src/hooks/useData.d.ts +2 -2
  188. package/react/src/hooks/useFeed.d.ts +2 -2
  189. package/react/src/hooks/useLayoutData.d.ts +2 -2
  190. package/react/src/hooks/useLlms.d.ts +2 -2
  191. package/react/src/hooks/useNavigate.d.ts +2 -2
  192. package/react/src/hooks/useParams.d.ts +2 -2
  193. package/react/src/hooks/useRouter.d.ts +2 -2
  194. package/react/src/hooks/useSearch.d.ts +2 -2
  195. package/react/src/hooks/useSearch.d.ts.map +1 -1
  196. package/react/src/hooks/useSearchParams.d.ts +2 -2
  197. package/react/src/hooks/useToc.d.ts +2 -2
  198. package/react/src/i18n/LocaleProvider.d.ts +49 -0
  199. package/react/src/i18n/LocaleProvider.d.ts.map +1 -0
  200. package/react/src/i18n/helpers.d.ts +24 -0
  201. package/react/src/i18n/helpers.d.ts.map +1 -0
  202. package/react/src/i18n/index.d.ts +13 -0
  203. package/react/src/i18n/index.d.ts.map +1 -0
  204. package/react/src/index.d.ts +3 -2
  205. package/react/src/index.d.ts.map +1 -1
  206. package/react/src/lib/collection.d.ts +2 -2
  207. package/react/src/lib/content.d.ts +4 -3
  208. package/react/src/lib/content.d.ts.map +1 -1
  209. package/react/src/lib/head.d.ts +2 -2
  210. package/react/src/lib/index.d.ts +2 -2
  211. package/react/src/lib/lazy-route.d.ts +2 -2
  212. package/react/src/lib/route-loader.d.ts +2 -2
  213. package/react/src/providers/ContentProvider.d.ts +3 -3
  214. package/react/src/providers/ContentProvider.d.ts.map +1 -1
  215. package/react/src/providers/RerouteProvider.d.ts +8 -6
  216. package/react/src/providers/RerouteProvider.d.ts.map +1 -1
  217. package/react/src/providers/RouterProvider.d.ts +2 -2
  218. package/react/src/providers/index.d.ts +2 -2
  219. package/react/src/types/any.d.ts +2 -2
  220. package/react/src/types/index.d.ts +2 -2
  221. package/react/src/types/router.d.ts +2 -2
  222. package/telemetry/react.d.ts +2 -2
  223. package/telemetry/react.js +3 -3
  224. package/telemetry/react.js.map +1 -1
  225. package/telemetry/server.d.ts +2 -2
  226. package/telemetry/server.js +56 -4
  227. package/telemetry/server.js.map +20 -15
  228. package/telemetry/src/react/api.d.ts +2 -2
  229. package/telemetry/src/react/context.d.ts +2 -2
  230. package/telemetry/src/react/index.d.ts +2 -2
  231. package/telemetry/src/react/telemetry.d.ts +2 -2
  232. package/telemetry/src/server/context.d.ts +2 -2
  233. package/telemetry/src/server/headers/extractor.d.ts +2 -2
  234. package/telemetry/src/server/headers/index.d.ts +2 -2
  235. package/telemetry/src/server/headers/presets.d.ts +2 -2
  236. package/telemetry/src/server/index.d.ts +2 -2
  237. package/telemetry/src/server/instrumentation.d.ts +2 -2
  238. package/telemetry/src/server/plugin.d.ts +2 -2
  239. package/telemetry/src/server/sourcemap.d.ts +2 -2
package/elysia/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * reroute-js v0.45.11
2
+ * reroute-js v0.46.0
3
3
  *
4
4
  * @license MIT
5
5
  * @copyright 2026 stewones <hi@stewan.io>
6
- * @see https://github.com/stewones/reroute
6
+ * @see https://github.com/Intenseloop/reroute
7
7
  *
8
8
  * Built with Bun <3
9
9
  */
@@ -200,6 +200,364 @@ var init_content = __esm(() => {
200
200
  init_metadata();
201
201
  });
202
202
 
203
+ // packages/core/src/i18n/catalog.ts
204
+ function getMessage(catalog, key) {
205
+ if (!(catalog && key))
206
+ return;
207
+ const parts = key.split(".");
208
+ let current = catalog;
209
+ for (const part of parts) {
210
+ if (current == null || typeof current === "string")
211
+ return;
212
+ current = current[part];
213
+ }
214
+ return typeof current === "string" ? current : undefined;
215
+ }
216
+ function lookupMessage(catalogs, locale, defaultLocale, key) {
217
+ return getMessage(catalogs[locale], key) ?? (locale !== defaultLocale ? getMessage(catalogs[defaultLocale], key) : undefined);
218
+ }
219
+ var init_catalog = () => {};
220
+
221
+ // packages/core/src/i18n/discover.ts
222
+ import { readdir as readdir2 } from "node:fs/promises";
223
+ import { join as join2 } from "node:path";
224
+ async function loadCatalog(filePath) {
225
+ try {
226
+ const text = await Bun.file(filePath).text();
227
+ return JSON.parse(text);
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ async function loadLocaleCatalogs(absDir) {
233
+ const entries = await readdir2(absDir, { withFileTypes: true });
234
+ const catalogs = {};
235
+ const locales = [];
236
+ for (const entry of entries) {
237
+ if (!(entry.isFile() && entry.name.endsWith(".json")))
238
+ continue;
239
+ const locale = entry.name.slice(0, -".json".length);
240
+ if (!locale)
241
+ continue;
242
+ const catalog = await loadCatalog(join2(absDir, entry.name));
243
+ if (!catalog)
244
+ continue;
245
+ catalogs[locale] = catalog;
246
+ locales.push(locale);
247
+ }
248
+ return { catalogs, locales };
249
+ }
250
+ function sortLocales(locales, defaultLocale) {
251
+ locales.sort((a, b) => {
252
+ if (a === defaultLocale)
253
+ return -1;
254
+ if (b === defaultLocale)
255
+ return 1;
256
+ return a.localeCompare(b);
257
+ });
258
+ }
259
+ async function discoverI18n(cwd, options = {}) {
260
+ const localesDir = options.localesDir ?? DEFAULT_LOCALES_DIR;
261
+ const defaultLocale = options.defaultLocale ?? DEFAULT_LOCALE;
262
+ const cookie = options.cookie ?? DEFAULT_COOKIE;
263
+ const redirectContentOnNegotiate = options.redirectContentOnNegotiate ?? true;
264
+ const absDir = join2(cwd, localesDir);
265
+ const disabled = {
266
+ enabled: false,
267
+ defaultLocale,
268
+ locales: [],
269
+ localesDir,
270
+ cookie,
271
+ redirectContentOnNegotiate,
272
+ catalogs: {}
273
+ };
274
+ let catalogs;
275
+ let locales;
276
+ try {
277
+ ({ catalogs, locales } = await loadLocaleCatalogs(absDir));
278
+ } catch {
279
+ return disabled;
280
+ }
281
+ if (locales.length === 0) {
282
+ return disabled;
283
+ }
284
+ sortLocales(locales, defaultLocale);
285
+ if (!catalogs[defaultLocale]) {
286
+ console.warn(`[reroute/i18n] defaultLocale "${defaultLocale}" has no catalog; using "${locales[0]}"`);
287
+ }
288
+ return {
289
+ enabled: true,
290
+ defaultLocale: catalogs[defaultLocale] ? defaultLocale : locales[0],
291
+ locales,
292
+ localesDir,
293
+ cookie,
294
+ redirectContentOnNegotiate,
295
+ catalogs
296
+ };
297
+ }
298
+ var DEFAULT_LOCALE = "en-US", DEFAULT_LOCALES_DIR = "locales", DEFAULT_COOKIE = "reroute-locale";
299
+ var init_discover = () => {};
300
+
301
+ // packages/core/src/i18n/messages.ts
302
+ import { IntlMessageFormat } from "intl-messageformat";
303
+ function formatMessage(message, locale, values) {
304
+ if (!values || Object.keys(values).length === 0) {
305
+ if (!/\{[^}]+\}/.test(message))
306
+ return message;
307
+ }
308
+ const cacheKey = `${locale}\x00${message}`;
309
+ let formatter = formatterCache.get(cacheKey);
310
+ if (!formatter) {
311
+ formatter = new IntlMessageFormat(message, locale);
312
+ formatterCache.set(cacheKey, formatter);
313
+ }
314
+ try {
315
+ const result = formatter.format(values);
316
+ return typeof result === "string" ? result : String(result);
317
+ } catch {
318
+ return message;
319
+ }
320
+ }
321
+ function createTranslator(catalogs, locale, defaultLocale) {
322
+ return (key, values) => {
323
+ const message = lookupMessage(catalogs, locale, defaultLocale, key);
324
+ if (message == null)
325
+ return key;
326
+ return formatMessage(message, locale, values);
327
+ };
328
+ }
329
+ var formatterCache;
330
+ var init_messages = __esm(() => {
331
+ init_catalog();
332
+ formatterCache = new Map;
333
+ });
334
+
335
+ // packages/core/src/i18n/negotiate.ts
336
+ function negotiateLocale(acceptLanguage, availableLocales, defaultLocale) {
337
+ if (!availableLocales.length)
338
+ return defaultLocale;
339
+ if (!acceptLanguage)
340
+ return defaultLocale;
341
+ const availableLower = new Map(availableLocales.map((l) => [l.toLowerCase(), l]));
342
+ const byLang = new Map;
343
+ for (const locale of availableLocales) {
344
+ const lang = locale.split("-")[0].toLowerCase();
345
+ if (!byLang.has(lang))
346
+ byLang.set(lang, locale);
347
+ }
348
+ const ranges = acceptLanguage.split(",").map((part) => {
349
+ const [tag, ...params] = part.trim().split(";");
350
+ let q = 1;
351
+ for (const p of params) {
352
+ const [k, v] = p.trim().split("=");
353
+ if (k === "q" && v)
354
+ q = Number.parseFloat(v) || 0;
355
+ }
356
+ return { tag: tag.trim().toLowerCase(), q };
357
+ }).filter((r) => r.tag && r.q > 0).sort((a, b) => b.q - a.q);
358
+ for (const { tag } of ranges) {
359
+ if (tag === "*")
360
+ return defaultLocale;
361
+ const exact = availableLower.get(tag);
362
+ if (exact)
363
+ return exact;
364
+ const langOnly = byLang.get(tag.split("-")[0]);
365
+ if (langOnly)
366
+ return langOnly;
367
+ }
368
+ return defaultLocale;
369
+ }
370
+ function parseCookieValue(cookieHeader, name) {
371
+ if (!cookieHeader)
372
+ return;
373
+ const parts = cookieHeader.split(";");
374
+ for (const part of parts) {
375
+ const idx = part.indexOf("=");
376
+ if (idx === -1)
377
+ continue;
378
+ const key = part.slice(0, idx).trim();
379
+ if (key === name) {
380
+ return decodeURIComponent(part.slice(idx + 1).trim());
381
+ }
382
+ }
383
+ return;
384
+ }
385
+ function buildLocaleCookie(name, locale, maxAgeSeconds = 60 * 60 * 24 * 365) {
386
+ return `${name}=${encodeURIComponent(locale)}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax`;
387
+ }
388
+ var init_negotiate = () => {};
389
+
390
+ // packages/core/src/i18n/pathname.ts
391
+ var exports_pathname = {};
392
+ __export(exports_pathname, {
393
+ toOgLocale: () => toOgLocale,
394
+ parseContentPath: () => parseContentPath,
395
+ normalizePathname: () => normalizePathname,
396
+ looksLikeLocale: () => looksLikeLocale,
397
+ localizeHref: () => localizeHref,
398
+ localizeContentHref: () => localizeContentHref,
399
+ hreflangTags: () => hreflangTags,
400
+ contentKey: () => contentKey
401
+ });
402
+ function looksLikeLocale(segment) {
403
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(segment);
404
+ }
405
+ function normalizePathname(pathname) {
406
+ const pathOnly = pathname.split("?")[0].split("#")[0];
407
+ if (!pathOnly || pathOnly === "/")
408
+ return "/";
409
+ return pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
410
+ }
411
+ function parseContentPath(pathname, availableLocales = [], collections) {
412
+ const normalized = normalizePathname(pathname);
413
+ const parts = normalized.split("/").filter(Boolean);
414
+ if (parts.length < 2)
415
+ return;
416
+ const [collection, second, third] = parts;
417
+ if (collections && !collections.includes(collection)) {
418
+ return;
419
+ }
420
+ const localeSet = new Set(availableLocales.map((l) => l.toLowerCase()));
421
+ if (parts.length >= 3 && third) {
422
+ const secondIsLocale = localeSet.size > 0 ? localeSet.has(second.toLowerCase()) : looksLikeLocale(second);
423
+ if (secondIsLocale) {
424
+ const locale = availableLocales.find((l) => l.toLowerCase() === second.toLowerCase()) || second;
425
+ return {
426
+ collection,
427
+ name: third,
428
+ locale,
429
+ routingPathname: `/${collection}/${third}`,
430
+ pathname: normalized
431
+ };
432
+ }
433
+ }
434
+ return {
435
+ collection,
436
+ name: second,
437
+ routingPathname: `/${collection}/${second}`,
438
+ pathname: normalized
439
+ };
440
+ }
441
+ function localizeContentHref(collection, name, locale, defaultLocale) {
442
+ if (!locale || locale === defaultLocale) {
443
+ return `/${collection}/${name}`;
444
+ }
445
+ return `/${collection}/${locale}/${name}`;
446
+ }
447
+ function localizeHref(to, locale, defaultLocale, availableLocales = [], collections) {
448
+ try {
449
+ const url = new URL(to, "http://local");
450
+ const parsed = parseContentPath(url.pathname, availableLocales, collections);
451
+ if (!parsed)
452
+ return to;
453
+ url.pathname = localizeContentHref(parsed.collection, parsed.name, locale, defaultLocale);
454
+ return `${url.pathname}${url.search}${url.hash}`;
455
+ } catch {
456
+ return to;
457
+ }
458
+ }
459
+ function contentKey(collection, name, locale, defaultLocale = "en-US") {
460
+ if (locale && locale !== defaultLocale) {
461
+ return `${collection}:${name}:${locale}`;
462
+ }
463
+ return `${collection}:${name}`;
464
+ }
465
+ function toOgLocale(locale) {
466
+ return locale.replace(/-/g, "_");
467
+ }
468
+ function hreflangTags(baseUrl, collection, name, locales, defaultLocale) {
469
+ const base = baseUrl.replace(/\/$/, "");
470
+ const tags = [];
471
+ for (const locale of locales) {
472
+ const path = localizeContentHref(collection, name, locale, defaultLocale);
473
+ tags.push(`<link rel="alternate" hreflang="${locale}" href="${base}${path}" />`);
474
+ }
475
+ const defaultPath = localizeContentHref(collection, name, defaultLocale, defaultLocale);
476
+ tags.push(`<link rel="alternate" hreflang="x-default" href="${base}${defaultPath}" />`);
477
+ return tags.join(`
478
+ `);
479
+ }
480
+ var init_pathname = () => {};
481
+
482
+ // packages/core/src/i18n/resolve.ts
483
+ function resolveLocale(input) {
484
+ const {
485
+ pathname,
486
+ cookieHeader,
487
+ acceptLanguage,
488
+ availableLocales,
489
+ defaultLocale,
490
+ cookieName = DEFAULT_COOKIE,
491
+ collections
492
+ } = input;
493
+ const parsed = parseContentPath(pathname, availableLocales, collections);
494
+ const availableLower = new Set(availableLocales.map((l) => l.toLowerCase()));
495
+ const isAvailable = (locale) => !!locale && availableLower.has(locale.toLowerCase());
496
+ if (parsed?.locale && isAvailable(parsed.locale)) {
497
+ const locale = availableLocales.find((l) => l.toLowerCase() === parsed.locale?.toLowerCase()) || parsed.locale;
498
+ if (locale === defaultLocale && parsed.pathname !== parsed.routingPathname) {
499
+ return {
500
+ locale,
501
+ routingPathname: parsed.routingPathname,
502
+ fromPath: true,
503
+ redirectTo: parsed.routingPathname,
504
+ parsed: { ...parsed, locale }
505
+ };
506
+ }
507
+ return {
508
+ locale,
509
+ routingPathname: parsed.routingPathname,
510
+ fromPath: true,
511
+ parsed: { ...parsed, locale }
512
+ };
513
+ }
514
+ const routingPathname = parsed?.routingPathname ?? pathname.split("?")[0];
515
+ const cookieLocale = parseCookieValue(cookieHeader, cookieName);
516
+ if (isAvailable(cookieLocale)) {
517
+ const locale = availableLocales.find((l) => l.toLowerCase() === cookieLocale.toLowerCase()) || cookieLocale;
518
+ return {
519
+ locale,
520
+ routingPathname,
521
+ fromPath: false,
522
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
523
+ };
524
+ }
525
+ const negotiated = negotiateLocale(acceptLanguage, availableLocales, defaultLocale);
526
+ return {
527
+ locale: negotiated,
528
+ routingPathname,
529
+ fromPath: false,
530
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
531
+ };
532
+ }
533
+ function contentNegotiateRedirect(resolution, defaultLocale, enabled) {
534
+ if (!enabled)
535
+ return;
536
+ if (resolution.fromPath)
537
+ return;
538
+ if (resolution.locale === defaultLocale)
539
+ return;
540
+ const parsed = resolution.parsed;
541
+ if (!parsed)
542
+ return;
543
+ return localizeContentHref(parsed.collection, parsed.name, resolution.locale, defaultLocale);
544
+ }
545
+ var init_resolve = __esm(() => {
546
+ init_discover();
547
+ init_negotiate();
548
+ init_pathname();
549
+ });
550
+
551
+ // packages/core/src/i18n/index.ts
552
+ var init_i18n = __esm(() => {
553
+ init_catalog();
554
+ init_discover();
555
+ init_messages();
556
+ init_negotiate();
557
+ init_pathname();
558
+ init_resolve();
559
+ });
560
+
203
561
  // packages/telemetry/src/server/context.ts
204
562
  import { AsyncLocalStorage } from "node:async_hooks";
205
563
  var contextStorage;
@@ -891,6 +1249,51 @@ function matchRoutes(routes, pathname) {
891
1249
  return null;
892
1250
  }
893
1251
 
1252
+ // packages/core/src/ssr/lib/content-path.ts
1253
+ var exports_content_path = {};
1254
+ __export(exports_content_path, {
1255
+ resolveContentPathParts: () => resolveContentPathParts
1256
+ });
1257
+ function resolveContentPathParts(pathname) {
1258
+ const pathOnly = pathname.split("?")[0].split("#")[0];
1259
+ const parts = pathOnly.split("/").filter(Boolean);
1260
+ if (parts.length < 2)
1261
+ return null;
1262
+ const g = globalThis;
1263
+ const locales = g.__REROUTE_I18N__?.locales || [];
1264
+ const defaultLocale = g.__REROUTE_I18N__?.defaultLocale || "en-US";
1265
+ const parsed = parseContentPath(pathOnly, locales);
1266
+ if (parsed) {
1267
+ const locale = parsed.locale || defaultLocale;
1268
+ return {
1269
+ collection: parsed.collection,
1270
+ name: parsed.name,
1271
+ locale: parsed.locale,
1272
+ key: contentKey(parsed.collection, parsed.name, locale, defaultLocale),
1273
+ routingPathname: parsed.routingPathname
1274
+ };
1275
+ }
1276
+ if (parts.length >= 3 && looksLikeLocale(parts[1])) {
1277
+ const locale = parts[1];
1278
+ return {
1279
+ collection: parts[0],
1280
+ name: parts[2],
1281
+ locale,
1282
+ key: contentKey(parts[0], parts[2], locale, defaultLocale),
1283
+ routingPathname: `/${parts[0]}/${parts[2]}`
1284
+ };
1285
+ }
1286
+ return {
1287
+ collection: parts[0],
1288
+ name: parts[1],
1289
+ key: `${parts[0]}:${parts[1]}`,
1290
+ routingPathname: `/${parts[0]}/${parts[1]}`
1291
+ };
1292
+ }
1293
+ var init_content_path = __esm(() => {
1294
+ init_pathname();
1295
+ });
1296
+
894
1297
  // packages/core/src/ssr/lib/compute/content.ts
895
1298
  function checkStatusCache(statusCacheKey, maxAgeMs) {
896
1299
  if (maxAgeMs <= 0)
@@ -943,6 +1346,14 @@ async function handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusC
943
1346
  }
944
1347
  return result;
945
1348
  }
1349
+ async function runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) {
1350
+ if (streaming) {
1351
+ handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
1352
+ } else {
1353
+ await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
1354
+ }
1355
+ return set.status !== 200 ? set.status : undefined;
1356
+ }
946
1357
  async function computeContentData(pathname, options, searchParams, sharedSsrData) {
947
1358
  const streaming = options.streaming === true;
948
1359
  const ssrData = streaming && sharedSsrData ? sharedSsrData : {};
@@ -952,18 +1363,20 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
952
1363
  const maxAge = options.maxAge || 0;
953
1364
  const maxAgeMs = maxAge * 1000;
954
1365
  try {
955
- const parts = pathname.split("/").filter(Boolean);
956
- if (parts.length < 2) {
1366
+ const resolved = resolveContentPathParts(pathname);
1367
+ if (!resolved) {
957
1368
  return { data: ssrData, pendingTasks };
958
1369
  }
1370
+ const parts = [resolved.collection, resolved.name];
1371
+ if (resolved.locale)
1372
+ parts.splice(1, 0, resolved.locale);
959
1373
  const g = globalThis;
960
- const key = `${parts[0]}:${parts[1]}`;
961
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1374
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
962
1375
  const dataFn = exp?.ssr?.data;
963
1376
  if (typeof dataFn !== "function") {
964
1377
  return { data: ssrData, pendingTasks };
965
1378
  }
966
- const cacheKey = `content:${pathname}:${parts[1]}`;
1379
+ const cacheKey = `content:${pathname}:${resolved.name}:${resolved.locale || ""}`;
967
1380
  const statusCacheKey = `status:${cacheKey}`;
968
1381
  const cachedStatus = checkStatusCache(statusCacheKey, maxAgeMs);
969
1382
  if (cachedStatus && cachedStatus !== 200) {
@@ -976,22 +1389,13 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
976
1389
  }
977
1390
  const set = { status: 200 };
978
1391
  const task = createDataTask(dataFn, pathname, parts, searchParams, set, options.headers);
979
- if (streaming) {
980
- handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
981
- if (set.status !== 200) {
982
- statusOverride = set.status;
983
- }
984
- } else {
985
- await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
986
- if (set.status !== 200) {
987
- statusOverride = set.status;
988
- }
989
- }
1392
+ statusOverride = await runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) ?? statusOverride;
990
1393
  } catch {}
991
1394
  return { data: ssrData, error, statusOverride, pendingTasks };
992
1395
  }
993
1396
  var init_content2 = __esm(() => {
994
1397
  init_cache();
1398
+ init_content_path();
995
1399
  init_imports();
996
1400
  });
997
1401
 
@@ -1190,7 +1594,8 @@ async function computeRouteData(pathname, clientDir, _cwd, isWatchMode, options,
1190
1594
  }).catch(() => {}));
1191
1595
  };
1192
1596
  try {
1193
- const pathnameOnly = pathname.split("?")[0];
1597
+ const resolved = resolveContentPathParts(pathname);
1598
+ const pathnameOnly = (resolved?.routingPathname || pathname).split("?")[0];
1194
1599
  const match = typeof routesModule?.matchRoute === "function" ? routesModule.matchRoute(pathnameOnly) : null;
1195
1600
  const route = match?.route;
1196
1601
  const params = match?.params || {};
@@ -1353,6 +1758,7 @@ function handleFlattenedResult(entries, set, ctx, cache, setError, setStatus) {
1353
1758
  }
1354
1759
  var init_routes = __esm(() => {
1355
1760
  init_cache();
1761
+ init_content_path();
1356
1762
  init_imports();
1357
1763
  });
1358
1764
 
@@ -1445,33 +1851,31 @@ var init_compute = __esm(() => {
1445
1851
  });
1446
1852
 
1447
1853
  // packages/core/src/ssr/lib/modules.ts
1448
- import { readdir as readdir2, stat as stat3 } from "node:fs/promises";
1854
+ import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
1449
1855
  async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1450
1856
  try {
1451
- const pathOnly = pathname.split("?")[0].split("#")[0];
1452
- const parts = pathOnly.split("/").filter(Boolean);
1453
- if (parts.length < 2)
1857
+ const resolved = resolveContentPathParts(pathname);
1858
+ if (!resolved)
1454
1859
  return null;
1455
- const collection = parts[0];
1456
- const name = parts[1];
1457
- const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode);
1860
+ const { collection, name, locale } = resolved;
1861
+ const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode, locale);
1458
1862
  if (registryModule)
1459
1863
  return registryModule;
1460
1864
  const chunkModule = await tryChunkImport(collection, name, cwd, isWatchMode);
1461
1865
  if (chunkModule)
1462
1866
  return chunkModule;
1463
- const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode);
1867
+ const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode, locale);
1464
1868
  if (sourceModule)
1465
1869
  return sourceModule;
1466
1870
  } catch {}
1467
1871
  return null;
1468
1872
  }
1469
- async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1873
+ async function tryRegistryImport(collection, name, cwd, isWatchMode, locale) {
1470
1874
  try {
1471
1875
  const registryPath = join(cwd, ".reroute", "content.ts");
1472
1876
  const reg = await dynamicImport(registryPath, isWatchMode);
1473
1877
  const get = reg?.getContentEntry;
1474
- const entry = typeof get === "function" ? get(collection, name) : undefined;
1878
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
1475
1879
  const moduleUrl = entry?.module;
1476
1880
  if (!moduleUrl?.endsWith(".js"))
1477
1881
  return null;
@@ -1497,7 +1901,7 @@ async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1497
1901
  async function tryChunkImport(collection, name, cwd, isWatchMode) {
1498
1902
  try {
1499
1903
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
1500
- const files = await readdir2(chunkDir);
1904
+ const files = await readdir3(chunkDir);
1501
1905
  const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
1502
1906
  if (!matches.length)
1503
1907
  return null;
@@ -1517,23 +1921,23 @@ async function tryChunkImport(collection, name, cwd, isWatchMode) {
1517
1921
  } catch {}
1518
1922
  return null;
1519
1923
  }
1520
- async function trySourceImport(collection, name, clientDir, isWatchMode) {
1924
+ async function trySourceImport(collection, name, clientDir, isWatchMode, locale) {
1521
1925
  try {
1522
- const srcTsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
1523
- const srcTs = join(clientDir, "routes", collection, "content", `${name}.ts`);
1524
- let absSrc = null;
1525
- if (await Bun.file(srcTsx).exists()) {
1526
- absSrc = srcTsx;
1527
- } else if (await Bun.file(srcTs).exists()) {
1528
- absSrc = srcTs;
1926
+ const candidates = [];
1927
+ if (locale) {
1928
+ candidates.push(join(clientDir, "routes", collection, "content", locale, `${name}.tsx`), join(clientDir, "routes", collection, "content", locale, `${name}.ts`));
1529
1929
  }
1530
- if (absSrc) {
1531
- return await dynamicImport(absSrc, isWatchMode);
1930
+ candidates.push(join(clientDir, "routes", collection, "content", `${name}.tsx`), join(clientDir, "routes", collection, "content", `${name}.ts`));
1931
+ for (const absSrc of candidates) {
1932
+ if (await Bun.file(absSrc).exists()) {
1933
+ return await dynamicImport(absSrc, isWatchMode);
1934
+ }
1532
1935
  }
1533
1936
  } catch {}
1534
1937
  return null;
1535
1938
  }
1536
1939
  var init_modules = __esm(() => {
1940
+ init_content_path();
1537
1941
  init_imports();
1538
1942
  });
1539
1943
 
@@ -1564,14 +1968,11 @@ function touchModule(key) {
1564
1968
  }
1565
1969
  async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1566
1970
  try {
1567
- const pathOnly = pathname.split("?")[0].split("#")[0];
1568
- const parts = pathOnly.split("/").filter(Boolean);
1569
- if (parts.length < 2) {
1971
+ const resolved = resolveContentPathParts(pathname);
1972
+ if (!resolved) {
1570
1973
  return;
1571
1974
  }
1572
- const collection = parts[0];
1573
- const name = parts[1];
1574
- const key = `${collection}:${name}`;
1975
+ const { key } = resolved;
1575
1976
  const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode);
1576
1977
  if (!mod) {
1577
1978
  return;
@@ -1591,6 +1992,7 @@ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1591
1992
  }
1592
1993
  var MAX_SSR_MODULES = 200, moduleAccessOrder;
1593
1994
  var init_seed = __esm(() => {
1995
+ init_content_path();
1594
1996
  init_modules();
1595
1997
  moduleAccessOrder = [];
1596
1998
  });
@@ -1598,17 +2000,17 @@ var init_seed = __esm(() => {
1598
2000
  // packages/core/src/ssr/lib/data.ts
1599
2001
  function loadContentData(pathname, searchParams) {
1600
2002
  try {
1601
- const parts = pathname.split("/").filter(Boolean);
1602
- if (parts.length < 2)
2003
+ const resolved = resolveContentPathParts(pathname);
2004
+ if (!resolved)
1603
2005
  return null;
1604
- const key = `${parts[0]}:${parts[1]}`;
2006
+ const key = resolved.key;
1605
2007
  const g = globalThis;
1606
2008
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1607
2009
  const dataFn = exp?.ssr?.data;
1608
2010
  if (typeof dataFn === "function") {
1609
2011
  const rawResult = dataFn({
1610
2012
  pathname,
1611
- params: { name: parts[1] },
2013
+ params: { name: resolved.name },
1612
2014
  searchParams
1613
2015
  });
1614
2016
  const result = processDataResult(rawResult, pathname, pathname);
@@ -1771,6 +2173,7 @@ function createLazyMarker(pathname, key) {
1771
2173
  };
1772
2174
  }
1773
2175
  var init_data = __esm(() => {
2176
+ init_content_path();
1774
2177
  init_imports();
1775
2178
  init_seed();
1776
2179
  });
@@ -1971,14 +2374,14 @@ function replaceAppRoot(html, appHtml, appId) {
1971
2374
  var init_html = () => {};
1972
2375
 
1973
2376
  // packages/core/src/og/discovery.ts
1974
- import { readdir as readdir3 } from "node:fs/promises";
2377
+ import { readdir as readdir4 } from "node:fs/promises";
1975
2378
  import { pathToFileURL as pathToFileURL4 } from "node:url";
1976
2379
  function isCatchAllRouteDirectory(name) {
1977
2380
  return CATCH_ALL_ROUTE_DIRECTORY.test(name);
1978
2381
  }
1979
2382
  async function listDynamicRouteDirectories(baseDir) {
1980
2383
  try {
1981
- const entries = await readdir3(baseDir, { withFileTypes: true });
2384
+ const entries = await readdir4(baseDir, { withFileTypes: true });
1982
2385
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => DYNAMIC_ROUTE_DIRECTORY.test(name)).sort((a, b) => {
1983
2386
  const aIsCatchAll = isCatchAllRouteDirectory(a);
1984
2387
  const bIsCatchAll = isCatchAllRouteDirectory(b);
@@ -1998,7 +2401,7 @@ async function collectOGCandidatePaths(currentDir, parts, depth, pathsToTry, all
1998
2401
  }
1999
2402
  const literalDir = join(currentDir, parts[depth]);
2000
2403
  try {
2001
- await readdir3(literalDir);
2404
+ await readdir4(literalDir);
2002
2405
  await collectOGCandidatePaths(literalDir, parts, depth + 1, pathsToTry, true);
2003
2406
  } catch {}
2004
2407
  const dynamicDirs = await listDynamicRouteDirectories(currentDir);
@@ -2153,7 +2556,9 @@ async function extractPageMetadata(pathname, clientDir, cwd, isWatchMode, curren
2153
2556
  let perPageHead = "";
2154
2557
  let pageLang;
2155
2558
  let statusOverride = currentStatusOverride;
2156
- await extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, (head) => {
2559
+ const resolved = resolveContentPathParts(pathname);
2560
+ const routingPathname = resolved?.routingPathname || pathname;
2561
+ await extractLayoutMetadata(routingPathname, clientDir, cwd, isWatchMode, (head) => {
2157
2562
  perPageHead += head;
2158
2563
  }, (lang) => {
2159
2564
  if (!pageLang)
@@ -2188,8 +2593,8 @@ ${ogMetaTags}`;
2188
2593
  perPageHead += head;
2189
2594
  }, (lang) => {
2190
2595
  pageLang = lang;
2191
- }, ogConfig, titleSuffix);
2192
- const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
2596
+ }, ogConfig, titleSuffix, baseUrl);
2597
+ const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2193
2598
  perPageHead += routeResult.head;
2194
2599
  if (routeResult.lang)
2195
2600
  pageLang = routeResult.lang;
@@ -2229,14 +2634,42 @@ async function extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, addH
2229
2634
  }
2230
2635
  } catch {}
2231
2636
  }
2232
- function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix) {
2637
+ function resolveContentLocales(collection, name, fallbackLocales) {
2638
+ const g = globalThis;
2639
+ const byCollection = g.__REROUTE_COLLECTIONS__;
2640
+ const items = byCollection?.[collection] || [];
2641
+ const localesForEntry = [
2642
+ ...new Set(items.filter((it) => it.name === name && it.locale).map((it) => String(it.locale)))
2643
+ ];
2644
+ return localesForEntry.length > 0 ? localesForEntry : fallbackLocales;
2645
+ }
2646
+ function addContentI18nHead(addHead, baseUrl, collection, name, locales, defaultLocale, activeLocale) {
2647
+ if (locales.length > 1) {
2648
+ addHead(`
2649
+ ${hreflangTags(baseUrl, collection, name, locales, defaultLocale)}`);
2650
+ }
2651
+ addHead(`
2652
+ <meta property="og:locale" content="${toOgLocale(activeLocale)}" />`);
2653
+ for (const loc of locales) {
2654
+ if (loc === activeLocale)
2655
+ continue;
2656
+ addHead(`
2657
+ <meta property="og:locale:alternate" content="${toOgLocale(loc)}" />`);
2658
+ }
2659
+ const canonicalPath = localizeContentHref(collection, name, activeLocale, defaultLocale);
2660
+ const base = baseUrl.replace(/\/$/, "");
2661
+ addHead(`
2662
+ <link rel="canonical" href="${base}${canonicalPath}" />`);
2663
+ addHead(`
2664
+ <meta property="og:url" content="${base}${canonicalPath}" />`);
2665
+ }
2666
+ function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix, baseUrl) {
2233
2667
  try {
2234
- const parts = pathname.split("/").filter(Boolean);
2235
- if (parts.length < 2)
2668
+ const resolved = resolveContentPathParts(pathname);
2669
+ if (!resolved)
2236
2670
  return;
2237
- const key = `${parts[0]}:${parts[1]}`;
2238
2671
  const g = globalThis;
2239
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2672
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
2240
2673
  const meta = exp?.meta;
2241
2674
  const ssr = exp?.ssr;
2242
2675
  addHead(buildHeadFromMeta(meta, ogConfig, titleSuffix));
@@ -2247,15 +2680,20 @@ ${ssrHead}`);
2247
2680
  const lang = extractSSRLang(ssr);
2248
2681
  if (lang)
2249
2682
  setLang(lang);
2683
+ const i18n = g.__REROUTE_I18N__;
2684
+ if (i18n?.enabled && baseUrl && i18n.locales && i18n.locales.length > 1) {
2685
+ const locales = resolveContentLocales(resolved.collection, resolved.name, i18n.locales);
2686
+ addContentI18nHead(addHead, baseUrl, resolved.collection, resolved.name, locales, i18n.defaultLocale || "en-US", i18n.locale || i18n.defaultLocale || "en-US");
2687
+ }
2250
2688
  } catch {}
2251
2689
  }
2252
2690
  function getContentMetaFromExports(pathname) {
2253
2691
  try {
2254
- const parts = pathname.split("/").filter(Boolean);
2255
- if (parts.length < 2)
2692
+ const resolved = resolveContentPathParts(pathname);
2693
+ if (!resolved)
2256
2694
  return;
2257
- const slug = parts[1];
2258
- const key = `${parts[0]}:${slug}`;
2695
+ const slug = resolved.name;
2696
+ const key = resolved.key;
2259
2697
  const g = globalThis;
2260
2698
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2261
2699
  const meta = exp?.meta;
@@ -2269,9 +2707,10 @@ function getContentMetaFromExports(pathname) {
2269
2707
  return;
2270
2708
  }
2271
2709
  }
2272
- async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix) {
2710
+ async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2273
2711
  const pathnameOnly = pathname.split("?")[0];
2274
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
2712
+ const matchPath = (routingPathname || pathname).split("?")[0];
2713
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2275
2714
  const r = match?.route;
2276
2715
  if (!r) {
2277
2716
  return { head: "", statusOverride: statusOverride || 404 };
@@ -2311,7 +2750,7 @@ ${ssrHead}`;
2311
2750
  }
2312
2751
  return { head: "", statusOverride };
2313
2752
  }
2314
- async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix) {
2753
+ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2315
2754
  let head = "";
2316
2755
  let lang;
2317
2756
  let statusOverride = currentStatusOverride;
@@ -2321,14 +2760,14 @@ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, curre
2321
2760
  if (!m) {
2322
2761
  return { head, lang, statusOverride: statusOverride || 404 };
2323
2762
  }
2324
- const pathnameOnly = pathname.split("?")[0];
2325
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
2763
+ const matchPath = (routingPathname || pathname).split("?")[0];
2764
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2326
2765
  const r = match?.route;
2327
2766
  if (!r) {
2328
2767
  statusOverride = statusOverride || 404;
2329
2768
  }
2330
2769
  if (r && typeof r.path === "string") {
2331
- const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
2770
+ const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2332
2771
  head = result.head;
2333
2772
  lang = result.lang;
2334
2773
  statusOverride = result.statusOverride;
@@ -2503,34 +2942,34 @@ function applyTitleSuffixToHead(headContent, titleSuffix) {
2503
2942
  }
2504
2943
  var init_metadata2 = __esm(() => {
2505
2944
  init_metadata();
2945
+ init_pathname();
2506
2946
  init_meta();
2507
2947
  init_compute();
2948
+ init_content_path();
2508
2949
  init_imports();
2509
2950
  });
2510
2951
 
2511
2952
  // packages/core/src/ssr/lib/preload.ts
2512
- import { readdir as readdir4, stat as stat4 } from "node:fs/promises";
2953
+ import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
2513
2954
  async function preloadContentModule(pathname, clientDir, cwd, isWatchMode) {
2514
2955
  let extraHead = "";
2515
2956
  let hydrationScript = "";
2516
2957
  let statusOverride;
2517
2958
  try {
2518
- const pathOnly = pathname.split("?")[0].split("#")[0];
2519
- const parts = pathOnly.split("/").filter(Boolean);
2520
- if (parts.length < 2) {
2959
+ const { resolveContentPathParts: resolveContentPathParts2 } = await Promise.resolve().then(() => (init_content_path(), exports_content_path));
2960
+ const resolved = resolveContentPathParts2(pathname);
2961
+ if (!resolved) {
2521
2962
  return { extraHead, hydrationScript };
2522
2963
  }
2523
- const collection = parts[0];
2524
- const name = parts[1];
2964
+ const { collection, name, key, locale } = resolved;
2525
2965
  if (!await isContentCollection(clientDir, collection)) {
2526
2966
  return { extraHead, hydrationScript };
2527
2967
  }
2528
- const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode);
2968
+ const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale);
2529
2969
  if (!modulePath) {
2530
2970
  statusOverride = 404;
2531
2971
  return { extraHead, hydrationScript, statusOverride };
2532
2972
  }
2533
- const key = `${collection}:${name}`;
2534
2973
  extraHead += `
2535
2974
  <link rel="modulepreload" href="${modulePath}" />`;
2536
2975
  hydrationScript += `<script>
@@ -2572,12 +3011,12 @@ async function findLatestCandidate(chunkDir, candidates) {
2572
3011
  }
2573
3012
  return latest;
2574
3013
  }
2575
- async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode) {
3014
+ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale) {
2576
3015
  try {
2577
3016
  const registryPath = join(cwd, ".reroute", "content.ts");
2578
3017
  const reg = await dynamicImport(registryPath, isWatchMode);
2579
3018
  const get = reg?.getContentEntry;
2580
- const entry = typeof get === "function" ? get(collection, name) : undefined;
3019
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
2581
3020
  const moduleUrl = entry?.module;
2582
3021
  if (moduleUrl?.endsWith(".js")) {
2583
3022
  return moduleUrl;
@@ -2585,7 +3024,7 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2585
3024
  } catch {}
2586
3025
  try {
2587
3026
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
2588
- const files = await readdir4(chunkDir);
3027
+ const files = await readdir5(chunkDir);
2589
3028
  const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2590
3029
  if (candidates.length) {
2591
3030
  const latest = await findLatestCandidate(chunkDir, candidates);
@@ -2593,13 +3032,18 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2593
3032
  }
2594
3033
  } catch {}
2595
3034
  try {
2596
- const tsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
2597
- const ts = join(clientDir, "routes", collection, "content", `${name}.ts`);
2598
- if (await Bun.file(tsx).exists()) {
2599
- return `/${join("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
2600
- }
2601
- if (await Bun.file(ts).exists()) {
2602
- return `/${join("routes", collection, "content", `${name}.ts`).replace(/\\+/g, "/")}`;
3035
+ const contentRoot = join(clientDir, "routes", collection, "content");
3036
+ const candidates = locale ? [
3037
+ join(contentRoot, locale, `${name}.tsx`),
3038
+ join(contentRoot, locale, `${name}.ts`),
3039
+ join(contentRoot, `${name}.tsx`),
3040
+ join(contentRoot, `${name}.ts`)
3041
+ ] : [join(contentRoot, `${name}.tsx`), join(contentRoot, `${name}.ts`)];
3042
+ for (const abs of candidates) {
3043
+ if (await Bun.file(abs).exists()) {
3044
+ const rel = abs.startsWith(clientDir) ? abs.slice(clientDir.length).replace(/^[/\\]/, "") : join("routes", collection, "content", `${name}.tsx`);
3045
+ return `/${rel.replace(/\\+/g, "/")}`;
3046
+ }
2603
3047
  }
2604
3048
  } catch {}
2605
3049
  return null;
@@ -2793,11 +3237,18 @@ function duplicateQueryParamData(ssrData) {
2793
3237
  return duplicated;
2794
3238
  }
2795
3239
  function getContentKeyFromPathname(pathname) {
2796
- const pathOnly = pathname.split("?")[0].split("#")[0];
2797
- const parts = pathOnly.split("/").filter(Boolean);
2798
- if (parts.length < 2)
2799
- return null;
2800
- return `${parts[0]}:${parts[1]}`;
3240
+ return resolveContentPathParts(pathname)?.key ?? null;
3241
+ }
3242
+ function createI18nScript(i18n) {
3243
+ if (!i18n?.enabled)
3244
+ return "";
3245
+ try {
3246
+ const json = escapeJsonForScript(JSON.stringify(i18n));
3247
+ return `<script>(function(){try{var w=(typeof window!=='undefined'?window:globalThis);w.__REROUTE_I18N__=${json};}catch(e){}})();</script>`;
3248
+ } catch (error) {
3249
+ console.error("[reroute] Failed to serialize i18n hydration:", error);
3250
+ return "";
3251
+ }
2801
3252
  }
2802
3253
  function createContentCaptureScript() {
2803
3254
  return `<script>(function(){try{var w=window;w.__REROUTE_INITIAL_HYDRATION__=true;var h={};document.querySelectorAll('[data-reroute-key]').forEach(function(el){var k=el.getAttribute('data-reroute-key');if(k)h[k]=el.innerHTML;});w.__REROUTE_CONTENT_HTML__=h;var r=document.querySelector('[data-reroute-route="ssr"]');if(r)w.__REROUTE_ROUTE_HTML__=r.innerHTML;}catch(e){}})();</script>`;
@@ -2873,13 +3324,14 @@ function createSSRPathnameScript(pathname) {
2873
3324
  return "";
2874
3325
  }
2875
3326
  }
2876
- function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix) {
3327
+ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n) {
2877
3328
  let script = "";
2878
3329
  script += createContentCaptureScript();
2879
3330
  script += createSSRPathnameScript(pathname);
2880
3331
  if (pathname) {
2881
3332
  script += createSSRExportsScript(pathname);
2882
3333
  }
3334
+ script += createI18nScript(i18n);
2883
3335
  script += createTelemetryConfigScript(browserTelemetryConfig);
2884
3336
  script += createTraceContextScript(traceContext);
2885
3337
  script += createTitleSuffixScript(titleSuffix);
@@ -2899,7 +3351,9 @@ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, t
2899
3351
  }
2900
3352
  return script;
2901
3353
  }
2902
- var init_data2 = () => {};
3354
+ var init_data2 = __esm(() => {
3355
+ init_content_path();
3356
+ });
2903
3357
 
2904
3358
  // packages/core/src/ssr/lib/scripts/feeds.ts
2905
3359
  function createFeedsScript() {
@@ -3127,10 +3581,10 @@ function generateCollectionScripts(byCollectionForSSR) {
3127
3581
  } catch {}
3128
3582
  return "";
3129
3583
  }
3130
- function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix) {
3584
+ function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix, i18n) {
3131
3585
  let scripts = preloadScript;
3132
3586
  scripts += collectionScripts;
3133
- scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix);
3587
+ scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n);
3134
3588
  scripts += createFeedsScript();
3135
3589
  scripts += createLlmsScript();
3136
3590
  if (bundleUrl) {
@@ -3293,7 +3747,7 @@ async function renderSSRDocument(options) {
3293
3747
  const browserTelemetryConfig = serializeBrowserTelemetryConfig2(browserTelemetryConfigRaw);
3294
3748
  const traceContext = getTraceContextForInjection(options.parentSpan);
3295
3749
  const hydrationScript = await withSpan("ssr.generate.scripts", async (span) => {
3296
- const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix);
3750
+ const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix, options.i18n);
3297
3751
  span.setAttribute("reroute.scripts.size", script.length);
3298
3752
  return script;
3299
3753
  });
@@ -3311,12 +3765,13 @@ async function renderSSRDocument(options) {
3311
3765
  dedent(setup2.metadataResult.perPageHead)
3312
3766
  ].filter(Boolean).join(`
3313
3767
  `));
3768
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
3314
3769
  const html2 = withSpanSync("ssr.apply.template", (span) => {
3315
3770
  const result = applyIndexTemplate(baseTemplate, appHtml, {
3316
3771
  head: [setup2.inlineStyleTag, combinedHead].filter(Boolean).join(`
3317
3772
  `),
3318
3773
  hydrationScript,
3319
- lang: setup2.metadataResult.pageLang || lang,
3774
+ lang: setup2.metadataResult.pageLang || i18nLocale || lang,
3320
3775
  appId
3321
3776
  });
3322
3777
  span.setAttributes({
@@ -4494,6 +4949,55 @@ async function discoverRoutes(cwd, isWatchMode) {
4494
4949
  }
4495
4950
  return { staticRoutes, dynamicRoutes };
4496
4951
  }
4952
+ function groupNamedCollectionItems(items) {
4953
+ const byName = new Map;
4954
+ for (const item of items) {
4955
+ const name = String(item.name || "");
4956
+ if (!name)
4957
+ continue;
4958
+ const list = byName.get(name) || [];
4959
+ list.push(item);
4960
+ byName.set(name, list);
4961
+ }
4962
+ return byName;
4963
+ }
4964
+ function resolveItemHref(collection, item, defaultLocale, localize) {
4965
+ const name = String(item.name || "");
4966
+ const locale = item.locale || defaultLocale;
4967
+ if (name)
4968
+ return localize(collection, name, locale, defaultLocale);
4969
+ return String(item.href || "");
4970
+ }
4971
+ function buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localize) {
4972
+ const locales = [
4973
+ ...new Set(variants.map((v) => v.locale || defaultLocale).filter(Boolean))
4974
+ ];
4975
+ const alternates = locales.length > 1 ? [
4976
+ ...locales.map((locale) => ({
4977
+ hreflang: locale,
4978
+ href: `${base}${localize(collection, name, locale, defaultLocale)}`
4979
+ })),
4980
+ {
4981
+ hreflang: "x-default",
4982
+ href: `${base}${localize(collection, name, defaultLocale, defaultLocale)}`
4983
+ }
4984
+ ] : undefined;
4985
+ const entries = [];
4986
+ for (const item of variants) {
4987
+ const href = resolveItemHref(collection, item, defaultLocale, localize);
4988
+ if (!href)
4989
+ continue;
4990
+ const meta = item.meta || {};
4991
+ entries.push({
4992
+ loc: `${base}${href}`,
4993
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4994
+ changefreq: "weekly",
4995
+ priority: 0.7,
4996
+ alternates
4997
+ });
4998
+ }
4999
+ return entries;
5000
+ }
4497
5001
  async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl) {
4498
5002
  const entries = [];
4499
5003
  try {
@@ -4505,18 +5009,26 @@ async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl)
4505
5009
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4506
5010
  const mod = await import(url);
4507
5011
  const items = mod.items || [];
5012
+ const defaultLocale = mod.defaultLocale || "en-US";
5013
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
5014
+ const base = baseUrl.replace(/\/$/, "");
5015
+ const byName = groupNamedCollectionItems(items);
5016
+ for (const [name, variants] of byName) {
5017
+ entries.push(...buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localizeContentHref2));
5018
+ }
4508
5019
  for (const item of items) {
5020
+ if (item.name)
5021
+ continue;
4509
5022
  const href = String(item.href || "");
5023
+ if (!href)
5024
+ continue;
4510
5025
  const meta = item.meta || {};
4511
- if (href) {
4512
- const entry = {
4513
- loc: `${baseUrl}${href}`,
4514
- lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4515
- changefreq: "weekly",
4516
- priority: 0.7
4517
- };
4518
- entries.push(entry);
4519
- }
5026
+ entries.push({
5027
+ loc: `${base}${href}`,
5028
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
5029
+ changefreq: "weekly",
5030
+ priority: 0.7
5031
+ });
4520
5032
  }
4521
5033
  } catch (error) {
4522
5034
  console.error(`[reroute] Failed to discover collection ${collection}:`, error);
@@ -4811,11 +5323,17 @@ async function discoverCollectionFeedItems(cwd, collection, isWatchMode, baseUrl
4811
5323
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4812
5324
  const mod = await import(url);
4813
5325
  const contentItems = mod.items || [];
5326
+ const defaultLocale = mod.defaultLocale || "en-US";
5327
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
4814
5328
  for (const item of contentItems) {
4815
- const href = String(item.href || "");
5329
+ const name = String(item.name || "");
5330
+ const locale = item.locale || defaultLocale;
5331
+ const href = name ? localizeContentHref2(collection, name, locale, defaultLocale) : String(item.href || "");
4816
5332
  const meta = item.meta || {};
4817
5333
  if (href) {
4818
- items.push(createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig));
5334
+ const rssItem = createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig);
5335
+ rssItem.locale = locale;
5336
+ items.push(rssItem);
4819
5337
  }
4820
5338
  }
4821
5339
  } catch (error) {
@@ -5081,7 +5599,7 @@ function generateRSSXML(feed, options = {}) {
5081
5599
  <link>${escapeXml(feed.link)}</link>
5082
5600
  <description>${escapeXml(feed.description)}</description>
5083
5601
  <lastBuildDate>${lastBuildDate}</lastBuildDate>
5084
- <language>en</language>
5602
+ <language>${escapeXml(feed.language || "en")}</language>
5085
5603
  <atom:link href="${escapeXml(feed.link)}" rel="self" type="application/rss+xml" />
5086
5604
  ${itemsXML}
5087
5605
  </channel>
@@ -5243,6 +5761,7 @@ async function generateSitemap(discovery4, baseUrl, options = {}) {
5243
5761
  return createSingleSitemap(allEntries);
5244
5762
  }
5245
5763
  function generateSitemapXML(entries) {
5764
+ const hasAlternates = entries.some((e) => e.alternates && e.alternates.length > 0);
5246
5765
  const urlsetEntries = entries.map((entry) => {
5247
5766
  let xml = ` <url>
5248
5767
  <loc>${escapeXml2(entry.loc)}</loc>`;
@@ -5258,13 +5777,20 @@ function generateSitemapXML(entries) {
5258
5777
  xml += `
5259
5778
  <priority>${entry.priority.toFixed(1)}</priority>`;
5260
5779
  }
5780
+ if (entry.alternates?.length) {
5781
+ for (const alt of entry.alternates) {
5782
+ xml += `
5783
+ <xhtml:link rel="alternate" hreflang="${escapeXml2(alt.hreflang)}" href="${escapeXml2(alt.href)}" />`;
5784
+ }
5785
+ }
5261
5786
  xml += `
5262
5787
  </url>`;
5263
5788
  return xml;
5264
5789
  }).join(`
5265
5790
  `);
5791
+ const xhtmlNs = hasAlternates ? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"' : "";
5266
5792
  return `<?xml version="1.0" encoding="UTF-8"?>
5267
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5793
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${xhtmlNs}>
5268
5794
  ${urlsetEntries}
5269
5795
  </urlset>`;
5270
5796
  }
@@ -5362,7 +5888,8 @@ async function renderSSRDocumentStream(options) {
5362
5888
  ...parts
5363
5889
  };
5364
5890
  });
5365
- const pageLang = setup2.metadataResult.pageLang || lang;
5891
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
5892
+ const pageLang = setup2.metadataResult.pageLang || i18nLocale || lang;
5366
5893
  let headWithLang = htmlHead.replace(/<html([^>]*)>/i, (_m, attrs) => {
5367
5894
  const hasLang = /(^|\s)lang\s*=/.test(attrs);
5368
5895
  const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${pageLang}"`) : `${attrs} lang="${pageLang}"`;
@@ -5405,7 +5932,8 @@ async function renderSSRDocumentStream(options) {
5405
5932
  parentSpan: options.parentSpan,
5406
5933
  timeout: options.timeout,
5407
5934
  ssrExport: setup2.metadataResult.ssrExport,
5408
- titleSuffix: options.titleSuffix
5935
+ titleSuffix: options.titleSuffix,
5936
+ i18n: options.i18n
5409
5937
  });
5410
5938
  if (debug)
5411
5939
  console.log(`[renderStream] ${pathname} - returning stream: ${(performance.now() - t0).toFixed(2)}ms`);
@@ -5518,7 +6046,7 @@ function buildStreamingScripts(ctx) {
5518
6046
  ssrDataSnapshot[key] = value;
5519
6047
  }
5520
6048
  }
5521
- const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix);
6049
+ const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix, ctx.i18n);
5522
6050
  const feedsScript = createFeedsScript();
5523
6051
  const llmsScript = createLlmsScript();
5524
6052
  let allScripts = dataScript + feedsScript + llmsScript + collectionScripts + ctx.preloadHydrationScript;
@@ -5650,6 +6178,7 @@ var init_src = __esm(() => {
5650
6178
  init_bundler();
5651
6179
  init_config();
5652
6180
  init_content();
6181
+ init_i18n();
5653
6182
  init_llms();
5654
6183
  init_og();
5655
6184
  init_robots();
@@ -5662,6 +6191,7 @@ var init_src = __esm(() => {
5662
6191
  var exports_core = {};
5663
6192
  __export(exports_core, {
5664
6193
  toPromise: () => toPromise,
6194
+ toOgLocale: () => toOgLocale,
5665
6195
  toBytes: () => toBytes,
5666
6196
  stripStart: () => stripStart,
5667
6197
  stripHtmlTags: () => stripHtmlTags,
@@ -5673,6 +6203,7 @@ __export(exports_core, {
5673
6203
  setGlobalSSRData: () => setGlobalSSRData,
5674
6204
  setGlobalCollections: () => setGlobalCollections,
5675
6205
  seedSSRModuleForPath: () => seedSSRModuleForPath,
6206
+ resolveLocale: () => resolveLocale,
5676
6207
  resetSSRAccessTracking: () => resetSSRAccessTracking,
5677
6208
  renderSSRDocumentStream: () => renderSSRDocumentStream,
5678
6209
  renderSSRDocument: () => renderSSRDocument,
@@ -5681,9 +6212,17 @@ __export(exports_core, {
5681
6212
  processCollections: () => processCollections,
5682
6213
  preloadContentModule: () => preloadContentModule,
5683
6214
  performSSRSetup: () => performSSRSetup,
6215
+ parseCookieValue: () => parseCookieValue,
6216
+ parseContentPath: () => parseContentPath,
6217
+ normalizePathname: () => normalizePathname,
6218
+ negotiateLocale: () => negotiateLocale,
5684
6219
  mergeLayoutData: () => mergeLayoutData,
5685
6220
  matchRoutes: () => matchRoutes,
5686
6221
  matchRoutePattern: () => matchRoutePattern,
6222
+ lookupMessage: () => lookupMessage,
6223
+ looksLikeLocale: () => looksLikeLocale,
6224
+ localizeHref: () => localizeHref,
6225
+ localizeContentHref: () => localizeContentHref,
5687
6226
  loadRoutesModule: () => loadRoutesModule,
5688
6227
  loadOGImageComponent: () => loadOGImageComponent,
5689
6228
  loadIndexHtml: () => loadIndexHtml,
@@ -5696,11 +6235,13 @@ __export(exports_core, {
5696
6235
  inlineTailwindCSS: () => inlineTailwindCSS,
5697
6236
  importContentModuleForPath: () => importContentModuleForPath,
5698
6237
  htmlToMarkdown: () => htmlToMarkdown,
6238
+ hreflangTags: () => hreflangTags,
5699
6239
  gzipIfAccepted: () => gzipIfAccepted,
5700
6240
  getSitemapExcludedRoutes: () => getSitemapExcludedRoutes,
5701
6241
  getRedirectSourcePaths: () => getRedirectSourcePaths,
5702
6242
  getPatternDepth: () => getPatternDepth,
5703
6243
  getMimeType: () => getMimeType,
6244
+ getMessage: () => getMessage,
5704
6245
  getMatchingLayouts: () => getMatchingLayouts,
5705
6246
  getContentMeta: () => getContentMeta,
5706
6247
  generateSitemapXML: () => generateSitemapXML,
@@ -5717,6 +6258,7 @@ __export(exports_core, {
5717
6258
  generateContentHash: () => generateContentHash,
5718
6259
  generateCollectionScripts: () => generateCollectionScripts,
5719
6260
  generateAtomXML: () => generateAtomXML,
6261
+ formatMessage: () => formatMessage,
5720
6262
  formatAsText: () => formatAsText,
5721
6263
  formatAsMarkdown: () => formatAsMarkdown,
5722
6264
  findOGImageForPath: () => findOGImageForPath,
@@ -5736,10 +6278,12 @@ __export(exports_core, {
5736
6278
  discoverRoutes: () => discoverRoutes,
5737
6279
  discoverRobotsData: () => discoverRobotsData,
5738
6280
  discoverRSSData: () => discoverRSSData,
6281
+ discoverI18n: () => discoverI18n,
5739
6282
  discoverCollections: () => discoverCollections,
5740
6283
  discoverCollectionContent: () => discoverCollectionContent,
5741
6284
  defineConfig: () => defineConfig,
5742
6285
  deduplicateMetaTags: () => deduplicateMetaTags,
6286
+ createTranslator: () => createTranslator,
5743
6287
  createLlmsScript: () => createLlmsScript,
5744
6288
  createLayoutDataKey: () => createLayoutDataKey,
5745
6289
  createLayoutCacheKey: () => createLayoutCacheKey,
@@ -5747,6 +6291,8 @@ __export(exports_core, {
5747
6291
  createDataScript: () => createDataScript,
5748
6292
  createCollectionScript: () => createCollectionScript,
5749
6293
  createBundlePreload: () => createBundlePreload,
6294
+ contentNegotiateRedirect: () => contentNegotiateRedirect,
6295
+ contentKey: () => contentKey,
5750
6296
  computeSSRDataForPath: () => computeSSRDataForPath,
5751
6297
  computeSSRData: () => computeSSRData,
5752
6298
  computeRouteData: () => computeRouteData,
@@ -5755,6 +6301,7 @@ __export(exports_core, {
5755
6301
  compressStreamIfAccepted: () => compressStreamIfAccepted,
5756
6302
  compareRouteSpecificity: () => compareRouteSpecificity,
5757
6303
  cleanupPathScopedSSRData: () => cleanupPathScopedSSRData,
6304
+ buildLocaleCookie: () => buildLocaleCookie,
5758
6305
  buildHeadFromMeta: () => buildHeadFromMeta,
5759
6306
  blockSearchCrawlers: () => blockSearchCrawlers,
5760
6307
  blockAITrainingCrawlers: () => blockAITrainingCrawlers,
@@ -5770,6 +6317,9 @@ __export(exports_core, {
5770
6317
  SEARCH_CRAWLERS: () => SEARCH_CRAWLERS,
5771
6318
  LRUCache: () => LRUCache,
5772
6319
  DefaultOGTemplate: () => DefaultOGTemplate,
6320
+ DEFAULT_LOCALES_DIR: () => DEFAULT_LOCALES_DIR,
6321
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
6322
+ DEFAULT_COOKIE: () => DEFAULT_COOKIE,
5773
6323
  AI_CRAWLERS: () => AI_CRAWLERS
5774
6324
  });
5775
6325
  var init_core = __esm(() => {
@@ -5784,7 +6334,7 @@ import { createElement as createElement2 } from "react";
5784
6334
 
5785
6335
  // packages/elysia/src/libs/image.ts
5786
6336
  import { existsSync } from "node:fs";
5787
- import { join as join2 } from "node:path";
6337
+ import { join as join3 } from "node:path";
5788
6338
  import sharp from "sharp";
5789
6339
  var imageCache = new Map;
5790
6340
  var MAX_CACHE_SIZE = 100;
@@ -5827,8 +6377,8 @@ async function loadFromDiskCache(cacheKey) {
5827
6377
  if (!diskCacheDir)
5828
6378
  return null;
5829
6379
  try {
5830
- const cachePath = join2(diskCacheDir, `${cacheKey}.json`);
5831
- const imagePath = join2(diskCacheDir, `${cacheKey}.img`);
6380
+ const cachePath = join3(diskCacheDir, `${cacheKey}.json`);
6381
+ const imagePath = join3(diskCacheDir, `${cacheKey}.img`);
5832
6382
  if (!(existsSync(cachePath) && existsSync(imagePath))) {
5833
6383
  return null;
5834
6384
  }
@@ -5850,8 +6400,8 @@ async function saveToDiskCache(cacheKey, result) {
5850
6400
  if (!diskCacheDir)
5851
6401
  return;
5852
6402
  try {
5853
- const cachePath = join2(diskCacheDir, `${cacheKey}.json`);
5854
- const imagePath = join2(diskCacheDir, `${cacheKey}.img`);
6403
+ const cachePath = join3(diskCacheDir, `${cacheKey}.json`);
6404
+ const imagePath = join3(diskCacheDir, `${cacheKey}.img`);
5855
6405
  await Bun.write(cachePath, JSON.stringify({
5856
6406
  contentType: result.contentType,
5857
6407
  format: result.format,
@@ -6272,7 +6822,7 @@ function registerContentRoutes(app, cwd, collectionCache, directive, maxAge, isW
6272
6822
  // packages/elysia/src/routes/image.ts
6273
6823
  init_core();
6274
6824
  import { existsSync as existsSync2 } from "node:fs";
6275
- import { join as join3 } from "node:path";
6825
+ import { join as join4 } from "node:path";
6276
6826
  function parseNumericParam(value) {
6277
6827
  if (!value)
6278
6828
  return;
@@ -6319,8 +6869,8 @@ function registerImageRoutes(app, options) {
6319
6869
  }
6320
6870
  function resolveInternalImagePath(url, clientDir2) {
6321
6871
  const cleanUrl = url.startsWith("/") ? url.slice(1) : url;
6322
- const imagePath = join3(clientDir2, cleanUrl);
6323
- const normalizedPath = join3(clientDir2, cleanUrl);
6872
+ const imagePath = join4(clientDir2, cleanUrl);
6873
+ const normalizedPath = join4(clientDir2, cleanUrl);
6324
6874
  if (!normalizedPath.startsWith(clientDir2)) {
6325
6875
  return { error: "Access denied" };
6326
6876
  }
@@ -7424,14 +7974,14 @@ function registerOGRoutes(app, options) {
7424
7974
  }
7425
7975
  return;
7426
7976
  }
7427
- function getFontPaths(fontPath, baseDir, join4) {
7977
+ function getFontPaths(fontPath, baseDir, join5) {
7428
7978
  const fontRelativePath = fontPath.replace(/^(\.\/)?src\/client\/assets\//, "").replace(/^assets\//, "");
7429
7979
  return [
7430
- join4(clientDir, "assets", fontRelativePath),
7431
- join4(baseDir, ".reroute", "assets", fontRelativePath),
7432
- join4(cwd, ".reroute", "assets", fontRelativePath),
7433
- join4(cwd, fontPath),
7434
- join4(baseDir, fontPath)
7980
+ join5(clientDir, "assets", fontRelativePath),
7981
+ join5(baseDir, ".reroute", "assets", fontRelativePath),
7982
+ join5(cwd, ".reroute", "assets", fontRelativePath),
7983
+ join5(cwd, fontPath),
7984
+ join5(baseDir, fontPath)
7435
7985
  ];
7436
7986
  }
7437
7987
  async function tryLoadFont(font, possiblePaths, existsSync3) {
@@ -7454,14 +8004,14 @@ function registerOGRoutes(app, options) {
7454
8004
  if (!ogConfig.fonts || ogConfig.fonts.length === 0) {
7455
8005
  return [];
7456
8006
  }
7457
- const { join: join4, dirname } = await import("node:path");
8007
+ const { join: join5, dirname } = await import("node:path");
7458
8008
  const { existsSync: existsSync3 } = await import("node:fs");
7459
8009
  const isCompiledBinary = typeof Bun !== "undefined" && process.argv[0]?.endsWith(process.argv[1]);
7460
8010
  const baseDir = isCompiledBinary ? dirname(process.argv[1]) : cwd;
7461
8011
  const loadedFonts = [];
7462
8012
  for (const font of ogConfig.fonts) {
7463
8013
  try {
7464
- const possiblePaths = getFontPaths(font.path, baseDir, join4);
8014
+ const possiblePaths = getFontPaths(font.path, baseDir, join5);
7465
8015
  const loadedFont = await tryLoadFont(font, possiblePaths, existsSync3);
7466
8016
  if (loadedFont) {
7467
8017
  loadedFonts.push(loadedFont);
@@ -7789,42 +8339,52 @@ async function registerMainFeedRoute(app, cwd, collections2, baseUrl, isWatchMod
7789
8339
  function registerCollectionFeedRoutes(app, collections2, options) {
7790
8340
  for (const collection of collections2) {
7791
8341
  options.availableFeeds.push(`/${collection}/feed.xml`);
7792
- app.get(`/${collection}/feed.xml`, async () => {
7793
- const cacheKey = `rss:${collection}`;
7794
- try {
7795
- const cached = options.rssCache.get(cacheKey);
7796
- if (cached) {
7797
- return feedResponse(cached, options.format, {
7798
- maxAge: options.maxAge,
7799
- cacheStatus: "HIT"
7800
- });
7801
- }
7802
- const discovery5 = await getOrDiscoverRSSData(options.cwd, [collection], options.baseUrl, options.isWatchMode, options.rssConfig);
7803
- const items = discovery5.collections.get(collection) ?? [];
7804
- if (items.length === 0)
7805
- throw new NotFoundError8;
7806
- const feed = {
7807
- title: createFeedTitle(options.rssConfig?.title, collection),
7808
- link: `${options.baseUrl}/${collection}/feed.xml`,
7809
- description: options.rssConfig?.description ?? `Latest ${collection} updates`,
7810
- items
7811
- };
7812
- const xml = generateFeed(feed, {
7813
- format: options.format,
7814
- limit: options.limit
7815
- });
7816
- options.rssCache.set(cacheKey, xml);
7817
- return feedResponse(xml, options.format, {
7818
- maxAge: options.maxAge,
7819
- cacheStatus: "MISS"
7820
- });
7821
- } catch (error) {
7822
- if (error instanceof NotFoundError8)
7823
- throw error;
7824
- console.error(`[reroute] Failed to generate ${collection} feed:`, error);
7825
- throw new NotFoundError8;
7826
- }
8342
+ app.get(`/${collection}/feed.xml`, async ({ request }) => handleCollectionFeed(collection, request, options));
8343
+ }
8344
+ }
8345
+ function filterFeedItemsByLang(items, lang) {
8346
+ if (!lang)
8347
+ return items;
8348
+ return items.filter((item) => !item.locale || item.locale === lang);
8349
+ }
8350
+ function buildCollectionFeed(collection, items, lang, options) {
8351
+ return {
8352
+ title: createFeedTitle(options.rssConfig?.title, collection),
8353
+ link: lang ? `${options.baseUrl}/${collection}/feed.xml?lang=${encodeURIComponent(lang)}` : `${options.baseUrl}/${collection}/feed.xml`,
8354
+ description: options.rssConfig?.description ?? `Latest ${collection} updates`,
8355
+ items,
8356
+ language: lang || items[0]?.locale || "en"
8357
+ };
8358
+ }
8359
+ async function handleCollectionFeed(collection, request, options) {
8360
+ const lang = new URL(request.url).searchParams.get("lang") || undefined;
8361
+ const cacheKey = lang ? `rss:${collection}:${lang}` : `rss:${collection}`;
8362
+ try {
8363
+ const cached = options.rssCache.get(cacheKey);
8364
+ if (cached) {
8365
+ return feedResponse(cached, options.format, {
8366
+ maxAge: options.maxAge,
8367
+ cacheStatus: "HIT"
8368
+ });
8369
+ }
8370
+ const discovery5 = await getOrDiscoverRSSData(options.cwd, [collection], options.baseUrl, options.isWatchMode, options.rssConfig);
8371
+ const items = filterFeedItemsByLang(discovery5.collections.get(collection) ?? [], lang);
8372
+ if (items.length === 0)
8373
+ throw new NotFoundError8;
8374
+ const xml = generateFeed(buildCollectionFeed(collection, items, lang, options), {
8375
+ format: options.format,
8376
+ limit: options.limit
8377
+ });
8378
+ options.rssCache.set(cacheKey, xml);
8379
+ return feedResponse(xml, options.format, {
8380
+ maxAge: options.maxAge,
8381
+ cacheStatus: "MISS"
7827
8382
  });
8383
+ } catch (error) {
8384
+ if (error instanceof NotFoundError8)
8385
+ throw error;
8386
+ console.error(`[reroute] Failed to generate ${collection} feed:`, error);
8387
+ throw new NotFoundError8;
7828
8388
  }
7829
8389
  }
7830
8390
  async function discoverRoutesWithFeeds(ssrDataRoutes, collections2, options) {
@@ -8000,11 +8560,11 @@ function scoreEntry(entry, terms) {
8000
8560
  return { score, matchedHeadings };
8001
8561
  }
8002
8562
  function searchEntries(entries, query, options = {}) {
8003
- const { collections: collections2, page = 1, pageSize = 20 } = options;
8563
+ const { collections: collections2, page = 1, pageSize = 20, locale } = options;
8004
8564
  const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
8005
8565
  if (terms.length === 0)
8006
8566
  return { results: [], total: 0 };
8007
- const scored = entries.filter((entry) => !collections2?.length || collections2.includes(entry.collection)).map((entry) => ({ entry, ...scoreEntry(entry, terms) })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score);
8567
+ const scored = entries.filter((entry) => !collections2?.length || collections2.includes(entry.collection)).filter((entry) => !(locale && entry.locale) || entry.locale === locale).map((entry) => ({ entry, ...scoreEntry(entry, terms) })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score);
8008
8568
  const total = scored.length;
8009
8569
  const startIndex = (page - 1) * pageSize;
8010
8570
  const paginatedScored = scored.slice(startIndex, startIndex + pageSize);
@@ -8055,10 +8615,12 @@ function registerSearchRoute(app, cwd, directive, maxAge, searchConfig) {
8055
8615
  const page = query.page ? Math.max(1, Number.parseInt(String(query.page), 10)) : 1;
8056
8616
  const requestedPageSize = query.pageSize ? Number.parseInt(String(query.pageSize), 10) : defaultPageSize;
8057
8617
  const pageSize = Math.min(Math.max(1, requestedPageSize), maxPageSize);
8618
+ const locale = query.lang ? String(query.lang) : undefined;
8058
8619
  const { results, total } = searchEntries(index.entries, searchQuery, {
8059
8620
  collections: collections2,
8060
8621
  page,
8061
- pageSize
8622
+ pageSize,
8623
+ locale
8062
8624
  });
8063
8625
  return Response.json({
8064
8626
  total,
@@ -8162,9 +8724,115 @@ function registerSitemapRoutes(app, options) {
8162
8724
  // packages/elysia/src/routes/ssr.ts
8163
8725
  init_core();
8164
8726
  init_server2();
8165
- import { join as join4 } from "node:path";
8727
+ import { join as join6 } from "node:path";
8166
8728
  import { trace as trace4 } from "@opentelemetry/api";
8167
8729
  import { NotFoundError as NotFoundError10 } from "elysia";
8730
+
8731
+ // packages/elysia/src/libs/i18n.ts
8732
+ init_core();
8733
+ import { join as join5 } from "node:path";
8734
+ var cachedI18n = null;
8735
+ async function loadI18nState(cwd) {
8736
+ const path2 = join5(cwd, ".reroute", "i18n.json");
8737
+ try {
8738
+ const file = Bun.file(path2);
8739
+ if (!await file.exists()) {
8740
+ return {
8741
+ enabled: false,
8742
+ defaultLocale: "en-US",
8743
+ locales: [],
8744
+ localesDir: "locales",
8745
+ cookie: "reroute-locale",
8746
+ redirectContentOnNegotiate: true,
8747
+ catalogs: {}
8748
+ };
8749
+ }
8750
+ const stat5 = await file.stat();
8751
+ const mtime = stat5?.mtime?.getTime?.() ?? Date.now();
8752
+ if (cachedI18n && cachedI18n.cwd === cwd && cachedI18n.mtime === mtime) {
8753
+ return cachedI18n.data;
8754
+ }
8755
+ const data = await file.json();
8756
+ cachedI18n = { cwd, data, mtime };
8757
+ return data;
8758
+ } catch {
8759
+ return {
8760
+ enabled: false,
8761
+ defaultLocale: "en-US",
8762
+ locales: [],
8763
+ localesDir: "locales",
8764
+ cookie: "reroute-locale",
8765
+ redirectContentOnNegotiate: true,
8766
+ catalogs: {}
8767
+ };
8768
+ }
8769
+ }
8770
+ async function listContentCollections(cwd) {
8771
+ try {
8772
+ const { readdir: readdir6 } = await import("node:fs/promises");
8773
+ const dir = join5(cwd, ".reroute", "collections");
8774
+ const entries = await readdir6(dir);
8775
+ return entries.filter((f) => f.endsWith(".js")).map((f) => f.replace(/\.js$/, ""));
8776
+ } catch {
8777
+ return [];
8778
+ }
8779
+ }
8780
+ async function resolveI18nForRequest(cwd, pathname, headers2, collections2) {
8781
+ const i18n2 = await loadI18nState(cwd);
8782
+ const disabled = {
8783
+ enabled: false,
8784
+ locale: i18n2.defaultLocale,
8785
+ defaultLocale: i18n2.defaultLocale,
8786
+ locales: [],
8787
+ cookie: i18n2.cookie,
8788
+ catalogs: {},
8789
+ hydration: { enabled: false }
8790
+ };
8791
+ if (!i18n2.enabled || i18n2.locales.length === 0) {
8792
+ return disabled;
8793
+ }
8794
+ const knownCollections = collections2?.length ? collections2 : await listContentCollections(cwd);
8795
+ const resolution = resolveLocale({
8796
+ pathname,
8797
+ cookieHeader: headers2.cookie,
8798
+ acceptLanguage: headers2["accept-language"],
8799
+ availableLocales: i18n2.locales,
8800
+ defaultLocale: i18n2.defaultLocale,
8801
+ cookieName: i18n2.cookie,
8802
+ collections: knownCollections.length > 0 ? knownCollections : undefined
8803
+ });
8804
+ let redirectTo = resolution.redirectTo;
8805
+ if (!redirectTo && i18n2.redirectContentOnNegotiate && knownCollections.length > 0) {
8806
+ redirectTo = contentNegotiateRedirect(resolution, i18n2.defaultLocale, true);
8807
+ }
8808
+ const cookieValue = buildLocaleCookie(i18n2.cookie, resolution.locale);
8809
+ const hydration = {
8810
+ enabled: true,
8811
+ locale: resolution.locale,
8812
+ defaultLocale: i18n2.defaultLocale,
8813
+ locales: i18n2.locales,
8814
+ cookie: i18n2.cookie,
8815
+ catalogs: i18n2.catalogs,
8816
+ collections: knownCollections
8817
+ };
8818
+ try {
8819
+ globalThis.__REROUTE_I18N__ = hydration;
8820
+ } catch {}
8821
+ return {
8822
+ enabled: true,
8823
+ locale: resolution.locale,
8824
+ defaultLocale: i18n2.defaultLocale,
8825
+ locales: i18n2.locales,
8826
+ cookie: i18n2.cookie,
8827
+ catalogs: i18n2.catalogs,
8828
+ collections: knownCollections,
8829
+ setCookie: cookieValue,
8830
+ redirectTo,
8831
+ hydration
8832
+ };
8833
+ }
8834
+
8835
+ // packages/elysia/src/routes/ssr.ts
8168
8836
  if (typeof process !== "undefined") {
8169
8837
  process.on("unhandledRejection", (reason, _promise) => {
8170
8838
  console.error("[reroute] Unhandled Promise Rejection:", reason);
@@ -8186,6 +8854,28 @@ function isOtelAvailable3() {
8186
8854
  return false;
8187
8855
  }
8188
8856
  }
8857
+ async function applyI18nForSSR(cwd, pathname, headers2, set, fallbackLang) {
8858
+ const i18nResult = await resolveI18nForRequest(cwd, pathname, headers2);
8859
+ if (i18nResult.redirectTo && i18nResult.redirectTo !== pathname) {
8860
+ const redirectHeaders = {
8861
+ Location: i18nResult.redirectTo
8862
+ };
8863
+ if (i18nResult.setCookie) {
8864
+ redirectHeaders["Set-Cookie"] = i18nResult.setCookie;
8865
+ }
8866
+ return {
8867
+ redirect: new Response(null, { status: 302, headers: redirectHeaders }),
8868
+ effectiveLang: fallbackLang
8869
+ };
8870
+ }
8871
+ if (i18nResult.setCookie) {
8872
+ set.headers["set-cookie"] = i18nResult.setCookie;
8873
+ }
8874
+ return {
8875
+ hydration: i18nResult.enabled ? i18nResult.hydration : undefined,
8876
+ effectiveLang: i18nResult.enabled ? i18nResult.locale : fallbackLang
8877
+ };
8878
+ }
8189
8879
  function registerSSRRoutes(app, options) {
8190
8880
  const {
8191
8881
  rootComponent,
@@ -8225,7 +8915,7 @@ function registerSSRRoutes(app, options) {
8225
8915
  let contentRoutes = [];
8226
8916
  let dynamicRoutes = [];
8227
8917
  try {
8228
- const manifestPath = join4(cwd, ".reroute", "manifest.json");
8918
+ const manifestPath = join6(cwd, ".reroute", "manifest.json");
8229
8919
  const { readFileSync } = __require("node:fs");
8230
8920
  const manifestText = readFileSync(manifestPath, "utf-8");
8231
8921
  const manifest = JSON.parse(manifestText);
@@ -8261,6 +8951,9 @@ function registerSSRRoutes(app, options) {
8261
8951
  request.headers.forEach((value, key) => {
8262
8952
  headers2[key.toLowerCase()] = value;
8263
8953
  });
8954
+ const i18n2 = await applyI18nForSSR(cwd, pathname, headers2, set, lang);
8955
+ if (i18n2.redirect)
8956
+ return i18n2.redirect;
8264
8957
  const parentSpan = store.telemetrySpan;
8265
8958
  return withParentSpan(parentSpan, async () => {
8266
8959
  let isFirstStreamingRequest = false;
@@ -8343,7 +9036,7 @@ function registerSSRRoutes(app, options) {
8343
9036
  cachedIndexHtml,
8344
9037
  debug,
8345
9038
  head,
8346
- lang,
9039
+ lang: i18n2.effectiveLang,
8347
9040
  appId,
8348
9041
  maxAge: serverMaxAge,
8349
9042
  searchParams,
@@ -8354,7 +9047,8 @@ function registerSSRRoutes(app, options) {
8354
9047
  parentSpan: renderSpan,
8355
9048
  headers: headers2,
8356
9049
  timeout: options.streamingTimeout,
8357
- titleSuffix: options.titleSuffix
9050
+ titleSuffix: options.titleSuffix,
9051
+ i18n: i18n2.hydration
8358
9052
  });
8359
9053
  renderSpan?.setAttribute("http.response.status_code", result.status);
8360
9054
  if (debug)
@@ -8525,7 +9219,7 @@ function registerSSRRoutes(app, options) {
8525
9219
  isWatchMode,
8526
9220
  bundleUrl,
8527
9221
  head,
8528
- lang,
9222
+ lang: i18n2.effectiveLang,
8529
9223
  appId,
8530
9224
  maxAge: serverMaxAge,
8531
9225
  searchParams,
@@ -8535,7 +9229,8 @@ function registerSSRRoutes(app, options) {
8535
9229
  autoCanonical: options.autoCanonical,
8536
9230
  parentSpan: renderSpan,
8537
9231
  headers: headers2,
8538
- titleSuffix: options.titleSuffix
9232
+ titleSuffix: options.titleSuffix,
9233
+ i18n: i18n2.hydration
8539
9234
  });
8540
9235
  renderSpan?.setAttribute("http.response.status_code", renderResult.status);
8541
9236
  return renderResult;
@@ -9113,4 +9808,4 @@ export {
9113
9808
  reroute
9114
9809
  };
9115
9810
 
9116
- //# debugId=C4FE93DB6E1B24E664756E2164756E21
9811
+ //# debugId=27029E2CFF5EA81E64756E2164756E21