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/core/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * reroute-js v0.45.11
2
+ * reroute-js v0.46.0
3
3
  *
4
4
  * @license MIT
5
5
  * @copyright 2026 stewones <hi@stewan.io>
6
- * @see https://github.com/stewones/reroute
6
+ * @see https://github.com/Intenseloop/reroute
7
7
  *
8
8
  * Built with Bun <3
9
9
  */
@@ -200,6 +200,364 @@ var init_content = __esm(() => {
200
200
  init_metadata();
201
201
  });
202
202
 
203
+ // packages/core/src/i18n/catalog.ts
204
+ function getMessage(catalog, key) {
205
+ if (!(catalog && key))
206
+ return;
207
+ const parts = key.split(".");
208
+ let current = catalog;
209
+ for (const part of parts) {
210
+ if (current == null || typeof current === "string")
211
+ return;
212
+ current = current[part];
213
+ }
214
+ return typeof current === "string" ? current : undefined;
215
+ }
216
+ function lookupMessage(catalogs, locale, defaultLocale, key) {
217
+ return getMessage(catalogs[locale], key) ?? (locale !== defaultLocale ? getMessage(catalogs[defaultLocale], key) : undefined);
218
+ }
219
+ var init_catalog = () => {};
220
+
221
+ // packages/core/src/i18n/discover.ts
222
+ import { readdir as readdir2 } from "node:fs/promises";
223
+ import { join as join2 } from "node:path";
224
+ async function loadCatalog(filePath) {
225
+ try {
226
+ const text = await Bun.file(filePath).text();
227
+ return JSON.parse(text);
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ async function loadLocaleCatalogs(absDir) {
233
+ const entries = await readdir2(absDir, { withFileTypes: true });
234
+ const catalogs = {};
235
+ const locales = [];
236
+ for (const entry of entries) {
237
+ if (!(entry.isFile() && entry.name.endsWith(".json")))
238
+ continue;
239
+ const locale = entry.name.slice(0, -".json".length);
240
+ if (!locale)
241
+ continue;
242
+ const catalog = await loadCatalog(join2(absDir, entry.name));
243
+ if (!catalog)
244
+ continue;
245
+ catalogs[locale] = catalog;
246
+ locales.push(locale);
247
+ }
248
+ return { catalogs, locales };
249
+ }
250
+ function sortLocales(locales, defaultLocale) {
251
+ locales.sort((a, b) => {
252
+ if (a === defaultLocale)
253
+ return -1;
254
+ if (b === defaultLocale)
255
+ return 1;
256
+ return a.localeCompare(b);
257
+ });
258
+ }
259
+ async function discoverI18n(cwd, options = {}) {
260
+ const localesDir = options.localesDir ?? DEFAULT_LOCALES_DIR;
261
+ const defaultLocale = options.defaultLocale ?? DEFAULT_LOCALE;
262
+ const cookie = options.cookie ?? DEFAULT_COOKIE;
263
+ const redirectContentOnNegotiate = options.redirectContentOnNegotiate ?? true;
264
+ const absDir = join2(cwd, localesDir);
265
+ const disabled = {
266
+ enabled: false,
267
+ defaultLocale,
268
+ locales: [],
269
+ localesDir,
270
+ cookie,
271
+ redirectContentOnNegotiate,
272
+ catalogs: {}
273
+ };
274
+ let catalogs;
275
+ let locales;
276
+ try {
277
+ ({ catalogs, locales } = await loadLocaleCatalogs(absDir));
278
+ } catch {
279
+ return disabled;
280
+ }
281
+ if (locales.length === 0) {
282
+ return disabled;
283
+ }
284
+ sortLocales(locales, defaultLocale);
285
+ if (!catalogs[defaultLocale]) {
286
+ console.warn(`[reroute/i18n] defaultLocale "${defaultLocale}" has no catalog; using "${locales[0]}"`);
287
+ }
288
+ return {
289
+ enabled: true,
290
+ defaultLocale: catalogs[defaultLocale] ? defaultLocale : locales[0],
291
+ locales,
292
+ localesDir,
293
+ cookie,
294
+ redirectContentOnNegotiate,
295
+ catalogs
296
+ };
297
+ }
298
+ var DEFAULT_LOCALE = "en-US", DEFAULT_LOCALES_DIR = "locales", DEFAULT_COOKIE = "reroute-locale";
299
+ var init_discover = () => {};
300
+
301
+ // packages/core/src/i18n/messages.ts
302
+ import { IntlMessageFormat } from "intl-messageformat";
303
+ function formatMessage(message, locale, values) {
304
+ if (!values || Object.keys(values).length === 0) {
305
+ if (!/\{[^}]+\}/.test(message))
306
+ return message;
307
+ }
308
+ const cacheKey = `${locale}\x00${message}`;
309
+ let formatter = formatterCache.get(cacheKey);
310
+ if (!formatter) {
311
+ formatter = new IntlMessageFormat(message, locale);
312
+ formatterCache.set(cacheKey, formatter);
313
+ }
314
+ try {
315
+ const result = formatter.format(values);
316
+ return typeof result === "string" ? result : String(result);
317
+ } catch {
318
+ return message;
319
+ }
320
+ }
321
+ function createTranslator(catalogs, locale, defaultLocale) {
322
+ return (key, values) => {
323
+ const message = lookupMessage(catalogs, locale, defaultLocale, key);
324
+ if (message == null)
325
+ return key;
326
+ return formatMessage(message, locale, values);
327
+ };
328
+ }
329
+ var formatterCache;
330
+ var init_messages = __esm(() => {
331
+ init_catalog();
332
+ formatterCache = new Map;
333
+ });
334
+
335
+ // packages/core/src/i18n/negotiate.ts
336
+ function negotiateLocale(acceptLanguage, availableLocales, defaultLocale) {
337
+ if (!availableLocales.length)
338
+ return defaultLocale;
339
+ if (!acceptLanguage)
340
+ return defaultLocale;
341
+ const availableLower = new Map(availableLocales.map((l) => [l.toLowerCase(), l]));
342
+ const byLang = new Map;
343
+ for (const locale of availableLocales) {
344
+ const lang = locale.split("-")[0].toLowerCase();
345
+ if (!byLang.has(lang))
346
+ byLang.set(lang, locale);
347
+ }
348
+ const ranges = acceptLanguage.split(",").map((part) => {
349
+ const [tag, ...params] = part.trim().split(";");
350
+ let q = 1;
351
+ for (const p of params) {
352
+ const [k, v] = p.trim().split("=");
353
+ if (k === "q" && v)
354
+ q = Number.parseFloat(v) || 0;
355
+ }
356
+ return { tag: tag.trim().toLowerCase(), q };
357
+ }).filter((r) => r.tag && r.q > 0).sort((a, b) => b.q - a.q);
358
+ for (const { tag } of ranges) {
359
+ if (tag === "*")
360
+ return defaultLocale;
361
+ const exact = availableLower.get(tag);
362
+ if (exact)
363
+ return exact;
364
+ const langOnly = byLang.get(tag.split("-")[0]);
365
+ if (langOnly)
366
+ return langOnly;
367
+ }
368
+ return defaultLocale;
369
+ }
370
+ function parseCookieValue(cookieHeader, name) {
371
+ if (!cookieHeader)
372
+ return;
373
+ const parts = cookieHeader.split(";");
374
+ for (const part of parts) {
375
+ const idx = part.indexOf("=");
376
+ if (idx === -1)
377
+ continue;
378
+ const key = part.slice(0, idx).trim();
379
+ if (key === name) {
380
+ return decodeURIComponent(part.slice(idx + 1).trim());
381
+ }
382
+ }
383
+ return;
384
+ }
385
+ function buildLocaleCookie(name, locale, maxAgeSeconds = 60 * 60 * 24 * 365) {
386
+ return `${name}=${encodeURIComponent(locale)}; Path=/; Max-Age=${maxAgeSeconds}; SameSite=Lax`;
387
+ }
388
+ var init_negotiate = () => {};
389
+
390
+ // packages/core/src/i18n/pathname.ts
391
+ var exports_pathname = {};
392
+ __export(exports_pathname, {
393
+ toOgLocale: () => toOgLocale,
394
+ parseContentPath: () => parseContentPath,
395
+ normalizePathname: () => normalizePathname,
396
+ looksLikeLocale: () => looksLikeLocale,
397
+ localizeHref: () => localizeHref,
398
+ localizeContentHref: () => localizeContentHref,
399
+ hreflangTags: () => hreflangTags,
400
+ contentKey: () => contentKey
401
+ });
402
+ function looksLikeLocale(segment) {
403
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(segment);
404
+ }
405
+ function normalizePathname(pathname) {
406
+ const pathOnly = pathname.split("?")[0].split("#")[0];
407
+ if (!pathOnly || pathOnly === "/")
408
+ return "/";
409
+ return pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
410
+ }
411
+ function parseContentPath(pathname, availableLocales = [], collections) {
412
+ const normalized = normalizePathname(pathname);
413
+ const parts = normalized.split("/").filter(Boolean);
414
+ if (parts.length < 2)
415
+ return;
416
+ const [collection, second, third] = parts;
417
+ if (collections && !collections.includes(collection)) {
418
+ return;
419
+ }
420
+ const localeSet = new Set(availableLocales.map((l) => l.toLowerCase()));
421
+ if (parts.length >= 3 && third) {
422
+ const secondIsLocale = localeSet.size > 0 ? localeSet.has(second.toLowerCase()) : looksLikeLocale(second);
423
+ if (secondIsLocale) {
424
+ const locale = availableLocales.find((l) => l.toLowerCase() === second.toLowerCase()) || second;
425
+ return {
426
+ collection,
427
+ name: third,
428
+ locale,
429
+ routingPathname: `/${collection}/${third}`,
430
+ pathname: normalized
431
+ };
432
+ }
433
+ }
434
+ return {
435
+ collection,
436
+ name: second,
437
+ routingPathname: `/${collection}/${second}`,
438
+ pathname: normalized
439
+ };
440
+ }
441
+ function localizeContentHref(collection, name, locale, defaultLocale) {
442
+ if (!locale || locale === defaultLocale) {
443
+ return `/${collection}/${name}`;
444
+ }
445
+ return `/${collection}/${locale}/${name}`;
446
+ }
447
+ function localizeHref(to, locale, defaultLocale, availableLocales = [], collections) {
448
+ try {
449
+ const url = new URL(to, "http://local");
450
+ const parsed = parseContentPath(url.pathname, availableLocales, collections);
451
+ if (!parsed)
452
+ return to;
453
+ url.pathname = localizeContentHref(parsed.collection, parsed.name, locale, defaultLocale);
454
+ return `${url.pathname}${url.search}${url.hash}`;
455
+ } catch {
456
+ return to;
457
+ }
458
+ }
459
+ function contentKey(collection, name, locale, defaultLocale = "en-US") {
460
+ if (locale && locale !== defaultLocale) {
461
+ return `${collection}:${name}:${locale}`;
462
+ }
463
+ return `${collection}:${name}`;
464
+ }
465
+ function toOgLocale(locale) {
466
+ return locale.replace(/-/g, "_");
467
+ }
468
+ function hreflangTags(baseUrl, collection, name, locales, defaultLocale) {
469
+ const base = baseUrl.replace(/\/$/, "");
470
+ const tags = [];
471
+ for (const locale of locales) {
472
+ const path = localizeContentHref(collection, name, locale, defaultLocale);
473
+ tags.push(`<link rel="alternate" hreflang="${locale}" href="${base}${path}" />`);
474
+ }
475
+ const defaultPath = localizeContentHref(collection, name, defaultLocale, defaultLocale);
476
+ tags.push(`<link rel="alternate" hreflang="x-default" href="${base}${defaultPath}" />`);
477
+ return tags.join(`
478
+ `);
479
+ }
480
+ var init_pathname = () => {};
481
+
482
+ // packages/core/src/i18n/resolve.ts
483
+ function resolveLocale(input) {
484
+ const {
485
+ pathname,
486
+ cookieHeader,
487
+ acceptLanguage,
488
+ availableLocales,
489
+ defaultLocale,
490
+ cookieName = DEFAULT_COOKIE,
491
+ collections
492
+ } = input;
493
+ const parsed = parseContentPath(pathname, availableLocales, collections);
494
+ const availableLower = new Set(availableLocales.map((l) => l.toLowerCase()));
495
+ const isAvailable = (locale) => !!locale && availableLower.has(locale.toLowerCase());
496
+ if (parsed?.locale && isAvailable(parsed.locale)) {
497
+ const locale = availableLocales.find((l) => l.toLowerCase() === parsed.locale?.toLowerCase()) || parsed.locale;
498
+ if (locale === defaultLocale && parsed.pathname !== parsed.routingPathname) {
499
+ return {
500
+ locale,
501
+ routingPathname: parsed.routingPathname,
502
+ fromPath: true,
503
+ redirectTo: parsed.routingPathname,
504
+ parsed: { ...parsed, locale }
505
+ };
506
+ }
507
+ return {
508
+ locale,
509
+ routingPathname: parsed.routingPathname,
510
+ fromPath: true,
511
+ parsed: { ...parsed, locale }
512
+ };
513
+ }
514
+ const routingPathname = parsed?.routingPathname ?? pathname.split("?")[0];
515
+ const cookieLocale = parseCookieValue(cookieHeader, cookieName);
516
+ if (isAvailable(cookieLocale)) {
517
+ const locale = availableLocales.find((l) => l.toLowerCase() === cookieLocale.toLowerCase()) || cookieLocale;
518
+ return {
519
+ locale,
520
+ routingPathname,
521
+ fromPath: false,
522
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
523
+ };
524
+ }
525
+ const negotiated = negotiateLocale(acceptLanguage, availableLocales, defaultLocale);
526
+ return {
527
+ locale: negotiated,
528
+ routingPathname,
529
+ fromPath: false,
530
+ parsed: parsed ? { ...parsed, locale: undefined } : undefined
531
+ };
532
+ }
533
+ function contentNegotiateRedirect(resolution, defaultLocale, enabled) {
534
+ if (!enabled)
535
+ return;
536
+ if (resolution.fromPath)
537
+ return;
538
+ if (resolution.locale === defaultLocale)
539
+ return;
540
+ const parsed = resolution.parsed;
541
+ if (!parsed)
542
+ return;
543
+ return localizeContentHref(parsed.collection, parsed.name, resolution.locale, defaultLocale);
544
+ }
545
+ var init_resolve = __esm(() => {
546
+ init_discover();
547
+ init_negotiate();
548
+ init_pathname();
549
+ });
550
+
551
+ // packages/core/src/i18n/index.ts
552
+ var init_i18n = __esm(() => {
553
+ init_catalog();
554
+ init_discover();
555
+ init_messages();
556
+ init_negotiate();
557
+ init_pathname();
558
+ init_resolve();
559
+ });
560
+
203
561
  // packages/telemetry/src/server/context.ts
204
562
  import { AsyncLocalStorage } from "node:async_hooks";
205
563
  var contextStorage;
@@ -891,6 +1249,51 @@ function matchRoutes(routes, pathname) {
891
1249
  return null;
892
1250
  }
893
1251
 
1252
+ // packages/core/src/ssr/lib/content-path.ts
1253
+ var exports_content_path = {};
1254
+ __export(exports_content_path, {
1255
+ resolveContentPathParts: () => resolveContentPathParts
1256
+ });
1257
+ function resolveContentPathParts(pathname) {
1258
+ const pathOnly = pathname.split("?")[0].split("#")[0];
1259
+ const parts = pathOnly.split("/").filter(Boolean);
1260
+ if (parts.length < 2)
1261
+ return null;
1262
+ const g = globalThis;
1263
+ const locales = g.__REROUTE_I18N__?.locales || [];
1264
+ const defaultLocale = g.__REROUTE_I18N__?.defaultLocale || "en-US";
1265
+ const parsed = parseContentPath(pathOnly, locales);
1266
+ if (parsed) {
1267
+ const locale = parsed.locale || defaultLocale;
1268
+ return {
1269
+ collection: parsed.collection,
1270
+ name: parsed.name,
1271
+ locale: parsed.locale,
1272
+ key: contentKey(parsed.collection, parsed.name, locale, defaultLocale),
1273
+ routingPathname: parsed.routingPathname
1274
+ };
1275
+ }
1276
+ if (parts.length >= 3 && looksLikeLocale(parts[1])) {
1277
+ const locale = parts[1];
1278
+ return {
1279
+ collection: parts[0],
1280
+ name: parts[2],
1281
+ locale,
1282
+ key: contentKey(parts[0], parts[2], locale, defaultLocale),
1283
+ routingPathname: `/${parts[0]}/${parts[2]}`
1284
+ };
1285
+ }
1286
+ return {
1287
+ collection: parts[0],
1288
+ name: parts[1],
1289
+ key: `${parts[0]}:${parts[1]}`,
1290
+ routingPathname: `/${parts[0]}/${parts[1]}`
1291
+ };
1292
+ }
1293
+ var init_content_path = __esm(() => {
1294
+ init_pathname();
1295
+ });
1296
+
894
1297
  // packages/core/src/ssr/lib/compute/content.ts
895
1298
  function checkStatusCache(statusCacheKey, maxAgeMs) {
896
1299
  if (maxAgeMs <= 0)
@@ -943,6 +1346,14 @@ async function handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusC
943
1346
  }
944
1347
  return result;
945
1348
  }
