@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.4 → 3.5.0-ultramodern.41
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/cli/index.js +2 -1
- package/dist/cjs/cli/locales.js +15 -15
- package/dist/cjs/runtime/Link.js +8 -89
- package/dist/cjs/runtime/context.js +18 -82
- package/dist/cjs/runtime/contextHelpers.js +137 -0
- package/dist/cjs/runtime/core.js +29 -132
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +7 -7
- package/dist/cjs/runtime/i18n/detection/cache.js +150 -0
- package/dist/cjs/runtime/i18n/detection/detector.js +100 -0
- package/dist/cjs/runtime/i18n/detection/index.js +8 -263
- package/dist/cjs/runtime/i18n/detection/initOptions.js +69 -0
- package/dist/cjs/runtime/i18n/detection/language.js +62 -0
- package/dist/cjs/runtime/i18n/detection/path.js +49 -0
- package/dist/cjs/runtime/i18n/detection/priority.js +66 -0
- package/dist/cjs/runtime/i18n/detection/ssr.js +70 -0
- package/dist/cjs/runtime/i18n/detection/types.js +18 -0
- package/dist/cjs/runtime/i18n/instance.js +7 -7
- package/dist/cjs/runtime/linkHelpers.js +148 -0
- package/dist/cjs/runtime/pluginSetup.js +102 -0
- package/dist/cjs/runtime/providerComposition.js +114 -0
- package/dist/cjs/runtime/reactI18next.js +43 -0
- package/dist/cjs/runtime/utils.js +1 -9
- package/dist/cjs/server/apiPrefix.js +72 -0
- package/dist/cjs/server/detectorOptions.js +97 -0
- package/dist/cjs/server/index.js +17 -144
- package/dist/cjs/server/redirectPolicy.js +96 -0
- package/dist/cjs/shared/detection.js +2 -104
- package/dist/cjs/shared/localisedUrls/config.js +49 -0
- package/dist/cjs/shared/localisedUrls/index.js +91 -0
- package/dist/cjs/shared/localisedUrls/normalise.js +87 -0
- package/dist/cjs/shared/localisedUrls/pathname.js +68 -0
- package/dist/cjs/shared/localisedUrls/patterns.js +130 -0
- package/dist/cjs/shared/localisedUrls/redirect.js +95 -0
- package/dist/cjs/shared/localisedUrls/resolve.js +88 -0
- package/dist/cjs/shared/localisedUrls/routes.js +129 -0
- package/dist/cjs/shared/localisedUrls/types.js +18 -0
- package/dist/cjs/shared/localisedUrls.js +28 -294
- package/dist/cjs/shared/utils.js +2 -8
- package/dist/esm/cli/index.mjs +2 -1
- package/dist/esm/cli/locales.mjs +13 -13
- package/dist/esm/runtime/Link.mjs +1 -82
- package/dist/esm/runtime/context.mjs +18 -82
- package/dist/esm/runtime/contextHelpers.mjs +84 -0
- package/dist/esm/runtime/core.mjs +30 -133
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm/runtime/i18n/detection/cache.mjs +103 -0
- package/dist/esm/runtime/i18n/detection/detector.mjs +62 -0
- package/dist/esm/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm/runtime/i18n/detection/initOptions.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/language.mjs +21 -0
- package/dist/esm/runtime/i18n/detection/path.mjs +11 -0
- package/dist/esm/runtime/i18n/detection/priority.mjs +28 -0
- package/dist/esm/runtime/i18n/detection/ssr.mjs +26 -0
- package/dist/esm/runtime/i18n/detection/types.mjs +0 -0
- package/dist/esm/runtime/i18n/instance.mjs +8 -5
- package/dist/esm/runtime/linkHelpers.mjs +98 -0
- package/dist/esm/runtime/pluginSetup.mjs +64 -0
- package/dist/esm/runtime/providerComposition.mjs +76 -0
- package/dist/esm/runtime/reactI18next.mjs +5 -0
- package/dist/esm/runtime/utils.mjs +2 -10
- package/dist/esm/server/apiPrefix.mjs +31 -0
- package/dist/esm/server/detectorOptions.mjs +59 -0
- package/dist/esm/server/index.mjs +8 -135
- package/dist/esm/server/redirectPolicy.mjs +46 -0
- package/dist/esm/shared/detection.mjs +1 -71
- package/dist/esm/shared/localisedUrls/config.mjs +11 -0
- package/dist/esm/shared/localisedUrls/index.mjs +7 -0
- package/dist/esm/shared/localisedUrls/normalise.mjs +37 -0
- package/dist/esm/shared/localisedUrls/pathname.mjs +24 -0
- package/dist/esm/shared/localisedUrls/patterns.mjs +86 -0
- package/dist/esm/shared/localisedUrls/redirect.mjs +48 -0
- package/dist/esm/shared/localisedUrls/resolve.mjs +47 -0
- package/dist/esm/shared/localisedUrls/routes.mjs +88 -0
- package/dist/esm/shared/localisedUrls/types.mjs +0 -0
- package/dist/esm/shared/localisedUrls.mjs +1 -283
- package/dist/esm/shared/utils.mjs +1 -1
- package/dist/esm-node/cli/index.mjs +2 -1
- package/dist/esm-node/cli/locales.mjs +13 -13
- package/dist/esm-node/runtime/Link.mjs +1 -82
- package/dist/esm-node/runtime/context.mjs +18 -82
- package/dist/esm-node/runtime/contextHelpers.mjs +85 -0
- package/dist/esm-node/runtime/core.mjs +30 -133
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +5 -5
- package/dist/esm-node/runtime/i18n/detection/cache.mjs +104 -0
- package/dist/esm-node/runtime/i18n/detection/detector.mjs +63 -0
- package/dist/esm-node/runtime/i18n/detection/index.mjs +4 -260
- package/dist/esm-node/runtime/i18n/detection/initOptions.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/language.mjs +22 -0
- package/dist/esm-node/runtime/i18n/detection/path.mjs +12 -0
- package/dist/esm-node/runtime/i18n/detection/priority.mjs +29 -0
- package/dist/esm-node/runtime/i18n/detection/ssr.mjs +27 -0
- package/dist/esm-node/runtime/i18n/detection/types.mjs +1 -0
- package/dist/esm-node/runtime/i18n/instance.mjs +8 -5
- package/dist/esm-node/runtime/linkHelpers.mjs +99 -0
- package/dist/esm-node/runtime/pluginSetup.mjs +65 -0
- package/dist/esm-node/runtime/providerComposition.mjs +77 -0
- package/dist/esm-node/runtime/reactI18next.mjs +6 -0
- package/dist/esm-node/runtime/utils.mjs +2 -10
- package/dist/esm-node/server/apiPrefix.mjs +32 -0
- package/dist/esm-node/server/detectorOptions.mjs +60 -0
- package/dist/esm-node/server/index.mjs +8 -135
- package/dist/esm-node/server/redirectPolicy.mjs +47 -0
- package/dist/esm-node/shared/detection.mjs +1 -71
- package/dist/esm-node/shared/localisedUrls/config.mjs +12 -0
- package/dist/esm-node/shared/localisedUrls/index.mjs +8 -0
- package/dist/esm-node/shared/localisedUrls/normalise.mjs +38 -0
- package/dist/esm-node/shared/localisedUrls/pathname.mjs +25 -0
- package/dist/esm-node/shared/localisedUrls/patterns.mjs +87 -0
- package/dist/esm-node/shared/localisedUrls/redirect.mjs +49 -0
- package/dist/esm-node/shared/localisedUrls/resolve.mjs +48 -0
- package/dist/esm-node/shared/localisedUrls/routes.mjs +89 -0
- package/dist/esm-node/shared/localisedUrls/types.mjs +1 -0
- package/dist/esm-node/shared/localisedUrls.mjs +1 -283
- package/dist/esm-node/shared/utils.mjs +1 -1
- package/dist/types/cli/locales.d.ts +1 -1
- package/dist/types/runtime/Link.d.ts +3 -7
- package/dist/types/runtime/context.d.ts +6 -5
- package/dist/types/runtime/contextHelpers.d.ts +28 -0
- package/dist/types/runtime/core.d.ts +3 -7
- package/dist/types/runtime/hooks.d.ts +5 -0
- package/dist/types/runtime/i18n/backend/middleware.common.d.ts +2 -1
- package/dist/types/runtime/i18n/detection/cache.d.ts +14 -0
- package/dist/types/runtime/i18n/detection/detector.d.ts +8 -0
- package/dist/types/runtime/i18n/detection/index.d.ts +5 -50
- package/dist/types/runtime/i18n/detection/initOptions.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/language.d.ts +11 -0
- package/dist/types/runtime/i18n/detection/middleware.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/middleware.node.d.ts +4 -4
- package/dist/types/runtime/i18n/detection/path.d.ts +5 -0
- package/dist/types/runtime/i18n/detection/priority.d.ts +10 -0
- package/dist/types/runtime/i18n/detection/ssr.d.ts +9 -0
- package/dist/types/runtime/i18n/detection/types.d.ts +32 -0
- package/dist/types/runtime/i18n/instance.d.ts +28 -26
- package/dist/types/runtime/i18n/react-i18next.d.ts +1 -6
- package/dist/types/runtime/i18n/utils.d.ts +14 -5
- package/dist/types/runtime/linkHelpers.d.ts +17 -0
- package/dist/types/runtime/pluginSetup.d.ts +32 -0
- package/dist/types/runtime/providerComposition.d.ts +16 -0
- package/dist/types/runtime/reactI18next.d.ts +7 -0
- package/dist/types/runtime/routerAdapter.d.ts +7 -6
- package/dist/types/runtime/utils.d.ts +7 -2
- package/dist/types/server/apiPrefix.d.ts +7 -0
- package/dist/types/server/detectorOptions.d.ts +21 -0
- package/dist/types/server/index.d.ts +1 -6
- package/dist/types/server/redirectPolicy.d.ts +22 -0
- package/dist/types/shared/detection.d.ts +1 -11
- package/dist/types/shared/localisedUrls/config.d.ts +9 -0
- package/dist/types/shared/localisedUrls/index.d.ts +9 -0
- package/dist/types/shared/localisedUrls/normalise.d.ts +10 -0
- package/dist/types/shared/localisedUrls/pathname.d.ts +4 -0
- package/dist/types/shared/localisedUrls/patterns.d.ts +5 -0
- package/dist/types/shared/localisedUrls/redirect.d.ts +11 -0
- package/dist/types/shared/localisedUrls/resolve.d.ts +7 -0
- package/dist/types/shared/localisedUrls/routes.d.ts +3 -0
- package/dist/types/shared/localisedUrls/types.d.ts +14 -0
- package/dist/types/shared/localisedUrls.d.ts +2 -36
- package/dist/types/shared/utils.d.ts +0 -2
- package/package.json +16 -18
- package/rstest.config.mts +1 -0
- package/src/cli/index.ts +5 -4
- package/src/cli/locales.ts +3 -3
- package/src/runtime/Link.tsx +11 -138
- package/src/runtime/context.tsx +45 -199
- package/src/runtime/contextHelpers.ts +232 -0
- package/src/runtime/core.tsx +43 -274
- package/src/runtime/hooks.ts +13 -2
- package/src/runtime/i18n/backend/defaults.node.ts +2 -2
- package/src/runtime/i18n/backend/middleware.common.ts +5 -4
- package/src/runtime/i18n/detection/cache.ts +189 -0
- package/src/runtime/i18n/detection/detector.ts +166 -0
- package/src/runtime/i18n/detection/index.ts +10 -641
- package/src/runtime/i18n/detection/initOptions.ts +74 -0
- package/src/runtime/i18n/detection/language.ts +64 -0
- package/src/runtime/i18n/detection/middleware.node.ts +26 -7
- package/src/runtime/i18n/detection/middleware.ts +15 -7
- package/src/runtime/i18n/detection/path.ts +41 -0
- package/src/runtime/i18n/detection/priority.ts +82 -0
- package/src/runtime/i18n/detection/ssr.ts +47 -0
- package/src/runtime/i18n/detection/types.ts +41 -0
- package/src/runtime/i18n/instance.ts +56 -31
- package/src/runtime/i18n/react-i18next.ts +1 -6
- package/src/runtime/i18n/utils.ts +18 -9
- package/src/runtime/linkHelpers.ts +174 -0
- package/src/runtime/pluginSetup.ts +189 -0
- package/src/runtime/providerComposition.tsx +144 -0
- package/src/runtime/reactI18next.ts +20 -0
- package/src/runtime/routerAdapter.tsx +6 -6
- package/src/runtime/utils.ts +13 -35
- package/src/server/apiPrefix.ts +67 -0
- package/src/server/detectorOptions.ts +105 -0
- package/src/server/index.ts +14 -319
- package/src/server/redirectPolicy.ts +127 -0
- package/src/shared/detection.ts +1 -1
- package/src/shared/localisedUrls/config.ts +18 -0
- package/src/shared/localisedUrls/index.ts +24 -0
- package/src/shared/localisedUrls/normalise.ts +67 -0
- package/src/shared/localisedUrls/pathname.ts +60 -0
- package/src/shared/localisedUrls/patterns.ts +160 -0
- package/src/shared/localisedUrls/redirect.ts +91 -0
- package/src/shared/localisedUrls/resolve.ts +135 -0
- package/src/shared/localisedUrls/routes.ts +226 -0
- package/src/shared/localisedUrls/types.ts +18 -0
- package/src/shared/localisedUrls.ts +26 -623
- package/src/shared/utils.ts +2 -2
- package/tests/fixtures/localised-routes.golden.json +156 -0
- package/tests/link.test.tsx +111 -0
- package/tests/linkTypes.test.ts +15 -4
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +147 -0
- package/tests/localisedUrls.test.ts +129 -2
- package/tests/reactI18nextRuntimeBoundary.test.ts +6 -3
- package/tests/redirectPolicy.test.ts +43 -0
- package/tests/routerAdapter.test.tsx +85 -0
- package/tests/type-fixture/fixture-globals.d.ts +11 -0
- package/tests/type-fixture/tsconfig.json +1 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": ":lang",
|
|
4
|
+
"path": ":lang",
|
|
5
|
+
"type": "nested",
|
|
6
|
+
"origin": "file-system",
|
|
7
|
+
"routeType": "layout",
|
|
8
|
+
"_component": ":lang.tsx",
|
|
9
|
+
"children": [
|
|
10
|
+
{
|
|
11
|
+
"id": "about",
|
|
12
|
+
"path": "about",
|
|
13
|
+
"type": "nested",
|
|
14
|
+
"origin": "file-system",
|
|
15
|
+
"routeType": "page",
|
|
16
|
+
"_component": "about.tsx",
|
|
17
|
+
"modernCanonicalPath": "/about"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "about__localised_o-nas",
|
|
21
|
+
"path": "o-nas",
|
|
22
|
+
"type": "nested",
|
|
23
|
+
"origin": "file-system",
|
|
24
|
+
"routeType": "page",
|
|
25
|
+
"_component": "about.tsx",
|
|
26
|
+
"modernCanonicalPath": "/about"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "about__localised_ueber-uns",
|
|
30
|
+
"path": "ueber-uns",
|
|
31
|
+
"type": "nested",
|
|
32
|
+
"origin": "file-system",
|
|
33
|
+
"routeType": "page",
|
|
34
|
+
"_component": "about.tsx",
|
|
35
|
+
"modernCanonicalPath": "/about"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "products",
|
|
39
|
+
"path": "products",
|
|
40
|
+
"type": "nested",
|
|
41
|
+
"origin": "file-system",
|
|
42
|
+
"routeType": "layout",
|
|
43
|
+
"_component": "products.tsx",
|
|
44
|
+
"children": [
|
|
45
|
+
{
|
|
46
|
+
"id": ":slug",
|
|
47
|
+
"path": ":slug",
|
|
48
|
+
"type": "nested",
|
|
49
|
+
"origin": "file-system",
|
|
50
|
+
"routeType": "page",
|
|
51
|
+
"_component": ":slug.tsx",
|
|
52
|
+
"modernCanonicalPath": "/products/:slug"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"modernCanonicalPath": "/products"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "products__localised_produkty",
|
|
59
|
+
"path": "produkty",
|
|
60
|
+
"type": "nested",
|
|
61
|
+
"origin": "file-system",
|
|
62
|
+
"routeType": "layout",
|
|
63
|
+
"_component": "products.tsx",
|
|
64
|
+
"children": [
|
|
65
|
+
{
|
|
66
|
+
"id": ":slug__localised_produkty",
|
|
67
|
+
"path": ":slug",
|
|
68
|
+
"type": "nested",
|
|
69
|
+
"origin": "file-system",
|
|
70
|
+
"routeType": "page",
|
|
71
|
+
"_component": ":slug.tsx",
|
|
72
|
+
"modernCanonicalPath": "/products/:slug"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"modernCanonicalPath": "/products"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "products__localised_produkte",
|
|
79
|
+
"path": "produkte",
|
|
80
|
+
"type": "nested",
|
|
81
|
+
"origin": "file-system",
|
|
82
|
+
"routeType": "layout",
|
|
83
|
+
"_component": "products.tsx",
|
|
84
|
+
"children": [
|
|
85
|
+
{
|
|
86
|
+
"id": ":slug__localised_produkte",
|
|
87
|
+
"path": ":slug",
|
|
88
|
+
"type": "nested",
|
|
89
|
+
"origin": "file-system",
|
|
90
|
+
"routeType": "page",
|
|
91
|
+
"_component": ":slug.tsx",
|
|
92
|
+
"modernCanonicalPath": "/products/:slug"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"modernCanonicalPath": "/products"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "docs",
|
|
99
|
+
"path": "docs",
|
|
100
|
+
"type": "nested",
|
|
101
|
+
"origin": "file-system",
|
|
102
|
+
"routeType": "layout",
|
|
103
|
+
"_component": "docs.tsx",
|
|
104
|
+
"children": [
|
|
105
|
+
{
|
|
106
|
+
"id": "*",
|
|
107
|
+
"path": "*",
|
|
108
|
+
"type": "nested",
|
|
109
|
+
"origin": "file-system",
|
|
110
|
+
"routeType": "page",
|
|
111
|
+
"_component": "*.tsx"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"modernCanonicalPath": "/docs"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "docs__localised_dokumenty",
|
|
118
|
+
"path": "dokumenty",
|
|
119
|
+
"type": "nested",
|
|
120
|
+
"origin": "file-system",
|
|
121
|
+
"routeType": "layout",
|
|
122
|
+
"_component": "docs.tsx",
|
|
123
|
+
"children": [
|
|
124
|
+
{
|
|
125
|
+
"id": "*__localised_dokumenty",
|
|
126
|
+
"path": "*",
|
|
127
|
+
"type": "nested",
|
|
128
|
+
"origin": "file-system",
|
|
129
|
+
"routeType": "page",
|
|
130
|
+
"_component": "*.tsx"
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"modernCanonicalPath": "/docs"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "docs__localised_dokumente",
|
|
137
|
+
"path": "dokumente",
|
|
138
|
+
"type": "nested",
|
|
139
|
+
"origin": "file-system",
|
|
140
|
+
"routeType": "layout",
|
|
141
|
+
"_component": "docs.tsx",
|
|
142
|
+
"children": [
|
|
143
|
+
{
|
|
144
|
+
"id": "*__localised_dokumente",
|
|
145
|
+
"path": "*",
|
|
146
|
+
"type": "nested",
|
|
147
|
+
"origin": "file-system",
|
|
148
|
+
"routeType": "page",
|
|
149
|
+
"_component": "*.tsx"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"modernCanonicalPath": "/docs"
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
package/tests/link.test.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { act } from 'react';
|
|
|
4
4
|
import { createRoot, type Root } from 'react-dom/client';
|
|
5
5
|
import { ModernI18nProvider } from '../src/runtime/context';
|
|
6
6
|
import type { I18nInstance } from '../src/runtime/i18n';
|
|
7
|
+
import { detectLanguageWithPriority } from '../src/runtime/i18n/detection';
|
|
7
8
|
import { interpolateRouteParams, Link } from '../src/runtime/Link';
|
|
8
9
|
import {
|
|
9
10
|
canonicalPath,
|
|
@@ -270,6 +271,60 @@ describe('localization utilities', () => {
|
|
|
270
271
|
});
|
|
271
272
|
});
|
|
272
273
|
|
|
274
|
+
describe('language detection priority', () => {
|
|
275
|
+
test('path locale overrides stale SSR data', async () => {
|
|
276
|
+
const previousSsrData = (window as any)._SSR_DATA;
|
|
277
|
+
(window as any)._SSR_DATA = { data: { i18nData: { lng: 'en' } } };
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
await expect(
|
|
281
|
+
detectLanguageWithPriority(createI18nInstance('en'), {
|
|
282
|
+
languages,
|
|
283
|
+
fallbackLanguage: 'en',
|
|
284
|
+
localePathRedirect: true,
|
|
285
|
+
i18nextDetector: false,
|
|
286
|
+
detection: {},
|
|
287
|
+
userInitOptions: {},
|
|
288
|
+
pathname: '/cs/produkty',
|
|
289
|
+
ssrContext: undefined,
|
|
290
|
+
}),
|
|
291
|
+
).resolves.toEqual({ detectedLanguage: 'cs', finalLanguage: 'cs' });
|
|
292
|
+
} finally {
|
|
293
|
+
if (previousSsrData === undefined) {
|
|
294
|
+
delete (window as any)._SSR_DATA;
|
|
295
|
+
} else {
|
|
296
|
+
(window as any)._SSR_DATA = previousSsrData;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('regional SSR language resolves to supported base language', async () => {
|
|
302
|
+
const previousSsrData = (window as any)._SSR_DATA;
|
|
303
|
+
(window as any)._SSR_DATA = { data: { i18nData: { lng: 'en-US' } } };
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
await expect(
|
|
307
|
+
detectLanguageWithPriority(createI18nInstance('cs'), {
|
|
308
|
+
languages,
|
|
309
|
+
fallbackLanguage: 'cs',
|
|
310
|
+
localePathRedirect: true,
|
|
311
|
+
i18nextDetector: false,
|
|
312
|
+
detection: {},
|
|
313
|
+
userInitOptions: {},
|
|
314
|
+
pathname: '/products',
|
|
315
|
+
ssrContext: undefined,
|
|
316
|
+
}),
|
|
317
|
+
).resolves.toEqual({ detectedLanguage: 'en', finalLanguage: 'en' });
|
|
318
|
+
} finally {
|
|
319
|
+
if (previousSsrData === undefined) {
|
|
320
|
+
delete (window as any)._SSR_DATA;
|
|
321
|
+
} else {
|
|
322
|
+
(window as any)._SSR_DATA = previousSsrData;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
273
328
|
describe('framework Link', () => {
|
|
274
329
|
let rendered: { container: HTMLElement; root: Root } | undefined;
|
|
275
330
|
|
|
@@ -328,6 +383,26 @@ describe('framework Link', () => {
|
|
|
328
383
|
expect(props.hash).toBe('list');
|
|
329
384
|
});
|
|
330
385
|
|
|
386
|
+
test('preserves array search values natively', async () => {
|
|
387
|
+
const router = createTanstackRouter('/en/products', 'en');
|
|
388
|
+
rendered = await renderWithRuntime(
|
|
389
|
+
<ModernI18nProvider value={providerValue('en')}>
|
|
390
|
+
<Link
|
|
391
|
+
to="/products"
|
|
392
|
+
search={{ tag: ['boots', 'sale'], page: 2 }}
|
|
393
|
+
data-testid="q"
|
|
394
|
+
>
|
|
395
|
+
Products
|
|
396
|
+
</Link>
|
|
397
|
+
</ModernI18nProvider>,
|
|
398
|
+
createTanstackRuntimeContext(router),
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const props = capturedLinkProps.at(-1);
|
|
402
|
+
expect(props.to).toBe('/en/products');
|
|
403
|
+
expect(props.search).toEqual({ tag: ['boots', 'sale'], page: '2' });
|
|
404
|
+
});
|
|
405
|
+
|
|
331
406
|
test('renders a plain anchor for external targets', async () => {
|
|
332
407
|
const router = createTanstackRouter('/en', 'en');
|
|
333
408
|
rendered = await renderWithRuntime(
|
|
@@ -381,6 +456,42 @@ describe('framework Link', () => {
|
|
|
381
456
|
expect(link?.hasAttribute('prefetch')).toBe(false);
|
|
382
457
|
});
|
|
383
458
|
|
|
459
|
+
test('serializes array search values for fallback anchors', async () => {
|
|
460
|
+
rendered = await renderWithRuntime(
|
|
461
|
+
<ModernI18nProvider value={providerValue('en')}>
|
|
462
|
+
<Link
|
|
463
|
+
to="/products"
|
|
464
|
+
search={{ tag: ['boots', 'sale'], page: 2 }}
|
|
465
|
+
data-testid="q"
|
|
466
|
+
>
|
|
467
|
+
Products
|
|
468
|
+
</Link>
|
|
469
|
+
</ModernI18nProvider>,
|
|
470
|
+
{ isBrowser: true, requestContext, context: requestContext } as any,
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const link = rendered.container.querySelector('[data-testid="q"]');
|
|
474
|
+
expect(link?.getAttribute('href')).toBe(
|
|
475
|
+
'/en/products?tag=boots&tag=sale&page=2',
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('empty search prop clears query from target fallback anchors', async () => {
|
|
480
|
+
rendered = await renderWithRuntime(
|
|
481
|
+
<ModernI18nProvider value={providerValue('en')}>
|
|
482
|
+
<Link to="/products?tag=x" search="" data-testid="clear-query">
|
|
483
|
+
Products
|
|
484
|
+
</Link>
|
|
485
|
+
</ModernI18nProvider>,
|
|
486
|
+
{ isBrowser: true, requestContext, context: requestContext } as any,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const link = rendered.container.querySelector(
|
|
490
|
+
'[data-testid="clear-query"]',
|
|
491
|
+
);
|
|
492
|
+
expect(link?.getAttribute('href')).toBe('/en/products');
|
|
493
|
+
});
|
|
494
|
+
|
|
384
495
|
test('maps prefetch to the TanStack preload prop', async () => {
|
|
385
496
|
const router = createTanstackRouter('/en/products', 'en');
|
|
386
497
|
rendered = await renderWithRuntime(
|
package/tests/linkTypes.test.ts
CHANGED
|
@@ -3,10 +3,21 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
const fixtureDir = path.resolve(__dirname, 'type-fixture');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
)
|
|
6
|
+
function resolveTsgoBin() {
|
|
7
|
+
const pkgPath = require.resolve('@typescript/native-preview/package.json');
|
|
8
|
+
const pkgDir = path.dirname(pkgPath);
|
|
9
|
+
const pkg = require(pkgPath) as {
|
|
10
|
+
bin?:
|
|
11
|
+
| string
|
|
12
|
+
| {
|
|
13
|
+
tsgo?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const binEntry = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.tsgo;
|
|
17
|
+
return path.resolve(pkgDir, binEntry ?? 'bin/tsgo.js');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tsgoBin = resolveTsgoBin();
|
|
10
21
|
|
|
11
22
|
describe('Link type-level tests', () => {
|
|
12
23
|
test('fixture type-checks correctly: valid uses compile, invalid uses are rejected', () => {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, expect, test } from '@rstest/core';
|
|
2
|
+
import {
|
|
3
|
+
interpolateRouteParams,
|
|
4
|
+
normalizeSearch,
|
|
5
|
+
} from '../src/runtime/linkHelpers';
|
|
6
|
+
import { canonicalPath, localizePath } from '../src/runtime/localizedPaths';
|
|
7
|
+
import { buildLocalizedUrl } from '../src/runtime/utils';
|
|
8
|
+
import type { LocalisedUrlsMap } from '../src/shared/localisedUrls';
|
|
9
|
+
|
|
10
|
+
const languages = ['en', 'cs'];
|
|
11
|
+
|
|
12
|
+
const localisedUrls = {
|
|
13
|
+
'/products/:slug': {
|
|
14
|
+
en: '/products/:slug',
|
|
15
|
+
cs: '/produkty/:slug',
|
|
16
|
+
},
|
|
17
|
+
'/files/*': {
|
|
18
|
+
en: '/files/*',
|
|
19
|
+
cs: '/soubory/*',
|
|
20
|
+
},
|
|
21
|
+
'/app/products/:slug': {
|
|
22
|
+
en: '/app/products/:slug',
|
|
23
|
+
cs: '/app/produkty/:slug',
|
|
24
|
+
},
|
|
25
|
+
} satisfies LocalisedUrlsMap;
|
|
26
|
+
|
|
27
|
+
const pathsConfig = {
|
|
28
|
+
languages,
|
|
29
|
+
localisedUrls,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('fork-owned localised URL rewrite matrix', () => {
|
|
33
|
+
const rewriteScenarios = [
|
|
34
|
+
{
|
|
35
|
+
name: 'adds the target locale prefix and localises canonical segments',
|
|
36
|
+
target: '/products/red-shoe',
|
|
37
|
+
language: 'cs',
|
|
38
|
+
expected: '/cs/produkty/red-shoe',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'strips a case-insensitive source locale before relocalising',
|
|
42
|
+
target: '/CS/produkty/red%20shoe?tag=boots#details',
|
|
43
|
+
language: 'en',
|
|
44
|
+
expected: '/en/products/red%20shoe?tag=boots#details',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'preserves repeated query keys and the fragment',
|
|
48
|
+
target: '/products/red-shoe?tag=boots&tag=sale#details',
|
|
49
|
+
language: 'cs',
|
|
50
|
+
expected: '/cs/produkty/red-shoe?tag=boots&tag=sale#details',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'keeps a base-path segment inside the localised route map',
|
|
54
|
+
target: '/app/products/red-shoe?ref=nav#details',
|
|
55
|
+
language: 'cs',
|
|
56
|
+
expected: '/cs/app/produkty/red-shoe?ref=nav#details',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'keeps the default locale explicitly prefixed',
|
|
60
|
+
target: '/products/red-shoe',
|
|
61
|
+
language: 'en',
|
|
62
|
+
expected: '/en/products/red-shoe',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'does not double-prefix an already-localised URL',
|
|
66
|
+
target: '/cs/produkty/red-shoe?tag=boots#details',
|
|
67
|
+
language: 'cs',
|
|
68
|
+
expected: '/cs/produkty/red-shoe?tag=boots#details',
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const scenario of rewriteScenarios) {
|
|
73
|
+
test(scenario.name, () => {
|
|
74
|
+
expect(
|
|
75
|
+
buildLocalizedUrl(
|
|
76
|
+
scenario.target,
|
|
77
|
+
scenario.language,
|
|
78
|
+
languages,
|
|
79
|
+
localisedUrls,
|
|
80
|
+
),
|
|
81
|
+
).toBe(scenario.expected);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test('canonical and localized path helpers strip and add prefixes exactly', () => {
|
|
86
|
+
expect(
|
|
87
|
+
canonicalPath('/CS/produkty/red-shoe?tag=boots#details', pathsConfig),
|
|
88
|
+
).toBe('/products/red-shoe?tag=boots#details');
|
|
89
|
+
expect(
|
|
90
|
+
localizePath('/products/red-shoe?tag=boots#details', 'cs', pathsConfig),
|
|
91
|
+
).toBe('/cs/produkty/red-shoe?tag=boots#details');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('splat params preserve separators and percent-encode each segment', () => {
|
|
95
|
+
const target = interpolateRouteParams('/files/*', {
|
|
96
|
+
'*': 'resume drafts/Q1 deck.pdf',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(target).toBe('/files/resume%20drafts/Q1%20deck.pdf');
|
|
100
|
+
expect(buildLocalizedUrl(target, 'cs', languages, localisedUrls)).toBe(
|
|
101
|
+
'/cs/soubory/resume%20drafts/Q1%20deck.pdf',
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const searchScenarios: Array<{
|
|
106
|
+
name: string;
|
|
107
|
+
search: Parameters<typeof normalizeSearch>[0];
|
|
108
|
+
searchFromTo: string;
|
|
109
|
+
expected: ReturnType<typeof normalizeSearch>;
|
|
110
|
+
}> = [
|
|
111
|
+
{
|
|
112
|
+
name: 'object array values are preserved',
|
|
113
|
+
search: { tag: ['boots', 'sale'], page: 2 },
|
|
114
|
+
searchFromTo: '?ignored=1',
|
|
115
|
+
expected: {
|
|
116
|
+
searchString: '?tag=boots&tag=sale&page=2',
|
|
117
|
+
searchObject: { tag: ['boots', 'sale'], page: '2' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'target query arrays are preserved',
|
|
122
|
+
search: undefined,
|
|
123
|
+
searchFromTo: '?tag=boots&tag=sale&page=2',
|
|
124
|
+
expected: {
|
|
125
|
+
searchString: '?tag=boots&tag=sale&page=2',
|
|
126
|
+
searchObject: { tag: ['boots', 'sale'], page: '2' },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'empty search clears the target query',
|
|
131
|
+
search: '',
|
|
132
|
+
searchFromTo: '?tag=boots&tag=sale',
|
|
133
|
+
expected: {
|
|
134
|
+
searchString: '',
|
|
135
|
+
searchObject: undefined,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
for (const scenario of searchScenarios) {
|
|
141
|
+
test(`normalizes search: ${scenario.name}`, () => {
|
|
142
|
+
expect(normalizeSearch(scenario.search, scenario.searchFromTo)).toEqual(
|
|
143
|
+
scenario.expected,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import type { NestedRouteForCli } from '@modern-js/types';
|
|
2
4
|
import { describe, expect, test } from '@rstest/core';
|
|
3
5
|
import { i18nPlugin as i18nCliPlugin } from '../src/cli';
|
|
@@ -17,6 +19,12 @@ import {
|
|
|
17
19
|
validateLocalisedUrls,
|
|
18
20
|
} from '../src/shared/localisedUrls';
|
|
19
21
|
|
|
22
|
+
const localisedRoutesGoldenPath = path.join(
|
|
23
|
+
__dirname,
|
|
24
|
+
'fixtures',
|
|
25
|
+
'localised-routes.golden.json',
|
|
26
|
+
);
|
|
27
|
+
|
|
20
28
|
const createRoute = (
|
|
21
29
|
path: string,
|
|
22
30
|
children?: NestedRouteForCli[],
|
|
@@ -37,6 +45,9 @@ const createRequestContext = (pathname: string) =>
|
|
|
37
45
|
},
|
|
38
46
|
}) as any;
|
|
39
47
|
|
|
48
|
+
const serializeRoutesForGolden = (routes: unknown): string =>
|
|
49
|
+
`${JSON.stringify(routes, null, 2)}\n`;
|
|
50
|
+
|
|
40
51
|
describe('resolveLocalisedUrlsConfig', () => {
|
|
41
52
|
test('is opt-in: only a non-empty map enables the feature', () => {
|
|
42
53
|
const map = { '/about': { en: '/about', cs: '/o-nas' } };
|
|
@@ -108,6 +119,70 @@ describe('cli modifyFileSystemRoutes', () => {
|
|
|
108
119
|
return modifyRoutes!;
|
|
109
120
|
};
|
|
110
121
|
|
|
122
|
+
const generateLocalisedRoutesGolden = (): string => {
|
|
123
|
+
const modifyRoutes = setupModifyRoutes({
|
|
124
|
+
localePathRedirect: true,
|
|
125
|
+
languages: ['en', 'cs', 'de'],
|
|
126
|
+
localisedUrls: {
|
|
127
|
+
'/about': {
|
|
128
|
+
en: '/about',
|
|
129
|
+
cs: '/o-nas',
|
|
130
|
+
de: '/ueber-uns',
|
|
131
|
+
},
|
|
132
|
+
'/products': {
|
|
133
|
+
en: '/products',
|
|
134
|
+
cs: '/produkty',
|
|
135
|
+
de: '/produkte',
|
|
136
|
+
},
|
|
137
|
+
'/products/:slug': {
|
|
138
|
+
en: '/products/:slug',
|
|
139
|
+
cs: '/produkty/:slug',
|
|
140
|
+
de: '/produkte/:slug',
|
|
141
|
+
},
|
|
142
|
+
'/docs': {
|
|
143
|
+
en: '/docs',
|
|
144
|
+
cs: '/dokumenty',
|
|
145
|
+
de: '/dokumente',
|
|
146
|
+
},
|
|
147
|
+
'/docs/*': {
|
|
148
|
+
en: '/docs/*',
|
|
149
|
+
cs: '/dokumenty/*',
|
|
150
|
+
de: '/dokumente/*',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const routes = [
|
|
155
|
+
createRoute(':lang', [
|
|
156
|
+
createRoute('about'),
|
|
157
|
+
createRoute('products', [createRoute(':slug')]),
|
|
158
|
+
createRoute('docs', [createRoute('*')]),
|
|
159
|
+
]),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const result = modifyRoutes({
|
|
163
|
+
entrypoint: { entryName: 'main' },
|
|
164
|
+
routes,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return serializeRoutesForGolden(result.routes);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
test('matches checked-in localised routes golden output', async () => {
|
|
171
|
+
const localisedRoutes = generateLocalisedRoutesGolden();
|
|
172
|
+
const repeatedLocalisedRoutes = generateLocalisedRoutesGolden();
|
|
173
|
+
|
|
174
|
+
expect(repeatedLocalisedRoutes).toBe(localisedRoutes);
|
|
175
|
+
|
|
176
|
+
if (process.env.UPDATE_I18N_LOCALISED_ROUTES_GOLDEN === '1') {
|
|
177
|
+
await mkdir(path.dirname(localisedRoutesGoldenPath), { recursive: true });
|
|
178
|
+
await writeFile(localisedRoutesGoldenPath, localisedRoutes);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await expect(readFile(localisedRoutesGoldenPath, 'utf8')).resolves.toBe(
|
|
182
|
+
localisedRoutes,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
111
186
|
test('upstream-style configs without a map keep routes untouched', () => {
|
|
112
187
|
const modifyRoutes = setupModifyRoutes({
|
|
113
188
|
localePathRedirect: true,
|
|
@@ -272,6 +347,31 @@ describe('localisedUrls', () => {
|
|
|
272
347
|
).toBe('/products/cervena-bota');
|
|
273
348
|
});
|
|
274
349
|
|
|
350
|
+
test('encodes wildcard params while preserving path separators', () => {
|
|
351
|
+
const localisedUrls = {
|
|
352
|
+
'/docs/*': {
|
|
353
|
+
en: '/docs/*',
|
|
354
|
+
cs: '/dokumenty/*',
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
expect(
|
|
359
|
+
resolveLocalisedPath(
|
|
360
|
+
'/docs/a%20b/c%23d',
|
|
361
|
+
'cs',
|
|
362
|
+
['en', 'cs'],
|
|
363
|
+
localisedUrls,
|
|
364
|
+
),
|
|
365
|
+
).toBe('/dokumenty/a%20b/c%23d');
|
|
366
|
+
expect(
|
|
367
|
+
resolveCanonicalLocalisedPath(
|
|
368
|
+
'/dokumenty/a%20b/c%23d',
|
|
369
|
+
['en', 'cs'],
|
|
370
|
+
localisedUrls,
|
|
371
|
+
),
|
|
372
|
+
).toBe('/docs/a%20b/c%23d');
|
|
373
|
+
});
|
|
374
|
+
|
|
275
375
|
test('resolves static patterns before param patterns', () => {
|
|
276
376
|
const localisedUrls = {
|
|
277
377
|
'/products/:slug': {
|
|
@@ -321,6 +421,31 @@ describe('localisedUrls', () => {
|
|
|
321
421
|
).toBe('/products/cervena-bota');
|
|
322
422
|
});
|
|
323
423
|
|
|
424
|
+
test('strips case-insensitive locale prefixes before relocalising full pathnames', () => {
|
|
425
|
+
const localisedUrls = {
|
|
426
|
+
'/products/:slug': {
|
|
427
|
+
en: '/products/:slug',
|
|
428
|
+
cs: '/produkty/:slug',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
expect(
|
|
433
|
+
localiseTargetPathname(
|
|
434
|
+
'/CS/produkty/cervena-bota',
|
|
435
|
+
'en',
|
|
436
|
+
['en', 'cs'],
|
|
437
|
+
localisedUrls,
|
|
438
|
+
),
|
|
439
|
+
).toBe('/en/products/cervena-bota');
|
|
440
|
+
expect(
|
|
441
|
+
canonicalTargetPathname(
|
|
442
|
+
'/CS/produkty/cervena-bota',
|
|
443
|
+
['en', 'cs'],
|
|
444
|
+
localisedUrls,
|
|
445
|
+
),
|
|
446
|
+
).toBe('/products/cervena-bota');
|
|
447
|
+
});
|
|
448
|
+
|
|
324
449
|
test('resolves nested optional route params with translated ancestors', () => {
|
|
325
450
|
const localisedUrls = {
|
|
326
451
|
'/checkout': {
|
|
@@ -509,7 +634,6 @@ describe('i18n server API prefix skips', () => {
|
|
|
509
634
|
header: () => ({ host: 'localhost' }),
|
|
510
635
|
},
|
|
511
636
|
get: () => null,
|
|
512
|
-
redirect: (url: string) => ({ redirectedTo: url }),
|
|
513
637
|
}) as any;
|
|
514
638
|
|
|
515
639
|
// Sanity: well-formed non-canonical slugs still redirect.
|
|
@@ -517,7 +641,10 @@ describe('i18n server API prefix skips', () => {
|
|
|
517
641
|
createContext('/cs/products/bota'),
|
|
518
642
|
async () => {},
|
|
519
643
|
);
|
|
520
|
-
expect(redirected).
|
|
644
|
+
expect(redirected.status).toBe(302);
|
|
645
|
+
expect(redirected.headers.get('location')).toBe('/cs/produkty/bota');
|
|
646
|
+
expect(redirected.headers.get('cache-control')).toBe('private, no-store');
|
|
647
|
+
expect(redirected.headers.get('vary')).toBe('Accept-Language, Cookie');
|
|
521
648
|
|
|
522
649
|
// Malformed encoding must fall through to next() instead of throwing.
|
|
523
650
|
let nextCalls = 0;
|
|
@@ -7,7 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
7
7
|
const runtimeRoot = resolve(__dirname, '../src/runtime');
|
|
8
8
|
|
|
9
9
|
const readRuntimeSource = (file: string) =>
|
|
10
|
-
readFileSync(resolve(runtimeRoot, file), 'utf8');
|
|
10
|
+
readFileSync(resolve(runtimeRoot, file), 'utf8').replace(/\r\n/gu, '\n');
|
|
11
11
|
|
|
12
12
|
describe('react-i18next runtime boundary', () => {
|
|
13
13
|
test('keeps the disabled runtime entry free of the optional adapter edge', () => {
|
|
@@ -21,11 +21,14 @@ describe('react-i18next runtime boundary', () => {
|
|
|
21
21
|
|
|
22
22
|
test('keeps the runtime plugin factory entry synchronous for federation', () => {
|
|
23
23
|
const core = readRuntimeSource('core.tsx');
|
|
24
|
+
const pluginSetup = readRuntimeSource('pluginSetup.ts');
|
|
24
25
|
|
|
25
26
|
expect(core).not.toContain("from './i18n/backend/middleware'");
|
|
26
27
|
expect(core).not.toContain("from './i18n/utils'");
|
|
27
|
-
expect(
|
|
28
|
-
expect(
|
|
28
|
+
expect(pluginSetup).not.toContain("from './i18n/backend/middleware'");
|
|
29
|
+
expect(pluginSetup).not.toContain("from './i18n/utils'");
|
|
30
|
+
expect(pluginSetup).toContain("import('./i18n/backend/middleware')");
|
|
31
|
+
expect(pluginSetup).toContain("import('./i18n/utils')");
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
test('keeps the Modern i18n context stable across federated runtime copies', () => {
|