langie 1.9.25

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/index.mjs ADDED
@@ -0,0 +1,2826 @@
1
+ /**
2
+ * langie v1.9.25
3
+ * (c) 2026 nlit
4
+ * @license Apache-2.0
5
+ *
6
+ * Licensed under the Apache License, Version 2.0
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ */
9
+
10
+ // src/useLangie.ts
11
+ import { watch } from "vue";
12
+
13
+ // src/composables/useLangie-core.ts
14
+ import { ref, reactive } from "vue";
15
+
16
+ // src/constants.ts
17
+ var DEFAULT_API_HOST = "https://api.langie.uk/v1";
18
+ var DEV_API_HOST = "http://localhost:8081/v1";
19
+ var API_FIELD_TEXT = "t";
20
+ var API_FIELD_FROM = "from";
21
+ var API_FIELD_TO = "to";
22
+ var API_FIELD_CTX = "ctx";
23
+ var API_FIELD_TRANSLATED = "t";
24
+ var API_FIELD_TRANSLATIONS = "translations";
25
+ var API_FIELD_ERROR = "error";
26
+
27
+ // src/utils/debug.ts
28
+ function devDebug(...args) {
29
+ if (import.meta.env && import.meta.env.DEV) {
30
+ console.debug(...args);
31
+ }
32
+ }
33
+
34
+ // src/utils/getCountryCode.ts
35
+ import * as ct from "countries-and-timezones";
36
+ async function getCountryCode() {
37
+ const localeCountry = getCountryCodeFromBrowser();
38
+ if (localeCountry) return localeCountry;
39
+ const timezoneCountry = getCountryFromTimezone();
40
+ if (timezoneCountry) return timezoneCountry;
41
+ return await getCountryFromIP();
42
+ }
43
+ function getCountryFromTimezone() {
44
+ try {
45
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
46
+ return getCountryFromTimezoneString(timezone);
47
+ } catch (error) {
48
+ return null;
49
+ }
50
+ }
51
+ function getCountryFromTimezoneString(timezone) {
52
+ try {
53
+ const country = ct.getCountryForTimezone(timezone);
54
+ return country?.id || null;
55
+ } catch (error) {
56
+ return null;
57
+ }
58
+ }
59
+ async function getCountryFromIP() {
60
+ try {
61
+ const response = await fetch("https://ipapi.co/json/");
62
+ const data = await response.json();
63
+ return data.country_code || null;
64
+ } catch (error) {
65
+ return null;
66
+ }
67
+ }
68
+ function getCountryCodeFromBrowser() {
69
+ const langs = navigator.languages || [navigator.language];
70
+ for (const locale of langs) {
71
+ const parts = locale.split("-");
72
+ if (parts.length > 1) {
73
+ return parts[1].toUpperCase();
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+
79
+ // src/utils/cache.ts
80
+ var DEFAULT_TTL = 7 * 24 * 60 * 60 * 1e3;
81
+ var DEFAULT_MAX_SIZE = 2 * 1024 * 1024;
82
+ var DEFAULT_MAX_ITEMS = 1e3;
83
+ var CacheManager = class {
84
+ options;
85
+ constructor(options = {}) {
86
+ this.options = {
87
+ ttl: options.ttl || DEFAULT_TTL,
88
+ maxSize: options.maxSize || DEFAULT_MAX_SIZE,
89
+ maxItems: options.maxItems || DEFAULT_MAX_ITEMS
90
+ };
91
+ }
92
+ set(key, data, ttl) {
93
+ if (typeof window === "undefined") return false;
94
+ try {
95
+ const item = {
96
+ data,
97
+ timestamp: Date.now(),
98
+ ttl: ttl || this.options.ttl
99
+ };
100
+ const serialized = JSON.stringify(item);
101
+ const size = new Blob([serialized]).size;
102
+ if (size > this.options.maxSize) {
103
+ console.warn(`[CacheManager] Item size ${size} exceeds max size ${this.options.maxSize}`);
104
+ return false;
105
+ }
106
+ this.cleanup();
107
+ const currentSize = this.getCurrentSize();
108
+ if (currentSize + size > this.options.maxSize) {
109
+ console.warn(`[CacheManager] Cache would exceed max size after adding item`);
110
+ return false;
111
+ }
112
+ localStorage.setItem(key, serialized);
113
+ return true;
114
+ } catch (error) {
115
+ console.warn(`[CacheManager] Failed to set cache item ${key}:`, error);
116
+ return false;
117
+ }
118
+ }
119
+ get(key) {
120
+ if (typeof window === "undefined") return null;
121
+ try {
122
+ const item = localStorage.getItem(key);
123
+ if (!item) return null;
124
+ const cacheItem = JSON.parse(item);
125
+ const now = Date.now();
126
+ if (now - cacheItem.timestamp > cacheItem.ttl) {
127
+ localStorage.removeItem(key);
128
+ return null;
129
+ }
130
+ return cacheItem.data;
131
+ } catch (error) {
132
+ console.warn(`[CacheManager] Failed to get cache item ${key}:`, error);
133
+ localStorage.removeItem(key);
134
+ return null;
135
+ }
136
+ }
137
+ remove(key) {
138
+ if (typeof window === "undefined") return false;
139
+ try {
140
+ localStorage.removeItem(key);
141
+ return true;
142
+ } catch (error) {
143
+ console.warn(`[CacheManager] Failed to remove cache item ${key}:`, error);
144
+ return false;
145
+ }
146
+ }
147
+ clear(pattern) {
148
+ if (typeof window === "undefined") return;
149
+ try {
150
+ if (pattern) {
151
+ Object.keys(localStorage).forEach((key) => {
152
+ if (key.includes(pattern)) {
153
+ localStorage.removeItem(key);
154
+ }
155
+ });
156
+ } else {
157
+ Object.keys(localStorage).forEach((key) => {
158
+ if (key.startsWith("langie_")) {
159
+ localStorage.removeItem(key);
160
+ }
161
+ });
162
+ }
163
+ } catch (error) {
164
+ console.warn("[CacheManager] Failed to clear cache:", error);
165
+ }
166
+ }
167
+ cleanup() {
168
+ if (typeof window === "undefined") return;
169
+ try {
170
+ const now = Date.now();
171
+ const items = [];
172
+ Object.keys(localStorage).forEach((key) => {
173
+ if (key.startsWith("langie_")) {
174
+ try {
175
+ const item = localStorage.getItem(key);
176
+ if (item) {
177
+ const cacheItem = JSON.parse(item);
178
+ const size = new Blob([item]).size;
179
+ if (now - cacheItem.timestamp > cacheItem.ttl) {
180
+ localStorage.removeItem(key);
181
+ return;
182
+ }
183
+ items.push({
184
+ key,
185
+ size,
186
+ timestamp: cacheItem.timestamp
187
+ });
188
+ }
189
+ } catch (error) {
190
+ localStorage.removeItem(key);
191
+ }
192
+ }
193
+ });
194
+ items.sort((a, b) => a.timestamp - b.timestamp);
195
+ while (items.length > this.options.maxItems) {
196
+ const oldest = items.shift();
197
+ if (oldest) {
198
+ localStorage.removeItem(oldest.key);
199
+ }
200
+ }
201
+ let totalSize = items.reduce((sum, item) => sum + item.size, 0);
202
+ while (totalSize > this.options.maxSize && items.length > 0) {
203
+ const oldest = items.shift();
204
+ if (oldest) {
205
+ localStorage.removeItem(oldest.key);
206
+ totalSize -= oldest.size;
207
+ }
208
+ }
209
+ } catch (error) {
210
+ console.warn("[CacheManager] Failed to cleanup cache:", error);
211
+ }
212
+ }
213
+ getCurrentSize() {
214
+ if (typeof window === "undefined") return 0;
215
+ try {
216
+ let totalSize = 0;
217
+ Object.keys(localStorage).forEach((key) => {
218
+ if (key.startsWith("langie_")) {
219
+ const item = localStorage.getItem(key);
220
+ if (item) {
221
+ totalSize += new Blob([item]).size;
222
+ }
223
+ }
224
+ });
225
+ return totalSize;
226
+ } catch (error) {
227
+ console.warn("[CacheManager] Failed to calculate cache size:", error);
228
+ return 0;
229
+ }
230
+ }
231
+ getStats() {
232
+ if (typeof window === "undefined") {
233
+ return { size: 0, items: 0, maxSize: this.options.maxSize, maxItems: this.options.maxItems };
234
+ }
235
+ try {
236
+ let totalSize = 0;
237
+ let itemCount = 0;
238
+ Object.keys(localStorage).forEach((key) => {
239
+ if (key.startsWith("langie_")) {
240
+ const item = localStorage.getItem(key);
241
+ if (item) {
242
+ totalSize += new Blob([item]).size;
243
+ itemCount++;
244
+ }
245
+ }
246
+ });
247
+ return {
248
+ size: totalSize,
249
+ items: itemCount,
250
+ maxSize: this.options.maxSize,
251
+ maxItems: this.options.maxItems
252
+ };
253
+ } catch (error) {
254
+ console.warn("[CacheManager] Failed to get cache stats:", error);
255
+ return { size: 0, items: 0, maxSize: this.options.maxSize, maxItems: this.options.maxItems };
256
+ }
257
+ }
258
+ };
259
+ var cacheManager = new CacheManager();
260
+ var setCache = (key, data, ttl) => {
261
+ return cacheManager.set(key, data, ttl);
262
+ };
263
+ var getCache = (key) => {
264
+ return cacheManager.get(key);
265
+ };
266
+ var removeCache = (key) => {
267
+ return cacheManager.remove(key);
268
+ };
269
+ var clearCache = (pattern) => {
270
+ return cacheManager.clear(pattern);
271
+ };
272
+ var getCacheStats = () => {
273
+ return cacheManager.getStats();
274
+ };
275
+
276
+ // src/composables/useLangie-core.ts
277
+ var availableLanguages = ref([]);
278
+ var translations = reactive({});
279
+ var uiTranslations = reactive({});
280
+ var currentLanguage = ref("en");
281
+ var _translatorHost = DEFAULT_API_HOST;
282
+ var _autoSelected = false;
283
+ var _languagesCache = null;
284
+ var _languagesPromise = null;
285
+ function useLangieCore(options = {}) {
286
+ if (options.translatorHost) {
287
+ _translatorHost = options.translatorHost;
288
+ }
289
+ const translatorHost = _translatorHost;
290
+ const defaultLanguage = options.defaultLanguage || "en";
291
+ const isLoading = ref(false);
292
+ if (defaultLanguage !== "en" && currentLanguage.value === "en") {
293
+ currentLanguage.value = defaultLanguage;
294
+ }
295
+ if (typeof window !== "undefined" && currentLanguage.value === "en") {
296
+ const savedLanguage = localStorage.getItem("interface_language");
297
+ if (savedLanguage) {
298
+ currentLanguage.value = savedLanguage;
299
+ }
300
+ }
301
+ if (typeof window !== "undefined") {
302
+ const cachedLanguages = getCache("langie_languages_cache");
303
+ if (cachedLanguages && availableLanguages.value.length === 0) {
304
+ availableLanguages.value = cachedLanguages;
305
+ _languagesCache = cachedLanguages;
306
+ devDebug("[useLangie] Loaded languages from cache:", cachedLanguages.length);
307
+ } else if (cachedLanguages) {
308
+ devDebug(
309
+ "[useLangie] Cache exists but languages already loaded:",
310
+ availableLanguages.value.length
311
+ );
312
+ } else {
313
+ devDebug("[useLangie] No cached languages found");
314
+ }
315
+ }
316
+ const setLanguage = (lang) => {
317
+ if (lang && lang !== currentLanguage.value) {
318
+ }
319
+ currentLanguage.value = lang;
320
+ if (typeof window !== "undefined") {
321
+ localStorage.setItem("interface_language", lang);
322
+ }
323
+ };
324
+ const fetchLanguages = async (opts = {}) => {
325
+ const { force = false, country: explicitCountry } = opts;
326
+ devDebug("[useLangie] fetchLanguages called:", {
327
+ force,
328
+ hasCache: !!_languagesCache,
329
+ hasPromise: !!_languagesPromise,
330
+ availableLanguagesLength: availableLanguages.value.length
331
+ });
332
+ if (!force) {
333
+ if (_languagesCache) {
334
+ devDebug("[useLangie] Returning cached languages:", _languagesCache.length);
335
+ return _languagesCache;
336
+ }
337
+ if (_languagesPromise) {
338
+ devDebug("[useLangie] Returning existing promise");
339
+ return _languagesPromise;
340
+ }
341
+ }
342
+ try {
343
+ let countryHint = explicitCountry || null;
344
+ if (!countryHint && !_languagesCache && availableLanguages.value && availableLanguages.value.length) {
345
+ const currentLang = currentLanguage.value;
346
+ const entry = availableLanguages.value.find(
347
+ (l) => l.code === currentLang
348
+ );
349
+ if (entry) {
350
+ const c = Array.isArray(entry.flag_country) ? entry.flag_country[0] : typeof entry.flag_country === "string" ? entry.flag_country.split(",")[0] : null;
351
+ if (c) countryHint = c.toUpperCase();
352
+ }
353
+ }
354
+ if (!countryHint && !_languagesCache && typeof window !== "undefined") {
355
+ const cc = await getCountryCode();
356
+ countryHint = cc;
357
+ }
358
+ let url = "/languages";
359
+ const language = typeof navigator !== "undefined" && navigator.languages && navigator.languages[0] || typeof navigator !== "undefined" && navigator.language || "";
360
+ const timezone = typeof Intl !== "undefined" && Intl.DateTimeFormat().resolvedOptions().timeZone || "";
361
+ const params = new URLSearchParams();
362
+ if (countryHint) params.append("country", countryHint);
363
+ if (language) params.append("language", language);
364
+ if (timezone) params.append("timezone", timezone);
365
+ if (Array.from(params).length > 0) url += `?${params.toString()}`;
366
+ _languagesPromise = fetch(`${translatorHost}${url}`).then((res) => res.json());
367
+ const response = await _languagesPromise;
368
+ const rawList = Array.isArray(response) ? response : response.languages || [];
369
+ const mapped = rawList.map(
370
+ (lang) => {
371
+ let flag = lang.flag_country || lang.code;
372
+ if (Array.isArray(flag)) {
373
+ flag = flag[0];
374
+ }
375
+ return {
376
+ ...lang,
377
+ value: lang.code,
378
+ flag_country: flag
379
+ };
380
+ }
381
+ );
382
+ const filtered = mapped.filter((l) => {
383
+ if (l.code.startsWith("sr")) {
384
+ return l.code === "sr-latn" || l.code === "sr-cyrl";
385
+ }
386
+ return true;
387
+ });
388
+ availableLanguages.value = filtered;
389
+ devDebug("[useLangie] Set availableLanguages:", filtered.length);
390
+ if (typeof window !== "undefined") {
391
+ const saved = setCache("langie_languages_cache", filtered, 7 * 24 * 60 * 60 * 1e3);
392
+ devDebug(
393
+ "[useLangie] Saved languages to cache:",
394
+ saved ? "success" : "failed",
395
+ filtered.length
396
+ );
397
+ }
398
+ if (!_autoSelected && !localStorage.getItem("interface_language")) {
399
+ const locale = typeof navigator !== "undefined" ? navigator.languages?.[0] || navigator.language || "" : "";
400
+ const browserCode = locale.split("-")[0];
401
+ if (browserCode) {
402
+ let pick = void 0;
403
+ if (browserCode === "sr") {
404
+ const isLatin = /latn/i.test(locale) || locale === "sr";
405
+ const target = isLatin ? "sr-latn" : "sr-cyrl";
406
+ pick = mapped.find((l) => l.value === target);
407
+ } else if (browserCode === "sh") {
408
+ pick = mapped.find((l) => l.value === "sr-latn") || mapped.find((l) => l.code.startsWith("sr"));
409
+ } else {
410
+ pick = mapped.find((l) => l.value === browserCode);
411
+ if (!pick) {
412
+ pick = mapped.find((l) => l.code.startsWith(browserCode));
413
+ }
414
+ }
415
+ if (pick && pick.value) {
416
+ setLanguage(pick.value);
417
+ }
418
+ }
419
+ _autoSelected = true;
420
+ }
421
+ _languagesCache = filtered;
422
+ _languagesPromise = null;
423
+ return filtered;
424
+ } catch (error) {
425
+ devDebug("[useLangie] Language fetch error:", { error });
426
+ return [];
427
+ }
428
+ };
429
+ const clearTranslations = () => {
430
+ Object.keys(translations).forEach((key) => delete translations[key]);
431
+ Object.keys(uiTranslations).forEach((key) => delete uiTranslations[key]);
432
+ if (typeof window !== "undefined") {
433
+ clearCache("translations_cache");
434
+ clearCache("ui_translations_cache");
435
+ }
436
+ };
437
+ return {
438
+ availableLanguages,
439
+ translations,
440
+ uiTranslations,
441
+ currentLanguage,
442
+ isLoading,
443
+ setLanguage,
444
+ fetchLanguages,
445
+ clearTranslations,
446
+ translatorHost
447
+ };
448
+ }
449
+
450
+ // src/composables/useLangie-batching.ts
451
+ var TranslationBatching = class {
452
+ constructor(options = {}, translatorHost, currentLanguage2, onBatchComplete) {
453
+ this.options = options;
454
+ this.translatorHost = translatorHost;
455
+ this.currentLanguage = currentLanguage2;
456
+ this.onBatchComplete = onBatchComplete;
457
+ }
458
+ pendingRequests = /* @__PURE__ */ new Set();
459
+ queueMap = /* @__PURE__ */ new Map();
460
+ flushTimeout = null;
461
+ queuedThisTick = /* @__PURE__ */ new Set();
462
+ clearQueuedThisTickScheduled = false;
463
+ firstItemTime = null;
464
+ get initialBatchDelay() {
465
+ return this.options.initialBatchDelay ?? 50;
466
+ }
467
+ get followupBatchDelay() {
468
+ return this.options.followupBatchDelay ?? 10;
469
+ }
470
+ get maxBatchSize() {
471
+ return this.options.maxBatchSize ?? 50;
472
+ }
473
+ get maxWaitTime() {
474
+ return this.options.maxWaitTime ?? 1e3;
475
+ }
476
+ scheduleClearQueuedThisTick() {
477
+ if (this.clearQueuedThisTickScheduled) return;
478
+ this.clearQueuedThisTickScheduled = true;
479
+ queueMicrotask(() => {
480
+ this.queuedThisTick.clear();
481
+ this.clearQueuedThisTickScheduled = false;
482
+ });
483
+ }
484
+ flushQueues = async () => {
485
+ const allRequests = [];
486
+ for (const [batchKey, map] of Array.from(this.queueMap.entries())) {
487
+ if (!map || map.size === 0) {
488
+ this.queueMap.delete(batchKey);
489
+ continue;
490
+ }
491
+ const [from, to] = batchKey.split("|");
492
+ for (const [cacheKey, item] of map.entries()) {
493
+ allRequests.push({
494
+ [API_FIELD_TEXT]: item[API_FIELD_TEXT],
495
+ [API_FIELD_CTX]: item[API_FIELD_CTX],
496
+ [API_FIELD_FROM]: from,
497
+ [API_FIELD_TO]: to,
498
+ cacheKey,
499
+ __explicitToLang: item.__explicitToLang
500
+ });
501
+ }
502
+ this.queueMap.delete(batchKey);
503
+ }
504
+ if (allRequests.length > 0) {
505
+ devDebug(
506
+ "[TranslationBatching] Sending batch:",
507
+ allRequests.length,
508
+ "translation items",
509
+ allRequests
510
+ );
511
+ const chunks = this.chunkArray(allRequests, this.maxBatchSize);
512
+ for (const chunk of chunks) {
513
+ try {
514
+ await this.fetchAndCacheBatchMixed(chunk);
515
+ } catch (error) {
516
+ devDebug("[TranslationBatching] Batch translation error:", error);
517
+ chunk.forEach((req) => this.pendingRequests.delete(req.cacheKey));
518
+ }
519
+ }
520
+ }
521
+ this.firstItemTime = null;
522
+ };
523
+ chunkArray(array, size) {
524
+ const chunks = [];
525
+ for (let i = 0; i < array.length; i += size) {
526
+ chunks.push(array.slice(i, i + size));
527
+ }
528
+ return chunks;
529
+ }
530
+ scheduleFlush = () => {
531
+ if (this.flushTimeout) {
532
+ clearTimeout(this.flushTimeout);
533
+ }
534
+ let totalItems = 0;
535
+ for (const map of this.queueMap.values()) {
536
+ totalItems += map.size;
537
+ }
538
+ if (this.firstItemTime && Date.now() - this.firstItemTime >= this.maxWaitTime) {
539
+ devDebug("[TranslationBatching] Flushing due to maximum wait time:", totalItems, "items");
540
+ this.flushQueues();
541
+ return;
542
+ }
543
+ const delay = this.queueMap.size === 1 ? this.initialBatchDelay : this.followupBatchDelay;
544
+ devDebug(
545
+ "[TranslationBatching] Scheduling flush in",
546
+ delay,
547
+ "ms",
548
+ Array.from(this.queueMap.entries())
549
+ );
550
+ this.flushTimeout = setTimeout(() => {
551
+ this.flushQueues();
552
+ this.flushTimeout = null;
553
+ }, delay);
554
+ };
555
+ queueTranslation(text, ctx, from, to, cacheKey, explicitToLang) {
556
+ if (this.pendingRequests.has(cacheKey) || this.queuedThisTick.has(cacheKey)) {
557
+ devDebug("[TranslationBatching] Skipping duplicate:", cacheKey);
558
+ return;
559
+ }
560
+ if (this.firstItemTime === null) {
561
+ this.firstItemTime = Date.now();
562
+ }
563
+ this.queuedThisTick.add(cacheKey);
564
+ this.scheduleClearQueuedThisTick();
565
+ this.pendingRequests.add(cacheKey);
566
+ const batchKey = `${from}|${to}`;
567
+ if (!this.queueMap.has(batchKey)) {
568
+ this.queueMap.set(batchKey, /* @__PURE__ */ new Map());
569
+ }
570
+ this.queueMap.get(batchKey).set(cacheKey, {
571
+ [API_FIELD_TEXT]: text,
572
+ [API_FIELD_CTX]: ctx,
573
+ __explicitToLang: explicitToLang
574
+ });
575
+ devDebug("[TranslationBatching] Queued translation:", {
576
+ text,
577
+ ctx,
578
+ from,
579
+ to,
580
+ cacheKey,
581
+ batchKey,
582
+ explicitToLang
583
+ });
584
+ this.scheduleFlush();
585
+ }
586
+ async fetchAndCacheBatchMixed(requests) {
587
+ const grouped = {};
588
+ requests.forEach((req) => {
589
+ const key = `${req.from}|${req.to}`;
590
+ if (!grouped[key]) grouped[key] = [];
591
+ grouped[key].push(req);
592
+ });
593
+ const allResults = [];
594
+ const allRequests = [];
595
+ for (const [langPair, batchRequests] of Object.entries(grouped)) {
596
+ const [from, to] = langPair.split("|");
597
+ try {
598
+ const contexts = [...new Set(batchRequests.map((req) => req[API_FIELD_CTX]))];
599
+ const useGlobalContext = contexts.length === 1 && contexts[0] === "ui";
600
+ devDebug(
601
+ "[TranslationBatching] Sending batch for",
602
+ langPair,
603
+ "with",
604
+ batchRequests.length,
605
+ "items",
606
+ batchRequests
607
+ );
608
+ const response = await fetch(`${this.translatorHost}/translate`, {
609
+ method: "POST",
610
+ headers: {
611
+ "Content-Type": "application/json"
612
+ },
613
+ body: JSON.stringify({
614
+ translations: batchRequests.map((req) => ({
615
+ [API_FIELD_TEXT]: req[API_FIELD_TEXT],
616
+ ...useGlobalContext ? {} : { [API_FIELD_CTX]: req[API_FIELD_CTX] }
617
+ })),
618
+ [API_FIELD_FROM]: from,
619
+ [API_FIELD_TO]: to,
620
+ ...useGlobalContext ? { [API_FIELD_CTX]: "ui" } : {}
621
+ })
622
+ });
623
+ let result;
624
+ try {
625
+ result = await response.json();
626
+ } catch (parseError) {
627
+ devDebug("[TranslationBatching] Failed to parse response as JSON:", parseError);
628
+ throw new Error(`Translation request failed: ${response.status}`);
629
+ }
630
+ if (!response.ok) {
631
+ devDebug(
632
+ "[TranslationBatching] Translation request failed:",
633
+ response.status,
634
+ response.statusText
635
+ );
636
+ if (result && result[API_FIELD_ERROR]) {
637
+ devDebug(
638
+ "[TranslationBatching] HTTP error with API error message:",
639
+ result[API_FIELD_ERROR]
640
+ );
641
+ const errorResponses = batchRequests.map((req) => ({
642
+ [API_FIELD_TEXT]: req[API_FIELD_TEXT],
643
+ [API_FIELD_ERROR]: result[API_FIELD_ERROR]
644
+ }));
645
+ allResults.push({ [API_FIELD_TRANSLATIONS]: errorResponses });
646
+ allRequests.push(...batchRequests);
647
+ batchRequests.forEach((req) => {
648
+ this.pendingRequests.delete(req.cacheKey);
649
+ });
650
+ this.onBatchComplete(allResults, allRequests);
651
+ return;
652
+ }
653
+ throw new Error(`Translation request failed: ${response.status}`);
654
+ }
655
+ if (result[API_FIELD_ERROR]) {
656
+ devDebug("[TranslationBatching] Top-level API error:", result[API_FIELD_ERROR]);
657
+ const errorResponses = batchRequests.map((req) => ({
658
+ [API_FIELD_TEXT]: req[API_FIELD_TEXT],
659
+ [API_FIELD_ERROR]: result[API_FIELD_ERROR]
660
+ }));
661
+ allResults.push({ [API_FIELD_TRANSLATIONS]: errorResponses });
662
+ allRequests.push(...batchRequests);
663
+ batchRequests.forEach((req) => {
664
+ this.pendingRequests.delete(req.cacheKey);
665
+ });
666
+ } else {
667
+ if (result[API_FIELD_TRANSLATIONS]) {
668
+ result[API_FIELD_TRANSLATIONS].forEach((translation, index) => {
669
+ if (translation[API_FIELD_ERROR]) {
670
+ const originalText = batchRequests[index]?.[API_FIELD_TEXT];
671
+ devDebug(
672
+ "[TranslationBatching] Translation error for",
673
+ originalText,
674
+ ":",
675
+ translation[API_FIELD_ERROR]
676
+ );
677
+ }
678
+ });
679
+ }
680
+ allResults.push(result);
681
+ allRequests.push(...batchRequests);
682
+ batchRequests.forEach((req) => {
683
+ this.pendingRequests.delete(req.cacheKey);
684
+ });
685
+ }
686
+ } catch (error) {
687
+ devDebug("[TranslationBatching] Batch request failed for", langPair, ":", error);
688
+ batchRequests.forEach((req) => {
689
+ this.pendingRequests.delete(req.cacheKey);
690
+ });
691
+ throw error;
692
+ }
693
+ }
694
+ this.onBatchComplete(allResults, allRequests);
695
+ }
696
+ clearPending(cacheKey) {
697
+ this.pendingRequests.delete(cacheKey);
698
+ }
699
+ clearAllPending() {
700
+ this.pendingRequests.clear();
701
+ }
702
+ cleanup() {
703
+ this.clearAllPending();
704
+ this.queueMap.clear();
705
+ this.queuedThisTick.clear();
706
+ if (this.flushTimeout) {
707
+ clearTimeout(this.flushTimeout);
708
+ this.flushTimeout = null;
709
+ }
710
+ this.clearQueuedThisTickScheduled = false;
711
+ this.firstItemTime = null;
712
+ }
713
+ getStats() {
714
+ return {
715
+ pendingRequests: this.pendingRequests.size,
716
+ queuedBatches: this.queueMap.size,
717
+ queuedThisTick: this.queuedThisTick.size,
718
+ hasFlushTimeout: !!this.flushTimeout
719
+ };
720
+ }
721
+ };
722
+
723
+ // src/useLangie.ts
724
+ var globalLangieInstance = null;
725
+ if (typeof window !== "undefined" && window.__LANGIE_SINGLETON__) {
726
+ globalLangieInstance = window.__LANGIE_SINGLETON__;
727
+ }
728
+ function safeLocalStorageAccess(operation) {
729
+ if (typeof window === "undefined") return void 0;
730
+ try {
731
+ return operation();
732
+ } catch (e) {
733
+ devDebug("[useLangie] localStorage error:", e);
734
+ return void 0;
735
+ }
736
+ }
737
+ if (!globalLangieInstance && typeof window !== "undefined") {
738
+ const stored = safeLocalStorageAccess(() => localStorage.getItem("__LANGIE_SINGLETON_URL__"));
739
+ if (stored) {
740
+ const storedOptions = { translatorHost: stored };
741
+ globalLangieInstance = createLangieInstance(storedOptions);
742
+ }
743
+ }
744
+ function createLangieInstance(options = {}) {
745
+ const core = useLangieCore(options);
746
+ const {
747
+ availableLanguages: availableLanguages2,
748
+ translations: translations2,
749
+ uiTranslations: uiTranslations2,
750
+ currentLanguage: currentLanguage2,
751
+ isLoading,
752
+ setLanguage,
753
+ fetchLanguages,
754
+ translatorHost,
755
+ clearTranslations
756
+ } = core;
757
+ const ltDefaults = {
758
+ ctx: "ui",
759
+ orig: ""
760
+ };
761
+ const setLtDefaults2 = (defaults) => {
762
+ Object.assign(ltDefaults, defaults);
763
+ };
764
+ const getLtDefaults2 = () => ({ ...ltDefaults });
765
+ const CACHE_KEY = "langie_translations_cache";
766
+ const UI_CACHE_KEY = "langie_ui_translations_cache";
767
+ const LANGUAGES_CACHE_KEY = "langie_languages_cache";
768
+ const loadCachedTranslations = () => {
769
+ if (typeof window === "undefined") return;
770
+ const cachedTranslations = getCache(CACHE_KEY);
771
+ const cachedUiTranslations = getCache(UI_CACHE_KEY);
772
+ if (cachedTranslations) {
773
+ const currentLang = currentLanguage2.value;
774
+ const langTranslations = cachedTranslations[currentLang] || {};
775
+ Object.assign(translations2, langTranslations);
776
+ }
777
+ if (cachedUiTranslations) {
778
+ const currentLang = currentLanguage2.value;
779
+ const langUiTranslations = cachedUiTranslations[currentLang] || {};
780
+ Object.assign(uiTranslations2, langUiTranslations);
781
+ }
782
+ };
783
+ const saveCachedTranslations = () => {
784
+ if (typeof window === "undefined") return;
785
+ const existingTranslations = getCache(CACHE_KEY) || {};
786
+ const existingUiTranslations = getCache(UI_CACHE_KEY) || {};
787
+ const currentLang = currentLanguage2.value;
788
+ existingTranslations[currentLang] = { ...translations2 };
789
+ existingUiTranslations[currentLang] = { ...uiTranslations2 };
790
+ setCache(CACHE_KEY, existingTranslations, 7 * 24 * 60 * 60 * 1e3);
791
+ setCache(UI_CACHE_KEY, existingUiTranslations, 7 * 24 * 60 * 60 * 1e3);
792
+ };
793
+ const loadCachedLanguages = () => {
794
+ if (typeof window === "undefined") return;
795
+ const cachedLanguages = getCache(LANGUAGES_CACHE_KEY);
796
+ if (cachedLanguages) {
797
+ availableLanguages2.value = cachedLanguages;
798
+ }
799
+ };
800
+ loadCachedTranslations();
801
+ loadCachedLanguages();
802
+ watch(currentLanguage2, () => {
803
+ Object.keys(translations2).forEach((key) => delete translations2[key]);
804
+ Object.keys(uiTranslations2).forEach((key) => delete uiTranslations2[key]);
805
+ loadCachedTranslations();
806
+ });
807
+ const batching = new TranslationBatching(
808
+ {
809
+ initialBatchDelay: options.initialBatchDelay,
810
+ followupBatchDelay: options.followupBatchDelay,
811
+ maxBatchSize: options.maxBatchSize,
812
+ maxWaitTime: options.maxWaitTime
813
+ },
814
+ translatorHost,
815
+ () => currentLanguage2.value,
816
+ (results, requests) => {
817
+ requests.forEach((req) => {
818
+ const cacheKey = `${req[API_FIELD_TEXT]}|${req[API_FIELD_CTX]}`;
819
+ const languageCacheKey = `${cacheKey}|${req[API_FIELD_FROM]}|${req[API_FIELD_TO]}`;
820
+ recentlyQueued.delete(languageCacheKey);
821
+ });
822
+ let translationsArray = [];
823
+ results.forEach((result) => {
824
+ if (Array.isArray(result)) {
825
+ translationsArray = translationsArray.concat(result);
826
+ } else if (result.translations && Array.isArray(result.translations)) {
827
+ translationsArray = translationsArray.concat(result.translations);
828
+ }
829
+ });
830
+ translationsArray.forEach((translation, idx) => {
831
+ const request = requests[idx];
832
+ if (!request) {
833
+ devDebug("[useLangie] No matching request for translation:", translation);
834
+ return;
835
+ }
836
+ const originalText = request[API_FIELD_TEXT];
837
+ const originalCtx = request[API_FIELD_CTX] ?? (ltDefaults.ctx || "ui");
838
+ if (translation[API_FIELD_ERROR]) {
839
+ devDebug(
840
+ "[useLangie] Translation error for",
841
+ originalText,
842
+ ":",
843
+ translation[API_FIELD_ERROR]
844
+ );
845
+ const errorKey = `${originalText}|${originalCtx}|${request[API_FIELD_FROM]}|${request[API_FIELD_TO]}`;
846
+ translationErrors.set(errorKey, translation[API_FIELD_ERROR]);
847
+ return;
848
+ }
849
+ if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
850
+ return;
851
+ }
852
+ const translatedText = translation[API_FIELD_TEXT];
853
+ if (translatedText) {
854
+ const requestedLanguage = request[API_FIELD_TO];
855
+ const explicitToLang = request.__explicitToLang;
856
+ if (!explicitToLang && requestedLanguage !== currentLanguage2.value) {
857
+ devDebug("[useLangie] Skipping outdated translation:", {
858
+ original: originalText,
859
+ translated: translatedText,
860
+ requestedLanguage,
861
+ currentLanguage: currentLanguage2.value
862
+ });
863
+ return;
864
+ }
865
+ if (translatedText === originalText) {
866
+ devDebug("[useLangie] Skipping cache for identical translation:", {
867
+ original: originalText,
868
+ translated: translatedText,
869
+ context: originalCtx
870
+ });
871
+ return;
872
+ }
873
+ const effectiveCtx = originalCtx;
874
+ const cacheKey = `${originalText}|${effectiveCtx}`;
875
+ const cache = effectiveCtx === "ui" ? uiTranslations2 : translations2;
876
+ cache[cacheKey] = translatedText;
877
+ devDebug("[useLangie] Cached translation:", {
878
+ original: originalText,
879
+ translated: translatedText,
880
+ context: effectiveCtx,
881
+ cacheKey,
882
+ language: requestedLanguage
883
+ });
884
+ saveCachedTranslations();
885
+ }
886
+ });
887
+ }
888
+ );
889
+ const recentlyQueued = /* @__PURE__ */ new Set();
890
+ const pendingTimeouts = /* @__PURE__ */ new Set();
891
+ const translationErrors = /* @__PURE__ */ new Map();
892
+ const translateInternal = (text, ctx, originalLang, toLang, reactive2 = false) => {
893
+ if (reactive2) {
894
+ void currentLanguage2.value;
895
+ }
896
+ const from = originalLang || ltDefaults.orig || "";
897
+ const to = toLang || currentLanguage2.value;
898
+ if (from === to) {
899
+ return text;
900
+ }
901
+ const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
902
+ const cacheKey = `${text}|${effectiveCtx}`;
903
+ const cache = effectiveCtx === "ui" ? uiTranslations2 : translations2;
904
+ if (cache[cacheKey]) {
905
+ return cache[cacheKey];
906
+ }
907
+ const errorKey = `${text}|${effectiveCtx}|${from}|${to}`;
908
+ if (translationErrors.has(errorKey)) {
909
+ return text;
910
+ }
911
+ const languageCacheKey = `${cacheKey}|${from}|${to}`;
912
+ if (recentlyQueued.has(languageCacheKey)) {
913
+ return text;
914
+ }
915
+ batching.queueTranslation(text, effectiveCtx, from, to, cacheKey, toLang !== void 0);
916
+ recentlyQueued.add(languageCacheKey);
917
+ const clearDelay = 100;
918
+ const timeoutId = setTimeout(() => {
919
+ recentlyQueued.delete(languageCacheKey);
920
+ pendingTimeouts.delete(timeoutId);
921
+ }, clearDelay);
922
+ pendingTimeouts.add(timeoutId);
923
+ return text;
924
+ };
925
+ const l = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, false);
926
+ const lr = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, true);
927
+ const fetchAndCacheBatch = async (items, from, to = currentLanguage2.value, globalCtx) => {
928
+ if (items.length === 0) return;
929
+ const effectiveFrom = from || ltDefaults.orig || "";
930
+ if (effectiveFrom === to) {
931
+ return;
932
+ }
933
+ isLoading.value = true;
934
+ try {
935
+ const effectiveCtx = globalCtx || ltDefaults.ctx || "ui";
936
+ const response = await fetch(`${translatorHost}/translate`, {
937
+ method: "POST",
938
+ headers: {
939
+ "Content-Type": "application/json"
940
+ },
941
+ body: JSON.stringify({
942
+ translations: items.map((item) => ({
943
+ [API_FIELD_TEXT]: item[API_FIELD_TEXT],
944
+ [API_FIELD_CTX]: item[API_FIELD_CTX] || effectiveCtx
945
+ })),
946
+ [API_FIELD_FROM]: effectiveFrom,
947
+ [API_FIELD_TO]: to
948
+ })
949
+ });
950
+ if (!response.ok) {
951
+ throw new Error(`Translation request failed: ${response.status}`);
952
+ }
953
+ const result = await response.json();
954
+ if (result[API_FIELD_ERROR]) {
955
+ devDebug("[useLangie] Top-level API error:", result[API_FIELD_ERROR]);
956
+ const errorResponses = items.map((item) => ({
957
+ [API_FIELD_TEXT]: item[API_FIELD_TEXT],
958
+ [API_FIELD_ERROR]: result[API_FIELD_ERROR]
959
+ }));
960
+ errorResponses.forEach((translation, index) => {
961
+ const item = items[index];
962
+ const originalText = item?.[API_FIELD_TEXT];
963
+ if (!originalText) {
964
+ return;
965
+ }
966
+ if (translation[API_FIELD_ERROR]) {
967
+ devDebug(
968
+ "[useLangie] Translation error for",
969
+ originalText,
970
+ ":",
971
+ translation[API_FIELD_ERROR]
972
+ );
973
+ const errorKey = `${originalText}|${item[API_FIELD_CTX] || effectiveCtx}|${effectiveFrom}|${to}`;
974
+ translationErrors.set(errorKey, translation[API_FIELD_ERROR]);
975
+ return;
976
+ }
977
+ });
978
+ return;
979
+ }
980
+ if (result[API_FIELD_TRANSLATIONS]) {
981
+ result[API_FIELD_TRANSLATIONS].forEach(
982
+ (translation, index) => {
983
+ const item = items[index];
984
+ const originalText = item?.[API_FIELD_TEXT];
985
+ if (!originalText) {
986
+ return;
987
+ }
988
+ if (translation[API_FIELD_ERROR]) {
989
+ devDebug(
990
+ "[useLangie] Translation error for",
991
+ originalText,
992
+ ":",
993
+ translation[API_FIELD_ERROR]
994
+ );
995
+ return;
996
+ }
997
+ if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
998
+ return;
999
+ }
1000
+ const translatedText = translation[API_FIELD_TEXT];
1001
+ if (translatedText) {
1002
+ if (to !== currentLanguage2.value) {
1003
+ devDebug("[useLangie] Skipping outdated translation (batch):", {
1004
+ original: originalText,
1005
+ translated: translatedText,
1006
+ requestedLanguage: to,
1007
+ currentLanguage: currentLanguage2.value
1008
+ });
1009
+ return;
1010
+ }
1011
+ if (translatedText === originalText) {
1012
+ devDebug("[useLangie] Skipping cache for identical translation (batch):", {
1013
+ original: originalText,
1014
+ translated: translatedText,
1015
+ context: item[API_FIELD_CTX] || effectiveCtx
1016
+ });
1017
+ return;
1018
+ }
1019
+ const originalCtx = item[API_FIELD_CTX];
1020
+ const translationCtx = originalCtx !== void 0 ? originalCtx : effectiveCtx;
1021
+ const cacheKey = `${originalText}|${translationCtx}`;
1022
+ const cache = translationCtx === "ui" ? uiTranslations2 : translations2;
1023
+ cache[cacheKey] = translatedText;
1024
+ devDebug("[useLangie] Cached translation (batch):", {
1025
+ original: originalText,
1026
+ translated: translatedText,
1027
+ context: translationCtx,
1028
+ cacheKey,
1029
+ language: to
1030
+ });
1031
+ saveCachedTranslations();
1032
+ }
1033
+ }
1034
+ );
1035
+ }
1036
+ } catch (error) {
1037
+ console.error("[useLangie] Translation error:", error);
1038
+ } finally {
1039
+ isLoading.value = false;
1040
+ }
1041
+ };
1042
+ watch(currentLanguage2, () => {
1043
+ recentlyQueued.clear();
1044
+ loadCachedTranslations();
1045
+ batching.cleanup();
1046
+ });
1047
+ return {
1048
+ // Core functionality
1049
+ availableLanguages: availableLanguages2,
1050
+ translations: translations2,
1051
+ uiTranslations: uiTranslations2,
1052
+ currentLanguage: currentLanguage2,
1053
+ isLoading,
1054
+ setLanguage,
1055
+ fetchLanguages,
1056
+ translatorHost,
1057
+ // Translation functions
1058
+ l,
1059
+ lr,
1060
+ fetchAndCacheBatch,
1061
+ // Error handling
1062
+ getTranslationError: (text, ctx, from, to) => {
1063
+ const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
1064
+ const effectiveFrom = from || ltDefaults.orig || "";
1065
+ const effectiveTo = to || currentLanguage2.value;
1066
+ const errorKey = `${text}|${effectiveCtx}|${effectiveFrom}|${effectiveTo}`;
1067
+ return translationErrors.get(errorKey) || null;
1068
+ },
1069
+ // Utility functions
1070
+ cleanup: () => {
1071
+ clearTranslations();
1072
+ batching.cleanup();
1073
+ pendingTimeouts.forEach((id) => clearTimeout(id));
1074
+ pendingTimeouts.clear();
1075
+ translationErrors.clear();
1076
+ },
1077
+ getBatchingStats: () => batching.getStats(),
1078
+ // lt component defaults management
1079
+ setLtDefaults: setLtDefaults2,
1080
+ getLtDefaults: getLtDefaults2
1081
+ };
1082
+ }
1083
+ function getGlobalLangieInstance() {
1084
+ if (typeof window !== "undefined") {
1085
+ return window.__LANGIE_SINGLETON__ || null;
1086
+ }
1087
+ return globalLangieInstance;
1088
+ }
1089
+ function setGlobalLangieInstance(instance, options) {
1090
+ if (typeof window !== "undefined") {
1091
+ ;
1092
+ window.__LANGIE_SINGLETON__ = instance;
1093
+ if (options && options.translatorHost) {
1094
+ safeLocalStorageAccess(
1095
+ () => localStorage.setItem("__LANGIE_SINGLETON_URL__", options.translatorHost)
1096
+ );
1097
+ }
1098
+ }
1099
+ globalLangieInstance = instance;
1100
+ }
1101
+ function useLangie(options = {}) {
1102
+ const globalInstance = getGlobalLangieInstance();
1103
+ if (globalInstance) {
1104
+ const currentHost = globalInstance.translatorHost;
1105
+ const newHost = options.translatorHost;
1106
+ if (!options.translatorHost || currentHost === newHost) {
1107
+ return globalInstance;
1108
+ }
1109
+ }
1110
+ const instance = createLangieInstance(options);
1111
+ setGlobalLangieInstance(instance, options);
1112
+ return instance;
1113
+ }
1114
+ function setLtDefaults(defaults) {
1115
+ const instance = getGlobalLangieInstance();
1116
+ if (instance && instance.setLtDefaults) {
1117
+ instance.setLtDefaults(defaults);
1118
+ }
1119
+ }
1120
+ function getLtDefaults() {
1121
+ const instance = getGlobalLangieInstance();
1122
+ if (instance && instance.getLtDefaults) {
1123
+ return instance.getLtDefaults();
1124
+ }
1125
+ return { ctx: "ui", orig: "" };
1126
+ }
1127
+
1128
+ // src/core.ts
1129
+ var DEFAULT_TRANSLATOR_HOST = "http://localhost:8081";
1130
+ var translationCache = /* @__PURE__ */ new Map();
1131
+ function getTranslationCacheKey(serviceTranslations, options) {
1132
+ return JSON.stringify({
1133
+ translations: serviceTranslations.map((t) => ({
1134
+ [API_FIELD_TEXT]: t[API_FIELD_TEXT],
1135
+ [API_FIELD_FROM]: t[API_FIELD_FROM],
1136
+ [API_FIELD_TO]: t[API_FIELD_TO],
1137
+ [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
1138
+ })),
1139
+ host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
1140
+ context: options[API_FIELD_CTX] || "ui"
1141
+ });
1142
+ }
1143
+ async function translateBatch(translations2 = [], options = {}) {
1144
+ if (!Array.isArray(translations2) || translations2.length === 0) {
1145
+ throw new Error("translations must be a non-empty array");
1146
+ }
1147
+ const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
1148
+ const apiKey = options.apiKey || process.env.TRANSLATOR_API_KEY;
1149
+ const serviceTranslations = [];
1150
+ const indexMap = [];
1151
+ translations2.forEach((tr, idx) => {
1152
+ const from = (tr[API_FIELD_FROM] || "").toLowerCase();
1153
+ const to = (tr[API_FIELD_TO] || "").toLowerCase();
1154
+ if (from === to) {
1155
+ return;
1156
+ }
1157
+ serviceTranslations.push(tr);
1158
+ indexMap.push(idx);
1159
+ });
1160
+ let serviceResults = [];
1161
+ if (serviceTranslations.length > 0) {
1162
+ const cacheKey = getTranslationCacheKey(serviceTranslations, options);
1163
+ if (translationCache.has(cacheKey)) {
1164
+ serviceResults = await translationCache.get(cacheKey);
1165
+ } else {
1166
+ const translationPromise = (async () => {
1167
+ try {
1168
+ const controller = new AbortController();
1169
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
1170
+ const resp = await fetch(`${translatorHost}/translate`, {
1171
+ method: "POST",
1172
+ headers: {
1173
+ "Content-Type": "application/json",
1174
+ ...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
1175
+ },
1176
+ body: JSON.stringify({
1177
+ [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({
1178
+ [API_FIELD_TEXT]: t[API_FIELD_TEXT],
1179
+ [API_FIELD_FROM]: t[API_FIELD_FROM],
1180
+ [API_FIELD_TO]: t[API_FIELD_TO],
1181
+ [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
1182
+ })),
1183
+ [API_FIELD_CTX]: options[API_FIELD_CTX] || "ui"
1184
+ }),
1185
+ signal: controller.signal
1186
+ });
1187
+ clearTimeout(timeoutId);
1188
+ if (!resp.ok) {
1189
+ console.error(
1190
+ "[translator-sdk] Translator error response:",
1191
+ resp.status,
1192
+ resp.statusText
1193
+ );
1194
+ throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`);
1195
+ }
1196
+ let parsed;
1197
+ try {
1198
+ parsed = await resp.clone().json();
1199
+ } catch (jsonErr) {
1200
+ console.error("[translator-sdk] Failed to parse JSON response:", jsonErr);
1201
+ throw new Error(`Failed to parse JSON response: ${jsonErr}`);
1202
+ }
1203
+ const data = parsed || {};
1204
+ if (data[API_FIELD_ERROR]) {
1205
+ console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR]);
1206
+ return serviceTranslations.map((translation) => ({
1207
+ [API_FIELD_TEXT]: translation[API_FIELD_TEXT],
1208
+ [API_FIELD_ERROR]: data[API_FIELD_ERROR]
1209
+ }));
1210
+ }
1211
+ const results = Array.isArray(data[API_FIELD_TRANSLATIONS]) ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {
1212
+ const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || "";
1213
+ if (translation[API_FIELD_ERROR]) {
1214
+ console.warn(
1215
+ `[translator-sdk] Translation error for "${originalText}":`,
1216
+ translation[API_FIELD_ERROR]
1217
+ );
1218
+ return {
1219
+ [API_FIELD_TEXT]: originalText,
1220
+ // Return original text on error
1221
+ [API_FIELD_ERROR]: translation[API_FIELD_ERROR]
1222
+ };
1223
+ }
1224
+ if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
1225
+ return {
1226
+ [API_FIELD_TEXT]: originalText,
1227
+ // For detection, return original text
1228
+ [API_FIELD_FROM]: translation[API_FIELD_FROM]
1229
+ };
1230
+ }
1231
+ return {
1232
+ [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText
1233
+ };
1234
+ }) : data[API_FIELD_TEXT] ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }] : [];
1235
+ return results;
1236
+ } catch (error) {
1237
+ translationCache.delete(cacheKey);
1238
+ if (error instanceof Error && error.name === "AbortError") {
1239
+ const message2 = `Translator request to ${translatorHost} timed out after 5 seconds.`;
1240
+ console.error(message2);
1241
+ throw new Error(message2);
1242
+ }
1243
+ const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`;
1244
+ console.error(message);
1245
+ throw new Error(message);
1246
+ }
1247
+ })();
1248
+ translationCache.set(cacheKey, translationPromise);
1249
+ serviceResults = await translationPromise;
1250
+ }
1251
+ }
1252
+ const final = translations2.map((tr, idx) => {
1253
+ const from = (tr[API_FIELD_FROM] || "").toLowerCase();
1254
+ const to = (tr[API_FIELD_TO] || "").toLowerCase();
1255
+ if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1256
+ const svcIdx = indexMap.indexOf(idx);
1257
+ if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1258
+ return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1259
+ });
1260
+ return final;
1261
+ }
1262
+ function clearTranslationCache() {
1263
+ translationCache.clear();
1264
+ }
1265
+ function getTranslationCacheSize() {
1266
+ return translationCache.size;
1267
+ }
1268
+ async function fetchAvailableLanguages(options = {}) {
1269
+ const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
1270
+ const apiKey = options.apiKey || process.env.TRANSLATOR_API_KEY;
1271
+ const minPop = options.minPopularity !== void 0 ? Number(options.minPopularity) : Number.parseFloat(process.env.MIN_LANGUAGE_POPULARITY || "0.1");
1272
+ const queryParams = [];
1273
+ if (options.country) queryParams.push(`country=${encodeURIComponent(options.country)}`);
1274
+ if (options.region) queryParams.push(`region=${encodeURIComponent(options.region)}`);
1275
+ const queryStr = queryParams.length ? `?${queryParams.join("&")}` : "";
1276
+ const requestUrl = `${translatorHost}/languages${queryStr}`;
1277
+ try {
1278
+ const resp = await fetch(requestUrl, {
1279
+ method: "GET",
1280
+ headers: {
1281
+ "Content-Type": "application/json",
1282
+ ...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
1283
+ }
1284
+ });
1285
+ if (!resp.ok) {
1286
+ throw new Error(`Translator languages error: ${resp.status}`);
1287
+ }
1288
+ const data = await resp.json();
1289
+ const languages = Array.isArray(data) ? data : data.languages || [];
1290
+ const filtered = languages.filter((lang) => {
1291
+ if (lang.popularity === void 0 || lang.popularity === null) return true;
1292
+ return Number(lang.popularity) >= minPop;
1293
+ });
1294
+ return filtered;
1295
+ } catch (error) {
1296
+ console.error("[translator-sdk] Languages fetch error:", error);
1297
+ throw error;
1298
+ }
1299
+ }
1300
+
1301
+ // vue-script:/Users/nlit/projects/langie-sdk/src/components/LanguageSelect.vue?type=script
1302
+ import { useCssVars as _useCssVars, defineComponent as _defineComponent } from "vue";
1303
+ import { createCommentVNode as _createCommentVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, unref as _unref, withCtx as _withCtx, createBlock as _createBlock, Fragment as _Fragment, normalizeClass as _normalizeClass } from "vue";
1304
+ import "@vueform/multiselect/themes/default.css";
1305
+ import { ref as ref2, computed, nextTick } from "vue";
1306
+ import Multiselect from "@vueform/multiselect";
1307
+ import Fuse from "fuse.js";
1308
+
1309
+ // src/language-aliases/european.ts
1310
+ var EUROPEAN_LANGUAGES = [
1311
+ // Major European Languages
1312
+ {
1313
+ lang: "english",
1314
+ match: [
1315
+ "eng",
1316
+ "en",
1317
+ "united states",
1318
+ "united states of america",
1319
+ "usa",
1320
+ "us",
1321
+ "great britain",
1322
+ "britain",
1323
+ "british",
1324
+ "england",
1325
+ "united kingdom",
1326
+ "uk"
1327
+ ],
1328
+ suggest: ["irish", "scottish", "welsh"]
1329
+ },
1330
+ {
1331
+ lang: "spanish",
1332
+ match: [
1333
+ "spa",
1334
+ "es",
1335
+ "esp",
1336
+ "spain",
1337
+ "espa\xF1ol",
1338
+ "mexico",
1339
+ "mexican",
1340
+ "argentina",
1341
+ "colombia",
1342
+ "venezuela"
1343
+ ],
1344
+ suggest: ["catalan", "galician", "basque"]
1345
+ },
1346
+ {
1347
+ lang: "french",
1348
+ match: [
1349
+ "fra",
1350
+ "fr",
1351
+ "fre",
1352
+ "france",
1353
+ "fran\xE7ais",
1354
+ "canada",
1355
+ "canadian",
1356
+ "quebec",
1357
+ "belgium",
1358
+ "swiss"
1359
+ ],
1360
+ suggest: ["occitan", "breton", "corsican"]
1361
+ },
1362
+ {
1363
+ lang: "german",
1364
+ match: [
1365
+ "ger",
1366
+ "de",
1367
+ "deu",
1368
+ "germany",
1369
+ "deutsch",
1370
+ "austria",
1371
+ "austrian",
1372
+ "switzerland",
1373
+ "swiss"
1374
+ ],
1375
+ suggest: ["bavarian", "saxon", "luxembourgish"]
1376
+ },
1377
+ {
1378
+ lang: "italian",
1379
+ match: ["ita", "it", "italy", "italiano", "swiss italian"],
1380
+ suggest: ["sardinian", "neapolitan", "venetian"]
1381
+ },
1382
+ {
1383
+ lang: "portuguese",
1384
+ match: ["por", "pt", "portugal", "brazil", "brazilian", "braz", "mozambique", "angola"],
1385
+ suggest: ["galician", "mirandese"]
1386
+ },
1387
+ {
1388
+ lang: "dutch",
1389
+ match: ["dut", "nl", "nld", "netherlands", "holland", "flemish", "belgium"],
1390
+ suggest: ["frisian", "afrikaans"]
1391
+ },
1392
+ // Slavic Languages
1393
+ {
1394
+ lang: "russian",
1395
+ match: ["rus", "ru", "russia", "belarus"],
1396
+ suggest: ["kazakh", "tatar", "belarusian", "tajik", "uzbek", "moldovan"]
1397
+ },
1398
+ {
1399
+ lang: "ukrainian",
1400
+ match: ["ukr", "uk", "ukraine"],
1401
+ suggest: ["russian", "belarusian"]
1402
+ },
1403
+ {
1404
+ lang: "polish",
1405
+ match: ["pol", "pl", "poland"],
1406
+ suggest: ["silesian", "kashubian"]
1407
+ },
1408
+ {
1409
+ lang: "czech",
1410
+ match: ["ces", "cs", "cze", "czech republic", "czechia"],
1411
+ suggest: ["slovak", "moravian"]
1412
+ },
1413
+ {
1414
+ lang: "slovak",
1415
+ match: ["slk", "sk", "slovakia"],
1416
+ suggest: ["czech", "rusyn"]
1417
+ },
1418
+ {
1419
+ lang: "croatian",
1420
+ match: ["hrv", "hr", "croatia"],
1421
+ suggest: ["serbian", "bosnian", "montenegrin"]
1422
+ },
1423
+ {
1424
+ lang: "serbian",
1425
+ match: ["srp", "sr", "ser", "serbia", "serbian", "srpski", "\u0441\u0440\u0431\u0438\u0458\u0430", "\u0441\u0440\u043F\u0441\u043A\u0438"],
1426
+ suggest: ["croatian", "bosnian", "montenegrin"]
1427
+ },
1428
+ {
1429
+ lang: "bulgarian",
1430
+ match: ["bul", "bg", "bulgaria"],
1431
+ suggest: ["macedonian"]
1432
+ },
1433
+ {
1434
+ lang: "slovenian",
1435
+ match: ["slv", "sl", "slovenia"],
1436
+ suggest: ["croatian"]
1437
+ },
1438
+ // Nordic Languages
1439
+ {
1440
+ lang: "swedish",
1441
+ match: ["swe", "sv", "sweden"],
1442
+ suggest: ["norwegian", "danish", "finnish"]
1443
+ },
1444
+ {
1445
+ lang: "norwegian",
1446
+ match: ["nor", "no", "norway"],
1447
+ suggest: ["swedish", "danish"]
1448
+ },
1449
+ {
1450
+ lang: "danish",
1451
+ match: ["dan", "da", "denmark"],
1452
+ suggest: ["norwegian", "swedish"]
1453
+ },
1454
+ {
1455
+ lang: "finnish",
1456
+ match: ["fin", "fi", "finland"],
1457
+ suggest: ["estonian", "swedish"]
1458
+ },
1459
+ {
1460
+ lang: "icelandic",
1461
+ match: ["isl", "is", "iceland"],
1462
+ suggest: ["faroese", "norwegian"]
1463
+ },
1464
+ // Other European Languages
1465
+ {
1466
+ lang: "greek",
1467
+ match: ["ell", "el", "gre", "greece", "hellenic"],
1468
+ suggest: ["macedonian", "albanian"]
1469
+ },
1470
+ {
1471
+ lang: "romanian",
1472
+ match: ["ron", "ro", "rum", "romania"],
1473
+ suggest: ["moldovan", "hungarian"]
1474
+ },
1475
+ {
1476
+ lang: "hungarian",
1477
+ match: ["hun", "hu", "hungary", "magyar"],
1478
+ suggest: ["romanian", "slovak"]
1479
+ },
1480
+ {
1481
+ lang: "albanian",
1482
+ match: ["sqi", "sq", "alb", "albania", "kosovo"],
1483
+ suggest: ["greek", "macedonian"]
1484
+ },
1485
+ {
1486
+ lang: "lithuanian",
1487
+ match: ["lit", "lt", "lithuania"],
1488
+ suggest: ["latvian", "polish"]
1489
+ },
1490
+ {
1491
+ lang: "latvian",
1492
+ match: ["lav", "lv", "latvia"],
1493
+ suggest: ["lithuanian", "estonian"]
1494
+ },
1495
+ {
1496
+ lang: "estonian",
1497
+ match: ["est", "et", "estonia"],
1498
+ suggest: ["finnish", "latvian"]
1499
+ },
1500
+ // Celtic Languages
1501
+ {
1502
+ lang: "irish",
1503
+ match: ["gle", "ga", "ireland", "gaelic"],
1504
+ suggest: ["scottish", "welsh"]
1505
+ },
1506
+ {
1507
+ lang: "welsh",
1508
+ match: ["cym", "cy", "wales", "cymru"],
1509
+ suggest: ["irish", "cornish"]
1510
+ },
1511
+ {
1512
+ lang: "scottish",
1513
+ match: ["gla", "gd", "scotland", "scots gaelic"],
1514
+ suggest: ["irish", "english"]
1515
+ },
1516
+ // Additional Languages
1517
+ {
1518
+ lang: "esperanto",
1519
+ match: ["epo", "eo", "esperanto"],
1520
+ suggest: []
1521
+ },
1522
+ {
1523
+ lang: "latin",
1524
+ match: ["lat", "la", "latin"],
1525
+ suggest: ["italian", "spanish", "french"]
1526
+ }
1527
+ ];
1528
+
1529
+ // src/language-aliases/asian.ts
1530
+ var ASIAN_LANGUAGES = [
1531
+ // Asian Languages
1532
+ {
1533
+ lang: "chinese",
1534
+ match: [
1535
+ "chi",
1536
+ "zh",
1537
+ "zho",
1538
+ "zh-cn",
1539
+ "cn",
1540
+ "china",
1541
+ "mandarin",
1542
+ "zhongwen",
1543
+ "simplified",
1544
+ "traditional"
1545
+ ],
1546
+ suggest: ["cantonese", "taiwanese", "hakka"]
1547
+ },
1548
+ {
1549
+ lang: "japanese",
1550
+ match: ["jpn", "ja", "jp", "japan", "nihongo"],
1551
+ suggest: ["okinawan"]
1552
+ },
1553
+ {
1554
+ lang: "korean",
1555
+ match: ["kor", "ko", "kr", "korea", "hangul", "south korea", "north korea"],
1556
+ suggest: ["jeju"]
1557
+ },
1558
+ {
1559
+ lang: "hindi",
1560
+ match: ["hin", "hi", "india", "indian", "devanagari"],
1561
+ suggest: ["urdu", "punjabi", "gujarati", "marathi", "bengali"]
1562
+ },
1563
+ {
1564
+ lang: "arabic",
1565
+ match: ["ara", "ar", "arab", "saudi", "egypt", "egyptian", "gulf", "levantine", "maghreb"],
1566
+ suggest: ["persian", "hebrew", "urdu"]
1567
+ },
1568
+ {
1569
+ lang: "thai",
1570
+ match: ["tha", "th", "thailand", "siam"],
1571
+ suggest: ["lao", "khmer"]
1572
+ },
1573
+ {
1574
+ lang: "vietnamese",
1575
+ match: ["vie", "vi", "vietnam"],
1576
+ suggest: ["khmer", "lao"]
1577
+ },
1578
+ {
1579
+ lang: "indonesian",
1580
+ match: ["ind", "id", "indonesia", "bahasa"],
1581
+ suggest: ["malay", "javanese", "sundanese"]
1582
+ },
1583
+ {
1584
+ lang: "malay",
1585
+ match: ["msa", "ms", "malaysia", "brunei"],
1586
+ suggest: ["indonesian"]
1587
+ },
1588
+ // Central Asian & Turkic Languages
1589
+ {
1590
+ lang: "kazakh",
1591
+ match: ["kaz", "kk", "kazakh", "kazakhstan"],
1592
+ suggest: ["russian", "kyrgyz", "uzbek"]
1593
+ },
1594
+ {
1595
+ lang: "turkish",
1596
+ match: ["tur", "tr", "turkey"],
1597
+ suggest: ["kurdish", "azerbaijani"]
1598
+ },
1599
+ {
1600
+ lang: "uzbek",
1601
+ match: ["uzb", "uz", "uzbekistan"],
1602
+ suggest: ["tajik", "kazakh", "kyrgyz"]
1603
+ },
1604
+ {
1605
+ lang: "kyrgyz",
1606
+ match: ["kir", "ky", "kyrgyzstan"],
1607
+ suggest: ["kazakh", "uzbek"]
1608
+ },
1609
+ {
1610
+ lang: "tajik",
1611
+ match: ["tgk", "tg", "tajikistan"],
1612
+ suggest: ["persian", "uzbek"]
1613
+ },
1614
+ {
1615
+ lang: "azerbaijani",
1616
+ match: ["aze", "az", "azerbaijan"],
1617
+ suggest: ["turkish", "persian"]
1618
+ },
1619
+ // Middle Eastern Languages
1620
+ {
1621
+ lang: "persian",
1622
+ match: ["fas", "fa", "per", "iran", "farsi", "dari", "afghanistan"],
1623
+ suggest: ["tajik", "kurdish", "pashto"]
1624
+ },
1625
+ {
1626
+ lang: "hebrew",
1627
+ match: ["heb", "he", "israel", "israeli"],
1628
+ suggest: ["arabic", "yiddish"]
1629
+ },
1630
+ {
1631
+ lang: "kurdish",
1632
+ match: ["kur", "ku", "kurdistan", "kurmanji", "sorani"],
1633
+ suggest: ["turkish", "persian", "arabic"]
1634
+ },
1635
+ // South Asian Languages
1636
+ {
1637
+ lang: "urdu",
1638
+ match: ["urd", "ur", "pakistan", "pakistani"],
1639
+ suggest: ["hindi", "punjabi"]
1640
+ },
1641
+ {
1642
+ lang: "bengali",
1643
+ match: ["ben", "bn", "bangladesh", "bengal"],
1644
+ suggest: ["hindi", "assamese"]
1645
+ },
1646
+ {
1647
+ lang: "punjabi",
1648
+ match: ["pan", "pa", "punjab"],
1649
+ suggest: ["hindi", "urdu"]
1650
+ },
1651
+ {
1652
+ lang: "gujarati",
1653
+ match: ["guj", "gu", "gujarat"],
1654
+ suggest: ["hindi", "marathi"]
1655
+ },
1656
+ {
1657
+ lang: "marathi",
1658
+ match: ["mar", "mr", "maharashtra"],
1659
+ suggest: ["hindi", "gujarati"]
1660
+ },
1661
+ {
1662
+ lang: "tamil",
1663
+ match: ["tam", "ta", "tamil nadu", "sri lanka"],
1664
+ suggest: ["malayalam", "telugu", "kannada"]
1665
+ },
1666
+ {
1667
+ lang: "telugu",
1668
+ match: ["tel", "te", "andhra pradesh"],
1669
+ suggest: ["tamil", "kannada"]
1670
+ },
1671
+ {
1672
+ lang: "kannada",
1673
+ match: ["kan", "kn", "karnataka"],
1674
+ suggest: ["tamil", "telugu", "malayalam"]
1675
+ },
1676
+ {
1677
+ lang: "malayalam",
1678
+ match: ["mal", "ml", "kerala"],
1679
+ suggest: ["tamil", "kannada"]
1680
+ },
1681
+ {
1682
+ lang: "nepali",
1683
+ match: ["nep", "ne", "nepal"],
1684
+ suggest: ["hindi"]
1685
+ },
1686
+ {
1687
+ lang: "sinhala",
1688
+ match: ["sin", "si", "sri lanka", "ceylon"],
1689
+ suggest: ["tamil"]
1690
+ },
1691
+ // Southeast Asian Languages
1692
+ {
1693
+ lang: "burmese",
1694
+ match: ["mya", "my", "myanmar", "burma"],
1695
+ suggest: ["thai"]
1696
+ },
1697
+ {
1698
+ lang: "khmer",
1699
+ match: ["khm", "km", "cambodia", "cambodian"],
1700
+ suggest: ["vietnamese", "thai"]
1701
+ },
1702
+ {
1703
+ lang: "lao",
1704
+ match: ["lao", "lo", "laos"],
1705
+ suggest: ["thai", "vietnamese"]
1706
+ },
1707
+ {
1708
+ lang: "tagalog",
1709
+ match: ["tgl", "tl", "philippines", "filipino"],
1710
+ suggest: ["cebuano", "ilocano"]
1711
+ }
1712
+ ];
1713
+
1714
+ // src/language-aliases/african.ts
1715
+ var AFRICAN_LANGUAGES = [
1716
+ // African Languages
1717
+ {
1718
+ lang: "swahili",
1719
+ match: ["swa", "sw", "kiswahili", "tanzania", "kenya"],
1720
+ suggest: ["arabic"]
1721
+ },
1722
+ {
1723
+ lang: "amharic",
1724
+ match: ["amh", "am", "ethiopia", "ethiopian"],
1725
+ suggest: ["tigrinya", "oromo"]
1726
+ },
1727
+ {
1728
+ lang: "yoruba",
1729
+ match: ["yor", "yo", "nigeria", "benin"],
1730
+ suggest: ["igbo", "hausa"]
1731
+ },
1732
+ {
1733
+ lang: "zulu",
1734
+ match: ["zul", "zu", "south africa"],
1735
+ suggest: ["xhosa", "afrikaans"]
1736
+ }
1737
+ ];
1738
+
1739
+ // src/language-aliases/index.ts
1740
+ var LANGUAGE_ALIAS_TABLE = [
1741
+ ...EUROPEAN_LANGUAGES,
1742
+ ...ASIAN_LANGUAGES,
1743
+ ...AFRICAN_LANGUAGES
1744
+ ];
1745
+
1746
+ // src/search-utils.ts
1747
+ function applyLanguageAlias(term = "") {
1748
+ const s = term.toLowerCase().trim();
1749
+ if (!s) return { primary: term, suggestions: [] };
1750
+ const isMatch = (alias, input) => {
1751
+ if (input === alias) return true;
1752
+ if (alias.length >= 3 && input.length >= alias.length && input.startsWith(alias)) return true;
1753
+ if (input.length >= 2 && alias.startsWith(input)) return true;
1754
+ return false;
1755
+ };
1756
+ const hits = /* @__PURE__ */ new Set();
1757
+ const suggestions = /* @__PURE__ */ new Set();
1758
+ for (const { lang, match, suggest } of LANGUAGE_ALIAS_TABLE) {
1759
+ for (const m of match) {
1760
+ if (isMatch(m, s)) {
1761
+ hits.add(lang);
1762
+ if (suggest) {
1763
+ suggest.forEach((sug) => suggestions.add(sug));
1764
+ }
1765
+ }
1766
+ }
1767
+ }
1768
+ let primary;
1769
+ if (hits.size === 0) {
1770
+ primary = term;
1771
+ } else if (hits.size === 1) {
1772
+ primary = Array.from(hits)[0];
1773
+ } else {
1774
+ primary = Array.from(hits);
1775
+ }
1776
+ return { primary, suggestions: Array.from(suggestions) };
1777
+ }
1778
+
1779
+ // src/constants/colors.ts
1780
+ var COLORS = {
1781
+ // Primary colors
1782
+ primary: {
1783
+ blue: "#3b82f6",
1784
+ blueLight: "#60a5fa",
1785
+ blueDark: "#2563eb",
1786
+ blueAlpha30: "#3b82f630",
1787
+ blueAlpha50: "#3b82f650"
1788
+ },
1789
+ // Neutral colors
1790
+ neutral: {
1791
+ white: "#fff",
1792
+ gray50: "#f9fafb",
1793
+ gray100: "#f3f4f6",
1794
+ gray200: "#e5e7eb",
1795
+ gray300: "#d1d5db",
1796
+ gray400: "#9ca3af",
1797
+ gray500: "#6b7280",
1798
+ gray600: "#4b5563",
1799
+ gray700: "#374151",
1800
+ gray800: "#1f2937",
1801
+ gray900: "#111827"
1802
+ },
1803
+ // Border colors
1804
+ border: {
1805
+ light: "#d1d5db",
1806
+ dark: "#4b5563",
1807
+ flag: "#eee"
1808
+ },
1809
+ // Text colors
1810
+ text: {
1811
+ primary: "#1f2937",
1812
+ secondary: "#6b7280",
1813
+ placeholder: "#9ca3af",
1814
+ inverse: "#f9fafb"
1815
+ }
1816
+ };
1817
+ var THEME_COLORS = {
1818
+ light: {
1819
+ background: COLORS.neutral.white,
1820
+ backgroundDisabled: COLORS.neutral.gray100,
1821
+ border: COLORS.border.light,
1822
+ placeholder: COLORS.text.placeholder,
1823
+ ring: COLORS.primary.blueAlpha30,
1824
+ optionPointed: COLORS.neutral.gray100,
1825
+ optionPointedText: COLORS.text.primary,
1826
+ optionSelected: COLORS.primary.blue,
1827
+ optionSelectedText: COLORS.neutral.white,
1828
+ dropdownBackground: COLORS.neutral.white,
1829
+ dropdownBorder: COLORS.border.light,
1830
+ dropdownText: COLORS.text.primary,
1831
+ tagBackground: COLORS.primary.blue
1832
+ },
1833
+ dark: {
1834
+ background: COLORS.neutral.gray800,
1835
+ backgroundDisabled: COLORS.neutral.gray700,
1836
+ border: COLORS.border.dark,
1837
+ placeholder: COLORS.text.placeholder,
1838
+ ring: COLORS.primary.blueAlpha50,
1839
+ optionPointed: COLORS.neutral.gray700,
1840
+ optionPointedText: COLORS.neutral.gray50,
1841
+ optionSelected: COLORS.primary.blue,
1842
+ optionSelectedText: COLORS.neutral.white,
1843
+ dropdownBackground: COLORS.neutral.gray800,
1844
+ dropdownBorder: COLORS.border.dark,
1845
+ dropdownText: COLORS.neutral.gray50,
1846
+ tagBackground: COLORS.primary.blue
1847
+ }
1848
+ };
1849
+ var CSS_VARS = {
1850
+ // Multiselect variables
1851
+ multiselect: {
1852
+ bg: "--ms-bg",
1853
+ bgDisabled: "--ms-bg-disabled",
1854
+ borderColor: "--ms-border-color",
1855
+ placeholderColor: "--ms-placeholder-color",
1856
+ ringColor: "--ms-ring-color",
1857
+ optionBgPointed: "--ms-option-bg-pointed",
1858
+ optionColorPointed: "--ms-option-color-pointed",
1859
+ optionBgSelected: "--ms-option-bg-selected",
1860
+ optionColorSelected: "--ms-option-color-selected",
1861
+ dropdownBg: "--ms-dropdown-bg",
1862
+ dropdownBorderColor: "--ms-dropdown-border-color",
1863
+ dropdownColor: "--ms-dropdown-color",
1864
+ tagBg: "--ms-tag-bg"
1865
+ }
1866
+ };
1867
+
1868
+ // vue-script:/Users/nlit/projects/langie-sdk/src/components/LanguageSelect.vue?type=script
1869
+ var _hoisted_1 = {
1870
+ key: 0,
1871
+ class: "multiselect-single-label"
1872
+ };
1873
+ var _hoisted_2 = ["src", "alt"];
1874
+ var _hoisted_3 = { class: "language-text" };
1875
+ var _hoisted_4 = { class: "native-name" };
1876
+ var _hoisted_5 = ["data-lang-code"];
1877
+ var _hoisted_6 = ["src", "alt"];
1878
+ var _hoisted_7 = { class: "language-text" };
1879
+ var _hoisted_8 = { class: "native-name" };
1880
+ var _hoisted_9 = { class: "multiselect-no-options" };
1881
+ var LanguageSelect_default = /* @__PURE__ */ _defineComponent({
1882
+ __name: "LanguageSelect",
1883
+ props: {
1884
+ modelValue: {
1885
+ type: Object,
1886
+ default: void 0
1887
+ },
1888
+ languages: {
1889
+ type: Array,
1890
+ default: () => []
1891
+ },
1892
+ placeholder: {
1893
+ type: String,
1894
+ default: "Select language"
1895
+ },
1896
+ disabled: {
1897
+ type: Boolean,
1898
+ default: false
1899
+ },
1900
+ isDark: {
1901
+ type: Boolean,
1902
+ default: false
1903
+ }
1904
+ },
1905
+ emits: ["update:modelValue"],
1906
+ setup(__props, { emit: __emit }) {
1907
+ _useCssVars((_ctx) => ({
1908
+ "25bc3fde-THEME_COLORS.light.background": _unref(THEME_COLORS).light.background,
1909
+ "25bc3fde-THEME_COLORS.light.backgroundDisabled": _unref(THEME_COLORS).light.backgroundDisabled,
1910
+ "25bc3fde-THEME_COLORS.light.border": _unref(THEME_COLORS).light.border,
1911
+ "25bc3fde-THEME_COLORS.light.ring": _unref(THEME_COLORS).light.ring,
1912
+ "25bc3fde-THEME_COLORS.light.placeholder": _unref(THEME_COLORS).light.placeholder,
1913
+ "25bc3fde-THEME_COLORS.light.optionPointed": _unref(THEME_COLORS).light.optionPointed,
1914
+ "25bc3fde-THEME_COLORS.light.optionPointedText": _unref(THEME_COLORS).light.optionPointedText,
1915
+ "25bc3fde-THEME_COLORS.light.optionSelected": _unref(THEME_COLORS).light.optionSelected,
1916
+ "25bc3fde-THEME_COLORS.light.optionSelectedText": _unref(THEME_COLORS).light.optionSelectedText,
1917
+ "25bc3fde-THEME_COLORS.light.dropdownBackground": _unref(THEME_COLORS).light.dropdownBackground,
1918
+ "25bc3fde-THEME_COLORS.light.dropdownBorder": _unref(THEME_COLORS).light.dropdownBorder,
1919
+ "25bc3fde-THEME_COLORS.light.dropdownText": _unref(THEME_COLORS).light.dropdownText,
1920
+ "25bc3fde-THEME_COLORS.light.tagBackground": _unref(THEME_COLORS).light.tagBackground,
1921
+ "25bc3fde-THEME_COLORS.dark.background": _unref(THEME_COLORS).dark.background,
1922
+ "25bc3fde-THEME_COLORS.dark.backgroundDisabled": _unref(THEME_COLORS).dark.backgroundDisabled,
1923
+ "25bc3fde-THEME_COLORS.dark.border": _unref(THEME_COLORS).dark.border,
1924
+ "25bc3fde-THEME_COLORS.dark.placeholder": _unref(THEME_COLORS).dark.placeholder,
1925
+ "25bc3fde-THEME_COLORS.dark.ring": _unref(THEME_COLORS).dark.ring,
1926
+ "25bc3fde-THEME_COLORS.dark.optionPointed": _unref(THEME_COLORS).dark.optionPointed,
1927
+ "25bc3fde-THEME_COLORS.dark.optionPointedText": _unref(THEME_COLORS).dark.optionPointedText,
1928
+ "25bc3fde-THEME_COLORS.dark.optionSelected": _unref(THEME_COLORS).dark.optionSelected,
1929
+ "25bc3fde-THEME_COLORS.dark.optionSelectedText": _unref(THEME_COLORS).dark.optionSelectedText,
1930
+ "25bc3fde-THEME_COLORS.dark.dropdownBackground": _unref(THEME_COLORS).dark.dropdownBackground,
1931
+ "25bc3fde-THEME_COLORS.dark.dropdownBorder": _unref(THEME_COLORS).dark.dropdownBorder,
1932
+ "25bc3fde-THEME_COLORS.dark.dropdownText": _unref(THEME_COLORS).dark.dropdownText,
1933
+ "25bc3fde-COLORS.border.flag": _unref(COLORS).border.flag,
1934
+ "25bc3fde-COLORS.border.dark": _unref(COLORS).border.dark,
1935
+ "25bc3fde-COLORS.text.secondary": _unref(COLORS).text.secondary,
1936
+ "25bc3fde-COLORS.neutral.gray400": _unref(COLORS).neutral.gray400
1937
+ }));
1938
+ const getFlagCode = (lang) => {
1939
+ const flagCode = lang.flag_country || lang.code;
1940
+ return flagCode;
1941
+ };
1942
+ const getFlagImageUrl = (lang) => {
1943
+ const flagCode = getFlagCode(lang);
1944
+ return `/flags/${flagCode}.svg`;
1945
+ };
1946
+ const props = __props;
1947
+ const emit = __emit;
1948
+ const isLoading = computed(() => {
1949
+ return props.languages.length <= 0;
1950
+ });
1951
+ const searchQuery = ref2("");
1952
+ const validLanguages = computed(() => {
1953
+ return props.languages.filter((lang) => lang && lang.code && lang.name && lang.native_name);
1954
+ });
1955
+ const fuse = computed(() => {
1956
+ if (!validLanguages.value.length) return null;
1957
+ return new Fuse(validLanguages.value, {
1958
+ keys: ["name", "native_name", "code"],
1959
+ includeScore: true,
1960
+ threshold: 0.6,
1961
+ // Use highest threshold to catch all cases
1962
+ isCaseSensitive: false,
1963
+ minMatchCharLength: 1
1964
+ // Allow single character matches
1965
+ });
1966
+ });
1967
+ const multiselectKey = computed(() => {
1968
+ const hasApiData = props.languages.length > 0 && props.languages[0].flag_country !== null;
1969
+ return hasApiData ? "api-data" : "fallback-data";
1970
+ });
1971
+ const selectedLanguageKey = computed(() => {
1972
+ if (!props.modelValue || !props.modelValue.code) return "no-selection";
1973
+ const key = `${props.modelValue.code}-${props.modelValue.flag_country || "fallback"}`;
1974
+ return key;
1975
+ });
1976
+ const selectedLanguage = computed({
1977
+ get: () => props.modelValue || null,
1978
+ set: (value) => {
1979
+ if (value && value.code) {
1980
+ emit("update:modelValue", value);
1981
+ }
1982
+ }
1983
+ });
1984
+ const filteredLanguages = computed(() => {
1985
+ const query = searchQuery.value.trim();
1986
+ let results;
1987
+ if (!query) {
1988
+ results = validLanguages.value;
1989
+ } else {
1990
+ if (!fuse.value) return [];
1991
+ const aliasResult = applyLanguageAlias(query);
1992
+ const searchTerm = Array.isArray(aliasResult.primary) ? aliasResult.primary[0] : aliasResult.primary;
1993
+ const fuseResults = fuse.value.search(searchTerm);
1994
+ results = fuseResults.map((result) => result.item);
1995
+ if (searchTerm !== query) {
1996
+ const originalResults = fuse.value.search(query);
1997
+ originalResults.forEach((result) => {
1998
+ if (!results.some((r) => r.code === result.item.code)) {
1999
+ results.push(result.item);
2000
+ }
2001
+ });
2002
+ }
2003
+ if (aliasResult.suggestions.length > 0) {
2004
+ aliasResult.suggestions.forEach((suggestion) => {
2005
+ const suggestedLang = validLanguages.value.find((lang) => {
2006
+ const langName = lang.name.toLowerCase();
2007
+ const suggestion_lower = suggestion.toLowerCase();
2008
+ return langName === suggestion_lower || // Exact match
2009
+ langName.includes(suggestion_lower) || // Contains match
2010
+ lang.code.toLowerCase() === suggestion_lower || // Code match
2011
+ langName.startsWith(suggestion_lower) || // Starts with match
2012
+ // Handle common variations
2013
+ suggestion_lower === "tatar" && langName.includes("tatar") || suggestion_lower === "belarusian" && (langName.includes("belarus") || langName.includes("belarusian")) || suggestion_lower === "moldovan" && (langName.includes("moldov") || langName.includes("moldova"));
2014
+ });
2015
+ if (suggestedLang && !results.some((r) => r.code === suggestedLang.code)) {
2016
+ results.push(suggestedLang);
2017
+ }
2018
+ });
2019
+ }
2020
+ if (query.length === 2) {
2021
+ const lowerQuery = query.toLowerCase();
2022
+ const manualResults = validLanguages.value.filter(
2023
+ (lang) => lang.name.toLowerCase().startsWith(lowerQuery) || lang.native_name.toLowerCase().startsWith(lowerQuery)
2024
+ );
2025
+ manualResults.forEach((lang) => {
2026
+ if (!results.some((r) => r.code === lang.code)) {
2027
+ results.push(lang);
2028
+ }
2029
+ });
2030
+ }
2031
+ }
2032
+ const filtered = results.filter(
2033
+ (lang) => !props.modelValue || !props.modelValue.code || lang.code !== props.modelValue.code
2034
+ );
2035
+ return filtered;
2036
+ });
2037
+ function handleSearch(query) {
2038
+ searchQuery.value = query;
2039
+ if (query.trim()) {
2040
+ nextTick(() => {
2041
+ const dropdown = document.querySelector(".multiselect-dropdown");
2042
+ const firstOption = dropdown?.querySelector(".multiselect-option");
2043
+ if (firstOption) {
2044
+ firstOption.scrollIntoView({ behavior: "smooth", block: "nearest" });
2045
+ firstOption.classList.add("multiselect-option-is-pointed");
2046
+ }
2047
+ });
2048
+ }
2049
+ }
2050
+ function handleKeydown(event) {
2051
+ const keyboardEvent = event;
2052
+ if (keyboardEvent.key === "Tab" || keyboardEvent.key === "Enter") {
2053
+ const dropdown = document.querySelector(".multiselect-dropdown");
2054
+ const firstOption = dropdown?.querySelector(".multiselect-option");
2055
+ if (firstOption && searchQuery.value.trim()) {
2056
+ const langCode = firstOption.getAttribute("data-lang-code");
2057
+ if (langCode) {
2058
+ const language = validLanguages.value.find((lang) => lang.code === langCode);
2059
+ if (language) {
2060
+ selectedLanguage.value = language;
2061
+ searchQuery.value = "";
2062
+ keyboardEvent.preventDefault();
2063
+ }
2064
+ }
2065
+ }
2066
+ }
2067
+ }
2068
+ const onFlagError = (event) => {
2069
+ const target = event.target;
2070
+ const currentSrc = target.src;
2071
+ if (currentSrc && currentSrc.includes("/flags/")) {
2072
+ const match = currentSrc.match(/\/flags\/([a-z]{2})\.svg/);
2073
+ if (match) {
2074
+ const countryCode = match[1];
2075
+ target.src = `https://flagcdn.com/${countryCode}.svg`;
2076
+ target.onerror = () => {
2077
+ target.style.display = "none";
2078
+ };
2079
+ return;
2080
+ }
2081
+ }
2082
+ target.style.display = "none";
2083
+ };
2084
+ return (_ctx, _cache) => {
2085
+ return _openBlock(), _createElementBlock(
2086
+ "div",
2087
+ {
2088
+ class: _normalizeClass(["language-select", { "is-dark": __props.isDark }])
2089
+ },
2090
+ [
2091
+ _createCommentVNode(" Show multiselect when we have languages OR when not loading "),
2092
+ validLanguages.value.length > 0 || !isLoading.value ? (_openBlock(), _createBlock(_unref(Multiselect), {
2093
+ key: multiselectKey.value,
2094
+ modelValue: selectedLanguage.value,
2095
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedLanguage.value = $event),
2096
+ options: filteredLanguages.value,
2097
+ searchable: true,
2098
+ onSearchChange: handleSearch,
2099
+ "can-clear": false,
2100
+ "allow-empty": false,
2101
+ object: true,
2102
+ placeholder: validLanguages.value.length === 0 ? "No languages available" : __props.placeholder,
2103
+ disabled: props.disabled || validLanguages.value.length === 0,
2104
+ loading: isLoading.value,
2105
+ "track-by": "name",
2106
+ label: "name",
2107
+ "value-prop": "code",
2108
+ "filter-results": false,
2109
+ onKeydown: handleKeydown
2110
+ }, {
2111
+ singlelabel: _withCtx(({ value }) => [
2112
+ value ? (_openBlock(), _createElementBlock("div", _hoisted_1, [
2113
+ value.code ? (_openBlock(), _createElementBlock("img", {
2114
+ key: selectedLanguageKey.value,
2115
+ src: getFlagImageUrl(value),
2116
+ class: "lang-flag",
2117
+ alt: `${value.native_name || value.name} flag`,
2118
+ onError: onFlagError
2119
+ }, null, 40, _hoisted_2)) : _createCommentVNode("v-if", true),
2120
+ _createElementVNode("span", _hoisted_3, [
2121
+ _createTextVNode(
2122
+ _toDisplayString(value.name) + " ",
2123
+ 1
2124
+ /* TEXT */
2125
+ ),
2126
+ _createElementVNode(
2127
+ "span",
2128
+ _hoisted_4,
2129
+ "(" + _toDisplayString(value.native_name || value.name) + ")",
2130
+ 1
2131
+ /* TEXT */
2132
+ )
2133
+ ])
2134
+ ])) : _createCommentVNode("v-if", true)
2135
+ ]),
2136
+ option: _withCtx(({ option }) => [
2137
+ _createElementVNode("div", {
2138
+ class: "multiselect-option",
2139
+ "data-lang-code": option.code
2140
+ }, [
2141
+ (_openBlock(), _createElementBlock("img", {
2142
+ key: `${option.code}-${option.flag_country || "fallback"}`,
2143
+ src: getFlagImageUrl(option),
2144
+ class: "lang-flag",
2145
+ alt: `${option.name} flag`,
2146
+ onError: onFlagError
2147
+ }, null, 40, _hoisted_6)),
2148
+ _createElementVNode("span", _hoisted_7, [
2149
+ _createTextVNode(
2150
+ _toDisplayString(option.name) + " ",
2151
+ 1
2152
+ /* TEXT */
2153
+ ),
2154
+ _createElementVNode(
2155
+ "span",
2156
+ _hoisted_8,
2157
+ "(" + _toDisplayString(option.native_name || option.name) + ")",
2158
+ 1
2159
+ /* TEXT */
2160
+ )
2161
+ ])
2162
+ ], 8, _hoisted_5)
2163
+ ]),
2164
+ noresults: _withCtx(() => _cache[1] || (_cache[1] = [
2165
+ _createElementVNode(
2166
+ "div",
2167
+ { class: "multiselect-no-results" },
2168
+ "No languages found.",
2169
+ -1
2170
+ /* CACHED */
2171
+ )
2172
+ ])),
2173
+ nooptions: _withCtx(() => [
2174
+ _createElementVNode(
2175
+ "div",
2176
+ _hoisted_9,
2177
+ _toDisplayString(validLanguages.value.length === 0 ? "Please provide languages via the :languages prop" : "No languages available."),
2178
+ 1
2179
+ /* TEXT */
2180
+ )
2181
+ ]),
2182
+ _: 1
2183
+ /* STABLE */
2184
+ }, 8, ["modelValue", "options", "placeholder", "disabled", "loading"])) : isLoading.value && validLanguages.value.length === 0 ? (_openBlock(), _createElementBlock(
2185
+ _Fragment,
2186
+ { key: 1 },
2187
+ [
2188
+ _createCommentVNode(" Only show skeleton loader when actually loading AND we have no languages yet "),
2189
+ _cache[2] || (_cache[2] = _createElementVNode(
2190
+ "div",
2191
+ { class: "skeleton-loader" },
2192
+ null,
2193
+ -1
2194
+ /* CACHED */
2195
+ ))
2196
+ ],
2197
+ 2112
2198
+ /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */
2199
+ )) : _createCommentVNode("v-if", true)
2200
+ ],
2201
+ 2
2202
+ /* CLASS */
2203
+ );
2204
+ };
2205
+ }
2206
+ });
2207
+
2208
+ // src/components/LanguageSelect.vue
2209
+ var LanguageSelect_default2 = LanguageSelect_default;
2210
+ LanguageSelect_default.__scopeId = "data-v-25bc3fde";
2211
+
2212
+ // vue-script:/Users/nlit/projects/langie-sdk/src/components/InterfaceLanguageSelect.vue?type=script
2213
+ import { defineComponent as _defineComponent2 } from "vue";
2214
+ import { openBlock as _openBlock2, createBlock as _createBlock2, createCommentVNode as _createCommentVNode2, createElementVNode as _createElementVNode2, unref as _unref2, toDisplayString as _toDisplayString2, normalizeClass as _normalizeClass2, createElementBlock as _createElementBlock2 } from "vue";
2215
+ import { computed as computed2, watch as watch2, onMounted, ref as ref3 } from "vue";
2216
+ var _hoisted_12 = { class: "interface-language-select-wrapper" };
2217
+ var _hoisted_22 = { class: "loader-text" };
2218
+ var InterfaceLanguageSelect_default = /* @__PURE__ */ _defineComponent2({
2219
+ __name: "InterfaceLanguageSelect",
2220
+ props: {
2221
+ placeholder: {
2222
+ type: String,
2223
+ default: "Select interface language"
2224
+ },
2225
+ disabled: {
2226
+ type: Boolean,
2227
+ default: false
2228
+ },
2229
+ isDark: {
2230
+ type: Boolean,
2231
+ default: false
2232
+ },
2233
+ translatorHost: {
2234
+ type: String,
2235
+ default: ""
2236
+ },
2237
+ apiKey: {
2238
+ type: String,
2239
+ default: ""
2240
+ },
2241
+ languages: {
2242
+ type: Array,
2243
+ default: () => []
2244
+ }
2245
+ },
2246
+ emits: ["update:modelValue"],
2247
+ setup(__props, { emit: __emit }) {
2248
+ const isChangingLanguage = ref3(false);
2249
+ const props = __props;
2250
+ const emit = __emit;
2251
+ const { availableLanguages: availableLanguages2, currentLanguage: currentLanguage2, setLanguage, fetchLanguages, lr } = useLangie();
2252
+ const effectiveLanguages = computed2(() => {
2253
+ const languages = props.languages && props.languages.length > 0 ? props.languages : availableLanguages2.value;
2254
+ const processed = languages.map((lang) => ({
2255
+ ...lang,
2256
+ native_name: lang.native_name || lang.name,
2257
+ flag_country: lang.flag_country || lang.code
2258
+ }));
2259
+ return processed;
2260
+ });
2261
+ const currentLanguageObject = computed2(() => {
2262
+ if (!currentLanguage2.value) return null;
2263
+ return effectiveLanguages.value.find((lang) => lang.code === currentLanguage2.value) || null;
2264
+ });
2265
+ function detectBrowserLanguage2(languages) {
2266
+ if (languages.length === 0) return null;
2267
+ const browserLanguages = navigator.languages || [navigator.language || "en"];
2268
+ for (const browserLang of browserLanguages) {
2269
+ const langCode = browserLang.toLowerCase().split("-")[0];
2270
+ const exactMatch = languages.find((lang) => lang.code.toLowerCase() === langCode);
2271
+ if (exactMatch) {
2272
+ return exactMatch.code;
2273
+ }
2274
+ }
2275
+ for (const browserLang of browserLanguages) {
2276
+ const fullLangCode = browserLang.toLowerCase();
2277
+ const localeMatch = languages.find((lang) => lang.code.toLowerCase() === fullLangCode);
2278
+ if (localeMatch) {
2279
+ return localeMatch.code;
2280
+ }
2281
+ }
2282
+ return null;
2283
+ }
2284
+ async function handleLanguageChange(selectedLanguage) {
2285
+ if (selectedLanguage) {
2286
+ isChangingLanguage.value = true;
2287
+ try {
2288
+ setLanguage(selectedLanguage.code);
2289
+ localStorage.setItem("interface_language", selectedLanguage.code);
2290
+ emit("update:modelValue", selectedLanguage);
2291
+ } finally {
2292
+ isChangingLanguage.value = false;
2293
+ }
2294
+ }
2295
+ }
2296
+ watch2(currentLanguage2, (newLangCode) => {
2297
+ if (newLangCode) {
2298
+ localStorage.setItem("interface_language", newLangCode);
2299
+ }
2300
+ });
2301
+ watch2(
2302
+ () => effectiveLanguages.value,
2303
+ (newLanguages) => {
2304
+ if (newLanguages.length > 0 && !currentLanguage2.value) {
2305
+ const savedLanguageCode = localStorage.getItem("interface_language");
2306
+ if (savedLanguageCode) {
2307
+ const savedLangExists = newLanguages.find((lang) => lang.code === savedLanguageCode);
2308
+ if (savedLangExists) {
2309
+ setLanguage(savedLanguageCode);
2310
+ return;
2311
+ }
2312
+ }
2313
+ const browserLang = detectBrowserLanguage2(newLanguages);
2314
+ if (browserLang) {
2315
+ setLanguage(browserLang);
2316
+ }
2317
+ }
2318
+ },
2319
+ { immediate: true }
2320
+ );
2321
+ onMounted(async () => {
2322
+ if (!props.languages || props.languages.length === 0) {
2323
+ const countryCode = await getCountryCode();
2324
+ await fetchLanguages({ country: countryCode || void 0 });
2325
+ }
2326
+ const savedLanguageCode = localStorage.getItem("interface_language");
2327
+ if (savedLanguageCode && savedLanguageCode !== currentLanguage2.value) {
2328
+ const currentLanguages = effectiveLanguages.value;
2329
+ const savedLangExists = currentLanguages.find((lang) => lang.code === savedLanguageCode);
2330
+ if (savedLangExists) {
2331
+ setLanguage(savedLanguageCode);
2332
+ } else if (currentLanguages.length > 0) {
2333
+ const browserLang = detectBrowserLanguage2(currentLanguages);
2334
+ if (browserLang) {
2335
+ setLanguage(browserLang);
2336
+ }
2337
+ }
2338
+ } else if (!currentLanguage2.value && effectiveLanguages.value.length > 0) {
2339
+ const browserLang = detectBrowserLanguage2(effectiveLanguages.value);
2340
+ if (browserLang) {
2341
+ setLanguage(browserLang);
2342
+ }
2343
+ }
2344
+ });
2345
+ watch2(
2346
+ currentLanguageObject,
2347
+ (newValue) => {
2348
+ if (newValue) {
2349
+ emit("update:modelValue", newValue);
2350
+ }
2351
+ },
2352
+ { immediate: true }
2353
+ );
2354
+ return (_ctx, _cache) => {
2355
+ return _openBlock2(), _createElementBlock2("div", _hoisted_12, [
2356
+ (_openBlock2(), _createBlock2(LanguageSelect_default2, {
2357
+ key: `interface-lang-${effectiveLanguages.value.length}-${effectiveLanguages.value[0]?.code || "empty"}`,
2358
+ "model-value": currentLanguageObject.value,
2359
+ languages: effectiveLanguages.value,
2360
+ placeholder: props.placeholder,
2361
+ disabled: props.disabled || isChangingLanguage.value,
2362
+ "is-dark": props.isDark,
2363
+ "onUpdate:modelValue": handleLanguageChange
2364
+ }, null, 8, ["model-value", "languages", "placeholder", "disabled", "is-dark"])),
2365
+ _createCommentVNode2(" Loading overlay "),
2366
+ isChangingLanguage.value ? (_openBlock2(), _createElementBlock2(
2367
+ "div",
2368
+ {
2369
+ key: 0,
2370
+ class: _normalizeClass2(["language-change-loader", { "is-dark": props.isDark }])
2371
+ },
2372
+ [
2373
+ _cache[0] || (_cache[0] = _createElementVNode2(
2374
+ "div",
2375
+ { class: "loader-spinner" },
2376
+ null,
2377
+ -1
2378
+ /* CACHED */
2379
+ )),
2380
+ _createElementVNode2(
2381
+ "span",
2382
+ _hoisted_22,
2383
+ _toDisplayString2(_unref2(lr)("Changing language...", "ui")),
2384
+ 1
2385
+ /* TEXT */
2386
+ )
2387
+ ],
2388
+ 2
2389
+ /* CLASS */
2390
+ )) : _createCommentVNode2("v-if", true)
2391
+ ]);
2392
+ };
2393
+ }
2394
+ });
2395
+
2396
+ // src/components/InterfaceLanguageSelect.vue
2397
+ var InterfaceLanguageSelect_default2 = InterfaceLanguageSelect_default;
2398
+ InterfaceLanguageSelect_default.__scopeId = "data-v-7192f8c0";
2399
+
2400
+ // vue-script:/Users/nlit/projects/langie-sdk/src/components/lt.vue?type=script
2401
+ import { defineComponent as _defineComponent3 } from "vue";
2402
+ import { toDisplayString as _toDisplayString3, openBlock as _openBlock3, createElementBlock as _createElementBlock3, Transition as _Transition, withCtx as _withCtx2, createBlock as _createBlock3 } from "vue";
2403
+ import { computed as computed3, useSlots, watch as watch3 } from "vue";
2404
+ import { ref as ref4, nextTick as nextTick2 } from "vue";
2405
+ var lt_default = /* @__PURE__ */ _defineComponent3({
2406
+ __name: "lt",
2407
+ props: {
2408
+ // Message key (optional, otherwise slot content is used)
2409
+ msg: {
2410
+ type: String,
2411
+ default: void 0
2412
+ },
2413
+ // Translation context shorthand
2414
+ ctx: {
2415
+ type: String,
2416
+ required: false,
2417
+ default: "ui"
2418
+ },
2419
+ // Original language shorthand
2420
+ orig: {
2421
+ type: String,
2422
+ required: false,
2423
+ default: void 0
2424
+ }
2425
+ },
2426
+ setup(__props) {
2427
+ const isNuxt = computed3(() => {
2428
+ if (typeof window !== "undefined") {
2429
+ return !!window.__NUXT__;
2430
+ }
2431
+ if (typeof process !== "undefined") {
2432
+ return !!process.env.NUXT_SSR_BASE || !!process.env.NUXT_PUBLIC_BASE_URL;
2433
+ }
2434
+ return false;
2435
+ });
2436
+ const props = __props;
2437
+ const slots = useSlots();
2438
+ const { lr, currentLanguage: currentLanguage2, uiTranslations: uiTranslations2, translations: translations2, getLtDefaults: getLtDefaults2 } = useLangie();
2439
+ const keyStr = computed3(() => {
2440
+ if (props.msg) return props.msg;
2441
+ const slotContent = slots.default ? slots.default().map((n) => n.children).join("") : "";
2442
+ return (slotContent || "").trim();
2443
+ });
2444
+ const forceUpdate = ref4(0);
2445
+ watch3(
2446
+ [uiTranslations2, translations2],
2447
+ () => {
2448
+ nextTick2(() => {
2449
+ forceUpdate.value++;
2450
+ });
2451
+ },
2452
+ { deep: true }
2453
+ );
2454
+ const translated = computed3(() => {
2455
+ if (isNuxt.value && typeof window === "undefined") {
2456
+ return keyStr.value;
2457
+ }
2458
+ const globalDefaults = getLtDefaults2();
2459
+ const effectiveCtx = props.ctx ?? globalDefaults.ctx;
2460
+ const effectiveOrig = props.orig ?? globalDefaults.orig;
2461
+ void currentLanguage2.value;
2462
+ void forceUpdate.value;
2463
+ const cacheKey = `${keyStr.value}|${effectiveCtx}`;
2464
+ const cache = effectiveCtx === "ui" ? uiTranslations2 : translations2;
2465
+ void cache[cacheKey];
2466
+ const result = lr(keyStr.value, effectiveCtx, effectiveOrig);
2467
+ return result;
2468
+ });
2469
+ return (_ctx, _cache) => {
2470
+ return _openBlock3(), _createBlock3(_Transition, {
2471
+ name: "fade",
2472
+ mode: "out-in"
2473
+ }, {
2474
+ default: _withCtx2(() => [
2475
+ (_openBlock3(), _createElementBlock3(
2476
+ "span",
2477
+ { key: translated.value },
2478
+ _toDisplayString3(translated.value),
2479
+ 1
2480
+ /* TEXT */
2481
+ ))
2482
+ ]),
2483
+ _: 1
2484
+ /* STABLE */
2485
+ });
2486
+ };
2487
+ }
2488
+ });
2489
+
2490
+ // src/components/lt.vue
2491
+ var lt_default2 = lt_default;
2492
+ lt_default.__scopeId = "data-v-14a7b6f6";
2493
+
2494
+ // src/utils/theme.ts
2495
+ function setThemeColors() {
2496
+ const root = document.documentElement;
2497
+ root.style.setProperty("--langie-flag-border", COLORS.border.flag);
2498
+ root.style.setProperty("--langie-flag-border-dark", COLORS.border.dark);
2499
+ root.style.setProperty("--langie-text-secondary", COLORS.text.secondary);
2500
+ root.style.setProperty("--langie-text-secondary-dark", COLORS.neutral.gray400);
2501
+ root.style.setProperty("--langie-primary-blue", COLORS.primary.blue);
2502
+ root.style.setProperty("--langie-primary-blue-alpha-30", COLORS.primary.blueAlpha30);
2503
+ root.style.setProperty("--langie-primary-blue-alpha-50", COLORS.primary.blueAlpha50);
2504
+ }
2505
+ function clearThemeColors() {
2506
+ const root = document.documentElement;
2507
+ const properties = [
2508
+ "--langie-flag-border",
2509
+ "--langie-flag-border-dark",
2510
+ "--langie-text-secondary",
2511
+ "--langie-text-secondary-dark",
2512
+ "--langie-primary-blue",
2513
+ "--langie-primary-blue-alpha-30",
2514
+ "--langie-primary-blue-alpha-50"
2515
+ ];
2516
+ properties.forEach((prop) => root.style.removeProperty(prop));
2517
+ }
2518
+
2519
+ // src/utils/languageMapping.ts
2520
+ var BROWSER_LANGUAGE_MAP = {
2521
+ // Chinese variants
2522
+ "zh": "zh-cn",
2523
+ // Chinese (Simplified)
2524
+ // Serbian variants
2525
+ "sr": "sr-latn",
2526
+ // Serbian (default to Latin)
2527
+ "me": "sr-latn",
2528
+ // Montenegrin
2529
+ "sh": "sr-latn",
2530
+ // Serbo-Croatian
2531
+ // Kazakh variants
2532
+ "kaz": "kk",
2533
+ // Kazakh (browser 'kaz' or 'kk' → 'kk')
2534
+ "kk": "kk"
2535
+ };
2536
+ function detectBrowserLanguage() {
2537
+ if (typeof navigator === "undefined") {
2538
+ return "en";
2539
+ }
2540
+ const locale = navigator.languages?.[0] || navigator.language || "";
2541
+ const browserCode = locale.split("-")[0];
2542
+ const mappedLanguage = BROWSER_LANGUAGE_MAP[browserCode];
2543
+ if (mappedLanguage) {
2544
+ return mappedLanguage;
2545
+ }
2546
+ return browserCode || "en";
2547
+ }
2548
+
2549
+ // src/utils/regionMapping.ts
2550
+ var COUNTRY_TO_REGION = {
2551
+ // EU
2552
+ DE: "EU",
2553
+ AT: "EU",
2554
+ CH: "EU",
2555
+ NL: "EU",
2556
+ BE: "EU",
2557
+ LU: "EU",
2558
+ DK: "EU",
2559
+ SE: "EU",
2560
+ NO: "EU",
2561
+ IS: "EU",
2562
+ GB: "EU",
2563
+ IE: "EU",
2564
+ FR: "EU",
2565
+ IT: "EU",
2566
+ ES: "EU",
2567
+ PT: "EU",
2568
+ RO: "EU",
2569
+ MT: "EU",
2570
+ PL: "EU",
2571
+ CZ: "EU",
2572
+ SK: "EU",
2573
+ SI: "EU",
2574
+ HR: "EU",
2575
+ BA: "EU",
2576
+ RS: "EU",
2577
+ ME: "EU",
2578
+ MK: "EU",
2579
+ BG: "EU",
2580
+ RU: "EU",
2581
+ BY: "EU",
2582
+ UA: "EU",
2583
+ LT: "EU",
2584
+ LV: "EU",
2585
+ EE: "EU",
2586
+ FI: "EU",
2587
+ HU: "EU",
2588
+ GR: "EU",
2589
+ CY: "EU",
2590
+ AL: "EU",
2591
+ MD: "EU",
2592
+ LI: "EU",
2593
+ AD: "EU",
2594
+ SM: "EU",
2595
+ VA: "EU",
2596
+ MC: "EU",
2597
+ // NA
2598
+ US: "NA",
2599
+ CA: "NA",
2600
+ MX: "NA",
2601
+ GT: "NA",
2602
+ BZ: "NA",
2603
+ SV: "NA",
2604
+ HN: "NA",
2605
+ NI: "NA",
2606
+ CR: "NA",
2607
+ PA: "NA",
2608
+ HT: "NA",
2609
+ JM: "NA",
2610
+ CU: "NA",
2611
+ DO: "NA",
2612
+ TT: "NA",
2613
+ BB: "NA",
2614
+ BS: "NA",
2615
+ GD: "NA",
2616
+ LC: "NA",
2617
+ VC: "NA",
2618
+ AG: "NA",
2619
+ DM: "NA",
2620
+ KN: "NA",
2621
+ // SA
2622
+ AR: "SA",
2623
+ CL: "SA",
2624
+ PE: "SA",
2625
+ CO: "SA",
2626
+ VE: "SA",
2627
+ EC: "SA",
2628
+ BO: "SA",
2629
+ PY: "SA",
2630
+ UY: "SA",
2631
+ BR: "SA",
2632
+ GY: "SA",
2633
+ SR: "SA",
2634
+ GF: "SA",
2635
+ // EA
2636
+ CN: "EA",
2637
+ TW: "EA",
2638
+ HK: "EA",
2639
+ MO: "EA",
2640
+ SG: "EA",
2641
+ JP: "EA",
2642
+ KR: "EA",
2643
+ KP: "EA",
2644
+ MN: "EA",
2645
+ // SEA
2646
+ VN: "SEA",
2647
+ KH: "SEA",
2648
+ TH: "SEA",
2649
+ LA: "SEA",
2650
+ MM: "SEA",
2651
+ ID: "SEA",
2652
+ MY: "SEA",
2653
+ PH: "SEA",
2654
+ BN: "SEA",
2655
+ TL: "SEA",
2656
+ // SAS
2657
+ IN: "SAS",
2658
+ PK: "SAS",
2659
+ BD: "SAS",
2660
+ LK: "SAS",
2661
+ NP: "SAS",
2662
+ BT: "SAS",
2663
+ MV: "SAS",
2664
+ AF: "SAS",
2665
+ // CAS
2666
+ KZ: "CAS",
2667
+ KG: "CAS",
2668
+ UZ: "CAS",
2669
+ TM: "CAS",
2670
+ TR: "CAS",
2671
+ AZ: "CAS",
2672
+ TJ: "CAS",
2673
+ IR: "CAS",
2674
+ // ME
2675
+ SA: "ME",
2676
+ AE: "ME",
2677
+ QA: "ME",
2678
+ KW: "ME",
2679
+ BH: "ME",
2680
+ OM: "ME",
2681
+ YE: "ME",
2682
+ JO: "ME",
2683
+ LB: "ME",
2684
+ SY: "ME",
2685
+ IQ: "ME",
2686
+ PS: "ME",
2687
+ IL: "ME",
2688
+ GE: "ME",
2689
+ AM: "ME",
2690
+ // NAF
2691
+ EG: "NAF",
2692
+ LY: "NAF",
2693
+ TN: "NAF",
2694
+ DZ: "NAF",
2695
+ MA: "NAF",
2696
+ SD: "NAF",
2697
+ SS: "NAF",
2698
+ ET: "NAF",
2699
+ ER: "NAF",
2700
+ DJ: "NAF",
2701
+ SO: "NAF",
2702
+ // WAF
2703
+ NG: "WAF",
2704
+ GH: "WAF",
2705
+ CI: "WAF",
2706
+ BF: "WAF",
2707
+ ML: "WAF",
2708
+ SN: "WAF",
2709
+ GN: "WAF",
2710
+ SL: "WAF",
2711
+ LR: "WAF",
2712
+ GM: "WAF",
2713
+ GW: "WAF",
2714
+ NE: "WAF",
2715
+ TD: "WAF",
2716
+ CF: "WAF",
2717
+ CM: "WAF",
2718
+ MR: "WAF",
2719
+ CV: "WAF",
2720
+ // EAF
2721
+ KE: "EAF",
2722
+ TZ: "EAF",
2723
+ UG: "EAF",
2724
+ RW: "EAF",
2725
+ BI: "EAF",
2726
+ MZ: "EAF",
2727
+ MW: "EAF",
2728
+ ZM: "EAF",
2729
+ ZW: "EAF",
2730
+ MG: "EAF",
2731
+ MU: "EAF",
2732
+ KM: "EAF",
2733
+ SC: "EAF",
2734
+ // SAF
2735
+ ZA: "SAF",
2736
+ NA: "SAF",
2737
+ BW: "SAF",
2738
+ LS: "SAF",
2739
+ SZ: "SAF",
2740
+ AO: "SAF",
2741
+ // CAF
2742
+ CD: "CAF",
2743
+ CG: "CAF",
2744
+ GA: "CAF",
2745
+ GQ: "CAF",
2746
+ ST: "CAF",
2747
+ // OC
2748
+ AU: "OC",
2749
+ NZ: "OC",
2750
+ PG: "OC",
2751
+ FJ: "OC",
2752
+ SB: "OC",
2753
+ VU: "OC",
2754
+ NC: "OC",
2755
+ PF: "OC",
2756
+ WS: "OC",
2757
+ TO: "OC",
2758
+ TV: "OC",
2759
+ KI: "OC",
2760
+ NR: "OC",
2761
+ PW: "OC",
2762
+ MH: "OC",
2763
+ FM: "OC",
2764
+ // CAR
2765
+ PR: "CAR",
2766
+ GP: "CAR",
2767
+ MQ: "CAR",
2768
+ AW: "CAR",
2769
+ CW: "CAR"
2770
+ };
2771
+ function getRegionForCountry(country) {
2772
+ if (!country) return "";
2773
+ return COUNTRY_TO_REGION[country.toUpperCase()] || "";
2774
+ }
2775
+ function getGeoRequestParams(country, language, timezone) {
2776
+ const params = {};
2777
+ if (country) {
2778
+ params.country = country;
2779
+ const region = getRegionForCountry(country);
2780
+ if (region) {
2781
+ params.region = region;
2782
+ }
2783
+ }
2784
+ if (language) params.language = language;
2785
+ if (timezone) params.timezone = timezone;
2786
+ return params;
2787
+ }
2788
+ export {
2789
+ API_FIELD_CTX,
2790
+ API_FIELD_FROM,
2791
+ API_FIELD_TEXT,
2792
+ API_FIELD_TO,
2793
+ API_FIELD_TRANSLATED,
2794
+ API_FIELD_TRANSLATIONS,
2795
+ BROWSER_LANGUAGE_MAP,
2796
+ COLORS,
2797
+ CSS_VARS,
2798
+ DEFAULT_API_HOST,
2799
+ DEV_API_HOST,
2800
+ InterfaceLanguageSelect_default2 as InterfaceLanguageSelect,
2801
+ LANGUAGE_ALIAS_TABLE,
2802
+ LanguageSelect_default2 as LanguageSelect,
2803
+ THEME_COLORS,
2804
+ cacheManager,
2805
+ clearCache,
2806
+ clearThemeColors,
2807
+ clearTranslationCache,
2808
+ detectBrowserLanguage,
2809
+ fetchAvailableLanguages,
2810
+ getCache,
2811
+ getCacheStats,
2812
+ getCountryCode,
2813
+ getGeoRequestParams,
2814
+ getLtDefaults,
2815
+ getRegionForCountry,
2816
+ getTranslationCacheSize,
2817
+ lt_default2 as lt,
2818
+ removeCache,
2819
+ setCache,
2820
+ setLtDefaults,
2821
+ setThemeColors,
2822
+ translateBatch,
2823
+ useLangie,
2824
+ useLangie as useTranslator
2825
+ };
2826
+ //# sourceMappingURL=index.mjs.map