@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.3 → 3.9.0-ultramodern.4

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.
@@ -32,6 +32,8 @@ __webpack_require__.d(__webpack_exports__, {
32
32
  applyLocalisedUrlsToRoutes: ()=>external_routes_js_namespaceObject.applyLocalisedUrlsToRoutes,
33
33
  buildPathFromPattern: ()=>external_patterns_js_namespaceObject.buildPathFromPattern,
34
34
  canonicalTargetPathname: ()=>external_pathname_js_namespaceObject.canonicalTargetPathname,
35
+ canonicaliseLocalisedRoutes: ()=>external_routeIdentity_js_namespaceObject.canonicaliseLocalisedRoutes,
36
+ createLocalisedRouteRewrite: ()=>external_routeIdentity_js_namespaceObject.createLocalisedRouteRewrite,
35
37
  isDefaultLocaleRedirectSkipPath: ()=>external_redirect_js_namespaceObject.isDefaultLocaleRedirectSkipPath,
36
38
  localiseTargetPathname: ()=>external_pathname_js_namespaceObject.localiseTargetPathname,
37
39
  matchPathPattern: ()=>external_patterns_js_namespaceObject.matchPathPattern,
@@ -51,11 +53,14 @@ const external_pathname_js_namespaceObject = require("./pathname.js");
51
53
  const external_patterns_js_namespaceObject = require("./patterns.js");
52
54
  const external_redirect_js_namespaceObject = require("./redirect.js");
53
55
  const external_resolve_js_namespaceObject = require("./resolve.js");
56
+ const external_routeIdentity_js_namespaceObject = require("./routeIdentity.js");
54
57
  const external_routes_js_namespaceObject = require("./routes.js");
55
58
  exports.DEFAULT_LOCALE_REDIRECT_SKIP_RULES = __webpack_exports__.DEFAULT_LOCALE_REDIRECT_SKIP_RULES;
56
59
  exports.applyLocalisedUrlsToRoutes = __webpack_exports__.applyLocalisedUrlsToRoutes;
57
60
  exports.buildPathFromPattern = __webpack_exports__.buildPathFromPattern;
58
61
  exports.canonicalTargetPathname = __webpack_exports__.canonicalTargetPathname;
62
+ exports.canonicaliseLocalisedRoutes = __webpack_exports__.canonicaliseLocalisedRoutes;
63
+ exports.createLocalisedRouteRewrite = __webpack_exports__.createLocalisedRouteRewrite;
59
64
  exports.isDefaultLocaleRedirectSkipPath = __webpack_exports__.isDefaultLocaleRedirectSkipPath;
60
65
  exports.localiseTargetPathname = __webpack_exports__.localiseTargetPathname;
61
66
  exports.matchPathPattern = __webpack_exports__.matchPathPattern;
@@ -73,6 +78,8 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
73
78
  "applyLocalisedUrlsToRoutes",
74
79
  "buildPathFromPattern",
75
80
  "canonicalTargetPathname",
81
+ "canonicaliseLocalisedRoutes",
82
+ "createLocalisedRouteRewrite",
76
83
  "isDefaultLocaleRedirectSkipPath",
77
84
  "localiseTargetPathname",
78
85
  "matchPathPattern",
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ canonicaliseLocalisedRoutes: ()=>canonicaliseLocalisedRoutes,
32
+ createLocalisedRouteRewrite: ()=>createLocalisedRouteRewrite
33
+ });
34
+ const external_normalise_js_namespaceObject = require("./normalise.js");
35
+ const external_patterns_js_namespaceObject = require("./patterns.js");
36
+ function canonicaliseLocalisedRoutes(routes) {
37
+ const seen = new Set();
38
+ return routes.flatMap((route)=>{
39
+ const identity = route.modernLocalisedRoute;
40
+ const key = identity?.id ?? identity?.path;
41
+ if (void 0 !== key) {
42
+ if (seen.has(key)) return [];
43
+ seen.add(key);
44
+ }
45
+ return [
46
+ {
47
+ ...route,
48
+ ...identity ? {
49
+ id: identity.id,
50
+ path: identity.path
51
+ } : {},
52
+ ...route.children ? {
53
+ children: canonicaliseLocalisedRoutes(route.children)
54
+ } : {}
55
+ }
56
+ ];
57
+ });
58
+ }
59
+ function rewritePath(url, mappings) {
60
+ for (const { from, to } of mappings){
61
+ const params = (0, external_patterns_js_namespaceObject.matchPathPattern)(url.pathname, from);
62
+ if (params) {
63
+ url.pathname = (0, external_patterns_js_namespaceObject.buildPathFromPattern)(to, params);
64
+ break;
65
+ }
66
+ }
67
+ return url;
68
+ }
69
+ function createLocalisedRouteRewrite(routes) {
70
+ const input = [];
71
+ const output = [];
72
+ const visit = (nodes, parent)=>{
73
+ for (const route of nodes){
74
+ const fullPath = (0, external_normalise_js_namespaceObject.normalisePathPattern)(`${parent}/${route.path ?? ''}`);
75
+ const identity = route.modernLocalisedRoute;
76
+ if (identity?.paths && fullPath.endsWith(identity.canonicalPath)) {
77
+ const prefix = fullPath.slice(0, -identity.canonicalPath.length);
78
+ const hasLocale = prefix.split('/').some((segment)=>/^:(?:lang|locale|language)\??$/.test(segment));
79
+ for (const [language, localisedPath] of Object.entries(identity.paths)){
80
+ const localePrefix = prefix.split('/').map((segment)=>/^:(?:lang|locale|language)\??$/.test(segment) ? language : segment).join('/');
81
+ const canonical = (0, external_normalise_js_namespaceObject.normalisePathPattern)(`${localePrefix}/${identity.canonicalPath}`);
82
+ const localised = (0, external_normalise_js_namespaceObject.normalisePathPattern)(`${localePrefix}/${localisedPath}`);
83
+ if (canonical !== localised) {
84
+ input.push({
85
+ from: localised,
86
+ to: canonical
87
+ });
88
+ if (hasLocale) output.push({
89
+ from: canonical,
90
+ to: localised
91
+ });
92
+ }
93
+ }
94
+ }
95
+ if (route.children) visit(route.children, fullPath);
96
+ }
97
+ };
98
+ visit(canonicaliseLocalisedRoutes(routes), '');
99
+ if (0 === input.length) return;
100
+ const specificity = (pattern)=>pattern.split('/').filter((segment)=>segment && !segment.startsWith(':') && '*' !== segment).length;
101
+ for (const mappings of [
102
+ input,
103
+ output
104
+ ])mappings.sort((a, b)=>specificity(b.from) - specificity(a.from));
105
+ return {
106
+ input: ({ url })=>rewritePath(url, input),
107
+ output: ({ url })=>rewritePath(url, output)
108
+ };
109
+ }
110
+ exports.canonicaliseLocalisedRoutes = __webpack_exports__.canonicaliseLocalisedRoutes;
111
+ exports.createLocalisedRouteRewrite = __webpack_exports__.createLocalisedRouteRewrite;
112
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
113
+ "canonicaliseLocalisedRoutes",
114
+ "createLocalisedRouteRewrite"
115
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
116
+ Object.defineProperty(exports, '__esModule', {
117
+ value: true
118
+ });
@@ -134,6 +134,14 @@ const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPath
134
134
  const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
