@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1

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 (106) hide show
  1. package/dist/cjs/cli/index.js +11 -0
  2. package/dist/cjs/cli/localisedRoutes.js +47 -0
  3. package/dist/cjs/runtime/context.js +6 -14
  4. package/dist/cjs/runtime/contexts.js +49 -0
  5. package/dist/cjs/runtime/core.js +12 -2
  6. package/dist/cjs/runtime/i18n/detection/middleware.js +1 -1
  7. package/dist/cjs/runtime/i18n/detection/middleware.node.js +10 -1
  8. package/dist/cjs/runtime/i18n/detection/priority.js +1 -1
  9. package/dist/cjs/runtime/navigation.js +48 -0
  10. package/dist/cjs/runtime/routerAdapter.js +3 -5
  11. package/dist/cjs/runtime/sharedContexts.js +43 -0
  12. package/dist/cjs/runtime/utils.js +6 -2
  13. package/dist/cjs/server/defaultUrlStrategy.js +48 -0
  14. package/dist/cjs/server/index.js +3 -2
  15. package/dist/cjs/server/mappedUrlStrategy.js +43 -0
  16. package/dist/cjs/server/redirectPolicy.js +13 -9
  17. package/dist/cjs/shared/detection.js +118 -2
  18. package/dist/cjs/shared/mappedUrlStrategy.js +63 -0
  19. package/dist/cjs/shared/urlStrategy.js +31 -1
  20. package/dist/esm/cli/index.mjs +11 -0
  21. package/dist/esm/cli/localisedRoutes.mjs +9 -0
  22. package/dist/esm/runtime/context.mjs +2 -10
  23. package/dist/esm/runtime/contexts.mjs +5 -0
  24. package/dist/esm/runtime/core.mjs +7 -3
  25. package/dist/esm/runtime/i18n/detection/middleware.mjs +1 -1
  26. package/dist/esm/runtime/i18n/detection/middleware.node.mjs +10 -1
  27. package/dist/esm/runtime/i18n/detection/priority.mjs +1 -1
  28. package/dist/esm/runtime/navigation.mjs +7 -0
  29. package/dist/esm/runtime/routerAdapter.mjs +2 -4
  30. package/dist/esm/runtime/sharedContexts.mjs +1 -0
  31. package/dist/esm/runtime/utils.mjs +6 -2
  32. package/dist/esm/server/defaultUrlStrategy.mjs +7 -0
  33. package/dist/esm/server/index.mjs +3 -2
  34. package/dist/esm/server/mappedUrlStrategy.mjs +1 -0
  35. package/dist/esm/server/redirectPolicy.mjs +6 -2
  36. package/dist/esm/shared/detection.mjs +85 -1
  37. package/dist/esm/shared/mappedUrlStrategy.mjs +19 -0
  38. package/dist/esm/shared/urlStrategy.mjs +7 -0
  39. package/dist/esm-node/cli/index.mjs +11 -0
  40. package/dist/esm-node/cli/localisedRoutes.mjs +10 -0
  41. package/dist/esm-node/runtime/context.mjs +2 -10
  42. package/dist/esm-node/runtime/contexts.mjs +6 -0
  43. package/dist/esm-node/runtime/core.mjs +7 -3
  44. package/dist/esm-node/runtime/i18n/detection/middleware.mjs +1 -1
  45. package/dist/esm-node/runtime/i18n/detection/middleware.node.mjs +10 -1
  46. package/dist/esm-node/runtime/i18n/detection/priority.mjs +1 -1
  47. package/dist/esm-node/runtime/navigation.mjs +8 -0
  48. package/dist/esm-node/runtime/routerAdapter.mjs +2 -4
  49. package/dist/esm-node/runtime/sharedContexts.mjs +2 -0
  50. package/dist/esm-node/runtime/utils.mjs +6 -2
  51. package/dist/esm-node/server/defaultUrlStrategy.mjs +8 -0
  52. package/dist/esm-node/server/index.mjs +3 -2
  53. package/dist/esm-node/server/mappedUrlStrategy.mjs +2 -0
  54. package/dist/esm-node/server/redirectPolicy.mjs +6 -2
  55. package/dist/esm-node/shared/detection.mjs +85 -1
  56. package/dist/esm-node/shared/mappedUrlStrategy.mjs +20 -0
  57. package/dist/esm-node/shared/urlStrategy.mjs +7 -0
  58. package/dist/types/cli/localisedRoutes.d.ts +19 -0
  59. package/dist/types/runtime/context.d.ts +2 -2
  60. package/dist/types/runtime/contexts.d.ts +6 -0
  61. package/dist/types/runtime/core.d.ts +2 -1
  62. package/dist/types/runtime/i18n/detection/middleware.d.ts +1 -1
  63. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +1 -5
  64. package/dist/types/runtime/navigation.d.ts +10 -0
  65. package/dist/types/runtime/sharedContexts.d.ts +1 -0
  66. package/dist/types/runtime/utils.d.ts +1 -1
  67. package/dist/types/server/defaultUrlStrategy.d.ts +11 -0
  68. package/dist/types/server/index.d.ts +1 -1
  69. package/dist/types/server/mappedUrlStrategy.d.ts +7 -0
  70. package/dist/types/server/redirectPolicy.d.ts +1 -1
  71. package/dist/types/shared/detection.d.ts +11 -1
  72. package/dist/types/shared/mappedUrlStrategy.d.ts +27 -0
  73. package/dist/types/shared/type.d.ts +10 -0
  74. package/dist/types/shared/urlStrategy.d.ts +11 -0
  75. package/package.json +43 -24
  76. package/src/cli/index.ts +43 -1
  77. package/src/cli/localisedRoutes.ts +53 -0
  78. package/src/runtime/context.tsx +6 -31
  79. package/src/runtime/contexts.ts +16 -0
  80. package/src/runtime/core.tsx +30 -3
  81. package/src/runtime/i18n/detection/middleware.node.ts +25 -7
  82. package/src/runtime/i18n/detection/middleware.ts +2 -0
  83. package/src/runtime/i18n/detection/priority.ts +2 -0
  84. package/src/runtime/navigation.tsx +20 -0
  85. package/src/runtime/routerAdapter.tsx +2 -11
  86. package/src/runtime/sharedContexts.ts +8 -0
  87. package/src/runtime/utils.ts +13 -4
  88. package/src/server/defaultUrlStrategy.ts +27 -0
  89. package/src/server/index.ts +7 -12
  90. package/src/server/mappedUrlStrategy.ts +10 -0
  91. package/src/server/redirectPolicy.ts +19 -4
  92. package/src/shared/detection.ts +34 -10
  93. package/src/shared/mappedUrlStrategy.ts +59 -0
  94. package/src/shared/type.ts +10 -0
  95. package/src/shared/urlStrategy.ts +27 -0
  96. package/rslib.config.mts +0 -4
  97. package/rstest.config.mts +0 -46
  98. package/tests/backendDefaults.test.ts +0 -30
  99. package/tests/federatedI18nBoundaryResolver.test.tsx +0 -65
  100. package/tests/i18nUtils.test.ts +0 -60
  101. package/tests/link.test.tsx +0 -139
  102. package/tests/localisedUrls.test.ts +0 -176
  103. package/tests/reactI18nextRuntimeBoundary.test.ts +0 -117
  104. package/tests/redirectPolicy.test.ts +0 -54
  105. package/tests/routerAdapter.test.tsx +0 -221
  106. package/tsconfig.json +0 -15
