@bleedingdev/modern-js-plugin-i18n 3.8.2-ultramodern.9 → 3.8.3-ultramodern.2

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 (97) hide show
  1. package/dist/cjs/cli/index.js +3 -3
  2. package/dist/cjs/runtime/context.js +19 -6
  3. package/dist/cjs/runtime/hooks.js +8 -11
  4. package/dist/cjs/runtime/localizedPaths.js +2 -2
  5. package/dist/cjs/runtime/providerComposition.js +3 -1
  6. package/dist/cjs/runtime/utils.js +3 -3
  7. package/dist/cjs/server/index.js +2 -2
  8. package/dist/cjs/server/redirectPolicy.js +6 -6
  9. package/dist/esm/cli/index.mjs +1 -1
  10. package/dist/esm/runtime/context.mjs +19 -6
  11. package/dist/esm/runtime/hooks.mjs +8 -11
  12. package/dist/esm/runtime/localizedPaths.mjs +1 -1
  13. package/dist/esm/runtime/providerComposition.mjs +3 -1
  14. package/dist/esm/runtime/utils.mjs +1 -1
  15. package/dist/esm/server/index.mjs +1 -1
  16. package/dist/esm/server/redirectPolicy.mjs +1 -1
  17. package/dist/esm-node/cli/index.mjs +1 -1
  18. package/dist/esm-node/runtime/context.mjs +19 -6
  19. package/dist/esm-node/runtime/hooks.mjs +8 -11
  20. package/dist/esm-node/runtime/localizedPaths.mjs +1 -1
  21. package/dist/esm-node/runtime/providerComposition.mjs +3 -1
  22. package/dist/esm-node/runtime/utils.mjs +1 -1
  23. package/dist/esm-node/server/index.mjs +1 -1
  24. package/dist/esm-node/server/redirectPolicy.mjs +1 -1
  25. package/dist/types/runtime/context.d.ts +3 -2
  26. package/dist/types/runtime/contextHelpers.d.ts +1 -1
  27. package/dist/types/runtime/hooks.d.ts +1 -1
  28. package/dist/types/runtime/localizedPaths.d.ts +1 -1
  29. package/dist/types/runtime/utils.d.ts +1 -1
  30. package/dist/types/shared/type.d.ts +1 -1
  31. package/package.json +32 -11
  32. package/src/cli/index.ts +4 -4
  33. package/src/runtime/context.tsx +38 -14
  34. package/src/runtime/contextHelpers.ts +1 -1
  35. package/src/runtime/hooks.ts +9 -10
  36. package/src/runtime/localizedPaths.ts +4 -2
  37. package/src/runtime/providerComposition.tsx +5 -2
  38. package/src/runtime/utils.ts +4 -4
  39. package/src/server/index.ts +1 -1
  40. package/src/server/redirectPolicy.ts +1 -1
  41. package/src/shared/type.ts +1 -1
  42. package/tests/federatedI18nBoundary.client.test.tsx +21 -4
  43. package/tests/federatedI18nBoundary.test.tsx +15 -3
  44. package/tests/linkTypes.test.ts +20 -0
  45. package/tests/localisedUrlRewriteMatrix.fork.test.ts +1 -1
  46. package/tests/localisedUrls.test.ts +0 -351
  47. package/tests/routerAdapter.test.tsx +5 -12
  48. package/dist/cjs/shared/localisedUrls/config.js +0 -49
  49. package/dist/cjs/shared/localisedUrls/index.js +0 -91
  50. package/dist/cjs/shared/localisedUrls/normalise.js +0 -87
  51. package/dist/cjs/shared/localisedUrls/pathname.js +0 -68
  52. package/dist/cjs/shared/localisedUrls/patterns.js +0 -130
  53. package/dist/cjs/shared/localisedUrls/redirect.js +0 -103
  54. package/dist/cjs/shared/localisedUrls/resolve.js +0 -88
  55. package/dist/cjs/shared/localisedUrls/routes.js +0 -130
  56. package/dist/cjs/shared/localisedUrls/types.js +0 -18
  57. package/dist/cjs/shared/localisedUrls.js +0 -85
  58. package/dist/esm/shared/localisedUrls/config.mjs +0 -11
  59. package/dist/esm/shared/localisedUrls/index.mjs +0 -7
  60. package/dist/esm/shared/localisedUrls/normalise.mjs +0 -37
  61. package/dist/esm/shared/localisedUrls/pathname.mjs +0 -24
  62. package/dist/esm/shared/localisedUrls/patterns.mjs +0 -86
  63. package/dist/esm/shared/localisedUrls/redirect.mjs +0 -56
  64. package/dist/esm/shared/localisedUrls/resolve.mjs +0 -47
  65. package/dist/esm/shared/localisedUrls/routes.mjs +0 -89
  66. package/dist/esm/shared/localisedUrls/types.mjs +0 -0
  67. package/dist/esm/shared/localisedUrls.mjs +0 -1
  68. package/dist/esm-node/shared/localisedUrls/config.mjs +0 -12
  69. package/dist/esm-node/shared/localisedUrls/index.mjs +0 -8
  70. package/dist/esm-node/shared/localisedUrls/normalise.mjs +0 -38
  71. package/dist/esm-node/shared/localisedUrls/pathname.mjs +0 -25
  72. package/dist/esm-node/shared/localisedUrls/patterns.mjs +0 -87
  73. package/dist/esm-node/shared/localisedUrls/redirect.mjs +0 -57
  74. package/dist/esm-node/shared/localisedUrls/resolve.mjs +0 -48
  75. package/dist/esm-node/shared/localisedUrls/routes.mjs +0 -90
  76. package/dist/esm-node/shared/localisedUrls/types.mjs +0 -1
  77. package/dist/esm-node/shared/localisedUrls.mjs +0 -2
  78. package/dist/types/shared/localisedUrls/config.d.ts +0 -9
  79. package/dist/types/shared/localisedUrls/index.d.ts +0 -9
  80. package/dist/types/shared/localisedUrls/normalise.d.ts +0 -10
  81. package/dist/types/shared/localisedUrls/pathname.d.ts +0 -4
  82. package/dist/types/shared/localisedUrls/patterns.d.ts +0 -5
  83. package/dist/types/shared/localisedUrls/redirect.d.ts +0 -11
  84. package/dist/types/shared/localisedUrls/resolve.d.ts +0 -7
  85. package/dist/types/shared/localisedUrls/routes.d.ts +0 -3
  86. package/dist/types/shared/localisedUrls/types.d.ts +0 -14
  87. package/dist/types/shared/localisedUrls.d.ts +0 -2
  88. package/src/shared/localisedUrls/config.ts +0 -18
  89. package/src/shared/localisedUrls/index.ts +0 -24
  90. package/src/shared/localisedUrls/normalise.ts +0 -67
  91. package/src/shared/localisedUrls/pathname.ts +0 -60
  92. package/src/shared/localisedUrls/patterns.ts +0 -160
  93. package/src/shared/localisedUrls/redirect.ts +0 -93
  94. package/src/shared/localisedUrls/resolve.ts +0 -135
  95. package/src/shared/localisedUrls/routes.ts +0 -234
  96. package/src/shared/localisedUrls/types.ts +0 -18
  97. package/src/shared/localisedUrls.ts +0 -26
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.8.2-ultramodern.9",
20
+ "version": "3.8.3-ultramodern.2",
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
@@ -48,6 +48,13 @@
48
48
  },
