@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.
@@ -77,6 +77,9 @@ var Config = class _Config {
77
77
  setToolName(name) {
78
78
  this.toolName = name;
79
79
  }
80
+ isBackendTranslationStrategy() {
81
+ return this.ToolConfig.strategy === "backend_translations";
82
+ }
80
83
  getToolName() {
81
84
  return this.toolName;
82
85
  }
@@ -414,6 +417,19 @@ var Storage = class _Storage {
414
417
  }
415
418
  }
416
419
  // ------------------------------------
420
+ // 🗑️ Clear Translation by ID
421
+ // ------------------------------------
422
+ static async deleteById(id) {
423
+ try {
424
+ const db = await _Storage.getDB();
425
+ await db.delete(_Storage.STORE_NAME, id);
426
+ return true;
427
+ } catch (error) {
428
+ console.error("[Storage] Failed to delete by id:", error);
429
+ return false;
430
+ }
431
+ }
432
+ // ------------------------------------
417
433
  // 🗑️ Clear All Translations
418
434
  // ------------------------------------
419
435
  static async clearAll() {
@@ -508,7 +524,9 @@ var TranslationRegistry = class _TranslationRegistry {
508
524
  async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
509
525
  for (const text of texts) {
510
526
  const id = await Storage.generateId(text, targetLanguage, this.tool);
511
- _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
527
+ _TranslationRegistry.enqueuedItems.delete(
528
+ this.generateEnqueuedKey(id, strategy)
529
+ );
512
530
  }
513
531
  }
514
532
  static removeFromEnqueued(key) {
@@ -517,10 +535,19 @@ var TranslationRegistry = class _TranslationRegistry {
517
535
  generateEnqueuedKey(id, strategy) {
518
536
  return `${id}:${strategy}`;
519
537
  }
520
- async get(text, targetLanguage, tool) {
538
+ async get(text, targetLanguage, tool, config2) {
521
539
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
522
- if (_TranslationRegistry.memoryCache.has(key)) {
523
- return _TranslationRegistry.memoryCache.get(key);
540
+ const cached = _TranslationRegistry.memoryCache.get(key);
541
+ if (cached) {
542
+ if (cached.translationStrategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
543
+ _TranslationRegistry.memoryCache.delete(key);
544
+ if (cached.translatedText) {
545
+ _TranslationRegistry.originalTextIndex.delete(cached.translatedText);
546
+ }
547
+ await Storage.deleteById(key);
548
+ return void 0;
549
+ }
550
+ return cached;
524
551
  }
525
552
  const translation = await Storage.getTranslation(
526
553
  text,
@@ -528,6 +555,10 @@ var TranslationRegistry = class _TranslationRegistry {
528
555
  tool
529
556
  );
530
557
  if (translation !== void 0) {
558
+ if (translation.translation_strategy === "frontend_translations" && config2.isBackendTranslationStrategy()) {
559
+ await Storage.deleteById(translation.id);
560
+ return void 0;
561
+ }
531
562
  return this.toTranslationRegistryEntry(translation);
532
563
  }
533
564
  return void 0;
@@ -774,19 +805,21 @@ var TranslationManager = class {
774
805
  this.translationProgress.total = queueManager.queueSize(
775
806
  this.currentTranslatorStrategy
776
807
  );
777
- for (const batch of queueManager.generateBatches(
778
- this.translator.getConfig()
779
- )) {
780
- batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
781
- const result = await this.translator.processTranslations(
782
- batch.map(
783
- (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
784
- )
785
- );
786
- this.setTranslationProgress(batch.length);
787
- await this.updateDatabaseWithTranslations(result);
788
- await this.notifyTranslationCompleted();
789
- }
808
+ do {
809
+ for (const batch of queueManager.generateBatches(
810
+ this.translator.getConfig()
811
+ )) {
812
+ batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
813
+ const result = await this.translator.processTranslations(
814
+ batch.map(
815
+ (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
816
+ )
817
+ );
818
+ this.setTranslationProgress(batch.length);
819
+ await this.updateDatabaseWithTranslations(result);
820
+ await this.notifyTranslationCompleted();
821
+ }
822
+ } while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
790
823
  this.resetTranslationProgress();
791
824
  this.publishTranslationProgress();
792
825
  if (this.currentTranslatorStrategy === "frontend_translations") {
@@ -940,8 +973,16 @@ var Client = class {
940
973
  method: "POST",
941
974
  body: JSON.stringify(requests)
942
975
  });
976
+ const responseBody = Array.isArray(data) ? data.map((group) => ({
977
+ ...group,
978
+ translations: Array.isArray(group.translations) ? group.translations.map((t) => ({
979
+ ...t,
980
+ isTranslated: t.isTranslated ?? (!!t.translation && t.translation !== ""),
981
+ retryable: t.retryable ?? true
982
+ })) : []
983
+ })) : data;
943
984
  return {
944
- responseBody: data,
985
+ responseBody,
945
986
  success: true
946
987
  };
947
988
  } catch (error) {
@@ -1018,12 +1059,55 @@ var getModelDownloadEventHandler = () => {
1018
1059
  }
1019
1060
  return _modelDownloadEventHandler;
1020
1061
  };
1062
+ var SUPPORTED_LANGUAGES = /* @__PURE__ */ new Set([
1063
+ "ar",
1064
+ "bn",
1065
+ "bg",
1066
+ "zh",
1067
+ "hr",
1068
+ "cs",
1069
+ "da",
1070
+ "nl",
1071
+ "en",
1072
+ "fi",
1073
+ "fr",
1074
+ "de",
1075
+ "el",
1076
+ "he",
1077
+ "hi",
1078
+ "hu",
1079
+ "id",
1080
+ "it",
1081
+ "ja",
1082
+ "kn",
1083
+ "ko",
1084
+ "lt",
1085
+ "mr",
1086
+ "no",
1087
+ "pl",
1088
+ "pt",
1089
+ "ro",
1090
+ "ru",
1091
+ "sk",
1092
+ "sl",
1093
+ "es",
1094
+ "sv",
1095
+ "ta",
1096
+ "te",
1097
+ "th",
1098
+ "tr",
1099
+ "uk",
1100
+ "vi"
1101
+ ]);
1021
1102
  var ChromeTranslator = class _ChromeTranslator {
1022
1103
  translator;
1023
1104
  constructor(translator) {
1024
1105
  this.translator = translator;
1025
1106
  }
1026
- static translatorReadyPromise = Promise.resolve();
1107
+ static translatorReadyPromises = /* @__PURE__ */ new Map();
1108
+ static isSupportedLanguage(lang) {
1109
+ return SUPPORTED_LANGUAGES.has(lang) || SUPPORTED_LANGUAGES.has(lang.split("-")[0] ?? "");
1110
+ }
1027
1111
  static ensureRegistry() {
1028
1112
  if (!globalThis.chromeTranslatorRegistry) {
1029
1113
  globalThis.chromeTranslatorRegistry = /* @__PURE__ */ new Map();
@@ -1045,30 +1129,31 @@ var ChromeTranslator = class _ChromeTranslator {
1045
1129
  );
1046
1130
  }
1047
1131
  static async createTranslator(source, target) {
1132
+ const key = this.generateKey(source, target);
1048
1133
  let resolveReady;
1049
- this.translatorReadyPromise = new Promise((resolve) => {
1134
+ const readyPromise = new Promise((resolve) => {
1050
1135
  resolveReady = resolve;
1051
1136
  });
1137
+ this.translatorReadyPromises.set(key, readyPromise);
1052
1138
  let isDownloading = false;
1053
1139
  const translator = await Translator.create({
1054
1140
  sourceLanguage: source,
1055
1141
  targetLanguage: target,
1056
1142
  monitor(m) {
1057
- m.addEventListener(
1058
- "downloadprogress",
1059
- (e) => {
1060
- isDownloading = true;
1061
- const progress = e.total > 0 ? Math.round(e.loaded / e.total * 100) : 0;
1062
- getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1063
- e.loaded,
1064
- e.total,
1065
- progress
1066
- );
1067
- if (e.loaded === e.total) {
1068
- resolveReady();
1069
- }
1143
+ m.addEventListener("downloadprogress", (e) => {
1144
+ isDownloading = true;
1145
+ const loaded = e.loaded ?? 0;
1146
+ const total = e.total ?? 0;
1147
+ const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
1148
+ getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1149
+ loaded,
1150
+ total,
1151
+ progress
1152
+ );
1153
+ if (total > 0 && loaded >= total) {
1154
+ resolveReady();
1070
1155
  }
1071
- );
1156
+ });
1072
1157
  }
1073
1158
  });
1074
1159
  if (!isDownloading) {
@@ -1076,8 +1161,8 @@ var ChromeTranslator = class _ChromeTranslator {
1076
1161
  }
1077
1162
  return translator;
1078
1163
  }
1079
- static waitForReady() {
1080
- return this.translatorReadyPromise;
1164
+ static waitForReady(key) {
1165
+ return this.translatorReadyPromises.get(key) ?? Promise.resolve();
1081
1166
  }
1082
1167
  static async translate(text, targetLanguage) {
1083
1168
  this.ensureRegistry();
@@ -1085,8 +1170,17 @@ var ChromeTranslator = class _ChromeTranslator {
1085
1170
  const detector = await ChromeLanguageDetector.getInstance();
1086
1171
  const sourceLanguage = await detector.detectLanguage(text);
1087
1172
  const key = this.generateKey(sourceLanguage, targetLanguage);
1088
- let instance = globalThis.chromeTranslatorRegistry.get(key);
1089
- if (!instance) {
1173
+ if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
1174
+ return {
1175
+ translation: text,
1176
+ sourceLanguage,
1177
+ success: false,
1178
+ retryable: false,
1179
+ errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${targetLanguage}`
1180
+ };
1181
+ }
1182
+ const existingInstance = globalThis.chromeTranslatorRegistry.get(key);
1183
+ if (!existingInstance) {
1090
1184
  const translatorCapabilities = await Translator.availability({
1091
1185
  sourceLanguage,
1092
1186
  targetLanguage
@@ -1104,10 +1198,16 @@ var ChromeTranslator = class _ChromeTranslator {
1104
1198
  sourceLanguage,
1105
1199
  targetLanguage
1106
1200
  );
1107
- instance = new _ChromeTranslator(chromeTranslator);
1108
- globalThis.chromeTranslatorRegistry.set(key, instance);
1201
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1202
+ globalThis.chromeTranslatorRegistry.set(
1203
+ key,
1204
+ new _ChromeTranslator(chromeTranslator)
1205
+ );
1206
+ }
1109
1207
  }
1110
- await this.waitForReady();
1208
+ const instance = globalThis.chromeTranslatorRegistry.get(key);
1209
+ await this.waitForReady(key);
1210
+ this.translatorReadyPromises.delete(key);
1111
1211
  const translation = await instance.translator?.translate(text);
1112
1212
  if (!translation) {
1113
1213
  return {
@@ -1115,7 +1215,7 @@ var ChromeTranslator = class _ChromeTranslator {
1115
1215
  sourceLanguage,
1116
1216
  success: false,
1117
1217
  errorMessage: "Translation failed",
1118
- retryable: true
1218
+ retryable: false
1119
1219
  };
1120
1220
  }
1121
1221
  return {
@@ -1125,11 +1225,12 @@ var ChromeTranslator = class _ChromeTranslator {
1125
1225
  retryable: true
1126
1226
  };
1127
1227
  } catch (error) {
1228
+ const message = error instanceof Error ? error.message : "Unknown error";
1128
1229
  return {
1129
1230
  translation: text,
1130
1231
  sourceLanguage: targetLanguage,
1131
1232
  success: false,
1132
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1233
+ errorMessage: message,
1133
1234
  retryable: true
1134
1235
  };
1135
1236
  }
@@ -1286,7 +1387,8 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1286
1387
  const existingByOriginal = await translationRegistry.get(
1287
1388
  text,
1288
1389
  targetLanguage,
1289
- tool
1390
+ tool,
1391
+ config2
1290
1392
  );
1291
1393
  if (existingByOriginal && existingByOriginal.isTranslated && existingByOriginal.translatedText && existingByOriginal.translatedText.trim() !== "") {
1292
1394
  return existingByOriginal.translatedText;
@@ -1403,15 +1505,15 @@ function AITranslationInnerProvider(props) {
1403
1505
  return () => unsubscribe();
1404
1506
  }, []);
1405
1507
  (0, import_react.useEffect)(() => {
1406
- if (isFetched && remoteConfig && remoteConfig[tool]) {
1407
- setConfig((prevConfig) => {
1408
- const clonedConfig = prevConfig.clone();
1409
- clonedConfig.setTargetLanguage(locale);
1410
- clonedConfig.setToolConfig(remoteConfig[tool]);
1411
- translator.current.updateConfig(clonedConfig);
1412
- return clonedConfig;
1413
- });
1414
- }
1508
+ if (!isFetched || !remoteConfig) return;
1509
+ const toolConfig = remoteConfig[tool] ?? remoteConfig;
1510
+ setConfig((prevConfig) => {
1511
+ const clonedConfig = prevConfig.clone();
1512
+ clonedConfig.setTargetLanguage(locale);
1513
+ clonedConfig.setToolConfig(toolConfig);
1514
+ translator.current.updateConfig(clonedConfig);
1515
+ return clonedConfig;
1516
+ });
1415
1517
  }, [tool, remoteConfig, isFetched, locale]);
1416
1518
  config2.setTargetLanguage(locale);
1417
1519
  config2.setToolName(tool);
@@ -1468,6 +1570,7 @@ var TranslatedIcon = ({
1468
1570
  className,
1469
1571
  xmlns: "http://www.w3.org/2000/svg",
1470
1572
  "aria-hidden": "true",
1573
+ style: { marginRight: "10px", flexShrink: 0 },
1471
1574
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1472
1575
  "path",
1473
1576
  {
@@ -1544,8 +1647,8 @@ var AITranslateText = ({
1544
1647
  };
1545
1648
  }, [text, shouldTranslate, context, showHighlight, reset]);
1546
1649
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1547
- displayText,
1548
- showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps })
1650
+ showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps }),
1651
+ displayText
1549
1652
  ] });
1550
1653
  };
1551
1654