@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.
@@ -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
  }
@@ -416,6 +419,19 @@ var _Storage = class _Storage {
416
419
  }
417
420
  }
418
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
+ // ------------------------------------
419
435
  // 🗑️ Clear All Translations
420
436
  // ------------------------------------
421
437
  static async clearAll() {
@@ -502,7 +518,9 @@ var _TranslationRegistry = class _TranslationRegistry {
502
518
  async removeTextsFromEnqueued(texts, targetLanguage, strategy) {
503
519
  for (const text of texts) {
504
520
  const id = await Storage.generateId(text, targetLanguage, this.tool);
505
- _TranslationRegistry.enqueuedItems.delete(`${id}:${strategy}`);
521
+ _TranslationRegistry.enqueuedItems.delete(
522
+ this.generateEnqueuedKey(id, strategy)
523
+ );
506
524
  }
507
525
  }
508
526
  static removeFromEnqueued(key) {
@@ -511,10 +529,19 @@ var _TranslationRegistry = class _TranslationRegistry {
511
529
  generateEnqueuedKey(id, strategy) {
512
530
  return `${id}:${strategy}`;
513
531
  }
514
- async get(text, targetLanguage, tool) {
532
+ async get(text, targetLanguage, tool, config2) {
515
533
  const key = await Hash.generateFromMultiple([text, targetLanguage, tool]);
516
- if (_TranslationRegistry.memoryCache.has(key)) {
517
- 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;
518
545
  }
519
546
  const translation = await Storage.getTranslation(
520
547
  text,
@@ -522,6 +549,10 @@ var _TranslationRegistry = class _TranslationRegistry {
522
549
  tool
523
550
  );
524
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
+ }
525
556
  return this.toTranslationRegistryEntry(translation);
526
557
  }
527
558
  return void 0;
@@ -794,19 +825,21 @@ var TranslationManager = class {
794
825
  this.translationProgress.total = queueManager.queueSize(
795
826
  this.currentTranslatorStrategy
796
827
  );
797
- for (const batch of queueManager.generateBatches(
798
- this.translator.getConfig()
799
- )) {
800
- batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
801
- const result = await this.translator.processTranslations(
802
- batch.map(
803
- (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
804
- )
805
- );
806
- this.setTranslationProgress(batch.length);
807
- await this.updateDatabaseWithTranslations(result);
808
- await this.notifyTranslationCompleted();
809
- }
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);
810
843
  this.resetTranslationProgress();
811
844
  this.publishTranslationProgress();
812
845
  if (this.currentTranslatorStrategy === "frontend_translations") {
@@ -960,8 +993,16 @@ var Client = class {
960
993
  method: "POST",
961
994
  body: JSON.stringify(requests)
962
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;
963
1004
  return {
964
- responseBody: data,
1005
+ responseBody,
965
1006
  success: true
966
1007
  };
967
1008
  } catch (error) {
@@ -1039,11 +1080,54 @@ var getModelDownloadEventHandler = () => {
1039
1080
  }
1040
1081
  return _modelDownloadEventHandler;
1041
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
+ ]);
1042
1123
  var _ChromeTranslator = class _ChromeTranslator {
1043
1124
  constructor(translator) {
1044
1125
  __publicField(this, "translator");
1045
1126
  this.translator = translator;
1046
1127
  }
1128
+ static isSupportedLanguage(lang) {
1129
+ return SUPPORTED_LANGUAGES.has(lang) || SUPPORTED_LANGUAGES.has(lang.split("-")[0] ?? "");
1130
+ }
1047
1131
  static ensureRegistry() {
1048
1132
  if (!globalThis.chromeTranslatorRegistry) {
1049
1133
  globalThis.chromeTranslatorRegistry = /* @__PURE__ */ new Map();
@@ -1065,30 +1149,31 @@ var _ChromeTranslator = class _ChromeTranslator {
1065
1149
  );
1066
1150
  }
1067
1151
  static async createTranslator(source, target) {
1152
+ const key = this.generateKey(source, target);
1068
1153
  let resolveReady;
1069
- this.translatorReadyPromise = new Promise((resolve) => {
1154
+ const readyPromise = new Promise((resolve) => {
1070
1155
  resolveReady = resolve;
1071
1156
  });
1157
+ this.translatorReadyPromises.set(key, readyPromise);
1072
1158
  let isDownloading = false;
1073
1159
  const translator = await Translator.create({
1074
1160
  sourceLanguage: source,
1075
1161
  targetLanguage: target,
1076
1162
  monitor(m) {
1077
- m.addEventListener(
1078
- "downloadprogress",
1079
- (e) => {
1080
- isDownloading = true;
1081
- const progress = e.total > 0 ? Math.round(e.loaded / e.total * 100) : 0;
1082
- getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1083
- e.loaded,
1084
- e.total,
1085
- progress
1086
- );
1087
- if (e.loaded === e.total) {
1088
- resolveReady();
1089
- }
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();
1090
1175
  }
1091
- );
1176
+ });
1092
1177
  }
1093
1178
  });
1094
1179
  if (!isDownloading) {
@@ -1096,8 +1181,8 @@ var _ChromeTranslator = class _ChromeTranslator {
1096
1181
  }
1097
1182
  return translator;
1098
1183
  }
1099
- static waitForReady() {
1100
- return this.translatorReadyPromise;
1184
+ static waitForReady(key) {
1185
+ return this.translatorReadyPromises.get(key) ?? Promise.resolve();
1101
1186
  }
1102
1187
  static async translate(text, targetLanguage) {
1103
1188
  var _a;
@@ -1106,8 +1191,17 @@ var _ChromeTranslator = class _ChromeTranslator {
1106
1191
  const detector = await ChromeLanguageDetector.getInstance();
1107
1192
  const sourceLanguage = await detector.detectLanguage(text);
1108
1193
  const key = this.generateKey(sourceLanguage, targetLanguage);
1109
- let instance = globalThis.chromeTranslatorRegistry.get(key);
1110
- 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) {
1111
1205
  const translatorCapabilities = await Translator.availability({
1112
1206
  sourceLanguage,
1113
1207
  targetLanguage
@@ -1125,10 +1219,16 @@ var _ChromeTranslator = class _ChromeTranslator {
1125
1219
  sourceLanguage,
1126
1220
  targetLanguage
1127
1221
  );
1128
- instance = new _ChromeTranslator(chromeTranslator);
1129
- globalThis.chromeTranslatorRegistry.set(key, instance);
1222
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1223
+ globalThis.chromeTranslatorRegistry.set(
1224
+ key,
1225
+ new _ChromeTranslator(chromeTranslator)
1226
+ );
1227
+ }
1130
1228
  }
1131
- await this.waitForReady();
1229
+ const instance = globalThis.chromeTranslatorRegistry.get(key);
1230
+ await this.waitForReady(key);
1231
+ this.translatorReadyPromises.delete(key);
1132
1232
  const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
1133
1233
  if (!translation) {
1134
1234
  return {
@@ -1136,7 +1236,7 @@ var _ChromeTranslator = class _ChromeTranslator {
1136
1236
  sourceLanguage,
1137
1237
  success: false,
1138
1238
  errorMessage: "Translation failed",
1139
- retryable: true
1239
+ retryable: false
1140
1240
  };
1141
1241
  }
1142
1242
  return {
@@ -1146,11 +1246,12 @@ var _ChromeTranslator = class _ChromeTranslator {
1146
1246
  retryable: true
1147
1247
  };
1148
1248
  } catch (error) {
1249
+ const message = error instanceof Error ? error.message : "Unknown error";
1149
1250
  return {
1150
1251
  translation: text,
1151
1252
  sourceLanguage: targetLanguage,
1152
1253
  success: false,
1153
- errorMessage: error instanceof Error ? error.message : "Unknown error",
1254
+ errorMessage: message,
1154
1255
  retryable: true
1155
1256
  };
1156
1257
  }
@@ -1159,7 +1260,7 @@ var _ChromeTranslator = class _ChromeTranslator {
1159
1260
  return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
1160
1261
  }
1161
1262
  };
1162
- __publicField(_ChromeTranslator, "translatorReadyPromise", Promise.resolve());
1263
+ __publicField(_ChromeTranslator, "translatorReadyPromises", /* @__PURE__ */ new Map());
1163
1264
  var ChromeTranslator = _ChromeTranslator;
1164
1265
 
1165
1266
  // src/translators/frontend_translators/chrome/client.ts
@@ -1309,7 +1410,8 @@ var aitFunction = async (text, translationRegistry, config2, tool) => {
1309
1410
  const existingByOriginal = await translationRegistry.get(
1310
1411
  text,
1311
1412
  targetLanguage,
1312
- tool
1413
+ tool,
1414
+ config2
1313
1415
  );
1314
1416
  if (existingByOriginal && existingByOriginal.isTranslated && existingByOriginal.translatedText && existingByOriginal.translatedText.trim() !== "") {
1315
1417
  return existingByOriginal.translatedText;
@@ -1426,15 +1528,15 @@ function AITranslationInnerProvider(props) {
1426
1528
  return () => unsubscribe();
1427
1529
  }, []);
1428
1530
  (0, import_react.useEffect)(() => {
1429
- if (isFetched && remoteConfig && remoteConfig[tool]) {
1430
- setConfig((prevConfig) => {
1431
- const clonedConfig = prevConfig.clone();
1432
- clonedConfig.setTargetLanguage(locale);
1433
- clonedConfig.setToolConfig(remoteConfig[tool]);
1434
- translator.current.updateConfig(clonedConfig);
1435
- return clonedConfig;
1436
- });
1437
- }
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
+ });
1438
1540
  }, [tool, remoteConfig, isFetched, locale]);
1439
1541
  config2.setTargetLanguage(locale);
1440
1542
  config2.setToolName(tool);
@@ -1491,6 +1593,7 @@ var TranslatedIcon = ({
1491
1593
  className,
1492
1594
  xmlns: "http://www.w3.org/2000/svg",
1493
1595
  "aria-hidden": "true",
1596
+ style: { marginRight: "10px", flexShrink: 0 },
1494
1597
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1495
1598
  "path",
1496
1599
  {
@@ -1567,8 +1670,8 @@ var AITranslateText = ({
1567
1670
  };
1568
1671
  }, [text, shouldTranslate, context, showHighlight, reset]);
1569
1672
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1570
- displayText,
1571
- showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps })
1673
+ showHighlightState && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TranslatedIcon, { ...translatedIconProps }),
1674
+ displayText
1572
1675
  ] });
1573
1676
  };
1574
1677