135
135
  const baseRoute = {
136
136
  ...route,
137
+ ...!isFrameworkInternalRoutePath(canonicalPath) ? {
138
+ modernLocalisedRoute: {
139
+ id: route.id,
140
+ path: route.path,
141
+ canonicalPath,
142
+ paths: localisedUrlEntry
143
+ }
144
+ } : {},
137
145
  ...children ? {
138
146
  children
139
147
  } : {}
@@ -4,4 +4,5 @@ export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix }
4
4
  export { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
5
5
  export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect } from "./redirect.mjs";
6
6
  export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from "./resolve.mjs";
7
+ export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite } from "./routeIdentity.mjs";
7
8
  export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from "./routes.mjs";
@@ -0,0 +1,77 @@
1
+ import { normalisePathPattern } from "./normalise.mjs";
2
+ import { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
3
+ function canonicaliseLocalisedRoutes(routes) {
4
+ const seen = new Set();
5
+ return routes.flatMap((route)=>{
6
+ const identity = route.modernLocalisedRoute;
7
+ const key = identity?.id ?? identity?.path;
8
+ if (void 0 !== key) {
9
+ if (seen.has(key)) return [];
10
+ seen.add(key);
11
+ }
12
+ return [
13
+ {
14
+ ...route,
15
+ ...identity ? {
16
+ id: identity.id,
17
+ path: identity.path
18
+ } : {},
19
+ ...route.children ? {
20
+ children: canonicaliseLocalisedRoutes(route.children)
21
+ } : {}
22
+ }
23
+ ];
24
+ });
25
+ }
26
+ function rewritePath(url, mappings) {
27
+ for (const { from, to } of mappings){
28
+ const params = matchPathPattern(url.pathname, from);
29
+ if (params) {
30
+ url.pathname = buildPathFromPattern(to, params);
31
+ break;
32
+ }
33
+ }
34
+ return url;
35
+ }
36
+ function createLocalisedRouteRewrite(routes) {
37
+ const input = [];
38
+ const output = [];
39
+ const visit = (nodes, parent)=>{
40
+ for (const route of nodes){
41
+ const fullPath = normalisePathPattern(`${parent}/${route.path ?? ''}`);
42
+ const identity = route.modernLocalisedRoute;
43
+ if (identity?.paths && fullPath.endsWith(identity.canonicalPath)) {
44
+ const prefix = fullPath.slice(0, -identity.canonicalPath.length);
45
+ const hasLocale = prefix.split('/').some((segment)=>/^:(?:lang|locale|language)\??$/.test(segment));
46
+ for (const [language, localisedPath] of Object.entries(identity.paths)){
47
+ const localePrefix = prefix.split('/').map((segment)=>/^:(?:lang|locale|language)\??$/.test(segment) ? language : segment).join('/');
48
+ const canonical = normalisePathPattern(`${localePrefix}/${identity.canonicalPath}`);
49
+ const localised = normalisePathPattern(`${localePrefix}/${localisedPath}`);
50
+ if (canonical !== localised) {
51
+ input.push({
52
+ from: localised,
53
+ to: canonical
54
+ });
55
+ if (hasLocale) output.push({
56
+ from: canonical,
57
+ to: localised
58
+ });
59
+ }
60
+ }
61
+ }
62
+ if (route.children) visit(route.children, fullPath);
63
+ }
64
+ };
65
+ visit(canonicaliseLocalisedRoutes(routes), '');
66
+ if (0 === input.length) return;
67
+ const specificity = (pattern)=>pattern.split('/').filter((segment)=>segment && !segment.startsWith(':') && '*' !== segment).length;
68
+ for (const mappings of [
69
+ input,
70
+ output
71
+ ])mappings.sort((a, b)=>specificity(b.from) - specificity(a.from));
72
+ return {
73
+ input: ({ url })=>rewritePath(url, input),
74
+ output: ({ url })=>rewritePath(url, output)
75
+ };
76
+ }
77
+ export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite };
@@ -101,6 +101,14 @@ const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPath
101
101
  const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
