@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.3 → 3.5.0-ultramodern.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +2 -1
- package/dist/cjs/runtime/Link.js +8 -89
- package/dist/cjs/runtime/context.js +18 -82
- package/dist/cjs/runtime/contextHelpers.js +137 -0
- package/dist/cjs/runtime/core.js +29 -132
- package/dist/cjs/runtime/i18n/detection/cache.js +149 -0
- package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
- package/dist/cjs/runtime/i18n/detection/index.js +8 -263
- package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
- package/dist/cjs/runtime/i18n/detection/language.js +62 -0
- package/dist/cjs/runtime/i18n/detection/path.js +49 -0
- package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
- package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
- package/dist/cjs/runtime/i18n/detection/types.js +18 -0
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +113 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- package/dist/cjs/server/apiPrefix.js +72 -0
- package/dist/cjs/server/detectorOptions.js +97 -0
- package/dist/cjs/server/index.js +17 -144
- package/dist/cjs/server/redirectPolicy.js +102 -0
- package/dist/cjs/shared/localisedUrls/config.js +49 -0
- package/dist/cjs/shared/localisedUrls/index.js +75 -0
- package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +65 -0
- package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +91 -0
- package/dist/cjs/shared/localisedUrls/routes.js +129 -0
- package/dist/cjs/shared/localisedUrls/types.js +18 -0
- package/dist/cjs/shared/localisedUrls.js +13 -294
- package/dist/esm/cli/index.mjs +2 -1
- package/dist/esm/runtime/Link.mjs +1 -82
- package/dist/esm/runtime/context.mjs +18 -82
- package/dist/esm/runtime/contextHelpers.mjs +84 -0
- package/dist/esm/runtime/core.mjs +30 -133
- package/dist/esm/runtime/i18n/detection/cache.mjs +102 -0
- package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
- package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
- package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
- package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
- package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +75 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- package/dist/esm/server/apiPrefix.mjs +31 -0
- package/dist/esm/server/detectorOptions.mjs +59 -0
- package/dist/esm/server/index.mjs +6 -133
- package/dist/esm/server/redirectPolicy.mjs +52 -0
- package/dist/esm/shared/localisedUrls/config.mjs +11 -0
- package/dist/esm/shared/localisedUrls/index.mjs +6 -0
- package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +50 -0
- package/dist/esm/shared/localisedUrls/routes.mjs +88 -0
- package/dist/esm/shared/localisedUrls/types.mjs +0 -0
- package/dist/esm/shared/localisedUrls.mjs +1 -283
- package/dist/esm-node/cli/index.mjs +2 -1
- package/dist/esm-node/runtime/Link.mjs +1 -82
- package/dist/esm-node/runtime/context.mjs +18 -82
- package/dist/esm-node/runtime/contextHelpers.mjs +85 -0
- package/dist/esm-node/runtime/core.mjs +30 -133
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +103 -0
- package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
- package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
- package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
- package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
- package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
- package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
- package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
- package/dist/esm-node/runtime/providerComposition.mjs +76 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- package/dist/esm-node/server/apiPrefix.mjs +32 -0
- package/dist/esm-node/server/detectorOptions.mjs +60 -0
- package/dist/esm-node/server/index.mjs +6 -133
- package/dist/esm-node/server/redirectPolicy.mjs +53 -0
- package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/shared/localisedUrls/index.mjs +7 -0
- package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +51 -0
- package/dist/esm-node/shared/localisedUrls/routes.mjs +89 -0
- package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/shared/localisedUrls.mjs +1 -283
- package/dist/types/runtime/Link.d.ts +3 -7
- package/dist/types/runtime/context.d.ts +2 -2
- package/dist/types/runtime/contextHelpers.d.ts +28 -0
- package/dist/types/runtime/core.d.ts +3 -7
- package/dist/types/runtime/hooks.d.ts +5 -0
- package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
- package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
- package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
- package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
- package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
- package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
- package/dist/types/runtime/i18n/detection/types.d.ts +29 -0
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
- package/dist/types/runtime/linkHelpers.d.ts +17 -0
- package/dist/types/runtime/pluginSetup.d.ts +32 -0
- package/dist/types/runtime/providerComposition.d.ts +15 -0
- package/dist/types/runtime/reactI18next.d.ts +7 -0
- package/dist/types/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 +16 -0
- package/dist/types/shared/localisedUrls/config.d.ts +9 -0
- package/dist/types/shared/localisedUrls/index.d.ts +7 -0
- package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/shared/localisedUrls/pathname.d.ts +3 -0
- package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +8 -0
- package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
- package/dist/types/shared/localisedUrls/types.d.ts +14 -0
- package/dist/types/shared/localisedUrls.d.ts +2 -36
- package/package.json +10 -10
- package/src/cli/index.ts +3 -2
- package/src/runtime/Link.tsx +11 -138
- package/src/runtime/context.tsx +42 -196
- package/src/runtime/contextHelpers.ts +232 -0
- package/src/runtime/core.tsx +43 -274
- package/src/runtime/hooks.ts +3 -0
- package/src/runtime/i18n/detection/cache.ts +175 -0
- package/src/runtime/i18n/detection/detector.ts +157 -0
- package/src/runtime/i18n/detection/index.ts +10 -641
- package/src/runtime/i18n/detection/initOptions.ts +74 -0
- package/src/runtime/i18n/detection/language.ts +64 -0
- package/src/runtime/i18n/detection/path.ts +41 -0
- package/src/runtime/i18n/detection/priority.ts +82 -0
- package/src/runtime/i18n/detection/ssr.ts +47 -0
- package/src/runtime/i18n/detection/types.ts +33 -0
- package/src/runtime/i18n/react-i18next.ts +1 -6
- package/src/runtime/linkHelpers.ts +174 -0
- package/src/runtime/pluginSetup.ts +189 -0
- package/src/runtime/providerComposition.tsx +142 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/runtime/utils.ts +7 -5
- 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 +152 -0
- package/src/shared/localisedUrls/config.ts +18 -0
- package/src/shared/localisedUrls/index.ts +13 -0
- package/src/shared/localisedUrls/normalise.ts +67 -0
- package/src/shared/localisedUrls/pathname.ts +57 -0
- package/src/shared/localisedUrls/patterns.ts +160 -0
- package/src/shared/localisedUrls/resolve.ts +118 -0
- package/src/shared/localisedUrls/routes.ts +226 -0
- package/src/shared/localisedUrls/types.ts +18 -0
- package/src/shared/localisedUrls.ts +20 -623
- package/tests/link.test.tsx +111 -0
- package/tests/linkTypes.test.ts +15 -4
- package/tests/localisedUrls.test.ts +54 -2
- package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
- package/tests/routerAdapter.test.tsx +56 -0
- package/tests/type-fixture/fixture-globals.d.ts +11 -0
- package/tests/type-fixture/tsconfig.json +1 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { RuntimeContext } from '@modern-js/runtime';
|
|
2
|
+
import { Helmet } from '@modern-js/runtime/head';
|
|
3
|
+
import type React from 'react';
|
|
4
|
+
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import type { BaseLocaleDetectionOptions } from '../shared/type';
|
|
6
|
+
import { ModernI18nProvider } from './context';
|
|
7
|
+
import {
|
|
8
|
+
createContextValue,
|
|
9
|
+
useClientSideRedirect,
|
|
10
|
+
useLanguageSync,
|
|
11
|
+
useSdkResourcesLoader,
|
|
12
|
+
} from './hooks';
|
|
13
|
+
import type { I18nInstance } from './i18n';
|
|
14
|
+
import { getI18nextInstanceForProvider } from './i18n/instance';
|
|
15
|
+
import type { RuntimeContextWithI18n } from './pluginSetup';
|
|
16
|
+
|
|
17
|
+
export interface I18nRootWrapperOptions {
|
|
18
|
+
entryName?: string;
|
|
19
|
+
htmlLangAttr: boolean;
|
|
20
|
+
localePathRedirect: boolean;
|
|
21
|
+
languages: string[];
|
|
22
|
+
fallbackLanguage: string;
|
|
23
|
+
ignoreRedirectRoutes?: BaseLocaleDetectionOptions['ignoreRedirectRoutes'];
|
|
24
|
+
localisedUrls?: BaseLocaleDetectionOptions['localisedUrls'];
|
|
25
|
+
getLatestI18nInstance: () => I18nInstance | undefined;
|
|
26
|
+
getI18nextProvider: () => React.ComponentType<any> | null | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const createI18nRootWrapper =
|
|
30
|
+
(options: I18nRootWrapperOptions) => (App: React.ComponentType<any>) => {
|
|
31
|
+
return (props: Record<string, unknown>) => {
|
|
32
|
+
const {
|
|
33
|
+
entryName,
|
|
34
|
+
htmlLangAttr,
|
|
35
|
+
localePathRedirect,
|
|
36
|
+
languages,
|
|
37
|
+
fallbackLanguage,
|
|
38
|
+
ignoreRedirectRoutes,
|
|
39
|
+
localisedUrls,
|
|
40
|
+
getLatestI18nInstance,
|
|
41
|
+
getI18nextProvider,
|
|
42
|
+
} = options;
|
|
43
|
+
const runtimeContext = useContext(
|
|
44
|
+
RuntimeContext,
|
|
45
|
+
) as RuntimeContextWithI18n;
|
|
46
|
+
const i18nInstance =
|
|
47
|
+
runtimeContext.i18nInstance || getLatestI18nInstance();
|
|
48
|
+
const initialLang = useMemo(
|
|
49
|
+
() => i18nInstance?.language || fallbackLanguage,
|
|
50
|
+
[i18nInstance?.language, fallbackLanguage],
|
|
51
|
+
);
|
|
52
|
+
const [lang, setLang] = useState(initialLang);
|
|
53
|
+
const [forceUpdate, setForceUpdate] = useState(0);
|
|
54
|
+
const prevLangRef = useRef(lang);
|
|
55
|
+
const runtimeContextRef = useRef(runtimeContext);
|
|
56
|
+
runtimeContextRef.current = runtimeContext;
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (i18nInstance?.language) {
|
|
60
|
+
const translator = (i18nInstance as any).translator;
|
|
61
|
+
if (translator) {
|
|
62
|
+
translator.language = i18nInstance.language;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, [i18nInstance?.language]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
prevLangRef.current = lang;
|
|
69
|
+
}, [lang]);
|
|
70
|
+
|
|
71
|
+
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
72
|
+
useLanguageSync(
|
|
73
|
+
i18nInstance,
|
|
74
|
+
localePathRedirect,
|
|
75
|
+
languages,
|
|
76
|
+
runtimeContextRef,
|
|
77
|
+
prevLangRef,
|
|
78
|
+
setLang,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Handle client-side redirect for static deployments.
|
|
82
|
+
useClientSideRedirect(
|
|
83
|
+
i18nInstance,
|
|
84
|
+
localePathRedirect,
|
|
85
|
+
languages,
|
|
86
|
+
fallbackLanguage,
|
|
87
|
+
ignoreRedirectRoutes,
|
|
88
|
+
localisedUrls,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const contextValue = useMemo(
|
|
92
|
+
() =>
|
|
93
|
+
createContextValue(
|
|
94
|
+
lang,
|
|
95
|
+
i18nInstance,
|
|
96
|
+
entryName,
|
|
97
|
+
languages,
|
|
98
|
+
localePathRedirect,
|
|
99
|
+
ignoreRedirectRoutes,
|
|
100
|
+
localisedUrls,
|
|
101
|
+
setLang,
|
|
102
|
+
),
|
|
103
|
+
[
|
|
104
|
+
lang,
|
|
105
|
+
i18nInstance,
|
|
106
|
+
entryName,
|
|
107
|
+
languages,
|
|
108
|
+
localePathRedirect,
|
|
109
|
+
ignoreRedirectRoutes,
|
|
110
|
+
localisedUrls,
|
|
111
|
+
forceUpdate,
|
|
112
|
+
],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const children = (props as React.PropsWithChildren).children;
|
|
116
|
+
const appContent = (
|
|
117
|
+
<>
|
|
118
|
+
{Boolean(htmlLangAttr) && <Helmet htmlAttributes={{ lang }} />}
|
|
119
|
+
<ModernI18nProvider value={contextValue}>
|
|
120
|
+
{App ? <App {...props}>{children}</App> : children}
|
|
121
|
+
</ModernI18nProvider>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!i18nInstance) {
|
|
126
|
+
return appContent;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const I18nextProvider = getI18nextProvider();
|
|
130
|
+
if (I18nextProvider) {
|
|
131
|
+
const i18nextInstanceForProvider =
|
|
132
|
+
getI18nextInstanceForProvider(i18nInstance);
|
|
133
|
+
return (
|
|
134
|
+
<I18nextProvider i18n={i18nextInstanceForProvider}>
|
|
135
|
+
{appContent}
|
|
136
|
+
</I18nextProvider>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return appContent;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ReactI18nextIntegration {
|
|
4
|
+
I18nextProvider: React.ComponentType<any> | null;
|
|
5
|
+
initReactI18next: any | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type LoadReactI18nextIntegration =
|
|
9
|
+
() => Promise<ReactI18nextIntegration | null>;
|
|
10
|
+
|
|
11
|
+
export const resolveReactI18nextIntegration = async (
|
|
12
|
+
reactI18next: boolean,
|
|
13
|
+
loadReactI18nextIntegration?: LoadReactI18nextIntegration,
|
|
14
|
+
): Promise<ReactI18nextIntegration | null> => {
|
|
15
|
+
if (!reactI18next) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return loadReactI18nextIntegration?.() ?? null;
|
|
20
|
+
};
|
package/src/runtime/utils.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { isBrowser } from '@modern-js/runtime';
|
|
2
|
-
import {
|
|
3
|
-
getGlobalBasename,
|
|
4
|
-
type TInternalRuntimeContext,
|
|
5
|
-
} from '@modern-js/runtime/context';
|
|
2
|
+
import { getGlobalBasename } from '@modern-js/runtime/context';
|
|
6
3
|
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
7
4
|
import { localiseTargetPathname } from '../shared/localisedUrls';
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
// Structural parameter: hooks.ts passes a public-TRuntimeContext-based
|
|
7
|
+
// context while core.tsx passes the internal one; both carry the request
|
|
8
|
+
// pathname shape this helper needs.
|
|
9
|
+
export const getPathname = (context: {
|
|
10
|
+
ssrContext?: { request?: { pathname?: string } };
|
|
11
|
+
}): string => {
|
|
10
12
|
if (isBrowser()) {
|
|
11
13
|
return window.location.pathname;
|
|
12
14
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
type ApiPrefixInput = string | string[] | undefined;
|
|
2
|
+
|
|
3
|
+
const normalizeApiPrefix = (prefix: string): string | null => {
|
|
4
|
+
const trimmedPrefix = prefix.trim();
|
|
5
|
+
if (!trimmedPrefix) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const prefixedPath = trimmedPrefix.startsWith('/')
|
|
10
|
+
? trimmedPrefix
|
|
11
|
+
: `/${trimmedPrefix}`;
|
|
12
|
+
const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
|
|
13
|
+
const normalizedPrefix =
|
|
14
|
+
withoutWildcard.length > 1
|
|
15
|
+
? withoutWildcard.replace(/\/+$/, '')
|
|
16
|
+
: withoutWildcard;
|
|
17
|
+
|
|
18
|
+
return normalizedPrefix === '/' ? null : normalizedPrefix;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const collectApiPrefixes = (
|
|
22
|
+
routes: Array<{ isApi?: boolean; urlPath?: string }>,
|
|
23
|
+
bffPrefix?: ApiPrefixInput,
|
|
24
|
+
): string[] => {
|
|
25
|
+
const prefixes = new Set<string>();
|
|
26
|
+
|
|
27
|
+
for (const route of routes) {
|
|
28
|
+
if (!route.isApi || !route.urlPath) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const normalizedPrefix = normalizeApiPrefix(route.urlPath);
|
|
33
|
+
if (normalizedPrefix) {
|
|
34
|
+
prefixes.add(normalizedPrefix);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const bffPrefixes = Array.isArray(bffPrefix)
|
|
39
|
+
? bffPrefix
|
|
40
|
+
: bffPrefix
|
|
41
|
+
? [bffPrefix]
|
|
42
|
+
: [];
|
|
43
|
+
|
|
44
|
+
for (const prefix of bffPrefixes) {
|
|
45
|
+
const normalizedPrefix = normalizeApiPrefix(prefix);
|
|
46
|
+
if (normalizedPrefix) {
|
|
47
|
+
prefixes.add(normalizedPrefix);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [...prefixes];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const matchesApiPrefix = (
|
|
55
|
+
pathname: string,
|
|
56
|
+
apiPrefixes: string[],
|
|
57
|
+
): boolean => {
|
|
58
|
+
const normalizedPathname = pathname.startsWith('/')
|
|
59
|
+
? pathname
|
|
60
|
+
: `/${pathname}`;
|
|
61
|
+
|
|
62
|
+
return apiPrefixes.some(
|
|
63
|
+
prefix =>
|
|
64
|
+
normalizedPathname === prefix ||
|
|
65
|
+
normalizedPathname.startsWith(`${prefix}/`),
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_I18NEXT_DETECTION_OPTIONS,
|
|
3
|
+
mergeDetectionOptions,
|
|
4
|
+
} from '../runtime/i18n/detection/config.js';
|
|
5
|
+
import type { LanguageDetectorOptions } from '../runtime/i18n/instance';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert i18next detection options to hono languageDetector options
|
|
9
|
+
*/
|
|
10
|
+
export const convertToHonoLanguageDetectorOptions = (
|
|
11
|
+
languages: string[],
|
|
12
|
+
fallbackLanguage: string,
|
|
13
|
+
detectionOptions?: LanguageDetectorOptions,
|
|
14
|
+
) => {
|
|
15
|
+
// Merge user detection options with defaults
|
|
16
|
+
const mergedDetection = detectionOptions
|
|
17
|
+
? mergeDetectionOptions(detectionOptions)
|
|
18
|
+
: DEFAULT_I18NEXT_DETECTION_OPTIONS;
|
|
19
|
+
|
|
20
|
+
// Get detection order, excluding 'path' and browser-only detectors
|
|
21
|
+
const order = (mergedDetection.order || []).filter(
|
|
22
|
+
(item: string) =>
|
|
23
|
+
!['path', 'localStorage', 'navigator', 'htmlTag', 'subdomain'].includes(
|
|
24
|
+
item,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// If no order specified, use default server-side order
|
|
29
|
+
const detectionOrder =
|
|
30
|
+
order.length > 0 ? order : ['querystring', 'cookie', 'header'];
|
|
31
|
+
|
|
32
|
+
// Map i18next order to hono order
|
|
33
|
+
const honoOrder = detectionOrder.map(item => {
|
|
34
|
+
// Map 'querystring' to 'querystring', 'cookie' to 'cookie', 'header' to 'header'
|
|
35
|
+
if (item === 'querystring') return 'querystring';
|
|
36
|
+
if (item === 'cookie') return 'cookie';
|
|
37
|
+
if (item === 'header') return 'header';
|
|
38
|
+
return item;
|
|
39
|
+
}) as ('querystring' | 'cookie' | 'header' | 'path')[];
|
|
40
|
+
|
|
41
|
+
// Determine caches option
|
|
42
|
+
// hono languageDetector expects: false | "cookie"[] | undefined
|
|
43
|
+
const caches: false | ['cookie'] | undefined =
|
|
44
|
+
mergedDetection.caches === false
|
|
45
|
+
? false
|
|
46
|
+
: Array.isArray(mergedDetection.caches) &&
|
|
47
|
+
!mergedDetection.caches.includes('cookie')
|
|
48
|
+
? false
|
|
49
|
+
: (['cookie'] as ['cookie']);
|
|
50
|
+
|
|
51
|
+
const cookieMinutes = (mergedDetection as Record<string, unknown>)
|
|
52
|
+
.cookieMinutes;
|
|
53
|
+
const cookieMaxAge =
|
|
54
|
+
typeof cookieMinutes === 'number' && Number.isFinite(cookieMinutes)
|
|
55
|
+
? Math.max(0, Math.floor(cookieMinutes * 60))
|
|
56
|
+
: DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes * 60;
|
|
57
|
+
|
|
58
|
+
const cookieDomain = (mergedDetection as Record<string, unknown>)
|
|
59
|
+
.cookieDomain;
|
|
60
|
+
const cookieSecure = (mergedDetection as Record<string, unknown>)
|
|
61
|
+
.cookieSecure;
|
|
62
|
+
const cookieHttpOnly = (mergedDetection as Record<string, unknown>)
|
|
63
|
+
.cookieHttpOnly;
|
|
64
|
+
const cookieSameSite = (mergedDetection as Record<string, unknown>)
|
|
65
|
+
.cookieSameSite;
|
|
66
|
+
|
|
67
|
+
const normalizedCookieDomain =
|
|
68
|
+
typeof cookieDomain === 'string' ? cookieDomain : undefined;
|
|
69
|
+
|
|
70
|
+
// Keep cookie defaults aligned with i18next language detector behavior:
|
|
71
|
+
// language cookie should be readable from browser-side detector.
|
|
72
|
+
const cookieOptions = {
|
|
73
|
+
maxAge: cookieMaxAge,
|
|
74
|
+
sameSite:
|
|
75
|
+
cookieSameSite === 'None' || cookieSameSite === 'none'
|
|
76
|
+
? ('None' as const)
|
|
77
|
+
: cookieSameSite === 'Lax' || cookieSameSite === 'lax'
|
|
78
|
+
? ('Lax' as const)
|
|
79
|
+
: ('Strict' as const),
|
|
80
|
+
secure: typeof cookieSecure === 'boolean' ? cookieSecure : false,
|
|
81
|
+
httpOnly: typeof cookieHttpOnly === 'boolean' ? cookieHttpOnly : false,
|
|
82
|
+
...(normalizedCookieDomain ? { domain: normalizedCookieDomain } : {}),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
supportedLanguages: languages.length > 0 ? languages : [fallbackLanguage],
|
|
87
|
+
fallbackLanguage,
|
|
88
|
+
order: honoOrder,
|
|
89
|
+
lookupQueryString:
|
|
90
|
+
mergedDetection.lookupQuerystring ||
|
|
91
|
+
DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring ||
|
|
92
|
+
'lng',
|
|
93
|
+
lookupCookie:
|
|
94
|
+
mergedDetection.lookupCookie ||
|
|
95
|
+
DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie ||
|
|
96
|
+
'i18next',
|
|
97
|
+
lookupFromHeaderKey:
|
|
98
|
+
mergedDetection.lookupHeader ||
|
|
99
|
+
DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader ||
|
|
100
|
+
'accept-language',
|
|
101
|
+
...(caches !== undefined && { caches }),
|
|
102
|
+
...(caches !== false && { cookieOptions }),
|
|
103
|
+
ignoreCase: true,
|
|
104
|
+
};
|
|
105
|
+
};
|