langie 1.11.0 → 1.12.0
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/InterfaceLanguageSelect.vue.d.ts +1 -1
- package/dist/components/index.cjs +288 -180
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.css.map +1 -1
- package/dist/components/index.mjs +289 -181
- package/dist/components/index.mjs.map +1 -1
- package/dist/composables/useLangie-core.d.ts +2 -0
- package/dist/core.cjs +10 -3
- package/dist/core.cjs.map +1 -1
- package/dist/core.mjs +10 -3
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +333 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +334 -226
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/useLangie.d.ts +9 -1
- package/dist/utils/displayNative.d.ts +10 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
2
|
+
* langie v1.12.0
|
|
3
3
|
* (c) 2026 nlit
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*
|
|
@@ -88,6 +88,8 @@ var EUROPEAN_LANGUAGES = [
|
|
|
88
88
|
match: [
|
|
89
89
|
"eng",
|
|
90
90
|
"en",
|
|
91
|
+
"\u0430\u043D\u0433\u043B\u0438\u0439\u0441\u043A\u0438\u0439",
|
|
92
|
+
"\u0430\u043D\u0433\u043B",
|
|
91
93
|
"united states",
|
|
92
94
|
"united states of america",
|
|
93
95
|
"usa",
|
|
@@ -101,6 +103,31 @@ var EUROPEAN_LANGUAGES = [
|
|
|
101
103
|
],
|
|
102
104
|
suggest: ["irish", "scottish", "welsh"]
|
|
103
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
|
+
},
|
|
104
131
|
{
|
|
105
132
|
lang: "spanish",
|
|
106
133
|
match: [
|
|
@@ -562,6 +589,12 @@ function applyLanguageAlias(term = "") {
|
|
|
562
589
|
return { primary, suggestions: Array.from(suggestions) };
|
|
563
590
|
}
|
|
564
591
|
|
|
592
|
+
// src/utils/displayNative.ts
|
|
593
|
+
var TRAILING_PAREN = /\s*[((][^()()]*[))]\s*$/;
|
|
594
|
+
function displayNative(name = "", native = "") {
|
|
595
|
+
return name !== native && TRAILING_PAREN.test(name) && TRAILING_PAREN.test(native) ? native.replace(TRAILING_PAREN, "") : native;
|
|
596
|
+
}
|
|
597
|
+
|
|
565
598
|
// src/constants/colors.ts
|
|
566
599
|
var COLORS = {
|
|
567
600
|
// Primary colors
|
|
@@ -719,6 +752,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
719
752
|
};
|
|
720
753
|
const props = __props;
|
|
721
754
|
const emit = __emit;
|
|
755
|
+
const multiselectRef = (0, import_vue3.ref)(null);
|
|
722
756
|
const isLoading = (0, import_vue3.computed)(() => {
|
|
723
757
|
return props.languages.length <= 0;
|
|
724
758
|
});
|
|
@@ -731,8 +765,10 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
731
765
|
return new import_fuse.default(validLanguages.value, {
|
|
732
766
|
keys: ["name", "native_name", "search_alias", "code"],
|
|
733
767
|
includeScore: true,
|
|
734
|
-
threshold: 0.
|
|
735
|
-
//
|
|
768
|
+
threshold: 0.4,
|
|
769
|
+
// Trim fuzzy noise; short abbreviations (e.g. "сша") no longer rank below it
|
|
770
|
+
ignoreLocation: true,
|
|
771
|
+
// Match anywhere in the field, not just near position 0
|
|
736
772
|
isCaseSensitive: false,
|
|
737
773
|
minMatchCharLength: 1
|
|
738
774
|
// Allow single character matches
|
|
@@ -813,33 +849,26 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
813
849
|
});
|
|
814
850
|
function handleSearch(query) {
|
|
815
851
|
searchQuery.value = query;
|
|
816
|
-
if (query.trim())
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
firstOption.classList.add("multiselect-option-is-pointed");
|
|
823
|
-
}
|
|
824
|
-
});
|
|
825
|
-
}
|
|
852
|
+
if (!query.trim()) return;
|
|
853
|
+
(0, import_vue3.nextTick)(() => {
|
|
854
|
+
multiselectRef.value?.setPointerFirst?.();
|
|
855
|
+
const dropdown = document.querySelector(".multiselect-dropdown");
|
|
856
|
+
if (dropdown) dropdown.scrollTop = 0;
|
|
857
|
+
});
|
|
826
858
|
}
|
|
827
859
|
function handleKeydown(event) {
|
|
828
860
|
const keyboardEvent = event;
|
|
829
|
-
if (keyboardEvent.key
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
861
|
+
if (keyboardEvent.key !== "Tab" && keyboardEvent.key !== "Enter") return;
|
|
862
|
+
if (!searchQuery.value.trim()) return;
|
|
863
|
+
const dropdown = document.querySelector(".multiselect-dropdown");
|
|
864
|
+
const pointed = dropdown?.querySelector(".multiselect-option.is-pointed [data-lang-code]") || dropdown?.querySelector(".multiselect-option [data-lang-code]");
|
|
865
|
+
const langCode = pointed?.getAttribute("data-lang-code");
|
|
866
|
+
if (!langCode) return;
|
|
867
|
+
const language = validLanguages.value.find((lang) => lang.code === langCode);
|
|
868
|
+
if (language) {
|
|
869
|
+
selectedLanguage.value = language;
|
|
870
|
+
searchQuery.value = "";
|
|
871
|
+
keyboardEvent.preventDefault();
|
|
843
872
|
}
|
|
844
873
|
}
|
|
845
874
|
const onFlagError = (event) => {
|
|
@@ -867,6 +896,8 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
867
896
|
[
|
|
868
897
|
(0, import_vue2.createCommentVNode)(" Show multiselect when we have languages OR when not loading "),
|
|
869
898
|
validLanguages.value.length > 0 || !isLoading.value ? ((0, import_vue2.openBlock)(), (0, import_vue2.createBlock)((0, import_vue2.unref)(import_multiselect.default), {
|
|
899
|
+
ref_key: "multiselectRef",
|
|
900
|
+
ref: multiselectRef,
|
|
870
901
|
key: multiselectKey.value,
|
|
871
902
|
modelValue: selectedLanguage.value,
|
|
872
903
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedLanguage.value = $event),
|
|
@@ -879,7 +910,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
879
910
|
placeholder: validLanguages.value.length === 0 ? "No languages available" : __props.placeholder,
|
|
880
911
|
disabled: props.disabled || validLanguages.value.length === 0,
|
|
881
912
|
loading: isLoading.value,
|
|
882
|
-
"track-by": "
|
|
913
|
+
"track-by": "code",
|
|
883
914
|
label: "name",
|
|
884
915
|
"value-prop": "code",
|
|
885
916
|
"filter-results": false,
|
|
@@ -903,7 +934,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
903
934
|
value.native_name && value.native_name !== value.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
|
|
904
935
|
"span",
|
|
905
936
|
_hoisted_4,
|
|
906
|
-
"(" + (0, import_vue2.toDisplayString)(value.native_name) + ")",
|
|
937
|
+
"(" + (0, import_vue2.toDisplayString)((0, import_vue2.unref)(displayNative)(value.name, value.native_name)) + ")",
|
|
907
938
|
1
|
|
908
939
|
/* TEXT */
|
|
909
940
|
)) : (0, import_vue2.createCommentVNode)("v-if", true)
|
|
@@ -931,7 +962,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
931
962
|
option.native_name && option.native_name !== option.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
|
|
932
963
|
"span",
|
|
933
964
|
_hoisted_8,
|
|
934
|
-
"(" + (0, import_vue2.toDisplayString)(option.native_name) + ")",
|
|
965
|
+
"(" + (0, import_vue2.toDisplayString)((0, import_vue2.unref)(displayNative)(option.name, option.native_name)) + ")",
|
|
935
966
|
1
|
|
936
967
|
/* TEXT */
|
|
937
968
|
)) : (0, import_vue2.createCommentVNode)("v-if", true)
|
|
@@ -994,9 +1025,6 @@ var import_vue8 = require("vue");
|
|
|
994
1025
|
// src/useLangie.ts
|
|
995
1026
|
var import_vue5 = require("vue");
|
|
996
1027
|
|
|
997
|
-
// src/composables/useLangie-core.ts
|
|
998
|
-
var import_vue4 = require("vue");
|
|
999
|
-
|
|
1000
1028
|
// src/constants.ts
|
|
1001
1029
|
var DEFAULT_API_HOST = "https://api.langie.uk/v1";
|
|
1002
1030
|
var API_FIELD_TEXT = "t";
|
|
@@ -1007,6 +1035,153 @@ var API_FIELD_TRANSLATIONS = "translations";
|
|
|
1007
1035
|
var API_FIELD_ERROR = "error";
|
|
1008
1036
|
var API_FIELD_TURNSTILE = "turnstileToken";
|
|
1009
1037
|
|
|
1038
|
+
// src/core.ts
|
|
1039
|
+
var DEFAULT_TRANSLATOR_HOST = "http://localhost:8081";
|
|
1040
|
+
var DEFAULT_API_KEY_ENV = "TRANSLATOR_API_KEY";
|
|
1041
|
+
function resolveApiKey(options) {
|
|
1042
|
+
if (options.apiKey) return options.apiKey;
|
|
1043
|
+
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1044
|
+
if (!env) return void 0;
|
|
1045
|
+
return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV];
|
|
1046
|
+
}
|
|
1047
|
+
var translationCache = /* @__PURE__ */ new Map();
|
|
1048
|
+
function getTranslationCacheKey(serviceTranslations, options) {
|
|
1049
|
+
return JSON.stringify({
|
|
1050
|
+
translations: serviceTranslations.map((t) => ({
|
|
1051
|
+
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1052
|
+
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1053
|
+
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1054
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1055
|
+
})),
|
|
1056
|
+
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
1057
|
+
context: options[API_FIELD_CTX] || "ui"
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
async function translateBatch(translations2 = [], options = {}) {
|
|
1061
|
+
if (!Array.isArray(translations2) || translations2.length === 0) {
|
|
1062
|
+
throw new Error("translations must be a non-empty array");
|
|
1063
|
+
}
|
|
1064
|
+
const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
|
|
1065
|
+
const apiKey = resolveApiKey(options);
|
|
1066
|
+
const serviceTranslations = [];
|
|
1067
|
+
const indexMap = [];
|
|
1068
|
+
translations2.forEach((tr, idx) => {
|
|
1069
|
+
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1070
|
+
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1071
|
+
if (from === to) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
serviceTranslations.push(tr);
|
|
1075
|
+
indexMap.push(idx);
|
|
1076
|
+
});
|
|
1077
|
+
let serviceResults = [];
|
|
1078
|
+
if (serviceTranslations.length > 0) {
|
|
1079
|
+
const cacheKey = getTranslationCacheKey(serviceTranslations, options);
|
|
1080
|
+
if (translationCache.has(cacheKey)) {
|
|
1081
|
+
serviceResults = await translationCache.get(cacheKey);
|
|
1082
|
+
} else {
|
|
1083
|
+
const translationPromise = (async () => {
|
|
1084
|
+
try {
|
|
1085
|
+
const turnstileToken = options.getTurnstileToken ? await options.getTurnstileToken() : void 0;
|
|
1086
|
+
const controller = new AbortController();
|
|
1087
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
1088
|
+
const resp = await fetch(`${translatorHost}/translate`, {
|
|
1089
|
+
method: "POST",
|
|
1090
|
+
headers: {
|
|
1091
|
+
"Content-Type": "application/json",
|
|
1092
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
|
|
1093
|
+
},
|
|
1094
|
+
body: JSON.stringify({
|
|
1095
|
+
[API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({
|
|
1096
|
+
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1097
|
+
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1098
|
+
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1099
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1100
|
+
})),
|
|
1101
|
+
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
1102
|
+
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
|
1103
|
+
}),
|
|
1104
|
+
signal: controller.signal
|
|
1105
|
+
});
|
|
1106
|
+
clearTimeout(timeoutId);
|
|
1107
|
+
if (!resp.ok) {
|
|
1108
|
+
console.error(
|
|
1109
|
+
"[translator-sdk] Translator error response:",
|
|
1110
|
+
resp.status,
|
|
1111
|
+
resp.statusText
|
|
1112
|
+
);
|
|
1113
|
+
throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`);
|
|
1114
|
+
}
|
|
1115
|
+
let parsed;
|
|
1116
|
+
try {
|
|
1117
|
+
parsed = await resp.clone().json();
|
|
1118
|
+
} catch (jsonErr) {
|
|
1119
|
+
console.error("[translator-sdk] Failed to parse JSON response:", jsonErr);
|
|
1120
|
+
throw new Error(`Failed to parse JSON response: ${jsonErr}`);
|
|
1121
|
+
}
|
|
1122
|
+
const data = parsed || {};
|
|
1123
|
+
if (data[API_FIELD_ERROR]) {
|
|
1124
|
+
console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR]);
|
|
1125
|
+
return serviceTranslations.map((translation) => ({
|
|
1126
|
+
[API_FIELD_TEXT]: translation[API_FIELD_TEXT],
|
|
1127
|
+
[API_FIELD_ERROR]: data[API_FIELD_ERROR]
|
|
1128
|
+
}));
|
|
1129
|
+
}
|
|
1130
|
+
const results = Array.isArray(data[API_FIELD_TRANSLATIONS]) ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {
|
|
1131
|
+
const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || "";
|
|
1132
|
+
if (translation[API_FIELD_ERROR]) {
|
|
1133
|
+
console.warn(
|
|
1134
|
+
`[translator-sdk] Translation error for "${originalText}":`,
|
|
1135
|
+
translation[API_FIELD_ERROR]
|
|
1136
|
+
);
|
|
1137
|
+
return {
|
|
1138
|
+
[API_FIELD_TEXT]: originalText,
|
|
1139
|
+
// Return original text on error
|
|
1140
|
+
[API_FIELD_ERROR]: translation[API_FIELD_ERROR]
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
|
|
1144
|
+
return {
|
|
1145
|
+
[API_FIELD_TEXT]: originalText,
|
|
1146
|
+
// For detection, return original text
|
|
1147
|
+
[API_FIELD_FROM]: translation[API_FIELD_FROM]
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
return {
|
|
1151
|
+
[API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText
|
|
1152
|
+
};
|
|
1153
|
+
}) : data[API_FIELD_TEXT] ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }] : [];
|
|
1154
|
+
return results;
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
translationCache.delete(cacheKey);
|
|
1157
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1158
|
+
const message2 = `Translator request to ${translatorHost} timed out after 5 seconds.`;
|
|
1159
|
+
console.error(message2);
|
|
1160
|
+
throw new Error(message2);
|
|
1161
|
+
}
|
|
1162
|
+
const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`;
|
|
1163
|
+
console.error(message);
|
|
1164
|
+
throw new Error(message);
|
|
1165
|
+
}
|
|
1166
|
+
})();
|
|
1167
|
+
translationCache.set(cacheKey, translationPromise);
|
|
1168
|
+
serviceResults = await translationPromise;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
const final = translations2.map((tr, idx) => {
|
|
1172
|
+
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1173
|
+
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1174
|
+
if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1175
|
+
const svcIdx = indexMap.indexOf(idx);
|
|
1176
|
+
if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1177
|
+
return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1178
|
+
});
|
|
1179
|
+
return final;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/composables/useLangie-core.ts
|
|
1183
|
+
var import_vue4 = require("vue");
|
|
1184
|
+
|
|
1010
1185
|
// src/utils/debug.ts
|
|
1011
1186
|
var import_meta = {};
|
|
1012
1187
|
function devDebug(...args) {
|
|
@@ -1251,143 +1426,6 @@ var clearCache = (pattern) => {
|
|
|
1251
1426
|
return cacheManager.clear(pattern);
|
|
1252
1427
|
};
|
|
1253
1428
|
|
|
1254
|
-
// src/core.ts
|
|
1255
|
-
var DEFAULT_TRANSLATOR_HOST = "http://localhost:8081";
|
|
1256
|
-
var translationCache = /* @__PURE__ */ new Map();
|
|
1257
|
-
function getTranslationCacheKey(serviceTranslations, options) {
|
|
1258
|
-
return JSON.stringify({
|
|
1259
|
-
translations: serviceTranslations.map((t) => ({
|
|
1260
|
-
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1261
|
-
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1262
|
-
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1263
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1264
|
-
})),
|
|
1265
|
-
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
1266
|
-
context: options[API_FIELD_CTX] || "ui"
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
async function translateBatch(translations2 = [], options = {}) {
|
|
1270
|
-
if (!Array.isArray(translations2) || translations2.length === 0) {
|
|
1271
|
-
throw new Error("translations must be a non-empty array");
|
|
1272
|
-
}
|
|
1273
|
-
const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
|
|
1274
|
-
const apiKey = options.apiKey || process.env.TRANSLATOR_API_KEY;
|
|
1275
|
-
const serviceTranslations = [];
|
|
1276
|
-
const indexMap = [];
|
|
1277
|
-
translations2.forEach((tr, idx) => {
|
|
1278
|
-
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1279
|
-
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1280
|
-
if (from === to) {
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
serviceTranslations.push(tr);
|
|
1284
|
-
indexMap.push(idx);
|
|
1285
|
-
});
|
|
1286
|
-
let serviceResults = [];
|
|
1287
|
-
if (serviceTranslations.length > 0) {
|
|
1288
|
-
const cacheKey = getTranslationCacheKey(serviceTranslations, options);
|
|
1289
|
-
if (translationCache.has(cacheKey)) {
|
|
1290
|
-
serviceResults = await translationCache.get(cacheKey);
|
|
1291
|
-
} else {
|
|
1292
|
-
const translationPromise = (async () => {
|
|
1293
|
-
try {
|
|
1294
|
-
const turnstileToken = options.getTurnstileToken ? await options.getTurnstileToken() : void 0;
|
|
1295
|
-
const controller = new AbortController();
|
|
1296
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
1297
|
-
const resp = await fetch(`${translatorHost}/translate`, {
|
|
1298
|
-
method: "POST",
|
|
1299
|
-
headers: {
|
|
1300
|
-
"Content-Type": "application/json",
|
|
1301
|
-
...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
|
|
1302
|
-
},
|
|
1303
|
-
body: JSON.stringify({
|
|
1304
|
-
[API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({
|
|
1305
|
-
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1306
|
-
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1307
|
-
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1308
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1309
|
-
})),
|
|
1310
|
-
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
1311
|
-
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
|
1312
|
-
}),
|
|
1313
|
-
signal: controller.signal
|
|
1314
|
-
});
|
|
1315
|
-
clearTimeout(timeoutId);
|
|
1316
|
-
if (!resp.ok) {
|
|
1317
|
-
console.error(
|
|
1318
|
-
"[translator-sdk] Translator error response:",
|
|
1319
|
-
resp.status,
|
|
1320
|
-
resp.statusText
|
|
1321
|
-
);
|
|
1322
|
-
throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`);
|
|
1323
|
-
}
|
|
1324
|
-
let parsed;
|
|
1325
|
-
try {
|
|
1326
|
-
parsed = await resp.clone().json();
|
|
1327
|
-
} catch (jsonErr) {
|
|
1328
|
-
console.error("[translator-sdk] Failed to parse JSON response:", jsonErr);
|
|
1329
|
-
throw new Error(`Failed to parse JSON response: ${jsonErr}`);
|
|
1330
|
-
}
|
|
1331
|
-
const data = parsed || {};
|
|
1332
|
-
if (data[API_FIELD_ERROR]) {
|
|
1333
|
-
console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR]);
|
|
1334
|
-
return serviceTranslations.map((translation) => ({
|
|
1335
|
-
[API_FIELD_TEXT]: translation[API_FIELD_TEXT],
|
|
1336
|
-
[API_FIELD_ERROR]: data[API_FIELD_ERROR]
|
|
1337
|
-
}));
|
|
1338
|
-
}
|
|
1339
|
-
const results = Array.isArray(data[API_FIELD_TRANSLATIONS]) ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {
|
|
1340
|
-
const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || "";
|
|
1341
|
-
if (translation[API_FIELD_ERROR]) {
|
|
1342
|
-
console.warn(
|
|
1343
|
-
`[translator-sdk] Translation error for "${originalText}":`,
|
|
1344
|
-
translation[API_FIELD_ERROR]
|
|
1345
|
-
);
|
|
1346
|
-
return {
|
|
1347
|
-
[API_FIELD_TEXT]: originalText,
|
|
1348
|
-
// Return original text on error
|
|
1349
|
-
[API_FIELD_ERROR]: translation[API_FIELD_ERROR]
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
|
|
1353
|
-
return {
|
|
1354
|
-
[API_FIELD_TEXT]: originalText,
|
|
1355
|
-
// For detection, return original text
|
|
1356
|
-
[API_FIELD_FROM]: translation[API_FIELD_FROM]
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
return {
|
|
1360
|
-
[API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText
|
|
1361
|
-
};
|
|
1362
|
-
}) : data[API_FIELD_TEXT] ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }] : [];
|
|
1363
|
-
return results;
|
|
1364
|
-
} catch (error) {
|
|
1365
|
-
translationCache.delete(cacheKey);
|
|
1366
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
1367
|
-
const message2 = `Translator request to ${translatorHost} timed out after 5 seconds.`;
|
|
1368
|
-
console.error(message2);
|
|
1369
|
-
throw new Error(message2);
|
|
1370
|
-
}
|
|
1371
|
-
const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`;
|
|
1372
|
-
console.error(message);
|
|
1373
|
-
throw new Error(message);
|
|
1374
|
-
}
|
|
1375
|
-
})();
|
|
1376
|
-
translationCache.set(cacheKey, translationPromise);
|
|
1377
|
-
serviceResults = await translationPromise;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
const final = translations2.map((tr, idx) => {
|
|
1381
|
-
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1382
|
-
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1383
|
-
if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1384
|
-
const svcIdx = indexMap.indexOf(idx);
|
|
1385
|
-
if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1386
|
-
return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1387
|
-
});
|
|
1388
|
-
return final;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
1429
|
// src/utils/localizeLanguageNames.ts
|
|
1392
1430
|
var CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1393
1431
|
var NAME_CTX = "language name";
|
|
@@ -1434,7 +1472,9 @@ var currentLanguage = (0, import_vue4.ref)("en");
|
|
|
1434
1472
|
var _translatorHost = DEFAULT_API_HOST;
|
|
1435
1473
|
var _autoSelected = false;
|
|
1436
1474
|
var _languagesCache = null;
|
|
1475
|
+
var _languagesCacheAt = 0;
|
|
1437
1476
|
var _languagesPromise = null;
|
|
1477
|
+
var LANGUAGES_CACHE_KEY = "langie_languages_cache";
|
|
1438
1478
|
function useLangieCore(options = {}) {
|
|
1439
1479
|
if (options.translatorHost) {
|
|
1440
1480
|
_translatorHost = options.translatorHost;
|
|
@@ -1452,7 +1492,7 @@ function useLangieCore(options = {}) {
|
|
|
1452
1492
|
}
|
|
1453
1493
|
}
|
|
1454
1494
|
if (typeof window !== "undefined") {
|
|
1455
|
-
const cachedLanguages = getCache(
|
|
1495
|
+
const cachedLanguages = getCache(LANGUAGES_CACHE_KEY);
|
|
1456
1496
|
if (cachedLanguages && availableLanguages.value.length === 0) {
|
|
1457
1497
|
availableLanguages.value = cachedLanguages;
|
|
1458
1498
|
devDebug("[useLangie] Seeded languages from cache (will revalidate):", cachedLanguages.length);
|
|
@@ -1485,15 +1525,18 @@ function useLangieCore(options = {}) {
|
|
|
1485
1525
|
applyLocalizedNames(lang);
|
|
1486
1526
|
};
|
|
1487
1527
|
const fetchLanguages = async (opts = {}) => {
|
|
1488
|
-
const { force = false, country: explicitCountry } = opts;
|
|
1528
|
+
const { force = false, country: explicitCountry, maxAgeMs } = opts;
|
|
1529
|
+
const cacheFresh = !!_languagesCache && (maxAgeMs === void 0 || Date.now() - _languagesCacheAt < maxAgeMs);
|
|
1489
1530
|
devDebug("[useLangie] fetchLanguages called:", {
|
|
1490
1531
|
force,
|
|
1532
|
+
maxAgeMs,
|
|
1533
|
+
cacheFresh,
|
|
1491
1534
|
hasCache: !!_languagesCache,
|
|
1492
1535
|
hasPromise: !!_languagesPromise,
|
|
1493
1536
|
availableLanguagesLength: availableLanguages.value.length
|
|
1494
1537
|
});
|
|
1495
1538
|
if (!force) {
|
|
1496
|
-
if (
|
|
1539
|
+
if (cacheFresh) {
|
|
1497
1540
|
devDebug("[useLangie] Returning cached languages:", _languagesCache.length);
|
|
1498
1541
|
return _languagesCache;
|
|
1499
1542
|
}
|
|
@@ -1551,7 +1594,7 @@ function useLangieCore(options = {}) {
|
|
|
1551
1594
|
availableLanguages.value = filtered;
|
|
1552
1595
|
devDebug("[useLangie] Set availableLanguages:", filtered.length);
|
|
1553
1596
|
if (typeof window !== "undefined") {
|
|
1554
|
-
const saved = setCache(
|
|
1597
|
+
const saved = setCache(LANGUAGES_CACHE_KEY, filtered, 7 * 24 * 60 * 60 * 1e3);
|
|
1555
1598
|
devDebug(
|
|
1556
1599
|
"[useLangie] Saved languages to cache:",
|
|
1557
1600
|
saved ? "success" : "failed",
|
|
@@ -1582,6 +1625,7 @@ function useLangieCore(options = {}) {
|
|
|
1582
1625
|
_autoSelected = true;
|
|
1583
1626
|
}
|
|
1584
1627
|
_languagesCache = filtered;
|
|
1628
|
+
_languagesCacheAt = Date.now();
|
|
1585
1629
|
_languagesPromise = null;
|
|
1586
1630
|
applyLocalizedNames(currentLanguage.value);
|
|
1587
1631
|
return filtered;
|
|
@@ -1590,6 +1634,14 @@ function useLangieCore(options = {}) {
|
|
|
1590
1634
|
return [];
|
|
1591
1635
|
}
|
|
1592
1636
|
};
|
|
1637
|
+
const invalidateLanguages = () => {
|
|
1638
|
+
_languagesCache = null;
|
|
1639
|
+
_languagesCacheAt = 0;
|
|
1640
|
+
_languagesPromise = null;
|
|
1641
|
+
if (typeof window !== "undefined") {
|
|
1642
|
+
clearCache(LANGUAGES_CACHE_KEY);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1593
1645
|
const clearTranslations = () => {
|
|
1594
1646
|
Object.keys(translations).forEach((key) => delete translations[key]);
|
|
1595
1647
|
Object.keys(uiTranslations).forEach((key) => delete uiTranslations[key]);
|
|
@@ -1606,6 +1658,7 @@ function useLangieCore(options = {}) {
|
|
|
1606
1658
|
isLoading,
|
|
1607
1659
|
setLanguage,
|
|
1608
1660
|
fetchLanguages,
|
|
1661
|
+
invalidateLanguages,
|
|
1609
1662
|
clearTranslations,
|
|
1610
1663
|
translatorHost
|
|
1611
1664
|
};
|
|
@@ -1923,6 +1976,7 @@ function createLangieInstance(options = {}) {
|
|
|
1923
1976
|
isLoading,
|
|
1924
1977
|
setLanguage,
|
|
1925
1978
|
fetchLanguages,
|
|
1979
|
+
invalidateLanguages,
|
|
1926
1980
|
translatorHost,
|
|
1927
1981
|
clearTranslations
|
|
1928
1982
|
} = core;
|
|
@@ -1934,9 +1988,17 @@ function createLangieInstance(options = {}) {
|
|
|
1934
1988
|
Object.assign(ltDefaults, defaults);
|
|
1935
1989
|
};
|
|
1936
1990
|
const getLtDefaults = () => ({ ...ltDefaults });
|
|
1991
|
+
const seed = {};
|
|
1992
|
+
if (options.ssrTranslations) seedTranslations(options.ssrTranslations);
|
|
1993
|
+
function seedTranslations(entries) {
|
|
1994
|
+
for (const lang of Object.keys(entries)) {
|
|
1995
|
+
seed[lang] = { ...seed[lang] || {}, ...entries[lang] };
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const getSeed = (lang, text, ctx) => seed[lang]?.[`${text}|${ctx}`];
|
|
1937
1999
|
const CACHE_KEY = "langie_translations_cache";
|
|
1938
2000
|
const UI_CACHE_KEY = "langie_ui_translations_cache";
|
|
1939
|
-
const
|
|
2001
|
+
const LANGUAGES_CACHE_KEY2 = "langie_languages_cache";
|
|
1940
2002
|
const loadCachedTranslations = () => {
|
|
1941
2003
|
if (typeof window === "undefined") return;
|
|
1942
2004
|
const cachedTranslations = getCache(CACHE_KEY);
|
|
@@ -1964,7 +2026,7 @@ function createLangieInstance(options = {}) {
|
|
|
1964
2026
|
};
|
|
1965
2027
|
const loadCachedLanguages = () => {
|
|
1966
2028
|
if (typeof window === "undefined") return;
|
|
1967
|
-
const cachedLanguages = getCache(
|
|
2029
|
+
const cachedLanguages = getCache(LANGUAGES_CACHE_KEY2);
|
|
1968
2030
|
if (cachedLanguages) {
|
|
1969
2031
|
availableLanguages2.value = cachedLanguages;
|
|
1970
2032
|
}
|
|
@@ -2078,6 +2140,10 @@ function createLangieInstance(options = {}) {
|
|
|
2078
2140
|
if (cache[cacheKey]) {
|
|
2079
2141
|
return cache[cacheKey];
|
|
2080
2142
|
}
|
|
2143
|
+
const seeded = getSeed(to, text, effectiveCtx);
|
|
2144
|
+
if (seeded) {
|
|
2145
|
+
return seeded;
|
|
2146
|
+
}
|
|
2081
2147
|
const errorKey = `${text}|${effectiveCtx}|${from}|${to}`;
|
|
2082
2148
|
if (translationErrors.has(errorKey)) {
|
|
2083
2149
|
return text;
|
|
@@ -2086,6 +2152,7 @@ function createLangieInstance(options = {}) {
|
|
|
2086
2152
|
if (recentlyQueued.has(languageCacheKey)) {
|
|
2087
2153
|
return text;
|
|
2088
2154
|
}
|
|
2155
|
+
options.onTranslationMiss?.({ text, ctx: effectiveCtx, from, to });
|
|
2089
2156
|
batching.queueTranslation(text, effectiveCtx, from, to, cacheKey, toLang !== void 0);
|
|
2090
2157
|
recentlyQueued.add(languageCacheKey);
|
|
2091
2158
|
const clearDelay = 100;
|
|
@@ -2098,6 +2165,42 @@ function createLangieInstance(options = {}) {
|
|
|
2098
2165
|
};
|
|
2099
2166
|
const l = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, false);
|
|
2100
2167
|
const lr = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, true);
|
|
2168
|
+
const peekTranslation = (text, ctx, toLang) => {
|
|
2169
|
+
const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
|
|
2170
|
+
const from = ltDefaults.orig || "";
|
|
2171
|
+
const to = toLang || currentLanguage2.value;
|
|
2172
|
+
if (from === to) return text;
|
|
2173
|
+
const baseKey = `${text}|${effectiveCtx}`;
|
|
2174
|
+
const cacheKey = to !== currentLanguage2.value ? `${baseKey}|${from}|${to}` : baseKey;
|
|
2175
|
+
const cache = effectiveCtx === "ui" ? uiTranslations2 : translations2;
|
|
2176
|
+
return cache[cacheKey] || getSeed(to, text, effectiveCtx) || text;
|
|
2177
|
+
};
|
|
2178
|
+
const warmTranslations = async (keys, lang, from = ltDefaults.orig || "") => {
|
|
2179
|
+
const valid = keys.filter((k) => k && k.text);
|
|
2180
|
+
if (valid.length === 0) return {};
|
|
2181
|
+
const items = valid.map((k) => ({
|
|
2182
|
+
[API_FIELD_TEXT]: k.text,
|
|
2183
|
+
[API_FIELD_FROM]: from,
|
|
2184
|
+
[API_FIELD_TO]: lang,
|
|
2185
|
+
[API_FIELD_CTX]: k.ctx ?? ltDefaults.ctx ?? "ui"
|
|
2186
|
+
}));
|
|
2187
|
+
const results = await translateBatch(items, {
|
|
2188
|
+
translatorHost,
|
|
2189
|
+
apiKey: options.apiKey,
|
|
2190
|
+
apiKeyEnv: options.apiKeyEnv
|
|
2191
|
+
});
|
|
2192
|
+
const resolved = {};
|
|
2193
|
+
results.forEach((res, i) => {
|
|
2194
|
+
const src = valid[i];
|
|
2195
|
+
const translated = res?.[API_FIELD_TEXT];
|
|
2196
|
+
if (src && translated && translated !== src.text) {
|
|
2197
|
+
const ctx = src.ctx ?? ltDefaults.ctx ?? "ui";
|
|
2198
|
+
resolved[`${src.text}|${ctx}`] = translated;
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
if (Object.keys(resolved).length > 0) seedTranslations({ [lang]: resolved });
|
|
2202
|
+
return resolved;
|
|
2203
|
+
};
|
|
2101
2204
|
const fetchAndCacheBatch = async (items, from, to = currentLanguage2.value, globalCtx) => {
|
|
2102
2205
|
if (items.length === 0) return;
|
|
2103
2206
|
const effectiveFrom = from || ltDefaults.orig || "";
|
|
@@ -2227,11 +2330,16 @@ function createLangieInstance(options = {}) {
|
|
|
2227
2330
|
isLoading,
|
|
2228
2331
|
setLanguage,
|
|
2229
2332
|
fetchLanguages,
|
|
2333
|
+
invalidateLanguages,
|
|
2230
2334
|
translatorHost,
|
|
2231
2335
|
// Translation functions
|
|
2232
2336
|
l,
|
|
2233
2337
|
lr,
|
|
2338
|
+
peekTranslation,
|
|
2234
2339
|
fetchAndCacheBatch,
|
|
2340
|
+
// SSR translation seam
|
|
2341
|
+
seedTranslations,
|
|
2342
|
+
warmTranslations,
|
|
2235
2343
|
// Error handling
|
|
2236
2344
|
getTranslationError: (text, ctx, from, to) => {
|
|
2237
2345
|
const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
|
|
@@ -2509,7 +2617,7 @@ var lt_default = /* @__PURE__ */ (0, import_vue9.defineComponent)({
|
|
|
2509
2617
|
});
|
|
2510
2618
|
const props = __props;
|
|
2511
2619
|
const slots = (0, import_vue11.useSlots)();
|
|
2512
|
-
const { lr, currentLanguage: currentLanguage2, uiTranslations: uiTranslations2, translations: translations2, getLtDefaults } = useLangie();
|
|
2620
|
+
const { lr, peekTranslation, currentLanguage: currentLanguage2, uiTranslations: uiTranslations2, translations: translations2, getLtDefaults } = useLangie();
|
|
2513
2621
|
const keyStr = (0, import_vue11.computed)(() => {
|
|
2514
2622
|
if (props.msg) return props.msg;
|
|
2515
2623
|
const slotContent = slots.default ? slots.default().map((n) => n.children).join("") : "";
|
|
@@ -2526,12 +2634,12 @@ var lt_default = /* @__PURE__ */ (0, import_vue9.defineComponent)({
|
|
|
2526
2634
|
{ deep: true }
|
|
2527
2635
|
);
|
|
2528
2636
|
const translated = (0, import_vue11.computed)(() => {
|
|
2529
|
-
if (isNuxt.value && typeof window === "undefined") {
|
|
2530
|
-
return keyStr.value;
|
|
2531
|
-
}
|
|
2532
2637
|
const globalDefaults = getLtDefaults();
|
|
2533
2638
|
const effectiveCtx = props.ctx ?? globalDefaults.ctx;
|
|
2534
2639
|
const effectiveOrig = props.orig ?? globalDefaults.orig;
|
|
2640
|
+
if (isNuxt.value && typeof window === "undefined") {
|
|
2641
|
+
return peekTranslation(keyStr.value, effectiveCtx);
|
|
2642
|
+
}
|
|
2535
2643
|
void currentLanguage2.value;
|
|
2536
2644
|
void forceUpdate.value;
|
|
2537
2645
|
const cacheKey = `${keyStr.value}|${effectiveCtx}`;
|