102
102
  const baseRoute = {
103
103
  ...route,
104
+ ...!isFrameworkInternalRoutePath(canonicalPath) ? {
105
+ modernLocalisedRoute: {
106
+ id: route.id,
107
+ path: route.path,
108
+ canonicalPath,
109
+ paths: localisedUrlEntry
110
+ }
111
+ } : {},
104
112
  ...children ? {
105
113
  children
106
114
  } : {}
@@ -5,4 +5,5 @@ export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix }
5
5
  export { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
6
6
  export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect } from "./redirect.mjs";
7
7
  export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from "./resolve.mjs";
8
+ export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite } from "./routeIdentity.mjs";
8
9
  export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from "./routes.mjs";
@@ -0,0 +1,78 @@
1
+ import "node:module";
2
+ import { normalisePathPattern } from "./normalise.mjs";
3
+ import { buildPathFromPattern, matchPathPattern } from "./patterns.mjs";
4
+ function canonicaliseLocalisedRoutes(routes) {
5
+ const seen = new Set();
6
+ return routes.flatMap((route)=>{
7
+ const identity = route.modernLocalisedRoute;
8
+ const key = identity?.id ?? identity?.path;
9
+ if (void 0 !== key) {
10
+ if (seen.has(key)) return [];
11
+ seen.add(key);
12
+ }
13
+ return [
14
+ {
15
+ ...route,
16
+ ...identity ? {
17
+ id: identity.id,
18
+ path: identity.path
19
+ } : {},
20
+ ...route.children ? {
21
+ children: canonicaliseLocalisedRoutes(route.children)
22
+ } : {}
23
+ }
24
+ ];
25
+ });
26
+ }
27
+ function rewritePath(url, mappings) {
28
+ for (const { from, to } of mappings){
29
+ const params = matchPathPattern(url.pathname, from);
30
+ if (params) {
31
+ url.pathname = buildPathFromPattern(to, params);
32
+ break;
33
+ }
34
+ }
35
+ return url;
36
+ }
37
+ function createLocalisedRouteRewrite(routes) {
38
+ const input = [];
39
+ const output = [];
40
+ const visit = (nodes, parent)=>{
41
+ for (const route of nodes){
42
+ const fullPath = normalisePathPattern(`${parent}/${route.path ?? ''}`);
43
+ const identity = route.modernLocalisedRoute;
44
+ if (identity?.paths && fullPath.endsWith(identity.canonicalPath)) {
45
+ const prefix = fullPath.slice(0, -identity.canonicalPath.length);
46
+ const hasLocale = prefix.split('/').some((segment)=>/^:(?:lang|locale|language)\??$/.test(segment));
47
+ for (const [language, localisedPath] of Object.entries(identity.paths)){
48
+ const localePrefix = prefix.split('/').map((segment)=>/^:(?:lang|locale|language)\??$/.test(segment) ? language : segment).join('/');
49
+ const canonical = normalisePathPattern(`${localePrefix}/${identity.canonicalPath}`);
50
+ const localised = normalisePathPattern(`${localePrefix}/${localisedPath}`);
51
+ if (canonical !== localised) {
52
+ input.push({
53
+ from: localised,
54
+ to: canonical
55
+ });
56
+ if (hasLocale) output.push({
57
+ from: canonical,
58
+ to: localised
59
+ });
60
+ }
61
+ }
62
+ }
63
+ if (route.children) visit(route.children, fullPath);
64
+ }
65
+ };
66
+ visit(canonicaliseLocalisedRoutes(routes), '');
67
+ if (0 === input.length) return;
68
+ const specificity = (pattern)=>pattern.split('/').filter((segment)=>segment && !segment.startsWith(':') && '*' !== segment).length;
69
+ for (const mappings of [
70
+ input,
71
+ output
72
+ ])mappings.sort((a, b)=>specificity(b.from) - specificity(a.from));
73
+ return {
74
+ input: ({ url })=>rewritePath(url, input),
75
+ output: ({ url })=>rewritePath(url, output)
76
+ };
77
+ }
78
+ export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite };
@@ -102,6 +102,14 @@ const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPath
102
102
  const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
