@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.6 → 3.9.0-ultramodern.8

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/index.js CHANGED
@@ -9,7 +9,7 @@ var __webpack_modules__ = {
9
9
  "./localeRedirectPolicy" (module) {
10
10
  module.exports = require("./localeRedirectPolicy.js");
11
11
  },
12
- "./localisedUrls" (module) {
12
+ "./localisedUrls/index" (module) {
13
13
  module.exports = require("./localisedUrls/index.js");
14
14
  }
15
15
  };
@@ -72,7 +72,7 @@ var __webpack_exports__ = {};
72
72
  var __rspack_reexport = {};
73
73
  for(const __rspack_import_key in _localeRedirectPolicy__rspack_import_2)if ("default" !== __rspack_import_key) __rspack_reexport[__rspack_import_key] = ()=>_localeRedirectPolicy__rspack_import_2[__rspack_import_key];
74
74
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
75
- var _localisedUrls_index__rspack_import_3 = __webpack_require__("./localisedUrls");
75
+ var _localisedUrls_index__rspack_import_3 = __webpack_require__("./localisedUrls/index");
76
76
  var __rspack_reexport = {};
77
77
  for(const __rspack_import_key in _localisedUrls_index__rspack_import_3)if ("default" !== __rspack_import_key) __rspack_reexport[__rspack_import_key] = ()=>_localisedUrls_index__rspack_import_3[__rspack_import_key];
78
78
  __webpack_require__.d(__webpack_exports__, __rspack_reexport);
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern.js",
18
18
  "i18n"
19
19
  ],
20
- "version": "3.9.0-ultramodern.6",
20
+ "version": "3.9.0-ultramodern.8",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "module": "./dist/esm/index.mjs",
@@ -55,15 +55,15 @@
55
55
  "node": ">=26.7.0"
56
56
  },
57
57
  "dependencies": {
58
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.6",
58
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.8",
59
59
  "i18next-browser-languagedetector": "^8.2.1"
60
60
  },
61
61
  "peerDependencies": {
62
- "@modern-js/runtime": "3.9.0-ultramodern.6",
62
+ "@modern-js/runtime": "3.9.0-ultramodern.8",
63
63
  "react": "^19.2.8"
64
64
  },
