reroute-js 0.45.11 → 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 (244) 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 +1175 -431
  8. package/cli/bin.js.map +33 -24
  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 +827 -192
  41. package/core/index.js.map +32 -23
  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/discovery.d.ts.map +1 -1
  74. package/core/src/og/index.d.ts +2 -2
  75. package/core/src/og/meta.d.ts +2 -2
  76. package/core/src/og/render.d.ts +2 -2
  77. package/core/src/og/types.d.ts +2 -2
  78. package/core/src/robots/discovery.d.ts +2 -2
  79. package/core/src/robots/generator.d.ts +2 -2
  80. package/core/src/robots/index.d.ts +2 -2
  81. package/core/src/robots/policies.d.ts +2 -2
  82. package/core/src/rss/discovery.d.ts +4 -2
  83. package/core/src/rss/discovery.d.ts.map +1 -1
  84. package/core/src/rss/generator.d.ts +2 -2
  85. package/core/src/rss/index.d.ts +2 -2
  86. package/core/src/sitemap/discovery.d.ts +7 -2
  87. package/core/src/sitemap/discovery.d.ts.map +1 -1
  88. package/core/src/sitemap/generator.d.ts +2 -2
  89. package/core/src/sitemap/generator.d.ts.map +1 -1
  90. package/core/src/sitemap/index.d.ts +2 -2
  91. package/core/src/ssr/index.d.ts +2 -2
  92. package/core/src/ssr/lib/cache.d.ts +2 -2
  93. package/core/src/ssr/lib/cleanup.d.ts +2 -2
  94. package/core/src/ssr/lib/collections.d.ts +2 -2
  95. package/core/src/ssr/lib/compression.d.ts +2 -2
  96. package/core/src/ssr/lib/compute/content.d.ts +2 -9
  97. package/core/src/ssr/lib/compute/content.d.ts.map +1 -1
  98. package/core/src/ssr/lib/compute/index.d.ts +2 -2
  99. package/core/src/ssr/lib/compute/index.d.ts.map +1 -1
  100. package/core/src/ssr/lib/compute/layouts.d.ts +2 -2
  101. package/core/src/ssr/lib/compute/routes.d.ts +2 -2
  102. package/core/src/ssr/lib/compute/routes.d.ts.map +1 -1
  103. package/core/src/ssr/lib/content-path.d.ts +22 -0
  104. package/core/src/ssr/lib/content-path.d.ts.map +1 -0
  105. package/core/src/ssr/lib/data.d.ts +2 -2
  106. package/core/src/ssr/lib/data.d.ts.map +1 -1
  107. package/core/src/ssr/lib/html.d.ts +2 -2
  108. package/core/src/ssr/lib/imports.d.ts +2 -2
  109. package/core/src/ssr/lib/index.d.ts +3 -2
  110. package/core/src/ssr/lib/index.d.ts.map +1 -1
  111. package/core/src/ssr/lib/layouts.d.ts +2 -2
  112. package/core/src/ssr/lib/metadata.d.ts +2 -2
  113. package/core/src/ssr/lib/metadata.d.ts.map +1 -1
  114. package/core/src/ssr/lib/mime.d.ts +2 -2
  115. package/core/src/ssr/lib/modules.d.ts +4 -4
  116. package/core/src/ssr/lib/modules.d.ts.map +1 -1
  117. package/core/src/ssr/lib/path.d.ts +2 -2
  118. package/core/src/ssr/lib/preload.d.ts +2 -2
  119. package/core/src/ssr/lib/preload.d.ts.map +1 -1
  120. package/core/src/ssr/lib/route-match.d.ts +20 -0
  121. package/core/src/ssr/lib/route-match.d.ts.map +1 -0
  122. package/core/src/ssr/lib/scripts/collections.d.ts +2 -2
  123. package/core/src/ssr/lib/scripts/data.d.ts +3 -3
  124. package/core/src/ssr/lib/scripts/data.d.ts.map +1 -1
  125. package/core/src/ssr/lib/scripts/escape.d.ts +2 -2
  126. package/core/src/ssr/lib/scripts/feeds.d.ts +2 -2
  127. package/core/src/ssr/lib/scripts/index.d.ts +2 -2
  128. package/core/src/ssr/lib/seed.d.ts +2 -2
  129. package/core/src/ssr/lib/seed.d.ts.map +1 -1
  130. package/core/src/ssr/lib/serialize.d.ts +2 -2
  131. package/core/src/ssr/lib/setup.d.ts +3 -3
  132. package/core/src/ssr/lib/setup.d.ts.map +1 -1
  133. package/core/src/ssr/lib/styles.d.ts +2 -2
  134. package/core/src/ssr/lib/template.d.ts +2 -2
  135. package/core/src/ssr/lib/types.d.ts +2 -2
  136. package/core/src/ssr/render.d.ts +4 -2
  137. package/core/src/ssr/render.d.ts.map +1 -1
  138. package/core/src/ssr/stream.d.ts +4 -2
  139. package/core/src/ssr/stream.d.ts.map +1 -1
  140. package/elysia/index.d.ts +2 -2
  141. package/elysia/index.js +1072 -276
  142. package/elysia/index.js.map +37 -27
  143. package/elysia/src/index.d.ts +2 -2
  144. package/elysia/src/libs/assets.d.ts +2 -2
  145. package/elysia/src/libs/cache.d.ts +2 -2
  146. package/elysia/src/libs/caching.d.ts +2 -2
  147. package/elysia/src/libs/http.d.ts +2 -2
  148. package/elysia/src/libs/i18n.d.ts +26 -0
  149. package/elysia/src/libs/i18n.d.ts.map +1 -0
  150. package/elysia/src/libs/image.d.ts +2 -2
  151. package/elysia/src/libs/index.d.ts +2 -2
  152. package/elysia/src/libs/llms.d.ts +2 -2
  153. package/elysia/src/libs/response.d.ts +2 -2
  154. package/elysia/src/libs/serving.d.ts +2 -2
  155. package/elysia/src/plugin.d.ts +2 -2
  156. package/elysia/src/routes/artifacts.d.ts +2 -2
  157. package/elysia/src/routes/content.d.ts +2 -2
  158. package/elysia/src/routes/image.d.ts +2 -2
  159. package/elysia/src/routes/index.d.ts +2 -2
  160. package/elysia/src/routes/internal.d.ts +2 -2
  161. package/elysia/src/routes/llms.d.ts +2 -2
  162. package/elysia/src/routes/og.d.ts +2 -2
  163. package/elysia/src/routes/og.d.ts.map +1 -1
  164. package/elysia/src/routes/redirects.d.ts +2 -2
  165. package/elysia/src/routes/robots.d.ts +2 -2
  166. package/elysia/src/routes/rss.d.ts +2 -2
  167. package/elysia/src/routes/rss.d.ts.map +1 -1
  168. package/elysia/src/routes/search.d.ts +2 -2
  169. package/elysia/src/routes/search.d.ts.map +1 -1
  170. package/elysia/src/routes/sitemap.d.ts +2 -2
  171. package/elysia/src/routes/ssr.d.ts +2 -2
  172. package/elysia/src/routes/ssr.d.ts.map +1 -1
  173. package/elysia/src/routes/static.d.ts +2 -2
  174. package/elysia/src/types.d.ts +2 -2
  175. package/package.json +7 -6
  176. package/react/index.d.ts +2 -2
  177. package/react/index.js +565 -171
  178. package/react/index.js.map +12 -10
  179. package/react/src/components/ClientOnly.d.ts +2 -2
  180. package/react/src/components/ContentRoute.d.ts +3 -3
  181. package/react/src/components/ContentRoute.d.ts.map +1 -1
  182. package/react/src/components/Image.d.ts +2 -2
  183. package/react/src/components/LazyRoute.d.ts +2 -2
  184. package/react/src/components/Link.d.ts +2 -2
  185. package/react/src/components/Link.d.ts.map +1 -1
  186. package/react/src/components/Markdown.d.ts +2 -2
  187. package/react/src/components/Outlet.d.ts +2 -2
  188. package/react/src/components/index.d.ts +2 -2
  189. package/react/src/hooks/index.d.ts +2 -2
  190. package/react/src/hooks/useContent.d.ts +2 -2
  191. package/react/src/hooks/useContent.d.ts.map +1 -1
  192. package/react/src/hooks/useData.d.ts +2 -2
  193. package/react/src/hooks/useFeed.d.ts +2 -2
  194. package/react/src/hooks/useLayoutData.d.ts +2 -2
  195. package/react/src/hooks/useLlms.d.ts +2 -2
  196. package/react/src/hooks/useNavigate.d.ts +2 -2
  197. package/react/src/hooks/useParams.d.ts +2 -2
  198. package/react/src/hooks/useRouter.d.ts +2 -2
  199. package/react/src/hooks/useSearch.d.ts +2 -2
  200. package/react/src/hooks/useSearch.d.ts.map +1 -1
  201. package/react/src/hooks/useSearchParams.d.ts +2 -2
  202. package/react/src/hooks/useToc.d.ts +2 -2
  203. package/react/src/i18n/LocaleProvider.d.ts +49 -0
  204. package/react/src/i18n/LocaleProvider.d.ts.map +1 -0
  205. package/react/src/i18n/helpers.d.ts +24 -0
  206. package/react/src/i18n/helpers.d.ts.map +1 -0
  207. package/react/src/i18n/index.d.ts +13 -0
  208. package/react/src/i18n/index.d.ts.map +1 -0
  209. package/react/src/index.d.ts +3 -2
  210. package/react/src/index.d.ts.map +1 -1
  211. package/react/src/lib/collection.d.ts +2 -2
  212. package/react/src/lib/content.d.ts +4 -3
  213. package/react/src/lib/content.d.ts.map +1 -1
  214. package/react/src/lib/head.d.ts +2 -2
  215. package/react/src/lib/index.d.ts +2 -2
  216. package/react/src/lib/lazy-route.d.ts +2 -2
  217. package/react/src/lib/route-loader.d.ts +2 -2
  218. package/react/src/providers/ContentProvider.d.ts +3 -3
  219. package/react/src/providers/ContentProvider.d.ts.map +1 -1
  220. package/react/src/providers/RerouteProvider.d.ts +8 -6
  221. package/react/src/providers/RerouteProvider.d.ts.map +1 -1
  222. package/react/src/providers/RouterProvider.d.ts +2 -2
  223. package/react/src/providers/index.d.ts +2 -2
  224. package/react/src/types/any.d.ts +2 -2
  225. package/react/src/types/index.d.ts +2 -2
  226. package/react/src/types/router.d.ts +2 -2
  227. package/telemetry/react.d.ts +2 -2
  228. package/telemetry/react.js +8 -4
  229. package/telemetry/react.js.map +2 -2
  230. package/telemetry/server.d.ts +2 -2
  231. package/telemetry/server.js +60 -5
  232. package/telemetry/server.js.map +22 -17
  233. package/telemetry/src/react/api.d.ts +2 -2
  234. package/telemetry/src/react/context.d.ts +2 -2
  235. package/telemetry/src/react/index.d.ts +2 -2
  236. package/telemetry/src/react/telemetry.d.ts +2 -2
  237. package/telemetry/src/server/context.d.ts +2 -2
  238. package/telemetry/src/server/headers/extractor.d.ts +2 -2
  239. package/telemetry/src/server/headers/index.d.ts +2 -2
  240. package/telemetry/src/server/headers/presets.d.ts +2 -2
  241. package/telemetry/src/server/index.d.ts +2 -2
  242. package/telemetry/src/server/instrumentation.d.ts +2 -2
  243. package/telemetry/src/server/plugin.d.ts +2 -2
  244. package/telemetry/src/server/sourcemap.d.ts +2 -2
