@bleedingdev/modern-js-plugin-i18n 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +10 -21
- package/dist/cjs/cli/localisedRoutes.js +46 -0
- package/dist/cjs/runtime/Link.js +19 -32
- package/dist/cjs/runtime/context.js +26 -61
- package/dist/cjs/runtime/contextHelpers.js +5 -5
- package/dist/cjs/runtime/core.js +27 -14
- package/dist/cjs/runtime/hooks.js +7 -8
- package/dist/cjs/runtime/localizedPaths.js +11 -9
- package/dist/cjs/runtime/navigation.js +48 -0
- package/dist/cjs/runtime/providerComposition.js +37 -13
- package/dist/cjs/runtime/routerAdapter.js +35 -140
- package/dist/cjs/runtime/utils.js +21 -19
- package/dist/cjs/server/defaultUrlStrategy.js +48 -0
- package/dist/cjs/server/index.js +11 -7
- package/dist/cjs/server/mappedUrlStrategy.js +43 -0
- package/dist/cjs/server/redirectPolicy.js +33 -24
- package/dist/cjs/shared/mappedUrlStrategy.js +63 -0
- package/dist/cjs/shared/urlStrategy.js +48 -0
- package/dist/esm/cli/index.mjs +10 -21
- package/dist/esm/cli/localisedRoutes.mjs +8 -0
- package/dist/esm/runtime/Link.mjs +19 -32
- package/dist/esm/runtime/context.mjs +18 -62
- package/dist/esm/runtime/contextHelpers.mjs +5 -5
- package/dist/esm/runtime/core.mjs +13 -6
- package/dist/esm/runtime/hooks.mjs +7 -8
- package/dist/esm/runtime/localizedPaths.mjs +11 -9
- package/dist/esm/runtime/navigation.mjs +7 -0
- package/dist/esm/runtime/providerComposition.mjs +38 -14
- package/dist/esm/runtime/routerAdapter.mjs +30 -141
- package/dist/esm/runtime/utils.mjs +19 -17
- package/dist/esm/server/defaultUrlStrategy.mjs +7 -0
- package/dist/esm/server/index.mjs +11 -7
- package/dist/esm/server/mappedUrlStrategy.mjs +1 -0
- package/dist/esm/server/redirectPolicy.mjs +33 -24
- package/dist/esm/shared/mappedUrlStrategy.mjs +19 -0
- package/dist/esm/shared/urlStrategy.mjs +7 -0
- package/dist/esm-node/cli/index.mjs +10 -21
- package/dist/esm-node/cli/localisedRoutes.mjs +9 -0
- package/dist/esm-node/runtime/Link.mjs +19 -32
- package/dist/esm-node/runtime/context.mjs +18 -62
- package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
- package/dist/esm-node/runtime/core.mjs +13 -6
- package/dist/esm-node/runtime/hooks.mjs +7 -8
- package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
- package/dist/esm-node/runtime/navigation.mjs +8 -0
- package/dist/esm-node/runtime/providerComposition.mjs +38 -14
- package/dist/esm-node/runtime/routerAdapter.mjs +30 -141
- package/dist/esm-node/runtime/utils.mjs +19 -17
- package/dist/esm-node/server/defaultUrlStrategy.mjs +8 -0
- package/dist/esm-node/server/index.mjs +11 -7
- package/dist/esm-node/server/mappedUrlStrategy.mjs +2 -0
- package/dist/esm-node/server/redirectPolicy.mjs +33 -24
- package/dist/esm-node/shared/mappedUrlStrategy.mjs +20 -0
- package/dist/esm-node/shared/urlStrategy.mjs +8 -0
- package/dist/types/cli/index.d.ts +2 -2
- package/dist/types/cli/locales.d.ts +1 -1
- package/dist/types/cli/localisedRoutes.d.ts +18 -0
- package/dist/types/runtime/Link.d.ts +2 -2
- package/dist/types/runtime/context.d.ts +10 -13
- package/dist/types/runtime/contextHelpers.d.ts +3 -3
- package/dist/types/runtime/core.d.ts +23 -13
- package/dist/types/runtime/hooks.d.ts +5 -6
- package/dist/types/runtime/i18n/backend/config.d.ts +1 -1
- package/dist/types/runtime/i18n/backend/index.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/middleware.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/middleware.node.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/sdk-backend.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/cache.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/config.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/detector.d.ts +2 -2
- package/dist/types/runtime/i18n/detection/index.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/initOptions.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/middleware.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/middleware.node.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/priority.d.ts +2 -2
- package/dist/types/runtime/i18n/detection/types.d.ts +1 -1
- package/dist/types/runtime/i18n/index.d.ts +3 -3
- package/dist/types/runtime/i18n/instance.d.ts +1 -1
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -1
- package/dist/types/runtime/i18n/utils.d.ts +2 -2
- package/dist/types/runtime/index.d.ts +2 -2
- package/dist/types/runtime/localizedPaths.d.ts +4 -4
- package/dist/types/runtime/navigation.d.ts +10 -0
- package/dist/types/runtime/no-react-i18next.d.ts +2 -2
- package/dist/types/runtime/pluginSetup.d.ts +3 -3
- package/dist/types/runtime/providerComposition.d.ts +15 -3
- package/dist/types/runtime/routerAdapter.d.ts +23 -6
- package/dist/types/runtime/types.d.ts +1 -1
- package/dist/types/runtime/utils.d.ts +4 -12
- package/dist/types/server/defaultUrlStrategy.d.ts +11 -0
- package/dist/types/server/detectorOptions.d.ts +1 -1
- package/dist/types/server/index.d.ts +3 -1
- package/dist/types/server/mappedUrlStrategy.d.ts +7 -0
- package/dist/types/server/redirectPolicy.d.ts +5 -13
- package/dist/types/shared/mappedUrlStrategy.d.ts +27 -0
- package/dist/types/shared/type.d.ts +8 -12
- package/dist/types/shared/urlStrategy.d.ts +18 -0
- package/dist/types/shared/utils.d.ts +1 -1
- package/package.json +30 -18
- package/rstest.config.mts +6 -5
- package/src/cli/index.ts +42 -43
- package/src/cli/localisedRoutes.ts +43 -0
- package/src/runtime/Link.tsx +20 -40
- package/src/runtime/context.tsx +34 -133
- package/src/runtime/contextHelpers.ts +7 -5
- package/src/runtime/core.tsx +53 -6
- package/src/runtime/hooks.ts +14 -9
- package/src/runtime/i18n/instance.ts +1 -2
- package/src/runtime/localizedPaths.ts +24 -19
- package/src/runtime/navigation.tsx +20 -0
- package/src/runtime/providerComposition.tsx +71 -23
- package/src/runtime/routerAdapter.tsx +73 -338
- package/src/runtime/utils.ts +49 -31
- package/src/server/defaultUrlStrategy.ts +27 -0
- package/src/server/index.ts +39 -9
- package/src/server/mappedUrlStrategy.ts +10 -0
- package/src/server/redirectPolicy.ts +85 -70
- package/src/shared/mappedUrlStrategy.ts +59 -0
- package/src/shared/type.ts +7 -11
- package/src/shared/urlStrategy.ts +38 -0
- package/tests/backendDefaults.test.ts +2 -23
- package/tests/defaultUrlStrategy.test.ts +107 -0
- package/tests/federatedI18nBoundaryResolver.test.tsx +65 -0
- package/tests/i18nUtils.test.ts +31 -65
- package/tests/link.test.tsx +13 -511
- package/tests/localisedRouteGeneration.test.ts +159 -0
- package/tests/localisedUrls.test.ts +16 -208
- package/tests/mappedUrlStrategyDerivation.test.ts +62 -0
- package/tests/navigation.test.tsx +365 -0
- package/tests/reactI18nextRuntimeBoundary.test.ts +1 -82
- package/tests/redirectPolicy.test.ts +89 -27
- package/tests/routerAdapter.test.tsx +98 -1171
- package/tests/urlStrategySerialization.test.ts +122 -0
- package/tests/federatedI18nBoundary.client.test.tsx +0 -129
- package/tests/federatedI18nBoundary.test.tsx +0 -132
- package/tests/linkTypes.test.ts +0 -59
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +0 -147
- package/tests/peerCohort.test.ts +0 -25
- package/tests/type-fixture/fixture-globals.d.ts +0 -11
- package/tests/type-fixture/i18nInstanceTypes.fixture.ts +0 -29
- package/tests/type-fixture/linkTypes.fixture.tsx +0 -51
- package/tests/type-fixture/tsconfig.json +0 -16
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { RuntimeComponentResolverContext } from "@modern-js/runtime/context";
|
|
3
4
|
import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
|
|
4
5
|
import { changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n } from "./contextHelpers.mjs";
|
|
5
|
-
import { getActualI18nextInstance } from "./i18n/instance.mjs";
|
|
6
6
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
7
7
|
const modernI18nContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ModernI18nContext');
|
|
8
8
|
const reactI18nextProviderContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ReactI18nextProviderContext');
|
|
@@ -23,68 +23,23 @@ const ModernI18nProvider = ({ children, i18nextProvider, value })=>{
|
|
|
23
23
|
children: content
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
initImmediate: false,
|
|
38
|
-
lng: parent.language,
|
|
39
|
-
ns: [
|
|
40
|
-
defaultNamespace
|
|
41
|
-
],
|
|
42
|
-
resources,
|
|
43
|
-
supportedLngs: languages
|
|
44
|
-
});
|
|
45
|
-
if (!clone) throw new Error('FederatedI18nBoundary requires an i18n instance with cloneInstance support');
|
|
46
|
-
const resourceStore = clone.store;
|
|
47
|
-
const parentResourceStore = parentInstance.store;
|
|
48
|
-
const sharesNestedResourceState = Object.entries(resourceStore?.data ?? {}).some(([language, namespaces])=>{
|
|
49
|
-
const parentNamespaces = parentResourceStore?.data?.[language];
|
|
50
|
-
return namespaces === parentNamespaces || Object.entries(namespaces).some(([namespace, resource])=>null !== resource && 'object' == typeof resource && resource === parentNamespaces?.[namespace]);
|
|
51
|
-
});
|
|
52
|
-
if (clone === parentInstance || resourceStore === parentResourceStore || resourceStore?.data === parentResourceStore?.data || sharesNestedResourceState) throw new Error('FederatedI18nBoundary cloneInstance did not isolate the host resource store');
|
|
53
|
-
if (!resourceStore?.addResourceBundle || !clone.removeResourceBundle || !resourceStore.data) throw new Error('FederatedI18nBoundary requires an isolated mutable i18n resource store');
|
|
54
|
-
for (const [language, namespaces] of Object.entries(resourceStore.data))for (const namespace of Object.keys(namespaces))clone.removeResourceBundle(language, namespace);
|
|
55
|
-
for (const [language, namespaces] of Object.entries(resources))for (const [namespace, resource] of Object.entries(namespaces))resourceStore.addResourceBundle(language, namespace, resource, true, true);
|
|
56
|
-
return clone;
|
|
57
|
-
}, [
|
|
58
|
-
defaultNamespace,
|
|
59
|
-
fallbackLanguage,
|
|
60
|
-
languages,
|
|
61
|
-
parent.i18nInstance,
|
|
62
|
-
parent.language,
|
|
63
|
-
resources
|
|
64
|
-
]);
|
|
65
|
-
const value = useMemo(()=>({
|
|
66
|
-
...parent,
|
|
67
|
-
i18nInstance: scopedInstance,
|
|
68
|
-
language: parent.language,
|
|
69
|
-
languages
|
|
70
|
-
}), [
|
|
71
|
-
languages,
|
|
72
|
-
parent,
|
|
73
|
-
scopedInstance
|
|
74
|
-
]);
|
|
75
|
-
const scopedContent = /*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
76
|
-
value: value,
|
|
77
|
-
children: children
|
|
26
|
+
const MissingFederatedI18nBoundary = ()=>{
|
|
27
|
+
throw new Error('FederatedI18nBoundary requires the @modern-js/i18n-integration runtime plugin');
|
|
28
|
+
};
|
|
29
|
+
const FederatedI18nBoundary = (props)=>{
|
|
30
|
+
const resolveComponent = useContext(RuntimeComponentResolverContext);
|
|
31
|
+
const Boundary = resolveComponent?.(MissingFederatedI18nBoundary, {
|
|
32
|
+
name: 'i18n.FederatedI18nBoundary'
|
|
33
|
+
}) ?? MissingFederatedI18nBoundary;
|
|
34
|
+
if (Boundary === FederatedI18nBoundary) throw new Error('FederatedI18nBoundary resolver returned its own wrapper');
|
|
35
|
+
return /*#__PURE__*/ jsx(Boundary, {
|
|
36
|
+
...props
|
|
78
37
|
});
|
|
79
|
-
return I18nextProvider ? /*#__PURE__*/ jsx(I18nextProvider, {
|
|
80
|
-
i18n: scopedInstance,
|
|
81
|
-
children: scopedContent
|
|
82
|
-
}) : scopedContent;
|
|
83
38
|
};
|
|
84
39
|
const useModernI18n = ()=>{
|
|
85
40
|
const context = useContext(ModernI18nContext);
|
|
86
41
|
if (!context) throw new Error('useModernI18n must be used within ModernI18nProvider');
|
|
87
|
-
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes,
|
|
42
|
+
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, urlStrategy, updateLanguage, synchronizeLanguage } = context;
|
|
88
43
|
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
89
44
|
const pathLanguage = useMemo(()=>getPathLanguage(location?.pathname, languages, localePathRedirect), [
|
|
90
45
|
languages,
|
|
@@ -103,7 +58,7 @@ const useModernI18n = ()=>{
|
|
|
103
58
|
updateLanguage,
|
|
104
59
|
localePathRedirect,
|
|
105
60
|
ignoreRedirectRoutes,
|
|
106
|
-
|
|
61
|
+
urlStrategy,
|
|
107
62
|
languages,
|
|
108
63
|
hasRouter,
|
|
109
64
|
navigate,
|
|
@@ -113,7 +68,7 @@ const useModernI18n = ()=>{
|
|
|
113
68
|
updateLanguage,
|
|
114
69
|
localePathRedirect,
|
|
115
70
|
ignoreRedirectRoutes,
|
|
116
|
-
|
|
71
|
+
urlStrategy,
|
|
117
72
|
languages,
|
|
118
73
|
hasRouter,
|
|
119
74
|
navigate,
|
|
@@ -136,9 +91,10 @@ const useModernI18n = ()=>{
|
|
|
136
91
|
t,
|
|
137
92
|
i18nInstance: i18nInstance,
|
|
138
93
|
supportedLanguages: languages || [],
|
|
139
|
-
|
|
94
|
+
urlStrategy,
|
|
140
95
|
isLanguageSupported,
|
|
141
96
|
isResourcesReady
|
|
142
97
|
};
|
|
143
98
|
};
|
|
144
|
-
export {
|
|
99
|
+
export { getActualI18nextInstance } from "./i18n/instance.mjs";
|
|
100
|
+
export { FederatedI18nBoundary, ModernI18nContext, ModernI18nProvider, ReactI18nextProviderContext, useModernI18n };
|
|
@@ -18,7 +18,7 @@ async function changeI18nInstanceLanguage(i18nInstance, language) {
|
|
|
18
18
|
else await i18nInstance.changeLanguage?.(language);
|
|
19
19
|
}
|
|
20
20
|
async function changeModernI18nLanguage(newLang, options) {
|
|
21
|
-
const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes,
|
|
21
|
+
const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes, urlStrategy, languages, hasRouter, navigate, location } = options;
|
|
22
22
|
try {
|
|
23
23
|
if (!newLang || 'string' != typeof newLang) throw new Error('Language must be non-empty string');
|
|
24
24
|
await changeI18nInstanceLanguage(i18nInstance, newLang);
|
|
@@ -29,8 +29,8 @@ async function changeModernI18nLanguage(newLang, options) {
|
|
|
29
29
|
const relativePath = currentPath.replace(entryPath, '');
|
|
30
30
|
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
31
31
|
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
32
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
33
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [],
|
|
32
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes, urlStrategy)) {
|
|
33
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], urlStrategy);
|
|
34
34
|
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
35
35
|
await navigate(newUrl, {
|
|
36
36
|
replace: true
|
|
@@ -42,8 +42,8 @@ async function changeModernI18nLanguage(newLang, options) {
|
|
|
42
42
|
const relativePath = currentPath.replace(entryPath, '');
|
|
43
43
|
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
44
44
|
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
45
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
46
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [],
|
|
45
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes, urlStrategy)) {
|
|
46
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], urlStrategy);
|
|
47
47
|
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
48
48
|
window.history.pushState(null, '', newUrl);
|
|
49
49
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { resolveMappedUrlStrategy } from "../shared/mappedUrlStrategy.mjs";
|
|
3
|
+
import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
|
|
4
|
+
import { I18nRouterNavigationProvider, useIntegratedRouterAdapter } from "./navigation.mjs";
|
|
2
5
|
import { setupI18nBeforeRender } from "./pluginSetup.mjs";
|
|
3
6
|
import { createI18nRootWrapper } from "./providerComposition.mjs";
|
|
4
7
|
import { resolveReactI18nextIntegration } from "./reactI18next.mjs";
|
|
@@ -6,9 +9,10 @@ import "./types.mjs";
|
|
|
6
9
|
const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
7
10
|
name: '@modern-js/plugin-i18n',
|
|
8
11
|
setup: (api)=>{
|
|
9
|
-
const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true } = options;
|
|
10
|
-
const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes
|
|
12
|
+
const { entryName, i18nInstance: userI18nInstance, initOptions, localeDetection, backend, htmlLangAttr = false, reactI18next = true, urlStrategy, NavigationProvider, LanguageSynchronization } = options;
|
|
13
|
+
const { localePathRedirect = false, i18nextDetector = true, languages = [], fallbackLanguage = 'en', detection, ignoreRedirectRoutes } = localeDetection || {};
|
|
11
14
|
const { enabled: backendEnabled = false } = backend || {};
|
|
15
|
+
const resolvedUrlStrategy = asI18nUrlStrategy(urlStrategy) ?? resolveMappedUrlStrategy(localeDetection);
|
|
12
16
|
let latestI18nInstance;
|
|
13
17
|
let I18nextProvider;
|
|
14
18
|
api.onBeforeRender(async (context)=>{
|
|
@@ -36,15 +40,18 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
|
36
40
|
languages,
|
|
37
41
|
fallbackLanguage,
|
|
38
42
|
ignoreRedirectRoutes,
|
|
39
|
-
|
|
43
|
+
urlStrategy: resolvedUrlStrategy,
|
|
44
|
+
NavigationProvider: NavigationProvider ?? I18nRouterNavigationProvider,
|
|
45
|
+
LanguageSynchronization,
|
|
40
46
|
getLatestI18nInstance: ()=>latestI18nInstance,
|
|
41
47
|
getI18nextProvider: ()=>I18nextProvider
|
|
42
48
|
}));
|
|
43
49
|
}
|
|
44
50
|
});
|
|
51
|
+
export { I18nNavigationProvider, useI18nRouterAdapter, useNativeI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
45
52
|
export { FederatedI18nBoundary, useModernI18n } from "./context.mjs";
|
|
46
53
|
export { I18nLink } from "./I18nLink.mjs";
|
|
47
54
|
export { Link } from "./Link.mjs";
|
|
48
|
-
export {
|
|
49
|
-
export {
|
|
50
|
-
export { createI18nPlugin };
|
|
55
|
+
export { useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
|
|
56
|
+
export { splitUrlTarget } from "./utils.mjs";
|
|
57
|
+
export { I18nRouterNavigationProvider, createI18nPlugin, useIntegratedRouterAdapter };
|
|
@@ -19,7 +19,7 @@ function createMinimalI18nInstance(language) {
|
|
|
19
19
|
};
|
|
20
20
|
return minimalInstance;
|
|
21
21
|
}
|
|
22
|
-
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes,
|
|
22
|
+
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang, urlStrategy) {
|
|
23
23
|
const instance = i18nInstance || createMinimalI18nInstance(lang);
|
|
24
24
|
return {
|
|
25
25
|
language: lang,
|
|
@@ -28,9 +28,8 @@ function createContextValue(lang, i18nInstance, entryName, languages, localePath
|
|
|
28
28
|
languages,
|
|
29
29
|
localePathRedirect,
|
|
30
30
|
ignoreRedirectRoutes,
|
|
31
|
-
|
|
32
|
-
updateLanguage: setLang
|
|
33
|
-
synchronizeLanguage
|
|
31
|
+
urlStrategy,
|
|
32
|
+
updateLanguage: setLang
|
|
34
33
|
};
|
|
35
34
|
}
|
|
36
35
|
function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
|
|
@@ -79,7 +78,7 @@ function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
|
|
|
79
78
|
setForceUpdate
|
|
80
79
|
]);
|
|
81
80
|
}
|
|
82
|
-
function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes,
|
|
81
|
+
function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, urlStrategy) {
|
|
83
82
|
const urlRef = useRef('');
|
|
84
83
|
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
85
84
|
useEffect(()=>{
|
|
@@ -96,10 +95,10 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
|
|
|
96
95
|
const currentHash = hasRouter && location ? location.hash : window.location.hash;
|
|
97
96
|
const entryPath = getEntryPath();
|
|
98
97
|
const relativePath = currentPathname.replace(entryPath, '');
|
|
99
|
-
if (shouldIgnoreRedirect(relativePath, languages, ignoreRedirectRoutes)) return;
|
|
98
|
+
if (shouldIgnoreRedirect(relativePath, languages, ignoreRedirectRoutes, urlStrategy)) return;
|
|
100
99
|
const pathDetection = detectLanguageFromPath(currentPathname, languages, localePathRedirect);
|
|
101
100
|
const targetLanguage = pathDetection.language || i18nInstance.language || fallbackLanguage || languages[0] || 'en';
|
|
102
|
-
const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages,
|
|
101
|
+
const newPath = buildLocalizedUrl(relativePath, targetLanguage, languages, urlStrategy);
|
|
103
102
|
const newUrl = entryPath + newPath + currentSearch + currentHash;
|
|
104
103
|
urlRef.current = currentPathname;
|
|
105
104
|
if (newUrl !== currentPathname + currentSearch + currentHash) if (hasRouter && navigate && location) navigate(newUrl, {
|
|
@@ -115,7 +114,7 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
|
|
|
115
114
|
languages,
|
|
116
115
|
fallbackLanguage,
|
|
117
116
|
ignoreRedirectRoutes,
|
|
118
|
-
|
|
117
|
+
urlStrategy
|
|
119
118
|
]);
|
|
120
119
|
}
|
|
121
120
|
function useLanguageSync(i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang) {
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import { canonicalTargetPathname } from "@modern-js/i18n-runtime-extensions";
|
|
3
2
|
import { useMemo } from "react";
|
|
4
3
|
import { useModernI18n } from "./context.mjs";
|
|
5
4
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
6
5
|
import { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
|
|
7
|
-
const localizePath = (pathname, language, config)=>buildLocalizedUrl(pathname, language, config.languages, config.
|
|
6
|
+
const localizePath = (pathname, language, config)=>buildLocalizedUrl(pathname, language, config.languages, config.urlStrategy);
|
|
8
7
|
const canonicalPath = (target, config)=>{
|
|
9
8
|
const { pathname, search, hash } = splitUrlTarget(target);
|
|
10
|
-
|
|
9
|
+
if (config.urlStrategy) return `${config.urlStrategy.canonicalPathname(pathname, config.languages)}${search}${hash}`;
|
|
10
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
11
|
+
if (segments[0] && config.languages.some((language)=>language.toLowerCase() === segments[0].toLowerCase())) segments.shift();
|
|
12
|
+
const resolvedPath = `/${segments.join('/')}`;
|
|
11
13
|
return `${resolvedPath}${search}${hash}`;
|
|
12
14
|
};
|
|
13
15
|
const useLocalizedPaths = ()=>{
|
|
14
|
-
const { supportedLanguages,
|
|
16
|
+
const { supportedLanguages, urlStrategy } = useModernI18n();
|
|
15
17
|
return useMemo(()=>{
|
|
16
18
|
const config = {
|
|
17
19
|
languages: supportedLanguages,
|
|
18
|
-
|
|
20
|
+
urlStrategy
|
|
19
21
|
};
|
|
20
22
|
return {
|
|
21
23
|
localizePath: (pathname, language)=>localizePath(pathname, language, config),
|
|
@@ -23,11 +25,11 @@ const useLocalizedPaths = ()=>{
|
|
|
23
25
|
};
|
|
24
26
|
}, [
|
|
25
27
|
supportedLanguages,
|
|
26
|
-
|
|
28
|
+
urlStrategy
|
|
27
29
|
]);
|
|
28
30
|
};
|
|
29
31
|
const useLocalizedLocation = ()=>{
|
|
30
|
-
const { language, supportedLanguages,
|
|
32
|
+
const { language, supportedLanguages, urlStrategy } = useModernI18n();
|
|
31
33
|
const { location } = useI18nRouterAdapter();
|
|
32
34
|
const pathname = location?.pathname ?? '/';
|
|
33
35
|
const search = location?.search ?? '';
|
|
@@ -35,7 +37,7 @@ const useLocalizedLocation = ()=>{
|
|
|
35
37
|
return useMemo(()=>{
|
|
36
38
|
const config = {
|
|
37
39
|
languages: supportedLanguages,
|
|
38
|
-
|
|
40
|
+
urlStrategy
|
|
39
41
|
};
|
|
40
42
|
const alternates = {};
|
|
41
43
|
for (const supportedLanguage of supportedLanguages)alternates[supportedLanguage] = `${localizePath(pathname, supportedLanguage, config)}${search}${hash}`;
|
|
@@ -47,7 +49,7 @@ const useLocalizedLocation = ()=>{
|
|
|
47
49
|
}, [
|
|
48
50
|
language,
|
|
49
51
|
supportedLanguages,
|
|
50
|
-
|
|
52
|
+
urlStrategy,
|
|
51
53
|
pathname,
|
|
52
54
|
search,
|
|
53
55
|
hash
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { createI18nRouterNavigation } from "@modern-js/i18n-runtime-extensions/router-navigation";
|
|
3
|
+
import { I18nNavigationProvider, useNativeI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
4
|
+
const { I18nRouterNavigationProvider: I18nRouterNavigationProvider, useIntegratedRouterAdapter: useIntegratedRouterAdapter } = createI18nRouterNavigation({
|
|
5
|
+
I18nNavigationProvider: I18nNavigationProvider,
|
|
6
|
+
useNativeI18nRouterAdapter: useNativeI18nRouterAdapter
|
|
7
|
+
});
|
|
8
|
+
export { I18nRouterNavigationProvider, useIntegratedRouterAdapter };
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useLanguageSync } from "@modern-js/i18n-runtime-extensions";
|
|
4
3
|
import { RuntimeContext } from "@modern-js/runtime";
|
|
5
4
|
import { Helmet } from "@modern-js/runtime/head";
|
|
6
5
|
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
7
6
|
import { ModernI18nProvider } from "./context.mjs";
|
|
8
|
-
import { createContextValue, useClientSideRedirect, useSdkResourcesLoader } from "./hooks.mjs";
|
|
7
|
+
import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
|
|
9
8
|
import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
|
|
10
9
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
11
|
-
const
|
|
12
|
-
|
|
10
|
+
const NativeLanguageSynchronization = ({ i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang, children })=>{
|
|
11
|
+
const synchronizeLanguage = useLanguageSync(i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang);
|
|
12
|
+
return children(synchronizeLanguage);
|
|
13
|
+
};
|
|
14
|
+
const createI18nRootWrapper = (options)=>(App)=>{
|
|
15
|
+
const LanguageSynchronization = options.LanguageSynchronization ?? NativeLanguageSynchronization;
|
|
16
|
+
const I18nRoot = (props)=>{
|
|
17
|
+
const { entryName, htmlLangAttr, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, urlStrategy, getLatestI18nInstance, getI18nextProvider } = options;
|
|
13
18
|
const runtimeContext = useContext(RuntimeContext);
|
|
14
19
|
const i18nInstance = runtimeContext.i18nInstance || getLatestI18nInstance();
|
|
15
20
|
const initialLang = useMemo(()=>i18nInstance?.language || fallbackLanguage, [
|
|
@@ -34,18 +39,16 @@ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
|
34
39
|
lang
|
|
35
40
|
]);
|
|
36
41
|
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang, synchronizeLanguage), [
|
|
42
|
+
useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, urlStrategy);
|
|
43
|
+
const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, setLang, urlStrategy), [
|
|
40
44
|
lang,
|
|
41
45
|
i18nInstance,
|
|
42
46
|
entryName,
|
|
43
47
|
languages,
|
|
44
48
|
localePathRedirect,
|
|
45
49
|
ignoreRedirectRoutes,
|
|
46
|
-
|
|
47
|
-
forceUpdate
|
|
48
|
-
synchronizeLanguage
|
|
50
|
+
urlStrategy,
|
|
51
|
+
forceUpdate
|
|
49
52
|
]);
|
|
50
53
|
const I18nextProvider = getI18nextProvider();
|
|
51
54
|
const i18nextInstanceForProvider = useMemo(()=>i18nInstance ? getI18nextInstanceForProvider(i18nInstance, lang) : void 0, [
|
|
@@ -68,12 +71,33 @@ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
|
68
71
|
lang
|
|
69
72
|
}
|
|
70
73
|
}),
|
|
71
|
-
/*#__PURE__*/ jsx(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
/*#__PURE__*/ jsx(LanguageSynchronization, {
|
|
75
|
+
i18nInstance: i18nInstance,
|
|
76
|
+
localePathRedirect: localePathRedirect,
|
|
77
|
+
languages: languages,
|
|
78
|
+
pathname: location?.pathname,
|
|
79
|
+
prevLangRef: prevLangRef,
|
|
80
|
+
setLang: setLang,
|
|
81
|
+
children: (synchronizeLanguage)=>/*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
82
|
+
i18nextProvider: I18nextProvider,
|
|
83
|
+
value: {
|
|
84
|
+
...contextValue,
|
|
85
|
+
synchronizeLanguage
|
|
86
|
+
},
|
|
87
|
+
children: appContent
|
|
88
|
+
})
|
|
75
89
|
})
|
|
76
90
|
]
|
|
77
91
|
});
|
|
78
92
|
};
|
|
93
|
+
const { NavigationProvider } = options;
|
|
94
|
+
if (!NavigationProvider) return I18nRoot;
|
|
95
|
+
return function(props) {
|
|
96
|
+
return /*#__PURE__*/ jsx(NavigationProvider, {
|
|
97
|
+
children: /*#__PURE__*/ jsx(I18nRoot, {
|
|
98
|
+
...props
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
};
|
|
79
103
|
export { createI18nRootWrapper };
|
|
@@ -1,151 +1,40 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
3
|
import { RuntimeContext, isBrowser } from "@modern-js/runtime";
|
|
3
|
-
import { InternalRuntimeContext
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
pathname: locationValue.pathname,
|
|
15
|
-
search: normalizeUrlPart('string' == typeof locationValue.search ? locationValue.search : locationValue.searchStr, '?'),
|
|
16
|
-
hash: normalizeUrlPart(locationValue.hash, '#')
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
const getWindowLocation = ()=>{
|
|
20
|
-
if (!isBrowser()) return null;
|
|
21
|
-
return {
|
|
22
|
-
pathname: window.location.pathname,
|
|
23
|
-
search: window.location.search,
|
|
24
|
-
hash: window.location.hash
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
const getRouterFramework = (runtimeContext, internalContext, inReactRouter)=>{
|
|
28
|
-
const framework = getRouterRuntimeState(internalContext)?.framework || getRouterRuntimeState(runtimeContext)?.framework;
|
|
29
|
-
if (framework) return framework;
|
|
30
|
-
if (internalContext.router?.useRouter || runtimeContext.router?.useRouter) return 'tanstack';
|
|
31
|
-
if (internalContext.router?.useLocation || internalContext.router?.useHref || runtimeContext.router?.useLocation || runtimeContext.router?.useHref) return 'react-router';
|
|
32
|
-
if (inReactRouter) return 'react-router';
|
|
33
|
-
};
|
|
34
|
-
const getRouterInstance = (internalContext, contextRouter)=>{
|
|
35
|
-
if (contextRouter) return contextRouter;
|
|
36
|
-
const router = getRouterRuntimeState(internalContext)?.instance;
|
|
37
|
-
if (!router || 'object' != typeof router) return null;
|
|
38
|
-
return router;
|
|
39
|
-
};
|
|
40
|
-
const getRouterStateLocation = (internalContext, contextRouter)=>{
|
|
41
|
-
const router = getRouterInstance(internalContext, contextRouter);
|
|
42
|
-
return normalizeLocation(router?.stores?.location?.get?.()) || normalizeLocation(router?.state?.location);
|
|
43
|
-
};
|
|
44
|
-
const getRouterParams = (internalContext, contextRouter)=>{
|
|
45
|
-
const router = getRouterInstance(internalContext, contextRouter);
|
|
46
|
-
const matches = router?.stores?.matches?.get?.() || router?.state?.matches;
|
|
47
|
-
if (!Array.isArray(matches)) return {};
|
|
48
|
-
return matches.reduce((params, match)=>{
|
|
49
|
-
if (match?.params) Object.assign(params, match.params);
|
|
50
|
-
return params;
|
|
51
|
-
}, {});
|
|
52
|
-
};
|
|
53
|
-
const getRouterSnapshot = (internalContext, contextRouter)=>{
|
|
54
|
-
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
55
|
-
const params = getRouterParams(internalContext, contextRouter);
|
|
56
|
-
return JSON.stringify([
|
|
57
|
-
location?.pathname ?? '',
|
|
58
|
-
location?.search ?? '',
|
|
59
|
-
location?.hash ?? '',
|
|
60
|
-
Object.entries(params).sort(([left], [right])=>left.localeCompare(right))
|
|
61
|
-
]);
|
|
62
|
-
};
|
|
63
|
-
const useI18nRouterAdapter = ()=>{
|
|
4
|
+
import { InternalRuntimeContext } from "@modern-js/runtime/context";
|
|
5
|
+
import { createContext, useContext } from "react";
|
|
6
|
+
const navigationContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/I18nNavigationContext');
|
|
7
|
+
const contextStore = globalThis;
|
|
8
|
+
const I18nNavigationContext = contextStore[navigationContextKey] ??= /*#__PURE__*/ createContext(null);
|
|
9
|
+
const I18nNavigationProvider = ({ children, value })=>/*#__PURE__*/ jsx(I18nNavigationContext.Provider, {
|
|
10
|
+
value: value,
|
|
11
|
+
children: children
|
|
12
|
+
});
|
|
13
|
+
const useNativeI18nRouterAdapter = ()=>{
|
|
64
14
|
const runtimeContext = useContext(RuntimeContext);
|
|
65
15
|
const internalContext = useContext(InternalRuntimeContext);
|
|
66
16
|
const routerApi = internalContext.router || runtimeContext.router;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
77
|
-
const subscribeToRouter = useCallback((update)=>{
|
|
78
|
-
const router = getRouterInstance(internalContext, contextRouter);
|
|
79
|
-
if (!router) return ()=>void 0;
|
|
80
|
-
const unsubscribers = [];
|
|
81
|
-
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
82
|
-
const subscribe = router.subscribe;
|
|
83
|
-
const unsubscribe = subscribe.call(router, update);
|
|
84
|
-
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
85
|
-
}
|
|
86
|
-
if ('tanstack' === framework && 'function' == typeof router.stores?.location?.subscribe) {
|
|
87
|
-
const unsubscribe = router.stores.location.subscribe(update);
|
|
88
|
-
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
89
|
-
}
|
|
90
|
-
if ('tanstack' === framework && 'function' == typeof router.subscribe) {
|
|
91
|
-
const subscribe = router.subscribe;
|
|
92
|
-
for (const eventType of [
|
|
93
|
-
'onBeforeNavigate',
|
|
94
|
-
'onBeforeLoad'
|
|
95
|
-
]){
|
|
96
|
-
const unsubscribe = subscribe.call(router, eventType, update);
|
|
97
|
-
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return ()=>{
|
|
101
|
-
for (const unsubscribe of unsubscribers)unsubscribe();
|
|
102
|
-
};
|
|
103
|
-
}, [
|
|
104
|
-
contextRouter,
|
|
105
|
-
framework,
|
|
106
|
-
inReactRouter,
|
|
107
|
-
internalContext
|
|
108
|
-
]);
|
|
109
|
-
const getSnapshot = useCallback(()=>getRouterSnapshot(internalContext, contextRouter), [
|
|
110
|
-
contextRouter,
|
|
111
|
-
internalContext
|
|
112
|
-
]);
|
|
113
|
-
useSyncExternalStore(subscribeToRouter, getSnapshot, getSnapshot);
|
|
114
|
-
const navigate = useCallback((href, options)=>{
|
|
115
|
-
const router = getRouterInstance(internalContext, contextRouter);
|
|
116
|
-
const activeFramework = getRouterFramework(runtimeContext, internalContext, inReactRouter);
|
|
117
|
-
if ('tanstack' === activeFramework) {
|
|
118
|
-
if ('function' == typeof router?.navigate) return router.navigate({
|
|
119
|
-
to: href,
|
|
120
|
-
replace: options?.replace,
|
|
121
|
-
...options?.state === void 0 ? {} : {
|
|
122
|
-
state: options.state
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
throw new Error('TanStack router instance is not available.');
|
|
126
|
-
}
|
|
127
|
-
if (reactRouterNavigate) return reactRouterNavigate(href, options);
|
|
128
|
-
if ('react-router' === activeFramework) {
|
|
129
|
-
if ('function' == typeof router?.navigate) return router.navigate(href, options);
|
|
130
|
-
throw new Error('React Router instance is not available.');
|
|
131
|
-
}
|
|
132
|
-
}, [
|
|
133
|
-
contextRouter,
|
|
134
|
-
internalContext,
|
|
135
|
-
inReactRouter,
|
|
136
|
-
reactRouterNavigate,
|
|
137
|
-
runtimeContext
|
|
138
|
-
]);
|
|
139
|
-
const location = (reactRouterLocation ? normalizeLocation(reactRouterLocation) : getRouterStateLocation(internalContext, contextRouter)) || getWindowLocation();
|
|
140
|
-
const params = inReactRouter ? reactRouterParams : getRouterParams(internalContext, contextRouter);
|
|
141
|
-
const Link = 'tanstack' === framework ? internalContext.router?.Link || runtimeContext.router?.Link || null : 'react-router' === framework || inReactRouter ? routerApi?.Link ?? null : null;
|
|
17
|
+
const hasRouter = routerApi?.useInRouterContext?.() ?? false;
|
|
18
|
+
const navigate = hasRouter ? routerApi?.useNavigate?.() ?? null : null;
|
|
19
|
+
const routerLocation = hasRouter ? routerApi?.useLocation?.() ?? null : null;
|
|
20
|
+
const params = hasRouter ? routerApi?.useParams?.() ?? {} : {};
|
|
21
|
+
const location = routerLocation ?? (isBrowser() ? {
|
|
22
|
+
pathname: window.location.pathname,
|
|
23
|
+
search: window.location.search,
|
|
24
|
+
hash: window.location.hash
|
|
25
|
+
} : null);
|
|
142
26
|
return {
|
|
143
|
-
framework,
|
|
27
|
+
framework: hasRouter ? 'react-router' : void 0,
|
|
144
28
|
hasRouter,
|
|
29
|
+
navigate,
|
|
145
30
|
location,
|
|
146
|
-
|
|
147
|
-
Link
|
|
148
|
-
params
|
|
31
|
+
params,
|
|
32
|
+
Link: hasRouter ? routerApi?.Link ?? null : null
|
|
149
33
|
};
|
|
150
34
|
};
|
|
151
|
-
|
|
35
|
+
const useI18nRouterAdapter = ()=>{
|
|
36
|
+
const provided = useContext(I18nNavigationContext);
|
|
37
|
+
const native = useNativeI18nRouterAdapter();
|
|
38
|
+
return provided ?? native;
|
|
39
|
+
};
|
|
40
|
+
export { I18nNavigationProvider, useI18nRouterAdapter, useNativeI18nRouterAdapter };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import { localiseTargetPathname, shouldSkipLocaleRedirect } from "@modern-js/i18n-runtime-extensions";
|
|
3
2
|
import { isBrowser } from "@modern-js/runtime";
|
|
4
3
|
import { getGlobalBasename } from "@modern-js/runtime/context";
|
|
4
|
+
import { splitUrlTarget } from "@modern-js/runtime-utils/url";
|
|
5
|
+
import { isDefaultLocaleRedirectSkipPath } from "../shared/mappedUrlStrategy.mjs";
|
|
6
|
+
import { asI18nUrlStrategy } from "../shared/urlStrategy.mjs";
|
|
5
7
|
const getPathname = (context)=>{
|
|
6
8
|
if (isBrowser()) return window.location.pathname;
|
|
7
9
|
return context.ssrContext?.request?.pathname || '/';
|
|
@@ -17,22 +19,14 @@ const getLanguageFromPath = (pathname, languages, fallbackLanguage)=>{
|
|
|
17
19
|
if (languages.includes(firstSegment)) return firstSegment;
|
|
18
20
|
return fallbackLanguage;
|
|
19
21
|
};
|
|
20
|
-
const
|
|
21
|
-
const hashIndex = target.indexOf('#');
|
|
22
|
-
const hash = hashIndex >= 0 ? target.slice(hashIndex) : '';
|
|
23
|
-
const beforeHash = hashIndex >= 0 ? target.slice(0, hashIndex) : target;
|
|
24
|
-
const searchIndex = beforeHash.indexOf('?');
|
|
25
|
-
const search = searchIndex >= 0 ? beforeHash.slice(searchIndex) : '';
|
|
26
|
-
const pathname = searchIndex >= 0 ? beforeHash.slice(0, searchIndex) : beforeHash;
|
|
27
|
-
return {
|
|
28
|
-
pathname,
|
|
29
|
-
search,
|
|
30
|
-
hash
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
const buildLocalizedUrl = (target, language, languages, localisedUrls)=>{
|
|
22
|
+
const buildLocalizedUrl = (target, language, languages, urlStrategy)=>{
|
|
34
23
|
const { pathname, search, hash } = splitUrlTarget(target);
|
|
35
|
-
const
|
|
24
|
+
const strategy = asI18nUrlStrategy(urlStrategy);
|
|
25
|
+
if (strategy) return `${strategy.localizePathname(pathname, language, languages)}${search}${hash}`;
|
|
26
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
27
|
+
if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments[0] = language;
|
|
28
|
+
else segments.unshift(language);
|
|
29
|
+
const localizedPathname = `/${segments.join('/')}`;
|
|
36
30
|
return `${localizedPathname}${search}${hash}`;
|
|
37
31
|
};
|
|
38
32
|
const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
|
|
@@ -52,5 +46,13 @@ const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
|
|
|
52
46
|
detected: false
|
|
53
47
|
};
|
|
54
48
|
};
|
|
55
|
-
const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes
|
|
49
|
+
const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes, urlStrategy)=>{
|
|
50
|
+
if (isDefaultLocaleRedirectSkipPath(pathname, languages)) return true;
|
|
51
|
+
if (asI18nUrlStrategy(urlStrategy)?.shouldSkipRedirect?.(pathname, languages)) return true;
|
|
52
|
+
if (!ignoreRedirectRoutes) return false;
|
|
53
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
54
|
+
if (segments[0] && languages.some((item)=>item.toLowerCase() === segments[0].toLowerCase())) segments.shift();
|
|
55
|
+
const normalizedPath = `/${segments.join('/')}`;
|
|
56
|
+
return 'function' == typeof ignoreRedirectRoutes ? ignoreRedirectRoutes(normalizedPath) : ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
|
|
57
|
+
};
|
|
56
58
|
export { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getLanguageFromPath, getPathname, shouldIgnoreRedirect, splitUrlTarget };
|