@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.5 → 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/linkTypes.test.ts +15 -4
- 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
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { ServerPlugin } from '@modern-js/server-runtime';
|
|
2
2
|
import type { LocaleDetectionOptions } from '../shared/type';
|
|
3
|
+
export { collectApiPrefixes, matchesApiPrefix } from './apiPrefix.js';
|
|
3
4
|
export interface I18nPluginOptions {
|
|
4
5
|
localeDetection: LocaleDetectionOptions;
|
|
5
6
|
staticRoutePrefixes: string[];
|
|
6
7
|
}
|
|
7
|
-
type ApiPrefixInput = string | string[] | undefined;
|
|
8
|
-
export declare const collectApiPrefixes: (routes: Array<{
|
|
9
|
-
isApi?: boolean;
|
|
10
|
-
urlPath?: string;
|
|
11
|
-
}>, bffPrefix?: ApiPrefixInput) => string[];
|
|
12
|
-
export declare const matchesApiPrefix: (pathname: string, apiPrefixes: string[]) => boolean;
|
|
13
8
|
export declare const i18nServerPlugin: (options: I18nPluginOptions) => ServerPlugin;
|
|
14
9
|
export default i18nServerPlugin;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LocaleDetectionOptions } from '../shared/type';
|
|
2
|
+
interface LocaleRedirectRequest {
|
|
3
|
+
url: string;
|
|
4
|
+
header: () => {
|
|
5
|
+
host?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Check if pathname should ignore automatic locale redirect.
|
|
10
|
+
*/
|
|
11
|
+
export declare const shouldIgnoreRedirect: (pathname: string, urlPath: string, ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean)) => boolean;
|
|
12
|
+
/**
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
export declare const isStaticResourceRequest: (pathname: string, staticRoutePrefixes: string[], languages?: string[]) => boolean;
|
|
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;
|
|
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 {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption, ResolvedLocalisedUrlsConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Localised URLs are strictly opt-in: only an explicit, non-empty map enables
|
|
4
|
+
* route expansion and validation. `true`, `false`, an empty map and absence
|
|
5
|
+
* all resolve to disabled, so upstream-style configs (`localePathRedirect` +
|
|
6
|
+
* `languages` without a map) keep plain locale-prefix behavior instead of
|
|
7
|
+
* failing the build for every route missing from a map they never wrote.
|
|
8
|
+
*/
|
|
9
|
+
export declare const resolveLocalisedUrlsConfig: (option: LocalisedUrlsOption | undefined) => ResolvedLocalisedUrlsConfig;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { resolveLocalisedUrlsConfig } from './config';
|
|
2
|
+
export { normalisePathname, normalisePathPattern } from './normalise';
|
|
3
|
+
export { canonicalTargetPathname, localiseTargetPathname, stripLanguagePrefix, } from './pathname';
|
|
4
|
+
export { buildPathFromPattern, matchPathPattern } from './patterns';
|
|
5
|
+
export type { LocaleRedirectSkipRule } from './redirect';
|
|
6
|
+
export { DEFAULT_LOCALE_REDIRECT_SKIP_RULES, isDefaultLocaleRedirectSkipPath, matchesPathPrefix, shouldSkipLocaleRedirect, } from './redirect';
|
|
7
|
+
export { resolveCanonicalLocalisedPath, resolveLocalisedPath } from './resolve';
|
|
8
|
+
export { applyLocalisedUrlsToRoutes, validateLocalisedUrls } from './routes';
|
|
9
|
+
export type { LocalisedRoute, LocalisedUrlPathMap, LocalisedUrlsMap, LocalisedUrlsOption, ResolvedLocalisedUrlsConfig, } from './types';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const normalisePathPattern: (path: string) => string;
|
|
2
|
+
/**
|
|
3
|
+
* Normalise a concrete request pathname: slash cleanup only. Unlike
|
|
4
|
+
* {@link normalisePathPattern} it must not rewrite literal `[x]` segments to
|
|
5
|
+
* `:x` params — pathnames are values, not patterns.
|
|
6
|
+
*/
|
|
7
|
+
export declare const normalisePathname: (pathname: string) => string;
|
|
8
|
+
export declare const normaliseRoutePath: (path: string) => string;
|
|
9
|
+
export declare const stripLeadingLocaleParam: (path?: string) => string | undefined;
|
|
10
|
+
export declare const getLeadingLocaleParam: (path?: string) => string | null;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from './types';
|
|
2
|
+
export declare const stripLanguagePrefix: (pathname: string, languages: string[]) => string;
|
|
3
|
+
export declare const localiseTargetPathname: (pathname: string, language: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
4
|
+
export declare const canonicalTargetPathname: (pathname: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const sortPatternsBySpecificity: <T extends {
|
|
2
|
+
pattern: string;
|
|
3
|
+
}>(patterns: T[]) => T[];
|
|
4
|
+
export declare const matchPathPattern: (pathname: string, pattern: string) => Record<string, string> | null;
|
|
5
|
+
export declare const buildPathFromPattern: (pattern: string, params: Record<string, string>) => 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;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LocalisedUrlsMap } from './types';
|
|
2
|
+
export declare const resolveLocalisedPath: (pathname: string, targetLanguage: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Reverse-map language-specific pathnames back to language-agnostic
|
|
5
|
+
* LocalisedUrlsMap keys.
|
|
6
|
+
*/
|
|
7
|
+
export declare const resolveCanonicalLocalisedPath: (pathname: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { LocalisedRoute, LocalisedUrlsMap } from './types';
|
|
2
|
+
export declare const validateLocalisedUrls: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => void;
|
|
3
|
+
export declare const applyLocalisedUrlsToRoutes: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => LocalisedRoute[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type LocalisedRoute = {
|
|
2
|
+
type: 'nested' | 'page';
|
|
3
|
+
path?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
children?: LocalisedRoute[];
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
export type LocalisedUrlPathMap = Record<string, string>;
|
|
9
|
+
export type LocalisedUrlsMap = Record<string, LocalisedUrlPathMap>;
|
|
10
|
+
export type LocalisedUrlsOption = boolean | LocalisedUrlsMap;
|
|
11
|
+
export interface ResolvedLocalisedUrlsConfig {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
map: LocalisedUrlsMap;
|
|
14
|
+
}
|
|
@@ -1,36 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export type LocalisedUrlsMap = Record<string, LocalisedUrlPathMap>;
|
|
4
|
-
export type LocalisedUrlsOption = boolean | LocalisedUrlsMap;
|
|
5
|
-
export interface ResolvedLocalisedUrlsConfig {
|
|
6
|
-
enabled: boolean;
|
|
7
|
-
map: LocalisedUrlsMap;
|
|
8
|
-
}
|
|
9
|
-
export declare const normalisePathPattern: (path: string) => string;
|
|
10
|
-
/**
|
|
11
|
-
* Normalise a concrete request pathname: slash cleanup only. Unlike
|
|
12
|
-
* {@link normalisePathPattern} it must not rewrite literal `[x]` segments to
|
|
13
|
-
* `:x` params — pathnames are values, not patterns.
|
|
14
|
-
*/
|
|
15
|
-
export declare const normalisePathname: (pathname: string) => string;
|
|
16
|
-
/**
|
|
17
|
-
* Localised URLs are strictly opt-in: only an explicit, non-empty map enables
|
|
18
|
-
* route expansion and validation. `true`, `false`, an empty map and absence
|
|
19
|
-
* all resolve to disabled, so upstream-style configs (`localePathRedirect` +
|
|
20
|
-
* `languages` without a map) keep plain locale-prefix behavior instead of
|
|
21
|
-
* failing the build for every route missing from a map they never wrote.
|
|
22
|
-
*/
|
|
23
|
-
export declare const resolveLocalisedUrlsConfig: (option: LocalisedUrlsOption | undefined) => ResolvedLocalisedUrlsConfig;
|
|
24
|
-
export declare const validateLocalisedUrls: (routes: (NestedRouteForCli | PageRoute)[], languages: string[], localisedUrls: LocalisedUrlsMap) => void;
|
|
25
|
-
export declare const applyLocalisedUrlsToRoutes: (routes: (NestedRouteForCli | PageRoute)[], languages: string[], localisedUrls: LocalisedUrlsMap) => (NestedRouteForCli | PageRoute)[];
|
|
26
|
-
export declare const matchPathPattern: (pathname: string, pattern: string) => Record<string, string> | null;
|
|
27
|
-
export declare const buildPathFromPattern: (pattern: string, params: Record<string, string>) => string;
|
|
28
|
-
export declare const resolveLocalisedPath: (pathname: string, targetLanguage: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
29
|
-
/**
|
|
30
|
-
* Reverse-map a language-specific pathname (without language prefix) back to
|
|
31
|
-
* the canonical, language-agnostic path: localized slug patterns are matched
|
|
32
|
-
* against every language variant and rebuilt from the canonical map key.
|
|
33
|
-
*/
|
|
34
|
-
export declare const resolveCanonicalLocalisedPath: (pathname: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
|
35
|
-
export declare const localiseTargetPathname: (pathname: string, language: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
36
|
-
export declare const canonicalTargetPathname: (pathname: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
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.75",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -91,25 +91,24 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.75",
|
|
95
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.75",
|
|
96
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.75",
|
|
97
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.75",
|
|
98
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.75",
|
|
94
99
|
"@swc/helpers": "^0.5.23",
|
|
95
100
|
"i18next-browser-languagedetector": "^8.2.1",
|
|
96
101
|
"i18next-chained-backend": "^5.0.5",
|
|
97
102
|
"i18next-fs-backend": "^2.6.6",
|
|
98
103
|
"i18next-http-backend": "^4.0.0",
|
|
99
|
-
"i18next-http-middleware": "^3.9.7"
|
|
100
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.5",
|
|
101
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.5",
|
|
102
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.5",
|
|
103
|
-
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.5",
|
|
104
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.5",
|
|
105
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.5"
|
|
104
|
+
"i18next-http-middleware": "^3.9.7"
|
|
106
105
|
},
|
|
107
106
|
"peerDependencies": {
|
|
108
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
107
|
+
"@modern-js/runtime": "3.5.0-ultramodern.75",
|
|
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,21 +119,19 @@
|
|
|
120
119
|
}
|
|
121
120
|
},
|
|
122
121
|
"devDependencies": {
|
|
123
|
-
"@
|
|
124
|
-
"@
|
|
125
|
-
"@
|
|
126
|
-
"
|
|
122
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.75",
|
|
123
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.75",
|
|
124
|
+
"@rslib/core": "0.23.2",
|
|
125
|
+
"@types/node": "^26.1.1",
|
|
126
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
127
|
+
"i18next": "26.3.6",
|
|
127
128
|
"react": "^19.2.7",
|
|
128
129
|
"react-dom": "^19.2.7",
|
|
129
|
-
"react-i18next": "17.0.
|
|
130
|
-
"
|
|
131
|
-
"typescript": "^6.0.3",
|
|
132
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.5",
|
|
133
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.5"
|
|
130
|
+
"react-i18next": "17.0.9",
|
|
131
|
+
"typescript": "^7.0.2"
|
|
134
132
|
},
|
|
135
133
|
"sideEffects": false,
|
|
136
134
|
"publishConfig": {
|
|
137
|
-
"registry": "https://registry.npmjs.org/",
|
|
138
135
|
"access": "public"
|
|
139
136
|
},
|
|
140
137
|
"scripts": {
|
package/rstest.config.mts
CHANGED
|
@@ -29,16 +29,23 @@ export default {
|
|
|
29
29
|
include: [
|
|
30
30
|
'tests/i18nUtils.test.ts',
|
|
31
31
|
'tests/localisedUrls.test.ts',
|
|
32
|
+
'tests/localisedUrlRewriteMatrix.fork.test.ts',
|
|
32
33
|
'tests/linkTypes.test.ts',
|
|
33
34
|
'tests/backendDefaults.test.ts',
|
|
35
|
+
'tests/redirectPolicy.test.ts',
|
|
34
36
|
'tests/reactI18nextRuntimeBoundary.test.ts',
|
|
37
|
+
'tests/federatedI18nBoundary.test.tsx',
|
|
35
38
|
],
|
|
36
39
|
extends: commonConfig,
|
|
37
40
|
}),
|
|
38
41
|
withTestPreset({
|
|
39
42
|
name: 'plugin-i18n-client',
|
|
40
43
|
testEnvironment: 'happy-dom',
|
|
41
|
-
include: [
|
|
44
|
+
include: [
|
|
45
|
+
'tests/routerAdapter.test.tsx',
|
|
46
|
+
'tests/link.test.tsx',
|
|
47
|
+
'tests/federatedI18nBoundary.client.test.tsx',
|
|
48
|
+
],
|
|
42
49
|
extends: commonConfig,
|
|
43
50
|
}),
|
|
44
51
|
],
|
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,
|
|
@@ -126,9 +126,10 @@ export const i18nPlugin = (
|
|
|
126
126
|
backend: backendOptions,
|
|
127
127
|
...extendedConfig,
|
|
128
128
|
};
|
|
129
|
+
const { reactI18next } = extendedConfig as { reactI18next?: boolean };
|
|
129
130
|
const runtimePluginPath =
|
|
130
131
|
customPlugin?.runtime?.path ||
|
|
131
|
-
(
|
|
132
|
+
(reactI18next === false
|
|
132
133
|
? `@${metaName}/plugin-i18n/runtime/no-react-i18next`
|
|
133
134
|
: `@${metaName}/plugin-i18n/runtime`);
|
|
134
135
|
|
|
@@ -173,7 +174,7 @@ export const i18nPlugin = (
|
|
|
173
174
|
routes as (NestedRouteForCli | PageRoute)[],
|
|
174
175
|
languages,
|
|
175
176
|
localisedUrlsConfig.map,
|
|
176
|
-
),
|
|
177
|
+
) as (NestedRouteForCli | PageRoute)[],
|
|
177
178
|
};
|
|
178
179
|
});
|
|
179
180
|
|
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/Link.tsx
CHANGED
|
@@ -6,96 +6,22 @@ import type {
|
|
|
6
6
|
ValidateLinkTo,
|
|
7
7
|
} from './canonicalRoutes';
|
|
8
8
|
import { useModernI18n } from './context';
|
|
9
|
+
import {
|
|
10
|
+
interpolateRouteParams,
|
|
11
|
+
type LinkParams,
|
|
12
|
+
mergeClassNames,
|
|
13
|
+
normalizeSearch,
|
|
14
|
+
splitActiveProps,
|
|
15
|
+
warnOnce,
|
|
16
|
+
} from './linkHelpers';
|
|
9
17
|
import { canonicalPath, type LocalizedPathsConfig } from './localizedPaths';
|
|
10
18
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
11
19
|
import { buildLocalizedUrl, splitUrlTarget } from './utils';
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const warnedTargets = new Set<string>();
|
|
16
|
-
|
|
17
|
-
const warnOnce = (key: string, message: string) => {
|
|
18
|
-
if (process.env.NODE_ENV !== 'development' || warnedTargets.has(key)) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
warnedTargets.add(key);
|
|
22
|
-
console.warn(message);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type LinkParams = Record<string, string | number | undefined>;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Interpolate `$param`, `:param`, optional (`{-$param}` / `:param?`) and splat
|
|
29
|
-
* (`$` / `*`) segments with concrete values before localization, so
|
|
30
|
-
* pattern-mapped slugs localize correctly.
|
|
31
|
-
*/
|
|
32
|
-
export const interpolateRouteParams = (
|
|
33
|
-
pathname: string,
|
|
34
|
-
params?: LinkParams,
|
|
35
|
-
): string => {
|
|
36
|
-
if (!/[$:*{]/.test(pathname)) {
|
|
37
|
-
return pathname;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const resolveParam = (name: string): string | undefined => {
|
|
41
|
-
const value = params?.[name];
|
|
42
|
-
return value === undefined ? undefined : String(value);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const segments = pathname
|
|
46
|
-
.split('/')
|
|
47
|
-
.map(segment => {
|
|
48
|
-
if (!segment) {
|
|
49
|
-
return segment;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (segment.startsWith('{-$') && segment.endsWith('}')) {
|
|
53
|
-
const value = resolveParam(segment.slice(3, -1));
|
|
54
|
-
return value === undefined ? null : encodeURIComponent(value);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (segment === '$' || segment === '*') {
|
|
58
|
-
const value = resolveParam('_splat') ?? resolveParam('*');
|
|
59
|
-
return value === undefined
|
|
60
|
-
? null
|
|
61
|
-
: value.split('/').map(encodeURIComponent).join('/');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (segment.startsWith('$')) {
|
|
65
|
-
const value = resolveParam(segment.slice(1));
|
|
66
|
-
if (value === undefined) {
|
|
67
|
-
warnOnce(
|
|
68
|
-
`missing-param:${pathname}:${segment}`,
|
|
69
|
-
`[plugin-i18n] <Link to="${pathname}"> is missing required param "${segment.slice(1)}".`,
|
|
70
|
-
);
|
|
71
|
-
return segment;
|
|
72
|
-
}
|
|
73
|
-
return encodeURIComponent(value);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (segment.startsWith(':')) {
|
|
77
|
-
const optional = segment.endsWith('?');
|
|
78
|
-
const name = segment.slice(1, optional ? -1 : undefined);
|
|
79
|
-
const value = resolveParam(name);
|
|
80
|
-
if (value === undefined) {
|
|
81
|
-
if (optional) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
warnOnce(
|
|
85
|
-
`missing-param:${pathname}:${segment}`,
|
|
86
|
-
`[plugin-i18n] <Link to="${pathname}"> is missing required param "${name}".`,
|
|
87
|
-
);
|
|
88
|
-
return segment;
|
|
89
|
-
}
|
|
90
|
-
return encodeURIComponent(value);
|
|
91
|
-
}
|
|
21
|
+
export type { LinkParams };
|
|
22
|
+
export { interpolateRouteParams };
|
|
92
23
|
|
|
93
|
-
|
|
94
|
-
})
|
|
95
|
-
.filter(segment => segment !== null);
|
|
96
|
-
|
|
97
|
-
return segments.join('/') || '/';
|
|
98
|
-
};
|
|
24
|
+
const EXTERNAL_TARGET_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
99
25
|
|
|
100
26
|
export interface LinkActiveOptions {
|
|
101
27
|
/**
|
|
@@ -142,59 +68,6 @@ export type LinkProps<TTo extends string = string> = LinkBaseProps & {
|
|
|
142
68
|
} & ValidateLinkTo<TTo> &
|
|
143
69
|
LinkParamsProp<LinkTargetPathname<TTo>>;
|
|
144
70
|
|
|
145
|
-
const normalizeSearch = (
|
|
146
|
-
search: string | Record<string, unknown> | undefined,
|
|
147
|
-
searchFromTo: string,
|
|
148
|
-
): {
|
|
149
|
-
searchString: string;
|
|
150
|
-
searchObject: Record<string, string> | undefined;
|
|
151
|
-
} => {
|
|
152
|
-
if (search && typeof search === 'object') {
|
|
153
|
-
const entries = Object.entries(search).filter(
|
|
154
|
-
([, value]) => value !== undefined && value !== null,
|
|
155
|
-
);
|
|
156
|
-
const searchObject = Object.fromEntries(
|
|
157
|
-
entries.map(([key, value]) => [key, String(value)]),
|
|
158
|
-
);
|
|
159
|
-
const params = new URLSearchParams(searchObject);
|
|
160
|
-
const serialized = params.toString();
|
|
161
|
-
return {
|
|
162
|
-
searchString: serialized ? `?${serialized}` : '',
|
|
163
|
-
searchObject,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const raw = typeof search === 'string' && search ? search : searchFromTo;
|
|
168
|
-
if (!raw) {
|
|
169
|
-
return { searchString: '', searchObject: undefined };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const searchString = raw.startsWith('?') ? raw : `?${raw}`;
|
|
173
|
-
const searchObject: Record<string, string> = {};
|
|
174
|
-
new URLSearchParams(searchString).forEach((value, key) => {
|
|
175
|
-
searchObject[key] = value;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
return { searchString, searchObject };
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const splitActiveProps = (
|
|
182
|
-
active: boolean,
|
|
183
|
-
activeProps?: LinkBaseProps['activeProps'],
|
|
184
|
-
) => {
|
|
185
|
-
if (!active || !activeProps) {
|
|
186
|
-
return {};
|
|
187
|
-
}
|
|
188
|
-
return activeProps;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const mergeClassNames = (...values: Array<unknown>): string | undefined => {
|
|
192
|
-
const classNames = values.filter(
|
|
193
|
-
(value): value is string => typeof value === 'string' && value.length > 0,
|
|
194
|
-
);
|
|
195
|
-
return classNames.length > 0 ? classNames.join(' ') : undefined;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
71
|
/**
|
|
199
72
|
* The standard UltraModern link: a vanilla link in every respect except that
|
|
200
73
|
* it localizes canonical, language-agnostic paths automatically.
|