@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.9 → 3.5.0-ultramodern.91
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
package/src/runtime/core.tsx
CHANGED
|
@@ -1,64 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isBrowser,
|
|
3
|
-
RuntimeContext,
|
|
4
|
-
type RuntimePlugin,
|
|
5
|
-
} from '@modern-js/runtime';
|
|
6
|
-
import { Helmet } from '@modern-js/runtime/head';
|
|
7
|
-
import type { TInternalRuntimeContext } from '@modern-js/runtime/internal';
|
|
8
|
-
import { merge } from '@modern-js/runtime-utils/merge';
|
|
1
|
+
import type { RuntimePlugin } from '@modern-js/runtime';
|
|
9
2
|
import type React from 'react';
|
|
10
|
-
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
3
|
import type {
|
|
12
4
|
BaseBackendOptions,
|
|
13
5
|
BaseLocaleDetectionOptions,
|
|
14
6
|
} from '../shared/type';
|
|
15
|
-
import { ModernI18nProvider } from './context';
|
|
16
|
-
import {
|
|
17
|
-
createContextValue,
|
|
18
|
-
useClientSideRedirect,
|
|
19
|
-
useLanguageSync,
|
|
20
|
-
useSdkResourcesLoader,
|
|
21
|
-
} from './hooks';
|
|
22
7
|
import type { I18nInitOptions, I18nInstance } from './i18n';
|
|
23
|
-
import { getI18nInstance } from './i18n';
|
|
24
|
-
import { mergeBackendOptions } from './i18n/backend';
|
|
25
8
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from './
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
9
|
+
type RuntimeContextWithI18n,
|
|
10
|
+
setupI18nBeforeRender,
|
|
11
|
+
} from './pluginSetup';
|
|
12
|
+
import { createI18nRootWrapper } from './providerComposition';
|
|
13
|
+
import {
|
|
14
|
+
type LoadReactI18nextIntegration,
|
|
15
|
+
resolveReactI18nextIntegration,
|
|
16
|
+
} from './reactI18next';
|
|
33
17
|
import './types';
|
|
34
18
|
|
|
35
19
|
export type { I18nSdkLoader, I18nSdkLoadOptions } from '../shared/type';
|
|
36
20
|
export type { Resources } from './i18n/instance';
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ensureLanguageMatch: typeof import('./i18n/utils')['ensureLanguageMatch'];
|
|
42
|
-
initializeI18nInstance: typeof import('./i18n/utils')['initializeI18nInstance'];
|
|
43
|
-
setupClonedInstance: typeof import('./i18n/utils')['setupClonedInstance'];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
let i18nLifecycleHelpersPromise: Promise<I18nLifecycleHelpers> | undefined;
|
|
47
|
-
|
|
48
|
-
function loadI18nLifecycleHelpers(): Promise<I18nLifecycleHelpers> {
|
|
49
|
-
i18nLifecycleHelpersPromise ??= Promise.all([
|
|
50
|
-
import('./i18n/backend/middleware'),
|
|
51
|
-
import('./i18n/utils'),
|
|
52
|
-
]).then(([backendMiddleware, utils]) => ({
|
|
53
|
-
useI18nextBackend: backendMiddleware.useI18nextBackend,
|
|
54
|
-
changeI18nLanguage: utils.changeI18nLanguage,
|
|
55
|
-
ensureLanguageMatch: utils.ensureLanguageMatch,
|
|
56
|
-
initializeI18nInstance: utils.initializeI18nInstance,
|
|
57
|
-
setupClonedInstance: utils.setupClonedInstance,
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
return i18nLifecycleHelpersPromise;
|
|
61
|
-
}
|
|
21
|
+
export type {
|
|
22
|
+
LoadReactI18nextIntegration,
|
|
23
|
+
ReactI18nextIntegration,
|
|
24
|
+
} from './reactI18next';
|
|
62
25
|
|
|
63
26
|
export interface I18nPluginOptions {
|
|
64
27
|
entryName?: string;
|
|
@@ -72,19 +35,6 @@ export interface I18nPluginOptions {
|
|
|
72
35
|
[key: string]: any;
|
|
73
36
|
}
|
|
74
37
|
|
|
75
|
-
interface RuntimeContextWithI18n extends TInternalRuntimeContext {
|
|
76
|
-
i18nInstance?: I18nInstance;
|
|
77
|
-
changeLanguage?: (lang: string) => Promise<void>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface ReactI18nextIntegration {
|
|
81
|
-
I18nextProvider: React.ComponentType<any> | null;
|
|
82
|
-
initReactI18next: any | null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export type LoadReactI18nextIntegration =
|
|
86
|
-
() => Promise<ReactI18nextIntegration | null>;
|
|
87
|
-
|
|
88
38
|
export const createI18nPlugin =
|
|
89
39
|
(
|
|
90
40
|
loadReactI18nextIntegration?: LoadReactI18nextIntegration,
|
|
@@ -114,223 +64,42 @@ export const createI18nPlugin =
|
|
|
114
64
|
let latestI18nInstance: I18nInstance | undefined;
|
|
115
65
|
let I18nextProvider: React.ComponentType<any> | null;
|
|
116
66
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
api.onBeforeRender(async context => {
|
|
125
|
-
const {
|
|
126
|
-
useI18nextBackend,
|
|
127
|
-
changeI18nLanguage,
|
|
128
|
-
ensureLanguageMatch,
|
|
129
|
-
initializeI18nInstance,
|
|
130
|
-
setupClonedInstance,
|
|
131
|
-
} = await loadI18nLifecycleHelpers();
|
|
132
|
-
let i18nInstance = await getI18nInstance(userI18nInstance);
|
|
133
|
-
const { i18n: otherConfig } = api.getRuntimeConfig();
|
|
134
|
-
const { initOptions: otherInitOptions } = otherConfig || {};
|
|
135
|
-
const userInitOptions = merge(
|
|
136
|
-
otherInitOptions || {},
|
|
137
|
-
initOptions || {},
|
|
138
|
-
);
|
|
139
|
-
const reactI18nextIntegration = await resolveReactI18nextIntegration();
|
|
140
|
-
I18nextProvider = reactI18nextIntegration?.I18nextProvider ?? null;
|
|
141
|
-
if (reactI18nextIntegration?.initReactI18next) {
|
|
142
|
-
i18nInstance.use(reactI18nextIntegration.initReactI18next);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const pathname = getPathname(context);
|
|
146
|
-
|
|
147
|
-
if (i18nextDetector) {
|
|
148
|
-
useI18nextLanguageDetector(i18nInstance);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const mergedDetection = mergeDetectionOptions(
|
|
67
|
+
api.onBeforeRender(async (context: RuntimeContextWithI18n) => {
|
|
68
|
+
latestI18nInstance = await setupI18nBeforeRender(context, {
|
|
69
|
+
api,
|
|
70
|
+
userI18nInstance,
|
|
71
|
+
initOptions,
|
|
72
|
+
backend,
|
|
73
|
+
backendEnabled,
|
|
152
74
|
i18nextDetector,
|
|
153
75
|
detection,
|
|
154
76
|
localePathRedirect,
|
|
155
|
-
userInitOptions,
|
|
156
|
-
);
|
|
157
|
-
const mergedBackend = mergeBackendOptions(backend, userInitOptions);
|
|
158
|
-
|
|
159
|
-
// Register Backend BEFORE detectLanguageWithPriority
|
|
160
|
-
// This is critical because detectLanguageWithPriority may trigger init()
|
|
161
|
-
// through i18next detector, and backend must be registered before init()
|
|
162
|
-
// Register backend if:
|
|
163
|
-
// 1. enabled is true (explicitly or auto-detected), OR
|
|
164
|
-
// 2. SDK is configured (allows standalone SDK usage even without locales directory)
|
|
165
|
-
const hasSdkConfig =
|
|
166
|
-
typeof userInitOptions?.backend?.sdk === 'function' ||
|
|
167
|
-
(mergedBackend?.sdk && typeof mergedBackend.sdk === 'function');
|
|
168
|
-
if (mergedBackend && (backendEnabled || hasSdkConfig)) {
|
|
169
|
-
useI18nextBackend(i18nInstance, mergedBackend);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const { finalLanguage } = await detectLanguageWithPriority(
|
|
173
|
-
i18nInstance,
|
|
174
|
-
{
|
|
175
|
-
languages,
|
|
176
|
-
fallbackLanguage,
|
|
177
|
-
localePathRedirect,
|
|
178
|
-
i18nextDetector,
|
|
179
|
-
detection,
|
|
180
|
-
userInitOptions,
|
|
181
|
-
mergedBackend,
|
|
182
|
-
pathname,
|
|
183
|
-
ssrContext: context.ssrContext,
|
|
184
|
-
},
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
await initializeI18nInstance(
|
|
188
|
-
i18nInstance,
|
|
189
|
-
finalLanguage,
|
|
190
|
-
fallbackLanguage,
|
|
191
77
|
languages,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
fallbackLanguage,
|
|
203
|
-
languages,
|
|
204
|
-
backendEnabled,
|
|
205
|
-
backend,
|
|
206
|
-
i18nextDetector,
|
|
207
|
-
detection,
|
|
208
|
-
localePathRedirect,
|
|
209
|
-
userInitOptions,
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (localePathRedirect) {
|
|
214
|
-
await ensureLanguageMatch(i18nInstance, finalLanguage);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (!isBrowser()) {
|
|
218
|
-
exportServerLngToWindow(context, finalLanguage);
|
|
219
|
-
}
|
|
220
|
-
context.i18nInstance = i18nInstance;
|
|
221
|
-
latestI18nInstance = i18nInstance;
|
|
222
|
-
|
|
223
|
-
// Add changeLanguage method to context for other runtime plugins to use
|
|
224
|
-
context.changeLanguage = async (newLang: string) => {
|
|
225
|
-
await changeI18nLanguage(i18nInstance, newLang, {
|
|
226
|
-
detectionOptions: mergedDetection,
|
|
227
|
-
});
|
|
228
|
-
};
|
|
78
|
+
fallbackLanguage,
|
|
79
|
+
resolveReactI18nextIntegration: () =>
|
|
80
|
+
resolveReactI18nextIntegration(
|
|
81
|
+
reactI18next,
|
|
82
|
+
loadReactI18nextIntegration,
|
|
83
|
+
),
|
|
84
|
+
setI18nextProvider: provider => {
|
|
85
|
+
I18nextProvider = provider;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
229
88
|
});
|
|
230
89
|
|
|
231
|
-
api.wrapRoot(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const [lang, setLang] = useState(initialLang);
|
|
245
|
-
const [forceUpdate, setForceUpdate] = useState(0);
|
|
246
|
-
const prevLangRef = useRef(lang);
|
|
247
|
-
const runtimeContextRef = useRef(runtimeContext);
|
|
248
|
-
runtimeContextRef.current = runtimeContext;
|
|
249
|
-
|
|
250
|
-
useEffect(() => {
|
|
251
|
-
if (i18nInstance?.language) {
|
|
252
|
-
const translator = (i18nInstance as any).translator;
|
|
253
|
-
if (translator) {
|
|
254
|
-
translator.language = i18nInstance.language;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}, [i18nInstance?.language]);
|
|
258
|
-
|
|
259
|
-
useEffect(() => {
|
|
260
|
-
prevLangRef.current = lang;
|
|
261
|
-
}, [lang]);
|
|
262
|
-
|
|
263
|
-
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
264
|
-
useLanguageSync(
|
|
265
|
-
i18nInstance,
|
|
266
|
-
localePathRedirect,
|
|
267
|
-
languages,
|
|
268
|
-
runtimeContextRef,
|
|
269
|
-
prevLangRef,
|
|
270
|
-
setLang,
|
|
271
|
-
);
|
|
272
|
-
// Handle client-side redirect for static deployments
|
|
273
|
-
// Note: This hook only executes in browser environment and skips SSR scenarios
|
|
274
|
-
useClientSideRedirect(
|
|
275
|
-
i18nInstance,
|
|
276
|
-
localePathRedirect,
|
|
277
|
-
languages,
|
|
278
|
-
fallbackLanguage,
|
|
279
|
-
ignoreRedirectRoutes,
|
|
280
|
-
localisedUrls,
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
const contextValue = useMemo(
|
|
284
|
-
() =>
|
|
285
|
-
createContextValue(
|
|
286
|
-
lang,
|
|
287
|
-
i18nInstance,
|
|
288
|
-
entryName,
|
|
289
|
-
languages,
|
|
290
|
-
localePathRedirect,
|
|
291
|
-
ignoreRedirectRoutes,
|
|
292
|
-
localisedUrls,
|
|
293
|
-
setLang,
|
|
294
|
-
),
|
|
295
|
-
[
|
|
296
|
-
lang,
|
|
297
|
-
i18nInstance,
|
|
298
|
-
entryName,
|
|
299
|
-
languages,
|
|
300
|
-
localePathRedirect,
|
|
301
|
-
ignoreRedirectRoutes,
|
|
302
|
-
localisedUrls,
|
|
303
|
-
forceUpdate,
|
|
304
|
-
],
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
const children = (props as React.PropsWithChildren).children;
|
|
308
|
-
const appContent = (
|
|
309
|
-
<>
|
|
310
|
-
{Boolean(htmlLangAttr) && <Helmet htmlAttributes={{ lang }} />}
|
|
311
|
-
<ModernI18nProvider value={contextValue}>
|
|
312
|
-
{App ? <App {...props}>{children}</App> : children}
|
|
313
|
-
</ModernI18nProvider>
|
|
314
|
-
</>
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
if (!i18nInstance) {
|
|
318
|
-
return appContent;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (I18nextProvider) {
|
|
322
|
-
const i18nextInstanceForProvider =
|
|
323
|
-
getI18nextInstanceForProvider(i18nInstance);
|
|
324
|
-
return (
|
|
325
|
-
<I18nextProvider i18n={i18nextInstanceForProvider}>
|
|
326
|
-
{appContent}
|
|
327
|
-
</I18nextProvider>
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return appContent;
|
|
332
|
-
};
|
|
333
|
-
});
|
|
90
|
+
api.wrapRoot(
|
|
91
|
+
createI18nRootWrapper({
|
|
92
|
+
entryName,
|
|
93
|
+
htmlLangAttr,
|
|
94
|
+
localePathRedirect,
|
|
95
|
+
languages,
|
|
96
|
+
fallbackLanguage,
|
|
97
|
+
ignoreRedirectRoutes,
|
|
98
|
+
localisedUrls,
|
|
99
|
+
getLatestI18nInstance: () => latestI18nInstance,
|
|
100
|
+
getI18nextProvider: () => I18nextProvider,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
334
103
|
},
|
|
335
104
|
});
|
|
336
105
|
|
|
@@ -339,7 +108,11 @@ export type {
|
|
|
339
108
|
CanonicalRoutePath,
|
|
340
109
|
UltramodernCanonicalRoutes,
|
|
341
110
|
} from './canonicalRoutes';
|
|
342
|
-
export {
|
|
111
|
+
export {
|
|
112
|
+
FederatedI18nBoundary,
|
|
113
|
+
type FederatedI18nBoundaryProps,
|
|
114
|
+
useModernI18n,
|
|
115
|
+
} from './context';
|
|
343
116
|
export { I18nLink, type I18nLinkProps } from './I18nLink';
|
|
344
117
|
export {
|
|
345
118
|
Link,
|
package/src/runtime/hooks.ts
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import type { TRuntimeContext } from '@modern-js/runtime';
|
|
2
1
|
import { isBrowser } from '@modern-js/runtime';
|
|
3
2
|
import type React from 'react';
|
|
4
|
-
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
5
4
|
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
5
|
+
import {
|
|
6
|
+
cacheI18nLanguage,
|
|
7
|
+
changeI18nInstanceLanguage,
|
|
8
|
+
} from './contextHelpers';
|
|
6
9
|
import type { I18nInstance } from './i18n';
|
|
7
10
|
import {
|
|
8
11
|
getI18nSdkBackendId,
|
|
9
12
|
I18N_SDK_RESOURCES_LOADED_EVENT,
|
|
10
13
|
type I18nSdkResourcesLoadedEventDetail,
|
|
11
14
|
} from './i18n/backend/sdk-event';
|
|
12
|
-
import { cacheUserLanguage } from './i18n/detection';
|
|
13
15
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
14
16
|
import {
|
|
15
17
|
buildLocalizedUrl,
|
|
16
18
|
detectLanguageFromPath,
|
|
17
19
|
getEntryPath,
|
|
18
|
-
getPathname,
|
|
19
20
|
shouldIgnoreRedirect,
|
|
20
21
|
} from './utils';
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
23
|
+
type WindowWithSSRData = Window & {
|
|
24
|
+
_SSR_DATA?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type ResourceStoreWithEvents = NonNullable<I18nInstance['store']> & {
|
|
28
|
+
emit?: (event: string, ...args: unknown[]) => void;
|
|
29
|
+
};
|
|
25
30
|
|
|
26
31
|
function createMinimalI18nInstance(language: string): I18nInstance {
|
|
27
32
|
const minimalInstance: I18nInstance = {
|
|
@@ -44,6 +49,7 @@ export function createContextValue(
|
|
|
44
49
|
ignoreRedirectRoutes: string[] | ((pathname: string) => boolean) | undefined,
|
|
45
50
|
localisedUrls: LocalisedUrlsOption | undefined,
|
|
46
51
|
setLang: (lang: string) => void,
|
|
52
|
+
synchronizeLanguage: (lang: string) => void,
|
|
47
53
|
) {
|
|
48
54
|
const instance = i18nInstance || createMinimalI18nInstance(lang);
|
|
49
55
|
return {
|
|
@@ -55,6 +61,7 @@ export function createContextValue(
|
|
|
55
61
|
ignoreRedirectRoutes,
|
|
56
62
|
localisedUrls,
|
|
57
63
|
updateLanguage: setLang,
|
|
64
|
+
synchronizeLanguage,
|
|
58
65
|
};
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -94,7 +101,7 @@ export function useSdkResourcesLoader(
|
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
const triggerUpdate = (retryCount = 0) => {
|
|
97
|
-
const store =
|
|
104
|
+
const store = i18nInstance.store as ResourceStoreWithEvents | undefined;
|
|
98
105
|
const hasResource = store?.data?.[language]?.[namespace];
|
|
99
106
|
|
|
100
107
|
if (hasResource || retryCount >= 10) {
|
|
@@ -179,7 +186,7 @@ export function useClientSideRedirect(
|
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
try {
|
|
182
|
-
const ssrData = (window as
|
|
189
|
+
const ssrData = (window as WindowWithSSRData)._SSR_DATA;
|
|
183
190
|
if (ssrData) {
|
|
184
191
|
return;
|
|
185
192
|
}
|
|
@@ -260,36 +267,88 @@ export function useLanguageSync(
|
|
|
260
267
|
i18nInstance: I18nInstance | undefined,
|
|
261
268
|
localePathRedirect: boolean,
|
|
262
269
|
languages: string[],
|
|
263
|
-
|
|
270
|
+
pathname: string | undefined,
|
|
264
271
|
prevLangRef: React.MutableRefObject<string>,
|
|
265
272
|
setLang: (lang: string) => void,
|
|
266
273
|
) {
|
|
274
|
+
const latestRequestRef = useRef(0);
|
|
275
|
+
const syncQueueRef = useRef(Promise.resolve());
|
|
276
|
+
const isMountedRef = useRef(false);
|
|
277
|
+
const desiredLanguageRef = useRef<string | undefined>(undefined);
|
|
278
|
+
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
isMountedRef.current = true;
|
|
281
|
+
return () => {
|
|
282
|
+
isMountedRef.current = false;
|
|
283
|
+
latestRequestRef.current += 1;
|
|
284
|
+
};
|
|
285
|
+
}, []);
|
|
286
|
+
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
desiredLanguageRef.current = undefined;
|
|
289
|
+
latestRequestRef.current += 1;
|
|
290
|
+
syncQueueRef.current = Promise.resolve();
|
|
291
|
+
}, [i18nInstance]);
|
|
292
|
+
|
|
293
|
+
const synchronizeLanguage = useCallback(
|
|
294
|
+
(currentLang: string) => {
|
|
295
|
+
if (
|
|
296
|
+
!i18nInstance ||
|
|
297
|
+
!currentLang ||
|
|
298
|
+
desiredLanguageRef.current === currentLang
|
|
299
|
+
) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
desiredLanguageRef.current = currentLang;
|
|
304
|
+
const requestId = ++latestRequestRef.current;
|
|
305
|
+
syncQueueRef.current = syncQueueRef.current.then(async () => {
|
|
306
|
+
if (requestId !== latestRequestRef.current || !isMountedRef.current) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
if (i18nInstance.language !== currentLang) {
|
|
312
|
+
await changeI18nInstanceLanguage(i18nInstance, currentLang);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (requestId !== latestRequestRef.current || !isMountedRef.current) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
prevLangRef.current = currentLang;
|
|
320
|
+
setLang(currentLang);
|
|
321
|
+
cacheI18nLanguage(i18nInstance, currentLang);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (requestId === latestRequestRef.current && isMountedRef.current) {
|
|
324
|
+
desiredLanguageRef.current = undefined;
|
|
325
|
+
console.error(
|
|
326
|
+
`Failed to synchronize i18n language "${currentLang}".`,
|
|
327
|
+
error,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
[i18nInstance, prevLangRef, setLang],
|
|
334
|
+
);
|
|
335
|
+
|
|
267
336
|
useEffect(() => {
|
|
268
337
|
if (!i18nInstance) {
|
|
269
338
|
return;
|
|
270
339
|
}
|
|
271
340
|
|
|
272
341
|
if (localePathRedirect) {
|
|
273
|
-
const currentPathname = getPathname(runtimeContextRef.current);
|
|
274
342
|
const pathDetection = detectLanguageFromPath(
|
|
275
|
-
|
|
343
|
+
pathname || '',
|
|
276
344
|
languages,
|
|
277
345
|
localePathRedirect,
|
|
278
346
|
);
|
|
279
347
|
if (pathDetection.detected && pathDetection.language) {
|
|
280
|
-
|
|
281
|
-
if (currentLang !== prevLangRef.current) {
|
|
282
|
-
prevLangRef.current = currentLang;
|
|
283
|
-
setLang(currentLang);
|
|
284
|
-
i18nInstance.setLang?.(currentLang);
|
|
285
|
-
i18nInstance.changeLanguage?.(currentLang);
|
|
286
|
-
if (isBrowser()) {
|
|
287
|
-
const detectionOptions = i18nInstance.options?.detection;
|
|
288
|
-
cacheUserLanguage(i18nInstance, currentLang, detectionOptions);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
348
|
+
synchronizeLanguage(pathDetection.language);
|
|
291
349
|
}
|
|
292
350
|
} else {
|
|
351
|
+
latestRequestRef.current += 1;
|
|
293
352
|
const instanceLang = i18nInstance.language;
|
|
294
353
|
if (instanceLang && instanceLang !== prevLangRef.current) {
|
|
295
354
|
prevLangRef.current = instanceLang;
|
|
@@ -300,8 +359,11 @@ export function useLanguageSync(
|
|
|
300
359
|
i18nInstance,
|
|
301
360
|
localePathRedirect,
|
|
302
361
|
languages,
|
|
303
|
-
|
|
362
|
+
pathname,
|
|
304
363
|
prevLangRef,
|
|
305
364
|
setLang,
|
|
365
|
+
synchronizeLanguage,
|
|
306
366
|
]);
|
|
367
|
+
|
|
368
|
+
return synchronizeLanguage;
|
|
307
369
|
}
|
|
@@ -9,6 +9,7 @@ import { SdkBackend } from './sdk-backend';
|
|
|
9
9
|
|
|
10
10
|
type BackendConfigWithChained = BaseBackendOptions &
|
|
11
11
|
Partial<ChainedBackendConfig>;
|
|
12
|
+
type BackendConstructor = new (...args: never[]) => unknown;
|
|
12
13
|
|
|
13
14
|
function checkBackendConfig(backend?: BackendConfigWithChained) {
|
|
14
15
|
const hasSdk = backend?.sdk && typeof backend.sdk === 'function';
|
|
@@ -20,7 +21,7 @@ function checkBackendConfig(backend?: BackendConfigWithChained) {
|
|
|
20
21
|
|
|
21
22
|
function buildChainedBackendConfig(
|
|
22
23
|
backend: BackendConfigWithChained,
|
|
23
|
-
BackendWithSave:
|
|
24
|
+
BackendWithSave: BackendConstructor,
|
|
24
25
|
) {
|
|
25
26
|
const cacheHitMode = backend.cacheHitMode || 'refreshAndUpdateStore';
|
|
26
27
|
|
|
@@ -51,7 +52,7 @@ function buildChainedBackendConfig(
|
|
|
51
52
|
function setupChainedBackend(
|
|
52
53
|
i18nInstance: I18nInstance,
|
|
53
54
|
backend: BackendConfigWithChained,
|
|
54
|
-
BackendWithSave:
|
|
55
|
+
BackendWithSave: BackendConstructor,
|
|
55
56
|
) {
|
|
56
57
|
i18nInstance.use(ChainedBackend);
|
|
57
58
|
const actualInstance = getActualI18nextInstance(i18nInstance);
|
|
@@ -86,8 +87,8 @@ function cleanBackendConfig(backend: BackendConfigWithChained) {
|
|
|
86
87
|
*/
|
|
87
88
|
export function useI18nextBackendCommon(
|
|
88
89
|
i18nInstance: I18nInstance,
|
|
89
|
-
BackendWithSave:
|
|
90
|
-
BackendBase:
|
|
90
|
+
BackendWithSave: BackendConstructor,
|
|
91
|
+
BackendBase: BackendConstructor,
|
|
91
92
|
backend?: BackendConfigWithChained,
|
|
92
93
|
) {
|
|
93
94
|
if (!backend) {
|