@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.35 → 3.5.0-ultramodern.37

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.
Files changed (91) hide show
  1. package/dist/cjs/cli/locales.js +15 -15
  2. package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
  3. package/dist/cjs/runtime/i18n/detection/cache.js +3 -2
  4. package/dist/cjs/runtime/i18n/instance.js +7 -7
  5. package/dist/cjs/runtime/utils.js +1 -9
  6. package/dist/cjs/server/redirectPolicy.js +14 -20
  7. package/dist/cjs/shared/detection.js +2 -104
  8. package/dist/cjs/shared/localisedUrls/index.js +16 -0
  9. package/dist/cjs/shared/localisedUrls/pathname.js +5 -2
  10. package/dist/cjs/shared/localisedUrls/redirect.js +95 -0
  11. package/dist/cjs/shared/localisedUrls/resolve.js +29 -32
  12. package/dist/cjs/shared/localisedUrls.js +15 -0
  13. package/dist/cjs/shared/utils.js +2 -8
  14. package/dist/esm/cli/locales.mjs +13 -13
  15. package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
  16. package/dist/esm/runtime/i18n/detection/cache.mjs +3 -2
  17. package/dist/esm/runtime/i18n/instance.mjs +8 -5
  18. package/dist/esm/runtime/utils.mjs +2 -10
  19. package/dist/esm/server/index.mjs +2 -2
  20. package/dist/esm/server/redirectPolicy.mjs +15 -21
  21. package/dist/esm/shared/detection.mjs +1 -71
  22. package/dist/esm/shared/localisedUrls/index.mjs +2 -1
  23. package/dist/esm/shared/localisedUrls/pathname.mjs +1 -1
  24. package/dist/esm/shared/localisedUrls/redirect.mjs +48 -0
  25. package/dist/esm/shared/localisedUrls/resolve.mjs +29 -32
  26. package/dist/esm/shared/localisedUrls.mjs +1 -1
  27. package/dist/esm/shared/utils.mjs +1 -1
  28. package/dist/esm-node/cli/locales.mjs +13 -13
  29. package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
  30. package/dist/esm-node/runtime/i18n/detection/cache.mjs +3 -2
  31. package/dist/esm-node/runtime/i18n/instance.mjs +8 -5
  32. package/dist/esm-node/runtime/utils.mjs +2 -10
  33. package/dist/esm-node/server/index.mjs +2 -2
  34. package/dist/esm-node/server/redirectPolicy.mjs +15 -21
  35. package/dist/esm-node/shared/detection.mjs +1 -71
  36. package/dist/esm-node/shared/localisedUrls/index.mjs +2 -1
  37. package/dist/esm-node/shared/localisedUrls/pathname.mjs +1 -1
  38. package/dist/esm-node/shared/localisedUrls/redirect.mjs +49 -0
  39. package/dist/esm-node/shared/localisedUrls/resolve.mjs +29 -32
  40. package/dist/esm-node/shared/localisedUrls.mjs +1 -1
  41. package/dist/esm-node/shared/utils.mjs +1 -1
  42. package/dist/types/cli/locales.d.ts +1 -1
  43. package/dist/types/runtime/context.d.ts +4 -3
  44. package/dist/types/runtime/contextHelpers.d.ts +2 -2
  45. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
  46. package/dist/types/runtime/i18n/detection/cache.d.ts +1 -1
  47. package/dist/types/runtime/i18n/detection/detector.d.ts +2 -2
  48. package/dist/types/runtime/i18n/detection/middleware.d.ts +4 -4
  49. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
  50. package/dist/types/runtime/i18n/detection/types.d.ts +8 -5
  51. package/dist/types/runtime/i18n/instance.d.ts +28 -26
  52. package/dist/types/runtime/i18n/utils.d.ts +14 -5
  53. package/dist/types/runtime/pluginSetup.d.ts +1 -1
  54. package/dist/types/runtime/providerComposition.d.ts +2 -1
  55. package/dist/types/runtime/routerAdapter.d.ts +7 -6
  56. package/dist/types/server/redirectPolicy.d.ts +14 -8
  57. package/dist/types/shared/detection.d.ts +1 -11
  58. package/dist/types/shared/localisedUrls/index.d.ts +3 -1
  59. package/dist/types/shared/localisedUrls/pathname.d.ts +1 -0
  60. package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
  61. package/dist/types/shared/localisedUrls/resolve.d.ts +2 -3
  62. package/dist/types/shared/localisedUrls.d.ts +2 -2
  63. package/dist/types/shared/utils.d.ts +0 -2
  64. package/package.json +16 -18
  65. package/src/cli/index.ts +2 -2
  66. package/src/cli/locales.ts +3 -3
  67. package/src/runtime/context.tsx +3 -3
  68. package/src/runtime/contextHelpers.ts +3 -3
  69. package/src/runtime/hooks.ts +10 -2
  70. package/src/runtime/i18n/backend/defaults.node.ts +2 -2
  71. package/src/runtime/i18n/backend/middleware.common.ts +5 -4
  72. package/src/runtime/i18n/detection/cache.ts +23 -9
  73. package/src/runtime/i18n/detection/detector.ts +15 -6
  74. package/src/runtime/i18n/detection/middleware.node.ts +26 -7
  75. package/src/runtime/i18n/detection/middleware.ts +15 -7
  76. package/src/runtime/i18n/detection/types.ts +13 -5
  77. package/src/runtime/i18n/instance.ts +56 -31
  78. package/src/runtime/i18n/utils.ts +18 -9
  79. package/src/runtime/pluginSetup.ts +1 -1
  80. package/src/runtime/providerComposition.tsx +1 -1
  81. package/src/runtime/routerAdapter.tsx +6 -6
  82. package/src/runtime/utils.ts +5 -29
  83. package/src/server/redirectPolicy.ts +51 -76
  84. package/src/shared/detection.ts +1 -1
  85. package/src/shared/localisedUrls/index.ts +12 -1
  86. package/src/shared/localisedUrls/pathname.ts +4 -1
  87. package/src/shared/localisedUrls/redirect.ts +91 -0
  88. package/src/shared/localisedUrls/resolve.ts +91 -74
  89. package/src/shared/localisedUrls.ts +6 -0
  90. package/src/shared/utils.ts +2 -2
  91. package/tests/redirectPolicy.test.ts +43 -0
