@blinkk/root 1.0.0-beta.5 → 1.0.0-beta.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/render.js CHANGED
@@ -1,18 +1,341 @@
1
- import {
2
- htmlMinify,
3
- htmlPretty,
4
- parseTagNames
5
- } from "./chunk-WVRC46JG.js";
6
1
  import {
7
2
  HTML_CONTEXT,
8
3
  I18N_CONTEXT,
9
4
  REQUEST_CONTEXT,
10
5
  getTranslations
11
- } from "./chunk-WTSNW7BB.js";
6
+ } from "./chunk-HQXIHYDN.js";
7
+ import {
8
+ htmlMinify,
9
+ htmlPretty,
10
+ parseTagNames
11
+ } from "./chunk-DXD5LKU3.js";
12
12
 
13
13
  // src/render/render.tsx
14
14
  import renderToString from "preact-render-to-string";
15
15
 
16
+ // src/core/pages/ErrorPage.tsx
17
+ import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
18
+ var STYLES = `
19
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
20
+
21
+ :root {
22
+ --font-family-text: "Inter", sans-serif;
23
+ }
24
+
25
+ body {
26
+ font-family: var(--font-family-text);
27
+ background: #F5F5F5;
28
+ padding: 40px 16px;
29
+ }
30
+
31
+ .root {
32
+ max-width: 1200px;
33
+ margin: 0 auto;
34
+ }
35
+
36
+ .root.align-center {
37
+ text-align: center;
38
+ }
39
+
40
+ h1.title {
41
+ margin-top: 0;
42
+ margin-bottom: 24px;
43
+ }
44
+
45
+ p.message {
46
+ margin-top: 0;
47
+ margin-bottom: 0;
48
+ }
49
+
50
+ .box {
51
+ font-size: 16px;
52
+ line-height: 1.5;
53
+ padding: 16px;
54
+ border-radius: 12px;
55
+ background: #ffffff;
56
+ }
57
+
58
+ pre.box {
59
+ white-space: pre-wrap;
60
+ }
61
+
62
+ @media (min-width: 500px) {
63
+ body {
64
+ padding: 40px;
65
+ }
66
+
67
+ .box {
68
+ padding: 24px;
69
+ }
70
+ }
71
+
72
+ @media (min-width: 1024px) {
73
+ body {
74
+ padding: 100px;
75
+ }
76
+ }
77
+ `;
78
+ function ErrorPage(props) {
79
+ const { code, message } = props;
80
+ const title = props.title || code;
81
+ return /* @__PURE__ */ jsxs(Fragment, {
82
+ children: [
83
+ /* @__PURE__ */ jsx("style", {
84
+ dangerouslySetInnerHTML: { __html: STYLES }
85
+ }),
86
+ /* @__PURE__ */ jsxs("div", {
87
+ className: `root align-${props.align || "left"}`,
88
+ children: [
89
+ /* @__PURE__ */ jsx("h1", {
90
+ className: "title",
91
+ children: title
92
+ }),
93
+ message && /* @__PURE__ */ jsx("p", {
94
+ className: "message",
95
+ children: message
96
+ }),
97
+ props.children
98
+ ]
99
+ })
100
+ ]
101
+ });
102
+ }
103
+
104
+ // src/core/pages/DevErrorPage.tsx
105
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
106
+ function DevErrorPage(props) {
107
+ var _a;
108
+ const req = props.req;
109
+ const err = props.error;
110
+ const route = props.route;
111
+ const routeParams = props.routeParams;
112
+ let errMsg = String(err);
113
+ if (err && err.stack) {
114
+ errMsg = err.stack.replace(/\(.*node_modules/g, "(node_modules").replace(/at \/.*node_modules/g, "at node_modules");
115
+ if ((_a = req.rootConfig) == null ? void 0 : _a.rootDir) {
116
+ errMsg = errMsg.replaceAll(req.rootConfig.rootDir, "<root>");
117
+ }
118
+ if (process.env.HOME) {
119
+ errMsg = errMsg.replaceAll(process.env.HOME, "$HOME");
120
+ }
121
+ }
122
+ return /* @__PURE__ */ jsxs2(ErrorPage, {
123
+ code: 500,
124
+ title: "Something went wrong",
125
+ children: [
126
+ errMsg && /* @__PURE__ */ jsxs2(Fragment2, {
127
+ children: [
128
+ /* @__PURE__ */ jsx2("h2", {
129
+ children: "Error"
130
+ }),
131
+ /* @__PURE__ */ jsx2("pre", {
132
+ className: "box",
133
+ children: /* @__PURE__ */ jsx2("code", {
134
+ children: errMsg
135
+ })
136
+ })
137
+ ]
138
+ }),
139
+ /* @__PURE__ */ jsx2("h2", {
140
+ children: "Debug Info"
141
+ }),
142
+ /* @__PURE__ */ jsx2("pre", {
143
+ className: "box",
144
+ children: /* @__PURE__ */ jsx2("code", {
145
+ children: `url: ${req.originalUrl}
146
+ route: ${(route == null ? void 0 : route.src) || "null"}
147
+ routeParams: ${routeParams && JSON.stringify(routeParams) || "null"}`
148
+ })
149
+ })
150
+ ]
151
+ });
152
+ }
153
+
154
+ // src/core/pages/DevNotFoundPage.tsx
155
+ import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
156
+ function DevNotFoundPage(props) {
157
+ const req = props.req;
158
+ const routesListMap = {};
159
+ let srcMaxLength = 0;
160
+ Object.keys(props.sitemap).forEach((urlPath) => {
161
+ const route = props.sitemap[urlPath].route;
162
+ routesListMap[route.src] ??= [];
163
+ routesListMap[route.src].push({ route, urlPath });
164
+ if (route.src.length > srcMaxLength) {
165
+ srcMaxLength = route.src.length;
166
+ }
167
+ });
168
+ const routeSrcs = Object.keys(routesListMap).sort(sortRouteFiles);
169
+ const lines = [];
170
+ routeSrcs.forEach((routeSrc) => {
171
+ const routeUrls = routesListMap[routeSrc].sort(sortRouteURLs);
172
+ routeUrls.forEach((routeUrl, i) => {
173
+ const urlPath = routeUrl.urlPath;
174
+ if (i === 0) {
175
+ lines.push(`${routeSrc.padEnd(srcMaxLength, " ")} => ${urlPath}`);
176
+ } else {
177
+ lines.push(`${"".padEnd(srcMaxLength, " ")} => ${urlPath}`);
178
+ }
179
+ });
180
+ });
181
+ const routesListString = lines.join("\n");
182
+ return /* @__PURE__ */ jsxs3(ErrorPage, {
183
+ code: 404,
184
+ title: "Not found",
185
+ children: [
186
+ /* @__PURE__ */ jsx3("h2", {
187
+ children: "Routes"
188
+ }),
189
+ Object.keys(routesListMap).length > 0 ? /* @__PURE__ */ jsx3("pre", {
190
+ className: "box",
191
+ children: /* @__PURE__ */ jsx3("code", {
192
+ children: routesListString
193
+ })
194
+ }) : /* @__PURE__ */ jsxs3("div", {
195
+ className: "box",
196
+ children: [
197
+ "Add your first route at ",
198
+ /* @__PURE__ */ jsx3("code", {
199
+ children: "/routes/index.tsx"
200
+ })
201
+ ]
202
+ }),
203
+ /* @__PURE__ */ jsx3("h2", {
204
+ children: "Debug Info"
205
+ }),
206
+ /* @__PURE__ */ jsx3("pre", {
207
+ className: "box",
208
+ children: /* @__PURE__ */ jsx3("code", {
209
+ children: `url: ${req.originalUrl}`
210
+ })
211
+ })
212
+ ]
213
+ });
214
+ }
215
+ function sortRouteFiles(a, b) {
216
+ if (a === "routes/index.tsx") {
217
+ return -1;
218
+ }
219
+ if (b === "routes/index.tsx") {
220
+ return 1;
221
+ }
222
+ return a.localeCompare(b);
223
+ }
224
+ function sortRouteURLs(a, b) {
225
+ if (a.route.isDefaultLocale && !b.route.isDefaultLocale) {
226
+ return -1;
227
+ }
228
+ if (!a.route.isDefaultLocale && b.route.isDefaultLocale) {
229
+ return 1;
230
+ }
231
+ return a.urlPath.localeCompare(b.urlPath);
232
+ }
233
+
234
+ // src/render/accept-language.ts
235
+ var ACCEPT_LANG_RE = /((([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?)*/g;
236
+ function parseAcceptLanguage(value) {
237
+ const matches = String(value).match(ACCEPT_LANG_RE);
238
+ if (!matches) {
239
+ return [];
240
+ }
241
+ const results = [];
242
+ matches.forEach((m) => {
243
+ if (!m) {
244
+ return;
245
+ }
246
+ const parts = m.split(";");
247
+ const ietf = parts[0].split("-");
248
+ const hasScript = ietf.length === 3;
249
+ results.push({
250
+ code: ietf[0],
251
+ script: hasScript ? ietf[1] : void 0,
252
+ region: hasScript ? ietf[2] : ietf[1],
253
+ quality: parts[1] ? parseFloat(parts[1].split("=")[1]) : 1
254
+ });
255
+ });
256
+ results.sort((a, b) => b.quality - a.quality);
257
+ return results;
258
+ }
259
+
260
+ // src/render/i18n-fallbacks.ts
261
+ var UNKNOWN_COUNTRY = "zz";
262
+ function getFallbackLocales(req) {
263
+ var _a, _b;
264
+ const hl = getFirstQueryParam(req, "hl");
265
+ const countryCode = getCountry(req);
266
+ if (isWebCrawler(req)) {
267
+ const defaultLocale = ((_b = (_a = req.rootConfig) == null ? void 0 : _a.i18n) == null ? void 0 : _b.defaultLocale) || "en";
268
+ if (hl && hl !== defaultLocale) {
269
+ return [hl, defaultLocale];
270
+ }
271
+ return [defaultLocale];
272
+ }
273
+ const locales = /* @__PURE__ */ new Set();
274
+ if (hl) {
275
+ const langCode = hl;
276
+ locales.add(`${langCode}_${countryCode}`);
277
+ locales.add(`${langCode}_ALL`);
278
+ locales.add(langCode);
279
+ }
280
+ const langs = getFallbackLanguages(req);
281
+ langs.forEach((langCode) => {
282
+ locales.add(`${langCode}_${countryCode}`);
283
+ });
284
+ locales.add(`ALL_${countryCode}`);
285
+ langs.forEach((langCode) => {
286
+ locales.add(`${langCode}_ALL`);
287
+ locales.add(langCode);
288
+ });
289
+ return Array.from(locales);
290
+ }
291
+ function getCountry(req) {
292
+ const normalize = (countryCode) => String(countryCode).toLowerCase();
293
+ const gl = getFirstQueryParam(req, "gl");
294
+ if (gl) {
295
+ return normalize(gl);
296
+ }
297
+ const gaeCountry = req.get("x-country-code") || req.get("x-appengine-country");
298
+ if (gaeCountry) {
299
+ return normalize(gaeCountry);
300
+ }
301
+ return UNKNOWN_COUNTRY;
302
+ }
303
+ function getFallbackLanguages(req) {
304
+ const langs = /* @__PURE__ */ new Set();
305
+ const acceptLangHeader = req.get("accept-language") || "";
306
+ if (acceptLangHeader) {
307
+ parseAcceptLanguage(acceptLangHeader).forEach((lang) => {
308
+ if (lang.region) {
309
+ langs.add(`${lang.code}-${lang.region}`);
310
+ }
311
+ langs.add(lang.code);
312
+ });
313
+ }
314
+ langs.add("en");
315
+ return Array.from(langs);
316
+ }
317
+ function getFirstQueryParam(req, key) {
318
+ const val = req.query[key];
319
+ if (val === null || val === void 0) {
320
+ return null;
321
+ }
322
+ if (Array.isArray(val)) {
323
+ if (val.length === 0) {
324
+ return null;
325
+ }
326
+ return String(val[0]);
327
+ }
328
+ return String(val);
329
+ }
330
+ function isWebCrawler(req) {
331
+ const userAgentHeader = req.get("User-Agent");
332
+ if (!userAgentHeader) {
333
+ return false;
334
+ }
335
+ const userAgent = userAgentHeader.toLowerCase();
336
+ return userAgent.includes("googlebot") || userAgent.includes("bingbot") || userAgent.includes("twitterbot");
337
+ }
338
+
16
339
  // src/render/router.ts
17
340
  import path from "node:path";
18
341
 
@@ -35,11 +358,14 @@ var RouteTrie = class {
35
358
  }
36
359
  let nextNode;
37
360
  if (head.startsWith("[") && head.endsWith("]")) {
38
- if (!this.paramChild) {
39
- const paramName = head.slice(1, -1);
40
- this.paramChild = new ParamChild(paramName);
361
+ if (!this.paramChildren) {
362
+ this.paramChildren = {};
41
363
  }
42
- nextNode = this.paramChild.trie;
364
+ const paramName = head.slice(1, -1);
365
+ if (!this.paramChildren[paramName]) {
366
+ this.paramChildren[paramName] = new ParamChild(paramName);
367
+ }
368
+ nextNode = this.paramChildren[paramName].trie;
43
369
  } else {
44
370
  nextNode = this.children[head];
45
371
  if (!nextNode) {
@@ -64,11 +390,13 @@ var RouteTrie = class {
64
390
  if (this.route) {
65
391
  addPromise(cb("/", this.route));
66
392
  }
67
- if (this.paramChild) {
68
- const param = `[${this.paramChild.name}]`;
69
- this.paramChild.trie.walk((childPath, route) => {
70
- const paramUrlPath = `/${param}${childPath}`;
71
- addPromise(cb(paramUrlPath, route));
393
+ if (this.paramChildren) {
394
+ Object.values(this.paramChildren).forEach((paramChild) => {
395
+ const param = `[${paramChild.name}]`;
396
+ paramChild.trie.walk((childPath, route) => {
397
+ const paramUrlPath = `/${param}${childPath}`;
398
+ addPromise(cb(paramUrlPath, route));
399
+ });
72
400
  });
73
401
  }
74
402
  if (this.wildcardChild) {
@@ -86,7 +414,7 @@ var RouteTrie = class {
86
414
  }
87
415
  clear() {
88
416
  this.children = {};
89
- this.paramChild = void 0;
417
+ this.paramChildren = void 0;
90
418
  this.wildcardChild = void 0;
91
419
  this.route = void 0;
92
420
  }
@@ -103,11 +431,13 @@ var RouteTrie = class {
103
431
  return route;
104
432
  }
105
433
  }
106
- if (this.paramChild) {
107
- const route = this.paramChild.trie.getRoute(tail, params);
108
- if (route) {
109
- params[this.paramChild.name] = head;
110
- return route;
434
+ if (this.paramChildren) {
435
+ for (const paramChild of Object.values(this.paramChildren)) {
436
+ const route = paramChild.trie.getRoute(tail, params);
437
+ if (route) {
438
+ params[paramChild.name] = head;
439
+ return route;
440
+ }
111
441
  }
112
442
  }
113
443
  if (this.wildcardChild) {
@@ -170,8 +500,9 @@ function getRoutes(config) {
170
500
  src,
171
501
  module: routes[modulePath],
172
502
  locale: defaultLocale,
173
- routePath,
174
- localeRoutePath
503
+ isDefaultLocale: true,
504
+ routePath: normalizeUrlPath(routePath),
505
+ localeRoutePath: normalizeUrlPath(localeRoutePath)
175
506
  });
176
507
  locales.forEach((locale) => {
177
508
  const localePath = localeRoutePath.replace("[locale]", locale);
@@ -180,6 +511,7 @@ function getRoutes(config) {
180
511
  src,
181
512
  module: routes[modulePath],
182
513
  locale,
514
+ isDefaultLocale: false,
183
515
  routePath,
184
516
  localeRoutePath
185
517
  });
@@ -206,7 +538,7 @@ async function getAllPathsForRoute(urlPathFormat, route) {
206
538
  );
207
539
  } else {
208
540
  urlPaths.push({
209
- urlPath: replaceParams(urlPathFormat, pathParams.params),
541
+ urlPath: normalizeUrlPath(urlPath),
210
542
  params: pathParams.params || {}
211
543
  });
212
544
  }
@@ -218,7 +550,7 @@ async function getAllPathsForRoute(urlPathFormat, route) {
218
550
  `path contains placeholders: ${urlPathFormat}, did you forget to define getStaticPaths()? more info: https://rootjs.dev/guide/routes#getStaticPaths`
219
551
  );
220
552
  } else {
221
- urlPaths.push({ urlPath: urlPathFormat, params: {} });
553
+ urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
222
554
  }
223
555
  return urlPaths;
224
556
  }
@@ -235,6 +567,12 @@ function replaceParams(urlPathFormat, params) {
235
567
  );
236
568
  return urlPath;
237
569
  }
570
+ function normalizeUrlPath(urlPath) {
571
+ if (urlPath !== "/" && urlPath.endsWith("/")) {
572
+ urlPath = urlPath.replace(/\/*$/g, "");
573
+ }
574
+ return urlPath;
575
+ }
238
576
  function pathContainsPlaceholders(urlPath) {
239
577
  const segments = urlPath.split("/");
240
578
  return segments.some((segment) => {
@@ -242,194 +580,6 @@ function pathContainsPlaceholders(urlPath) {
242
580
  });
243
581
  }
244
582
 
245
- // src/core/pages/ErrorPage.tsx
246
- import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
247
- var STYLES = `
248
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
249
-
250
- :root {
251
- --font-family-text: "Inter", sans-serif;
252
- }
253
-
254
- body {
255
- font-family: var(--font-family-text);
256
- background: #F5F5F5;
257
- padding: 40px 16px;
258
- }
259
-
260
- .root {
261
- max-width: 1200px;
262
- margin: 0 auto;
263
- }
264
-
265
- .root.align-center {
266
- text-align: center;
267
- }
268
-
269
- h1.title {
270
- margin-top: 0;
271
- margin-bottom: 24px;
272
- }
273
-
274
- p.message {
275
- margin-top: 0;
276
- margin-bottom: 0;
277
- }
278
-
279
- .box {
280
- font-size: 16px;
281
- line-height: 1.5;
282
- padding: 16px;
283
- border-radius: 12px;
284
- background: #ffffff;
285
- }
286
-
287
- pre.box {
288
- white-space: pre-wrap;
289
- }
290
-
291
- @media (min-width: 500px) {
292
- body {
293
- padding: 40px;
294
- }
295
-
296
- .box {
297
- padding: 24px;
298
- }
299
- }
300
-
301
- @media (min-width: 1024px) {
302
- body {
303
- padding: 100px;
304
- }
305
- }
306
- `;
307
- function ErrorPage(props) {
308
- const { code, message } = props;
309
- const title = props.title || code;
310
- return /* @__PURE__ */ jsxs(Fragment, {
311
- children: [
312
- /* @__PURE__ */ jsx("style", {
313
- dangerouslySetInnerHTML: { __html: STYLES }
314
- }),
315
- /* @__PURE__ */ jsxs("div", {
316
- className: `root align-${props.align || "left"}`,
317
- children: [
318
- /* @__PURE__ */ jsx("h1", {
319
- className: "title",
320
- children: title
321
- }),
322
- message && /* @__PURE__ */ jsx("p", {
323
- className: "message",
324
- children: message
325
- }),
326
- props.children
327
- ]
328
- })
329
- ]
330
- });
331
- }
332
-
333
- // src/core/pages/DevNotFoundPage.tsx
334
- import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
335
- function DevNotFoundPage(props) {
336
- const req = props.req;
337
- const routesList = [];
338
- let urlMaxLength = 0;
339
- Object.keys(props.sitemap).forEach((urlPath) => {
340
- const route = props.sitemap[urlPath].route;
341
- routesList.push(Object.assign({}, route, { urlPath }));
342
- if (urlPath.length > urlMaxLength) {
343
- urlMaxLength = urlPath.length;
344
- }
345
- });
346
- const routesListString = routesList.map((route) => {
347
- return `${route.urlPath.padEnd(urlMaxLength, " ")} => ${route.src}`;
348
- }).join("\n");
349
- return /* @__PURE__ */ jsxs2(ErrorPage, {
350
- code: 404,
351
- title: "Not found",
352
- children: [
353
- /* @__PURE__ */ jsx2("h2", {
354
- children: "Routes"
355
- }),
356
- routesList.length > 0 ? /* @__PURE__ */ jsx2("pre", {
357
- className: "box",
358
- children: /* @__PURE__ */ jsx2("code", {
359
- children: routesListString
360
- })
361
- }) : /* @__PURE__ */ jsxs2("div", {
362
- className: "box",
363
- children: [
364
- "Add your first route at ",
365
- /* @__PURE__ */ jsx2("code", {
366
- children: "/routes/index.tsx"
367
- })
368
- ]
369
- }),
370
- /* @__PURE__ */ jsx2("h2", {
371
- children: "Debug Info"
372
- }),
373
- /* @__PURE__ */ jsx2("pre", {
374
- className: "box",
375
- children: /* @__PURE__ */ jsx2("code", {
376
- children: `url: ${req.originalUrl}`
377
- })
378
- })
379
- ]
380
- });
381
- }
382
-
383
- // src/core/pages/DevErrorPage.tsx
384
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
385
- function DevErrorPage(props) {
386
- var _a;
387
- const req = props.req;
388
- const err = props.error;
389
- const route = props.route;
390
- const routeParams = props.routeParams;
391
- let errMsg = String(err);
392
- if (err && err.stack) {
393
- errMsg = err.stack.replace(/\(.*node_modules/g, "(node_modules").replace(/at \/.*node_modules/g, "at node_modules");
394
- if ((_a = req.rootConfig) == null ? void 0 : _a.rootDir) {
395
- errMsg = errMsg.replaceAll(req.rootConfig.rootDir, "<root>");
396
- }
397
- if (process.env.HOME) {
398
- errMsg = errMsg.replaceAll(process.env.HOME, "$HOME");
399
- }
400
- }
401
- return /* @__PURE__ */ jsxs3(ErrorPage, {
402
- code: 500,
403
- title: "Something went wrong",
404
- children: [
405
- errMsg && /* @__PURE__ */ jsxs3(Fragment2, {
406
- children: [
407
- /* @__PURE__ */ jsx3("h2", {
408
- children: "Error"
409
- }),
410
- /* @__PURE__ */ jsx3("pre", {
411
- className: "box",
412
- children: /* @__PURE__ */ jsx3("code", {
413
- children: errMsg
414
- })
415
- })
416
- ]
417
- }),
418
- /* @__PURE__ */ jsx3("h2", {
419
- children: "Debug Info"
420
- }),
421
- /* @__PURE__ */ jsx3("pre", {
422
- className: "box",
423
- children: /* @__PURE__ */ jsx3("code", {
424
- children: `url: ${req.originalUrl}
425
- route: ${(route == null ? void 0 : route.src) || "null"}
426
- routeParams: ${routeParams && JSON.stringify(routeParams) || "null"}`
427
- })
428
- })
429
- ]
430
- });
431
- }
432
-
433
583
  // src/render/render.tsx
434
584
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
435
585
  var Renderer = class {
@@ -449,18 +599,35 @@ var Renderer = class {
449
599
  if (route.locale) {
450
600
  routeParams.$locale = route.locale;
451
601
  }
602
+ const fallbackLocales = route.isDefaultLocale ? getFallbackLocales(req) : [route.locale];
603
+ const getPreferredLocale = (availableLocales) => {
604
+ var _a, _b;
605
+ const lowerLocales = availableLocales.map((l) => l.toLowerCase());
606
+ for (const fallbackLocale of fallbackLocales) {
607
+ if (lowerLocales.includes(fallbackLocale.toLowerCase())) {
608
+ return fallbackLocale;
609
+ }
610
+ }
611
+ return ((_b = (_a = req.rootConfig) == null ? void 0 : _a.i18n) == null ? void 0 : _b.defaultLocale) || "en";
612
+ };
452
613
  const render404 = async () => {
453
614
  next();
454
615
  };
455
- const render = async (props2) => {
616
+ const render = async (props2, options) => {
456
617
  if (!route.module.default) {
457
618
  console.error(`no default component exported in route: ${route.src}`);
458
619
  render404();
459
620
  return;
460
621
  }
622
+ const currentPath = req.path;
623
+ const locale = (options == null ? void 0 : options.locale) || route.locale;
624
+ const translations = options == null ? void 0 : options.translations;
461
625
  const output = await this.renderComponent(route.module.default, props2, {
626
+ currentPath,
462
627
  route,
463
- routeParams
628
+ routeParams,
629
+ locale,
630
+ translations
464
631
  });
465
632
  let html = output.html;
466
633
  if (this.rootConfig.prettyHtml) {
@@ -469,7 +636,7 @@ var Renderer = class {
469
636
  html = await htmlMinify(html, this.rootConfig.minifyHtmlOptions);
470
637
  }
471
638
  if (req.viteServer) {
472
- html = await req.viteServer.transformIndexHtml(req.originalUrl, html);
639
+ html = await req.viteServer.transformIndexHtml(currentPath, html);
473
640
  }
474
641
  let statusCode = 200;
475
642
  if (route.src === "routes/404.tsx") {
@@ -483,6 +650,8 @@ var Renderer = class {
483
650
  const handlerContext = {
484
651
  route,
485
652
  params: routeParams,
653
+ i18nFallbackLocales: fallbackLocales,
654
+ getPreferredLocale,
486
655
  render,
487
656
  render404
488
657
  };
@@ -505,10 +674,14 @@ var Renderer = class {
505
674
  await render(props);
506
675
  }
507
676
  async renderComponent(Component, props, options) {
508
- const { route, routeParams } = options;
509
- const locale = route.locale;
510
- const translations = getTranslations(locale);
677
+ const { currentPath, route, routeParams } = options;
678
+ const locale = options.locale;
679
+ const translations = {
680
+ ...getTranslations(locale),
681
+ ...options.translations || {}
682
+ };
511
683
  const ctx = {
684
+ currentPath,
512
685
  route,
513
686
  props,
514
687
  routeParams,
@@ -592,6 +765,8 @@ var Renderer = class {
592
765
  );
593
766
  }
594
767
  let props = {};
768
+ let locale = route.locale;
769
+ let translations = void 0;
595
770
  if (route.module.getStaticProps) {
596
771
  const propsData = await route.module.getStaticProps({
597
772
  rootConfig: this.rootConfig,
@@ -603,8 +778,25 @@ var Renderer = class {
603
778
  if (propsData.props) {
604
779
  props = propsData.props;
605
780
  }
781
+ if (propsData.locale) {
782
+ locale = propsData.locale;
783
+ }
784
+ if (propsData.translations) {
785
+ translations = propsData.translations;
786
+ }
606
787
  }
607
- return this.renderComponent(Component, props, { route, routeParams });
788
+ const routePath = route.isDefaultLocale ? route.routePath : route.localeRoutePath;
789
+ const currentPath = replaceParams(routePath, {
790
+ ...routeParams,
791
+ locale
792
+ });
793
+ return this.renderComponent(Component, props, {
794
+ currentPath,
795
+ route,
796
+ routeParams,
797
+ locale,
798
+ translations
799
+ });
608
800
  }
609
801
  async getSitemap() {
610
802
  const sitemap = {};
@@ -645,17 +837,23 @@ var Renderer = class {
645
837
  ${renderToString(page)}
646
838
  `;
647
839
  }
648
- async render404() {
840
+ async render404(options) {
841
+ const currentPath = (options == null ? void 0 : options.currentPath) || "/404";
649
842
  const [route, routeParams] = this.routes.get("/404");
650
843
  if (route && route.src === "routes/404.tsx" && route.module.default) {
651
844
  const Component = route.module.default;
652
- return this.renderComponent(Component, {}, { route, routeParams });
845
+ return this.renderComponent(
846
+ Component,
847
+ {},
848
+ { currentPath, route, routeParams, locale: "en" }
849
+ );
653
850
  }
654
851
  const mainHtml = renderToString(
655
852
  /* @__PURE__ */ jsx4(ErrorPage, {
656
853
  code: 404,
657
854
  title: "Not found",
658
- message: "Double-check the URL entered and try again."
855
+ message: "Double-check the URL entered and try again.",
856
+ align: "center"
659
857
  })
660
858
  );
661
859
  const html = await this.renderHtml(mainHtml, {
@@ -671,21 +869,23 @@ ${renderToString(page)}
671
869
  });
672
870
  return { html };
673
871
  }
674
- async renderError(err) {
872
+ async renderError(err, options) {
873
+ const currentPath = (options == null ? void 0 : options.currentPath) || "/500";
675
874
  const [route, routeParams] = this.routes.get("/500");
676
875
  if (route && route.src === "routes/500.tsx" && route.module.default) {
677
876
  const Component = route.module.default;
678
877
  return this.renderComponent(
679
878
  Component,
680
879
  { error: err },
681
- { route, routeParams }
880
+ { currentPath, route, routeParams, locale: "en" }
682
881
  );
683
882
  }
684
883
  const mainHtml = renderToString(
685
884
  /* @__PURE__ */ jsx4(ErrorPage, {
686
885
  code: 500,
687
886
  title: "Something went wrong",
688
- message: "An unknown error occurred."
887
+ message: "An unknown error occurred.",
888
+ align: "center"
689
889
  })
690
890
  );
691
891
  const html = await this.renderHtml(mainHtml, {