@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.2 → 3.2.0-ultramodern.22

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 (83) hide show
  1. package/dist/cjs/cli/index.js +22 -0
  2. package/dist/cjs/runtime/I18nLink.js +4 -12
  3. package/dist/cjs/runtime/context.js +32 -5
  4. package/dist/cjs/runtime/hooks.js +8 -5
  5. package/dist/cjs/runtime/i18n/backend/defaults.js +1 -1
  6. package/dist/cjs/runtime/i18n/backend/middleware.node.js +4 -4
  7. package/dist/cjs/runtime/i18n/instance.js +95 -80
  8. package/dist/cjs/runtime/index.js +7 -6
  9. package/dist/cjs/runtime/routerAdapter.js +164 -0
  10. package/dist/cjs/runtime/utils.js +63 -94
  11. package/dist/cjs/server/index.js +64 -8
  12. package/dist/cjs/shared/localisedUrls.js +237 -0
  13. package/dist/esm/cli/index.mjs +22 -0
  14. package/dist/esm/runtime/I18nLink.mjs +4 -12
  15. package/dist/esm/runtime/context.mjs +34 -7
  16. package/dist/esm/runtime/hooks.mjs +9 -6
  17. package/dist/esm/runtime/i18n/backend/defaults.mjs +1 -1
  18. package/dist/esm/runtime/i18n/backend/middleware.node.mjs +3 -3
  19. package/dist/esm/runtime/i18n/instance.mjs +8 -2
  20. package/dist/esm/runtime/index.mjs +7 -6
  21. package/dist/esm/runtime/routerAdapter.mjs +130 -0
  22. package/dist/esm/runtime/utils.mjs +11 -30
  23. package/dist/esm/server/index.mjs +57 -7
  24. package/dist/esm/shared/localisedUrls.mjs +191 -0
  25. package/dist/esm-node/cli/index.mjs +22 -0
  26. package/dist/esm-node/runtime/I18nLink.mjs +4 -12
  27. package/dist/esm-node/runtime/context.mjs +34 -7
  28. package/dist/esm-node/runtime/hooks.mjs +9 -6
  29. package/dist/esm-node/runtime/i18n/backend/defaults.mjs +1 -1
  30. package/dist/esm-node/runtime/i18n/backend/middleware.node.mjs +3 -3
  31. package/dist/esm-node/runtime/i18n/instance.mjs +8 -2
  32. package/dist/esm-node/runtime/index.mjs +7 -6
  33. package/dist/esm-node/runtime/routerAdapter.mjs +131 -0
  34. package/dist/esm-node/runtime/utils.mjs +11 -30
  35. package/dist/esm-node/server/index.mjs +57 -7
  36. package/dist/esm-node/shared/localisedUrls.mjs +192 -0
  37. package/dist/types/cli/index.d.ts +21 -0
  38. package/dist/types/runtime/I18nLink.d.ts +23 -0
  39. package/dist/types/runtime/context.d.ts +41 -0
  40. package/dist/types/runtime/hooks.d.ts +30 -0
  41. package/dist/types/runtime/i18n/backend/config.d.ts +2 -0
  42. package/dist/types/runtime/i18n/backend/defaults.d.ts +13 -0
  43. package/dist/types/runtime/i18n/backend/defaults.node.d.ts +8 -0
  44. package/dist/types/runtime/i18n/backend/index.d.ts +3 -0
  45. package/dist/types/runtime/i18n/backend/middleware.common.d.ts +14 -0
  46. package/dist/types/runtime/i18n/backend/middleware.d.ts +12 -0
  47. package/dist/types/runtime/i18n/backend/middleware.node.d.ts +13 -0
  48. package/dist/types/runtime/i18n/backend/sdk-backend.d.ts +53 -0
  49. package/dist/types/runtime/i18n/backend/sdk-event.d.ts +9 -0
  50. package/dist/types/runtime/i18n/detection/config.d.ts +11 -0
  51. package/dist/types/runtime/i18n/detection/index.d.ts +50 -0
  52. package/dist/types/runtime/i18n/detection/middleware.d.ts +24 -0
  53. package/dist/types/runtime/i18n/detection/middleware.node.d.ts +17 -0
  54. package/dist/types/runtime/i18n/index.d.ts +3 -0
  55. package/dist/types/runtime/i18n/instance.d.ts +96 -0
  56. package/dist/types/runtime/i18n/utils.d.ts +29 -0
  57. package/dist/types/runtime/index.d.ts +21 -0
  58. package/dist/types/runtime/routerAdapter.d.ts +26 -0
  59. package/dist/types/runtime/types.d.ts +15 -0
  60. package/dist/types/runtime/utils.d.ts +28 -0
  61. package/dist/types/server/index.d.ts +14 -0
  62. package/dist/types/shared/deepMerge.d.ts +1 -0
  63. package/dist/types/shared/detection.d.ts +11 -0
  64. package/dist/types/shared/localisedUrls.d.ts +13 -0
  65. package/dist/types/shared/type.d.ts +168 -0
  66. package/dist/types/shared/utils.d.ts +5 -0
  67. package/package.json +15 -15
  68. package/rstest.config.mts +39 -0
  69. package/src/cli/index.ts +43 -1
  70. package/src/runtime/I18nLink.tsx +10 -16
  71. package/src/runtime/context.tsx +45 -7
  72. package/src/runtime/hooks.ts +13 -4
  73. package/src/runtime/i18n/backend/defaults.ts +3 -1
  74. package/src/runtime/i18n/backend/middleware.node.ts +1 -1
  75. package/src/runtime/i18n/instance.ts +8 -5
  76. package/src/runtime/index.tsx +10 -2
  77. package/src/runtime/routerAdapter.tsx +333 -0
  78. package/src/runtime/utils.ts +22 -34
  79. package/src/server/index.ts +135 -10
  80. package/src/shared/localisedUrls.ts +393 -0
  81. package/src/shared/type.ts +12 -0
  82. package/tests/localisedUrls.test.ts +278 -0
  83. package/tests/routerAdapter.test.tsx +278 -0