@@ -14,7 +14,10 @@ import { getSupportedLanguage, isLanguageSupported } from './language';
14
14
 
15
15
  import { detectLanguage, useI18nextLanguageDetector } from './middleware';
16
16
 
17
- import type { BaseLanguageDetectionOptions } from './types';
17
+ import type {
18
+ BaseLanguageDetectionOptions,
19
+ LanguageDetectionSsrContext,
20
+ } from './types';
18
21
 
19
22
  /**
20
23
  * Initialize i18n instance for detector if needed
@@ -24,6 +27,10 @@ interface DetectorInitResult {
24
27
  isTemporary: boolean;
25
28
  }
26
29
 
30
+ type MutableDetectorInstanceState = Omit<I18nInstance, 'language'> & {
31
+ language?: string;
32
+ };
33
+
27
34
  const initializeI18nForDetector = async (
28
35
  i18nInstance: I18nInstance,
29
36
  options: BaseLanguageDetectionOptions,
@@ -48,7 +55,7 @@ const initializeI18nForDetector = async (
48
55
 
49
56
  const safeUserOptions = pickSafeDetectionOptions(options.userInitOptions);
50
57
 
51
- // Only initialize detection capability, don't load any resources to avoid conflicts with subsequent backend initialization
58
+ // Only initialize detection capability, don't load resources to avoid conflicts with subsequent backend initialization
52
59
  const initOptions: I18nInitOptions = {
53
60
  ...safeUserOptions,
54
61
  fallbackLng: options.fallbackLanguage,
@@ -81,7 +88,9 @@ const initializeI18nForDetector = async (
81
88
  */