package/elysia/index.js CHANGED
@@ -1,21 +1,25 @@
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
  */
10
10
  import { createRequire } from "node:module";
11
11
  var __defProp = Object.defineProperty;
12
+ var __returnValue = (v) => v;
13
+ function __exportSetter(name, newValue) {
14
+ this[name] = __returnValue.bind(null, newValue);
15
+ }
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
14
18
  __defProp(target, name, {
15
19
  get: all[name],
16
20
  enumerable: true,
17
21
  configurable: true,
18
- set: (newValue) => all[name] = () => newValue
22
+ set: __exportSetter.bind(all, name)
19
23
  });
20
24
  };
21
25
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -196,6 +200,364 @@ var init_content = __esm(() => {
196
200
  init_metadata();
197
201
  });
198
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
+
199
561
  // packages/telemetry/src/server/context.ts
200
562
  import { AsyncLocalStorage } from "node:async_hooks";
201
563
  var contextStorage;
@@ -790,6 +1152,148 @@ function compressStreamIfAccepted(stream, contentType, _acceptEncoding) {
790
1152
  }
791
1153
  var init_compression = () => {};
792
1154
 
1155
+ // packages/core/src/ssr/lib/route-match.ts
1156
+ function getPatternParts(pattern) {
1157
+ return pattern.split("/").filter(Boolean);
1158
+ }
1159
+ function isDynamicPatternPart(patternPart) {
1160
+ return patternPart.startsWith(":");
1161
+ }
1162
+ function isCatchAllPatternPart(patternPart) {
1163
+ return patternPart.startsWith(":...");
1164
+ }
1165
+ function isCatchAllParamName(paramName) {
1166
+ return paramName.startsWith("...");
1167
+ }
1168
+ function getPatternPartSpecificity(patternPart) {
1169
+ if (isCatchAllPatternPart(patternPart)) {
1170
+ return 2;
1171
+ }
1172
+ if (isDynamicPatternPart(patternPart)) {
1173
+ return 1;
1174
+ }
1175
+ return 0;
1176
+ }
1177
+ function compareRouteSpecificity(a, b) {
1178
+ const aPatternParts = getPatternParts(a.pattern);
1179
+ const bPatternParts = getPatternParts(b.pattern);
1180
+ const aDepth = aPatternParts.length;
1181
+ const bDepth = bPatternParts.length;
1182
+ if (aDepth !== bDepth) {
1183
+ return bDepth - aDepth;
1184
+ }
1185
+ for (let i = 0;i < aPatternParts.length; i++) {
1186
+ const aSpecificity = getPatternPartSpecificity(aPatternParts[i]);
1187
+ const bSpecificity = getPatternPartSpecificity(bPatternParts[i]);
1188
+ if (aSpecificity !== bSpecificity) {
1189
+ return aSpecificity - bSpecificity;
1190
+ }
1191
+ if (aSpecificity === 0 && aPatternParts[i] !== bPatternParts[i]) {
1192
+ return aPatternParts[i].localeCompare(bPatternParts[i]);
1193
+ }
1194
+ }
1195
+ return a.pattern.localeCompare(b.pattern);
1196
+ }
1197
+ function matchCatchAllParam(paramName, patternIndex, patternLength, pathnameParts, pathnameIndex, params) {
1198
+ if (patternIndex !== patternLength - 1 || pathnameIndex >= pathnameParts.length) {
1199
+ return false;
1200
+ }
1201
+ params[paramName.slice(3)] = pathnameParts.slice(pathnameIndex).join("/");
1202
+ return true;
1203
+ }
1204
+ function matchDynamicParam(paramName, pathnameParts, pathnameIndex, params) {
1205
+ const pathnamePart = pathnameParts[pathnameIndex];
1206
+ if (pathnamePart === undefined) {
1207
+ return null;
1208
+ }
1209
+ params[paramName] = pathnamePart;
1210
+ return pathnameIndex + 1;
1211
+ }
1212
+ function matchPatternPart(patternPart, patternIndex, patternLength, pathnameParts, pathnameIndex, params) {
1213
+ if (isDynamicPatternPart(patternPart)) {
1214
+ const paramName = patternPart.slice(1);
1215
+ if (isCatchAllParamName(paramName)) {
1216
+ return matchCatchAllParam(paramName, patternIndex, patternLength, pathnameParts, pathnameIndex, params) ? "catch-all" : null;
1217
+ }
1218
+ return matchDynamicParam(paramName, pathnameParts, pathnameIndex, params);
1219
+ }
1220
+ if (patternPart !== pathnameParts[pathnameIndex]) {
1221
+ return null;
1222
+ }
1223
+ return pathnameIndex + 1;
1224
+ }
1225
+ function matchRoutePattern(pattern, pathname) {
1226
+ const patternParts = getPatternParts(pattern);
1227
+ const pathnameParts = pathname.split("/").filter(Boolean);
1228
+ const params = {};
1229
+ let pathnameIndex = 0;
1230
+ for (let i = 0;i < patternParts.length; i++) {
1231
+ const nextPathnameIndex = matchPatternPart(patternParts[i], i, patternParts.length, pathnameParts, pathnameIndex, params);
1232
+ if (nextPathnameIndex === null) {
1233
+ return null;
1234
+ }
1235
+ if (nextPathnameIndex === "catch-all") {
1236
+ return params;
1237
+ }
1238
+ pathnameIndex = nextPathnameIndex;
1239
+ }
1240
+ return pathnameIndex === pathnameParts.length ? params : null;
1241
+ }
1242
+ function matchRoutes(routes, pathname) {
1243
+ for (const route of routes) {
1244
+ const params = matchRoutePattern(route.pattern, pathname);
1245
+ if (params) {
1246
+ return { route, params };
1247
+ }
1248
+ }
1249
+ return null;
1250
+ }
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
+
793
1297
  // packages/core/src/ssr/lib/compute/content.ts
794
1298
  function checkStatusCache(statusCacheKey, maxAgeMs) {
795
1299
  if (maxAgeMs <= 0)
@@ -842,6 +1346,14 @@ async function handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusC
842
1346
  }
843
1347
  return result;
844
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
+ }
845
1357
  async function computeContentData(pathname, options, searchParams, sharedSsrData) {
846
1358
  const streaming = options.streaming === true;
847
1359
  const ssrData = streaming && sharedSsrData ? sharedSsrData : {};
@@ -851,18 +1363,20 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
851
1363
  const maxAge = options.maxAge || 0;
852
1364
  const maxAgeMs = maxAge * 1000;
853
1365
  try {
854
- const parts = pathname.split("/").filter(Boolean);
855
- if (parts.length < 2) {
1366
+ const resolved = resolveContentPathParts(pathname);
1367
+ if (!resolved) {
856
1368
  return { data: ssrData, pendingTasks };
857
1369
  }
1370
+ const parts = [resolved.collection, resolved.name];
1371
+ if (resolved.locale)
1372
+ parts.splice(1, 0, resolved.locale);
858
1373
  const g = globalThis;
859
- const key = `${parts[0]}:${parts[1]}`;
860
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1374
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
861
1375
  const dataFn = exp?.ssr?.data;
862
1376
  if (typeof dataFn !== "function") {
863
1377
  return { data: ssrData, pendingTasks };
864
1378
  }
865
- const cacheKey = `content:${pathname}:${parts[1]}`;
1379
+ const cacheKey = `content:${pathname}:${resolved.name}:${resolved.locale || ""}`;
866
1380
  const statusCacheKey = `status:${cacheKey}`;
867
1381
  const cachedStatus = checkStatusCache(statusCacheKey, maxAgeMs);
868
1382
  if (cachedStatus && cachedStatus !== 200) {
@@ -875,22 +1389,13 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
875
1389
  }
876
1390
  const set = { status: 200 };
877
1391
  const task = createDataTask(dataFn, pathname, parts, searchParams, set, options.headers);
878
- if (streaming) {
879
- handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
880
- if (set.status !== 200) {
881
- statusOverride = set.status;
882
- }
883
- } else {
884
- await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
885
- if (set.status !== 200) {
886
- statusOverride = set.status;
887
- }
888
- }
1392
+ statusOverride = await runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) ?? statusOverride;
889
1393
  } catch {}
890
1394
  return { data: ssrData, error, statusOverride, pendingTasks };
891
1395
  }