103
103
  const baseRoute = {
104
104
  ...route,
105
+ ...!isFrameworkInternalRoutePath(canonicalPath) ? {
106
+ modernLocalisedRoute: {
107
+ id: route.id,
108
+ path: route.path,
109
+ canonicalPath,
110
+ paths: localisedUrlEntry
111
+ }
112
+ } : {},
105
113
  ...children ? {
106
114
  children
107
115
  } : {}
@@ -5,5 +5,6 @@ export { buildPathFromPattern, matchPathPattern } from './patterns.js';
5
5
  export type { LocaleRedirectSkipRule } from './redirect.js';
6
6
  export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect, } from './redirect.js';
7
7
  export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve.js';
8
+ export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite, } from './routeIdentity.js';
8
9
  export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes.js';
9
10
  export type { LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './types.js';
@@ -0,0 +1,24 @@
1
+ interface RouteIdentity {
2
+ id?: string;
3
+ path?: string;
4
+ canonicalPath: string;
5
+ paths?: Record<string, string>;
6
+ }
7
+ interface RouteNode {
8
+ id?: string;
9
+ path?: string;
10
+ children?: RouteNode[];
11
+ modernLocalisedRoute?: RouteIdentity;
12
+ }
13
+ /** Restore one source route identity for routers with strict route-ID hooks. */
14
+ export declare function canonicaliseLocalisedRoutes<T extends RouteNode>(routes: T[]): T[];
15
+ /** Use the router's URL rewrite seam; browser URLs retain their locale spelling. */
16
+ export declare function createLocalisedRouteRewrite(routes: RouteNode[]): {
17
+ input: ({ url }: {
18
+ url: URL;
19
+ }) => URL;
20
+ output: ({ url }: {
21
+ url: URL;
22
+ }) => URL;
23
+ } | undefined;
24
+ export {};
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern.js",
18
18
  "i18n"
19
19
  ],