49
49
  "default": "./dist/esm/runtime/index.mjs"
50
50
  },
51
+ "./runtime/consumer": {
52
+ "types": "./dist/types/runtime/context.d.ts",
53
+ "node": {
54
+ "module": "./dist/esm/runtime/context.mjs"
55
+ },
56
+ "default": "./dist/esm/runtime/context.mjs"
57
+ },
51
58
  "./runtime/no-react-i18next": {
52
59
  "types": "./dist/types/runtime/no-react-i18next.d.ts",
53
60
  "node": {
@@ -55,6 +62,13 @@
55
62
  },
56
63
  "default": "./dist/esm/runtime/no-react-i18next.mjs"
57
64
  },
65
+ "./runtime/no-react-i18next/consumer": {
66
+ "types": "./dist/types/runtime/context.d.ts",
67
+ "node": {
68
+ "module": "./dist/esm/runtime/context.mjs"
69
+ },
70
+ "default": "./dist/esm/runtime/context.mjs"
71
+ },
58
72
  "./server": {
59
73
  "types": "./dist/types/server/index.d.ts",
60
74
  "node": {
@@ -79,9 +93,15 @@
79
93
  "runtime": [
80
94
  "./dist/types/runtime/index.d.ts"
81
95
  ],
96
+ "runtime/consumer": [
97
+ "./dist/types/runtime/context.d.ts"
98
+ ],
82
99
  "runtime/no-react-i18next": [
83
100
  "./dist/types/runtime/no-react-i18next.d.ts"
84
101
  ],
102
+ "runtime/no-react-i18next/consumer": [
103
+ "./dist/types/runtime/context.d.ts"
104
+ ],
85
105
  "server": [
86
106
  "./dist/types/server/index.d.ts"
87
107
  ],
@@ -91,12 +111,13 @@
91
111
  }
92
112
  },