82
89
  export const detectLanguageFromI18nextDetector = async (
83
90
  i18nInstance: I18nInstance,
84
- options: BaseLanguageDetectionOptions & { ssrContext?: any },
91
+ options: BaseLanguageDetectionOptions & {
92
+ ssrContext?: LanguageDetectionSsrContext;
93
+ },
85
94
  ): Promise<string | undefined> => {
86
95
  if (!options.i18nextDetector) {
87
96
  return undefined;
@@ -108,7 +117,7 @@ export const detectLanguageFromI18nextDetector = async (
108
117
 
109
118
  const detectorLang = detectLanguage(
110
119
  detectorInstance,
111
- request as any,
120
+ request,
112
121
  mergedDetection,
113
122
  );
114
123
 
@@ -148,8 +157,8 @@ export const detectLanguageFromI18nextDetector = async (
148
157
  });
149
158
  } else if (detectorInstance === i18nInstance) {
150
159
  // As a fallback, prevent i18nInstance from being polluted by detector init
151
- (i18nInstance as any).isInitialized = false;
152
- delete (i18nInstance as any).language;
160
+ i18nInstance.isInitialized = false;
161
+ delete (i18nInstance as unknown as MutableDetectorInstanceState).language;
153
162
  }
154
163
  }
155
164
 
@@ -1,10 +1,22 @@
1
1
  import { LanguageDetector } from 'i18next-http-middleware';
2
- import type { I18nInstance } from '../instance';
2
+ import type { I18nInstance, LanguageDetectorOptions } from '../instance';
3
+
4
+ type HttpDetectorInit = (
5
+ services: NonNullable<I18nInstance['services']>,
6
+ options?: unknown,
7
+ allOptions?: unknown,
8
+ ) => void;
9
+
10
+ type HttpDetectorDetect = (
11
+ request: unknown,
12
+ response: unknown,
13
+ detectionOrder?: unknown,
14
+ ) => string | string[] | undefined;
3
15
 
4
16
  export const cacheUserLanguage = (
5
17
  _i18nInstance: I18nInstance,
6
18
  _language: string,
7
- _detectionOptions?: any,
19
+ _detectionOptions?: unknown,
8
20
  ): void => {
9
21
  return;
10
22
  };
