@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.9 → 3.2.0-ultramodern.90

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 (80) hide show
  1. package/dist/cjs/cli/index.js +22 -0
  2. package/dist/cjs/runtime/I18nLink.js +4 -12
  3. package/dist/cjs/runtime/context.js +32 -5
  4. package/dist/cjs/runtime/hooks.js +8 -5
  5. package/dist/cjs/runtime/i18n/backend/defaults.js +1 -1
  6. package/dist/cjs/runtime/i18n/backend/defaults.node.js +2 -2
  7. package/dist/cjs/runtime/i18n/backend/middleware.node.js +4 -4
  8. package/dist/cjs/runtime/i18n/instance.js +0 -24
  9. package/dist/cjs/runtime/i18n/react-i18next.js +49 -0
  10. package/dist/cjs/runtime/i18n/utils.js +0 -12
  11. package/dist/cjs/runtime/index.js +18 -10
  12. package/dist/cjs/runtime/routerAdapter.js +163 -0
  13. package/dist/cjs/runtime/utils.js +63 -94
  14. package/dist/cjs/server/index.js +60 -8
  15. package/dist/cjs/shared/localisedUrls.js +237 -0
  16. package/dist/esm/cli/index.mjs +22 -0
  17. package/dist/esm/runtime/I18nLink.mjs +4 -12
  18. package/dist/esm/runtime/context.mjs +34 -7
  19. package/dist/esm/runtime/hooks.mjs +9 -6
  20. package/dist/esm/runtime/i18n/backend/defaults.mjs +1 -1
  21. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +2 -2
  22. package/dist/esm/runtime/i18n/backend/middleware.node.mjs +3 -3
  23. package/dist/esm/runtime/i18n/instance.mjs +1 -19
  24. package/dist/esm/runtime/i18n/react-i18next.mjs +15 -0
  25. package/dist/esm/runtime/i18n/utils.mjs +0 -12
  26. package/dist/esm/runtime/index.mjs +19 -11
  27. package/dist/esm/runtime/routerAdapter.mjs +129 -0
  28. package/dist/esm/runtime/utils.mjs +11 -30
  29. package/dist/esm/server/index.mjs +53 -7
  30. package/dist/esm/shared/localisedUrls.mjs +191 -0
  31. package/dist/esm-node/cli/index.mjs +22 -0
  32. package/dist/esm-node/runtime/I18nLink.mjs +4 -12
  33. package/dist/esm-node/runtime/context.mjs +34 -7
  34. package/dist/esm-node/runtime/hooks.mjs +9 -6
  35. package/dist/esm-node/runtime/i18n/backend/defaults.mjs +1 -1
  36. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +2 -2
  37. package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +3 -3
  38. package/dist/esm-node/runtime/i18n/instance.mjs +1 -19
  39. package/dist/esm-node/runtime/i18n/react-i18next.mjs +16 -0
  40. package/dist/esm-node/runtime/i18n/utils.mjs +0 -12
  41. package/dist/esm-node/runtime/index.mjs +19 -11
  42. package/dist/esm-node/runtime/routerAdapter.mjs +130 -0
  43. package/dist/esm-node/runtime/utils.mjs +11 -30
  44. package/dist/esm-node/server/index.mjs +53 -7
  45. package/dist/esm-node/shared/localisedUrls.mjs +192 -0
  46. package/dist/types/runtime/I18nLink.d.ts +15 -0
  47. package/dist/types/runtime/context.d.ts +3 -0
  48. package/dist/types/runtime/hooks.d.ts +4 -2
  49. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +1 -1
  50. package/dist/types/runtime/i18n/instance.d.ts +0 -5
  51. package/dist/types/runtime/i18n/react-i18next.d.ts +7 -0
  52. package/dist/types/runtime/index.d.ts +1 -0
  53. package/dist/types/runtime/routerAdapter.d.ts +26 -0
  54. package/dist/types/runtime/utils.d.ts +2 -7
  55. package/dist/types/server/index.d.ts +6 -0
  56. package/dist/types/shared/localisedUrls.d.ts +13 -0
  57. package/dist/types/shared/type.d.ts +12 -0
  58. package/package.json +18 -22
  59. package/rstest.config.mts +39 -0
  60. package/src/cli/index.ts +43 -1
  61. package/src/runtime/I18nLink.tsx +10 -16
  62. package/src/runtime/context.tsx +45 -7
  63. package/src/runtime/hooks.ts +13 -4
  64. package/src/runtime/i18n/backend/defaults.node.ts +2 -2
  65. package/src/runtime/i18n/backend/defaults.ts +3 -1
  66. package/src/runtime/i18n/backend/middleware.node.ts +1 -1
  67. package/src/runtime/i18n/instance.ts +0 -29
  68. package/src/runtime/i18n/react-i18next.ts +25 -0
  69. package/src/runtime/i18n/utils.ts +4 -26
  70. package/src/runtime/index.tsx +23 -10
  71. package/src/runtime/routerAdapter.tsx +333 -0
  72. package/src/runtime/utils.ts +22 -34
  73. package/src/server/index.ts +117 -10
  74. package/src/shared/localisedUrls.ts +393 -0
  75. package/src/shared/type.ts +12 -0
  76. package/tests/i18nUtils.test.ts +52 -0
  77. package/tests/localisedUrls.test.ts +312 -0
  78. package/tests/routerAdapter.test.tsx +382 -0
  79. package/dist/esm/rslib-runtime.mjs +0 -18
  80. package/dist/esm-node/rslib-runtime.mjs +0 -19
