@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.6 → 3.5.0-ultramodern.75
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/cli/locales.js +15 -15
- package/dist/cjs/runtime/Link.js +8 -89
- package/dist/cjs/runtime/context.js +78 -91
- package/dist/cjs/runtime/contextHelpers.js +143 -0
- package/dist/cjs/runtime/core.js +32 -132
- package/dist/cjs/runtime/hooks.js +54 -21
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
- package/dist/cjs/runtime/i18n/detection/cache.js +150 -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/i18n/instance.js +34 -10
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +115 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- package/dist/cjs/runtime/routerAdapter.js +16 -8
- package/dist/cjs/runtime/utils.js +1 -9
- package/dist/cjs/server/apiPrefix.js +72 -0
- package/dist/cjs/server/detectorOptions.js +97 -0
- package/dist/cjs/server/index.js +17 -144
- package/dist/cjs/server/redirectPolicy.js +96 -0
- package/dist/cjs/shared/detection.js +2 -104
- package/dist/cjs/shared/localisedUrls/config.js +49 -0
- package/dist/cjs/shared/localisedUrls/index.js +91 -0
- package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +68 -0
- package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
- package/dist/cjs/shared/localisedUrls/redirect.js +103 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +88 -0
- package/dist/cjs/shared/localisedUrls/routes.js +130 -0
- package/dist/cjs/shared/localisedUrls/types.js +18 -0
- package/dist/cjs/shared/localisedUrls.js +28 -294
- package/dist/cjs/shared/utils.js +2 -8
- package/dist/esm/cli/index.mjs +2 -1
- package/dist/esm/cli/locales.mjs +13 -13
- package/dist/esm/runtime/Link.mjs +1 -82
- package/dist/esm/runtime/context.mjs +76 -92
- package/dist/esm/runtime/contextHelpers.mjs +87 -0
- package/dist/esm/runtime/core.mjs +30 -133
- package/dist/esm/runtime/hooks.mjs +56 -23
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm/runtime/i18n/detection/cache.mjs +103 -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/i18n/instance.mjs +35 -8
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +77 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- package/dist/esm/runtime/routerAdapter.mjs +16 -8
- package/dist/esm/runtime/utils.mjs +2 -10
- package/dist/esm/server/apiPrefix.mjs +31 -0
- package/dist/esm/server/detectorOptions.mjs +59 -0
- package/dist/esm/server/index.mjs +8 -135
- package/dist/esm/server/redirectPolicy.mjs +46 -0
- package/dist/esm/shared/detection.mjs +1 -71
- package/dist/esm/shared/localisedUrls/config.mjs +11 -0
- package/dist/esm/shared/localisedUrls/index.mjs +7 -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/redirect.mjs +56 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +47 -0
- package/dist/esm/shared/localisedUrls/routes.mjs +89 -0
- package/dist/esm/shared/localisedUrls/types.mjs +0 -0
- package/dist/esm/shared/localisedUrls.mjs +1 -283
- package/dist/esm/shared/utils.mjs +1 -1
- package/dist/esm-node/cli/index.mjs +2 -1
- package/dist/esm-node/cli/locales.mjs +13 -13
- package/dist/esm-node/runtime/Link.mjs +1 -82
- package/dist/esm-node/runtime/context.mjs +76 -92
- package/dist/esm-node/runtime/contextHelpers.mjs +88 -0
- package/dist/esm-node/runtime/core.mjs +30 -133
- package/dist/esm-node/runtime/hooks.mjs +56 -23
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +104 -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/i18n/instance.mjs +35 -8
- 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 +78 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- package/dist/esm-node/runtime/routerAdapter.mjs +16 -8
- package/dist/esm-node/runtime/utils.mjs +2 -10
- 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 +8 -135
- package/dist/esm-node/server/redirectPolicy.mjs +47 -0
- package/dist/esm-node/shared/detection.mjs +1 -71
- package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/shared/localisedUrls/index.mjs +8 -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/redirect.mjs +57 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -0
- package/dist/esm-node/shared/localisedUrls/routes.mjs +90 -0
- package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/shared/localisedUrls.mjs +1 -283
- package/dist/esm-node/shared/utils.mjs +1 -1
- package/dist/types/cli/locales.d.ts +1 -1
- package/dist/types/runtime/Link.d.ts +3 -7
- package/dist/types/runtime/context.d.ts +21 -5
- package/dist/types/runtime/contextHelpers.d.ts +29 -0
- package/dist/types/runtime/core.d.ts +4 -8
- package/dist/types/runtime/hooks.d.ts +3 -7
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
- 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/middleware.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
- 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 +32 -0
- package/dist/types/runtime/i18n/instance.d.ts +31 -27
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
- package/dist/types/runtime/i18n/utils.d.ts +14 -5
- 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 +16 -0
- package/dist/types/runtime/reactI18next.d.ts +7 -0
- package/dist/types/runtime/routerAdapter.d.ts +7 -6
- package/dist/types/runtime/utils.d.ts +7 -2
- 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 +22 -0
- package/dist/types/shared/detection.d.ts +1 -11
- package/dist/types/shared/localisedUrls/config.d.ts +9 -0
- package/dist/types/shared/localisedUrls/index.d.ts +9 -0
- package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/shared/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
- package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +7 -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 -36
- package/dist/types/shared/utils.d.ts +0 -2
- package/package.json +17 -20
- package/rstest.config.mts +8 -1
- package/src/cli/index.ts +5 -4
- package/src/cli/locales.ts +3 -3
- package/src/runtime/Link.tsx +11 -138
- package/src/runtime/context.tsx +170 -207
- package/src/runtime/contextHelpers.ts +242 -0
- package/src/runtime/core.tsx +48 -275
- package/src/runtime/hooks.ts +86 -24
- package/src/runtime/i18n/backend/defaults.node.ts +2 -2
- package/src/runtime/i18n/backend/middleware.common.ts +5 -4
- package/src/runtime/i18n/detection/cache.ts +189 -0
- package/src/runtime/i18n/detection/detector.ts +166 -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/middleware.node.ts +26 -7
- package/src/runtime/i18n/detection/middleware.ts +15 -7
- 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 +41 -0
- package/src/runtime/i18n/instance.ts +98 -34
- package/src/runtime/i18n/react-i18next.ts +1 -6
- package/src/runtime/i18n/utils.ts +18 -9
- package/src/runtime/linkHelpers.ts +174 -0
- package/src/runtime/pluginSetup.ts +189 -0
- package/src/runtime/providerComposition.tsx +148 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/runtime/routerAdapter.tsx +36 -15
- package/src/runtime/utils.ts +13 -35
- package/src/server/apiPrefix.ts +67 -0
- package/src/server/detectorOptions.ts +105 -0
- package/src/server/index.ts +14 -319
- package/src/server/redirectPolicy.ts +127 -0
- package/src/shared/detection.ts +1 -1
- package/src/shared/localisedUrls/config.ts +18 -0
- package/src/shared/localisedUrls/index.ts +24 -0
- package/src/shared/localisedUrls/normalise.ts +67 -0
- package/src/shared/localisedUrls/pathname.ts +60 -0
- package/src/shared/localisedUrls/patterns.ts +160 -0
- package/src/shared/localisedUrls/redirect.ts +93 -0
- package/src/shared/localisedUrls/resolve.ts +135 -0
- package/src/shared/localisedUrls/routes.ts +234 -0
- package/src/shared/localisedUrls/types.ts +18 -0
- package/src/shared/localisedUrls.ts +26 -623
- package/src/shared/utils.ts +2 -2
- package/tests/federatedI18nBoundary.client.test.tsx +112 -0
- package/tests/federatedI18nBoundary.test.tsx +120 -0
- package/tests/fixtures/localised-routes.golden.json +156 -0
- package/tests/link.test.tsx +111 -0
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
- package/tests/localisedUrls.test.ts +148 -2
- package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
- package/tests/redirectPolicy.test.ts +45 -0
- package/tests/routerAdapter.test.tsx +603 -0
- package/tests/type-fixture/fixture-globals.d.ts +11 -0
- package/tests/type-fixture/tsconfig.json +1 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { isBrowser } from "@modern-js/runtime";
|
|
3
2
|
import { createContext, useCallback, useContext, useEffect, useMemo } from "react";
|
|
4
|
-
import {
|
|
3
|
+
import { changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n } from "./contextHelpers.mjs";
|
|
4
|
+
import { getActualI18nextInstance } from "./i18n/instance.mjs";
|
|
5
5
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
6
|
-
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
7
6
|
const modernI18nContextKey = Symbol.for('@modern-js/plugin-i18n/runtime/ModernI18nContext');
|
|
8
7
|
const getModernI18nContext = ()=>{
|
|
9
8
|
const globalStore = globalThis;
|
|
@@ -15,76 +14,87 @@ const ModernI18nProvider = ({ children, value })=>/*#__PURE__*/ jsx(ModernI18nCo
|
|
|
15
14
|
value: value,
|
|
16
15
|
children: children
|
|
17
16
|
});
|
|
17
|
+
const FederatedI18nBoundary = ({ children, defaultNamespace, fallbackLanguage, resources, supportedLanguages })=>{
|
|
18
|
+
const parent = useContext(ModernI18nContext);
|
|
19
|
+
if (!parent) throw new Error('FederatedI18nBoundary must be used within ModernI18nProvider');
|
|
20
|
+
const languages = supportedLanguages ?? parent.languages ?? Object.keys(resources);
|
|
21
|
+
const scopedInstance = useMemo(()=>{
|
|
22
|
+
const parentInstance = getActualI18nextInstance(parent.i18nInstance);
|
|
23
|
+
const clone = parentInstance.cloneInstance?.({
|
|
24
|
+
defaultNS: defaultNamespace,
|
|
25
|
+
fallbackLng: fallbackLanguage ?? languages[0] ?? parent.language,
|
|
26
|
+
forkResourceStore: true,
|
|
27
|
+
initImmediate: false,
|
|
28
|
+
lng: parent.language,
|
|
29
|
+
ns: [
|
|
30
|
+
defaultNamespace
|
|
31
|
+
],
|
|
32
|
+
resources,
|
|
33
|
+
supportedLngs: languages
|
|
34
|
+
});
|
|
35
|
+
if (!clone) throw new Error('FederatedI18nBoundary requires an i18n instance with cloneInstance support');
|
|
36
|
+
const resourceStore = clone.store;
|
|
37
|
+
const parentResourceStore = parentInstance.store;
|
|
38
|
+
const sharesNestedResourceState = Object.entries(resourceStore?.data ?? {}).some(([language, namespaces])=>{
|
|
39
|
+
const parentNamespaces = parentResourceStore?.data?.[language];
|
|
40
|
+
return namespaces === parentNamespaces || Object.entries(namespaces).some(([namespace, resource])=>null !== resource && 'object' == typeof resource && resource === parentNamespaces?.[namespace]);
|
|
41
|
+
});
|
|
42
|
+
if (clone === parentInstance || resourceStore === parentResourceStore || resourceStore?.data === parentResourceStore?.data || sharesNestedResourceState) throw new Error('FederatedI18nBoundary cloneInstance did not isolate the host resource store');
|
|
43
|
+
if (!resourceStore?.addResourceBundle || !clone.removeResourceBundle || !resourceStore.data) throw new Error('FederatedI18nBoundary requires an isolated mutable i18n resource store');
|
|
44
|
+
for (const [language, namespaces] of Object.entries(resourceStore.data))for (const namespace of Object.keys(namespaces))clone.removeResourceBundle(language, namespace);
|
|
45
|
+
for (const [language, namespaces] of Object.entries(resources))for (const [namespace, resource] of Object.entries(namespaces))resourceStore.addResourceBundle(language, namespace, resource, true, true);
|
|
46
|
+
return clone;
|
|
47
|
+
}, [
|
|
48
|
+
defaultNamespace,
|
|
49
|
+
fallbackLanguage,
|
|
50
|
+
languages,
|
|
51
|
+
parent.i18nInstance,
|
|
52
|
+
parent.language,
|
|
53
|
+
resources
|
|
54
|
+
]);
|
|
55
|
+
const value = useMemo(()=>({
|
|
56
|
+
...parent,
|
|
57
|
+
i18nInstance: scopedInstance,
|
|
58
|
+
language: parent.language,
|
|
59
|
+
languages
|
|
60
|
+
}), [
|
|
61
|
+
languages,
|
|
62
|
+
parent,
|
|
63
|
+
scopedInstance
|
|
64
|
+
]);
|
|
65
|
+
return /*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
66
|
+
value: value,
|
|
67
|
+
children: children
|
|
68
|
+
});
|
|
69
|
+
};
|
|
18
70
|
const useModernI18n = ()=>{
|
|
19
71
|
const context = useContext(ModernI18nContext);
|
|
20
|
-
if (!context) throw new Error('useModernI18n must be used within
|
|
21
|
-
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage } = context;
|
|
72
|
+
if (!context) throw new Error('useModernI18n must be used within ModernI18nProvider');
|
|
73
|
+
const { language: contextLanguage, i18nInstance, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, updateLanguage, synchronizeLanguage } = context;
|
|
22
74
|
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
23
|
-
const pathLanguage = useMemo(()=>
|
|
24
|
-
if (!localePathRedirect || !location?.pathname) return;
|
|
25
|
-
const detected = detectLanguageFromPath(location.pathname, languages || [], localePathRedirect);
|
|
26
|
-
return detected.detected ? detected.language : void 0;
|
|
27
|
-
}, [
|
|
75
|
+
const pathLanguage = useMemo(()=>getPathLanguage(location?.pathname, languages, localePathRedirect), [
|
|
28
76
|
languages,
|
|
29
77
|
localePathRedirect,
|
|
30
78
|
location?.pathname
|
|
31
79
|
]);
|
|
32
|
-
const currentLanguage = pathLanguage || contextLanguage;
|
|
33
80
|
useEffect(()=>{
|
|
34
|
-
if (
|
|
35
|
-
updateLanguage?.(pathLanguage);
|
|
36
|
-
i18nInstance?.setLang?.(pathLanguage);
|
|
37
|
-
i18nInstance?.changeLanguage?.(pathLanguage);
|
|
38
|
-
if (isBrowser()) {
|
|
39
|
-
const detectionOptions = i18nInstance.options?.detection;
|
|
40
|
-
cacheUserLanguage(i18nInstance, pathLanguage, detectionOptions);
|
|
41
|
-
}
|
|
81
|
+
if (pathLanguage) synchronizeLanguage?.(pathLanguage);
|
|
42
82
|
}, [
|
|
43
|
-
contextLanguage,
|
|
44
|
-
i18nInstance,
|
|
45
83
|
pathLanguage,
|
|
46
|
-
|
|
84
|
+
synchronizeLanguage
|
|
47
85
|
]);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
61
|
-
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
62
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
63
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
64
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
65
|
-
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
66
|
-
await navigate(newUrl, {
|
|
67
|
-
replace: true
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
71
|
-
const currentPath = window.location.pathname;
|
|
72
|
-
const entryPath = getEntryPath();
|
|
73
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
74
|
-
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
75
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) return;
|
|
76
|
-
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
77
|
-
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
78
|
-
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
79
|
-
window.history.pushState(null, '', newUrl);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (updateLanguage) updateLanguage(newLang);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error('Failed to change language:', error);
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
}, [
|
|
86
|
+
const currentLanguage = contextLanguage;
|
|
87
|
+
const changeLanguage = useCallback((newLang)=>changeModernI18nLanguage(newLang, {
|
|
88
|
+
i18nInstance,
|
|
89
|
+
updateLanguage,
|
|
90
|
+
localePathRedirect,
|
|
91
|
+
ignoreRedirectRoutes,
|
|
92
|
+
localisedUrls,
|
|
93
|
+
languages,
|
|
94
|
+
hasRouter,
|
|
95
|
+
navigate,
|
|
96
|
+
location
|
|
97
|
+
}), [
|
|
88
98
|
i18nInstance,
|
|
89
99
|
updateLanguage,
|
|
90
100
|
localePathRedirect,
|
|
@@ -95,40 +105,14 @@ const useModernI18n = ()=>{
|
|
|
95
105
|
navigate,
|
|
96
106
|
location
|
|
97
107
|
]);
|
|
98
|
-
const t = useCallback((key, ...args)=>
|
|
99
|
-
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t is required');
|
|
100
|
-
return i18nInstance.t(key, ...args);
|
|
101
|
-
}, [
|
|
108
|
+
const t = useCallback((key, ...args)=>translateI18n(i18nInstance, key, ...args), [
|
|
102
109
|
currentLanguage,
|
|
103
110
|
i18nInstance
|
|
104
111
|
]);
|
|
105
|
-
const isLanguageSupported = useCallback((lang)=>languages
|
|
112
|
+
const isLanguageSupported = useCallback((lang)=>isI18nLanguageSupported(languages, lang), [
|
|
106
113
|
languages
|
|
107
114
|
]);
|
|
108
|
-
const isResourcesReady = useMemo(()=>
|
|
109
|
-
if (!i18nInstance?.isInitialized) return false;
|
|
110
|
-
const backend = i18nInstance?.services?.backend;
|
|
111
|
-
if (backend && 'function' == typeof backend.isLoading) {
|
|
112
|
-
const loadingResources = backend.getLoadingResources();
|
|
113
|
-
const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
|
|
114
|
-
if (isCurrentLanguageLoading) return false;
|
|
115
|
-
}
|
|
116
|
-
const store = i18nInstance.store;
|
|
117
|
-
if (!store?.data) return false;
|
|
118
|
-
const langData = store.data[currentLanguage];
|
|
119
|
-
if (!langData || 'object' != typeof langData) return false;
|
|
120
|
-
const options = i18nInstance.options;
|
|
121
|
-
const namespaces = options?.ns || options?.defaultNS || [
|
|
122
|
-
'translation'
|
|
123
|
-
];
|
|
124
|
-
const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
|
|
125
|
-
namespaces
|
|
126
|
-
];
|
|
127
|
-
return requiredNamespaces.every((ns)=>{
|
|
128
|
-
const nsData = langData[ns];
|
|
129
|
-
return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
|
|
130
|
-
});
|
|
131
|
-
}, [
|
|
115
|
+
const isResourcesReady = useMemo(()=>isI18nResourcesReady(i18nInstance, currentLanguage), [
|
|
132
116
|
currentLanguage,
|
|
133
117
|
i18nInstance
|
|
134
118
|
]);
|
|
@@ -143,4 +127,4 @@ const useModernI18n = ()=>{
|
|
|
143
127
|
isResourcesReady
|
|
144
128
|
};
|
|
145
129
|
};
|
|
146
|
-
export { ModernI18nProvider, useModernI18n };
|
|
130
|
+
export { FederatedI18nBoundary, ModernI18nProvider, useModernI18n };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isBrowser } from "@modern-js/runtime";
|
|
2
|
+
import { cacheUserLanguage } from "./i18n/detection/index.mjs";
|
|
3
|
+
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
4
|
+
function getPathLanguage(pathname, languages, localePathRedirect) {
|
|
5
|
+
if (!localePathRedirect || !pathname) return;
|
|
6
|
+
const detected = detectLanguageFromPath(pathname, languages || [], localePathRedirect);
|
|
7
|
+
return detected.detected ? detected.language : void 0;
|
|
8
|
+
}
|
|
9
|
+
function cacheI18nLanguage(i18nInstance, language) {
|
|
10
|
+
if (isBrowser()) {
|
|
11
|
+
const detectionOptions = i18nInstance.options?.detection;
|
|
12
|
+
cacheUserLanguage(i18nInstance, language, detectionOptions);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function changeI18nInstanceLanguage(i18nInstance, language) {
|
|
16
|
+
if (i18nInstance.setLang) await i18nInstance.setLang(language);
|
|
17
|
+
else await i18nInstance.changeLanguage?.(language);
|
|
18
|
+
}
|
|
19
|
+
async function changeModernI18nLanguage(newLang, options) {
|
|
20
|
+
const { i18nInstance, updateLanguage, localePathRedirect, ignoreRedirectRoutes, localisedUrls, languages, hasRouter, navigate, location } = options;
|
|
21
|
+
try {
|
|
22
|
+
if (!newLang || 'string' != typeof newLang) throw new Error('Language must be non-empty string');
|
|
23
|
+
await changeI18nInstanceLanguage(i18nInstance, newLang);
|
|
24
|
+
cacheI18nLanguage(i18nInstance, newLang);
|
|
25
|
+
if (localePathRedirect && isBrowser() && hasRouter && navigate && location) {
|
|
26
|
+
const currentPath = location.pathname;
|
|
27
|
+
const entryPath = getEntryPath();
|
|
28
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
29
|
+
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
30
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
31
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
32
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
33
|
+
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
34
|
+
await navigate(newUrl, {
|
|
35
|
+
replace: true
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
39
|
+
const currentPath = window.location.pathname;
|
|
40
|
+
const entryPath = getEntryPath();
|
|
41
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
42
|
+
const pathLanguage = detectLanguageFromPath(currentPath, languages || [], localePathRedirect);
|
|
43
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) return void updateLanguage?.(newLang);
|
|
44
|
+
if (!shouldIgnoreRedirect(relativePath, languages || [], ignoreRedirectRoutes)) {
|
|
45
|
+
const newPath = buildLocalizedUrl(relativePath, newLang, languages || [], localisedUrls);
|
|
46
|
+
const newUrl = entryPath + newPath + window.location.search + window.location.hash;
|
|
47
|
+
window.history.pushState(null, '', newUrl);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (updateLanguage) updateLanguage(newLang);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Failed change language:', error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function translateI18n(i18nInstance, key, ...args) {
|
|
57
|
+
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t required');
|
|
58
|
+
return i18nInstance.t(key, ...args);
|
|
59
|
+
}
|
|
60
|
+
function isI18nLanguageSupported(languages, lang) {
|
|
61
|
+
return languages?.includes(lang) || false;
|
|
62
|
+
}
|
|
63
|
+
function isI18nResourcesReady(i18nInstance, currentLanguage) {
|
|
64
|
+
if (!i18nInstance?.isInitialized) return false;
|
|
65
|
+
const backend = i18nInstance?.services?.backend;
|
|
66
|
+
if (backend && 'function' == typeof backend.isLoading) {
|
|
67
|
+
const loadingResources = backend.getLoadingResources();
|
|
68
|
+
const isCurrentLanguageLoading = loadingResources.some(({ language })=>language === currentLanguage);
|
|
69
|
+
if (isCurrentLanguageLoading) return false;
|
|
70
|
+
}
|
|
71
|
+
const store = i18nInstance.store;
|
|
72
|
+
if (!store?.data) return false;
|
|
73
|
+
const langData = store.data[currentLanguage];
|
|
74
|
+
if (!langData || 'object' != typeof langData) return false;
|
|
75
|
+
const options = i18nInstance.options;
|
|
76
|
+
const namespaces = options?.ns || options?.defaultNS || [
|
|
77
|
+
'translation'
|
|
78
|
+
];
|
|
79
|
+
const requiredNamespaces = Array.isArray(namespaces) ? namespaces : [
|
|
80
|
+
namespaces
|
|
81
|
+
];
|
|
82
|
+
return requiredNamespaces.every((ns)=>{
|
|
83
|
+
const nsData = langData[ns];
|
|
84
|
+
return nsData && 'object' == typeof nsData && Object.keys(nsData).length > 0;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export { cacheI18nLanguage, changeI18nInstanceLanguage, changeModernI18nLanguage, getPathLanguage, isI18nLanguageSupported, isI18nResourcesReady, translateI18n };
|
|
@@ -1,31 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { merge } from "@modern-js/runtime-utils/merge";
|
|
5
|
-
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
-
import { ModernI18nProvider, useModernI18n } from "./context.mjs";
|
|
7
|
-
import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
|
|
8
|
-
import { getI18nInstance } from "./i18n/index.mjs";
|
|
9
|
-
import { mergeBackendOptions } from "./i18n/backend/index.mjs";
|
|
10
|
-
import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
|
|
11
|
-
import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
|
|
12
|
-
import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
|
|
13
|
-
import { buildLocalizedUrl, getPathname, splitUrlTarget } from "./utils.mjs";
|
|
1
|
+
import { setupI18nBeforeRender } from "./pluginSetup.mjs";
|
|
2
|
+
import { createI18nRootWrapper } from "./providerComposition.mjs";
|
|
3
|
+
import { resolveReactI18nextIntegration } from "./reactI18next.mjs";
|
|
14
4
|
import "./types.mjs";
|
|
15
|
-
let i18nLifecycleHelpersPromise;
|
|
16
|
-
function loadI18nLifecycleHelpers() {
|
|
17
|
-
i18nLifecycleHelpersPromise ??= Promise.all([
|
|
18
|
-
import("./i18n/backend/middleware.mjs"),
|
|
19
|
-
import("./i18n/utils.mjs")
|
|
20
|
-
]).then(([backendMiddleware, utils])=>({
|
|
21
|
-
useI18nextBackend: backendMiddleware.useI18nextBackend,
|
|
22
|
-
changeI18nLanguage: utils.changeI18nLanguage,
|
|
23
|
-
ensureLanguageMatch: utils.ensureLanguageMatch,
|
|
24
|
-
initializeI18nInstance: utils.initializeI18nInstance,
|
|
25
|
-
setupClonedInstance: utils.setupClonedInstance
|
|
26
|
-
}));
|
|
27
|
-
return i18nLifecycleHelpersPromise;
|
|
28
|
-
}
|
|
29
5
|
const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
30
6
|
name: '@modern-js/plugin-i18n',
|
|
31
7
|
setup: (api)=>{
|
|
@@ -34,119 +10,40 @@ const createI18nPlugin = (loadReactI18nextIntegration)=>(options)=>({
|
|
|
34
10
|
const { enabled: backendEnabled = false } = backend || {};
|
|
35
11
|
let latestI18nInstance;
|
|
36
12
|
let I18nextProvider;
|
|
37
|
-
const resolveReactI18nextIntegration = async ()=>{
|
|
38
|
-
if (!reactI18next) return null;
|
|
39
|
-
return loadReactI18nextIntegration?.() ?? null;
|
|
40
|
-
};
|
|
41
13
|
api.onBeforeRender(async (context)=>{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
|
|
49
|
-
if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
|
|
50
|
-
const pathname = getPathname(context);
|
|
51
|
-
if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
|
|
52
|
-
const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
53
|
-
const mergedBackend = mergeBackendOptions(backend, userInitOptions);
|
|
54
|
-
const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
|
|
55
|
-
if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
|
|
56
|
-
const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
|
|
57
|
-
languages,
|
|
58
|
-
fallbackLanguage,
|
|
59
|
-
localePathRedirect,
|
|
14
|
+
latestI18nInstance = await setupI18nBeforeRender(context, {
|
|
15
|
+
api,
|
|
16
|
+
userI18nInstance,
|
|
17
|
+
initOptions,
|
|
18
|
+
backend,
|
|
19
|
+
backendEnabled,
|
|
60
20
|
i18nextDetector,
|
|
61
21
|
detection,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!isBrowser() && i18nInstance.cloneInstance) {
|
|
69
|
-
i18nInstance = i18nInstance.cloneInstance();
|
|
70
|
-
await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
71
|
-
}
|
|
72
|
-
if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
|
|
73
|
-
if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
|
|
74
|
-
context.i18nInstance = i18nInstance;
|
|
75
|
-
latestI18nInstance = i18nInstance;
|
|
76
|
-
context.changeLanguage = async (newLang)=>{
|
|
77
|
-
await changeI18nLanguage(i18nInstance, newLang, {
|
|
78
|
-
detectionOptions: mergedDetection
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
|
-
api.wrapRoot((App)=>(props)=>{
|
|
83
|
-
const runtimeContext = useContext(RuntimeContext);
|
|
84
|
-
const i18nInstance = runtimeContext.i18nInstance || latestI18nInstance;
|
|
85
|
-
const initialLang = useMemo(()=>i18nInstance?.language || (localeDetection?.fallbackLanguage ?? 'en'), [
|
|
86
|
-
i18nInstance?.language,
|
|
87
|
-
localeDetection?.fallbackLanguage
|
|
88
|
-
]);
|
|
89
|
-
const [lang, setLang] = useState(initialLang);
|
|
90
|
-
const [forceUpdate, setForceUpdate] = useState(0);
|
|
91
|
-
const prevLangRef = useRef(lang);
|
|
92
|
-
const runtimeContextRef = useRef(runtimeContext);
|
|
93
|
-
runtimeContextRef.current = runtimeContext;
|
|
94
|
-
useEffect(()=>{
|
|
95
|
-
if (i18nInstance?.language) {
|
|
96
|
-
const translator = i18nInstance.translator;
|
|
97
|
-
if (translator) translator.language = i18nInstance.language;
|
|
98
|
-
}
|
|
99
|
-
}, [
|
|
100
|
-
i18nInstance?.language
|
|
101
|
-
]);
|
|
102
|
-
useEffect(()=>{
|
|
103
|
-
prevLangRef.current = lang;
|
|
104
|
-
}, [
|
|
105
|
-
lang
|
|
106
|
-
]);
|
|
107
|
-
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
108
|
-
useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeContextRef, prevLangRef, setLang);
|
|
109
|
-
useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
|
|
110
|
-
const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang), [
|
|
111
|
-
lang,
|
|
112
|
-
i18nInstance,
|
|
113
|
-
entryName,
|
|
114
|
-
languages,
|
|
115
|
-
localePathRedirect,
|
|
116
|
-
ignoreRedirectRoutes,
|
|
117
|
-
localisedUrls,
|
|
118
|
-
forceUpdate
|
|
119
|
-
]);
|
|
120
|
-
const children = props.children;
|
|
121
|
-
const appContent = /*#__PURE__*/ jsxs(Fragment, {
|
|
122
|
-
children: [
|
|
123
|
-
Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
|
|
124
|
-
htmlAttributes: {
|
|
125
|
-
lang
|
|
126
|
-
}
|
|
127
|
-
}),
|
|
128
|
-
/*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
129
|
-
value: contextValue,
|
|
130
|
-
children: App ? /*#__PURE__*/ jsx(App, {
|
|
131
|
-
...props,
|
|
132
|
-
children: children
|
|
133
|
-
}) : children
|
|
134
|
-
})
|
|
135
|
-
]
|
|
136
|
-
});
|
|
137
|
-
if (!i18nInstance) return appContent;
|
|
138
|
-
if (I18nextProvider) {
|
|
139
|
-
const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance);
|
|
140
|
-
return /*#__PURE__*/ jsx(I18nextProvider, {
|
|
141
|
-
i18n: i18nextInstanceForProvider,
|
|
142
|
-
children: appContent
|
|
143
|
-
});
|
|
22
|
+
localePathRedirect,
|
|
23
|
+
languages,
|
|
24
|
+
fallbackLanguage,
|
|
25
|
+
resolveReactI18nextIntegration: ()=>resolveReactI18nextIntegration(reactI18next, loadReactI18nextIntegration),
|
|
26
|
+
setI18nextProvider: (provider)=>{
|
|
27
|
+
I18nextProvider = provider;
|
|
144
28
|
}
|
|
145
|
-
return appContent;
|
|
146
29
|
});
|
|
30
|
+
});
|
|
31
|
+
api.wrapRoot(createI18nRootWrapper({
|
|
32
|
+
entryName,
|
|
33
|
+
htmlLangAttr,
|
|
34
|
+
localePathRedirect,
|
|
35
|
+
languages,
|
|
36
|
+
fallbackLanguage,
|
|
37
|
+
ignoreRedirectRoutes,
|
|
38
|
+
localisedUrls,
|
|
39
|
+
getLatestI18nInstance: ()=>latestI18nInstance,
|
|
40
|
+
getI18nextProvider: ()=>I18nextProvider
|
|
41
|
+
}));
|
|
147
42
|
}
|
|
148
43
|
});
|
|
44
|
+
export { FederatedI18nBoundary, useModernI18n } from "./context.mjs";
|
|
149
45
|
export { I18nLink } from "./I18nLink.mjs";
|
|
150
46
|
export { Link } from "./Link.mjs";
|
|
151
47
|
export { canonicalPath, localizePath, useLocalizedLocation, useLocalizedPaths } from "./localizedPaths.mjs";
|
|
152
|
-
export { buildLocalizedUrl,
|
|
48
|
+
export { buildLocalizedUrl, splitUrlTarget } from "./utils.mjs";
|
|
49
|
+
export { createI18nPlugin };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isBrowser } from "@modern-js/runtime";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { cacheI18nLanguage, changeI18nInstanceLanguage } from "./contextHelpers.mjs";
|
|
3
4
|
import { I18N_SDK_RESOURCES_LOADED_EVENT, getI18nSdkBackendId } from "./i18n/backend/sdk-event.mjs";
|
|
4
|
-
import { cacheUserLanguage } from "./i18n/detection/index.mjs";
|
|
5
5
|
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
6
|
-
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath,
|
|
6
|
+
import { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, shouldIgnoreRedirect } from "./utils.mjs";
|
|
7
7
|
function createMinimalI18nInstance(language) {
|
|
8
8
|
const minimalInstance = {
|
|
9
9
|
language,
|
|
@@ -15,7 +15,7 @@ function createMinimalI18nInstance(language) {
|
|
|
15
15
|
};
|
|
16
16
|
return minimalInstance;
|
|
17
17
|
}
|
|
18
|
-
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang) {
|
|
18
|
+
function createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang, synchronizeLanguage) {
|
|
19
19
|
const instance = i18nInstance || createMinimalI18nInstance(lang);
|
|
20
20
|
return {
|
|
21
21
|
language: lang,
|
|
@@ -25,7 +25,8 @@ function createContextValue(lang, i18nInstance, entryName, languages, localePath
|
|
|
25
25
|
localePathRedirect,
|
|
26
26
|
ignoreRedirectRoutes,
|
|
27
27
|
localisedUrls,
|
|
28
|
-
updateLanguage: setLang
|
|
28
|
+
updateLanguage: setLang,
|
|
29
|
+
synchronizeLanguage
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
function useSdkResourcesLoader(i18nInstance, setForceUpdate) {
|
|
@@ -116,26 +117,56 @@ function useClientSideRedirect(i18nInstance, localePathRedirect, languages, fall
|
|
|
116
117
|
localisedUrls
|
|
117
118
|
]);
|
|
118
119
|
}
|
|
119
|
-
function useLanguageSync(i18nInstance, localePathRedirect, languages,
|
|
120
|
+
function useLanguageSync(i18nInstance, localePathRedirect, languages, pathname, prevLangRef, setLang) {
|
|
121
|
+
const latestRequestRef = useRef(0);
|
|
122
|
+
const syncQueueRef = useRef(Promise.resolve());
|
|
123
|
+
const isMountedRef = useRef(false);
|
|
124
|
+
const desiredLanguageRef = useRef(void 0);
|
|
120
125
|
useEffect(()=>{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
isMountedRef.current = true;
|
|
127
|
+
return ()=>{
|
|
128
|
+
isMountedRef.current = false;
|
|
129
|
+
latestRequestRef.current += 1;
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
useEffect(()=>{
|
|
133
|
+
desiredLanguageRef.current = void 0;
|
|
134
|
+
latestRequestRef.current += 1;
|
|
135
|
+
syncQueueRef.current = Promise.resolve();
|
|
136
|
+
}, [
|
|
137
|
+
i18nInstance
|
|
138
|
+
]);
|
|
139
|
+
const synchronizeLanguage = useCallback((currentLang)=>{
|
|
140
|
+
if (!i18nInstance || !currentLang || desiredLanguageRef.current === currentLang) return;
|
|
141
|
+
desiredLanguageRef.current = currentLang;
|
|
142
|
+
const requestId = ++latestRequestRef.current;
|
|
143
|
+
syncQueueRef.current = syncQueueRef.current.then(async ()=>{
|
|
144
|
+
if (requestId !== latestRequestRef.current || !isMountedRef.current) return;
|
|
145
|
+
try {
|
|
146
|
+
if (i18nInstance.language !== currentLang) await changeI18nInstanceLanguage(i18nInstance, currentLang);
|
|
147
|
+
if (requestId !== latestRequestRef.current || !isMountedRef.current) return;
|
|
148
|
+
prevLangRef.current = currentLang;
|
|
149
|
+
setLang(currentLang);
|
|
150
|
+
cacheI18nLanguage(i18nInstance, currentLang);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (requestId === latestRequestRef.current && isMountedRef.current) {
|
|
153
|
+
desiredLanguageRef.current = void 0;
|
|
154
|
+
console.error(`Failed to synchronize i18n language "${currentLang}".`, error);
|
|
136
155
|
}
|
|
137
156
|
}
|
|
157
|
+
});
|
|
158
|
+
}, [
|
|
159
|
+
i18nInstance,
|
|
160
|
+
prevLangRef,
|
|
161
|
+
setLang
|
|
162
|
+
]);
|
|
163
|
+
useEffect(()=>{
|
|
164
|
+
if (!i18nInstance) return;
|
|
165
|
+
if (localePathRedirect) {
|
|
166
|
+
const pathDetection = detectLanguageFromPath(pathname || '', languages, localePathRedirect);
|
|
167
|
+
if (pathDetection.detected && pathDetection.language) synchronizeLanguage(pathDetection.language);
|
|
138
168
|
} else {
|
|
169
|
+
latestRequestRef.current += 1;
|
|
139
170
|
const instanceLang = i18nInstance.language;
|
|
140
171
|
if (instanceLang && instanceLang !== prevLangRef.current) {
|
|
141
172
|
prevLangRef.current = instanceLang;
|
|
@@ -146,9 +177,11 @@ function useLanguageSync(i18nInstance, localePathRedirect, languages, runtimeCon
|
|
|
146
177
|
i18nInstance,
|
|
147
178
|
localePathRedirect,
|
|
148
179
|
languages,
|
|
149
|
-
|
|
180
|
+
pathname,
|
|
150
181
|
prevLangRef,
|
|
151
|
-
setLang
|
|
182
|
+
setLang,
|
|
183
|
+
synchronizeLanguage
|
|
152
184
|
]);
|
|
185
|
+
return synchronizeLanguage;
|
|
153
186
|
}
|
|
154
187
|
export { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader };
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import node_path from "node:path";
|
|
3
3
|
const CONVENTIONAL_LOCALES_DIRS = [
|
|
4
4
|
'./locales',
|
|
5
5
|
'./config/public/locales'
|
|
6
6
|
];
|
|
7
7
|
const isDirectory = (dirPath)=>{
|
|
8
8
|
try {
|
|
9
|
-
return
|
|
9
|
+
return node_fs.statSync(dirPath).isDirectory();
|
|
10
10
|
} catch {
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
const resolveDefaultLocalesDir = (cwd = process.cwd())=>{
|
|
15
|
-
for (const dir of CONVENTIONAL_LOCALES_DIRS)if (isDirectory(
|
|
15
|
+
for (const dir of CONVENTIONAL_LOCALES_DIRS)if (isDirectory(node_path.resolve(cwd, dir))) return dir;
|
|
16
16
|
return CONVENTIONAL_LOCALES_DIRS[0];
|
|
17
17
|
};
|
|
18
18
|
const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
|
|
@@ -38,7 +38,7 @@ function getResourceBasePath(resourcePath) {
|
|
|
38
38
|
}
|
|
39
39
|
function pathExists(resourcePath) {
|
|
40
40
|
try {
|
|
41
|
-
return
|
|
41
|
+
return node_fs.existsSync(getResourceBasePath(resourcePath));
|
|
42
42
|
} catch {
|
|
43
43
|
return false;
|
|
44
44
|
}
|