@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.4 → 3.5.0-ultramodern.41
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
package/src/runtime/context.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isBrowser } from '@modern-js/runtime';
|
|
2
1
|
import type { FC, ReactNode } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
createContext,
|
|
@@ -8,18 +7,18 @@ import {
|
|
|
8
7
|
useMemo,
|
|
9
8
|
} from 'react';
|
|
10
9
|
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
10
|
+
import {
|
|
11
|
+
cacheI18nLanguage,
|
|
12
|
+
changeModernI18nLanguage,
|
|
13
|
+
getPathLanguage,
|
|
14
|
+
isI18nLanguageSupported,
|
|
15
|
+
isI18nResourcesReady,
|
|
16
|
+
translateI18n,
|
|
17
|
+
} from './contextHelpers';
|
|
11
18
|
import type { I18nInstance } from './i18n';
|
|
12
|
-
import type { SdkBackend } from './i18n/backend/sdk-backend';
|
|
13
|
-
import { cacheUserLanguage } from './i18n/detection';
|
|
14
19
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
15
|
-
import {
|
|
16
|
-
buildLocalizedUrl,
|
|
17
|
-
detectLanguageFromPath,
|
|
18
|
-
getEntryPath,
|
|
19
|
-
shouldIgnoreRedirect,
|
|
20
|
-
} from './utils';
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
interface ModernI18nContextValue {
|
|
23
22
|
language: string;
|
|
24
23
|
i18nInstance: I18nInstance;
|
|
25
24
|
// Plugin configuration for useModernI18n hook
|
|
@@ -51,7 +50,7 @@ const getModernI18nContext = () => {
|
|
|
51
50
|
|
|
52
51
|
const ModernI18nContext = getModernI18nContext();
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
interface ModernI18nProviderProps {
|
|
55
54
|
children: ReactNode;
|
|
56
55
|
value: ModernI18nContextValue;
|
|
57
56
|
}
|
|
@@ -67,7 +66,7 @@ export const ModernI18nProvider: FC<ModernI18nProviderProps> = ({
|
|
|
67
66
|
);
|
|
68
67
|
};
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
interface UseModernI18nReturn {
|
|
71
70
|
language: string;
|
|
72
71
|
changeLanguage: (newLang: string) => Promise<void>;
|
|
73
72
|
t: (key: string | string[], ...args: any[]) => string;
|
|
@@ -75,7 +74,7 @@ export interface UseModernI18nReturn {
|
|
|
75
74
|
supportedLanguages: string[];
|
|
76
75
|
localisedUrls?: LocalisedUrlsOption;
|
|
77
76
|
isLanguageSupported: (lang: string) => boolean;
|
|
78
|
-
// Indicates
|
|
77
|
+
// Indicates whether translation resources for current language are ready
|
|
79
78
|
isResourcesReady: boolean;
|
|
80
79
|
}
|
|
81
80
|
|
|
@@ -85,9 +84,9 @@ export interface UseModernI18nReturn {
|
|
|
85
84
|
* This hook provides:
|
|
86
85
|
* - Current language from URL params or i18n context
|
|
87
86
|
* - changeLanguage function that updates both i18n instance and URL
|
|
88
|
-
* - Direct access to
|
|
87
|
+
* - Direct access to i18n instance
|
|
89
88
|
* - List of supported languages
|
|
90
|
-
* - Helper function to check if
|
|
89
|
+
* - Helper function to check if language is supported
|
|
91
90
|
*
|
|
92
91
|
* @param options - Optional configuration to override context settings
|
|
93
92
|
* @returns Object containing i18n functionality and utilities
|
|
@@ -95,7 +94,7 @@ export interface UseModernI18nReturn {
|
|
|
95
94
|
export const useModernI18n = (): UseModernI18nReturn => {
|
|
96
95
|
const context = useContext(ModernI18nContext);
|
|
97
96
|
if (!context) {
|
|
98
|
-
throw new Error('useModernI18n must be used within
|
|
97
|
+
throw new Error('useModernI18n must be used within ModernI18nProvider');
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
const {
|
|
@@ -110,17 +109,10 @@ export const useModernI18n = (): UseModernI18nReturn => {
|
|
|
110
109
|
|
|
111
110
|
const { navigate, location, hasRouter } = useI18nRouterAdapter();
|
|
112
111
|
|
|
113
|
-
const pathLanguage = useMemo(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const detected = detectLanguageFromPath(
|
|
118
|
-
location.pathname,
|
|
119
|
-
languages || [],
|
|
120
|
-
localePathRedirect,
|
|
121
|
-
);
|
|
122
|
-
return detected.detected ? detected.language : undefined;
|
|
123
|
-
}, [languages, localePathRedirect, location?.pathname]);
|
|
112
|
+
const pathLanguage = useMemo(
|
|
113
|
+
() => getPathLanguage(location?.pathname, languages, localePathRedirect),
|
|
114
|
+
[languages, localePathRedirect, location?.pathname],
|
|
115
|
+
);
|
|
124
116
|
|
|
125
117
|
const currentLanguage = pathLanguage || contextLanguage;
|
|
126
118
|
|
|
@@ -132,129 +124,32 @@ export const useModernI18n = (): UseModernI18nReturn => {
|
|
|
132
124
|
updateLanguage?.(pathLanguage);
|
|
133
125
|
i18nInstance?.setLang?.(pathLanguage);
|
|
134
126
|
void i18nInstance?.changeLanguage?.(pathLanguage);
|
|
135
|
-
|
|
136
|
-
if (isBrowser()) {
|
|
137
|
-
const detectionOptions = i18nInstance.options?.detection;
|
|
138
|
-
cacheUserLanguage(i18nInstance, pathLanguage, detectionOptions);
|
|
139
|
-
}
|
|
127
|
+
cacheI18nLanguage(i18nInstance, pathLanguage);
|
|
140
128
|
}, [contextLanguage, i18nInstance, pathLanguage, updateLanguage]);
|
|
141
129
|
|
|
142
130
|
/**
|
|
143
|
-
* Changes the current language and updates
|
|
131
|
+
* Changes the current language and updates URL accordingly.
|
|
144
132
|
*
|
|
145
133
|
* This function:
|
|
146
|
-
* 1. Updates
|
|
147
|
-
* 2. Updates
|
|
148
|
-
* 3. Triggers
|
|
134
|
+
* 1. Updates i18n instance language
|
|
135
|
+
* 2. Updates URL by replacing language prefix in the current path
|
|
136
|
+
* 3. Triggers navigation to the new URL
|
|
149
137
|
*
|
|
150
138
|
* @param newLang - The new language code to switch to
|
|
151
139
|
*/
|
|
152
140
|
const changeLanguage = useCallback(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
cacheUserLanguage(i18nInstance, newLang, detectionOptions);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
localePathRedirect &&
|
|
170
|
-
isBrowser() &&
|
|
171
|
-
hasRouter &&
|
|
172
|
-
navigate &&
|
|
173
|
-
location
|
|
174
|
-
) {
|
|
175
|
-
const currentPath = location.pathname;
|
|
176
|
-
const entryPath = getEntryPath();
|
|
177
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
178
|
-
|
|
179
|
-
// Check if the path already contains the target language
|
|
180
|
-
const pathLanguage = detectLanguageFromPath(
|
|
181
|
-
currentPath,
|
|
182
|
-
languages || [],
|
|
183
|
-
localePathRedirect,
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// If path already has the target language, skip redirect
|
|
187
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
!shouldIgnoreRedirect(
|
|
193
|
-
relativePath,
|
|
194
|
-
languages || [],
|
|
195
|
-
ignoreRedirectRoutes,
|
|
196
|
-
)
|
|
197
|
-
) {
|
|
198
|
-
const newPath = buildLocalizedUrl(
|
|
199
|
-
relativePath,
|
|
200
|
-
newLang,
|
|
201
|
-
languages || [],
|
|
202
|
-
localisedUrls,
|
|
203
|
-
);
|
|
204
|
-
const newUrl =
|
|
205
|
-
entryPath + newPath + location.search + location.hash;
|
|
206
|
-
|
|
207
|
-
await navigate(newUrl, { replace: true });
|
|
208
|
-
}
|
|
209
|
-
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
210
|
-
const currentPath = window.location.pathname;
|
|
211
|
-
const entryPath = getEntryPath();
|
|
212
|
-
const relativePath = currentPath.replace(entryPath, '');
|
|
213
|
-
|
|
214
|
-
// Check if the path already contains the target language
|
|
215
|
-
const pathLanguage = detectLanguageFromPath(
|
|
216
|
-
currentPath,
|
|
217
|
-
languages || [],
|
|
218
|
-
localePathRedirect,
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
// If path already has the target language, skip redirect
|
|
222
|
-
if (pathLanguage.detected && pathLanguage.language === newLang) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
!shouldIgnoreRedirect(
|
|
228
|
-
relativePath,
|
|
229
|
-
languages || [],
|
|
230
|
-
ignoreRedirectRoutes,
|
|
231
|
-
)
|
|
232
|
-
) {
|
|
233
|
-
const newPath = buildLocalizedUrl(
|
|
234
|
-
relativePath,
|
|
235
|
-
newLang,
|
|
236
|
-
languages || [],
|
|
237
|
-
localisedUrls,
|
|
238
|
-
);
|
|
239
|
-
const newUrl =
|
|
240
|
-
entryPath +
|
|
241
|
-
newPath +
|
|
242
|
-
window.location.search +
|
|
243
|
-
window.location.hash;
|
|
244
|
-
|
|
245
|
-
window.history.pushState(null, '', newUrl);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Update language state after URL update
|
|
250
|
-
if (updateLanguage) {
|
|
251
|
-
updateLanguage(newLang);
|
|
252
|
-
}
|
|
253
|
-
} catch (error) {
|
|
254
|
-
console.error('Failed to change language:', error);
|
|
255
|
-
throw error;
|
|
256
|
-
}
|
|
257
|
-
},
|
|
141
|
+
(newLang: string) =>
|
|
142
|
+
changeModernI18nLanguage(newLang, {
|
|
143
|
+
i18nInstance,
|
|
144
|
+
updateLanguage,
|
|
145
|
+
localePathRedirect,
|
|
146
|
+
ignoreRedirectRoutes,
|
|
147
|
+
localisedUrls,
|
|
148
|
+
languages,
|
|
149
|
+
hasRouter,
|
|
150
|
+
navigate,
|
|
151
|
+
location,
|
|
152
|
+
}),
|
|
258
153
|
[
|
|
259
154
|
i18nInstance,
|
|
260
155
|
updateLanguage,
|
|
@@ -269,72 +164,23 @@ export const useModernI18n = (): UseModernI18nReturn => {
|
|
|
269
164
|
);
|
|
270
165
|
|
|
271
166
|
const t = useCallback(
|
|
272
|
-
(key: string | string[], ...args: any[]) =>
|
|
273
|
-
|
|
274
|
-
throw new Error('i18nInstance.t is required');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return i18nInstance.t(key, ...args) as string;
|
|
278
|
-
},
|
|
167
|
+
(key: string | string[], ...args: any[]) =>
|
|
168
|
+
translateI18n(i18nInstance, key, ...args),
|
|
279
169
|
[currentLanguage, i18nInstance],
|
|
280
170
|
);
|
|
281
171
|
|
|
282
|
-
// Helper function to check if
|
|
172
|
+
// Helper function to check if language is supported
|
|
283
173
|
const isLanguageSupported = useCallback(
|
|
284
|
-
(lang: string) =>
|
|
285
|
-
return languages?.includes(lang) || false;
|
|
286
|
-
},
|
|
174
|
+
(lang: string) => isI18nLanguageSupported(languages, lang),
|
|
287
175
|
[languages],
|
|
288
176
|
);
|
|
289
177
|
|
|
290
178
|
// Check if current language resources are ready
|
|
291
|
-
// This checks if all required namespaces for
|
|
292
|
-
const isResourcesReady = useMemo(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// Get backend instance
|
|
298
|
-
const backend = i18nInstance?.services?.backend as SdkBackend | undefined;
|
|
299
|
-
|
|
300
|
-
// If using SDK backend, check loading state
|
|
301
|
-
if (backend && typeof backend.isLoading === 'function') {
|
|
302
|
-
// Check if any resource for current language is loading
|
|
303
|
-
const loadingResources = backend.getLoadingResources();
|
|
304
|
-
const isCurrentLanguageLoading = loadingResources.some(
|
|
305
|
-
({ language }) => language === currentLanguage,
|
|
306
|
-
);
|
|
307
|
-
if (isCurrentLanguageLoading) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Check if resources exist in store
|
|
313
|
-
const store = (i18nInstance as any).store;
|
|
314
|
-
if (!store?.data) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const langData = store.data[currentLanguage];
|
|
319
|
-
if (!langData || typeof langData !== 'object') {
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Get required namespaces
|
|
324
|
-
const options = i18nInstance.options;
|
|
325
|
-
const namespaces = options?.ns || options?.defaultNS || ['translation'];
|
|
326
|
-
const requiredNamespaces = Array.isArray(namespaces)
|
|
327
|
-
? namespaces
|
|
328
|
-
: [namespaces];
|
|
329
|
-
|
|
330
|
-
// Check if all required namespaces are loaded
|
|
331
|
-
return requiredNamespaces.every(ns => {
|
|
332
|
-
const nsData = langData[ns];
|
|
333
|
-
return (
|
|
334
|
-
nsData && typeof nsData === 'object' && Object.keys(nsData).length > 0
|
|
335
|
-
);
|
|
336
|
-
});
|
|
337
|
-
}, [currentLanguage, i18nInstance]);
|
|
179
|
+
// This checks if all required namespaces for current language are loaded
|
|
180
|
+
const isResourcesReady = useMemo(
|
|
181
|
+
() => isI18nResourcesReady(i18nInstance, currentLanguage),
|
|
182
|
+
[currentLanguage, i18nInstance],
|
|
183
|
+
);
|
|
338
184
|
|
|
339
185
|
return {
|
|
340
186
|
language: currentLanguage,
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { isBrowser } from '@modern-js/runtime';
|
|
2
|
+
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
3
|
+
import type { I18nInstance } from './i18n';
|
|
4
|
+
import type { SdkBackend } from './i18n/backend/sdk-backend';
|
|
5
|
+
import { cacheUserLanguage } from './i18n/detection';
|
|
6
|
+
import {
|
|
7
|
+
buildLocalizedUrl,
|
|
8
|
+
detectLanguageFromPath,
|
|
9
|
+
getEntryPath,
|
|
10
|
+
shouldIgnoreRedirect,
|
|
11
|
+
} from './utils';
|
|
12
|
+
|
|
13
|
+
type Navigate = (
|
|
14
|
+
url: string,
|
|
15
|
+
options?: {
|
|
16
|
+
replace?: boolean;
|
|
17
|
+
},
|
|
18
|
+
) => Promise<void> | void;
|
|
19
|
+
|
|
20
|
+
type LocationLike = {
|
|
21
|
+
pathname: string;
|
|
22
|
+
search: string;
|
|
23
|
+
hash: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function getPathLanguage(
|
|
27
|
+
pathname: string | undefined,
|
|
28
|
+
languages: string[] | undefined,
|
|
29
|
+
localePathRedirect: boolean | undefined,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
if (!localePathRedirect || !pathname) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const detected = detectLanguageFromPath(
|
|
36
|
+
pathname,
|
|
37
|
+
languages || [],
|
|
38
|
+
localePathRedirect,
|
|
39
|
+
);
|
|
40
|
+
return detected.detected ? detected.language : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function cacheI18nLanguage(
|
|
44
|
+
i18nInstance: I18nInstance,
|
|
45
|
+
language: string,
|
|
46
|
+
) {
|
|
47
|
+
if (isBrowser()) {
|
|
48
|
+
const detectionOptions = i18nInstance.options?.detection;
|
|
49
|
+
cacheUserLanguage(i18nInstance, language, detectionOptions);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ChangeModernI18nLanguageOptions {
|
|
54
|
+
i18nInstance: I18nInstance;
|
|
55
|
+
updateLanguage?: (newLang: string) => void;
|
|
56
|
+
localePathRedirect?: boolean;
|
|
57
|
+
ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean);
|
|
58
|
+
localisedUrls?: LocalisedUrlsOption;
|
|
59
|
+
languages?: string[];
|
|
60
|
+
hasRouter: boolean;
|
|
61
|
+
navigate?: Navigate | null;
|
|
62
|
+
location?: LocationLike | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function changeModernI18nLanguage(
|
|
66
|
+
newLang: string,
|
|
67
|
+
options: ChangeModernI18nLanguageOptions,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const {
|
|
70
|
+
i18nInstance,
|
|
71
|
+
updateLanguage,
|
|
72
|
+
localePathRedirect,
|
|
73
|
+
ignoreRedirectRoutes,
|
|
74
|
+
localisedUrls,
|
|
75
|
+
languages,
|
|
76
|
+
hasRouter,
|
|
77
|
+
navigate,
|
|
78
|
+
location,
|
|
79
|
+
} = options;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (!newLang || typeof newLang !== 'string') {
|
|
83
|
+
throw new Error('Language must be non-empty string');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await i18nInstance?.setLang?.(newLang);
|
|
87
|
+
await i18nInstance?.changeLanguage?.(newLang);
|
|
88
|
+
cacheI18nLanguage(i18nInstance, newLang);
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
localePathRedirect &&
|
|
92
|
+
isBrowser() &&
|
|
93
|
+
hasRouter &&
|
|
94
|
+
navigate &&
|
|
95
|
+
location
|
|
96
|
+
) {
|
|
97
|
+
const currentPath = location.pathname;
|
|
98
|
+
const entryPath = getEntryPath();
|
|
99
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
100
|
+
const pathLanguage = detectLanguageFromPath(
|
|
101
|
+
currentPath,
|
|
102
|
+
languages || [],
|
|
103
|
+
localePathRedirect,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) {
|
|
107
|
+
updateLanguage?.(newLang);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
!shouldIgnoreRedirect(
|
|
113
|
+
relativePath,
|
|
114
|
+
languages || [],
|
|
115
|
+
ignoreRedirectRoutes,
|
|
116
|
+
)
|
|
117
|
+
) {
|
|
118
|
+
const newPath = buildLocalizedUrl(
|
|
119
|
+
relativePath,
|
|
120
|
+
newLang,
|
|
121
|
+
languages || [],
|
|
122
|
+
localisedUrls,
|
|
123
|
+
);
|
|
124
|
+
const newUrl = entryPath + newPath + location.search + location.hash;
|
|
125
|
+
|
|
126
|
+
await navigate(newUrl, { replace: true });
|
|
127
|
+
}
|
|
128
|
+
} else if (localePathRedirect && isBrowser() && !hasRouter) {
|
|
129
|
+
const currentPath = window.location.pathname;
|
|
130
|
+
const entryPath = getEntryPath();
|
|
131
|
+
const relativePath = currentPath.replace(entryPath, '');
|
|
132
|
+
const pathLanguage = detectLanguageFromPath(
|
|
133
|
+
currentPath,
|
|
134
|
+
languages || [],
|
|
135
|
+
localePathRedirect,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (pathLanguage.detected && pathLanguage.language === newLang) {
|
|
139
|
+
updateLanguage?.(newLang);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
!shouldIgnoreRedirect(
|
|
145
|
+
relativePath,
|
|
146
|
+
languages || [],
|
|
147
|
+
ignoreRedirectRoutes,
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
const newPath = buildLocalizedUrl(
|
|
151
|
+
relativePath,
|
|
152
|
+
newLang,
|
|
153
|
+
languages || [],
|
|
154
|
+
localisedUrls,
|
|
155
|
+
);
|
|
156
|
+
const newUrl =
|
|
157
|
+
entryPath + newPath + window.location.search + window.location.hash;
|
|
158
|
+
|
|
159
|
+
window.history.pushState(null, '', newUrl);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (updateLanguage) {
|
|
164
|
+
updateLanguage(newLang);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Failed change language:', error);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function translateI18n(
|
|
173
|
+
i18nInstance: I18nInstance,
|
|
174
|
+
key: string | string[],
|
|
175
|
+
...args: unknown[]
|
|
176
|
+
): string {
|
|
177
|
+
if (typeof i18nInstance.t !== 'function') {
|
|
178
|
+
throw new Error('i18nInstance.t required');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return i18nInstance.t(key, ...args) as string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function isI18nLanguageSupported(
|
|
185
|
+
languages: string[] | undefined,
|
|
186
|
+
lang: string,
|
|
187
|
+
): boolean {
|
|
188
|
+
return languages?.includes(lang) || false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isI18nResourcesReady(
|
|
192
|
+
i18nInstance: I18nInstance,
|
|
193
|
+
currentLanguage: string,
|
|
194
|
+
): boolean {
|
|
195
|
+
if (!i18nInstance?.isInitialized) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const backend = i18nInstance?.services?.backend as SdkBackend | undefined;
|
|
200
|
+
if (backend && typeof backend.isLoading === 'function') {
|
|
201
|
+
const loadingResources = backend.getLoadingResources();
|
|
202
|
+
const isCurrentLanguageLoading = loadingResources.some(
|
|
203
|
+
({ language }) => language === currentLanguage,
|
|
204
|
+
);
|
|
205
|
+
if (isCurrentLanguageLoading) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const store = i18nInstance.store;
|
|
211
|
+
if (!store?.data) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const langData = store.data[currentLanguage];
|
|
216
|
+
if (!langData || typeof langData !== 'object') {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const options = i18nInstance.options;
|
|
221
|
+
const namespaces = options?.ns || options?.defaultNS || ['translation'];
|
|
222
|
+
const requiredNamespaces = Array.isArray(namespaces)
|
|
223
|
+
? namespaces
|
|
224
|
+
: [namespaces];
|
|
225
|
+
|
|
226
|
+
return requiredNamespaces.every(ns => {
|
|
227
|
+
const nsData = langData[ns];
|
|
228
|
+
return (
|
|
229
|
+
nsData && typeof nsData === 'object' && Object.keys(nsData).length > 0
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
}
|