892
1396
  var init_content2 = __esm(() => {
893
1397
  init_cache();
1398
+ init_content_path();
894
1399
  init_imports();
895
1400
  });
896
1401
 
@@ -1089,7 +1594,8 @@ async function computeRouteData(pathname, clientDir, _cwd, isWatchMode, options,
1089
1594
  }).catch(() => {}));
1090
1595
  };
1091
1596
  try {
1092
- const pathnameOnly = pathname.split("?")[0];
1597
+ const resolved = resolveContentPathParts(pathname);
1598
+ const pathnameOnly = (resolved?.routingPathname || pathname).split("?")[0];
1093
1599
  const match = typeof routesModule?.matchRoute === "function" ? routesModule.matchRoute(pathnameOnly) : null;
1094
1600
  const route = match?.route;
1095
1601
  const params = match?.params || {};
@@ -1252,44 +1758,18 @@ function handleFlattenedResult(entries, set, ctx, cache, setError, setStatus) {
1252
1758
  }
1253
1759
  var init_routes = __esm(() => {
1254
1760
  init_cache();
1761
+ init_content_path();
1255
1762
  init_imports();
1256
1763
  });
1257
1764
 
1258
1765
  // packages/core/src/ssr/lib/compute/index.ts
1259
- function matchPatternUtil(pattern, pathname) {
1260
- const patternParts = pattern.split("/").filter(Boolean);
1261
- const pathnameParts = pathname.split("/").filter(Boolean);
1262
- if (patternParts.length !== pathnameParts.length) {
1263
- return null;
1264
- }
1265
- const params = {};
1266
- for (let i = 0;i < patternParts.length; i++) {
1267
- const patternPart = patternParts[i];
1268
- const pathnamePart = pathnameParts[i];
1269
- if (patternPart.startsWith(":")) {
1270
- params[patternPart.slice(1)] = pathnamePart;
1271
- } else if (patternPart !== pathnamePart) {
1272
- return null;
1273
- }
1274
- }
1275
- return params;
1276
- }
1277
1766
  function createBundledRoutesModule(bundledRoutes, bundledLayouts) {
1278
1767
  const bundledNotFoundRoutes = globalThis.__REROUTE_NOT_FOUND_ROUTES__;
1279
1768
  return {
1280
1769
  routes: bundledRoutes,
1281
1770
  layouts: bundledLayouts,
1282
1771
  notFoundRoutes: bundledNotFoundRoutes,
1283
- matchRoute: (pathname) => {
1284
- for (const route of bundledRoutes) {
1285
- const pattern = String(route?.pattern || "");
1286
- const match = matchPatternUtil(pattern, pathname);
1287
- if (match) {
1288
- return { route, params: match };
1289
- }
1290
- }
1291
- return null;
1292
- }
1772
+ matchRoute: (pathname) => matchRoutes(bundledRoutes, pathname)
1293
1773
  };
1294
1774
  }
1295
1775
  async function loadRoutesModule(cwd, isWatchMode) {
@@ -1371,33 +1851,31 @@ var init_compute = __esm(() => {
1371
1851
  });
1372
1852
 
1373
1853
  // packages/core/src/ssr/lib/modules.ts
1374
- import { readdir as readdir2, stat as stat3 } from "node:fs/promises";
1854
+ import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
1375
1855
  async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1376
1856
  try {
1377
- const pathOnly = pathname.split("?")[0].split("#")[0];
1378
- const parts = pathOnly.split("/").filter(Boolean);
1379
- if (parts.length < 2)
1857
+ const resolved = resolveContentPathParts(pathname);
1858
+ if (!resolved)
1380
1859
  return null;
1381
- const collection = parts[0];
1382
- const name = parts[1];
1383
- const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode);
1860
+ const { collection, name, locale } = resolved;
1861
+ const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode, locale);
1384
1862
  if (registryModule)
1385
1863
  return registryModule;
1386
1864
  const chunkModule = await tryChunkImport(collection, name, cwd, isWatchMode);
1387
1865
  if (chunkModule)
1388
1866
  return chunkModule;
1389
- const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode);
1867
+ const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode, locale);
1390
1868
  if (sourceModule)
1391
1869
  return sourceModule;
1392
1870
  } catch {}
1393
1871
  return null;
1394
1872
  }
