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.
- package/dist/components/index.cjs +282 -48
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.css +1 -0
- package/dist/components/index.css.map +1 -1
- package/dist/components/index.mjs +283 -49
- package/dist/components/index.mjs.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +327 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -0
- package/dist/index.css.map +1 -1
- package/dist/index.mjs +328 -231
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils/localizeLanguageNames.d.ts +9 -0
- package/package.json +3 -2
- package/src/index.d.ts +2 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
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: [
|
|
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 = {
|
|
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 = {
|
|
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.
|
|
716
|
-
//
|
|
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
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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": "
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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 });
|