@bleedingdev/modern-js-plugin-i18n 3.5.0-ultramodern.2 → 3.5.0-ultramodern.20
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/server/index.js +10 -2
- package/dist/esm/server/index.mjs +10 -2
- package/dist/esm-node/server/index.mjs +10 -2
- package/dist/types/shared/localisedUrls.d.ts +9 -3
- package/package.json +10 -10
- package/src/server/index.ts +12 -2
- package/src/shared/localisedUrls.ts +18 -12
- package/tests/linkTypes.test.ts +15 -4
- package/tests/localisedUrls.test.ts +4 -2
package/dist/cjs/server/index.js
CHANGED
|
@@ -167,6 +167,14 @@ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
|
|
|
167
167
|
const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
|
|
168
168
|
return localizedUrl;
|
|
169
169
|
};
|
|
170
|
+
const createLocaleRedirectResponse = (location)=>new Response(null, {
|
|
171
|
+
status: 302,
|
|
172
|
+
headers: {
|
|
173
|
+
'Cache-Control': 'private, no-store',
|
|
174
|
+
Location: location,
|
|
175
|
+
Vary: 'Accept-Language, Cookie'
|
|
176
|
+
}
|
|
177
|
+
});
|
|
170
178
|
const i18nServerPlugin = (options)=>({
|
|
171
179
|
name: '@modern-js/plugin-i18n/server',
|
|
172
180
|
setup: (api)=>{
|
|
@@ -235,12 +243,12 @@ const i18nServerPlugin = (options)=>({
|
|
|
235
243
|
if (i18nextDetector) detectedLanguage = c.get('language') || null;
|
|
236
244
|
const targetLanguage = detectedLanguage || fallbackLanguage;
|
|
237
245
|
const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
|
|
238
|
-
return
|
|
246
|
+
return createLocaleRedirectResponse(localizedUrl);
|
|
239
247
|
}
|
|
240
248
|
const localisedUrlsConfig = (0, localisedUrls_js_namespaceObject.resolveLocalisedUrlsConfig)(localisedUrls);
|
|
241
249
|
if (localisedUrlsConfig.enabled) {
|
|
242
250
|
const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
|
|
243
|
-
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return
|
|
251
|
+
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return createLocaleRedirectResponse(expectedUrl);
|
|
244
252
|
}
|
|
245
253
|
await next();
|
|
246
254
|
}
|
|
@@ -132,6 +132,14 @@ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
|
|
|
132
132
|
const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
|
|
133
133
|
return localizedUrl;
|
|
134
134
|
};
|
|
135
|
+
const createLocaleRedirectResponse = (location)=>new Response(null, {
|
|
136
|
+
status: 302,
|
|
137
|
+
headers: {
|
|
138
|
+
'Cache-Control': 'private, no-store',
|
|
139
|
+
Location: location,
|
|
140
|
+
Vary: 'Accept-Language, Cookie'
|
|
141
|
+
}
|
|
142
|
+
});
|
|
135
143
|
const i18nServerPlugin = (options)=>({
|
|
136
144
|
name: '@modern-js/plugin-i18n/server',
|
|
137
145
|
setup: (api)=>{
|
|
@@ -200,12 +208,12 @@ const i18nServerPlugin = (options)=>({
|
|
|
200
208
|
if (i18nextDetector) detectedLanguage = c.get('language') || null;
|
|
201
209
|
const targetLanguage = detectedLanguage || fallbackLanguage;
|
|
202
210
|
const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
|
|
203
|
-
return
|
|
211
|
+
return createLocaleRedirectResponse(localizedUrl);
|
|
204
212
|
}
|
|
205
213
|
const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
|
|
206
214
|
if (localisedUrlsConfig.enabled) {
|
|
207
215
|
const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
|
|
208
|
-
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return
|
|
216
|
+
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return createLocaleRedirectResponse(expectedUrl);
|
|
209
217
|
}
|
|
210
218
|
await next();
|
|
211
219
|
}
|
|
@@ -133,6 +133,14 @@ const buildLocalizedUrl = (req, urlPath, language, languages, localisedUrls)=>{
|
|
|
133
133
|
const localizedUrl = '/' === basePath ? newPathname + suffix : basePath + newPathname + suffix;
|
|
134
134
|
return localizedUrl;
|
|
135
135
|
};
|
|
136
|
+
const createLocaleRedirectResponse = (location)=>new Response(null, {
|
|
137
|
+
status: 302,
|
|
138
|
+
headers: {
|
|
139
|
+
'Cache-Control': 'private, no-store',
|
|
140
|
+
Location: location,
|
|
141
|
+
Vary: 'Accept-Language, Cookie'
|
|
142
|
+
}
|
|
143
|
+
});
|
|
136
144
|
const i18nServerPlugin = (options)=>({
|
|
137
145
|
name: '@modern-js/plugin-i18n/server',
|
|
138
146
|
setup: (api)=>{
|
|
@@ -201,12 +209,12 @@ const i18nServerPlugin = (options)=>({
|
|
|
201
209
|
if (i18nextDetector) detectedLanguage = c.get('language') || null;
|
|
202
210
|
const targetLanguage = detectedLanguage || fallbackLanguage;
|
|
203
211
|
const localizedUrl = buildLocalizedUrl(c.req, originUrlPath, targetLanguage, languages, localisedUrls);
|
|
204
|
-
return
|
|
212
|
+
return createLocaleRedirectResponse(localizedUrl);
|
|
205
213
|
}
|
|
206
214
|
const localisedUrlsConfig = resolveLocalisedUrlsConfig(localisedUrls);
|
|
207
215
|
if (localisedUrlsConfig.enabled) {
|
|
208
216
|
const expectedUrl = buildLocalizedUrl(c.req, originUrlPath, language, languages, localisedUrls);
|
|
209
|
-
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return
|
|
217
|
+
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) return createLocaleRedirectResponse(expectedUrl);
|
|
210
218
|
}
|
|
211
219
|
await next();
|
|
212
220
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
export type LocalisedRoute = {
|
|
2
|
+
type: 'nested' | 'page';
|
|
3
|
+
path?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
children?: LocalisedRoute[];
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
2
8
|
export type LocalisedUrlPathMap = Record<string, string>;
|
|
3
9
|
export type LocalisedUrlsMap = Record<string, LocalisedUrlPathMap>;
|
|
4
10
|
export type LocalisedUrlsOption = boolean | LocalisedUrlsMap;
|
|
@@ -21,8 +27,8 @@ export declare const normalisePathname: (pathname: string) => string;
|
|
|
21
27
|
* failing the build for every route missing from a map they never wrote.
|
|
22
28
|
*/
|
|
23
29
|
export declare const resolveLocalisedUrlsConfig: (option: LocalisedUrlsOption | undefined) => ResolvedLocalisedUrlsConfig;
|
|
24
|
-
export declare const validateLocalisedUrls: (routes:
|
|
25
|
-
export declare const applyLocalisedUrlsToRoutes: (routes:
|
|
30
|
+
export declare const validateLocalisedUrls: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => void;
|
|
31
|
+
export declare const applyLocalisedUrlsToRoutes: (routes: LocalisedRoute[], languages: string[], localisedUrls: LocalisedUrlsMap) => LocalisedRoute[];
|
|
26
32
|
export declare const matchPathPattern: (pathname: string, pattern: string) => Record<string, string> | null;
|
|
27
33
|
export declare const buildPathFromPattern: (pattern: string, params: Record<string, string>) => string;
|
|
28
34
|
export declare const resolveLocalisedPath: (pathname: string, targetLanguage: string, languages: string[], localisedUrls: LocalisedUrlsMap) => string;
|
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.20",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -97,15 +97,15 @@
|
|
|
97
97
|
"i18next-fs-backend": "^2.6.6",
|
|
98
98
|
"i18next-http-backend": "^4.0.0",
|
|
99
99
|
"i18next-http-middleware": "^3.9.7",
|
|
100
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.
|
|
101
|
-
"@modern-js/
|
|
102
|
-
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.
|
|
103
|
-
"@modern-js/
|
|
104
|
-
"@modern-js/
|
|
105
|
-
"@modern-js/
|
|
100
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.20",
|
|
101
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.20",
|
|
102
|
+
"@modern-js/server-runtime": "npm:@bleedingdev/modern-js-server-runtime@3.5.0-ultramodern.20",
|
|
103
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.5.0-ultramodern.20",
|
|
104
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.20",
|
|
105
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.20"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
108
|
+
"@modern-js/runtime": "3.5.0-ultramodern.20",
|
|
109
109
|
"i18next": ">=26.3.1",
|
|
110
110
|
"react": "^19.2.7",
|
|
111
111
|
"react-dom": "^19.2.7",
|
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
"react-i18next": "17.0.8",
|
|
130
130
|
"ts-node": "^10.9.2",
|
|
131
131
|
"typescript": "^6.0.3",
|
|
132
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.
|
|
133
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.
|
|
132
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.20",
|
|
133
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.20"
|
|
134
134
|
},
|
|
135
135
|
"sideEffects": false,
|
|
136
136
|
"publishConfig": {
|
package/src/server/index.ts
CHANGED
|
@@ -328,6 +328,16 @@ const buildLocalizedUrl = (
|
|
|
328
328
|
return localizedUrl;
|
|
329
329
|
};
|
|
330
330
|
|
|
331
|
+
const createLocaleRedirectResponse = (location: string): Response =>
|
|
332
|
+
new Response(null, {
|
|
333
|
+
status: 302,
|
|
334
|
+
headers: {
|
|
335
|
+
'Cache-Control': 'private, no-store',
|
|
336
|
+
Location: location,
|
|
337
|
+
Vary: 'Accept-Language, Cookie',
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
331
341
|
export const i18nServerPlugin = (options: I18nPluginOptions): ServerPlugin => ({
|
|
332
342
|
name: '@modern-js/plugin-i18n/server',
|
|
333
343
|
setup: api => {
|
|
@@ -475,7 +485,7 @@ export const i18nServerPlugin = (options: I18nPluginOptions): ServerPlugin => ({
|
|
|
475
485
|
languages,
|
|
476
486
|
localisedUrls,
|
|
477
487
|
);
|
|
478
|
-
return
|
|
488
|
+
return createLocaleRedirectResponse(localizedUrl);
|
|
479
489
|
}
|
|
480
490
|
const localisedUrlsConfig =
|
|
481
491
|
resolveLocalisedUrlsConfig(localisedUrls);
|
|
@@ -488,7 +498,7 @@ export const i18nServerPlugin = (options: I18nPluginOptions): ServerPlugin => ({
|
|
|
488
498
|
localisedUrls,
|
|
489
499
|
);
|
|
490
500
|
if (expectedUrl !== `${pathname}${url.search}${url.hash}`) {
|
|
491
|
-
return
|
|
501
|
+
return createLocaleRedirectResponse(expectedUrl);
|
|
492
502
|
}
|
|
493
503
|
}
|
|
494
504
|
await next();
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
export type LocalisedRoute = {
|
|
2
|
+
type: 'nested' | 'page';
|
|
3
|
+
path?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
children?: LocalisedRoute[];
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
2
8
|
|
|
3
9
|
export type LocalisedUrlPathMap = Record<string, string>;
|
|
4
10
|
export type LocalisedUrlsMap = Record<string, LocalisedUrlPathMap>;
|
|
@@ -144,11 +150,11 @@ const ensureLocalisedUrlsForPath = (
|
|
|
144
150
|
};
|
|
145
151
|
|
|
146
152
|
export const validateLocalisedUrls = (
|
|
147
|
-
routes:
|
|
153
|
+
routes: LocalisedRoute[],
|
|
148
154
|
languages: string[],
|
|
149
155
|
localisedUrls: LocalisedUrlsMap,
|
|
150
156
|
) => {
|
|
151
|
-
const visit = (route:
|
|
157
|
+
const visit = (route: LocalisedRoute, parentPath: string) => {
|
|
152
158
|
const canonicalPath = joinPath(parentPath, route.path);
|
|
153
159
|
if (isLocalisableRoutePath(route.path)) {
|
|
154
160
|
ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls);
|
|
@@ -191,12 +197,12 @@ const getLocalisedRoutePaths = (
|
|
|
191
197
|
};
|
|
192
198
|
|
|
193
199
|
const transformLocalisedRoute = (
|
|
194
|
-
route:
|
|
200
|
+
route: LocalisedRoute,
|
|
195
201
|
parentCanonicalPath: string,
|
|
196
202
|
parentLocalisedPaths: Record<string, string>,
|
|
197
203
|
languages: string[],
|
|
198
204
|
localisedUrls: LocalisedUrlsMap,
|
|
199
|
-
):
|
|
205
|
+
): LocalisedRoute[] => {
|
|
200
206
|
const canonicalPath = joinPath(parentCanonicalPath, route.path);
|
|
201
207
|
const localisedUrlEntry = isLocalisableRoutePath(route.path)
|
|
202
208
|
? ensureLocalisedUrlsForPath(canonicalPath, languages, localisedUrls)
|
|
@@ -224,7 +230,7 @@ const transformLocalisedRoute = (
|
|
|
224
230
|
const baseRoute = {
|
|
225
231
|
...route,
|
|
226
232
|
...(children ? { children } : {}),
|
|
227
|
-
} as
|
|
233
|
+
} as LocalisedRoute;
|
|
228
234
|
|
|
229
235
|
if (!localisedUrlEntry) {
|
|
230
236
|
return [baseRoute];
|
|
@@ -243,7 +249,7 @@ const transformLocalisedRoute = (
|
|
|
243
249
|
const legalRouteIdPart = (value: string): string =>
|
|
244
250
|
value.replace(/[^a-zA-Z0-9_$-]+/g, '_').replace(/^_+|_+$/g, '') || 'index';
|
|
245
251
|
|
|
246
|
-
const suffixRouteIds = <T extends
|
|
252
|
+
const suffixRouteIds = <T extends LocalisedRoute>(
|
|
247
253
|
route: T,
|
|
248
254
|
suffix: string,
|
|
249
255
|
): T => {
|
|
@@ -260,11 +266,11 @@ const suffixRouteIds = <T extends NestedRouteForCli | PageRoute>(
|
|
|
260
266
|
};
|
|
261
267
|
|
|
262
268
|
const cloneRouteWithLocalisedPath = (
|
|
263
|
-
route:
|
|
269
|
+
route: LocalisedRoute,
|
|
264
270
|
path: string,
|
|
265
271
|
index: number,
|
|
266
272
|
canonicalPath: string,
|
|
267
|
-
):
|
|
273
|
+
): LocalisedRoute => {
|
|
268
274
|
const leadingLocaleParam = getLeadingLocaleParam(route.path);
|
|
269
275
|
const localisedPath = leadingLocaleParam
|
|
270
276
|
? normaliseRoutePath(`${leadingLocaleParam}/${path}`)
|
|
@@ -272,7 +278,7 @@ const cloneRouteWithLocalisedPath = (
|
|
|
272
278
|
const routeWithPath = {
|
|
273
279
|
...route,
|
|
274
280
|
path: localisedPath,
|
|
275
|
-
} as
|
|
281
|
+
} as LocalisedRoute;
|
|
276
282
|
// Language-agnostic source pattern; lets downstream codegen collapse the
|
|
277
283
|
// localized physical variants back to one canonical route.
|
|
278
284
|
(routeWithPath as { modernCanonicalPath?: string }).modernCanonicalPath =
|
|
@@ -284,10 +290,10 @@ const cloneRouteWithLocalisedPath = (
|
|
|
284
290
|
};
|
|
285
291
|
|
|
286
292
|
export const applyLocalisedUrlsToRoutes = (
|
|
287
|
-
routes:
|
|
293
|
+
routes: LocalisedRoute[],
|
|
288
294
|
languages: string[],
|
|
289
295
|
localisedUrls: LocalisedUrlsMap,
|
|
290
|
-
):
|
|
296
|
+
): LocalisedRoute[] => {
|
|
291
297
|
const rootLocalisedPaths = languages.reduce<Record<string, string>>(
|
|
292
298
|
(acc, language) => {
|
|
293
299
|
acc[language] = '/';
|
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', () => {
|
|
@@ -509,7 +509,6 @@ describe('i18n server API prefix skips', () => {
|
|
|
509
509
|
header: () => ({ host: 'localhost' }),
|
|
510
510
|
},
|
|
511
511
|
get: () => null,
|
|
512
|
-
redirect: (url: string) => ({ redirectedTo: url }),
|
|
513
512
|
}) as any;
|
|
514
513
|
|
|
515
514
|
// Sanity: well-formed non-canonical slugs still redirect.
|
|
@@ -517,7 +516,10 @@ describe('i18n server API prefix skips', () => {
|
|
|
517
516
|
createContext('/cs/products/bota'),
|
|
518
517
|
async () => {},
|
|
519
518
|
);
|
|
520
|
-
expect(redirected).
|
|
519
|
+
expect(redirected.status).toBe(302);
|
|
520
|
+
expect(redirected.headers.get('location')).toBe('/cs/produkty/bota');
|
|
521
|
+
expect(redirected.headers.get('cache-control')).toBe('private, no-store');
|
|
522
|
+
expect(redirected.headers.get('vary')).toBe('Accept-Language, Cookie');
|
|
521
523
|
|
|
522
524
|
// Malformed encoding must fall through to next() instead of throwing.
|
|
523
525
|
let nextCalls = 0;
|