1349
+ async function runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) {
1350
+ if (streaming) {
1351
+ handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
1352
+ } else {
1353
+ await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
1354
+ }
1355
+ return set.status !== 200 ? set.status : undefined;
1356
+ }
946
1357
  async function computeContentData(pathname, options, searchParams, sharedSsrData) {
947
1358
  const streaming = options.streaming === true;
948
1359
  const ssrData = streaming && sharedSsrData ? sharedSsrData : {};
@@ -952,18 +1363,20 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
952
1363
  const maxAge = options.maxAge || 0;
953
1364
  const maxAgeMs = maxAge * 1000;
954
1365
  try {
955
- const parts = pathname.split("/").filter(Boolean);
956
- if (parts.length < 2) {
1366
+ const resolved = resolveContentPathParts(pathname);
1367
+ if (!resolved) {
957
1368
  return { data: ssrData, pendingTasks };
958
1369
  }
1370
+ const parts = [resolved.collection, resolved.name];
1371
+ if (resolved.locale)
1372
+ parts.splice(1, 0, resolved.locale);
959
1373
  const g = globalThis;
960
- const key = `${parts[0]}:${parts[1]}`;
961
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1374
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
962
1375
  const dataFn = exp?.ssr?.data;
963
1376
  if (typeof dataFn !== "function") {
964
1377
  return { data: ssrData, pendingTasks };
965
1378
  }
966
- const cacheKey = `content:${pathname}:${parts[1]}`;
1379
+ const cacheKey = `content:${pathname}:${resolved.name}:${resolved.locale || ""}`;
967
1380
  const statusCacheKey = `status:${cacheKey}`;
968
1381
  const cachedStatus = checkStatusCache(statusCacheKey, maxAgeMs);
969
1382
  if (cachedStatus && cachedStatus !== 200) {
@@ -976,22 +1389,13 @@ async function computeContentData(pathname, options, searchParams, sharedSsrData
976
1389
  }
977
1390
  const set = { status: 200 };
978
1391
  const task = createDataTask(dataFn, pathname, parts, searchParams, set, options.headers);
979
- if (streaming) {
980
- handleStreamingMode(ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks);
981
- if (set.status !== 200) {
982
- statusOverride = set.status;
983
- }
984
- } else {
985
- await handleNonStreamingMode(ssrData, pathname, task, cacheKey, statusCacheKey, maxAgeMs);
986
- if (set.status !== 200) {
987
- statusOverride = set.status;
988
- }
989
- }
1392
+ statusOverride = await runContentDataTask(streaming, ssrData, pathname, task, set, cacheKey, statusCacheKey, maxAgeMs, pendingTasks) ?? statusOverride;
990
1393
  } catch {}
991
1394
  return { data: ssrData, error, statusOverride, pendingTasks };
992
1395
  }
993
1396
  var init_content2 = __esm(() => {
994
1397
  init_cache();
1398
+ init_content_path();
995
1399
  init_imports();
996
1400
  });
997
1401
 
@@ -1190,7 +1594,8 @@ async function computeRouteData(pathname, clientDir, _cwd, isWatchMode, options,
1190
1594
  }).catch(() => {}));
