reroute-js 0.45.12-beta.1 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +100 -2
  2. package/_/blog/src/client/routes/blog/[layout].tsx +45 -13
  3. package/_/blog/src/client/routes/blog/content/pt-BR/1-hello-world.tsx +27 -0
  4. package/_/blog/src/client/routes/index.tsx +4 -4
  5. package/_/search/src/client/routes/docs/content/content-collections.md +18 -0
  6. package/cli/bin.d.ts +2 -2
  7. package/cli/bin.js +980 -337
  8. package/cli/bin.js.map +29 -21
  9. package/cli/index.d.ts +2 -2
  10. package/cli/index.js +5 -5
  11. package/cli/index.js.map +1 -1
  12. package/cli/src/cli.d.ts +2 -2
  13. package/cli/src/commands/analyze.d.ts +2 -2
  14. package/cli/src/commands/build.d.ts +2 -2
  15. package/cli/src/commands/dev.d.ts +2 -2
  16. package/cli/src/commands/gen.d.ts +2 -2
  17. package/cli/src/commands/gen.d.ts.map +1 -1
  18. package/cli/src/commands/index.d.ts +2 -2
  19. package/cli/src/commands/init.d.ts +2 -2
  20. package/cli/src/commands/lib/assets.d.ts +2 -2
  21. package/cli/src/commands/lib/bundler.d.ts +2 -2
  22. package/cli/src/commands/lib/command.d.ts +2 -2
  23. package/cli/src/commands/lib/env.d.ts +2 -2
  24. package/cli/src/commands/lib/index.d.ts +2 -2
  25. package/cli/src/commands/lib/log.d.ts +2 -2
  26. package/cli/src/commands/lib/markdown/availability.d.ts +2 -2
  27. package/cli/src/commands/lib/markdown/index.d.ts +2 -2
  28. package/cli/src/commands/lib/markdown/processor.d.ts +2 -2
  29. package/cli/src/commands/lib/production.d.ts +2 -2
  30. package/cli/src/commands/lib/server.d.ts +2 -2
  31. package/cli/src/commands/lib/streaming/analyzer.d.ts +2 -2
  32. package/cli/src/commands/lib/streaming/suspense.d.ts +2 -2
  33. package/cli/src/commands/lib/tailwind.d.ts +2 -2
  34. package/cli/src/commands/lib/terminal-ui.d.ts +2 -2
  35. package/cli/src/commands/lib/version.d.ts +2 -2
  36. package/cli/src/commands/og.d.ts +2 -2
  37. package/cli/src/commands/start.d.ts +2 -2
  38. package/cli/src/index.d.ts +2 -2
  39. package/core/index.d.ts +2 -2
  40. package/core/index.js +667 -117
  41. package/core/index.js.map +28 -20
  42. package/core/src/bundler/hash.d.ts +2 -2
  43. package/core/src/bundler/index.d.ts +2 -2
  44. package/core/src/config.d.ts +6 -3
  45. package/core/src/config.d.ts.map +1 -1
  46. package/core/src/content/discovery.d.ts +2 -2
  47. package/core/src/content/index.d.ts +2 -2
  48. package/core/src/content/metadata.d.ts +2 -2
  49. package/core/src/i18n/catalog.d.ts +17 -0
  50. package/core/src/i18n/catalog.d.ts.map +1 -0
  51. package/core/src/i18n/discover.d.ts +19 -0
  52. package/core/src/i18n/discover.d.ts.map +1 -0
  53. package/core/src/i18n/index.d.ts +17 -0
  54. package/core/src/i18n/index.d.ts.map +1 -0
  55. package/core/src/i18n/messages.d.ts +18 -0
  56. package/core/src/i18n/messages.d.ts.map +1 -0
  57. package/core/src/i18n/negotiate.d.ts +18 -0
  58. package/core/src/i18n/negotiate.d.ts.map +1 -0
  59. package/core/src/i18n/pathname.d.ts +34 -0
  60. package/core/src/i18n/pathname.d.ts.map +1 -0
  61. package/core/src/i18n/resolve.d.ts +22 -0
  62. package/core/src/i18n/resolve.d.ts.map +1 -0
  63. package/core/src/i18n/types.d.ts +68 -0
  64. package/core/src/i18n/types.d.ts.map +1 -0
  65. package/core/src/index.d.ts +3 -2
  66. package/core/src/index.d.ts.map +1 -1
  67. package/core/src/llms/extractor.d.ts +2 -2
  68. package/core/src/llms/formatter.d.ts +2 -2
  69. package/core/src/llms/full-generator.d.ts +2 -2
  70. package/core/src/llms/index-generator.d.ts +2 -2
  71. package/core/src/llms/index.d.ts +2 -2
  72. package/core/src/og/discovery.d.ts +2 -2
  73. package/core/src/og/index.d.ts +2 -2
  74. package/core/src/og/meta.d.ts +2 -2
  75. package/core/src/og/render.d.ts +2 -2
  76. package/core/src/og/types.d.ts +2 -2
  77. package/core/src/robots/discovery.d.ts +2 -2
  78. package/core/src/robots/generator.d.ts +2 -2
  79. package/core/src/robots/index.d.ts +2 -2
  80. package/core/src/robots/policies.d.ts +2 -2
  81. package/core/src/rss/discovery.d.ts +4 -2
  82. package/core/src/rss/discovery.d.ts.map +1 -1
  83. package/core/src/rss/generator.d.ts +2 -2
  84. package/core/src/rss/index.d.ts +2 -2
  85. package/core/src/sitemap/discovery.d.ts +7 -2
  86. package/core/src/sitemap/discovery.d.ts.map +1 -1
  87. package/core/src/sitemap/generator.d.ts +2 -2
  88. package/core/src/sitemap/generator.d.ts.map +1 -1
  89. package/core/src/sitemap/index.d.ts +2 -2
  90. package/core/src/ssr/index.d.ts +2 -2
  91. package/core/src/ssr/lib/cache.d.ts +2 -2
  92. package/core/src/ssr/lib/cleanup.d.ts +2 -2
  93. package/core/src/ssr/lib/collections.d.ts +2 -2
  94. package/core/src/ssr/lib/compression.d.ts +2 -2
  95. package/core/src/ssr/lib/compute/content.d.ts +2 -9
  96. package/core/src/ssr/lib/compute/content.d.ts.map +1 -1
  97. package/core/src/ssr/lib/compute/index.d.ts +2 -2
  98. package/core/src/ssr/lib/compute/layouts.d.ts +2 -2
  99. package/core/src/ssr/lib/compute/routes.d.ts +2 -2
  100. package/core/src/ssr/lib/compute/routes.d.ts.map +1 -1
  101. package/core/src/ssr/lib/content-path.d.ts +22 -0
  102. package/core/src/ssr/lib/content-path.d.ts.map +1 -0
  103. package/core/src/ssr/lib/data.d.ts +2 -2
  104. package/core/src/ssr/lib/data.d.ts.map +1 -1
  105. package/core/src/ssr/lib/html.d.ts +2 -2
  106. package/core/src/ssr/lib/imports.d.ts +2 -2
  107. package/core/src/ssr/lib/index.d.ts +2 -2
  108. package/core/src/ssr/lib/layouts.d.ts +2 -2
  109. package/core/src/ssr/lib/metadata.d.ts +2 -2
  110. package/core/src/ssr/lib/metadata.d.ts.map +1 -1
  111. package/core/src/ssr/lib/mime.d.ts +2 -2
  112. package/core/src/ssr/lib/modules.d.ts +4 -4
  113. package/core/src/ssr/lib/modules.d.ts.map +1 -1
  114. package/core/src/ssr/lib/path.d.ts +2 -2
  115. package/core/src/ssr/lib/preload.d.ts +2 -2
  116. package/core/src/ssr/lib/preload.d.ts.map +1 -1
  117. package/core/src/ssr/lib/route-match.d.ts +2 -2
  118. package/core/src/ssr/lib/scripts/collections.d.ts +2 -2
  119. package/core/src/ssr/lib/scripts/data.d.ts +3 -3
  120. package/core/src/ssr/lib/scripts/data.d.ts.map +1 -1
  121. package/core/src/ssr/lib/scripts/escape.d.ts +2 -2
  122. package/core/src/ssr/lib/scripts/feeds.d.ts +2 -2
  123. package/core/src/ssr/lib/scripts/index.d.ts +2 -2
  124. package/core/src/ssr/lib/seed.d.ts +2 -2
  125. package/core/src/ssr/lib/seed.d.ts.map +1 -1
  126. package/core/src/ssr/lib/serialize.d.ts +2 -2
  127. package/core/src/ssr/lib/setup.d.ts +3 -3
  128. package/core/src/ssr/lib/setup.d.ts.map +1 -1
  129. package/core/src/ssr/lib/styles.d.ts +2 -2
  130. package/core/src/ssr/lib/template.d.ts +2 -2
  131. package/core/src/ssr/lib/types.d.ts +2 -2
  132. package/core/src/ssr/render.d.ts +4 -2
  133. package/core/src/ssr/render.d.ts.map +1 -1
  134. package/core/src/ssr/stream.d.ts +4 -2
  135. package/core/src/ssr/stream.d.ts.map +1 -1
  136. package/elysia/index.d.ts +2 -2
  137. package/elysia/index.js +872 -177
  138. package/elysia/index.js.map +32 -23
  139. package/elysia/src/index.d.ts +2 -2
  140. package/elysia/src/libs/assets.d.ts +2 -2
  141. package/elysia/src/libs/cache.d.ts +2 -2
  142. package/elysia/src/libs/caching.d.ts +2 -2
  143. package/elysia/src/libs/http.d.ts +2 -2
  144. package/elysia/src/libs/i18n.d.ts +26 -0
  145. package/elysia/src/libs/i18n.d.ts.map +1 -0
  146. package/elysia/src/libs/image.d.ts +2 -2
  147. package/elysia/src/libs/index.d.ts +2 -2
  148. package/elysia/src/libs/llms.d.ts +2 -2
  149. package/elysia/src/libs/response.d.ts +2 -2
  150. package/elysia/src/libs/serving.d.ts +2 -2
  151. package/elysia/src/plugin.d.ts +2 -2
  152. package/elysia/src/routes/artifacts.d.ts +2 -2
  153. package/elysia/src/routes/content.d.ts +2 -2
  154. package/elysia/src/routes/image.d.ts +2 -2
  155. package/elysia/src/routes/index.d.ts +2 -2
  156. package/elysia/src/routes/internal.d.ts +2 -2
  157. package/elysia/src/routes/llms.d.ts +2 -2
  158. package/elysia/src/routes/og.d.ts +2 -2
  159. package/elysia/src/routes/redirects.d.ts +2 -2
  160. package/elysia/src/routes/robots.d.ts +2 -2
  161. package/elysia/src/routes/rss.d.ts +2 -2
  162. package/elysia/src/routes/rss.d.ts.map +1 -1
  163. package/elysia/src/routes/search.d.ts +2 -2
  164. package/elysia/src/routes/search.d.ts.map +1 -1
  165. package/elysia/src/routes/sitemap.d.ts +2 -2
  166. package/elysia/src/routes/ssr.d.ts +2 -2
  167. package/elysia/src/routes/ssr.d.ts.map +1 -1
  168. package/elysia/src/routes/static.d.ts +2 -2
  169. package/elysia/src/types.d.ts +2 -2
  170. package/package.json +7 -6
  171. package/react/index.d.ts +2 -2
  172. package/react/index.js +560 -170
  173. package/react/index.js.map +12 -10
  174. package/react/src/components/ClientOnly.d.ts +2 -2
  175. package/react/src/components/ContentRoute.d.ts +3 -3
  176. package/react/src/components/ContentRoute.d.ts.map +1 -1
  177. package/react/src/components/Image.d.ts +2 -2
  178. package/react/src/components/LazyRoute.d.ts +2 -2
  179. package/react/src/components/Link.d.ts +2 -2
  180. package/react/src/components/Link.d.ts.map +1 -1
  181. package/react/src/components/Markdown.d.ts +2 -2
  182. package/react/src/components/Outlet.d.ts +2 -2
  183. package/react/src/components/index.d.ts +2 -2
  184. package/react/src/hooks/index.d.ts +2 -2
  185. package/react/src/hooks/useContent.d.ts +2 -2
  186. package/react/src/hooks/useContent.d.ts.map +1 -1
  187. package/react/src/hooks/useData.d.ts +2 -2
  188. package/react/src/hooks/useFeed.d.ts +2 -2
  189. package/react/src/hooks/useLayoutData.d.ts +2 -2
  190. package/react/src/hooks/useLlms.d.ts +2 -2
  191. package/react/src/hooks/useNavigate.d.ts +2 -2
  192. package/react/src/hooks/useParams.d.ts +2 -2
  193. package/react/src/hooks/useRouter.d.ts +2 -2
  194. package/react/src/hooks/useSearch.d.ts +2 -2
  195. package/react/src/hooks/useSearch.d.ts.map +1 -1
  196. package/react/src/hooks/useSearchParams.d.ts +2 -2
  197. package/react/src/hooks/useToc.d.ts +2 -2
  198. package/react/src/i18n/LocaleProvider.d.ts +49 -0
  199. package/react/src/i18n/LocaleProvider.d.ts.map +1 -0
  200. package/react/src/i18n/helpers.d.ts +24 -0
  201. package/react/src/i18n/helpers.d.ts.map +1 -0
  202. package/react/src/i18n/index.d.ts +13 -0
  203. package/react/src/i18n/index.d.ts.map +1 -0
  204. package/react/src/index.d.ts +3 -2
  205. package/react/src/index.d.ts.map +1 -1
  206. package/react/src/lib/collection.d.ts +2 -2
  207. package/react/src/lib/content.d.ts +4 -3
  208. package/react/src/lib/content.d.ts.map +1 -1
  209. package/react/src/lib/head.d.ts +2 -2
  210. package/react/src/lib/index.d.ts +2 -2
  211. package/react/src/lib/lazy-route.d.ts +2 -2
  212. package/react/src/lib/route-loader.d.ts +2 -2
  213. package/react/src/providers/ContentProvider.d.ts +3 -3
  214. package/react/src/providers/ContentProvider.d.ts.map +1 -1
  215. package/react/src/providers/RerouteProvider.d.ts +8 -6
  216. package/react/src/providers/RerouteProvider.d.ts.map +1 -1
  217. package/react/src/providers/RouterProvider.d.ts +2 -2
  218. package/react/src/providers/index.d.ts +2 -2
  219. package/react/src/types/any.d.ts +2 -2
  220. package/react/src/types/index.d.ts +2 -2
  221. package/react/src/types/router.d.ts +2 -2
  222. package/telemetry/react.d.ts +2 -2
  223. package/telemetry/react.js +3 -3
  224. package/telemetry/react.js.map +1 -1
  225. package/telemetry/server.d.ts +2 -2
  226. package/telemetry/server.js +56 -4
  227. package/telemetry/server.js.map +20 -15
  228. package/telemetry/src/react/api.d.ts +2 -2
  229. package/telemetry/src/react/context.d.ts +2 -2
  230. package/telemetry/src/react/index.d.ts +2 -2
  231. package/telemetry/src/react/telemetry.d.ts +2 -2
  232. package/telemetry/src/server/context.d.ts +2 -2
  233. package/telemetry/src/server/headers/extractor.d.ts +2 -2
  234. package/telemetry/src/server/headers/index.d.ts +2 -2
  235. package/telemetry/src/server/headers/presets.d.ts +2 -2
  236. package/telemetry/src/server/index.d.ts +2 -2
  237. package/telemetry/src/server/instrumentation.d.ts +2 -2
  238. package/telemetry/src/server/plugin.d.ts +2 -2
  239. package/telemetry/src/server/sourcemap.d.ts +2 -2
package/cli/bin.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
3
  /**
4
- * reroute-js v0.45.11
4
+ * reroute-js v0.46.0
5
5
  *
6
6
  * @license MIT
7
7
  * @copyright 2026 stewones <hi@stewan.io>
8
- * @see https://github.com/stewones/reroute
8
+ * @see https://github.com/Intenseloop/reroute
9
9
  *
10
10
  * Built with Bun <3
11
11
  */
@@ -66,7 +66,7 @@ async function getVersionString() {
66
66
  }
