@bleedingdev/modern-js-i18n-runtime-extensions 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10

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 (56) hide show
  1. package/dist/cjs/index.js +9 -2
  2. package/dist/cjs/localeRedirectPolicy.js +96 -0
  3. package/dist/cjs/localisedUrls/index.js +7 -0
  4. package/dist/cjs/localisedUrls/redirect.js +3 -6
  5. package/dist/cjs/localisedUrls/routeIdentity.js +118 -0
  6. package/dist/cjs/localisedUrls/routes.js +8 -0
  7. package/dist/cjs/paths.js +56 -0
  8. package/dist/cjs/router-navigation/adapter.js +235 -0
  9. package/dist/cjs/router-navigation/index.js +40 -0
  10. package/dist/esm/index.mjs +1 -0
  11. package/dist/esm/localeRedirectPolicy.mjs +46 -0
  12. package/dist/esm/localisedUrls/index.mjs +1 -0
  13. package/dist/esm/localisedUrls/redirect.mjs +3 -6
  14. package/dist/esm/localisedUrls/routeIdentity.mjs +77 -0
  15. package/dist/esm/localisedUrls/routes.mjs +8 -0
  16. package/dist/esm/paths.mjs +12 -0
  17. package/dist/esm/router-navigation/adapter.mjs +197 -0
  18. package/dist/esm/router-navigation/index.mjs +1 -0
  19. package/dist/esm-node/index.mjs +1 -0
  20. package/dist/esm-node/localeRedirectPolicy.mjs +47 -0
  21. package/dist/esm-node/localisedUrls/index.mjs +1 -0
  22. package/dist/esm-node/localisedUrls/redirect.mjs +3 -6
  23. package/dist/esm-node/localisedUrls/routeIdentity.mjs +78 -0
  24. package/dist/esm-node/localisedUrls/routes.mjs +8 -0
  25. package/dist/esm-node/paths.mjs +13 -0
  26. package/dist/esm-node/router-navigation/adapter.mjs +198 -0
  27. package/dist/esm-node/router-navigation/index.mjs +2 -0
  28. package/dist/types/index.d.ts +4 -3
  29. package/dist/types/language-sync/react.d.ts +1 -1
  30. package/dist/types/localeRedirectPolicy.d.ts +22 -0
  31. package/dist/types/localisedUrls/config.d.ts +1 -1
  32. package/dist/types/localisedUrls/index.d.ts +10 -9
  33. package/dist/types/localisedUrls/pathname.d.ts +1 -1
  34. package/dist/types/localisedUrls/redirect.d.ts +8 -0
  35. package/dist/types/localisedUrls/resolve.d.ts +1 -1
  36. package/dist/types/localisedUrls/routeIdentity.d.ts +24 -0
  37. package/dist/types/localisedUrls/routes.d.ts +1 -1
  38. package/dist/types/paths.d.ts +9 -0
  39. package/dist/types/router-navigation/adapter.d.ts +71 -0
  40. package/dist/types/router-navigation/index.d.ts +1 -0
  41. package/package.json +30 -6
  42. package/src/index.ts +1 -0
  43. package/src/localeRedirectPolicy.ts +127 -0
  44. package/src/localisedUrls/index.ts +4 -0
  45. package/src/localisedUrls/redirect.ts +16 -2
  46. package/src/localisedUrls/routeIdentity.ts +107 -0
  47. package/src/localisedUrls/routes.ts +10 -0
  48. package/src/paths.ts +37 -0
  49. package/src/router-navigation/adapter.tsx +536 -0
  50. package/src/router-navigation/index.ts +6 -0
  51. package/tests/languageSyncController.test.ts +0 -77
  52. package/tests/localeRedirectPolicy.test.ts +173 -0
  53. package/tests/localisedUrls.test.ts +0 -141
  54. package/tests/paths.test.ts +24 -0
  55. package/tests/reactLanguageSync.test.tsx +20 -1
  56. package/tests/clientRedirectIntegration.test.tsx +0 -74
@@ -9,15 +9,26 @@ export type LocaleRedirectSkipRule =
9
9
  | {
10
10
  type: 'prefix';
11
11
  path: string;
12
+ }
13
+ | {
14
+ type: 'pattern';
15
+ path: RegExp;
12
16
  };
13
17
 
18
+ /**
19
+ * Federation artifacts are never pages. The remote entries match the same
20
+ * shapes the federation layer itself recognises and serves (`mfCache.ts`):
21
+ * a manifest may name a root entry such as `remoteEntry.catalog.js`.
22
+ */
14
23
  export const DEFAULT_LOCALE_REDIRECT_SKIP_RULES: readonly LocaleRedirectSkipRule[] =
15
24
  [
16
25
  { type: 'exact', path: '/backend-mf-manifest.json' },
17
- { type: 'exact', path: '/backendRemoteEntry.cjs' },
18
26
  { type: 'exact', path: '/mf-manifest.json' },
19
27
  { type: 'exact', path: '/mf-stats.json' },
20
- { type: 'exact', path: '/remoteEntry.js' },
28
+ {
29
+ type: 'pattern',
30
+ path: /^\/(?:backendRemoteEntry(?:\.[a-zA-Z0-9_-]+)?\.cjs|remoteEntry(?:\.[a-zA-Z0-9_-]+)?\.js)$/,
31
+ },
21
32
  { type: 'prefix', path: '/static/' },
22
33
  { type: 'prefix', path: '/upload/' },
23
34
  ];
@@ -45,6 +56,9 @@ const matchesSkipRule = (
45
56
  if (rule.type === 'exact') {
46
57
  return normalisePathname(pathname) === normalisePathname(rule.path);
47
58
  }
59
+ if (rule.type === 'pattern') {
60
+ return rule.path.test(normalisePathname(pathname));
61
+ }
48
62
 
49
63
  return matchesPathPrefix(pathname, rule.path);
50
64
  };
@@ -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
 
package/src/paths.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { splitUrlTarget } from '@modern-js/runtime-utils/url';
2
+ import {
3
+ canonicalTargetPathname,
4
+ type LocalisedUrlsOption,
5
+ localiseTargetPathname,
6
+ } from './localisedUrls';
7
+
8
+ export interface LocalizedPathsConfig {
9
+ languages: string[];
10
+ localisedUrls?: LocalisedUrlsOption;
11
+ }
12
+
13
+ /** Synchronous mapped URL construction, independent of React/plugin setup. */
14
+ export const buildLocalizedUrl = (
15
+ target: string,
16
+ language: string,
17
+ languages: string[],
18
+ localisedUrls?: LocalisedUrlsOption,
19
+ ): string => {
20
+ const { pathname, search, hash } = splitUrlTarget(target);
21
+ return `${localiseTargetPathname(pathname, language, languages, localisedUrls)}${search}${hash}`;
22
+ };
23
+
24
+ export const localizePath = (
25
+ target: string,
26
+ language: string,
27
+ config: LocalizedPathsConfig,
28
+ ): string =>
29
+ buildLocalizedUrl(target, language, config.languages, config.localisedUrls);
30
+
31
+ export const canonicalPath = (
32
+ target: string,
33
+ config: LocalizedPathsConfig,
34
+ ): string => {
35
+ const { pathname, search, hash } = splitUrlTarget(target);
36
+ return `${canonicalTargetPathname(pathname, config.languages, config.localisedUrls)}${search}${hash}`;
37
+ };