@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13

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 (73) hide show
  1. package/dist/cjs/index.js +10 -10
  2. package/dist/cjs/localeRedirectPolicy.js +6 -6
  3. package/dist/cjs/paths.js +3 -3
  4. package/dist/cjs/router-navigation/adapter.js +24 -166
  5. package/dist/esm/index.mjs +1 -1
  6. package/dist/esm/localeRedirectPolicy.mjs +1 -1
  7. package/dist/esm/paths.mjs +1 -1
  8. package/dist/esm/router-navigation/adapter.mjs +26 -168
  9. package/dist/esm-node/index.mjs +1 -1
  10. package/dist/esm-node/localeRedirectPolicy.mjs +1 -1
  11. package/dist/esm-node/paths.mjs +1 -1
  12. package/dist/esm-node/router-navigation/adapter.mjs +26 -168
  13. package/dist/types/index.d.ts +1 -1
  14. package/dist/types/localeRedirectPolicy.d.ts +1 -1
  15. package/dist/types/paths.d.ts +1 -1
  16. package/dist/types/router-navigation/adapter.d.ts +7 -55
  17. package/package.json +9 -9
  18. package/src/index.ts +1 -1
  19. package/src/localeRedirectPolicy.ts +2 -2
  20. package/src/paths.ts +2 -2
  21. package/src/router-navigation/adapter.tsx +62 -478
  22. package/tests/routerNavigation.test.tsx +80 -0
  23. package/dist/cjs/localisedUrls/config.js +0 -49
  24. package/dist/cjs/localisedUrls/index.js +0 -98
  25. package/dist/cjs/localisedUrls/normalise.js +0 -87
  26. package/dist/cjs/localisedUrls/pathname.js +0 -68
  27. package/dist/cjs/localisedUrls/patterns.js +0 -214
  28. package/dist/cjs/localisedUrls/redirect.js +0 -100
  29. package/dist/cjs/localisedUrls/resolve.js +0 -78
  30. package/dist/cjs/localisedUrls/routeIdentity.js +0 -118
  31. package/dist/cjs/localisedUrls/routes.js +0 -210
  32. package/dist/cjs/localisedUrls/types.js +0 -18
  33. package/dist/esm/localisedUrls/config.mjs +0 -11
  34. package/dist/esm/localisedUrls/index.mjs +0 -8
  35. package/dist/esm/localisedUrls/normalise.mjs +0 -37
  36. package/dist/esm/localisedUrls/pathname.mjs +0 -24
  37. package/dist/esm/localisedUrls/patterns.mjs +0 -152
  38. package/dist/esm/localisedUrls/redirect.mjs +0 -53
  39. package/dist/esm/localisedUrls/resolve.mjs +0 -37
  40. package/dist/esm/localisedUrls/routeIdentity.mjs +0 -77
  41. package/dist/esm/localisedUrls/routes.mjs +0 -169
  42. package/dist/esm/localisedUrls/types.mjs +0 -0
  43. package/dist/esm-node/localisedUrls/config.mjs +0 -12
  44. package/dist/esm-node/localisedUrls/index.mjs +0 -9
  45. package/dist/esm-node/localisedUrls/normalise.mjs +0 -38
  46. package/dist/esm-node/localisedUrls/pathname.mjs +0 -25
  47. package/dist/esm-node/localisedUrls/patterns.mjs +0 -153
  48. package/dist/esm-node/localisedUrls/redirect.mjs +0 -54
  49. package/dist/esm-node/localisedUrls/resolve.mjs +0 -38
  50. package/dist/esm-node/localisedUrls/routeIdentity.mjs +0 -78
  51. package/dist/esm-node/localisedUrls/routes.mjs +0 -170
  52. package/dist/esm-node/localisedUrls/types.mjs +0 -1
  53. package/dist/types/localisedUrls/config.d.ts +0 -9
  54. package/dist/types/localisedUrls/index.d.ts +0 -10
  55. package/dist/types/localisedUrls/normalise.d.ts +0 -10
  56. package/dist/types/localisedUrls/pathname.d.ts +0 -4
  57. package/dist/types/localisedUrls/patterns.d.ts +0 -11
  58. package/dist/types/localisedUrls/redirect.d.ts +0 -19
  59. package/dist/types/localisedUrls/resolve.d.ts +0 -7
  60. package/dist/types/localisedUrls/routeIdentity.d.ts +0 -24
  61. package/dist/types/localisedUrls/routes.d.ts +0 -3
  62. package/dist/types/localisedUrls/types.d.ts +0 -14
  63. package/src/localisedUrls/config.ts +0 -18
  64. package/src/localisedUrls/index.ts +0 -28
  65. package/src/localisedUrls/normalise.ts +0 -62
  66. package/src/localisedUrls/pathname.ts +0 -60
  67. package/src/localisedUrls/patterns.ts +0 -283
  68. package/src/localisedUrls/redirect.ts +0 -107
  69. package/src/localisedUrls/resolve.ts +0 -106
  70. package/src/localisedUrls/routeIdentity.ts +0 -107
  71. package/src/localisedUrls/routes.ts +0 -432
  72. package/src/localisedUrls/types.ts +0 -18
  73. package/tests/localisedUrls.test.ts +0 -532
