mnfst 0.5.163 → 0.5.164

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.
@@ -1,18 +1,12 @@
1
1
  /* Manifest Localization */
2
2
 
3
- // Snapshot the original <html lang> attribute at the moment this script
4
- // loads, before any locale-mutation code (this plugin's own init, or any
5
- // other script) has had a chance to overwrite it. This is the developer's
6
- // declared default — the value baked into index.html's <html lang="…"> tag
7
- // — and is the locale $locale.reset() restores to. Captured at module
8
- // scope so both initializeLocalizationPlugin and the reset implementation
9
- // can reach it through lexical closure.
3
+ // Original <html lang>, snapshotted before any locale mutation — the declared
4
+ // default that $locale.reset() restores to.
10
5
  const originalHtmlLang = (typeof document !== 'undefined' && document.documentElement)
11
6
  ? (document.documentElement.lang || '')
12
7
  : '';
13
8
 
14
- // RTL language codes — module scope so both the init plugin (sets <html dir>)
15
- // and the $locale magic (sets $locale.list direction) share one table.
9
+ // RTL language codes — shared by the init plugin and the $locale magic.
16
10
  const rtlLanguages = new Set([
17
11
  // Arabic script
18
12
  'ar', // Arabic
@@ -67,13 +61,8 @@ function isRTL(lang) {
67
61
  return rtlLanguages.has(lang);
68
62
  }
69
63
 
70
- // Endonym overrides — browsers' Intl.DisplayNames (CLDR) returns a wrong,
71
- // anglicized, or bare-code value for these, so they're pinned here (which also
72
- // insulates them from future CLDR drift). Overrides win over Intl. Add project-
73
- // or region-specific labels as needed.
74
- // tl → Intl gives "Filipino", conflating it with canonical `fil`
75
- // dv/bal/glk/pnb → Intl gives a Latin exonym, not the native-script endonym
76
- // aii → Intl gives the bare code (Assyrian Neo-Aramaic / Sureth)
64
+ // Endonym overrides — Intl.DisplayNames returns a wrong/anglicized/bare-code
65
+ // value for these, so pin the native-script names here. Overrides win over Intl.
77
66
  const localeNameOverrides = {
78
67
  tl: 'Tagalog',
79
68
  dv: 'ދިވެހި',
@@ -83,9 +72,8 @@ const localeNameOverrides = {
83
72
  aii: 'ܣܘܪܝܬ',
84
73
  };
85
74
 
86
- // Native language name (endonym) for a BCP-47 code, e.g. 'fr' → "français".
87
- // Derived from the browser's Intl.DisplayNames no data files, no fetches.
88
- // Falls back to the raw code for custom/unsupported codes.
75
+ // Native language name (endonym) for a BCP-47 code via Intl.DisplayNames,
76
+ // e.g. 'fr' "français". Falls back to the raw code if unsupported.
89
77
  const localeNameCache = new Map();
90
78
  function localeName(code) {
91
79
  if (localeNameOverrides[code]) return localeNameOverrides[code];
@@ -100,12 +88,9 @@ function localeName(code) {
100
88
  return name;
101
89
  }
102
90
 
103
- // Rich list backing $locale.list: one object per available locale, with
104
- // ordering views hung off the array as non-enumerable getters so x-for and
105
- // spreads only ever see the locale items, never the view accessors.
106
- // $locale.list → source (manifest.json) order
107
- // $locale.list.alphabetical → by native name, locale-aware compare
108
- // $locale.list.currentFirst → active locale first, rest in source order
91
+ // Rich list backing $locale.list: one item per locale, with ordering views
92
+ // (alphabetical, currentFirst) as non-enumerable getters so x-for/spreads
93
+ // only see the items.
109
94
  function buildLocaleList(available, current) {
110
95
  const list = available.map(code => ({
111
96
  code,
@@ -124,16 +109,14 @@ function buildLocaleList(available, current) {
124
109
  return list;
125
110
  }
126
111
 
127
- // Global setLocale wrapper - will be replaced with real implementation
112
+ // Global setLocale wrapper replaced with the real implementation at init
128
113
  let setLocaleImpl = null;
129
114
 
130
- // Wrapper function available immediately
131
115
  async function setLocale(newLang, updateUrl = false) {
132
116
  if (setLocaleImpl) {
133
117
  return await setLocaleImpl(newLang, updateUrl);
134
118
  } else {
135
119
  console.warn('[Manifest Localization] setLocale implementation not ready yet, will retry');
136
- // Wait a bit and try again
137
120
  await new Promise(resolve => setTimeout(resolve, 100));
138
121
  if (setLocaleImpl) {
139
122
  return await setLocaleImpl(newLang, updateUrl);
@@ -148,26 +131,20 @@ window.__manifestSetLocale = setLocale;
148
131
 
149
132
  function initializeLocalizationPlugin() {
150
133
 
151
- // Environment detection for debug logging
152
134
  const isDevelopment = window.location.hostname === 'localhost' ||
153
135
  window.location.hostname === '127.0.0.1' ||
154
136
  window.location.hostname.includes('dev') ||
155
137
  window.location.search.includes('debug=true');
156
138
 
157
- // Debug logging helper (always enabled for now)
158
- // Debug logging disabled for production
159
139
  const debugLog = () => { };
160
140
 
161
141
  function isPrerenderedStaticBuild() {
162
142
  return document.head?.querySelector('meta[name="manifest:prerendered"][content="1"]') !== null;
163
143
  }
164
144
 
165
- // Returns the set of locales the prerender actually generated URL paths for.
166
- // Read from `<meta name="manifest:prerender-locales" content="en,fr,...">`.
167
- // When the target locale isn't in this set, MPA locale switching should NOT
168
- // navigate (it would 404) — instead, fall back to the in-page store update
169
- // so locale-aware data sources (e.g. examples on a localization docs page)
170
- // can re-render without leaving the current page.
145
+ // Locales the prerender generated URL paths for (from
146
+ // <meta name="manifest:prerender-locales">). Switching to a locale not in
147
+ // this set falls back to an in-page update rather than navigating (404).
171
148
  function getPrerenderLocales() {
172
149
  const meta = document.head?.querySelector('meta[name="manifest:prerender-locales"]');
173
150
  const content = meta?.getAttribute('content') || '';
@@ -182,13 +159,10 @@ function initializeLocalizationPlugin() {
182
159
  const pathParts = currentUrl.pathname.split('/').filter(Boolean);
183
160
  const hasLanguageInUrl = pathParts[0] && availableLocales.includes(pathParts[0]);
184
161
 
185
- // Determine path segments without any current locale prefix, for exclude-pattern checking.
186
162
  const pathWithoutLocale = hasLanguageInUrl ? pathParts.slice(1) : pathParts;
187
163
 
188
- // If the current path matches a manifest:locale-route-exclude pattern, do NOT add or
189
- // change the locale prefix this prevents an infinite redirect loop on prerendered
190
- // builds where normalizeRedundantLocalePrefixInUrl() strips the locale from the URL
191
- // but the localization init would otherwise re-add it via window.location.assign().
164
+ // Skip locale-prefix changes for paths matching manifest:locale-route-exclude
165
+ // avoids a redirect loop against the prerender's locale-stripping.
192
166
  const routeExcludeMeta = document.querySelector('meta[name="manifest:locale-route-exclude"]');
193
167
  if (routeExcludeMeta) {
194
168
  try {
@@ -205,7 +179,7 @@ function initializeLocalizationPlugin() {
205
179
  if (lower[i] !== p[i]) { match = false; break; }
206
180
  }
207
181
  if (match) {
208
- // Path is locale-excluded — return URL unchanged so no navigation is triggered
182
+ // Locale-excluded — return URL unchanged so no navigation fires.
209
183
  return currentUrl.toString();
210
184
  }
211
185
  }
@@ -219,24 +193,16 @@ function initializeLocalizationPlugin() {
219
193
  return currentUrl.toString();
220
194
  }
221
195
 
222
- // Input validation for language codes.
223
- //
224
- // Conforms to BCP 47 / ISO 639 shape:
225
- // - 2 or 3 letter primary tag (e.g. en, fr, zh, kab)
226
- // - Optional region: -XX (ISO 3166 alpha-2, e.g. en-US) or -DDD (UN M.49)
227
- // - Optional script: -Xxxx (ISO 15924, e.g. zh-Hans, zh-Hant)
228
- //
229
- // The previous /^[a-zA-Z0-9_-]+$/ pattern was too permissive — it matched
230
- // arbitrary identifiers like `appwriteTableId`, `scope`, `storage` from
231
- // non-localization data source configs and added them to `available`.
196
+ // Validate a BCP 47 / ISO 639 language code: 2-3 letter primary tag, optional
197
+ // region (-XX / -DDD) or script (-Xxxx). Strict enough to reject stray config
198
+ // keys (appwriteTableId, scope, …) that a looser pattern let into `available`.
232
199
  function isValidLanguageCode(lang) {
233
200
  if (typeof lang !== 'string' || lang.length === 0) return false;
234
201
  return /^[a-z]{2,3}(?:-(?:[A-Z][a-z]{3}|[A-Z]{2}|\d{3}))?$/i.test(lang);
235
202
  }
236
203
 
237
- // Identify data source entries that are Appwrite collections/buckets.
238
- // These have structural keys like `appwriteTableId` / `appwriteBucketId`
239
- // that must not be treated as locale codes during locale discovery.
204
+ // Appwrite data sources carry structural keys (appwriteTableId, …) that must
205
+ // not be mistaken for locale codes during discovery.
240
206
  function isAppwriteDataSource(collection) {
241
207
  return !!(collection && typeof collection === 'object' &&
242
208
  (collection.appwriteTableId || collection.appwriteBucketId ||
@@ -270,7 +236,7 @@ function initializeLocalizationPlugin() {
270
236
  }
271
237
  };
272
238
 
273
- // Initialize empty localization store (create immediately so magic method works)
239
+ // Seed an empty store immediately so the magic method works
274
240
  if (!Alpine.store('locale')) {
275
241
  Alpine.store('locale', {
276
242
  current: document.documentElement.lang || 'en',
@@ -281,22 +247,18 @@ function initializeLocalizationPlugin() {
281
247
  } else {
282
248
  }
283
249
 
284
- // Cache for manifest data
285
250
  let manifestCache = null;
286
251
 
287
- // Get available locales from manifest with caching
252
+ // Available locales from manifest, cached
288
253
  async function getAvailableLocales() {
289
- // Return cached data if available
290
254
  if (manifestCache) {
291
255
  return manifestCache;
292
256
  }
293
257
 
294
258
  try {
295
259
  let manifest = window.__manifestLoaded || window.ManifestComponentsRegistry?.manifest;
296
- // A partially-seeded global can be truthy yet lack `data` — e.g. another
297
- // plugin doing `window.__manifestLoaded.payments = …` before the loader
298
- // has populated the full manifest (or in a no-loader setup). Fetch the
299
- // real manifest in that case so locale detection still sees every source.
260
+ // A partially-seeded global can be truthy yet lack `data` — fetch the
261
+ // real manifest so locale detection sees every source.
300
262
  if (!manifest || !manifest.data) {
301
263
  const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
302
264
  const response = await fetch(manifestUrl);
@@ -306,21 +268,18 @@ function initializeLocalizationPlugin() {
306
268
  manifest = await response.json();
307
269
  }
308
270
 
309
- // Validate manifest structure
310
271
  if (!manifest || typeof manifest !== 'object') {
311
272
  throw new Error('Invalid manifest structure');
312
273
  }
313
274
 
314
- // Get unique locales from data sources
275
+ // Collect unique locales across data sources
315
276
  const locales = new Set();
316
277
  if (manifest.data && typeof manifest.data === 'object') {
317
- // Process each data source
318
278
  for (const [sourceName, collection] of Object.entries(manifest.data)) {
319
- // Skip Appwrite collections their config keys (appwriteTableId,
320
- // scope, storage, etc.) shouldn't be treated as locale codes.
279
+ // Skip Appwrite collections (their config keys aren't locales)
321
280
  if (isAppwriteDataSource(collection)) continue;
322
281
  if (collection && typeof collection === 'object') {
323
- // Check for single-file multi-locale CSV (e.g., {"locales": "/path/to/file.csv"})
282
+ // Single-file multi-locale CSV, e.g. {"locales": "file.csv"}
324
283
  if (collection.locales && typeof collection.locales === 'string' && collection.locales.endsWith('.csv')) {
325
284
  try {
326
285
  const base = typeof window.getManifestBase === 'function' ? window.getManifestBase() : '';
@@ -333,7 +292,7 @@ function initializeLocalizationPlugin() {
333
292
  const lines = csvText.split('\n').filter(line => line.trim());
334
293
  if (lines.length > 0) {
335
294
  const headers = lines[0].split(',').map(h => h.trim());
336
- // First column is typically 'key', rest are locale columns
295
+ // First column is 'key', rest are locale columns
337
296
  headers.forEach(header => {
338
297
  if (header !== 'key' && isValidLanguageCode(header)) {
339
298
  locales.add(header);
@@ -346,19 +305,15 @@ function initializeLocalizationPlugin() {
346
305
  }
347
306
  }
348
307
 
349
- // Check for locale keys in manifest (e.g., {"en": "/path/to/en.csv", "fr": "/path/to/fr.csv"})
308
+ // Per-locale keys, e.g. {"en": "en.csv", "fr": "fr.csv"}
350
309
  Object.keys(collection).forEach(key => {
351
- // Exclude reserved config keys (add more as needed for future phases)
352
310
  const reservedKeys = ['url', 'headers', 'params', 'transform', 'defaultValue', 'locales'];
353
-
354
- // Accept any valid language code that's not a reserved key
355
- // This allows custom locale codes like "klingon", "en", "fr", etc.
356
311
  if (isValidLanguageCode(key) && !reservedKeys.includes(key)) {
357
312
  locales.add(key);
358
313
  }
359
314
  });
360
315
  } else if (typeof collection === 'string' && collection.endsWith('.csv')) {
361
- // Simple CSV file path - check if it has locale columns
316
+ // Bare CSV path inspect for locale columns
362
317
  try {
363
318
  const base = typeof window.getManifestBase === 'function' ? window.getManifestBase() : '';
364
319
  const csvPath = collection.startsWith('/') ? collection.slice(1) : collection;
@@ -369,11 +324,9 @@ function initializeLocalizationPlugin() {
369
324
  const lines = csvText.split('\n').filter(line => line.trim());
370
325
  if (lines.length > 0) {
371
326
  const headers = lines[0].split(',').map(h => h.trim());
372
- // Check if this looks like a localized CSV (has 'key' column + locale columns)
373
- // vs tabular data (has 'id' column)
327
+ // Localized CSV ('key' + locale columns) vs tabular ('id')
374
328
  const firstHeader = headers[0]?.toLowerCase();
375
329
  if (firstHeader === 'key' && headers.length > 1) {
376
- // This is a key-value CSV, check for locale columns
377
330
  headers.forEach(header => {
378
331
  if (header !== 'key' && isValidLanguageCode(header)) {
379
332
  locales.add(header);
@@ -391,7 +344,7 @@ function initializeLocalizationPlugin() {
391
344
  }
392
345
  }
393
346
 
394
- // If no locales found, fallback to HTML lang or 'en'
347
+ // No locales found fall back to HTML lang or 'en'
395
348
  if (locales.size === 0) {
396
349
  const htmlLang = document.documentElement.lang;
397
350
  const fallbackLang = htmlLang && isValidLanguageCode(htmlLang) ? htmlLang : 'en';
@@ -399,41 +352,38 @@ function initializeLocalizationPlugin() {
399
352
  }
400
353
 
401
354
  const availableLocales = Array.from(locales);
402
-
403
- // Cache the result
404
355
  manifestCache = availableLocales;
405
356
  return availableLocales;
406
357
  } catch (error) {
407
358
  console.error('[Manifest Localization] Error loading manifest:', error);
408
- // Fallback to HTML lang or 'en'
409
359
  const htmlLang = document.documentElement.lang;
410
360
  const fallbackLang = htmlLang && isValidLanguageCode(htmlLang) ? htmlLang : 'en';
411
361
  return [fallbackLang];
412
362
  }
413
363
  }
414
364
 
415
- // Detect initial locale
365
+ // Detect initial locale, by priority: URL > localStorage > <html lang> > browser > first available
416
366
  function detectInitialLocale(availableLocales) {
417
367
 
418
- // 1. Check URL path first (highest priority for direct links)
368
+ // URL path (direct links)
419
369
  const pathParts = window.location.pathname.split('/').filter(Boolean);
420
370
  if (pathParts[0] && isValidLanguageCode(pathParts[0]) && availableLocales.includes(pathParts[0])) {
421
371
  return pathParts[0];
422
372
  }
423
373
 
424
- // 2. Check localStorage (user preference from UI toggles)
374
+ // localStorage (UI-toggle preference)
425
375
  const storedLang = safeStorage.get('lang');
426
376
  if (storedLang && isValidLanguageCode(storedLang) && availableLocales.includes(storedLang)) {
427
377
  return storedLang;
428
378
  }
429
379
 
430
- // 3. Check HTML lang attribute
380
+ // <html lang>
431
381
  const htmlLang = document.documentElement.lang;
432
382
  if (htmlLang && isValidLanguageCode(htmlLang) && availableLocales.includes(htmlLang)) {
433
383
  return htmlLang;
434
384
  }
435
385
 
436
- // 4. Check browser language
386
+ // Browser language
437
387
  if (navigator.language) {
438
388
  const browserLang = navigator.language.split('-')[0];
439
389
  if (isValidLanguageCode(browserLang) && availableLocales.includes(browserLang)) {
@@ -441,15 +391,12 @@ function initializeLocalizationPlugin() {
441
391
  }
442
392
  }
443
393
 
444
- // Default to first available locale
445
394
  const defaultLang = availableLocales[0] || 'en';
446
395
  return defaultLang;
447
396
  }
448
397
 
449
- // Update locale - this is the real implementation
450
398
  async function setLocaleReal(newLang, updateUrl = false) {
451
399
 
452
- // Validate input
453
400
  if (!isValidLanguageCode(newLang)) {
454
401
  console.error('[Manifest Localization] Invalid language code:', newLang);
455
402
  return false;
@@ -457,7 +404,7 @@ function initializeLocalizationPlugin() {
457
404
 
458
405
  const store = Alpine.store('locale');
459
406
 
460
- // If available locales aren't loaded yet, load them first
407
+ // Load available locales if not yet present
461
408
  if (!store.available || store.available.length === 0) {
462
409
  const availableLocales = await getAvailableLocales();
463
410
  if (!availableLocales.includes(newLang)) {
@@ -475,30 +422,15 @@ function initializeLocalizationPlugin() {
475
422
 
476
423
 
477
424
  try {
478
- // In prerendered static output, locale switching normally navigates
479
- // to the target locale's URL. But only do this when the target
480
- // locale was ACTUALLY prerendered otherwise navigation would 404.
481
- //
482
- // When the host site has a single locale (e.g. an English docs site
483
- // with locale-aware example data on one page), `prerender-locales`
484
- // contains only that locale. Switching to any other locale falls
485
- // through to the in-page store update below — locale-aware data
486
- // re-loads via the `localechange` event and the page reflects the
487
- // new locale without navigating.
488
- // Track whether this is a "scoped" change (in-page only on a
489
- // prerendered single-locale site). Scoped changes must NOT persist
490
- // to localStorage or update the URL — the locale is a transient
491
- // example/demo state, not a site-wide preference. Persisting would
492
- // leak the demo locale to subsequent page loads via localStorage,
493
- // making `<html lang>` mismatch the actual baked content (English
494
- // article body with `lang="fr"`) and muddying the SEO signal.
425
+ // In prerendered output, switching normally navigates to the target
426
+ // locale's URL — but only if that locale was actually prerendered
427
+ // (else 404). A single-locale site has no other locale URLs, so every
428
+ // switch becomes an in-page "scoped demo" change instead. Scoped
429
+ // changes skip localStorage/URL persistence so they don't leak the
430
+ // demo locale into later page loads.
495
431
  let isScopedDemoChange = false;
496
432
  if (isPrerenderedStaticBuild()) {
497
433
  const prerenderLocales = getPrerenderLocales();
498
- // Multi-locale site: navigate to the target locale's URL.
499
- // Single-locale site: there are no other locale URLs to navigate
500
- // to (the renderer skips the redundant default-locale mirror),
501
- // so ALL switches must be treated as in-page demos.
502
434
  const isMultiLocaleSite = prerenderLocales.length > 1;
503
435
  const targetIsPrerendered =
504
436
  prerenderLocales.length === 0 ||
@@ -510,17 +442,13 @@ function initializeLocalizationPlugin() {
510
442
  }
511
443
  return true;
512
444
  }
513
- // In-page demo: locale-aware data re-renders without navigating.
514
- // Mark as scoped so we skip URL/localStorage persistence below.
515
445
  isScopedDemoChange = true;
516
446
  }
517
447
 
518
- // Update store
519
448
  store.current = newLang;
520
449
  store.direction = isRTL(newLang) ? 'rtl' : 'ltr';
521
450
  store._initialized = true;
522
451
 
523
- // Update HTML safely
524
452
  try {
525
453
  document.documentElement.lang = newLang;
526
454
  document.documentElement.dir = store.direction;
@@ -528,34 +456,26 @@ function initializeLocalizationPlugin() {
528
456
  console.error('[Manifest Localization] DOM update error:', domError);
529
457
  }
530
458
 
531
- // Update localStorage safely — but skip for scoped demo changes
532
- // so the next page load doesn't restore a non-prerendered locale.
459
+ // Persist preference, except for scoped demo changes
533
460
  if (!isScopedDemoChange) {
534
461
  safeStorage.set('lang', newLang);
535
462
  }
536
463
 
537
- // Update URL based on current URL state and updateUrl parameter.
538
- // Skip entirely for scoped demo changes — adding/replacing a locale
539
- // prefix would point at a URL that wasn't prerendered (404).
464
+ // Update URL (replace existing locale prefix, or add when updateUrl).
465
+ // Skipped for scoped demo changes — the prefixed URL wasn't prerendered.
540
466
  try {
541
467
  const currentUrl = new URL(window.location.href);
542
468
  const pathParts = currentUrl.pathname.split('/').filter(Boolean);
543
469
  const hasLanguageInUrl = pathParts[0] && store.available.includes(pathParts[0]);
544
470
 
545
471
  if (!isScopedDemoChange && (updateUrl || hasLanguageInUrl)) {
546
- // Update URL if:
547
- // 1. updateUrl is explicitly true (router navigation, initialization)
548
- // 2. OR there's already a language code in the URL (user expects URL to update)
549
-
550
472
  if (hasLanguageInUrl) {
551
- // Replace existing language code
552
473
  if (pathParts[0] !== newLang) {
553
474
  pathParts[0] = newLang;
554
475
  currentUrl.pathname = '/' + pathParts.join('/');
555
476
  window.history.replaceState({}, '', currentUrl);
556
477
  }
557
478
  } else if (updateUrl && pathParts.length > 0) {
558
- // Add language code only if explicitly requested (router/init)
559
479
  pathParts.unshift(newLang);
560
480
  currentUrl.pathname = '/' + pathParts.join('/');
561
481
  window.history.replaceState({}, '', currentUrl);
@@ -565,7 +485,6 @@ function initializeLocalizationPlugin() {
565
485
  console.error('[Manifest Localization] URL update error:', urlError);
566
486
  }
567
487
 
568
- // Trigger locale change event
569
488
  try {
570
489
  window.dispatchEvent(new CustomEvent('localechange', {
571
490
  detail: { locale: newLang }
@@ -578,7 +497,7 @@ function initializeLocalizationPlugin() {
578
497
 
579
498
  } catch (error) {
580
499
  console.error('[Manifest Localization] Error setting locale:', error);
581
- // Restore previous state safely
500
+ // Restore previous state
582
501
  const fallbackLang = safeStorage.get('lang') || store.available[0] || 'en';
583
502
  store.current = fallbackLang;
584
503
  store.direction = isRTL(fallbackLang) ? 'rtl' : 'ltr';
@@ -596,23 +515,17 @@ function initializeLocalizationPlugin() {
596
515
  setLocaleImpl = setLocaleReal;
597
516
  window.__manifestSetLocale = setLocaleReal;
598
517
 
599
- // $locale.reset implementation — exposed across functions via window so the
600
- // magic registration (in registerLocaleMagic, a sibling top-level function)
601
- // can call it. Inlining the logic in the magic's closure would put it out
602
- // of scope from safeStorage / isValidLanguageCode / isRTL / originalHtmlLang.
518
+ // $locale.reset implementation — exposed via window so registerLocaleMagic
519
+ // (a sibling top-level fn) can call it while keeping closure access to
520
+ // safeStorage / isValidLanguageCode / isRTL / originalHtmlLang.
603
521
  function resetLocaleReal(href) {
604
522
  const store = Alpine.store('locale');
605
523
  const available = store?.available || [originalHtmlLang || 'en'];
606
524
 
607
- // 1. Clear stored UI-toggle preference so future page loads re-detect.
525
+ // Clear stored preference so future loads re-detect
608
526
  safeStorage.remove('lang');
609
527
 
610
- // 2. Resolve the default locale. Matches initial detection minus URL
611
- // and localStorage layers, since reset opts out of both:
612
- // a. Original <html lang> baked into index.html (snapshotted at
613
- // plugin init, before any locale mutation).
614
- // b. Browser language if it matches an available locale.
615
- // c. First locale registered in manifest.json.
528
+ // Resolve default: original <html lang> > browser language > first available
616
529
  let defaultLocale = null;
617
530
  if (originalHtmlLang
618
531
  && isValidLanguageCode(originalHtmlLang)
@@ -628,8 +541,7 @@ function initializeLocalizationPlugin() {
628
541
  defaultLocale = available[0] || 'en';
629
542
  }
630
543
 
631
- // 3. Resolve target URL (passed-in href or current) and strip its
632
- // leading locale segment, if any.
544
+ // Resolve target URL and strip any leading locale segment
633
545
  let target;
634
546
  try {
635
547
  target = new URL(href || window.location.href, window.location.href);
@@ -642,9 +554,7 @@ function initializeLocalizationPlugin() {
642
554
  }
643
555
  target.pathname = '/' + segs.join('/');
644
556
 
645
- // 4. Apply the default locale to the live store + DOM before
646
- // navigating, so $locale.current and any reactive readers update
647
- // immediately.
557
+ // Apply default to live store + DOM before navigating
648
558
  if (store && store.current !== defaultLocale) {
649
559
  store.current = defaultLocale;
650
560
  store.direction = isRTL(defaultLocale) ? 'rtl' : 'ltr';
@@ -659,9 +569,7 @@ function initializeLocalizationPlugin() {
659
569
  } catch { /* event dispatch unavailable */ }
660
570
  }
661
571
 
662
- // 5. Navigate to the locale-stripped URL. SPA hop in the live app,
663
- // MPA hop in prerendered output so the new URL's prerendered HTML
664
- // loads from disk.
572
+ // Navigate to the locale-stripped URL: SPA hop live, MPA hop when prerendered
665
573
  const isSameOrigin = target.origin === window.location.origin;
666
574
  const isPrerendered = !!document.querySelector('meta[name="manifest:prerendered"]:not([content="0"]):not([content="false"])');
667
575
 
@@ -675,10 +583,9 @@ function initializeLocalizationPlugin() {
675
583
  }
676
584
  window.__manifestResetLocale = resetLocaleReal;
677
585
 
678
- // Event listener cleanup tracking
679
586
  let routeChangeListener = null;
680
587
 
681
- // Initialize with manifest data
588
+ // Initialize from manifest data
682
589
  (async () => {
683
590
  try {
684
591
  const availableLocales = await getAvailableLocales();
@@ -688,25 +595,22 @@ function initializeLocalizationPlugin() {
688
595
  const initialLocale = detectInitialLocale(availableLocales);
689
596
 
690
597
  const success = await setLocale(initialLocale, true);
691
- // Locale initialization complete
692
598
  } catch (error) {
693
599
  console.error('[Manifest Localization] Initialization error:', error);
694
600
  }
695
601
  })();
696
602
 
697
- // Listen for router navigation to detect locale changes
603
+ // Sync locale to router navigation
698
604
  routeChangeListener = async (event) => {
699
605
  try {
700
606
  const newPath = event.detail.to;
701
607
 
702
- // Extract locale from new path
703
608
  const pathParts = newPath.split('/').filter(Boolean);
704
609
  const store = Alpine.store('locale');
705
610
 
706
611
  if (pathParts[0] && isValidLanguageCode(pathParts[0]) && store.available.includes(pathParts[0])) {
707
612
  const newLocale = pathParts[0];
708
613
 
709
- // Only change if it's different from current locale
710
614
  if (newLocale !== store.current) {
711
615
  await setLocale(newLocale, true);
712
616
  }
@@ -718,7 +622,6 @@ function initializeLocalizationPlugin() {
718
622
 
719
623
  window.addEventListener('manifest:route-change', routeChangeListener);
720
624
 
721
- // Cleanup function for memory management
722
625
  const cleanup = () => {
723
626
  if (routeChangeListener) {
724
627
  window.removeEventListener('manifest:route-change', routeChangeListener);
@@ -727,12 +630,10 @@ function initializeLocalizationPlugin() {
727
630
  manifestCache = null;
728
631
  };
729
632
 
730
- // Expose cleanup for external use
731
633
  window.__manifestLocalizationCleanup = cleanup;
732
634
  }
733
635
 
734
- // Register $locale magic method immediately when Alpine is available
735
- // This ensures it's available even before full initialization completes
636
+ // Register the $locale magic runs before full init so it's available early
736
637
  function registerLocaleMagic() {
737
638
 
738
639
  if (!window.Alpine) {
@@ -743,7 +644,6 @@ function registerLocaleMagic() {
743
644
  return false;
744
645
  }
745
646
 
746
- // Only register once
747
647
  if (window.__manifestLocaleMagicRegistered) {
748
648
  return true;
749
649
  }
@@ -754,7 +654,7 @@ function registerLocaleMagic() {
754
654
  Alpine.magic('locale', () => {
755
655
  const store = Alpine.store('locale');
756
656
 
757
- // If store doesn't exist yet, create minimal one
657
+ // Create a minimal store if none exists yet
758
658
  if (!store) {
759
659
  Alpine.store('locale', {
760
660
  current: document.documentElement.lang || 'en',
@@ -782,7 +682,6 @@ function registerLocaleMagic() {
782
682
  );
783
683
  }
784
684
  if (prop === 'set') {
785
- // Use the global setLocale function (wrapper or real implementation)
786
685
  return async (locale, updateUrl = false) => {
787
686
  if (window.__manifestSetLocale) {
788
687
  const result = await window.__manifestSetLocale(locale, updateUrl);
@@ -804,35 +703,9 @@ function registerLocaleMagic() {
804
703
  }
805
704
  };
806
705
  }
807
- // $locale.reset([href]) — restore the project's default
808
- // locale. Reset clears the user's stored language preference,
809
- // recomputes the default from the developer's declarations,
810
- // and applies it to the live store + <html lang>/<html dir>.
811
- //
812
- // Default resolution (matches initial detection minus the
813
- // URL and localStorage layers, since reset explicitly opts
814
- // out of both):
815
- // 1. Original <html lang> baked into index.html
816
- // (snapshotted at plugin init, before any mutation)
817
- // 2. Browser language (navigator.language) if it matches
818
- // an available locale
819
- // 3. First locale registered in manifest.json
820
- //
821
- // As a side effect, any leading locale slug in the URL is
822
- // stripped (since the URL prefix would otherwise re-detect
823
- // back into a non-default locale on the next page load).
824
- //
825
- // Forms:
826
- // $locale.reset() → reset current URL
827
- // $locale.reset('/fr/foo') → strip prefix from given href,
828
- // then navigate (useful for
829
- // "View in default language"
830
- // links anywhere on the page)
831
- //
832
- // Routing path: SPA hop via history.pushState when running
833
- // in the live SPA; full navigation via location.assign in
834
- // prerendered MPA mode so the new URL's prerendered HTML
835
- // loads from disk.
706
+ // $locale.reset([href]) — restore the project's default locale
707
+ // (see resetLocaleReal). Also strips any leading locale slug
708
+ // from the URL so it won't re-detect on the next load.
836
709
  if (prop === 'reset') {
837
710
  return (href) => {
838
711
  if (window.__manifestResetLocale) {
@@ -854,16 +727,13 @@ function registerLocaleMagic() {
854
727
  }
855
728
  }
856
729
 
857
- // Handle initialization
858
730
  function setupLocalization() {
859
731
 
860
- // Try to register magic method immediately
861
732
  const registered = registerLocaleMagic();
862
733
 
863
734
  if (window.Alpine) {
864
735
  initializeLocalizationPlugin();
865
736
  } else {
866
- // Wait for Alpine, then register magic method and initialize
867
737
  document.addEventListener('alpine:init', () => {
868
738
  const registered = registerLocaleMagic();
869
739
  if (registered) {
@@ -875,7 +745,7 @@ function setupLocalization() {
875
745
  }
876
746
  }
877
747
 
878
- // Track initialization to prevent duplicates
748
+ // Guard against duplicate initialization
879
749
  let localizationPluginInitialized = false;
880
750
 
881
751
  function ensureLocalizationPluginInitialized() {
@@ -887,22 +757,19 @@ function ensureLocalizationPluginInitialized() {
887
757
  setupLocalization();
888
758
  }
889
759
 
890
- // Expose on window for loader to call if needed
891
760
  window.ensureLocalizationPluginInitialized = ensureLocalizationPluginInitialized;
892
761
 
893
- // Register magic method on alpine:init (fires when Alpine initializes)
894
762
  document.addEventListener('alpine:init', () => {
895
763
  ensureLocalizationPluginInitialized();
896
764
  }, { once: true });
897
765
 
898
- // Handle both DOMContentLoaded and immediate execution
899
766
  if (document.readyState === 'loading') {
900
767
  document.addEventListener('DOMContentLoaded', ensureLocalizationPluginInitialized);
901
768
  } else {
902
769
  ensureLocalizationPluginInitialized();
903
770
  }
904
771
 
905
- // If Alpine is already initialized when this script loads, initialize immediately
772
+ // Alpine already up when this loads init on next tick / poll
906
773
  if (window.Alpine && typeof window.Alpine.magic === 'function') {
907
774
  setTimeout(ensureLocalizationPluginInitialized, 0);
908
775
  } else {