@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21

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 (77) hide show
  1. package/dist/cjs/index.js +15 -8
  2. package/dist/cjs/localeRedirectPolicy.js +96 -0
  3. package/dist/cjs/paths.js +56 -0
  4. package/dist/cjs/router-navigation/adapter.js +93 -0
  5. package/dist/cjs/{localisedUrls/config.js → router-navigation/index.js} +5 -14
  6. package/dist/esm/index.mjs +2 -1
  7. package/dist/esm/localeRedirectPolicy.mjs +46 -0
  8. package/dist/esm/paths.mjs +12 -0
  9. package/dist/esm/router-navigation/adapter.mjs +55 -0
  10. package/dist/esm/router-navigation/index.mjs +1 -0
  11. package/dist/esm-node/index.mjs +2 -1
  12. package/dist/esm-node/localeRedirectPolicy.mjs +47 -0
  13. package/dist/esm-node/paths.mjs +13 -0
  14. package/dist/esm-node/router-navigation/adapter.mjs +56 -0
  15. package/dist/esm-node/router-navigation/index.mjs +2 -0
  16. package/dist/types/index.d.ts +2 -1
  17. package/dist/types/localeRedirectPolicy.d.ts +22 -0
  18. package/dist/types/paths.d.ts +9 -0
  19. package/dist/types/router-navigation/adapter.d.ts +23 -0
  20. package/dist/types/router-navigation/index.d.ts +1 -0
  21. package/package.json +33 -9
  22. package/src/index.ts +2 -1
  23. package/src/localeRedirectPolicy.ts +127 -0
  24. package/src/paths.ts +37 -0
  25. package/src/router-navigation/adapter.tsx +120 -0
  26. package/src/router-navigation/index.ts +6 -0
  27. package/tests/languageSyncController.test.ts +0 -77
  28. package/tests/localeRedirectPolicy.test.ts +173 -0
  29. package/tests/paths.test.ts +24 -0
  30. package/tests/reactLanguageSync.test.tsx +21 -2
  31. package/tests/routerNavigation.test.tsx +80 -0
  32. package/dist/cjs/localisedUrls/index.js +0 -91
  33. package/dist/cjs/localisedUrls/normalise.js +0 -87
  34. package/dist/cjs/localisedUrls/pathname.js +0 -68
  35. package/dist/cjs/localisedUrls/patterns.js +0 -214
  36. package/dist/cjs/localisedUrls/redirect.js +0 -103
  37. package/dist/cjs/localisedUrls/resolve.js +0 -78
  38. package/dist/cjs/localisedUrls/routes.js +0 -202
  39. package/dist/cjs/localisedUrls/types.js +0 -18
  40. package/dist/esm/localisedUrls/config.mjs +0 -11
  41. package/dist/esm/localisedUrls/index.mjs +0 -7
  42. package/dist/esm/localisedUrls/normalise.mjs +0 -37
  43. package/dist/esm/localisedUrls/pathname.mjs +0 -24
  44. package/dist/esm/localisedUrls/patterns.mjs +0 -152
  45. package/dist/esm/localisedUrls/redirect.mjs +0 -56
  46. package/dist/esm/localisedUrls/resolve.mjs +0 -37
  47. package/dist/esm/localisedUrls/routes.mjs +0 -161
  48. package/dist/esm/localisedUrls/types.mjs +0 -0
  49. package/dist/esm-node/localisedUrls/config.mjs +0 -12
  50. package/dist/esm-node/localisedUrls/index.mjs +0 -8
  51. package/dist/esm-node/localisedUrls/normalise.mjs +0 -38
  52. package/dist/esm-node/localisedUrls/pathname.mjs +0 -25
  53. package/dist/esm-node/localisedUrls/patterns.mjs +0 -153
  54. package/dist/esm-node/localisedUrls/redirect.mjs +0 -57
  55. package/dist/esm-node/localisedUrls/resolve.mjs +0 -38
  56. package/dist/esm-node/localisedUrls/routes.mjs +0 -162
  57. package/dist/esm-node/localisedUrls/types.mjs +0 -1
  58. package/dist/types/localisedUrls/config.d.ts +0 -9
  59. package/dist/types/localisedUrls/index.d.ts +0 -9
  60. package/dist/types/localisedUrls/normalise.d.ts +0 -10
  61. package/dist/types/localisedUrls/pathname.d.ts +0 -4
  62. package/dist/types/localisedUrls/patterns.d.ts +0 -11
  63. package/dist/types/localisedUrls/redirect.d.ts +0 -11
  64. package/dist/types/localisedUrls/resolve.d.ts +0 -7
  65. package/dist/types/localisedUrls/routes.d.ts +0 -3
  66. package/dist/types/localisedUrls/types.d.ts +0 -14
  67. package/src/localisedUrls/config.ts +0 -18
  68. package/src/localisedUrls/index.ts +0 -24
  69. package/src/localisedUrls/normalise.ts +0 -62
  70. package/src/localisedUrls/pathname.ts +0 -60
  71. package/src/localisedUrls/patterns.ts +0 -283
  72. package/src/localisedUrls/redirect.ts +0 -93
  73. package/src/localisedUrls/resolve.ts +0 -106
  74. package/src/localisedUrls/routes.ts +0 -422
  75. package/src/localisedUrls/types.ts +0 -18
  76. package/tests/clientRedirectIntegration.test.tsx +0 -74
  77. package/tests/localisedUrls.test.ts +0 -673