67
67
  async function getVersion() {
68
68
  if (true) {
69
- return "0.45.11";
69
+ return "0.46.0";
70
70
  }
71
71
  const possiblePaths = [
72
72
  path.join(import.meta.dir, "../../../../package.json"),
@@ -84,7 +84,7 @@ async function getVersion() {
84
84
  }
85
85
  async function getCommit() {
86
86
  if (true) {
87
- return "c64466b";
87
+ return "9116acf";
88
88
  }
89
89
  return "dev";
90
90
  }
@@ -536,6 +536,364 @@ var init_content = __esm(() => {
536
536
  init_metadata();
537
537
  });
538
538
 
539
+ // packages/core/src/i18n/catalog.ts
540
+ function getMessage(catalog, key) {
541
+ if (!(catalog && key))
542
+ return;
543
+ const parts = key.split(".");
544
+ let current = catalog;
545
+ for (const part of parts) {
546
+ if (current == null || typeof current === "string")
547
+ return;
548
+ current = current[part];
549
+ }
550
+ return typeof current === "string" ? current : undefined;
551
+ }
552
+ function lookupMessage(catalogs, locale, defaultLocale, key) {
553
+ return getMessage(catalogs[locale], key) ?? (locale !== defaultLocale ? getMessage(catalogs[defaultLocale], key) : undefined);
554
+ }
555
+ var init_catalog = () => {};
556
+
557
+ // packages/core/src/i18n/discover.ts
558
+ import { readdir as readdir3 } from "node:fs/promises";
559
+ import { join as join3 } from "node:path";
560
+ async function loadCatalog(filePath) {
561
+ try {
562
+ const text = await Bun.file(filePath).text();
563
+ return JSON.parse(text);
564
+ } catch {
565
+ return null;
566
+ }
567
+ }
568
+ async function loadLocaleCatalogs(absDir) {
569
+ const entries = await readdir3(absDir, { withFileTypes: true });
570
+ const catalogs = {};
571
+ const locales = [];
572
+ for (const entry of entries) {
573
+ if (!(entry.isFile() && entry.name.endsWith(".json")))
574
+ continue;
575
+ const locale = entry.name.slice(0, -".json".length);
576
+ if (!locale)
577
+ continue;
578
+ const catalog = await loadCatalog(join3(absDir, entry.name));
579
+ if (!catalog)
580
+ continue;
581
+ catalogs[locale] = catalog;
582
+ locales.push(locale);
583
+ }
584
+ return { catalogs, locales };
585
+ }
586
+ function sortLocales(locales, defaultLocale) {
587
+ locales.sort((a, b) => {
588
+ if (a === defaultLocale)
589
+ return -1;
590
+ if (b === defaultLocale)
591
+ return 1;
592
+ return a.localeCompare(b);
593
+ });
594
+ }
595
+ async function discoverI18n(cwd, options = {}) {
596
+ const localesDir = options.localesDir ?? DEFAULT_LOCALES_DIR;
597
+ const defaultLocale = options.defaultLocale ?? DEFAULT_LOCALE;
598
+ const cookie = options.cookie ?? DEFAULT_COOKIE;
599
+ const redirectContentOnNegotiate = options.redirectContentOnNegotiate ?? true;
600
+ const absDir = join3(cwd, localesDir);
601
+ const disabled = {
602
+ enabled: false,
603
+ defaultLocale,
604
+ locales: [],
605
+ localesDir,
606
+ cookie,
607
+ redirectContentOnNegotiate,
608
+ catalogs: {}
609
+ };
610
+ let catalogs;
611
+ let locales;
612
+ try {
613
+ ({ catalogs, locales } = await loadLocaleCatalogs(absDir));
614
+ } catch {
615
+ return disabled;
616
+ }
617
+ if (locales.length === 0) {
618
+ return disabled;
619
+ }
620
+ sortLocales(locales, defaultLocale);
621
+ if (!catalogs[defaultLocale]) {
622
+ console.warn(`[reroute/i18n] defaultLocale "${defaultLocale}" has no catalog; using "${locales[0]}"`);
623
+ }
624
+ return {
625
+ enabled: true,
626
+ defaultLocale: catalogs[defaultLocale] ? defaultLocale : locales[0],
627
+ locales,
628
+ localesDir,
629
+ cookie,
630
+ redirectContentOnNegotiate,
631
+ catalogs
632
+ };
633
+ }
634
+ var DEFAULT_LOCALE = "en-US", DEFAULT_LOCALES_DIR = "locales", DEFAULT_COOKIE = "reroute-locale";
635
+ var init_discover = () => {};
636
+
637
+ // packages/core/src/i18n/messages.ts
638
+ import { IntlMessageFormat } from "intl-messageformat";
639
+ function formatMessage(message, locale, values) {
640
+ if (!values || Object.keys(values).length === 0) {
641
+ if (!/\{[^}]+\}/.test(message))
642
+ return message;
643
+ }
644
+ const cacheKey = `${locale}\x00${message}`;
645
+ let formatter = formatterCache.get(cacheKey);
646
+ if (!formatter) {
647
+ formatter = new IntlMessageFormat(message, locale);
648
+ formatterCache.set(cacheKey, formatter);
649
+ }
650
+ try {
651
+ const result = formatter.format(values);
652
+ return typeof result === "string" ? result : String(result);
653
+ } catch {
654
+ return message;
655
+ }
656
+ }
657
+ function createTranslator(catalogs, locale, defaultLocale) {
658
+ return (key, values) => {
659
+ const message = lookupMessage(catalogs, locale, defaultLocale, key);
660
+ if (message == null)
661
+ return key;
662
+ return formatMessage(message, locale, values);
663
+ };
664
+ }
665
+ var formatterCache;
666
+ var init_messages = __esm(() => {
667
+ init_catalog();
668
+ formatterCache = new Map;
669
+ });
670
+
671
+ // packages/core/src/i18n/negotiate.ts
672
+ function negotiateLocale(acceptLanguage, availableLocales, defaultLocale) {
673
+ if (!availableLocales.length)
674
+ return defaultLocale;
675
+ if (!acceptLanguage)
676
+ return defaultLocale;
677
+ const availableLower = new Map(availableLocales.map((l) => [l.toLowerCase(), l]));
678
+ const byLang = new Map;
679
+ for (const locale of availableLocales) {
680
+ const lang = locale.split("-")[0].toLowerCase();
681
+ if (!byLang.has(lang))
682
+ byLang.set(lang, locale);
683
+ }
684
+ const ranges = acceptLanguage.split(",").map((part) => {
685
+ const [tag, ...params] = part.trim().split(";");
686
+ let q = 1;
687
+ for (const p of params) {
688
+ const [k, v] = p.trim().split("=");
689
+ if (k === "q" && v)
690
+ q = Number.parseFloat(v) || 0;
691
+ }
692
+ return { tag: tag.trim().toLowerCase(), q };
693
+ }).filter((r) => r.tag && r.q > 0).sort((a, b) => b.q - a.q);
694
+ for (const { tag } of ranges) {
695
+ if (tag === "*")
696
+ return defaultLocale;
697
+ const exact = availableLower.get(tag);
698
+ if (exact)
699
+ return exact;
700
+ const langOnly = byLang.get(tag.split("-")[0]);
701
+ if (langOnly)
702
+ return langOnly;
703
+ }
704
+ return defaultLocale;
705
+ }
706
+ function parseCookieValue(cookieHeader, name) {
707
+ if (!cookieHeader)
708
+ return;
709
+ const parts = cookieHeader.split(";");
710
+ for (const part of parts) {
711
+ const idx = part.indexOf("=");
712
+ if (idx === -1)
713
+ continue;
714
+ const key = part.slice(0, idx).trim();
715
+ if (key === name) {
716
+ return decodeURIComponent(part.slice(idx + 1).trim());
717
+ }
718
+ }
719
+ return;
720
+ }
721
+ function buildLocaleCookie(name, locale, maxAgeSeconds = 60 * 60 * 24 * 365) {
722
+ return `${name}=${encodeURIComponent(locale)}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax`;
723
+ }
724
+ var init_negotiate = () => {};
725
+
726
+ // packages/core/src/i18n/pathname.ts
727
+ var exports_pathname = {};
728
+ __export(exports_pathname, {
729
+ toOgLocale: () => toOgLocale,
730
+ parseContentPath: () => parseContentPath,
731
+ normalizePathname: () => normalizePathname,
732
+ looksLikeLocale: () => looksLikeLocale,
733
+ localizeHref: () => localizeHref,
734
+ localizeContentHref: () => localizeContentHref,
735
+ hreflangTags: () => hreflangTags,
736
+ contentKey: () => contentKey
737
+ });
738
+ function looksLikeLocale(segment) {
739
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(segment);
740
+ }
741
+ function normalizePathname(pathname) {
742
+ const pathOnly = pathname.split("?")[0].split("#")[0];
743
+ if (!pathOnly || pathOnly === "/")
744
+ return "/";
745
+ return pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
746
+ }
747
+ function parseContentPath(pathname, availableLocales = [], collections) {
748
+ const normalized = normalizePathname(pathname);
749
+ const parts = normalized.split("/").filter(Boolean);
750
+ if (parts.length < 2)
751
+ return;
752
+ const [collection, second, third] = parts;
753
+ if (collections && !collections.includes(collection)) {
754
+ return;
755
+ }
756
+ const localeSet = new Set(availableLocales.map((l) => l.toLowerCase()));
757
+ if (parts.length >= 3 && third) {
758
+ const secondIsLocale = localeSet.size > 0 ? localeSet.has(second.toLowerCase()) : looksLikeLocale(second);
759
+ if (secondIsLocale) {
760
+ const locale = availableLocales.find((l) => l.toLowerCase() === second.toLowerCase()) || second;
761
+ return {
762
+ collection,
763
+ name: third,
764
+ locale,
765
+ routingPathname: `/${collection}/${third}`,
766
+ pathname: normalized
767
+ };
768
+ }
769
+ }
770
+ return {
771
+ collection,
772
+ name: second,
773
+ routingPathname: `/${collection}/${second}`,
774
+ pathname: normalized
775
+ };
776
+ }
777
+ function localizeContentHref(collection, name, locale, defaultLocale) {
778
+ if (!locale || locale === defaultLocale) {
779
+ return `/${collection}/${name}`;
780
+ }
781
+ return `/${collection}/${locale}/${name}`;
782
+ }
783
+ function localizeHref(to, locale, defaultLocale, availableLocales = [], collections) {
784
+ try {
785
+ const url = new URL(to, "http://local");
786
+ const parsed = parseContentPath(url.pathname, availableLocales, collections);
787
+ if (!parsed)
788
+ return to;
789
+ url.pathname = localizeContentHref(parsed.collection, parsed.name, locale, defaultLocale);
790
+ return `${url.pathname}${url.search}${url.hash}`;
791
+ } catch {
792
+ return to;
793
+ }
794
+ }
795
+ function contentKey(collection, name, locale, defaultLocale = "en-US") {
796
+ if (locale && locale !== defaultLocale) {
797
+ return `${collection}:${name}:${locale}`;
798
+ }
799
+ return `${collection}:${name}`;
800
+ }
801
+ function toOgLocale(locale) {
802
+ return locale.replace(/-/g, "_");
803
+ }
804
+ function hreflangTags(baseUrl, collection, name, locales, defaultLocale) {
805
+ const base = baseUrl.replace(/\/$/, "");
806
+ const tags = [];
807
+ for (const locale of locales) {
808
+ const path2 = localizeContentHref(collection, name, locale, defaultLocale);
809
+ tags.push(`<link rel="alternate" hreflang="${locale}" href="${base}${path2}" />`);
810
+ }
811
+ const defaultPath = localizeContentHref(collection, name, defaultLocale, defaultLocale);
812
+ tags.push(`<link rel="alternate" hreflang="x-default" href="${base}${defaultPath}" />`);
813
+ return tags.join(`
814
+ `);
815
+ }
816
+ var init_pathname = () => {};
817
+
818
+ // packages/core/src/i18n/resolve.ts
819
+ function resolveLocale(input) {
820
+ const {
821
+ pathname,
822
+ cookieHeader,
823
+ acceptLanguage,
824
+ availableLocales,
825
+ defaultLocale,
826
+ cookieName = DEFAULT_COOKIE,
827
+ collections
828
+ } = input;
829
+ const parsed = parseContentPath(pathname, availableLocales, collections);
830
+ const availableLower = new Set(availableLocales.map((l) => l.toLowerCase()));
831
+ const isAvailable = (locale) => !!locale && availableLower.has(locale.toLowerCase());
832
+ if (parsed?.locale && isAvailable(parsed.locale)) {
833
+ const locale = availableLocales.find((l) => l.toLowerCase() === parsed.locale?.toLowerCase()) || parsed.locale;
834
+ if (locale === defaultLocale && parsed.pathname !== parsed.routingPathname) {
835
+ return {
836
+ locale,
837
+ routingPathname: parsed.routingPathname,
838
+ fromPath: true,
839
+ redirectTo: parsed.routingPathname,
840
+ parsed: { ...parsed, locale }
841
+ };
842
+ }
843
+ return {
844
+ locale,
845
+ routingPathname: parsed.routingPathname,
846
+ fromPath: true,
847
+ parsed: { ...parsed, locale }
848
+ };
849
+ }
850
+ const routingPathname = parsed?.routingPathname ?? pathname.split("?")[0];
851
+ const cookieLocale = parseCookieValue(cookieHeader, cookieName);
852
+ if (isAvailable(cookieLocale)) {
853
+ const locale = availableLocales.find((l) => l.toLowerCase() === cookieLocale.toLowerCase()) || cookieLocale;
854
+ return {
855
+ locale,
856
+ routingPathname,
857
+ fromPath: false,
858
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
859
+ };
860
+ }
861
+ const negotiated = negotiateLocale(acceptLanguage, availableLocales, defaultLocale);
862
+ return {
863
+ locale: negotiated,
864
+ routingPathname,
865
+ fromPath: false,
866
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
867
+ };
868
+ }
869
+ function contentNegotiateRedirect(resolution, defaultLocale, enabled) {
870
+ if (!enabled)
871
+ return;
872
+ if (resolution.fromPath)
873
+ return;
874
+ if (resolution.locale === defaultLocale)
875
+ return;
876
+ const parsed = resolution.parsed;
877
+ if (!parsed)
878
+ return;
879
+ return localizeContentHref(parsed.collection, parsed.name, resolution.locale, defaultLocale);
880
+ }
881
+ var init_resolve = __esm(() => {
882
+ init_discover();
883
+ init_negotiate();
884
+ init_pathname();
885
+ });
886
+
887
+ // packages/core/src/i18n/index.ts
888
+ var init_i18n = __esm(() => {
889
+ init_catalog();
890
+ init_discover();
891
+ init_messages();
892
+ init_negotiate();
893
+ init_pathname();
894
+ init_resolve();
895
+ });
896
+
539
897
  // packages/telemetry/src/server/context.ts
540
898
  import { AsyncLocalStorage } from "node:async_hooks";
541
899
  var contextStorage;
@@ -1227,6 +1585,51 @@ function matchRoutes(routes, pathname) {
1227
1585
  return null;
1228
1586
  }
1229
1587
 
1588
+ // packages/core/src/ssr/lib/content-path.ts
1589
+ var exports_content_path = {};
1590
+ __export(exports_content_path, {
1591
+ resolveContentPathParts: () => resolveContentPathParts
1592
+ });
1593
+ function resolveContentPathParts(pathname) {
1594
+ const pathOnly = pathname.split("?")[0].split("#")[0];
1595
+ const parts = pathOnly.split("/").filter(Boolean);
1596
+ if (parts.length < 2)
1597
+ return null;
1598
+ const g = globalThis;
1599
+ const locales = g.__REROUTE_I18N__?.locales || [];
1600
+ const defaultLocale = g.__REROUTE_I18N__?.defaultLocale || "en-US";
1601
+ const parsed = parseContentPath(pathOnly, locales);
1602
+ if (parsed) {
1603
+ const locale = parsed.locale || defaultLocale;
1604
+ return {
1605
+ collection: parsed.collection,
1606
+ name: parsed.name,
1607
+ locale: parsed.locale,
1608
+ key: contentKey(parsed.collection, parsed.name, locale, defaultLocale),
1609
+ routingPathname: parsed.routingPathname
1610
+ };
1611
+ }
1612
+ if (parts.length >= 3 && looksLikeLocale(parts[1])) {
1613
+ const locale = parts[1];
1614
+ return {
1615
+ collection: parts[0],
1616
+ name: parts[2],
1617
+ locale,
1618
+ key: contentKey(parts[0], parts[2], locale, defaultLocale),
1619
+ routingPathname: `/${parts[0]}/${parts[2]}`
1620
+ };
1621
+ }
1622
+ return {
1623
+ collection: parts[0],
1624
+ name: parts[1],
1625
+ key: `${parts[0]}:${parts[1]}`,
1626
+ routingPathname: `/${parts[0]}/${parts[1]}`
1627
+ };
1628
+ }
1629
+ var init_content_path = __esm(() => {
1630
+ init_pathname();
1631
+ });
1632
+
1230
1633
  // packages/core/src/ssr/lib/compute/content.ts
1231
1634
  function checkStatusCache(statusCacheKey, maxAgeMs) {
1232
1635
  if (maxAgeMs <= 0)
@@ -1279,6 +1682,14 @@ async function handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusC
1279
1682
  }
1280
1683
  return result;
1281
1684
  }
1685
+ async function runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) {
1686
+ if (streaming) {
1687
+ handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
1688
+ } else {
1689
+ await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
1690
+ }
1691
+ return set.status !== 200 ? set.status : undefined;
1692
+ }
1282
1693
  async function computeContentData(pathname, options, searchParams, sharedSsrData) {
1283
1694
  const streaming = options.streaming === true;
1284
1695
  const ssrData = streaming && sharedSsrData ? sharedSsrData : {};
@@ -1288,18 +1699,20 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
1288
1699
  const maxAge = options.maxAge || 0;
1289
1700
  const maxAgeMs = maxAge * 1000;
1290
1701
  try {
1291
- const parts = pathname.split("/").filter(Boolean);
1292
- if (parts.length < 2) {
1702
+ const resolved = resolveContentPathParts(pathname);
1703
+ if (!resolved) {
1293
1704
  return { data: ssrData, pendingTasks };
1294
1705
  }
1706
+ const parts = [resolved.collection, resolved.name];
1707
+ if (resolved.locale)
1708
+ parts.splice(1, 0, resolved.locale);
1295
1709
  const g = globalThis;
1296
- const key = `${parts[0]}:${parts[1]}`;
1297
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1710
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
1298
1711
  const dataFn = exp?.ssr?.data;
1299
1712
  if (typeof dataFn !== "function") {
1300
1713
  return { data: ssrData, pendingTasks };
1301
1714
  }
1302
- const cacheKey = `content:${pathname}:${parts[1]}`;
1715
+ const cacheKey = `content:${pathname}:${resolved.name}:${resolved.locale || ""}`;
1303
1716
  const statusCacheKey = `status:${cacheKey}`;
1304
1717
  const cachedStatus = checkStatusCache(statusCacheKey, maxAgeMs);
1305
1718
  if (cachedStatus && cachedStatus !== 200) {
@@ -1312,22 +1725,13 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
1312
1725
  }
1313
1726
  const set = { status: 200 };
1314
1727
  const task = createDataTask(dataFn, pathname, parts, searchParams, set, options.headers);
1315
- if (streaming) {
1316
- handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
1317
- if (set.status !== 200) {
1318
- statusOverride = set.status;
1319
- }
1320
- } else {
1321
- await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
1322
- if (set.status !== 200) {
1323
- statusOverride = set.status;
1324
- }
1325
- }
1728
+ statusOverride = await runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) ?? statusOverride;
1326
1729
  } catch {}
1327
1730
  return { data: ssrData, error, statusOverride, pendingTasks };
1328
1731
  }
1329
1732
  var init_content2 = __esm(() => {
1330
1733
  init_cache();
1734
+ init_content_path();
1331
1735
  init_imports();
1332
1736
  });
1333
1737
 
@@ -1526,7 +1930,8 @@ async function computeRouteData(pathname, clientDir, _cwd, isWatchMode, options,
1526
1930
  }).catch(() => {}));
1527
1931
  };
1528
1932
  try {
1529
- const pathnameOnly = pathname.split("?")[0];
1933
+ const resolved = resolveContentPathParts(pathname);
1934
+ const pathnameOnly = (resolved?.routingPathname || pathname).split("?")[0];
1530
1935
  const match = typeof routesModule?.matchRoute === "function" ? routesModule.matchRoute(pathnameOnly) : null;
1531
1936
  const route = match?.route;
1532
1937
  const params = match?.params || {};
@@ -1689,6 +2094,7 @@ function handleFlattenedResult(entries, set, ctx, cache, setError, setStatus) {
1689
2094
  }
1690
2095
  var init_routes = __esm(() => {
1691
2096
  init_cache();
2097
+ init_content_path();
1692
2098
  init_imports();
1693
2099
  });
1694
2100
 
@@ -1781,33 +2187,31 @@ var init_compute = __esm(() => {
1781
2187
  });
1782
2188
 
1783
2189
  // packages/core/src/ssr/lib/modules.ts
1784
- import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
2190
+ import { readdir as readdir4, stat as stat3 } from "node:fs/promises";
1785
2191
  async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1786
2192
  try {
1787
- const pathOnly = pathname.split("?")[0].split("#")[0];
1788
- const parts = pathOnly.split("/").filter(Boolean);
1789
- if (parts.length < 2)
2193
+ const resolved = resolveContentPathParts(pathname);
2194
+ if (!resolved)
1790
2195
  return null;
1791
- const collection = parts[0];
1792
- const name = parts[1];
1793
- const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode);
2196
+ const { collection, name, locale } = resolved;
2197
+ const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode, locale);
1794
2198
  if (registryModule)
1795
2199
  return registryModule;
1796
2200
  const chunkModule = await tryChunkImport(collection, name, cwd, isWatchMode);
1797
2201
  if (chunkModule)
1798
2202
  return chunkModule;
1799
- const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode);
2203
+ const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode, locale);
1800
2204
  if (sourceModule)
1801
2205
  return sourceModule;
1802
2206
  } catch {}
1803
2207
  return null;
1804
2208
  }