@@ -1,19 +1,5 @@
1
1
  "use strict";
2
- var __webpack_modules__ = {
3
- "@modern-js/runtime/router" (module) {
4
- module.exports = require("@modern-js/runtime/router");
5
- }
6
- };
7
- var __webpack_module_cache__ = {};
8
- function __webpack_require__(moduleId) {
9
- var cachedModule = __webpack_module_cache__[moduleId];
10
- if (void 0 !== cachedModule) return cachedModule.exports;
11
- var module = __webpack_module_cache__[moduleId] = {
12
- exports: {}
13
- };
14
- __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
15
- return module.exports;
16
- }
2
+ var __webpack_require__ = {};
17
3
  (()=>{
18
4
  __webpack_require__.d = (exports1, definition)=>{
19
5
  for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
@@ -36,100 +22,83 @@ function __webpack_require__(moduleId) {
36
22
  };
37
23
  })();
38
24
  var __webpack_exports__ = {};
39
- (()=>{
40
- __webpack_require__.r(__webpack_exports__);
41
- __webpack_require__.d(__webpack_exports__, {
42
- buildLocalizedUrl: ()=>buildLocalizedUrl,
43
- detectLanguageFromPath: ()=>detectLanguageFromPath,
44
- getEntryPath: ()=>getEntryPath,
45
- getLanguageFromPath: ()=>getLanguageFromPath,
46
- getPathname: ()=>getPathname,
47
- shouldIgnoreRedirect: ()=>shouldIgnoreRedirect,
48
- useRouterHooks: ()=>useRouterHooks
49
- });
50
- const runtime_namespaceObject = require("@modern-js/runtime");
51
- const context_namespaceObject = require("@modern-js/runtime/context");
52
- const getPathname = (context)=>{
53
- if ((0, runtime_namespaceObject.isBrowser)()) return window.location.pathname;
54
- return context.ssrContext?.request?.pathname || '/';
55
- };
56
- const getEntryPath = ()=>{
57
- const basename = (0, context_namespaceObject.getGlobalBasename)();
58
- if (basename) return '/' === basename ? '' : basename;
59
- return '';
60
- };
61
- const getLanguageFromPath = (pathname, languages, fallbackLanguage)=>{
62
- const segments = pathname.split('/').filter(Boolean);
63
- const firstSegment = segments[0];
64
- if (languages.includes(firstSegment)) return firstSegment;
65
- return fallbackLanguage;
66
- };
67
- const buildLocalizedUrl = (pathname, language, languages)=>{
68
- const segments = pathname.split('/').filter(Boolean);
69
- if (segments.length > 0 && languages.includes(segments[0])) segments[0] = language;
70
- else segments.unshift(language);
71
- return `/${segments.join('/')}`;
72
- };
73
- const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
74
- if (!localePathRedirect) return {
75
- detected: false
76
- };
77
- const entryPath = getEntryPath();
78
- const relativePath = pathname.replace(entryPath, '');
79
- const segments = relativePath.split('/').filter(Boolean);
80
- const segmentsToCheck = !entryPath && segments.length > 1 && segments[0] && !languages.includes(segments[0]) ? segments.slice(1) : segments;
81
- const firstSegment = segmentsToCheck[0];
82
- if (firstSegment && languages.includes(firstSegment)) return {
83
- detected: true,
84
- language: firstSegment
85
- };
86
- return {
87
- detected: false
88
- };
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ buildLocalizedUrl: ()=>buildLocalizedUrl,
28
+ detectLanguageFromPath: ()=>detectLanguageFromPath,
29
+ getEntryPath: ()=>getEntryPath,
30
+ getLanguageFromPath: ()=>getLanguageFromPath,
31
+ getPathname: ()=>getPathname,
32
+ shouldIgnoreRedirect: ()=>shouldIgnoreRedirect
33
+ });
34
+ const runtime_namespaceObject = require("@modern-js/runtime");
35
+ const context_namespaceObject = require("@modern-js/runtime/context");
36
+ const localisedUrls_js_namespaceObject = require("../shared/localisedUrls.js");
37
+ const getPathname = (context)=>{
38
+ if ((0, runtime_namespaceObject.isBrowser)()) return window.location.pathname;
39
+ return context.ssrContext?.request?.pathname || '/';
40
+ };
41
+ const getEntryPath = ()=>{
42
+ const basename = (0, context_namespaceObject.getGlobalBasename)();
43
+ if (basename) return '/' === basename ? '' : basename;
44
+ return '';
45
+ };
46
+ const getLanguageFromPath = (pathname, languages, fallbackLanguage)=>{
47
+ const segments = pathname.split('/').filter(Boolean);
48
+ const firstSegment = segments[0];
49
+ if (languages.includes(firstSegment)) return firstSegment;
50
+ return fallbackLanguage;
51
+ };
52
+ const buildLocalizedUrl = (pathname, language, languages, localisedUrls)=>{
53
+ const segments = pathname.split('/').filter(Boolean);
54
+ const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
55
+ const pathWithoutLanguage = segments.length > 0 && languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : pathname;
56
+ const resolvedPath = localisedUrlsConfig.enabled ? (0, localisedUrls_js_namespaceObject.resolveLocalisedPath)(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
57
+ const resolvedSegments = resolvedPath.split('/').filter(Boolean);
58
+ return `/${[
59
+ language,
60
+ ...resolvedSegments
61
+ ].join('/')}`;
62
+ };
63
+ const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
64
+ if (!localePathRedirect) return {
65
+ detected: false
89
66
  };
90
- const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>{
91
- if (!ignoreRedirectRoutes) return false;
92
- const segments = pathname.split('/').filter(Boolean);
93
- let pathWithoutLang = pathname;
94
- if (segments.length > 0 && languages.includes(segments[0])) pathWithoutLang = `/${segments.slice(1).join('/')}`;
95
- const normalizedPath = pathWithoutLang.startsWith('/') ? pathWithoutLang : `/${pathWithoutLang}`;
96
- if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
97
- return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
67
+ const entryPath = getEntryPath();
68
+ const relativePath = pathname.replace(entryPath, '');
69
+ const segments = relativePath.split('/').filter(Boolean);
70
+ const segmentsToCheck = !entryPath && segments.length > 1 && segments[0] && !languages.includes(segments[0]) ? segments.slice(1) : segments;
71
+ const firstSegment = segmentsToCheck[0];
72
+ if (firstSegment && languages.includes(firstSegment)) return {
73
+ detected: true,
74
+ language: firstSegment
98
75
  };
99
- const useRouterHooks = ()=>{
100
- try {
101
- const { useLocation, useNavigate, useParams } = __webpack_require__("@modern-js/runtime/router");
102
- return {
103
- navigate: useNavigate(),
104
- location: useLocation(),
105
- params: useParams(),
106
- hasRouter: true
107
- };
108
- } catch (error) {
109
- return {
110
- navigate: null,
111
- location: null,
112
- params: {},
113
- hasRouter: false
114
- };
115
- }
76
+ return {
77
+ detected: false
116
78
  };
117
- })();
79
+ };
80
+ const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>{
81
+ if (!ignoreRedirectRoutes) return false;
82
+ const segments = pathname.split('/').filter(Boolean);
83
+ let pathWithoutLang = pathname;
84
+ if (segments.length > 0 && languages.includes(segments[0])) pathWithoutLang = `/${segments.slice(1).join('/')}`;
85
+ const normalizedPath = pathWithoutLang.startsWith('/') ? pathWithoutLang : `/${pathWithoutLang}`;
86
+ if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
87
+ return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
88
+ };
118
89
  exports.buildLocalizedUrl = __webpack_exports__.buildLocalizedUrl;
119
90
  exports.detectLanguageFromPath = __webpack_exports__.detectLanguageFromPath;
120
91
  exports.getEntryPath = __webpack_exports__.getEntryPath;
121
92
  exports.getLanguageFromPath = __webpack_exports__.getLanguageFromPath;
122
93
  exports.getPathname = __webpack_exports__.getPathname;
123
94
  exports.shouldIgnoreRedirect = __webpack_exports__.shouldIgnoreRedirect;
124
- exports.useRouterHooks = __webpack_exports__.useRouterHooks;
125
95
  for(var __rspack_i in __webpack_exports__)if (-1 === [
126
96
  "buildLocalizedUrl",
127
97
  "detectLanguageFromPath",
128
98
  "getEntryPath",
129
99
  "getLanguageFromPath",
130
100
  "getPathname",
131
- "shouldIgnoreRedirect",
132
- "useRouterHooks"
101
+ "shouldIgnoreRedirect"
133
102
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
134
103
  Object.defineProperty(exports, '__esModule', {
135
104
  value: true
@@ -24,13 +24,46 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
+ collectApiPrefixes: ()=>collectApiPrefixes,
27
28
  default: ()=>server,
28
- i18nServerPlugin: ()=>i18nServerPlugin
29
+ i18nServerPlugin: ()=>i18nServerPlugin,
30
+ matchesApiPrefix: ()=>matchesApiPrefix
29
31
  });
30
32
  const hono_namespaceObject = require("@modern-js/server-core/hono");
31
33
  const config_js_namespaceObject = require("../runtime/i18n/detection/config.js");
34
+ const localisedUrls_js_namespaceObject = require("../shared/localisedUrls.js");
32
35
  const utils_js_namespaceObject = require("../shared/utils.js");
33
36
  const { languageDetector } = hono_namespaceObject;
37
+ const normalizeApiPrefix = (prefix)=>{
38
+ const trimmedPrefix = prefix.trim();
39
+ if (!trimmedPrefix) return null;
40
+ const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
41
+ const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
42
+ const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
43
+ return '/' === normalizedPrefix ? null : normalizedPrefix;
44
+ };
45
+ const collectApiPrefixes = (routes, bffPrefix)=>{
46
+ const prefixes = new Set();
47
+ for (const route of routes){
48
+ if (!route.isApi || !route.urlPath) continue;
49
+ const normalizedPrefix = normalizeApiPrefix(route.urlPath);
50
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
51
+ }
52
+ const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
53
+ bffPrefix
54
+ ] : [];
55
+ for (const prefix of bffPrefixes){
56
+ const normalizedPrefix = normalizeApiPrefix(prefix);
57
+ if (normalizedPrefix) prefixes.add(normalizedPrefix);
58
+ }
59
+ return [
60
+ ...prefixes
61
+ ];
62
+ };
63
+ const matchesApiPrefix = (pathname, apiPrefixes)=>{
64
+ const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
65
+ return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
66
+ };
34
67
  const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
35
68
  const mergedDetection = detectionOptions ? (0, config_js_namespaceObject.mergeDetectionOptions)(detectionOptions) : config_js_namespaceObject.DEFAULT_I18NEXT_DETECTION_OPTIONS;
36
69
  const order = (mergedDetection.order || []).filter((item)=>![
@@ -120,15 +153,20 @@ const getLanguageFromPath = (req, urlPath, languages)=>{
120
153
  if (languages.includes(firstSegment)) return firstSegment;
121
154
  return null;
122
155
  };
123
- const buildLocalizedUrl = (req, urlPath, language, languages)=>{
156
+ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
124
157
  const url = new URL(req.url);
125
158
  const pathname = url.pathname;
126
159
  const basePath = urlPath.replace('/*', '');
127
160
  const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
128
161
  const segments = remainingPath.split('/').filter(Boolean);
129
- if (segments.length > 0 && languages.includes(segments[0])) segments[0] = language;
130
- else segments.unshift(language);
131
- const newPathname = `/${segments.join('/')}`;
162
+ const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
163
+ const pathWithoutLanguage = segments.length > 0 && languages.includes(segments[0]) ? `/${segments.slice(1).join('/')}` : remainingPath;
164
+ const resolvedPath = localisedUrlsConfig.enabled ? (0, localisedUrls_js_namespaceObject.resolveLocalisedPath)(pathWithoutLanguage, language, languages, localisedUrlsConfig.map) : pathWithoutLanguage;
165
+ const resolvedSegments = resolvedPath.split('/').filter(Boolean);
166
+ const newPathname = `/${[
167
+ language,
168
+ ...resolvedSegments
169
+ ].join('/')}`;
132
170
  const suffix = `${url.search}${url.hash}`;
133
171
  const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
134
172
  return localizedUrl;
@@ -138,6 +176,9 @@ const i18nServerPlugin = (options)=>({
138
176
  setup: (api)=>{
139
177
  api.onPrepare(()=>{
140
178
  const { middlewares, routes } = api.getServerContext();
179
+ const serverConfig = api.getServerConfig();
180
+ const bffPrefix = serverConfig?.bff ? serverConfig.bff.prefix ?? '/api' : void 0;
181
+ const apiPrefixes = collectApiPrefixes(routes, bffPrefix);
141
182
  const entryPaths = new Set();
142
183
  routes.forEach((route)=>{
143
184
  if (route.entryName && route.urlPath && '/' !== route.urlPath) {
@@ -149,7 +190,7 @@ const i18nServerPlugin = (options)=>({
149
190
  const { entryName } = route;
150
191
  if (!entryName) return;
151
192
  if (!options.localeDetection) return;
152
- const { localePathRedirect, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes } = (0, utils_js_namespaceObject.getLocaleDetectionOptions)(entryName, options.localeDetection);
193
+ const { localePathRedirect, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes, localisedUrls } = (0, utils_js_namespaceObject.getLocaleDetectionOptions)(entryName, options.localeDetection);
153
194
  const staticRoutePrefixes = options.staticRoutePrefixes;
154
195
  const originUrlPath = route.urlPath;
155
196
  const urlPath = originUrlPath.endsWith('/') ? `${originUrlPath}*` : `${originUrlPath}/*`;
@@ -163,6 +204,7 @@ const i18nServerPlugin = (options)=>({
163
204
  handler: async (c, next)=>{
164
205
  const url = new URL(c.req.url);
165
206
  const pathname = url.pathname;
207
+ if (matchesApiPrefix(pathname, apiPrefixes)) return await next();
166
208
  if (isStaticResourceRequest(pathname, staticRoutePrefixes, languages)) return await next();
167
209
  if ('/' === originUrlPath) {
168
210
  const pathSegments = pathname.split('/').filter(Boolean);
@@ -181,6 +223,7 @@ const i18nServerPlugin = (options)=>({
181
223
  handler: async (c, next)=>{
182
224
  const url = new URL(c.req.url);
183
225
  const pathname = url.pathname;
226
+ if (matchesApiPrefix(pathname, apiPrefixes)) return await next();
184
227
  if (isStaticResourceRequest(pathname, staticRoutePrefixes, languages)) return await next();
185
228
  if (shouldIgnoreRedirect(pathname, urlPath, ignoreRedirectRoutes)) return await next();
186
229
  if ('/' === originUrlPath) {
@@ -195,9 +238,14 @@ const i18nServerPlugin = (options)=>({
195
238
  let detectedLanguage = null;
196
239
  if (i18nextDetector) detectedLanguage = c.get('language') || null;
197
240
  const targetLanguage = detectedLanguage || fallbackLanguage;
198
- const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages);
241
+ const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
199
242
  return c.redirect(localizedUrl);
200
243
  }
244
+ const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
245
+ if (localisedUrlsConfig.enabled) {
246
+ const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
247
+ if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return c.redirect(expectedUrl);
248
+ }
201
249
  await next();
202
250
  }
203
251
  });
@@ -207,11 +255,15 @@ const i18nServerPlugin = (options)=>({
207
255
  }
208
256
  });
209
257
  const server = i18nServerPlugin;
258
+ exports.collectApiPrefixes = __webpack_exports__.collectApiPrefixes;
210
259
  exports["default"] = __webpack_exports__["default"];
211
260
  exports.i18nServerPlugin = __webpack_exports__.i18nServerPlugin;
261
+ exports.matchesApiPrefix = __webpack_exports__.matchesApiPrefix;
212
262
  for(var __rspack_i in __webpack_exports__)if (-1 === [
263
+ "collectApiPrefixes",
213
264
  "default",
214
- "i18nServerPlugin"
265
+ "i18nServerPlugin",
266
+ "matchesApiPrefix"
215
267
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
216
268
  Object.defineProperty(exports, '__esModule', {
217
269
  value: true
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ applyLocalisedUrlsToRoutes: ()=>applyLocalisedUrlsToRoutes,
28
+ normalisePathPattern: ()=>normalisePathPattern,
29
+ resolveLocalisedPath: ()=>resolveLocalisedPath,
30
+ resolveLocalisedUrlsConfig: ()=>resolveLocalisedUrlsConfig,
31
+ validateLocalisedUrls: ()=>validateLocalisedUrls
32
+ });
33
+ const LOCALE_PARAM_NAMES = new Set([
34
+ 'lang',
35
+ 'locale',
36
+ 'language'
37
+ ]);
38
+ const normalisePathPattern = (path)=>{
39
+ const withoutDuplicateSlashes = path.replace(/\/+/g, '/');
40
+ const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
41
+ const withoutTrailingSlash = withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, '') : withLeadingSlash;
42
+ return withoutTrailingSlash.replace(/\[(.+?)\]/g, ':$1');
43
+ };
44
+ const normaliseRoutePath = (path)=>{
45
+ const normalized = normalisePathPattern(path);
46
+ return '/' === normalized ? '' : normalized.slice(1);
47
+ };
48
+ const getLocaleParamSegment = (segment)=>{
49
+ if (!segment.startsWith(':')) return null;
50
+ const paramName = segment.slice(1).replace(/\?$/, '');
51
+ return LOCALE_PARAM_NAMES.has(paramName) ? segment : null;
52
+ };
53
+ const splitPathSegments = (path)=>{
54
+ if (!path) return [];
55
+ return normalisePathPattern(path).split('/').filter(Boolean);
56
+ };
57
+ const stripLeadingLocaleParam = (path)=>{
58
+ const segments = splitPathSegments(path);
59
+ const leadingLocaleParam = getLocaleParamSegment(segments[0] || '');
60
+ if (!leadingLocaleParam) return path;
61
+ const remainingPath = segments.slice(1).join('/');
62
+ return remainingPath ? `/${remainingPath}` : void 0;
63
+ };
64
+ const getLeadingLocaleParam = (path)=>{
65
+ const segments = splitPathSegments(path);
66
+ return getLocaleParamSegment(segments[0] || '');
67
+ };
68
+ const resolveLocalisedUrlsConfig = (option)=>{
69
+ if (false === option) return {
70
+ enabled: false,
71
+ map: {}
72
+ };
73
+ if (option && 'object' == typeof option) return {
74
+ enabled: true,
75
+ map: option
76
+ };
77
+ return {
78
+ enabled: true,
79
+ map: {}
80
+ };
81
+ };
82
+ const isLocalisableRoutePath = (path)=>{
83
+ const pathWithoutLocale = stripLeadingLocaleParam(path);
84
+ if (!pathWithoutLocale || '/' === pathWithoutLocale || '*' === pathWithoutLocale) return false;
85
+ return true;
86
+ };
87
+ const joinPath = (parentPath, routePath)=>{
88
+ if (!isLocalisableRoutePath(routePath)) return parentPath;
89
+ const segment = normaliseRoutePath(stripLeadingLocaleParam(routePath) || '');
90
+ return normalisePathPattern(`${parentPath}/${segment}`);
91
+ };
92
+ const ensureLocalisedUrlsForPath = (canonicalPath, languages, localisedUrls)=>{
93
+ const entry = localisedUrls[canonicalPath];
94
+ 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.`);
95
+ const missingLanguages = languages.filter((language)=>!entry[language]);
96
+ if (missingLanguages.length > 0) throw new Error(`localisedUrls["${canonicalPath}"] is missing languages: ${missingLanguages.join(', ')}. Every configured language must have a localised URL.`);
97
+ return entry;
98
+ };
99
+ const validateLocalisedUrls = (routes, languages, localisedUrls)=>{
100
+ const visit = (route, parentPath)=>{
101
+ const canonicalPath = joinPath(parentPath, route.path);
102
+ if (isLocalisableRoutePath(route.path)) ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls);
103
+ if ('children' in route && route.children) route.children.forEach((child)=>visit(child, canonicalPath));
104
+ };
105
+ routes.forEach((route)=>visit(route, ''));
106
+ };
107
+ const getLocalisedRoutePaths = (canonicalPath, parentLocalisedPaths, languages, entry)=>{
108
+ const paths = languages.map((language)=>{
109
+ const fullPath = normalisePathPattern(entry[language]);
110
+ const parentPath = normalisePathPattern(parentLocalisedPaths[language] || '/');
111
+ if ('/' === parentPath) return normaliseRoutePath(fullPath) || void 0;
112
+ const parentPrefix = `${parentPath}/`;
113
+ if (!fullPath.startsWith(parentPrefix)) throw new Error(`localisedUrls["${canonicalPath}"].${language} must be nested under "${parentPath}" because its parent route is localised there.`);
114
+ return normaliseRoutePath(fullPath.slice(parentPath.length));
115
+ });
116
+ return Array.from(new Set(paths.filter(Boolean)));
117
+ };
118
+ const transformLocalisedRoute = (route, parentCanonicalPath, parentLocalisedPaths, languages, localisedUrls)=>{
119
+ const canonicalPath = joinPath(parentCanonicalPath, route.path);
120
+ const localisedUrlEntry = isLocalisableRoutePath(route.path) ? ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls) : void 0;
121
+ const routeLocalisedPaths = localisedUrlEntry ? languages.reduce((acc, language)=>{
122
+ acc[language] = normalisePathPattern(localisedUrlEntry[language]);
123
+ return acc;
124
+ }, {}) : parentLocalisedPaths;
125
+ const children = 'children' in route && route.children ? route.children.flatMap((child)=>transformLocalisedRoute(child, canonicalPath, routeLocalisedPaths, languages, localisedUrls)) : void 0;
126
+ const baseRoute = {
127
+ ...route,
128
+ ...children ? {
129
+ children
130
+ } : {}
131
+ };
132
+ if (!localisedUrlEntry) return [
133
+ baseRoute
134
+ ];
135
+ return getLocalisedRoutePaths(canonicalPath, parentLocalisedPaths, languages, localisedUrlEntry).map((localisedPath, index)=>cloneRouteWithLocalisedPath(baseRoute, localisedPath, index));
136
+ };
137
+ const legalRouteIdPart = (value)=>value.replace(/[^a-zA-Z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'index';
138
+ const suffixRouteIds = (route, suffix)=>{
139
+ const children = 'children' in route && route.children ? route.children.map((child)=>suffixRouteIds(child, suffix)) : void 0;
140
+ return {
141
+ ...route,
142
+ ...route.id ? {
143
+ id: `${route.id}__localised_${suffix}`
144
+ } : {},
145
+ ...children ? {
146
+ children
147
+ } : {}
148
+ };
149
+ };
150
+ const cloneRouteWithLocalisedPath = (route, path, index)=>{
151
+ const leadingLocaleParam = getLeadingLocaleParam(route.path);
152
+ const localisedPath = leadingLocaleParam ? normaliseRoutePath(`${leadingLocaleParam}/${path}`) : path;
153
+ const routeWithPath = {
154
+ ...route,
155
+ path: localisedPath
156
+ };
157
+ return 0 === index ? routeWithPath : suffixRouteIds(routeWithPath, legalRouteIdPart(localisedPath));
158
+ };
159
+ const applyLocalisedUrlsToRoutes = (routes, languages, localisedUrls)=>{
160
+ const rootLocalisedPaths = languages.reduce((acc, language)=>{
161
+ acc[language] = '/';
162
+ return acc;
163
+ }, {});
164
+ validateLocalisedUrls(routes, languages, localisedUrls);
165
+ return routes.flatMap((route)=>transformLocalisedRoute(route, '', rootLocalisedPaths, languages, localisedUrls));
166
+ };
167
+ const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
168
+ const getParamName = (segment)=>segment.slice(1).replace(/\?$/, '');
169
+ const compilePathPattern = (pattern)=>{
170
+ const names = [];
171
+ const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
172
+ const source = segments.map((segment)=>{
173
+ if (segment.startsWith(':')) {
174
+ names.push(getParamName(segment));
175
+ const paramPattern = '([^/]+)';
176
+ return segment.endsWith('?') ? `(?:/${paramPattern})?` : `/${paramPattern}`;
177
+ }
178
+ if ('*' === segment) {
179
+ names.push('*');
180
+ return '/(.*)';
181
+ }
182
+ return `/${escapeRegExp(segment)}`;
183
+ }).join('');
184
+ return {
185
+ names,
186
+ regexp: new RegExp(`^${source || '/'}$`)
187
+ };
188
+ };
189
+ const matchPathPattern = (pathname, pattern)=>{
190
+ const { names, regexp } = compilePathPattern(pattern);
191
+ const match = regexp.exec(normalisePathPattern(pathname));
192
+ if (!match) return null;
193
+ return names.reduce((params, name, index)=>{
194
+ params[name] = decodeURIComponent(match[index + 1] || '');
195
+ return params;
196
+ }, {});
197
+ };
198
+ const buildPathFromPattern = (pattern, params)=>{
199
+ const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
200
+ const path = segments.map((segment)=>{
201
+ if (segment.startsWith(':')) {
202
+ const param = params[getParamName(segment)];
203
+ return param ? encodeURIComponent(param) : '';
204
+ }
205
+ if ('*' === segment) return params['*'] || '';
206
+ return segment;
207
+ }).filter(Boolean).join('/');
208
+ return `/${path}`;
209
+ };
210
+ const resolveLocalisedPath = (pathname, targetLanguage, languages, localisedUrls)=>{
211
+ const normalizedPathname = normalisePathPattern(pathname);
212
+ for (const localisedUrlEntry of Object.values(localisedUrls)){
213
+ const targetPattern = localisedUrlEntry[targetLanguage];
214
+ if (targetPattern) for (const language of languages){
215
+ const sourcePattern = localisedUrlEntry[language];
216
+ if (!sourcePattern) continue;
217
+ const params = matchPathPattern(normalizedPathname, sourcePattern);
218
+ if (params) return buildPathFromPattern(targetPattern, params);
219
+ }
220
+ }
221
+ return normalizedPathname;
222
+ };
223
+ exports.applyLocalisedUrlsToRoutes = __webpack_exports__.applyLocalisedUrlsToRoutes;
224
+ exports.normalisePathPattern = __webpack_exports__.normalisePathPattern;
225
+ exports.resolveLocalisedPath = __webpack_exports__.resolveLocalisedPath;
226
+ exports.resolveLocalisedUrlsConfig = __webpack_exports__.resolveLocalisedUrlsConfig;
227
+ exports.validateLocalisedUrls = __webpack_exports__.validateLocalisedUrls;
228
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
229
+ "applyLocalisedUrlsToRoutes",
230
+ "normalisePathPattern",
231
+ "resolveLocalisedPath",
232
+ "resolveLocalisedUrlsConfig",
233
+ "validateLocalisedUrls"
234
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
235
+ Object.defineProperty(exports, '__esModule', {
236
+ value: true
237
+ });
@@ -1,6 +1,7 @@
1
1
  import { getPublicDirRoutePrefixes } from "@modern-js/server-core";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
+ import { applyLocalisedUrlsToRoutes, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
4
5
  import { getBackendOptions, getLocaleDetectionOptions } from "../shared/utils.mjs";
5
6
  function hasJsonFiles(dirPath) {
6
7
  try {
@@ -80,6 +81,27 @@ const i18nPlugin = (options = {})=>({
80
81
  plugins
81
82
  };
82
83
  });
84
+ api.modifyFileSystemRoutes(({ entrypoint, routes })=>{
85
+ if (!localeDetection) return {
86
+ entrypoint,
87
+ routes
88
+ };
89
+ const localeDetectionOptions = getLocaleDetectionOptions(entrypoint.entryName, localeDetection);
90
+ const { localePathRedirect, languages = [], localisedUrls } = localeDetectionOptions;
91
+ if (!localePathRedirect || 0 === languages.length) return {
92
+ entrypoint,
93
+ routes
94
+ };
95
+ const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
96
+ if (!localisedUrlsConfig.enabled) return {
97
+ entrypoint,
98
+ routes
99
+ };
100
+ return {
101
+ entrypoint,
102
+ routes: applyLocalisedUrlsToRoutes(routes, languages, localisedUrlsConfig.map)
103
+ };
104
+ });
83
105
  api._internalServerPlugins(({ plugins })=>{
84
106
  const { serverRoutes, metaName } = api.getAppContext();
85
107
  const normalizedConfig = api.getNormalizedConfig();
@@ -1,20 +1,12 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { Link as router_Link, useInRouterContext, useParams } from "@modern-js/runtime/router";
3
2
  import { useModernI18n } from "./context.mjs";
3
+ import { useI18nRouterAdapter } from "./routerAdapter.mjs";
4
4
  import { buildLocalizedUrl } from "./utils.mjs";
5
- const useRouterHooks = ()=>{
6
- const inRouter = useInRouterContext();
7
- return {
8
- Link: inRouter ? router_Link : null,
9
- params: inRouter ? useParams() : {},
10
- hasRouter: inRouter
11
- };
12
- };
13
5
  const I18nLink = ({ to, children, ...props })=>{
14
- const { Link, params, hasRouter } = useRouterHooks();
15
- const { language, supportedLanguages } = useModernI18n();
6
+ const { Link, params, hasRouter } = useI18nRouterAdapter();
7
+ const { language, supportedLanguages, localisedUrls } = useModernI18n();
16
8
  const currentLang = language;
17
- const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages);
9
+ const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages, localisedUrls);
18
10
  if ('development' === process.env.NODE_ENV && hasRouter && !params.lang) console.warn("I18nLink is being used outside of a :lang dynamic route context. This may cause unexpected behavior. Please ensure I18nLink is used within a route that has a :lang parameter.");
19
11
  if (!hasRouter || !Link) return /*#__PURE__*/ jsx("a", {
20
12
  href: localizedTo,