@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20

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 (127) hide show
  1. package/dist/cjs/cli/index.js +10 -21
  2. package/dist/cjs/cli/localisedRoutes.js +47 -0
  3. package/dist/cjs/runtime/Link.js +19 -32
  4. package/dist/cjs/runtime/context.js +30 -73
  5. package/dist/cjs/runtime/contextHelpers.js +5 -5
  6. package/dist/cjs/runtime/contexts.js +49 -0
  7. package/dist/cjs/runtime/core.js +27 -14
  8. package/dist/cjs/runtime/hooks.js +7 -8
  9. package/dist/cjs/runtime/localizedPaths.js +11 -9
  10. package/dist/cjs/runtime/navigation.js +48 -0
  11. package/dist/cjs/runtime/providerComposition.js +37 -13
  12. package/dist/cjs/runtime/routerAdapter.js +33 -140
  13. package/dist/cjs/runtime/sharedContexts.js +43 -0
  14. package/dist/cjs/runtime/utils.js +21 -19
  15. package/dist/cjs/server/defaultUrlStrategy.js +48 -0
  16. package/dist/cjs/server/index.js +11 -7
  17. package/dist/cjs/server/mappedUrlStrategy.js +43 -0
  18. package/dist/cjs/server/redirectPolicy.js +33 -24
  19. package/dist/cjs/shared/mappedUrlStrategy.js +63 -0
  20. package/dist/cjs/shared/urlStrategy.js +48 -0
  21. package/dist/esm/cli/index.mjs +10 -21
  22. package/dist/esm/cli/localisedRoutes.mjs +9 -0
  23. package/dist/esm/runtime/Link.mjs +19 -32
  24. package/dist/esm/runtime/context.mjs +20 -72
  25. package/dist/esm/runtime/contextHelpers.mjs +5 -5
  26. package/dist/esm/runtime/contexts.mjs +5 -0
  27. package/dist/esm/runtime/core.mjs +13 -6
  28. package/dist/esm/runtime/hooks.mjs +7 -8
  29. package/dist/esm/runtime/localizedPaths.mjs +11 -9
  30. package/dist/esm/runtime/navigation.mjs +7 -0
  31. package/dist/esm/runtime/providerComposition.mjs +38 -14
  32. package/dist/esm/runtime/routerAdapter.mjs +28 -141
  33. package/dist/esm/runtime/sharedContexts.mjs +1 -0
  34. package/dist/esm/runtime/utils.mjs +19 -17
  35. package/dist/esm/server/defaultUrlStrategy.mjs +7 -0
  36. package/dist/esm/server/index.mjs +11 -7
  37. package/dist/esm/server/mappedUrlStrategy.mjs +1 -0
  38. package/dist/esm/server/redirectPolicy.mjs +33 -24
  39. package/dist/esm/shared/mappedUrlStrategy.mjs +19 -0
  40. package/dist/esm/shared/urlStrategy.mjs +7 -0
  41. package/dist/esm-node/cli/index.mjs +10 -21
  42. package/dist/esm-node/cli/localisedRoutes.mjs +10 -0
  43. package/dist/esm-node/runtime/Link.mjs +19 -32
  44. package/dist/esm-node/runtime/context.mjs +20 -72
  45. package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
  46. package/dist/esm-node/runtime/contexts.mjs +6 -0
  47. package/dist/esm-node/runtime/core.mjs +13 -6
  48. package/dist/esm-node/runtime/hooks.mjs +7 -8
  49. package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
  50. package/dist/esm-node/runtime/navigation.mjs +8 -0
  51. package/dist/esm-node/runtime/providerComposition.mjs +38 -14
  52. package/dist/esm-node/runtime/routerAdapter.mjs +28 -141
  53. package/dist/esm-node/runtime/sharedContexts.mjs +2 -0
  54. package/dist/esm-node/runtime/utils.mjs +19 -17
  55. package/dist/esm-node/server/defaultUrlStrategy.mjs +8 -0
  56. package/dist/esm-node/server/index.mjs +11 -7
  57. package/dist/esm-node/server/mappedUrlStrategy.mjs +2 -0
  58. package/dist/esm-node/server/redirectPolicy.mjs +33 -24
  59. package/dist/esm-node/shared/mappedUrlStrategy.mjs +20 -0
  60. package/dist/esm-node/shared/urlStrategy.mjs +8 -0
  61. package/dist/types/cli/localisedRoutes.d.ts +19 -0
  62. package/dist/types/runtime/context.d.ts +8 -11
  63. package/dist/types/runtime/contextHelpers.d.ts +2 -2
  64. package/dist/types/runtime/contexts.d.ts +6 -0
  65. package/dist/types/runtime/core.d.ts +12 -2
  66. package/dist/types/runtime/hooks.d.ts +4 -5
  67. package/dist/types/runtime/localizedPaths.d.ts +4 -4
  68. package/dist/types/runtime/navigation.d.ts +10 -0
  69. package/dist/types/runtime/providerComposition.d.ts +13 -1
  70. package/dist/types/runtime/routerAdapter.d.ts +23 -6
  71. package/dist/types/runtime/sharedContexts.d.ts +1 -0
  72. package/dist/types/runtime/utils.d.ts +4 -12
  73. package/dist/types/server/defaultUrlStrategy.d.ts +11 -0
  74. package/dist/types/server/index.d.ts +2 -0
  75. package/dist/types/server/mappedUrlStrategy.d.ts +7 -0
  76. package/dist/types/server/redirectPolicy.d.ts +5 -13
  77. package/dist/types/shared/mappedUrlStrategy.d.ts +27 -0
  78. package/dist/types/shared/type.d.ts +7 -11
  79. package/dist/types/shared/urlStrategy.d.ts +18 -0
  80. package/package.json +42 -19
  81. package/rslib.config.mts +8 -1
  82. package/rstest.config.mts +6 -5
  83. package/src/cli/index.ts +42 -42
  84. package/src/cli/localisedRoutes.ts +53 -0
  85. package/src/runtime/Link.tsx +20 -40
  86. package/src/runtime/context.tsx +33 -157
  87. package/src/runtime/contextHelpers.ts +7 -5
  88. package/src/runtime/contexts.ts +16 -0
  89. package/src/runtime/core.tsx +53 -6
  90. package/src/runtime/hooks.ts +14 -9
  91. package/src/runtime/i18n/instance.ts +1 -2
  92. package/src/runtime/localizedPaths.ts +24 -19
  93. package/src/runtime/navigation.tsx +20 -0
  94. package/src/runtime/providerComposition.tsx +71 -23
  95. package/src/runtime/routerAdapter.tsx +65 -339
  96. package/src/runtime/sharedContexts.ts +8 -0
  97. package/src/runtime/utils.ts +49 -31
  98. package/src/server/defaultUrlStrategy.ts +27 -0
  99. package/src/server/index.ts +39 -9
  100. package/src/server/mappedUrlStrategy.ts +10 -0
  101. package/src/server/redirectPolicy.ts +85 -70
  102. package/src/shared/mappedUrlStrategy.ts +59 -0
  103. package/src/shared/type.ts +7 -11
  104. package/src/shared/urlStrategy.ts +38 -0
  105. package/tests/backendDefaults.test.ts +2 -23
  106. package/tests/defaultUrlStrategy.test.ts +107 -0
  107. package/tests/federatedI18nBoundaryResolver.test.tsx +65 -0
  108. package/tests/i18nUtils.test.ts +31 -65
  109. package/tests/link.test.tsx +13 -511
  110. package/tests/localisedRouteGeneration.test.ts +200 -0
  111. package/tests/localisedUrls.test.ts +16 -208
  112. package/tests/mappedUrlStrategyDerivation.test.ts +62 -0
  113. package/tests/navigation.test.tsx +403 -0
  114. package/tests/reactI18nextRuntimeBoundary.test.ts +8 -83
  115. package/tests/redirectPolicy.test.ts +89 -27
  116. package/tests/routerAdapter.test.tsx +98 -1171
  117. package/tests/urlStrategySerialization.test.ts +122 -0
  118. package/tsconfig.json +4 -2
  119. package/tests/federatedI18nBoundary.client.test.tsx +0 -129
  120. package/tests/federatedI18nBoundary.test.tsx +0 -132
  121. package/tests/linkTypes.test.ts +0 -59
  122. package/tests/localisedUrlRewriteMatrix.fork.test.ts +0 -147
  123. package/tests/peerCohort.test.ts +0 -25
  124. package/tests/type-fixture/fixture-globals.d.ts +0 -11
  125. package/tests/type-fixture/i18nInstanceTypes.fixture.ts +0 -29
  126. package/tests/type-fixture/linkTypes.fixture.tsx +0 -51
  127. package/tests/type-fixture/tsconfig.json +0 -16
