mnfst 0.5.163 → 0.5.165
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/lib/manifest.appwrite.auth.js +107 -199
- package/lib/manifest.appwrite.data.js +1 -3
- package/lib/manifest.appwrite.presences.js +292 -0
- package/lib/manifest.charts.js +34 -79
- package/lib/manifest.chat.js +515 -0
- package/lib/manifest.code.js +110 -340
- package/lib/manifest.colorpicker.js +252 -551
- package/lib/manifest.combobox.js +73 -91
- package/lib/manifest.components.js +34 -95
- package/lib/manifest.css +0 -132
- package/lib/manifest.data.js +189 -562
- package/lib/manifest.datepicker.js +43 -128
- package/lib/manifest.dropdowns.js +18 -39
- package/lib/manifest.export.js +29 -147
- package/lib/manifest.icons.js +9 -24
- package/lib/manifest.integrity.json +24 -23
- package/lib/manifest.js +65 -129
- package/lib/manifest.localization.js +67 -200
- package/lib/manifest.markdown.js +65 -174
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.payments.js +40 -139
- package/lib/manifest.router.js +24 -73
- package/lib/manifest.status.js +20 -56
- package/lib/manifest.svg.js +12 -30
- package/lib/manifest.tooltips.js +28 -65
- package/lib/manifest.utilities.js +40 -107
- package/lib/manifest.virtual.js +15 -53
- package/package.json +1 -1
- package/lib/manifest.appwrite.presence.js +0 -1650
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
/* Manifest Localization */
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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 —
|
|
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 —
|
|
71
|
-
//
|
|
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
|
|
87
|
-
//
|
|
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
|
|
104
|
-
//
|
|
105
|
-
//
|
|
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
|
|
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
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
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
|
-
//
|
|
189
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
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
|
-
//
|
|
238
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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` —
|
|
297
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
479
|
-
//
|
|
480
|
-
// locale
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
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
|
-
//
|
|
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
|
|
538
|
-
//
|
|
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
|
|
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
|
|
600
|
-
//
|
|
601
|
-
//
|
|
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
|
-
//
|
|
525
|
+
// Clear stored preference so future loads re-detect
|
|
608
526
|
safeStorage.remove('lang');
|
|
609
527
|
|
|
610
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
809
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 {
|