@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.36 → 3.5.0-ultramodern.38
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/locales.js +15 -15
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
- package/dist/cjs/runtime/i18n/detection/cache.js +3 -2
- package/dist/cjs/runtime/i18n/instance.js +7 -7
- package/dist/cjs/runtime/utils.js +1 -9
- package/dist/cjs/server/redirectPolicy.js +14 -20
- package/dist/cjs/shared/detection.js +2 -104
- package/dist/cjs/shared/localisedUrls/index.js +16 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +5 -2
- package/dist/cjs/shared/localisedUrls/redirect.js +95 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +29 -32
- package/dist/cjs/shared/localisedUrls.js +15 -0
- package/dist/cjs/shared/utils.js +2 -8
- package/dist/esm/cli/locales.mjs +13 -13
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm/runtime/i18n/detection/cache.mjs +3 -2
- package/dist/esm/runtime/i18n/instance.mjs +8 -5
- package/dist/esm/runtime/utils.mjs +2 -10
- package/dist/esm/server/index.mjs +2 -2
- package/dist/esm/server/redirectPolicy.mjs +15 -21
- package/dist/esm/shared/detection.mjs +1 -71
- package/dist/esm/shared/localisedUrls/index.mjs +2 -1
- package/dist/esm/shared/localisedUrls/pathname.mjs +1 -1
- package/dist/esm/shared/localisedUrls/redirect.mjs +48 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +29 -32
- package/dist/esm/shared/localisedUrls.mjs +1 -1
- package/dist/esm/shared/utils.mjs +1 -1
- package/dist/esm-node/cli/locales.mjs +13 -13
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +3 -2
- package/dist/esm-node/runtime/i18n/instance.mjs +8 -5
- package/dist/esm-node/runtime/utils.mjs +2 -10
- package/dist/esm-node/server/index.mjs +2 -2
- package/dist/esm-node/server/redirectPolicy.mjs +15 -21
- package/dist/esm-node/shared/detection.mjs +1 -71
- package/dist/esm-node/shared/localisedUrls/index.mjs +2 -1
- package/dist/esm-node/shared/localisedUrls/pathname.mjs +1 -1
- package/dist/esm-node/shared/localisedUrls/redirect.mjs +49 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +29 -32
- package/dist/esm-node/shared/localisedUrls.mjs +1 -1
- package/dist/esm-node/shared/utils.mjs +1 -1
- package/dist/types/cli/locales.d.ts +1 -1
- package/dist/types/runtime/context.d.ts +4 -3
- package/dist/types/runtime/contextHelpers.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
- package/dist/types/runtime/i18n/detection/cache.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/detector.d.ts +2 -2
- 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/types.d.ts +8 -5
- package/dist/types/runtime/i18n/instance.d.ts +28 -26
- package/dist/types/runtime/i18n/utils.d.ts +14 -5
- package/dist/types/runtime/pluginSetup.d.ts +1 -1
- package/dist/types/runtime/providerComposition.d.ts +2 -1
- package/dist/types/runtime/routerAdapter.d.ts +7 -6
- package/dist/types/server/redirectPolicy.d.ts +14 -8
- package/dist/types/shared/detection.d.ts +1 -11
- package/dist/types/shared/localisedUrls/index.d.ts +3 -1
- package/dist/types/shared/localisedUrls/pathname.d.ts +1 -0
- package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +2 -3
- package/dist/types/shared/localisedUrls.d.ts +2 -2
- package/dist/types/shared/utils.d.ts +0 -2
- package/package.json +16 -18
- package/src/cli/index.ts +2 -2
- package/src/cli/locales.ts +3 -3
- package/src/runtime/context.tsx +3 -3
- package/src/runtime/contextHelpers.ts +3 -3
- package/src/runtime/hooks.ts +10 -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 +23 -9
- package/src/runtime/i18n/detection/detector.ts +15 -6
- package/src/runtime/i18n/detection/middleware.node.ts +26 -7
- package/src/runtime/i18n/detection/middleware.ts +15 -7
- package/src/runtime/i18n/detection/types.ts +13 -5
- package/src/runtime/i18n/instance.ts +56 -31
- package/src/runtime/i18n/utils.ts +18 -9
- package/src/runtime/pluginSetup.ts +1 -1
- package/src/runtime/providerComposition.tsx +1 -1
- package/src/runtime/routerAdapter.tsx +6 -6
- package/src/runtime/utils.ts +5 -29
- package/src/server/redirectPolicy.ts +51 -76
- package/src/shared/detection.ts +1 -1
- package/src/shared/localisedUrls/index.ts +12 -1
- package/src/shared/localisedUrls/pathname.ts +4 -1
- package/src/shared/localisedUrls/redirect.ts +91 -0
- package/src/shared/localisedUrls/resolve.ts +91 -74
- package/src/shared/localisedUrls.ts +6 -0
- package/src/shared/utils.ts +2 -2
- package/tests/redirectPolicy.test.ts +43 -0
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
import type { BaseBackendOptions } from '../../shared/type';
|
|
2
|
-
|
|
2
|
+
interface I18nResourceStore {
|
|
3
3
|
data?: {
|
|
4
4
|
[language: string]: {
|
|
5
|
-
[namespace: string]:
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
};
|
|
5
|
+
[namespace: string]: ResourceValue;
|
|
8
6
|
};
|
|
9
7
|
};
|
|
10
8
|
addResourceBundle?: (language: string, namespace: string, resources: Record<string, string>, deep?: boolean, overwrite?: boolean) => void;
|
|
11
9
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
type I18nWrapperInstance = I18nInstance & {
|
|
11
|
+
i18nInstance: {
|
|
12
|
+
instance: I18nInstance;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function isI18nWrapperInstance(obj: unknown): obj is I18nWrapperInstance;
|
|
16
|
+
export declare function getActualI18nextInstance(instance: I18nInstance): I18nInstance;
|
|
15
17
|
export interface I18nInstance {
|
|
16
18
|
language: string;
|
|
17
19
|
isInitialized?: boolean;
|
|
18
20
|
init: {
|
|
19
|
-
(callback?: (error:
|
|
20
|
-
(options: I18nInitOptions, callback?: (error:
|
|
21
|
+
(callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
|
|
22
|
+
(options: I18nInitOptions, callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
|
|
21
23
|
};
|
|
22
|
-
changeLanguage?: (lng?: string, callback?: (error:
|
|
24
|
+
changeLanguage?: (lng?: string, callback?: (error: unknown, t: unknown) => void) => Promise<unknown>;
|
|
23
25
|
setLang?: (lang: string) => void | Promise<void>;
|
|
24
|
-
use: (plugin:
|
|
26
|
+
use: (plugin: unknown) => void;
|
|
25
27
|
createInstance?: (options?: I18nInitOptions) => I18nInstance;
|
|
26
28
|
cloneInstance?: () => I18nInstance;
|
|
27
29
|
store?: I18nResourceStore;
|
|
28
|
-
emit?: (event: string, ...args:
|
|
30
|
+
emit?: (event: string, ...args: unknown[]) => void;
|
|
29
31
|
reloadResources?: (language?: string, namespace?: string) => Promise<void>;
|
|
30
32
|
services?: {
|
|
31
33
|
languageDetector?: {
|
|
32
|
-
detect: (request?:
|
|
33
|
-
[key: string]:
|
|
34
|
+
detect: (request?: unknown, options?: unknown) => string | string[] | undefined;
|
|
35
|
+
[key: string]: unknown;
|
|
34
36
|
};
|
|
35
37
|
resourceStore?: I18nResourceStore;
|
|
36
|
-
backend?:
|
|
37
|
-
[key: string]:
|
|
38
|
+
backend?: unknown;
|
|
39
|
+
[key: string]: unknown;
|
|
38
40
|
};
|
|
39
41
|
options?: {
|
|
40
42
|
backend?: BackendOptions;
|
|
41
|
-
[key: string]:
|
|
43
|
+
[key: string]: unknown;
|
|
42
44
|
};
|
|
43
|
-
[key: string]:
|
|
45
|
+
[key: string]: unknown;
|
|
44
46
|
}
|
|
45
47
|
type LanguageDetectorOrder = string[];
|
|
46
48
|
type LanguageDetectorCaches = boolean | string[];
|
|
@@ -57,11 +59,11 @@ export interface LanguageDetectorOptions {
|
|
|
57
59
|
lookupHeader?: string;
|
|
58
60
|
}
|
|
59
61
|
export interface BackendOptions extends Omit<BaseBackendOptions, 'enabled'> {
|
|
60
|
-
parse?: (data: string) =>
|
|
61
|
-
stringify?: (data:
|
|
62
|
+
parse?: (data: string) => unknown;
|
|
63
|
+
stringify?: (data: unknown) => string;
|
|
62
64
|
[key: string]: any;
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
type ResourceValue = string | {
|
|
65
67
|
[key: string]: ResourceValue;
|
|
66
68
|
};
|
|
67
69
|
export interface Resources {
|
|
@@ -81,14 +83,14 @@ export type I18nInitOptions = {
|
|
|
81
83
|
defaultNS?: string | string[];
|
|
82
84
|
interpolation?: {
|
|
83
85
|
escapeValue?: boolean;
|
|
84
|
-
[key: string]:
|
|
86
|
+
[key: string]: unknown;
|
|
85
87
|
};
|
|
86
88
|
react?: {
|
|
87
89
|
useSuspense?: boolean;
|
|
88
|
-
[key: string]:
|
|
90
|
+
[key: string]: unknown;
|
|
89
91
|
};
|
|
90
92
|
};
|
|
91
|
-
export declare function isI18nInstance(obj:
|
|
92
|
-
export declare function getI18nextInstanceForProvider(instance: I18nInstance
|
|
93
|
-
export declare function getI18nInstance(userInstance?:
|
|
93
|
+
export declare function isI18nInstance(obj: unknown): obj is I18nInstance;
|
|
94
|
+
export declare function getI18nextInstanceForProvider(instance: I18nInstance): I18nInstance;
|
|
95
|
+
export declare function getI18nInstance(userInstance?: unknown): Promise<I18nInstance>;
|
|
94
96
|
export {};
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { BaseBackendOptions } from '../../shared/type';
|
|
2
2
|
import type { I18nInitOptions, I18nInstance } from './instance';
|
|
3
|
-
|
|
3
|
+
type MergedBackendOptions = NonNullable<I18nInitOptions['backend']> & {
|
|
4
|
+
_useChainedBackend?: boolean;
|
|
5
|
+
_chainedBackendConfig?: {
|
|
6
|
+
backendOptions: Array<Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
backends?: unknown[];
|
|
9
|
+
backendOptions?: unknown;
|
|
10
|
+
};
|
|
11
|
+
export declare function assertI18nInstance(obj: unknown): asserts obj is I18nInstance;
|
|
4
12
|
/**
|
|
5
13
|
* Build initialization options for i18n instance
|
|
6
14
|
*/
|
|
7
|
-
export declare const buildInitOptions: (finalLanguage: string, fallbackLanguage: string, languages: string[], mergedDetection:
|
|
15
|
+
export declare const buildInitOptions: (finalLanguage: string, fallbackLanguage: string, languages: string[], mergedDetection: I18nInitOptions['detection'], mergedBackend: MergedBackendOptions | undefined, userInitOptions?: I18nInitOptions, useSuspense?: boolean, i18nInstance?: I18nInstance) => Promise<I18nInitOptions>;
|
|
8
16
|
/**
|
|
9
17
|
* Ensure i18n instance language matches the final detected language
|
|
10
18
|
*/
|
|
@@ -17,13 +25,14 @@ export declare const ensureLanguageMatch: (i18nInstance: I18nInstance, finalLang
|
|
|
17
25
|
* @param options - Optional configuration
|
|
18
26
|
*/
|
|
19
27
|
export declare const changeI18nLanguage: (i18nInstance: I18nInstance, newLang: string, options?: {
|
|
20
|
-
detectionOptions?:
|
|
28
|
+
detectionOptions?: I18nInitOptions['detection'];
|
|
21
29
|
}) => Promise<void>;
|
|
22
30
|
/**
|
|
23
31
|
* Initialize i18n instance if not already initialized
|
|
24
32
|
*/
|
|
25
|
-
export declare const initializeI18nInstance: (i18nInstance: I18nInstance, finalLanguage: string, fallbackLanguage: string, languages: string[], mergedDetection:
|
|
33
|
+
export declare const initializeI18nInstance: (i18nInstance: I18nInstance, finalLanguage: string, fallbackLanguage: string, languages: string[], mergedDetection: I18nInitOptions['detection'], mergedBackend: MergedBackendOptions | undefined, userInitOptions?: I18nInitOptions, useSuspense?: boolean) => Promise<void>;
|
|
26
34
|
/**
|
|
27
35
|
* Setup cloned instance for SSR with backend support
|
|
28
36
|
*/
|
|
29
|
-
export declare const setupClonedInstance: (i18nInstance: I18nInstance, finalLanguage: string, fallbackLanguage: string, languages: string[], backendEnabled: boolean, backend: BaseBackendOptions | undefined, i18nextDetector: boolean, detection:
|
|
37
|
+
export declare const setupClonedInstance: (i18nInstance: I18nInstance, finalLanguage: string, fallbackLanguage: string, languages: string[], backendEnabled: boolean, backend: BaseBackendOptions | undefined, i18nextDetector: boolean, detection: I18nInitOptions['detection'], localePathRedirect: boolean, userInitOptions: I18nInitOptions | undefined) => Promise<void>;
|
|
38
|
+
export {};
|
|
@@ -14,7 +14,7 @@ export interface RuntimeContextWithI18n extends TInternalRuntimeContext {
|
|
|
14
14
|
i18nInstance?: I18nInstance;
|
|
15
15
|
changeLanguage?: (lang: string) => Promise<void>;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
interface SetupI18nBeforeRenderOptions {
|
|
18
18
|
api: I18nRuntimeApi;
|
|
19
19
|
userI18nInstance?: I18nInstance;
|
|
20
20
|
initOptions?: I18nInitOptions;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { BaseLocaleDetectionOptions } from '../shared/type';
|
|
3
3
|
import type { I18nInstance } from './i18n';
|
|
4
|
-
|
|
4
|
+
interface I18nRootWrapperOptions {
|
|
5
5
|
entryName?: string;
|
|
6
6
|
htmlLangAttr: boolean;
|
|
7
7
|
localePathRedirect: boolean;
|
|
@@ -13,3 +13,4 @@ export interface I18nRootWrapperOptions {
|
|
|
13
13
|
getI18nextProvider: () => React.ComponentType<any> | null | undefined;
|
|
14
14
|
}
|
|
15
15
|
export declare const createI18nRootWrapper: (options: I18nRootWrapperOptions) => (App: React.ComponentType<any>) => (props: Record<string, unknown>) => React.JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
type I18nRouterFramework = 'react-router' | 'tanstack' | string;
|
|
3
|
+
interface I18nRouterLocation {
|
|
4
4
|
pathname: string;
|
|
5
5
|
search: string;
|
|
6
6
|
hash: string;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
interface I18nRouterNavigateOptions {
|
|
9
9
|
replace?: boolean;
|
|
10
10
|
state?: unknown;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
type I18nRouterNavigate = (href: string, options?: I18nRouterNavigateOptions) => void | Promise<void>;
|
|
13
|
+
type I18nRouterLink = React.ComponentType<{
|
|
14
14
|
to: string;
|
|
15
15
|
children?: React.ReactNode;
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}>;
|
|
18
|
-
|
|
18
|
+
interface I18nRouterAdapter {
|
|
19
19
|
framework?: I18nRouterFramework;
|
|
20
20
|
hasRouter: boolean;
|
|
21
21
|
location: I18nRouterLocation | null;
|
|
@@ -24,3 +24,4 @@ export interface I18nRouterAdapter {
|
|
|
24
24
|
params: Record<string, string>;
|
|
25
25
|
}
|
|
26
26
|
export declare const useI18nRouterAdapter: () => I18nRouterAdapter;
|
|
27
|
+
export {};
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { LocaleDetectionOptions } from '../shared/type';
|
|
2
|
+
interface LocaleRedirectRequest {
|
|
3
|
+
url: string;
|
|
4
|
+
header: () => {
|
|
5
|
+
host?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
|
-
* Check if
|
|
9
|
+
* Check if pathname should ignore automatic locale redirect.
|
|
4
10
|
*/
|
|
5
11
|
export declare const shouldIgnoreRedirect: (pathname: string, urlPath: string, ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean)) => boolean;
|
|
6
12
|
/**
|
|
7
|
-
* Check if
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 3. Paths with language prefix like /en/static/, /zh/static/
|
|
13
|
+
* Check if pathname is a static or federation resource request.
|
|
14
|
+
*
|
|
15
|
+
* This includes configured staticRoutePrefixes, the shared default skip policy
|
|
16
|
+
* from ADR-0002, and language-prefixed variants such as /en/static/app.js.
|
|
12
17
|
*/
|
|
13
18
|
export declare const isStaticResourceRequest: (pathname: string, staticRoutePrefixes: string[], languages?: string[]) => boolean;
|
|
14
|
-
export declare const getLanguageFromPath: (req:
|
|
15
|
-
export declare const buildLocalizedUrl: (req:
|
|
19
|
+
export declare const getLanguageFromPath: (req: LocaleRedirectRequest, urlPath: string, languages: string[]) => string | null;
|
|
20
|
+
export declare const buildLocalizedUrl: (req: LocaleRedirectRequest, urlPath: string, language: string, languages: string[], localisedUrls?: LocaleDetectionOptions['localisedUrls']) => string;
|
|
16
21
|
export declare const createLocaleRedirectResponse: (location: string) => Response;
|
|
22
|
+
export {};
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Detect language from request using the same detection logic as i18next
|
|
4
|
-
* This ensures consistency between server-side and client-side detection
|
|
5
|
-
*/
|
|
6
|
-
export declare function detectLanguageFromRequest(req: {
|
|
7
|
-
url: string;
|
|
8
|
-
headers: {
|
|
9
|
-
get: (name: string) => string | null;
|
|
10
|
-
} | Headers;
|
|
11
|
-
}, languages: string[], detectionOptions?: LanguageDetectorOptions): string | null;
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { resolveLocalisedUrlsConfig } from './config';
|
|
2
2
|
export { normalisePathname, normalisePathPattern } from './normalise';
|
|
3
|
-
export { canonicalTargetPathname, localiseTargetPathname } from './pathname';
|
|
3
|
+
export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix, } from './pathname';
|
|
4
4
|
export { buildPathFromPattern, matchPathPattern } from './patterns';
|
|
5
|
+
export type { LocaleRedirectSkipRule } from './redirect';
|
|
6
|
+
export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect, } from './redirect';
|
|
5
7
|
export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve';
|
|
6
8
|
export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes';
|
|
7
9
|
export type { LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './types';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { LocalisedUrlsOption } from './types';
|
|
2
|
+
export declare const stripLanguagePrefix: (pathname: string, languages: string[]) => string;
|
|
2
3
|
export declare const localiseTargetPathname: (pathname: string, language: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
3
4
|
export declare const canonicalTargetPathname: (pathname: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type LocaleRedirectSkipRule = {
|
|
2
|
+
type: 'exact';
|
|
3
|
+
path: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: 'prefix';
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const DEFAULT_LOCALE_REDIRECT_SKIP_RULES: readonly LocaleRedirectSkipRule[];
|
|
9
|
+
export declare const matchesPathPrefix: (pathname: string, prefix: string) => boolean;
|
|
10
|
+
export declare const isDefaultLocaleRedirectSkipPath: (pathname: string, languages?: string[]) => boolean;
|
|
11
|
+
export declare const shouldSkipLocaleRedirect: (pathname: string, languages?: string[], ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean)) => boolean;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { LocalisedUrlsMap } from './types';
|
|
2
2
|
export declare const resolveLocalisedPath: (pathname: string, targetLanguage: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
3
3
|
/**
|
|
4
|
-
* Reverse-map
|
|
5
|
-
*
|
|
6
|
-
* against every language variant and rebuilt from the canonical map key.
|
|
4
|
+
* Reverse-map language-specific pathnames back to language-agnostic
|
|
5
|
+
* LocalisedUrlsMap keys.
|
|
7
6
|
*/
|
|
8
7
|
export declare const resolveCanonicalLocalisedPath: (pathname: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './localisedUrls/index';
|
|
2
|
-
export { applyLocalisedUrlsToRoutes, buildPathFromPattern, canonicalTargetPathname, localiseTargetPathname, matchPathPattern, normalisePathname, normalisePathPattern, resolveCanonicalLocalisedPath, resolveLocalisedPath, resolveLocalisedUrlsConfig, validateLocalisedUrls, } from './localisedUrls/index';
|
|
1
|
+
export type { LocaleRedirectSkipRule, LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './localisedUrls/index';
|
|
2
|
+
export { applyLocalisedUrlsToRoutes, buildPathFromPattern, canonicalTargetPathname, DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, matchPathPattern, normalisePathname, normalisePathPattern, resolveCanonicalLocalisedPath, resolveLocalisedPath, resolveLocalisedUrlsConfig, shouldSkipLocaleRedirect, stripLanguagePrefix, validateLocalisedUrls, } from './localisedUrls/index';
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import type { BaseBackendOptions, BaseLocaleDetectionOptions } from './type';
|
|
2
|
-
export declare function getEntryConfig<T extends Record<string, any>>(entryName: string, config: T, entryKey: string): T | undefined;
|
|
3
|
-
export declare function removeEntryConfigKey<T extends Record<string, any>>(config: T, entryKey: string): Omit<T, typeof entryKey>;
|
|
4
2
|
export declare function getLocaleDetectionOptions(entryName: string, localeDetection: BaseLocaleDetectionOptions): BaseLocaleDetectionOptions;
|
|
5
3
|
export declare function getBackendOptions(entryName: string, backend: BaseBackendOptions): BaseBackendOptions;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.5.0-ultramodern.
|
|
20
|
+
"version": "3.5.0-ultramodern.38",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -97,19 +97,18 @@
|
|
|
97
97
|
"i18next-fs-backend": "^2.6.6",
|
|
98
98
|
"i18next-http-backend": "^4.0.0",
|
|
99
99
|
"i18next-http-middleware": "^3.9.7",
|
|
100
|
-
"@modern-js/
|
|
101
|
-
"@modern-js/
|
|
102
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.
|
|
103
|
-
"@modern-js/
|
|
104
|
-
"@modern-js/
|
|
105
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.36"
|
|
100
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.38",
|
|
101
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.38",
|
|
102
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.38",
|
|
103
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.38",
|
|
104
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.38"
|
|
106
105
|
},
|
|
107
106
|
"peerDependencies": {
|
|
108
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
107
|
+
"@modern-js/runtime": "3.5.0-ultramodern.38",
|
|
109
108
|
"i18next": ">=26.3.1",
|
|
110
109
|
"react": "^19.2.7",
|
|
111
110
|
"react-dom": "^19.2.7",
|
|
112
|
-
"react-i18next": "^17.0.
|
|
111
|
+
"react-i18next": "^17.0.9"
|
|
113
112
|
},
|
|
114
113
|
"peerDependenciesMeta": {
|
|
115
114
|
"i18next": {
|
|
@@ -120,17 +119,16 @@
|
|
|
120
119
|
}
|
|
121
120
|
},
|
|
122
121
|
"devDependencies": {
|
|
123
|
-
"@rslib/core": "0.23.
|
|
124
|
-
"@types/node": "^26.
|
|
125
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
126
|
-
"i18next": "26.3.
|
|
122
|
+
"@rslib/core": "0.23.2",
|
|
123
|
+
"@types/node": "^26.1.1",
|
|
124
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
125
|
+
"i18next": "26.3.6",
|
|
127
126
|
"react": "^19.2.7",
|
|
128
127
|
"react-dom": "^19.2.7",
|
|
129
|
-
"react-i18next": "17.0.
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"@modern-js/
|
|
133
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.36"
|
|
128
|
+
"react-i18next": "17.0.9",
|
|
129
|
+
"typescript": "^7.0.2",
|
|
130
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.38",
|
|
131
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.38"
|
|
134
132
|
},
|
|
135
133
|
"sideEffects": false,
|
|
136
134
|
"publishConfig": {
|
package/src/cli/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import type { AppTools, CliPlugin } from '@modern-js/app-tools';
|
|
2
4
|
import { getPublicDirRoutePrefixes } from '@modern-js/server-core';
|
|
3
5
|
import type {
|
|
@@ -5,8 +7,6 @@ import type {
|
|
|
5
7
|
NestedRouteForCli,
|
|
6
8
|
PageRoute,
|
|
7
9
|
} from '@modern-js/types';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
10
|
import {
|
|
11
11
|
applyLocalisedUrlsToRoutes,
|
|
12
12
|
resolveLocalisedUrlsConfig,
|
package/src/cli/locales.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import {
|
|
2
4
|
getPublicDirRoutePrefixes,
|
|
3
5
|
normalizePublicDir,
|
|
4
6
|
normalizePublicDirPath,
|
|
5
7
|
} from '@modern-js/server-core';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
8
|
import type { BackendOptions } from '../shared/type';
|
|
9
9
|
|
|
10
10
|
interface NormalizedConfigForLocales {
|
|
@@ -13,7 +13,7 @@ interface NormalizedConfigForLocales {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
interface DetectedLocalesDirectory {
|
|
17
17
|
loadPath: string;
|
|
18
18
|
addPath: string;
|
|
19
19
|
serverLoadPath: string;
|
package/src/runtime/context.tsx
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import type { I18nInstance } from './i18n';
|
|
19
19
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
interface ModernI18nContextValue {
|
|
22
22
|
language: string;
|
|
23
23
|
i18nInstance: I18nInstance;
|
|
24
24
|
// Plugin configuration for useModernI18n hook
|
|
@@ -50,7 +50,7 @@ const getModernI18nContext = () => {
|
|
|
50
50
|
|
|
51
51
|
const ModernI18nContext = getModernI18nContext();
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
interface ModernI18nProviderProps {
|
|
54
54
|
children: ReactNode;
|
|
55
55
|
value: ModernI18nContextValue;
|
|
56
56
|
}
|
|
@@ -66,7 +66,7 @@ export const ModernI18nProvider: FC<ModernI18nProviderProps> = ({
|
|
|
66
66
|
);
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
interface UseModernI18nReturn {
|
|
70
70
|
language: string;
|
|
71
71
|
changeLanguage: (newLang: string) => Promise<void>;
|
|
72
72
|
t: (key: string | string[], ...args: any[]) => string;
|
|
@@ -50,7 +50,7 @@ export function cacheI18nLanguage(
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
interface ChangeModernI18nLanguageOptions {
|
|
54
54
|
i18nInstance: I18nInstance;
|
|
55
55
|
updateLanguage?: (newLang: string) => void;
|
|
56
56
|
localePathRedirect?: boolean;
|
|
@@ -172,7 +172,7 @@ export async function changeModernI18nLanguage(
|
|
|
172
172
|
export function translateI18n(
|
|
173
173
|
i18nInstance: I18nInstance,
|
|
174
174
|
key: string | string[],
|
|
175
|
-
...args:
|
|
175
|
+
...args: unknown[]
|
|
176
176
|
): string {
|
|
177
177
|
if (typeof i18nInstance.t !== 'function') {
|
|
178
178
|
throw new Error('i18nInstance.t required');
|
|
@@ -207,7 +207,7 @@ export function isI18nResourcesReady(
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
const store =
|
|
210
|
+
const store = i18nInstance.store;
|
|
211
211
|
if (!store?.data) {
|
|
212
212
|
return false;
|
|
213
213
|
}
|
package/src/runtime/hooks.ts
CHANGED
|
@@ -26,6 +26,14 @@ interface RuntimeContextWithI18n extends TRuntimeContext {
|
|
|
26
26
|
ssrContext?: { request?: { pathname?: string } };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
type WindowWithSSRData = Window & {
|
|
30
|
+
_SSR_DATA?: unknown;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ResourceStoreWithEvents = NonNullable<I18nInstance['store']> & {
|
|
34
|
+
emit?: (event: string, ...args: unknown[]) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
29
37
|
function createMinimalI18nInstance(language: string): I18nInstance {
|
|
30
38
|
const minimalInstance: I18nInstance = {
|
|
31
39
|
language,
|
|
@@ -97,7 +105,7 @@ export function useSdkResourcesLoader(
|
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
const triggerUpdate = (retryCount = 0) => {
|
|
100
|
-
const store =
|
|
108
|
+
const store = i18nInstance.store as ResourceStoreWithEvents | undefined;
|
|
101
109
|
const hasResource = store?.data?.[language]?.[namespace];
|
|
102
110
|
|
|
103
111
|
if (hasResource || retryCount >= 10) {
|
|
@@ -182,7 +190,7 @@ export function useClientSideRedirect(
|
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
try {
|
|
185
|
-
const ssrData = (window as
|
|
193
|
+
const ssrData = (window as WindowWithSSRData)._SSR_DATA;
|
|
186
194
|
if (ssrData) {
|
|
187
195
|
return;
|
|
188
196
|
}
|
|
@@ -9,6 +9,7 @@ import { SdkBackend } from './sdk-backend';
|
|
|
9
9
|
|
|
10
10
|
type BackendConfigWithChained = BaseBackendOptions &
|
|
11
11
|
Partial<ChainedBackendConfig>;
|
|
12
|
+
type BackendConstructor = new (...args: never[]) => unknown;
|
|
12
13
|
|
|
13
14
|
function checkBackendConfig(backend?: BackendConfigWithChained) {
|
|
14
15
|
const hasSdk = backend?.sdk && typeof backend.sdk === 'function';
|
|
@@ -20,7 +21,7 @@ function checkBackendConfig(backend?: BackendConfigWithChained) {
|
|
|
20
21
|
|
|
21
22
|
function buildChainedBackendConfig(
|
|
22
23
|
backend: BackendConfigWithChained,
|
|
23
|
-
BackendWithSave:
|
|
24
|
+
BackendWithSave: BackendConstructor,
|
|
24
25
|
) {
|
|
25
26
|
const cacheHitMode = backend.cacheHitMode || 'refreshAndUpdateStore';
|
|
26
27
|
|
|
@@ -51,7 +52,7 @@ function buildChainedBackendConfig(
|
|
|
51
52
|
function setupChainedBackend(
|
|
52
53
|
i18nInstance: I18nInstance,
|
|
53
54
|
backend: BackendConfigWithChained,
|
|
54
|
-
BackendWithSave:
|
|
55
|
+
BackendWithSave: BackendConstructor,
|
|
55
56
|
) {
|
|
56
57
|
i18nInstance.use(ChainedBackend);
|
|
57
58
|
const actualInstance = getActualI18nextInstance(i18nInstance);
|
|
@@ -86,8 +87,8 @@ function cleanBackendConfig(backend: BackendConfigWithChained) {
|
|
|
86
87
|
*/
|
|
87
88
|
export function useI18nextBackendCommon(
|
|
88
89
|
i18nInstance: I18nInstance,
|
|
89
|
-
BackendWithSave:
|
|
90
|
-
BackendBase:
|
|
90
|
+
BackendWithSave: BackendConstructor,
|
|
91
|
+
BackendBase: BackendConstructor,
|
|
91
92
|
backend?: BackendConfigWithChained,
|
|
92
93
|
) {
|
|
93
94
|
if (!backend) {
|
|
@@ -10,6 +10,19 @@ interface DetectorCacheEntry {
|
|
|
10
10
|
configKey: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
type DetectorCleanupInstance = I18nInstance & {
|
|
14
|
+
removeAllListeners?: () => void;
|
|
15
|
+
off?: (event: string) => void;
|
|
16
|
+
services?: I18nInstance['services'] & {
|
|
17
|
+
backendConnector?: {
|
|
18
|
+
backend?: {
|
|
19
|
+
stop?: () => void;
|
|
20
|
+
close?: () => void;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
13
26
|
export const detectorInstanceCache = new WeakMap<
|
|
14
27
|
I18nInstance,
|
|
15
28
|
DetectorCacheEntry
|
|
@@ -38,7 +51,7 @@ const DETECTOR_SAFE_OPTION_KEYS: string[] = [
|
|
|
38
51
|
* Stable stringify that sorts object keys to ensure consistent output
|
|
39
52
|
* regardless of property order
|
|
40
53
|
*/
|
|
41
|
-
const stableStringify = (value:
|
|
54
|
+
const stableStringify = (value: unknown): string => {
|
|
42
55
|
if (value === null || value === undefined) {
|
|
43
56
|
return JSON.stringify(value);
|
|
44
57
|
}
|
|
@@ -53,9 +66,10 @@ const stableStringify = (value: any): string => {
|
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
// For objects, sort keys and recursively stringify values
|
|
56
|
-
const
|
|
69
|
+
const record = value as Record<string, unknown>;
|
|
70
|
+
const sortedKeys = Object.keys(record).sort();
|
|
57
71
|
const sortedEntries = sortedKeys.map(key => {
|
|
58
|
-
const stringifiedValue = stableStringify(
|
|
72
|
+
const stringifiedValue = stableStringify(record[key]);
|
|
59
73
|
return `${JSON.stringify(key)}:${stringifiedValue}`;
|
|
60
74
|
});
|
|
61
75
|
|
|
@@ -76,19 +90,19 @@ export const buildDetectorConfigKey = (
|
|
|
76
90
|
|
|
77
91
|
export const pickSafeDetectionOptions = (
|
|
78
92
|
userInitOptions?: I18nInitOptions,
|
|
79
|
-
): Partial<I18nInitOptions> & Record<string,
|
|
93
|
+
): Partial<I18nInitOptions> & Record<string, unknown> => {
|
|
80
94
|
if (!userInitOptions) {
|
|
81
95
|
return {};
|
|
82
96
|
}
|
|
83
|
-
const safeOptions: Partial<I18nInitOptions> & Record<string,
|
|
97
|
+
const safeOptions: Partial<I18nInitOptions> & Record<string, unknown> = {};
|
|
84
98
|
for (const key of DETECTOR_SAFE_OPTION_KEYS) {
|
|
85
|
-
const value = (userInitOptions as
|
|
99
|
+
const value = (userInitOptions as Record<string, unknown>)[key];
|
|
86
100
|
if (value !== undefined) {
|
|
87
101
|
safeOptions[key] = value;
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
|
-
if (
|
|
91
|
-
safeOptions.interpolation = { ...
|
|
104
|
+
if (userInitOptions.interpolation) {
|
|
105
|
+
safeOptions.interpolation = { ...userInitOptions.interpolation };
|
|
92
106
|
}
|
|
93
107
|
return safeOptions;
|
|
94
108
|
};
|
|
@@ -97,7 +111,7 @@ const cleanupDetectorCacheEntry = (entry?: DetectorCacheEntry) => {
|
|
|
97
111
|
if (!entry || !entry.isTemporary) {
|
|
98
112
|
return;
|
|
99
113
|
}
|
|
100
|
-
const instance = entry.instance as
|
|
114
|
+
const instance = entry.instance as DetectorCleanupInstance;
|
|
101
115
|
try {
|
|
102
116
|
instance?.removeAllListeners?.();
|
|
103
117
|
} catch (error) {
|