@@ -0,0 +1,43 @@
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
+ createMappedUrlStrategy: ()=>mappedUrlStrategy_js_namespaceObject.createMappedUrlStrategy,
32
+ resolveMappedUrlStrategy: ()=>mappedUrlStrategy_js_namespaceObject.resolveMappedUrlStrategy
33
+ });
34
+ const mappedUrlStrategy_js_namespaceObject = require("../shared/mappedUrlStrategy.js");
35
+ exports.createMappedUrlStrategy = __webpack_exports__.createMappedUrlStrategy;
36
+ exports.resolveMappedUrlStrategy = __webpack_exports__.resolveMappedUrlStrategy;
37
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
38
+ "createMappedUrlStrategy",
39
+ "resolveMappedUrlStrategy"
40
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
41
+ Object.defineProperty(exports, '__esModule', {
42
+ value: true
43
+ });
@@ -34,42 +34,51 @@ __webpack_require__.d(__webpack_exports__, {
34
34
  isStaticResourceRequest: ()=>isStaticResourceRequest,
35
35
  shouldIgnoreRedirect: ()=>shouldIgnoreRedirect
36
36
  });
37
- const i18n_runtime_extensions_namespaceObject = require("@modern-js/i18n-runtime-extensions");
37
+ const mappedUrlStrategy_js_namespaceObject = require("../shared/mappedUrlStrategy.js");
38
+ const urlStrategy_js_namespaceObject = require("../shared/urlStrategy.js");
38
39
  const stripUrlPathPrefix = (pathname, urlPath)=>{
39
40
  const basePath = urlPath.replace('/*', '');
40
41
  if (!basePath || '/' === basePath) return pathname;
41
- const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
42
- return remainingPath || '/';
42
+ return pathname === basePath || pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) || '/' : pathname;
43
43
  };
