langie 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * langie v1.10.1
2
+ * langie v1.11.1
3
3
  * (c) 2026 nlit
4
4
  * @license Apache-2.0
5
5
  *
@@ -78,6 +78,7 @@ styleInject(".multiselect-single-label,\n.multiselect-option {\n display: flex;
78
78
  var import_vue3 = require("vue");
79
79
  var import_multiselect = __toESM(require("@vueform/multiselect"));
80
80
  var import_fuse = __toESM(require("fuse.js"));
81
+ var import_transliteration = require("transliteration");
81
82
 
82
83
  // src/language-aliases/european.ts
83
84
  var EUROPEAN_LANGUAGES = [
@@ -87,6 +88,8 @@ var EUROPEAN_LANGUAGES = [
87
88
  match: [
88
89
  "eng",
89
90
  "en",
91
+ "\u0430\u043D\u0433\u043B\u0438\u0439\u0441\u043A\u0438\u0439",
92
+ "\u0430\u043D\u0433\u043B",
90
93
  "united states",
91
94
  "united states of america",
92
95
  "usa",
@@ -100,6 +103,31 @@ var EUROPEAN_LANGUAGES = [
100
103
  ],
101
104
  suggest: ["irish", "scottish", "welsh"]
102
105
  },
106
+ // English variants: country name / abbreviation → the specific variant CODE.
107
+ // `lang` here is a variant code (not a language name) so Fuse matches it against
108
+ // the `code` key and ranks that exact variant first (e.g. "сша" → en-US #1).
109
+ // Latin "usa"/"us" stay on the `english` entry above (surface the whole group);
110
+ // these cover the Cyrillic exonyms the translated names never contain.
111
+ {
112
+ lang: "en-us",
113
+ match: ["\u0441\u0448\u0430", "\u0448\u0442\u0430\u0442\u044B", "\u0430\u043C\u0435\u0440\u0438\u043A\u0430", "\u0441\u043E\u0435\u0434\u0438\u043D\u0451\u043D\u043D\u044B\u0435 \u0448\u0442\u0430\u0442\u044B", "\u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u043D\u044B\u0435 \u0448\u0442\u0430\u0442\u044B"],
114
+ suggest: []
115
+ },
116
+ {
117
+ lang: "en-au",
118
+ match: ["\u0430\u0432\u0441\u0442\u0440\u0430\u043B\u0438\u044F"],
119
+ suggest: []
120
+ },
121
+ {
122
+ lang: "en-ca",
123
+ match: ["\u043A\u0430\u043D\u0430\u0434\u0430"],
124
+ suggest: []
125
+ },
126
+ {
127
+ lang: "en-ie",
128
+ match: ["\u0438\u0440\u043B\u0430\u043D\u0434\u0438\u044F"],
129
+ suggest: []
130
+ },
103
131
  {
104
132
  lang: "spanish",
105
133
  match: [
@@ -195,7 +223,19 @@ var EUROPEAN_LANGUAGES = [
195
223
  },
196
224
  {
197
225
  lang: "serbian",
198
- match: ["srp", "sr", "ser", "serbia", "serbian", "srpski", "\u0441\u0440\u0431\u0438\u0458\u0430", "\u0441\u0440\u043F\u0441\u043A\u0438"],
226
+ match: [
227
+ "srp",
228
+ "sr",
229
+ "ser",
230
+ "serbia",
231
+ "serbian",
232
+ "srpski",
233
+ "\u0441\u0440\u0431\u0438\u0458\u0430",
234
+ "\u0441\u0440\u043F\u0441\u043A\u0438",
235
+ "\u0441\u0435\u0440\u0431",
236
+ "\u0441\u0435\u0440\u0431\u0441\u043A\u0438\u0439",
237
+ "\u0441\u0435\u0440\u0431\u0441"
238
+ ],
199
239
  suggest: ["croatian", "bosnian", "montenegrin"]
200
240
  },
201
241
  {
@@ -361,7 +401,7 @@ var ASIAN_LANGUAGES = [
361
401
  // Central Asian & Turkic Languages
362
402
  {
363
403
  lang: "kazakh",
364
- match: ["kaz", "kk", "kazakh", "kazakhstan"],
404
+ match: ["kaz", "kk", "kazlat", "kazakh", "kazakhstan", "\u043A\u0430\u0437\u0430\u0445", "\u043A\u0430\u0437\u0430\u0445\u0441\u043A\u0438\u0439", "\u043A\u0430\u0437\u0430\u0445\u0441"],
365
405
  suggest: ["russian", "kyrgyz", "uzbek"]
366
406
  },
367
407
  {
@@ -627,11 +667,17 @@ var _hoisted_1 = {
627
667
  };
628
668
  var _hoisted_2 = ["src", "alt"];
629
669
  var _hoisted_3 = { class: "language-text" };
630
- var _hoisted_4 = { class: "native-name" };
670
+ var _hoisted_4 = {
671
+ key: 0,
672
+ class: "native-name"
673
+ };
631
674
  var _hoisted_5 = ["data-lang-code"];
632
675
  var _hoisted_6 = ["src", "alt"];
633
676
  var _hoisted_7 = { class: "language-text" };
634
- var _hoisted_8 = { class: "native-name" };
677
+ var _hoisted_8 = {
678
+ key: 0,
679
+ class: "native-name"
680
+ };
635
681
  var _hoisted_9 = { class: "multiselect-no-options" };
636
682
  var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
637
683
  __name: "LanguageSelect",
@@ -678,13 +724,13 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
678
724
  "5b09cc17-THEME_COLORS.dark.border": (0, import_vue2.unref)(THEME_COLORS).dark.border,
679
725
  "5b09cc17-THEME_COLORS.dark.placeholder": (0, import_vue2.unref)(THEME_COLORS).dark.placeholder,
680
726
  "5b09cc17-THEME_COLORS.dark.ring": (0, import_vue2.unref)(THEME_COLORS).dark.ring,
727
+ "5b09cc17-THEME_COLORS.dark.dropdownText": (0, import_vue2.unref)(THEME_COLORS).dark.dropdownText,
681
728
  "5b09cc17-THEME_COLORS.dark.optionPointed": (0, import_vue2.unref)(THEME_COLORS).dark.optionPointed,
682
729
  "5b09cc17-THEME_COLORS.dark.optionPointedText": (0, import_vue2.unref)(THEME_COLORS).dark.optionPointedText,
683
730
  "5b09cc17-THEME_COLORS.dark.optionSelected": (0, import_vue2.unref)(THEME_COLORS).dark.optionSelected,
684
731
  "5b09cc17-THEME_COLORS.dark.optionSelectedText": (0, import_vue2.unref)(THEME_COLORS).dark.optionSelectedText,
685
732
  "5b09cc17-THEME_COLORS.dark.dropdownBackground": (0, import_vue2.unref)(THEME_COLORS).dark.dropdownBackground,
686
733
  "5b09cc17-THEME_COLORS.dark.dropdownBorder": (0, import_vue2.unref)(THEME_COLORS).dark.dropdownBorder,
687
- "5b09cc17-THEME_COLORS.dark.dropdownText": (0, import_vue2.unref)(THEME_COLORS).dark.dropdownText,
688
734
  "5b09cc17-COLORS.border.flag": (0, import_vue2.unref)(COLORS).border.flag,
689
735
  "5b09cc17-COLORS.border.dark": (0, import_vue2.unref)(COLORS).border.dark,
690
736
  "5b09cc17-COLORS.text.secondary": (0, import_vue2.unref)(COLORS).text.secondary,
@@ -700,6 +746,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
700
746
  };
701
747
  const props = __props;
702
748
  const emit = __emit;
749
+ const multiselectRef = (0, import_vue3.ref)(null);
703
750
  const isLoading = (0, import_vue3.computed)(() => {
704
751
  return props.languages.length <= 0;
705
752
  });
@@ -710,10 +757,12 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
710
757
  const fuse = (0, import_vue3.computed)(() => {
711
758
  if (!validLanguages.value.length) return null;
712
759
  return new import_fuse.default(validLanguages.value, {
713
- keys: ["name", "native_name", "code"],
760
+ keys: ["name", "native_name", "search_alias", "code"],
714
761
  includeScore: true,
715
- threshold: 0.6,
716
- // Use highest threshold to catch all cases
762
+ threshold: 0.4,
763
+ // Trim fuzzy noise; short abbreviations (e.g. "сша") no longer rank below it
764
+ ignoreLocation: true,
765
+ // Match anywhere in the field, not just near position 0
717
766
  isCaseSensitive: false,
718
767
  minMatchCharLength: 1
719
768
  // Allow single character matches
@@ -745,15 +794,18 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
745
794
  if (!fuse.value) return [];
746
795
  const aliasResult = applyLanguageAlias(query);
747
796
  const searchTerm = Array.isArray(aliasResult.primary) ? aliasResult.primary[0] : aliasResult.primary;
748
- const fuseResults = fuse.value.search(searchTerm);
749
- results = fuseResults.map((result) => result.item);
750
- if (searchTerm !== query) {
751
- const originalResults = fuse.value.search(query);
752
- originalResults.forEach((result) => {
753
- if (!results.some((r) => r.code === result.item.code)) {
754
- results.push(result.item);
755
- }
797
+ const mergeByCode = (hits) => {
798
+ hits.forEach((hit) => {
799
+ if (!results.some((r) => r.code === hit.item.code)) results.push(hit.item);
756
800
  });
801
+ };
802
+ results = fuse.value.search(searchTerm).map((result) => result.item);
803
+ if (searchTerm !== query) {
804
+ mergeByCode(fuse.value.search(query));
805
+ }
806
+ const latinQuery = (0, import_transliteration.transliterate)(query);
807
+ if (latinQuery && latinQuery.toLowerCase() !== query.toLowerCase()) {
808
+ mergeByCode(fuse.value.search(latinQuery));
757
809
  }
758
810
  if (aliasResult.suggestions.length > 0) {
759
811
  aliasResult.suggestions.forEach((suggestion) => {
@@ -791,33 +843,26 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
791
843
  });
792
844
  function handleSearch(query) {
793
845
  searchQuery.value = query;
794
- if (query.trim()) {
795
- (0, import_vue3.nextTick)(() => {
796
- const dropdown = document.querySelector(".multiselect-dropdown");
797
- const firstOption = dropdown?.querySelector(".multiselect-option");
798
- if (firstOption) {
799
- firstOption.scrollIntoView({ behavior: "smooth", block: "nearest" });
800
- firstOption.classList.add("multiselect-option-is-pointed");
801
- }
802
- });
803
- }
846
+ if (!query.trim()) return;
847
+ (0, import_vue3.nextTick)(() => {
848
+ multiselectRef.value?.setPointerFirst?.();
849
+ const dropdown = document.querySelector(".multiselect-dropdown");
850
+ if (dropdown) dropdown.scrollTop = 0;
851
+ });
804
852
  }
805
853
  function handleKeydown(event) {
806
854
  const keyboardEvent = event;
807
- if (keyboardEvent.key === "Tab" || keyboardEvent.key === "Enter") {
808
- const dropdown = document.querySelector(".multiselect-dropdown");
809
- const firstOption = dropdown?.querySelector(".multiselect-option");
810
- if (firstOption && searchQuery.value.trim()) {
811
- const langCode = firstOption.getAttribute("data-lang-code");
812
- if (langCode) {
813
- const language = validLanguages.value.find((lang) => lang.code === langCode);
814
- if (language) {
815
- selectedLanguage.value = language;
816
- searchQuery.value = "";
817
- keyboardEvent.preventDefault();
818
- }
819
- }
820
- }
855
+ if (keyboardEvent.key !== "Tab" && keyboardEvent.key !== "Enter") return;
856
+ if (!searchQuery.value.trim()) return;
857
+ const dropdown = document.querySelector(".multiselect-dropdown");
858
+ const pointed = dropdown?.querySelector(".multiselect-option.is-pointed [data-lang-code]") || dropdown?.querySelector(".multiselect-option [data-lang-code]");
859
+ const langCode = pointed?.getAttribute("data-lang-code");
860
+ if (!langCode) return;
861
+ const language = validLanguages.value.find((lang) => lang.code === langCode);
862
+ if (language) {
863
+ selectedLanguage.value = language;
864
+ searchQuery.value = "";
865
+ keyboardEvent.preventDefault();
821
866
  }
822
867
  }
823
868
  const onFlagError = (event) => {
@@ -845,6 +890,8 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
845
890
  [
846
891
  (0, import_vue2.createCommentVNode)(" Show multiselect when we have languages OR when not loading "),
847
892
  validLanguages.value.length > 0 || !isLoading.value ? ((0, import_vue2.openBlock)(), (0, import_vue2.createBlock)((0, import_vue2.unref)(import_multiselect.default), {
893
+ ref_key: "multiselectRef",
894
+ ref: multiselectRef,
848
895
  key: multiselectKey.value,
849
896
  modelValue: selectedLanguage.value,
850
897
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedLanguage.value = $event),
@@ -857,7 +904,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
857
904
  placeholder: validLanguages.value.length === 0 ? "No languages available" : __props.placeholder,
858
905
  disabled: props.disabled || validLanguages.value.length === 0,
859
906
  loading: isLoading.value,
860
- "track-by": "name",
907
+ "track-by": "code",
861
908
  label: "name",
862
909
  "value-prop": "code",
863
910
  "filter-results": false,
@@ -878,13 +925,13 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
878
925
  1
879
926
  /* TEXT */
880
927
  ),
881
- (0, import_vue2.createElementVNode)(
928
+ value.native_name && value.native_name !== value.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
882
929
  "span",
883
930
  _hoisted_4,
884
- "(" + (0, import_vue2.toDisplayString)(value.native_name || value.name) + ")",
931
+ "(" + (0, import_vue2.toDisplayString)(value.native_name) + ")",
885
932
  1
886
933
  /* TEXT */
887
- )
934
+ )) : (0, import_vue2.createCommentVNode)("v-if", true)
888
935
  ])
889
936
  ])) : (0, import_vue2.createCommentVNode)("v-if", true)
890
937
  ]),
@@ -906,13 +953,13 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
906
953
  1
907
954
  /* TEXT */
908
955
  ),
909
- (0, import_vue2.createElementVNode)(
956
+ option.native_name && option.native_name !== option.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
910
957
  "span",
911
958
  _hoisted_8,
912
- "(" + (0, import_vue2.toDisplayString)(option.native_name || option.name) + ")",
959
+ "(" + (0, import_vue2.toDisplayString)(option.native_name) + ")",
913
960
  1
914
961
  /* TEXT */
915
- )
962
+ )) : (0, import_vue2.createCommentVNode)("v-if", true)
916
963
  ])
917
964
  ], 8, _hoisted_5)
918
965
  ]),
@@ -1229,6 +1276,181 @@ var clearCache = (pattern) => {
1229
1276
  return cacheManager.clear(pattern);
1230
1277
  };
1231
1278
 
1279
+ // src/core.ts
1280
+ var DEFAULT_TRANSLATOR_HOST = "http://localhost:8081";
1281
+ var translationCache = /* @__PURE__ */ new Map();
1282
+ function getTranslationCacheKey(serviceTranslations, options) {
1283
+ return JSON.stringify({
1284
+ translations: serviceTranslations.map((t) => ({
1285
+ [API_FIELD_TEXT]: t[API_FIELD_TEXT],
1286
+ [API_FIELD_FROM]: t[API_FIELD_FROM],
1287
+ [API_FIELD_TO]: t[API_FIELD_TO],
1288
+ [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
1289
+ })),
1290
+ host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
1291
+ context: options[API_FIELD_CTX] || "ui"
1292
+ });
1293
+ }
1294
+ async function translateBatch(translations2 = [], options = {}) {
1295
+ if (!Array.isArray(translations2) || translations2.length === 0) {
1296
+ throw new Error("translations must be a non-empty array");
1297
+ }
1298
+ const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
1299
+ const apiKey = options.apiKey || process.env.TRANSLATOR_API_KEY;
1300
+ const serviceTranslations = [];
1301
+ const indexMap = [];
1302
+ translations2.forEach((tr, idx) => {
1303
+ const from = (tr[API_FIELD_FROM] || "").toLowerCase();
1304
+ const to = (tr[API_FIELD_TO] || "").toLowerCase();
1305
+ if (from === to) {
1306
+ return;
1307
+ }
1308
+ serviceTranslations.push(tr);
1309
+ indexMap.push(idx);
1310
+ });
1311
+ let serviceResults = [];
1312
+ if (serviceTranslations.length > 0) {
1313
+ const cacheKey = getTranslationCacheKey(serviceTranslations, options);
1314
+ if (translationCache.has(cacheKey)) {
1315
+ serviceResults = await translationCache.get(cacheKey);
1316
+ } else {
1317
+ const translationPromise = (async () => {
1318
+ try {
1319
+ const turnstileToken = options.getTurnstileToken ? await options.getTurnstileToken() : void 0;
1320
+ const controller = new AbortController();
1321
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
1322
+ const resp = await fetch(`${translatorHost}/translate`, {
1323
+ method: "POST",
1324
+ headers: {
1325
+ "Content-Type": "application/json",
1326
+ ...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
1327
+ },
1328
+ body: JSON.stringify({
1329
+ [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({
1330
+ [API_FIELD_TEXT]: t[API_FIELD_TEXT],
1331
+ [API_FIELD_FROM]: t[API_FIELD_FROM],
1332
+ [API_FIELD_TO]: t[API_FIELD_TO],
1333
+ [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
1334
+ })),
1335
+ [API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
1336
+ ...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
1337
+ }),
1338
+ signal: controller.signal
1339
+ });
1340
+ clearTimeout(timeoutId);
1341
+ if (!resp.ok) {
1342
+ console.error(
1343
+ "[translator-sdk] Translator error response:",
1344
+ resp.status,
1345
+ resp.statusText
1346
+ );
1347
+ throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`);
1348
+ }
1349
+ let parsed;
1350
+ try {
1351
+ parsed = await resp.clone().json();
1352
+ } catch (jsonErr) {
1353
+ console.error("[translator-sdk] Failed to parse JSON response:", jsonErr);
1354
+ throw new Error(`Failed to parse JSON response: ${jsonErr}`);
1355
+ }
1356
+ const data = parsed || {};
1357
+ if (data[API_FIELD_ERROR]) {
1358
+ console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR]);
1359
+ return serviceTranslations.map((translation) => ({
1360
+ [API_FIELD_TEXT]: translation[API_FIELD_TEXT],
1361
+ [API_FIELD_ERROR]: data[API_FIELD_ERROR]
1362
+ }));
1363
+ }
1364
+ const results = Array.isArray(data[API_FIELD_TRANSLATIONS]) ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {
1365
+ const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || "";
1366
+ if (translation[API_FIELD_ERROR]) {
1367
+ console.warn(
1368
+ `[translator-sdk] Translation error for "${originalText}":`,
1369
+ translation[API_FIELD_ERROR]
1370
+ );
1371
+ return {
1372
+ [API_FIELD_TEXT]: originalText,
1373
+ // Return original text on error
1374
+ [API_FIELD_ERROR]: translation[API_FIELD_ERROR]
1375
+ };
1376
+ }
1377
+ if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
1378
+ return {
1379
+ [API_FIELD_TEXT]: originalText,
1380
+ // For detection, return original text
1381
+ [API_FIELD_FROM]: translation[API_FIELD_FROM]
1382
+ };
1383
+ }
1384
+ return {
1385
+ [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText
1386
+ };
1387
+ }) : data[API_FIELD_TEXT] ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }] : [];
1388
+ return results;
1389
+ } catch (error) {
1390
+ translationCache.delete(cacheKey);
1391
+ if (error instanceof Error && error.name === "AbortError") {
1392
+ const message2 = `Translator request to ${translatorHost} timed out after 5 seconds.`;
1393
+ console.error(message2);
1394
+ throw new Error(message2);
1395
+ }
1396
+ const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`;
1397
+ console.error(message);
1398
+ throw new Error(message);
1399
+ }
1400
+ })();
1401
+ translationCache.set(cacheKey, translationPromise);
1402
+ serviceResults = await translationPromise;
1403
+ }
1404
+ }
1405
+ const final = translations2.map((tr, idx) => {
1406
+ const from = (tr[API_FIELD_FROM] || "").toLowerCase();
1407
+ const to = (tr[API_FIELD_TO] || "").toLowerCase();
1408
+ if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1409
+ const svcIdx = indexMap.indexOf(idx);
1410
+ if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1411
+ return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
1412
+ });
1413
+ return final;
1414
+ }
1415
+
1416
+ // src/utils/localizeLanguageNames.ts
1417
+ var CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
1418
+ var NAME_CTX = "language name";
1419
+ async function translateNames(names, targetLang, options) {
1420
+ const requests = names.map((name) => ({
1421
+ [API_FIELD_TEXT]: name,
1422
+ [API_FIELD_FROM]: "en",
1423
+ [API_FIELD_TO]: targetLang,
1424
+ [API_FIELD_CTX]: NAME_CTX
1425
+ }));
1426
+ try {
1427
+ const results = await translateBatch(requests, options);
1428
+ const map = {};
1429
+ names.forEach((name, i) => {
1430
+ const translated = results[i]?.[API_FIELD_TEXT];
1431
+ if (translated) map[name] = translated;
1432
+ });
1433
+ return map;
1434
+ } catch (error) {
1435
+ devDebug("[localizeLanguageNames] translate failed:", error);
1436
+ return null;
1437
+ }
1438
+ }
1439
+ async function localizeLanguageNames(languages, targetLang, options = {}) {
1440
+ const to = (targetLang || "").toLowerCase();
1441
+ if (!to || to === "en" || languages.length === 0) return languages;
1442
+ const cacheKey = `langie_langnames_${to}`;
1443
+ let map = getCache(cacheKey);
1444
+ if (!map) {
1445
+ const names = [...new Set(languages.map((l) => l.name).filter(Boolean))];
1446
+ map = await translateNames(names, to, options);
1447
+ if (map && Object.keys(map).length) setCache(cacheKey, map, CACHE_TTL_MS);
1448
+ }
1449
+ if (!map) return languages;
1450
+ const resolved = map;
1451
+ return languages.map((l) => ({ ...l, search_alias: resolved[l.name] || l.search_alias }));
1452
+ }
1453
+
1232
1454
  // src/composables/useLangie-core.ts
1233
1455
  var availableLanguages = (0, import_vue4.ref)([]);
1234
1456
  var translations = (0, import_vue4.reactive)({});
@@ -1268,6 +1490,16 @@ function useLangieCore(options = {}) {
1268
1490
  devDebug("[useLangie] No cached languages found");
1269
1491
  }
1270
1492
  }
1493
+ const applyLocalizedNames = (lang) => {
1494
+ const base = availableLanguages.value;
1495
+ if (!base.length) return;
1496
+ localizeLanguageNames(base, lang, { translatorHost }).then((localized) => {
1497
+ if (localized !== base && availableLanguages.value === base) {
1498
+ availableLanguages.value = localized;
1499
+ }
1500
+ }).catch(() => {
1501
+ });
1502
+ };
1271
1503
  const setLanguage = (lang) => {
1272
1504
  if (lang && lang !== currentLanguage.value) {
1273
1505
  }
@@ -1275,6 +1507,7 @@ function useLangieCore(options = {}) {
1275
1507
  if (typeof window !== "undefined") {
1276
1508
  localStorage.setItem("interface_language", lang);
1277
1509
  }
1510
+ applyLocalizedNames(lang);
1278
1511
  };
1279
1512
  const fetchLanguages = async (opts = {}) => {
1280
1513
  const { force = false, country: explicitCountry } = opts;
@@ -1375,6 +1608,7 @@ function useLangieCore(options = {}) {
1375
1608
  }
1376
1609
  _languagesCache = filtered;
1377
1610
  _languagesPromise = null;
1611
+ applyLocalizedNames(currentLanguage.value);
1378
1612
  return filtered;
1379
1613
  } catch (error) {
1380
1614
  devDebug("[useLangie] Language fetch error:", { error });