@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.99 → 3.4.0-ultramodern.0
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/README.md +221 -11
- package/dist/cjs/cli/index.js +17 -64
- package/dist/cjs/cli/locales.js +132 -0
- package/dist/cjs/runtime/I18nLink.js +17 -20
- package/dist/cjs/runtime/Link.js +264 -0
- package/dist/cjs/runtime/canonicalRoutes.js +18 -0
- package/dist/cjs/runtime/context.js +9 -5
- package/dist/cjs/runtime/hooks.js +9 -5
- package/dist/cjs/runtime/i18n/backend/config.js +9 -5
- package/dist/cjs/runtime/i18n/backend/defaults.js +20 -11
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +79 -10
- package/dist/cjs/runtime/i18n/backend/index.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.common.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.node.js +9 -5
- package/dist/cjs/runtime/i18n/backend/sdk-backend.js +9 -5
- package/dist/cjs/runtime/i18n/backend/sdk-event.js +16 -11
- package/dist/cjs/runtime/i18n/detection/config.js +9 -5
- package/dist/cjs/runtime/i18n/detection/index.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.node.js +9 -5
- package/dist/cjs/runtime/i18n/index.js +9 -5
- package/dist/cjs/runtime/i18n/instance.js +17 -13
- package/dist/cjs/runtime/i18n/react-i18next.js +12 -8
- package/dist/cjs/runtime/i18n/utils.js +9 -5
- package/dist/cjs/runtime/index.js +32 -5
- package/dist/cjs/runtime/localizedPaths.js +102 -0
- package/dist/cjs/runtime/routerAdapter.js +11 -7
- package/dist/cjs/runtime/utils.js +31 -17
- package/dist/cjs/server/index.js +10 -14
- package/dist/cjs/shared/deepMerge.js +12 -8
- package/dist/cjs/shared/detection.js +9 -5
- package/dist/cjs/shared/localisedUrls.js +148 -34
- package/dist/cjs/shared/utils.js +15 -11
- package/dist/esm/cli/index.mjs +8 -48
- package/dist/esm/cli/locales.mjs +80 -0
- package/dist/esm/runtime/I18nLink.mjs +7 -14
- package/dist/esm/runtime/Link.mjs +221 -0
- package/dist/esm/runtime/canonicalRoutes.mjs +0 -0
- package/dist/esm/runtime/i18n/backend/defaults.mjs +6 -2
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +56 -5
- package/dist/esm/runtime/index.mjs +4 -2
- package/dist/esm/runtime/localizedPaths.mjs +55 -0
- package/dist/esm/runtime/routerAdapter.mjs +3 -3
- package/dist/esm/runtime/utils.mjs +19 -12
- package/dist/esm/server/index.mjs +2 -10
- package/dist/esm/shared/localisedUrls.mjs +115 -23
- package/dist/esm-node/cli/index.mjs +8 -48
- package/dist/esm-node/cli/locales.mjs +81 -0
- package/dist/esm-node/runtime/I18nLink.mjs +7 -14
- package/dist/esm-node/runtime/Link.mjs +222 -0
- package/dist/esm-node/runtime/canonicalRoutes.mjs +1 -0
- package/dist/esm-node/runtime/i18n/backend/defaults.mjs +6 -2
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +56 -5
- package/dist/esm-node/runtime/index.mjs +4 -2
- package/dist/esm-node/runtime/localizedPaths.mjs +56 -0
- package/dist/esm-node/runtime/routerAdapter.mjs +3 -3
- package/dist/esm-node/runtime/utils.mjs +19 -12
- package/dist/esm-node/server/index.mjs +2 -10
- package/dist/esm-node/shared/localisedUrls.mjs +115 -23
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/locales.d.ts +17 -0
- package/dist/types/runtime/I18nLink.d.ts +4 -13
- package/dist/types/runtime/Link.d.ts +66 -0
- package/dist/types/runtime/canonicalRoutes.d.ts +60 -0
- package/dist/types/runtime/i18n/backend/defaults.d.ts +10 -7
- package/dist/types/runtime/i18n/backend/defaults.node.d.ts +13 -4
- package/dist/types/runtime/index.d.ts +5 -1
- package/dist/types/runtime/localizedPaths.d.ts +39 -0
- package/dist/types/runtime/types.d.ts +1 -1
- package/dist/types/runtime/utils.d.ts +13 -4
- package/dist/types/shared/localisedUrls.d.ts +23 -0
- package/dist/types/shared/type.d.ts +27 -5
- package/package.json +28 -25
- package/rstest.config.mts +7 -2
- package/src/cli/index.ts +25 -98
- package/src/cli/locales.ts +186 -0
- package/src/runtime/I18nLink.tsx +13 -44
- package/src/runtime/Link.tsx +430 -0
- package/src/runtime/canonicalRoutes.ts +93 -0
- package/src/runtime/i18n/backend/defaults.node.ts +112 -7
- package/src/runtime/i18n/backend/defaults.ts +20 -18
- package/src/runtime/index.tsx +24 -2
- package/src/runtime/localizedPaths.ts +107 -0
- package/src/runtime/routerAdapter.tsx +4 -5
- package/src/runtime/types.ts +1 -1
- package/src/runtime/utils.ts +33 -26
- package/src/server/index.ts +7 -17
- package/src/shared/localisedUrls.ts +256 -26
- package/src/shared/type.ts +27 -5
- package/tests/backendDefaults.test.ts +51 -0
- package/tests/i18nUtils.test.ts +10 -3
- package/tests/link.test.tsx +525 -0
- package/tests/linkTypes.test.ts +28 -0
- package/tests/localisedUrls.test.ts +224 -0
- package/tests/routerAdapter.test.tsx +86 -12
- package/tests/type-fixture/linkTypes.fixture.tsx +51 -0
- package/tests/type-fixture/tsconfig.json +15 -0
|
@@ -2,12 +2,15 @@ export declare const DEFAULT_I18NEXT_BACKEND_OPTIONS: {
|
|
|
2
2
|
loadPath: string;
|
|
3
3
|
addPath: string;
|
|
4
4
|
};
|
|
5
|
-
|
|
6
|
-
interface Window {
|
|
7
|
-
__assetPrefix__?: string;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export declare function convertBackendOptions<T extends {
|
|
5
|
+
interface InternalBackendPathOptions {
|
|
11
6
|
loadPath?: string;
|
|
12
7
|
addPath?: string;
|
|
13
|
-
|
|
8
|
+
serverLoadPath?: string;
|
|
9
|
+
serverAddPath?: string;
|
|
10
|
+
serverLoadPaths?: string[];
|
|
11
|
+
serverAddPaths?: string[];
|
|
12
|
+
_detectedLoadPath?: string;
|
|
13
|
+
_detectedAddPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function convertBackendOptions<T extends InternalBackendPathOptions>(options: T): T;
|
|
16
|
+
export {};
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
export declare const resolveDefaultLocalesDir: (cwd?: string) => string;
|
|
1
2
|
export declare const DEFAULT_I18NEXT_BACKEND_OPTIONS: {
|
|
2
|
-
loadPath: string;
|
|
3
|
-
addPath: string;
|
|
3
|
+
readonly loadPath: string;
|
|
4
|
+
readonly addPath: string;
|
|
4
5
|
};
|
|
5
|
-
|
|
6
|
+
interface InternalBackendPathOptions {
|
|
6
7
|
loadPath?: string;
|
|
7
8
|
addPath?: string;
|
|
8
|
-
|
|
9
|
+
serverLoadPath?: string;
|
|
10
|
+
serverAddPath?: string;
|
|
11
|
+
serverLoadPaths?: string[];
|
|
12
|
+
serverAddPaths?: string[];
|
|
13
|
+
_detectedLoadPath?: string;
|
|
14
|
+
_detectedAddPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function convertBackendOptions<T extends InternalBackendPathOptions>(options: T): T;
|
|
17
|
+
export {};
|
|
@@ -16,6 +16,10 @@ export interface I18nPluginOptions {
|
|
|
16
16
|
[key: string]: any;
|
|
17
17
|
}
|
|
18
18
|
export declare const i18nPlugin: (options: I18nPluginOptions) => RuntimePlugin;
|
|
19
|
+
export type { AllowedLinkTarget, CanonicalRoutePath, UltramodernCanonicalRoutes, } from './canonicalRoutes';
|
|
19
20
|
export { useModernI18n } from './context';
|
|
20
|
-
export { I18nLink } from './I18nLink';
|
|
21
|
+
export { I18nLink, type I18nLinkProps } from './I18nLink';
|
|
22
|
+
export { Link, type LinkActiveOptions, type LinkBaseProps, type LinkParams, type LinkProps, } from './Link';
|
|
23
|
+
export { canonicalPath, type LocalizedPathsConfig, localizePath, type UseLocalizedLocationReturn, type UseLocalizedPathsReturn, useLocalizedLocation, useLocalizedPaths, } from './localizedPaths';
|
|
24
|
+
export { buildLocalizedUrl, splitUrlTarget } from './utils';
|
|
21
25
|
export default i18nPlugin;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
2
|
+
export interface LocalizedPathsConfig {
|
|
3
|
+
languages: string[];
|
|
4
|
+
localisedUrls?: LocalisedUrlsOption;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Localize a canonical, language-agnostic target for the given language:
|
|
8
|
+
* adds the language prefix and applies `localisedUrls` pattern mapping.
|
|
9
|
+
* `?search`/`#hash` suffixes are preserved verbatim.
|
|
10
|
+
*/
|
|
11
|
+
export declare const localizePath: (pathname: string, language: string, config: LocalizedPathsConfig) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Reverse of {@link localizePath}: strip the language prefix and map localized
|
|
14
|
+
* slugs back to the canonical pattern's path. `?search`/`#hash` suffixes are
|
|
15
|
+
* preserved verbatim.
|
|
16
|
+
*/
|
|
17
|
+
export declare const canonicalPath: (target: string, config: LocalizedPathsConfig) => string;
|
|
18
|
+
export interface UseLocalizedPathsReturn {
|
|
19
|
+
localizePath: (pathname: string, language: string) => string;
|
|
20
|
+
canonicalPath: (pathname: string) => string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Context-bound versions of {@link localizePath} and {@link canonicalPath} —
|
|
24
|
+
* the plugin configuration (languages, localisedUrls) is read from the i18n
|
|
25
|
+
* provider, so apps never copy pattern-matching helpers again.
|
|
26
|
+
*/
|
|
27
|
+
export declare const useLocalizedPaths: () => UseLocalizedPathsReturn;
|
|
28
|
+
export interface UseLocalizedLocationReturn {
|
|
29
|
+
language: string;
|
|
30
|
+
/** Canonical (language-agnostic) path of the current location. */
|
|
31
|
+
canonical: string;
|
|
32
|
+
/** Per-language hrefs for the current location, search+hash preserved. */
|
|
33
|
+
alternates: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Per-language hrefs for the current location — for hreflang `<link>` tags and
|
|
37
|
+
* language switchers. SSR-safe: the location comes from the router adapter.
|
|
38
|
+
*/
|
|
39
|
+
export declare const useLocalizedLocation: () => UseLocalizedLocationReturn;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TInternalRuntimeContext } from '@modern-js/runtime/context';
|
|
2
|
-
import type {
|
|
2
|
+
import type { LocalisedUrlsOption } from '../shared/localisedUrls';
|
|
3
3
|
export declare const getPathname: (context: TInternalRuntimeContext) => string;
|
|
4
4
|
export declare const getEntryPath: () => string;
|
|
5
5
|
/**
|
|
@@ -10,14 +10,23 @@ export declare const getEntryPath: () => string;
|
|
|
10
10
|
* @returns The detected language or fallback language
|
|
11
11
|
*/
|
|
12
12
|
export declare const getLanguageFromPath: (pathname: string, languages: string[], fallbackLanguage: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Split a link target into its pathname, search and hash parts without
|
|
15
|
+
* relying on `new URL` (SSR-hot path; targets are relative).
|
|
16
|
+
*/
|
|
17
|
+
export declare const splitUrlTarget: (target: string) => {
|
|
18
|
+
pathname: string;
|
|
19
|
+
search: string;
|
|
20
|
+
hash: string;
|
|
21
|
+
};
|
|
13
22
|
/**
|
|
14
23
|
* Helper function to build localized URL
|
|
15
|
-
* @param
|
|
24
|
+
* @param target - The language-agnostic target; may include `?search` and `#hash`
|
|
16
25
|
* @param language - The target language
|
|
17
26
|
* @param languages - Array of supported languages
|
|
18
|
-
* @returns The localized URL path
|
|
27
|
+
* @returns The localized URL path with search and hash re-appended verbatim
|
|
19
28
|
*/
|
|
20
|
-
export declare const buildLocalizedUrl: (
|
|
29
|
+
export declare const buildLocalizedUrl: (target: string, language: string, languages: string[], localisedUrls?: LocalisedUrlsOption) => string;
|
|
21
30
|
export declare const detectLanguageFromPath: (pathname: string, languages: string[], localePathRedirect: boolean) => {
|
|
22
31
|
detected: boolean;
|
|
23
32
|
language?: string;
|
|
@@ -7,7 +7,30 @@ export interface ResolvedLocalisedUrlsConfig {
|
|
|
7
7
|
map: LocalisedUrlsMap;
|
|
8
8
|
}
|
|
9
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
|
+
*/
|
|
10
23
|
export declare const resolveLocalisedUrlsConfig: (option: LocalisedUrlsOption | undefined) => ResolvedLocalisedUrlsConfig;
|
|
11
24
|
export declare const validateLocalisedUrls: (routes: (NestedRouteForCli | PageRoute)[], languages: string[], localisedUrls: LocalisedUrlsMap) => void;
|
|
12
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;
|
|
13
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;
|
|
@@ -10,12 +10,14 @@ export interface BaseLocaleDetectionOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* Enables localised pathnames in addition to the locale prefix.
|
|
12
12
|
*
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
13
|
+
* - non-empty object: map canonical route paths to every configured
|
|
14
|
+
* language; route generation then validates that every localisable route
|
|
15
|
+
* path has entries for all configured languages.
|
|
16
|
+
* - absent / `false` / `true` / empty object: keep only locale-prefix
|
|
17
|
+
* behavior (`/en/about`).
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* configured languages.
|
|
19
|
+
* Strictly opt-in: without a map, `localePathRedirect` + `languages` behave
|
|
20
|
+
* exactly like upstream Modern.js.
|
|
19
21
|
*/
|
|
20
22
|
localisedUrls?: LocalisedUrlsOption;
|
|
21
23
|
}
|
|
@@ -70,6 +72,26 @@ export interface BaseBackendOptions {
|
|
|
70
72
|
enabled?: boolean;
|
|
71
73
|
loadPath?: string;
|
|
72
74
|
addPath?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Internal file-system path used by the Node.js FS backend.
|
|
77
|
+
* Browser HTTP backend keeps using `loadPath`.
|
|
78
|
+
*/
|
|
79
|
+
serverLoadPath?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Internal file-system path candidates used by the Node.js FS backend.
|
|
82
|
+
* The first existing path under current cwd will be used.
|
|
83
|
+
*/
|
|
84
|
+
serverLoadPaths?: string[];
|
|
85
|
+
/**
|
|
86
|
+
* Internal file-system path used by the Node.js FS backend.
|
|
87
|
+
* Browser HTTP backend keeps using `addPath`.
|
|
88
|
+
*/
|
|
89
|
+
serverAddPath?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Internal file-system path candidates used by the Node.js FS backend.
|
|
92
|
+
* The first existing path under current cwd will be used.
|
|
93
|
+
*/
|
|
94
|
+
serverAddPaths?: string[];
|
|
73
95
|
/**
|
|
74
96
|
* Cache hit mode for chained backend (only effective when both `loadPath` and `sdk` are provided)
|
|
75
97
|
*
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.
|
|
20
|
+
"version": "3.4.0-ultramodern.0",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -83,41 +83,44 @@
|
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@swc/helpers": "^0.5.23",
|
|
85
85
|
"i18next-browser-languagedetector": "^8.2.1",
|
|
86
|
-
"i18next-chained-backend": "^5.0.
|
|
86
|
+
"i18next-chained-backend": "^5.0.5",
|
|
87
87
|
"i18next-fs-backend": "^2.6.6",
|
|
88
88
|
"i18next-http-backend": "^4.0.0",
|
|
89
89
|
"i18next-http-middleware": "^3.9.7",
|
|
90
|
-
"
|
|
91
|
-
"@modern-js/runtime
|
|
92
|
-
"@modern-js/
|
|
93
|
-
"@modern-js/
|
|
94
|
-
"@modern-js/
|
|
95
|
-
"@modern-js/
|
|
96
|
-
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.2.0-ultramodern.99"
|
|
90
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.4.0-ultramodern.0",
|
|
91
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.4.0-ultramodern.0",
|
|
92
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.4.0-ultramodern.0",
|
|
93
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.4.0-ultramodern.0",
|
|
94
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.4.0-ultramodern.0",
|
|
95
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.4.0-ultramodern.0"
|
|
97
96
|
},
|
|
98
97
|
"peerDependencies": {
|
|
99
|
-
"@modern-js/runtime": "3.
|
|
100
|
-
"i18next": ">=
|
|
101
|
-
"react": "^19.2.
|
|
102
|
-
"react-dom": "^19.2.
|
|
98
|
+
"@modern-js/runtime": "3.4.0-ultramodern.0",
|
|
99
|
+
"i18next": ">=26.3.1",
|
|
100
|
+
"react": "^19.2.7",
|
|
101
|
+
"react-dom": "^19.2.7",
|
|
102
|
+
"react-i18next": "^17.0.8"
|
|
103
103
|
},
|
|
104
104
|
"peerDependenciesMeta": {
|
|
105
105
|
"i18next": {
|
|
106
106
|
"optional": true
|
|
107
|
+
},
|
|
108
|
+
"react-i18next": {
|
|
109
|
+
"optional": true
|
|
107
110
|
}
|
|
108
111
|
},
|
|
109
112
|
"devDependencies": {
|
|
110
|
-
"@rslib/core": "0.
|
|
111
|
-
"@types/
|
|
112
|
-
"@
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"react": "
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.
|
|
120
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.
|
|
113
|
+
"@rslib/core": "0.23.0",
|
|
114
|
+
"@types/node": "^26.0.0",
|
|
115
|
+
"@typescript/native-preview": "7.0.0-dev.20260624.1",
|
|
116
|
+
"i18next": "26.3.2",
|
|
117
|
+
"react": "^19.2.7",
|
|
118
|
+
"react-dom": "^19.2.7",
|
|
119
|
+
"react-i18next": "17.0.8",
|
|
120
|
+
"ts-node": "^10.9.2",
|
|
121
|
+
"typescript": "^6.0.3",
|
|
122
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.4.0-ultramodern.0",
|
|
123
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.4.0-ultramodern.0"
|
|
121
124
|
},
|
|
122
125
|
"sideEffects": false,
|
|
123
126
|
"publishConfig": {
|
|
@@ -126,7 +129,7 @@
|
|
|
126
129
|
},
|
|
127
130
|
"scripts": {
|
|
128
131
|
"dev": "rslib build --watch",
|
|
129
|
-
"build": "rslib build",
|
|
132
|
+
"build": "rslib build && pnpm -w tsgo:dts \"$PWD\"",
|
|
130
133
|
"test": "rstest --passWithNoTests"
|
|
131
134
|
}
|
|
132
135
|
}
|
package/rstest.config.mts
CHANGED
|
@@ -26,13 +26,18 @@ export default {
|
|
|
26
26
|
withTestPreset({
|
|
27
27
|
name: 'plugin-i18n-node',
|
|
28
28
|
testEnvironment: 'node',
|
|
29
|
-
include: [
|
|
29
|
+
include: [
|
|
30
|
+
'tests/i18nUtils.test.ts',
|
|
31
|
+
'tests/localisedUrls.test.ts',
|
|
32
|
+
'tests/linkTypes.test.ts',
|
|
33
|
+
'tests/backendDefaults.test.ts',
|
|
34
|
+
],
|
|
30
35
|
extends: commonConfig,
|
|
31
36
|
}),
|
|
32
37
|
withTestPreset({
|
|
33
38
|
name: 'plugin-i18n-client',
|
|
34
39
|
testEnvironment: 'happy-dom',
|
|
35
|
-
include: ['tests/routerAdapter.test.tsx'],
|
|
40
|
+
include: ['tests/routerAdapter.test.tsx', 'tests/link.test.tsx'],
|
|
36
41
|
extends: commonConfig,
|
|
37
42
|
}),
|
|
38
43
|
],
|
package/src/cli/index.ts
CHANGED
|
@@ -13,81 +13,14 @@ import {
|
|
|
13
13
|
} from '../shared/localisedUrls';
|
|
14
14
|
import type { BackendOptions, LocaleDetectionOptions } from '../shared/type';
|
|
15
15
|
import { getBackendOptions, getLocaleDetectionOptions } from '../shared/utils';
|
|
16
|
+
import { applyDetectedBackendPaths, detectLocalesDirectory } from './locales';
|
|
17
|
+
import '../runtime/types';
|
|
16
18
|
|
|
17
19
|
export type TransformRuntimeConfigFn = (
|
|
18
20
|
extendedConfig: Record<string, any>,
|
|
19
21
|
entrypoint: Entrypoint,
|
|
20
22
|
) => Record<string, any>;
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Check if a directory exists and contains JSON files
|
|
24
|
-
*/
|
|
25
|
-
function hasJsonFiles(dirPath: string): boolean {
|
|
26
|
-
try {
|
|
27
|
-
if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
const entries = fs.readdirSync(dirPath);
|
|
31
|
-
// Check if there are any JSON files in the directory or subdirectories
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
const entryPath = path.join(dirPath, entry);
|
|
34
|
-
const stat = fs.statSync(entryPath);
|
|
35
|
-
if (stat.isFile() && entry.endsWith('.json')) {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
if (stat.isDirectory()) {
|
|
39
|
-
// Recursively check subdirectories (e.g., locales/en/, locales/zh/)
|
|
40
|
-
if (hasJsonFiles(entryPath)) {
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Auto-detect if locales directory exists with JSON files
|
|
53
|
-
* Checks both project root and config/public directory
|
|
54
|
-
*/
|
|
55
|
-
function detectLocalesDirectory(
|
|
56
|
-
appDirectory: string,
|
|
57
|
-
normalizedConfig?: any,
|
|
58
|
-
): boolean {
|
|
59
|
-
// Check project root directory
|
|
60
|
-
const rootLocalesPath = path.join(appDirectory, 'locales');
|
|
61
|
-
if (hasJsonFiles(rootLocalesPath)) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Check config/public directory
|
|
66
|
-
const configPublicPath = path.join(
|
|
67
|
-
appDirectory,
|
|
68
|
-
'config',
|
|
69
|
-
'public',
|
|
70
|
-
'locales',
|
|
71
|
-
);
|
|
72
|
-
if (hasJsonFiles(configPublicPath)) {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check publicDir if configured
|
|
77
|
-
const publicDir = normalizedConfig?.server?.publicDir;
|
|
78
|
-
if (publicDir) {
|
|
79
|
-
const publicDirPath = Array.isArray(publicDir) ? publicDir[0] : publicDir;
|
|
80
|
-
const localesPath = path.isAbsolute(publicDirPath)
|
|
81
|
-
? path.join(publicDirPath, 'locales')
|
|
82
|
-
: path.join(appDirectory, publicDirPath, 'locales');
|
|
83
|
-
if (hasJsonFiles(localesPath)) {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
24
|
export interface I18nPluginOptions {
|
|
92
25
|
localeDetection?: LocaleDetectionOptions;
|
|
93
26
|
backend?: BackendOptions;
|
|
@@ -125,10 +58,14 @@ export const i18nPlugin = (
|
|
|
125
58
|
// Auto-detect locales directory and enable backend if:
|
|
126
59
|
// 1. User didn't explicitly set backend.enabled to false
|
|
127
60
|
// 2. Locales directory exists with JSON files
|
|
128
|
-
// 3. If user configured loadPath or addPath, auto-enable backend
|
|
61
|
+
// 3. If user configured loadPath or addPath, auto-enable backend
|
|
129
62
|
let backendOptions: BackendOptions | undefined;
|
|
130
63
|
const { appDirectory } = api.getAppContext();
|
|
131
64
|
const normalizedConfig = api.getNormalizedConfig();
|
|
65
|
+
const detectedLocales = detectLocalesDirectory(
|
|
66
|
+
appDirectory,
|
|
67
|
+
normalizedConfig,
|
|
68
|
+
);
|
|
132
69
|
|
|
133
70
|
if (backend) {
|
|
134
71
|
const entryBackendOptions = getBackendOptions(
|
|
@@ -139,45 +76,35 @@ export const i18nPlugin = (
|
|
|
139
76
|
if (entryBackendOptions?.enabled === false) {
|
|
140
77
|
backendOptions = entryBackendOptions;
|
|
141
78
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
79
|
+
if (detectedLocales) {
|
|
80
|
+
backendOptions = applyDetectedBackendPaths(
|
|
81
|
+
entryBackendOptions,
|
|
82
|
+
detectedLocales,
|
|
83
|
+
);
|
|
84
|
+
} else if (
|
|
85
|
+
entryBackendOptions?.loadPath ||
|
|
86
|
+
entryBackendOptions?.addPath
|
|
87
|
+
) {
|
|
88
|
+
// If user configured loadPath or addPath, auto-enable backend even
|
|
89
|
+
// when no local locales directory is detected.
|
|
145
90
|
backendOptions = {
|
|
146
91
|
...entryBackendOptions,
|
|
147
92
|
enabled: true,
|
|
148
93
|
};
|
|
149
|
-
} else if (entryBackendOptions?.enabled !== true) {
|
|
150
|
-
// Auto-detect if enabled is not explicitly true and no loadPath/addPath configured
|
|
151
|
-
const hasLocales = detectLocalesDirectory(
|
|
152
|
-
appDirectory,
|
|
153
|
-
normalizedConfig,
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (hasLocales) {
|
|
157
|
-
// Auto-enable backend if locales directory is detected
|
|
158
|
-
backendOptions = {
|
|
159
|
-
...entryBackendOptions,
|
|
160
|
-
enabled: true,
|
|
161
|
-
};
|
|
162
|
-
} else {
|
|
163
|
-
backendOptions = entryBackendOptions;
|
|
164
|
-
}
|
|
165
94
|
} else {
|
|
166
95
|
backendOptions = entryBackendOptions;
|
|
167
96
|
}
|
|
168
97
|
}
|
|
169
98
|
} else {
|
|
170
99
|
// No backend config provided, try auto-detection
|
|
171
|
-
|
|
172
|
-
appDirectory,
|
|
173
|
-
normalizedConfig,
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (hasLocales) {
|
|
100
|
+
if (detectedLocales) {
|
|
177
101
|
// Auto-enable backend if locales directory is detected
|
|
178
|
-
backendOptions =
|
|
179
|
-
|
|
180
|
-
|
|
102
|
+
backendOptions = applyDetectedBackendPaths(
|
|
103
|
+
getBackendOptions(entrypoint.entryName, {
|
|
104
|
+
enabled: true,
|
|
105
|
+
}),
|
|
106
|
+
detectedLocales,
|
|
107
|
+
);
|
|
181
108
|
}
|
|
182
109
|
}
|
|
183
110
|
|