44
- const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
44
+ const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes, urlStrategy, languages = [])=>{
45
45
  const remainingPath = stripUrlPathPrefix(pathname, urlPath);
46
- return (0, i18n_runtime_extensions_namespaceObject.shouldSkipLocaleRedirect)(remainingPath, [], ignoreRedirectRoutes);
46
+ if ((0, mappedUrlStrategy_js_namespaceObject.isDefaultLocaleRedirectSkipPath)(remainingPath, languages)) return true;
47
+ if ((0, urlStrategy_js_namespaceObject.asI18nUrlStrategy)(urlStrategy)?.shouldSkipRedirect?.(remainingPath, languages)) return true;
48
+ if (!ignoreRedirectRoutes) return false;
49
+ const segments = remainingPath.split('/').filter(Boolean);
50
+ if (segments[0] && languages.some((language)=>language.toLowerCase() === segments[0].toLowerCase())) segments.shift();
51
+ const normalizedPath = `/${segments.join('/')}`;
52
+ return 'function' == typeof ignoreRedirectRoutes ? ignoreRedirectRoutes(normalizedPath) : ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
47
53
  };
48
54
  const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
49
- if ((0, i18n_runtime_extensions_namespaceObject.isDefaultLocaleRedirectSkipPath)(pathname, languages)) return true;
50
- const matchesStaticRoutePrefix = (targetPathname)=>staticRoutePrefixes.some((prefix)=>(0, i18n_runtime_extensions_namespaceObject.matchesPathPrefix)(targetPathname, prefix));
51
- if (matchesStaticRoutePrefix(pathname)) return true;
52
- const pathWithoutLanguage = (0, i18n_runtime_extensions_namespaceObject.stripLanguagePrefix)(pathname, languages);
53
- return pathWithoutLanguage !== pathname && matchesStaticRoutePrefix(pathWithoutLanguage);
55
+ const prefixes = [
56
+ ...staticRoutePrefixes,
57
+ '/static',
58
+ '/upload'
59
+ ];
60
+ const matches = (target)=>prefixes.some((prefix)=>target === prefix || target.startsWith(`${prefix}/`));
61
+ if (matches(pathname)) return true;
62
+ const segments = pathname.split('/').filter(Boolean);
63
+ if (segments[0] && languages.some((language)=>language.toLowerCase() === segments[0].toLowerCase())) return matches(`/${segments.slice(1).join('/')}`);
64
+ return false;
54
65
  };
55
66
  const getLanguageFromPath = (req, urlPath, languages)=>{
56
- const url = new URL(req.url, `http://${req.header().host}`);
57
- const pathname = url.pathname;
58
- const remainingPath = stripUrlPathPrefix(pathname, urlPath);
59
- const segments = remainingPath.split('/').filter(Boolean);
60
- const firstSegment = segments[0];
61
- if (languages.includes(firstSegment)) return firstSegment;
62
- return null;
67
+ const host = req.header?.().host;
68
+ const url = new URL(req.url, host ? `http://${host}` : void 0);
69
+ const firstSegment = stripUrlPathPrefix(url.pathname, urlPath).split('/').filter(Boolean)[0];
70
+ return languages.find((language)=>language.toLowerCase() === firstSegment?.toLowerCase()) ?? null;
63
71
  };
64
- const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
72
+ const buildLocalizedUrl = (req, urlPath, language, languages, urlStrategy)=>{
65
73
  const url = new URL(req.url);
66
- const pathname = url.pathname;
67
74
  const basePath = urlPath.replace('/*', '');
68
- const remainingPath = stripUrlPathPrefix(pathname, urlPath);
69
- const newPathname = (0, i18n_runtime_extensions_namespaceObject.localiseTargetPathname)(remainingPath, language, languages, localisedUrls);
70
- const suffix = `${url.search}${url.hash}`;
71
- const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
72
- return localizedUrl;
75
+ const remainingPath = stripUrlPathPrefix(url.pathname, urlPath);
76
+ const segments = remainingPath.split('/').filter(Boolean);
77
+ if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments[0] = language;
78
+ else segments.unshift(language);
79
+ const strategy = (0, urlStrategy_js_namespaceObject.asI18nUrlStrategy)(urlStrategy);
80
+ const pathname = strategy ? strategy.localizePathname(remainingPath, language, languages) : `/${segments.join('/')}`;
81
+ return `${'/' === basePath ? '' : basePath}${pathname}${url.search}${url.hash}`;
73
82
  };
