@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.
@@ -154,7 +154,7 @@ const useModernI18n = ()=>{
154
154
  language: currentLanguage,
155
155
  changeLanguage,
156
156
  t,
157
- i18nInstance,
157
+ i18nInstance: i18nInstance,
158
158
  supportedLanguages: languages || [],
159
159
  localisedUrls,
160
160
  isLanguageSupported,
@@ -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
- return i18nInstance.t(key, ...args);
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: {}
@@ -120,7 +120,7 @@ const useModernI18n = ()=>{
120
120
  language: currentLanguage,
121
121
  changeLanguage,
122
122
  t,
123
- i18nInstance,
123
+ i18nInstance: i18nInstance,
124
124
  supportedLanguages: languages || [],
125
125
  localisedUrls,
126
126
  isLanguageSupported,
@@ -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
- return i18nInstance.t(key, ...args);
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;
@@ -9,6 +9,9 @@ function createMinimalI18nInstance(language) {
9
9
  language,
10
10
  isInitialized: false,
11
11
  init: ()=>Promise.resolve(void 0),
12
+ t: ()=>{
13
+ throw new Error('i18nInstance.t required');
14
+ },
12
15
  use: ()=>{},
13
16
  createInstance: ()=>minimalInstance,
14
17
  services: {}
@@ -121,7 +121,7 @@ const useModernI18n = ()=>{
121
121
  language: currentLanguage,
122
122
  changeLanguage,
123
123
  t,
124
- i18nInstance,
124
+ i18nInstance: i18nInstance,
125
125
  supportedLanguages: languages || [],
126
126
  localisedUrls,
127
127
  isLanguageSupported,
@@ -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
- return i18nInstance.t(key, ...args);
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: 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): void;
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) => void;
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) => void;
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) => void | 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) => void | 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
- (callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
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: (plugin: unknown) => void;
27
- createInstance?: (options?: I18nInitOptions) => I18nInstance;
28
- cloneInstance?: (options?: I18nInitOptions) => I18nInstance;
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?: (event: string, ...args: unknown[]) => void;
31
- reloadResources?: (language?: string, namespace?: string) => Promise<void>;
32
- removeResourceBundle?: (language: string, namespace: string) => I18nInstance;
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
- languageDetector?: {
35
- detect: (request?: unknown, options?: unknown) => string | string[] | undefined;
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
- backend?: BackendOptions;
44
- [key: string]: unknown;
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.97",
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.97",
95
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.97",
96
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.97",
97
- "@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.97",
98
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.97",
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.97",
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.97",
123
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.97",
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,
@@ -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: 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 = (): UseModernI18nReturn => {
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
- i18nInstance,
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
- return i18nInstance.t(key, ...args) as string;
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(
@@ -17,7 +17,12 @@ import {
17
17
  import './types';
18
18
 
19
19
  export type { I18nSdkLoader, I18nSdkLoadOptions } from '../shared/type';
20
- export type { Resources } from './i18n/instance';
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';
@@ -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
- init: {
72
- (callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
73
- (
74
- options: I18nInitOptions,
75
- callback?: (error: unknown, t: unknown) => void,
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: (plugin: unknown) => void;
84
- createInstance?: (options?: I18nInitOptions) => I18nInstance;
85
- cloneInstance?: (options?: I18nInitOptions) => I18nInstance; // ssr need
86
- // i18next store (may not be in type definition but exists at runtime)
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?: (event: string, ...args: unknown[]) => void;
89
- reloadResources?: (language?: string, namespace?: string) => Promise<void>;
90
- removeResourceBundle?: (language: string, namespace: string) => I18nInstance;
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
- languageDetector?: {
93
- detect: (
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., SdkBackend)
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
- backend?: BackendOptions;
106
- [key: string]: unknown;
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[];
@@ -16,6 +16,7 @@ function createBackendI18nInstance(): I18nInstance {
16
16
  isInitialized: false,
17
17
  init: async () => undefined,
18
18
  use: () => {},
19
+ t: (key: string | string[]) => (Array.isArray(key) ? key[0] : key),
19
20
  options: {},
20
21
  store: {
21
22
  data: {},
@@ -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
- "baseUrl": "./",
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"