@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.
- package/dist/cjs/cli/index.js +22 -0
- package/dist/cjs/runtime/I18nLink.js +4 -12
- package/dist/cjs/runtime/context.js +32 -5
- package/dist/cjs/runtime/hooks.js +8 -5
- package/dist/cjs/runtime/i18n/backend/defaults.js +1 -1
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +2 -2
- package/dist/cjs/runtime/i18n/backend/middleware.node.js +4 -4
- package/dist/cjs/runtime/i18n/instance.js +0 -24
- package/dist/cjs/runtime/i18n/react-i18next.js +49 -0
- package/dist/cjs/runtime/i18n/utils.js +0 -12
- package/dist/cjs/runtime/index.js +18 -10
- package/dist/cjs/runtime/routerAdapter.js +163 -0
- package/dist/cjs/runtime/utils.js +63 -94
- package/dist/cjs/server/index.js +60 -8
- package/dist/cjs/shared/localisedUrls.js +237 -0
- package/dist/esm/cli/index.mjs +22 -0
- package/dist/esm/runtime/I18nLink.mjs +4 -12
- package/dist/esm/runtime/context.mjs +34 -7
- package/dist/esm/runtime/hooks.mjs +9 -6
- package/dist/esm/runtime/i18n/backend/defaults.mjs +1 -1
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +2 -2
- package/dist/esm/runtime/i18n/backend/middleware.node.mjs +3 -3
- package/dist/esm/runtime/i18n/instance.mjs +1 -19
- package/dist/esm/runtime/i18n/react-i18next.mjs +15 -0
- package/dist/esm/runtime/i18n/utils.mjs +0 -12
- package/dist/esm/runtime/index.mjs +19 -11
- package/dist/esm/runtime/routerAdapter.mjs +129 -0
- package/dist/esm/runtime/utils.mjs +11 -30
- package/dist/esm/server/index.mjs +53 -7
- package/dist/esm/shared/localisedUrls.mjs +191 -0
- package/dist/esm-node/cli/index.mjs +22 -0
- package/dist/esm-node/runtime/I18nLink.mjs +4 -12
- package/dist/esm-node/runtime/context.mjs +34 -7
- package/dist/esm-node/runtime/hooks.mjs +9 -6
- package/dist/esm-node/runtime/i18n/backend/defaults.mjs +1 -1
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +2 -2
- package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +3 -3
- package/dist/esm-node/runtime/i18n/instance.mjs +1 -19
- package/dist/esm-node/runtime/i18n/react-i18next.mjs +16 -0
- package/dist/esm-node/runtime/i18n/utils.mjs +0 -12
- package/dist/esm-node/runtime/index.mjs +19 -11
- package/dist/esm-node/runtime/routerAdapter.mjs +130 -0
- package/dist/esm-node/runtime/utils.mjs +11 -30
- package/dist/esm-node/server/index.mjs +53 -7
- package/dist/esm-node/shared/localisedUrls.mjs +192 -0
- package/dist/types/runtime/I18nLink.d.ts +15 -0
- package/dist/types/runtime/context.d.ts +3 -0
- package/dist/types/runtime/hooks.d.ts +4 -2
- package/dist/types/runtime/i18n/backend/middleware.node.d.ts +1 -1
- package/dist/types/runtime/i18n/instance.d.ts +0 -5
- package/dist/types/runtime/i18n/react-i18next.d.ts +7 -0
- package/dist/types/runtime/index.d.ts +1 -0
- package/dist/types/runtime/routerAdapter.d.ts +26 -0
- package/dist/types/runtime/utils.d.ts +2 -7
- package/dist/types/server/index.d.ts +6 -0
- package/dist/types/shared/localisedUrls.d.ts +13 -0
- package/dist/types/shared/type.d.ts +12 -0
- package/package.json +18 -22
- package/rstest.config.mts +39 -0
- package/src/cli/index.ts +43 -1
- package/src/runtime/I18nLink.tsx +10 -16
- package/src/runtime/context.tsx +45 -7
- package/src/runtime/hooks.ts +13 -4
- package/src/runtime/i18n/backend/defaults.node.ts +2 -2
- package/src/runtime/i18n/backend/defaults.ts +3 -1
- package/src/runtime/i18n/backend/middleware.node.ts +1 -1
- package/src/runtime/i18n/instance.ts +0 -29
- package/src/runtime/i18n/react-i18next.ts +25 -0
- package/src/runtime/i18n/utils.ts +4 -26
- package/src/runtime/index.tsx +23 -10
- package/src/runtime/routerAdapter.tsx +333 -0
- package/src/runtime/utils.ts +22 -34
- package/src/server/index.ts +117 -10
- package/src/shared/localisedUrls.ts +393 -0
- package/src/shared/type.ts +12 -0
- package/tests/i18nUtils.test.ts +52 -0
- package/tests/localisedUrls.test.ts +312 -0
- package/tests/routerAdapter.test.tsx +382 -0
- package/dist/esm/rslib-runtime.mjs +0 -18
- package/dist/esm-node/rslib-runtime.mjs +0 -19
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -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
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
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
|
+
});
|
package/dist/esm/cli/index.mjs
CHANGED
|
@@ -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 } =
|
|
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,
|