@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.2 → 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.
- package/dist/cjs/localisedUrls/index.js +7 -0
- package/dist/cjs/localisedUrls/routeIdentity.js +118 -0
- package/dist/cjs/localisedUrls/routes.js +8 -0
- package/dist/esm/localisedUrls/index.mjs +1 -0
- package/dist/esm/localisedUrls/routeIdentity.mjs +77 -0
- package/dist/esm/localisedUrls/routes.mjs +8 -0
- package/dist/esm-node/localisedUrls/index.mjs +1 -0
- package/dist/esm-node/localisedUrls/routeIdentity.mjs +78 -0
- package/dist/esm-node/localisedUrls/routes.mjs +8 -0
- package/dist/types/localisedUrls/index.d.ts +1 -0
- package/dist/types/localisedUrls/routeIdentity.d.ts +24 -0
- package/package.json +3 -3
- package/src/localisedUrls/index.ts +4 -0
- package/src/localisedUrls/routeIdentity.ts +107 -0
- package/src/localisedUrls/routes.ts +10 -0
- package/tests/reactLanguageSync.test.tsx +5 -1
|
@@ -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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|