@@ -1,7 +1,123 @@
1
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
+ })();
2
28
  var __webpack_exports__ = {};
3
- require("../runtime/i18n/detection/config.js");
4
- for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ detectLanguageFromRequest: ()=>detectLanguageFromRequest
32
+ });
33
+ const config_js_namespaceObject = require("../runtime/i18n/detection/config.js");
34
+ function detectLanguageFromRequest(req, languages, detectionOptions) {
35
+ try {
36
+ const mergedDetection = detectionOptions ? (0, config_js_namespaceObject.mergeDetectionOptions)(detectionOptions) : config_js_namespaceObject.DEFAULT_I18NEXT_DETECTION_OPTIONS;
37
+ const order = (mergedDetection.order || []).filter((item)=>![
38
+ 'path',
39
+ 'localStorage',
40
+ 'navigator',
41
+ 'htmlTag',
42
+ 'subdomain'
43
+ ].includes(item));
44
+ const detectionOrder = order.length > 0 ? order : [
45
+ 'querystring',
46
+ 'cookie',
47
+ 'header'
48
+ ];
49
+ const getHeader = (name)=>{
50
+ const headers = req.headers;
51
+ if ('function' == typeof headers?.get) return headers.get(name);
52
+ const value = headers[name.toLowerCase()];
53
+ if (Array.isArray(value)) return value[0] ?? null;
54
+ return value ?? null;
55
+ };
56
+ for (const method of detectionOrder){
57
+ let detectedLang = null;
58
+ switch(method){
59
+ case 'querystring':
60
+ {
61
+ const lookupKey = mergedDetection.lookupQuerystring || config_js_namespaceObject.DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng';
62
+ const host = getHeader('host') || 'localhost';
63
+ const url = new URL(req.url, `http://${host}`);
64
+ detectedLang = url.searchParams.get(lookupKey);
65
+ break;
66
+ }
67
+ case 'cookie':
68
+ {
69
+ const lookupKey = mergedDetection.lookupCookie || config_js_namespaceObject.DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next';
70
+ const cookieHeader = getHeader('Cookie');
71
+ if (cookieHeader) {
72
+ const cookies = cookieHeader.split(';').reduce((acc, item)=>{
73
+ const [key, value] = item.trim().split('=');
74
+ if (key && value) acc[key] = value;
75
+ return acc;
76
+ }, {});
77
+ detectedLang = cookies[lookupKey] || null;
78
+ }
79
+ break;
80
+ }
81
+ case 'header':
82
+ {
83
+ const lookupKey = mergedDetection.lookupHeader || config_js_namespaceObject.DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language';
84
+ const acceptLanguage = getHeader(lookupKey);
85
+ if (acceptLanguage) {
86
+ const languagesList = acceptLanguage.split(',').map((lang)=>{
87
+ const [code, q] = lang.trim().split(';');
88
+ return {
89
+ code,
90
+ baseCode: code.split('-')[0],
91
+ quality: q ? parseFloat(q.split('=')[1]) : 1.0
92
+ };
93
+ }).sort((a, b)=>b.quality - a.quality);
94
+ for (const lang of languagesList){
95
+ if (0 === languages.length || languages.includes(lang.code)) {
96
+ detectedLang = lang.code;
97
+ break;
98
+ }
99
+ if (languages.includes(lang.baseCode)) {
100
+ detectedLang = lang.baseCode;
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ break;
106
+ }
107
+ }
108
+ if (detectedLang && (0 === languages.length || languages.includes(detectedLang))) return detectedLang;
109
+ if (detectedLang) {
110
+ const baseLang = detectedLang.split('-')[0];
111
+ if (languages.includes(baseLang)) return baseLang;
112
+ }
113
+ }
114
+ } catch (error) {}
115
+ return null;
116
+ }
117
+ exports.detectLanguageFromRequest = __webpack_exports__.detectLanguageFromRequest;
118
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
119
+ "detectLanguageFromRequest"
120
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
5
121
  Object.defineProperty(exports, '__esModule', {
6
122
  value: true
7
123
  });
@@ -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
+ });
@@ -1,5 +1,20 @@
1
1
  "use strict";
2
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
+ })();
3
18
  (()=>{
4
19
  __webpack_require__.r = (exports1)=>{
5
20
  if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
@@ -12,7 +27,22 @@ var __webpack_require__ = {};
12
27
  })();
13
28
  var __webpack_exports__ = {};
14
29
  __webpack_require__.r(__webpack_exports__);
15
- for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
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];
16
46
  Object.defineProperty(exports, '__esModule', {
17
47
  value: true
18
48
  });