20
- "version": "3.9.0-ultramodern.3",
20
+ "version": "3.9.0-ultramodern.4",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "module": "./dist/esm/index.mjs",
@@ -47,11 +47,11 @@
47
47
  "i18next-browser-languagedetector": "^8.2.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@modern-js/runtime": "3.9.0-ultramodern.3",
50
+ "@modern-js/runtime": "3.9.0-ultramodern.4",
51
51
  "react": "^19.2.8"
52
52
  },
53
53
  "devDependencies": {
54
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.3",
54
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.4",
55
55
  "@rslib/core": "1.0.0",
56
56
  "@scripts/rstest-config": "2.66.0",
57
57
  "@types/node": "^26.4.1",
@@ -14,6 +14,10 @@ export {
14
14
  shouldSkipLocaleRedirect,
15
15
  } from './redirect';
16
16
  export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve';
17
+ export {
18
+ canonicaliseLocalisedRoutes,
19
+ createLocalisedRouteRewrite,
20
+ } from './routeIdentity';
17
21
  export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes';
18
22
  export type {
19
23
  LocalisedRoute,
@@ -0,0 +1,107 @@
1
+ import { normalisePathPattern } from './normalise';
2
+ import { buildPathFromPattern, matchPathPattern } from './patterns';
3
+
4
+ interface RouteIdentity {
5
+ id?: string;
6
+ path?: string;
7
+ canonicalPath: string;
8
+ paths?: Record<string, string>;
9
+ }
10
+
11
+ interface RouteNode {
12
+ id?: string;
13
+ path?: string;
14
+ children?: RouteNode[];
15
+ modernLocalisedRoute?: RouteIdentity;
16
+ }
17
+
18
+ /** Restore one source route identity for routers with strict route-ID hooks. */
19
+ export function canonicaliseLocalisedRoutes<T extends RouteNode>(
20
+ routes: T[],
21
+ ): T[] {
22
+ const seen = new Set<string>();
23
+ return routes.flatMap(route => {
24
+ const identity = route.modernLocalisedRoute;
25
+ const key = identity?.id ?? identity?.path;
26
+ if (key !== undefined) {
27
+ if (seen.has(key)) return [];
28
+ seen.add(key);
29
+ }
30
+ return [
31
+ {
32
+ ...route,
33
+ ...(identity ? { id: identity.id, path: identity.path } : {}),
34
+ ...(route.children
35
+ ? { children: canonicaliseLocalisedRoutes(route.children) }
36
+ : {}),
37
+ },
38
+ ];
39
+ });
40
+ }
41
+
42
+ type PathRewrite = { from: string; to: string };
43
+
44
+ function rewritePath(url: URL, mappings: PathRewrite[]) {
45
+ for (const { from, to } of mappings) {
46
+ const params = matchPathPattern(url.pathname, from);
47
+ if (params) {
48
+ url.pathname = buildPathFromPattern(to, params);
49
+ break;
50
+ }
51
+ }
52
+ return url;
53
+ }
54
+
55
+ /** Use the router's URL rewrite seam; browser URLs retain their locale spelling. */
56
+ export function createLocalisedRouteRewrite(routes: RouteNode[]) {
57
+ const input: PathRewrite[] = [];
58
+ const output: PathRewrite[] = [];
59
+ const visit = (nodes: RouteNode[], parent: string) => {
60
+ for (const route of nodes) {
61
+ const fullPath = normalisePathPattern(`${parent}/${route.path ?? ''}`);
62
+ const identity = route.modernLocalisedRoute;
63
+ if (identity?.paths && fullPath.endsWith(identity.canonicalPath)) {
64
+ const prefix = fullPath.slice(0, -identity.canonicalPath.length);
65
+ const hasLocale = prefix
66
+ .split('/')
67
+ .some(segment => /^:(?:lang|locale|language)\??$/.test(segment));
68
+ for (const [language, localisedPath] of Object.entries(
69
+ identity.paths,
70
+ )) {
71
+ const localePrefix = prefix
72
+ .split('/')
73
+ .map(segment =>
74
+ /^:(?:lang|locale|language)\??$/.test(segment)
75
+ ? language
76
+ : segment,
77
+ )
78
+ .join('/');
79
+ const canonical = normalisePathPattern(
80
+ `${localePrefix}/${identity.canonicalPath}`,
81
+ );
82
+ const localised = normalisePathPattern(
83
+ `${localePrefix}/${localisedPath}`,
84
+ );
85
+ if (canonical !== localised) {
86
+ input.push({ from: localised, to: canonical });
87
+ if (hasLocale) output.push({ from: canonical, to: localised });
88
+ }
89
+ }
90
+ }
91
+ if (route.children) visit(route.children, fullPath);
92
+ }
93
+ };
94
+ visit(canonicaliseLocalisedRoutes(routes), '');
95
+ if (input.length === 0) return undefined;
96
+ const specificity = (pattern: string) =>
97
+ pattern
98
+ .split('/')
99
+ .filter(segment => segment && !segment.startsWith(':') && segment !== '*')
100
+ .length;
101
+ for (const mappings of [input, output])
102
+ mappings.sort((a, b) => specificity(b.from) - specificity(a.from));
103
+ return {
104
+ input: ({ url }: { url: URL }) => rewritePath(url, input),
105
+ output: ({ url }: { url: URL }) => rewritePath(url, output),
106
+ };
107
+ }
@@ -291,6 +291,16 @@ const transformLocalisedRoute = (
291
291
 
292
292
  const baseRoute = {
293
293
  ...route,
294
+ ...(!isFrameworkInternalRoutePath(canonicalPath)
295
+ ? {
296
+ modernLocalisedRoute: {
297
+ id: route.id,
298
+ path: route.path,
299
+ canonicalPath,
300
+ paths: localisedUrlEntry,
301
+ },
302
+ }
303
+ : {}),
294
304
  ...(children ? { children } : {}),
295
305
  } as LocalisedRoute;
296
306
 
@@ -49,7 +49,11 @@ test('an abandoned suspended render cannot publish an uncommitted target', async
49
49
  const container = document.createElement('div');
50
50
  const root = createRoot(container);
51
51
  await act(async () => {
52
- root.render(<Harness target={first} />);
52
+ root.render(
53
+ <Suspense fallback={<p>loading</p>}>
54
+ <Harness target={first} />
55
+ </Suspense>,
56
+ );
53
57
  });
54
58
  expect(changes).toEqual(['first']);
55
59