@@ -14,7 +26,7 @@ export const cacheUserLanguage = (
14
26
  * Not available in Node.js environment, returns undefined
15
27
  */
16
28
  export const readLanguageFromStorage = (
17
- _detectionOptions?: any,
29
+ _detectionOptions?: LanguageDetectorOptions,
18
30
  ): string | undefined => {
19
31
  // In Node.js environment, storage-based detection is not available
20
32
  return undefined;
@@ -36,8 +48,8 @@ export const useI18nextLanguageDetector = (i18nInstance: I18nInstance) => {
36
48
  */
37
49
  export const detectLanguage = (
38
50
  i18nInstance: I18nInstance,
39
- request?: any,
40
- detectionOptions?: any,
51
+ request?: unknown,
52
+ detectionOptions?: LanguageDetectorOptions,
41
53
  ): string | undefined => {
42
54
  if (!request) {
43
55
  return undefined;
@@ -65,9 +77,16 @@ export const detectLanguage = (
65
77
  const optionsToUse = detectionOptions
66
78
  ? { ...i18nInstance.options, detection: detectionOptions }
67
79
  : i18nInstance.options;
68
- manualDetector.init(i18nInstance.services, optionsToUse as any);
80
+ (manualDetector.init as unknown as HttpDetectorInit)(
81
+ i18nInstance.services,
82
+ optionsToUse,
83
+ );
69
84
 
70
- const result = (manualDetector.detect as any)(request, {}, undefined);
85
+ const result = (manualDetector.detect as unknown as HttpDetectorDetect)(
86
+ request,
87
+ {},
88
+ undefined,
89
+ );
71
90
  if (typeof result === 'string') {
72
91
  return result;
73
92
  }
@@ -1,8 +1,10 @@
1
1
  import { isBrowser } from '@modern-js/runtime';
2
2
  import LanguageDetector from 'i18next-browser-languagedetector';
3
- import type { I18nInstance } from '../instance';
3
+ import type { I18nInstance, LanguageDetectorOptions } from '../instance';
4
4
  import { getActualI18nextInstance, isI18nWrapperInstance } from '../instance';
5
5
 
6
+ type BrowserDetectorInitOptions = Parameters<LanguageDetector['init']>[1];
7
+
6
8
  /**
7
9
  * Register LanguageDetector plugin to i18n instance
8
10
  * Must be called before init() to properly register the detector
@@ -27,7 +29,7 @@ export const useI18nextLanguageDetector = (i18nInstance: I18nInstance) => {
27
29
  * Fallback when detector is not available in services
28
30
  */
29
31
  export const readLanguageFromStorage = (
30
- detectionOptions?: any,
32
+ detectionOptions?: LanguageDetectorOptions,
31
33
  ): string | undefined => {
32
34
  try {
33
35
  const options = detectionOptions || {};
@@ -112,8 +114,8 @@ export const readLanguageFromStorage = (
112
114
  */
113
115
  export const detectLanguage = (
114
116
  i18nInstance: I18nInstance,
115
- _request?: any,
116
- detectionOptions?: any,
117
+ _request?: unknown,
118
+ detectionOptions?: LanguageDetectorOptions,
117
119
  ): string | undefined => {
118
120
  try {
119
121
  // For wrapper instances, get the underlying i18next instance
@@ -160,7 +162,10 @@ export const detectLanguage = (
160
162
  const mergedOptions = detectionOptions
161
163
  ? { ...optionsToUse, detection: detectionOptions }
162
164
  : optionsToUse;
163
- manualDetector.init(servicesToUse, mergedOptions as any);
165
+ manualDetector.init(
166
+ servicesToUse,
167
+ mergedOptions as unknown as BrowserDetectorInitOptions,
168
+ );
164
169
 
165
170
  const result = manualDetector.detect();
166
171
  if (typeof result === 'string') {
@@ -186,7 +191,7 @@ export const detectLanguage = (
186
191
  export const cacheUserLanguage = (
187
192
  i18nInstance: I18nInstance,
188
193
  language: string,
189
- detectionOptions?: any,
194
+ detectionOptions?: unknown,
190
195
  ): void => {
191
196
  if (typeof window === 'undefined') {
192
197
  return;
@@ -231,7 +236,10 @@ export const cacheUserLanguage = (
231
236
  : optionsToUse;
232
237
 
233
238
  const manualDetector = new LanguageDetector();
234
- manualDetector.init(servicesToUse, mergedOptions as any);
239
+ manualDetector.init(
240
+ servicesToUse,
241
+ mergedOptions as unknown as BrowserDetectorInitOptions,
242
+ );
235
243
 
236
244
  if (typeof manualDetector.cacheUserLanguage === 'function') {
237
245
  manualDetector.cacheUserLanguage(language);
@@ -1,4 +1,12 @@
1
- import type { I18nInitOptions, LanguageDetectorOptions } from '../instance';
1
+ import type {
2
+ BackendOptions,
3
+ I18nInitOptions,
4
+ LanguageDetectorOptions,
5
+ } from '../instance';
6
+
7
+ export interface LanguageDetectionSsrContext {
8
+ request?: unknown;
9
+ }
2
10
 
3
11
  export interface BaseLanguageDetectionOptions {
4
12
  languages: string[];
@@ -7,12 +15,12 @@ export interface BaseLanguageDetectionOptions {
7
15
  i18nextDetector: boolean;
8
16
  detection?: LanguageDetectorOptions;
9
17
  userInitOptions?: I18nInitOptions;
10
- mergedBackend?: any;
18
+ mergedBackend?: BackendOptions;
11
19
  }
12
20
 
13
21
  export interface LanguageDetectionOptions extends BaseLanguageDetectionOptions {
14
22
  pathname: string;
15
- ssrContext?: any;
23
+ ssrContext?: LanguageDetectionSsrContext;
16
24
  }
17
25
 
18
26
  export interface LanguageDetectionResult {
@@ -28,6 +36,6 @@ export interface BuildInitOptionsParams {
28
36
  fallbackLanguage: string;
29
37
  languages: string[];
30
38
  userInitOptions?: I18nInitOptions;
31
- mergedDetection?: any;
32
- mergeBackend?: any;
39
+ mergedDetection?: LanguageDetectorOptions;
40
+ mergeBackend?: BackendOptions;
33
41
  }
@@ -1,9 +1,9 @@
1
1
  import type { BaseBackendOptions } from '../../shared/type';
2
2
 
3
- export interface I18nResourceStore {
3
+ interface I18nResourceStore {
4
4
  data?: {
5
5
  [language: string]: {
6
- [namespace: string]: string | { [key: string]: any };
6
+ [namespace: string]: ResourceValue;
7
7
  };
8
8
  };
9
9
  addResourceBundle?: (
@@ -15,30 +15,49 @@ export interface I18nResourceStore {
15
15
  ) => void;
16
16
  }
17
17
 
18
- export function isI18nWrapperInstance(obj: any): boolean {
18
+ type I18nWrapperInstance = I18nInstance & {
19
+ i18nInstance: {
20
+ instance: I18nInstance;
21
+ };
22
+ };
23
+
24
+ export function isI18nWrapperInstance(
25
+ obj: unknown,
26
+ ): obj is I18nWrapperInstance {
19
27
  if (!obj || typeof obj !== 'object') {
20
28
  return false;
21
29
  }
22
- if (!obj.i18nInstance || typeof obj.i18nInstance !== 'object') {
30
+ const candidate = obj as {
31
+ i18nInstance?: unknown;
32
+ init?: unknown;
33
+ use?: unknown;
34
+ };
35
+ if (!candidate.i18nInstance || typeof candidate.i18nInstance !== 'object') {
23
36
  return false;
24
37
  }
25
- if (!obj.i18nInstance.instance) {
38
+ const wrapper = candidate.i18nInstance as { instance?: unknown };
39
+ if (!wrapper.instance) {
26
40
  return false;
27
41
  }
28
- if (typeof obj.init !== 'function' || typeof obj.use !== 'function') {
42
+ if (
43
+ typeof candidate.init !== 'function' ||
44
+ typeof candidate.use !== 'function'
45
+ ) {
29
46
  return false;
30
47
  }
31
48
  return true;
32
49
  }
33
50
 
34
- export function getI18nWrapperI18nextInstance(wrapperInstance: any): any {
51
+ function getI18nWrapperI18nextInstance(
52
+ wrapperInstance: unknown,
53
+ ): I18nInstance | null {
35
54
  if (isI18nWrapperInstance(wrapperInstance)) {
36
55
  return wrapperInstance.i18nInstance?.instance;
37
56
  }
38
57
  return null;
39
58
  }
40
59
 
41
- export function getActualI18nextInstance(instance: I18nInstance | any): any {
60
+ export function getActualI18nextInstance(instance: I18nInstance): I18nInstance {
42
61
  if (isI18nWrapperInstance(instance)) {
43
62
  const i18nextInstance = getI18nWrapperI18nextInstance(instance);
44
63
  return i18nextInstance || instance;
@@ -50,39 +69,42 @@ export interface I18nInstance {
50
69
  language: string;
51
70
  isInitialized?: boolean;
52
71
  init: {
53
- (callback?: (error: any, t: any) => void): Promise<any>;
72
+ (callback?: (error: unknown, t: unknown) => void): Promise<unknown>;
54
73
  (
55
74
  options: I18nInitOptions,
56
- callback?: (error: any, t: any) => void,
57
- ): Promise<any>;
75
+ callback?: (error: unknown, t: unknown) => void,
76
+ ): Promise<unknown>;
58
77
  };
59
78
  changeLanguage?: (
60
79
  lng?: string,
61
- callback?: (error: any, t: any) => void,
62
- ) => Promise<any>;
80
+ callback?: (error: unknown, t: unknown) => void,
81
+ ) => Promise<unknown>;
63
82
  setLang?: (lang: string) => void | Promise<void>;
64
- use: (plugin: any) => void;
83
+ use: (plugin: unknown) => void;
65
84
  createInstance?: (options?: I18nInitOptions) => I18nInstance;
66
85
  cloneInstance?: () => I18nInstance; // ssr need
67
86
  // i18next store (may not be in type definition but exists at runtime)
68
87
  store?: I18nResourceStore;
69
- emit?: (event: string, ...args: any[]) => void;
88
+ emit?: (event: string, ...args: unknown[]) => void;
70
89
  reloadResources?: (language?: string, namespace?: string) => Promise<void>;
71
90
  services?: {
72
91
  languageDetector?: {
73
- detect: (request?: any, options?: any) => string | string[] | undefined;
74
- [key: string]: any;
92
+ detect: (
93
+ request?: unknown,
94
+ options?: unknown,
95
+ ) => string | string[] | undefined;
96
+ [key: string]: unknown;
75
97
  };
76
98
  resourceStore?: I18nResourceStore;
77
- backend?: any; // Backend instance (e.g., SdkBackend)
78
- [key: string]: any;
99
+ backend?: unknown; // Backend instance (e.g., SdkBackend)
100
+ [key: string]: unknown;
79
101
  };
80
102
  // i18next instance options (available after initialization)
81
103
  options?: {
82
104
  backend?: BackendOptions;
83
- [key: string]: any;
105
+ [key: string]: unknown;
84
106
  };
85
- [key: string]: any;
107
+ [key: string]: unknown;
86
108
  }
87
109
 
88
110
  type LanguageDetectorOrder = string[];
@@ -101,12 +123,12 @@ export interface LanguageDetectorOptions {
101
123
  }
102
124
 
103
125
  export interface BackendOptions extends Omit<BaseBackendOptions, 'enabled'> {
104
- parse?: (data: string) => any;
105
- stringify?: (data: any) => string;
126
+ parse?: (data: string) => unknown;
127
+ stringify?: (data: unknown) => string;
106
128
  [key: string]: any;
107
129
  }
108
130
 
109
- export type ResourceValue = string | { [key: string]: ResourceValue };
131
+ type ResourceValue = string | { [key: string]: ResourceValue };
110
132
 
111
133
  export interface Resources {
112
134
  [lng: string]: {
@@ -126,15 +148,15 @@ export type I18nInitOptions = {
126
148
  defaultNS?: string | string[];
127
149
  interpolation?: {
128
150
  escapeValue?: boolean;
129
- [key: string]: any;
151
+ [key: string]: unknown;
130
152
  };
131
153
  react?: {
132
154
  useSuspense?: boolean;
133
- [key: string]: any;
155
+ [key: string]: unknown;
134
156
  };
135
157
  };
136
158
 
137
- export function isI18nInstance(obj: any): obj is I18nInstance {
159
+ export function isI18nInstance(obj: unknown): obj is I18nInstance {
138
160
  if (!obj || typeof obj !== 'object') {
139
161
  return false;
140
162
  }
@@ -143,7 +165,10 @@ export function isI18nInstance(obj: any): obj is I18nInstance {
143
165
  return true;
144
166
  }
145
167
 
146
- return typeof obj.init === 'function' && typeof obj.use === 'function';
168
+ const candidate = obj as { init?: unknown; use?: unknown };
169
+ return (
170
+ typeof candidate.init === 'function' && typeof candidate.use === 'function'
171
+ );
147
172
  }
148
173
 
149
174
  async function tryImportI18next(): Promise<I18nInstance | null> {
@@ -170,8 +195,8 @@ async function createI18nextInstance(): Promise<I18nInstance | null> {
170
195
  }
171
196
 
172
197
  export function getI18nextInstanceForProvider(
173
- instance: I18nInstance | any,
174
- ): any {
198
+ instance: I18nInstance,
199
+ ): I18nInstance {
175
200
  if (isI18nWrapperInstance(instance)) {
176
201
  const i18nextInstance = getI18nWrapperI18nextInstance(instance);
177
202
  if (i18nextInstance) {
@@ -183,7 +208,7 @@ export function getI18nextInstanceForProvider(
183
208
  }
184
209
 
185
210
  export async function getI18nInstance(
186
- userInstance?: I18nInstance | any,
211
+ userInstance?: unknown,
187
212
  ): Promise<I18nInstance> {
188
213
  if (userInstance) {
189
214
  if (isI18nWrapperInstance(userInstance)) {
@@ -11,7 +11,16 @@ import {
11
11
  isI18nWrapperInstance,
12
12
  } from './instance';
13
13
 
14
- export function assertI18nInstance(obj: any): asserts obj is I18nInstance {
14
+ type MergedBackendOptions = NonNullable<I18nInitOptions['backend']> & {
15
+ _useChainedBackend?: boolean;
16
+ _chainedBackendConfig?: {
17
+ backendOptions: Array<Record<string, unknown>>;
18
+ };
19
+ backends?: unknown[];
20
+ backendOptions?: unknown;
21
+ };
22
+
23
+ export function assertI18nInstance(obj: unknown): asserts obj is I18nInstance {
15
24
  if (!isI18nInstance(obj)) {
16
25
  throw new Error('Object does not implement I18nInstance interface');
17
26
  }
@@ -24,8 +33,8 @@ export const buildInitOptions = async (
24
33
  finalLanguage: string,
25
34
  fallbackLanguage: string,
26
35
  languages: string[],
27
- mergedDetection: any,
28
- mergedBackend: any,
36
+ mergedDetection: I18nInitOptions['detection'],
37
+ mergedBackend: MergedBackendOptions | undefined,
29
38
  userInitOptions?: I18nInitOptions,
30
39
  useSuspense?: boolean,
31
40
  i18nInstance?: I18nInstance,
@@ -84,8 +93,8 @@ export const buildInitOptions = async (
84
93
  if (isChainedBackend && mergedBackend._chainedBackendConfig) {
85
94
  // Try to get backend classes from i18nInstance.options.backend.backends first
86
95
  // This avoids importing fs-backend in browser environment
87
- let HttpBackend: any;
88
- let SdkBackendClass: any;
96
+ let HttpBackend: unknown;
97
+ let SdkBackendClass: unknown;
89
98
 
90
99
  if (
91
100
  i18nInstance?.options?.backend?.backends &&
@@ -148,7 +157,7 @@ export const changeI18nLanguage = async (
148
157
  i18nInstance: I18nInstance,
149
158
  newLang: string,
150
159
  options?: {
151
- detectionOptions?: any;
160
+ detectionOptions?: I18nInitOptions['detection'];
152
161
  },
153
162
  ): Promise<void> => {
154
163
  if (!newLang || typeof newLang !== 'string') {
@@ -179,8 +188,8 @@ export const initializeI18nInstance = async (
179
188
  finalLanguage: string,
180
189
  fallbackLanguage: string,
181
190
  languages: string[],
182
- mergedDetection: any,
183
- mergedBackend: any,
191
+ mergedDetection: I18nInitOptions['detection'],
192
+ mergedBackend: MergedBackendOptions | undefined,
184
193
  userInitOptions?: I18nInitOptions,
185
194
  useSuspense?: boolean,
186
195
  ): Promise<void> => {
@@ -265,7 +274,7 @@ export const setupClonedInstance = async (
265
274
  backendEnabled: boolean,
266
275
  backend: BaseBackendOptions | undefined,
267
276
  i18nextDetector: boolean,
268
- detection: any,
277
+ detection: I18nInitOptions['detection'],
269
278
  localePathRedirect: boolean,
270
279
  userInitOptions: I18nInitOptions | undefined,
271
280
  ): Promise<void> => {
@@ -57,7 +57,7 @@ export interface RuntimeContextWithI18n extends TInternalRuntimeContext {
57
57
  changeLanguage?: (lang: string) => Promise<void>;
58
58
  }
59
59
 
60
- export interface SetupI18nBeforeRenderOptions {
60
+ interface SetupI18nBeforeRenderOptions {
61
61
  api: I18nRuntimeApi;
62
62
  userI18nInstance?: I18nInstance;
63
63
  initOptions?: I18nInitOptions;
@@ -14,7 +14,7 @@ import type { I18nInstance } from './i18n';
14
14
  import { getI18nextInstanceForProvider } from './i18n/instance';
15
15
  import type { RuntimeContextWithI18n } from './pluginSetup';
16
16
 
17
- export interface I18nRootWrapperOptions {
17
+ interface I18nRootWrapperOptions {
18
18
  entryName?: string;
19
19
  htmlLangAttr: boolean;
20
20
  localePathRedirect: boolean;
@@ -15,31 +15,31 @@ import {
15
15
  import type React from 'react';
16
16
  import { useCallback, useContext, useEffect, useState } from 'react';
17
17
 
18
- export type I18nRouterFramework = 'react-router' | 'tanstack' | string;
18
+ type I18nRouterFramework = 'react-router' | 'tanstack' | string;
19
19
 
20
- export interface I18nRouterLocation {
20
+ interface I18nRouterLocation {
21
21
  pathname: string;
22
22
  search: string;
23
23
  hash: string;
24
24
  }
25
25
 
26
- export interface I18nRouterNavigateOptions {
26
+ interface I18nRouterNavigateOptions {
27
27
  replace?: boolean;
28
28
  state?: unknown;
29
29
  }
30
30
 
31
- export type I18nRouterNavigate = (
31
+ type I18nRouterNavigate = (
32
32
  href: string,
33
33
  options?: I18nRouterNavigateOptions,
34
34
  ) => void | Promise<void>;
35
35
 
36
- export type I18nRouterLink = React.ComponentType<{
36
+ type I18nRouterLink = React.ComponentType<{
37
37
  to: string;
38
38
  children?: React.ReactNode;
39
39
  [key: string]: unknown;
40
40
  }>;
41
41
 
42
- export interface I18nRouterAdapter {
42
+ interface I18nRouterAdapter {
43
43
  framework?: I18nRouterFramework;
44
44
  hasRouter: boolean;
45
45
  location: I18nRouterLocation | null;
@@ -1,7 +1,10 @@
1
1
  import { isBrowser } from '@modern-js/runtime';
2
2
  import { getGlobalBasename } from '@modern-js/runtime/context';
3
3
  import type { LocalisedUrlsOption } from '../shared/localisedUrls';
4
- import { localiseTargetPathname } from '../shared/localisedUrls';
4
+ import {
5
+ localiseTargetPathname,
6
+ shouldSkipLocaleRedirect,
7
+ } from '../shared/localisedUrls';
5
8
 
6
9
  // Structural parameter: hooks.ts passes a public-TRuntimeContext-based
7
10
  // context while core.tsx passes the internal one; both carry the request
@@ -129,32 +132,5 @@ export const shouldIgnoreRedirect = (
129
132
  languages: string[],
130
133
  ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean),
131
134
  ): boolean => {
132
- if (!ignoreRedirectRoutes) {
133
- return false;
134
- }
135
-
136
- // Remove language prefix if present (e.g., /en/api -> /api)
137
- const segments = pathname.split('/').filter(Boolean);
138
- let pathWithoutLang = pathname;
139
- if (segments.length > 0 && languages.includes(segments[0])) {
140
- // Remove language prefix
141
- pathWithoutLang = `/${segments.slice(1).join('/')}`;
142
- }
143
-
144
- // Normalize path (ensure it starts with /)
145
- const normalizedPath = pathWithoutLang.startsWith('/')
146
- ? pathWithoutLang
147
- : `/${pathWithoutLang}`;
148
-
149
- if (typeof ignoreRedirectRoutes === 'function') {
150
- return ignoreRedirectRoutes(normalizedPath);
151
- }
152
-
153
- // Check if pathname matches any of the ignore patterns
154
- return ignoreRedirectRoutes.some(pattern => {
155
- // Support both exact match and prefix match
156
- return (
157
- normalizedPath === pattern || normalizedPath.startsWith(`${pattern}/`)
158
- );
159
- });
135
+ return shouldSkipLocaleRedirect(pathname, languages, ignoreRedirectRoutes);
160
136
  };