@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.4 → 3.5.0-ultramodern.40
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 +18 -82
- package/dist/cjs/runtime/contextHelpers.js +137 -0
- package/dist/cjs/runtime/core.js +29 -132
- 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 +7 -7
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +114 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- 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 +95 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +88 -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 +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 +18 -82
- package/dist/esm/runtime/contextHelpers.mjs +84 -0
- package/dist/esm/runtime/core.mjs +30 -133
- 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 +8 -5
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +76 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- 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 +48 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +47 -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/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 +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/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 +8 -5
- 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 +77 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- 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 +49 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -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/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 +6 -5
- package/dist/types/runtime/contextHelpers.d.ts +28 -0
- package/dist/types/runtime/core.d.ts +3 -7
- package/dist/types/runtime/hooks.d.ts +5 -0
- 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 +28 -26
- 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 +16 -18
- package/rstest.config.mts +1 -0
- 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 +45 -199
- package/src/runtime/contextHelpers.ts +232 -0
- package/src/runtime/core.tsx +43 -274
- package/src/runtime/hooks.ts +13 -2
- 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 +56 -31
- 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 +144 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/runtime/routerAdapter.tsx +6 -6
- 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 +91 -0
- package/src/shared/localisedUrls/resolve.ts +135 -0
- package/src/shared/localisedUrls/routes.ts +226 -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/fixtures/localised-routes.golden.json +156 -0
- package/tests/link.test.tsx +111 -0
- package/tests/linkTypes.test.ts +15 -4
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
- package/tests/localisedUrls.test.ts +129 -2
- package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
- package/tests/redirectPolicy.test.ts +43 -0
- package/tests/routerAdapter.test.tsx +85 -0
- package/tests/type-fixture/fixture-globals.d.ts +11 -0
- package/tests/type-fixture/tsconfig.json +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize language code (e.g., 'zh-CN' -> 'zh', 'en-US' -> 'en')
|
|
3
|
+
*/
|
|
4
|
+
const normalizeLanguageCode = (language: string): string => {
|
|
5
|
+
if (!language) {
|
|
6
|
+
return language;
|
|
7
|
+
}
|
|
8
|
+
// Extract base language code (before hyphen)
|
|
9
|
+
const baseLang = language.split('-')[0];
|
|
10
|
+
return baseLang;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a language is supported
|
|
15
|
+
* Also checks the base language code (e.g., 'zh-CN' matches 'zh')
|
|
16
|
+
*/
|
|
17
|
+
export const isLanguageSupported = (
|
|
18
|
+
language: string | undefined,
|
|
19
|
+
supportedLanguages: string[],
|
|
20
|
+
): boolean => {
|
|
21
|
+
if (!language) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (supportedLanguages.length === 0) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Check exact match first
|
|
28
|
+
if (supportedLanguages.includes(language)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Check base language code match (e.g., 'zh-CN' matches 'zh')
|
|
32
|
+
const baseLang = normalizeLanguageCode(language);
|
|
33
|
+
if (baseLang !== language && supportedLanguages.includes(baseLang)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the supported language that matches the given language
|
|
41
|
+
* Returns the exact match if available, otherwise returns the base language code match
|
|
42
|
+
* Returns undefined if no match is found
|
|
43
|
+
*/
|
|
44
|
+
export const getSupportedLanguage = (
|
|
45
|
+
language: string | undefined,
|
|
46
|
+
supportedLanguages: string[],
|
|
47
|
+
): string | undefined => {
|
|
48
|
+
if (!language) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
if (supportedLanguages.length === 0) {
|
|
52
|
+
return language;
|
|
53
|
+
}
|
|
54
|
+
// Check exact match first
|
|
55
|
+
if (supportedLanguages.includes(language)) {
|
|
56
|
+
return language;
|
|
57
|
+
}
|
|
58
|
+
// Check base language code match (e.g., 'zh-CN' matches 'zh')
|
|
59
|
+
const baseLang = normalizeLanguageCode(language);
|
|
60
|
+
if (baseLang !== language && supportedLanguages.includes(baseLang)) {
|
|
61
|
+
return baseLang;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { LanguageDetector } from 'i18next-http-middleware';
|
|
2
|
-
import type { I18nInstance } from '../instance';
|
|
2
|
+
import type { I18nInstance, LanguageDetectorOptions } from '../instance';
|
|
3
|
+
|
|
4
|
+
type HttpDetectorInit = (
|
|
5
|
+
services: NonNullable<I18nInstance['services']>,
|
|
6
|
+
options?: unknown,
|
|
7
|
+
allOptions?: unknown,
|
|
8
|
+
) => void;
|
|
9
|
+
|
|
10
|
+
type HttpDetectorDetect = (
|
|
11
|
+
request: unknown,
|
|
12
|
+
response: unknown,
|
|
13
|
+
detectionOrder?: unknown,
|
|
14
|
+
) => string | string[] | undefined;
|
|
3
15
|
|
|
4
16
|
export const cacheUserLanguage = (
|
|
5
17
|
_i18nInstance: I18nInstance,
|
|
6
18
|
_language: string,
|
|
7
|
-
_detectionOptions?:
|
|
19
|
+
_detectionOptions?: unknown,
|
|
8
20
|
): void => {
|
|
9
21
|
return;
|
|
10
22
|
};
|
|
@@ -14,7 +26,7 @@ export const cacheUserLanguage = (
|
|
|
14
26
|
* Not available in Node.js environment, returns undefined
|
|
15
27
|
*/
|
|
16
28
|
export const readLanguageFromStorage = (
|
|
17
|
-
_detectionOptions?:
|
|
29
|
+
_detectionOptions?: LanguageDetectorOptions,
|
|
18
30
|
): string | undefined => {
|
|
19
31
|
// In Node.js environment, storage-based detection is not available
|
|
20
32
|
return undefined;
|
|
@@ -36,8 +48,8 @@ export const useI18nextLanguageDetector = (i18nInstance: I18nInstance) => {
|
|
|
36
48
|
*/
|
|
37
49
|
export const detectLanguage = (
|
|
38
50
|
i18nInstance: I18nInstance,
|
|
39
|
-
request?:
|
|
40
|
-
detectionOptions?:
|
|
51
|
+
request?: unknown,
|
|
52
|
+
detectionOptions?: LanguageDetectorOptions,
|
|
41
53
|
): string | undefined => {
|
|
42
54
|
if (!request) {
|
|
43
55
|
return undefined;
|
|
@@ -65,9 +77,16 @@ export const detectLanguage = (
|
|
|
65
77
|
const optionsToUse = detectionOptions
|
|
66
78
|
? { ...i18nInstance.options, detection: detectionOptions }
|
|
67
79
|
: i18nInstance.options;
|
|
68
|
-
manualDetector.init
|
|
80
|
+
(manualDetector.init as unknown as HttpDetectorInit)(
|
|
81
|
+
i18nInstance.services,
|
|
82
|
+
optionsToUse,
|
|
83
|
+
);
|
|
69
84
|
|
|
70
|
-
const result = (manualDetector.detect as
|
|
85
|
+
const result = (manualDetector.detect as unknown as HttpDetectorDetect)(
|
|
86
|
+
request,
|
|
87
|
+
{},
|
|
88
|
+
undefined,
|
|
89
|
+
);
|
|
71
90
|
if (typeof result === 'string') {
|
|
72
91
|
return result;
|
|
73
92
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { isBrowser } from '@modern-js/runtime';
|
|
2
2
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
3
|
-
import type { I18nInstance } from '../instance';
|
|
3
|
+
import type { I18nInstance, LanguageDetectorOptions } from '../instance';
|
|
4
4
|
import { getActualI18nextInstance, isI18nWrapperInstance } from '../instance';
|
|
5
5
|
|
|
6
|
+
type BrowserDetectorInitOptions = Parameters<LanguageDetector['init']>[1];
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Register LanguageDetector plugin to i18n instance
|
|
8
10
|
* Must be called before init() to properly register the detector
|
|
@@ -27,7 +29,7 @@ export const useI18nextLanguageDetector = (i18nInstance: I18nInstance) => {
|
|
|
27
29
|
* Fallback when detector is not available in services
|
|
28
30
|
*/
|
|
29
31
|
export const readLanguageFromStorage = (
|
|
30
|
-
detectionOptions?:
|
|
32
|
+
detectionOptions?: LanguageDetectorOptions,
|
|
31
33
|
): string | undefined => {
|
|
32
34
|
try {
|
|
33
35
|
const options = detectionOptions || {};
|
|
@@ -112,8 +114,8 @@ export const readLanguageFromStorage = (
|
|
|
112
114
|
*/
|
|
113
115
|
export const detectLanguage = (
|
|
114
116
|
i18nInstance: I18nInstance,
|
|
115
|
-
_request?:
|
|
116
|
-
detectionOptions?:
|
|
117
|
+
_request?: unknown,
|
|
118
|
+
detectionOptions?: LanguageDetectorOptions,
|
|
117
119
|
): string | undefined => {
|
|
118
120
|
try {
|
|
119
121
|
// For wrapper instances, get the underlying i18next instance
|
|
@@ -160,7 +162,10 @@ export const detectLanguage = (
|
|
|
160
162
|
const mergedOptions = detectionOptions
|
|
161
163
|
? { ...optionsToUse, detection: detectionOptions }
|
|
162
164
|
: optionsToUse;
|
|
163
|
-
manualDetector.init(
|
|
165
|
+
manualDetector.init(
|
|
166
|
+
servicesToUse,
|
|
167
|
+
mergedOptions as unknown as BrowserDetectorInitOptions,
|
|
168
|
+
);
|
|
164
169
|
|
|
165
170
|
const result = manualDetector.detect();
|
|
166
171
|
if (typeof result === 'string') {
|
|
@@ -186,7 +191,7 @@ export const detectLanguage = (
|
|
|
186
191
|
export const cacheUserLanguage = (
|
|
187
192
|
i18nInstance: I18nInstance,
|
|
188
193
|
language: string,
|
|
189
|
-
detectionOptions?:
|
|
194
|
+
detectionOptions?: unknown,
|
|
190
195
|
): void => {
|
|
191
196
|
if (typeof window === 'undefined') {
|
|
192
197
|
return;
|
|
@@ -231,7 +236,10 @@ export const cacheUserLanguage = (
|
|
|
231
236
|
: optionsToUse;
|
|
232
237
|
|
|
233
238
|
const manualDetector = new LanguageDetector();
|
|
234
|
-
manualDetector.init(
|
|
239
|
+
manualDetector.init(
|
|
240
|
+
servicesToUse,
|
|
241
|
+
mergedOptions as unknown as BrowserDetectorInitOptions,
|
|
242
|
+
);
|
|
235
243
|
|
|
236
244
|
if (typeof manualDetector.cacheUserLanguage === 'function') {
|
|
237
245
|
manualDetector.cacheUserLanguage(language);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { detectLanguageFromPath } from '../../utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Priority 2: Detect language from URL path
|
|
5
|
+
* Only returns a language if the path explicitly contains a language prefix
|
|
6
|
+
*/
|
|
7
|
+
export const detectLanguageFromPathPriority = (
|
|
8
|
+
pathname: string,
|
|
9
|
+
languages: string[],
|
|
10
|
+
localePathRedirect: boolean,
|
|
11
|
+
): string | undefined => {
|
|
12
|
+
if (!localePathRedirect) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// If no languages are configured, cannot detect from path
|
|
17
|
+
if (!languages || languages.length === 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// If pathname is empty or invalid, no language in path
|
|
22
|
+
if (!pathname || pathname.trim() === '') {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const pathDetection = detectLanguageFromPath(
|
|
28
|
+
pathname,
|
|
29
|
+
languages,
|
|
30
|
+
localePathRedirect,
|
|
31
|
+
);
|
|
32
|
+
// Only return language if explicitly detected in path
|
|
33
|
+
if (pathDetection.detected === true && pathDetection.language) {
|
|
34
|
+
return pathDetection.language;
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Silently ignore errors, return undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined;
|
|
41
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type I18nInstance, isI18nWrapperInstance } from '../instance';
|
|
2
|
+
|
|
3
|
+
import { detectLanguageFromI18nextDetector } from './detector';
|
|
4
|
+
|
|
5
|
+
import { mergeDetectionOptions } from './initOptions';
|
|
6
|
+
import { readLanguageFromStorage } from './middleware';
|
|
7
|
+
import { detectLanguageFromPathPriority } from './path';
|
|
8
|
+
import { detectLanguageFromSSR } from './ssr';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
LanguageDetectionOptions,
|
|
12
|
+
LanguageDetectionResult,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Detect language with priority:
|
|
17
|
+
* Priority 1: Path detection
|
|
18
|
+
* Priority 2: SSR data (try window._SSR_DATA first, works for both SSR and CSR)
|
|
19
|
+
* Priority 3: i18next detector (reads from cookie/localStorage)
|
|
20
|
+
* Priority 4: User config language or fallback
|
|
21
|
+
*/
|
|
22
|
+
export const detectLanguageWithPriority = async (
|
|
23
|
+
i18nInstance: I18nInstance,
|
|
24
|
+
options: LanguageDetectionOptions,
|
|
25
|
+
): Promise<LanguageDetectionResult> => {
|
|
26
|
+
const {
|
|
27
|
+
languages,
|
|
28
|
+
fallbackLanguage,
|
|
29
|
+
localePathRedirect,
|
|
30
|
+
i18nextDetector,
|
|
31
|
+
detection,
|
|
32
|
+
userInitOptions,
|
|
33
|
+
pathname,
|
|
34
|
+
ssrContext,
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
let detectedLanguage: string | undefined;
|
|
38
|
+
|
|
39
|
+
// Priority 1: Path detection. Explicit URL locale must win over stale
|
|
40
|
+
// hydration data after client-side navigation.
|
|
41
|
+
detectedLanguage = detectLanguageFromPathPriority(
|
|
42
|
+
pathname,
|
|
43
|
+
languages,
|
|
44
|
+
localePathRedirect,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Priority 2: Try SSR data when the current path has no explicit locale.
|
|
48
|
+
if (!detectedLanguage) {
|
|
49
|
+
detectedLanguage = detectLanguageFromSSR(languages);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Priority 3: i18next detector (reads from cookie/localStorage)
|
|
53
|
+
if (!detectedLanguage && i18nextDetector) {
|
|
54
|
+
if (isI18nWrapperInstance(i18nInstance)) {
|
|
55
|
+
detectedLanguage = readLanguageFromStorage(
|
|
56
|
+
mergeDetectionOptions(
|
|
57
|
+
i18nextDetector,
|
|
58
|
+
detection,
|
|
59
|
+
localePathRedirect,
|
|
60
|
+
userInitOptions,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
} else {
|
|
64
|
+
detectedLanguage = await detectLanguageFromI18nextDetector(i18nInstance, {
|
|
65
|
+
languages,
|
|
66
|
+
fallbackLanguage,
|
|
67
|
+
localePathRedirect,
|
|
68
|
+
i18nextDetector,
|
|
69
|
+
detection,
|
|
70
|
+
userInitOptions,
|
|
71
|
+
mergedBackend: options.mergedBackend,
|
|
72
|
+
ssrContext,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Priority 4: Use user config language or fallback
|
|
78
|
+
const finalLanguage =
|
|
79
|
+
detectedLanguage || userInitOptions?.lng || fallbackLanguage;
|
|
80
|
+
|
|
81
|
+
return { detectedLanguage, finalLanguage };
|
|
82
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isBrowser, type TRuntimeContext } from '@modern-js/runtime';
|
|
2
|
+
import { getSupportedLanguage } from './language';
|
|
3
|
+
|
|
4
|
+
export function exportServerLngToWindow(context: TRuntimeContext, lng: string) {
|
|
5
|
+
context.__i18nData__ = { lng };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const getLanguageFromSSRData = (window: Window): string | undefined => {
|
|
9
|
+
try {
|
|
10
|
+
const ssrData = (window as any)._SSR_DATA;
|
|
11
|
+
// Check if SSR data exists and has valid structure
|
|
12
|
+
if (!ssrData || !ssrData.data || !ssrData.data.i18nData) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const lng = ssrData.data.i18nData.lng;
|
|
16
|
+
// Return language only if it's a non-empty string
|
|
17
|
+
return typeof lng === 'string' && lng.trim() !== '' ? lng : undefined;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// If accessing window._SSR_DATA throws an error, return undefined
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Priority 1: Detect language from SSR data
|
|
26
|
+
* Try to get language from window._SSR_DATA first (both SSR and CSR projects)
|
|
27
|
+
* Returns undefined if SSR data is not available or invalid
|
|
28
|
+
*/
|
|
29
|
+
export const detectLanguageFromSSR = (
|
|
30
|
+
languages: string[],
|
|
31
|
+
): string | undefined => {
|
|
32
|
+
if (!isBrowser()) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const ssrLanguage = getLanguageFromSSRData(window);
|
|
38
|
+
const supportedLanguage = getSupportedLanguage(ssrLanguage, languages);
|
|
39
|
+
if (supportedLanguage) {
|
|
40
|
+
return supportedLanguage;
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Silently ignore errors
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BackendOptions,
|
|
3
|
+
I18nInitOptions,
|
|
4
|
+
LanguageDetectorOptions,
|
|
5
|
+
} from '../instance';
|
|
6
|
+
|
|
7
|
+
export interface LanguageDetectionSsrContext {
|
|
8
|
+
request?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BaseLanguageDetectionOptions {
|
|
12
|
+
languages: string[];
|
|
13
|
+
fallbackLanguage: string;
|
|
14
|
+
localePathRedirect: boolean;
|
|
15
|
+
i18nextDetector: boolean;
|
|
16
|
+
detection?: LanguageDetectorOptions;
|
|
17
|
+
userInitOptions?: I18nInitOptions;
|
|
18
|
+
mergedBackend?: BackendOptions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LanguageDetectionOptions extends BaseLanguageDetectionOptions {
|
|
22
|
+
pathname: string;
|
|
23
|
+
ssrContext?: LanguageDetectionSsrContext;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LanguageDetectionResult {
|
|
27
|
+
detectedLanguage?: string;
|
|
28
|
+
finalLanguage: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for building i18n init options
|
|
33
|
+
*/
|
|
34
|
+
export interface BuildInitOptionsParams {
|
|
35
|
+
finalLanguage: string;
|
|
36
|
+
fallbackLanguage: string;
|
|
37
|
+
languages: string[];
|
|
38
|
+
userInitOptions?: I18nInitOptions;
|
|
39
|
+
mergedDetection?: LanguageDetectorOptions;
|
|
40
|
+
mergeBackend?: BackendOptions;
|
|
41
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { BaseBackendOptions } from '../../shared/type';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface I18nResourceStore {
|
|
4
4
|
data?: {
|
|
5
5
|
[language: string]: {
|
|
6
|
-
[namespace: string]:
|
|
6
|
+
[namespace: string]: ResourceValue;
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
9
|
addResourceBundle?: (
|
|
@@ -15,30 +15,49 @@ export interface I18nResourceStore {
|
|
|
15
15
|
) => void;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type I18nWrapperInstance = I18nInstance & {
|
|
19
|
+
i18nInstance: {
|
|
20
|
+
instance: I18nInstance;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function isI18nWrapperInstance(
|
|
25
|
+
obj: unknown,
|
|
26
|
+
): obj is I18nWrapperInstance {
|
|
19
27
|
if (!obj || typeof obj !== 'object') {
|
|
20
28
|
return false;
|
|
21
29
|
}
|
|
22
|
-
|
|
30
|
+
const candidate = obj as {
|
|
31
|
+
i18nInstance?: unknown;
|
|
32
|
+
init?: unknown;
|
|
33
|
+
use?: unknown;
|
|
34
|
+
};
|
|
35
|
+
if (!candidate.i18nInstance || typeof candidate.i18nInstance !== 'object') {
|
|
23
36
|
return false;
|
|
24
37
|
}
|
|
25
|
-
|
|
38
|
+
const wrapper = candidate.i18nInstance as { instance?: unknown };
|
|
39
|
+
if (!wrapper.instance) {
|
|
26
40
|
return false;
|
|
27
41
|
}
|
|
28
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
typeof candidate.init !== 'function' ||
|
|
44
|
+
typeof candidate.use !== 'function'
|
|
45
|
+
) {
|
|
29
46
|
return false;
|
|
30
47
|
}
|
|
31
48
|
return true;
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
function getI18nWrapperI18nextInstance(
|
|
52
|
+
wrapperInstance: unknown,
|
|
53
|
+
): I18nInstance | null {
|
|
35
54
|
if (isI18nWrapperInstance(wrapperInstance)) {
|
|
36
55
|
return wrapperInstance.i18nInstance?.instance;
|
|
37
56
|
}
|
|
38
57
|
return null;
|
|
39
58
|
}
|
|
40
59
|
|
|
41
|
-
export function getActualI18nextInstance(instance: I18nInstance
|
|
60
|
+
export function getActualI18nextInstance(instance: I18nInstance): I18nInstance {
|
|
42
61
|
if (isI18nWrapperInstance(instance)) {
|
|
43
62
|
const i18nextInstance = getI18nWrapperI18nextInstance(instance);
|
|
44
63
|
return i18nextInstance || instance;
|
|
@@ -50,39 +69,42 @@ export interface I18nInstance {
|
|
|
50
69
|
language: string;
|
|
51
70
|
isInitialized?: boolean;
|
|
52
71
|
init: {
|
|
53
|
-
(callback?: (error:
|
|
72
|
+
(callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
|
|
54
73
|
(
|
|
55
74
|
options: I18nInitOptions,
|
|
56
|
-
callback?: (error:
|
|
57
|
-
): Promise<
|
|
75
|
+
callback?: (error: unknown, t: unknown) => void,
|
|
76
|
+
): Promise<unknown>;
|
|
58
77
|
};
|
|
59
78
|
changeLanguage?: (
|
|
60
79
|
lng?: string,
|
|
61
|
-
callback?: (error:
|
|
62
|
-
) => Promise<
|
|
80
|
+
callback?: (error: unknown, t: unknown) => void,
|
|
81
|
+
) => Promise<unknown>;
|
|
63
82
|
setLang?: (lang: string) => void | Promise<void>;
|
|
64
|
-
use: (plugin:
|
|
83
|
+
use: (plugin: unknown) => void;
|
|
65
84
|
createInstance?: (options?: I18nInitOptions) => I18nInstance;
|
|
66
85
|
cloneInstance?: () => I18nInstance; // ssr need
|
|
67
86
|
// i18next store (may not be in type definition but exists at runtime)
|
|
68
87
|
store?: I18nResourceStore;
|
|
69
|
-
emit?: (event: string, ...args:
|
|
88
|
+
emit?: (event: string, ...args: unknown[]) => void;
|
|
70
89
|
reloadResources?: (language?: string, namespace?: string) => Promise<void>;
|
|
71
90
|
services?: {
|
|
72
91
|
languageDetector?: {
|
|
73
|
-
detect: (
|
|
74
|
-
|
|
92
|
+
detect: (
|
|
93
|
+
request?: unknown,
|
|
94
|
+
options?: unknown,
|
|
95
|
+
) => string | string[] | undefined;
|
|
96
|
+
[key: string]: unknown;
|
|
75
97
|
};
|
|
76
98
|
resourceStore?: I18nResourceStore;
|
|
77
|
-
backend?:
|
|
78
|
-
[key: string]:
|
|
99
|
+
backend?: unknown; // Backend instance (e.g., SdkBackend)
|
|
100
|
+
[key: string]: unknown;
|
|
79
101
|
};
|
|
80
102
|
// i18next instance options (available after initialization)
|
|
81
103
|
options?: {
|
|
82
104
|
backend?: BackendOptions;
|
|
83
|
-
[key: string]:
|
|
105
|
+
[key: string]: unknown;
|
|
84
106
|
};
|
|
85
|
-
[key: string]:
|
|
107
|
+
[key: string]: unknown;
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
type LanguageDetectorOrder = string[];
|
|
@@ -101,12 +123,12 @@ export interface LanguageDetectorOptions {
|
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
export interface BackendOptions extends Omit<BaseBackendOptions, 'enabled'> {
|
|
104
|
-
parse?: (data: string) =>
|
|
105
|
-
stringify?: (data:
|
|
126
|
+
parse?: (data: string) => unknown;
|
|
127
|
+
stringify?: (data: unknown) => string;
|
|
106
128
|
[key: string]: any;
|
|
107
129
|
}
|
|
108
130
|
|
|
109
|
-
|
|
131
|
+
type ResourceValue = string | { [key: string]: ResourceValue };
|
|
110
132
|
|
|
111
133
|
export interface Resources {
|
|
112
134
|
[lng: string]: {
|
|
@@ -126,15 +148,15 @@ export type I18nInitOptions = {
|
|
|
126
148
|
defaultNS?: string | string[];
|
|
127
149
|
interpolation?: {
|
|
128
150
|
escapeValue?: boolean;
|
|
129
|
-
[key: string]:
|
|
151
|
+
[key: string]: unknown;
|
|
130
152
|
};
|
|
131
153
|
react?: {
|
|
132
154
|
useSuspense?: boolean;
|
|
133
|
-
[key: string]:
|
|
155
|
+
[key: string]: unknown;
|
|
134
156
|
};
|
|
135
157
|
};
|
|
136
158
|
|
|
137
|
-
export function isI18nInstance(obj:
|
|
159
|
+
export function isI18nInstance(obj: unknown): obj is I18nInstance {
|
|
138
160
|
if (!obj || typeof obj !== 'object') {
|
|
139
161
|
return false;
|
|
140
162
|
}
|
|
@@ -143,7 +165,10 @@ export function isI18nInstance(obj: any): obj is I18nInstance {
|
|
|
143
165
|
return true;
|
|
144
166
|
}
|
|
145
167
|
|
|
146
|
-
|
|
168
|
+
const candidate = obj as { init?: unknown; use?: unknown };
|
|
169
|
+
return (
|
|
170
|
+
typeof candidate.init === 'function' && typeof candidate.use === 'function'
|
|
171
|
+
);
|
|
147
172
|
}
|
|
148
173
|
|
|
149
174
|
async function tryImportI18next(): Promise<I18nInstance | null> {
|
|
@@ -170,8 +195,8 @@ async function createI18nextInstance(): Promise<I18nInstance | null> {
|
|
|
170
195
|
}
|
|
171
196
|
|
|
172
197
|
export function getI18nextInstanceForProvider(
|
|
173
|
-
instance: I18nInstance
|
|
174
|
-
):
|
|
198
|
+
instance: I18nInstance,
|
|
199
|
+
): I18nInstance {
|
|
175
200
|
if (isI18nWrapperInstance(instance)) {
|
|
176
201
|
const i18nextInstance = getI18nWrapperI18nextInstance(instance);
|
|
177
202
|
if (i18nextInstance) {
|
|
@@ -183,7 +208,7 @@ export function getI18nextInstanceForProvider(
|
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
export async function getI18nInstance(
|
|
186
|
-
userInstance?:
|
|
211
|
+
userInstance?: unknown,
|
|
187
212
|
): Promise<I18nInstance> {
|
|
188
213
|
if (userInstance) {
|
|
189
214
|
if (isI18nWrapperInstance(userInstance)) {
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { ReactI18nextIntegration } from '../reactI18next';
|
|
2
2
|
|
|
3
3
|
type ReactI18nextModule = typeof import('react-i18next');
|
|
4
4
|
|
|
5
|
-
interface ReactI18nextIntegration {
|
|
6
|
-
I18nextProvider: React.ComponentType<any> | null;
|
|
7
|
-
initReactI18next: any | null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
5
|
async function tryImportReactI18next(): Promise<ReactI18nextModule | null> {
|
|
11
6
|
try {
|
|
12
7
|
return (await import('react-i18next')) as ReactI18nextModule;
|
|
@@ -11,7 +11,16 @@ import {
|
|
|
11
11
|
isI18nWrapperInstance,
|
|
12
12
|
} from './instance';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
type MergedBackendOptions = NonNullable<I18nInitOptions['backend']> & {
|
|
15
|
+
_useChainedBackend?: boolean;
|
|
16
|
+
_chainedBackendConfig?: {
|
|
17
|
+
backendOptions: Array<Record<string, unknown>>;
|
|
18
|
+
};
|
|
19
|
+
backends?: unknown[];
|
|
20
|
+
backendOptions?: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function assertI18nInstance(obj: unknown): asserts obj is I18nInstance {
|
|
15
24
|
if (!isI18nInstance(obj)) {
|
|
16
25
|
throw new Error('Object does not implement I18nInstance interface');
|
|
17
26
|
}
|
|
@@ -24,8 +33,8 @@ export const buildInitOptions = async (
|
|
|
24
33
|
finalLanguage: string,
|
|
25
34
|
fallbackLanguage: string,
|
|
26
35
|
languages: string[],
|
|
27
|
-
mergedDetection:
|
|
28
|
-
mergedBackend:
|
|
36
|
+
mergedDetection: I18nInitOptions['detection'],
|
|
37
|
+
mergedBackend: MergedBackendOptions | undefined,
|
|
29
38
|
userInitOptions?: I18nInitOptions,
|
|
30
39
|
useSuspense?: boolean,
|
|
31
40
|
i18nInstance?: I18nInstance,
|
|
@@ -84,8 +93,8 @@ export const buildInitOptions = async (
|
|
|
84
93
|
if (isChainedBackend && mergedBackend._chainedBackendConfig) {
|
|
85
94
|
// Try to get backend classes from i18nInstance.options.backend.backends first
|
|
86
95
|
// This avoids importing fs-backend in browser environment
|
|
87
|
-
let HttpBackend:
|
|
88
|
-
let SdkBackendClass:
|
|
96
|
+
let HttpBackend: unknown;
|
|
97
|
+
let SdkBackendClass: unknown;
|
|
89
98
|
|
|
90
99
|
if (
|
|
91
100
|
i18nInstance?.options?.backend?.backends &&
|
|
@@ -148,7 +157,7 @@ export const changeI18nLanguage = async (
|
|
|
148
157
|
i18nInstance: I18nInstance,
|
|
149
158
|
newLang: string,
|
|
150
159
|
options?: {
|
|
151
|
-
detectionOptions?:
|
|
160
|
+
detectionOptions?: I18nInitOptions['detection'];
|
|
152
161
|
},
|
|
153
162
|
): Promise<void> => {
|
|
154
163
|
if (!newLang || typeof newLang !== 'string') {
|
|
@@ -179,8 +188,8 @@ export const initializeI18nInstance = async (
|
|
|
179
188
|
finalLanguage: string,
|
|
180
189
|
fallbackLanguage: string,
|
|
181
190
|
languages: string[],
|
|
182
|
-
mergedDetection:
|
|
183
|
-
mergedBackend:
|
|
191
|
+
mergedDetection: I18nInitOptions['detection'],
|
|
192
|
+
mergedBackend: MergedBackendOptions | undefined,
|
|
184
193
|
userInitOptions?: I18nInitOptions,
|
|
185
194
|
useSuspense?: boolean,
|
|
186
195
|
): Promise<void> => {
|
|
@@ -265,7 +274,7 @@ export const setupClonedInstance = async (
|
|
|
265
274
|
backendEnabled: boolean,
|
|
266
275
|
backend: BaseBackendOptions | undefined,
|
|
267
276
|
i18nextDetector: boolean,
|
|
268
|
-
detection:
|
|
277
|
+
detection: I18nInitOptions['detection'],
|
|
269
278
|
localePathRedirect: boolean,
|
|
270
279
|
userInitOptions: I18nInitOptions | undefined,
|
|
271
280
|
): Promise<void> => {
|