@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.101 → 3.5.0-ultramodern.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/runtime/providerComposition.js +9 -10
- package/dist/cjs/runtime/routerAdapter.js +14 -1
- package/dist/esm/runtime/providerComposition.mjs +9 -10
- package/dist/esm/runtime/routerAdapter.mjs +15 -2
- package/dist/esm-node/runtime/providerComposition.mjs +9 -10
- package/dist/esm-node/runtime/routerAdapter.mjs +15 -2
- package/package.json +10 -10
- package/src/runtime/providerComposition.tsx +14 -13
- package/src/runtime/routerAdapter.tsx +28 -2
- package/tests/routerAdapter.test.tsx +81 -0
|
@@ -77,21 +77,20 @@ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
|
77
77
|
forceUpdate,
|
|
78
78
|
synchronizeLanguage
|
|
79
79
|
]);
|
|
80
|
+
const I18nextProvider = getI18nextProvider();
|
|
81
|
+
const i18nextInstanceForProvider = (0, external_react_namespaceObject.useMemo)(()=>i18nInstance ? (0, instance_js_namespaceObject.getI18nextInstanceForProvider)(i18nInstance, lang) : void 0, [
|
|
82
|
+
i18nInstance,
|
|
83
|
+
lang
|
|
84
|
+
]);
|
|
80
85
|
const children = props.children;
|
|
81
86
|
let appContent = App ? /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(App, {
|
|
82
87
|
...props,
|
|
83
88
|
children: children
|
|
84
89
|
}) : children;
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
appContent = /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(I18nextProvider, {
|
|
90
|
-
i18n: i18nextInstanceForProvider,
|
|
91
|
-
children: appContent
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
90
|
+
if (I18nextProvider && i18nextInstanceForProvider) appContent = /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(I18nextProvider, {
|
|
91
|
+
i18n: i18nextInstanceForProvider,
|
|
92
|
+
children: appContent
|
|
93
|
+
});
|
|
95
94
|
return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsxs)(jsx_runtime_namespaceObject.Fragment, {
|
|
96
95
|
children: [
|
|
97
96
|
Boolean(htmlLangAttr) && /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(head_namespaceObject.Helmet, {
|
|
@@ -82,6 +82,12 @@ const getRouterParams = (internalContext, contextRouter)=>{
|
|
|
82
82
|
return params;
|
|
83
83
|
}, {});
|
|
84
84
|
};
|
|
85
|
+
const getRouterSnapshot = (internalContext, contextRouter)=>{
|
|
86
|
+
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
87
|
+
const params = getRouterParams(internalContext, contextRouter);
|
|
88
|
+
const serializedParams = Object.entries(params).sort(([left], [right])=>left.localeCompare(right)).map(([key, value])=>`${key}=${value}`).join('&');
|
|
89
|
+
return `${location?.pathname ?? ''}\u0000${location?.search ?? ''}\u0000${location?.hash ?? ''}\u0000${serializedParams}`;
|
|
90
|
+
};
|
|
85
91
|
const useI18nRouterAdapter = ()=>{
|
|
86
92
|
const runtimeContext = (0, external_react_namespaceObject.useContext)(runtime_namespaceObject.RuntimeContext);
|
|
87
93
|
const internalContext = (0, external_react_namespaceObject.useContext)(context_namespaceObject.InternalRuntimeContext);
|
|
@@ -95,11 +101,18 @@ const useI18nRouterAdapter = ()=>{
|
|
|
95
101
|
warn: false
|
|
96
102
|
}) : null;
|
|
97
103
|
const [, setRouterVersion] = (0, external_react_namespaceObject.useState)(0);
|
|
104
|
+
const routerSnapshotRef = (0, external_react_namespaceObject.useRef)(getRouterSnapshot(internalContext, contextRouter));
|
|
105
|
+
routerSnapshotRef.current = getRouterSnapshot(internalContext, contextRouter);
|
|
98
106
|
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
99
107
|
(0, external_react_namespaceObject.useEffect)(()=>{
|
|
100
108
|
const router = getRouterInstance(internalContext, contextRouter);
|
|
101
109
|
if (!router) return;
|
|
102
|
-
const update = ()=>
|
|
110
|
+
const update = ()=>{
|
|
111
|
+
const nextSnapshot = getRouterSnapshot(internalContext, contextRouter);
|
|
112
|
+
if (nextSnapshot === routerSnapshotRef.current) return;
|
|
113
|
+
routerSnapshotRef.current = nextSnapshot;
|
|
114
|
+
setRouterVersion((version)=>version + 1);
|
|
115
|
+
};
|
|
103
116
|
const unsubscribers = [];
|
|
104
117
|
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
105
118
|
const subscribe = router.subscribe;
|
|
@@ -45,21 +45,20 @@ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
|
45
45
|
forceUpdate,
|
|
46
46
|
synchronizeLanguage
|
|
47
47
|
]);
|
|
48
|
+
const I18nextProvider = getI18nextProvider();
|
|
49
|
+
const i18nextInstanceForProvider = useMemo(()=>i18nInstance ? getI18nextInstanceForProvider(i18nInstance, lang) : void 0, [
|
|
50
|
+
i18nInstance,
|
|
51
|
+
lang
|
|
52
|
+
]);
|
|
48
53
|
const children = props.children;
|
|
49
54
|
let appContent = App ? /*#__PURE__*/ jsx(App, {
|
|
50
55
|
...props,
|
|
51
56
|
children: children
|
|
52
57
|
}) : children;
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
appContent = /*#__PURE__*/ jsx(I18nextProvider, {
|
|
58
|
-
i18n: i18nextInstanceForProvider,
|
|
59
|
-
children: appContent
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
58
|
+
if (I18nextProvider && i18nextInstanceForProvider) appContent = /*#__PURE__*/ jsx(I18nextProvider, {
|
|
59
|
+
i18n: i18nextInstanceForProvider,
|
|
60
|
+
children: appContent
|
|
61
|
+
});
|
|
63
62
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
64
63
|
children: [
|
|
65
64
|
Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RuntimeContext, isBrowser } from "@modern-js/runtime";
|
|
2
2
|
import { InternalRuntimeContext, getRouterRuntimeState } from "@modern-js/runtime/context";
|
|
3
3
|
import { Link as router_Link, useInRouterContext, useLocation, useNavigate, useParams } from "@modern-js/runtime/router";
|
|
4
|
-
import { useCallback, useContext, useEffect, useState } from "react";
|
|
4
|
+
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
5
5
|
const normalizeUrlPart = (value, prefix)=>{
|
|
6
6
|
if ('string' != typeof value || !value) return '';
|
|
7
7
|
return value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
@@ -50,6 +50,12 @@ const getRouterParams = (internalContext, contextRouter)=>{
|
|
|
50
50
|
return params;
|
|
51
51
|
}, {});
|
|
52
52
|
};
|
|
53
|
+
const getRouterSnapshot = (internalContext, contextRouter)=>{
|
|
54
|
+
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
55
|
+
const params = getRouterParams(internalContext, contextRouter);
|
|
56
|
+
const serializedParams = Object.entries(params).sort(([left], [right])=>left.localeCompare(right)).map(([key, value])=>`${key}=${value}`).join('&');
|
|
57
|
+
return `${location?.pathname ?? ''}\u0000${location?.search ?? ''}\u0000${location?.hash ?? ''}\u0000${serializedParams}`;
|
|
58
|
+
};
|
|
53
59
|
const useI18nRouterAdapter = ()=>{
|
|
54
60
|
const runtimeContext = useContext(RuntimeContext);
|
|
55
61
|
const internalContext = useContext(InternalRuntimeContext);
|
|
@@ -63,11 +69,18 @@ const useI18nRouterAdapter = ()=>{
|
|
|
63
69
|
warn: false
|
|
64
70
|
}) : null;
|
|
65
71
|
const [, setRouterVersion] = useState(0);
|
|
72
|
+
const routerSnapshotRef = useRef(getRouterSnapshot(internalContext, contextRouter));
|
|
73
|
+
routerSnapshotRef.current = getRouterSnapshot(internalContext, contextRouter);
|
|
66
74
|
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
67
75
|
useEffect(()=>{
|
|
68
76
|
const router = getRouterInstance(internalContext, contextRouter);
|
|
69
77
|
if (!router) return;
|
|
70
|
-
const update = ()=>
|
|
78
|
+
const update = ()=>{
|
|
79
|
+
const nextSnapshot = getRouterSnapshot(internalContext, contextRouter);
|
|
80
|
+
if (nextSnapshot === routerSnapshotRef.current) return;
|
|
81
|
+
routerSnapshotRef.current = nextSnapshot;
|
|
82
|
+
setRouterVersion((version)=>version + 1);
|
|
83
|
+
};
|
|
71
84
|
const unsubscribers = [];
|
|
72
85
|
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
73
86
|
const subscribe = router.subscribe;
|
|
@@ -46,21 +46,20 @@ const createI18nRootWrapper = (options)=>(App)=>(props)=>{
|
|
|
46
46
|
forceUpdate,
|
|
47
47
|
synchronizeLanguage
|
|
48
48
|
]);
|
|
49
|
+
const I18nextProvider = getI18nextProvider();
|
|
50
|
+
const i18nextInstanceForProvider = useMemo(()=>i18nInstance ? getI18nextInstanceForProvider(i18nInstance, lang) : void 0, [
|
|
51
|
+
i18nInstance,
|
|
52
|
+
lang
|
|
53
|
+
]);
|
|
49
54
|
const children = props.children;
|
|
50
55
|
let appContent = App ? /*#__PURE__*/ jsx(App, {
|
|
51
56
|
...props,
|
|
52
57
|
children: children
|
|
53
58
|
}) : children;
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
appContent = /*#__PURE__*/ jsx(I18nextProvider, {
|
|
59
|
-
i18n: i18nextInstanceForProvider,
|
|
60
|
-
children: appContent
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
59
|
+
if (I18nextProvider && i18nextInstanceForProvider) appContent = /*#__PURE__*/ jsx(I18nextProvider, {
|
|
60
|
+
i18n: i18nextInstanceForProvider,
|
|
61
|
+
children: appContent
|
|
62
|
+
});
|
|
64
63
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
65
64
|
children: [
|
|
66
65
|
Boolean(htmlLangAttr) && /*#__PURE__*/ jsx(Helmet, {
|
|
@@ -2,7 +2,7 @@ import "node:module";
|
|
|
2
2
|
import { RuntimeContext, isBrowser } from "@modern-js/runtime";
|
|
3
3
|
import { InternalRuntimeContext, getRouterRuntimeState } from "@modern-js/runtime/context";
|
|
4
4
|
import { Link as router_Link, useInRouterContext, useLocation, useNavigate, useParams } from "@modern-js/runtime/router";
|
|
5
|
-
import { useCallback, useContext, useEffect, useState } from "react";
|
|
5
|
+
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
6
6
|
const normalizeUrlPart = (value, prefix)=>{
|
|
7
7
|
if ('string' != typeof value || !value) return '';
|
|
8
8
|
return value.startsWith(prefix) ? value : `${prefix}${value}`;
|
|
@@ -51,6 +51,12 @@ const getRouterParams = (internalContext, contextRouter)=>{
|
|
|
51
51
|
return params;
|
|
52
52
|
}, {});
|
|
53
53
|
};
|
|
54
|
+
const getRouterSnapshot = (internalContext, contextRouter)=>{
|
|
55
|
+
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
56
|
+
const params = getRouterParams(internalContext, contextRouter);
|
|
57
|
+
const serializedParams = Object.entries(params).sort(([left], [right])=>left.localeCompare(right)).map(([key, value])=>`${key}=${value}`).join('&');
|
|
58
|
+
return `${location?.pathname ?? ''}\u0000${location?.search ?? ''}\u0000${location?.hash ?? ''}\u0000${serializedParams}`;
|
|
59
|
+
};
|
|
54
60
|
const useI18nRouterAdapter = ()=>{
|
|
55
61
|
const runtimeContext = useContext(RuntimeContext);
|
|
56
62
|
const internalContext = useContext(InternalRuntimeContext);
|
|
@@ -64,11 +70,18 @@ const useI18nRouterAdapter = ()=>{
|
|
|
64
70
|
warn: false
|
|
65
71
|
}) : null;
|
|
66
72
|
const [, setRouterVersion] = useState(0);
|
|
73
|
+
const routerSnapshotRef = useRef(getRouterSnapshot(internalContext, contextRouter));
|
|
74
|
+
routerSnapshotRef.current = getRouterSnapshot(internalContext, contextRouter);
|
|
67
75
|
const hasRouter = 'tanstack' === framework || 'react-router' === framework || Boolean(reactRouterNavigate);
|
|
68
76
|
useEffect(()=>{
|
|
69
77
|
const router = getRouterInstance(internalContext, contextRouter);
|
|
70
78
|
if (!router) return;
|
|
71
|
-
const update = ()=>
|
|
79
|
+
const update = ()=>{
|
|
80
|
+
const nextSnapshot = getRouterSnapshot(internalContext, contextRouter);
|
|
81
|
+
if (nextSnapshot === routerSnapshotRef.current) return;
|
|
82
|
+
routerSnapshotRef.current = nextSnapshot;
|
|
83
|
+
setRouterVersion((version)=>version + 1);
|
|
84
|
+
};
|
|
72
85
|
const unsubscribers = [];
|
|
73
86
|
if ('react-router' === framework && !inReactRouter && 'function' == typeof router.subscribe) {
|
|
74
87
|
const subscribe = router.subscribe;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.5.0-ultramodern.
|
|
20
|
+
"version": "3.5.0-ultramodern.102",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -91,12 +91,12 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.
|
|
95
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.
|
|
96
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.
|
|
97
|
-
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.
|
|
98
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.
|
|
99
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.
|
|
94
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.102",
|
|
95
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.102",
|
|
96
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.102",
|
|
97
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.102",
|
|
98
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.102",
|
|
99
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.102",
|
|
100
100
|
"@swc/helpers": "^0.5.23",
|
|
101
101
|
"i18next-browser-languagedetector": "^8.2.1",
|
|
102
102
|
"i18next-chained-backend": "^5.0.5",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"i18next-http-middleware": "^3.9.7"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
108
|
+
"@modern-js/runtime": "3.5.0-ultramodern.102",
|
|
109
109
|
"i18next": ">=26.3.1",
|
|
110
110
|
"react": "^19.2.7",
|
|
111
111
|
"react-dom": "^19.2.7",
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.
|
|
124
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.
|
|
123
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.102",
|
|
124
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.102",
|
|
125
125
|
"@rslib/core": "0.23.2",
|
|
126
126
|
"@types/node": "^26.1.1",
|
|
127
127
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
@@ -113,6 +113,14 @@ export const createI18nRootWrapper =
|
|
|
113
113
|
synchronizeLanguage,
|
|
114
114
|
],
|
|
115
115
|
);
|
|
116
|
+
const I18nextProvider = getI18nextProvider();
|
|
117
|
+
const i18nextInstanceForProvider = useMemo(
|
|
118
|
+
() =>
|
|
119
|
+
i18nInstance
|
|
120
|
+
? getI18nextInstanceForProvider(i18nInstance, lang)
|
|
121
|
+
: undefined,
|
|
122
|
+
[i18nInstance, lang],
|
|
123
|
+
);
|
|
116
124
|
|
|
117
125
|
const children = (props as React.PropsWithChildren).children;
|
|
118
126
|
let appContent: React.ReactNode = App ? (
|
|
@@ -121,19 +129,12 @@ export const createI18nRootWrapper =
|
|
|
121
129
|
children
|
|
122
130
|
);
|
|
123
131
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
131
|
-
appContent = (
|
|
132
|
-
<I18nextProvider i18n={i18nextInstanceForProvider}>
|
|
133
|
-
{appContent}
|
|
134
|
-
</I18nextProvider>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
132
|
+
if (I18nextProvider && i18nextInstanceForProvider) {
|
|
133
|
+
appContent = (
|
|
134
|
+
<I18nextProvider i18n={i18nextInstanceForProvider}>
|
|
135
|
+
{appContent}
|
|
136
|
+
</I18nextProvider>
|
|
137
|
+
);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
return (
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useParams as useReactRouterParams,
|
|
14
14
|
} from '@modern-js/runtime/router';
|
|
15
15
|
import type React from 'react';
|
|
16
|
-
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
16
|
+
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
17
17
|
|
|
18
18
|
type I18nRouterFramework = 'react-router' | 'tanstack' | string;
|
|
19
19
|
|
|
@@ -205,6 +205,21 @@ const getRouterParams = (
|
|
|
205
205
|
}, {});
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
+
const getRouterSnapshot = (
|
|
209
|
+
internalContext: InternalRuntimeContextWithRouter,
|
|
210
|
+
contextRouter?: RouterInstance | null,
|
|
211
|
+
) => {
|
|
212
|
+
const location = getRouterStateLocation(internalContext, contextRouter);
|
|
213
|
+
const params = getRouterParams(internalContext, contextRouter);
|
|
214
|
+
const serializedParams = Object.entries(params)
|
|
215
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
216
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
217
|
+
.join('&');
|
|
218
|
+
return `${location?.pathname ?? ''}\u0000${location?.search ?? ''}\u0000${
|
|
219
|
+
location?.hash ?? ''
|
|
220
|
+
}\u0000${serializedParams}`;
|
|
221
|
+
};
|
|
222
|
+
|
|
208
223
|
export const useI18nRouterAdapter = (): I18nRouterAdapter => {
|
|
209
224
|
const runtimeContext = useContext(RuntimeContext) as RuntimeContextWithRouter;
|
|
210
225
|
const internalContext = useContext(
|
|
@@ -227,6 +242,10 @@ export const useI18nRouterAdapter = (): I18nRouterAdapter => {
|
|
|
227
242
|
? (contextUseRouter({ warn: false }) as RouterInstance | null)
|
|
228
243
|
: null;
|
|
229
244
|
const [, setRouterVersion] = useState(0);
|
|
245
|
+
const routerSnapshotRef = useRef(
|
|
246
|
+
getRouterSnapshot(internalContext, contextRouter),
|
|
247
|
+
);
|
|
248
|
+
routerSnapshotRef.current = getRouterSnapshot(internalContext, contextRouter);
|
|
230
249
|
const hasRouter =
|
|
231
250
|
framework === 'tanstack' ||
|
|
232
251
|
framework === 'react-router' ||
|
|
@@ -238,7 +257,14 @@ export const useI18nRouterAdapter = (): I18nRouterAdapter => {
|
|
|
238
257
|
return;
|
|
239
258
|
}
|
|
240
259
|
|
|
241
|
-
const update = () =>
|
|
260
|
+
const update = () => {
|
|
261
|
+
const nextSnapshot = getRouterSnapshot(internalContext, contextRouter);
|
|
262
|
+
if (nextSnapshot === routerSnapshotRef.current) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
routerSnapshotRef.current = nextSnapshot;
|
|
266
|
+
setRouterVersion(version => version + 1);
|
|
267
|
+
};
|
|
242
268
|
const unsubscribers: Array<() => void> = [];
|
|
243
269
|
|
|
244
270
|
if (
|
|
@@ -20,6 +20,7 @@ import { I18nLink } from '../src/runtime/I18nLink';
|
|
|
20
20
|
import type { I18nInstance } from '../src/runtime/i18n';
|
|
21
21
|
import { getReactI18nextIntegration } from '../src/runtime/i18n/react-i18next';
|
|
22
22
|
import { createI18nRootWrapper } from '../src/runtime/providerComposition';
|
|
23
|
+
import { useI18nRouterAdapter } from '../src/runtime/routerAdapter';
|
|
23
24
|
|
|
24
25
|
(
|
|
25
26
|
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
@@ -244,6 +245,11 @@ function createMutableTanstackRouter(pathname = '/en') {
|
|
|
244
245
|
listener();
|
|
245
246
|
}
|
|
246
247
|
},
|
|
248
|
+
publishStateUpdate() {
|
|
249
|
+
for (const listener of listeners) {
|
|
250
|
+
listener();
|
|
251
|
+
}
|
|
252
|
+
},
|
|
247
253
|
};
|
|
248
254
|
}
|
|
249
255
|
|
|
@@ -459,6 +465,53 @@ describe('i18n runtime wrapRoot', () => {
|
|
|
459
465
|
expect(rendered.container.textContent).toContain('router content');
|
|
460
466
|
});
|
|
461
467
|
|
|
468
|
+
test('keeps the i18next provider instance stable across unrelated parent renders', async () => {
|
|
469
|
+
const i18nInstance = createI18nInstance('cs');
|
|
470
|
+
const providerInstances: I18nInstance[] = [];
|
|
471
|
+
const I18nextProvider = ({
|
|
472
|
+
children,
|
|
473
|
+
i18n,
|
|
474
|
+
}: PropsWithChildren<{ i18n: I18nInstance }>) => {
|
|
475
|
+
providerInstances.push(i18n);
|
|
476
|
+
return <>{children}</>;
|
|
477
|
+
};
|
|
478
|
+
const App = () => <main>router content</main>;
|
|
479
|
+
const I18nRoot = createI18nRootWrapper({
|
|
480
|
+
htmlLangAttr: false,
|
|
481
|
+
localePathRedirect: false,
|
|
482
|
+
languages: ['en', 'cs'],
|
|
483
|
+
fallbackLanguage: 'en',
|
|
484
|
+
getLatestI18nInstance: () => i18nInstance,
|
|
485
|
+
getI18nextProvider: () => I18nextProvider,
|
|
486
|
+
})(App);
|
|
487
|
+
const Parent = () => {
|
|
488
|
+
const [renderVersion, setRenderVersion] = useState(0);
|
|
489
|
+
return (
|
|
490
|
+
<>
|
|
491
|
+
<button
|
|
492
|
+
type="button"
|
|
493
|
+
onClick={() => setRenderVersion(version => version + 1)}
|
|
494
|
+
>
|
|
495
|
+
Render {renderVersion}
|
|
496
|
+
</button>
|
|
497
|
+
<I18nRoot renderVersion={renderVersion} />
|
|
498
|
+
</>
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
rendered = await renderI18nRoot(<Parent />);
|
|
503
|
+
const initialProviderInstance = providerInstances.at(-1);
|
|
504
|
+
|
|
505
|
+
await act(async () => {
|
|
506
|
+
rendered?.container
|
|
507
|
+
.querySelector('button')
|
|
508
|
+
?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(providerInstances.length).toBeGreaterThan(1);
|
|
512
|
+
expect(providerInstances.at(-1)).toBe(initialProviderInstance);
|
|
513
|
+
});
|
|
514
|
+
|
|
462
515
|
test('commits route language and translated copy after the instance changes', async () => {
|
|
463
516
|
window.history.replaceState(null, '', '/en');
|
|
464
517
|
const router = createMutableTanstackRouter();
|
|
@@ -762,6 +815,34 @@ describe('i18n router adapter', () => {
|
|
|
762
815
|
expect(link?.getAttribute('data-router-link')).toBe('tanstack');
|
|
763
816
|
});
|
|
764
817
|
|
|
818
|
+
test('ignores router state updates that do not change location or params', async () => {
|
|
819
|
+
const router = createMutableTanstackRouter('/en');
|
|
820
|
+
let renders = 0;
|
|
821
|
+
const LocationProbe = () => {
|
|
822
|
+
renders += 1;
|
|
823
|
+
const { location } = useI18nRouterAdapter();
|
|
824
|
+
return <output>{location?.pathname}</output>;
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
rendered = await renderWithRuntime(
|
|
828
|
+
<LocationProbe />,
|
|
829
|
+
createTanstackRuntimeContext(router),
|
|
830
|
+
);
|
|
831
|
+
const initialRenders = renders;
|
|
832
|
+
|
|
833
|
+
await act(async () => {
|
|
834
|
+
router.publishStateUpdate();
|
|
835
|
+
});
|
|
836
|
+
expect(renders).toBe(initialRenders);
|
|
837
|
+
expect(rendered.container.textContent).toBe('/en');
|
|
838
|
+
|
|
839
|
+
await act(async () => {
|
|
840
|
+
router.publishPathname('/cs');
|
|
841
|
+
});
|
|
842
|
+
expect(renders).toBe(initialRenders + 1);
|
|
843
|
+
expect(rendered.container.textContent).toBe('/cs');
|
|
844
|
+
});
|
|
845
|
+
|
|
765
846
|
test('forwards warmup props through I18nLink with a localized string target', async () => {
|
|
766
847
|
const router = createTanstackRouter('/cs/podminky-pouzivani', 'cs');
|
|
767
848
|
rendered = await renderWithRuntime(
|