74
83
  const createLocaleRedirectResponse = (location)=>new Response(null, {
75
84
  status: 302,
@@ -0,0 +1,63 @@
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
+ createMappedUrlStrategy: ()=>createMappedUrlStrategy,
32
+ isDefaultLocaleRedirectSkipPath: ()=>i18n_runtime_extensions_namespaceObject.isDefaultLocaleRedirectSkipPath,
33
+ resolveMappedUrlStrategy: ()=>resolveMappedUrlStrategy
34
+ });
35
+ const i18n_runtime_extensions_namespaceObject = require("@modern-js/i18n-runtime-extensions");
36
+ const createMappedUrlStrategy = (localisedUrls)=>({
37
+ localizePathname: (pathname, language, languages)=>(0, i18n_runtime_extensions_namespaceObject.localiseTargetPathname)(pathname, language, [
38
+ ...languages
39
+ ], localisedUrls),
40
+ canonicalPathname: (pathname, languages)=>(0, i18n_runtime_extensions_namespaceObject.canonicalTargetPathname)(pathname, [
41
+ ...languages
42
+ ], localisedUrls),
43
+ shouldSkipRedirect: (pathname, languages)=>(0, i18n_runtime_extensions_namespaceObject.isDefaultLocaleRedirectSkipPath)(pathname, [
44
+ ...languages
45
+ ])
46
+ });
47
+ const resolveMappedUrlStrategy = (localeDetection)=>{
48
+ if (!localeDetection || 'object' != typeof localeDetection) return;
49
+ const localisedUrls = Reflect.get(localeDetection, 'localisedUrls');
50
+ if (!localisedUrls || 'object' != typeof localisedUrls || 0 === Object.keys(localisedUrls).length) return;
51
+ return createMappedUrlStrategy(localisedUrls);
52
+ };
53
+ exports.createMappedUrlStrategy = __webpack_exports__.createMappedUrlStrategy;
54
+ exports.isDefaultLocaleRedirectSkipPath = __webpack_exports__.isDefaultLocaleRedirectSkipPath;
55
+ exports.resolveMappedUrlStrategy = __webpack_exports__.resolveMappedUrlStrategy;
56
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
57
+ "createMappedUrlStrategy",
58
+ "isDefaultLocaleRedirectSkipPath",
59
+ "resolveMappedUrlStrategy"
60
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
61
+ Object.defineProperty(exports, '__esModule', {
62
+ value: true
63
+ });
@@ -0,0 +1,48 @@
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
+ const asI18nUrlStrategy = (candidate)=>{
31
+ if (!candidate || 'object' != typeof candidate) return;
32
+ const strategy = candidate;
33
+ return 'function' == typeof strategy.localizePathname && 'function' == typeof strategy.canonicalPathname ? strategy : void 0;
34
+ };
35
+ const isUnusableI18nUrlStrategy = (candidate)=>Boolean(candidate) && 'object' == typeof candidate && void 0 === asI18nUrlStrategy(candidate);
36
+ __webpack_require__.d(__webpack_exports__, {}, {
37
+ asI18nUrlStrategy: asI18nUrlStrategy,
38
+ isUnusableI18nUrlStrategy: isUnusableI18nUrlStrategy
39
+ });
40
+ exports.asI18nUrlStrategy = __webpack_exports__.asI18nUrlStrategy;
41
+ exports.isUnusableI18nUrlStrategy = __webpack_exports__.isUnusableI18nUrlStrategy;
42
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
43
+ "asI18nUrlStrategy",
44
+ "isUnusableI18nUrlStrategy"
45
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
46
+ Object.defineProperty(exports, '__esModule', {
47
+ value: true
48
+ });
@@ -1,7 +1,9 @@
1
- import { applyLocalisedUrlsToRoutes, resolveLocalisedUrlsConfig } from "@modern-js/i18n-runtime-extensions";
2
1
  import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
2
+ import { logger } from "@modern-js/utils";
3
+ import { isUnusableI18nUrlStrategy } from "../shared/urlStrategy.mjs";
3
4
  import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
4
5
  import { applyDetectedBackendPaths, detectLocalesDirectory } from "./locales.mjs";
6
+ import { applyLocalisedRoutes } from "./localisedRoutes.mjs";
5
7
  import "../runtime/types.mjs";