@@ -1,162 +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
- ...children ? {
106
- children
107
- } : {}
108
- };
109
- if (!localisedUrlEntry) return [
110
- baseRoute
111
- ];
112
- const localisedPaths = getLocalisedRoutePaths(canonicalPath, parentLocalisedPaths, languages, localisedUrlEntry);
113
- const leadingLocaleParam = getLeadingLocaleParam(route.path);
114
- const routeIdPaths = leadingLocaleParam ? localisedPaths.map((path)=>normaliseRoutePath(`${leadingLocaleParam}/${path}`)) : localisedPaths;
115
- const routeIdSuffixes = getLocalisedRouteIdSuffixes(routeIdPaths);
116
- return localisedPaths.map((localisedPath, index)=>cloneRouteWithLocalisedPath(baseRoute, localisedPath, routeIdSuffixes[index], canonicalPath));
117
- };
118
- const suffixRouteIds = (route, suffix)=>{
119
- const children = 'children' in route && route.children ? route.children.map((child)=>suffixRouteIds(child, suffix)) : void 0;
120
- return {
121
- ...route,
122
- ...route.id ? {
123
- id: `${route.id}__localised_${suffix}`
124
- } : {},
125
- ...children ? {
126
- children
127
- } : {}
128
- };
129
- };
130
- const collectRouteIdCounts = (routes, counts = new Map())=>{
131
- for (const route of routes){
132
- if (route.id) counts.set(route.id, (counts.get(route.id) ?? 0) + 1);
133
- if ('children' in route && route.children) collectRouteIdCounts(route.children, counts);
134
- }
135
- return counts;
136
- };
137
- const validateNoNewRouteIdCollisions = (sourceRoutes, localisedRoutes)=>{
138
- const sourceCounts = collectRouteIdCounts(sourceRoutes);
139
- const localisedCounts = collectRouteIdCounts(localisedRoutes);
140
- 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.`);
141
- };
142
- const cloneRouteWithLocalisedPath = (route, path, routeIdSuffix, canonicalPath)=>{
143
- const leadingLocaleParam = getLeadingLocaleParam(route.path);
144
- const localisedPath = leadingLocaleParam ? normaliseRoutePath(`${leadingLocaleParam}/${path}`) : path;
145
- const routeWithPath = {
146
- ...route,
147
- path: localisedPath
148
- };
149
- routeWithPath.modernCanonicalPath = canonicalPath;
150
- return routeIdSuffix ? suffixRouteIds(routeWithPath, routeIdSuffix) : routeWithPath;
151
- };
152
- const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
153
- const rootLocalisedPaths = languages.reduce((acc, language)=>{
154
- acc[language] = '/';
155
- return acc;
156
- }, {});
157
- validateLocalisedUrls(routes, languages, localisedUrls);
158
- const localisedRoutes = routes.flatMap((route)=>transformLocalisedRoute(route, '', rootLocalisedPaths, languages, localisedUrls));
159
- validateNoNewRouteIdCollisions(routes, localisedRoutes);
160
- return localisedRoutes;
161
- };
162
- 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,9 +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 { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes.js';
9
- 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,11 +0,0 @@
1
- export type LocaleRedirectSkipRule = {
2
- type: 'exact';
3
- path: string;
4
- } | {
5
- type: 'prefix';
6
- path: string;
7
- };
8
- export declare const DEFAULT_LOCALE_REDIRECT_SKIP_RULES: readonly LocaleRedirectSkipRule[];
9
- export declare const matchesPathPrefix: (pathname: string, prefix: string) => boolean;
10
- export declare const isDefaultLocaleRedirectSkipPath: (pathname: string, languages?: string[]) => boolean;
11
- 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,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,24 +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 { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes';
18
- export type {
19
- LocalisedRoute,
20
- LocalisedUrlPathMap,
21
- LocalisedUrlsMap,
22
- LocalisedUrlsOption,
23
- ResolvedLocalisedUrlsConfig,
24
- } 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
- };