1191
1595
  };
1192
1596
  try {
1193
- const pathnameOnly = pathname.split("?")[0];
1597
+ const resolved = resolveContentPathParts(pathname);
1598
+ const pathnameOnly = (resolved?.routingPathname || pathname).split("?")[0];
1194
1599
  const match = typeof routesModule?.matchRoute === "function" ? routesModule.matchRoute(pathnameOnly) : null;
1195
1600
  const route = match?.route;
1196
1601
  const params = match?.params || {};
@@ -1353,6 +1758,7 @@ function handleFlattenedResult(entries, set, ctx, cache, setError, setStatus) {
1353
1758
  }
1354
1759
  var init_routes = __esm(() => {
1355
1760
  init_cache();
1761
+ init_content_path();
1356
1762
  init_imports();
1357
1763
  });
1358
1764
 
@@ -1445,33 +1851,31 @@ var init_compute = __esm(() => {
1445
1851
  });
1446
1852
 
1447
1853
  // packages/core/src/ssr/lib/modules.ts
1448
- import { readdir as readdir2, stat as stat3 } from "node:fs/promises";
1854
+ import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
1449
1855
  async function importContentModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1450
1856
  try {
1451
- const pathOnly = pathname.split("?")[0].split("#")[0];
1452
- const parts = pathOnly.split("/").filter(Boolean);
1453
- if (parts.length < 2)
1857
+ const resolved = resolveContentPathParts(pathname);
1858
+ if (!resolved)
1454
1859
  return null;
1455
- const collection = parts[0];
1456
- const name = parts[1];
1457
- const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode);
1860
+ const { collection, name, locale } = resolved;
1861
+ const registryModule = await tryRegistryImport(collection, name, cwd, isWatchMode, locale);
1458
1862
  if (registryModule)
1459
1863
  return registryModule;
1460
1864
  const chunkModule = await tryChunkImport(collection, name, cwd, isWatchMode);
1461
1865
  if (chunkModule)
1462
1866
  return chunkModule;
1463
- const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode);
1867
+ const sourceModule = await trySourceImport(collection, name, clientDir, isWatchMode, locale);
1464
1868
  if (sourceModule)
1465
1869
  return sourceModule;
1466
1870
  } catch {}
1467
1871
  return null;
1468
1872
  }