6
8
  const i18nPlugin = (options = {})=>({
7
9
  name: '@modern-js/plugin-i18n',
@@ -31,6 +33,10 @@ const i18nPlugin = (options = {})=>({
31
33
  backend: backendOptions,
32
34
  ...extendedConfig
33
35
  };
36
+ if (isUnusableI18nUrlStrategy(config.urlStrategy)) {
37
+ delete config.urlStrategy;
38
+ logger.warn("[i18n] `urlStrategy` cannot be passed through `modern.config.ts`: the generated runtime registration carries plugin options as JSON, which drops its methods. Configure `localeDetection.localisedUrls` instead — the runtime and the server both derive the URL policy from it — or register a runtime plugin that supplies the strategy directly.");
39
+ }
34
40
  const { reactI18next } = extendedConfig;
35
41
  const runtimePluginPath = customPlugin?.runtime?.path || (false === reactI18next ? `@${metaName}/plugin-i18n/runtime/no-react-i18next` : `@${metaName}/plugin-i18n/runtime`);
36
42
  plugins.push({
@@ -43,27 +49,10 @@ const i18nPlugin = (options = {})=>({
43
49
  plugins
44
50
  };
45
51
  });
46
- api.modifyFileSystemRoutes(({ entrypoint, routes })=>{
47
- if (!localeDetection) return {
52
+ api.modifyFileSystemRoutes(({ entrypoint, routes })=>({
48
53
  entrypoint,
49
- routes
50
- };
51
- const localeDetectionOptions = getLocaleDetectionOptions(entrypoint.entryName, localeDetection);
52
- const { localePathRedirect, languages = [], localisedUrls } = localeDetectionOptions;
53
- if (!localePathRedirect || 0 === languages.length) return {
54
- entrypoint,
55
- routes
56
- };
57
- const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
58
- if (!localisedUrlsConfig.enabled) return {
59
- entrypoint,
60
- routes
61
- };
62
- return {
63
- entrypoint,
64
- routes: applyLocalisedUrlsToRoutes(routes, languages, localisedUrlsConfig.map)
65
- };
66
- });
54
+ routes: applyLocalisedRoutes(routes, localeDetection ? getLocaleDetectionOptions(entrypoint.entryName, localeDetection) : void 0, entrypoint)
55
+ }));
67
56
  api._internalServerPlugins(({ plugins })=>{
68
57
  const { serverRoutes, metaName } = api.getAppContext();
69
58
  const normalizedConfig = api.getNormalizedConfig();
@@ -0,0 +1,9 @@
1
+ import { applyLocalisedUrlsToRoutes, resolveLocalisedUrlsConfig } from "@modern-js/i18n-runtime-extensions";
2
+ import { getEntrypointRoutesOwner } from "@modern-js/runtime/cli";
3
+ const applyLocalisedRoutes = (routes, localeDetection, entrypoint)=>{
4
+ const { localePathRedirect, languages = [], localisedUrls } = localeDetection ?? {};
5
+ const resolved = resolveLocalisedUrlsConfig(localisedUrls);
6
+ if (!localePathRedirect || !languages.length || !resolved.enabled) return routes;
7
+ return applyLocalisedUrlsToRoutes(routes, languages, resolved.map, entrypoint && '@modern-js/plugin-tanstack' === getEntrypointRoutesOwner(entrypoint) ? 'canonical' : 'physical');
8
+ };
9
+ export { applyLocalisedRoutes };
@@ -9,10 +9,10 @@ const EXTERNAL_TARGET_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
9
9
  const Link = (props)=>{
10
10
  const { to, params, children, hash: hashProp, search: searchProp, hashScrollIntoView, activeOptions, activeProps, prefetch, preload, ...rest } = props;
11
11
  const adapter = useI18nRouterAdapter();
12
- const { language, supportedLanguages, localisedUrls } = useModernI18n();
12
+ const { language, supportedLanguages, urlStrategy } = useModernI18n();
13
13
  const config = {
14
14
  languages: supportedLanguages,
15
- localisedUrls
15
+ urlStrategy
16
16
  };
17
17
  const isExternal = EXTERNAL_TARGET_RE.test(to);
18
18
  const isBareHash = to.startsWith('#');
@@ -22,7 +22,7 @@ const Link = (props)=>{
22
22
  const interpolated = interpolateRouteParams(pathname || '/', params);
23
23
  const firstSegment = interpolated.split('/').filter(Boolean)[0];
24
24
  if (firstSegment && supportedLanguages.includes(firstSegment)) warnOnce(`lang-prefix:${to}`, `[plugin-i18n] <Link to="${to}"> starts with a language prefix. Write language-agnostic canonical paths; the Link localizes them automatically.`);
25
- const localizedPathname = buildLocalizedUrl(interpolated, language, supportedLanguages, localisedUrls);
25
+ const localizedPathname = buildLocalizedUrl(interpolated, language, supportedLanguages, urlStrategy);
26
26
  const hash = hashProp ?? (hashFromTo ? hashFromTo.slice(1) : '');
27
27
  const { searchString, searchObject } = normalizeSearch(searchProp, searchFromTo);
28
28
  return {
@@ -42,7 +42,7 @@ const Link = (props)=>{
42
42
  isBareHash,
43
43
  language,
44
44
  supportedLanguages,
45
- localisedUrls
45
+ urlStrategy
46
46
  ]);
47
47
  const isActive = useMemo(()=>{
48
48
  if (!target || !adapter.location) return false;
@@ -57,7 +57,7 @@ const Link = (props)=>{
57
57
  adapter.location,
58
58
  activeOptions?.exact,
59
59
  supportedLanguages,
60
- localisedUrls
60
+ urlStrategy
61
61
  ]);
62
62
  const resolvedActiveProps = splitActiveProps(isActive, activeProps);
63
63
  const activeAttributes = isActive ? {
@@ -72,7 +72,7 @@ const Link = (props)=>{
72
72
  children: children
73
73
  });
74
74
  }
75
- const { Link: RouterLink, hasRouter, framework } = adapter;
75
+ const { Link: RouterLink, hasRouter } = adapter;
76
76
  if (!hasRouter || !RouterLink) {
77
77
  const { replace: _replace, ...anchorProps } = rest;
78
78
  const { className: activeClassName, style: activeStyle, ...activeRest } = resolvedActiveProps;
@@ -95,38 +95,25 @@ const Link = (props)=>{
95
95
  ...rest.style,
96
96
  ...activeStyle
97
97
  };
98
- if ('tanstack' === framework) {
99
- const tanstackPreload = void 0 !== preload ? preload : void 0 === prefetch ? void 0 : 'none' === prefetch ? false : prefetch;
100
- return /*#__PURE__*/ jsx(RouterLink, {
101
- to: target.localizedPathname,
102
- ...target.searchObject ? {
103
- search: target.searchObject
104
- } : {},
105
- ...target.hash ? {
106
- hash: target.hash
107
- } : {},
108
- ...void 0 === hashScrollIntoView ? {} : {
109
- hashScrollIntoView
110
- },
111
- ...void 0 === tanstackPreload ? {} : {
112
- preload: tanstackPreload
113
- },
114
- ...rest,
115
- ...activeRest,
116
- ...activeAttributes,
117
- className: mergedClassName,
118
- style: mergedStyle,
119
- children: children
120
- });
121
- }
122
- return /*#__PURE__*/ jsx(RouterLink, {
98
+ const routerProps = adapter.createLinkProps?.({
99
+ pathname: target.localizedPathname,
100
+ href: target.href,
101
+ search: target.searchObject,
102
+ hash: target.hash,
103
+ hashScrollIntoView,
104
+ prefetch,
105
+ preload
106
+ }) ?? {
123
107
  to: target.href,
124
108
  ...void 0 === prefetch ? {} : {
125
109
  prefetch
126
110
  },
127
111
  ...void 0 === preload ? {} : {
128
112
  preload
129
- },
113
+ }
114
+ };
115
+ return /*#__PURE__*/ jsx(RouterLink, {
116
+ ...routerProps,
130
117
  ...rest,
131
118
  ...activeRest,
132
119
  ...activeAttributes,
@@ -1,17 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
2
+ import { RuntimeComponentResolverContext } from "@modern-js/runtime/context";
3
+ import { useCallback, useContext, useEffect, useMemo } from "react";
3
4
  import { changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n } from "./contextHelpers.mjs";
4
- import { getActualI18nextInstance } from "./i18n/instance.mjs";
5
5
  import { useI18nRouterAdapter } from "./routerAdapter.mjs";
6
- const modernI18nContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ModernI18nContext');
7
- const reactI18nextProviderContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ReactI18nextProviderContext');
8
- const getGlobalContext = (key, defaultValue)=>{
9
- const globalStore = globalThis;
10
- globalStore[key] ??= /*#__PURE__*/ createContext(defaultValue);
11
- return globalStore[key];
12
- };
13
- const ModernI18nContext = getGlobalContext(modernI18nContextKey, null);
14
- const ReactI18nextProviderContext = getGlobalContext(reactI18nextProviderContextKey, null);
6
+ import { ModernI18nContext, ReactI18nextProviderContext } from "./sharedContexts.mjs";
15
7
  const ModernI18nProvider = ({ children, i18nextProvider, value })=>{
16
8
  const content = /*#__PURE__*/ jsx(ModernI18nContext.Provider, {
17
9
  value: value,
@@ -22,68 +14,23 @@ const ModernI18nProvider = ({ children, i18nextProvider, value })=>{
22
14
  children: content
23
15
  });
24
16
  };
25
- const FederatedI18nBoundary = ({ children, defaultNamespace, fallbackLanguage, resources, supportedLanguages })=>{
26
- const parent = useContext(ModernI18nContext);
27
- const I18nextProvider = useContext(ReactI18nextProviderContext);
28
- if (!parent) throw new Error('FederatedI18nBoundary must be used within ModernI18nProvider');
29
- const languages = supportedLanguages ?? parent.languages ?? Object.keys(resources);
30
- const scopedInstance = useMemo(()=>{
31
- const parentInstance = getActualI18nextInstance(parent.i18nInstance);
32
- const clone = parentInstance.cloneInstance?.({
33
- defaultNS: defaultNamespace,
34
- fallbackLng: fallbackLanguage ?? languages[0] ?? parent.language,
35
- forkResourceStore: true,
36
- initImmediate: false,
37
- lng: parent.language,
38
- ns: [
39
- defaultNamespace
40
- ],
41
- resources,
42
- supportedLngs: languages
43
- });
44
- if (!clone) throw new Error('FederatedI18nBoundary requires an i18n instance with cloneInstance support');
45
- const resourceStore = clone.store;
46
- const parentResourceStore = parentInstance.store;
47
- const sharesNestedResourceState = Object.entries(resourceStore?.data ?? {}).some(([language, namespaces])=>{
48
- const parentNamespaces = parentResourceStore?.data?.[language];
49
- return namespaces === parentNamespaces || Object.entries(namespaces).some(([namespace, resource])=>null !== resource && 'object' == typeof resource && resource === parentNamespaces?.[namespace]);
50
- });
51
- if (clone === parentInstance || resourceStore === parentResourceStore || resourceStore?.data === parentResourceStore?.data || sharesNestedResourceState) throw new Error('FederatedI18nBoundary cloneInstance did not isolate the host resource store');
52
- if (!resourceStore?.addResourceBundle || !clone.removeResourceBundle || !resourceStore.data) throw new Error('FederatedI18nBoundary requires an isolated mutable i18n resource store');
53
- for (const [language, namespaces] of Object.entries(resourceStore.data))for (const namespace of Object.keys(namespaces))clone.removeResourceBundle(language, namespace);
54
- for (const [language, namespaces] of Object.entries(resources))for (const [namespace, resource] of Object.entries(namespaces))resourceStore.addResourceBundle(language, namespace, resource, true, true);
55
- return clone;
56
- }, [
57
- defaultNamespace,
58
- fallbackLanguage,
59
- languages,
60
- parent.i18nInstance,
61
- parent.language,
62
- resources
63
- ]);
64
- const value = useMemo(()=>({
65
- ...parent,
66
- i18nInstance: scopedInstance,
67
- language: parent.language,
68
- languages
69
- }), [
70
- languages,
71
- parent,
72
- scopedInstance
73
- ]);
74
- const scopedContent = /*#__PURE__*/ jsx(ModernI18nProvider, {
75
- value: value,
76
- children: children
17
+ const MissingFederatedI18nBoundary = ()=>{
18
+ throw new Error('FederatedI18nBoundary requires the @modern-js/i18n-integration runtime plugin');
19
+ };
20
+ const FederatedI18nBoundary = (props)=>{
21
+ const resolveComponent = useContext(RuntimeComponentResolverContext);
22
+ const Boundary = resolveComponent?.(MissingFederatedI18nBoundary, {
23
+ name: 'i18n.FederatedI18nBoundary'
24
+ }) ?? MissingFederatedI18nBoundary;
25
+ if (Boundary === FederatedI18nBoundary) throw new Error('FederatedI18nBoundary resolver returned its own wrapper');
26
+ return /*#__PURE__*/ jsx(Boundary, {
27
+ ...props
77
28
  });
78
- return I18nextProvider ? /*#__PURE__*/ jsx(I18nextProvider, {
79
- i18n: scopedInstance,
80
- children: scopedContent
81
- }) : scopedContent;
82
29
  };
83
30
  const useModernI18n = ()=>{
84
31
  const context = useContext(ModernI18nContext);
85
32
  if (!context) throw new Error('useModernI18n must be used within ModernI18nProvider');
86
- const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage, synchronizeLanguage } = context;
33
+ const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, urlStrategy, updateLanguage, synchronizeLanguage } = context;
87
34
  const { navigate, location, hasRouter } = useI18nRouterAdapter();
88
35
  const pathLanguage = useMemo(()=>getPathLanguage(location?.pathname, languages, localePathRedirect), [
89
36
  languages,
@@ -102,7 +49,7 @@ const useModernI18n = ()=>{
102
49
  updateLanguage,
103
50
  localePathRedirect,
104
51
  ignoreRedirectRoutes,
105
- localisedUrls,
52
+ urlStrategy,
106
53
  languages,
107
54
  hasRouter,
108
55
  navigate,
@@ -112,7 +59,7 @@ const useModernI18n = ()=>{
112
59
  updateLanguage,
113
60
  localePathRedirect,
114
61
  ignoreRedirectRoutes,
115
- localisedUrls,
62
+ urlStrategy,
116
63
  languages,
117
64
  hasRouter,
118
65
  navigate,
@@ -135,9 +82,10 @@ const useModernI18n = ()=>{
135
82
  t,
136
83
  i18nInstance: i18nInstance,
137
84
  supportedLanguages: languages || [],
138
- localisedUrls,
85
+ urlStrategy,
139
86
  isLanguageSupported,
140
87
  isResourcesReady
141
88
  };
142
89
  };
143
- export { FederatedI18nBoundary, ModernI18nProvider, useModernI18n };
90
+ export { getActualI18nextInstance } from "./i18n/instance.mjs";
91
+ export { FederatedI18nBoundary, ModernI18nContext, ModernI18nProvider, ReactI18nextProviderContext, useModernI18n };
@@ -17,7 +17,7 @@ async function changeI18nInstanceLanguage(i18nInstance, language) {
17
17
  else await i18nInstance.changeLanguage?.(language);
18
18
  }
19
19
  async function changeModernI18nLanguage(newLang, options) {
20
- const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes, localisedUrls, languages, hasRouter, navigate, location } = options;
20
+ const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes, urlStrategy, languages, hasRouter, navigate, location } = options;
21
21
  try {
22
22
  if (!newLang || 'string' != typeof newLang) throw new Error('Language must be non-empty string');
23
23
  await changeI18nInstanceLanguage(i18nInstance, newLang);
@@ -28,8 +28,8 @@ async function changeModernI18nLanguage(newLang, options) {
28
28
  const relativePath = currentPath.replace(entryPath, '');
29
29
  const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
30
30
  if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
31
- if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
32
- const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
31
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes, urlStrategy)) {
32
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], urlStrategy);
33
33
  const newUrl = entryPath + newPath + location.search + location.hash;
34
34
  await navigate(newUrl, {
35
35
  replace: true
@@ -41,8 +41,8 @@ async function changeModernI18nLanguage(newLang, options) {
41
41
  const relativePath = currentPath.replace(entryPath, '');
42
42
  const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
43
43
  if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
44
- if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
45
- const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
44
+ if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes, urlStrategy)) {
45
+ const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], urlStrategy);
46
46
  const newUrl = entryPath + newPath + window.location.search + window.location.hash;
47
47
  window.history.pushState(null, '', newUrl);
48
48
  }
@@ -0,0 +1,5 @@
1
+ import { createContext } from "react";
2
+ const ModernI18nContext = createContext(null);
3
+ const ReactI18nextProviderContext = createContext(null);
4
+ const I18nNavigationContext = createContext(null);
5
+ export { I18nNavigationContext, ModernI18nContext, ReactI18nextProviderContext };
@@ -1,3 +1,6 @@
1
+ import { resolveMappedUrlStrategy } from "../shared/mappedUrlStrategy.mjs";
2
+ import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
3
+ import { I18nRouterNavigationProvider, useIntegratedRouterAdapter } from "./navigation.mjs";
1
4
  import { setupI18nBeforeRender } from "./pluginSetup.mjs";
2
5
  import { createI18nRootWrapper } from "./providerComposition.mjs";
3
6
  import { resolveReactI18nextIntegration } from "./reactI18next.mjs";
@@ -5,9 +8,10 @@ import "./types.mjs";
5
8
  const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
6
9
  name: '@modern-js/plugin-i18n',
7
10
  setup: (api)=>{
8
- const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true } = options;
9
- const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes, localisedUrls } = localeDetection || {};
11
+ const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true, urlStrategy, NavigationProvider, LanguageSynchronization } = options;
12
+ const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes } = localeDetection || {};
10
13
  const { enabled: backendEnabled = false } = backend || {};
14
+ const resolvedUrlStrategy = asI18nUrlStrategy(urlStrategy) ?? resolveMappedUrlStrategy(localeDetection);
11
15
  let latestI18nInstance;
12
16
  let I18nextProvider;
13
17
  api.onBeforeRender(async (context)=>{
@@ -35,15 +39,18 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
35
39
  languages,
36
40
  fallbackLanguage,
37
41
  ignoreRedirectRoutes,
38
- localisedUrls,
42
+ urlStrategy: resolvedUrlStrategy,
43
+ NavigationProvider: NavigationProvider ?? I18nRouterNavigationProvider,
44
+ LanguageSynchronization,
39
45
  getLatestI18nInstance: ()=>latestI18nInstance,
40
46
  getI18nextProvider: ()=>I18nextProvider
41
47
  }));
42
48
  }
43
49
  });
50
+ export { I18nNavigationProvider, useI18nRouterAdapter, useNativeI18nRouterAdapter } from "./routerAdapter.mjs";
44
51
  export { FederatedI18nBoundary, useModernI18n } from "./context.mjs";
45
52
  export { I18nLink } from "./I18nLink.mjs";
46
53
  export { Link } from "./Link.mjs";
47
- export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
48
- export { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
49
- export { createI18nPlugin };
54
+ export { useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
55
+ export { splitUrlTarget } from "./utils.mjs";
56
+ export { I18nRouterNavigationProvider, createI18nPlugin, useIntegratedRouterAdapter };