1395
- async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1873
+ async function tryRegistryImport(collection, name, cwd, isWatchMode, locale) {
1396
1874
  try {
1397
1875
  const registryPath = join(cwd, ".reroute", "content.ts");
1398
1876
  const reg = await dynamicImport(registryPath, isWatchMode);
1399
1877
  const get = reg?.getContentEntry;
1400
- const entry = typeof get === "function" ? get(collection, name) : undefined;
1878
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
1401
1879
  const moduleUrl = entry?.module;
1402
1880
  if (!moduleUrl?.endsWith(".js"))
1403
1881
  return null;
@@ -1423,7 +1901,7 @@ async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1423
1901
  async function tryChunkImport(collection, name, cwd, isWatchMode) {
1424
1902
  try {
1425
1903
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
1426
- const files = await readdir2(chunkDir);
1904
+ const files = await readdir3(chunkDir);
1427
1905
  const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
1428
1906
  if (!matches.length)
1429
1907
  return null;
@@ -1443,23 +1921,23 @@ async function tryChunkImport(collection, name, cwd, isWatchMode) {
1443
1921
  } catch {}
1444
1922
  return null;
1445
1923
  }
1446
- async function trySourceImport(collection, name, clientDir, isWatchMode) {
1924
+ async function trySourceImport(collection, name, clientDir, isWatchMode, locale) {
1447
1925
  try {
1448
- const srcTsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
1449
- const srcTs = join(clientDir, "routes", collection, "content", `${name}.ts`);
1450
- let absSrc = null;
1451
- if (await Bun.file(srcTsx).exists()) {
1452
- absSrc = srcTsx;
1453
- } else if (await Bun.file(srcTs).exists()) {
1454
- 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`));
1455
1929
  }
1456
- if (absSrc) {
1457
- 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
+ }
1458
1935
  }
1459
1936
  } catch {}
1460
1937
  return null;
1461
1938
  }
1462
1939
  var init_modules = __esm(() => {
1940
+ init_content_path();
1463
1941
  init_imports();
1464
1942
  });
1465
1943
 
@@ -1490,14 +1968,11 @@ function touchModule(key) {
1490
1968
  }
1491
1969
  async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1492
1970
  try {
1493
- const pathOnly = pathname.split("?")[0].split("#")[0];
1494
- const parts = pathOnly.split("/").filter(Boolean);
1495
- if (parts.length < 2) {
1971
+ const resolved = resolveContentPathParts(pathname);
1972
+ if (!resolved) {
1496
1973
  return;
1497
1974
  }
1498
- const collection = parts[0];
1499
- const name = parts[1];
1500
- const key = `${collection}:${name}`;
1975
+ const { key } = resolved;
1501
1976
  const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode);
1502
1977
  if (!mod) {
1503
1978
  return;
@@ -1517,42 +1992,25 @@ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1517
1992
  }
1518
1993
  var MAX_SSR_MODULES = 200, moduleAccessOrder;
1519
1994
  var init_seed = __esm(() => {
1995
+ init_content_path();
1520
1996
  init_modules();
1521
1997
  moduleAccessOrder = [];
1522
1998
  });
1523
1999
 
1524
2000
  // packages/core/src/ssr/lib/data.ts
1525
- function matchPattern(pattern, pathname) {
1526
- const patternParts = pattern.split("/").filter(Boolean);
1527
- const pathnameParts = pathname.split("/").filter(Boolean);
1528
- if (patternParts.length !== pathnameParts.length) {
1529
- return null;
1530
- }
1531
- const params = {};
1532
- for (let i = 0;i < patternParts.length; i++) {
1533
- const patternPart = patternParts[i];
1534
- const pathnamePart = pathnameParts[i];
1535
- if (patternPart.startsWith(":")) {
1536
- params[patternPart.slice(1)] = pathnamePart;
1537
- } else if (patternPart !== pathnamePart) {
1538
- return null;
1539
- }
1540
- }
1541
- return params;
1542
- }
1543
2001
  function loadContentData(pathname, searchParams) {
1544
2002
  try {
1545
- const parts = pathname.split("/").filter(Boolean);
1546
- if (parts.length < 2)
2003
+ const resolved = resolveContentPathParts(pathname);
2004
+ if (!resolved)
1547
2005
  return null;
1548
- const key = `${parts[0]}:${parts[1]}`;
2006
+ const key = resolved.key;
1549
2007
  const g = globalThis;
1550
2008
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1551
2009
  const dataFn = exp?.ssr?.data;
1552
2010
  if (typeof dataFn === "function") {
1553
2011
  const rawResult = dataFn({
1554
2012
  pathname,
1555
- params: { name: parts[1] },
2013
+ params: { name: resolved.name },
1556
2014
  searchParams
1557
2015
  });
1558
2016
  const result = processDataResult(rawResult, pathname, pathname);
@@ -1574,16 +2032,7 @@ async function loadRouteDataInternal(pathname, clientDir, cwd, isWatchMode, sear
1574
2032
  routes = m?.routes;
1575
2033
  matchRouteFn = m?.matchRoute;
1576
2034
  } else {
1577
- matchRouteFn = (pathname2) => {
1578
- for (const route of routes || []) {
1579
- const pattern = String(route?.pattern || "");
1580
- const match2 = matchPattern(pattern, pathname2);
1581
- if (match2) {
1582
- return { route, params: match2 };
1583
- }
1584
- }
1585
- return null;
1586
- };
2035
+ matchRouteFn = (pathname2) => matchRoutes(routes || [], pathname2);
1587
2036
  }
1588
2037
  const pathnameOnly = pathname.split("?")[0];
1589
2038
  const match = typeof matchRouteFn === "function" ? matchRouteFn(pathnameOnly) : null;
@@ -1724,6 +2173,7 @@ function createLazyMarker(pathname, key) {
1724
2173
  };
1725
2174
  }
1726
2175
  var init_data = __esm(() => {
2176
+ init_content_path();
1727
2177
  init_imports();
1728
2178
  init_seed();
1729
2179
  });
@@ -1924,18 +2374,53 @@ function replaceAppRoot(html, appHtml, appId) {
1924
2374
  var init_html = () => {};
1925
2375
 
1926
2376
  // packages/core/src/og/discovery.ts
2377
+ import { readdir as readdir4 } from "node:fs/promises";
1927
2378
  import { pathToFileURL as pathToFileURL4 } from "node:url";
2379
+ function isCatchAllRouteDirectory(name) {
2380
+ return CATCH_ALL_ROUTE_DIRECTORY.test(name);
2381
+ }
2382
+ async function listDynamicRouteDirectories(baseDir) {
2383
+ try {
2384
+ const entries = await readdir4(baseDir, { withFileTypes: true });
2385
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => DYNAMIC_ROUTE_DIRECTORY.test(name)).sort((a, b) => {
2386
+ const aIsCatchAll = isCatchAllRouteDirectory(a);
2387
+ const bIsCatchAll = isCatchAllRouteDirectory(b);
2388
+ if (aIsCatchAll !== bIsCatchAll) {
2389
+ return Number(aIsCatchAll) - Number(bIsCatchAll);
2390
+ }
2391
+ return a.localeCompare(b);
2392
+ });
2393
+ } catch {
2394
+ return [];
2395
+ }
2396
+ }
2397
+ async function collectOGCandidatePaths(currentDir, parts, depth, pathsToTry, allowPartialFallback) {
2398
+ if (depth === parts.length) {
2399
+ pathsToTry.push(join(currentDir, "[og].tsx"));
2400
+ return;
2401
+ }
2402
+ const literalDir = join(currentDir, parts[depth]);
2403
+ try {
2404
+ await readdir4(literalDir);
2405
+ await collectOGCandidatePaths(literalDir, parts, depth + 1, pathsToTry, true);
2406
+ } catch {}
2407
+ const dynamicDirs = await listDynamicRouteDirectories(currentDir);
2408
+ for (const dynamicDir of dynamicDirs) {
2409
+ if (isCatchAllRouteDirectory(dynamicDir)) {
2410
+ pathsToTry.push(join(currentDir, dynamicDir, "[og].tsx"));
2411
+ continue;
2412
+ }
2413
+ await collectOGCandidatePaths(join(currentDir, dynamicDir), parts, depth + 1, pathsToTry, false);
2414
+ }
2415
+ if (allowPartialFallback) {
2416
+ pathsToTry.push(join(currentDir, "[og].tsx"));
2417
+ }
2418
+ }
1928
2419
  async function findOGImageForPath(pathname, clientDir) {
1929
2420
  const parts = pathname.split("/").filter(Boolean).filter((p) => !p.startsWith("__reroute"));
1930
2421
  const routesDir = join(clientDir, "routes");
1931
2422
  const pathsToTry = [];
1932
- if (parts.length > 0) {
1933
- for (let i = parts.length;i > 0; i--) {
1934
- const dirPath = join(routesDir, ...parts.slice(0, i), "[og].tsx");
1935
- pathsToTry.push(dirPath);
1936
- }
1937
- }
1938
- pathsToTry.push(join(routesDir, "[og].tsx"));
2423
+ await collectOGCandidatePaths(routesDir, parts, 0, pathsToTry, true);
1939
2424
  const uniquePaths = [...new Set(pathsToTry)];
1940
2425
  for (const path of uniquePaths) {
1941
2426
  try {
@@ -1959,28 +2444,28 @@ async function loadOGImageComponent(ogPath, isWatchMode) {
1959
2444
  function extractParamsFromPath(pathname, ogPath, clientDir) {
1960
2445
  const params = {};
1961
2446
  const ogDir = ogPath.replace(/\/\[og\]\.tsx$/, "");
1962
- const pagesDir = join(clientDir, "pages");
1963
- const relativePath = ogDir.replace(pagesDir, "").split("/").filter(Boolean);
2447
+ const routesDir = join(clientDir, "routes");
2448
+ const relativePath = ogDir.replace(routesDir, "").split("/").filter(Boolean);
1964
2449
  const pathnameParts = pathname.split("/").filter(Boolean);
1965
2450
  for (let i = 0;i < relativePath.length && i < pathnameParts.length; i++) {
1966
2451
  const part = relativePath[i];
1967
- const match = part.match(/^\[([^\]]+)\]$/);
1968
- if (match) {
1969
- const paramName = match[1];
1970
- params[paramName] = pathnameParts[i];
2452
+ const catchAllMatch = part.match(/^\[\.\.\.([^\]]+)\]$/);
2453
+ if (catchAllMatch) {
2454
+ params[catchAllMatch[1]] = pathnameParts.slice(i).join("/");
2455
+ break;
1971
2456
  }
1972
- }
1973
- if (pathnameParts.length > relativePath.length && relativePath.length > 0) {
1974
- const lastRelativePart = relativePath[relativePath.length - 1];
1975
- const match = lastRelativePart.match(/^\[([^\]]+)\]$/);
1976
- if (match) {
1977
- const paramName = match[1];
1978
- params[paramName] = pathnameParts[relativePath.length - 1];
2457
+ const dynamicMatch = part.match(/^\[([^\]]+)\]$/);
2458
+ if (dynamicMatch) {
2459
+ params[dynamicMatch[1]] = pathnameParts[i];
1979
2460
  }
1980
2461
  }
1981
2462
  return params;
1982
2463
  }
1983
- var init_discovery2 = () => {};
2464
+ var CATCH_ALL_ROUTE_DIRECTORY, DYNAMIC_ROUTE_DIRECTORY;
2465
+ var init_discovery2 = __esm(() => {
2466
+ CATCH_ALL_ROUTE_DIRECTORY = /^\[\.\.\.([^\]]+)\]$/;
2467
+ DYNAMIC_ROUTE_DIRECTORY = /^\[(?:\.\.\.)?[^\]]+\]$/;
2468
+ });
1984
2469
 
1985
2470
  // packages/core/src/og/meta.ts
1986
2471
  async function generateOGImageMetaTags(pathname, clientDir, ogConfig) {
@@ -2071,7 +2556,9 @@ async function extractPageMetadata(pathname, clientDir, cwd, isWatchMode, curren
2071
2556
  let perPageHead = "";
2072
2557
  let pageLang;
2073
2558
  let statusOverride = currentStatusOverride;
2074
- 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) => {
2075
2562
  perPageHead += head;
2076
2563
  }, (lang) => {
2077
2564
  if (!pageLang)
@@ -2106,8 +2593,8 @@ ${ogMetaTags}`;
2106
2593
  perPageHead += head;
2107
2594
  }, (lang) => {
2108
2595
  pageLang = lang;
2109
- }, ogConfig, titleSuffix);
2110
- 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);
2111
2598
  perPageHead += routeResult.head;
2112
2599
  if (routeResult.lang)
2113
2600
  pageLang = routeResult.lang;
@@ -2147,14 +2634,42 @@ async function extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, addH
2147
2634
  }
2148
2635
  } catch {}
2149
2636
  }
2150
- 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) {
2151
2667
  try {
2152
- const parts = pathname.split("/").filter(Boolean);
2153
- if (parts.length < 2)
2668
+ const resolved = resolveContentPathParts(pathname);
2669
+ if (!resolved)
2154
2670
  return;
2155
- const key = `${parts[0]}:${parts[1]}`;
2156
2671
  const g = globalThis;
2157
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2672
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
2158
2673
  const meta = exp?.meta;
2159
2674
  const ssr = exp?.ssr;
2160
2675
  addHead(buildHeadFromMeta(meta, ogConfig, titleSuffix));
@@ -2165,15 +2680,20 @@ ${ssrHead}`);
2165
2680
  const lang = extractSSRLang(ssr);
2166
2681
  if (lang)
2167
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
+ }
2168
2688
  } catch {}
2169
2689
  }
2170
2690
  function getContentMetaFromExports(pathname) {
2171
2691
  try {
2172
- const parts = pathname.split("/").filter(Boolean);
2173
- if (parts.length < 2)
2692
+ const resolved = resolveContentPathParts(pathname);
2693
+ if (!resolved)
2174
2694
  return;
2175
- const slug = parts[1];
2176
- const key = `${parts[0]}:${slug}`;
2695
+ const slug = resolved.name;
2696
+ const key = resolved.key;
2177
2697
  const g = globalThis;
2178
2698
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2179
2699
  const meta = exp?.meta;
@@ -2187,9 +2707,10 @@ function getContentMetaFromExports(pathname) {
2187
2707
  return;
2188
2708
  }
2189
2709
  }
2190
- async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix) {
2710
+ async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2191
2711
  const pathnameOnly = pathname.split("?")[0];
2192
- 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;
2193
2714
  const r = match?.route;
2194
2715
  if (!r) {
2195
2716
  return { head: "", statusOverride: statusOverride || 404 };
@@ -2229,7 +2750,7 @@ ${ssrHead}`;
2229
2750
  }
2230
2751
  return { head: "", statusOverride };
2231
2752
  }
