@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.30 → 3.5.0-ultramodern.31
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 +2 -1
- package/dist/cjs/runtime/Link.js +8 -89
- package/dist/cjs/runtime/context.js +18 -82
- package/dist/cjs/runtime/contextHelpers.js +137 -0
- package/dist/cjs/runtime/core.js +29 -132
- package/dist/cjs/runtime/i18n/detection/cache.js +149 -0
- package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
- package/dist/cjs/runtime/i18n/detection/index.js +8 -263
- package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
- package/dist/cjs/runtime/i18n/detection/language.js +62 -0
- package/dist/cjs/runtime/i18n/detection/path.js +49 -0
- package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
- package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
- package/dist/cjs/runtime/i18n/detection/types.js +18 -0
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +113 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- package/dist/cjs/server/apiPrefix.js +72 -0
- package/dist/cjs/server/detectorOptions.js +97 -0
- package/dist/cjs/server/index.js +17 -152
- package/dist/cjs/server/redirectPolicy.js +102 -0
- package/dist/cjs/shared/localisedUrls/config.js +49 -0
- package/dist/cjs/shared/localisedUrls/index.js +75 -0
- package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +65 -0
- package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +91 -0
- package/dist/cjs/shared/localisedUrls/routes.js +129 -0
- package/dist/cjs/shared/localisedUrls/types.js +18 -0
- package/dist/cjs/shared/localisedUrls.js +13 -294
- package/dist/esm/cli/index.mjs +2 -1
- package/dist/esm/runtime/Link.mjs +1 -82
- package/dist/esm/runtime/context.mjs +18 -82
- package/dist/esm/runtime/contextHelpers.mjs +84 -0
- package/dist/esm/runtime/core.mjs +30 -133
- package/dist/esm/runtime/i18n/detection/cache.mjs +102 -0
- package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
- package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
- package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
- package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
- package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +75 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- package/dist/esm/server/apiPrefix.mjs +31 -0
- package/dist/esm/server/detectorOptions.mjs +59 -0
- package/dist/esm/server/index.mjs +4 -139
- package/dist/esm/server/redirectPolicy.mjs +52 -0
- package/dist/esm/shared/localisedUrls/config.mjs +11 -0
- package/dist/esm/shared/localisedUrls/index.mjs +6 -0
- package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +50 -0
- package/dist/esm/shared/localisedUrls/routes.mjs +88 -0
- package/dist/esm/shared/localisedUrls/types.mjs +0 -0
- package/dist/esm/shared/localisedUrls.mjs +1 -283
- package/dist/esm-node/cli/index.mjs +2 -1
- package/dist/esm-node/runtime/Link.mjs +1 -82
- package/dist/esm-node/runtime/context.mjs +18 -82
- package/dist/esm-node/runtime/contextHelpers.mjs +85 -0
- package/dist/esm-node/runtime/core.mjs +30 -133
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +103 -0
- package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
- package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
- package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
- package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
- package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
- package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
- package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
- package/dist/esm-node/runtime/providerComposition.mjs +76 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- package/dist/esm-node/server/apiPrefix.mjs +32 -0
- package/dist/esm-node/server/detectorOptions.mjs +60 -0
- package/dist/esm-node/server/index.mjs +4 -139
- package/dist/esm-node/server/redirectPolicy.mjs +53 -0
- package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/shared/localisedUrls/index.mjs +7 -0
- package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +51 -0
- package/dist/esm-node/shared/localisedUrls/routes.mjs +89 -0
- package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/shared/localisedUrls.mjs +1 -283
- package/dist/types/runtime/Link.d.ts +3 -7
- package/dist/types/runtime/context.d.ts +2 -2
- package/dist/types/runtime/contextHelpers.d.ts +28 -0
- package/dist/types/runtime/core.d.ts +3 -7
- package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
- package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
- package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
- package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
- package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
- package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
- package/dist/types/runtime/i18n/detection/types.d.ts +29 -0
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
- package/dist/types/runtime/linkHelpers.d.ts +17 -0
- package/dist/types/runtime/pluginSetup.d.ts +32 -0
- package/dist/types/runtime/providerComposition.d.ts +15 -0
- package/dist/types/runtime/reactI18next.d.ts +7 -0
- package/dist/types/server/apiPrefix.d.ts +7 -0
- package/dist/types/server/detectorOptions.d.ts +21 -0
- package/dist/types/server/index.d.ts +1 -6
- package/dist/types/server/redirectPolicy.d.ts +16 -0
- package/dist/types/shared/localisedUrls/config.d.ts +9 -0
- package/dist/types/shared/localisedUrls/index.d.ts +7 -0
- package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/shared/localisedUrls/pathname.d.ts +3 -0
- package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +8 -0
- package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
- package/dist/types/shared/localisedUrls/types.d.ts +14 -0
- package/dist/types/shared/localisedUrls.d.ts +2 -42
- package/package.json +10 -10
- package/src/cli/index.ts +3 -2
- package/src/runtime/Link.tsx +11 -138
- package/src/runtime/context.tsx +42 -196
- package/src/runtime/contextHelpers.ts +232 -0
- package/src/runtime/core.tsx +42 -273
- package/src/runtime/i18n/detection/cache.ts +175 -0
- package/src/runtime/i18n/detection/detector.ts +157 -0
- package/src/runtime/i18n/detection/index.ts +10 -641
- package/src/runtime/i18n/detection/initOptions.ts +74 -0
- package/src/runtime/i18n/detection/language.ts +64 -0
- package/src/runtime/i18n/detection/path.ts +41 -0
- package/src/runtime/i18n/detection/priority.ts +82 -0
- package/src/runtime/i18n/detection/ssr.ts +47 -0
- package/src/runtime/i18n/detection/types.ts +33 -0
- package/src/runtime/i18n/react-i18next.ts +1 -6
- package/src/runtime/linkHelpers.ts +174 -0
- package/src/runtime/pluginSetup.ts +189 -0
- package/src/runtime/providerComposition.tsx +142 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/server/apiPrefix.ts +67 -0
- package/src/server/detectorOptions.ts +105 -0
- package/src/server/index.ts +12 -327
- package/src/server/redirectPolicy.ts +152 -0
- package/src/shared/localisedUrls/config.ts +18 -0
- package/src/shared/localisedUrls/index.ts +13 -0
- package/src/shared/localisedUrls/normalise.ts +67 -0
- package/src/shared/localisedUrls/pathname.ts +57 -0
- package/src/shared/localisedUrls/patterns.ts +160 -0
- package/src/shared/localisedUrls/resolve.ts +118 -0
- package/src/shared/localisedUrls/routes.ts +226 -0
- package/src/shared/localisedUrls/types.ts +18 -0
- package/src/shared/localisedUrls.ts +20 -629
- package/tests/link.test.tsx +111 -0
- package/tests/localisedUrls.test.ts +50 -0
- package/tests/reactI18nextRuntimeBoundary.test.ts +5 -2
- package/tests/routerAdapter.test.tsx +56 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { isBrowser } from "@modern-js/runtime";
|
|
4
3
|
import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
|
|
5
|
-
import {
|
|
4
|
+
import { cacheI18nLanguage, changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n } from "./contextHelpers.mjs";
|
|
6
5
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
7
|
-
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
8
6
|
const modernI18nContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ModernI18nContext');
|
|
9
7
|
const getModernI18nContext = ()=>{
|
|
10
8
|
const globalStore = globalThis;
|
|
@@ -18,14 +16,10 @@ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nCo
|
|
|
18
16
|
});
|
|
19
17
|
const useModernI18n = ()=>{
|
|
20
18
|
const context = useContext(ModernI18nContext);
|
|
21
|
-
if (!context) throw new Error('useModernI18n must be used within
|
|
19
|
+
if (!context) throw new Error('useModernI18n must be used within ModernI18nProvider');
|
|
22
20
|
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage } = context;
|
|
23
21
|
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
24
|
-
const pathLanguage = useMemo(()=>
|
|
25
|
-
if (!localePathRedirect || !location?.pathname) return;
|
|
26
|
-
const detected = detectLanguageFromPath(location.pathname, languages || [], localePathRedirect);
|
|
27
|
-
return detected.detected ? detected.language : void 0;
|
|
28
|
-
}, [
|
|
22
|
+
const pathLanguage = useMemo(()=>getPathLanguage(location?.pathname, languages, localePathRedirect), [
|
|
29
23
|
languages,
|
|
30
24
|
localePathRedirect,
|
|
31
25
|
location?.pathname
|
|
@@ -36,56 +30,24 @@ const useModernI18n = ()=>{
|
|
|
36
30
|
updateLanguage?.(pathLanguage);
|
|
37
31
|
i18nInstance?.setLang?.(pathLanguage);
|
|
38
32
|
i18nInstance?.changeLanguage?.(pathLanguage);
|
|
39
|
-
|
|
40
|
-
const detectionOptions = i18nInstance.options?.detection;
|
|
41
|
-
cacheUserLanguage(i18nInstance, pathLanguage, detectionOptions);
|
|
42
|
-
}
|
|
33
|
+
cacheI18nLanguage(i18nInstance, pathLanguage);
|
|
43
34
|
}, [
|
|
44
35
|
contextLanguage,
|
|
45
36
|
i18nInstance,
|
|
46
37
|
pathLanguage,
|
|
47
38
|
updateLanguage
|
|
48
39
|
]);
|
|
49
|
-
const changeLanguage = useCallback(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const entryPath = getEntryPath();
|
|
61
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
62
|
-
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
63
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
64
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
65
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
66
|
-
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
67
|
-
await navigate(newUrl, {
|
|
68
|
-
replace: true
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
72
|
-
const currentPath = window.location.pathname;
|
|
73
|
-
const entryPath = getEntryPath();
|
|
74
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
75
|
-
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
76
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
77
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
78
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
79
|
-
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
80
|
-
window.history.pushState(null, '', newUrl);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (updateLanguage) updateLanguage(newLang);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error('Failed to change language:', error);
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
|
-
}, [
|
|
40
|
+
const changeLanguage = useCallback((newLang)=>changeModernI18nLanguage(newLang, {
|
|
41
|
+
i18nInstance,
|
|
42
|
+
updateLanguage,
|
|
43
|
+
localePathRedirect,
|
|
44
|
+
ignoreRedirectRoutes,
|
|
45
|
+
localisedUrls,
|
|
46
|
+
languages,
|
|
47
|
+
hasRouter,
|
|
48
|
+
navigate,
|
|
49
|
+
location
|
|
50
|
+
}), [
|
|
89
51
|
i18nInstance,
|
|
90
52
|
updateLanguage,
|
|
91
53
|
localePathRedirect,
|
|
@@ -96,40 +58,14 @@ const useModernI18n = ()=>{
|
|
|
96
58
|
navigate,
|
|
97
59
|
location
|
|
98
60
|
]);
|
|
99
|
-
const t = useCallback((key, ...args)=>
|
|
100
|
-
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t is required');
|
|
101
|
-
return i18nInstance.t(key, ...args);
|
|
102
|
-
}, [
|
|
61
|
+
const t = useCallback((key, ...args)=>translateI18n(i18nInstance, key, ...args), [
|
|
103
62
|
currentLanguage,
|
|
104
63
|
i18nInstance
|
|
105
64
|
]);
|
|
106
|
-
const isLanguageSupported = useCallback((lang)=>languages
|
|
65
|
+
const isLanguageSupported = useCallback((lang)=>isI18nLanguageSupported(languages, lang), [
|
|
107
66
|
languages
|
|
108
67
|
]);
|
|
109
|
-
const isResourcesReady = useMemo(()=>
|
|
110
|
-
if (!i18nInstance?.isInitialized) return false;
|
|
111
|
-
const backend = i18nInstance?.services?.backend;
|
|
112
|
-
if (backend && 'function' == typeof backend.isLoading) {
|
|
113
|
-
const loadingResources = backend.getLoadingResources();
|
|
114
|
-
const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
|
|
115
|
-
if (isCurrentLanguageLoading) return false;
|
|
116
|
-
}
|
|
117
|
-
const store = i18nInstance.store;
|
|
118
|
-
if (!store?.data) return false;
|
|
119
|
-
const langData = store.data[currentLanguage];
|
|
120
|
-
if (!langData || 'object' != typeof langData) return false;
|
|
121
|
-
const options = i18nInstance.options;
|
|
122
|
-
const namespaces = options?.ns || options?.defaultNS || [
|
|
123
|
-
'translation'
|
|
124
|
-
];
|
|
125
|
-
const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
|
|
126
|
-
namespaces
|
|
127
|
-
];
|
|
128
|
-
return requiredNamespaces.every((ns)=>{
|
|
129
|
-
const nsData = langData[ns];
|
|
130
|
-
return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
|
|
131
|
-
});
|
|
132
|
-
}, [
|
|
68
|
+
const isResourcesReady = useMemo(()=>isI18nResourcesReady(i18nInstance, currentLanguage), [
|
|
133
69
|
currentLanguage,
|
|
134
70
|
i18nInstance
|
|
135
71
|
]);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { isBrowser } from "@modern-js/runtime";
|
|
3
|
+
import { cacheUserLanguage } from "./i18n/detection/index.mjs";
|
|
4
|
+
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
5
|
+
function getPathLanguage(pathname, languages, localePathRedirect) {
|
|
6
|
+
if (!localePathRedirect || !pathname) return;
|
|
7
|
+
const detected = detectLanguageFromPath(pathname, languages || [], localePathRedirect);
|
|
8
|
+
return detected.detected ? detected.language : void 0;
|
|
9
|
+
}
|
|
10
|
+
function cacheI18nLanguage(i18nInstance, language) {
|
|
11
|
+
if (isBrowser()) {
|
|
12
|
+
const detectionOptions = i18nInstance.options?.detection;
|
|
13
|
+
cacheUserLanguage(i18nInstance, language, detectionOptions);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function changeModernI18nLanguage(newLang, options) {
|
|
17
|
+
const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes, localisedUrls, languages, hasRouter, navigate, location } = options;
|
|
18
|
+
try {
|
|
19
|
+
if (!newLang || 'string' != typeof newLang) throw new Error('Language must be non-empty string');
|
|
20
|
+
await i18nInstance?.setLang?.(newLang);
|
|
21
|
+
await i18nInstance?.changeLanguage?.(newLang);
|
|
22
|
+
cacheI18nLanguage(i18nInstance, newLang);
|
|
23
|
+
if (localePathRedirect && isBrowser() && hasRouter && navigate && location) {
|
|
24
|
+
const currentPath = location.pathname;
|
|
25
|
+
const entryPath = getEntryPath();
|
|
26
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
27
|
+
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
28
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
29
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
30
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
31
|
+
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
32
|
+
await navigate(newUrl, {
|
|
33
|
+
replace: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
37
|
+
const currentPath = window.location.pathname;
|
|
38
|
+
const entryPath = getEntryPath();
|
|
39
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
40
|
+
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
41
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
42
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
43
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
44
|
+
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
45
|
+
window.history.pushState(null, '', newUrl);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (updateLanguage) updateLanguage(newLang);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Failed change language:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function translateI18n(i18nInstance, key, ...args) {
|
|
55
|
+
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t required');
|
|
56
|
+
return i18nInstance.t(key, ...args);
|
|
57
|
+
}
|
|
58
|
+
function isI18nLanguageSupported(languages, lang) {
|
|
59
|
+
return languages?.includes(lang) || false;
|
|
60
|
+
}
|
|
61
|
+
function isI18nResourcesReady(i18nInstance, currentLanguage) {
|
|
62
|
+
if (!i18nInstance?.isInitialized) return false;
|
|
63
|
+
const backend = i18nInstance?.services?.backend;
|
|
64
|
+
if (backend && 'function' == typeof backend.isLoading) {
|
|
65
|
+
const loadingResources = backend.getLoadingResources();
|
|
66
|
+
const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
|
|
67
|
+
if (isCurrentLanguageLoading) return false;
|
|
68
|
+
}
|
|
69
|
+
const store = i18nInstance.store;
|
|
70
|
+
if (!store?.data) return false;
|
|
71
|
+
const langData = store.data[currentLanguage];
|
|
72
|
+
if (!langData || 'object' != typeof langData) return false;
|
|
73
|
+
const options = i18nInstance.options;
|
|
74
|
+
const namespaces = options?.ns || options?.defaultNS || [
|
|
75
|
+
'translation'
|
|
76
|
+
];
|
|
77
|
+
const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
|
|
78
|
+
namespaces
|
|
79
|
+
];
|
|
80
|
+
return requiredNamespaces.every((ns)=>{
|
|
81
|
+
const nsData = langData[ns];
|
|
82
|
+
return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export { cacheI18nLanguage, changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n };
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { merge } from "@modern-js/runtime-utils/merge";
|
|
6
|
-
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
-
import { ModernI18nProvider, useModernI18n } from "./context.mjs";
|
|
8
|
-
import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
|
|
9
|
-
import { getI18nInstance } from "./i18n/index.mjs";
|
|
10
|
-
import { mergeBackendOptions } from "./i18n/backend/index.mjs";
|
|
11
|
-
import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
|
|
12
|
-
import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
|
|
13
|
-
import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
|
|
14
|
-
import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
|
|
2
|
+
import { setupI18nBeforeRender } from "./pluginSetup.mjs";
|
|
3
|
+
import { createI18nRootWrapper } from "./providerComposition.mjs";
|
|
4
|
+
import { resolveReactI18nextIntegration } from "./reactI18next.mjs";
|
|
15
5
|
import "./types.mjs";
|
|
16
|
-
let i18nLifecycleHelpersPromise;
|
|
17
|
-
function loadI18nLifecycleHelpers() {
|
|
18
|
-
i18nLifecycleHelpersPromise ??= Promise.all([
|
|
19
|
-
import("./i18n/backend/middleware.mjs"),
|
|
20
|
-
import("./i18n/utils.mjs")
|
|
21
|
-
]).then(([backendMiddleware, utils])=>({
|
|
22
|
-
useI18nextBackend: backendMiddleware.useI18nextBackend,
|
|
23
|
-
changeI18nLanguage: utils.changeI18nLanguage,
|
|
24
|
-
ensureLanguageMatch: utils.ensureLanguageMatch,
|
|
25
|
-
initializeI18nInstance: utils.initializeI18nInstance,
|
|
26
|
-
setupClonedInstance: utils.setupClonedInstance
|
|
27
|
-
}));
|
|
28
|
-
return i18nLifecycleHelpersPromise;
|
|
29
|
-
}
|
|
30
6
|
const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
31
7
|
name: '@modern-js/plugin-i18n',
|
|
32
8
|
setup: (api)=>{
|
|
@@ -35,119 +11,40 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
|
35
11
|
const { enabled: backendEnabled = false } = backend || {};
|
|
36
12
|
let latestI18nInstance;
|
|
37
13
|
let I18nextProvider;
|
|
38
|
-
const resolveReactI18nextIntegration = async ()=>{
|
|
39
|
-
if (!reactI18next) return null;
|
|
40
|
-
return loadReactI18nextIntegration?.() ?? null;
|
|
41
|
-
};
|
|
42
14
|
api.onBeforeRender(async (context)=>{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
|
|
50
|
-
if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
|
|
51
|
-
const pathname = getPathname(context);
|
|
52
|
-
if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
|
|
53
|
-
const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
54
|
-
const mergedBackend = mergeBackendOptions(backend, userInitOptions);
|
|
55
|
-
const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
|
|
56
|
-
if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
|
|
57
|
-
const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
|
|
58
|
-
languages,
|
|
59
|
-
fallbackLanguage,
|
|
60
|
-
localePathRedirect,
|
|
15
|
+
latestI18nInstance = await setupI18nBeforeRender(context, {
|
|
16
|
+
api,
|
|
17
|
+
userI18nInstance,
|
|
18
|
+
initOptions,
|
|
19
|
+
backend,
|
|
20
|
+
backendEnabled,
|
|
61
21
|
i18nextDetector,
|
|
62
22
|
detection,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!isBrowser() && i18nInstance.cloneInstance) {
|
|
70
|
-
i18nInstance = i18nInstance.cloneInstance();
|
|
71
|
-
await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
72
|
-
}
|
|
73
|
-
if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
|
|
74
|
-
if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
|
|
75
|
-
context.i18nInstance = i18nInstance;
|
|
76
|
-
latestI18nInstance = i18nInstance;
|
|
77
|
-
context.changeLanguage = async (newLang)=>{
|
|
78
|
-
await changeI18nLanguage(i18nInstance, newLang, {
|
|
79
|
-
detectionOptions: mergedDetection
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
api.wrapRoot((App)=>(props)=>{
|
|
84
|
-
const runtimeContext = useContext(RuntimeContext);
|
|
85
|
-
const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
|
|
86
|
-
const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
|
|
87
|
-
i18nInstance?.language,
|
|
88
|
-
localeDetection?.fallbackLanguage
|
|
89
|
-
]);
|
|
90
|
-
const [lang, setLang] = useState(initialLang);
|
|
91
|
-
const [forceUpdate, setForceUpdate] = useState(0);
|
|
92
|
-
const prevLangRef = useRef(lang);
|
|
93
|
-
const runtimeContextRef = useRef(runtimeContext);
|
|
94
|
-
runtimeContextRef.current = runtimeContext;
|
|
95
|
-
useEffect(()=>{
|
|
96
|
-
if (i18nInstance?.language) {
|
|
97
|
-
const translator = i18nInstance.translator;
|
|
98
|
-
if (translator) translator.language = i18nInstance.language;
|
|
99
|
-
}
|
|
100
|
-
}, [
|
|
101
|
-
i18nInstance?.language
|
|
102
|
-
]);
|
|
103
|
-
useEffect(()=>{
|
|
104
|
-
prevLangRef.current = lang;
|
|
105
|
-
}, [
|
|
106
|
-
lang
|
|
107
|
-
]);
|
|
108
|
-
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
109
|
-
useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
|
|
110
|
-
useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
|
|
111
|
-
const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
|
|
112
|
-
lang,
|
|
113
|
-
i18nInstance,
|
|
114
|
-
entryName,
|
|
115
|
-
languages,
|
|
116
|
-
localePathRedirect,
|
|
117
|
-
ignoreRedirectRoutes,
|
|
118
|
-
localisedUrls,
|
|
119
|
-
forceUpdate
|
|
120
|
-
]);
|
|
121
|
-
const children = props.children;
|
|
122
|
-
const appContent = /*#__PURE__*/ jsxs(Fragment, {
|
|
123
|
-
children: [
|
|
124
|
-
Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
|
|
125
|
-
htmlAttributes: {
|
|
126
|
-
lang
|
|
127
|
-
}
|
|
128
|
-
}),
|
|
129
|
-
/*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
130
|
-
value: contextValue,
|
|
131
|
-
children: App ? /*#__PURE__*/ jsx(App, {
|
|
132
|
-
...props,
|
|
133
|
-
children: children
|
|
134
|
-
}) : children
|
|
135
|
-
})
|
|
136
|
-
]
|
|
137
|
-
});
|
|
138
|
-
if (!i18nInstance) return appContent;
|
|
139
|
-
if (I18nextProvider) {
|
|
140
|
-
const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
|
|
141
|
-
return /*#__PURE__*/ jsx(I18nextProvider, {
|
|
142
|
-
i18n: i18nextInstanceForProvider,
|
|
143
|
-
children: appContent
|
|
144
|
-
});
|
|
23
|
+
localePathRedirect,
|
|
24
|
+
languages,
|
|
25
|
+
fallbackLanguage,
|
|
26
|
+
resolveReactI18nextIntegration: ()=>resolveReactI18nextIntegration(reactI18next, loadReactI18nextIntegration),
|
|
27
|
+
setI18nextProvider: (provider)=>{
|
|
28
|
+
I18nextProvider = provider;
|
|
145
29
|
}
|
|
146
|
-
return appContent;
|
|
147
30
|
});
|
|
31
|
+
});
|
|
32
|
+
api.wrapRoot(createI18nRootWrapper({
|
|
33
|
+
entryName,
|
|
34
|
+
htmlLangAttr,
|
|
35
|
+
localePathRedirect,
|
|
36
|
+
languages,
|
|
37
|
+
fallbackLanguage,
|
|
38
|
+
ignoreRedirectRoutes,
|
|
39
|
+
localisedUrls,
|
|
40
|
+
getLatestI18nInstance: ()=>latestI18nInstance,
|
|
41
|
+
getI18nextProvider: ()=>I18nextProvider
|
|
42
|
+
}));
|
|
148
43
|
}
|
|
149
44
|
});
|
|
45
|
+
export { useModernI18n } from "./context.mjs";
|
|
150
46
|
export { I18nLink } from "./I18nLink.mjs";
|
|
151
47
|
export { Link } from "./Link.mjs";
|
|
152
48
|
export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
|
|
153
|
-
export { buildLocalizedUrl,
|
|
49
|
+
export { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
|
|
50
|
+
export { createI18nPlugin };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
const detectorInstanceCache = new WeakMap();
|
|
3
|
+
const DETECTOR_SAFE_OPTION_KEYS = [
|
|
4
|
+
'lowerCaseLng',
|
|
5
|
+
'nonExplicitSupportedLngs',
|
|
6
|
+
'load',
|
|
7
|
+
'partialBundledLanguages',
|
|
8
|
+
'returnNull',
|
|
9
|
+
'returnEmptyString',
|
|
10
|
+
'returnObjects',
|
|
11
|
+
'joinArrays',
|
|
12
|
+
'keySeparator',
|
|
13
|
+
'nsSeparator',
|
|
14
|
+
'pluralSeparator',
|
|
15
|
+
'contextSeparator',
|
|
16
|
+
'fallbackNS',
|
|
17
|
+
'ns',
|
|
18
|
+
'defaultNS',
|
|
19
|
+
'debug'
|
|
20
|
+
];
|
|
21
|
+
const stableStringify = (value)=>{
|
|
22
|
+
if (null == value) return JSON.stringify(value);
|
|
23
|
+
if ('object' != typeof value) return JSON.stringify(value);
|
|
24
|
+
if (Array.isArray(value)) return `[${value.map((item)=>stableStringify(item)).join(',')}]`;
|
|
25
|
+
const sortedKeys = Object.keys(value).sort();
|
|
26
|
+
const sortedEntries = sortedKeys.map((key)=>{
|
|
27
|
+
const stringifiedValue = stableStringify(value[key]);
|
|
28
|
+
return `${JSON.stringify(key)}:${stringifiedValue}`;
|
|
29
|
+
});
|
|
30
|
+
return `{${sortedEntries.join(',')}}`;
|
|
31
|
+
};
|
|
32
|
+
const buildDetectorConfigKey = (languages, fallbackLanguage, mergedDetection)=>stableStringify({
|
|
33
|
+
languages,
|
|
34
|
+
fallbackLanguage,
|
|
35
|
+
detection: mergedDetection
|
|
36
|
+
});
|
|
37
|
+
const pickSafeDetectionOptions = (userInitOptions)=>{
|
|
38
|
+
if (!userInitOptions) return {};
|
|
39
|
+
const safeOptions = {};
|
|
40
|
+
for (const key of DETECTOR_SAFE_OPTION_KEYS){
|
|
41
|
+
const value = userInitOptions[key];
|
|
42
|
+
if (void 0 !== value) safeOptions[key] = value;
|
|
43
|
+
}
|
|
44
|
+
if (userInitOptions.interpolation) safeOptions.interpolation = {
|
|
45
|
+
...userInitOptions.interpolation
|
|
46
|
+
};
|
|
47
|
+
return safeOptions;
|
|
48
|
+
};
|
|
49
|
+
const cleanupDetectorCacheEntry = (entry)=>{
|
|
50
|
+
if (!entry || !entry.isTemporary) return;
|
|
51
|
+
const instance = entry.instance;
|
|
52
|
+
try {
|
|
53
|
+
instance?.removeAllListeners?.();
|
|
54
|
+
} catch (error) {}
|
|
55
|
+
try {
|
|
56
|
+
instance?.off?.('*');
|
|
57
|
+
} catch (error) {}
|
|
58
|
+
try {
|
|
59
|
+
instance?.services?.backendConnector?.backend?.stop?.();
|
|
60
|
+
} catch (error) {}
|
|
61
|
+
try {
|
|
62
|
+
instance?.services?.backendConnector?.backend?.close?.();
|
|
63
|
+
} catch (error) {}
|
|
64
|
+
};
|
|
65
|
+
const createDetectorInstance = (baseInstance, configKey)=>{
|
|
66
|
+
const cached = detectorInstanceCache.get(baseInstance);
|
|
67
|
+
if (cached && cached.configKey === configKey) return {
|
|
68
|
+
instance: cached.instance,
|
|
69
|
+
isTemporary: cached.isTemporary
|
|
70
|
+
};
|
|
71
|
+
if (cached) {
|
|
72
|
+
cleanupDetectorCacheEntry(cached);
|
|
73
|
+
detectorInstanceCache.delete(baseInstance);
|
|
74
|
+
}
|
|
75
|
+
const createNewInstance = ()=>{
|
|
76
|
+
if ('function' == typeof baseInstance.createInstance) try {
|
|
77
|
+
const created = baseInstance.createInstance();
|
|
78
|
+
if (created) return {
|
|
79
|
+
instance: created,
|
|
80
|
+
isTemporary: true
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {}
|
|
83
|
+
if ('function' == typeof baseInstance.cloneInstance) try {
|
|
84
|
+
const cloned = baseInstance.cloneInstance();
|
|
85
|
+
if (cloned) return {
|
|
86
|
+
instance: cloned,
|
|
87
|
+
isTemporary: true
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {}
|
|
90
|
+
return {
|
|
91
|
+
instance: baseInstance,
|
|
92
|
+
isTemporary: false
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const created = createNewInstance();
|
|
96
|
+
if (created.isTemporary) detectorInstanceCache.set(baseInstance, {
|
|
97
|
+
instance: created.instance,
|
|
98
|
+
isTemporary: true,
|
|
99
|
+
configKey
|
|
100
|
+
});
|
|
101
|
+
return created;
|
|
102
|
+
};
|
|
103
|
+
export { buildDetectorConfigKey, createDetectorInstance, detectorInstanceCache, pickSafeDetectionOptions };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { isBrowser } from "@modern-js/runtime";
|
|
3
|
+
import { buildDetectorConfigKey, createDetectorInstance, detectorInstanceCache, pickSafeDetectionOptions } from "./cache.mjs";
|
|
4
|
+
import { mergeDetectionOptions } from "./initOptions.mjs";
|
|
5
|
+
import { getSupportedLanguage, isLanguageSupported } from "./language.mjs";
|
|
6
|
+
import { detectLanguage, useI18nextLanguageDetector } from "./middleware.mjs";
|
|
7
|
+
const initializeI18nForDetector = async (i18nInstance, options)=>{
|
|
8
|
+
const mergedDetection = mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
|
|
9
|
+
const configKey = buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection);
|
|
10
|
+
const { instance, isTemporary } = createDetectorInstance(i18nInstance, configKey);
|
|
11
|
+
const safeUserOptions = pickSafeDetectionOptions(options.userInitOptions);
|
|
12
|
+
const initOptions = {
|
|
13
|
+
...safeUserOptions,
|
|
14
|
+
fallbackLng: options.fallbackLanguage,
|
|
15
|
+
supportedLngs: options.languages,
|
|
16
|
+
detection: mergedDetection,
|
|
17
|
+
initImmediate: true,
|
|
18
|
+
interpolation: {
|
|
19
|
+
...safeUserOptions?.interpolation || {},
|
|
20
|
+
escapeValue: safeUserOptions?.interpolation?.escapeValue ?? false
|
|
21
|
+
},
|
|
22
|
+
react: {
|
|
23
|
+
useSuspense: false
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
useI18nextLanguageDetector(instance);
|
|
27
|
+
if (instance.isInitialized) {
|
|
28
|
+
if (isTemporary) await instance.init(initOptions);
|
|
29
|
+
} else await instance.init(initOptions);
|
|
30
|
+
return {
|
|
31
|
+
detectorInstance: instance,
|
|
32
|
+
isTemporary
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const detectLanguageFromI18nextDetector = async (i18nInstance, options)=>{
|
|
36
|
+
if (!options.i18nextDetector) return;
|
|
37
|
+
const mergedDetection = mergeDetectionOptions(options.i18nextDetector, options.detection, options.localePathRedirect, options.userInitOptions);
|
|
38
|
+
const { detectorInstance, isTemporary } = await initializeI18nForDetector(i18nInstance, options);
|
|
39
|
+
try {
|
|
40
|
+
const request = options.ssrContext?.request;
|
|
41
|
+
if (!isBrowser() && !request) return;
|
|
42
|
+
const detectorLang = detectLanguage(detectorInstance, request, mergedDetection);
|
|
43
|
+
if (detectorLang) {
|
|
44
|
+
const supportedLang = getSupportedLanguage(detectorLang, options.languages);
|
|
45
|
+
if (supportedLang) return supportedLang;
|
|
46
|
+
}
|
|
47
|
+
if (detectorInstance.isInitialized && detectorInstance.language) {
|
|
48
|
+
const currentLang = detectorInstance.language;
|
|
49
|
+
if (isLanguageSupported(currentLang, options.languages)) return currentLang;
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {} finally{
|
|
52
|
+
if (isTemporary && detectorInstance !== i18nInstance) detectorInstanceCache.set(i18nInstance, {
|
|
53
|
+
instance: detectorInstance,
|
|
54
|
+
isTemporary: true,
|
|
55
|
+
configKey: buildDetectorConfigKey(options.languages, options.fallbackLanguage, mergedDetection)
|
|
56
|
+
});
|
|
57
|
+
else if (detectorInstance === i18nInstance) {
|
|
58
|
+
i18nInstance.isInitialized = false;
|
|
59
|
+
delete i18nInstance.language;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export { detectLanguageFromI18nextDetector };
|