@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.97 → 3.5.0-ultramodern.99
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/runtime/context.js +1 -1
- package/dist/cjs/runtime/contextHelpers.js +2 -1
- package/dist/cjs/runtime/hooks.js +3 -0
- package/dist/esm/runtime/context.mjs +1 -1
- package/dist/esm/runtime/contextHelpers.mjs +2 -1
- package/dist/esm/runtime/hooks.mjs +3 -0
- package/dist/esm-node/runtime/context.mjs +1 -1
- package/dist/esm-node/runtime/contextHelpers.mjs +2 -1
- package/dist/esm-node/runtime/hooks.mjs +3 -0
- package/dist/types/runtime/context.d.ts +9 -3
- package/dist/types/runtime/core.d.ts +2 -2
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +1 -1
- package/dist/types/runtime/i18n/backend/middleware.d.ts +1 -1
- package/dist/types/runtime/i18n/backend/middleware.node.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/middleware.d.ts +1 -1
- package/dist/types/runtime/i18n/detection/middleware.node.d.ts +1 -1
- package/dist/types/runtime/i18n/instance.d.ts +28 -19
- package/package.json +11 -9
- package/src/runtime/context.tsx +16 -4
- package/src/runtime/contextHelpers.ts +10 -1
- package/src/runtime/core.tsx +7 -1
- package/src/runtime/hooks.ts +7 -0
- package/src/runtime/i18n/instance.ts +84 -30
- package/tests/i18nUtils.test.ts +1 -0
- package/tests/link.test.tsx +1 -0
- package/tests/routerAdapter.test.tsx +1 -0
- package/tests/type-fixture/i18nInstanceTypes.fixture.ts +29 -0
- package/tsconfig.json +3 -1
|
@@ -93,7 +93,8 @@ async function changeModernI18nLanguage(newLang, options) {
|
|
|
93
93
|
}
|
|
94
94
|
function translateI18n(i18nInstance, key, ...args) {
|
|
95
95
|
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t required');
|
|
96
|
-
|
|
96
|
+
const translate = i18nInstance.t;
|
|
97
|
+
return translate.call(i18nInstance, key, ...args);
|
|
97
98
|
}
|
|
98
99
|
function isI18nLanguageSupported(languages, lang) {
|
|
99
100
|
return languages?.includes(lang) || false;
|
|
@@ -44,6 +44,9 @@ function createMinimalI18nInstance(language) {
|
|
|
44
44
|
language,
|
|
45
45
|
isInitialized: false,
|
|
46
46
|
init: ()=>Promise.resolve(void 0),
|
|
47
|
+
t: ()=>{
|
|
48
|
+
throw new Error('i18nInstance.t required');
|
|
49
|
+
},
|
|
47
50
|
use: ()=>{},
|
|
48
51
|
createInstance: ()=>minimalInstance,
|
|
49
52
|
services: {}
|
|
@@ -55,7 +55,8 @@ async function changeModernI18nLanguage(newLang, options) {
|
|
|
55
55
|
}
|
|
56
56
|
function translateI18n(i18nInstance, key, ...args) {
|
|
57
57
|
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t required');
|
|
58
|
-
|
|
58
|
+
const translate = i18nInstance.t;
|
|
59
|
+
return translate.call(i18nInstance, key, ...args);
|
|
59
60
|
}
|
|
60
61
|
function isI18nLanguageSupported(languages, lang) {
|
|
61
62
|
return languages?.includes(lang) || false;
|
|
@@ -56,7 +56,8 @@ async function changeModernI18nLanguage(newLang, options) {
|
|
|
56
56
|
}
|
|
57
57
|
function translateI18n(i18nInstance, key, ...args) {
|
|
58
58
|
if ('function' != typeof i18nInstance.t) throw new Error('i18nInstance.t required');
|
|
59
|
-
|
|
59
|
+
const translate = i18nInstance.t;
|
|
60
|
+
return translate.call(i18nInstance, key, ...args);
|
|
60
61
|
}
|
|
61
62
|
function isI18nLanguageSupported(languages, lang) {
|
|
62
63
|
return languages?.includes(lang) || false;
|
|
@@ -10,6 +10,9 @@ function createMinimalI18nInstance(language) {
|
|
|
10
10
|
language,
|
|
11
11
|
isInitialized: false,
|
|
12
12
|
init: ()=>Promise.resolve(void 0),
|
|
13
|
+
t: ()=>{
|
|
14
|
+
throw new Error('i18nInstance.t required');
|
|
15
|
+
},
|
|
13
16
|
use: ()=>{},
|
|
14
17
|
createInstance: ()=>minimalInstance,
|
|
15
18
|
services: {}
|
|
@@ -31,11 +31,11 @@ export interface FederatedI18nBoundaryProps {
|
|
|
31
31
|
* resources used below this boundary.
|
|
32
32
|
*/
|
|
33
33
|
export declare const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps>;
|
|
34
|
-
interface UseModernI18nReturn {
|
|
34
|
+
export interface UseModernI18nReturn<TInstance extends I18nInstance = I18nInstance> {
|
|
35
35
|
language: string;
|
|
36
36
|
changeLanguage: (newLang: string) => Promise<void>;
|
|
37
37
|
t: (key: string | string[], ...args: any[]) => string;
|
|
38
|
-
i18nInstance:
|
|
38
|
+
i18nInstance: TInstance;
|
|
39
39
|
supportedLanguages: string[];
|
|
40
40
|
localisedUrls?: LocalisedUrlsOption;
|
|
41
41
|
isLanguageSupported: (lang: string) => boolean;
|
|
@@ -51,8 +51,14 @@ interface UseModernI18nReturn {
|
|
|
51
51
|
* - List of supported languages
|
|
52
52
|
* - Helper function to check if language is supported
|
|
53
53
|
*
|
|
54
|
+
* @typeParam TInstance - The concrete shape of the i18n instance held by the
|
|
55
|
+
* provider (e.g. i18next's `i18n`, or a wrapper type). Constrained to
|
|
56
|
+
* `I18nInstance`, so a nonsense argument is rejected; within that constraint it
|
|
57
|
+
* is still a caller assertion — the provider stores the base type and the
|
|
58
|
+
* narrowing is not verified at runtime. Pass it only when you know which
|
|
59
|
+
* instance the provider was given.
|
|
54
60
|
* @param options - Optional configuration to override context settings
|
|
55
61
|
* @returns Object containing i18n functionality and utilities
|
|
56
62
|
*/
|
|
57
|
-
export declare const useModernI18n: () => UseModernI18nReturn
|
|
63
|
+
export declare const useModernI18n: <TInstance extends I18nInstance = I18nInstance>() => UseModernI18nReturn<TInstance>;
|
|
58
64
|
export {};
|
|
@@ -4,7 +4,7 @@ import type { I18nInitOptions, I18nInstance } from './i18n';
|
|
|
4
4
|
import { type LoadReactI18nextIntegration } from './reactI18next';
|
|
5
5
|
import './types';
|
|
6
6
|
export type { I18nSdkLoader, I18nSdkLoadOptions } from '../shared/type';
|
|
7
|
-
export type { Resources } from './i18n/instance';
|
|
7
|
+
export type { I18nInitOptions, I18nInstance, Resources, TranslateFn, } from './i18n/instance';
|
|
8
8
|
export type { LoadReactI18nextIntegration, ReactI18nextIntegration, } from './reactI18next';
|
|
9
9
|
export interface I18nPluginOptions {
|
|
10
10
|
entryName?: string;
|
|
@@ -19,7 +19,7 @@ export interface I18nPluginOptions {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const createI18nPlugin: (loadReactI18nextIntegration?: LoadReactI18nextIntegration) => ((options: I18nPluginOptions) => RuntimePlugin);
|
|
21
21
|
export type { AllowedLinkTarget, CanonicalRoutePath, UltramodernCanonicalRoutes, } from './canonicalRoutes';
|
|
22
|
-
export { FederatedI18nBoundary, type FederatedI18nBoundaryProps, useModernI18n, } from './context';
|
|
22
|
+
export { FederatedI18nBoundary, type FederatedI18nBoundaryProps, type UseModernI18nReturn, useModernI18n, } from './context';
|
|
23
23
|
export { I18nLink, type I18nLinkProps } from './I18nLink';
|
|
24
24
|
export { Link, type LinkActiveOptions, type LinkBaseProps, type LinkParams, type LinkProps, } from './Link';
|
|
25
25
|
export { canonicalPath, type LocalizedPathsConfig, localizePath, type UseLocalizedLocationReturn, type UseLocalizedPathsReturn, useLocalizedLocation, useLocalizedPaths, } from './localizedPaths';
|
|
@@ -11,5 +11,5 @@ type BackendConstructor = new (...args: never[]) => unknown;
|
|
|
11
11
|
* @param BackendBase - The base backend class (for non-chained use)
|
|
12
12
|
* @param backend - Optional backend configuration
|
|
13
13
|
*/
|
|
14
|
-
export declare function useI18nextBackendCommon(i18nInstance: I18nInstance, BackendWithSave: BackendConstructor, BackendBase: BackendConstructor, backend?: BackendConfigWithChained):
|
|
14
|
+
export declare function useI18nextBackendCommon(i18nInstance: I18nInstance, BackendWithSave: BackendConstructor, BackendBase: BackendConstructor, backend?: BackendConfigWithChained): unknown;
|
|
15
15
|
export {};
|
|
@@ -9,4 +9,4 @@ import type { I18nInstance } from '../instance';
|
|
|
9
9
|
export declare class HttpBackendWithSave extends Backend {
|
|
10
10
|
save(_language: string, _namespace: string, _data: unknown): void;
|
|
11
11
|
}
|
|
12
|
-
export declare const useI18nextBackend: (i18nInstance: I18nInstance, backend?: ExtendedBackendOptions) =>
|
|
12
|
+
export declare const useI18nextBackend: (i18nInstance: I18nInstance, backend?: ExtendedBackendOptions) => unknown;
|
|
@@ -12,5 +12,5 @@ export declare class FsBackendWithSave extends Backend {
|
|
|
12
12
|
save(_language: string, _namespace: string, _data: unknown): void;
|
|
13
13
|
}
|
|
14
14
|
export declare const HttpBackendWithSave: typeof FsBackendWithSave;
|
|
15
|
-
export declare const useI18nextBackend: (i18nInstance: I18nInstance, backend?: ExtendedBackendOptions) =>
|
|
15
|
+
export declare const useI18nextBackend: (i18nInstance: I18nInstance, backend?: ExtendedBackendOptions) => unknown;
|
|
16
16
|
export {};
|
|
@@ -4,7 +4,7 @@ import type { I18nInstance, LanguageDetectorOptions } from '../instance';
|
|
|
4
4
|
* Must be called before init() to properly register the detector
|
|
5
5
|
* For wrapper instances, ensure detector is registered on the underlying i18next instance
|
|
6
6
|
*/
|
|
7
|
-
export declare const useI18nextLanguageDetector: (i18nInstance: I18nInstance) =>
|
|
7
|
+
export declare const useI18nextLanguageDetector: (i18nInstance: I18nInstance) => unknown;
|
|
8
8
|
/**
|
|
9
9
|
* Read language directly from localStorage/cookie
|
|
10
10
|
* Fallback when detector is not available in services
|
|
@@ -9,7 +9,7 @@ export declare const readLanguageFromStorage: (_detectionOptions?: LanguageDetec
|
|
|
9
9
|
* Register LanguageDetector plugin to i18n instance
|
|
10
10
|
* Must be called before init() to properly register the detector
|
|
11
11
|
*/
|
|
12
|
-
export declare const useI18nextLanguageDetector: (i18nInstance: I18nInstance) =>
|
|
12
|
+
export declare const useI18nextLanguageDetector: (i18nInstance: I18nInstance) => unknown;
|
|
13
13
|
/**
|
|
14
14
|
* Detect language using i18next-http-middleware LanguageDetector
|
|
15
15
|
* For initialized instances without detector in services, manually create a detector instance
|
|
@@ -14,36 +14,45 @@ type I18nWrapperInstance = I18nInstance & {
|
|
|
14
14
|
};
|
|
15
15
|
export declare function isI18nWrapperInstance(obj: unknown): obj is I18nWrapperInstance;
|
|
16
16
|
export declare function getActualI18nextInstance(instance: I18nInstance): I18nInstance;
|
|
17
|
+
export type TranslateFn = (key: string | string[], options?: Record<string, unknown>) => string;
|
|
17
18
|
export interface I18nInstance {
|
|
18
19
|
language: string;
|
|
19
20
|
isInitialized?: boolean;
|
|
20
|
-
init:
|
|
21
|
-
|
|
22
|
-
(options: I18nInitOptions, callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
|
|
23
|
-
};
|
|
24
|
-
changeLanguage?: (lng?: string, callback?: (error: unknown, t: unknown) => void) => Promise<unknown>;
|
|
21
|
+
init(options?: any, callback?: any): Promise<any>;
|
|
22
|
+
changeLanguage?(lng?: string, callback?: any): Promise<any>;
|
|
25
23
|
setLang?: (lang: string) => void | Promise<void>;
|
|
26
|
-
use
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
use(plugin: any): unknown;
|
|
25
|
+
t: TranslateFn;
|
|
26
|
+
exists?: (key: string | string[], options?: Record<string, unknown>) => boolean;
|
|
27
|
+
getFixedT?: (lng: string | readonly string[] | null, ns?: string | readonly string[] | null, keyPrefix?: string) => TranslateFn;
|
|
28
|
+
hasLoadedNamespace?: (ns: string | readonly string[], options?: Record<string, unknown>) => boolean;
|
|
29
|
+
dir?: (lng?: string) => string;
|
|
30
|
+
format?: (value: unknown, format?: string, lng?: string, options?: Record<string, unknown>) => string;
|
|
31
|
+
languages?: readonly string[];
|
|
32
|
+
resolvedLanguage?: string;
|
|
33
|
+
loadNamespaces?: (ns: string | readonly string[], callback?: (...args: any[]) => void) => Promise<void>;
|
|
34
|
+
loadLanguages?: (lngs: string | readonly string[], callback?: (...args: any[]) => void) => Promise<void>;
|
|
35
|
+
addResourceBundle?: (lng: string, ns: string, resources: Record<string, unknown>, deep?: boolean, overwrite?: boolean) => unknown;
|
|
36
|
+
getResourceBundle?: (lng: string, ns: string) => unknown;
|
|
37
|
+
getDataByLanguage?: (lng: string) => Record<string, Record<string, string>> | undefined;
|
|
38
|
+
createInstance?(options?: any, callback?: any): I18nInstance;
|
|
39
|
+
cloneInstance?(options?: any, callback?: any): I18nInstance;
|
|
29
40
|
store?: I18nResourceStore;
|
|
30
|
-
emit
|
|
31
|
-
reloadResources
|
|
32
|
-
removeResourceBundle
|
|
41
|
+
emit?(event: string, ...args: any[]): unknown;
|
|
42
|
+
reloadResources?(language?: any, namespace?: any, callback?: any): Promise<void>;
|
|
43
|
+
removeResourceBundle?(language: string, namespace: string): I18nInstance;
|
|
33
44
|
services?: {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[key: string]: unknown;
|
|
37
|
-
};
|
|
45
|
+
store?: unknown;
|
|
46
|
+
languageDetector?: any;
|
|
38
47
|
resourceStore?: I18nResourceStore;
|
|
39
48
|
backend?: unknown;
|
|
40
|
-
[key: string]: unknown;
|
|
41
49
|
};
|
|
42
50
|
options?: {
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
detection?: any;
|
|
52
|
+
backend?: any;
|
|
53
|
+
ns?: any;
|
|
54
|
+
defaultNS?: any;
|
|
45
55
|
};
|
|
46
|
-
[key: string]: unknown;
|
|
47
56
|
}
|
|
48
57
|
type LanguageDetectorOrder = string[];
|
|
49
58
|
type LanguageDetectorCaches = boolean | string[];
|
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.99",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -91,11 +91,12 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.
|
|
95
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.
|
|
96
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.
|
|
97
|
-
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.
|
|
98
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.
|
|
94
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.99",
|
|
95
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.99",
|
|
96
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.99",
|
|
97
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.99",
|
|
98
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.99",
|
|
99
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.99",
|
|
99
100
|
"@swc/helpers": "^0.5.23",
|
|
100
101
|
"i18next-browser-languagedetector": "^8.2.1",
|
|
101
102
|
"i18next-chained-backend": "^5.0.5",
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
"i18next-http-middleware": "^3.9.7"
|
|
105
106
|
},
|
|
106
107
|
"peerDependencies": {
|
|
107
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
108
|
+
"@modern-js/runtime": "3.5.0-ultramodern.99",
|
|
108
109
|
"i18next": ">=26.3.1",
|
|
109
110
|
"react": "^19.2.7",
|
|
110
111
|
"react-dom": "^19.2.7",
|
|
@@ -119,8 +120,8 @@
|
|
|
119
120
|
}
|
|
120
121
|
},
|
|
121
122
|
"devDependencies": {
|
|
122
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.
|
|
123
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.
|
|
123
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.99",
|
|
124
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.99",
|
|
124
125
|
"@rslib/core": "0.23.2",
|
|
125
126
|
"@types/node": "^26.1.1",
|
|
126
127
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
@@ -128,6 +129,7 @@
|
|
|
128
129
|
"react": "^19.2.7",
|
|
129
130
|
"react-dom": "^19.2.7",
|
|
130
131
|
"react-i18next": "17.0.9",
|
|
132
|
+
"ts-node": "^10.9.2",
|
|
131
133
|
"typescript": "^7.0.2"
|
|
132
134
|
},
|
|
133
135
|
"sideEffects": false,
|
package/src/runtime/context.tsx
CHANGED
|
@@ -187,11 +187,13 @@ export const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps> = ({
|
|
|
187
187
|
return <ModernI18nProvider value={value}>{children}</ModernI18nProvider>;
|
|
188
188
|
};
|
|
189
189
|
|
|
190
|
-
interface UseModernI18nReturn
|
|
190
|
+
export interface UseModernI18nReturn<
|
|
191
|
+
TInstance extends I18nInstance = I18nInstance,
|
|
192
|
+
> {
|
|
191
193
|
language: string;
|
|
192
194
|
changeLanguage: (newLang: string) => Promise<void>;
|
|
193
195
|
t: (key: string | string[], ...args: any[]) => string;
|
|
194
|
-
i18nInstance:
|
|
196
|
+
i18nInstance: TInstance;
|
|
195
197
|
supportedLanguages: string[];
|
|
196
198
|
localisedUrls?: LocalisedUrlsOption;
|
|
197
199
|
isLanguageSupported: (lang: string) => boolean;
|
|
@@ -209,10 +211,18 @@ interface UseModernI18nReturn {
|
|
|
209
211
|
* - List of supported languages
|
|
210
212
|
* - Helper function to check if language is supported
|
|
211
213
|
*
|
|
214
|
+
* @typeParam TInstance - The concrete shape of the i18n instance held by the
|
|
215
|
+
* provider (e.g. i18next's `i18n`, or a wrapper type). Constrained to
|
|
216
|
+
* `I18nInstance`, so a nonsense argument is rejected; within that constraint it
|
|
217
|
+
* is still a caller assertion — the provider stores the base type and the
|
|
218
|
+
* narrowing is not verified at runtime. Pass it only when you know which
|
|
219
|
+
* instance the provider was given.
|
|
212
220
|
* @param options - Optional configuration to override context settings
|
|
213
221
|
* @returns Object containing i18n functionality and utilities
|
|
214
222
|
*/
|
|
215
|
-
export const useModernI18n =
|
|
223
|
+
export const useModernI18n = <
|
|
224
|
+
TInstance extends I18nInstance = I18nInstance,
|
|
225
|
+
>(): UseModernI18nReturn<TInstance> => {
|
|
216
226
|
const context = useContext(ModernI18nContext);
|
|
217
227
|
if (!context) {
|
|
218
228
|
throw new Error('useModernI18n must be used within ModernI18nProvider');
|
|
@@ -303,7 +313,9 @@ export const useModernI18n = (): UseModernI18nReturn => {
|
|
|
303
313
|
language: currentLanguage,
|
|
304
314
|
changeLanguage,
|
|
305
315
|
t,
|
|
306
|
-
|
|
316
|
+
// The provider stores the instance as the base `I18nInstance`; the caller
|
|
317
|
+
// narrows to the concrete instance type via the TInstance type argument.
|
|
318
|
+
i18nInstance: i18nInstance as TInstance,
|
|
307
319
|
supportedLanguages: languages || [],
|
|
308
320
|
localisedUrls,
|
|
309
321
|
isLanguageSupported,
|
|
@@ -188,7 +188,16 @@ export function translateI18n(
|
|
|
188
188
|
throw new Error('i18nInstance.t required');
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
// Called through `.call` with the original receiver: `I18nInstance.t` is a
|
|
192
|
+
// method, and custom instances (wrappers, class-based translators) may read
|
|
193
|
+
// `this`. Extracting the function and calling it bare would silently drop the
|
|
194
|
+
// receiver — i18next's own `t` happens to be bound, which would hide it.
|
|
195
|
+
const translate = i18nInstance.t as (
|
|
196
|
+
this: I18nInstance,
|
|
197
|
+
key: string | string[],
|
|
198
|
+
...rest: unknown[]
|
|
199
|
+
) => string;
|
|
200
|
+
return translate.call(i18nInstance, key, ...args);
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
export function isI18nLanguageSupported(
|
package/src/runtime/core.tsx
CHANGED
|
@@ -17,7 +17,12 @@ import {
|
|
|
17
17
|
import './types';
|
|
18
18
|
|
|
19
19
|
export type { I18nSdkLoader, I18nSdkLoadOptions } from '../shared/type';
|
|
20
|
-
export type {
|
|
20
|
+
export type {
|
|
21
|
+
I18nInitOptions,
|
|
22
|
+
I18nInstance,
|
|
23
|
+
Resources,
|
|
24
|
+
TranslateFn,
|
|
25
|
+
} from './i18n/instance';
|
|
21
26
|
export type {
|
|
22
27
|
LoadReactI18nextIntegration,
|
|
23
28
|
ReactI18nextIntegration,
|
|
@@ -111,6 +116,7 @@ export type {
|
|
|
111
116
|
export {
|
|
112
117
|
FederatedI18nBoundary,
|
|
113
118
|
type FederatedI18nBoundaryProps,
|
|
119
|
+
type UseModernI18nReturn,
|
|
114
120
|
useModernI18n,
|
|
115
121
|
} from './context';
|
|
116
122
|
export { I18nLink, type I18nLinkProps } from './I18nLink';
|
package/src/runtime/hooks.ts
CHANGED
|
@@ -33,6 +33,13 @@ function createMinimalI18nInstance(language: string): I18nInstance {
|
|
|
33
33
|
language,
|
|
34
34
|
isInitialized: false,
|
|
35
35
|
init: () => Promise.resolve(undefined),
|
|
36
|
+
// Keep the pre-existing loud failure: an uninitialised instance has no
|
|
37
|
+
// translator. Never echo the key back — that would silently render raw
|
|
38
|
+
// keys as if they were translations. This throws the same error that
|
|
39
|
+
// translateI18n (contextHelpers.ts:187) already throws today.
|
|
40
|
+
t: () => {
|
|
41
|
+
throw new Error('i18nInstance.t required');
|
|
42
|
+
},
|
|
36
43
|
use: () => {},
|
|
37
44
|
createInstance: () => minimalInstance,
|
|
38
45
|
services: {},
|
|
@@ -65,47 +65,101 @@ export function getActualI18nextInstance(instance: I18nInstance): I18nInstance {
|
|
|
65
65
|
return instance;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export type TranslateFn = (
|
|
69
|
+
key: string | string[],
|
|
70
|
+
options?: Record<string, unknown>,
|
|
71
|
+
) => string;
|
|
72
|
+
|
|
73
|
+
// FORK: intentionally diverges from upstream @modern-js/plugin-i18n. Upstream
|
|
74
|
+
// declares a top-level `[key: string]: any` and overloaded call-signature
|
|
75
|
+
// properties; both make i18next's `i18n` structurally unassignable to this
|
|
76
|
+
// type, so the documented `i18nInstance: i18next` usage does not typecheck.
|
|
77
|
+
// Do NOT restore upstream's shape when resolving a sync merge — the guard is
|
|
78
|
+
// tests/type-fixture/i18nInstanceTypes.fixture.ts.
|
|
68
79
|
export interface I18nInstance {
|
|
69
80
|
language: string;
|
|
70
81
|
isInitialized?: boolean;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
): Promise<unknown>;
|
|
77
|
-
};
|
|
78
|
-
changeLanguage?: (
|
|
79
|
-
lng?: string,
|
|
80
|
-
callback?: (error: unknown, t: unknown) => void,
|
|
81
|
-
) => Promise<unknown>;
|
|
82
|
+
// Single non-overloaded method signatures. Method syntax is required for
|
|
83
|
+
// bivariant parameter checking; a SINGLE signature is required because
|
|
84
|
+
// overload bivariance is order-dependent across program compositions.
|
|
85
|
+
init(options?: any, callback?: any): Promise<any>;
|
|
86
|
+
changeLanguage?(lng?: string, callback?: any): Promise<any>;
|
|
82
87
|
setLang?: (lang: string) => void | Promise<void>;
|
|
83
|
-
use
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
use(plugin: any): unknown;
|
|
89
|
+
t: TranslateFn;
|
|
90
|
+
exists?: (
|
|
91
|
+
key: string | string[],
|
|
92
|
+
options?: Record<string, unknown>,
|
|
93
|
+
) => boolean;
|
|
94
|
+
// `lng` is required: i18next's getFixedT does not accept `undefined`.
|
|
95
|
+
getFixedT?: (
|
|
96
|
+
lng: string | readonly string[] | null,
|
|
97
|
+
ns?: string | readonly string[] | null,
|
|
98
|
+
keyPrefix?: string,
|
|
99
|
+
) => TranslateFn;
|
|
100
|
+
hasLoadedNamespace?: (
|
|
101
|
+
ns: string | readonly string[],
|
|
102
|
+
options?: Record<string, unknown>,
|
|
103
|
+
) => boolean;
|
|
104
|
+
dir?: (lng?: string) => string;
|
|
105
|
+
format?: (
|
|
106
|
+
value: unknown,
|
|
107
|
+
format?: string,
|
|
108
|
+
lng?: string,
|
|
109
|
+
options?: Record<string, unknown>,
|
|
110
|
+
) => string;
|
|
111
|
+
// readonly: i18next declares `languages: readonly string[]`.
|
|
112
|
+
languages?: readonly string[];
|
|
113
|
+
resolvedLanguage?: string;
|
|
114
|
+
loadNamespaces?: (
|
|
115
|
+
ns: string | readonly string[],
|
|
116
|
+
callback?: (...args: any[]) => void,
|
|
117
|
+
) => Promise<void>;
|
|
118
|
+
loadLanguages?: (
|
|
119
|
+
lngs: string | readonly string[],
|
|
120
|
+
callback?: (...args: any[]) => void,
|
|
121
|
+
) => Promise<void>;
|
|
122
|
+
addResourceBundle?: (
|
|
123
|
+
lng: string,
|
|
124
|
+
ns: string,
|
|
125
|
+
resources: Record<string, unknown>,
|
|
126
|
+
deep?: boolean,
|
|
127
|
+
overwrite?: boolean,
|
|
128
|
+
) => unknown;
|
|
129
|
+
getResourceBundle?: (lng: string, ns: string) => unknown;
|
|
130
|
+
getDataByLanguage?: (
|
|
131
|
+
lng: string,
|
|
132
|
+
) => Record<string, Record<string, string>> | undefined;
|
|
133
|
+
createInstance?(options?: any, callback?: any): I18nInstance;
|
|
134
|
+
cloneInstance?(options?: any, callback?: any): I18nInstance; // ssr need
|
|
135
|
+
// i18next store (may not be in the type definition but exists at runtime)
|
|
87
136
|
store?: I18nResourceStore;
|
|
88
|
-
emit
|
|
89
|
-
reloadResources
|
|
90
|
-
|
|
137
|
+
emit?(event: string, ...args: any[]): unknown;
|
|
138
|
+
reloadResources?(
|
|
139
|
+
language?: any,
|
|
140
|
+
namespace?: any,
|
|
141
|
+
callback?: any,
|
|
142
|
+
): Promise<void>;
|
|
143
|
+
removeResourceBundle?(language: string, namespace: string): I18nInstance;
|
|
144
|
+
// No nested index signature: i18next's `Services` is an interface and would
|
|
145
|
+
// fail "Index signature for type 'string' is missing".
|
|
91
146
|
services?: {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
request?: unknown,
|
|
95
|
-
options?: unknown,
|
|
96
|
-
) => string | string[] | undefined;
|
|
97
|
-
[key: string]: unknown;
|
|
98
|
-
};
|
|
147
|
+
store?: unknown;
|
|
148
|
+
languageDetector?: any;
|
|
99
149
|
resourceStore?: I18nResourceStore;
|
|
100
|
-
backend?: unknown; // Backend instance (e.g
|
|
101
|
-
[key: string]: unknown;
|
|
150
|
+
backend?: unknown; // Backend instance (e.g. SdkBackend)
|
|
102
151
|
};
|
|
103
152
|
// i18next instance options (available after initialization)
|
|
104
153
|
options?: {
|
|
105
|
-
|
|
106
|
-
|
|
154
|
+
detection?: any;
|
|
155
|
+
backend?: any;
|
|
156
|
+
ns?: any;
|
|
157
|
+
defaultNS?: any;
|
|
107
158
|
};
|
|
108
|
-
[key: string]: unknown
|
|
159
|
+
// NO `[key: string]: unknown`. TypeScript never grants an interface an
|
|
160
|
+
// implicit index signature, so any index signature here makes i18next's
|
|
161
|
+
// `i18n` permanently unassignable. BREAKING for consumers that read
|
|
162
|
+
// undeclared properties off I18nInstance.
|
|
109
163
|
}
|
|
110
164
|
|
|
111
165
|
type LanguageDetectorOrder = string[];
|
package/tests/i18nUtils.test.ts
CHANGED
package/tests/link.test.tsx
CHANGED
|
@@ -75,6 +75,7 @@ function createI18nInstance(language = 'en'): I18nInstance {
|
|
|
75
75
|
isInitialized: true,
|
|
76
76
|
init: () => Promise.resolve(undefined),
|
|
77
77
|
use: () => {},
|
|
78
|
+
t: (key: string | string[]) => (Array.isArray(key) ? key[0] : key),
|
|
78
79
|
createInstance: () => createI18nInstance(language),
|
|
79
80
|
services: {},
|
|
80
81
|
options: {},
|
|
@@ -56,6 +56,7 @@ function createI18nInstance(language = 'en'): I18nInstance {
|
|
|
56
56
|
isInitialized: true,
|
|
57
57
|
init: () => Promise.resolve(undefined),
|
|
58
58
|
use: () => {},
|
|
59
|
+
t: (key: string | string[]) => (Array.isArray(key) ? key[0] : key),
|
|
59
60
|
createInstance: () => createI18nInstance(language),
|
|
60
61
|
setLang: rstest.fn(async () => undefined),
|
|
61
62
|
changeLanguage: rstest.fn(async () => undefined),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type I18nInstance,
|
|
3
|
+
useModernI18n,
|
|
4
|
+
} from '@modern-js/plugin-i18n/runtime';
|
|
5
|
+
import i18next, { type i18n } from 'i18next';
|
|
6
|
+
|
|
7
|
+
// WRITE direction — this is what had zero coverage and was broken before the
|
|
8
|
+
// top-level index signature was removed from `I18nInstance`. Eight integration
|
|
9
|
+
// fixtures (tests/integration/i18n/*/src/modern.runtime.tsx) write exactly
|
|
10
|
+
// this and none of them is ever typechecked; this file is the guard.
|
|
11
|
+
export const w1: I18nInstance = i18next.createInstance();
|
|
12
|
+
export const w2: I18nInstance = i18next;
|
|
13
|
+
|
|
14
|
+
// READ direction.
|
|
15
|
+
declare const inst: I18nInstance;
|
|
16
|
+
export const s: string = inst.t('k');
|
|
17
|
+
export const b: boolean = inst.exists!('k');
|
|
18
|
+
export const langs: readonly string[] = inst.languages!;
|
|
19
|
+
export const fixed: string = inst.getFixedT!('en')('k');
|
|
20
|
+
|
|
21
|
+
// W2 generic, both with and without an explicit type argument.
|
|
22
|
+
type StarlingLike = i18n & { setLang?: (l: string) => void | Promise<void> };
|
|
23
|
+
export function useTyped() {
|
|
24
|
+
const r = useModernI18n<StarlingLike>();
|
|
25
|
+
return r.i18nInstance.t('k') satisfies string;
|
|
26
|
+
}
|
|
27
|
+
export function useDefault() {
|
|
28
|
+
return useModernI18n().i18nInstance.t('k') satisfies string;
|
|
29
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
"declaration": false,
|
|
5
5
|
"lib": ["DOM", "ESNext", "dom.iterable"],
|
|
6
6
|
"jsx": "preserve",
|
|
7
|
-
|
|
7
|
+
// FORK: `baseUrl` removed. TypeScript 7 / tsgo reject it outright (TS5102),
|
|
8
|
+
// so this package's own tsconfig could not be typechecked at all. `paths`
|
|
9
|
+
// is empty here, so nothing resolved through baseUrl.
|
|
8
10
|
"isolatedModules": true,
|
|
9
11
|
"paths": {},
|
|
10
12
|
"rootDir": "./src"
|