2232
- async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix) {
2753
+ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2233
2754
  let head = "";
2234
2755
  let lang;
2235
2756
  let statusOverride = currentStatusOverride;
@@ -2239,14 +2760,14 @@ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, curre
2239
2760
  if (!m) {
2240
2761
  return { head, lang, statusOverride: statusOverride || 404 };
2241
2762
  }
2242
- const pathnameOnly = pathname.split("?")[0];
2243
- 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;
2244
2765
  const r = match?.route;
2245
2766
  if (!r) {
2246
2767
  statusOverride = statusOverride || 404;
2247
2768
  }
2248
2769
  if (r && typeof r.path === "string") {
2249
- 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);
2250
2771
  head = result.head;
2251
2772
  lang = result.lang;
2252
2773
  statusOverride = result.statusOverride;
@@ -2421,34 +2942,34 @@ function applyTitleSuffixToHead(headContent, titleSuffix) {
2421
2942
  }
2422
2943
  var init_metadata2 = __esm(() => {
2423
2944
  init_metadata();
2945
+ init_pathname();
2424
2946
  init_meta();
2425
2947
  init_compute();
2948
+ init_content_path();
2426
2949
  init_imports();
2427
2950
  });
2428
2951
 
2429
2952
  // packages/core/src/ssr/lib/preload.ts
2430
- import { readdir as readdir3, stat as stat4 } from "node:fs/promises";
2953
+ import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
2431
2954
  async function preloadContentModule(pathname, clientDir, cwd, isWatchMode) {
2432
2955
  let extraHead = "";
2433
2956
  let hydrationScript = "";
2434
2957
  let statusOverride;
2435
2958
  try {
2436
- const pathOnly = pathname.split("?")[0].split("#")[0];
2437
- const parts = pathOnly.split("/").filter(Boolean);
2438
- 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) {
2439
2962
  return { extraHead, hydrationScript };
2440
2963
  }
2441
- const collection = parts[0];
2442
- const name = parts[1];
2964
+ const { collection, name, key, locale } = resolved;
2443
2965
  if (!await isContentCollection(clientDir, collection)) {
2444
2966
  return { extraHead, hydrationScript };
2445
2967
  }
2446
- const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode);
2968
+ const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale);
2447
2969
  if (!modulePath) {
2448
2970
  statusOverride = 404;
2449
2971
  return { extraHead, hydrationScript, statusOverride };
2450
2972
  }
2451
- const key = `${collection}:${name}`;
2452
2973
  extraHead += `
2453
2974
  <link rel="modulepreload" href="${modulePath}" />`;
2454
2975
  hydrationScript += `<script>
@@ -2490,12 +3011,12 @@ async function findLatestCandidate(chunkDir, candidates) {
2490
3011
  }
2491
3012
  return latest;
2492
3013
  }
2493
- async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode) {
3014
+ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale) {
2494
3015
  try {
2495
3016
  const registryPath = join(cwd, ".reroute", "content.ts");
2496
3017
  const reg = await dynamicImport(registryPath, isWatchMode);
2497
3018
  const get = reg?.getContentEntry;
2498
- const entry = typeof get === "function" ? get(collection, name) : undefined;
3019
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
2499
3020
  const moduleUrl = entry?.module;
2500
3021
  if (moduleUrl?.endsWith(".js")) {
2501
3022
  return moduleUrl;
@@ -2503,7 +3024,7 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2503
3024
  } catch {}
2504
3025
  try {
2505
3026
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
2506
- const files = await readdir3(chunkDir);
3027
+ const files = await readdir5(chunkDir);
2507
3028
  const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2508
3029
  if (candidates.length) {
2509
3030
  const latest = await findLatestCandidate(chunkDir, candidates);
@@ -2511,13 +3032,18 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2511
3032
  }
2512
3033
  } catch {}
2513
3034
  try {
2514
- const tsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
2515
- const ts = join(clientDir, "routes", collection, "content", `${name}.ts`);
2516
- if (await Bun.file(tsx).exists()) {
2517
- return `/${join("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
2518
- }
2519
- if (await Bun.file(ts).exists()) {
2520
- 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
+ }
2521
3047
  }
2522
3048
  } catch {}
2523
3049
  return null;
@@ -2711,11 +3237,18 @@ function duplicateQueryParamData(ssrData) {
2711
3237
  return duplicated;
2712
3238
  }
2713
3239
  function getContentKeyFromPathname(pathname) {
2714
- const pathOnly = pathname.split("?")[0].split("#")[0];
2715
- const parts = pathOnly.split("/").filter(Boolean);
2716
- if (parts.length < 2)
2717
- return null;
2718
- 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
+ }
2719
3252
  }
2720
3253
  function createContentCaptureScript() {
2721
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>`;
@@ -2791,13 +3324,14 @@ function createSSRPathnameScript(pathname) {
2791
3324
  return "";
2792
3325
  }
2793
3326
  }
2794
- function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix) {
3327
+ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n) {
2795
3328
  let script = "";
2796
3329
  script += createContentCaptureScript();
2797
3330
  script += createSSRPathnameScript(pathname);
2798
3331
  if (pathname) {
2799
3332
  script += createSSRExportsScript(pathname);
2800
3333
  }
3334
+ script += createI18nScript(i18n);
2801
3335
  script += createTelemetryConfigScript(browserTelemetryConfig);
2802
3336
  script += createTraceContextScript(traceContext);
2803
3337
  script += createTitleSuffixScript(titleSuffix);
@@ -2817,7 +3351,9 @@ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, t
2817
3351
  }
2818
3352
  return script;
2819
3353
  }
2820
- var init_data2 = () => {};
3354
+ var init_data2 = __esm(() => {
3355
+ init_content_path();
3356
+ });
2821
3357
 
2822
3358
  // packages/core/src/ssr/lib/scripts/feeds.ts
2823
3359
  function createFeedsScript() {
@@ -3045,10 +3581,10 @@ function generateCollectionScripts(byCollectionForSSR) {
3045
3581
  } catch {}
3046
3582
  return "";
3047
3583
  }
3048
- 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) {
3049
3585
  let scripts = preloadScript;
3050
3586
  scripts += collectionScripts;
3051
- scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix);
3587
+ scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n);
3052
3588
  scripts += createFeedsScript();
3053
3589
  scripts += createLlmsScript();
3054
3590
  if (bundleUrl) {
@@ -3211,7 +3747,7 @@ async function renderSSRDocument(options) {
3211
3747
  const browserTelemetryConfig = serializeBrowserTelemetryConfig2(browserTelemetryConfigRaw);
3212
3748
  const traceContext = getTraceContextForInjection(options.parentSpan);
3213
3749
  const hydrationScript = await withSpan("ssr.generate.scripts", async (span) => {
3214
- 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);
3215
3751
  span.setAttribute("reroute.scripts.size", script.length);
3216
3752
  return script;
3217
3753
  });
@@ -3229,12 +3765,13 @@ async function renderSSRDocument(options) {
3229
3765
  dedent(setup2.metadataResult.perPageHead)
3230
3766
  ].filter(Boolean).join(`
3231
3767
  `));
3768
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
3232
3769
  const html2 = withSpanSync("ssr.apply.template", (span) => {
3233
3770
  const result = applyIndexTemplate(baseTemplate, appHtml, {
3234
3771
  head: [setup2.inlineStyleTag, combinedHead].filter(Boolean).join(`
3235
3772
  `),
3236
3773
  hydrationScript,
3237
- lang: setup2.metadataResult.pageLang || lang,
3774
+ lang: setup2.metadataResult.pageLang || i18nLocale || lang,
3238
3775
  appId
3239
3776
  });
3240
3777
  span.setAttributes({
@@ -4412,6 +4949,55 @@ async function discoverRoutes(cwd, isWatchMode) {
4412
4949
  }
4413
4950
  return { staticRoutes, dynamicRoutes };
4414
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
+ }
4415
5001
  async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl) {
4416
5002
  const entries = [];
4417
5003
  try {
@@ -4423,18 +5009,26 @@ async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl)
4423
5009
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4424
5010
  const mod = await import(url);
4425
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
+ }
4426
5019
  for (const item of items) {
5020
+ if (item.name)
5021
+ continue;
4427
5022
  const href = String(item.href || "");
5023
+ if (!href)
5024
+ continue;
4428
5025
  const meta = item.meta || {};
4429
- if (href) {
4430
- const entry = {
4431
- loc: `${baseUrl}${href}`,
4432
- lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4433
- changefreq: "weekly",
4434
- priority: 0.7
4435
- };
4436
- entries.push(entry);
4437
- }
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
+ });
4438
5032
  }
4439
5033
  } catch (error) {
4440
5034
  console.error(`[reroute] Failed to discover collection ${collection}:`, error);
@@ -4729,11 +5323,17 @@ async function discoverCollectionFeedItems(cwd, collection, isWatchMode, baseUrl
4729
5323
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4730
5324
  const mod = await import(url);
4731
5325
  const contentItems = mod.items || [];
5326
+ const defaultLocale = mod.defaultLocale || "en-US";
5327
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
4732
5328
  for (const item of contentItems) {
4733
- 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 || "");
4734
5332
  const meta = item.meta || {};
4735
5333
  if (href) {
4736
- 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);
4737
5337
  }
4738
5338
  }
4739
5339
  } catch (error) {
@@ -4999,7 +5599,7 @@ function generateRSSXML(feed, options = {}) {
4999
5599
  <link>${escapeXml(feed.link)}</link>
5000
5600
  <description>${escapeXml(feed.description)}</description>
5001
5601
  <lastBuildDate>${lastBuildDate}</lastBuildDate>
5002
- <language>en</language>
5602
+ <language>${escapeXml(feed.language || "en")}</language>
5003
5603
  <atom:link href="${escapeXml(feed.link)}" rel="self" type="application/rss+xml" />
5004
5604
  ${itemsXML}
5005
5605
  </channel>
@@ -5161,6 +5761,7 @@ async function generateSitemap(discovery4, baseUrl, options = {}) {
5161
5761
  return createSingleSitemap(allEntries);
5162
5762
  }
5163
5763
  function generateSitemapXML(entries) {
5764
+ const hasAlternates = entries.some((e) => e.alternates && e.alternates.length > 0);
5164
5765
  const urlsetEntries = entries.map((entry) => {
5165
5766
  let xml = ` <url>
5166
5767
  <loc>${escapeXml2(entry.loc)}</loc>`;
@@ -5176,13 +5777,20 @@ function generateSitemapXML(entries) {
5176
5777
  xml += `
5177
5778
  <priority>${entry.priority.toFixed(1)}</priority>`;
5178
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
+ }
5179
5786
  xml += `
5180
5787
  </url>`;
5181
5788
  return xml;
5182
5789
  }).join(`
5183
5790
  `);
5791
+ const xhtmlNs = hasAlternates ? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"' : "";
5184
5792
  return `<?xml version="1.0" encoding="UTF-8"?>
5185
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5793
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${xhtmlNs}>
5186
5794
  ${urlsetEntries}
5187
5795
  </urlset>`;
5188
5796
  }
@@ -5280,7 +5888,8 @@ async function renderSSRDocumentStream(options) {
5280
5888
  ...parts
5281
5889
  };
5282
5890
  });
5283
- const pageLang = setup2.metadataResult.pageLang || lang;
5891
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
5892
+ const pageLang = setup2.metadataResult.pageLang || i18nLocale || lang;
5284
5893
  let headWithLang = htmlHead.replace(/<html([^>]*)>/i, (_m, attrs) => {
5285
5894
  const hasLang = /(^|\s)lang\s*=/.test(attrs);
5286
5895
  const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${pageLang}"`) : `${attrs} lang="${pageLang}"`;
