langie 1.11.1 → 1.12.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/InterfaceLanguageSelect.vue.d.ts +1 -1
- package/dist/components/index.cjs +249 -159
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.css.map +1 -1
- package/dist/components/index.mjs +250 -160
- 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 +294 -204
- 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 +295 -205
- 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/dist/utils/flagUrl.d.ts +12 -0
- package/package.json +1 -1
|
@@ -55,11 +55,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
55
55
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
56
56
|
}>, {
|
|
57
57
|
translatorHost: string;
|
|
58
|
+
apiKey: string;
|
|
58
59
|
languages: TranslatorLanguage[];
|
|
59
60
|
placeholder: string;
|
|
60
61
|
disabled: boolean;
|
|
61
62
|
isDark: boolean;
|
|
62
|
-
apiKey: string;
|
|
63
63
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
64
64
|
declare const _default: typeof __VLS_export;
|
|
65
65
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
2
|
+
* langie v1.12.1
|
|
3
3
|
* (c) 2026 nlit
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*
|
|
@@ -589,6 +589,16 @@ function applyLanguageAlias(term = "") {
|
|
|
589
589
|
return { primary, suggestions: Array.from(suggestions) };
|
|
590
590
|
}
|
|
591
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
|
+
|
|
598
|
+
// src/utils/flagUrl.ts
|
|
599
|
+
var isReadyFlagUrl = (flagCode) => flagCode.startsWith("/") || /^https?:\/\//i.test(flagCode);
|
|
600
|
+
var flagImageUrl = (flagCode) => isReadyFlagUrl(flagCode) ? flagCode : `/flags/${flagCode}.svg`;
|
|
601
|
+
|
|
592
602
|
// src/constants/colors.ts
|
|
593
603
|
var COLORS = {
|
|
594
604
|
// Primary colors
|
|
@@ -665,14 +675,14 @@ var _hoisted_1 = {
|
|
|
665
675
|
key: 0,
|
|
666
676
|
class: "multiselect-single-label"
|
|
667
677
|
};
|
|
668
|
-
var _hoisted_2 = ["src", "alt"];
|
|
678
|
+
var _hoisted_2 = ["src", "alt", "data-custom-flag"];
|
|
669
679
|
var _hoisted_3 = { class: "language-text" };
|
|
670
680
|
var _hoisted_4 = {
|
|
671
681
|
key: 0,
|
|
672
682
|
class: "native-name"
|
|
673
683
|
};
|
|
674
684
|
var _hoisted_5 = ["data-lang-code"];
|
|
675
|
-
var _hoisted_6 = ["src", "alt"];
|
|
685
|
+
var _hoisted_6 = ["src", "alt", "data-custom-flag"];
|
|
676
686
|
var _hoisted_7 = { class: "language-text" };
|
|
677
687
|
var _hoisted_8 = {
|
|
678
688
|
key: 0,
|
|
@@ -740,10 +750,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
740
750
|
const flagCode = lang.flag_country || lang.code;
|
|
741
751
|
return flagCode;
|
|
742
752
|
};
|
|
743
|
-
const getFlagImageUrl = (lang) =>
|
|
744
|
-
const flagCode = getFlagCode(lang);
|
|
745
|
-
return `/flags/${flagCode}.svg`;
|
|
746
|
-
};
|
|
753
|
+
const getFlagImageUrl = (lang) => flagImageUrl(getFlagCode(lang));
|
|
747
754
|
const props = __props;
|
|
748
755
|
const emit = __emit;
|
|
749
756
|
const multiselectRef = (0, import_vue3.ref)(null);
|
|
@@ -868,6 +875,10 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
868
875
|
const onFlagError = (event) => {
|
|
869
876
|
const target = event.target;
|
|
870
877
|
const currentSrc = target.src;
|
|
878
|
+
if (target.dataset.customFlag) {
|
|
879
|
+
target.style.display = "none";
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
871
882
|
if (currentSrc && currentSrc.includes("/flags/")) {
|
|
872
883
|
const match = currentSrc.match(/\/flags\/([a-z]{2})\.svg/);
|
|
873
884
|
if (match) {
|
|
@@ -917,6 +928,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
917
928
|
src: getFlagImageUrl(value),
|
|
918
929
|
class: "lang-flag",
|
|
919
930
|
alt: `${value.native_name || value.name} flag`,
|
|
931
|
+
"data-custom-flag": (0, import_vue2.unref)(isReadyFlagUrl)(getFlagCode(value)) || void 0,
|
|
920
932
|
onError: onFlagError
|
|
921
933
|
}, null, 40, _hoisted_2)) : (0, import_vue2.createCommentVNode)("v-if", true),
|
|
922
934
|
(0, import_vue2.createElementVNode)("span", _hoisted_3, [
|
|
@@ -928,7 +940,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
928
940
|
value.native_name && value.native_name !== value.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
|
|
929
941
|
"span",
|
|
930
942
|
_hoisted_4,
|
|
931
|
-
"(" + (0, import_vue2.toDisplayString)(value.native_name) + ")",
|
|
943
|
+
"(" + (0, import_vue2.toDisplayString)((0, import_vue2.unref)(displayNative)(value.name, value.native_name)) + ")",
|
|
932
944
|
1
|
|
933
945
|
/* TEXT */
|
|
934
946
|
)) : (0, import_vue2.createCommentVNode)("v-if", true)
|
|
@@ -945,6 +957,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
945
957
|
src: getFlagImageUrl(option),
|
|
946
958
|
class: "lang-flag",
|
|
947
959
|
alt: `${option.name} flag`,
|
|
960
|
+
"data-custom-flag": (0, import_vue2.unref)(isReadyFlagUrl)(getFlagCode(option)) || void 0,
|
|
948
961
|
onError: onFlagError
|
|
949
962
|
}, null, 40, _hoisted_6)),
|
|
950
963
|
(0, import_vue2.createElementVNode)("span", _hoisted_7, [
|
|
@@ -956,7 +969,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue.defineComponent)({
|
|
|
956
969
|
option.native_name && option.native_name !== option.name ? ((0, import_vue2.openBlock)(), (0, import_vue2.createElementBlock)(
|
|
957
970
|
"span",
|
|
958
971
|
_hoisted_8,
|
|
959
|
-
"(" + (0, import_vue2.toDisplayString)(option.native_name) + ")",
|
|
972
|
+
"(" + (0, import_vue2.toDisplayString)((0, import_vue2.unref)(displayNative)(option.name, option.native_name)) + ")",
|
|
960
973
|
1
|
|
961
974
|
/* TEXT */
|
|
962
975
|
)) : (0, import_vue2.createCommentVNode)("v-if", true)
|
|
@@ -1019,9 +1032,6 @@ var import_vue8 = require("vue");
|
|
|
1019
1032
|
// src/useLangie.ts
|
|
1020
1033
|
var import_vue5 = require("vue");
|
|
1021
1034
|
|
|
1022
|
-
// src/composables/useLangie-core.ts
|
|
1023
|
-
var import_vue4 = require("vue");
|
|
1024
|
-
|
|
1025
1035
|
// src/constants.ts
|
|
1026
1036
|
var DEFAULT_API_HOST = "https://api.langie.uk/v1";
|
|
1027
1037
|
var API_FIELD_TEXT = "t";
|
|
@@ -1032,6 +1042,153 @@ var API_FIELD_TRANSLATIONS = "translations";
|
|
|
1032
1042
|
var API_FIELD_ERROR = "error";
|
|
1033
1043
|
var API_FIELD_TURNSTILE = "turnstileToken";
|
|
1034
1044
|
|
|
1045
|
+
// src/core.ts
|
|
1046
|
+
var DEFAULT_TRANSLATOR_HOST = "http://localhost:8081";
|
|
1047
|
+
var DEFAULT_API_KEY_ENV = "TRANSLATOR_API_KEY";
|
|
1048
|
+
function resolveApiKey(options) {
|
|
1049
|
+
if (options.apiKey) return options.apiKey;
|
|
1050
|
+
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1051
|
+
if (!env) return void 0;
|
|
1052
|
+
return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV];
|
|
1053
|
+
}
|
|
1054
|
+
var translationCache = /* @__PURE__ */ new Map();
|
|
1055
|
+
function getTranslationCacheKey(serviceTranslations, options) {
|
|
1056
|
+
return JSON.stringify({
|
|
1057
|
+
translations: serviceTranslations.map((t) => ({
|
|
1058
|
+
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1059
|
+
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1060
|
+
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1061
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1062
|
+
})),
|
|
1063
|
+
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
1064
|
+
context: options[API_FIELD_CTX] || "ui"
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
async function translateBatch(translations2 = [], options = {}) {
|
|
1068
|
+
if (!Array.isArray(translations2) || translations2.length === 0) {
|
|
1069
|
+
throw new Error("translations must be a non-empty array");
|
|
1070
|
+
}
|
|
1071
|
+
const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST;
|
|
1072
|
+
const apiKey = resolveApiKey(options);
|
|
1073
|
+
const serviceTranslations = [];
|
|
1074
|
+
const indexMap = [];
|
|
1075
|
+
translations2.forEach((tr, idx) => {
|
|
1076
|
+
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1077
|
+
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1078
|
+
if (from === to) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
serviceTranslations.push(tr);
|
|
1082
|
+
indexMap.push(idx);
|
|
1083
|
+
});
|
|
1084
|
+
let serviceResults = [];
|
|
1085
|
+
if (serviceTranslations.length > 0) {
|
|
1086
|
+
const cacheKey = getTranslationCacheKey(serviceTranslations, options);
|
|
1087
|
+
if (translationCache.has(cacheKey)) {
|
|
1088
|
+
serviceResults = await translationCache.get(cacheKey);
|
|
1089
|
+
} else {
|
|
1090
|
+
const translationPromise = (async () => {
|
|
1091
|
+
try {
|
|
1092
|
+
const turnstileToken = options.getTurnstileToken ? await options.getTurnstileToken() : void 0;
|
|
1093
|
+
const controller = new AbortController();
|
|
1094
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
1095
|
+
const resp = await fetch(`${translatorHost}/translate`, {
|
|
1096
|
+
method: "POST",
|
|
1097
|
+
headers: {
|
|
1098
|
+
"Content-Type": "application/json",
|
|
1099
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}`, "X-Api-Key": apiKey } : {}
|
|
1100
|
+
},
|
|
1101
|
+
body: JSON.stringify({
|
|
1102
|
+
[API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({
|
|
1103
|
+
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
1104
|
+
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
1105
|
+
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
1106
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
1107
|
+
})),
|
|
1108
|
+
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
1109
|
+
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
|
1110
|
+
}),
|
|
1111
|
+
signal: controller.signal
|
|
1112
|
+
});
|
|
1113
|
+
clearTimeout(timeoutId);
|
|
1114
|
+
if (!resp.ok) {
|
|
1115
|
+
console.error(
|
|
1116
|
+
"[translator-sdk] Translator error response:",
|
|
1117
|
+
resp.status,
|
|
1118
|
+
resp.statusText
|
|
1119
|
+
);
|
|
1120
|
+
throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`);
|
|
1121
|
+
}
|
|
1122
|
+
let parsed;
|
|
1123
|
+
try {
|
|
1124
|
+
parsed = await resp.clone().json();
|
|
1125
|
+
} catch (jsonErr) {
|
|
1126
|
+
console.error("[translator-sdk] Failed to parse JSON response:", jsonErr);
|
|
1127
|
+
throw new Error(`Failed to parse JSON response: ${jsonErr}`);
|
|
1128
|
+
}
|
|
1129
|
+
const data = parsed || {};
|
|
1130
|
+
if (data[API_FIELD_ERROR]) {
|
|
1131
|
+
console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR]);
|
|
1132
|
+
return serviceTranslations.map((translation) => ({
|
|
1133
|
+
[API_FIELD_TEXT]: translation[API_FIELD_TEXT],
|
|
1134
|
+
[API_FIELD_ERROR]: data[API_FIELD_ERROR]
|
|
1135
|
+
}));
|
|
1136
|
+
}
|
|
1137
|
+
const results = Array.isArray(data[API_FIELD_TRANSLATIONS]) ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {
|
|
1138
|
+
const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || "";
|
|
1139
|
+
if (translation[API_FIELD_ERROR]) {
|
|
1140
|
+
console.warn(
|
|
1141
|
+
`[translator-sdk] Translation error for "${originalText}":`,
|
|
1142
|
+
translation[API_FIELD_ERROR]
|
|
1143
|
+
);
|
|
1144
|
+
return {
|
|
1145
|
+
[API_FIELD_TEXT]: originalText,
|
|
1146
|
+
// Return original text on error
|
|
1147
|
+
[API_FIELD_ERROR]: translation[API_FIELD_ERROR]
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {
|
|
1151
|
+
return {
|
|
1152
|
+
[API_FIELD_TEXT]: originalText,
|
|
1153
|
+
// For detection, return original text
|
|
1154
|
+
[API_FIELD_FROM]: translation[API_FIELD_FROM]
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
[API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText
|
|
1159
|
+
};
|
|
1160
|
+
}) : data[API_FIELD_TEXT] ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }] : [];
|
|
1161
|
+
return results;
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
translationCache.delete(cacheKey);
|
|
1164
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1165
|
+
const message2 = `Translator request to ${translatorHost} timed out after 5 seconds.`;
|
|
1166
|
+
console.error(message2);
|
|
1167
|
+
throw new Error(message2);
|
|
1168
|
+
}
|
|
1169
|
+
const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`;
|
|
1170
|
+
console.error(message);
|
|
1171
|
+
throw new Error(message);
|
|
1172
|
+
}
|
|
1173
|
+
})();
|
|
1174
|
+
translationCache.set(cacheKey, translationPromise);
|
|
1175
|
+
serviceResults = await translationPromise;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const final = translations2.map((tr, idx) => {
|
|
1179
|
+
const from = (tr[API_FIELD_FROM] || "").toLowerCase();
|
|
1180
|
+
const to = (tr[API_FIELD_TO] || "").toLowerCase();
|
|
1181
|
+
if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1182
|
+
const svcIdx = indexMap.indexOf(idx);
|
|
1183
|
+
if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1184
|
+
return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] };
|
|
1185
|
+
});
|
|
1186
|
+
return final;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/composables/useLangie-core.ts
|
|
1190
|
+
var import_vue4 = require("vue");
|
|
1191
|
+
|
|
1035
1192
|
// src/utils/debug.ts
|
|
1036
1193
|
var import_meta = {};
|
|
1037
1194
|
function devDebug(...args) {
|
|
@@ -1276,143 +1433,6 @@ var clearCache = (pattern) => {
|
|
|
1276
1433
|
return cacheManager.clear(pattern);
|
|
1277
1434
|
};
|
|
1278
1435
|
|
|
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
1436
|
// src/utils/localizeLanguageNames.ts
|
|
1417
1437
|
var CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1418
1438
|
var NAME_CTX = "language name";
|
|
@@ -1459,7 +1479,9 @@ var currentLanguage = (0, import_vue4.ref)("en");
|
|
|
1459
1479
|
var _translatorHost = DEFAULT_API_HOST;
|
|
1460
1480
|
var _autoSelected = false;
|
|
1461
1481
|
var _languagesCache = null;
|
|
1482
|
+
var _languagesCacheAt = 0;
|
|
1462
1483
|
var _languagesPromise = null;
|
|
1484
|
+
var LANGUAGES_CACHE_KEY = "langie_languages_cache";
|
|
1463
1485
|
function useLangieCore(options = {}) {
|
|
1464
1486
|
if (options.translatorHost) {
|
|
1465
1487
|
_translatorHost = options.translatorHost;
|
|
@@ -1477,7 +1499,7 @@ function useLangieCore(options = {}) {
|
|
|
1477
1499
|
}
|
|
1478
1500
|
}
|
|
1479
1501
|
if (typeof window !== "undefined") {
|
|
1480
|
-
const cachedLanguages = getCache(
|
|
1502
|
+
const cachedLanguages = getCache(LANGUAGES_CACHE_KEY);
|
|
1481
1503
|
if (cachedLanguages && availableLanguages.value.length === 0) {
|
|
1482
1504
|
availableLanguages.value = cachedLanguages;
|
|
1483
1505
|
devDebug("[useLangie] Seeded languages from cache (will revalidate):", cachedLanguages.length);
|
|
@@ -1510,15 +1532,18 @@ function useLangieCore(options = {}) {
|
|
|
1510
1532
|
applyLocalizedNames(lang);
|
|
1511
1533
|
};
|
|
1512
1534
|
const fetchLanguages = async (opts = {}) => {
|
|
1513
|
-
const { force = false, country: explicitCountry } = opts;
|
|
1535
|
+
const { force = false, country: explicitCountry, maxAgeMs } = opts;
|
|
1536
|
+
const cacheFresh = !!_languagesCache && (maxAgeMs === void 0 || Date.now() - _languagesCacheAt < maxAgeMs);
|
|
1514
1537
|
devDebug("[useLangie] fetchLanguages called:", {
|
|
1515
1538
|
force,
|
|
1539
|
+
maxAgeMs,
|
|
1540
|
+
cacheFresh,
|
|
1516
1541
|
hasCache: !!_languagesCache,
|
|
1517
1542
|
hasPromise: !!_languagesPromise,
|
|
1518
1543
|
availableLanguagesLength: availableLanguages.value.length
|
|
1519
1544
|
});
|
|
1520
1545
|
if (!force) {
|
|
1521
|
-
if (
|
|
1546
|
+
if (cacheFresh) {
|
|
1522
1547
|
devDebug("[useLangie] Returning cached languages:", _languagesCache.length);
|
|
1523
1548
|
return _languagesCache;
|
|
1524
1549
|
}
|
|
@@ -1576,7 +1601,7 @@ function useLangieCore(options = {}) {
|
|
|
1576
1601
|
availableLanguages.value = filtered;
|
|
1577
1602
|
devDebug("[useLangie] Set availableLanguages:", filtered.length);
|
|
1578
1603
|
if (typeof window !== "undefined") {
|
|
1579
|
-
const saved = setCache(
|
|
1604
|
+
const saved = setCache(LANGUAGES_CACHE_KEY, filtered, 7 * 24 * 60 * 60 * 1e3);
|
|
1580
1605
|
devDebug(
|
|
1581
1606
|
"[useLangie] Saved languages to cache:",
|
|
1582
1607
|
saved ? "success" : "failed",
|
|
@@ -1607,6 +1632,7 @@ function useLangieCore(options = {}) {
|
|
|
1607
1632
|
_autoSelected = true;
|
|
1608
1633
|
}
|
|
1609
1634
|
_languagesCache = filtered;
|
|
1635
|
+
_languagesCacheAt = Date.now();
|
|
1610
1636
|
_languagesPromise = null;
|
|
1611
1637
|
applyLocalizedNames(currentLanguage.value);
|
|
1612
1638
|
return filtered;
|
|
@@ -1615,6 +1641,14 @@ function useLangieCore(options = {}) {
|
|
|
1615
1641
|
return [];
|
|
1616
1642
|
}
|
|
1617
1643
|
};
|
|
1644
|
+
const invalidateLanguages = () => {
|
|
1645
|
+
_languagesCache = null;
|
|
1646
|
+
_languagesCacheAt = 0;
|
|
1647
|
+
_languagesPromise = null;
|
|
1648
|
+
if (typeof window !== "undefined") {
|
|
1649
|
+
clearCache(LANGUAGES_CACHE_KEY);
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1618
1652
|
const clearTranslations = () => {
|
|
1619
1653
|
Object.keys(translations).forEach((key) => delete translations[key]);
|
|
1620
1654
|
Object.keys(uiTranslations).forEach((key) => delete uiTranslations[key]);
|
|
@@ -1631,6 +1665,7 @@ function useLangieCore(options = {}) {
|
|
|
1631
1665
|
isLoading,
|
|
1632
1666
|
setLanguage,
|
|
1633
1667
|
fetchLanguages,
|
|
1668
|
+
invalidateLanguages,
|
|
1634
1669
|
clearTranslations,
|
|
1635
1670
|
translatorHost
|
|
1636
1671
|
};
|
|
@@ -1948,6 +1983,7 @@ function createLangieInstance(options = {}) {
|
|
|
1948
1983
|
isLoading,
|
|
1949
1984
|
setLanguage,
|
|
1950
1985
|
fetchLanguages,
|
|
1986
|
+
invalidateLanguages,
|
|
1951
1987
|
translatorHost,
|
|
1952
1988
|
clearTranslations
|
|
1953
1989
|
} = core;
|
|
@@ -1959,9 +1995,17 @@ function createLangieInstance(options = {}) {
|
|
|
1959
1995
|
Object.assign(ltDefaults, defaults);
|
|
1960
1996
|
};
|
|
1961
1997
|
const getLtDefaults = () => ({ ...ltDefaults });
|
|
1998
|
+
const seed = {};
|
|
1999
|
+
if (options.ssrTranslations) seedTranslations(options.ssrTranslations);
|
|
2000
|
+
function seedTranslations(entries) {
|
|
2001
|
+
for (const lang of Object.keys(entries)) {
|
|
2002
|
+
seed[lang] = { ...seed[lang] || {}, ...entries[lang] };
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
const getSeed = (lang, text, ctx) => seed[lang]?.[`${text}|${ctx}`];
|
|
1962
2006
|
const CACHE_KEY = "langie_translations_cache";
|
|
1963
2007
|
const UI_CACHE_KEY = "langie_ui_translations_cache";
|
|
1964
|
-
const
|
|
2008
|
+
const LANGUAGES_CACHE_KEY2 = "langie_languages_cache";
|
|
1965
2009
|
const loadCachedTranslations = () => {
|
|
1966
2010
|
if (typeof window === "undefined") return;
|
|
1967
2011
|
const cachedTranslations = getCache(CACHE_KEY);
|
|
@@ -1989,7 +2033,7 @@ function createLangieInstance(options = {}) {
|
|
|
1989
2033
|
};
|
|
1990
2034
|
const loadCachedLanguages = () => {
|
|
1991
2035
|
if (typeof window === "undefined") return;
|
|
1992
|
-
const cachedLanguages = getCache(
|
|
2036
|
+
const cachedLanguages = getCache(LANGUAGES_CACHE_KEY2);
|
|
1993
2037
|
if (cachedLanguages) {
|
|
1994
2038
|
availableLanguages2.value = cachedLanguages;
|
|
1995
2039
|
}
|
|
@@ -2103,6 +2147,10 @@ function createLangieInstance(options = {}) {
|
|
|
2103
2147
|
if (cache[cacheKey]) {
|
|
2104
2148
|
return cache[cacheKey];
|
|
2105
2149
|
}
|
|
2150
|
+
const seeded = getSeed(to, text, effectiveCtx);
|
|
2151
|
+
if (seeded) {
|
|
2152
|
+
return seeded;
|
|
2153
|
+
}
|
|
2106
2154
|
const errorKey = `${text}|${effectiveCtx}|${from}|${to}`;
|
|
2107
2155
|
if (translationErrors.has(errorKey)) {
|
|
2108
2156
|
return text;
|
|
@@ -2111,6 +2159,7 @@ function createLangieInstance(options = {}) {
|
|
|
2111
2159
|
if (recentlyQueued.has(languageCacheKey)) {
|
|
2112
2160
|
return text;
|
|
2113
2161
|
}
|
|
2162
|
+
options.onTranslationMiss?.({ text, ctx: effectiveCtx, from, to });
|
|
2114
2163
|
batching.queueTranslation(text, effectiveCtx, from, to, cacheKey, toLang !== void 0);
|
|
2115
2164
|
recentlyQueued.add(languageCacheKey);
|
|
2116
2165
|
const clearDelay = 100;
|
|
@@ -2123,6 +2172,42 @@ function createLangieInstance(options = {}) {
|
|
|
2123
2172
|
};
|
|
2124
2173
|
const l = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, false);
|
|
2125
2174
|
const lr = (text, ctx, originalLang, toLang) => translateInternal(text, ctx, originalLang, toLang, true);
|
|
2175
|
+
const peekTranslation = (text, ctx, toLang) => {
|
|
2176
|
+
const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
|
|
2177
|
+
const from = ltDefaults.orig || "";
|
|
2178
|
+
const to = toLang || currentLanguage2.value;
|
|
2179
|
+
if (from === to) return text;
|
|
2180
|
+
const baseKey = `${text}|${effectiveCtx}`;
|
|
2181
|
+
const cacheKey = to !== currentLanguage2.value ? `${baseKey}|${from}|${to}` : baseKey;
|
|
2182
|
+
const cache = effectiveCtx === "ui" ? uiTranslations2 : translations2;
|
|
2183
|
+
return cache[cacheKey] || getSeed(to, text, effectiveCtx) || text;
|
|
2184
|
+
};
|
|
2185
|
+
const warmTranslations = async (keys, lang, from = ltDefaults.orig || "") => {
|
|
2186
|
+
const valid = keys.filter((k) => k && k.text);
|
|
2187
|
+
if (valid.length === 0) return {};
|
|
2188
|
+
const items = valid.map((k) => ({
|
|
2189
|
+
[API_FIELD_TEXT]: k.text,
|
|
2190
|
+
[API_FIELD_FROM]: from,
|
|
2191
|
+
[API_FIELD_TO]: lang,
|
|
2192
|
+
[API_FIELD_CTX]: k.ctx ?? ltDefaults.ctx ?? "ui"
|
|
2193
|
+
}));
|
|
2194
|
+
const results = await translateBatch(items, {
|
|
2195
|
+
translatorHost,
|
|
2196
|
+
apiKey: options.apiKey,
|
|
2197
|
+
apiKeyEnv: options.apiKeyEnv
|
|
2198
|
+
});
|
|
2199
|
+
const resolved = {};
|
|
2200
|
+
results.forEach((res, i) => {
|
|
2201
|
+
const src = valid[i];
|
|
2202
|
+
const translated = res?.[API_FIELD_TEXT];
|
|
2203
|
+
if (src && translated && translated !== src.text) {
|
|
2204
|
+
const ctx = src.ctx ?? ltDefaults.ctx ?? "ui";
|
|
2205
|
+
resolved[`${src.text}|${ctx}`] = translated;
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
if (Object.keys(resolved).length > 0) seedTranslations({ [lang]: resolved });
|
|
2209
|
+
return resolved;
|
|
2210
|
+
};
|
|
2126
2211
|
const fetchAndCacheBatch = async (items, from, to = currentLanguage2.value, globalCtx) => {
|
|
2127
2212
|
if (items.length === 0) return;
|
|
2128
2213
|
const effectiveFrom = from || ltDefaults.orig || "";
|
|
@@ -2252,11 +2337,16 @@ function createLangieInstance(options = {}) {
|
|
|
2252
2337
|
isLoading,
|
|
2253
2338
|
setLanguage,
|
|
2254
2339
|
fetchLanguages,
|
|
2340
|
+
invalidateLanguages,
|
|
2255
2341
|
translatorHost,
|
|
2256
2342
|
// Translation functions
|
|
2257
2343
|
l,
|
|
2258
2344
|
lr,
|
|
2345
|
+
peekTranslation,
|
|
2259
2346
|
fetchAndCacheBatch,
|
|
2347
|
+
// SSR translation seam
|
|
2348
|
+
seedTranslations,
|
|
2349
|
+
warmTranslations,
|
|
2260
2350
|
// Error handling
|
|
2261
2351
|
getTranslationError: (text, ctx, from, to) => {
|
|
2262
2352
|
const effectiveCtx = ctx !== void 0 ? ctx : ltDefaults.ctx || "ui";
|
|
@@ -2534,7 +2624,7 @@ var lt_default = /* @__PURE__ */ (0, import_vue9.defineComponent)({
|
|
|
2534
2624
|
});
|
|
2535
2625
|
const props = __props;
|
|
2536
2626
|
const slots = (0, import_vue11.useSlots)();
|
|
2537
|
-
const { lr, currentLanguage: currentLanguage2, uiTranslations: uiTranslations2, translations: translations2, getLtDefaults } = useLangie();
|
|
2627
|
+
const { lr, peekTranslation, currentLanguage: currentLanguage2, uiTranslations: uiTranslations2, translations: translations2, getLtDefaults } = useLangie();
|
|
2538
2628
|
const keyStr = (0, import_vue11.computed)(() => {
|
|
2539
2629
|
if (props.msg) return props.msg;
|
|
2540
2630
|
const slotContent = slots.default ? slots.default().map((n) => n.children).join("") : "";
|
|
@@ -2551,12 +2641,12 @@ var lt_default = /* @__PURE__ */ (0, import_vue9.defineComponent)({
|
|
|
2551
2641
|
{ deep: true }
|
|
2552
2642
|
);
|
|
2553
2643
|
const translated = (0, import_vue11.computed)(() => {
|
|
2554
|
-
if (isNuxt.value && typeof window === "undefined") {
|
|
2555
|
-
return keyStr.value;
|
|
2556
|
-
}
|
|
2557
2644
|
const globalDefaults = getLtDefaults();
|
|
2558
2645
|
const effectiveCtx = props.ctx ?? globalDefaults.ctx;
|
|
2559
2646
|
const effectiveOrig = props.orig ?? globalDefaults.orig;
|
|
2647
|
+
if (isNuxt.value && typeof window === "undefined") {
|
|
2648
|
+
return peekTranslation(keyStr.value, effectiveCtx);
|
|
2649
|
+
}
|
|
2560
2650
|
void currentLanguage2.value;
|
|
2561
2651
|
void forceUpdate.value;
|
|
2562
2652
|
const cacheKey = `${keyStr.value}|${effectiveCtx}`;
|