65
65
  "devDependencies": {
66
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.6",
66
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.8",
67
67
  "@rslib/core": "1.0.0",
68
68
  "@scripts/rstest-config": "2.66.0",
69
69
  "@types/node": "^26.4.1",
@@ -80,7 +80,6 @@ describe('latest language synchronization coordinator', () => {
80
80
 
81
81
  binding.request('cs');
82
82
  binding.request('de');
83
- expect(pending.map(change => change.language)).toEqual(['cs', 'de']);
84
83
 
85
84
  pending[1].resolve();
86
85
  await pending[1].promise;
@@ -91,7 +90,6 @@ describe('latest language synchronization coordinator', () => {
91
90
  await pending[0].promise;
92
91
  await flush();
93
92
  expect(target.language).toBe('cs');
94
- expect(pending.map(change => change.language)).toEqual(['cs', 'de', 'de']);
95
93
 
96
94
  pending[2].resolve();
97
95
  await pending[2].promise;
@@ -100,81 +98,6 @@ describe('latest language synchronization coordinator', () => {
100
98
  expect(commits.at(-1)).toBe('de');
101
99
  });
102
100
 
103
- test('repairs a shared singleton after the starting mount leaves', async () => {
104
- const target = { language: 'en' };
105
- const { changeLanguage, pending } = deferredChanger(target);
106
- const commits: string[] = [];
107
- const first = createLatestLanguageSyncBinding<Target>();
108
- const second = createLatestLanguageSyncBinding<Target>();
109
- for (const binding of [first, second]) {
110
- binding.updateCallbacks({
111
- changeLanguage,
112
- commitLanguage: (_target, language) => commits.push(language),
113
- readLanguage: current => current.language,
114
- });
115
- }
116
-
117
- first.activate(target);
118
- first.request('cs');
119
- first.deactivate();
120
- second.activate(target);
121
- second.request('de');
122
-
123
- pending[1].resolve();
124
- await pending[1].promise;
125
- await flush();
126
- pending[0].resolve();
127
- await pending[0].promise;
128
- await flush();
129
-
130
- expect(pending.map(change => change.language)).toEqual(['cs', 'de', 'de']);
131
- pending[2].resolve();
132
- await pending[2].promise;
133
- await flush();
134
- expect(target.language).toBe('de');
135
- expect(commits.at(-1)).toBe('de');
136
- });
137
-
138
- test('supersedes a hung same-language attempt and repairs it after a later route', async () => {
139
- rstest.useFakeTimers();
140
- const target = { language: 'en' };
141
- const { changeLanguage, pending } = deferredChanger(target);
142
- const commits: string[] = [];
143
- const binding = createLatestLanguageSyncBinding<Target>({
144
- attemptTimeoutMs: 100,
145
- retryDelayMs: () => 10,
146
- });
147
- binding.updateCallbacks({
148
- changeLanguage,
149
- commitLanguage: (_target, language) => commits.push(language),
150
- readLanguage: current => current.language,
151
- });
152
- binding.activate(target);
153
- binding.request('cs');
154
-
155
- await rstest.advanceTimersByTimeAsync(110);
156
- expect(pending.map(change => change.language)).toEqual(['cs', 'cs']);
157
- pending[1].resolve();
158
- await pending[1].promise;
159
- await flush();
160
- expect(commits.at(-1)).toBe('cs');
161
-
162
- binding.request('de');
163
- pending[2].resolve();
164
- await pending[2].promise;
165
- await flush();
166
- expect(target.language).toBe('de');
167
-
168
- pending[0].resolve();
169
- await pending[0].promise;
170
- await flush();
171
- expect(pending.at(-1)?.language).toBe('de');
172
- pending.at(-1)?.resolve();
173
- await pending.at(-1)?.promise;
174
- await flush();
175
- expect(target.language).toBe('de');
176
- });
177
-
178
101
  test('bounds permanent failures and reports the final consequence once', async () => {
179
102
  rstest.useFakeTimers();
180
103
  const target = { language: 'en' };
@@ -83,17 +83,6 @@ describe('locale redirect resource policy', () => {
83
83
  expect(
84
84
  shouldIgnoreRedirect('/store/static/app.js', '/store/*', ignore),
85
85
  ).toBe(true);
86
- expect(seen).toEqual(['/health', '/products']);
87
- });
88
-
89
- test('preserves startsWith mount removal and empty remaining path behavior', () => {
90
- expect(shouldIgnoreRedirect('/storefront', '/store/*', ['/front'])).toBe(
91
- true,
92
- );
93
- expect(shouldIgnoreRedirect('/store', '/store/*', ['/'])).toBe(true);
94
- expect(shouldIgnoreRedirect('/health', '', ['/health'])).toBe(true);
95
- expect(shouldIgnoreRedirect('/health', '/', ['/health'])).toBe(true);
96
- expect(shouldIgnoreRedirect('/health', '/other/*', ['/health'])).toBe(true);
97
86
  });
98
87
  });
99
88
 
@@ -121,19 +110,6 @@ describe('locale redirect URL policy', () => {
121
110
  ).toBe('cs');
122
111
  });
123
112
 
124
- test('retains case-sensitive language detection and does not search later segments', () => {
125
- for (const pathname of [
126
- '/store',
127
- '/store/CS/products',
128
- '/store/fr/products',
129
- '/store/products/cs',
130
- ]) {
131
- expect(
132
- getLanguageFromPath(request(pathname), '/store/*', languages),
133
- ).toBeNull();
134
- }
135
- });
136
-
137
113
  test('switches localized dynamic paths while preserving mount, query and hash', () => {
138
114
  expect(
139
115
  buildLocalizedUrl(
@@ -170,25 +146,6 @@ describe('locale redirect URL policy', () => {
170
146
  ).toBe('/store/cs');
171
147
  });
172
148
 
173
- test('preserves the existing nonmatching and partial mount behavior', () => {
174
- expect(
175
- buildLocalizedUrl(
176
- request('https://example.com/products'),
177
- '/store/*',
178
- 'cs',
179
- languages,
180
- ),
181
- ).toBe('/store/cs/products');
182
- expect(
183
- buildLocalizedUrl(
184
- request('https://example.com/storefront'),
185
- '/store/*',
186
- 'cs',
187
- languages,
188
- ),
189
- ).toBe('/store/cs/front');
190
- });
191
-
192
149
  test('returns a non-cacheable temporary redirect with language negotiation headers', async () => {
193
150
  const response = createLocaleRedirectResponse(
194
151
  '/cs/produkty?sort=price#details',
@@ -7,7 +7,6 @@ import {
7
7
  matchPathPattern,
8
8
  resolveCanonicalLocalisedPath,
9
9
  resolveLocalisedPath,
10
- resolveLocalisedUrlsConfig,
11
10
  validateLocalisedUrls,
12
11
  } from '../src/localisedUrls/index';
13
12
 
@@ -24,19 +23,6 @@ const createRoute = (
24
23
  children,
25
24
  });
26
25
 
27
- describe('resolveLocalisedUrlsConfig', () => {
28
- test('enables the feature only for a non-empty map', () => {
29
- const map = { '/about': { en: '/about', cs: '/o-nas' } };
30
- const disabled = { enabled: false, map: {} };
31
-
32
- expect(resolveLocalisedUrlsConfig(map)).toEqual({ enabled: true, map });
33
- expect(resolveLocalisedUrlsConfig(undefined)).toEqual(disabled);
34
- expect(resolveLocalisedUrlsConfig(false)).toEqual(disabled);
35
- expect(resolveLocalisedUrlsConfig(true)).toEqual(disabled);
36
- expect(resolveLocalisedUrlsConfig({})).toEqual(disabled);
37
- });
38
- });
39
-
40
26
  describe('localisedUrls', () => {
41
27
  test('leaves framework-internal Module Federation routes unlocalised', () => {
42
28
  const internalRoute = createRoute('_mf', [
@@ -415,23 +401,6 @@ describe('localisedUrls', () => {
415
401
  ).toBe('/shoes/red');
416
402
  });
417
403
 
418
- test('a specific localised source outranks a broad canonical source', () => {
419
- const localisedUrls = {
420
- '/:section/:slug': {
421
- en: '/generic/:section/:slug',
422
- cs: '/obecne/:section/:slug',
423
- },
424
- '/products/:id': {
425
- en: '/fixed/:id',
426
- cs: '/produkty/:id',
427
- },
428
- };
429
-
430
- expect(
431
- resolveCanonicalLocalisedPath('/fixed/red', ['en', 'cs'], localisedUrls),
432
- ).toBe('/products/red');
433
- });
434
-
435
404
  test('missing required target params never collapse into empty segments', () => {
436
405
  expect(() =>
437
406
  resolveLocalisedPath('/products/red-shoe', 'cs', ['en', 'cs'], {
@@ -1,189 +1,24 @@
1
1
  import { describe, expect, test } from '@rstest/core';
2
- import type { LocalisedUrlsMap } from '../src/localisedUrls';
3
- import { buildLocalizedUrl, canonicalPath, localizePath } from '../src/paths';
2
+ import { canonicalPath, localizePath } from '../src/paths';
4
3
 
5
- const languages = ['en', 'cs'];
6
-
7
- const localisedUrls = {
8
- '/products/:slug': {
9
- en: '/products/:slug',
10
- cs: '/produkty/:slug',
11
- },
12
- '/files/*': {
13
- en: '/files/*',
14
- cs: '/soubory/*',
4
+ const config = {
5
+ languages: ['en', 'cs'],
6
+ localisedUrls: {
7
+ '/products/:slug': { en: '/products/:slug', cs: '/produkty/:slug' },
15
8
  },
16
- '/app/products/:slug': {
17
- en: '/app/products/:slug',
18
- cs: '/app/produkty/:slug',
19
- },
20
- } satisfies LocalisedUrlsMap;
21
-
22
- const pathsConfig = {
23
- languages,
24
- localisedUrls,
25
9
  };
26
10
 
27
- describe('fork-owned localised URL rewrite matrix', () => {
28
- const rewriteScenarios = [
29
- {
30
- name: 'adds the target locale prefix and localises canonical segments',
31
- target: '/products/red-shoe',
32
- language: 'cs',
33
- expected: '/cs/produkty/red-shoe',
34
- },
35
- {
36
- name: 'strips a case-insensitive source locale before relocalising',
37
- target: '/CS/produkty/red%20shoe?tag=boots#details',
38
- language: 'en',
39
- expected: '/en/products/red%20shoe?tag=boots#details',
40
- },
41
- {
42
- name: 'preserves repeated query keys and the fragment',
43
- target: '/products/red-shoe?tag=boots&tag=sale#details',
44
- language: 'cs',
45
- expected: '/cs/produkty/red-shoe?tag=boots&tag=sale#details',
46
- },
47
- {
48
- name: 'keeps a base-path segment inside the localised route map',
49
- target: '/app/products/red-shoe?ref=nav#details',
50
- language: 'cs',
51
- expected: '/cs/app/produkty/red-shoe?ref=nav#details',
52
- },
53
- {
54
- name: 'keeps the default locale explicitly prefixed',
55
- target: '/products/red-shoe',
56
- language: 'en',
57
- expected: '/en/products/red-shoe',
58
- },
59
- {
60
- name: 'does not double-prefix an already-localised URL',
61
- target: '/cs/produkty/red-shoe?tag=boots#details',
62
- language: 'cs',
63
- expected: '/cs/produkty/red-shoe?tag=boots#details',
64
- },
65
- ];
66
-
67
- for (const scenario of rewriteScenarios) {
68
- test(scenario.name, () => {
69
- expect(
70
- buildLocalizedUrl(
71
- scenario.target,
72
- scenario.language,
73
- languages,
74
- localisedUrls,
75
- ),
76
- ).toBe(scenario.expected);
77
- });
78
- }
79
-
80
- test('canonical and localized path helpers strip and add prefixes exactly', () => {
81
- expect(
82
- canonicalPath('/CS/produkty/red-shoe?tag=boots#details', pathsConfig),
83
- ).toBe('/products/red-shoe?tag=boots#details');
84
- expect(
85
- localizePath('/products/red-shoe?tag=boots#details', 'cs', pathsConfig),
86
- ).toBe('/cs/produkty/red-shoe?tag=boots#details');
87
- });
88
-
89
- test('splat params preserve separators and percent-encode each segment', () => {
90
- expect(
91
- buildLocalizedUrl(
92
- '/files/resume%20drafts/Q1%20deck.pdf',
93
- 'cs',
94
- languages,
95
- localisedUrls,
96
- ),
97
- ).toBe('/cs/soubory/resume%20drafts/Q1%20deck.pdf');
98
- });
99
- test('explicit maps work without plugin registration and keep suffixes verbatim', () => {
100
- const config = {
101
- languages,
102
- localisedUrls: {
103
- '/docs/:slug?': { en: '/docs/:slug?', cs: '/dokumenty/:slug?' },
104
- },
105
- };
106
- expect(localizePath('/docs?x=%2f&x=2#literal?query', 'cs', config)).toBe(
107
- '/cs/dokumenty?x=%2f&x=2#literal?query',
108
- );
109
- expect(canonicalPath('/cs/dokumenty/%E0%A4%A', config)).toBe(
110
- '/dokumenty/%E0%A4%A',
111
- );
112
- expect(localizePath('/[literal]?x=1#x', 'cs', { languages })).toBe(
113
- '/cs/[literal]?x=1#x',
114
- );
115
- });
116
- });
117
-
118
- describe('migrated public mapped helper contract', () => {
119
- const localisedUrls = {
120
- '/terms-of-service': {
121
- en: '/terms-of-service',
122
- cs: '/podminky-pouzivani',
123
- },
124
- '/products': {
125
- en: '/products',
126
- cs: '/produkty',
127
- },
128
- '/products/:slug': {
129
- en: '/products/:slug',
130
- cs: '/produkty/:slug',
131
- },
132
- // Canonical key that matches no language pattern.
133
- '/talks/:slug': {
134
- en: '/lectures/:slug',
135
- cs: '/prednasky/:slug',
136
- },
137
- };
138
-
139
- const languages = ['en', 'cs'];
140
- const pathsConfig = { languages, localisedUrls };
141
-
142
- test('query and hash survive localized slug mapping', () => {
143
- expect(
144
- buildLocalizedUrl(
145
- '/products/bota?tag=x#detail',
146
- 'cs',
147
- languages,
148
- localisedUrls,
149
- ),
150
- ).toBe('/cs/produkty/bota?tag=x#detail');
151
- });
152
-
153
- test('localizes canonical keys that match no language pattern', () => {
11
+ describe('@modern-js/i18n-runtime-extensions/paths', () => {
12
+ test('localises the pathname while leaving query and hash verbatim', () => {
154
13
  expect(
155
- buildLocalizedUrl(
156
- '/talks/ai-slop#abstract',
14
+ localizePath(
15
+ '/products/red-shoe?tag=boots&tag=sale#details',
157
16
  'cs',
158
- languages,
159
- localisedUrls,
17
+ config,
160
18
  ),
161
- ).toBe('/cs/prednasky/ai-slop#abstract');
162
- expect(
163
- buildLocalizedUrl('/talks/ai-slop', 'en', languages, localisedUrls),
164
- ).toBe('/en/lectures/ai-slop');
165
- });
166
-
167
- test('re-localizes already-localized paths', () => {
19
+ ).toBe('/cs/produkty/red-shoe?tag=boots&tag=sale#details');
168
20
  expect(
169
- buildLocalizedUrl('/cs/produkty/bota#x', 'en', languages, localisedUrls),
170
- ).toBe('/en/products/bota#x');
171
- });
172
- test('localizePath maps canonical paths per language', () => {
173
- expect(localizePath('/products/bota', 'cs', pathsConfig)).toBe(
174
- '/cs/produkty/bota',
175
- );
176
- expect(localizePath('/talks/x', 'en', pathsConfig)).toBe('/en/lectures/x');
177
- });
178
-
179
- test('canonicalPath strips language and reverse-maps localized slugs', () => {
180
- expect(canonicalPath('/cs/produkty/bota', pathsConfig)).toBe(
181
- '/products/bota',
182
- );
183
- expect(canonicalPath('/en/lectures/x?q=1#h', pathsConfig)).toBe(
184
- '/talks/x?q=1#h',
185
- );
186
- expect(canonicalPath('/cs', pathsConfig)).toBe('/');
187
- expect(canonicalPath('/en/products', pathsConfig)).toBe('/products');
21
+ canonicalPath('/CS/produkty/red-shoe?tag=boots#details', config),
22
+ ).toBe('/products/red-shoe?tag=boots#details');
188
23
  });
189
24
  });