@procore/ai-translations 0.3.0 → 0.5.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
  }
@@ -268,10 +268,10 @@ var Hash = class {
268
268
  var QueueManager = class {
269
269
  constructor() {
270
270
  if (!globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__) {
271
- globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Set();
271
+ globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Map();
272
272
  }
273
273
  if (!globalThis.__AI_TRANSLATION_BACKEND_QUEUE__) {
274
- globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Set();
274
+ globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Map();
275
275
  }
276
276
  }
277
277
  getQueue(strategy) {
@@ -284,42 +284,20 @@ var QueueManager = class {
284
284
  return void 0;
285
285
  }
286
286
  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
- }
287
+ const queue = this.getQueue(entry.translationStrategy);
288
+ if (!queue || queue.has(entry.id)) return;
289
+ queue.set(entry.id, entry);
312
290
  }
313
- *generateBatchesV2(config2) {
291
+ *generateBatches(config2) {
314
292
  const queue = this.getQueue(config2.getToolConfig().strategy);
315
293
  if (!queue) throw new Error("Invalid translation strategy");
316
294
  const batchSize = config2.getToolConfig().translationBatchSize;
317
295
  while (queue.size > 0) {
318
296
  const batch = [];
319
297
  while (batch.length < batchSize && queue.size > 0) {
320
- const next = queue.values().next().value;
321
- queue.delete(next);
322
- batch.push(next);
298
+ const [key, entry] = queue.entries().next().value;
299
+ queue.delete(key);
300
+ batch.push(entry);
323
301
  }
324
302
  yield batch;
325
303
  }
@@ -512,6 +490,27 @@ var _TranslationRegistry = class _TranslationRegistry {
512
490
  isNonRetryable(key) {
513
491
  return _TranslationRegistry.nonRetryableTexts.has(key);
514
492
  }
493
+ enqueue(enqueuedKey) {
494
+ _TranslationRegistry.enqueuedItems.add(enqueuedKey);
495
+ }
496
+ isEnqueued(enqueuedKey) {
497
+ return _TranslationRegistry.enqueuedItems.has(enqueuedKey);
498
+ }
499
+ clearEnqueued() {
500
+ _TranslationRegistry.enqueuedItems.clear();
501
+ }
502
+ async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
503
+ for (const text of texts) {
504
+ const id = await Storage.generateId(text, targetLanguage, this.tool);
505
+ _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
506
+ }
507
+ }
508
+ static removeFromEnqueued(key) {
509
+ _TranslationRegistry.enqueuedItems.delete(key);
510
+ }
511
+ generateEnqueuedKey(id, strategy) {
512
+ return `${id}:${strategy}`;
513
+ }
515
514
  async get(text, targetLanguage, tool) {
516
515
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
517
516
  if (_TranslationRegistry.memoryCache.has(key)) {
@@ -537,6 +536,7 @@ var _TranslationRegistry = class _TranslationRegistry {
537
536
  async clear() {
538
537
  _TranslationRegistry.memoryCache.clear();
539
538
  _TranslationRegistry.originalTextIndex.clear();
539
+ _TranslationRegistry.enqueuedItems.clear();
540
540
  await Storage.clearAll();
541
541
  return;
542
542
  }
@@ -544,6 +544,7 @@ var _TranslationRegistry = class _TranslationRegistry {
544
544
  await Storage.deleteByToolAndStrategy(tool, strategy);
545
545
  _TranslationRegistry.memoryCache.clear();
546
546
  _TranslationRegistry.originalTextIndex.clear();
547
+ _TranslationRegistry.enqueuedItems.clear();
547
548
  return;
548
549
  }
549
550
  toTranslationRegistryEntry(translation) {
@@ -565,10 +566,16 @@ var _TranslationRegistry = class _TranslationRegistry {
565
566
  translation.id
566
567
  );
567
568
  const newTranslation = this.toTranslationRegistryEntry(translation);
569
+ const enqueuedKey = this.generateEnqueuedKey(
570
+ translation.id,
571
+ translation.translation_strategy
572
+ );
568
573
  if (currentTranslation && !this.shouldUpdateTranslation(currentTranslation, newTranslation)) {
574
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
569
575
  continue;
570
576
  }
571
577
  this.setTranslationToRegistry(newTranslation);
578
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
572
579
  }
573
580
  return;
574
581
  }
@@ -611,6 +618,7 @@ var _TranslationRegistry = class _TranslationRegistry {
611
618
  __publicField(_TranslationRegistry, "memoryCache", /* @__PURE__ */ new Map());
612
619
  __publicField(_TranslationRegistry, "originalTextIndex", /* @__PURE__ */ new Map());
613
620
  __publicField(_TranslationRegistry, "nonRetryableTexts", /* @__PURE__ */ new Set());
621
+ __publicField(_TranslationRegistry, "enqueuedItems", /* @__PURE__ */ new Set());
614
622
  var TranslationRegistry = _TranslationRegistry;
615
623
 
616
624
  // src/utils/eventHandler.ts
@@ -631,12 +639,13 @@ var EventHandler = class {
631
639
  this.aiTranslationEvents = new import_web_sdk_events.SystemEvents(this.sourceEventIdentifier);
632
640
  this.toolName = toolName;
633
641
  }
634
- publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts) {
642
+ publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts = []) {
635
643
  this.aiTranslationEvents.publish(
636
644
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
637
645
  {
638
646
  sourceTranslatedTexts,
639
- nonRetryableTexts
647
+ nonRetryableTexts,
648
+ retryableFailedTexts
640
649
  }
641
650
  );
642
651
  }
@@ -644,12 +653,19 @@ var EventHandler = class {
644
653
  return this.aiTranslationEvents.subscribe(
645
654
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
646
655
  (detail) => {
647
- var _a, _b;
656
+ var _a, _b, _c;
648
657
  const sourceTranslatedTexts = Array.isArray(
649
658
  (_a = detail.data) == null ? void 0 : _a.sourceTranslatedTexts
650
659
  ) ? detail.data.sourceTranslatedTexts : [];
651
660
  const nonRetryableTexts = Array.isArray((_b = detail.data) == null ? void 0 : _b.nonRetryableTexts) ? detail.data.nonRetryableTexts : [];
652
- callback(sourceTranslatedTexts, nonRetryableTexts);
661
+ const retryableFailedTexts = Array.isArray(
662
+ (_c = detail.data) == null ? void 0 : _c.retryableFailedTexts
663
+ ) ? detail.data.retryableFailedTexts : [];
664
+ callback(
665
+ sourceTranslatedTexts,
666
+ nonRetryableTexts,
667
+ retryableFailedTexts
668
+ );
653
669
  }
654
670
  );
655
671
  }
@@ -750,6 +766,7 @@ var TranslationManager = class {
750
766
  __publicField(this, "currentTranslatorStrategy");
751
767
  __publicField(this, "sourceTexts", /* @__PURE__ */ new Map());
752
768
  __publicField(this, "nonRetryableTexts", /* @__PURE__ */ new Set());
769
+ __publicField(this, "retryableFailedTexts", /* @__PURE__ */ new Map());
753
770
  __publicField(this, "progressEventHandler");
754
771
  __publicField(this, "translationProgress", {
755
772
  progress: 0,
@@ -777,7 +794,7 @@ var TranslationManager = class {
777
794
  this.translationProgress.total = queueManager.queueSize(
778
795
  this.currentTranslatorStrategy
779
796
  );
780
- for (const batch of queueManager.generateBatchesV2(
797
+ for (const batch of queueManager.generateBatches(
781
798
  this.translator.getConfig()
782
799
  )) {
783
800
  batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
@@ -808,6 +825,7 @@ var TranslationManager = class {
808
825
  continue;
809
826
  }
810
827
  if (!translation.isTranslated) {
828
+ this.addRetryableFailedText(response.tool, translation.sourceText);
811
829
  continue;
812
830
  }
813
831
  this.addSourceTextToMap(response.tool, translation.sourceText);
@@ -827,12 +845,14 @@ var TranslationManager = class {
827
845
  const eventhandler = new EventHandler(mfe);
828
846
  eventhandler.publishTranslationCompleteEvent(
829
847
  Array.from(this.sourceTexts.get(mfe) ?? /* @__PURE__ */ new Set()),
830
- Array.from(this.nonRetryableTexts)
848
+ Array.from(this.nonRetryableTexts),
849
+ Array.from(this.retryableFailedTexts.get(mfe) ?? /* @__PURE__ */ new Set())
831
850
  );
832
851
  }
833
852
  this.mfeToBeNotified.clear();
834
853
  this.sourceTexts.clear();
835
854
  this.nonRetryableTexts.clear();
855
+ this.retryableFailedTexts.clear();
836
856
  }
837
857
  setTranslationProgress(batchSize) {
838
858
  this.translationProgress.current += batchSize;
@@ -869,6 +889,11 @@ var TranslationManager = class {
869
889
  existing.add(sourceText);
870
890
  this.sourceTexts.set(tool, existing);
871
891
  }
892
+ addRetryableFailedText(tool, sourceText) {
893
+ const existing = this.retryableFailedTexts.get(tool) ?? /* @__PURE__ */ new Set();
894
+ existing.add(sourceText);
895
+ this.retryableFailedTexts.set(tool, existing);
896
+ }
872
897
  publishTranslationProgress() {
873
898
  this.progressEventHandler.publishTranslationProgressEvent(
874
899
  this.translationProgress.progress,
@@ -1290,7 +1315,15 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1290
1315
  return existingByOriginal.translatedText;
1291
1316
  }
1292
1317
  if (!existingByOriginal) {
1318
+ const id = await Storage.generateId(text, targetLanguage, tool);
1319
+ const strategy = config2.getToolConfig().strategy;
1320
+ const enqueuedKey = translationRegistry.generateEnqueuedKey(id, strategy);
1321
+ if (translationRegistry.isEnqueued(enqueuedKey)) {
1322
+ return text;
1323
+ }
1324
+ translationRegistry.enqueue(enqueuedKey);
1293
1325
  queueManager.add({
1326
+ id,
1294
1327
  originalText: text,
1295
1328
  targetLanguage,
1296
1329
  tool,
@@ -1354,12 +1387,17 @@ function AITranslationInnerProvider(props) {
1354
1387
  });
1355
1388
  (0, import_react.useEffect)(() => {
1356
1389
  const unsubscribe = eventHandler.current.subscribeToTranslationCompleteEvent(
1357
- async (sourceTranslatedTexts, nonRetryableTexts) => {
1390
+ async (sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts) => {
1358
1391
  try {
1359
1392
  await translationRegistry.current.populateRegistryFromDatabase();
1360
1393
  translationRegistry.current.populateNonRetryableTexts(
1361
1394
  nonRetryableTexts
1362
1395
  );
1396
+ translationRegistry.current.removeTextsFromEnqueued(
1397
+ retryableFailedTexts,
1398
+ locale,
1399
+ config2.getToolConfig().strategy
1400
+ );
1363
1401
  eventHandler.current.publishRerenderEvent(sourceTranslatedTexts);
1364
1402
  } catch (error) {
1365
1403
  console.error(
@@ -1370,7 +1408,7 @@ function AITranslationInnerProvider(props) {
1370
1408
  }
1371
1409
  );
1372
1410
  return () => unsubscribe();
1373
- }, []);
1411
+ }, [locale, config2]);
1374
1412
  (0, import_react.useEffect)(() => {
1375
1413
  const unsubscribe = eventHandler.current.subscribeToTranslationProgressEvent(
1376
1414
  (progress, current, total) => {
@@ -1487,12 +1525,14 @@ var AITranslateText = ({
1487
1525
  return;
1488
1526
  }
1489
1527
  if (sourceTexts.includes(text)) {
1490
- setDisplayText(await context.ait(text));
1528
+ const translatedText = await context.ait(text);
1529
+ setDisplayText(translatedText);
1530
+ setShowHighlightState(showHighlight && translatedText !== text);
1491
1531
  }
1492
1532
  }
1493
1533
  );
1494
1534
  return () => unsubscribe();
1495
- }, [context, text]);
1535
+ }, [context, text, showHighlight]);
1496
1536
  const reset = (0, import_react4.useCallback)(
1497
1537
  (displayValue = text) => {
1498
1538
  setDisplayText(displayValue);
@@ -1509,16 +1549,22 @@ var AITranslateText = ({
1509
1549
  reset();
1510
1550
  return;
1511
1551
  }
1552
+ let cancelled = false;
1512
1553
  const translateText = async () => {
1513
1554
  try {
1514
1555
  const translatedText = await context.ait(text);
1556
+ if (cancelled) return;
1515
1557
  setDisplayText(translatedText);
1516
- setShowHighlightState(showHighlight);
1558
+ setShowHighlightState(showHighlight && translatedText !== text);
1517
1559
  } catch {
1560
+ if (cancelled) return;
1518
1561
  reset();
1519
1562
  }
1520
1563
  };
1521
1564
  translateText();
1565
+ return () => {
1566
+ cancelled = true;
1567
+ };
1522
1568
  }, [text, shouldTranslate, context, showHighlight, reset]);
1523
1569
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1524
1570
  displayText,
@@ -245,10 +245,10 @@ var Hash = class {
245
245
  var QueueManager = class {
246
246
  constructor() {
247
247
  if (!globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__) {
248
- globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Set();
248
+ globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Map();
249
249
  }
250
250
  if (!globalThis.__AI_TRANSLATION_BACKEND_QUEUE__) {
251
- globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Set();
251
+ globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Map();
252
252
  }
253
253
  }
254
254
  getQueue(strategy) {
@@ -261,42 +261,20 @@ var QueueManager = class {
261
261
  return void 0;
262
262
  }
263
263
  add(entry) {
264
- var _a;
265
- (_a = this.getQueue(entry.translationStrategy)) == null ? void 0 : _a.add(entry);
266
- }
267
- *generateBatches(strategy, config2) {
268
- const queue = this.getQueue(strategy);
269
- if (!queue) {
270
- throw new Error("Invalid translation strategy");
271
- }
272
- if (queue.size === 0) {
273
- console.warn("No translations to process");
274
- return;
275
- }
276
- const batch = config2.getToolConfig().translationBatchSize;
277
- let batches = [];
278
- for (const entry of queue.values()) {
279
- batches.push(entry);
280
- if (batches.length >= batch) {
281
- yield batches;
282
- batches = [];
283
- }
284
- }
285
- if (batches.length > 0) {
286
- queue.clear();
287
- yield batches;
288
- }
264
+ const queue = this.getQueue(entry.translationStrategy);
265
+ if (!queue || queue.has(entry.id)) return;
266
+ queue.set(entry.id, entry);
289
267
  }
290
- *generateBatchesV2(config2) {
268
+ *generateBatches(config2) {
291
269
  const queue = this.getQueue(config2.getToolConfig().strategy);
292
270
  if (!queue) throw new Error("Invalid translation strategy");
293
271
  const batchSize = config2.getToolConfig().translationBatchSize;
294
272
  while (queue.size > 0) {
295
273
  const batch = [];
296
274
  while (batch.length < batchSize && queue.size > 0) {
297
- const next = queue.values().next().value;
298
- queue.delete(next);
299
- batch.push(next);
275
+ const [key, entry] = queue.entries().next().value;
276
+ queue.delete(key);
277
+ batch.push(entry);
300
278
  }
301
279
  yield batch;
302
280
  }
@@ -489,6 +467,27 @@ var _TranslationRegistry = class _TranslationRegistry {
489
467
  isNonRetryable(key) {
490
468
  return _TranslationRegistry.nonRetryableTexts.has(key);
491
469
  }
470
+ enqueue(enqueuedKey) {
471
+ _TranslationRegistry.enqueuedItems.add(enqueuedKey);
472
+ }
473
+ isEnqueued(enqueuedKey) {
474
+ return _TranslationRegistry.enqueuedItems.has(enqueuedKey);
475
+ }
476
+ clearEnqueued() {
477
+ _TranslationRegistry.enqueuedItems.clear();
478
+ }
479
+ async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
480
+ for (const text of texts) {
481
+ const id = await Storage.generateId(text, targetLanguage, this.tool);
482
+ _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
483
+ }
484
+ }
485
+ static removeFromEnqueued(key) {
486
+ _TranslationRegistry.enqueuedItems.delete(key);
487
+ }
488
+ generateEnqueuedKey(id, strategy) {
489
+ return `${id}:${strategy}`;
490
+ }
492
491
  async get(text, targetLanguage, tool) {
493
492
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
494
493
  if (_TranslationRegistry.memoryCache.has(key)) {
@@ -514,6 +513,7 @@ var _TranslationRegistry = class _TranslationRegistry {
514
513
  async clear() {
515
514
  _TranslationRegistry.memoryCache.clear();
516
515
  _TranslationRegistry.originalTextIndex.clear();
516
+ _TranslationRegistry.enqueuedItems.clear();
517
517
  await Storage.clearAll();
518
518
  return;
519
519
  }
@@ -521,6 +521,7 @@ var _TranslationRegistry = class _TranslationRegistry {
521
521
  await Storage.deleteByToolAndStrategy(tool, strategy);
522
522
  _TranslationRegistry.memoryCache.clear();
523
523
  _TranslationRegistry.originalTextIndex.clear();
524
+ _TranslationRegistry.enqueuedItems.clear();
524
525
  return;
525
526
  }
526
527
  toTranslationRegistryEntry(translation) {
@@ -542,10 +543,16 @@ var _TranslationRegistry = class _TranslationRegistry {
542
543
  translation.id
543
544
  );
544
545
  const newTranslation = this.toTranslationRegistryEntry(translation);
546
+ const enqueuedKey = this.generateEnqueuedKey(
547
+ translation.id,
548
+ translation.translation_strategy
549
+ );
545
550
  if (currentTranslation && !this.shouldUpdateTranslation(currentTranslation, newTranslation)) {
551
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
546
552
  continue;
547
553
  }
548
554
  this.setTranslationToRegistry(newTranslation);
555
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
549
556
  }
550
557
  return;
551
558
  }
@@ -588,6 +595,7 @@ var _TranslationRegistry = class _TranslationRegistry {
588
595
  __publicField(_TranslationRegistry, "memoryCache", /* @__PURE__ */ new Map());
589
596
  __publicField(_TranslationRegistry, "originalTextIndex", /* @__PURE__ */ new Map());
590
597
  __publicField(_TranslationRegistry, "nonRetryableTexts", /* @__PURE__ */ new Set());
598
+ __publicField(_TranslationRegistry, "enqueuedItems", /* @__PURE__ */ new Set());
591
599
  var TranslationRegistry = _TranslationRegistry;
592
600
 
593
601
  // src/utils/eventHandler.ts
@@ -608,12 +616,13 @@ var EventHandler = class {
608
616
  this.aiTranslationEvents = new SystemEvents(this.sourceEventIdentifier);
609
617
  this.toolName = toolName;
610
618
  }
611
- publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts) {
619
+ publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts = []) {
612
620
  this.aiTranslationEvents.publish(
613
621
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
614
622
  {
615
623
  sourceTranslatedTexts,
616
- nonRetryableTexts
624
+ nonRetryableTexts,
625
+ retryableFailedTexts
617
626
  }
618
627
  );
619
628
  }
@@ -621,12 +630,19 @@ var EventHandler = class {
621
630
  return this.aiTranslationEvents.subscribe(
622
631
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
623
632
  (detail) => {
624
- var _a, _b;
633
+ var _a, _b, _c;
625
634
  const sourceTranslatedTexts = Array.isArray(
626
635
  (_a = detail.data) == null ? void 0 : _a.sourceTranslatedTexts
627
636
  ) ? detail.data.sourceTranslatedTexts : [];
628
637
  const nonRetryableTexts = Array.isArray((_b = detail.data) == null ? void 0 : _b.nonRetryableTexts) ? detail.data.nonRetryableTexts : [];
629
- callback(sourceTranslatedTexts, nonRetryableTexts);
638
+ const retryableFailedTexts = Array.isArray(
639
+ (_c = detail.data) == null ? void 0 : _c.retryableFailedTexts
640
+ ) ? detail.data.retryableFailedTexts : [];
641
+ callback(
642
+ sourceTranslatedTexts,
643
+ nonRetryableTexts,
644
+ retryableFailedTexts
645
+ );
630
646
  }
631
647
  );
632
648
  }
@@ -727,6 +743,7 @@ var TranslationManager = class {
727
743
  __publicField(this, "currentTranslatorStrategy");
728
744
  __publicField(this, "sourceTexts", /* @__PURE__ */ new Map());
729
745
  __publicField(this, "nonRetryableTexts", /* @__PURE__ */ new Set());
746
+ __publicField(this, "retryableFailedTexts", /* @__PURE__ */ new Map());
730
747
  __publicField(this, "progressEventHandler");
731
748
  __publicField(this, "translationProgress", {
732
749
  progress: 0,
@@ -754,7 +771,7 @@ var TranslationManager = class {
754
771
  this.translationProgress.total = queueManager.queueSize(
755
772
  this.currentTranslatorStrategy
756
773
  );
757
- for (const batch of queueManager.generateBatchesV2(
774
+ for (const batch of queueManager.generateBatches(
758
775
  this.translator.getConfig()
759
776
  )) {
760
777
  batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
@@ -785,6 +802,7 @@ var TranslationManager = class {
785
802
  continue;
786
803
  }
787
804
  if (!translation.isTranslated) {
805
+ this.addRetryableFailedText(response.tool, translation.sourceText);
788
806
  continue;
789
807
  }
790
808
  this.addSourceTextToMap(response.tool, translation.sourceText);
@@ -804,12 +822,14 @@ var TranslationManager = class {
804
822
  const eventhandler = new EventHandler(mfe);
805
823
  eventhandler.publishTranslationCompleteEvent(
806
824
  Array.from(this.sourceTexts.get(mfe) ?? /* @__PURE__ */ new Set()),
807
- Array.from(this.nonRetryableTexts)
825
+ Array.from(this.nonRetryableTexts),
826
+ Array.from(this.retryableFailedTexts.get(mfe) ?? /* @__PURE__ */ new Set())
808
827
  );
809
828
  }
810
829
  this.mfeToBeNotified.clear();
811
830
  this.sourceTexts.clear();
812
831
  this.nonRetryableTexts.clear();
832
+ this.retryableFailedTexts.clear();
813
833
  }
814
834
  setTranslationProgress(batchSize) {
815
835
  this.translationProgress.current += batchSize;
@@ -846,6 +866,11 @@ var TranslationManager = class {
846
866
  existing.add(sourceText);
847
867
  this.sourceTexts.set(tool, existing);
848
868
  }
869
+ addRetryableFailedText(tool, sourceText) {
870
+ const existing = this.retryableFailedTexts.get(tool) ?? /* @__PURE__ */ new Set();
871
+ existing.add(sourceText);
872
+ this.retryableFailedTexts.set(tool, existing);
873
+ }
849
874
  publishTranslationProgress() {
850
875
  this.progressEventHandler.publishTranslationProgressEvent(
851
876
  this.translationProgress.progress,
@@ -1267,7 +1292,15 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1267
1292
  return existingByOriginal.translatedText;
1268
1293
  }
1269
1294
  if (!existingByOriginal) {
1295
+ const id = await Storage.generateId(text, targetLanguage, tool);
1296
+ const strategy = config2.getToolConfig().strategy;
1297
+ const enqueuedKey = translationRegistry.generateEnqueuedKey(id, strategy);
1298
+ if (translationRegistry.isEnqueued(enqueuedKey)) {
1299
+ return text;
1300
+ }
1301
+ translationRegistry.enqueue(enqueuedKey);
1270
1302
  queueManager.add({
1303
+ id,
1271
1304
  originalText: text,
1272
1305
  targetLanguage,
1273
1306
  tool,
@@ -1331,12 +1364,17 @@ function AITranslationInnerProvider(props) {
1331
1364
  });
1332
1365
  useEffect(() => {
1333
1366
  const unsubscribe = eventHandler.current.subscribeToTranslationCompleteEvent(
1334
- async (sourceTranslatedTexts, nonRetryableTexts) => {
1367
+ async (sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts) => {
1335
1368
  try {
1336
1369
  await translationRegistry.current.populateRegistryFromDatabase();
1337
1370
  translationRegistry.current.populateNonRetryableTexts(
1338
1371
  nonRetryableTexts
1339
1372
  );
1373
+ translationRegistry.current.removeTextsFromEnqueued(
1374
+ retryableFailedTexts,
1375
+ locale,
1376
+ config2.getToolConfig().strategy
1377
+ );
1340
1378
  eventHandler.current.publishRerenderEvent(sourceTranslatedTexts);
1341
1379
  } catch (error) {
1342
1380
  console.error(
@@ -1347,7 +1385,7 @@ function AITranslationInnerProvider(props) {
1347
1385
  }
1348
1386
  );
1349
1387
  return () => unsubscribe();
1350
- }, []);
1388
+ }, [locale, config2]);
1351
1389
  useEffect(() => {
1352
1390
  const unsubscribe = eventHandler.current.subscribeToTranslationProgressEvent(
1353
1391
  (progress, current, total) => {
@@ -1470,12 +1508,14 @@ var AITranslateText = ({
1470
1508
  return;
1471
1509
  }
1472
1510
  if (sourceTexts.includes(text)) {
1473
- setDisplayText(await context.ait(text));
1511
+ const translatedText = await context.ait(text);
1512
+ setDisplayText(translatedText);
1513
+ setShowHighlightState(showHighlight && translatedText !== text);
1474
1514
  }
1475
1515
  }
1476
1516
  );
1477
1517
  return () => unsubscribe();
1478
- }, [context, text]);
1518
+ }, [context, text, showHighlight]);
1479
1519
  const reset = useCallback2(
1480
1520
  (displayValue = text) => {
1481
1521
  setDisplayText(displayValue);
@@ -1492,16 +1532,22 @@ var AITranslateText = ({
1492
1532
  reset();
1493
1533
  return;
1494
1534
  }
1535
+ let cancelled = false;
1495
1536
  const translateText = async () => {
1496
1537
  try {
1497
1538
  const translatedText = await context.ait(text);
1539
+ if (cancelled) return;
1498
1540
  setDisplayText(translatedText);
1499
- setShowHighlightState(showHighlight);
1541
+ setShowHighlightState(showHighlight && translatedText !== text);
1500
1542
  } catch {
1543
+ if (cancelled) return;
1501
1544
  reset();
1502
1545
  }
1503
1546
  };
1504
1547
  translateText();
1548
+ return () => {
1549
+ cancelled = true;
1550
+ };
1505
1551
  }, [text, shouldTranslate, context, showHighlight, reset]);
1506
1552
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1507
1553
  displayText,
@@ -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
  }
@@ -264,10 +264,10 @@ var Hash = class {
264
264
  var QueueManager = class {
265
265
  constructor() {
266
266
  if (!globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__) {
267
- globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Set();
267
+ globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Map();
268
268
  }
269
269
  if (!globalThis.__AI_TRANSLATION_BACKEND_QUEUE__) {
270
- globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Set();
270
+ globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Map();
271
271
  }
272
272
  }
273
273
  getQueue(strategy) {
@@ -280,41 +280,20 @@ var QueueManager = class {
280
280
  return void 0;
281
281
  }
282
282
  add(entry) {
283
- this.getQueue(entry.translationStrategy)?.add(entry);
283
+ const queue = this.getQueue(entry.translationStrategy);
284
+ if (!queue || queue.has(entry.id)) return;
285
+ queue.set(entry.id, entry);
284
286
  }
285
- *generateBatches(strategy, config2) {
286
- const queue = this.getQueue(strategy);
287
- if (!queue) {
288
- throw new Error("Invalid translation strategy");
289
- }
290
- if (queue.size === 0) {
291
- console.warn("No translations to process");
292
- return;
293
- }
294
- const batch = config2.getToolConfig().translationBatchSize;
295
- let batches = [];
296
- for (const entry of queue.values()) {
297
- batches.push(entry);
298
- if (batches.length >= batch) {
299
- yield batches;
300
- batches = [];
301
- }
302
- }
303
- if (batches.length > 0) {
304
- queue.clear();
305
- yield batches;
306
- }
307
- }
308
- *generateBatchesV2(config2) {
287
+ *generateBatches(config2) {
309
288
  const queue = this.getQueue(config2.getToolConfig().strategy);
310
289
  if (!queue) throw new Error("Invalid translation strategy");
311
290
  const batchSize = config2.getToolConfig().translationBatchSize;
312
291
  while (queue.size > 0) {
313
292
  const batch = [];
314
293
  while (batch.length < batchSize && queue.size > 0) {
315
- const next = queue.values().next().value;
316
- queue.delete(next);
317
- batch.push(next);
294
+ const [key, entry] = queue.entries().next().value;
295
+ queue.delete(key);
296
+ batch.push(entry);
318
297
  }
319
298
  yield batch;
320
299
  }
@@ -463,6 +442,7 @@ var TranslationRegistry = class _TranslationRegistry {
463
442
  static memoryCache = /* @__PURE__ */ new Map();
464
443
  static originalTextIndex = /* @__PURE__ */ new Map();
465
444
  static nonRetryableTexts = /* @__PURE__ */ new Set();
445
+ static enqueuedItems = /* @__PURE__ */ new Set();
466
446
  tool;
467
447
  constructor(tool) {
468
448
  this.tool = tool;
@@ -516,6 +496,27 @@ var TranslationRegistry = class _TranslationRegistry {
516
496
  isNonRetryable(key) {
517
497
  return _TranslationRegistry.nonRetryableTexts.has(key);
518
498
  }
499
+ enqueue(enqueuedKey) {
500
+ _TranslationRegistry.enqueuedItems.add(enqueuedKey);
501
+ }
502
+ isEnqueued(enqueuedKey) {
503
+ return _TranslationRegistry.enqueuedItems.has(enqueuedKey);
504
+ }
505
+ clearEnqueued() {
506
+ _TranslationRegistry.enqueuedItems.clear();
507
+ }
508
+ async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
509
+ for (const text of texts) {
510
+ const id = await Storage.generateId(text, targetLanguage, this.tool);
511
+ _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
512
+ }
513
+ }
514
+ static removeFromEnqueued(key) {
515
+ _TranslationRegistry.enqueuedItems.delete(key);
516
+ }
517
+ generateEnqueuedKey(id, strategy) {
518
+ return `${id}:${strategy}`;
519
+ }
519
520
  async get(text, targetLanguage, tool) {
520
521
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
521
522
  if (_TranslationRegistry.memoryCache.has(key)) {
@@ -541,6 +542,7 @@ var TranslationRegistry = class _TranslationRegistry {
541
542
  async clear() {
542
543
  _TranslationRegistry.memoryCache.clear();
543
544
  _TranslationRegistry.originalTextIndex.clear();
545
+ _TranslationRegistry.enqueuedItems.clear();
544
546
  await Storage.clearAll();
545
547
  return;
546
548
  }
@@ -548,6 +550,7 @@ var TranslationRegistry = class _TranslationRegistry {
548
550
  await Storage.deleteByToolAndStrategy(tool, strategy);
549
551
  _TranslationRegistry.memoryCache.clear();
550
552
  _TranslationRegistry.originalTextIndex.clear();
553
+ _TranslationRegistry.enqueuedItems.clear();
551
554
  return;
552
555
  }
553
556
  toTranslationRegistryEntry(translation) {
@@ -569,10 +572,16 @@ var TranslationRegistry = class _TranslationRegistry {
569
572
  translation.id
570
573
  );
571
574
  const newTranslation = this.toTranslationRegistryEntry(translation);
575
+ const enqueuedKey = this.generateEnqueuedKey(
576
+ translation.id,
577
+ translation.translation_strategy
578
+ );
572
579
  if (currentTranslation && !this.shouldUpdateTranslation(currentTranslation, newTranslation)) {
580
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
573
581
  continue;
574
582
  }
575
583
  this.setTranslationToRegistry(newTranslation);
584
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
576
585
  }
577
586
  return;
578
587
  }
@@ -622,12 +631,13 @@ var EventHandler = class {
622
631
  this.aiTranslationEvents = new import_web_sdk_events.SystemEvents(this.sourceEventIdentifier);
623
632
  this.toolName = toolName;
624
633
  }
625
- publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts) {
634
+ publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts = []) {
626
635
  this.aiTranslationEvents.publish(
627
636
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
628
637
  {
629
638
  sourceTranslatedTexts,
630
- nonRetryableTexts
639
+ nonRetryableTexts,
640
+ retryableFailedTexts
631
641
  }
632
642
  );
633
643
  }
@@ -639,7 +649,14 @@ var EventHandler = class {
639
649
  detail.data?.sourceTranslatedTexts
640
650
  ) ? detail.data.sourceTranslatedTexts : [];
641
651
  const nonRetryableTexts = Array.isArray(detail.data?.nonRetryableTexts) ? detail.data.nonRetryableTexts : [];
642
- callback(sourceTranslatedTexts, nonRetryableTexts);
652
+ const retryableFailedTexts = Array.isArray(
653
+ detail.data?.retryableFailedTexts
654
+ ) ? detail.data.retryableFailedTexts : [];
655
+ callback(
656
+ sourceTranslatedTexts,
657
+ nonRetryableTexts,
658
+ retryableFailedTexts
659
+ );
643
660
  }
644
661
  );
645
662
  }
@@ -728,6 +745,7 @@ var TranslationManager = class {
728
745
  currentTranslatorStrategy;
729
746
  sourceTexts = /* @__PURE__ */ new Map();
730
747
  nonRetryableTexts = /* @__PURE__ */ new Set();
748
+ retryableFailedTexts = /* @__PURE__ */ new Map();
731
749
  progressEventHandler;
732
750
  translationProgress = {
733
751
  progress: 0,
@@ -756,7 +774,7 @@ var TranslationManager = class {
756
774
  this.translationProgress.total = queueManager.queueSize(
757
775
  this.currentTranslatorStrategy
758
776
  );
759
- for (const batch of queueManager.generateBatchesV2(
777
+ for (const batch of queueManager.generateBatches(
760
778
  this.translator.getConfig()
761
779
  )) {
762
780
  batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
@@ -787,6 +805,7 @@ var TranslationManager = class {
787
805
  continue;
788
806
  }
789
807
  if (!translation.isTranslated) {
808
+ this.addRetryableFailedText(response.tool, translation.sourceText);
790
809
  continue;
791
810
  }
792
811
  this.addSourceTextToMap(response.tool, translation.sourceText);
@@ -806,12 +825,14 @@ var TranslationManager = class {
806
825
  const eventhandler = new EventHandler(mfe);
807
826
  eventhandler.publishTranslationCompleteEvent(
808
827
  Array.from(this.sourceTexts.get(mfe) ?? /* @__PURE__ */ new Set()),
809
- Array.from(this.nonRetryableTexts)
828
+ Array.from(this.nonRetryableTexts),
829
+ Array.from(this.retryableFailedTexts.get(mfe) ?? /* @__PURE__ */ new Set())
810
830
  );
811
831
  }
812
832
  this.mfeToBeNotified.clear();
813
833
  this.sourceTexts.clear();
814
834
  this.nonRetryableTexts.clear();
835
+ this.retryableFailedTexts.clear();
815
836
  }
816
837
  setTranslationProgress(batchSize) {
817
838
  this.translationProgress.current += batchSize;
@@ -848,6 +869,11 @@ var TranslationManager = class {
848
869
  existing.add(sourceText);
849
870
  this.sourceTexts.set(tool, existing);
850
871
  }
872
+ addRetryableFailedText(tool, sourceText) {
873
+ const existing = this.retryableFailedTexts.get(tool) ?? /* @__PURE__ */ new Set();
874
+ existing.add(sourceText);
875
+ this.retryableFailedTexts.set(tool, existing);
876
+ }
851
877
  publishTranslationProgress() {
852
878
  this.progressEventHandler.publishTranslationProgressEvent(
853
879
  this.translationProgress.progress,
@@ -1266,7 +1292,15 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1266
1292
  return existingByOriginal.translatedText;
1267
1293
  }
1268
1294
  if (!existingByOriginal) {
1295
+ const id = await Storage.generateId(text, targetLanguage, tool);
1296
+ const strategy = config2.getToolConfig().strategy;
1297
+ const enqueuedKey = translationRegistry.generateEnqueuedKey(id, strategy);
1298
+ if (translationRegistry.isEnqueued(enqueuedKey)) {
1299
+ return text;
1300
+ }
1301
+ translationRegistry.enqueue(enqueuedKey);
1269
1302
  queueManager.add({
1303
+ id,
1270
1304
  originalText: text,
1271
1305
  targetLanguage,
1272
1306
  tool,
@@ -1330,12 +1364,17 @@ function AITranslationInnerProvider(props) {
1330
1364
  });
1331
1365
  (0, import_react.useEffect)(() => {
1332
1366
  const unsubscribe = eventHandler.current.subscribeToTranslationCompleteEvent(
1333
- async (sourceTranslatedTexts, nonRetryableTexts) => {
1367
+ async (sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts) => {
1334
1368
  try {
1335
1369
  await translationRegistry.current.populateRegistryFromDatabase();
1336
1370
  translationRegistry.current.populateNonRetryableTexts(
1337
1371
  nonRetryableTexts
1338
1372
  );
1373
+ translationRegistry.current.removeTextsFromEnqueued(
1374
+ retryableFailedTexts,
1375
+ locale,
1376
+ config2.getToolConfig().strategy
1377
+ );
1339
1378
  eventHandler.current.publishRerenderEvent(sourceTranslatedTexts);
1340
1379
  } catch (error) {
1341
1380
  console.error(
@@ -1346,7 +1385,7 @@ function AITranslationInnerProvider(props) {
1346
1385
  }
1347
1386
  );
1348
1387
  return () => unsubscribe();
1349
- }, []);
1388
+ }, [locale, config2]);
1350
1389
  (0, import_react.useEffect)(() => {
1351
1390
  const unsubscribe = eventHandler.current.subscribeToTranslationProgressEvent(
1352
1391
  (progress, current, total) => {
@@ -1463,12 +1502,14 @@ var AITranslateText = ({
1463
1502
  return;
1464
1503
  }
1465
1504
  if (sourceTexts.includes(text)) {
1466
- setDisplayText(await context.ait(text));
1505
+ const translatedText = await context.ait(text);
1506
+ setDisplayText(translatedText);
1507
+ setShowHighlightState(showHighlight && translatedText !== text);
1467
1508
  }
1468
1509
  }
1469
1510
  );
1470
1511
  return () => unsubscribe();
1471
- }, [context, text]);
1512
+ }, [context, text, showHighlight]);
1472
1513
  const reset = (0, import_react4.useCallback)(
1473
1514
  (displayValue = text) => {
1474
1515
  setDisplayText(displayValue);
@@ -1485,16 +1526,22 @@ var AITranslateText = ({
1485
1526
  reset();
1486
1527
  return;
1487
1528
  }
1529
+ let cancelled = false;
1488
1530
  const translateText = async () => {
1489
1531
  try {
1490
1532
  const translatedText = await context.ait(text);
1533
+ if (cancelled) return;
1491
1534
  setDisplayText(translatedText);
1492
- setShowHighlightState(showHighlight);
1535
+ setShowHighlightState(showHighlight && translatedText !== text);
1493
1536
  } catch {
1537
+ if (cancelled) return;
1494
1538
  reset();
1495
1539
  }
1496
1540
  };
1497
1541
  translateText();
1542
+ return () => {
1543
+ cancelled = true;
1544
+ };
1498
1545
  }, [text, shouldTranslate, context, showHighlight, reset]);
1499
1546
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1500
1547
  displayText,
@@ -239,10 +239,10 @@ var Hash = class {
239
239
  var QueueManager = class {
240
240
  constructor() {
241
241
  if (!globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__) {
242
- globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Set();
242
+ globalThis.__AI_TRANSLATION_FRONTEND_QUEUE__ = /* @__PURE__ */ new Map();
243
243
  }
244
244
  if (!globalThis.__AI_TRANSLATION_BACKEND_QUEUE__) {
245
- globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Set();
245
+ globalThis.__AI_TRANSLATION_BACKEND_QUEUE__ = /* @__PURE__ */ new Map();
246
246
  }
247
247
  }
248
248
  getQueue(strategy) {
@@ -255,41 +255,20 @@ var QueueManager = class {
255
255
  return void 0;
256
256
  }
257
257
  add(entry) {
258
- this.getQueue(entry.translationStrategy)?.add(entry);
258
+ const queue = this.getQueue(entry.translationStrategy);
259
+ if (!queue || queue.has(entry.id)) return;
260
+ queue.set(entry.id, entry);
259
261
  }
260
- *generateBatches(strategy, config2) {
261
- const queue = this.getQueue(strategy);
262
- if (!queue) {
263
- throw new Error("Invalid translation strategy");
264
- }
265
- if (queue.size === 0) {
266
- console.warn("No translations to process");
267
- return;
268
- }
269
- const batch = config2.getToolConfig().translationBatchSize;
270
- let batches = [];
271
- for (const entry of queue.values()) {
272
- batches.push(entry);
273
- if (batches.length >= batch) {
274
- yield batches;
275
- batches = [];
276
- }
277
- }
278
- if (batches.length > 0) {
279
- queue.clear();
280
- yield batches;
281
- }
282
- }
283
- *generateBatchesV2(config2) {
262
+ *generateBatches(config2) {
284
263
  const queue = this.getQueue(config2.getToolConfig().strategy);
285
264
  if (!queue) throw new Error("Invalid translation strategy");
286
265
  const batchSize = config2.getToolConfig().translationBatchSize;
287
266
  while (queue.size > 0) {
288
267
  const batch = [];
289
268
  while (batch.length < batchSize && queue.size > 0) {
290
- const next = queue.values().next().value;
291
- queue.delete(next);
292
- batch.push(next);
269
+ const [key, entry] = queue.entries().next().value;
270
+ queue.delete(key);
271
+ batch.push(entry);
293
272
  }
294
273
  yield batch;
295
274
  }
@@ -438,6 +417,7 @@ var TranslationRegistry = class _TranslationRegistry {
438
417
  static memoryCache = /* @__PURE__ */ new Map();
439
418
  static originalTextIndex = /* @__PURE__ */ new Map();
440
419
  static nonRetryableTexts = /* @__PURE__ */ new Set();
420
+ static enqueuedItems = /* @__PURE__ */ new Set();
441
421
  tool;
442
422
  constructor(tool) {
443
423
  this.tool = tool;
@@ -491,6 +471,27 @@ var TranslationRegistry = class _TranslationRegistry {
491
471
  isNonRetryable(key) {
492
472
  return _TranslationRegistry.nonRetryableTexts.has(key);
493
473
  }
474
+ enqueue(enqueuedKey) {
475
+ _TranslationRegistry.enqueuedItems.add(enqueuedKey);
476
+ }
477
+ isEnqueued(enqueuedKey) {
478
+ return _TranslationRegistry.enqueuedItems.has(enqueuedKey);
479
+ }
480
+ clearEnqueued() {
481
+ _TranslationRegistry.enqueuedItems.clear();
482
+ }
483
+ async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
484
+ for (const text of texts) {
485
+ const id = await Storage.generateId(text, targetLanguage, this.tool);
486
+ _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
487
+ }
488
+ }
489
+ static removeFromEnqueued(key) {
490
+ _TranslationRegistry.enqueuedItems.delete(key);
491
+ }
492
+ generateEnqueuedKey(id, strategy) {
493
+ return `${id}:${strategy}`;
494
+ }
494
495
  async get(text, targetLanguage, tool) {
495
496
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
496
497
  if (_TranslationRegistry.memoryCache.has(key)) {
@@ -516,6 +517,7 @@ var TranslationRegistry = class _TranslationRegistry {
516
517
  async clear() {
517
518
  _TranslationRegistry.memoryCache.clear();
518
519
  _TranslationRegistry.originalTextIndex.clear();
520
+ _TranslationRegistry.enqueuedItems.clear();
519
521
  await Storage.clearAll();
520
522
  return;
521
523
  }
@@ -523,6 +525,7 @@ var TranslationRegistry = class _TranslationRegistry {
523
525
  await Storage.deleteByToolAndStrategy(tool, strategy);
524
526
  _TranslationRegistry.memoryCache.clear();
525
527
  _TranslationRegistry.originalTextIndex.clear();
528
+ _TranslationRegistry.enqueuedItems.clear();
526
529
  return;
527
530
  }
528
531
  toTranslationRegistryEntry(translation) {
@@ -544,10 +547,16 @@ var TranslationRegistry = class _TranslationRegistry {
544
547
  translation.id
545
548
  );
546
549
  const newTranslation = this.toTranslationRegistryEntry(translation);
550
+ const enqueuedKey = this.generateEnqueuedKey(
551
+ translation.id,
552
+ translation.translation_strategy
553
+ );
547
554
  if (currentTranslation && !this.shouldUpdateTranslation(currentTranslation, newTranslation)) {
555
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
548
556
  continue;
549
557
  }
550
558
  this.setTranslationToRegistry(newTranslation);
559
+ _TranslationRegistry.removeFromEnqueued(enqueuedKey);
551
560
  }
552
561
  return;
553
562
  }
@@ -597,12 +606,13 @@ var EventHandler = class {
597
606
  this.aiTranslationEvents = new SystemEvents(this.sourceEventIdentifier);
598
607
  this.toolName = toolName;
599
608
  }
600
- publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts) {
609
+ publishTranslationCompleteEvent(sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts = []) {
601
610
  this.aiTranslationEvents.publish(
602
611
  this.withEventName(TRANSLATION_COMPLETE_EVENT_NAME),
603
612
  {
604
613
  sourceTranslatedTexts,
605
- nonRetryableTexts
614
+ nonRetryableTexts,
615
+ retryableFailedTexts
606
616
  }
607
617
  );
608
618
  }
@@ -614,7 +624,14 @@ var EventHandler = class {
614
624
  detail.data?.sourceTranslatedTexts
615
625
  ) ? detail.data.sourceTranslatedTexts : [];
616
626
  const nonRetryableTexts = Array.isArray(detail.data?.nonRetryableTexts) ? detail.data.nonRetryableTexts : [];
617
- callback(sourceTranslatedTexts, nonRetryableTexts);
627
+ const retryableFailedTexts = Array.isArray(
628
+ detail.data?.retryableFailedTexts
629
+ ) ? detail.data.retryableFailedTexts : [];
630
+ callback(
631
+ sourceTranslatedTexts,
632
+ nonRetryableTexts,
633
+ retryableFailedTexts
634
+ );
618
635
  }
619
636
  );
620
637
  }
@@ -703,6 +720,7 @@ var TranslationManager = class {
703
720
  currentTranslatorStrategy;
704
721
  sourceTexts = /* @__PURE__ */ new Map();
705
722
  nonRetryableTexts = /* @__PURE__ */ new Set();
723
+ retryableFailedTexts = /* @__PURE__ */ new Map();
706
724
  progressEventHandler;
707
725
  translationProgress = {
708
726
  progress: 0,
@@ -731,7 +749,7 @@ var TranslationManager = class {
731
749
  this.translationProgress.total = queueManager.queueSize(
732
750
  this.currentTranslatorStrategy
733
751
  );
734
- for (const batch of queueManager.generateBatchesV2(
752
+ for (const batch of queueManager.generateBatches(
735
753
  this.translator.getConfig()
736
754
  )) {
737
755
  batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
@@ -762,6 +780,7 @@ var TranslationManager = class {
762
780
  continue;
763
781
  }
764
782
  if (!translation.isTranslated) {
783
+ this.addRetryableFailedText(response.tool, translation.sourceText);
765
784
  continue;
766
785
  }
767
786
  this.addSourceTextToMap(response.tool, translation.sourceText);
@@ -781,12 +800,14 @@ var TranslationManager = class {
781
800
  const eventhandler = new EventHandler(mfe);
782
801
  eventhandler.publishTranslationCompleteEvent(
783
802
  Array.from(this.sourceTexts.get(mfe) ?? /* @__PURE__ */ new Set()),
784
- Array.from(this.nonRetryableTexts)
803
+ Array.from(this.nonRetryableTexts),
804
+ Array.from(this.retryableFailedTexts.get(mfe) ?? /* @__PURE__ */ new Set())
785
805
  );
786
806
  }
787
807
  this.mfeToBeNotified.clear();
788
808
  this.sourceTexts.clear();
789
809
  this.nonRetryableTexts.clear();
810
+ this.retryableFailedTexts.clear();
790
811
  }
791
812
  setTranslationProgress(batchSize) {
792
813
  this.translationProgress.current += batchSize;
@@ -823,6 +844,11 @@ var TranslationManager = class {
823
844
  existing.add(sourceText);
824
845
  this.sourceTexts.set(tool, existing);
825
846
  }
847
+ addRetryableFailedText(tool, sourceText) {
848
+ const existing = this.retryableFailedTexts.get(tool) ?? /* @__PURE__ */ new Set();
849
+ existing.add(sourceText);
850
+ this.retryableFailedTexts.set(tool, existing);
851
+ }
826
852
  publishTranslationProgress() {
827
853
  this.progressEventHandler.publishTranslationProgressEvent(
828
854
  this.translationProgress.progress,
@@ -1241,7 +1267,15 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1241
1267
  return existingByOriginal.translatedText;
1242
1268
  }
1243
1269
  if (!existingByOriginal) {
1270
+ const id = await Storage.generateId(text, targetLanguage, tool);
1271
+ const strategy = config2.getToolConfig().strategy;
1272
+ const enqueuedKey = translationRegistry.generateEnqueuedKey(id, strategy);
1273
+ if (translationRegistry.isEnqueued(enqueuedKey)) {
1274
+ return text;
1275
+ }
1276
+ translationRegistry.enqueue(enqueuedKey);
1244
1277
  queueManager.add({
1278
+ id,
1245
1279
  originalText: text,
1246
1280
  targetLanguage,
1247
1281
  tool,
@@ -1305,12 +1339,17 @@ function AITranslationInnerProvider(props) {
1305
1339
  });
1306
1340
  useEffect(() => {
1307
1341
  const unsubscribe = eventHandler.current.subscribeToTranslationCompleteEvent(
1308
- async (sourceTranslatedTexts, nonRetryableTexts) => {
1342
+ async (sourceTranslatedTexts, nonRetryableTexts, retryableFailedTexts) => {
1309
1343
  try {
1310
1344
  await translationRegistry.current.populateRegistryFromDatabase();
1311
1345
  translationRegistry.current.populateNonRetryableTexts(
1312
1346
  nonRetryableTexts
1313
1347
  );
1348
+ translationRegistry.current.removeTextsFromEnqueued(
1349
+ retryableFailedTexts,
1350
+ locale,
1351
+ config2.getToolConfig().strategy
1352
+ );
1314
1353
  eventHandler.current.publishRerenderEvent(sourceTranslatedTexts);
1315
1354
  } catch (error) {
1316
1355
  console.error(
@@ -1321,7 +1360,7 @@ function AITranslationInnerProvider(props) {
1321
1360
  }
1322
1361
  );
1323
1362
  return () => unsubscribe();
1324
- }, []);
1363
+ }, [locale, config2]);
1325
1364
  useEffect(() => {
1326
1365
  const unsubscribe = eventHandler.current.subscribeToTranslationProgressEvent(
1327
1366
  (progress, current, total) => {
@@ -1444,12 +1483,14 @@ var AITranslateText = ({
1444
1483
  return;
1445
1484
  }
1446
1485
  if (sourceTexts.includes(text)) {
1447
- setDisplayText(await context.ait(text));
1486
+ const translatedText = await context.ait(text);
1487
+ setDisplayText(translatedText);
1488
+ setShowHighlightState(showHighlight && translatedText !== text);
1448
1489
  }
1449
1490
  }
1450
1491
  );
1451
1492
  return () => unsubscribe();
1452
- }, [context, text]);
1493
+ }, [context, text, showHighlight]);
1453
1494
  const reset = useCallback2(
1454
1495
  (displayValue = text) => {
1455
1496
  setDisplayText(displayValue);
@@ -1466,16 +1507,22 @@ var AITranslateText = ({
1466
1507
  reset();
1467
1508
  return;
1468
1509
  }
1510
+ let cancelled = false;
1469
1511
  const translateText = async () => {
1470
1512
  try {
1471
1513
  const translatedText = await context.ait(text);
1514
+ if (cancelled) return;
1472
1515
  setDisplayText(translatedText);
1473
- setShowHighlightState(showHighlight);
1516
+ setShowHighlightState(showHighlight && translatedText !== text);
1474
1517
  } catch {
1518
+ if (cancelled) return;
1475
1519
  reset();
1476
1520
  }
1477
1521
  };
1478
1522
  translateText();
1523
+ return () => {
1524
+ cancelled = true;
1525
+ };
1479
1526
  }, [text, shouldTranslate, context, showHighlight, reset]);
1480
1527
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1481
1528
  displayText,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@procore/ai-translations",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Library that provides a solution to use AI to translate text into a language",
5
5
  "main": "dist/legacy/index.js",
6
6
  "types": "dist/legacy/index.d.ts",