1469
- async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1873
+ async function tryRegistryImport(collection, name, cwd, isWatchMode, locale) {
1470
1874
  try {
1471
1875
  const registryPath = join(cwd, ".reroute", "content.ts");
1472
1876
  const reg = await dynamicImport(registryPath, isWatchMode);
1473
1877
  const get = reg?.getContentEntry;
1474
- const entry = typeof get === "function" ? get(collection, name) : undefined;
1878
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
1475
1879
  const moduleUrl = entry?.module;
1476
1880
  if (!moduleUrl?.endsWith(".js"))
1477
1881
  return null;
@@ -1497,7 +1901,7 @@ async function tryRegistryImport(collection, name, cwd, isWatchMode) {
1497
1901
  async function tryChunkImport(collection, name, cwd, isWatchMode) {
1498
1902
  try {
1499
1903
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
1500
- const files = await readdir2(chunkDir);
1904
+ const files = await readdir3(chunkDir);
1501
1905
  const matches = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
1502
1906
  if (!matches.length)
1503
1907
  return null;
@@ -1517,23 +1921,23 @@ async function tryChunkImport(collection, name, cwd, isWatchMode) {
1517
1921
  } catch {}
1518
1922
  return null;
1519
1923
  }
1520
- async function trySourceImport(collection, name, clientDir, isWatchMode) {
1924
+ async function trySourceImport(collection, name, clientDir, isWatchMode, locale) {
1521
1925
  try {
1522
- const srcTsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
1523
- const srcTs = join(clientDir, "routes", collection, "content", `${name}.ts`);
1524
- let absSrc = null;
1525
- if (await Bun.file(srcTsx).exists()) {
1526
- absSrc = srcTsx;
1527
- } else if (await Bun.file(srcTs).exists()) {
1528
- absSrc = srcTs;
1926
+ const candidates = [];
1927
+ if (locale) {
1928
+ candidates.push(join(clientDir, "routes", collection, "content", locale, `${name}.tsx`), join(clientDir, "routes", collection, "content", locale, `${name}.ts`));
1529
1929
  }
1530
- if (absSrc) {
1531
- return await dynamicImport(absSrc, isWatchMode);
1930
+ candidates.push(join(clientDir, "routes", collection, "content", `${name}.tsx`), join(clientDir, "routes", collection, "content", `${name}.ts`));
1931
+ for (const absSrc of candidates) {
1932
+ if (await Bun.file(absSrc).exists()) {
1933
+ return await dynamicImport(absSrc, isWatchMode);
1934
+ }
1532
1935
  }
1533
1936
  } catch {}
1534
1937
  return null;
1535
1938
  }
1536
1939
  var init_modules = __esm(() => {
1940
+ init_content_path();
1537
1941
  init_imports();
1538
1942
  });
1539
1943
 
@@ -1564,14 +1968,11 @@ function touchModule(key) {
1564
1968
  }
1565
1969
  async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1566
1970
  try {
1567
- const pathOnly = pathname.split("?")[0].split("#")[0];
1568
- const parts = pathOnly.split("/").filter(Boolean);
1569
- if (parts.length < 2) {
1971
+ const resolved = resolveContentPathParts(pathname);
1972
+ if (!resolved) {
1570
1973
  return;
1571
1974
  }
1572
- const collection = parts[0];
1573
- const name = parts[1];
1574
- const key = `${collection}:${name}`;
1975
+ const { key } = resolved;
1575
1976
  const mod = await importContentModuleForPath(pathname, clientDir, cwd, isWatchMode);
1576
1977
  if (!mod) {
1577
1978
  return;
@@ -1591,6 +1992,7 @@ async function seedSSRModuleForPath(pathname, clientDir, cwd, isWatchMode) {
1591
1992
  }
1592
1993
  var MAX_SSR_MODULES = 200, moduleAccessOrder;
1593
1994
  var init_seed = __esm(() => {
1995
+ init_content_path();
1594
1996
  init_modules();
1595
1997
  moduleAccessOrder = [];
1596
1998
  });
@@ -1598,17 +2000,17 @@ var init_seed = __esm(() => {
1598
2000
  // packages/core/src/ssr/lib/data.ts
1599
2001
  function loadContentData(pathname, searchParams) {
1600
2002
  try {
1601
- const parts = pathname.split("/").filter(Boolean);
1602
- if (parts.length < 2)
2003
+ const resolved = resolveContentPathParts(pathname);
2004
+ if (!resolved)
1603
2005
  return null;
1604
- const key = `${parts[0]}:${parts[1]}`;
2006
+ const key = resolved.key;
1605
2007
  const g = globalThis;
1606
2008
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
1607
2009
  const dataFn = exp?.ssr?.data;
1608
2010
  if (typeof dataFn === "function") {
1609
2011
  const rawResult = dataFn({
1610
2012
  pathname,
1611
- params: { name: parts[1] },
2013
+ params: { name: resolved.name },
1612
2014
  searchParams
1613
2015
  });
1614
2016
  const result = processDataResult(rawResult, pathname, pathname);
@@ -1771,6 +2173,7 @@ function createLazyMarker(pathname, key) {
1771
2173
  };
1772
2174
  }
1773
2175
  var init_data = __esm(() => {
2176
+ init_content_path();
1774
2177
  init_imports();
1775
2178
  init_seed();
1776
2179
  });
@@ -1971,14 +2374,14 @@ function replaceAppRoot(html, appHtml, appId) {
1971
2374
  var init_html = () => {};
1972
2375
 
1973
2376
  // packages/core/src/og/discovery.ts
1974
- import { readdir as readdir3 } from "node:fs/promises";
2377
+ import { readdir as readdir4 } from "node:fs/promises";
1975
2378
  import { pathToFileURL as pathToFileURL4 } from "node:url";
1976
2379
  function isCatchAllRouteDirectory(name) {
1977
2380
  return CATCH_ALL_ROUTE_DIRECTORY.test(name);
1978
2381
  }
1979
2382
  async function listDynamicRouteDirectories(baseDir) {
1980
2383
  try {
1981
- const entries = await readdir3(baseDir, { withFileTypes: true });
2384
+ const entries = await readdir4(baseDir, { withFileTypes: true });
1982
2385
  return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => DYNAMIC_ROUTE_DIRECTORY.test(name)).sort((a, b) => {
1983
2386
  const aIsCatchAll = isCatchAllRouteDirectory(a);
1984
2387
  const bIsCatchAll = isCatchAllRouteDirectory(b);
@@ -1998,7 +2401,7 @@ async function collectOGCandidatePaths(currentDir, parts, depth, pathsToTry, all
1998
2401
  }
1999
2402
  const literalDir = join(currentDir, parts[depth]);
2000
2403
  try {
2001
- await readdir3(literalDir);
2404
+ await readdir4(literalDir);
2002
2405
  await collectOGCandidatePaths(literalDir, parts, depth + 1, pathsToTry, true);
2003
2406
  } catch {}
2004
2407
  const dynamicDirs = await listDynamicRouteDirectories(currentDir);
@@ -2153,7 +2556,9 @@ async function extractPageMetadata(pathname, clientDir, cwd, isWatchMode, curren
2153
2556
  let perPageHead = "";
2154
2557
  let pageLang;
2155
2558
  let statusOverride = currentStatusOverride;
2156
- await extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, (head) => {
2559
+ const resolved = resolveContentPathParts(pathname);
2560
+ const routingPathname = resolved?.routingPathname || pathname;
2561
+ await extractLayoutMetadata(routingPathname, clientDir, cwd, isWatchMode, (head) => {
2157
2562
  perPageHead += head;
2158
2563
  }, (lang) => {
2159
2564
  if (!pageLang)
@@ -2188,8 +2593,8 @@ ${ogMetaTags}`;
2188
2593
  perPageHead += head;
2189
2594
  }, (lang) => {
2190
2595
  pageLang = lang;
2191
- }, ogConfig, titleSuffix);
2192
- const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
2596
+ }, ogConfig, titleSuffix, baseUrl);
2597
+ const routeResult = await extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2193
2598
  perPageHead += routeResult.head;
2194
2599
  if (routeResult.lang)
2195
2600
  pageLang = routeResult.lang;
@@ -2229,14 +2634,42 @@ async function extractLayoutMetadata(pathname, clientDir, cwd, isWatchMode, addH
2229
2634
  }
2230
2635
  } catch {}
2231
2636
  }
2232
- function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix) {
2637
+ function resolveContentLocales(collection, name, fallbackLocales) {
2638
+ const g = globalThis;
2639
+ const byCollection = g.__REROUTE_COLLECTIONS__;
2640
+ const items = byCollection?.[collection] || [];
2641
+ const localesForEntry = [
2642
+ ...new Set(items.filter((it) => it.name === name && it.locale).map((it) => String(it.locale)))
2643
+ ];
2644
+ return localesForEntry.length > 0 ? localesForEntry : fallbackLocales;
2645
+ }
2646
+ function addContentI18nHead(addHead, baseUrl, collection, name, locales, defaultLocale, activeLocale) {
2647
+ if (locales.length > 1) {
2648
+ addHead(`
2649
+ ${hreflangTags(baseUrl, collection, name, locales, defaultLocale)}`);
2650
+ }
2651
+ addHead(`
2652
+ <meta property="og:locale" content="${toOgLocale(activeLocale)}" />`);
2653
+ for (const loc of locales) {
2654
+ if (loc === activeLocale)
2655
+ continue;
2656
+ addHead(`
2657
+ <meta property="og:locale:alternate" content="${toOgLocale(loc)}" />`);
2658
+ }
2659
+ const canonicalPath = localizeContentHref(collection, name, activeLocale, defaultLocale);
2660
+ const base = baseUrl.replace(/\/$/, "");
2661
+ addHead(`
2662
+ <link rel="canonical" href="${base}${canonicalPath}" />`);
2663
+ addHead(`
2664
+ <meta property="og:url" content="${base}${canonicalPath}" />`);
2665
+ }
2666
+ function extractContentMetadata(pathname, addHead, setLang, ogConfig, titleSuffix, baseUrl) {
2233
2667
  try {
2234
- const parts = pathname.split("/").filter(Boolean);
2235
- if (parts.length < 2)
2668
+ const resolved = resolveContentPathParts(pathname);
2669
+ if (!resolved)
2236
2670
  return;
2237
- const key = `${parts[0]}:${parts[1]}`;
2238
2671
  const g = globalThis;
2239
- const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2672
+ const exp = g.__REROUTE_SSR_EXPORTS__?.[resolved.key];
2240
2673
  const meta = exp?.meta;
2241
2674
  const ssr = exp?.ssr;
2242
2675
  addHead(buildHeadFromMeta(meta, ogConfig, titleSuffix));
@@ -2247,15 +2680,20 @@ ${ssrHead}`);
2247
2680
  const lang = extractSSRLang(ssr);
2248
2681
  if (lang)
2249
2682
  setLang(lang);
2683
+ const i18n = g.__REROUTE_I18N__;
2684
+ if (i18n?.enabled && baseUrl && i18n.locales && i18n.locales.length > 1) {
2685
+ const locales = resolveContentLocales(resolved.collection, resolved.name, i18n.locales);
2686
+ addContentI18nHead(addHead, baseUrl, resolved.collection, resolved.name, locales, i18n.defaultLocale || "en-US", i18n.locale || i18n.defaultLocale || "en-US");
2687
+ }
2250
2688
  } catch {}
2251
2689
  }
2252
2690
  function getContentMetaFromExports(pathname) {
2253
2691
  try {
2254
- const parts = pathname.split("/").filter(Boolean);
2255
- if (parts.length < 2)
2692
+ const resolved = resolveContentPathParts(pathname);
2693
+ if (!resolved)
2256
2694
  return;
2257
- const slug = parts[1];
2258
- const key = `${parts[0]}:${slug}`;
2695
+ const slug = resolved.name;
2696
+ const key = resolved.key;
2259
2697
  const g = globalThis;
2260
2698
  const exp = g.__REROUTE_SSR_EXPORTS__?.[key];
2261
2699
  const meta = exp?.meta;
@@ -2269,9 +2707,10 @@ function getContentMetaFromExports(pathname) {
2269
2707
  return;
2270
2708
  }
2271
2709
  }
2272
- async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix) {
2710
+ async function extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2273
2711
  const pathnameOnly = pathname.split("?")[0];
2274
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
2712
+ const matchPath = (routingPathname || pathname).split("?")[0];
2713
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2275
2714
  const r = match?.route;
2276
2715
  if (!r) {
2277
2716
  return { head: "", statusOverride: statusOverride || 404 };
@@ -2311,7 +2750,7 @@ ${ssrHead}`;
2311
2750
  }
2312
2751
  return { head: "", statusOverride };
2313
2752
  }
2314
- async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix) {
2753
+ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, currentStatusOverride, ssrData, ogConfig, titleSuffix, routingPathname) {
2315
2754
  let head = "";
2316
2755
  let lang;
2317
2756
  let statusOverride = currentStatusOverride;
@@ -2321,14 +2760,14 @@ async function extractRouteMetadata(pathname, clientDir, cwd, isWatchMode, curre
2321
2760
  if (!m) {
2322
2761
  return { head, lang, statusOverride: statusOverride || 404 };
2323
2762
  }
2324
- const pathnameOnly = pathname.split("?")[0];
2325
- const match = typeof m.matchRoute === "function" ? m.matchRoute(pathnameOnly) : null;
2763
+ const matchPath = (routingPathname || pathname).split("?")[0];
2764
+ const match = typeof m.matchRoute === "function" ? m.matchRoute(matchPath) : null;
2326
2765
  const r = match?.route;
2327
2766
  if (!r) {
2328
2767
  statusOverride = statusOverride || 404;
2329
2768
  }
2330
2769
  if (r && typeof r.path === "string") {
2331
- const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix);
2770
+ const result = await extractMatchedRouteMetadata(pathname, clientDir, m, isWatchMode, statusOverride, ssrData, ogConfig, titleSuffix, routingPathname);
2332
2771
  head = result.head;
2333
2772
  lang = result.lang;
2334
2773
  statusOverride = result.statusOverride;
@@ -2503,34 +2942,34 @@ function applyTitleSuffixToHead(headContent, titleSuffix) {
2503
2942
  }
2504
2943
  var init_metadata2 = __esm(() => {
2505
2944
  init_metadata();
2945
+ init_pathname();
2506
2946
  init_meta();
2507
2947
  init_compute();
2948
+ init_content_path();
2508
2949
  init_imports();
2509
2950
  });
2510
2951
 
2511
2952
  // packages/core/src/ssr/lib/preload.ts
2512
- import { readdir as readdir4, stat as stat4 } from "node:fs/promises";
2953
+ import { readdir as readdir5, stat as stat4 } from "node:fs/promises";
2513
2954
  async function preloadContentModule(pathname, clientDir, cwd, isWatchMode) {
2514
2955
  let extraHead = "";
2515
2956
  let hydrationScript = "";
2516
2957
  let statusOverride;
2517
2958
  try {
2518
- const pathOnly = pathname.split("?")[0].split("#")[0];
2519
- const parts = pathOnly.split("/").filter(Boolean);
2520
- if (parts.length < 2) {
2959
+ const { resolveContentPathParts: resolveContentPathParts2 } = await Promise.resolve().then(() => (init_content_path(), exports_content_path));
2960
+ const resolved = resolveContentPathParts2(pathname);
2961
+ if (!resolved) {
2521
2962
  return { extraHead, hydrationScript };
2522
2963
  }
2523
- const collection = parts[0];
2524
- const name = parts[1];
2964
+ const { collection, name, key, locale } = resolved;
2525
2965
  if (!await isContentCollection(clientDir, collection)) {
2526
2966
  return { extraHead, hydrationScript };
2527
2967
  }
2528
- const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode);
2968
+ const modulePath = await findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale);
2529
2969
  if (!modulePath) {
2530
2970
  statusOverride = 404;
2531
2971
  return { extraHead, hydrationScript, statusOverride };
2532
2972
  }
2533
- const key = `${collection}:${name}`;
2534
2973
  extraHead += `
2535
2974
  <link rel="modulepreload" href="${modulePath}" />`;
2536
2975
  hydrationScript += `<script>
@@ -2572,12 +3011,12 @@ async function findLatestCandidate(chunkDir, candidates) {
2572
3011
  }
2573
3012
  return latest;
2574
3013
  }
2575
- async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode) {
3014
+ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMode, locale) {
2576
3015
  try {
2577
3016
  const registryPath = join(cwd, ".reroute", "content.ts");
2578
3017
  const reg = await dynamicImport(registryPath, isWatchMode);
2579
3018
  const get = reg?.getContentEntry;
2580
- const entry = typeof get === "function" ? get(collection, name) : undefined;
3019
+ const entry = typeof get === "function" ? get(collection, name, locale) : undefined;
2581
3020
  const moduleUrl = entry?.module;
2582
3021
  if (moduleUrl?.endsWith(".js")) {
2583
3022
  return moduleUrl;
@@ -2585,7 +3024,7 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2585
3024
  } catch {}
2586
3025
  try {
2587
3026
  const chunkDir = join(cwd, ".reroute", "chunks", collection);
2588
- const files = await readdir4(chunkDir);
3027
+ const files = await readdir5(chunkDir);
2589
3028
  const candidates = files.filter((n) => n.startsWith(`${name}.`) && n.endsWith(".js"));
2590
3029
  if (candidates.length) {
2591
3030
  const latest = await findLatestCandidate(chunkDir, candidates);
@@ -2593,13 +3032,18 @@ async function findContentModulePath(collection, name, clientDir, cwd, isWatchMo
2593
3032
  }
2594
3033
  } catch {}
2595
3034
  try {
2596
- const tsx = join(clientDir, "routes", collection, "content", `${name}.tsx`);
2597
- const ts = join(clientDir, "routes", collection, "content", `${name}.ts`);
2598
- if (await Bun.file(tsx).exists()) {
2599
- return `/${join("routes", collection, "content", `${name}.tsx`).replace(/\\+/g, "/")}`;
2600
- }
2601
- if (await Bun.file(ts).exists()) {
2602
- return `/${join("routes", collection, "content", `${name}.ts`).replace(/\\+/g, "/")}`;
3035
+ const contentRoot = join(clientDir, "routes", collection, "content");
3036
+ const candidates = locale ? [
3037
+ join(contentRoot, locale, `${name}.tsx`),
3038
+ join(contentRoot, locale, `${name}.ts`),
3039
+ join(contentRoot, `${name}.tsx`),
3040
+ join(contentRoot, `${name}.ts`)
3041
+ ] : [join(contentRoot, `${name}.tsx`), join(contentRoot, `${name}.ts`)];
3042
+ for (const abs of candidates) {
3043
+ if (await Bun.file(abs).exists()) {
3044
+ const rel = abs.startsWith(clientDir) ? abs.slice(clientDir.length).replace(/^[/\\]/, "") : join("routes", collection, "content", `${name}.tsx`);
3045
+ return `/${rel.replace(/\\+/g, "/")}`;
3046
+ }
2603
3047
  }
2604
3048
  } catch {}
2605
3049
  return null;
@@ -2793,11 +3237,18 @@ function duplicateQueryParamData(ssrData) {
2793
3237
  return duplicated;
2794
3238
  }
2795
3239
  function getContentKeyFromPathname(pathname) {
2796
- const pathOnly = pathname.split("?")[0].split("#")[0];
2797
- const parts = pathOnly.split("/").filter(Boolean);
2798
- if (parts.length < 2)
2799
- return null;
2800
- return `${parts[0]}:${parts[1]}`;
3240
+ return resolveContentPathParts(pathname)?.key ?? null;
3241
+ }
3242
+ function createI18nScript(i18n) {
3243
+ if (!i18n?.enabled)
3244
+ return "";
3245
+ try {
3246
+ const json = escapeJsonForScript(JSON.stringify(i18n));
3247
+ return `<script>(function(){try{var w=(typeof window!=='undefined'?window:globalThis);w.__REROUTE_I18N__=${json};}catch(e){}})();</script>`;
3248
+ } catch (error) {
3249
+ console.error("[reroute] Failed to serialize i18n hydration:", error);
3250
+ return "";
3251
+ }
2801
3252
  }
2802
3253
  function createContentCaptureScript() {
2803
3254
  return `<script>(function(){try{var w=window;w.__REROUTE_INITIAL_HYDRATION__=true;var h={};document.querySelectorAll('[data-reroute-key]').forEach(function(el){var k=el.getAttribute('data-reroute-key');if(k)h[k]=el.innerHTML;});w.__REROUTE_CONTENT_HTML__=h;var r=document.querySelector('[data-reroute-route="ssr"]');if(r)w.__REROUTE_ROUTE_HTML__=r.innerHTML;}catch(e){}})();</script>`;
@@ -2873,13 +3324,14 @@ function createSSRPathnameScript(pathname) {
2873
3324
  return "";
2874
3325
  }
2875
3326
  }
2876
- function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix) {
3327
+ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n) {
2877
3328
  let script = "";
2878
3329
  script += createContentCaptureScript();
2879
3330
  script += createSSRPathnameScript(pathname);
2880
3331
  if (pathname) {
2881
3332
  script += createSSRExportsScript(pathname);
2882
3333
  }
3334
+ script += createI18nScript(i18n);
2883
3335
  script += createTelemetryConfigScript(browserTelemetryConfig);
2884
3336
  script += createTraceContextScript(traceContext);
2885
3337
  script += createTitleSuffixScript(titleSuffix);
@@ -2899,7 +3351,9 @@ function createDataScript(ssrData, errorMsg, pathname, browserTelemetryConfig, t
2899
3351
  }
2900
3352
  return script;
2901
3353
  }
2902
- var init_data2 = () => {};
3354
+ var init_data2 = __esm(() => {
3355
+ init_content_path();
3356
+ });
2903
3357
 
2904
3358
  // packages/core/src/ssr/lib/scripts/feeds.ts
2905
3359
  function createFeedsScript() {
@@ -3127,10 +3581,10 @@ function generateCollectionScripts(byCollectionForSSR) {
3127
3581
  } catch {}
3128
3582
  return "";
3129
3583
  }
3130
- function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix) {
3584
+ function generateHydrationScripts(ssrData, isWatchMode, ssrError, collectionScripts = "", preloadScript = "", bundleUrl = "", pathname = "", browserTelemetryConfig, traceContext, titleSuffix, i18n) {
3131
3585
  let scripts = preloadScript;
3132
3586
  scripts += collectionScripts;
3133
- scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix);
3587
+ scripts += createDataScript(ssrData, isWatchMode ? ssrError : undefined, pathname, browserTelemetryConfig, traceContext, titleSuffix, i18n);
3134
3588
  scripts += createFeedsScript();
3135
3589
  scripts += createLlmsScript();
3136
3590
  if (bundleUrl) {
@@ -3293,7 +3747,7 @@ async function renderSSRDocument(options) {
3293
3747
  const browserTelemetryConfig = serializeBrowserTelemetryConfig2(browserTelemetryConfigRaw);
3294
3748
  const traceContext = getTraceContextForInjection(options.parentSpan);
3295
3749
  const hydrationScript = await withSpan("ssr.generate.scripts", async (span) => {
3296
- const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix);
3750
+ const script = generateHydrationScripts(setup2.ssrData, isWatchMode, setup2.ssrError, collectionScripts, setup2.preloadHydrationScript, bundleUrl, pathname, browserTelemetryConfig, traceContext, options.titleSuffix, options.i18n);
3297
3751
  span.setAttribute("reroute.scripts.size", script.length);
3298
3752
  return script;
3299
3753
  });
@@ -3311,12 +3765,13 @@ async function renderSSRDocument(options) {
3311
3765
  dedent(setup2.metadataResult.perPageHead)
3312
3766
  ].filter(Boolean).join(`
3313
3767
  `));
3768
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
3314
3769
  const html2 = withSpanSync("ssr.apply.template", (span) => {
3315
3770
  const result = applyIndexTemplate(baseTemplate, appHtml, {
3316
3771
  head: [setup2.inlineStyleTag, combinedHead].filter(Boolean).join(`
3317
3772
  `),
3318
3773
  hydrationScript,
3319
- lang: setup2.metadataResult.pageLang || lang,
3774
+ lang: setup2.metadataResult.pageLang || i18nLocale || lang,
3320
3775
  appId
3321
3776
  });
3322
3777
  span.setAttributes({
@@ -4494,6 +4949,55 @@ async function discoverRoutes(cwd, isWatchMode) {
4494
4949
  }
4495
4950
  return { staticRoutes, dynamicRoutes };
4496
4951
  }
4952
+ function groupNamedCollectionItems(items) {
4953
+ const byName = new Map;
4954
+ for (const item of items) {
4955
+ const name = String(item.name || "");
4956
+ if (!name)
4957
+ continue;
4958
+ const list = byName.get(name) || [];
4959
+ list.push(item);
4960
+ byName.set(name, list);
4961
+ }
4962
+ return byName;
4963
+ }
4964
+ function resolveItemHref(collection, item, defaultLocale, localize) {
4965
+ const name = String(item.name || "");
4966
+ const locale = item.locale || defaultLocale;
4967
+ if (name)
4968
+ return localize(collection, name, locale, defaultLocale);
4969
+ return String(item.href || "");
4970
+ }
4971
+ function buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localize) {
4972
+ const locales = [
4973
+ ...new Set(variants.map((v) => v.locale || defaultLocale).filter(Boolean))
4974
+ ];
4975
+ const alternates = locales.length > 1 ? [
4976
+ ...locales.map((locale) => ({
4977
+ hreflang: locale,
4978
+ href: `${base}${localize(collection, name, locale, defaultLocale)}`
4979
+ })),
4980
+ {
4981
+ hreflang: "x-default",
4982
+ href: `${base}${localize(collection, name, defaultLocale, defaultLocale)}`
4983
+ }
4984
+ ] : undefined;
4985
+ const entries = [];
4986
+ for (const item of variants) {
4987
+ const href = resolveItemHref(collection, item, defaultLocale, localize);
4988
+ if (!href)
4989
+ continue;
4990
+ const meta = item.meta || {};
4991
+ entries.push({
4992
+ loc: `${base}${href}`,
4993
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4994
+ changefreq: "weekly",
4995
+ priority: 0.7,
4996
+ alternates
4997
+ });
4998
+ }
4999
+ return entries;
5000
+ }
4497
5001
  async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl) {
4498
5002
  const entries = [];
4499
5003
  try {
@@ -4505,18 +5009,26 @@ async function discoverCollectionContent(cwd, collection, isWatchMode, baseUrl)
4505
5009
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4506
5010
  const mod = await import(url);
4507
5011
  const items = mod.items || [];
5012
+ const defaultLocale = mod.defaultLocale || "en-US";
5013
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
5014
+ const base = baseUrl.replace(/\/$/, "");
5015
+ const byName = groupNamedCollectionItems(items);
5016
+ for (const [name, variants] of byName) {
5017
+ entries.push(...buildNamedCollectionSitemapEntries(collection, name, variants, defaultLocale, base, localizeContentHref2));
5018
+ }
4508
5019
  for (const item of items) {
5020
+ if (item.name)
5021
+ continue;
4509
5022
  const href = String(item.href || "");
5023
+ if (!href)
5024
+ continue;
4510
5025
  const meta = item.meta || {};
4511
- if (href) {
4512
- const entry = {
4513
- loc: `${baseUrl}${href}`,
4514
- lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
4515
- changefreq: "weekly",
4516
- priority: 0.7
4517
- };
4518
- entries.push(entry);
4519
- }
5026
+ entries.push({
5027
+ loc: `${base}${href}`,
5028
+ lastmod: meta.date ? new Date(meta.date).toISOString() : undefined,
5029
+ changefreq: "weekly",
5030
+ priority: 0.7
5031
+ });
4520
5032
  }
4521
5033
  } catch (error) {
4522
5034
  console.error(`[reroute] Failed to discover collection ${collection}:`, error);
@@ -4811,11 +5323,17 @@ async function discoverCollectionFeedItems(cwd, collection, isWatchMode, baseUrl
4811
5323
  const url = `${Bun.pathToFileURL(collectionFile).href}${isWatchMode ? `?t=${Date.now()}` : ""}`;
4812
5324
  const mod = await import(url);
4813
5325
  const contentItems = mod.items || [];
5326
+ const defaultLocale = mod.defaultLocale || "en-US";
5327
+ const { localizeContentHref: localizeContentHref2 } = await Promise.resolve().then(() => (init_pathname(), exports_pathname));
4814
5328
  for (const item of contentItems) {
4815
- const href = String(item.href || "");
5329
+ const name = String(item.name || "");
5330
+ const locale = item.locale || defaultLocale;
5331
+ const href = name ? localizeContentHref2(collection, name, locale, defaultLocale) : String(item.href || "");
4816
5332
  const meta = item.meta || {};
4817
5333
  if (href) {
4818
- items.push(createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig));
5334
+ const rssItem = createRSSItemFromMetaAndHref(href, meta, baseUrl, item, rssConfig);
5335
+ rssItem.locale = locale;
5336
+ items.push(rssItem);
4819
5337
  }
4820
5338
  }
4821
5339
  } catch (error) {
@@ -5081,7 +5599,7 @@ function generateRSSXML(feed, options = {}) {
5081
5599
  <link>${escapeXml(feed.link)}</link>
5082
5600
  <description>${escapeXml(feed.description)}</description>
5083
5601
  <lastBuildDate>${lastBuildDate}</lastBuildDate>
5084
- <language>en</language>
5602
+ <language>${escapeXml(feed.language || "en")}</language>
5085
5603
  <atom:link href="${escapeXml(feed.link)}" rel="self" type="application/rss+xml" />
5086
5604
  ${itemsXML}
5087
5605
  </channel>
@@ -5243,6 +5761,7 @@ async function generateSitemap(discovery4, baseUrl, options = {}) {
5243
5761
  return createSingleSitemap(allEntries);
5244
5762
  }
5245
5763
  function generateSitemapXML(entries) {
5764
+ const hasAlternates = entries.some((e) => e.alternates && e.alternates.length > 0);
5246
5765
  const urlsetEntries = entries.map((entry) => {
5247
5766
  let xml = ` <url>
5248
5767
  <loc>${escapeXml2(entry.loc)}</loc>`;
@@ -5258,13 +5777,20 @@ function generateSitemapXML(entries) {
5258
5777
  xml += `
5259
5778
  <priority>${entry.priority.toFixed(1)}</priority>`;
5260
5779
  }
5780
+ if (entry.alternates?.length) {
5781
+ for (const alt of entry.alternates) {
5782
+ xml += `
5783
+ <xhtml:link rel="alternate" hreflang="${escapeXml2(alt.hreflang)}" href="${escapeXml2(alt.href)}" />`;
5784
+ }
5785
+ }
5261
5786
  xml += `
5262
5787
  </url>`;
5263
5788
  return xml;
5264
5789
  }).join(`
5265
5790
  `);
5791
+ const xhtmlNs = hasAlternates ? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"' : "";
5266
5792
  return `<?xml version="1.0" encoding="UTF-8"?>
5267
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5793
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${xhtmlNs}>
5268
5794
  ${urlsetEntries}
5269
5795
  </urlset>`;
5270
5796
  }
@@ -5362,7 +5888,8 @@ async function renderSSRDocumentStream(options) {
5362
5888
  ...parts
5363
5889
  };
5364
5890
  });
5365
- const pageLang = setup2.metadataResult.pageLang || lang;
5891
+ const i18nLocale = options.i18n?.enabled ? String(options.i18n.locale || "") : "";
5892
+ const pageLang = setup2.metadataResult.pageLang || i18nLocale || lang;
5366
5893
  let headWithLang = htmlHead.replace(/<html([^>]*)>/i, (_m, attrs) => {
5367
5894
  const hasLang = /(^|\s)lang\s*=/.test(attrs);
5368
5895
  const newAttrs = hasLang ? attrs.replace(/lang\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/i, `lang="${pageLang}"`) : `${attrs} lang="${pageLang}"`;
@@ -5405,7 +5932,8 @@ async function renderSSRDocumentStream(options) {
5405
5932
  parentSpan: options.parentSpan,
5406
5933
  timeout: options.timeout,
5407
5934
  ssrExport: setup2.metadataResult.ssrExport,
5408
- titleSuffix: options.titleSuffix
5935
+ titleSuffix: options.titleSuffix,
5936
+ i18n: options.i18n
5409
5937
  });
5410
5938
  if (debug)
5411
5939
  console.log(`[renderStream] ${pathname} - returning stream: ${(performance.now() - t0).toFixed(2)}ms`);
@@ -5518,7 +6046,7 @@ function buildStreamingScripts(ctx) {
5518
6046
  ssrDataSnapshot[key] = value;
5519
6047
  }
5520
6048
  }
5521
- const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix);
6049
+ const dataScript = createDataScript(ssrDataSnapshot, ctx.isWatchMode ? ctx.ssrError : undefined, ctx.pathname, ctx.browserTelemetryConfig, ctx.traceContext, ctx.titleSuffix, ctx.i18n);
5522
6050
  const feedsScript = createFeedsScript();
5523
6051
  const llmsScript = createLlmsScript();
5524
6052
  let allScripts = dataScript + feedsScript + llmsScript + collectionScripts + ctx.preloadHydrationScript;
@@ -5650,6 +6178,7 @@ var init_src = __esm(() => {
5650
6178
  init_bundler();
5651
6179
  init_config();
5652
6180
  init_content();
6181
+ init_i18n();
5653
6182
  init_llms();
5654
6183
  init_og();
5655
6184
  init_robots();
@@ -5666,6 +6195,7 @@ init_core();
5666
6195
 
5667
6196
  export {
5668
6197
  toPromise,
6198
+ toOgLocale,
5669
6199
  toBytes,
5670
6200
  stripStart,
5671
6201
  stripHtmlTags,
@@ -5677,6 +6207,7 @@ export {
5677
6207
  setGlobalSSRData,
5678
6208
  setGlobalCollections,
5679
6209
  seedSSRModuleForPath,
6210
+ resolveLocale,
5680
6211
  resetSSRAccessTracking,
5681
6212
  renderSSRDocumentStream,
5682
6213
  renderSSRDocument,
@@ -5685,9 +6216,17 @@ export {
5685
6216
  processCollections,
5686
6217
  preloadContentModule,
5687
6218
  performSSRSetup,
6219
+ parseCookieValue,
6220
+ parseContentPath,
6221
+ normalizePathname,
6222
+ negotiateLocale,
5688
6223
  mergeLayoutData,
5689
6224
  matchRoutes,
5690
6225
  matchRoutePattern,
6226
+ lookupMessage,
6227
+ looksLikeLocale,
6228
+ localizeHref,
6229
+ localizeContentHref,
5691
6230
  loadRoutesModule,
5692
6231
  loadOGImageComponent,
5693
6232
  loadIndexHtml,
@@ -5700,11 +6239,13 @@ export {
5700
6239
  inlineTailwindCSS,
5701
6240
  importContentModuleForPath,
5702
6241
  htmlToMarkdown,
6242
+ hreflangTags,
5703
6243
  gzipIfAccepted,
5704
6244
  getSitemapExcludedRoutes,
5705
6245
  getRedirectSourcePaths,
5706
6246
  getPatternDepth,
5707
6247
  getMimeType,
6248
+ getMessage,
5708
6249
  getMatchingLayouts,
5709
6250
  getContentMeta,
5710
6251
  generateSitemapXML,
@@ -5721,6 +6262,7 @@ export {
5721
6262
  generateContentHash,
5722
6263
  generateCollectionScripts,
5723
6264
  generateAtomXML,
6265
+ formatMessage,
5724
6266
  formatAsText,
5725
6267
  formatAsMarkdown,
5726
6268
  findOGImageForPath,
@@ -5740,10 +6282,12 @@ export {
5740
6282
  discoverRoutes,
5741
6283
  discoverRobotsData,
5742
6284
  discoverRSSData,
6285
+ discoverI18n,
5743
6286
  discoverCollections,
5744
6287
  discoverCollectionContent,
5745
6288
  defineConfig,
5746
6289
  deduplicateMetaTags,
6290
+ createTranslator,
5747
6291
  createLlmsScript,
5748
6292
  createLayoutDataKey,
5749
6293
  createLayoutCacheKey,
@@ -5751,6 +6295,8 @@ export {
5751
6295
  createDataScript,
5752
6296
  createCollectionScript,
5753
6297
  createBundlePreload,
6298
+ contentNegotiateRedirect,
6299
+ contentKey,
5754
6300
  computeSSRDataForPath,
5755
6301
  computeSSRData,
5756
6302
  computeRouteData,
@@ -5759,6 +6305,7 @@ export {
5759
6305
  compressStreamIfAccepted,
5760
6306
  compareRouteSpecificity,
5761
6307
  cleanupPathScopedSSRData,
6308
+ buildLocaleCookie,
5762
6309
  buildHeadFromMeta,
5763
6310
  blockSearchCrawlers,
5764
6311
  blockAITrainingCrawlers,
@@ -5774,7 +6321,10 @@ export {
5774
6321
  SEARCH_CRAWLERS,
5775
6322
  LRUCache,
5776
6323
  DefaultOGTemplate,
6324
+ DEFAULT_LOCALES_DIR,
6325
+ DEFAULT_LOCALE,
6326
+ DEFAULT_COOKIE,
5777
6327
  AI_CRAWLERS
5778
6328
  };
5779
6329
 
5780
- //# debugId=87E733A691C556DC64756E2164756E21
6330
+ //# debugId=C4F445FA1614E66D64756E2164756E21