@@ -1,6 +1,9 @@
1
1
  import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
2
+ import { logger } from "@modern-js/utils";
3
+ import { isUnusableI18nUrlStrategy } from "../shared/urlStrategy.mjs";
2
4
  import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
3
5
  import { applyDetectedBackendPaths, detectLocalesDirectory } from "./locales.mjs";
6
+ import { applyLocalisedRoutes } from "./localisedRoutes.mjs";
4
7
  import "../runtime/types.mjs";
5
8
  const i18nPlugin = (options = {})=>({
6
9
  name: '@modern-js/plugin-i18n',
@@ -30,6 +33,10 @@ const i18nPlugin = (options = {})=>({
30
33
  backend: backendOptions,
31
34
  ...extendedConfig
32
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
+ }
33
40
  const { reactI18next } = extendedConfig;
34
41
  const runtimePluginPath = customPlugin?.runtime?.path || (false === reactI18next ? `@${metaName}/plugin-i18n/runtime/no-react-i18next` : `@${metaName}/plugin-i18n/runtime`);
35
42
  plugins.push({
@@ -42,6 +49,10 @@ const i18nPlugin = (options = {})=>({
42
49
  plugins
43
50
  };
44
51
  });
52
+ api.modifyFileSystemRoutes(({ entrypoint, routes })=>({
53
+ entrypoint,
54
+ routes: applyLocalisedRoutes(routes, localeDetection ? getLocaleDetectionOptions(entrypoint.entryName, localeDetection) : void 0, entrypoint)
55
+ }));
45
56
  api._internalServerPlugins(({ plugins })=>{
46
57
  const { serverRoutes, metaName } = api.getAppContext();
47
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 };
@@ -1,17 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { RuntimeComponentResolverContext } from "@modern-js/runtime/context";
3
- import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
3
+ import { useCallback, useContext, useEffect, useMemo } from "react";
4
4
  import { changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n } from "./contextHelpers.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,
@@ -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";
@@ -8,6 +11,7 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
8
11
  const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true, urlStrategy, NavigationProvider, LanguageSynchronization } = options;
9
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,8 +39,8 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
35
39
  languages,
36
40
  fallbackLanguage,
37
41
  ignoreRedirectRoutes,
38
- urlStrategy,
39
- NavigationProvider,
42
+ urlStrategy: resolvedUrlStrategy,
43
+ NavigationProvider: NavigationProvider ?? I18nRouterNavigationProvider,
40
44
  LanguageSynchronization,
41
45
  getLatestI18nInstance: ()=>latestI18nInstance,
42
46
  getI18nextProvider: ()=>I18nextProvider
@@ -49,4 +53,4 @@ export { I18nLink } from "./I18nLink.mjs";
49
53
  export { Link } from "./Link.mjs";
50
54
  export { useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
51
55
  export { splitUrlTarget } from "./utils.mjs";
52
- export { createI18nPlugin };
56
+ export { I18nRouterNavigationProvider, createI18nPlugin, useIntegratedRouterAdapter };
@@ -11,7 +11,7 @@ const useI18nextLanguageDetector = (i18nInstance)=>{
11
11
  }
12
12
  return i18nInstance;
13
13
  };
14
- const readLanguageFromStorage = (detectionOptions)=>{
14
+ const readLanguageFromStorage = (detectionOptions, _request, _languages)=>{
15
15
  try {
16
16
  const options = detectionOptions || {};
17
17
  const order = options.order || [
@@ -1,6 +1,15 @@
1
1
  import { LanguageDetector } from "i18next-http-middleware";
2
+ import { detectLanguageFromRequest } from "../../../shared/detection.mjs";
3
+ const isDetectionRequest = (request)=>{
4
+ if (!request || 'object' != typeof request) return false;
5
+ const { url, headers } = request;
6
+ return 'string' == typeof url && !!headers && 'object' == typeof headers;
7
+ };
2
8
  const cacheUserLanguage = (_i18nInstance, _language, _detectionOptions)=>{};
3
- const readLanguageFromStorage = (_detectionOptions)=>{};
9
+ const readLanguageFromStorage = (detectionOptions, request, languages = [])=>{
10
+ if (!isDetectionRequest(request)) return;
11
+ return detectLanguageFromRequest(request, languages, detectionOptions) ?? void 0;
12
+ };
4
13
  const useI18nextLanguageDetector = (i18nInstance)=>{
5
14
  if (!i18nInstance.isInitialized) return i18nInstance.use(LanguageDetector);
6
15
  return i18nInstance;
@@ -9,7 +9,7 @@ const detectLanguageWithPriority = async (i18nInstance, options)=>{
9
9
  let detectedLanguage;
10
10
  detectedLanguage = detectLanguageFromPathPriority(pathname, languages, localePathRedirect);
11
11
  if (!detectedLanguage) detectedLanguage = detectLanguageFromSSR(languages);
12
- if (!detectedLanguage && i18nextDetector) detectedLanguage = isI18nWrapperInstance(i18nInstance) ? readLanguageFromStorage(mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions)) : await detectLanguageFromI18nextDetector(i18nInstance, {
12
+ if (!detectedLanguage && i18nextDetector) detectedLanguage = isI18nWrapperInstance(i18nInstance) ? readLanguageFromStorage(mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions), ssrContext?.request, languages) : await detectLanguageFromI18nextDetector(i18nInstance, {
13
13
  languages,
14
14
  fallbackLanguage,
15
15
  localePathRedirect,
@@ -0,0 +1,7 @@
1
+ import { createI18nRouterNavigation } from "@modern-js/i18n-runtime-extensions/router-navigation";
2
+ import { I18nNavigationProvider, useNativeI18nRouterAdapter } from "./routerAdapter.mjs";
3
+ const { I18nRouterNavigationProvider: I18nRouterNavigationProvider, useIntegratedRouterAdapter: useIntegratedRouterAdapter } = createI18nRouterNavigation({
4
+ I18nNavigationProvider: I18nNavigationProvider,
5
+ useNativeI18nRouterAdapter: useNativeI18nRouterAdapter
6
+ });
7
+ export { I18nRouterNavigationProvider, useIntegratedRouterAdapter };
@@ -1,10 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import { I18nNavigationContext } from "@modern-js/plugin-i18n/runtime/contexts";
2
3
  import { RuntimeContext, isBrowser } from "@modern-js/runtime";
3
4
  import { InternalRuntimeContext } from "@modern-js/runtime/context";
4
- import { createContext, useContext } from "react";
5
- const navigationContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/I18nNavigationContext');
6
- const contextStore = globalThis;
7
- const I18nNavigationContext = contextStore[navigationContextKey] ??= /*#__PURE__*/ createContext(null);
5
+ import { useContext } from "react";
8
6
  const I18nNavigationProvider = ({ children, value })=>/*#__PURE__*/ jsx(I18nNavigationContext.Provider, {
9
7
  value: value,
10
8
  children: children
@@ -0,0 +1 @@
1
+ export { ModernI18nContext, ReactI18nextProviderContext } from "@modern-js/plugin-i18n/runtime/contexts";
@@ -1,6 +1,8 @@
1
1
  import { isBrowser } from "@modern-js/runtime";
2
2
  import { getGlobalBasename } from "@modern-js/runtime/context";
3
3
  import { splitUrlTarget } from "@modern-js/runtime-utils/url";
4
+ import { isDefaultLocaleRedirectSkipPath } from "../shared/mappedUrlStrategy.mjs";
5
+ import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
4
6
  const getPathname = (context)=>{
5
7
  if (isBrowser()) return window.location.pathname;
6
8
  return context.ssrContext?.request?.pathname || '/';
@@ -18,7 +20,8 @@ const getLanguageFromPath = (pathname, languages, fallbackLanguage)=>{
18
20
  };
19
21
  const buildLocalizedUrl = (target, language, languages, urlStrategy)=>{
20
22
  const { pathname, search, hash } = splitUrlTarget(target);
21
- if (urlStrategy) return `${urlStrategy.localizePathname(pathname, language, languages)}${search}${hash}`;
23
+ const strategy = asI18nUrlStrategy(urlStrategy);
24
+ if (strategy) return `${strategy.localizePathname(pathname, language, languages)}${search}${hash}`;
22
25
  const segments = pathname.split('/').filter(Boolean);
23
26
  if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments[0] = language;
24
27
  else segments.unshift(language);
@@ -43,7 +46,8 @@ const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
43
46
  };
44
47
  };
45
48
  const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes, urlStrategy)=>{
46
- if (urlStrategy?.shouldSkipRedirect?.(pathname, languages)) return true;
49
+ if (isDefaultLocaleRedirectSkipPath(pathname, languages)) return true;
50
+ if (asI18nUrlStrategy(urlStrategy)?.shouldSkipRedirect?.(pathname, languages)) return true;
47
51
  if (!ignoreRedirectRoutes) return false;
48
52
  const segments = pathname.split('/').filter(Boolean);
49
53
  if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments.shift();
@@ -0,0 +1,7 @@
1
+ import { createMappedUrlStrategy } from "./mappedUrlStrategy.mjs";
2
+ const createDefaultUrlStrategy = (localisedUrls)=>createMappedUrlStrategy(localisedUrls);
3
+ const resolveDefaultUrlStrategy = (localisedUrls)=>{
4
+ if (!localisedUrls || 'object' != typeof localisedUrls || 0 === Object.keys(localisedUrls).length) return;
5
+ return createDefaultUrlStrategy(localisedUrls);
6
+ };
7
+ export { createDefaultUrlStrategy, resolveDefaultUrlStrategy };
@@ -1,5 +1,7 @@
1
+ import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
1
2
  import { getLocaleDetectionOptions } from "../shared/utils.mjs";
2
3
  import { collectApiPrefixes, matchesApiPrefix } from "./apiPrefix.mjs";
4
+ import { resolveDefaultUrlStrategy } from "./defaultUrlStrategy.mjs";
3
5
  import { convertToHonoLanguageDetectorOptions } from "./detectorOptions.mjs";
4
6
  import { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect } from "./redirectPolicy.mjs";
5
7
  import * as __rspack_external__modern_js_server_core_hono_0af3baa7 from "@modern-js/server-core/hono";
@@ -26,8 +28,7 @@ const i18nServerPlugin = (options)=>({
26
28
  const entryLocaleDetection = getLocaleDetectionOptions(entryName, options.localeDetection);
27
29
  const { localePathRedirect, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes } = entryLocaleDetection;
28
30
  const legacyLocalisedUrls = Reflect.get(entryLocaleDetection, 'localisedUrls');
29
- const urlStrategy = options.resolveUrlStrategy?.(entryName);
30
- if (legacyLocalisedUrls && 'object' == typeof legacyLocalisedUrls && Object.keys(legacyLocalisedUrls).length > 0 && !urlStrategy) throw new Error('Mapped locale URLs require a URL strategy. Use the i18n integration server plugin or supply resolveUrlStrategy.');
31
+ const urlStrategy = asI18nUrlStrategy(options.resolveUrlStrategy?.(entryName)) ?? resolveDefaultUrlStrategy(legacyLocalisedUrls);
31
32
  const staticRoutePrefixes = options.staticRoutePrefixes;
32
33
  const originUrlPath = route.urlPath;
33
34
  const urlPath = originUrlPath.endsWith('/') ? `${originUrlPath}*` : `${originUrlPath}/*`;
@@ -0,0 +1 @@
1
+ export { createMappedUrlStrategy, resolveMappedUrlStrategy } from "../shared/mappedUrlStrategy.mjs";
@@ -1,3 +1,5 @@
1
+ import { isDefaultLocaleRedirectSkipPath } from "../shared/mappedUrlStrategy.mjs";
2
+ import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
1
3
  const stripUrlPathPrefix = (pathname, urlPath)=>{
2
4
  const basePath = urlPath.replace('/*', '');
3
5
  if (!basePath || '/' === basePath) return pathname;
@@ -5,7 +7,8 @@ const stripUrlPathPrefix = (pathname, urlPath)=>{
5
7
  };
6
8
  const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes, urlStrategy, languages = [])=>{
7
9
  const remainingPath = stripUrlPathPrefix(pathname, urlPath);
8
- if (urlStrategy?.shouldSkipRedirect?.(remainingPath, languages)) return true;
10
+ if (isDefaultLocaleRedirectSkipPath(remainingPath, languages)) return true;
11
+ if (asI18nUrlStrategy(urlStrategy)?.shouldSkipRedirect?.(remainingPath, languages)) return true;
9
12
  if (!ignoreRedirectRoutes) return false;
10
13
  const segments = remainingPath.split('/').filter(Boolean);
11
14
  if (segments[0] && languages.some((language)=>language.toLowerCase() === segments[0].toLowerCase())) segments.shift();
@@ -37,7 +40,8 @@ const buildLocalizedUrl = (req, urlPath, language, languages, urlStrategy)=>{
37
40
  const segments = remainingPath.split('/').filter(Boolean);
38
41
  if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments[0] = language;
39
42
  else segments.unshift(language);
40
- const pathname = urlStrategy ? urlStrategy.localizePathname(remainingPath, language, languages) : `/${segments.join('/')}`;
43
+ const strategy = asI18nUrlStrategy(urlStrategy);
44
+ const pathname = strategy ? strategy.localizePathname(remainingPath, language, languages) : `/${segments.join('/')}`;
41
45
  return `${'/' === basePath ? '' : basePath}${pathname}${url.search}${url.hash}`;
42
46
  };
43
47
  const createLocaleRedirectResponse = (location)=>new Response(null, {
@@ -1 +1,85 @@
1
- import "../runtime/i18n/detection/config.mjs";
1
+ import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
2
+ function detectLanguageFromRequest(req, languages, detectionOptions) {
3
+ try {
4
+ const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
5
+ const order = (mergedDetection.order || []).filter((item)=>![
6
+ 'path',
7
+ 'localStorage',
8
+ 'navigator',
9
+ 'htmlTag',
10
+ 'subdomain'
11
+ ].includes(item));
12
+ const detectionOrder = order.length > 0 ? order : [
13
+ 'querystring',
14
+ 'cookie',
15
+ 'header'
16
+ ];
17
+ const getHeader = (name)=>{
18
+ const headers = req.headers;
19
+ if ('function' == typeof headers?.get) return headers.get(name);
20
+ const value = headers[name.toLowerCase()];
21
+ if (Array.isArray(value)) return value[0] ?? null;
22
+ return value ?? null;
23
+ };
24
+ for (const method of detectionOrder){
25
+ let detectedLang = null;
26
+ switch(method){
27
+ case 'querystring':
28
+ {
29
+ const lookupKey = mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng';
30
+ const host = getHeader('host') || 'localhost';
31
+ const url = new URL(req.url, `http://${host}`);
32
+ detectedLang = url.searchParams.get(lookupKey);
33
+ break;
34
+ }
35
+ case 'cookie':
36
+ {
37
+ const lookupKey = mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next';
38
+ const cookieHeader = getHeader('Cookie');
39
+ if (cookieHeader) {
40
+ const cookies = cookieHeader.split(';').reduce((acc, item)=>{
41
+ const [key, value] = item.trim().split('=');
42
+ if (key && value) acc[key] = value;
43
+ return acc;
44
+ }, {});
45
+ detectedLang = cookies[lookupKey] || null;
46
+ }
47
+ break;
48
+ }
49
+ case 'header':
50
+ {
51
+ const lookupKey = mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language';
52
+ const acceptLanguage = getHeader(lookupKey);
53
+ if (acceptLanguage) {
54
+ const languagesList = acceptLanguage.split(',').map((lang)=>{
55
+ const [code, q] = lang.trim().split(';');
56
+ return {
57
+ code,
58
+ baseCode: code.split('-')[0],
59
+ quality: q ? parseFloat(q.split('=')[1]) : 1.0
60
+ };
61
+ }).sort((a, b)=>b.quality - a.quality);
62
+ for (const lang of languagesList){
63
+ if (0 === languages.length || languages.includes(lang.code)) {
64
+ detectedLang = lang.code;
65
+ break;
66
+ }
67
+ if (languages.includes(lang.baseCode)) {
68
+ detectedLang = lang.baseCode;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ break;
74
+ }
75
+ }
76
+ if (detectedLang && (0 === languages.length || languages.includes(detectedLang))) return detectedLang;
77
+ if (detectedLang) {
78
+ const baseLang = detectedLang.split('-')[0];
79
+ if (languages.includes(baseLang)) return baseLang;
80
+ }
81
+ }
82
+ } catch (error) {}
83
+ return null;
84
+ }
85
+ export { detectLanguageFromRequest };
@@ -0,0 +1,19 @@
1
+ import { canonicalTargetPathname, isDefaultLocaleRedirectSkipPath, localiseTargetPathname } from "@modern-js/i18n-runtime-extensions";
2
+ const createMappedUrlStrategy = (localisedUrls)=>({
3
+ localizePathname: (pathname, language, languages)=>localiseTargetPathname(pathname, language, [
4
+ ...languages
5
+ ], localisedUrls),
6
+ canonicalPathname: (pathname, languages)=>canonicalTargetPathname(pathname, [
7
+ ...languages
8
+ ], localisedUrls),
9
+ shouldSkipRedirect: (pathname, languages)=>isDefaultLocaleRedirectSkipPath(pathname, [
10
+ ...languages
11
+ ])
12
+ });
13
+ const resolveMappedUrlStrategy = (localeDetection)=>{
14
+ if (!localeDetection || 'object' != typeof localeDetection) return;
15
+ const localisedUrls = Reflect.get(localeDetection, 'localisedUrls');
16
+ if (!localisedUrls || 'object' != typeof localisedUrls || 0 === Object.keys(localisedUrls).length) return;
17
+ return createMappedUrlStrategy(localisedUrls);
18
+ };
19
+ export { createMappedUrlStrategy, isDefaultLocaleRedirectSkipPath, resolveMappedUrlStrategy };
@@ -0,0 +1,7 @@
1
+ const asI18nUrlStrategy = (candidate)=>{
2
+ if (!candidate || 'object' != typeof candidate) return;
3
+ const strategy = candidate;
4
+ return 'function' == typeof strategy.localizePathname && 'function' == typeof strategy.canonicalPathname ? strategy : void 0;
5
+ };
6
+ const isUnusableI18nUrlStrategy = (candidate)=>Boolean(candidate) && 'object' == typeof candidate && void 0 === asI18nUrlStrategy(candidate);
7
+ export { asI18nUrlStrategy, isUnusableI18nUrlStrategy };
@@ -1,7 +1,10 @@
1
1
  import "node:module";
2
2
  import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
3
+ import { logger } from "@modern-js/utils";
4
+ import { isUnusableI18nUrlStrategy } from "../shared/urlStrategy.mjs";
3
5
  import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
4
6
  import { applyDetectedBackendPaths, detectLocalesDirectory } from "./locales.mjs";
7
+ import { applyLocalisedRoutes } from "./localisedRoutes.mjs";
5
8
  import "../runtime/types.mjs";
6
9
  const i18nPlugin = (options = {})=>({
7
10
  name: '@modern-js/plugin-i18n',
@@ -31,6 +34,10 @@ const i18nPlugin = (options = {})=>({
31
34
  backend: backendOptions,
32
35
  ...extendedConfig
33
36
  };
37
+ if (isUnusableI18nUrlStrategy(config.urlStrategy)) {
38
+ delete config.urlStrategy;
39
+ 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.");
40
+ }
34
41
  const { reactI18next } = extendedConfig;
35
42
  const runtimePluginPath = customPlugin?.runtime?.path || (false === reactI18next ? `@${metaName}/plugin-i18n/runtime/no-react-i18next` : `@${metaName}/plugin-i18n/runtime`);
36
43
  plugins.push({
@@ -43,6 +50,10 @@ const i18nPlugin = (options = {})=>({
43
50
  plugins
44
51
  };
45
52
  });
53
+ api.modifyFileSystemRoutes(({ entrypoint, routes })=>({
54
+ entrypoint,
55
+ routes: applyLocalisedRoutes(routes, localeDetection ? getLocaleDetectionOptions(entrypoint.entryName, localeDetection) : void 0, entrypoint)
56
+ }));
46
57
  api._internalServerPlugins(({ plugins })=>{
47
58
  const { serverRoutes, metaName } = api.getAppContext();
48
59
  const normalizedConfig = api.getNormalizedConfig();