1805
- async function tryRegistryImport(collection, name, cwd, isWatchMode) {
2209
+ async function tryRegistryImport(collection, name, cwd, isWatchMode, locale) {
1806
2210
  try {
1807
2211
  const registryPath = join2(cwd, ".reroute", "content.ts");
1808
2212
  const reg = await dynamicImport(registryPath, isWatchMode);
1809
2213
  const get = reg?.getContentEntry;
1810
- const entry = typeof get === "function" ? get(collection, name) : undefined;
2214
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
1811
2215
  const moduleUrl = entry?.module;
1812
2216
  if (!moduleUrl?.endsWith(".js"))
1813
2217
  return null;
@@ -1833,7 +2237,7 @@ async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1833
2237
  async function tryChunkImport(collection, name, cwd, isWatchMode) {
1834
2238
  try {
1835
2239
  const chunkDir = join2(cwd, ".reroute", "chunks", collection);
1836
- const files = await readdir3(chunkDir);
2240
+ const files = await readdir4(chunkDir);
1837
2241
  const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
1838
2242
  if (!matches.length)
1839
2243
  return null;
@@ -1853,23 +2257,23 @@ async function tryChunkImport(collection, name, cwd, isWatchMode) {
1853
2257
  } catch {}
1854
2258
  return null;
1855
2259
  }
1856
- async function trySourceImport(collection, name, clientDir, isWatchMode) {
2260
+ async function trySourceImport(collection, name, clientDir, isWatchMode, locale) {
1857
2261
  try {
1858
- const srcTsx = join2(clientDir, "routes", collection, "content", `${name}.tsx`);
1859
- const srcTs = join2(clientDir, "routes", collection, "content", `${name}.ts`);
1860
- let absSrc = null;
1861
- if (await Bun.file(srcTsx).exists()) {
1862
- absSrc = srcTsx;
1863
- } else if (await Bun.file(srcTs).exists()) {
1864
- absSrc = srcTs;
2262
+ const candidates = [];
2263
+ if (locale) {
2264
+ candidates.push(join2(clientDir, "routes", collection, "content", locale, `${name}.tsx`), join2(clientDir, "routes", collection, "content", locale, `${name}.ts`));
1865
2265
  }
1866
- if (absSrc) {
1867
- return await dynamicImport(absSrc, isWatchMode);
2266
+ candidates.push(join2(clientDir, "routes", collection, "content", `${name}.tsx`), join2(clientDir, "routes", collection, "content", `${name}.ts`));
2267
+ for (const absSrc of candidates) {
2268
+ if (await Bun.file(absSrc).exists()) {
2269
+ return await dynamicImport(absSrc, isWatchMode);
2270
+ }
1868
2271
  }
1869
2272
  } catch {}
1870
2273
  return null;
1871
2274
  }
1872
2275
  var init_modules = __esm(() => {
2276
+ init_content_path();
1873
2277
  init_imports();
1874
2278
  });
1875
2279
 
@@ -1900,14 +2304,11 @@ function touchModule(key) {
1900
2304
  }
1901
2305
  async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1902
2306
  try {
1903
- const pathOnly = pathname.split("?")[0].split("#")[0];
1904
- const parts = pathOnly.split("/").filter(Boolean);
1905
- if (parts.length < 2) {
2307
+ const resolved = resolveContentPathParts(pathname);
2308
+ if (!resolved) {
1906
2309
  return;
1907
2310
  }
1908
- const collection = parts[0];
1909
- const name = parts[1];
1910
- const key = `${collection}:${name}`;
2311
+ const { key } = resolved;
1911
2312
  const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode);
1912
2313
  if (!mod) {
1913
2314
  return;
@@ -1927,6 +2328,7 @@ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1927
2328
  }
1928
2329
  var MAX_SSR_MODULES = 200, moduleAccessOrder;
1929
2330
  var init_seed = __esm(() => {
2331
+ init_content_path();
1930
2332
  init_modules();
1931
2333
  moduleAccessOrder = [];
1932
2334
  });
@@ -1934,17 +2336,17 @@ var init_seed = __esm(() => {
1934
2336
  // packages/core/src/ssr/lib/data.ts
1935
2337
  function loadContentData(pathname, searchParams) {
1936
2338
  try {
1937
- const parts = pathname.split("/").filter(Boolean);
1938
- if (parts.length < 2)
2339
+ const resolved = resolveContentPathParts(pathname);
2340
+ if (!resolved)
1939
2341
  return null;
1940
- const key = `${parts[0]}:${parts[1]}`;
2342
+ const key = resolved.key;
1941
2343
  const g = globalThis;
1942
2344
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1943
2345
  const dataFn = exp?.ssr?.data;
1944
2346
  if (typeof dataFn === "function") {
1945
2347
  const rawResult = dataFn({
1946
2348
  pathname,
1947
- params: { name: parts[1] },
2349
+ params: { name: resolved.name },
1948
2350
  searchParams
1949
2351
  });
1950
2352
  const result = processDataResult(rawResult, pathname, pathname);
@@ -2107,6 +2509,7 @@ function createLazyMarker(pathname, key) {
2107
2509
  };
2108
2510
  }
2109
2511
  var init_data = __esm(() => {
2512
+ init_content_path();
2110
2513
  init_imports();
2111
2514
  init_seed();
2112
2515
  });
@@ -2307,14 +2710,14 @@ function replaceAppRoot(html, appHtml, appId) {
2307
2710
  var init_html = () => {};
2308
2711
 
2309
2712
  // packages/core/src/og/discovery.ts
2310
- import { readdir as readdir4 } from "node:fs/promises";
2713
+ import { readdir as readdir5 } from "node:fs/promises";
2311
2714
  import { pathToFileURL as pathToFileURL4 } from "node:url";
2312
2715
  function isCatchAllRouteDirectory(name) {
2313
2716
  return CATCH_ALL_ROUTE_DIRECTORY.test(name);
2314
2717
  }
2315
2718
  async function listDynamicRouteDirectories(baseDir) {
2316
2719
  try {
2317
- const entries = await readdir4(baseDir, { withFileTypes: true });
2720
+ const entries = await readdir5(baseDir, { withFileTypes: true });
2318
2721
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => DYNAMIC_ROUTE_DIRECTORY.test(name)).sort((a, b) => {
2319
2722
  const aIsCatchAll = isCatchAllRouteDirectory(a);
2320
2723
  const bIsCatchAll = isCatchAllRouteDirectory(b);
@@ -2334,7 +2737,7 @@ async function collectOGCandidatePaths(currentDir, parts, depth, pathsToTry, all
2334
2737
  }
2335
2738
  const literalDir = join2(currentDir, parts[depth]);
2336
2739
  try {
2337
- await readdir4(literalDir);
2740
+ await readdir5(literalDir);
2338
2741
  await collectOGCandidatePaths(literalDir, parts, depth + 1, pathsToTry, true);
2339
2742
  } catch {}
2340
2743
  const dynamicDirs = await listDynamicRouteDirectories(currentDir);
@@ -2489,7 +2892,9 @@ async function extractPageMetadata(pathname, clientDir, cwd, isWatchMode, curren
2489
2892
  let perPageHead = "";
2490
2893
  let pageLang;
2491
2894
  let statusOverride = currentStatusOverride;
2492
- await extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, (head) => {
2895
+ const resolved = resolveContentPathParts(pathname);
2896
+ const routingPathname = resolved?.routingPathname || pathname;
2897
+ await extractLayoutMetadata(routingPathname, clientDir, cwd, isWatchMode, (head) => {
2493
2898
  perPageHead += head;
2494
2899
  }, (lang) => {
2495
2900
  if (!pageLang)
@@ -2524,8 +2929,8 @@ ${ogMetaTags}`;
2524
2929
  perPageHead += head;
2525
2930
  }, (lang) => {
2526
2931
  pageLang = lang;
2527
- }, ogConfig, titleSuffix);
2528
- const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
2932
+ }, ogConfig, titleSuffix, baseUrl);
2933
+ const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2529
2934
  perPageHead += routeResult.head;
2530
2935
  if (routeResult.lang)
2531
2936
  pageLang = routeResult.lang;
@@ -2565,14 +2970,42 @@ async function extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, addH
2565
2970
  }
2566
2971
  } catch {}
2567
2972
  }
2568
- function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix) {
2973
+ function resolveContentLocales(collection, name, fallbackLocales) {
2974
+ const g = globalThis;
2975
+ const byCollection = g.__REROUTE_COLLECTIONS__;
2976
+ const items = byCollection?.[collection] || [];
2977
+ const localesForEntry = [
2978
+ ...new Set(items.filter((it) => it.name === name && it.locale).map((it) => String(it.locale)))
2979
+ ];
2980
+ return localesForEntry.length > 0 ? localesForEntry : fallbackLocales;
2981
+ }
2982
+ function addContentI18nHead(addHead, baseUrl, collection, name, locales, defaultLocale, activeLocale) {
2983
+ if (locales.length > 1) {
2984
+ addHead(`
2985
+ ${hreflangTags(baseUrl, collection, name, locales, defaultLocale)}`);
2986
+ }
2987
+ addHead(`
2988
+ <meta property="og:locale" content="${toOgLocale(activeLocale)}" />`);
2989
+ for (const loc of locales) {
2990
+ if (loc === activeLocale)
2991
+ continue;
2992
+ addHead(`
2993
+ <meta property="og:locale:alternate" content="${toOgLocale(loc)}" />`);
2994
+ }
2995
+ const canonicalPath = localizeContentHref(collection, name, activeLocale, defaultLocale);
2996
+ const base = baseUrl.replace(/\/$/, "");
2997
+ addHead(`
2998
+ <link rel="canonical" href="${base}${canonicalPath}" />`);
2999
+ addHead(`
3000
+ <meta property="og:url" content="${base}${canonicalPath}" />`);
3001
+ }
3002
+ function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix, baseUrl) {
2569
3003
  try {
2570
- const parts = pathname.split("/").filter(Boolean);
2571
- if (parts.length < 2)
3004
+ const resolved = resolveContentPathParts(pathname);
3005
+ if (!resolved)
2572
3006
  return;
2573
- const key = `${parts[0]}:${parts[1]}`;
2574
3007
  const g = globalThis;
2575
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
3008
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
2576
3009
  const meta = exp?.meta;
2577
3010
  const ssr = exp?.ssr;
2578
3011
  addHead(buildHeadFromMeta(meta, ogConfig, titleSuffix));
@@ -2583,15 +3016,20 @@ ${ssrHead}`);
2583
3016
  const lang = extractSSRLang(ssr);
2584
3017
  if (lang)
2585
3018
  setLang(lang);
3019
+ const i18n = g.__REROUTE_I18N__;
3020
+ if (i18n?.enabled && baseUrl && i18n.locales && i18n.locales.length > 1) {
3021
+ const locales = resolveContentLocales(resolved.collection, resolved.name, i18n.locales);
3022
+ addContentI18nHead(addHead, baseUrl, resolved.collection, resolved.name, locales, i18n.defaultLocale || "en-US", i18n.locale || i18n.defaultLocale || "en-US");
3023
+ }
2586
3024
  } catch {}
2587
3025
  }
2588
3026
  function getContentMetaFromExports(pathname) {
2589
3027
  try {
2590
- const parts = pathname.split("/").filter(Boolean);
2591
- if (parts.length < 2)
3028
+ const resolved = resolveContentPathParts(pathname);
3029
+ if (!resolved)
2592
3030
  return;
2593
- const slug = parts[1];
2594
- const key = `${parts[0]}:${slug}`;
3031
+ const slug = resolved.name;
3032
+ const key = resolved.key;
2595
3033
  const g = globalThis;
2596
3034
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2597
3035
  const meta = exp?.meta;
@@ -2605,9 +3043,10 @@ function getContentMetaFromExports(pathname) {
2605
3043
  return;
2606
3044
  }
2607
3045
  }
2608
- async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix) {
3046
+ async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2609
3047
  const pathnameOnly = pathname.split("?")[0];
2610
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
3048
+ const matchPath = (routingPathname || pathname).split("?")[0];
3049
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2611
3050
  const r = match?.route;
2612
3051
  if (!r) {
2613
3052
  return { head: "", statusOverride: statusOverride || 404 };
@@ -2647,7 +3086,7 @@ ${ssrHead}`;
2647
3086
  }
2648
3087
  return { head: "", statusOverride };
2649
3088
  }
2650
- async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix) {
3089
+ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2651
3090
  let head = "";
2652
3091
  let lang;
2653
3092
  let statusOverride = currentStatusOverride;
@@ -2657,14 +3096,14 @@ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, curre
2657
3096
  if (!m) {
2658
3097
  return { head, lang, statusOverride: statusOverride || 404 };
2659
3098
  }
2660
- const pathnameOnly = pathname.split("?")[0];
2661
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
3099
+ const matchPath = (routingPathname || pathname).split("?")[0];
3100
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2662
3101
  const r = match?.route;
2663
3102
  if (!r) {
2664
3103
  statusOverride = statusOverride || 404;
2665
3104
  }
2666
3105
  if (r && typeof r.path === "string") {
2667
- const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
3106
+ const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2668
3107
  head = result.head;
2669
3108
  lang = result.lang;
2670
3109
  statusOverride = result.statusOverride;
@@ -2839,34 +3278,34 @@ function applyTitleSuffixToHead(headContent, titleSuffix) {
2839
3278
  }
2840
3279
  var init_metadata2 = __esm(() => {
2841
3280
  init_metadata();
3281
+ init_pathname();
2842
3282
  init_meta();
2843
3283
  init_compute();
3284
+ init_content_path();
2844
3285
  init_imports();
2845
3286
  });
2846
3287
 
2847
3288
  // packages/core/src/ssr/lib/preload.ts
2848
- import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
3289
+ import { readdir as readdir6, stat as stat4 } from "node:fs/promises";
2849
3290
  async function preloadContentModule(pathname, clientDir, cwd, isWatchMode) {
2850
3291
  let extraHead = "";
2851
3292
  let hydrationScript = "";
2852
3293
  let statusOverride;
2853
3294
  try {
2854
- const pathOnly = pathname.split("?")[0].split("#")[0];
2855
- const parts = pathOnly.split("/").filter(Boolean);
2856
- if (parts.length < 2) {
3295
+ const { resolveContentPathParts: resolveContentPathParts2 } = await Promise.resolve().then(() => (init_content_path(), exports_content_path));
3296
+ const resolved = resolveContentPathParts2(pathname);
3297
+ if (!resolved) {
2857
3298
  return { extraHead, hydrationScript };
2858
3299
  }
2859
- const collection = parts[0];
2860
- const name = parts[1];
3300
+ const { collection, name, key, locale } = resolved;
2861
3301
  if (!await isContentCollection(clientDir, collection)) {
2862
3302
  return { extraHead, hydrationScript };
2863
3303
  }
2864
- const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode);
3304
+ const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale);
2865
3305
  if (!modulePath) {
2866
3306
  statusOverride = 404;
2867
3307
  return { extraHead, hydrationScript, statusOverride };
2868
3308
  }
2869
- const key = `${collection}:${name}`;
2870
3309
  extraHead += `
2871
3310
  <link rel="modulepreload" href="${modulePath}" />`;
2872
3311
  hydrationScript += `<script>
@@ -2908,12 +3347,12 @@ async function findLatestCandidate(chunkDir, candidates) {
2908
3347
  }
2909
3348
  return latest;
2910
3349
  }
2911
- async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode) {
3350
+ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale) {
2912
3351
  try {
2913
3352
  const registryPath = join2(cwd, ".reroute", "content.ts");
2914
3353
  const reg = await dynamicImport(registryPath, isWatchMode);
2915
3354
  const get = reg?.getContentEntry;
2916
- const entry = typeof get === "function" ? get(collection, name) : undefined;
3355
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
2917
3356
  const moduleUrl = entry?.module;
2918
3357
  if (moduleUrl?.endsWith(".js")) {
2919
3358
  return moduleUrl;
@@ -2921,7 +3360,7 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2921
3360
  } catch {}
2922
3361
  try {
2923
3362
  const chunkDir = join2(cwd, ".reroute", "chunks", collection);
2924
- const files = await readdir5(chunkDir);
3363
+ const files = await readdir6(chunkDir);
2925
3364
  const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2926
3365
  if (candidates.length) {
2927
3366
  const latest = await findLatestCandidate(chunkDir, candidates);
@@ -2929,13 +3368,18 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2929
3368
  }
2930
3369
  } catch {}
2931
3370
  try {
2932
- const tsx = join2(clientDir, "routes", collection, "content", `${name}.tsx`);
2933
- const ts = join2(clientDir, "routes", collection, "content", `${name}.ts`);
2934
- if (await Bun.file(tsx).exists()) {
2935
- return `/${join2("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
2936
- }
2937
- if (await Bun.file(ts).exists()) {
2938
- return `/${join2("routes", collection, "content", `${name}.ts`).replace(/\\+/g, "/")}`;
3371
+ const contentRoot = join2(clientDir, "routes", collection, "content");
3372
+ const candidates = locale ? [
3373
+ join2(contentRoot, locale, `${name}.tsx`),
3374
+ join2(contentRoot, locale, `${name}.ts`),
3375
+ join2(contentRoot, `${name}.tsx`),
3376
+ join2(contentRoot, `${name}.ts`)
3377
+ ] : [join2(contentRoot, `${name}.tsx`), join2(contentRoot, `${name}.ts`)];
3378
+ for (const abs of candidates) {
3379
+ if (await Bun.file(abs).exists()) {
3380
+ const rel = abs.startsWith(clientDir) ? abs.slice(clientDir.length).replace(/^[/\\]/, "") : join2("routes", collection, "content", `${name}.tsx`);
3381
+ return `/${rel.replace(/\\+/g, "/")}`;
3382
+ }
2939
3383
  }
2940
3384
  } catch {}
2941
3385
  return null;
@@ -3129,11 +3573,18 @@ function duplicateQueryParamData(ssrData) {
3129
3573
  return duplicated;
3130
3574
  }
3131
3575
  function getContentKeyFromPathname(pathname) {
3132
- const pathOnly = pathname.split("?")[0].split("#")[0];
3133
- const parts = pathOnly.split("/").filter(Boolean);
3134
- if (parts.length < 2)
3135
- return null;
3136
- return `${parts[0]}:${parts[1]}`;
3576
+ return resolveContentPathParts(pathname)?.key ?? null;
3577
+ }
3578
+ function createI18nScript(i18n) {
3579
+ if (!i18n?.enabled)
3580
+ return "";
3581
+ try {
3582
+ const json2 = escapeJsonForScript(JSON.stringify(i18n));
3583
+ return `<script>(function(){try{var w=(typeof window!=='undefined'?window:globalThis);w.__REROUTE_I18N__=${json2};}catch(e){}})();</script>`;
3584
+ } catch (error) {
3585
+ console.error("[reroute] Failed to serialize i18n hydration:", error);
3586
+ return "";
3587
+ }
3137
3588
  }
3138
3589
  function createContentCaptureScript() {
3139
3590
  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>`;
@@ -3209,13 +3660,14 @@ function createSSRPathnameScript(pathname) {
3209
3660
  return "";
3210
3661
  }
3211
3662
  }
3212
- function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix) {
3663
+ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n) {
3213
3664
  let script = "";
3214
3665
  script += createContentCaptureScript();
3215
3666
  script += createSSRPathnameScript(pathname);
3216
3667
  if (pathname) {
3217
3668
  script += createSSRExportsScript(pathname);
3218
3669
  }
3670
+ script += createI18nScript(i18n);
3219
3671
  script += createTelemetryConfigScript(browserTelemetryConfig);
3220
3672
  script += createTraceContextScript(traceContext);
3221
3673
  script += createTitleSuffixScript(titleSuffix);
@@ -3235,7 +3687,9 @@ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, t
3235
3687
  }
3236
3688
  return script;
3237
3689
  }
3238
- var init_data2 = () => {};
3690
+ var init_data2 = __esm(() => {
3691
+ init_content_path();
3692
+ });
3239
3693
 
3240
3694
  // packages/core/src/ssr/lib/scripts/feeds.ts
3241
3695
  function createFeedsScript() {
@@ -3463,10 +3917,10 @@ function generateCollectionScripts(byCollectionForSSR) {
3463
3917
  } catch {}
3464
3918
  return "";
3465
3919
  }
3466
- function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix) {
3920
+ function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix, i18n) {
3467
3921
  let scripts = preloadScript;
3468
3922
  scripts += collectionScripts;
3469
- scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix);
3923
+ scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n);
3470
3924
  scripts += createFeedsScript();
3471
3925
  scripts += createLlmsScript();
3472
3926
  if (bundleUrl) {
@@ -3629,7 +4083,7 @@ async function renderSSRDocument(options) {
3629
4083
  const browserTelemetryConfig = serializeBrowserTelemetryConfig2(browserTelemetryConfigRaw);
3630
4084
  const traceContext = getTraceContextForInjection(options.parentSpan);
3631
4085
  const hydrationScript = await withSpan("ssr.generate.scripts", async (span) => {
3632
- const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix);
4086
+ const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix, options.i18n);
3633
4087
  span.setAttribute("reroute.scripts.size", script.length);
3634
4088
  return script;
3635
4089
  });
@@ -3647,12 +4101,13 @@ async function renderSSRDocument(options) {
3647
4101
  dedent(setup2.metadataResult.perPageHead)
3648
4102
  ].filter(Boolean).join(`
3649
4103
  `));
4104
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
3650
4105
  const html2 = withSpanSync("ssr.apply.template", (span) => {
3651
4106
  const result = applyIndexTemplate(baseTemplate, appHtml, {
3652
4107
  head: [setup2.inlineStyleTag, combinedHead].filter(Boolean).join(`
3653
4108
  `),
3654
4109
  hydrationScript,
3655
- lang: setup2.metadataResult.pageLang || lang,
4110
+ lang: setup2.metadataResult.pageLang || i18nLocale || lang,
3656
4111
  appId
3657
4112
  });
3658
4113
  span.setAttributes({
@@ -4830,6 +5285,55 @@ async function discoverRoutes(cwd, isWatchMode) {
4830
5285
  }
4831
5286
  return { staticRoutes, dynamicRoutes };
4832
5287
  }
5288
+ function groupNamedCollectionItems(items) {
5289
+ const byName = new Map;
5290
+ for (const item of items) {
5291
+ const name = String(item.name || "");
5292
+ if (!name)
5293
+ continue;
5294
+ const list = byName.get(name) || [];
5295
+ list.push(item);
5296
+ byName.set(name, list);
5297
+ }
5298
+ return byName;
5299
+ }
5300
+ function resolveItemHref(collection, item, defaultLocale, localize) {
5301
+ const name = String(item.name || "");
5302
+ const locale = item.locale || defaultLocale;
5303
+ if (name)
5304
+ return localize(collection, name, locale, defaultLocale);
5305
+ return String(item.href || "");
5306
+ }
5307
+ function buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localize) {
5308
+ const locales = [
5309
+ ...new Set(variants.map((v) => v.locale || defaultLocale).filter(Boolean))
5310
+ ];
5311
+ const alternates = locales.length > 1 ? [
5312
+ ...locales.map((locale) => ({
5313
+ hreflang: locale,
5314
+ href: `${base}${localize(collection, name, locale, defaultLocale)}`
5315
+ })),
5316
+ {
5317
+ hreflang: "x-default",
5318
+ href: `${base}${localize(collection, name, defaultLocale, defaultLocale)}`
5319
+ }
5320
+ ] : undefined;
5321
+ const entries = [];
5322
+ for (const item of variants) {
5323
+ const href = resolveItemHref(collection, item, defaultLocale, localize);
5324
+ if (!href)
5325
+ continue;
5326
+ const meta = item.meta || {};
5327
+ entries.push({
5328
+ loc: `${base}${href}`,
5329
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
5330
+ changefreq: "weekly",
5331
+ priority: 0.7,
5332
+ alternates
5333
+ });
5334
+ }
5335
+ return entries;
5336
+ }
4833
5337
  async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl) {
4834
5338
  const entries = [];
4835
5339
  try {
@@ -4841,18 +5345,26 @@ async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl)
4841
5345
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4842
5346
  const mod = await import(url);
4843
5347
  const items = mod.items || [];
5348
+ const defaultLocale = mod.defaultLocale || "en-US";
5349
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
5350
+ const base = baseUrl.replace(/\/$/, "");
5351
+ const byName = groupNamedCollectionItems(items);
5352
+ for (const [name, variants] of byName) {
5353
+ entries.push(...buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localizeContentHref2));
5354
+ }
4844
5355
  for (const item of items) {
5356
+ if (item.name)
5357
+ continue;
4845
5358
  const href = String(item.href || "");
5359
+ if (!href)
5360
+ continue;
4846
5361
  const meta = item.meta || {};
4847
- if (href) {
4848
- const entry = {
4849
- loc: `${baseUrl}${href}`,
4850
- lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4851
- changefreq: "weekly",
4852
- priority: 0.7
4853
- };
4854
- entries.push(entry);
4855
- }
5362
+ entries.push({
5363
+ loc: `${base}${href}`,
5364
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
5365
+ changefreq: "weekly",
5366
+ priority: 0.7
5367
+ });
4856
5368
  }
4857
5369
  } catch (error) {
4858
5370
  console.error(`[reroute] Failed to discover collection ${collection}:`, error);
@@ -5147,11 +5659,17 @@ async function discoverCollectionFeedItems(cwd, collection, isWatchMode, baseUrl
5147
5659
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
5148
5660
  const mod = await import(url);
5149
5661
  const contentItems = mod.items || [];
5662
+ const defaultLocale = mod.defaultLocale || "en-US";
5663
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
5150
5664
  for (const item of contentItems) {
5151
- const href = String(item.href || "");
5665
+ const name = String(item.name || "");
5666
+ const locale = item.locale || defaultLocale;
5667
+ const href = name ? localizeContentHref2(collection, name, locale, defaultLocale) : String(item.href || "");
5152
5668
  const meta = item.meta || {};
5153
5669
  if (href) {
5154
- items.push(createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig));
5670
+ const rssItem = createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig);
5671
+ rssItem.locale = locale;
5672
+ items.push(rssItem);
5155
5673
  }
5156
5674
  }
5157
5675
  } catch (error) {
@@ -5417,7 +5935,7 @@ function generateRSSXML(feed, options = {}) {
5417
5935
  <link>${escapeXml(feed.link)}</link>
5418
5936
  <description>${escapeXml(feed.description)}</description>
5419
5937
  <lastBuildDate>${lastBuildDate}</lastBuildDate>
5420
- <language>en</language>
5938
+ <language>${escapeXml(feed.language || "en")}</language>
5421
5939
  <atom:link href="${escapeXml(feed.link)}" rel="self" type="application/rss+xml" />
5422
5940
  ${itemsXML}
5423
5941
  </channel>
@@ -5579,6 +6097,7 @@ async function generateSitemap(discovery4, baseUrl, options = {}) {
5579
6097
  return createSingleSitemap(allEntries);
5580
6098
  }
5581
6099
  function generateSitemapXML(entries) {
6100
+ const hasAlternates = entries.some((e) => e.alternates && e.alternates.length > 0);
5582
6101
  const urlsetEntries = entries.map((entry) => {
5583
6102
  let xml = ` <url>
5584
6103
  <loc>${escapeXml2(entry.loc)}</loc>`;
@@ -5594,13 +6113,20 @@ function generateSitemapXML(entries) {
5594
6113
  xml += `
5595
6114
  <priority>${entry.priority.toFixed(1)}</priority>`;
5596
6115
  }
6116
+ if (entry.alternates?.length) {
6117
+ for (const alt of entry.alternates) {
6118
+ xml += `
6119
+ <xhtml:link rel="alternate" hreflang="${escapeXml2(alt.hreflang)}" href="${escapeXml2(alt.href)}" />`;
6120
+ }
6121
+ }
5597
6122
  xml += `
5598
6123
  </url>`;
5599
6124
  return xml;
5600
6125
  }).join(`
5601
6126
  `);
6127
+ const xhtmlNs = hasAlternates ? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"' : "";
5602
6128
  return `<?xml version="1.0" encoding="UTF-8"?>
5603
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
6129
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${xhtmlNs}>
5604
6130
  ${urlsetEntries}
5605
6131
  </urlset>`;
5606
6132
  }
@@ -5698,7 +6224,8 @@ async function renderSSRDocumentStream(options) {
5698
6224
  ...parts
5699
6225
  };
5700
6226
  });
5701
- const pageLang = setup2.metadataResult.pageLang || lang;
6227
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
6228
+ const pageLang = setup2.metadataResult.pageLang || i18nLocale || lang;
5702
6229
  let headWithLang = htmlHead.replace(/<html([^>]*)>/i, (_m, attrs) => {
5703
6230
  const hasLang = /(^|\s)lang\s*=/.test(attrs);
5704
6231
  const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${pageLang}"`) : `${attrs} lang="${pageLang}"`;
@@ -5741,7 +6268,8 @@ async function renderSSRDocumentStream(options) {
5741
6268
  parentSpan: options.parentSpan,
5742
6269
  timeout: options.timeout,
5743
6270
  ssrExport: setup2.metadataResult.ssrExport,
5744
- titleSuffix: options.titleSuffix
6271
+ titleSuffix: options.titleSuffix,
6272
+ i18n: options.i18n
5745
6273
  });
5746
6274
  if (debug)
5747
6275
  console.log(`[renderStream] ${pathname} - returning stream: ${(performance.now() - t0).toFixed(2)}ms`);
@@ -5854,7 +6382,7 @@ function buildStreamingScripts(ctx) {
5854
6382
  ssrDataSnapshot[key] = value;
5855
6383
  }
5856
6384
  }
5857
- const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix);
6385
+ const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix, ctx.i18n);
5858
6386
  const feedsScript = createFeedsScript();
5859
6387
  const llmsScript = createLlmsScript();
5860
6388
  let allScripts = dataScript + feedsScript + llmsScript + collectionScripts + ctx.preloadHydrationScript;
@@ -5986,6 +6514,7 @@ var init_src = __esm(() => {
5986
6514
  init_bundler();
5987
6515
  init_config();
5988
6516
  init_content();
6517
+ init_i18n();
5989
6518
  init_llms();
5990
6519
  init_og();
5991
6520
  init_robots();
@@ -5998,6 +6527,7 @@ var init_src = __esm(() => {
5998
6527
  var exports_core = {};
5999
6528
  __export(exports_core, {
6000
6529
  toPromise: () => toPromise,
6530
+ toOgLocale: () => toOgLocale,
6001
6531
  toBytes: () => toBytes,
6002
6532
  stripStart: () => stripStart,
6003
6533
  stripHtmlTags: () => stripHtmlTags,
@@ -6009,6 +6539,7 @@ __export(exports_core, {
6009
6539
  setGlobalSSRData: () => setGlobalSSRData,
6010
6540
  setGlobalCollections: () => setGlobalCollections,
6011
6541
  seedSSRModuleForPath: () => seedSSRModuleForPath,
6542
+ resolveLocale: () => resolveLocale,
6012
6543
  resetSSRAccessTracking: () => resetSSRAccessTracking,
6013
6544
  renderSSRDocumentStream: () => renderSSRDocumentStream,
6014
6545
  renderSSRDocument: () => renderSSRDocument,
@@ -6017,9 +6548,17 @@ __export(exports_core, {
6017
6548
  processCollections: () => processCollections,
6018
6549
  preloadContentModule: () => preloadContentModule,
6019
6550
  performSSRSetup: () => performSSRSetup,
6551
+ parseCookieValue: () => parseCookieValue,
6552
+ parseContentPath: () => parseContentPath,
6553
+ normalizePathname: () => normalizePathname,
6554
+ negotiateLocale: () => negotiateLocale,
6020
6555
  mergeLayoutData: () => mergeLayoutData,
6021
6556
  matchRoutes: () => matchRoutes,
6022
6557
  matchRoutePattern: () => matchRoutePattern,
6558
+ lookupMessage: () => lookupMessage,
6559
+ looksLikeLocale: () => looksLikeLocale,
6560
+ localizeHref: () => localizeHref,
6561
+ localizeContentHref: () => localizeContentHref,
6023
6562
  loadRoutesModule: () => loadRoutesModule,
6024
6563
  loadOGImageComponent: () => loadOGImageComponent,
6025
6564
  loadIndexHtml: () => loadIndexHtml,
@@ -6032,11 +6571,13 @@ __export(exports_core, {
6032
6571
  inlineTailwindCSS: () => inlineTailwindCSS,
6033
6572
  importContentModuleForPath: () => importContentModuleForPath,
6034
6573
  htmlToMarkdown: () => htmlToMarkdown,
6574
+ hreflangTags: () => hreflangTags,
6035
6575
  gzipIfAccepted: () => gzipIfAccepted,
6036
6576
  getSitemapExcludedRoutes: () => getSitemapExcludedRoutes,
6037
6577
  getRedirectSourcePaths: () => getRedirectSourcePaths,
6038
6578
  getPatternDepth: () => getPatternDepth,
6039
6579
  getMimeType: () => getMimeType,
6580
+ getMessage: () => getMessage,
6040
6581
  getMatchingLayouts: () => getMatchingLayouts,
6041
6582
  getContentMeta: () => getContentMeta,
6042
6583
  generateSitemapXML: () => generateSitemapXML,
@@ -6053,6 +6594,7 @@ __export(exports_core, {
6053
6594
  generateContentHash: () => generateContentHash,
6054
6595
  generateCollectionScripts: () => generateCollectionScripts,
6055
6596
  generateAtomXML: () => generateAtomXML,
6597
+ formatMessage: () => formatMessage,
6056
6598
  formatAsText: () => formatAsText,
6057
6599
  formatAsMarkdown: () => formatAsMarkdown,
6058
6600
  findOGImageForPath: () => findOGImageForPath,
@@ -6072,10 +6614,12 @@ __export(exports_core, {
6072
6614
  discoverRoutes: () => discoverRoutes,
6073
6615
  discoverRobotsData: () => discoverRobotsData,
6074
6616
  discoverRSSData: () => discoverRSSData,
6617
+ discoverI18n: () => discoverI18n,
6075
6618
  discoverCollections: () => discoverCollections,
6076
6619
  discoverCollectionContent: () => discoverCollectionContent,
6077
6620
  defineConfig: () => defineConfig,
6078
6621
  deduplicateMetaTags: () => deduplicateMetaTags,
6622
+ createTranslator: () => createTranslator,
6079
6623
  createLlmsScript: () => createLlmsScript,
6080
6624
  createLayoutDataKey: () => createLayoutDataKey,
6081
6625
  createLayoutCacheKey: () => createLayoutCacheKey,
@@ -6083,6 +6627,8 @@ __export(exports_core, {
6083
6627
  createDataScript: () => createDataScript,
6084
6628
  createCollectionScript: () => createCollectionScript,
6085
6629
  createBundlePreload: () => createBundlePreload,
6630
+ contentNegotiateRedirect: () => contentNegotiateRedirect,
6631
+ contentKey: () => contentKey,
6086
6632
  computeSSRDataForPath: () => computeSSRDataForPath,
6087
6633
  computeSSRData: () => computeSSRData,
6088
6634
  computeRouteData: () => computeRouteData,
@@ -6091,6 +6637,7 @@ __export(exports_core, {
6091
6637
  compressStreamIfAccepted: () => compressStreamIfAccepted,
6092
6638
  compareRouteSpecificity: () => compareRouteSpecificity,
6093
6639
  cleanupPathScopedSSRData: () => cleanupPathScopedSSRData,
6640
+ buildLocaleCookie: () => buildLocaleCookie,
6094
6641
  buildHeadFromMeta: () => buildHeadFromMeta,
6095
6642
  blockSearchCrawlers: () => blockSearchCrawlers,
6096
6643
  blockAITrainingCrawlers: () => blockAITrainingCrawlers,
@@ -6106,6 +6653,9 @@ __export(exports_core, {
6106
6653
  SEARCH_CRAWLERS: () => SEARCH_CRAWLERS,
6107
6654
  LRUCache: () => LRUCache,
6108
6655
  DefaultOGTemplate: () => DefaultOGTemplate,
6656
+ DEFAULT_LOCALES_DIR: () => DEFAULT_LOCALES_DIR,
6657
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
6658
+ DEFAULT_COOKIE: () => DEFAULT_COOKIE,
6109
6659
  AI_CRAWLERS: () => AI_CRAWLERS
6110
6660
  });
6111
6661
  var init_core = __esm(() => {
@@ -6130,7 +6680,7 @@ function getNamingConfig(options) {
6130
6680
 
6131
6681
  // packages/cli/src/commands/lib/env.ts
6132
6682
  import { existsSync as existsSync2, readFileSync } from "node:fs";
6133
- import { join as join3 } from "node:path";
6683
+ import { join as join4 } from "node:path";
6134
6684
  function parseEnvFile(content2) {
6135
6685
  const env = {};
6136
6686
  const lines = content2.split(`
@@ -6156,7 +6706,7 @@ function loadEnvFiles(cwd, mode = "development") {
6156
6706
  const env = {};
6157
6707
  const loadedFiles = [];
6158
6708
  for (const file of envFiles) {
6159
- const filePath = join3(cwd, file);
6709
+ const filePath = join4(cwd, file);
6160
6710
  if (existsSync2(filePath)) {
6161
6711
  try {
6162
6712
  const content2 = readFileSync(filePath, "utf-8");
@@ -6200,11 +6750,11 @@ var init_env = () => {};
6200
6750
 
6201
6751
  // packages/cli/src/commands/lib/markdown/availability.ts
6202
6752
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
6203
- import { dirname, join as join4 } from "node:path";
6753
+ import { dirname, join as join5 } from "node:path";
6204
6754
  function isPackageAvailable(cwd, packageName) {
6205
6755
  let currentDir = cwd;
6206
6756
  for (let i = 0;i < 5; i++) {
6207
- const packageJsonPath = join4(currentDir, "package.json");
6757
+ const packageJsonPath = join5(currentDir, "package.json");
6208
6758
  if (existsSync3(packageJsonPath)) {
6209
6759
  try {
6210
6760
  const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
@@ -6276,7 +6826,7 @@ var init_availability = () => {};
6276
6826
 
6277
6827
  // packages/cli/src/commands/lib/markdown/processor.ts
6278
6828
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
6279
- import { basename as basename2, dirname as dirname2, extname as extname2, join as join5, relative, resolve } from "node:path";
6829
+ import { basename as basename2, dirname as dirname2, extname as extname2, join as join6, relative, resolve } from "node:path";
6280
6830
  import matter from "gray-matter";
6281
6831
  function convertPropertiesToJsx(properties) {
6282
6832
  const newProperties = {};
@@ -6335,7 +6885,7 @@ function processMarkdownFile(filePath) {
6335
6885
  function isMdxAvailable2(cwd) {
6336
6886
  let currentDir = cwd;
6337
6887
  for (let i = 0;i < 5; i++) {
6338
- const packageJsonPath = join5(currentDir, "package.json");
6888
+ const packageJsonPath = join6(currentDir, "package.json");
6339
6889
  if (existsSync4(packageJsonPath)) {
6340
6890
  try {
6341
6891
  const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -6359,10 +6909,10 @@ function isMdxAvailable2(cwd) {
6359
6909
  }
6360
6910
  function tryLoadModule(cwd, moduleName) {
6361
6911
  const pathsToTry = [
6362
- join5(cwd, `node_modules/${moduleName}`),
6363
- join5(cwd, `../node_modules/${moduleName}`),
6364
- join5(cwd, `../../node_modules/${moduleName}`),
6365
- join5(cwd, `../../../node_modules/${moduleName}`)
6912
+ join6(cwd, `node_modules/${moduleName}`),
6913
+ join6(cwd, `../node_modules/${moduleName}`),
6914
+ join6(cwd, `../../node_modules/${moduleName}`),
6915
+ join6(cwd, `../../../node_modules/${moduleName}`)
6366
6916
  ];
6367
6917
  for (const modulePath of pathsToTry) {
6368
6918
  try {
@@ -6692,7 +7242,7 @@ function isProductionMode(args = []) {
6692
7242
  }
6693
7243
 
6694
7244
  // packages/cli/src/commands/lib/streaming/analyzer.ts
6695
- import { dirname as dirname3, join as join6 } from "node:path";
7245
+ import { dirname as dirname3, join as join7 } from "node:path";
6696
7246
  function hasSSRDataExport(source) {
6697
7247
  if (SSR_DATA_INLINE_EXPORT_REGEX.test(source)) {
6698
7248
  return true;
@@ -6720,10 +7270,10 @@ async function analyzeRouteForStreaming(options) {
6720
7270
  }
6721
7271
  async function findSkeletonForTarget(routesDir, relativePath, type) {
6722
7272
  const dirName = normalizeRelativePath(dirname3(relativePath));
6723
- const baseDir = dirName === "." ? routesDir : join6(routesDir, dirName).replace(/\\/g, "/");
7273
+ const baseDir = dirName === "." ? routesDir : join7(routesDir, dirName).replace(/\\/g, "/");
6724
7274
  const candidates = type === "layout" ? ["[layout+skeleton].tsx", "[layout+skeleton].ts"] : ["[skeleton].tsx", "[skeleton].ts"];
6725
7275
  for (const candidate of candidates) {
6726
- const candidatePath = join6(baseDir, candidate);
7276
+ const candidatePath = join7(baseDir, candidate);
6727
7277
  try {
6728
7278
  const file = Bun.file(candidatePath);
6729
7279
  if (await file.exists()) {
@@ -6744,12 +7294,12 @@ var init_analyzer = __esm(() => {
6744
7294
  });
6745
7295
 
6746
7296
  // packages/cli/src/commands/lib/streaming/suspense.ts
6747
- import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
7297
+ import { dirname as dirname4, join as join8, relative as relative2 } from "node:path";
6748
7298
  async function generateStreamingWrapper(options) {
6749
7299
  const { cwd, relativeFilePath, analysis, config: config2 } = options;
6750
- const streamingModulePath = join7(cwd, ".reroute", "streaming", relativeFilePath).replace(/\.(tsx|ts)$/, ".tsx");
7300
+ const streamingModulePath = join8(cwd, ".reroute", "streaming", relativeFilePath).replace(/\.(tsx|ts)$/, ".tsx");
6751
7301
  const wrapperDir = dirname4(streamingModulePath);
6752
- const originalModuleWithoutExt = removeExtension(join7(cwd, "src", "client", "routes", relativeFilePath));
7302
+ const originalModuleWithoutExt = removeExtension(join8(cwd, "src", "client", "routes", relativeFilePath));
6753
7303
  const originalImportPath = toImportPath(wrapperDir, originalModuleWithoutExt);
6754
7304
  const skeletonImportBlock = await resolveSkeletonBlock({
6755
7305
  analysis,
@@ -6791,7 +7341,7 @@ async function resolveSkeletonBlock(args) {
6791
7341
  }
6792
7342
  function resolveConfiguredSkeletonPath(value, cwd) {
6793
7343
  if (typeof value === "string" && value.trim().length > 0) {
6794
- return value.startsWith("/") ? value : join7(cwd, value);
7344
+ return value.startsWith("/") ? value : join8(cwd, value);
6795
7345
  }
6796
7346
  if (value && !warnedSkeletonType) {
6797
7347
  console.warn("[reroute/streaming] default skeletons must be provided as string module paths in reroute.config.ts");
@@ -6828,13 +7378,13 @@ var init_suspense = () => {};
6828
7378
  // packages/cli/src/commands/lib/tailwind.ts
6829
7379
  import { spawn } from "node:child_process";
6830
7380
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
6831
- import { join as join8 } from "node:path";
7381
+ import { join as join9 } from "node:path";
6832
7382
  function isTailwindAvailable(cwd) {
6833
7383
  const pathsToTry = [
6834
- join8(cwd, "package.json"),
6835
- join8(cwd, "../package.json"),
6836
- join8(cwd, "../../package.json"),
6837
- join8(cwd, "../../../package.json")
7384
+ join9(cwd, "package.json"),
7385
+ join9(cwd, "../package.json"),
7386
+ join9(cwd, "../../package.json"),
7387
+ join9(cwd, "../../../package.json")
6838
7388
  ];
6839
7389
  for (const packageJsonPath of pathsToTry) {
6840
7390
  if (!existsSync5(packageJsonPath)) {
@@ -6855,8 +7405,8 @@ function isTailwindAvailable(cwd) {
6855
7405
  return false;
6856
7406
  }
6857
7407
  function getTailwindPaths(cwd) {
6858
- const input = join8(cwd, "src/client/theme.css");
6859
- const output = join8(cwd, ".reroute/theme.css");
7408
+ const input = join9(cwd, "src/client/theme.css");
7409
+ const output = join9(cwd, ".reroute/theme.css");
6860
7410
  return { input, output };
6861
7411
  }
6862
7412
  function hasTailwindInput(cwd) {
@@ -6873,10 +7423,10 @@ function hasTailwindInput(cwd) {
6873
7423
  }
6874
7424
  function resolveTailwindBin(cwd) {
6875
7425
  const pathsToTry = [
6876
- join8(cwd, "node_modules/.bin/tailwindcss"),
6877
- join8(cwd, "../node_modules/.bin/tailwindcss"),
6878
- join8(cwd, "../../node_modules/.bin/tailwindcss"),
6879
- join8(cwd, "../../../node_modules/.bin/tailwindcss")
7426
+ join9(cwd, "node_modules/.bin/tailwindcss"),
7427
+ join9(cwd, "../node_modules/.bin/tailwindcss"),
7428
+ join9(cwd, "../../node_modules/.bin/tailwindcss"),
7429
+ join9(cwd, "../../../node_modules/.bin/tailwindcss")
6880
7430
  ];
6881
7431
  for (const binPath of pathsToTry) {
6882
7432
  if (existsSync5(binPath)) {
@@ -6947,12 +7497,12 @@ __export(exports_gen, {
6947
7497
  default: () => gen
6948
7498
  });
6949
7499
  import { watch } from "node:fs";
6950
- import { mkdir as mkdir2, readdir as readdir6, rm } from "node:fs/promises";
6951
- import { dirname as dirname5, join as join9 } from "node:path";
7500
+ import { mkdir as mkdir2, readdir as readdir7, rm } from "node:fs/promises";
7501
+ import { dirname as dirname5, join as join10 } from "node:path";
6952
7502
  import { pathToFileURL as pathToFileURL8 } from "node:url";
6953
7503
  async function bundleThemeCssIntoArtifacts(cwd) {
6954
- const themeCssPath = join9(cwd, ".reroute", "theme.css");
6955
- const artifactsPath = join9(cwd, OUTPUT_ARTIFACTS);
7504
+ const themeCssPath = join10(cwd, ".reroute", "theme.css");
7505
+ const artifactsPath = join10(cwd, OUTPUT_ARTIFACTS);
6956
7506
  try {
6957
7507
  const cssContent = await Bun.file(themeCssPath).text();
6958
7508
  if (!cssContent)
@@ -6978,11 +7528,11 @@ async function bundleThemeCssIntoArtifacts(cwd) {
6978
7528
  async function ensureOutputDir(cwd, options = {}) {
6979
7529
  const { clean = true } = options;
6980
7530
  try {
6981
- const outputPath = join9(cwd, OUTPUT_DIR);
7531
+ const outputPath = join10(cwd, OUTPUT_DIR);
6982
7532
  if (clean) {
6983
7533
  let needsCleanup = false;
6984
7534
  try {
6985
- const entries = await readdir6(outputPath);
7535
+ const entries = await readdir7(outputPath);
6986
7536
  needsCleanup = entries.length > 0;
6987
7537
  } catch {}
6988
7538
  if (needsCleanup) {
@@ -6991,9 +7541,9 @@ async function ensureOutputDir(cwd, options = {}) {
6991
7541
  }
6992
7542
  }
6993
7543
  await mkdir2(outputPath, { recursive: true });
6994
- await mkdir2(join9(cwd, OUTPUT_COLLECTIONS_DIR), { recursive: true });
6995
- await mkdir2(join9(cwd, ".reroute/bundles"), { recursive: true });
6996
- await mkdir2(join9(cwd, ".reroute/streaming"), { recursive: true });
7544
+ await mkdir2(join10(cwd, OUTPUT_COLLECTIONS_DIR), { recursive: true });
7545
+ await mkdir2(join10(cwd, ".reroute/bundles"), { recursive: true });
7546
+ await mkdir2(join10(cwd, ".reroute/streaming"), { recursive: true });
6997
7547
  } catch {}
6998
7548
  }
6999
7549
  async function writeStubFiles(cwd) {
@@ -7051,11 +7601,11 @@ import './artifacts';
7051
7601
  import './routes'; // Import routes (bundles all route components)
7052
7602
  import '../src/index';
7053
7603
  `;
7054
- await Bun.write(join9(cwd, OUTPUT_ROUTES), STUB_ROUTES);
7055
- await Bun.write(join9(cwd, OUTPUT_CONTENT_TS), STUB_CONTENT);
7056
- await Bun.write(join9(cwd, OUTPUT_ARTIFACTS), STUB_ARTIFACTS);
7057
- await Bun.write(join9(cwd, OUTPUT_INDEX), STUB_INDEX);
7058
- await Bun.write(join9(cwd, ".reroute/entry.ts"), STUB_ENTRY);
7604
+ await Bun.write(join10(cwd, OUTPUT_ROUTES), STUB_ROUTES);
7605
+ await Bun.write(join10(cwd, OUTPUT_CONTENT_TS), STUB_CONTENT);
7606
+ await Bun.write(join10(cwd, OUTPUT_ARTIFACTS), STUB_ARTIFACTS);
7607
+ await Bun.write(join10(cwd, OUTPUT_INDEX), STUB_INDEX);
7608
+ await Bun.write(join10(cwd, ".reroute/entry.ts"), STUB_ENTRY);
7059
7609
  console.log("[reroute/gen] Initialized stub files");
7060
7610
  }
7061
7611
  function shouldSkipFile(relativePath, entryName) {
@@ -7075,10 +7625,10 @@ function shouldSkipFile(relativePath, entryName) {
7075
7625
  }
7076
7626
  async function scanDirectory(dir, base = "") {
7077
7627
  const files = [];
7078
- const entries = await readdir6(dir, { withFileTypes: true });
7628
+ const entries = await readdir7(dir, { withFileTypes: true });
7079
7629
  for (const entry of entries) {
7080
- const fullPath = join9(dir, entry.name);
7081
- const relativePath = join9(base, entry.name);
7630
+ const fullPath = join10(dir, entry.name);
7631
+ const relativePath = join10(base, entry.name);
7082
7632
  if (entry.isDirectory()) {
7083
7633
  if (entry.name === "content")
7084
7634
  continue;
@@ -7094,10 +7644,10 @@ async function scanDirectory(dir, base = "") {
7094
7644
  }
7095
7645
  async function scan404Files(dir, base = "") {
7096
7646
  const files = [];
7097
- const entries = await readdir6(dir, { withFileTypes: true });
7647
+ const entries = await readdir7(dir, { withFileTypes: true });
7098
7648
  for (const entry of entries) {
7099
- const fullPath = join9(dir, entry.name);
7100
- const relativePath = join9(base, entry.name);
7649
+ const fullPath = join10(dir, entry.name);
7650
+ const relativePath = join10(base, entry.name);
7101
7651
  if (entry.isDirectory()) {
7102
7652
  const nested = await scan404Files(fullPath, relativePath);
7103
7653
  files.push(...nested);
@@ -7110,10 +7660,10 @@ async function scan404Files(dir, base = "") {
7110
7660
  }
7111
7661
  async function scanOgFiles(dir, base = "") {
7112
7662
  const files = [];
7113
- const entries = await readdir6(dir, { withFileTypes: true });
7663
+ const entries = await readdir7(dir, { withFileTypes: true });
7114
7664
  for (const entry of entries) {
7115
- const fullPath = join9(dir, entry.name);
7116
- const relativePath = join9(base, entry.name);
7665
+ const fullPath = join10(dir, entry.name);
7666
+ const relativePath = join10(base, entry.name);
7117
7667
  if (entry.isDirectory()) {
7118
7668
  if (entry.name === "content")
7119
7669
  continue;
@@ -7367,7 +7917,7 @@ function matchPattern(route: Route, pathname: string): Record<string, string> |
7367
7917
  `;
7368
7918
  }
7369
7919
  async function buildStreamingWrappersForTree(cwd, routesPath, tree, config2) {
7370
- const streamingDir = join9(cwd, ".reroute", "streaming");
7920
+ const streamingDir = join10(cwd, ".reroute", "streaming");
7371
7921
  const analyses = new Map;
7372
7922
  const candidates = [
7373
7923
  ...tree.routes.map((route) => ({
@@ -7386,7 +7936,7 @@ async function buildStreamingWrappersForTree(cwd, routesPath, tree, config2) {
7386
7936
  for (const candidate of candidates) {
7387
7937
  if (!/\.(tsx|ts)$/.test(candidate.filePath))
7388
7938
  continue;
7389
- const absolutePath = join9(routesPath, candidate.filePath);
7939
+ const absolutePath = join10(routesPath, candidate.filePath);
7390
7940
  const analysis = await analyzeRouteForStreaming({
7391
7941
  absolutePath,
7392
7942
  relativePath: candidate.filePath,
@@ -7395,7 +7945,7 @@ async function buildStreamingWrappersForTree(cwd, routesPath, tree, config2) {
7395
7945
  });
7396
7946
  if (!analysis.needsStreaming)
7397
7947
  continue;
7398
- const wrapperPath = join9(streamingDir, candidate.filePath).replace(/\.(tsx|ts)$/, ".tsx");
7948
+ const wrapperPath = join10(streamingDir, candidate.filePath).replace(/\.(tsx|ts)$/, ".tsx");
7399
7949
  await mkdir2(dirname5(wrapperPath), { recursive: true });
7400
7950
  const code = await generateStreamingWrapper({
7401
7951
  cwd,
@@ -7412,10 +7962,42 @@ async function buildStreamingWrappersForTree(cwd, routesPath, tree, config2) {
7412
7962
  }
7413
7963
  return analyses;
7414
7964
  }
7965
+ function isLocaleDirName(name) {
7966
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(name);
7967
+ }
7968
+ function isContentFileName(name) {
7969
+ return /\.(tsx|ts|md|mdx)$/.test(name) && !name.startsWith("_");
7970
+ }
7971
+ async function listLocaleContentFiles(collectionDir, locale) {
7972
+ try {
7973
+ const nested = await readdir7(join10(collectionDir, locale), {
7974
+ withFileTypes: true
7975
+ });
7976
+ const files = [];
7977
+ for (const n of nested) {
7978
+ if (n.isFile() && isContentFileName(n.name)) {
7979
+ files.push({ relativePath: join10(locale, n.name), locale });
7980
+ }
7981
+ }
7982
+ return files;
7983
+ } catch {
7984
+ return [];
7985
+ }
7986
+ }
7415
7987
  async function listContentFiles2(collectionDir) {
7416
7988
  try {
7417
- const entries = await readdir6(collectionDir, { withFileTypes: true });
7418
- return entries.filter((e) => e.isFile() && /\.(tsx|ts|md|mdx)$/.test(e.name) && !e.name.startsWith("_")).map((e) => e.name);
7989
+ const entries = await readdir7(collectionDir, { withFileTypes: true });
7990
+ const files = [];
7991
+ for (const entry of entries) {
7992
+ if (entry.isFile() && isContentFileName(entry.name)) {
7993
+ files.push({ relativePath: entry.name });
7994
+ continue;
7995
+ }
7996
+ if (entry.isDirectory() && isLocaleDirName(entry.name)) {
7997
+ files.push(...await listLocaleContentFiles(collectionDir, entry.name));
7998
+ }
7999
+ }
8000
+ return files;
7419
8001
  } catch {
7420
8002
  return [];
7421
8003
  }
@@ -7472,11 +8054,14 @@ ${spaces}}`;
7472
8054
  }
7473
8055
  return serializeValue(config2);
7474
8056
  }
7475
- async function buildSingleContentChunk(cwd, collection, file, contentDir, args) {
7476
- const name = file.replace(/\.(tsx|ts|md|mdx)$/, "");
7477
- const absSrc = join9(contentDir, file);
8057
+ async function buildSingleContentChunk(cwd, collection, fileRef, contentDir, args, defaultLocale) {
8058
+ const file = fileRef.relativePath;
8059
+ const locale = fileRef.locale || defaultLocale;
8060
+ const baseName = file.split("/").pop() || file;
8061
+ const name = baseName.replace(/\.(tsx|ts|md|mdx)$/, "");
8062
+ const absSrc = join10(contentDir, file);
7478
8063
  const isMarkdown = /\.(md|mdx)$/.test(file);
7479
- const buildSrc = isMarkdown ? join9(cwd, ".reroute", "markdown", collection, "content", `${name}.tsx`) : absSrc;
8064
+ const buildSrc = isMarkdown ? join10(cwd, ".reroute", "markdown", collection, "content", file.replace(/\.(md|mdx)$/, ".tsx")) : absSrc;
7480
8065
  if (isMarkdown) {
7481
8066
  try {
7482
8067
  const exists = await Bun.file(buildSrc).exists();
@@ -7522,10 +8107,11 @@ async function buildSingleContentChunk(cwd, collection, file, contentDir, args)
7522
8107
  return null;
7523
8108
  }
7524
8109
  const hash2 = await sha8(code);
7525
- const chunkRelDir = join9(".reroute", "bundles");
7526
- const chunkAbsDir = join9(cwd, chunkRelDir);
7527
- const outFile = `${collection}-${name}.${hash2}.js`;
7528
- const absOut = join9(chunkAbsDir, outFile);
8110
+ const chunkRelDir = join10(".reroute", "bundles");
8111
+ const chunkAbsDir = join10(cwd, chunkRelDir);
8112
+ const localeSuffix = locale !== defaultLocale ? `${locale}-` : "";
8113
+ const outFile = `${collection}-${localeSuffix}${name}.${hash2}.js`;
8114
+ const absOut = join10(chunkAbsDir, outFile);
7529
8115
  if (sourcemapContent) {
7530
8116
  code = code.replace(/\/\/# sourceMappingURL=.*\.js\.map/, `//# sourceMappingURL=${outFile}.map`);
7531
8117
  }
@@ -7555,26 +8141,27 @@ async function buildSingleContentChunk(cwd, collection, file, contentDir, args)
7555
8141
  slug: name,
7556
8142
  href: `/${collection}/${name}`,
7557
8143
  moduleUrl: `/bundles/${outFile}`,
7558
- meta
8144
+ meta,
8145
+ locale
7559
8146
  };
7560
8147
  }
7561
- async function buildContentChunks(cwd, args = []) {
7562
- const routesRoot = join9(cwd, ROUTES_DIR);
8148
+ async function buildContentChunks(cwd, args = [], defaultLocale = "en-US") {
8149
+ const routesRoot = join10(cwd, ROUTES_DIR);
7563
8150
  console.log(`[reroute/content] scan ${routesRoot}`);
7564
- const collections2 = await readdir6(routesRoot, { withFileTypes: true });
8151
+ const collections2 = await readdir7(routesRoot, { withFileTypes: true });
7565
8152
  const results = [];
7566
8153
  for (const c of collections2) {
7567
8154
  if (!c.isDirectory())
7568
8155
  continue;
7569
8156
  const collection = c.name;
7570
- const contentDir = join9(routesRoot, collection, "content");
8157
+ const contentDir = join10(routesRoot, collection, "content");
7571
8158
  console.log(`[reroute/content] collection ${collection} dir ${contentDir}`);
7572
8159
  const files = await listContentFiles2(contentDir);
7573
8160
  console.log(`[reroute/content] files ${files.length}`);
7574
8161
  if (!files.length)
7575
8162
  continue;
7576
8163
  for (const file of files) {
7577
- const result = await buildSingleContentChunk(cwd, collection, file, contentDir, args);
8164
+ const result = await buildSingleContentChunk(cwd, collection, file, contentDir, args, defaultLocale);
7578
8165
  if (result) {
7579
8166
  results.push(result);
7580
8167
  }
@@ -7587,7 +8174,7 @@ async function buildContentChunks(cwd, args = []) {
7587
8174
  lines.push("");
7588
8175
  lines.push("export const contents = [");
7589
8176
  for (const e of results) {
7590
- lines.push(" { collection: '" + e.collection + "', slug: '" + e.slug + "', name: '" + e.name + "', href: '" + e.href + "', module: '" + e.moduleUrl + "', meta: " + JSON.stringify(e.meta || {}) + " },");
8177
+ lines.push(" { collection: '" + e.collection + "', slug: '" + e.slug + "', name: '" + e.name + "', href: '" + e.href + "', module: '" + e.moduleUrl + "', locale: '" + e.locale + "', meta: " + JSON.stringify(e.meta || {}) + " },");
7591
8178
  }
7592
8179
  lines.push("] as const;");
7593
8180
  lines.push("");
@@ -7595,20 +8182,31 @@ async function buildContentChunks(cwd, args = []) {
7595
8182
  lines.push("for (const c of contents) { (byCollection[c.collection] ||= []).push(c as any); }");
7596
8183
  lines.push("");
7597
8184
  lines.push("export const byCollectionAndName: Record<string, Record<string, any>> = {};");
7598
- lines.push("for (const c of contents) { ((byCollectionAndName[c.collection] ||= {})[c.name] = c as any); }");
8185
+ lines.push("for (const c of contents) {");
8186
+ lines.push(` const cur = (byCollectionAndName[c.collection] ||= {})[c.name];`);
8187
+ lines.push(` if (!cur || c.locale === ${JSON.stringify(defaultLocale)}) (byCollectionAndName[c.collection] ||= {})[c.name] = c as any;`);
8188
+ lines.push("}");
8189
+ lines.push("");
8190
+ lines.push("export const byCollectionNameLocale: Record<string, Record<string, Record<string, any>>> = {};");
8191
+ lines.push("for (const c of contents) { (((byCollectionNameLocale[c.collection] ||= {})[c.name] ||= {})[c.locale] = c as any); }");
7599
8192
  lines.push("");
7600
8193
  lines.push("export type Collections = keyof typeof byCollection;");
7601
8194
  lines.push("export type Names<C extends Collections> = keyof (typeof byCollectionAndName)[C];");
7602
8195
  lines.push("export type ContentEntry = (typeof contents)[number];");
7603
8196
  lines.push("");
7604
- lines.push("export function getContentEntry<C extends string, N extends string>(collection: C, name: N) {");
8197
+ lines.push("export function getContentEntry<C extends string, N extends string>(collection: C, name: N, locale?: string) {");
8198
+ lines.push(" if (locale) {");
8199
+ lines.push(" const byLocale = (byCollectionNameLocale as any)[collection]?.[name];");
8200
+ lines.push(" if (byLocale?.[locale]) return byLocale[locale];");
8201
+ lines.push(` if (byLocale?.[${JSON.stringify(defaultLocale)}]) return byLocale[${JSON.stringify(defaultLocale)}];`);
8202
+ lines.push(" }");
7605
8203
  lines.push(" const m = (byCollectionAndName as any)[collection];");
7606
8204
  lines.push(" return m ? (m as Record<string, any>)[name] : undefined;");
7607
8205
  lines.push("}");
7608
8206
  lines.push("");
7609
8207
  const writes = [];
7610
8208
  writes.push({
7611
- path: join9(cwd, OUTPUT_CONTENT_TS),
8209
+ path: join10(cwd, OUTPUT_CONTENT_TS),
7612
8210
  content: lines.join(`
7613
8211
  `)
7614
8212
  });
@@ -7620,20 +8218,29 @@ async function buildContentChunks(cwd, args = []) {
7620
8218
  js.push("");
7621
8219
  js.push("export const items = [");
7622
8220
  for (const e of items) {
7623
- js.push(` { collection: '${e.collection}', slug: '${e.slug}', name: '${e.name}', href: '${e.href}', module: '${e.moduleUrl}', meta: ${JSON.stringify(e.meta || {})} },`);
8221
+ js.push(` { collection: '${e.collection}', slug: '${e.slug}', name: '${e.name}', href: '${e.href}', module: '${e.moduleUrl}', locale: '${e.locale}', meta: ${JSON.stringify(e.meta || {})} },`);
7624
8222
  }
7625
8223
  js.push("];");
7626
8224
  js.push("");
7627
8225
  js.push("export const byName = {};");
7628
- js.push("for (const it of items) { byName[it.name] = it; }");
8226
+ js.push("export const byNameLocale = {};");
8227
+ js.push("for (const it of items) {");
8228
+ js.push(" ((byNameLocale[it.name] ||= {})[it.locale] = it);");
8229
+ js.push(` if (!byName[it.name] || it.locale === ${JSON.stringify(defaultLocale)}) byName[it.name] = it;`);
8230
+ js.push("}");
7629
8231
  js.push("");
7630
- js.push("export function get(collectionName, name) {");
8232
+ js.push(`export const defaultLocale = ${JSON.stringify(defaultLocale)};`);
8233
+ js.push("");
8234
+ js.push("export function get(collectionName, name, locale) {");
7631
8235
  js.push(` if (collectionName !== '${collection}') return undefined;`);
8236
+ js.push(" if (locale && byNameLocale[name]) {");
8237
+ js.push(" return byNameLocale[name][locale] || byNameLocale[name][defaultLocale] || byName[name];");
8238
+ js.push(" }");
7632
8239
  js.push(" return byName[name];");
7633
8240
  js.push("}");
7634
8241
  js.push("");
7635
8242
  writes.push({
7636
- path: join9(cwd, OUTPUT_COLLECTIONS_DIR, `${collection}.js`),
8243
+ path: join10(cwd, OUTPUT_COLLECTIONS_DIR, `${collection}.js`),
7637
8244
  content: js.join(`
7638
8245
  `)
7639
8246
  });
@@ -7657,8 +8264,10 @@ async function buildSearchIndex(cwd, config2, args = []) {
7657
8264
  minHeadingLevel = 1,
7658
8265
  maxHeadingLevel = 6
7659
8266
  } = searchConfig;
7660
- const routesRoot = join9(cwd, ROUTES_DIR);
7661
- const collections2 = await readdir6(routesRoot, { withFileTypes: true });
8267
+ const routesRoot = join10(cwd, ROUTES_DIR);
8268
+ const collections2 = await readdir7(routesRoot, { withFileTypes: true });
8269
+ const i18n2 = await discoverI18n(cwd, config2.i18n);
8270
+ const defaultLocale = i18n2.defaultLocale;
7662
8271
  const searchIndex = [];
7663
8272
  for (const c of collections2) {
7664
8273
  if (!c.isDirectory())
@@ -7667,14 +8276,17 @@ async function buildSearchIndex(cwd, config2, args = []) {
7667
8276
  if (targetCollections.length > 0 && !targetCollections.includes(collection)) {
7668
8277
  continue;
7669
8278
  }
7670
- const contentDir = join9(routesRoot, collection, "content");
8279
+ const contentDir = join10(routesRoot, collection, "content");
7671
8280
  try {
7672
- const files = await readdir6(contentDir);
7673
- for (const file of files) {
8281
+ const files = await listContentFiles2(contentDir);
8282
+ for (const fileRef of files) {
8283
+ const file = fileRef.relativePath;
7674
8284
  if (!/\.(md|mdx)$/.test(file))
7675
8285
  continue;
7676
- const name = file.replace(/\.(md|mdx)$/, "");
7677
- const filePath = join9(contentDir, file);
8286
+ const baseName = file.split("/").pop() || file;
8287
+ const name = baseName.replace(/\.(md|mdx)$/, "");
8288
+ const locale = fileRef.locale || defaultLocale;
8289
+ const filePath = join10(contentDir, file);
7678
8290
  try {
7679
8291
  const processed = processMarkdownFile(filePath);
7680
8292
  const { frontmatter, content: content2 } = processed;
@@ -7688,16 +8300,18 @@ async function buildSearchIndex(cwd, config2, args = []) {
7688
8300
  maxLevel: maxHeadingLevel
7689
8301
  });
7690
8302
  const excerpt = String(frontmatter?.excerpt || frontmatter?.description || stripMarkdown(content2).slice(0, 160));
8303
+ const href = localizeContentHref(collection, name, locale, defaultLocale);
7691
8304
  const entry = {
7692
- id: `${collection}:${name}`,
8305
+ id: locale !== defaultLocale ? `${collection}:${name}:${locale}` : `${collection}:${name}`,
7693
8306
  collection,
7694
8307
  name,
7695
8308
  slug: name,
7696
- href: `/${collection}/${name}`,
8309
+ href,
7697
8310
  title: String(frontmatter?.title || name),
7698
8311
  excerpt,
7699
8312
  headings,
7700
- meta: {}
8313
+ meta: {},
8314
+ locale
7701
8315
  };
7702
8316
  for (const field of metaFields) {
7703
8317
  if (frontmatter[field] !== undefined) {
@@ -7720,13 +8334,13 @@ async function buildSearchIndex(cwd, config2, args = []) {
7720
8334
  generated: new Date().toISOString(),
7721
8335
  entries: searchIndex
7722
8336
  };
7723
- await Bun.write(join9(cwd, OUTPUT_SEARCH_INDEX), JSON.stringify(indexData, null, 2));
7724
- console.log(`[reroute/search] Generated search index with ${searchIndex.length} entries: ${join9(cwd, OUTPUT_SEARCH_INDEX)}`);
8337
+ await Bun.write(join10(cwd, OUTPUT_SEARCH_INDEX), JSON.stringify(indexData, null, 2));
8338
+ console.log(`[reroute/search] Generated search index with ${searchIndex.length} entries: ${join10(cwd, OUTPUT_SEARCH_INDEX)}`);
7725
8339
  }
7726
8340
  async function preprocessMarkdownFiles(cwd, config2) {
7727
8341
  console.log("[reroute/markdown] Starting markdown preprocessing...");
7728
- const routesPath = join9(cwd, ROUTES_DIR);
7729
- const markdownDir = join9(cwd, ".reroute", "markdown");
8342
+ const routesPath = join10(cwd, ROUTES_DIR);
8343
+ const markdownDir = join10(cwd, ".reroute", "markdown");
7730
8344
  try {
7731
8345
  await rm(markdownDir, { force: true, recursive: true });
7732
8346
  } catch {}
@@ -7734,10 +8348,10 @@ async function preprocessMarkdownFiles(cwd, config2) {
7734
8348
  async function scanMarkdown(dir, base = "") {
7735
8349
  const files = [];
7736
8350
  try {
7737
- const entries = await readdir6(dir, { withFileTypes: true });
8351
+ const entries = await readdir7(dir, { withFileTypes: true });
7738
8352
  for (const entry of entries) {
7739
- const fullPath = join9(dir, entry.name);
7740
- const relativePath = join9(base, entry.name);
8353
+ const fullPath = join10(dir, entry.name);
8354
+ const relativePath = join10(base, entry.name);
7741
8355
  if (entry.isDirectory()) {
7742
8356
  const nested = await scanMarkdown(fullPath, relativePath);
7743
8357
  files.push(...nested);
@@ -7759,8 +8373,8 @@ async function preprocessMarkdownFiles(cwd, config2) {
7759
8373
  }
7760
8374
  console.log(`[reroute/markdown] Found ${markdownFiles.length} markdown file(s)`);
7761
8375
  for (const file of markdownFiles) {
7762
- const sourcePath = join9(routesPath, file);
7763
- const targetPath = join9(markdownDir, file.replace(/\.mdx?$/, ".tsx"));
8376
+ const sourcePath = join10(routesPath, file);
8377
+ const targetPath = join10(markdownDir, file.replace(/\.mdx?$/, ".tsx"));
7764
8378
  try {
7765
8379
  const processed = processMarkdownFile(sourcePath);
7766
8380
  const isMdx = file.endsWith(".mdx");
@@ -7786,8 +8400,8 @@ async function preprocessMarkdownFiles(cwd, config2) {
7786
8400
  }
7787
8401
  async function buildEntrypointBundle(cwd, args = []) {
7788
8402
  const buildStartTime = performance.now();
7789
- const clientDir = join9(cwd, "src", "client");
7790
- const entrypoint = join9(clientDir, "index.tsx");
8403
+ const clientDir = join10(cwd, "src", "client");
8404
+ const entrypoint = join10(clientDir, "index.tsx");
7791
8405
  const exists = await Bun.file(entrypoint).exists();
7792
8406
  if (!exists) {
7793
8407
  console.log("[reroute/gen] No index.tsx found, skipping bundle build");
@@ -7795,7 +8409,7 @@ async function buildEntrypointBundle(cwd, args = []) {
7795
8409
  }
7796
8410
  console.log("[reroute/gen] Building entrypoint bundle...");
7797
8411
  const isProd = args.includes("--prod");
7798
- const bundlesDir = join9(cwd, ".reroute", "bundles");
8412
+ const bundlesDir = join10(cwd, ".reroute", "bundles");
7799
8413
  await mkdir2(bundlesDir, { recursive: true });
7800
8414
  const mode = isProd ? "production" : "development";
7801
8415
  const publicEnv = getPublicEnvVars(cwd, mode);
@@ -7894,10 +8508,10 @@ async function buildEntrypointBundle(cwd, args = []) {
7894
8508
  }
7895
8509
  const newFiles = new Set(outputs.map((o) => o.fileName));
7896
8510
  try {
7897
- const existingFiles = await readdir6(bundlesDir);
8511
+ const existingFiles = await readdir7(bundlesDir);
7898
8512
  for (const file of existingFiles) {
7899
8513
  if (/^(index|chunk|asset)\.[a-z0-9]+\.(js|css|js\.map|css\.map)(\.gz|\.br)?$/i.test(file) && !newFiles.has(file.replace(/\.(gz|br)$/, ""))) {
7900
- await rm(join9(bundlesDir, file), { force: true });
8514
+ await rm(join10(bundlesDir, file), { force: true });
7901
8515
  }
7902
8516
  }
7903
8517
  } catch (_error) {}
@@ -7913,7 +8527,7 @@ async function buildEntrypointBundle(cwd, args = []) {
7913
8527
  }
7914
8528
  }
7915
8529
  await Promise.all(outputs.map(async (output) => {
7916
- const destPath = join9(bundlesDir, output.fileName);
8530
+ const destPath = join10(bundlesDir, output.fileName);
7917
8531
  await Bun.write(destPath, output.content);
7918
8532
  if (shouldPreCompress) {
7919
8533
  const shouldCompress = /\.(js|css)$/i.test(output.fileName) && !output.fileName.endsWith(".map");
@@ -7982,7 +8596,7 @@ async function generate(cwd, args = [], options = {}) {
7982
8596
  await writeStubFiles(cwd);
7983
8597
  }
7984
8598
  await perfTrack("Markdown preprocessing", () => preprocessMarkdownFiles(cwd, config2));
7985
- const routesPath = join9(cwd, ROUTES_DIR);
8599
+ const routesPath = join10(cwd, ROUTES_DIR);
7986
8600
  try {
7987
8601
  await perfTrack("Routes generation", async () => {
7988
8602
  const files = await scanDirectory(routesPath);
@@ -7991,19 +8605,36 @@ async function generate(cwd, args = [], options = {}) {
7991
8605
  const tree = generateRouteTree(all.map((f) => f.replace(/\\/g, "/")));
7992
8606
  const streamingAnalyses = streamingEnabled ? await buildStreamingWrappersForTree(cwd, routesPath, tree, config2) : new Map;
7993
8607
  const ts = generateTypeScript(tree, streamingAnalyses);
7994
- await Bun.write(join9(cwd, OUTPUT_ROUTES), ts);
8608
+ await Bun.write(join10(cwd, OUTPUT_ROUTES), ts);
7995
8609
  });
7996
8610
  if (!showPerformance) {
7997
- console.log(`[reroute/gen] Generated routes: ${join9(cwd, OUTPUT_ROUTES)}`);
8611
+ console.log(`[reroute/gen] Generated routes: ${join10(cwd, OUTPUT_ROUTES)}`);
7998
8612
  }
7999
8613
  } catch (error) {
8000
8614
  console.error("[reroute/gen] Failed to generate routes:", error);
8001
8615
  throw error;
8002
8616
  }
8617
+ const i18n2 = await discoverI18n(cwd, config2.i18n);
8618
+ const defaultLocale = i18n2.defaultLocale;
8619
+ try {
8620
+ await Bun.write(join10(cwd, ".reroute", "i18n.json"), JSON.stringify({
8621
+ enabled: i18n2.enabled,
8622
+ defaultLocale: i18n2.defaultLocale,
8623
+ locales: i18n2.locales,
8624
+ cookie: i18n2.cookie,
8625
+ redirectContentOnNegotiate: i18n2.redirectContentOnNegotiate,
8626
+ catalogs: i18n2.catalogs
8627
+ }, null, 2));
8628
+ if (i18n2.enabled && !showPerformance) {
8629
+ console.log(`[reroute/gen] i18n enabled: ${i18n2.locales.join(", ")} (default ${i18n2.defaultLocale})`);
8630
+ }
8631
+ } catch (error) {
8632
+ console.warn("[reroute/gen] Failed to write i18n.json:", error);
8633
+ }
8003
8634
  try {
8004
- await perfTrack("Content building", () => buildContentChunks(cwd, args));
8635
+ await perfTrack("Content building", () => buildContentChunks(cwd, args, defaultLocale));
8005
8636
  if (!showPerformance) {
8006
- console.log(`[reroute/gen] Generated content: ${join9(cwd, OUTPUT_CONTENT_TS)} + collections/*.js`);
8637
+ console.log(`[reroute/gen] Generated content: ${join10(cwd, OUTPUT_CONTENT_TS)} + collections/*.js`);
8007
8638
  }
8008
8639
  } catch (error) {
8009
8640
  console.error("[reroute/gen] Failed to generate content:", error);
@@ -8011,22 +8642,25 @@ async function generate(cwd, args = [], options = {}) {
8011
8642
  }
8012
8643
  try {
8013
8644
  await perfTrack("Manifest generation", async () => {
8014
- const routesPath2 = join9(cwd, OUTPUT_ROUTES);
8645
+ const routesPath2 = join10(cwd, OUTPUT_ROUTES);
8015
8646
  const routesModule = await import(`file://${routesPath2}?t=${Date.now()}`);
8016
8647
  const routes = routesModule.routes || [];
8017
8648
  const staticRoutes = routes.filter((r) => !r.params || r.params.length === 0).map((r) => r.pattern);
8018
8649
  const contentRoutes = [];
8019
- const collectionsDir = join9(cwd, ".reroute", "collections");
8650
+ const collectionsDir = join10(cwd, ".reroute", "collections");
8020
8651
  try {
8021
- const collectionFiles = await readdir6(collectionsDir);
8652
+ const collectionFiles = await readdir7(collectionsDir);
8022
8653
  for (const file of collectionFiles) {
8023
8654
  if (file.endsWith(".js")) {
8024
- const collectionPath = join9(collectionsDir, file);
8655
+ const collectionPath = join10(collectionsDir, file);
8025
8656
  const collectionModule = await import(`file://${collectionPath}?t=${Date.now()}`);
8026
8657
  const items = collectionModule.items || [];
8027
8658
  for (const item of items) {
8028
8659
  if (item.href) {
8029
8660
  contentRoutes.push(item.href);
8661
+ if (i18n2.enabled && item.locale && item.locale !== defaultLocale && item.name) {
8662
+ contentRoutes.push(localizeContentHref(item.collection || file.replace(/\.js$/, ""), item.name, item.locale, defaultLocale));
8663
+ }
8030
8664
  }
8031
8665
  }
8032
8666
  }
@@ -8043,7 +8677,7 @@ async function generate(cwd, args = [], options = {}) {
8043
8677
  dynamicRoutes,
8044
8678
  version: 1
8045
8679
  };
8046
- await Bun.write(join9(cwd, ".reroute", "manifest.json"), JSON.stringify(manifest, null, 2));
8680
+ await Bun.write(join10(cwd, ".reroute", "manifest.json"), JSON.stringify(manifest, null, 2));
8047
8681
  });
8048
8682
  } catch (error) {
8049
8683
  console.error("[reroute/gen] Failed to generate manifest:", error);
@@ -8064,7 +8698,7 @@ async function generate(cwd, args = [], options = {}) {
8064
8698
  mdComponentsLines.push("");
8065
8699
  mdComponentsLines.push(`import markdownComponents from '${componentsPath}';`);
8066
8700
  mdComponentsLines.push("export { markdownComponents };");
8067
- await Bun.write(join9(cwd, ".reroute/markdown-components.ts"), mdComponentsLines.join(`
8701
+ await Bun.write(join10(cwd, ".reroute/markdown-components.ts"), mdComponentsLines.join(`
8068
8702
  `));
8069
8703
  }
8070
8704
  const indexLines = [];
@@ -8084,7 +8718,7 @@ async function generate(cwd, args = [], options = {}) {
8084
8718
  const ogTemplatePath = config2.ogImage.defaultTemplate.startsWith(".") ? `../${config2.ogImage.defaultTemplate}` : config2.ogImage.defaultTemplate;
8085
8719
  indexLines.push(`import OGTemplate from '${ogTemplatePath}';`);
8086
8720
  }
8087
- const ogFiles = await scanOgFiles(join9(cwd, ROUTES_DIR));
8721
+ const ogFiles = await scanOgFiles(join10(cwd, ROUTES_DIR));
8088
8722
  const ogImports = [];
8089
8723
  for (let i = 0;i < ogFiles.length; i++) {
8090
8724
  const ogFile = ogFiles[i];
@@ -8130,7 +8764,7 @@ async function generate(cwd, args = [], options = {}) {
8130
8764
  }
8131
8765
  indexLines.push("};");
8132
8766
  }
8133
- const templatePath = join9(cwd, "src", "client", "index.html");
8767
+ const templatePath = join10(cwd, "src", "client", "index.html");
8134
8768
  try {
8135
8769
  const templateContent = await Bun.file(templatePath).text();
8136
8770
  indexLines.push("");
@@ -8142,7 +8776,7 @@ async function generate(cwd, args = [], options = {}) {
8142
8776
  indexLines.push(`(globalThis as any).__REROUTE_CONFIG__ = ${serializeConfig(config2)};`);
8143
8777
  indexLines.push("");
8144
8778
  indexLines.push("export type RerouteBundle = typeof bundle;");
8145
- await Bun.write(join9(cwd, OUTPUT_ARTIFACTS), indexLines.join(`
8779
+ await Bun.write(join10(cwd, OUTPUT_ARTIFACTS), indexLines.join(`
8146
8780
  `));
8147
8781
  const barrelLines = [
8148
8782
  "// \uD83D\uDEA8 Auto-generated by Reroute - DO NOT EDIT \uD83D\uDEA8",
@@ -8152,7 +8786,7 @@ async function generate(cwd, args = [], options = {}) {
8152
8786
  "",
8153
8787
  "export * from './artifacts';"
8154
8788
  ];
8155
- await Bun.write(join9(cwd, OUTPUT_INDEX), barrelLines.join(`
8789
+ await Bun.write(join10(cwd, OUTPUT_INDEX), barrelLines.join(`
8156
8790
  `));
8157
8791
  const entryLines = [
8158
8792
  "// \uD83D\uDEA8 Auto-generated by Reroute - DO NOT EDIT \uD83D\uDEA8",
@@ -8168,10 +8802,10 @@ async function generate(cwd, args = [], options = {}) {
8168
8802
  "import './routes'; // Import routes (bundles all route components)",
8169
8803
  "import '../src/index'; // Import user's server entry"
8170
8804
  ];
8171
- await Bun.write(join9(cwd, ".reroute/entry.ts"), entryLines.join(`
8805
+ await Bun.write(join10(cwd, ".reroute/entry.ts"), entryLines.join(`
8172
8806
  `));
8173
8807
  if (!showPerformance) {
8174
- console.log(`[reroute/gen] Generated artifacts: ${join9(cwd, OUTPUT_ARTIFACTS)}`);
8808
+ console.log(`[reroute/gen] Generated artifacts: ${join10(cwd, OUTPUT_ARTIFACTS)}`);
8175
8809
  }
8176
8810
  await perfTrack("Bundle building", () => buildEntrypointBundle(cwd, args));
8177
8811
  const endTime = performance.now();
@@ -8298,7 +8932,7 @@ async function gen(args) {
8298
8932
  const mediumDebounce = createDebouncer(150);
8299
8933
  const slowDebounce = createDebouncer(300);
8300
8934
  const loadedConfig = await loadConfig(cwd);
8301
- const configPath = join9(cwd, "reroute.config.ts");
8935
+ const configPath = join10(cwd, "reroute.config.ts");
8302
8936
  let configWatcher = null;
8303
8937
  try {
8304
8938
  configWatcher = watch(configPath, () => {
@@ -8320,7 +8954,7 @@ async function gen(args) {
8320
8954
  ];
8321
8955
  const envWatchers = [];
8322
8956
  for (const envFile of envFiles) {
8323
- const envPath = join9(cwd, envFile);
8957
+ const envPath = join10(cwd, envFile);
8324
8958
  try {
8325
8959
  const envWatcher = watch(envPath, () => {
8326
8960
  mediumDebounce(() => {
@@ -8339,15 +8973,15 @@ async function gen(args) {
8339
8973
  let packagesWatcher = null;
8340
8974
  const isRerouteWorkspace = async () => {
8341
8975
  try {
8342
- const packagesPath = join9(cwd, "..", "..", "packages");
8343
- const exists = await Bun.file(join9(packagesPath, "core", "index.ts")).exists();
8976
+ const packagesPath = join10(cwd, "..", "..", "packages");
8977
+ const exists = await Bun.file(join10(packagesPath, "core", "index.ts")).exists();
8344
8978
  return exists;
8345
8979
  } catch {
8346
8980
  return false;
8347
8981
  }
8348
8982
  };
8349
8983
  if (await isRerouteWorkspace()) {
8350
- const packagesPath = join9(cwd, "..", "..", "packages");
8984
+ const packagesPath = join10(cwd, "..", "..", "packages");
8351
8985
  console.log("[reroute/watch] \uD83D\uDD27 Workspace development mode enabled");
8352
8986
  console.log(`[reroute/watch] \uD83D\uDD27 Watching ${packagesPath}...`);
8353
8987
  packagesWatcher = watch(packagesPath, { recursive: true }, (_ev, filename) => {
@@ -8370,7 +9004,7 @@ async function gen(args) {
8370
9004
  });
8371
9005
  });
8372
9006
  }
8373
- const routesPath = join9(cwd, ROUTES_DIR);
9007
+ const routesPath = join10(cwd, ROUTES_DIR);
8374
9008
  console.log(`[reroute/watch] \uD83D\uDCC1 Watching ${routesPath}...`);
8375
9009
  const routesWatcher = watch(routesPath, { recursive: true }, (_ev, filename) => {
8376
9010
  if (!filename)
@@ -8392,7 +9026,16 @@ async function gen(args) {
8392
9026
  }
8393
9027
  console.log("[reroute/watch] ├─ Building content chunks...");
8394
9028
  const contentStart = performance.now();
8395
- await buildContentChunks(cwd, args);
9029
+ const watchI18n = await discoverI18n(cwd, loadedConfig.i18n);
9030
+ await Bun.write(join10(cwd, ".reroute", "i18n.json"), JSON.stringify({
9031
+ enabled: watchI18n.enabled,
9032
+ defaultLocale: watchI18n.defaultLocale,
9033
+ locales: watchI18n.locales,
9034
+ cookie: watchI18n.cookie,
9035
+ redirectContentOnNegotiate: watchI18n.redirectContentOnNegotiate,
9036
+ catalogs: watchI18n.catalogs
9037
+ }, null, 2));
9038
+ await buildContentChunks(cwd, args, watchI18n.defaultLocale);
8396
9039
  const contentTime = ((performance.now() - contentStart) / 1000).toFixed(2);
8397
9040
  console.log(`[reroute/watch] └─ Content: ${contentTime}s`);
8398
9041
  if (loadedConfig.search?.enabled) {
@@ -8421,7 +9064,7 @@ async function gen(args) {
8421
9064
  }, "routes change");
8422
9065
  });
8423
9066
  });
8424
- const clientPath = join9(cwd, "src", "client");
9067
+ const clientPath = join10(cwd, "src", "client");
8425
9068
  console.log(`[reroute/watch] \uD83D\uDCE6 Watching ${clientPath}...`);
8426
9069
  const twWatcher = watch(clientPath, { recursive: true }, (_ev, filename) => {
8427
9070
  if (!filename)
@@ -9034,7 +9677,7 @@ var require_alias = __commonJS((exports) => {
9034
9677
 
9035
9678
  // node_modules/.bun/blessed@0.1.81/node_modules/blessed/lib/tput.js
9036
9679
  var require_tput = __commonJS((exports, module) => {
9037
- var __dirname = "/Users/stewan/dev/reroute/node_modules/.bun/blessed@0.1.81/node_modules/blessed/lib";
9680
+ var __dirname = "/home/runner/work/reroute/reroute/node_modules/.bun/blessed@0.1.81/node_modules/blessed/lib";
9038
9681
  var assert = __require("assert");
9039
9682
  var path3 = __require("path");
9040
9683
  var fs = __require("fs");
@@ -13279,7 +13922,7 @@ var require_program = __commonJS((exports, module) => {
13279
13922
  }
13280
13923
  break;
13281
13924
  case "underline":
13282
- if (!blink) {} else {}
13925
+ if (!blink) {}
13283
13926
  break;
13284
13927
  case "line":
13285
13928
  if (!blink) {
@@ -100543,7 +101186,7 @@ var require_lodash = __commonJS((exports, module) => {
100543
101186
  }
100544
101187
  return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, undefined2, comparator) : [];
100545
101188
  });
100546
- function join10(array, separator) {
101189
+ function join11(array, separator) {
100547
101190
  return array == null ? "" : nativeJoin.call(array, separator);
100548
101191
  }
100549
101192
  function last(array) {
@@ -102475,7 +103118,7 @@ __p += '`;
102475
103118
  lodash.isUndefined = isUndefined;
102476
103119
  lodash.isWeakMap = isWeakMap;
102477
103120
  lodash.isWeakSet = isWeakSet;
102478
- lodash.join = join10;
103121
+ lodash.join = join11;
102479
103122
  lodash.kebabCase = kebabCase;
102480
103123
  lodash.last = last;
102481
103124
  lodash.lastIndexOf = lastIndexOf;
@@ -103157,19 +103800,19 @@ __export(exports_dev, {
103157
103800
  });
103158
103801
  import { spawn as spawn2 } from "node:child_process";
103159
103802
  import { existsSync as existsSync6 } from "node:fs";
103160
- import { join as join10 } from "node:path";
103803
+ import { join as join11 } from "node:path";
103161
103804
  function getRerouteCommand() {
103162
- const binPath = join10(import.meta.dir, "..", "..", "bin.ts");
103805
+ const binPath = join11(import.meta.dir, "..", "..", "bin.ts");
103163
103806
  const isBunDev = existsSync6(binPath);
103164
103807
  if (isBunDev) {
103165
103808
  return `bun ${binPath}`;
103166
103809
  }
103167
103810
  const cwd = process.cwd();
103168
103811
  const nodeModulesPaths = [
103169
- join10(cwd, "node_modules", ".bin", "reroute"),
103170
- join10(cwd, "..", "node_modules", ".bin", "reroute"),
103171
- join10(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103172
- join10(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103812
+ join11(cwd, "node_modules", ".bin", "reroute"),
103813
+ join11(cwd, "..", "node_modules", ".bin", "reroute"),
103814
+ join11(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103815
+ join11(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103173
103816
  ];
103174
103817
  for (const binPath2 of nodeModulesPaths) {
103175
103818
  if (existsSync6(binPath2)) {
@@ -103325,19 +103968,19 @@ __export(exports_start, {
103325
103968
  });
103326
103969
  import { spawn as spawn3 } from "node:child_process";
103327
103970
  import { existsSync as existsSync7 } from "node:fs";
103328
- import { join as join11 } from "node:path";
103971
+ import { join as join12 } from "node:path";
103329
103972
  function getRerouteCommand2() {
103330
- const binPath = join11(import.meta.dir, "..", "..", "bin.ts");
103973
+ const binPath = join12(import.meta.dir, "..", "..", "bin.ts");
103331
103974
  const isBunDev = existsSync7(binPath);
103332
103975
  if (isBunDev) {
103333
103976
  return `bun ${binPath}`;
103334
103977
  }
103335
103978
  const cwd = process.cwd();
103336
103979
  const nodeModulesPaths = [
103337
- join11(cwd, "node_modules", ".bin", "reroute"),
103338
- join11(cwd, "..", "node_modules", ".bin", "reroute"),
103339
- join11(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103340
- join11(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103980
+ join12(cwd, "node_modules", ".bin", "reroute"),
103981
+ join12(cwd, "..", "node_modules", ".bin", "reroute"),
103982
+ join12(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103983
+ join12(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103341
103984
  ];
103342
103985
  for (const binPath2 of nodeModulesPaths) {
103343
103986
  if (existsSync7(binPath2)) {
@@ -103465,12 +104108,12 @@ import {
103465
104108
  mkdirSync,
103466
104109
  readdirSync
103467
104110
  } from "node:fs";
103468
- import { join as join12 } from "node:path";
104111
+ import { join as join13 } from "node:path";
103469
104112
  function copyAssetsToReroute(cwd, sourceAssetsDir, rerouteAssetsDir) {
103470
104113
  if (!existsSync8(sourceAssetsDir)) {
103471
104114
  return 0;
103472
104115
  }
103473
- const rerouteDir = join12(cwd, ".reroute");
104116
+ const rerouteDir = join13(cwd, ".reroute");
103474
104117
  if (!existsSync8(rerouteDir)) {
103475
104118
  mkdirSync(rerouteDir, { recursive: true });
103476
104119
  }
@@ -103482,7 +104125,7 @@ function countFiles(dir) {
103482
104125
  const entries = readdirSync(dir, { withFileTypes: true });
103483
104126
  for (const entry of entries) {
103484
104127
  if (entry.isDirectory()) {
103485
- count += countFiles(join12(dir, entry.name));
104128
+ count += countFiles(join13(dir, entry.name));
103486
104129
  } else {
103487
104130
  count++;
103488
104131
  }
@@ -103498,19 +104141,19 @@ __export(exports_build, {
103498
104141
  });
103499
104142
  import { spawn as spawn4 } from "node:child_process";
103500
104143
  import { existsSync as existsSync9, readFileSync as readFileSync5, rmSync, unlinkSync } from "node:fs";
103501
- import { join as join13 } from "node:path";
104144
+ import { join as join14 } from "node:path";
103502
104145
  function getRerouteCommand3() {
103503
- const binPath = join13(import.meta.dir, "..", "..", "bin.ts");
104146
+ const binPath = join14(import.meta.dir, "..", "..", "bin.ts");
103504
104147
  const isBunDev = existsSync9(binPath);
103505
104148
  if (isBunDev) {
103506
104149
  return `bun ${binPath}`;
103507
104150
  }
103508
104151
  const cwd = process.cwd();
103509
104152
  const nodeModulesPaths = [
103510
- join13(cwd, "node_modules", ".bin", "reroute"),
103511
- join13(cwd, "..", "node_modules", ".bin", "reroute"),
103512
- join13(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103513
- join13(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
104153
+ join14(cwd, "node_modules", ".bin", "reroute"),
104154
+ join14(cwd, "..", "node_modules", ".bin", "reroute"),
104155
+ join14(cwd, "..", "..", "node_modules", ".bin", "reroute"),
104156
+ join14(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103514
104157
  ];
103515
104158
  for (const binPath2 of nodeModulesPaths) {
103516
104159
  if (existsSync9(binPath2)) {
@@ -103521,7 +104164,7 @@ function getRerouteCommand3() {
103521
104164
  }
103522
104165
  function getOutputName() {
103523
104166
  const cwd = process.cwd();
103524
- const packageJsonPath = join13(cwd, "package.json");
104167
+ const packageJsonPath = join14(cwd, "package.json");
103525
104168
  if (existsSync9(packageJsonPath)) {
103526
104169
  try {
103527
104170
  const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
@@ -103553,7 +104196,7 @@ function parseBuildArgs(args) {
103553
104196
  return { outfile, compileBinary, noCompression, externalArgs };
103554
104197
  }
103555
104198
  function cleanDistFolder(cwd) {
103556
- const distDir = join13(cwd, "dist");
104199
+ const distDir = join14(cwd, "dist");
103557
104200
  if (existsSync9(distDir)) {
103558
104201
  process.stdout.write(colorizeLogPrefixAnsi(`[reroute/build] Cleaning dist folder...
103559
104202
  `));
@@ -103561,7 +104204,7 @@ function cleanDistFolder(cwd) {
103561
104204
  }
103562
104205
  }
103563
104206
  async function cleanRerouteFolder(cwd) {
103564
- const rerouteDir = join13(cwd, ".reroute");
104207
+ const rerouteDir = join14(cwd, ".reroute");
103565
104208
  if (existsSync9(rerouteDir)) {
103566
104209
  process.stdout.write(colorizeLogPrefixAnsi(`[reroute/build] Cleaning .reroute folder...
103567
104210
  `));
@@ -103618,8 +104261,8 @@ async function copyAssets(cwd) {
103618
104261
  const appSrc = config2.app?.src ?? "./src/client/App";
103619
104262
  const dirname6 = __require("node:path").dirname;
103620
104263
  const assetsPath = dirname6(appSrc);
103621
- const sourceAssetsDir = join13(cwd, assetsPath, "assets");
103622
- const rerouteAssetsDir = join13(cwd, ".reroute", "assets");
104264
+ const sourceAssetsDir = join14(cwd, assetsPath, "assets");
104265
+ const rerouteAssetsDir = join14(cwd, ".reroute", "assets");
103623
104266
  process.stdout.write(colorizeLogPrefixAnsi(`[reroute/build] Copying assets...
103624
104267
  `));
103625
104268
  const fileCount = copyAssetsToReroute(cwd, sourceAssetsDir, rerouteAssetsDir);
@@ -103634,9 +104277,9 @@ async function copyAssets(cwd) {
103634
104277
  async function buildBundle(cwd, buildArgs) {
103635
104278
  const external = [...buildArgs.externalArgs, "sharp", "@vercel/og"];
103636
104279
  const buildResult = await Bun.build({
103637
- entrypoints: [join13(cwd, ".reroute/entry.ts")],
104280
+ entrypoints: [join14(cwd, ".reroute/entry.ts")],
103638
104281
  target: "bun",
103639
- outdir: join13(cwd, "dist"),
104282
+ outdir: join14(cwd, "dist"),
103640
104283
  sourcemap: "external",
103641
104284
  ...getCommonBuildOptions(),
103642
104285
  external,
@@ -103679,7 +104322,7 @@ function cleanupBundledFiles(rerouteDir) {
103679
104322
  "markdown-components.ts"
103680
104323
  ];
103681
104324
  for (const file of tsFilesToRemove) {
103682
- const filePath = join13(rerouteDir, file);
104325
+ const filePath = join14(rerouteDir, file);
103683
104326
  if (existsSync9(filePath)) {
103684
104327
  try {
103685
104328
  unlinkSync(filePath);
@@ -103689,7 +104332,7 @@ function cleanupBundledFiles(rerouteDir) {
103689
104332
  }
103690
104333
  const dirsToRemove = ["markdown", "streaming"];
103691
104334
  for (const dir of dirsToRemove) {
103692
- const dirPath = join13(rerouteDir, dir);
104335
+ const dirPath = join14(rerouteDir, dir);
103693
104336
  if (existsSync9(dirPath)) {
103694
104337
  try {
103695
104338
  rmSync(dirPath, { recursive: true });
@@ -103723,7 +104366,7 @@ async function build(args) {
103723
104366
  await runGenCommand(rerouteCmd, useShell, buildArgs.noCompression);
103724
104367
  await copyAssets(cwd);
103725
104368
  await buildBundle(cwd, buildArgs);
103726
- const rerouteDir = join13(cwd, ".reroute");
104369
+ const rerouteDir = join14(cwd, ".reroute");
103727
104370
  cleanupBundledFiles(rerouteDir);
103728
104371
  logBuildComplete(buildStartTime, buildArgs);
103729
104372
  process.exit(0);
@@ -103745,19 +104388,19 @@ __export(exports_command, {
103745
104388
  getRerouteCommand: () => getRerouteCommand4
103746
104389
  });
103747
104390
  import { existsSync as existsSync10 } from "node:fs";
103748
- import { join as join14 } from "node:path";
104391
+ import { join as join15 } from "node:path";
103749
104392
  function getRerouteCommand4() {
103750
- const binPath = join14(import.meta.dir, "..", "..", "bin.ts");
104393
+ const binPath = join15(import.meta.dir, "..", "..", "bin.ts");
103751
104394
  const isBunDev = existsSync10(binPath);
103752
104395
  if (isBunDev) {
103753
104396
  return `bun ${binPath}`;
103754
104397
  }
103755
104398
  const cwd = process.cwd();
103756
104399
  const nodeModulesPaths = [
103757
- join14(cwd, "node_modules", ".bin", "reroute"),
103758
- join14(cwd, "..", "node_modules", ".bin", "reroute"),
103759
- join14(cwd, "..", "..", "node_modules", ".bin", "reroute"),
103760
- join14(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
104400
+ join15(cwd, "node_modules", ".bin", "reroute"),
104401
+ join15(cwd, "..", "node_modules", ".bin", "reroute"),
104402
+ join15(cwd, "..", "..", "node_modules", ".bin", "reroute"),
104403
+ join15(cwd, "..", "..", "..", "node_modules", ".bin", "reroute")
103761
104404
  ];
103762
104405
  for (const binPath2 of nodeModulesPaths) {
103763
104406
  if (existsSync10(binPath2)) {
@@ -103774,24 +104417,24 @@ __export(exports_analyze, {
103774
104417
  default: () => analyze
103775
104418
  });
103776
104419
  import { existsSync as existsSync11, statSync } from "node:fs";
103777
- import { readdir as readdir7, readFile, stat as stat5, writeFile } from "node:fs/promises";
103778
- import { basename as basename3, join as join15, relative as relative3 } from "node:path";
104420
+ import { readdir as readdir8, readFile, stat as stat5, writeFile } from "node:fs/promises";
104421
+ import { basename as basename3, join as join16, relative as relative3 } from "node:path";
103779
104422
  import Table from "cli-table3";
103780
104423
  async function findChunkBundles(cwd) {
103781
104424
  const bundles = [];
103782
- const chunksDir = join15(cwd, ".reroute", "chunks");
104425
+ const chunksDir = join16(cwd, ".reroute", "chunks");
103783
104426
  if (!existsSync11(chunksDir))
103784
104427
  return bundles;
103785
- const collections2 = await readdir7(chunksDir, { withFileTypes: true });
104428
+ const collections2 = await readdir8(chunksDir, { withFileTypes: true });
103786
104429
  for (const collection of collections2) {
103787
104430
  if (!collection.isDirectory())
103788
104431
  continue;
103789
- const collectionDir = join15(chunksDir, collection.name);
103790
- const files = await readdir7(collectionDir);
104432
+ const collectionDir = join16(chunksDir, collection.name);
104433
+ const files = await readdir8(collectionDir);
103791
104434
  for (const file of files) {
103792
104435
  if (!file.endsWith(".js"))
103793
104436
  continue;
103794
- const filePath = join15(collectionDir, file);
104437
+ const filePath = join16(collectionDir, file);
103795
104438
  const stats = await stat5(filePath);
103796
104439
  const content2 = await Bun.file(filePath).arrayBuffer();
103797
104440
  const gzippedSize = Bun.gzipSync(new Uint8Array(content2)).length;
@@ -103809,12 +104452,12 @@ async function findChunkBundles(cwd) {
103809
104452
  }
103810
104453
  async function findDistBundles(cwd) {
103811
104454
  const bundles = [];
103812
- const distDir = join15(cwd, "dist");
104455
+ const distDir = join16(cwd, "dist");
103813
104456
  if (!existsSync11(distDir))
103814
104457
  return bundles;
103815
- const files = await readdir7(distDir);
104458
+ const files = await readdir8(distDir);
103816
104459
  for (const file of files) {
103817
- const filePath = join15(distDir, file);
104460
+ const filePath = join16(distDir, file);
103818
104461
  const stats = statSync(filePath);
103819
104462
  if (stats.isFile() && !file.endsWith(".map") && !file.endsWith(".d.ts")) {
103820
104463
  const content2 = await Bun.file(filePath).arrayBuffer();
@@ -103840,9 +104483,9 @@ async function findBundles(cwd) {
103840
104483
  }
103841
104484
  async function findBrowserEntry(cwd) {
103842
104485
  try {
103843
- const clientDir = join15(cwd, "src", "client");
103844
- const tsxEntry = join15(clientDir, "index.tsx");
103845
- const tsEntry = join15(clientDir, "index.ts");
104486
+ const clientDir = join16(cwd, "src", "client");
104487
+ const tsxEntry = join16(clientDir, "index.tsx");
104488
+ const tsEntry = join16(clientDir, "index.ts");
103846
104489
  let sourceEntry = null;
103847
104490
  if (existsSync11(tsxEntry)) {
103848
104491
  sourceEntry = tsxEntry;
@@ -103875,12 +104518,12 @@ async function findBrowserEntry(cwd) {
103875
104518
  console.warn("[reroute/analyze] Browser build produced no outputs");
103876
104519
  return null;
103877
104520
  }
103878
- const tempDir = join15(cwd, ".reroute", "browser");
104521
+ const tempDir = join16(cwd, ".reroute", "browser");
103879
104522
  await import("node:fs/promises").then((fs) => fs.mkdir(tempDir, { recursive: true }));
103880
104523
  const output = result.outputs[0];
103881
104524
  const code = await output.text();
103882
104525
  const actualHash = await generateContentHash2(code);
103883
- const bundlePath = join15(tempDir, `index.${actualHash}.js`);
104526
+ const bundlePath = join16(tempDir, `index.${actualHash}.js`);
103884
104527
  await writeFile(bundlePath, code, "utf-8");
103885
104528
  const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
103886
104529
  if (mapOutput) {
@@ -103932,7 +104575,7 @@ function calculateStats(bundles) {
103932
104575
  }
103933
104576
  async function getInstalledPackageSizes(cwd) {
103934
104577
  const packageSizes = new Map;
103935
- const nodeModulesDir = join15(cwd, "node_modules");
104578
+ const nodeModulesDir = join16(cwd, "node_modules");
103936
104579
  if (!existsSync11(nodeModulesDir))
103937
104580
  return packageSizes;
103938
104581
  try {
@@ -103988,7 +104631,7 @@ async function buildDependencyTree(entryPath, cwd) {
103988
104631
  if (!pkgNode) {
103989
104632
  pkgNode = {
103990
104633
  name: pkgName || "unknown",
103991
- path: join15(cwd, "node_modules", pkgName || ""),
104634
+ path: join16(cwd, "node_modules", pkgName || ""),
103992
104635
  size: 0,
103993
104636
  children: []
103994
104637
  };
@@ -104009,7 +104652,7 @@ async function buildDependencyTree(entryPath, cwd) {
104009
104652
  if (!child) {
104010
104653
  child = {
104011
104654
  name: part,
104012
- path: join15(cwd, ...parts.slice(0, i + 1)),
104655
+ path: join16(cwd, ...parts.slice(0, i + 1)),
104013
104656
  size: 0,
104014
104657
  children: []
104015
104658
  };
@@ -104040,7 +104683,7 @@ async function buildDependencyTree(entryPath, cwd) {
104040
104683
  const modulePattern = /\/\/\s*(.+?\.(?:ts|tsx|js|jsx|mjs|cjs))\s*$/gm;
104041
104684
  const nodeModulesNode = {
104042
104685
  name: "node_modules",
104043
- path: join15(cwd, "node_modules"),
104686
+ path: join16(cwd, "node_modules"),
104044
104687
  size: 0,
104045
104688
  children: []
104046
104689
  };
@@ -104106,7 +104749,7 @@ async function buildTreeFromSourceMap(bundlePath, cwd) {
104106
104749
  if (sourcePath.startsWith("/") || sourcePath.match(/^[A-Za-z]:\\/))
104107
104750
  return sourcePath;
104108
104751
  const bundleDir = bundlePath.substring(0, bundlePath.lastIndexOf("/"));
104109
- return join15(bundleDir, sourceRoot || "", sourcePath);
104752
+ return join16(bundleDir, sourceRoot || "", sourcePath);
104110
104753
  }, computeSizes = function(node) {
104111
104754
  if (node.children && node.children.length > 0) {
104112
104755
  node.size = node.children.reduce((s, c) => s + computeSizes(c), 0);
@@ -104178,7 +104821,7 @@ async function buildTreeFromSourceMap(bundlePath, cwd) {
104178
104821
  };
104179
104822
  const nodeModulesNode = {
104180
104823
  name: "node_modules",
104181
- path: join15(cwd, "node_modules"),
104824
+ path: join16(cwd, "node_modules"),
104182
104825
  size: 0,
104183
104826
  children: []
104184
104827
  };
@@ -104218,7 +104861,7 @@ async function buildTreeFromSourceMap(bundlePath, cwd) {
104218
104861
  if (!pkgNode) {
104219
104862
  pkgNode = {
104220
104863
  name: pkgName || "unknown",
104221
- path: join15(cwd, "node_modules", pkgName || ""),
104864
+ path: join16(cwd, "node_modules", pkgName || ""),
104222
104865
  size: 0,
104223
104866
  children: []
104224
104867
  };
@@ -104239,7 +104882,7 @@ async function buildTreeFromSourceMap(bundlePath, cwd) {
104239
104882
  if (!child) {
104240
104883
  child = {
104241
104884
  name: part,
104242
- path: join15(cwd, ...parts.slice(0, i2 + 1)),
104885
+ path: join16(cwd, ...parts.slice(0, i2 + 1)),
104243
104886
  size: 0,
104244
104887
  children: []
104245
104888
  };
@@ -104269,11 +104912,11 @@ async function generateHTMLReport(stats, depTree, mainInfo, browserDepTree, brow
104269
104912
  let actualServerBinarySize = null;
104270
104913
  let actualServerBinaryGzipped = null;
104271
104914
  try {
104272
- const distDir = join15(process.cwd(), "dist");
104915
+ const distDir = join16(process.cwd(), "dist");
104273
104916
  if (existsSync11(distDir)) {
104274
- const files = await readdir7(distDir);
104917
+ const files = await readdir8(distDir);
104275
104918
  for (const file of files) {
104276
- const filePath = join15(distDir, file);
104919
+ const filePath = join16(distDir, file);
104277
104920
  const stats2 = statSync(filePath);
104278
104921
  if (stats2.isFile() && !file.includes(".")) {
104279
104922
  actualServerBinarySize = stats2.size;
@@ -105755,11 +106398,11 @@ async function printConsoleReport(stats, depTree, mainInfo, browserDepTree, brow
105755
106398
  let actualServerBinarySize = null;
105756
106399
  let actualServerBinaryGzipped = null;
105757
106400
  try {
105758
- const distDir = join15(process.cwd(), "dist");
106401
+ const distDir = join16(process.cwd(), "dist");
105759
106402
  if (existsSync11(distDir)) {
105760
- const files = await readdir7(distDir);
106403
+ const files = await readdir8(distDir);
105761
106404
  for (const file of files) {
105762
- const filePath = join15(distDir, file);
106405
+ const filePath = join16(distDir, file);
105763
106406
  const stats2 = statSync(filePath);
105764
106407
  if (stats2.isFile() && !file.includes(".")) {
105765
106408
  actualServerBinarySize = stats2.size;
@@ -105793,7 +106436,7 @@ async function analyze(args) {
105793
106436
  const isProd = isProductionMode(args);
105794
106437
  const searchIndex = args.indexOf("--search");
105795
106438
  const initialSearch = searchIndex !== -1 && args[searchIndex + 1] ? args[searchIndex + 1] : "";
105796
- const rerouteIndexPath = join15(cwd, ".reroute", "index.ts");
106439
+ const rerouteIndexPath = join16(cwd, ".reroute", "index.ts");
105797
106440
  if (!existsSync11(rerouteIndexPath)) {
105798
106441
  console.log(colorizeLogPrefixAnsi(`[reroute/analyze] .reroute artifacts not found, running build...
105799
106442
  `));
@@ -105856,12 +106499,12 @@ async function analyze(args) {
105856
106499
  let entry = null;
105857
106500
  let depTree = null;
105858
106501
  let mainInfo = null;
105859
- const srcEntry = join15(cwd, "src", "index.ts");
106502
+ const srcEntry = join16(cwd, "src", "index.ts");
105860
106503
  if (existsSync11(srcEntry)) {
105861
106504
  console.log(colorizeLogPrefixAnsi(`[reroute/analyze] Building server binary for analysis...
105862
106505
  `));
105863
106506
  const { mkdir: mkdir3 } = await import("node:fs/promises");
105864
- const serverAnalysisDir = join15(cwd, ".reroute", "server");
106507
+ const serverAnalysisDir = join16(cwd, ".reroute", "server");
105865
106508
  await mkdir3(serverAnalysisDir, { recursive: true });
105866
106509
  const result = await Bun.build({
105867
106510
  entrypoints: [srcEntry],
@@ -105874,7 +106517,7 @@ async function analyze(args) {
105874
106517
  if (result.success && result.outputs.length > 0) {
105875
106518
  const output = result.outputs[0];
105876
106519
  const code = await output.text();
105877
- const analysisEntry = join15(serverAnalysisDir, "index.js");
106520
+ const analysisEntry = join16(serverAnalysisDir, "index.js");
105878
106521
  await writeFile(analysisEntry, code, "utf-8");
105879
106522
  const mapOutput = result.outputs.find((o) => o.path.endsWith(".map"));
105880
106523
  if (mapOutput) {
@@ -105892,7 +106535,7 @@ async function analyze(args) {
105892
106535
  `));
105893
106536
  const installedSizes = await getInstalledPackageSizes(cwd);
105894
106537
  if (depTree) {
105895
- const packageJsonPath = join15(cwd, "package.json");
106538
+ const packageJsonPath = join16(cwd, "package.json");
105896
106539
  if (existsSync11(packageJsonPath)) {
105897
106540
  try {
105898
106541
  const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
@@ -105904,7 +106547,7 @@ async function analyze(args) {
105904
106547
  if (!trackedPkgs.has(pkgName) && installedSizes.has(pkgName)) {
105905
106548
  nodeModulesNode.children?.push({
105906
106549
  name: pkgName,
105907
- path: join15(cwd, "node_modules", pkgName),
106550
+ path: join16(cwd, "node_modules", pkgName),
105908
106551
  size: installedSizes.get(pkgName) || 0
105909
106552
  });
105910
106553
  }
@@ -105935,10 +106578,10 @@ async function analyze(args) {
105935
106578
  const fullOutput = args.includes("--full");
105936
106579
  await printConsoleReport(stats, depTree, mainInfo, browserDepTree, browserInfo, fullOutput);
105937
106580
  if (outputHtml || openBrowser) {
105938
- const htmlPath = join15(cwd, ".reroute", "analysis.html");
106581
+ const htmlPath = join16(cwd, ".reroute", "analysis.html");
105939
106582
  const html2 = await generateHTMLReport(stats, depTree, mainInfo, browserDepTree, browserInfo, initialSearch);
105940
106583
  const { mkdir: mkdir3 } = await import("node:fs/promises");
105941
- await mkdir3(join15(cwd, ".reroute"), { recursive: true });
106584
+ await mkdir3(join16(cwd, ".reroute"), { recursive: true });
105942
106585
  await writeFile(htmlPath, html2, "utf-8");
105943
106586
  console.log(colorizeLogPrefixAnsi(`[reroute/analyze] HTML report generated: ${relative3(cwd, htmlPath)}
105944
106587
  `));
@@ -106001,8 +106644,8 @@ __export(exports_og, {
106001
106644
  default: () => og2
106002
106645
  });
106003
106646
  import { spawn as spawn5 } from "node:child_process";
106004
- import { readdir as readdir8 } from "node:fs/promises";
106005
- import { join as join16 } from "node:path";
106647
+ import { readdir as readdir9 } from "node:fs/promises";
106648
+ import { join as join17 } from "node:path";
106006
106649
  function getPortFromConfig(baseUrl) {
106007
106650
  if (!baseUrl)
106008
106651
  return 3000;
@@ -106079,10 +106722,10 @@ Error output:`, errorOutput);
106079
106722
  }
106080
106723
  async function scanForOgTemplates(dir, base, routes) {
106081
106724
  try {
106082
- const entries = await readdir8(dir, { withFileTypes: true });
106725
+ const entries = await readdir9(dir, { withFileTypes: true });
106083
106726
  for (const entry of entries) {
106084
- const fullPath = join16(dir, entry.name);
106085
- const relativePath = join16(base, entry.name);
106727
+ const fullPath = join17(dir, entry.name);
106728
+ const relativePath = join17(base, entry.name);
106086
106729
  if (entry.isDirectory()) {
106087
106730
  if (entry.name === "content")
106088
106731
  continue;
@@ -106099,13 +106742,13 @@ function findFirstContentFile(contentFiles) {
106099
106742
  }
106100
106743
  async function addContentRoutes(routesPath, routes) {
106101
106744
  try {
106102
- const entries = await readdir8(routesPath, { withFileTypes: true });
106745
+ const entries = await readdir9(routesPath, { withFileTypes: true });
106103
106746
  for (const entry of entries) {
106104
106747
  if (!entry.isDirectory())
106105
106748
  continue;
106106
- const contentDir = join16(routesPath, entry.name, "content");
106749
+ const contentDir = join17(routesPath, entry.name, "content");
106107
106750
  try {
106108
- const contentFiles = await readdir8(contentDir);
106751
+ const contentFiles = await readdir9(contentDir);
106109
106752
  if (contentFiles.length === 0)
106110
106753
  continue;
106111
106754
  const firstFile = findFirstContentFile(contentFiles);
@@ -106120,7 +106763,7 @@ async function addContentRoutes(routesPath, routes) {
106120
106763
  async function findRoutesWithOG(cwd) {
106121
106764
  const routes = [];
106122
106765
  try {
106123
- const manifestPath = join16(cwd, ".reroute", "manifest.json");
106766
+ const manifestPath = join17(cwd, ".reroute", "manifest.json");
106124
106767
  const manifestFile = Bun.file(manifestPath);
106125
106768
  const manifest = await manifestFile.json();
106126
106769
  if (manifest.staticRoutes && Array.isArray(manifest.staticRoutes)) {
@@ -106131,7 +106774,7 @@ async function findRoutesWithOG(cwd) {
106131
106774
  }
106132
106775
  } catch {
106133
106776
  console.warn("⚠️ .reroute/manifest.json not found, scanning routes directory...");
106134
- const routesPath = join16(cwd, ROUTES_DIR2);
106777
+ const routesPath = join17(cwd, ROUTES_DIR2);
106135
106778
  await scanForOgTemplates(routesPath, "", routes);
106136
106779
  await addContentRoutes(routesPath, routes);
106137
106780
  }
@@ -106383,7 +107026,7 @@ async function getVersionString2() {
106383
107026
  }
106384
107027
  async function getVersion2() {
106385
107028
  if (true) {
106386
- return "0.45.11";
107029
+ return "0.46.0";
106387
107030
  }
106388
107031
  const possiblePaths = [
106389
107032
  path3.join(import.meta.dir, "../../../package.json"),
@@ -106400,10 +107043,10 @@ async function getVersion2() {
106400
107043
  }
106401
107044
  async function getCommit2() {
106402
107045
  if (true) {
106403
- return "c64466b";
107046
+ return "9116acf";
106404
107047
  }
106405
107048
  return "dev";
106406
107049
  }
106407
107050
  main();
106408
107051
 
106409
- //# debugId=7E1CB9A746872E8064756E2164756E21
107052
+ //# debugId=0B12295AB270224764756E2164756E21