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.
@@ -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.11.1
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("langie_languages_cache");
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 (_languagesCache) {
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("langie_languages_cache", filtered, 7 * 24 * 60 * 60 * 1e3);
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 LANGUAGES_CACHE_KEY = "langie_languages_cache";
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(LANGUAGES_CACHE_KEY);
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}`;