@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.6 → 3.5.0-ultramodern.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +2 -1
- package/dist/cjs/cli/locales.js +15 -15
- package/dist/cjs/runtime/Link.js +8 -89
- package/dist/cjs/runtime/context.js +78 -91
- package/dist/cjs/runtime/contextHelpers.js +143 -0
- package/dist/cjs/runtime/core.js +32 -132
- package/dist/cjs/runtime/hooks.js +54 -21
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
- package/dist/cjs/runtime/i18n/detection/cache.js +150 -0
- package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
- package/dist/cjs/runtime/i18n/detection/index.js +8 -263
- package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
- package/dist/cjs/runtime/i18n/detection/language.js +62 -0
- package/dist/cjs/runtime/i18n/detection/path.js +49 -0
- package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
- package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
- package/dist/cjs/runtime/i18n/detection/types.js +18 -0
- package/dist/cjs/runtime/i18n/instance.js +34 -10
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +115 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- package/dist/cjs/runtime/routerAdapter.js +16 -8
- package/dist/cjs/runtime/utils.js +1 -9
- package/dist/cjs/server/apiPrefix.js +72 -0
- package/dist/cjs/server/detectorOptions.js +97 -0
- package/dist/cjs/server/index.js +17 -144
- package/dist/cjs/server/redirectPolicy.js +96 -0
- package/dist/cjs/shared/detection.js +2 -104
- package/dist/cjs/shared/localisedUrls/config.js +49 -0
- package/dist/cjs/shared/localisedUrls/index.js +91 -0
- package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +68 -0
- package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
- package/dist/cjs/shared/localisedUrls/redirect.js +103 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +88 -0
- package/dist/cjs/shared/localisedUrls/routes.js +130 -0
- package/dist/cjs/shared/localisedUrls/types.js +18 -0
- package/dist/cjs/shared/localisedUrls.js +28 -294
- package/dist/cjs/shared/utils.js +2 -8
- package/dist/esm/cli/index.mjs +2 -1
- package/dist/esm/cli/locales.mjs +13 -13
- package/dist/esm/runtime/Link.mjs +1 -82
- package/dist/esm/runtime/context.mjs +76 -92
- package/dist/esm/runtime/contextHelpers.mjs +87 -0
- package/dist/esm/runtime/core.mjs +30 -133
- package/dist/esm/runtime/hooks.mjs +56 -23
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm/runtime/i18n/detection/cache.mjs +103 -0
- package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
- package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
- package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
- package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
- package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
- package/dist/esm/runtime/i18n/instance.mjs +35 -8
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +77 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- package/dist/esm/runtime/routerAdapter.mjs +16 -8
- package/dist/esm/runtime/utils.mjs +2 -10
- package/dist/esm/server/apiPrefix.mjs +31 -0
- package/dist/esm/server/detectorOptions.mjs +59 -0
- package/dist/esm/server/index.mjs +8 -135
- package/dist/esm/server/redirectPolicy.mjs +46 -0
- package/dist/esm/shared/detection.mjs +1 -71
- package/dist/esm/shared/localisedUrls/config.mjs +11 -0
- package/dist/esm/shared/localisedUrls/index.mjs +7 -0
- package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
- package/dist/esm/shared/localisedUrls/redirect.mjs +56 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +47 -0
- package/dist/esm/shared/localisedUrls/routes.mjs +89 -0
- package/dist/esm/shared/localisedUrls/types.mjs +0 -0
- package/dist/esm/shared/localisedUrls.mjs +1 -283
- package/dist/esm/shared/utils.mjs +1 -1
- package/dist/esm-node/cli/index.mjs +2 -1
- package/dist/esm-node/cli/locales.mjs +13 -13
- package/dist/esm-node/runtime/Link.mjs +1 -82
- package/dist/esm-node/runtime/context.mjs +76 -92
- package/dist/esm-node/runtime/contextHelpers.mjs +88 -0
- package/dist/esm-node/runtime/core.mjs +30 -133
- package/dist/esm-node/runtime/hooks.mjs +56 -23
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +104 -0
- package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
- package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
- package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
- package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
- package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
- package/dist/esm-node/runtime/i18n/instance.mjs +35 -8
- package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
- package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
- package/dist/esm-node/runtime/providerComposition.mjs +78 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- package/dist/esm-node/runtime/routerAdapter.mjs +16 -8
- package/dist/esm-node/runtime/utils.mjs +2 -10
- package/dist/esm-node/server/apiPrefix.mjs +32 -0
- package/dist/esm-node/server/detectorOptions.mjs +60 -0
- package/dist/esm-node/server/index.mjs +8 -135
- package/dist/esm-node/server/redirectPolicy.mjs +47 -0
- package/dist/esm-node/shared/detection.mjs +1 -71
- package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/shared/localisedUrls/index.mjs +8 -0
- package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
- package/dist/esm-node/shared/localisedUrls/redirect.mjs +57 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -0
- package/dist/esm-node/shared/localisedUrls/routes.mjs +90 -0
- package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/shared/localisedUrls.mjs +1 -283
- package/dist/esm-node/shared/utils.mjs +1 -1
- package/dist/types/cli/locales.d.ts +1 -1
- package/dist/types/runtime/Link.d.ts +3 -7
- package/dist/types/runtime/context.d.ts +21 -5
- package/dist/types/runtime/contextHelpers.d.ts +29 -0
- package/dist/types/runtime/core.d.ts +4 -8
- package/dist/types/runtime/hooks.d.ts +3 -7
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
- package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
- package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
- package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
- package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
- package/dist/types/runtime/i18n/detection/middleware.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
- package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
- package/dist/types/runtime/i18n/detection/types.d.ts +32 -0
- package/dist/types/runtime/i18n/instance.d.ts +31 -27
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
- package/dist/types/runtime/i18n/utils.d.ts +14 -5
- package/dist/types/runtime/linkHelpers.d.ts +17 -0
- package/dist/types/runtime/pluginSetup.d.ts +32 -0
- package/dist/types/runtime/providerComposition.d.ts +16 -0
- package/dist/types/runtime/reactI18next.d.ts +7 -0
- package/dist/types/runtime/routerAdapter.d.ts +7 -6
- package/dist/types/runtime/utils.d.ts +7 -2
- package/dist/types/server/apiPrefix.d.ts +7 -0
- package/dist/types/server/detectorOptions.d.ts +21 -0
- package/dist/types/server/index.d.ts +1 -6
- package/dist/types/server/redirectPolicy.d.ts +22 -0
- package/dist/types/shared/detection.d.ts +1 -11
- package/dist/types/shared/localisedUrls/config.d.ts +9 -0
- package/dist/types/shared/localisedUrls/index.d.ts +9 -0
- package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/shared/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
- package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
- package/dist/types/shared/localisedUrls/types.d.ts +14 -0
- package/dist/types/shared/localisedUrls.d.ts +2 -36
- package/dist/types/shared/utils.d.ts +0 -2
- package/package.json +17 -20
- package/rstest.config.mts +8 -1
- package/src/cli/index.ts +5 -4
- package/src/cli/locales.ts +3 -3
- package/src/runtime/Link.tsx +11 -138
- package/src/runtime/context.tsx +170 -207
- package/src/runtime/contextHelpers.ts +242 -0
- package/src/runtime/core.tsx +48 -275
- package/src/runtime/hooks.ts +86 -24
- package/src/runtime/i18n/backend/defaults.node.ts +2 -2
- package/src/runtime/i18n/backend/middleware.common.ts +5 -4
- package/src/runtime/i18n/detection/cache.ts +189 -0
- package/src/runtime/i18n/detection/detector.ts +166 -0
- package/src/runtime/i18n/detection/index.ts +10 -641
- package/src/runtime/i18n/detection/initOptions.ts +74 -0
- package/src/runtime/i18n/detection/language.ts +64 -0
- package/src/runtime/i18n/detection/middleware.node.ts +26 -7
- package/src/runtime/i18n/detection/middleware.ts +15 -7
- package/src/runtime/i18n/detection/path.ts +41 -0
- package/src/runtime/i18n/detection/priority.ts +82 -0
- package/src/runtime/i18n/detection/ssr.ts +47 -0
- package/src/runtime/i18n/detection/types.ts +41 -0
- package/src/runtime/i18n/instance.ts +98 -34
- package/src/runtime/i18n/react-i18next.ts +1 -6
- package/src/runtime/i18n/utils.ts +18 -9
- package/src/runtime/linkHelpers.ts +174 -0
- package/src/runtime/pluginSetup.ts +189 -0
- package/src/runtime/providerComposition.tsx +148 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/runtime/routerAdapter.tsx +36 -15
- package/src/runtime/utils.ts +13 -35
- package/src/server/apiPrefix.ts +67 -0
- package/src/server/detectorOptions.ts +105 -0
- package/src/server/index.ts +14 -319
- package/src/server/redirectPolicy.ts +127 -0
- package/src/shared/detection.ts +1 -1
- package/src/shared/localisedUrls/config.ts +18 -0
- package/src/shared/localisedUrls/index.ts +24 -0
- package/src/shared/localisedUrls/normalise.ts +67 -0
- package/src/shared/localisedUrls/pathname.ts +60 -0
- package/src/shared/localisedUrls/patterns.ts +160 -0
- package/src/shared/localisedUrls/redirect.ts +93 -0
- package/src/shared/localisedUrls/resolve.ts +135 -0
- package/src/shared/localisedUrls/routes.ts +234 -0
- package/src/shared/localisedUrls/types.ts +18 -0
- package/src/shared/localisedUrls.ts +26 -623
- package/src/shared/utils.ts +2 -2
- package/tests/federatedI18nBoundary.client.test.tsx +112 -0
- package/tests/federatedI18nBoundary.test.tsx +120 -0
- package/tests/fixtures/localised-routes.golden.json +156 -0
- package/tests/link.test.tsx +111 -0
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
- package/tests/localisedUrls.test.ts +148 -2
- package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
- package/tests/redirectPolicy.test.ts +45 -0
- package/tests/routerAdapter.test.tsx +603 -0
- package/tests/type-fixture/fixture-globals.d.ts +11 -0
- package/tests/type-fixture/tsconfig.json +1 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const warnedTargets = new Set();
|
|
2
|
+
const warnOnce = (key, message)=>{
|
|
3
|
+
if ('development' !== process.env.NODE_ENV || warnedTargets.has(key)) return;
|
|
4
|
+
warnedTargets.add(key);
|
|
5
|
+
console.warn(message);
|
|
6
|
+
};
|
|
7
|
+
const interpolateRouteParams = (pathname, params)=>{
|
|
8
|
+
if (!/[$:*{]/.test(pathname)) return pathname;
|
|
9
|
+
const resolveParam = (name)=>{
|
|
10
|
+
const value = params?.[name];
|
|
11
|
+
return void 0 === value ? void 0 : String(value);
|
|
12
|
+
};
|
|
13
|
+
const segments = pathname.split('/').map((segment)=>{
|
|
14
|
+
if (!segment) return segment;
|
|
15
|
+
if (segment.startsWith('{-$') && segment.endsWith('}')) {
|
|
16
|
+
const value = resolveParam(segment.slice(3, -1));
|
|
17
|
+
return void 0 === value ? null : encodeURIComponent(value);
|
|
18
|
+
}
|
|
19
|
+
if ('$' === segment || '*' === segment) {
|
|
20
|
+
const value = resolveParam('_splat') ?? resolveParam('*');
|
|
21
|
+
return void 0 === value ? null : value.split('/').map(encodeURIComponent).join('/');
|
|
22
|
+
}
|
|
23
|
+
if (segment.startsWith('$')) {
|
|
24
|
+
const value = resolveParam(segment.slice(1));
|
|
25
|
+
if (void 0 === value) {
|
|
26
|
+
warnOnce(`missing-param:${pathname}:${segment}`, `[plugin-i18n] <Link to="${pathname}"> is missing required param "${segment.slice(1)}".`);
|
|
27
|
+
return segment;
|
|
28
|
+
}
|
|
29
|
+
return encodeURIComponent(value);
|
|
30
|
+
}
|
|
31
|
+
if (segment.startsWith(':')) {
|
|
32
|
+
const optional = segment.endsWith('?');
|
|
33
|
+
const name = segment.slice(1, optional ? -1 : void 0);
|
|
34
|
+
const value = resolveParam(name);
|
|
35
|
+
if (void 0 === value) {
|
|
36
|
+
if (optional) return null;
|
|
37
|
+
warnOnce(`missing-param:${pathname}:${segment}`, `[plugin-i18n] <Link to="${pathname}"> is missing required param "${name}".`);
|
|
38
|
+
return segment;
|
|
39
|
+
}
|
|
40
|
+
return encodeURIComponent(value);
|
|
41
|
+
}
|
|
42
|
+
return segment;
|
|
43
|
+
}).filter((segment)=>null !== segment);
|
|
44
|
+
return segments.join('/') || '/';
|
|
45
|
+
};
|
|
46
|
+
const normalizeSearch = (search, searchFromTo)=>{
|
|
47
|
+
if (search && 'object' == typeof search) {
|
|
48
|
+
const searchObject = {};
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
for (const [key, value] of Object.entries(search)){
|
|
51
|
+
if (null == value) continue;
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
const values = value.filter((item)=>null != item).map(String);
|
|
54
|
+
if (0 === values.length) continue;
|
|
55
|
+
searchObject[key] = values;
|
|
56
|
+
for (const item of values)params.append(key, item);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const stringValue = String(value);
|
|
60
|
+
searchObject[key] = stringValue;
|
|
61
|
+
params.append(key, stringValue);
|
|
62
|
+
}
|
|
63
|
+
const serialized = params.toString();
|
|
64
|
+
return {
|
|
65
|
+
searchString: serialized ? `?${serialized}` : '',
|
|
66
|
+
searchObject: Object.keys(searchObject).length > 0 ? searchObject : void 0
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const raw = 'string' == typeof search ? search : searchFromTo;
|
|
70
|
+
if (!raw) return {
|
|
71
|
+
searchString: '',
|
|
72
|
+
searchObject: void 0
|
|
73
|
+
};
|
|
74
|
+
const searchString = raw.startsWith('?') ? raw : `?${raw}`;
|
|
75
|
+
const searchObject = {};
|
|
76
|
+
new URLSearchParams(searchString).forEach((value, key)=>{
|
|
77
|
+
const existing = searchObject[key];
|
|
78
|
+
if (void 0 === existing) searchObject[key] = value;
|
|
79
|
+
else if (Array.isArray(existing)) existing.push(value);
|
|
80
|
+
else searchObject[key] = [
|
|
81
|
+
existing,
|
|
82
|
+
value
|
|
83
|
+
];
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
searchString,
|
|
87
|
+
searchObject
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const splitActiveProps = (active, activeProps)=>{
|
|
91
|
+
if (!active || !activeProps) return {};
|
|
92
|
+
return activeProps;
|
|
93
|
+
};
|
|
94
|
+
const mergeClassNames = (...values)=>{
|
|
95
|
+
const classNames = values.filter((value)=>'string' == typeof value && value.length > 0);
|
|
96
|
+
return classNames.length > 0 ? classNames.join(' ') : void 0;
|
|
97
|
+
};
|
|
98
|
+
export { interpolateRouteParams, mergeClassNames, normalizeSearch, splitActiveProps, warnOnce };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isBrowser } from "@modern-js/runtime";
|
|
2
|
+
import { merge } from "@modern-js/runtime-utils/merge";
|
|
3
|
+
import { getI18nInstance } from "./i18n/index.mjs";
|
|
4
|
+
import { mergeBackendOptions } from "./i18n/backend/index.mjs";
|
|
5
|
+
import { detectLanguageWithPriority, exportServerLngToWindow, mergeDetectionOptions } from "./i18n/detection/index.mjs";
|
|
6
|
+
import { useI18nextLanguageDetector } from "./i18n/detection/middleware.mjs";
|
|
7
|
+
import { getPathname } from "./utils.mjs";
|
|
8
|
+
let i18nLifecycleHelpersPromise;
|
|
9
|
+
function loadI18nLifecycleHelpers() {
|
|
10
|
+
i18nLifecycleHelpersPromise ??= Promise.all([
|
|
11
|
+
import("./i18n/backend/middleware.mjs"),
|
|
12
|
+
import("./i18n/utils.mjs")
|
|
13
|
+
]).then(([backendMiddleware, utils])=>({
|
|
14
|
+
useI18nextBackend: backendMiddleware.useI18nextBackend,
|
|
15
|
+
changeI18nLanguage: utils.changeI18nLanguage,
|
|
16
|
+
ensureLanguageMatch: utils.ensureLanguageMatch,
|
|
17
|
+
initializeI18nInstance: utils.initializeI18nInstance,
|
|
18
|
+
setupClonedInstance: utils.setupClonedInstance
|
|
19
|
+
}));
|
|
20
|
+
return i18nLifecycleHelpersPromise;
|
|
21
|
+
}
|
|
22
|
+
async function setupI18nBeforeRender(context, options) {
|
|
23
|
+
const { api, userI18nInstance, initOptions, backend, backendEnabled, i18nextDetector, detection, localePathRedirect, languages, fallbackLanguage, resolveReactI18nextIntegration, setI18nextProvider } = options;
|
|
24
|
+
const { useI18nextBackend, changeI18nLanguage, ensureLanguageMatch, initializeI18nInstance, setupClonedInstance } = await loadI18nLifecycleHelpers();
|
|
25
|
+
let i18nInstance = await getI18nInstance(userI18nInstance);
|
|
26
|
+
const { i18n: otherConfig } = api.getRuntimeConfig();
|
|
27
|
+
const { initOptions: otherInitOptions } = otherConfig || {};
|
|
28
|
+
const userInitOptions = merge(otherInitOptions || {}, initOptions || {});
|
|
29
|
+
const reactI18nextIntegration = await resolveReactI18nextIntegration();
|
|
30
|
+
setI18nextProvider(reactI18nextIntegration?.I18nextProvider ?? null);
|
|
31
|
+
if (reactI18nextIntegration?.initReactI18next) i18nInstance.use(reactI18nextIntegration.initReactI18next);
|
|
32
|
+
const pathname = getPathname(context);
|
|
33
|
+
if (i18nextDetector) useI18nextLanguageDetector(i18nInstance);
|
|
34
|
+
const mergedDetection = mergeDetectionOptions(i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
35
|
+
const mergedBackend = mergeBackendOptions(backend, userInitOptions);
|
|
36
|
+
const hasSdkConfig = 'function' == typeof userInitOptions?.backend?.sdk || mergedBackend?.sdk && 'function' == typeof mergedBackend.sdk;
|
|
37
|
+
if (mergedBackend && (backendEnabled || hasSdkConfig)) useI18nextBackend(i18nInstance, mergedBackend);
|
|
38
|
+
const { finalLanguage } = await detectLanguageWithPriority(i18nInstance, {
|
|
39
|
+
languages,
|
|
40
|
+
fallbackLanguage,
|
|
41
|
+
localePathRedirect,
|
|
42
|
+
i18nextDetector,
|
|
43
|
+
detection,
|
|
44
|
+
userInitOptions,
|
|
45
|
+
mergedBackend,
|
|
46
|
+
pathname,
|
|
47
|
+
ssrContext: context.ssrContext
|
|
48
|
+
});
|
|
49
|
+
await initializeI18nInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, mergedDetection, mergedBackend, userInitOptions);
|
|
50
|
+
if (!isBrowser() && i18nInstance.cloneInstance) {
|
|
51
|
+
i18nInstance = i18nInstance.cloneInstance();
|
|
52
|
+
await setupClonedInstance(i18nInstance, finalLanguage, fallbackLanguage, languages, backendEnabled, backend, i18nextDetector, detection, localePathRedirect, userInitOptions);
|
|
53
|
+
}
|
|
54
|
+
if (localePathRedirect) await ensureLanguageMatch(i18nInstance, finalLanguage);
|
|
55
|
+
if (!isBrowser()) exportServerLngToWindow(context, finalLanguage);
|
|
56
|
+
context.i18nInstance = i18nInstance;
|
|
57
|
+
context.changeLanguage = async (newLang)=>{
|
|
58
|
+
await changeI18nLanguage(i18nInstance, newLang, {
|
|
59
|
+
detectionOptions: mergedDetection
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return i18nInstance;
|
|
63
|
+
}
|
|
64
|
+
export { setupI18nBeforeRender };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RuntimeContext } from "@modern-js/runtime";
|
|
3
|
+
import { Helmet } from "@modern-js/runtime/head";
|
|
4
|
+
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { ModernI18nProvider } from "./context.mjs";
|
|
6
|
+
import { createContextValue, useClientSideRedirect, useLanguageSync, useSdkResourcesLoader } from "./hooks.mjs";
|
|
7
|
+
import { getI18nextInstanceForProvider } from "./i18n/instance.mjs";
|
|
8
|
+
import { useI18nRouterAdapter } from "./routerAdapter.mjs";
|
|
9
|
+
const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
10
|
+
const { entryName, htmlLangAttr, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls, getLatestI18nInstance, getI18nextProvider } = options;
|
|
11
|
+
const runtimeContext = useContext(RuntimeContext);
|
|
12
|
+
const i18nInstance = runtimeContext.i18nInstance || getLatestI18nInstance();
|
|
13
|
+
const initialLang = useMemo(()=>i18nInstance?.language || fallbackLanguage, [
|
|
14
|
+
i18nInstance?.language,
|
|
15
|
+
fallbackLanguage
|
|
16
|
+
]);
|
|
17
|
+
const [lang, setLang] = useState(initialLang);
|
|
18
|
+
const [forceUpdate, setForceUpdate] = useState(0);
|
|
19
|
+
const prevLangRef = useRef(lang);
|
|
20
|
+
const { location } = useI18nRouterAdapter();
|
|
21
|
+
useEffect(()=>{
|
|
22
|
+
if (i18nInstance?.language) {
|
|
23
|
+
const translator = i18nInstance.translator;
|
|
24
|
+
if (translator) translator.language = i18nInstance.language;
|
|
25
|
+
}
|
|
26
|
+
}, [
|
|
27
|
+
i18nInstance?.language
|
|
28
|
+
]);
|
|
29
|
+
useEffect(()=>{
|
|
30
|
+
prevLangRef.current = lang;
|
|
31
|
+
}, [
|
|
32
|
+
lang
|
|
33
|
+
]);
|
|
34
|
+
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
35
|
+
const synchronizeLanguage = useLanguageSync(i18nInstance, localePathRedirect, languages, location?.pathname, prevLangRef, setLang);
|
|
36
|
+
useClientSideRedirect(i18nInstance, localePathRedirect, languages, fallbackLanguage, ignoreRedirectRoutes, localisedUrls);
|
|
37
|
+
const contextValue = useMemo(()=>createContextValue(lang, i18nInstance, entryName, languages, localePathRedirect, ignoreRedirectRoutes, localisedUrls, setLang, synchronizeLanguage), [
|
|
38
|
+
lang,
|
|
39
|
+
i18nInstance,
|
|
40
|
+
entryName,
|
|
41
|
+
languages,
|
|
42
|
+
localePathRedirect,
|
|
43
|
+
ignoreRedirectRoutes,
|
|
44
|
+
localisedUrls,
|
|
45
|
+
forceUpdate,
|
|
46
|
+
synchronizeLanguage
|
|
47
|
+
]);
|
|
48
|
+
const children = props.children;
|
|
49
|
+
let appContent = App ? /*#__PURE__*/ jsx(App, {
|
|
50
|
+
...props,
|
|
51
|
+
children: children
|
|
52
|
+
}) : children;
|
|
53
|
+
if (i18nInstance) {
|
|
54
|
+
const I18nextProvider = getI18nextProvider();
|
|
55
|
+
if (I18nextProvider) {
|
|
56
|
+
const i18nextInstanceForProvider = getI18nextInstanceForProvider(i18nInstance, lang);
|
|
57
|
+
appContent = /*#__PURE__*/ jsx(I18nextProvider, {
|
|
58
|
+
i18n: i18nextInstanceForProvider,
|
|
59
|
+
children: appContent
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
64
|
+
children: [
|
|
65
|
+
Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
|
|
66
|
+
htmlAttributes: {
|
|
67
|
+
lang
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
/*#__PURE__*/ jsx(ModernI18nProvider, {
|
|
71
|
+
value: contextValue,
|
|
72
|
+
children: appContent
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
export { createI18nRootWrapper };
|
|
@@ -65,28 +65,36 @@ const useI18nRouterAdapter = ()=>{
|
|
|
65
65
|
const [, setRouterVersion] = useState(0);
|
|
66
66
|
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
67
67
|
useEffect(()=>{
|
|
68
|
-
if ('tanstack' !== framework) return;
|
|
69
68
|
const router = getRouterInstance(internalContext, contextRouter);
|
|
70
69
|
if (!router) return;
|
|
71
70
|
const update = ()=>setRouterVersion((version)=>version + 1);
|
|
72
71
|
const unsubscribers = [];
|
|
73
|
-
if ('function' == typeof router.
|
|
74
|
-
const
|
|
72
|
+
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
73
|
+
const subscribe = router.subscribe;
|
|
74
|
+
const unsubscribe = subscribe.call(router, update);
|
|
75
75
|
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
76
76
|
}
|
|
77
|
-
if ('function' == typeof router.subscribe)
|
|
78
|
-
|
|
79
|
-
'onBeforeLoad'
|
|
80
|
-
]){
|
|
81
|
-
const unsubscribe = router.subscribe(eventType, update);
|
|
77
|
+
if ('tanstack' === framework && 'function' == typeof router.stores?.location?.subscribe) {
|
|
78
|
+
const unsubscribe = router.stores.location.subscribe(update);
|
|
82
79
|
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
83
80
|
}
|
|
81
|
+
if ('tanstack' === framework && 'function' == typeof router.subscribe) {
|
|
82
|
+
const subscribe = router.subscribe;
|
|
83
|
+
for (const eventType of [
|
|
84
|
+
'onBeforeNavigate',
|
|
85
|
+
'onBeforeLoad'
|
|
86
|
+
]){
|
|
87
|
+
const unsubscribe = subscribe.call(router, eventType, update);
|
|
88
|
+
if ('function' == typeof unsubscribe) unsubscribers.push(unsubscribe);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
84
91
|
return ()=>{
|
|
85
92
|
for (const unsubscribe of unsubscribers)unsubscribe();
|
|
86
93
|
};
|
|
87
94
|
}, [
|
|
88
95
|
contextRouter,
|
|
89
96
|
framework,
|
|
97
|
+
inReactRouter,
|
|
90
98
|
internalContext
|
|
91
99
|
]);
|
|
92
100
|
const navigate = useCallback((href, options)=>{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isBrowser } from "@modern-js/runtime";
|
|
2
2
|
import { getGlobalBasename } from "@modern-js/runtime/context";
|
|
3
|
-
import { localiseTargetPathname } from "../shared/localisedUrls.mjs";
|
|
3
|
+
import { localiseTargetPathname, shouldSkipLocaleRedirect } from "../shared/localisedUrls.mjs";
|
|
4
4
|
const getPathname = (context)=>{
|
|
5
5
|
if (isBrowser()) return window.location.pathname;
|
|
6
6
|
return context.ssrContext?.request?.pathname || '/';
|
|
@@ -51,13 +51,5 @@ const detectLanguageFromPath = (pathname, languages, localePathRedirect)=>{
|
|
|
51
51
|
detected: false
|
|
52
52
|
};
|
|
53
53
|
};
|
|
54
|
-
const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>
|
|
55
|
-
if (!ignoreRedirectRoutes) return false;
|
|
56
|
-
const segments = pathname.split('/').filter(Boolean);
|
|
57
|
-
let pathWithoutLang = pathname;
|
|
58
|
-
if (segments.length > 0 && languages.includes(segments[0])) pathWithoutLang = `/${segments.slice(1).join('/')}`;
|
|
59
|
-
const normalizedPath = pathWithoutLang.startsWith('/') ? pathWithoutLang : `/${pathWithoutLang}`;
|
|
60
|
-
if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
|
|
61
|
-
return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
|
|
62
|
-
};
|
|
54
|
+
const shouldIgnoreRedirect = (pathname, languages, ignoreRedirectRoutes)=>shouldSkipLocaleRedirect(pathname, languages, ignoreRedirectRoutes);
|
|
63
55
|
export { buildLocalizedUrl, detectLanguageFromPath, getEntryPath, getLanguageFromPath, getPathname, shouldIgnoreRedirect, splitUrlTarget };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const normalizeApiPrefix = (prefix)=>{
|
|
2
|
+
const trimmedPrefix = prefix.trim();
|
|
3
|
+
if (!trimmedPrefix) return null;
|
|
4
|
+
const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
|
|
5
|
+
const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
|
|
6
|
+
const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
|
|
7
|
+
return '/' === normalizedPrefix ? null : normalizedPrefix;
|
|
8
|
+
};
|
|
9
|
+
const collectApiPrefixes = (routes, bffPrefix)=>{
|
|
10
|
+
const prefixes = new Set();
|
|
11
|
+
for (const route of routes){
|
|
12
|
+
if (!route.isApi || !route.urlPath) continue;
|
|
13
|
+
const normalizedPrefix = normalizeApiPrefix(route.urlPath);
|
|
14
|
+
if (normalizedPrefix) prefixes.add(normalizedPrefix);
|
|
15
|
+
}
|
|
16
|
+
const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
|
|
17
|
+
bffPrefix
|
|
18
|
+
] : [];
|
|
19
|
+
for (const prefix of bffPrefixes){
|
|
20
|
+
const normalizedPrefix = normalizeApiPrefix(prefix);
|
|
21
|
+
if (normalizedPrefix) prefixes.add(normalizedPrefix);
|
|
22
|
+
}
|
|
23
|
+
return [
|
|
24
|
+
...prefixes
|
|
25
|
+
];
|
|
26
|
+
};
|
|
27
|
+
const matchesApiPrefix = (pathname, apiPrefixes)=>{
|
|
28
|
+
const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
29
|
+
return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
|
|
30
|
+
};
|
|
31
|
+
export { collectApiPrefixes, matchesApiPrefix };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DEFAULT_I18NEXT_DETECTION_OPTIONS, mergeDetectionOptions } from "../runtime/i18n/detection/config.mjs";
|
|
2
|
+
const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
|
|
3
|
+
const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
|
|
4
|
+
const order = (mergedDetection.order || []).filter((item)=>![
|
|
5
|
+
'path',
|
|
6
|
+
'localStorage',
|
|
7
|
+
'navigator',
|
|
8
|
+
'htmlTag',
|
|
9
|
+
'subdomain'
|
|
10
|
+
].includes(item));
|
|
11
|
+
const detectionOrder = order.length > 0 ? order : [
|
|
12
|
+
'querystring',
|
|
13
|
+
'cookie',
|
|
14
|
+
'header'
|
|
15
|
+
];
|
|
16
|
+
const honoOrder = detectionOrder.map((item)=>{
|
|
17
|
+
if ('querystring' === item) return 'querystring';
|
|
18
|
+
if ('cookie' === item) return 'cookie';
|
|
19
|
+
if ('header' === item) return 'header';
|
|
20
|
+
return item;
|
|
21
|
+
});
|
|
22
|
+
const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
|
|
23
|
+
'cookie'
|
|
24
|
+
];
|
|
25
|
+
const cookieMinutes = mergedDetection.cookieMinutes;
|
|
26
|
+
const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
|
|
27
|
+
const cookieDomain = mergedDetection.cookieDomain;
|
|
28
|
+
const cookieSecure = mergedDetection.cookieSecure;
|
|
29
|
+
const cookieHttpOnly = mergedDetection.cookieHttpOnly;
|
|
30
|
+
const cookieSameSite = mergedDetection.cookieSameSite;
|
|
31
|
+
const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
|
|
32
|
+
const cookieOptions = {
|
|
33
|
+
maxAge: cookieMaxAge,
|
|
34
|
+
sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
|
|
35
|
+
secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
|
|
36
|
+
httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
|
|
37
|
+
...normalizedCookieDomain ? {
|
|
38
|
+
domain: normalizedCookieDomain
|
|
39
|
+
} : {}
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
supportedLanguages: languages.length > 0 ? languages : [
|
|
43
|
+
fallbackLanguage
|
|
44
|
+
],
|
|
45
|
+
fallbackLanguage,
|
|
46
|
+
order: honoOrder,
|
|
47
|
+
lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
|
|
48
|
+
lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
|
|
49
|
+
lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
|
|
50
|
+
...void 0 !== caches && {
|
|
51
|
+
caches
|
|
52
|
+
},
|
|
53
|
+
...false !== caches && {
|
|
54
|
+
cookieOptions
|
|
55
|
+
},
|
|
56
|
+
ignoreCase: true
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export { convertToHonoLanguageDetectorOptions };
|
|
@@ -1,137 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { localiseTargetPathname, resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
|
|
1
|
+
import { resolveLocalisedUrlsConfig } from "../shared/localisedUrls.mjs";
|
|
3
2
|
import { getLocaleDetectionOptions } from "../shared/utils.mjs";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const prefixedPath = trimmedPrefix.startsWith('/') ? trimmedPrefix : `/${trimmedPrefix}`;
|
|
10
|
-
const withoutWildcard = prefixedPath.replace(/\/\*$/, '');
|
|
11
|
-
const normalizedPrefix = withoutWildcard.length > 1 ? withoutWildcard.replace(/\/+$/, '') : withoutWildcard;
|
|
12
|
-
return '/' === normalizedPrefix ? null : normalizedPrefix;
|
|
13
|
-
};
|
|
14
|
-
const collectApiPrefixes = (routes, bffPrefix)=>{
|
|
15
|
-
const prefixes = new Set();
|
|
16
|
-
for (const route of routes){
|
|
17
|
-
if (!route.isApi || !route.urlPath) continue;
|
|
18
|
-
const normalizedPrefix = normalizeApiPrefix(route.urlPath);
|
|
19
|
-
if (normalizedPrefix) prefixes.add(normalizedPrefix);
|
|
20
|
-
}
|
|
21
|
-
const bffPrefixes = Array.isArray(bffPrefix) ? bffPrefix : bffPrefix ? [
|
|
22
|
-
bffPrefix
|
|
23
|
-
] : [];
|
|
24
|
-
for (const prefix of bffPrefixes){
|
|
25
|
-
const normalizedPrefix = normalizeApiPrefix(prefix);
|
|
26
|
-
if (normalizedPrefix) prefixes.add(normalizedPrefix);
|
|
27
|
-
}
|
|
28
|
-
return [
|
|
29
|
-
...prefixes
|
|
30
|
-
];
|
|
31
|
-
};
|
|
32
|
-
const matchesApiPrefix = (pathname, apiPrefixes)=>{
|
|
33
|
-
const normalizedPathname = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
34
|
-
return apiPrefixes.some((prefix)=>normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`));
|
|
35
|
-
};
|
|
36
|
-
const convertToHonoLanguageDetectorOptions = (languages, fallbackLanguage, detectionOptions)=>{
|
|
37
|
-
const mergedDetection = detectionOptions ? mergeDetectionOptions(detectionOptions) : DEFAULT_I18NEXT_DETECTION_OPTIONS;
|
|
38
|
-
const order = (mergedDetection.order || []).filter((item)=>![
|
|
39
|
-
'path',
|
|
40
|
-
'localStorage',
|
|
41
|
-
'navigator',
|
|
42
|
-
'htmlTag',
|
|
43
|
-
'subdomain'
|
|
44
|
-
].includes(item));
|
|
45
|
-
const detectionOrder = order.length > 0 ? order : [
|
|
46
|
-
'querystring',
|
|
47
|
-
'cookie',
|
|
48
|
-
'header'
|
|
49
|
-
];
|
|
50
|
-
const honoOrder = detectionOrder.map((item)=>{
|
|
51
|
-
if ('querystring' === item) return 'querystring';
|
|
52
|
-
if ('cookie' === item) return 'cookie';
|
|
53
|
-
if ('header' === item) return 'header';
|
|
54
|
-
return item;
|
|
55
|
-
});
|
|
56
|
-
const caches = false === mergedDetection.caches ? false : Array.isArray(mergedDetection.caches) && !mergedDetection.caches.includes('cookie') ? false : [
|
|
57
|
-
'cookie'
|
|
58
|
-
];
|
|
59
|
-
const cookieMinutes = mergedDetection.cookieMinutes;
|
|
60
|
-
const cookieMaxAge = 'number' == typeof cookieMinutes && Number.isFinite(cookieMinutes) ? Math.max(0, Math.floor(60 * cookieMinutes)) : 60 * DEFAULT_I18NEXT_DETECTION_OPTIONS.cookieMinutes;
|
|
61
|
-
const cookieDomain = mergedDetection.cookieDomain;
|
|
62
|
-
const cookieSecure = mergedDetection.cookieSecure;
|
|
63
|
-
const cookieHttpOnly = mergedDetection.cookieHttpOnly;
|
|
64
|
-
const cookieSameSite = mergedDetection.cookieSameSite;
|
|
65
|
-
const normalizedCookieDomain = 'string' == typeof cookieDomain ? cookieDomain : void 0;
|
|
66
|
-
const cookieOptions = {
|
|
67
|
-
maxAge: cookieMaxAge,
|
|
68
|
-
sameSite: 'None' === cookieSameSite || 'none' === cookieSameSite ? 'None' : 'Lax' === cookieSameSite || 'lax' === cookieSameSite ? 'Lax' : 'Strict',
|
|
69
|
-
secure: 'boolean' == typeof cookieSecure ? cookieSecure : false,
|
|
70
|
-
httpOnly: 'boolean' == typeof cookieHttpOnly ? cookieHttpOnly : false,
|
|
71
|
-
...normalizedCookieDomain ? {
|
|
72
|
-
domain: normalizedCookieDomain
|
|
73
|
-
} : {}
|
|
74
|
-
};
|
|
75
|
-
return {
|
|
76
|
-
supportedLanguages: languages.length > 0 ? languages : [
|
|
77
|
-
fallbackLanguage
|
|
78
|
-
],
|
|
79
|
-
fallbackLanguage,
|
|
80
|
-
order: honoOrder,
|
|
81
|
-
lookupQueryString: mergedDetection.lookupQuerystring || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupQuerystring || 'lng',
|
|
82
|
-
lookupCookie: mergedDetection.lookupCookie || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupCookie || 'i18next',
|
|
83
|
-
lookupFromHeaderKey: mergedDetection.lookupHeader || DEFAULT_I18NEXT_DETECTION_OPTIONS.lookupHeader || 'accept-language',
|
|
84
|
-
...void 0 !== caches && {
|
|
85
|
-
caches
|
|
86
|
-
},
|
|
87
|
-
...false !== caches && {
|
|
88
|
-
cookieOptions
|
|
89
|
-
},
|
|
90
|
-
ignoreCase: true
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
|
|
94
|
-
if (!ignoreRedirectRoutes) return false;
|
|
95
|
-
const basePath = urlPath.replace('/*', '');
|
|
96
|
-
const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
97
|
-
const normalizedPath = remainingPath.startsWith('/') ? remainingPath : `/${remainingPath}`;
|
|
98
|
-
if ('function' == typeof ignoreRedirectRoutes) return ignoreRedirectRoutes(normalizedPath);
|
|
99
|
-
return ignoreRedirectRoutes.some((pattern)=>normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`));
|
|
100
|
-
};
|
|
101
|
-
const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
|
|
102
|
-
if (staticRoutePrefixes.some((prefix)=>pathname.startsWith(`${prefix}/`) || pathname === prefix)) return true;
|
|
103
|
-
const standardStaticPrefixes = [
|
|
104
|
-
'/static/',
|
|
105
|
-
'/upload/'
|
|
106
|
-
];
|
|
107
|
-
if (standardStaticPrefixes.some((prefix)=>pathname.startsWith(prefix))) return true;
|
|
108
|
-
const pathSegments = pathname.split('/').filter(Boolean);
|
|
109
|
-
if (pathSegments.length > 0 && languages.includes(pathSegments[0])) {
|
|
110
|
-
const pathWithoutLang = '/' + pathSegments.slice(1).join('/');
|
|
111
|
-
if (standardStaticPrefixes.some((prefix)=>pathWithoutLang.startsWith(prefix)) || staticRoutePrefixes.some((prefix)=>pathWithoutLang.startsWith(`${prefix}/`) || pathWithoutLang === prefix)) return true;
|
|
112
|
-
}
|
|
113
|
-
return false;
|
|
114
|
-
};
|
|
115
|
-
const getLanguageFromPath = (req, urlPath, languages)=>{
|
|
116
|
-
const url = new URL(req.url, `http://${req.header().host}`);
|
|
117
|
-
const pathname = url.pathname;
|
|
118
|
-
const basePath = urlPath.replace('/*', '');
|
|
119
|
-
const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
120
|
-
const segments = remainingPath.split('/').filter(Boolean);
|
|
121
|
-
const firstSegment = segments[0];
|
|
122
|
-
if (languages.includes(firstSegment)) return firstSegment;
|
|
123
|
-
return null;
|
|
124
|
-
};
|
|
125
|
-
const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
|
|
126
|
-
const url = new URL(req.url);
|
|
127
|
-
const pathname = url.pathname;
|
|
128
|
-
const basePath = urlPath.replace('/*', '');
|
|
129
|
-
const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
130
|
-
const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
|
|
131
|
-
const suffix = `${url.search}${url.hash}`;
|
|
132
|
-
const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
|
|
133
|
-
return localizedUrl;
|
|
134
|
-
};
|
|
3
|
+
import { collectApiPrefixes, matchesApiPrefix } from "./apiPrefix.mjs";
|
|
4
|
+
import { convertToHonoLanguageDetectorOptions } from "./detectorOptions.mjs";
|
|
5
|
+
import { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect } from "./redirectPolicy.mjs";
|
|
6
|
+
import * as __rspack_external__modern_js_server_core_hono_0af3baa7 from "@modern-js/server-core/hono";
|
|
7
|
+
const { languageDetector: languageDetector } = __rspack_external__modern_js_server_core_hono_0af3baa7;
|
|
135
8
|
const i18nServerPlugin = (options)=>({
|
|
136
9
|
name: '@modern-js/plugin-i18n/server',
|
|
137
10
|
setup: (api)=>{
|
|
@@ -200,12 +73,12 @@ const i18nServerPlugin = (options)=>({
|
|
|
200
73
|
if (i18nextDetector) detectedLanguage = c.get('language') || null;
|
|
201
74
|
const targetLanguage = detectedLanguage || fallbackLanguage;
|
|
202
75
|
const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
|
|
203
|
-
return
|
|
76
|
+
return createLocaleRedirectResponse(localizedUrl);
|
|
204
77
|
}
|
|
205
78
|
const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
|
|
206
79
|
if (localisedUrlsConfig.enabled) {
|
|
207
80
|
const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
|
|
208
|
-
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return
|
|
81
|
+
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return createLocaleRedirectResponse(expectedUrl);
|
|
209
82
|
}
|
|
210
83
|
await next();
|
|
211
84
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "../shared/localisedUrls.mjs";
|
|
2
|
+
const stripUrlPathPrefix = (pathname, urlPath)=>{
|
|
3
|
+
const basePath = urlPath.replace('/*', '');
|
|
4
|
+
if (!basePath || '/' === basePath) return pathname;
|
|
5
|
+
const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
6
|
+
return remainingPath || '/';
|
|
7
|
+
};
|
|
8
|
+
const shouldIgnoreRedirect = (pathname, urlPath, ignoreRedirectRoutes)=>{
|
|
9
|
+
const remainingPath = stripUrlPathPrefix(pathname, urlPath);
|
|
10
|
+
return shouldSkipLocaleRedirect(remainingPath, [], ignoreRedirectRoutes);
|
|
11
|
+
};
|
|
12
|
+
const isStaticResourceRequest = (pathname, staticRoutePrefixes, languages = [])=>{
|
|
13
|
+
if (isDefaultLocaleRedirectSkipPath(pathname, languages)) return true;
|
|
14
|
+
const matchesStaticRoutePrefix = (targetPathname)=>staticRoutePrefixes.some((prefix)=>matchesPathPrefix(targetPathname, prefix));
|
|
15
|
+
if (matchesStaticRoutePrefix(pathname)) return true;
|
|
16
|
+
const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
|
|
17
|
+
return pathWithoutLanguage !== pathname && matchesStaticRoutePrefix(pathWithoutLanguage);
|
|
18
|
+
};
|
|
19
|
+
const getLanguageFromPath = (req, urlPath, languages)=>{
|
|
20
|
+
const url = new URL(req.url, `http://${req.header().host}`);
|
|
21
|
+
const pathname = url.pathname;
|
|
22
|
+
const remainingPath = stripUrlPathPrefix(pathname, urlPath);
|
|
23
|
+
const segments = remainingPath.split('/').filter(Boolean);
|
|
24
|
+
const firstSegment = segments[0];
|
|
25
|
+
if (languages.includes(firstSegment)) return firstSegment;
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
|
|
29
|
+
const url = new URL(req.url);
|
|
30
|
+
const pathname = url.pathname;
|
|
31
|
+
const basePath = urlPath.replace('/*', '');
|
|
32
|
+
const remainingPath = stripUrlPathPrefix(pathname, urlPath);
|
|
33
|
+
const newPathname = localiseTargetPathname(remainingPath, language, languages, localisedUrls);
|
|
34
|
+
const suffix = `${url.search}${url.hash}`;
|
|
35
|
+
const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
|
|
36
|
+
return localizedUrl;
|
|
37
|
+
};
|
|
38
|
+
const createLocaleRedirectResponse = (location)=>new Response(null, {
|
|
39
|
+
status: 302,
|
|
40
|
+
headers: {
|
|
41
|
+
'Cache-Control': 'private, no-store',
|
|
42
|
+
Location: location,
|
|
43
|
+
Vary: 'Accept-Language, Cookie'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
export { buildLocalizedUrl, createLocaleRedirectResponse, getLanguageFromPath, isStaticResourceRequest, shouldIgnoreRedirect };
|