@@ -5323,7 +5932,8 @@ async function renderSSRDocumentStream(options) {
5323
5932
  parentSpan: options.parentSpan,
5324
5933
  timeout: options.timeout,
5325
5934
  ssrExport: setup2.metadataResult.ssrExport,
5326
- titleSuffix: options.titleSuffix
5935
+ titleSuffix: options.titleSuffix,
5936
+ i18n: options.i18n
5327
5937
  });
5328
5938
  if (debug)
5329
5939
  console.log(`[renderStream] ${pathname} - returning stream: ${(performance.now() - t0).toFixed(2)}ms`);
@@ -5436,7 +6046,7 @@ function buildStreamingScripts(ctx) {
5436
6046
  ssrDataSnapshot[key] = value;
5437
6047
  }
5438
6048
  }
5439
- 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);
5440
6050
  const feedsScript = createFeedsScript();
5441
6051
  const llmsScript = createLlmsScript();
5442
6052
  let allScripts = dataScript + feedsScript + llmsScript + collectionScripts + ctx.preloadHydrationScript;
@@ -5568,6 +6178,7 @@ var init_src = __esm(() => {
5568
6178
  init_bundler();
5569
6179
  init_config();
5570
6180
  init_content();
6181
+ init_i18n();
5571
6182
  init_llms();
5572
6183
  init_og();
5573
6184
  init_robots();
@@ -5580,6 +6191,7 @@ var init_src = __esm(() => {
5580
6191
  var exports_core = {};
5581
6192
  __export(exports_core, {
5582
6193
  toPromise: () => toPromise,
6194
+ toOgLocale: () => toOgLocale,
5583
6195
  toBytes: () => toBytes,
5584
6196
  stripStart: () => stripStart,
5585
6197
  stripHtmlTags: () => stripHtmlTags,
@@ -5591,6 +6203,7 @@ __export(exports_core, {
5591
6203
  setGlobalSSRData: () => setGlobalSSRData,
5592
6204
  setGlobalCollections: () => setGlobalCollections,
5593
6205
  seedSSRModuleForPath: () => seedSSRModuleForPath,
6206
+ resolveLocale: () => resolveLocale,
5594
6207
  resetSSRAccessTracking: () => resetSSRAccessTracking,
5595
6208
  renderSSRDocumentStream: () => renderSSRDocumentStream,
5596
6209
  renderSSRDocument: () => renderSSRDocument,
@@ -5599,7 +6212,17 @@ __export(exports_core, {
5599
6212
  processCollections: () => processCollections,
5600
6213
  preloadContentModule: () => preloadContentModule,
5601
6214
  performSSRSetup: () => performSSRSetup,
6215
+ parseCookieValue: () => parseCookieValue,
6216
+ parseContentPath: () => parseContentPath,
6217
+ normalizePathname: () => normalizePathname,
6218
+ negotiateLocale: () => negotiateLocale,
5602
6219
  mergeLayoutData: () => mergeLayoutData,
6220
+ matchRoutes: () => matchRoutes,
6221
+ matchRoutePattern: () => matchRoutePattern,
6222
+ lookupMessage: () => lookupMessage,
6223
+ looksLikeLocale: () => looksLikeLocale,
6224
+ localizeHref: () => localizeHref,
6225
+ localizeContentHref: () => localizeContentHref,
5603
6226
  loadRoutesModule: () => loadRoutesModule,
5604
6227
  loadOGImageComponent: () => loadOGImageComponent,
5605
6228
  loadIndexHtml: () => loadIndexHtml,
@@ -5612,11 +6235,13 @@ __export(exports_core, {
5612
6235
  inlineTailwindCSS: () => inlineTailwindCSS,
5613
6236
  importContentModuleForPath: () => importContentModuleForPath,
5614
6237
  htmlToMarkdown: () => htmlToMarkdown,
6238
+ hreflangTags: () => hreflangTags,
5615
6239
  gzipIfAccepted: () => gzipIfAccepted,
5616
6240
  getSitemapExcludedRoutes: () => getSitemapExcludedRoutes,
5617
6241
  getRedirectSourcePaths: () => getRedirectSourcePaths,
5618
6242
  getPatternDepth: () => getPatternDepth,
5619
6243
  getMimeType: () => getMimeType,
6244
+ getMessage: () => getMessage,
5620
6245
  getMatchingLayouts: () => getMatchingLayouts,
5621
6246
  getContentMeta: () => getContentMeta,
5622
6247
  generateSitemapXML: () => generateSitemapXML,
@@ -5633,6 +6258,7 @@ __export(exports_core, {
5633
6258
  generateContentHash: () => generateContentHash,
5634
6259
  generateCollectionScripts: () => generateCollectionScripts,
5635
6260
  generateAtomXML: () => generateAtomXML,
6261
+ formatMessage: () => formatMessage,
5636
6262
  formatAsText: () => formatAsText,
5637
6263
  formatAsMarkdown: () => formatAsMarkdown,
5638
6264
  findOGImageForPath: () => findOGImageForPath,
@@ -5652,10 +6278,12 @@ __export(exports_core, {
5652
6278
  discoverRoutes: () => discoverRoutes,
5653
6279
  discoverRobotsData: () => discoverRobotsData,
5654
6280
  discoverRSSData: () => discoverRSSData,
6281
+ discoverI18n: () => discoverI18n,
5655
6282
  discoverCollections: () => discoverCollections,
5656
6283
  discoverCollectionContent: () => discoverCollectionContent,
5657
6284
  defineConfig: () => defineConfig,
5658
6285
  deduplicateMetaTags: () => deduplicateMetaTags,
6286
+ createTranslator: () => createTranslator,
5659
6287
  createLlmsScript: () => createLlmsScript,
5660
6288
  createLayoutDataKey: () => createLayoutDataKey,
5661
6289
  createLayoutCacheKey: () => createLayoutCacheKey,
@@ -5663,13 +6291,17 @@ __export(exports_core, {
5663
6291
  createDataScript: () => createDataScript,
5664
6292
  createCollectionScript: () => createCollectionScript,
5665
6293
  createBundlePreload: () => createBundlePreload,
6294
+ contentNegotiateRedirect: () => contentNegotiateRedirect,
6295
+ contentKey: () => contentKey,
5666
6296
  computeSSRDataForPath: () => computeSSRDataForPath,
5667
6297
  computeSSRData: () => computeSSRData,
5668
6298
  computeRouteData: () => computeRouteData,
5669
6299
  computeLayoutData: () => computeLayoutData,
5670
6300
  computeContentData: () => computeContentData,
5671
6301
  compressStreamIfAccepted: () => compressStreamIfAccepted,
6302
+ compareRouteSpecificity: () => compareRouteSpecificity,
5672
6303
  cleanupPathScopedSSRData: () => cleanupPathScopedSSRData,
6304
+ buildLocaleCookie: () => buildLocaleCookie,
5673
6305
  buildHeadFromMeta: () => buildHeadFromMeta,
5674
6306
  blockSearchCrawlers: () => blockSearchCrawlers,
5675
6307
  blockAITrainingCrawlers: () => blockAITrainingCrawlers,
@@ -5685,6 +6317,9 @@ __export(exports_core, {
5685
6317
  SEARCH_CRAWLERS: () => SEARCH_CRAWLERS,
5686
6318
  LRUCache: () => LRUCache,
5687
6319
  DefaultOGTemplate: () => DefaultOGTemplate,
6320
+ DEFAULT_LOCALES_DIR: () => DEFAULT_LOCALES_DIR,
6321
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
6322
+ DEFAULT_COOKIE: () => DEFAULT_COOKIE,
5688
6323
  AI_CRAWLERS: () => AI_CRAWLERS
5689
6324
  });
5690
6325
  var init_core = __esm(() => {
@@ -5699,7 +6334,7 @@ import { createElement as createElement2 } from "react";
5699
6334
 
5700
6335
  // packages/elysia/src/libs/image.ts
5701
6336
  import { existsSync } from "node:fs";
5702
- import { join as join2 } from "node:path";
6337
+ import { join as join3 } from "node:path";
5703
6338
  import sharp from "sharp";
5704
6339
  var imageCache = new Map;
5705
6340
  var MAX_CACHE_SIZE = 100;
@@ -5742,8 +6377,8 @@ async function loadFromDiskCache(cacheKey) {
5742
6377
  if (!diskCacheDir)
5743
6378
  return null;
5744
6379
  try {
5745
- const cachePath = join2(diskCacheDir, `${cacheKey}.json`);
5746
- const imagePath = join2(diskCacheDir, `${cacheKey}.img`);
6380
+ const cachePath = join3(diskCacheDir, `${cacheKey}.json`);
6381
+ const imagePath = join3(diskCacheDir, `${cacheKey}.img`);
5747
6382
  if (!(existsSync(cachePath) && existsSync(imagePath))) {
5748
6383
  return null;
5749
6384
  }
@@ -5765,8 +6400,8 @@ async function saveToDiskCache(cacheKey, result) {
5765
6400
  if (!diskCacheDir)
5766
6401
  return;
5767
6402
  try {
5768
- const cachePath = join2(diskCacheDir, `${cacheKey}.json`);
5769
- const imagePath = join2(diskCacheDir, `${cacheKey}.img`);
6403
+ const cachePath = join3(diskCacheDir, `${cacheKey}.json`);
6404
+ const imagePath = join3(diskCacheDir, `${cacheKey}.img`);
5770
6405
  await Bun.write(cachePath, JSON.stringify({
5771
6406
  contentType: result.contentType,
5772
6407
  format: result.format,
@@ -6187,7 +6822,7 @@ function registerContentRoutes(app, cwd, collectionCache, directive, maxAge, isW
6187
6822
  // packages/elysia/src/routes/image.ts
6188
6823
  init_core();
6189
6824
  import { existsSync as existsSync2 } from "node:fs";
6190
- import { join as join3 } from "node:path";
6825
+ import { join as join4 } from "node:path";
6191
6826
  function parseNumericParam(value) {
6192
6827
  if (!value)
6193
6828
  return;
@@ -6234,8 +6869,8 @@ function registerImageRoutes(app, options) {
6234
6869
  }
6235
6870
  function resolveInternalImagePath(url, clientDir2) {
6236
6871
  const cleanUrl = url.startsWith("/") ? url.slice(1) : url;
6237
- const imagePath = join3(clientDir2, cleanUrl);
6238
- const normalizedPath = join3(clientDir2, cleanUrl);
6872
+ const imagePath = join4(clientDir2, cleanUrl);
6873
+ const normalizedPath = join4(clientDir2, cleanUrl);
6239
6874
  if (!normalizedPath.startsWith(clientDir2)) {
6240
6875
  return { error: "Access denied" };
6241
6876
  }
@@ -7213,44 +7848,60 @@ async function loadContentMeta(clientDir, collection, slug, isWatchMode, meta) {
7213
7848
  return;
7214
7849
  }
7215
7850
  }
7216
- async function loadOgImageFromRoute(_cwd, clientDir, pathname, isWatchMode) {
7851
+ async function loadMatchedRouteModule(clientDir, routePath, isWatchMode) {
7852
+ const { existsSync: existsSync3 } = await import("node:fs");
7853
+ const { pathToFileURL: pathToFileURL8 } = await import("node:url");
7854
+ const normalizedRoutePath = routePath === "" ? "index" : routePath;
7855
+ const possiblePaths = [
7856
+ `${clientDir}/routes/${normalizedRoutePath}.tsx`,
7857
+ `${clientDir}/routes/${normalizedRoutePath}/index.tsx`,
7858
+ `${clientDir}/routes/${normalizedRoutePath}.ts`,
7859
+ `${clientDir}/routes/${normalizedRoutePath}/index.ts`,
7860
+ `${clientDir}/routes/${normalizedRoutePath}.mdx`,
7861
+ `${clientDir}/routes/${normalizedRoutePath}/index.mdx`,
7862
+ `${clientDir}/routes/${normalizedRoutePath}.md`,
7863
+ `${clientDir}/routes/${normalizedRoutePath}/index.md`
7864
+ ];
7865
+ for (const possiblePath of possiblePaths) {
7866
+ if (existsSync3(possiblePath)) {
7867
+ return await import(pathToFileURL8(possiblePath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
7868
+ }
7869
+ }
7870
+ return null;
7871
+ }
7872
+ async function loadOgImageFromRoute(cwd, clientDir, pathname, isWatchMode) {
7217
7873
  try {
7874
+ const pathnameOnly = pathname.split("?")[0];
7218
7875
  const routesGlobal = globalThis.__REROUTE_ROUTES__;
7219
7876
  if (routesGlobal && Array.isArray(routesGlobal)) {
7220
- const matchedRoute = routesGlobal.find((r) => {
7221
- if (r.isDynamic) {
7222
- const pattern = r.pattern.replace(/:[^/]+/g, "[^/]+");
7223
- const regex = new RegExp(`^${pattern}$`);
7224
- return regex.test(pathname);
7225
- }
7226
- return r.pattern === pathname;
7227
- });
7228
- if (matchedRoute) {
7877
+ const matchedRoute2 = matchRoutes(routesGlobal, pathnameOnly)?.route;
7878
+ if (matchedRoute2) {
7229
7879
  return {
7230
- meta: matchedRoute.meta,
7231
- ogImage: matchedRoute.ssr?.ogImage
7880
+ meta: matchedRoute2.meta,
7881
+ ogImage: matchedRoute2.ssr?.ogImage
7232
7882
  };
7233
7883
  }
7234
7884
  }
7235
7885
  const { pathToFileURL: pathToFileURL8 } = await import("node:url");
7236
- const { existsSync: existsSync3 } = await import("node:fs");
7237
- const pathnameOnly = pathname.split("?")[0];
7238
- const cleanPath = pathnameOnly === "/" ? "index" : pathnameOnly.replace(/^\//, "").replace(/\/$/, "");
7239
- const possiblePaths = [
7240
- `${clientDir}/routes/${cleanPath}.tsx`,
7241
- `${clientDir}/routes/${cleanPath}/index.tsx`,
7242
- `${clientDir}/routes/${cleanPath}.ts`,
7243
- `${clientDir}/routes/${cleanPath}/index.ts`
7244
- ];
7245
- for (const routePath of possiblePaths) {
7246
- if (existsSync3(routePath)) {
7247
- const routeMod = await import(pathToFileURL8(routePath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
7886
+ const routesPath = `${cwd}/.reroute/routes.ts`;
7887
+ const routesMod = await import(pathToFileURL8(routesPath).href + (isWatchMode ? `?t=${Date.now()}` : ""));
7888
+ const match = typeof routesMod.matchRoute === "function" ? routesMod.matchRoute(pathnameOnly) : Array.isArray(routesMod.routes) ? matchRoutes(routesMod.routes, pathnameOnly) : null;
7889
+ const matchedRoute = match?.route;
7890
+ if (matchedRoute && typeof matchedRoute.path === "string") {
7891
+ const routeMod = await loadMatchedRouteModule(clientDir, String(matchedRoute.path), isWatchMode);
7892
+ if (routeMod) {
7248
7893
  return {
7249
7894
  meta: routeMod.meta,
7250
7895
  ogImage: routeMod.ssr?.ogImage
7251
7896
  };
7252
7897
  }
7253
7898
  }
7899
+ if (matchedRoute) {
7900
+ return {
7901
+ meta: matchedRoute.meta,
7902
+ ogImage: matchedRoute.ssr?.ogImage
7903
+ };
7904
+ }
7254
7905
  } catch (error) {
7255
7906
  console.warn("[reroute/og] Error loading route metadata:", error);
7256
7907
  }
@@ -7323,14 +7974,14 @@ function registerOGRoutes(app, options) {
7323
7974
  }
7324
7975
  return;
7325
7976
  }
7326
- function getFontPaths(fontPath, baseDir, join4) {
7977
+ function getFontPaths(fontPath, baseDir, join5) {
7327
7978
  const fontRelativePath = fontPath.replace(/^(\.\/)?src\/client\/assets\//, "").replace(/^assets\//, "");
7328
7979
  return [
7329
- join4(clientDir, "assets", fontRelativePath),
7330
- join4(baseDir, ".reroute", "assets", fontRelativePath),
7331
- join4(cwd, ".reroute", "assets", fontRelativePath),
7332
- join4(cwd, fontPath),
7333
- 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)
7334
7985
  ];
7335
7986
  }
7336
7987
  async function tryLoadFont(font, possiblePaths, existsSync3) {
@@ -7353,14 +8004,14 @@ function registerOGRoutes(app, options) {
7353
8004
  if (!ogConfig.fonts || ogConfig.fonts.length === 0) {
7354
8005
  return [];
7355
8006
  }
7356
- const { join: join4, dirname } = await import("node:path");
8007
+ const { join: join5, dirname } = await import("node:path");
7357
8008
  const { existsSync: existsSync3 } = await import("node:fs");
7358
8009
  const isCompiledBinary = typeof Bun !== "undefined" && process.argv[0]?.endsWith(process.argv[1]);
7359
8010
  const baseDir = isCompiledBinary ? dirname(process.argv[1]) : cwd;
7360
8011
  const loadedFonts = [];
7361
8012
  for (const font of ogConfig.fonts) {
7362
8013
  try {
7363
- const possiblePaths = getFontPaths(font.path, baseDir, join4);
8014
+ const possiblePaths = getFontPaths(font.path, baseDir, join5);
7364
8015
  const loadedFont = await tryLoadFont(font, possiblePaths, existsSync3);
7365
8016
  if (loadedFont) {
7366
8017
  loadedFonts.push(loadedFont);
@@ -7688,42 +8339,52 @@ async function registerMainFeedRoute(app, cwd, collections2, baseUrl, isWatchMod
7688
8339
  function registerCollectionFeedRoutes(app, collections2, options) {
7689
8340
  for (const collection of collections2) {
7690
8341
  options.availableFeeds.push(`/${collection}/feed.xml`);
7691
- app.get(`/${collection}/feed.xml`, async () => {
7692
- const cacheKey = `rss:${collection}`;
7693
- try {
7694
- const cached = options.rssCache.get(cacheKey);
7695
- if (cached) {
7696
- return feedResponse(cached, options.format, {
7697
- maxAge: options.maxAge,
7698
- cacheStatus: "HIT"
7699
- });
7700
- }
7701
- const discovery5 = await getOrDiscoverRSSData(options.cwd, [collection], options.baseUrl, options.isWatchMode, options.rssConfig);
7702
- const items = discovery5.collections.get(collection) ?? [];
7703
- if (items.length === 0)
7704
- throw new NotFoundError8;
7705
- const feed = {
7706
- title: createFeedTitle(options.rssConfig?.title, collection),
7707
- link: `${options.baseUrl}/${collection}/feed.xml`,
7708
- description: options.rssConfig?.description ?? `Latest ${collection} updates`,
7709
- items
7710
- };
7711
- const xml = generateFeed(feed, {
7712
- format: options.format,
7713
- limit: options.limit
7714
- });
7715
- options.rssCache.set(cacheKey, xml);
7716
- return feedResponse(xml, options.format, {
7717
- maxAge: options.maxAge,
7718
- cacheStatus: "MISS"
7719
- });
7720
- } catch (error) {
7721
- if (error instanceof NotFoundError8)
7722
- throw error;
7723
- console.error(`[reroute] Failed to generate ${collection} feed:`, error);
7724
- throw new NotFoundError8;
7725
- }
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
7726
8377
  });
8378
+ options.rssCache.set(cacheKey, xml);
8379
+ return feedResponse(xml, options.format, {
8380
+ maxAge: options.maxAge,
8381
+ cacheStatus: "MISS"
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;
7727
8388
  }
7728
8389
  }
7729
8390
  async function discoverRoutesWithFeeds(ssrDataRoutes, collections2, options) {
@@ -7899,11 +8560,11 @@ function scoreEntry(entry, terms) {
7899
8560
  return { score, matchedHeadings };
7900
8561
  }
7901
8562
  function searchEntries(entries, query, options = {}) {
7902
- const { collections: collections2, page = 1, pageSize = 20 } = options;
8563
+ const { collections: collections2, page = 1, pageSize = 20, locale } = options;
7903
8564
  const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
7904
8565
  if (terms.length === 0)
7905
8566
  return { results: [], total: 0 };
7906
- 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);
7907
8568
  const total = scored.length;
7908
8569
  const startIndex = (page - 1) * pageSize;
7909
8570
  const paginatedScored = scored.slice(startIndex, startIndex + pageSize);
@@ -7954,10 +8615,12 @@ function registerSearchRoute(app, cwd, directive, maxAge, searchConfig) {
7954
8615
  const page = query.page ? Math.max(1, Number.parseInt(String(query.page), 10)) : 1;
7955
8616
  const requestedPageSize = query.pageSize ? Number.parseInt(String(query.pageSize), 10) : defaultPageSize;
7956
8617
  const pageSize = Math.min(Math.max(1, requestedPageSize), maxPageSize);
8618
+ const locale = query.lang ? String(query.lang) : undefined;
7957
8619
  const { results, total } = searchEntries(index.entries, searchQuery, {
7958
8620
  collections: collections2,
7959
8621
  page,
7960
- pageSize
8622
+ pageSize,
8623
+ locale
7961
8624
  });
7962
8625
  return Response.json({
7963
8626
  total,
@@ -8061,9 +8724,115 @@ function registerSitemapRoutes(app, options) {
8061
8724
  // packages/elysia/src/routes/ssr.ts
8062
8725
  init_core();
8063
8726
  init_server2();
8064
- import { join as join4 } from "node:path";
8727
+ import { join as join6 } from "node:path";
8065
8728
  import { trace as trace4 } from "@opentelemetry/api";
8066
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
8067
8836
  if (typeof process !== "undefined") {
8068
8837
  process.on("unhandledRejection", (reason, _promise) => {
8069
8838
  console.error("[reroute] Unhandled Promise Rejection:", reason);
@@ -8085,6 +8854,28 @@ function isOtelAvailable3() {
8085
8854
  return false;
8086
8855
  }
8087
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
+ }
8088
8879
  function registerSSRRoutes(app, options) {
8089
8880
  const {
8090
8881
  rootComponent,
@@ -8124,7 +8915,7 @@ function registerSSRRoutes(app, options) {
8124
8915
  let contentRoutes = [];
8125
8916
  let dynamicRoutes = [];
8126
8917
  try {
8127
- const manifestPath = join4(cwd, ".reroute", "manifest.json");
8918
+ const manifestPath = join6(cwd, ".reroute", "manifest.json");
8128
8919
  const { readFileSync } = __require("node:fs");
8129
8920
  const manifestText = readFileSync(manifestPath, "utf-8");
8130
8921
  const manifest = JSON.parse(manifestText);
@@ -8160,6 +8951,9 @@ function registerSSRRoutes(app, options) {
8160
8951
  request.headers.forEach((value, key) => {
8161
8952
  headers2[key.toLowerCase()] = value;
8162
8953
  });
8954
+ const i18n2 = await applyI18nForSSR(cwd, pathname, headers2, set, lang);
8955
+ if (i18n2.redirect)
8956
+ return i18n2.redirect;
8163
8957
  const parentSpan = store.telemetrySpan;
8164
8958
  return withParentSpan(parentSpan, async () => {
8165
8959
  let isFirstStreamingRequest = false;
@@ -8242,7 +9036,7 @@ function registerSSRRoutes(app, options) {
8242
9036
  cachedIndexHtml,
8243
9037
  debug,
8244
9038
  head,
8245
- lang,
9039
+ lang: i18n2.effectiveLang,
8246
9040
  appId,
8247
9041
  maxAge: serverMaxAge,
8248
9042
  searchParams,
@@ -8253,7 +9047,8 @@ function registerSSRRoutes(app, options) {
8253
9047
  parentSpan: renderSpan,
8254
9048
  headers: headers2,
8255
9049
  timeout: options.streamingTimeout,
8256
- titleSuffix: options.titleSuffix
9050
+ titleSuffix: options.titleSuffix,
9051
+ i18n: i18n2.hydration
8257
9052
  });
8258
9053
  renderSpan?.setAttribute("http.response.status_code", result.status);
8259
9054
  if (debug)
@@ -8424,7 +9219,7 @@ function registerSSRRoutes(app, options) {
8424
9219
  isWatchMode,
8425
9220
  bundleUrl,
8426
9221
  head,
8427
- lang,
9222
+ lang: i18n2.effectiveLang,
8428
9223
  appId,
8429
9224
  maxAge: serverMaxAge,
8430
9225
  searchParams,
@@ -8434,7 +9229,8 @@ function registerSSRRoutes(app, options) {
8434
9229
  autoCanonical: options.autoCanonical,
8435
9230
  parentSpan: renderSpan,
8436
9231
  headers: headers2,
8437
- titleSuffix: options.titleSuffix
9232
+ titleSuffix: options.titleSuffix,
9233
+ i18n: i18n2.hydration
8438
9234
  });
8439
9235
  renderSpan?.setAttribute("http.response.status_code", renderResult.status);
8440
9236
  return renderResult;
@@ -9012,4 +9808,4 @@ export {
9012
9808
  reroute
9013
9809
  };
9014
9810
 
9015
- //# debugId=46D34975F51945E064756E2164756E21
9811
+ //# debugId=27029E2CFF5EA81E64756E2164756E21