93
113
  "dependencies": {
94
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.2-ultramodern.9",
95
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.8.2-ultramodern.9",
96
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.2-ultramodern.9",
97
- "@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.8.2-ultramodern.9",
98
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.2-ultramodern.9",
99
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.2-ultramodern.9",
114
+ "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.8.3-ultramodern.2",
115
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.3-ultramodern.2",
116
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.8.3-ultramodern.2",
117
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.3-ultramodern.2",
118
+ "@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.8.3-ultramodern.2",
119
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.3-ultramodern.2",
120
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.3-ultramodern.2",
100
121
  "@swc/helpers": "^0.5.23",
101
122
  "i18next-browser-languagedetector": "^8.2.1",
102
123
  "i18next-chained-backend": "^5.0.5",
@@ -105,7 +126,7 @@
105
126
  "i18next-http-middleware": "^3.9.8"
106
127
  },
107
128
  "peerDependencies": {
108
- "@modern-js/runtime": "3.8.2-ultramodern.9",
129
+ "@modern-js/runtime": "3.8.3-ultramodern.2",
109
130
  "i18next": ">=25.7.4",
110
131
  "react": "^19.2.8",
111
132
  "react-dom": "^19.2.8",
@@ -120,8 +141,8 @@
120
141
  }
121
142
  },
122
143
  "devDependencies": {
123
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.8.2-ultramodern.9",
124
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.8.2-ultramodern.9",
144
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.8.3-ultramodern.2",
145
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.8.3-ultramodern.2",
125
146
  "@rslib/core": "0.23.2",
126
147
  "@types/node": "^26.2.0",
127
148
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
@@ -139,7 +160,7 @@
139
160
  },
140
161
  "scripts": {
141
162
  "dev": "rslib build --watch",
142
- "build": "rslib build && pnpm -w tsgo:dts \"$PWD\"",
163
+ "build": "rslib build",
143
164
  "test": "rstest --passWithNoTests"
144
165
  }
145
166
  }
package/src/cli/index.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import type { AppTools, CliPlugin } from '@modern-js/app-tools';
4
+ import {
5
+ applyLocalisedUrlsToRoutes,
6
+ resolveLocalisedUrlsConfig,
7
+ } from '@modern-js/i18n-runtime-extensions';
4
8
  import { getPublicDirRoutePrefixes } from '@modern-js/server-core';
5
9
  import type {
6
10
  Entrypoint,
7
11
  NestedRouteForCli,
8
12
  PageRoute,
9
13
  } from '@modern-js/types';
10
- import {
11
- applyLocalisedUrlsToRoutes,
12
- resolveLocalisedUrlsConfig,
13
- } from '../shared/localisedUrls';
14
14
  import type { BackendOptions, LocaleDetectionOptions } from '../shared/type';