@@ -0,0 +1,278 @@
1
+ import { InternalRuntimeContext } from '@modern-js/runtime/context';
2
+ import type React from 'react';
3
+ import { act } from 'react';
4
+ import { createRoot, type Root } from 'react-dom/client';
5
+ import { ModernI18nProvider, useModernI18n } from '../src/runtime/context';
6
+ import { I18nLink } from '../src/runtime/I18nLink';
7
+ import type { I18nInstance } from '../src/runtime/i18n';
8
+
9
+ (
10
+ globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
11
+ ).IS_REACT_ACT_ENVIRONMENT = true;
12
+
13
+ const localisedUrls = {
14
+ '/terms-of-service': {
15
+ en: '/terms-of-service',
16
+ cs: '/podminky-pouzivani',
17
+ },
18
+ };
19
+
20
+ const requestContext = {
21
+ request: {},
22
+ response: {},
23
+ };
24
+
25
+ const TanstackLink = ({ to, children, ...props }: any) => (
26
+ <a href={to} data-router-link="tanstack" {...props}>
27
+ {children}
28
+ </a>
29
+ );
30
+
31
+ function createI18nInstance(language = 'en'): I18nInstance {
32
+ return {
33
+ language,
34
+ isInitialized: true,
35
+ init: () => Promise.resolve(undefined),
36
+ use: () => {},
37
+ createInstance: () => createI18nInstance(language),
38
+ setLang: rstest.fn(async () => undefined),
39
+ changeLanguage: rstest.fn(async () => undefined),
40
+ services: {},
41
+ options: {},
42
+ };
43
+ }
44
+
45
+ function createRuntimeContext(
46
+ router: unknown,
47
+ framework: 'tanstack' | 'react-router',
48
+ ) {
49
+ return {
50
+ isBrowser: true,
51
+ requestContext,
52
+ context: requestContext,
53
+ routerFramework: framework,
54
+ routerInstance: router,
55
+ routerRuntime: {
56
+ framework,
57
+ instance: router,
58
+ },
59
+ router: {
60
+ ...(framework === 'tanstack'
61
+ ? { Link: TanstackLink, useRouter: () => router }
62
+ : { useLocation: () => undefined, useHref: () => undefined }),
63
+ },
64
+ } as any;
65
+ }
66
+
67
+ function createTanstackRuntimeContext(router: unknown) {
68
+ return createRuntimeContext(router, 'tanstack');
69
+ }
70
+
71
+ function createReactRouterRuntimeContext(router: unknown) {
72
+ return createRuntimeContext(router, 'react-router');
73
+ }
74
+
75
+ function createTanstackRouter(pathname = '/en/terms-of-service', lang = 'en') {
76
+ const url = new URL(pathname, 'https://modernjs.test');
77
+
78
+ return {
79
+ navigate: rstest.fn(async () => undefined),
80
+ state: {
81
+ location: {
82
+ pathname: url.pathname,
83
+ searchStr: url.search,
84
+ hash: url.hash,
85
+ },
86
+ matches: [
87
+ {
88
+ params: {
89
+ lang,
90
+ },
91
+ },
92
+ ],
93
+ },
94
+ };
95
+ }
96
+
97
+ async function renderWithRuntime(
98
+ node: React.ReactNode,
99
+ runtimeContext: ReturnType<typeof createTanstackRuntimeContext>,
100
+ ) {
101
+ const container = document.createElement('div');
102
+ document.body.appendChild(container);
103
+ const root = createRoot(container);
104
+
105
+ await act(async () => {
106
+ root.render(
107
+ <InternalRuntimeContext.Provider value={runtimeContext}>
108
+ {node}
109
+ </InternalRuntimeContext.Provider>,
110
+ );
111
+ });
112
+
113
+ return {
114
+ container,
115
+ root,
116
+ };
117
+ }
118
+
119
+ function cleanup(rendered?: { container: HTMLElement; root: Root }) {
120
+ if (!rendered) {
121
+ return;
122
+ }
123
+ act(() => {
124
+ rendered.root.unmount();
125
+ });
126
+ rendered.container.remove();
127
+ }
128
+
129
+ describe('i18n router adapter', () => {
130
+ let rendered: { container: HTMLElement; root: Root } | undefined;
131
+
132
+ afterEach(() => {
133
+ cleanup(rendered);
134
+ rendered = undefined;
135
+ window.history.replaceState(null, '', '/');
136
+ });
137
+
138
+ test('uses the TanStack router Link for I18nLink rendering', async () => {
139
+ const router = createTanstackRouter('/cs/podminky-pouzivani', 'cs');
140
+ rendered = await renderWithRuntime(
141
+ <ModernI18nProvider
142
+ value={{
143
+ language: 'cs',
144
+ i18nInstance: createI18nInstance('cs'),
145
+ languages: ['en', 'cs'],
146
+ localePathRedirect: true,
147
+ localisedUrls,
148
+ }}
149
+ >
150
+ <I18nLink to="/terms-of-service" data-testid="terms-link">
151
+ Terms
152
+ </I18nLink>
153
+ </ModernI18nProvider>,
154
+ createTanstackRuntimeContext(router),
155
+ );
156
+
157
+ const link = rendered.container.querySelector<HTMLAnchorElement>(
158
+ '[data-testid="terms-link"]',
159
+ );
160
+ expect(link?.getAttribute('href')).toBe('/cs/podminky-pouzivani');
161
+ expect(link?.getAttribute('data-router-link')).toBe('tanstack');
162
+ });
163
+
164
+ test('uses TanStack-shaped replacement when changeLanguage updates the URL', async () => {
165
+ window.history.replaceState(
166
+ null,
167
+ '',
168
+ '/en/terms-of-service?from=test#section',
169
+ );
170
+
171
+ const router = createTanstackRouter(
172
+ '/en/terms-of-service?from=test#section',
173
+ );
174
+ let changeLanguagePromise: Promise<void> | undefined;
175
+
176
+ const Harness = () => {
177
+ const { changeLanguage } = useModernI18n();
178
+ return (
179
+ <button
180
+ type="button"
181
+ onClick={() => {
182
+ changeLanguagePromise = changeLanguage('cs');
183
+ }}
184
+ >
185
+ Change language
186
+ </button>
187
+ );
188
+ };
189
+
190
+ rendered = await renderWithRuntime(
191
+ <ModernI18nProvider
192
+ value={{
193
+ language: 'en',
194
+ i18nInstance: createI18nInstance('en'),
195
+ languages: ['en', 'cs'],
196
+ localePathRedirect: true,
197
+ localisedUrls,
198
+ }}
199
+ >
200
+ <Harness />
201
+ </ModernI18nProvider>,
202
+ createTanstackRuntimeContext(router),
203
+ );
204
+
205
+ const button = rendered.container.querySelector('button');
206
+
207
+ await act(async () => {
208
+ button?.dispatchEvent(
209
+ new MouseEvent('click', {
210
+ bubbles: true,
211
+ cancelable: true,
212
+ button: 0,
213
+ }),
214
+ );
215
+ await changeLanguagePromise;
216
+ });
217
+
218
+ expect(router.navigate).toHaveBeenCalledWith({
219
+ to: '/cs/podminky-pouzivani?from=test#section',
220
+ replace: true,
221
+ });
222
+ });
223
+
224
+ test('keeps React Router positional replacement when changeLanguage updates the URL', async () => {
225
+ window.history.replaceState(null, '', '/en/terms-of-service');
226
+
227
+ const router = {
228
+ navigate: rstest.fn(async () => undefined),
229
+ };
230
+ let changeLanguagePromise: Promise<void> | undefined;
231
+
232
+ const Harness = () => {
233
+ const { changeLanguage } = useModernI18n();
234
+ return (
235
+ <button
236
+ type="button"
237
+ onClick={() => {
238
+ changeLanguagePromise = changeLanguage('cs');
239
+ }}
240
+ >
241
+ Change language
242
+ </button>
243
+ );
244
+ };
245
+
246
+ rendered = await renderWithRuntime(
247
+ <ModernI18nProvider
248
+ value={{
249
+ language: 'en',
250
+ i18nInstance: createI18nInstance('en'),
251
+ languages: ['en', 'cs'],
252
+ localePathRedirect: true,
253
+ localisedUrls,
254
+ }}
255
+ >
256
+ <Harness />
257
+ </ModernI18nProvider>,
258
+ createReactRouterRuntimeContext(router),
259
+ );
260
+
261
+ const button = rendered.container.querySelector('button');
262
+
263
+ await act(async () => {
264
+ button?.dispatchEvent(
265
+ new MouseEvent('click', {
266
+ bubbles: true,
267
+ cancelable: true,
268
+ button: 0,
269
+ }),
270
+ );
271
+ await changeLanguagePromise;
272
+ });
273
+
274
+ expect(router.navigate).toHaveBeenCalledWith('/cs/podminky-pouzivani', {
275
+ replace: true,
276
+ });
277
+ });
278
+ });