mnfst 0.5.42 → 0.5.43

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.
@@ -228,6 +228,82 @@ function shouldSkipStickyLocaleForLogicalSegments(segments) {
228
228
  return STICKY_LOCALE_STATIC_FILE_EXT.has(ext);
229
229
  }
230
230
 
231
+ // manifest.prerender.localeRouteExclude → JSON array in meta; logical path prefixes (after locale).
232
+ function parseLocaleRouteExcludePatterns() {
233
+ const meta = document.querySelector('meta[name="manifest:locale-route-exclude"]');
234
+ const raw = meta?.getAttribute('content') || '';
235
+ if (raw.trim().startsWith('[')) {
236
+ try {
237
+ const parsed = JSON.parse(raw.replace(/"/g, '"'));
238
+ if (Array.isArray(parsed)) {
239
+ return parsed.map((s) => String(s).trim()).filter(Boolean);
240
+ }
241
+ } catch {
242
+ /* fall through */
243
+ }
244
+ }
245
+ const legacy = document.querySelector('meta[name="manifest:locale-sticky-exclude"]');
246
+ const legacyRaw = legacy?.getAttribute('content') || '';
247
+ if (!legacyRaw.trim()) return [];
248
+ return legacyRaw.split(',').map((s) => s.trim().replace(/^\/+/, '').split('/')[0]).filter(Boolean);
249
+ }
250
+
251
+ function logicalPathMatchesLocaleRouteExclude(segments, patterns) {
252
+ if (!patterns.length || !segments.length) return false;
253
+ const lower = segments.map((s) => s.toLowerCase());
254
+ for (const pattern of patterns) {
255
+ const p = String(pattern)
256
+ .trim()
257
+ .replace(/^\/+/, '')
258
+ .split('/')
259
+ .filter(Boolean)
260
+ .map((x) => x.toLowerCase());
261
+ if (p.length === 0) continue;
262
+ if (lower.length < p.length) continue;
263
+ let match = true;
264
+ for (let i = 0; i < p.length; i++) {
265
+ if (lower[i] !== p[i]) {
266
+ match = false;
267
+ break;
268
+ }
269
+ }
270
+ if (match) return true;
271
+ }
272
+ return false;
273
+ }
274
+
275
+ // /fr/legal/terms → /legal/terms when patterns include "legal" or "legal/terms" (prefix match).
276
+ function normalizeRedundantLocalePrefixInUrl() {
277
+ if (!isPrerenderedStaticBuild()) return false;
278
+ const codes = getLocalizationCodesFromManifest();
279
+ const patterns = parseLocaleRouteExcludePatterns();
280
+ if (!codes.length || !patterns.length) return false;
281
+
282
+ const segs = logicalSegmentsFromPathname(window.location.pathname);
283
+ if (segs.length < 2) return false;
284
+ if (!codes.includes(segs[0])) return false;
285
+ const rest = segs.slice(1);
286
+ if (!logicalPathMatchesLocaleRouteExclude(rest, patterns)) return false;
287
+
288
+ const newLogical = '/' + rest.join('/');
289
+ const base = getBasePath();
290
+ let newPathname = base ? base.replace(/\/+$/, '') + newLogical : newLogical;
291
+ newPathname = newPathname.replace(/\/{2,}/g, '/');
292
+ if (!newPathname.startsWith('/')) newPathname = '/' + newPathname;
293
+ if (newPathname === window.location.pathname) return false;
294
+
295
+ const prevLogical = pathnameToLogical(window.location.pathname);
296
+ const u = new URL(window.location.href);
297
+ u.pathname = newPathname;
298
+ history.replaceState(null, '', u.toString());
299
+ currentRoute = pathnameToLogical(newPathname);
300
+ const np = currentRoute === '/' ? '/' : String(currentRoute).replace(/^\/|\/$/g, '');
301
+ window.dispatchEvent(new CustomEvent('manifest:route-change', {
302
+ detail: { from: prevLogical, to: currentRoute, normalizedPath: np }
303
+ }));
304
+ return true;
305
+ }
306
+
231
307
  // When the URL already has a locale prefix (e.g. /zh/pricing), keep it for same-origin links
232
308
  // that omit the prefix (/articles → /zh/articles). No-op on default-locale URLs (/pricing).
233
309
  function applyStickyLocaleToPathname(absolutePathname) {
@@ -242,6 +318,12 @@ function applyStickyLocaleToPathname(absolutePathname) {
242
318
  if (targetSegs.length && codes.includes(targetSegs[0])) {
243
319
  return absolutePathname;
244
320
  }
321
+
322
+ const routeEx = parseLocaleRouteExcludePatterns();
323
+ if (routeEx.length && logicalPathMatchesLocaleRouteExclude(targetSegs, routeEx)) {
324
+ return absolutePathname;
325
+ }
326
+
245
327
  if (shouldSkipStickyLocaleForLogicalSegments(targetSegs)) {
246
328
  return absolutePathname;
247
329
  }
@@ -489,8 +571,8 @@ function interceptLinkClicks() {
489
571
 
490
572
  // Initialize navigation
491
573
  function initializeNavigation() {
492
- // Set initial route (logical path for matching)
493
574
  currentRoute = pathnameToLogical(window.location.pathname);
575
+ normalizeRedundantLocalePrefixInUrl();
494
576
 
495
577
  // In prerendered/static output, use default browser navigation (no SPA interception)
496
578
  if (!isPrerenderedStaticBuild()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.42",
3
+ "version": "0.5.43",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",