@procore/ai-translations 0.4.0 → 0.6.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.
@@ -17,6 +17,7 @@ interface TranslationProgress {
17
17
  total: number;
18
18
  }
19
19
  interface TranslationQueueEntry {
20
+ id: string;
20
21
  originalText: string;
21
22
  isTranslated: boolean;
22
23
  targetLanguage: string;
@@ -170,8 +171,8 @@ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
170
171
  declare const getAITranslationLDId: (domain: string) => "67e17b925ace3c08088c4bd2" | "570f2f6e348a2806d7006b6f" | "570e9423348a2806d7004354" | "570f37b4eee8b907140070c5";
171
172
 
172
173
  declare global {
173
- var __AI_TRANSLATION_FRONTEND_QUEUE__: Set<TranslationQueueEntry> | undefined;
174
- var __AI_TRANSLATION_BACKEND_QUEUE__: Set<TranslationQueueEntry> | undefined;
174
+ var __AI_TRANSLATION_FRONTEND_QUEUE__: Map<string, TranslationQueueEntry> | undefined;
175
+ var __AI_TRANSLATION_BACKEND_QUEUE__: Map<string, TranslationQueueEntry> | undefined;
175
176
  var _FRONTEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
176
177
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
177
178
  }
@@ -17,6 +17,7 @@ interface TranslationProgress {
17
17
  total: number;
18
18
  }
19
19
  interface TranslationQueueEntry {
20
+ id: string;
20
21
  originalText: string;
21
22
  isTranslated: boolean;
22
23
  targetLanguage: string;
@@ -170,8 +171,8 @@ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
170
171
  declare const getAITranslationLDId: (domain: string) => "67e17b925ace3c08088c4bd2" | "570f2f6e348a2806d7006b6f" | "570e9423348a2806d7004354" | "570f37b4eee8b907140070c5";
171
172
 
172
173
  declare global {
173
- var __AI_TRANSLATION_FRONTEND_QUEUE__: Set<TranslationQueueEntry> | undefined;
174
- var __AI_TRANSLATION_BACKEND_QUEUE__: Set<TranslationQueueEntry> | undefined;
174
+ var __AI_TRANSLATION_FRONTEND_QUEUE__: Map<string, TranslationQueueEntry> | undefined;
175
+ var __AI_TRANSLATION_BACKEND_QUEUE__: Map<string, TranslationQueueEntry> | undefined;
175
176
  var _FRONTEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
176
177
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
177
178
  }
@@ -81,6 +81,9 @@ var Config = class _Config {
81
81
  setToolName(name) {
82
82
  this.toolName = name;
83
83
  }
84
+ isBackendTranslationStrategy() {
85
+ return this.ToolConfig.strategy === "backend_translations";
86
+ }
84
87
  getToolName() {
85
88
  return this.toolName;
86
89
  }
@@ -268,10 +271,10 @@ var Hash = class {
268
271
  var QueueManager = class {
269
272
  constructor() {
270
273
  if (!globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__) {
271
- globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Set();
274
+ globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Map();
272
275
  }
273
276
  if (!globalThis.__AI_TRANSLATION_BACKEND_QUEUE__) {
274
- globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Set();
277
+ globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Map();
275
278
  }
276
279
  }
277
280
  getQueue(strategy) {
@@ -284,42 +287,20 @@ var QueueManager = class {
284
287
  return void 0;
285
288
  }
286
289
  add(entry) {
287
- var _a;
288
- (_a = this.getQueue(entry.translationStrategy)) == null ? void 0 : _a.add(entry);
289
- }
290
- *generateBatches(strategy, config2) {
291
- const queue = this.getQueue(strategy);
292
- if (!queue) {
293
- throw new Error("Invalid translation strategy");
294
- }
295
- if (queue.size === 0) {
296
- console.warn("No translations to process");
297
- return;
298
- }
299
- const batch = config2.getToolConfig().translationBatchSize;
300
- let batches = [];
301
- for (const entry of queue.values()) {
302
- batches.push(entry);
303
- if (batches.length >= batch) {
304
- yield batches;
305
- batches = [];
306
- }
307
- }
308
- if (batches.length > 0) {
309
- queue.clear();
310
- yield batches;
311
- }
290
+ const queue = this.getQueue(entry.translationStrategy);
291
+ if (!queue || queue.has(entry.id)) return;
292
+ queue.set(entry.id, entry);
312
293
  }
313
- *generateBatchesV2(config2) {
294
+ *generateBatches(config2) {
314
295
  const queue = this.getQueue(config2.getToolConfig().strategy);
315
296
  if (!queue) throw new Error("Invalid translation strategy");
316
297
  const batchSize = config2.getToolConfig().translationBatchSize;
317
298
  while (queue.size > 0) {
318
299
  const batch = [];
319
300
  while (batch.length < batchSize && queue.size > 0) {
320
- const next = queue.values().next().value;
321
- queue.delete(next);
322
- batch.push(next);
301
+ const [key, entry] = queue.entries().next().value;
302
+ queue.delete(key);
303
+ batch.push(entry);
323
304
  }
324
305
  yield batch;
325
306
  }
@@ -438,6 +419,19 @@ var _Storage = class _Storage {
438
419
  }
439
420
  }
440
421
  // ------------------------------------
422
+ // 🗑️ Clear Translation by ID
423
+ // ------------------------------------
424
+ static async deleteById(id) {
425
+ try {
426
+ const db = await _Storage.getDB();
427
+ await db.delete(_Storage.STORE_NAME, id);
428
+ return true;
429
+ } catch (error) {
430
+ console.error("[Storage] Failed to delete by id:", error);
431
+ return false;
432
+ }
433
+ }
434
+ // ------------------------------------
441
435
  // 🗑️ Clear All Translations
442
436
  // ------------------------------------
443
437
  static async clearAll() {
@@ -512,10 +506,42 @@ var _TranslationRegistry = class _TranslationRegistry {
512
506
  isNonRetryable(key) {
513
507
  return _TranslationRegistry.nonRetryableTexts.has(key);
514
508
  }
515
- async get(text, targetLanguage, tool) {
509
+ enqueue(enqueuedKey) {
510
+ _TranslationRegistry.enqueuedItems.add(enqueuedKey);
511
+ }
512
+ isEnqueued(enqueuedKey) {
513
+ return _TranslationRegistry.enqueuedItems.has(enqueuedKey);
514
+ }
515
+ clearEnqueued() {
516
+ _TranslationRegistry.enqueuedItems.clear();
517
+ }
518
+ async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
519
+ for (const text of texts) {
520
+ const id = await Storage.generateId(text, targetLanguage, this.tool);
521
+ _TranslationRegistry.enqueuedItems.delete(
522
+ this.generateEnqueuedKey(id, strategy)
523
+ );
524
+ }
525
+ }
526
+ static removeFromEnqueued(key) {
527
+ _TranslationRegistry.enqueuedItems.delete(key);
528
+ }
529
+ generateEnqueuedKey(id, strategy) {
530
+ return `${id}:${strategy}`;
531
+ }
532
+ async get(text, targetLanguage, tool, config2) {
516
533
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
517
- if (_TranslationRegistry.memoryCache.has(key)) {
518
- return _TranslationRegistry.memoryCache.get(key);
534
+ const cached = _TranslationRegistry.memoryCache.get(key);
535
+ if (cached) {
536
+ if (cached.translationStrategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
537
+ _TranslationRegistry.memoryCache.delete(key);
538
+ if (cached.translatedText) {
539
+ _TranslationRegistry.originalTextIndex.delete(cached.translatedText);
540
+ }
541
+ await Storage.deleteById(key);
542
+ return void 0;
543
+ }
544
+ return cached;
519
545
  }
520
546
  const translation = await Storage.getTranslation(
521
547
  text,
@@ -523,6 +549,10 @@ var _TranslationRegistry = class _TranslationRegistry {
523
549
  tool
524
550
  );
525
551
  if (translation !== void 0) {
552
+ if (translation.translation_strategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
553
+ await Storage.deleteById(translation.id);
554
+ return void 0;
555
+ }
526
556
  return this.toTranslationRegistryEntry(translation);
527
557
  }
528
558
  return void 0;
@@ -537,6 +567,7 @@ var _TranslationRegistry = class _TranslationRegistry {
537
567
  async clear() {
538
568
  _TranslationRegistry.memoryCache.clear();
539
569
  _TranslationRegistry.originalTextIndex.clear();
570
+ _TranslationRegistry.enqueuedItems.clear();
540
571
  await Storage.clearAll();
541
572
  return;
542
573
  }
@@ -544,6 +575,7 @@ var _TranslationRegistry = class _TranslationRegistry {
544
575
  await Storage.deleteByToolAndStrategy(tool, strategy);
545
576
  _TranslationRegistry.memoryCache.clear();
546
577
  _TranslationRegistry.originalTextIndex.clear();
578
+ _TranslationRegistry.enqueuedItems.clear();
547
579
  return;
548
580
  }
549
581
  toTranslationRegistryEntry(translation) {
@@ -565,10 +597,16 @@ var _TranslationRegistry = class _TranslationRegistry {
565
597
  translation.id
566
598
  );
567
599
  const newTranslation = this.toTranslationRegistryEntry(translation);
600
+ const enqueuedKey = this.generateEnqueuedKey(
601
+ translation.id,
602
+ translation.translation_strategy
603
+ );
568
604
  if (currentTranslation && !this.shouldUpdateTranslation(currentTranslation, newTranslation)) {
605
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
569
606
  continue;
570
607
  }
571
608
  this.setTranslationToRegistry(newTranslation);
609
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
572
610
  }
573
611
  return;
574
612
  }
@@ -611,6 +649,7 @@ var _TranslationRegistry = class _TranslationRegistry {
611
649
  __publicField(_TranslationRegistry, "memoryCache", /* @__PURE__ */ new Map());
612
650
  __publicField(_TranslationRegistry, "originalTextIndex", /* @__PURE__ */ new Map());
613
651
  __publicField(_TranslationRegistry, "nonRetryableTexts", /* @__PURE__ */ new Set());
652
+ __publicField(_TranslationRegistry, "enqueuedItems", /* @__PURE__ */ new Set());
614
653
  var TranslationRegistry = _TranslationRegistry;
615
654
 
616
655
  // src/utils/eventHandler.ts
@@ -631,12 +670,13 @@ var EventHandler = class {
631
670
  this.aiTranslationEvents = new import_web_sdk_events.SystemEvents(this.sourceEventIdentifier);
632
671
  this.toolName = toolName;
633
672
  }
634
- publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts) {
673
+ publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts = []) {
635
674
  this.aiTranslationEvents.publish(
636
675
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
637
676
  {
638
677
  sourceTranslatedTexts,
639
- nonRetryableTexts
678
+ nonRetryableTexts,
679
+ retryableFailedTexts
640
680
  }
641
681
  );
642
682
  }
@@ -644,12 +684,19 @@ var EventHandler = class {
644
684
  return this.aiTranslationEvents.subscribe(
645
685
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
646
686
  (detail) => {
647
- var _a, _b;
687
+ var _a, _b, _c;
648
688
  const sourceTranslatedTexts = Array.isArray(
649
689
  (_a = detail.data) == null ? void 0 : _a.sourceTranslatedTexts
650
690
  ) ? detail.data.sourceTranslatedTexts : [];
651
691
  const nonRetryableTexts = Array.isArray((_b = detail.data) == null ? void 0 : _b.nonRetryableTexts) ? detail.data.nonRetryableTexts : [];
652
- callback(sourceTranslatedTexts, nonRetryableTexts);
692
+ const retryableFailedTexts = Array.isArray(
693
+ (_c = detail.data) == null ? void 0 : _c.retryableFailedTexts
694
+ ) ? detail.data.retryableFailedTexts : [];
695
+ callback(
696
+ sourceTranslatedTexts,
697
+ nonRetryableTexts,
698
+ retryableFailedTexts
699
+ );
653
700
  }
654
701
  );
655
702
  }
@@ -750,6 +797,7 @@ var TranslationManager = class {
750
797
  __publicField(this, "currentTranslatorStrategy");
751
798
  __publicField(this, "sourceTexts", /* @__PURE__ */ new Map());
752
799
  __publicField(this, "nonRetryableTexts", /* @__PURE__ */ new Set());
800
+ __publicField(this, "retryableFailedTexts", /* @__PURE__ */ new Map());
753
801
  __publicField(this, "progressEventHandler");
754
802
  __publicField(this, "translationProgress", {
755
803
  progress: 0,
@@ -777,19 +825,21 @@ var TranslationManager = class {
777
825
  this.translationProgress.total = queueManager.queueSize(
778
826
  this.currentTranslatorStrategy
779
827
  );
780
- for (const batch of queueManager.generateBatchesV2(
781
- this.translator.getConfig()
782
- )) {
783
- batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
784
- const result = await this.translator.processTranslations(
785
- batch.map(
786
- (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
787
- )
788
- );
789
- this.setTranslationProgress(batch.length);
790
- await this.updateDatabaseWithTranslations(result);
791
- await this.notifyTranslationCompleted();
792
- }
828
+ do {
829
+ for (const batch of queueManager.generateBatches(
830
+ this.translator.getConfig()
831
+ )) {
832
+ batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
833
+ const result = await this.translator.processTranslations(
834
+ batch.map(
835
+ (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
836
+ )
837
+ );
838
+ this.setTranslationProgress(batch.length);
839
+ await this.updateDatabaseWithTranslations(result);
840
+ await this.notifyTranslationCompleted();
841
+ }
842
+ } while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
793
843
  this.resetTranslationProgress();
794
844
  this.publishTranslationProgress();
795
845
  if (this.currentTranslatorStrategy === "frontend_translations") {
@@ -808,6 +858,7 @@ var TranslationManager = class {
808
858
  continue;
809
859
  }
810
860
  if (!translation.isTranslated) {
861
+ this.addRetryableFailedText(response.tool, translation.sourceText);
811
862
  continue;
812
863
  }
813
864
  this.addSourceTextToMap(response.tool, translation.sourceText);
@@ -827,12 +878,14 @@ var TranslationManager = class {
827
878
  const eventhandler = new EventHandler(mfe);
828
879
  eventhandler.publishTranslationCompleteEvent(
829
880
  Array.from(this.sourceTexts.get(mfe) ?? /* @__PURE__ */ new Set()),
830
- Array.from(this.nonRetryableTexts)
881
+ Array.from(this.nonRetryableTexts),
882
+ Array.from(this.retryableFailedTexts.get(mfe) ?? /* @__PURE__ */ new Set())
831
883
  );
832
884
  }
833
885
  this.mfeToBeNotified.clear();
834
886
  this.sourceTexts.clear();
835
887
  this.nonRetryableTexts.clear();
888
+ this.retryableFailedTexts.clear();
836
889
  }
837
890
  setTranslationProgress(batchSize) {
838
891
  this.translationProgress.current += batchSize;
@@ -869,6 +922,11 @@ var TranslationManager = class {
869
922
  existing.add(sourceText);
870
923
  this.sourceTexts.set(tool, existing);
871
924
  }
925
+ addRetryableFailedText(tool, sourceText) {
926
+ const existing = this.retryableFailedTexts.get(tool) ?? /* @__PURE__ */ new Set();
927
+ existing.add(sourceText);
928
+ this.retryableFailedTexts.set(tool, existing);
929
+ }
872
930
  publishTranslationProgress() {
873
931
  this.progressEventHandler.publishTranslationProgressEvent(
874
932
  this.translationProgress.progress,
@@ -935,8 +993,16 @@ var Client = class {
935
993
  method: "POST",
936
994
  body: JSON.stringify(requests)
937
995
  });
996
+ const responseBody = Array.isArray(data) ? data.map((group) => ({
997
+ ...group,
998
+ translations: Array.isArray(group.translations) ? group.translations.map((t) => ({
999
+ ...t,
1000
+ isTranslated: t.isTranslated ?? (!!t.translation && t.translation !== ""),
1001
+ retryable: t.retryable ?? true
1002
+ })) : []
1003
+ })) : data;
938
1004
  return {
939
- responseBody: data,
1005
+ responseBody,
940
1006
  success: true
941
1007
  };
942
1008
  } catch (error) {
@@ -1014,11 +1080,54 @@ var getModelDownloadEventHandler = () => {
1014
1080
  }
1015
1081
  return _modelDownloadEventHandler;
1016
1082
  };
1083
+ var SUPPORTED_LANGUAGES = /* @__PURE__ */ new Set([
1084
+ "ar",
1085
+ "bn",
1086
+ "bg",
1087
+ "zh",
1088
+ "hr",
1089
+ "cs",
1090
+ "da",
1091
+ "nl",
1092
+ "en",
1093
+ "fi",
1094
+ "fr",
1095
+ "de",
1096
+ "el",
1097
+ "he",
1098
+ "hi",
1099
+ "hu",
1100
+ "id",
1101
+ "it",
1102
+ "ja",
1103
+ "kn",
1104
+ "ko",
1105
+ "lt",
1106
+ "mr",
1107
+ "no",
1108
+ "pl",
1109
+ "pt",
1110
+ "ro",
1111
+ "ru",
1112
+ "sk",
1113
+ "sl",
1114
+ "es",
1115
+ "sv",
1116
+ "ta",
1117
+ "te",
1118
+ "th",
1119
+ "tr",
1120
+ "uk",
1121
+ "vi"
1122
+ ]);
1017
1123
  var _ChromeTranslator = class _ChromeTranslator {
1018
1124
  constructor(translator) {
1019
1125
  __publicField(this, "translator");
1020
1126
  this.translator = translator;
1021
1127
  }
1128
+ static isSupportedLanguage(lang) {
1129
+ return SUPPORTED_LANGUAGES.has(lang) || SUPPORTED_LANGUAGES.has(lang.split("-")[0] ?? "");
1130
+ }
1022
1131
  static ensureRegistry() {
1023
1132
  if (!globalThis.chromeTranslatorRegistry) {
1024
1133
  globalThis.chromeTranslatorRegistry = /* @__PURE__ */ new Map();
@@ -1040,30 +1149,31 @@ var _ChromeTranslator = class _ChromeTranslator {
1040
1149
  );
1041
1150
  }
1042
1151
  static async createTranslator(source, target) {
1152
+ const key = this.generateKey(source, target);
1043
1153
  let resolveReady;
1044
- this.translatorReadyPromise = new Promise((resolve) => {
1154
+ const readyPromise = new Promise((resolve) => {
1045
1155
  resolveReady = resolve;
1046
1156
  });
1157
+ this.translatorReadyPromises.set(key, readyPromise);
1047
1158
  let isDownloading = false;
1048
1159
  const translator = await Translator.create({
1049
1160
  sourceLanguage: source,
1050
1161
  targetLanguage: target,
1051
1162
  monitor(m) {
1052
- m.addEventListener(
1053
- "downloadprogress",
1054
- (e) => {
1055
- isDownloading = true;
1056
- const progress = e.total > 0 ? Math.round(e.loaded / e.total * 100) : 0;
1057
- getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1058
- e.loaded,
1059
- e.total,
1060
- progress
1061
- );
1062
- if (e.loaded === e.total) {
1063
- resolveReady();
1064
- }
1163
+ m.addEventListener("downloadprogress", (e) => {
1164
+ isDownloading = true;
1165
+ const loaded = e.loaded ?? 0;
1166
+ const total = e.total ?? 0;
1167
+ const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
1168
+ getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1169
+ loaded,
1170
+ total,
1171
+ progress
1172
+ );
1173
+ if (total > 0 && loaded >= total) {
1174
+ resolveReady();
1065
1175
  }
1066
- );
1176
+ });
1067
1177
  }
1068
1178
  });
1069
1179
  if (!isDownloading) {
@@ -1071,8 +1181,8 @@ var _ChromeTranslator = class _ChromeTranslator {
1071
1181
  }
1072
1182
  return translator;
1073
1183
  }
1074
- static waitForReady() {
1075
- return this.translatorReadyPromise;
1184
+ static waitForReady(key) {
1185
+ return this.translatorReadyPromises.get(key) ?? Promise.resolve();
1076
1186
  }
1077
1187
  static async translate(text, targetLanguage) {
1078
1188
  var _a;
@@ -1081,8 +1191,17 @@ var _ChromeTranslator = class _ChromeTranslator {
1081
1191
  const detector = await ChromeLanguageDetector.getInstance();
1082
1192
  const sourceLanguage = await detector.detectLanguage(text);
1083
1193
  const key = this.generateKey(sourceLanguage, targetLanguage);
1084
- let instance = globalThis.chromeTranslatorRegistry.get(key);
1085
- if (!instance) {
1194
+ if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
1195
+ return {
1196
+ translation: text,
1197
+ sourceLanguage,
1198
+ success: false,
1199
+ retryable: false,
1200
+ errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${targetLanguage}`
1201
+ };
1202
+ }
1203
+ const existingInstance = globalThis.chromeTranslatorRegistry.get(key);
1204
+ if (!existingInstance) {
1086
1205
  const translatorCapabilities = await Translator.availability({
1087
1206
  sourceLanguage,
1088
1207
  targetLanguage
@@ -1100,10 +1219,16 @@ var _ChromeTranslator = class _ChromeTranslator {
1100
1219
  sourceLanguage,
1101
1220
  targetLanguage
1102
1221
  );
1103
- instance = new _ChromeTranslator(chromeTranslator);
1104
- globalThis.chromeTranslatorRegistry.set(key, instance);
1222
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1223
+ globalThis.chromeTranslatorRegistry.set(
1224
+ key,
1225
+ new _ChromeTranslator(chromeTranslator)
1226
+ );
1227
+ }
1105
1228
  }
1106
- await this.waitForReady();
1229
+ const instance = globalThis.chromeTranslatorRegistry.get(key);
1230
+ await this.waitForReady(key);
1231
+ this.translatorReadyPromises.delete(key);
1107
1232
  const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
1108
1233
  if (!translation) {
1109
1234
  return {
@@ -1111,7 +1236,7 @@ var _ChromeTranslator = class _ChromeTranslator {
1111
1236
  sourceLanguage,
1112
1237
  success: false,
1113
1238
  errorMessage: "Translation failed",
1114
- retryable: true
1239
+ retryable: false
1115
1240
  };
1116
1241
  }
1117
1242
  return {
@@ -1121,11 +1246,12 @@ var _ChromeTranslator = class _ChromeTranslator {
1121
1246
  retryable: true
1122
1247
  };
1123
1248
  } catch (error) {
1249
+ const message = error instanceof Error ? error.message : "Unknown error";
1124
1250
  return {
1125
1251
  translation: text,
1126
1252
  sourceLanguage: targetLanguage,
1127
1253
  success: false,
1128
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1254
+ errorMessage: message,
1129
1255
  retryable: true
1130
1256
  };
1131
1257
  }
@@ -1134,7 +1260,7 @@ var _ChromeTranslator = class _ChromeTranslator {
1134
1260
  return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
1135
1261
  }
1136
1262
  };
1137
- __publicField(_ChromeTranslator, "translatorReadyPromise", Promise.resolve());
1263
+ __publicField(_ChromeTranslator, "translatorReadyPromises", /* @__PURE__ */ new Map());
1138
1264
  var ChromeTranslator = _ChromeTranslator;
1139
1265
 
1140
1266
  // src/translators/frontend_translators/chrome/client.ts
@@ -1284,13 +1410,22 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1284
1410
  const existingByOriginal = await translationRegistry.get(
1285
1411
  text,
1286
1412
  targetLanguage,
1287
- tool
1413
+ tool,
1414
+ config2
1288
1415
  );
1289
1416
  if (existingByOriginal && existingByOriginal.isTranslated && existingByOriginal.translatedText && existingByOriginal.translatedText.trim() !== "") {
1290
1417
  return existingByOriginal.translatedText;
1291
1418
  }
1292
1419
  if (!existingByOriginal) {
1420
+ const id = await Storage.generateId(text, targetLanguage, tool);
1421
+ const strategy = config2.getToolConfig().strategy;
1422
+ const enqueuedKey = translationRegistry.generateEnqueuedKey(id, strategy);
1423
+ if (translationRegistry.isEnqueued(enqueuedKey)) {
1424
+ return text;
1425
+ }
1426
+ translationRegistry.enqueue(enqueuedKey);
1293
1427
  queueManager.add({
1428
+ id,
1294
1429
  originalText: text,
1295
1430
  targetLanguage,
1296
1431
  tool,
@@ -1354,12 +1489,17 @@ function AITranslationInnerProvider(props) {
1354
1489
  });
1355
1490
  (0, import_react.useEffect)(() => {
1356
1491
  const unsubscribe = eventHandler.current.subscribeToTranslationCompleteEvent(
1357
- async (sourceTranslatedTexts, nonRetryableTexts) => {
1492
+ async (sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts) => {
1358
1493
  try {
1359
1494
  await translationRegistry.current.populateRegistryFromDatabase();
1360
1495
  translationRegistry.current.populateNonRetryableTexts(
1361
1496
  nonRetryableTexts
1362
1497
  );
1498
+ translationRegistry.current.removeTextsFromEnqueued(
1499
+ retryableFailedTexts,
1500
+ locale,
1501
+ config2.getToolConfig().strategy
1502
+ );
1363
1503
  eventHandler.current.publishRerenderEvent(sourceTranslatedTexts);
1364
1504
  } catch (error) {
1365
1505
  console.error(
@@ -1370,7 +1510,7 @@ function AITranslationInnerProvider(props) {
1370
1510
  }
1371
1511
  );
1372
1512
  return () => unsubscribe();
1373
- }, []);
1513
+ }, [locale, config2]);
1374
1514
  (0, import_react.useEffect)(() => {
1375
1515
  const unsubscribe = eventHandler.current.subscribeToTranslationProgressEvent(
1376
1516
  (progress, current, total) => {
@@ -1388,15 +1528,15 @@ function AITranslationInnerProvider(props) {
1388
1528
  return () => unsubscribe();
1389
1529
  }, []);
1390
1530
  (0, import_react.useEffect)(() => {
1391
- if (isFetched && remoteConfig && remoteConfig[tool]) {
1392
- setConfig((prevConfig) => {
1393
- const clonedConfig = prevConfig.clone();
1394
- clonedConfig.setTargetLanguage(locale);
1395
- clonedConfig.setToolConfig(remoteConfig[tool]);
1396
- translator.current.updateConfig(clonedConfig);
1397
- return clonedConfig;
1398
- });
1399
- }
1531
+ if (!isFetched || !remoteConfig) return;
1532
+ const toolConfig = remoteConfig[tool] ?? remoteConfig;
1533
+ setConfig((prevConfig) => {
1534
+ const clonedConfig = prevConfig.clone();
1535
+ clonedConfig.setTargetLanguage(locale);
1536
+ clonedConfig.setToolConfig(toolConfig);
1537
+ translator.current.updateConfig(clonedConfig);
1538
+ return clonedConfig;
1539
+ });
1400
1540
  }, [tool, remoteConfig, isFetched, locale]);
1401
1541
  config2.setTargetLanguage(locale);
1402
1542
  config2.setToolName(tool);
@@ -1453,6 +1593,7 @@ var TranslatedIcon = ({
1453
1593
  className,
1454
1594
  xmlns: "http://www.w3.org/2000/svg",
1455
1595
  "aria-hidden": "true",
1596
+ style: { marginRight: "10px", flexShrink: 0 },
1456
1597
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1457
1598
  "path",
1458
1599
  {
@@ -1487,12 +1628,14 @@ var AITranslateText = ({
1487
1628
  return;
1488
1629
  }
1489
1630
  if (sourceTexts.includes(text)) {
1490
- setDisplayText(await context.ait(text));
1631
+ const translatedText = await context.ait(text);
1632
+ setDisplayText(translatedText);
1633
+ setShowHighlightState(showHighlight && translatedText !== text);
1491
1634
  }
1492
1635
  }
1493
1636
  );
1494
1637
  return () => unsubscribe();
1495
- }, [context, text]);
1638
+ }, [context, text, showHighlight]);
1496
1639
  const reset = (0, import_react4.useCallback)(
1497
1640
  (displayValue = text) => {
1498
1641
  setDisplayText(displayValue);
@@ -1515,7 +1658,7 @@ var AITranslateText = ({
1515
1658
  const translatedText = await context.ait(text);
1516
1659
  if (cancelled) return;
1517
1660
  setDisplayText(translatedText);
1518
- setShowHighlightState(showHighlight);
1661
+ setShowHighlightState(showHighlight && translatedText !== text);
1519
1662
  } catch {
1520
1663
  if (cancelled) return;
1521
1664
  reset();
@@ -1527,8 +1670,8 @@ var AITranslateText = ({
1527
1670
  };
1528
1671
  }, [text, shouldTranslate, context, showHighlight, reset]);
1529
1672
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1530
- displayText,
1531
- showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps })
1673
+ showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps }),
1674
+ displayText
1532
1675
  ] });
1533
1676
  };
1534
1677