@@ -1,38 +0,0 @@
1
- import "node:module";
2
- import { normalisePathname } from "./normalise.mjs";
3
- import { buildPathFromPattern, matchPathPattern, sortPatternsBySpecificity } from "./patterns.mjs";
4
- const getCanonicalCandidates = (localisedUrls)=>sortPatternsBySpecificity(Object.entries(localisedUrls).map(([canonicalPattern, localisedUrlEntry])=>({
5
- pattern: canonicalPattern,
6
- canonicalPattern,
7
- localisedUrlEntry
8
- })));
9
- const resolveLocalisedUrlPath = (pathname, languages, localisedUrls, options)=>{
10
- const normalizedPathname = normalisePathname(pathname);
11
- const canonicalCandidates = getCanonicalCandidates(localisedUrls);
12
- const candidates = sortPatternsBySpecificity(canonicalCandidates.flatMap(({ canonicalPattern, localisedUrlEntry })=>{
13
- const targetPattern = 'localised' === options.mode ? localisedUrlEntry[options.targetLanguage] : canonicalPattern;
14
- if (!targetPattern) return [];
15
- const sourcePatterns = new Set([
16
- canonicalPattern,
17
- ...languages.map((language)=>localisedUrlEntry[language]).filter((sourcePattern)=>Boolean(sourcePattern))
18
- ]);
19
- return Array.from(sourcePatterns, (sourcePattern)=>({
20
- pattern: sourcePattern,
21
- sourcePattern,
22
- targetPattern
23
- }));
24
- }));
25
- for (const { sourcePattern, targetPattern } of candidates){
26
- const params = matchPathPattern(normalizedPathname, sourcePattern);
27
- if (params) return buildPathFromPattern(targetPattern, params);
28
- }
29
- return normalizedPathname;
30
- };
31
- const resolveLocalisedPath = (pathname, targetLanguage, languages, localisedUrls)=>resolveLocalisedUrlPath(pathname, languages, localisedUrls, {
32
- mode: 'localised',
33
- targetLanguage
34
- });
35
- const resolveCanonicalLocalisedPath = (pathname, languages, localisedUrls)=>resolveLocalisedUrlPath(pathname, languages, localisedUrls, {
36
- mode: 'canonical'
37
- });
38
- export { resolveCanonicalLocalisedPath, resolveLocalisedPath };
@@ -1,78 +0,0 @@
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 };
@@ -1,170 +0,0 @@
1
- import "node:module";
2
- import { getLeadingLocaleParam, normalisePathPattern, normaliseRoutePath, stripLeadingLocaleParam } from "./normalise.mjs";
3
- import { doPathPatternsOverlap, getOrderedPathParamSignature, getPathParamSignature, getPathPatternValidationIssue, getPhysicalPathPattern, haveEqualPatternSpecificity } from "./patterns.mjs";
4
- const isLocalisableRoutePath = (path)=>{
5
- const pathWithoutLocale = stripLeadingLocaleParam(path);
6
- if (!pathWithoutLocale || '/' === pathWithoutLocale || '*' === pathWithoutLocale) return false;
7
- return true;
8
- };
9
- const joinPath = (parentPath, routePath)=>{
10
- if (!isLocalisableRoutePath(routePath)) return parentPath;
11
- const segment = normaliseRoutePath(stripLeadingLocaleParam(routePath) || '');
12
- return normalisePathPattern(`${parentPath}/${segment}`);
13
- };
14
- const isFrameworkInternalRoutePath = (canonicalPath)=>'/_mf' === canonicalPath || canonicalPath.startsWith('/_mf/');
15
- const legalRouteIdPart = (value)=>value.replace(/[^a-zA-Z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'index';
16
- const formatParamSignature = (signature)=>signature.length > 0 ? signature.join(', ') : '(none)';
17
- const validateLocalisedUrlPatterns = (languages, localisedUrls)=>{
18
- const physicalPatterns = new Map();
19
- const claimedPatterns = [];
20
- const claimPhysicalPattern = (sourcePattern, canonicalPath, source)=>{
21
- const physicalPattern = getPhysicalPathPattern(sourcePattern);
22
- const existing = physicalPatterns.get(physicalPattern);
23
- if (existing && existing.canonicalPath !== canonicalPath) throw new Error(`localisedUrls["${existing.canonicalPath}"] ${existing.source} and localisedUrls["${canonicalPath}"] ${source} generate the same physical route pattern "${physicalPattern}". Localised route patterns must identify exactly one canonical route.`);
24
- const overlapping = claimedPatterns.find((claimed)=>claimed.canonicalPath !== canonicalPath && haveEqualPatternSpecificity(claimed.pattern, sourcePattern) && doPathPatternsOverlap(claimed.pattern, sourcePattern));
25
- if (overlapping) throw new Error(`localisedUrls["${overlapping.canonicalPath}"] ${overlapping.source} pattern "${overlapping.pattern}" and localisedUrls["${canonicalPath}"] ${source} pattern "${sourcePattern}" are equal-specificity overlapping route patterns. Localised route patterns must identify exactly one canonical route.`);
26
- physicalPatterns.set(physicalPattern, {
27
- canonicalPath,
28
- source
29
- });
30
- claimedPatterns.push({
31
- canonicalPath,
32
- pattern: sourcePattern,
33
- source
34
- });
35
- };
36
- for (const [canonicalPath, entry] of Object.entries(localisedUrls).sort(([left], [right])=>left < right ? -1 : left > right ? 1 : 0)){
37
- const canonicalIssue = getPathPatternValidationIssue(canonicalPath);
38
- if (canonicalIssue) throw new Error(`localisedUrls["${canonicalPath}"] canonical pattern has ${canonicalIssue}.`);
39
- const expectedSignature = getPathParamSignature(canonicalPath);
40
- const expectedOrderedSignature = getOrderedPathParamSignature(canonicalPath);
41
- claimPhysicalPattern(canonicalPath, canonicalPath, 'canonical pattern');
42
- for (const language of languages){
43
- const localisedPattern = entry[language];
44
- if (!localisedPattern) continue;
45
- const localisedIssue = getPathPatternValidationIssue(localisedPattern);
46
- if (localisedIssue) throw new Error(`localisedUrls["${canonicalPath}"].${language} has ${localisedIssue}.`);
47
- const actualSignature = getPathParamSignature(localisedPattern);
48
- if (actualSignature.length !== expectedSignature.length || actualSignature.some((parameter, index)=>parameter !== expectedSignature[index])) throw new Error(`localisedUrls["${canonicalPath}"].${language} must use the same parameter signature as "${canonicalPath}": expected ${formatParamSignature(expectedSignature)}; received ${formatParamSignature(actualSignature)}.`);
49
- const actualOrderedSignature = getOrderedPathParamSignature(localisedPattern);
50
- if (expectedOrderedSignature.some((parameter)=>'*' === parameter || parameter.endsWith('?')) && actualOrderedSignature.some((parameter, index)=>parameter !== expectedOrderedSignature[index])) throw new Error(`localisedUrls["${canonicalPath}"].${language} must keep optional and splat parameters in canonical order: expected ${formatParamSignature(expectedOrderedSignature)}; received ${formatParamSignature(actualOrderedSignature)}.`);
51
- claimPhysicalPattern(localisedPattern, canonicalPath, `.${language}`);
52
- }
53
- }
54
- };
55
- const ensureLocalisedUrlsForPath = (canonicalPath, languages, localisedUrls)=>{
56
- const entry = localisedUrls[canonicalPath];
57
- if (!entry) throw new Error(`localisedUrls is enabled, but route "${canonicalPath}" does not define localised URLs for languages: ${languages.join(', ')}. Add localisedUrls["${canonicalPath}"] or set localeDetection.localisedUrls to false.`);
58
- const missingLanguages = languages.filter((language)=>!entry[language]);
59
- if (missingLanguages.length > 0) throw new Error(`localisedUrls["${canonicalPath}"] is missing languages: ${missingLanguages.join(', ')}. Every configured language must have a localised URL.`);
60
- return entry;
61
- };
62
- const validateLocalisedUrls = (routes, languages, localisedUrls)=>{
63
- validateLocalisedUrlPatterns(languages, localisedUrls);
64
- const visit = (route, parentPath)=>{
65
- const canonicalPath = joinPath(parentPath, route.path);
66
- if (isLocalisableRoutePath(route.path) && !isFrameworkInternalRoutePath(canonicalPath)) ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls);
67
- if ('children' in route && route.children) route.children.forEach((child)=>visit(child, canonicalPath));
68
- };
69
- routes.forEach((route)=>visit(route, ''));
70
- };
71
- const getLocalisedRouteIdSuffixes = (paths)=>{
72
- const usedSuffixes = new Set();
73
- return paths.map((path, index)=>{
74
- if (0 === index) return;
75
- const baseSuffix = legalRouteIdPart(path);
76
- let suffix = baseSuffix;
77
- let disambiguator = 2;
78
- while(usedSuffixes.has(suffix))suffix = `${baseSuffix}_${disambiguator++}`;
79
- usedSuffixes.add(suffix);
80
- return suffix;
81
- });
82
- };
83
- const getLocalisedRoutePaths = (canonicalPath, parentLocalisedPaths, languages, entry)=>{
84
- const paths = languages.map((language)=>{
85
- const fullPath = normalisePathPattern(entry[language]);
86
- const parentPath = normalisePathPattern(parentLocalisedPaths[language] || '/');
87
- if ('/' === parentPath) return normaliseRoutePath(fullPath) || void 0;
88
- const parentPrefix = `${parentPath}/`;
89
- if (!fullPath.startsWith(parentPrefix)) throw new Error(`localisedUrls["${canonicalPath}"].${language} must be nested under "${parentPath}" because its parent route is localised there.`);
90
- return normaliseRoutePath(fullPath.slice(parentPath.length));
91
- });
92
- const uniquePaths = Array.from(new Set(paths.filter(Boolean)));
93
- return uniquePaths;
94
- };
95
- const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPaths, languages, localisedUrls)=>{
96
- const canonicalPath = joinPath(parentCanonicalPath, route.path);
97
- const localisedUrlEntry = isLocalisableRoutePath(route.path) && !isFrameworkInternalRoutePath(canonicalPath) ? ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls) : void 0;
98
- const routeLocalisedPaths = localisedUrlEntry ? languages.reduce((acc, language)=>{
99
- acc[language] = normalisePathPattern(localisedUrlEntry[language]);
100
- return acc;
101
- }, {}) : parentLocalisedPaths;
102
- const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
103
- const baseRoute = {
104
- ...route,
105
- ...!isFrameworkInternalRoutePath(canonicalPath) ? {
106
- modernLocalisedRoute: {
107
- id: route.id,
108
- path: route.path,
109
- canonicalPath,
110
- paths: localisedUrlEntry
111
- }
112
- } : {},
113
- ...children ? {
114
- children
115
- } : {}
116
- };
117
- if (!localisedUrlEntry) return [
118
- baseRoute
119
- ];
120
- const localisedPaths = getLocalisedRoutePaths(canonicalPath, parentLocalisedPaths, languages, localisedUrlEntry);
121
- const leadingLocaleParam = getLeadingLocaleParam(route.path);
122
- const routeIdPaths = leadingLocaleParam ? localisedPaths.map((path)=>normaliseRoutePath(`${leadingLocaleParam}/${path}`)) : localisedPaths;
123
- const routeIdSuffixes = getLocalisedRouteIdSuffixes(routeIdPaths);
124
- return localisedPaths.map((localisedPath, index)=>cloneRouteWithLocalisedPath(baseRoute, localisedPath, routeIdSuffixes[index], canonicalPath));
125
- };
126
- const suffixRouteIds = (route, suffix)=>{
127
- const children = 'children' in route && route.children ? route.children.map((child)=>suffixRouteIds(child, suffix)) : void 0;
128
- return {
129
- ...route,
130
- ...route.id ? {
131
- id: `${route.id}__localised_${suffix}`
132
- } : {},
133
- ...children ? {
134
- children
135
- } : {}
136
- };
137
- };
138
- const collectRouteIdCounts = (routes, counts = new Map())=>{
139
- for (const route of routes){
140
- if (route.id) counts.set(route.id, (counts.get(route.id) ?? 0) + 1);
141
- if ('children' in route && route.children) collectRouteIdCounts(route.children, counts);
142
- }
143
- return counts;
144
- };
145
- const validateNoNewRouteIdCollisions = (sourceRoutes, localisedRoutes)=>{
146
- const sourceCounts = collectRouteIdCounts(sourceRoutes);
147
- const localisedCounts = collectRouteIdCounts(localisedRoutes);
148
- for (const [routeId, count] of localisedCounts)if (count > 1 && count > (sourceCounts.get(routeId) ?? 0)) throw new Error(`Localised route expansion generated duplicate route ID "${routeId}". Change the source route ID or localised path so every generated route remains identifiable.`);
149
- };
150
- const cloneRouteWithLocalisedPath = (route, path, routeIdSuffix, canonicalPath)=>{
151
- const leadingLocaleParam = getLeadingLocaleParam(route.path);
152
- const localisedPath = leadingLocaleParam ? normaliseRoutePath(`${leadingLocaleParam}/${path}`) : path;
153
- const routeWithPath = {
154
- ...route,
155
- path: localisedPath
156
- };
157
- routeWithPath.modernCanonicalPath = canonicalPath;
158
- return routeIdSuffix ? suffixRouteIds(routeWithPath, routeIdSuffix) : routeWithPath;
159
- };
160
- const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
161
- const rootLocalisedPaths = languages.reduce((acc, language)=>{
162
- acc[language] = '/';
163
- return acc;
164
- }, {});
165
- validateLocalisedUrls(routes, languages, localisedUrls);
166
- const localisedRoutes = routes.flatMap((route)=>transformLocalisedRoute(route, '', rootLocalisedPaths, languages, localisedUrls));
167
- validateNoNewRouteIdCollisions(routes, localisedRoutes);
168
- return localisedRoutes;
169
- };
170
- export { applyLocalisedUrlsToRoutes, validateLocalisedUrls };
@@ -1 +0,0 @@
1
- import "node:module";
@@ -1,9 +0,0 @@
1
- import type { LocalisedUrlsOption, ResolvedLocalisedUrlsConfig } from './types.js';
2
- /**
3
- * Localised URLs are strictly opt-in: only an explicit, non-empty map enables
4
- * route expansion and validation. `true`, `false`, an empty map and absence
5
- * all resolve to disabled, so upstream-style configs (`localePathRedirect` +
6
- * `languages` without a map) keep plain locale-prefix behavior instead of
7
- * failing the build for every route missing from a map they never wrote.
8
- */
9
- export declare const resolveLocalisedUrlsConfig: (option: LocalisedUrlsOption | undefined) => ResolvedLocalisedUrlsConfig;
@@ -1,10 +0,0 @@
1
- export { resolveLocalisedUrlsConfig } from './config.js';
2
- export { normalisePathname, normalisePathPattern } from './normalise.js';
3
- export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix, } from './pathname.js';
4
- export { buildPathFromPattern, matchPathPattern } from './patterns.js';
5
- export type { LocaleRedirectSkipRule } from './redirect.js';
6
- export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect, } from './redirect.js';
7
- export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve.js';
8
- export { canonicaliseLocalisedRoutes, createLocalisedRouteRewrite, } from './routeIdentity.js';
9
- export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes.js';
10
- export type { LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './types.js';
@@ -1,10 +0,0 @@
1
- export declare const normalisePathPattern: (path: string) => string;
2
- /**
3
- * Normalise a concrete request pathname: slash cleanup only. Unlike
4
- * {@link normalisePathPattern} it must not rewrite literal `[x]` segments to
5
- * `:x` params — pathnames are values, not patterns.
6
- */
7
- export declare const normalisePathname: (pathname: string) => string;
8
- export declare const normaliseRoutePath: (path: string) => string;
9
- export declare const stripLeadingLocaleParam: (path?: string) => string | undefined;
10
- export declare const getLeadingLocaleParam: (path?: string) => string | null;
@@ -1,4 +0,0 @@
1
- import type { LocalisedUrlsOption } from './types.js';
2
- export declare const stripLanguagePrefix: (pathname: string, languages: string[]) => string;
3
- export declare const localiseTargetPathname: (pathname: string, language: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
4
- export declare const canonicalTargetPathname: (pathname: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
@@ -1,11 +0,0 @@
1
- export declare const getOrderedPathParamSignature: (pattern: string) => string[];
2
- export declare const getPathParamSignature: (pattern: string) => string[];
3
- export declare const getPathPatternValidationIssue: (pattern: string) => string | undefined;
4
- export declare const getPhysicalPathPattern: (pattern: string) => string;
5
- export declare const haveEqualPatternSpecificity: (left: string, right: string) => boolean;
6
- export declare const doPathPatternsOverlap: (left: string, right: string) => boolean;
7
- export declare const sortPatternsBySpecificity: <T extends {
8
- pattern: string;
9
- }>(patterns: T[]) => T[];
10
- export declare const matchPathPattern: (pathname: string, pattern: string) => Record<string, string> | null;
11
- export declare const buildPathFromPattern: (pattern: string, params: Record<string, string>) => string;
@@ -1,19 +0,0 @@
1
- export type LocaleRedirectSkipRule = {
2
- type: 'exact';
3
- path: string;
4
- } | {
5
- type: 'prefix';
6
- path: string;
7
- } | {
8
- type: 'pattern';
9
- path: RegExp;
10
- };
11
- /**
12
- * Federation artifacts are never pages. The remote entries match the same
13
- * shapes the federation layer itself recognises and serves (`mfCache.ts`):
14
- * a manifest may name a root entry such as `remoteEntry.catalog.js`.
15
- */
16
- export declare const DEFAULT_LOCALE_REDIRECT_SKIP_RULES: readonly LocaleRedirectSkipRule[];
17
- export declare const matchesPathPrefix: (pathname: string, prefix: string) => boolean;
18
- export declare const isDefaultLocaleRedirectSkipPath: (pathname: string, languages?: string[]) => boolean;
19
- export declare const shouldSkipLocaleRedirect: (pathname: string, languages?: string[], ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean)) => boolean;
@@ -1,7 +0,0 @@
1
- import type { LocalisedUrlsMap } from './types.js';
2
- export declare const resolveLocalisedPath: (pathname: string, targetLanguage: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
3
- /**
4
- * Reverse-map language-specific pathnames back to language-agnostic
5
- * LocalisedUrlsMap keys.
6
- */
7
- export declare const resolveCanonicalLocalisedPath: (pathname: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
@@ -1,24 +0,0 @@
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 {};
@@ -1,3 +0,0 @@
1
- import type { LocalisedRoute, LocalisedUrlsMap } from './types.js';
2
- export declare const validateLocalisedUrls: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => void;
3
- export declare const applyLocalisedUrlsToRoutes: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => LocalisedRoute[];
@@ -1,14 +0,0 @@
1
- export type LocalisedRoute = {
2
- type: 'nested' | 'page';
3
- path?: string;
4
- id?: string;
5
- children?: LocalisedRoute[];
6
- [key: string]: any;
7
- };
8
- export type LocalisedUrlPathMap = Record<string, string>;
9
- export type LocalisedUrlsMap = Record<string, LocalisedUrlPathMap>;
10
- export type LocalisedUrlsOption = boolean | LocalisedUrlsMap;
11
- export interface ResolvedLocalisedUrlsConfig {
12
- enabled: boolean;
13
- map: LocalisedUrlsMap;
14
- }
@@ -1,18 +0,0 @@
1
- import type { LocalisedUrlsOption, ResolvedLocalisedUrlsConfig } from './types';
2
-
3
- /**
4
- * Localised URLs are strictly opt-in: only an explicit, non-empty map enables
5
- * route expansion and validation. `true`, `false`, an empty map and absence
6
- * all resolve to disabled, so upstream-style configs (`localePathRedirect` +
7
- * `languages` without a map) keep plain locale-prefix behavior instead of
8
- * failing the build for every route missing from a map they never wrote.
9
- */
10
- export const resolveLocalisedUrlsConfig = (
11
- option: LocalisedUrlsOption | undefined,
12
- ): ResolvedLocalisedUrlsConfig => {
13
- if (option && typeof option === 'object' && Object.keys(option).length > 0) {
14
- return { enabled: true, map: option };
15
- }
16
-
17
- return { enabled: false, map: {} };
18
- };
@@ -1,28 +0,0 @@
1
- export { resolveLocalisedUrlsConfig } from './config';
2
- export { normalisePathname, normalisePathPattern } from './normalise';
3
- export {
4
- canonicalTargetPathname,
5
- localiseTargetPathname,
6
- stripLanguagePrefix,
7
- } from './pathname';
8
- export { buildPathFromPattern, matchPathPattern } from './patterns';
9
- export type { LocaleRedirectSkipRule } from './redirect';
10
- export {
11
- DEFAULT_LOCALE_REDIRECT_SKIP_RULES,
12
- isDefaultLocaleRedirectSkipPath,
13
- matchesPathPrefix,
14
- shouldSkipLocaleRedirect,
15
- } from './redirect';
16
- export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve';
17
- export {
18
- canonicaliseLocalisedRoutes,
19
- createLocalisedRouteRewrite,
20
- } from './routeIdentity';
21
- export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes';
22
- export type {
23
- LocalisedRoute,
24
- LocalisedUrlPathMap,
25
- LocalisedUrlsMap,
26
- LocalisedUrlsOption,
27
- ResolvedLocalisedUrlsConfig,
28
- } from './types';
@@ -1,62 +0,0 @@
1
- const LOCALE_PARAM_NAMES = new Set(['lang', 'locale', 'language']);
2
-
3
- const normaliseSlashes = (path: string): string => {
4
- const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
5
- const withLeadingSlash = withoutDuplicateSlashes.startsWith('/')
6
- ? withoutDuplicateSlashes
7
- : `/${withoutDuplicateSlashes}`;
8
-
9
- return withLeadingSlash.length > 1
10
- ? withLeadingSlash.replace(/\/+$/, '')
11
- : withLeadingSlash;
12
- };
13
-
14
- export const normalisePathPattern = (path: string): string =>
15
- normaliseSlashes(path).replace(/\[(.+?)\]/g, ':$1');
16
-
17
- /**
18
- * Normalise a concrete request pathname: slash cleanup only. Unlike
19
- * {@link normalisePathPattern} it must not rewrite literal `[x]` segments to
20
- * `:x` params — pathnames are values, not patterns.
21
- */
22
- export const normalisePathname = (pathname: string): string =>
23
- normaliseSlashes(pathname);
24
-
25
- export const normaliseRoutePath = (path: string): string => {
26
- const normalized = normalisePathPattern(path);
27
- return normalized === '/' ? '' : normalized.slice(1);
28
- };
29
-
30
- const getLocaleParamSegment = (segment: string): string | null => {
31
- if (!segment.startsWith(':')) {
32
- return null;
33
- }
34
-
35
- const paramName = segment.slice(1).replace(/\?$/, '');
36
- return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
37
- };
38
-
39
- const splitPathSegments = (path?: string): string[] => {
40
- if (!path) {
41
- return [];
42
- }
43
-
44
- return normalisePathPattern(path).split('/').filter(Boolean);
45
- };
46
-
47
- export const stripLeadingLocaleParam = (path?: string): string | undefined => {
48
- const segments = splitPathSegments(path);
49
- const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
50
-
51
- if (!leadingLocaleParam) {
52
- return path;
53
- }
54
-
55
- const remainingPath = segments.slice(1).join('/');
56
- return remainingPath ? `/${remainingPath}` : undefined;
57
- };
58
-
59
- export const getLeadingLocaleParam = (path?: string): string | null => {
60
- const segments = splitPathSegments(path);
61
- return getLocaleParamSegment(segments[0] || '');
62
- };
@@ -1,60 +0,0 @@
1
- import { resolveLocalisedUrlsConfig } from './config';
2
-
3
- import { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve';
4
-
5
- import type { LocalisedUrlsOption } from './types';
6
-
7
- export const stripLanguagePrefix = (
8
- pathname: string,
9
- languages: string[],
10
- ): string => {
11
- const segments = pathname.split('/').filter(Boolean);
12
- const firstSegment = segments[0]?.toLowerCase();
13
-
14
- if (
15
- firstSegment &&
16
- languages.some(language => language.toLowerCase() === firstSegment)
17
- ) {
18
- return `/${segments.slice(1).join('/')}`;
19
- }
20
-
21
- return pathname || '/';
22
- };
23
-
24
- export const localiseTargetPathname = (
25
- pathname: string,
26
- language: string,
27
- languages: string[],
28
- localisedUrls?: LocalisedUrlsOption,
29
- ): string => {
30
- const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
31
- const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
32
- const resolvedPath = localisedUrlsConfig.enabled
33
- ? resolveLocalisedPath(
34
- pathWithoutLanguage,
35
- language,
36
- languages,
37
- localisedUrlsConfig.map,
38
- )
39
- : pathWithoutLanguage;
40
- const resolvedSegments = resolvedPath.split('/').filter(Boolean);
41
-
42
- return `/${[language, ...resolvedSegments].join('/')}`;
43
- };
44
-
45
- export const canonicalTargetPathname = (
46
- pathname: string,
47
- languages: string[],
48
- localisedUrls?: LocalisedUrlsOption,
49
- ): string => {
50
- const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
51
- const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
52
-
53
- return localisedUrlsConfig.enabled
54
- ? resolveCanonicalLocalisedPath(
55
- pathWithoutLanguage,
56
- languages,
57
- localisedUrlsConfig.map,
58
- )
59
- : pathWithoutLanguage;
60
- };