@procore/ai-translations 0.5.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.
@@ -46,6 +46,9 @@ var Config = class _Config {
46
46
  setToolName(name) {
47
47
  this.toolName = name;
48
48
  }
49
+ isBackendTranslationStrategy() {
50
+ return this.ToolConfig.strategy === "backend_translations";
51
+ }
49
52
  getToolName() {
50
53
  return this.toolName;
51
54
  }
@@ -389,6 +392,19 @@ var Storage = class _Storage {
389
392
  }
390
393
  }
391
394
  // ------------------------------------
395
+ // 🗑️ Clear Translation by ID
396
+ // ------------------------------------
397
+ static async deleteById(id) {
398
+ try {
399
+ const db = await _Storage.getDB();
400
+ await db.delete(_Storage.STORE_NAME, id);
401
+ return true;
402
+ } catch (error) {
403
+ console.error("[Storage] Failed to delete by id:", error);
404
+ return false;
405
+ }
406
+ }
407
+ // ------------------------------------
392
408
  // 🗑️ Clear All Translations
393
409
  // ------------------------------------
394
410
  static async clearAll() {
@@ -483,7 +499,9 @@ var TranslationRegistry = class _TranslationRegistry {
483
499
  async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
484
500
  for (const text of texts) {
485
501
  const id = await Storage.generateId(text, targetLanguage, this.tool);
486
- _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
502
+ _TranslationRegistry.enqueuedItems.delete(
503
+ this.generateEnqueuedKey(id, strategy)
504
+ );
487
505
  }
488
506
  }
489
507
  static removeFromEnqueued(key) {
@@ -492,10 +510,19 @@ var TranslationRegistry = class _TranslationRegistry {
492
510
  generateEnqueuedKey(id, strategy) {
493
511
  return `${id}:${strategy}`;
494
512
  }
495
- async get(text, targetLanguage, tool) {
513
+ async get(text, targetLanguage, tool, config2) {
496
514
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
497
- if (_TranslationRegistry.memoryCache.has(key)) {
498
- return _TranslationRegistry.memoryCache.get(key);
515
+ const cached = _TranslationRegistry.memoryCache.get(key);
516
+ if (cached) {
517
+ if (cached.translationStrategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
518
+ _TranslationRegistry.memoryCache.delete(key);
519
+ if (cached.translatedText) {
520
+ _TranslationRegistry.originalTextIndex.delete(cached.translatedText);
521
+ }
522
+ await Storage.deleteById(key);
523
+ return void 0;
524
+ }
525
+ return cached;
499
526
  }
500
527
  const translation = await Storage.getTranslation(
501
528
  text,
@@ -503,6 +530,10 @@ var TranslationRegistry = class _TranslationRegistry {
503
530
  tool
504
531
  );
505
532
  if (translation !== void 0) {
533
+ if (translation.translation_strategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
534
+ await Storage.deleteById(translation.id);
535
+ return void 0;
536
+ }
506
537
  return this.toTranslationRegistryEntry(translation);
507
538
  }
508
539
  return void 0;
@@ -749,19 +780,21 @@ var TranslationManager = class {
749
780
  this.translationProgress.total = queueManager.queueSize(
750
781
  this.currentTranslatorStrategy
751
782
  );
752
- for (const batch of queueManager.generateBatches(
753
- this.translator.getConfig()
754
- )) {
755
- batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
756
- const result = await this.translator.processTranslations(
757
- batch.map(
758
- (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
759
- )
760
- );
761
- this.setTranslationProgress(batch.length);
762
- await this.updateDatabaseWithTranslations(result);
763
- await this.notifyTranslationCompleted();
764
- }
783
+ do {
784
+ for (const batch of queueManager.generateBatches(
785
+ this.translator.getConfig()
786
+ )) {
787
+ batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
788
+ const result = await this.translator.processTranslations(
789
+ batch.map(
790
+ (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
791
+ )
792
+ );
793
+ this.setTranslationProgress(batch.length);
794
+ await this.updateDatabaseWithTranslations(result);
795
+ await this.notifyTranslationCompleted();
796
+ }
797
+ } while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
765
798
  this.resetTranslationProgress();
766
799
  this.publishTranslationProgress();
767
800
  if (this.currentTranslatorStrategy === "frontend_translations") {
@@ -915,8 +948,16 @@ var Client = class {
915
948
  method: "POST",
916
949
  body: JSON.stringify(requests)
917
950
  });
951
+ const responseBody = Array.isArray(data) ? data.map((group) => ({
952
+ ...group,
953
+ translations: Array.isArray(group.translations) ? group.translations.map((t) => ({
954
+ ...t,
955
+ isTranslated: t.isTranslated ?? (!!t.translation && t.translation !== ""),
956
+ retryable: t.retryable ?? true
957
+ })) : []
958
+ })) : data;
918
959
  return {
919
- responseBody: data,
960
+ responseBody,
920
961
  success: true
921
962
  };
922
963
  } catch (error) {
@@ -993,12 +1034,55 @@ var getModelDownloadEventHandler = () => {
993
1034
  }
994
1035
  return _modelDownloadEventHandler;
995
1036
  };
1037
+ var SUPPORTED_LANGUAGES = /* @__PURE__ */ new Set([
1038
+ "ar",
1039
+ "bn",
1040
+ "bg",
1041
+ "zh",
1042
+ "hr",
1043
+ "cs",
1044
+ "da",
1045
+ "nl",
1046
+ "en",
1047
+ "fi",
1048
+ "fr",
1049
+ "de",
1050
+ "el",
1051
+ "he",
1052
+ "hi",
1053
+ "hu",
1054
+ "id",
1055
+ "it",
1056
+ "ja",
1057
+ "kn",
1058
+ "ko",
1059
+ "lt",
1060
+ "mr",
1061
+ "no",
1062
+ "pl",
1063
+ "pt",
1064
+ "ro",
1065
+ "ru",
1066
+ "sk",
1067
+ "sl",
1068
+ "es",
1069
+ "sv",
1070
+ "ta",
1071
+ "te",
1072
+ "th",
1073
+ "tr",
1074
+ "uk",
1075
+ "vi"
1076
+ ]);
996
1077
  var ChromeTranslator = class _ChromeTranslator {
997
1078
  translator;
998
1079
  constructor(translator) {
999
1080
  this.translator = translator;
1000
1081
  }
1001
- static translatorReadyPromise = Promise.resolve();
1082
+ static translatorReadyPromises = /* @__PURE__ */ new Map();
1083
+ static isSupportedLanguage(lang) {
1084
+ return SUPPORTED_LANGUAGES.has(lang) || SUPPORTED_LANGUAGES.has(lang.split("-")[0] ?? "");
1085
+ }
1002
1086
  static ensureRegistry() {
1003
1087
  if (!globalThis.chromeTranslatorRegistry) {
1004
1088
  globalThis.chromeTranslatorRegistry = /* @__PURE__ */ new Map();
@@ -1020,30 +1104,31 @@ var ChromeTranslator = class _ChromeTranslator {
1020
1104
  );
1021
1105
  }
1022
1106
  static async createTranslator(source, target) {
1107
+ const key = this.generateKey(source, target);
1023
1108
  let resolveReady;
1024
- this.translatorReadyPromise = new Promise((resolve) => {
1109
+ const readyPromise = new Promise((resolve) => {
1025
1110
  resolveReady = resolve;
1026
1111
  });
1112
+ this.translatorReadyPromises.set(key, readyPromise);
1027
1113
  let isDownloading = false;
1028
1114
  const translator = await Translator.create({
1029
1115
  sourceLanguage: source,
1030
1116
  targetLanguage: target,
1031
1117
  monitor(m) {
1032
- m.addEventListener(
1033
- "downloadprogress",
1034
- (e) => {
1035
- isDownloading = true;
1036
- const progress = e.total > 0 ? Math.round(e.loaded / e.total * 100) : 0;
1037
- getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1038
- e.loaded,
1039
- e.total,
1040
- progress
1041
- );
1042
- if (e.loaded === e.total) {
1043
- resolveReady();
1044
- }
1118
+ m.addEventListener("downloadprogress", (e) => {
1119
+ isDownloading = true;
1120
+ const loaded = e.loaded ?? 0;
1121
+ const total = e.total ?? 0;
1122
+ const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
1123
+ getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1124
+ loaded,
1125
+ total,
1126
+ progress
1127
+ );
1128
+ if (total > 0 && loaded >= total) {
1129
+ resolveReady();
1045
1130
  }
1046
- );
1131
+ });
1047
1132
  }
1048
1133
  });
1049
1134
  if (!isDownloading) {
@@ -1051,8 +1136,8 @@ var ChromeTranslator = class _ChromeTranslator {
1051
1136
  }
1052
1137
  return translator;
1053
1138
  }
1054
- static waitForReady() {
1055
- return this.translatorReadyPromise;
1139
+ static waitForReady(key) {
1140
+ return this.translatorReadyPromises.get(key) ?? Promise.resolve();
1056
1141
  }
1057
1142
  static async translate(text, targetLanguage) {
1058
1143
  this.ensureRegistry();
@@ -1060,8 +1145,17 @@ var ChromeTranslator = class _ChromeTranslator {
1060
1145
  const detector = await ChromeLanguageDetector.getInstance();
1061
1146
  const sourceLanguage = await detector.detectLanguage(text);
1062
1147
  const key = this.generateKey(sourceLanguage, targetLanguage);
1063
- let instance = globalThis.chromeTranslatorRegistry.get(key);
1064
- if (!instance) {
1148
+ if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
1149
+ return {
1150
+ translation: text,
1151
+ sourceLanguage,
1152
+ success: false,
1153
+ retryable: false,
1154
+ errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${targetLanguage}`
1155
+ };
1156
+ }
1157
+ const existingInstance = globalThis.chromeTranslatorRegistry.get(key);
1158
+ if (!existingInstance) {
1065
1159
  const translatorCapabilities = await Translator.availability({
1066
1160
  sourceLanguage,
1067
1161
  targetLanguage
@@ -1079,10 +1173,16 @@ var ChromeTranslator = class _ChromeTranslator {
1079
1173
  sourceLanguage,
1080
1174
  targetLanguage
1081
1175
  );
1082
- instance = new _ChromeTranslator(chromeTranslator);
1083
- globalThis.chromeTranslatorRegistry.set(key, instance);
1176
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1177
+ globalThis.chromeTranslatorRegistry.set(
1178
+ key,
1179
+ new _ChromeTranslator(chromeTranslator)
1180
+ );
1181
+ }
1084
1182
  }
1085
- await this.waitForReady();
1183
+ const instance = globalThis.chromeTranslatorRegistry.get(key);
1184
+ await this.waitForReady(key);
1185
+ this.translatorReadyPromises.delete(key);
1086
1186
  const translation = await instance.translator?.translate(text);
1087
1187
  if (!translation) {
1088
1188
  return {
@@ -1090,7 +1190,7 @@ var ChromeTranslator = class _ChromeTranslator {
1090
1190
  sourceLanguage,
1091
1191
  success: false,
1092
1192
  errorMessage: "Translation failed",
1093
- retryable: true
1193
+ retryable: false
1094
1194
  };
1095
1195
  }
1096
1196
  return {
@@ -1100,11 +1200,12 @@ var ChromeTranslator = class _ChromeTranslator {
1100
1200
  retryable: true
1101
1201
  };
1102
1202
  } catch (error) {
1203
+ const message = error instanceof Error ? error.message : "Unknown error";
1103
1204
  return {
1104
1205
  translation: text,
1105
1206
  sourceLanguage: targetLanguage,
1106
1207
  success: false,
1107
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1208
+ errorMessage: message,
1108
1209
  retryable: true
1109
1210
  };
1110
1211
  }
@@ -1261,7 +1362,8 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1261
1362
  const existingByOriginal = await translationRegistry.get(
1262
1363
  text,
1263
1364
  targetLanguage,
1264
- tool
1365
+ tool,
1366
+ config2
1265
1367
  );
1266
1368
  if (existingByOriginal && existingByOriginal.isTranslated && existingByOriginal.translatedText && existingByOriginal.translatedText.trim() !== "") {
1267
1369
  return existingByOriginal.translatedText;
@@ -1378,15 +1480,15 @@ function AITranslationInnerProvider(props) {
1378
1480
  return () => unsubscribe();
1379
1481
  }, []);
1380
1482
  useEffect(() => {
1381
- if (isFetched && remoteConfig && remoteConfig[tool]) {
1382
- setConfig((prevConfig) => {
1383
- const clonedConfig = prevConfig.clone();
1384
- clonedConfig.setTargetLanguage(locale);
1385
- clonedConfig.setToolConfig(remoteConfig[tool]);
1386
- translator.current.updateConfig(clonedConfig);
1387
- return clonedConfig;
1388
- });
1389
- }
1483
+ if (!isFetched || !remoteConfig) return;
1484
+ const toolConfig = remoteConfig[tool] ?? remoteConfig;
1485
+ setConfig((prevConfig) => {
1486
+ const clonedConfig = prevConfig.clone();
1487
+ clonedConfig.setTargetLanguage(locale);
1488
+ clonedConfig.setToolConfig(toolConfig);
1489
+ translator.current.updateConfig(clonedConfig);
1490
+ return clonedConfig;
1491
+ });
1390
1492
  }, [tool, remoteConfig, isFetched, locale]);
1391
1493
  config2.setTargetLanguage(locale);
1392
1494
  config2.setToolName(tool);
@@ -1449,6 +1551,7 @@ var TranslatedIcon = ({
1449
1551
  className,
1450
1552
  xmlns: "http://www.w3.org/2000/svg",
1451
1553
  "aria-hidden": "true",
1554
+ style: { marginRight: "10px", flexShrink: 0 },
1452
1555
  children: /* @__PURE__ */ jsx2(
1453
1556
  "path",
1454
1557
  {
@@ -1525,8 +1628,8 @@ var AITranslateText = ({
1525
1628
  };
1526
1629
  }, [text, shouldTranslate, context, showHighlight, reset]);
1527
1630
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1528
- displayText,
1529
- showHighlightState && /* @__PURE__ */ jsx3(TranslatedIcon, { ...translatedIconProps })
1631
+ showHighlightState && /* @__PURE__ */ jsx3(TranslatedIcon, { ...translatedIconProps }),
1632
+ displayText
1530
1633
  ] });
1531
1634
  };
1532
1635
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@procore/ai-translations",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",