15
15
  import { getBackendOptions, getLocaleDetectionOptions } from '../shared/utils';
16
16
  import { applyDetectedBackendPaths, detectLocalesDirectory } from './locales';
@@ -1,4 +1,5 @@
1
- import type { FC, ReactNode } from 'react';
1
+ import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
2
+ import type { ComponentType, FC, ReactNode } from 'react';
2
3
  import {
3
4
  createContext,
4
5
  useCallback,
@@ -6,7 +7,6 @@ import {
6
7
  useEffect,
7
8
  useMemo,
8
9
  } from 'react';
9
- import type { LocalisedUrlsOption } from '../shared/localisedUrls';
10
10
  import {
11
11
  changeModernI18nLanguage,
12
12
  getPathLanguage,
@@ -36,36 +36,52 @@ interface ModernI18nContextValue {
36
36
  const modernI18nContextKey = Symbol.for(
37
37
  '@modern-js/plugin-i18n/runtime/ModernI18nContext',
38
38
  );
39
+ const reactI18nextProviderContextKey = Symbol.for(
40
+ '@modern-js/plugin-i18n/runtime/ReactI18nextProviderContext',
41
+ );
39
42
 
40
- type ModernI18nGlobal = typeof globalThis & {
41
- [key: symbol]:
42
- | ReturnType<typeof createContext<ModernI18nContextValue | null>>
43
- | undefined;
43
+ type GlobalContextStore<T> = typeof globalThis & {
44
+ [key: symbol]: ReturnType<typeof createContext<T>> | undefined;
44
45
  };
45
46
 
46
- const getModernI18nContext = () => {
47
- const globalStore = globalThis as ModernI18nGlobal;
48
- globalStore[modernI18nContextKey] ??=
49
- createContext<ModernI18nContextValue | null>(null);
50
- return globalStore[modernI18nContextKey];
47
+ const getGlobalContext = <T,>(key: symbol, defaultValue: T) => {
48
+ const globalStore = globalThis as GlobalContextStore<T>;
49
+ globalStore[key] ??= createContext<T>(defaultValue);
50
+ return globalStore[key];
51
51
  };
52
52
 
53
- const ModernI18nContext = getModernI18nContext();
53
+ const ModernI18nContext = getGlobalContext<ModernI18nContextValue | null>(
54
+ modernI18nContextKey,
55
+ null,
56
+ );
57
+ const ReactI18nextProviderContext = getGlobalContext<ComponentType<any> | null>(
58
+ reactI18nextProviderContextKey,
59
+ null,
60
+ );
54
61
 
55
62
  interface ModernI18nProviderProps {
56
63
  children: ReactNode;
64
+ i18nextProvider?: ComponentType<any> | null;
57
65
  value: ModernI18nContextValue;
58
66
  }
59
67
 
60
68
  export const ModernI18nProvider: FC<ModernI18nProviderProps> = ({
61
69
  children,
70
+ i18nextProvider,
62
71
  value,
63
72
  }) => {
64
- return (
73
+ const content = (
65
74
  <ModernI18nContext.Provider value={value}>
66
75
  {children}
67
76
  </ModernI18nContext.Provider>
68
77
  );
78
+ return i18nextProvider === undefined ? (
79
+ content
80
+ ) : (
81
+ <ReactI18nextProviderContext.Provider value={i18nextProvider}>
82
+ {content}
83
+ </ReactI18nextProviderContext.Provider>
84
+ );
69
85
  };
70
86
 
71
87
  export interface FederatedI18nBoundaryProps {
@@ -89,6 +105,7 @@ export const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps> = ({
89
105
  supportedLanguages,
90
106
  }) => {
91
107
  const parent = useContext(ModernI18nContext);
108
+ const I18nextProvider = useContext(ReactI18nextProviderContext);
92
109
  if (!parent) {
93
110
  throw new Error(
94
111
  'FederatedI18nBoundary must be used within ModernI18nProvider',
@@ -184,7 +201,14 @@ export const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps> = ({
184
201
  [languages, parent, scopedInstance],
185
202
  );
186
203
 
187
- return <ModernI18nProvider value={value}>{children}</ModernI18nProvider>;
204
+ const scopedContent = (
205
+ <ModernI18nProvider value={value}>{children}</ModernI18nProvider>
206
+ );
207
+ return I18nextProvider ? (
208
+ <I18nextProvider i18n={scopedInstance}>{scopedContent}</I18nextProvider>
209
+ ) : (
210
+ scopedContent
211
+ );
188
212
  };
189
213
 
190
214
  export interface UseModernI18nReturn<
@@ -1,5 +1,5 @@
1
+ import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
1
2
  import { isBrowser } from '@modern-js/runtime';
2
- import type { LocalisedUrlsOption } from '../shared/localisedUrls';
3
3
  import type { I18nInstance } from './i18n';
4
4
  import type { SdkBackend } from './i18n/backend/sdk-backend';
5
5
  import { cacheUserLanguage } from './i18n/detection';
@@ -1,7 +1,7 @@
1
+ import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
1
2
  import { isBrowser } from '@modern-js/runtime';
2
3
  import type React from 'react';
3
4
  import { useCallback, useEffect, useRef } from 'react';
4
- import type { LocalisedUrlsOption } from '../shared/localisedUrls';
5
5
  import {
6
6
  cacheI18nLanguage,
7
7
  changeI18nInstanceLanguage,
@@ -181,7 +181,7 @@ export function useClientSideRedirect(
181
181
  ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean),
182
182
  localisedUrls?: LocalisedUrlsOption,
183
183
  ) {
184
- const hasRedirectedRef = useRef(false);
184
+ const urlRef = useRef('');
185
185
  const { navigate, location, hasRouter } = useI18nRouterAdapter();
186
186
 
187
187
  useEffect(() => {
@@ -201,7 +201,7 @@ export function useClientSideRedirect(
201
201
  // Ignore errors when checking SSR data
202
202
  }
203
203
 
204
- if (hasRedirectedRef.current) {
204
+ if (urlRef.current === (location?.pathname || window.location.pathname)) {
205
205
  return;
206
206
  }
207
207
 
@@ -230,12 +230,12 @@ export function useClientSideRedirect(
230
230
  localePathRedirect,
231
231
  );
232
232
 
233
- if (pathDetection.detected) {
234
- return;
235
- }
236
-
237
233
  const targetLanguage =
238
- i18nInstance.language || fallbackLanguage || languages[0] || 'en';
234
+ pathDetection.language ||
235
+ i18nInstance.language ||
236
+ fallbackLanguage ||
237
+ languages[0] ||
238
+ 'en';
239
239
 
240
240
  const newPath = buildLocalizedUrl(
241
241
  relativePath,
@@ -245,9 +245,8 @@ export function useClientSideRedirect(
245
245
  );
246
246
  const newUrl = entryPath + newPath + currentSearch + currentHash;
247
247
 
248
+ urlRef.current = currentPathname;
248
249
  if (newUrl !== currentPathname + currentSearch + currentHash) {
249
- hasRedirectedRef.current = true;
250
-
251
250
  // Use navigate if router is available (similar to changeLanguage implementation)
252
251
  if (hasRouter && navigate && location) {
253
252
  navigate(newUrl, { replace: true });
@@ -1,6 +1,8 @@
1
+ import {
2
+ canonicalTargetPathname,
3
+ type LocalisedUrlsOption,
4
+ } from '@modern-js/i18n-runtime-extensions';
1
5
  import { useMemo } from 'react';
2
- import type { LocalisedUrlsOption } from '../shared/localisedUrls';
3
- import { canonicalTargetPathname } from '../shared/localisedUrls';
4
6
  import { useModernI18n } from './context';
5
7
  import { useI18nRouterAdapter } from './routerAdapter';
6
8
  import { buildLocalizedUrl, splitUrlTarget } from './utils';
@@ -1,3 +1,4 @@
1
+ import { useLanguageSync } from '@modern-js/i18n-runtime-extensions';
1
2
  import { RuntimeContext } from '@modern-js/runtime';
2
3
  import { Helmet } from '@modern-js/runtime/head';
3
4
  import type React from 'react';
@@ -7,7 +8,6 @@ import { ModernI18nProvider } from './context';
7
8
  import {
8
9
  createContextValue,
9
10
  useClientSideRedirect,
10
- useLanguageSync,
11
11
  useSdkResourcesLoader,
12
12
  } from './hooks';
13
13
  import type { I18nInstance } from './i18n';
@@ -140,7 +140,10 @@ export const createI18nRootWrapper =
140
140
  return (
141
141
  <>
142
142
  {Boolean(htmlLangAttr) && <Helmet htmlAttributes={{ lang }} />}
143
- <ModernI18nProvider value={contextValue}>
143
+ <ModernI18nProvider
144
+ i18nextProvider={I18nextProvider}
145
+ value={contextValue}
146
+ >
144
147
  {appContent}
145
148
  </ModernI18nProvider>
146
149
  </>
@@ -1,10 +1,10 @@
1
- import { isBrowser } from '@modern-js/runtime';
2
- import { getGlobalBasename } from '@modern-js/runtime/context';
3
- import type { LocalisedUrlsOption } from '../shared/localisedUrls';
4
1
  import {
2
+ type LocalisedUrlsOption,
5
3
  localiseTargetPathname,
6
4
  shouldSkipLocaleRedirect,
7
- } from '../shared/localisedUrls';
5
+ } from '@modern-js/i18n-runtime-extensions';
6
+ import { isBrowser } from '@modern-js/runtime';
7
+ import { getGlobalBasename } from '@modern-js/runtime/context';
8
8
 
9
9
  // Structural parameter: hooks.ts passes a public-TRuntimeContext-based
10
10
  // context while core.tsx passes the internal one; both carry the request
@@ -1,9 +1,9 @@
1
+ import { resolveLocalisedUrlsConfig } from '@modern-js/i18n-runtime-extensions';
1
2
  import * as honoPkg from '@modern-js/server-core/hono';
2
3
 
3
4
  const { languageDetector } = honoPkg;
4
5
 
5
6
  import type { Context, Next, ServerPlugin } from '@modern-js/server-runtime';
6
- import { resolveLocalisedUrlsConfig } from '../shared/localisedUrls.js';
7
7
  import type { LocaleDetectionOptions } from '../shared/type';
8
8
  import { getLocaleDetectionOptions } from '../shared/utils.js';
9
9
  import { collectApiPrefixes, matchesApiPrefix } from './apiPrefix.js';
@@ -4,7 +4,7 @@ import {
4
4
  matchesPathPrefix,
5
5
  shouldSkipLocaleRedirect,
6
6
  stripLanguagePrefix,
7
- } from '../shared/localisedUrls.js';
7
+ } from '@modern-js/i18n-runtime-extensions';
8
8
  import type { LocaleDetectionOptions } from '../shared/type';
9
9
 
10
10
  interface LocaleRedirectRequest {
@@ -1,8 +1,8 @@
1
+ import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
1
2
  import type {
2
3
  LanguageDetectorOptions,
3
4
  Resources,
4
5
  } from '../runtime/i18n/instance';
5
- import type { LocalisedUrlsOption } from './localisedUrls';
6
6
 
7
7
  export interface BaseLocaleDetectionOptions {
8
8
  localePathRedirect?: boolean;
@@ -3,6 +3,7 @@ import i18next, { type i18n } from 'i18next';
3
3
  import { act, useState } from 'react';
4
4
  import { hydrateRoot } from 'react-dom/client';
5
5
  import { renderToString } from 'react-dom/server';
6
+ import { I18nextProvider, useTranslation } from 'react-i18next';
6
7
  import { ModernI18nProvider } from '../src/runtime/context';
7
8
  import { FederatedI18nBoundary, useModernI18n } from '../src/runtime/core';
8
9
 
@@ -21,9 +22,19 @@ const remoteResources = {
21
22
  };
22
23
 
23
24
  function InventoryCopy() {
24
- const { t } = useModernI18n();
25
+ const modernI18n = useModernI18n<i18n>();
26
+ const reactI18n = useTranslation('inventory');
27
+ const reactI18nInstance = reactI18n.i18n as i18n & { __original?: i18n };
28
+ const sharesScopedInstance =
29
+ reactI18nInstance === modernI18n.i18nInstance ||
30
+ reactI18nInstance.__original === modernI18n.i18nInstance;
25
31
 
26
- return <p data-testid="inventory-copy">{t('inventory.widgetBody')}</p>;
32
+ return (
33
+ <p data-testid="inventory-copy">
34
+ {modernI18n.t('inventory.widgetBody')}|
35
+ {reactI18n.t('inventory.widgetBody')}|{String(sharesScopedInstance)}
36
+ </p>
37
+ );
27
38
  }
28
39
 
29
40
  function Host({ hostI18n }: { hostI18n: i18n }) {
@@ -31,6 +42,7 @@ function Host({ hostI18n }: { hostI18n: i18n }) {
31
42
 
32
43
  return (
33
44
  <ModernI18nProvider
45
+ i18nextProvider={I18nextProvider}
34
46
  value={{
35
47
  i18nInstance: hostI18n,
36
48
  language,
@@ -97,14 +109,19 @@ test('hydrates remote-owned copy and follows the host language', async () => {
97
109
  await act(async () => undefined);
98
110
  expect(
99
111
  container.querySelector('[data-testid="inventory-copy"]')?.textContent,
100
- ).toBe('C1 inventory surface owned by the MicroVertical.');
112
+ ).toBe(
113
+ 'C1 inventory surface owned by the MicroVertical.|C1 inventory surface owned by the MicroVertical.|true',
114
+ );
101
115
 
102
116
  await act(async () => {
103
117
  (container.querySelector('button') as HTMLButtonElement).click();
104
118
  });
105
119
  expect(
106
120
  container.querySelector('[data-testid="inventory-copy"]')?.textContent,
107
- ).toBe('C1 skladová plocha vlastněná MicroVerticalem.');
121
+ ).toBe(
122
+ 'C1 skladová plocha vlastněná MicroVerticalem.|C1 skladová plocha vlastněná MicroVerticalem.|true',
123
+ );
124
+ expect(clientI18n.t('inventory.widgetBody')).toBe('stale host Czech copy');
108
125
  expect(hydrationErrors).toEqual([]);
109
126
 
110
127
  await act(async () => root.unmount());
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from '@rstest/core';
2
- import i18next from 'i18next';
2
+ import i18next, { type i18n } from 'i18next';
3
3
  import { renderToStaticMarkup } from 'react-dom/server';
4
+ import { I18nextProvider, useTranslation } from 'react-i18next';
4
5
  import { ModernI18nProvider } from '../src/runtime/context';
5
6
  import { FederatedI18nBoundary, useModernI18n } from '../src/runtime/core';
6
7
 
@@ -11,11 +12,19 @@ const HOST_ONLY_INVENTORY_COPY = 'inventory copy owned only by the shell';
11
12
  const HOST_ONLY_SHELL_COPY = 'private shell copy';
12
13
 
13
14
  function InventoryCopy() {
14
- const { t } = useModernI18n();
15
+ const modernI18n = useModernI18n<i18n>();
16
+ const reactI18n = useTranslation('inventory');
17
+ const reactI18nInstance = reactI18n.i18n as i18n & { __original?: i18n };
18
+ const sharesScopedInstance =
19
+ reactI18nInstance === modernI18n.i18nInstance ||
20
+ reactI18nInstance.__original === modernI18n.i18nInstance;
15
21
 
16
22
  return (
17
23
  <p>
18
- {t('inventory.widgetBody')}|{t('inventory.hostOnly')}|{t('shell:private')}
24
+ {modernI18n.t('inventory.widgetBody')}|
25
+ {reactI18n.t('inventory.widgetBody')}|{String(sharesScopedInstance)}|
26
+ {modernI18n.t('inventory.hostOnly')}|{modernI18n.t('shell:private')}|
27
+ {reactI18n.t('inventory.hostOnly')}|{reactI18n.t('shell:private')}
19
28
  </p>
20
29
  );
21
30
  }
@@ -45,6 +54,7 @@ describe('FederatedI18nBoundary', () => {
45
54
 
46
55
  const html = renderToStaticMarkup(
47
56
  <ModernI18nProvider
57
+ i18nextProvider={I18nextProvider}
48
58
  value={{
49
59
  i18nInstance: hostI18n,
50
60
  language: 'en',
@@ -68,6 +78,7 @@ describe('FederatedI18nBoundary', () => {
68
78
 
69
79
  expect(html).toContain(C1_COPY);
70
80
  expect(html).not.toContain(C0_COPY);
81
+ expect(html).toContain(`${C1_COPY}|${C1_COPY}|true`);
71
82
  expect(html).not.toContain(HOST_ONLY_INVENTORY_COPY);
72
83
  expect(html).not.toContain(HOST_ONLY_SHELL_COPY);
73
84
  expect(hostI18n.t('inventory.widgetBody')).toBe(C0_COPY);
@@ -94,6 +105,7 @@ describe('FederatedI18nBoundary', () => {
94
105
  expect(() =>
95
106
  renderToStaticMarkup(
96
107
  <ModernI18nProvider
108
+ i18nextProvider={I18nextProvider}
97
109
  value={{
98
110
  i18nInstance: hostI18n,
99
111
  language: 'en',
@@ -18,6 +18,9 @@ function resolveTsgoBin() {
18
18
  }
19
19
 
20
20
  const tsgoBin = resolveTsgoBin();
21
+ const useShell = process.platform === 'win32';
22
+ const execOptions = { cwd: path.dirname(__dirname), shell: useShell };
23
+ execFileSync('pnpm', ['build'], execOptions);
21
24
 
22
25
  describe('Link type-level tests', () => {
23
26
  test('fixture type-checks correctly: valid uses compile, invalid uses are rejected', () => {
@@ -36,4 +39,21 @@ describe('Link type-level tests', () => {
36
39
  throw new Error(`TypeScript type-check failed:\n${stdout}\n${stderr}`);
37
40
  }
38
41
  }, 60_000);
42
+
43
+ test('consumer declarations avoid toolchain internals', () => {
44
+ const output = execFileSync(process.execPath, [
45
+ tsgoBin,
46
+ '--ignoreConfig',
47
+ '--listFilesOnly',
48
+ '--module',
49
+ 'preserve',
50
+ '--moduleResolution',
51
+ 'bundler',
52
+ path.resolve(__dirname, '../dist/types/runtime/context.d.ts'),
53
+ ]);
54
+ const files = output.toString().replaceAll('\\', '/');
55
+ expect(files).not.toMatch(
56
+ /\/packages\/(cli|runtime\/plugin-runtime\/dist\/types|solutions\/app-tools|toolkit\/(types|utils))\//u,
57
+ );
58
+ }, 60_000);
39
59
  });
@@ -1,3 +1,4 @@
1
+ import type { LocalisedUrlsMap } from '@modern-js/i18n-runtime-extensions';
1
2
  import { describe, expect, test } from '@rstest/core';
2
3
  import {
3
4
  interpolateRouteParams,
@@ -5,7 +6,6 @@ import {
5
6
  } from '../src/runtime/linkHelpers';
6
7
  import { canonicalPath, localizePath } from '../src/runtime/localizedPaths';
7
8
  import { buildLocalizedUrl } from '../src/runtime/utils';
8
- import type { LocalisedUrlsMap } from '../src/shared/localisedUrls';
9
9
 
10
10
  const languages = ['en', 'cs'];
11
11