@procore/ai-translations 0.8.0 → 0.9.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.
@@ -627,6 +627,7 @@ var TRANSLATION_COMPLETE_EVENT_NAME = "ai-translation-completed";
627
627
  var RERENDER_EVENT_NAME = "ai-translations-component-rerender";
628
628
  var TRANSLATION_PROGRESS_EVENT_NAME = "ai-translations-progress";
629
629
  var MODEL_DOWNLOAD_PROGRESS_EVENT_NAME = "ai-translations-model-download-progress";
630
+ var MODEL_CONSENT_REQUIRED_EVENT_NAME = "ai-translations-model-consent-required";
630
631
 
631
632
  // src/utils/eventHandler.ts
632
633
  var EventHandler = class {
@@ -700,12 +701,14 @@ var EventHandler = class {
700
701
  * Not tool-scoped — a Chrome language model is shared across the entire
701
702
  * browser session, so all Provider instances receive it.
702
703
  */
703
- publishModelDownloadProgressEvent(loaded, total, progress) {
704
+ publishModelDownloadProgressEvent(loaded, total, progress, sourceLanguage, targetLanguage) {
704
705
  this.aiTranslationEvents.publish(MODEL_DOWNLOAD_PROGRESS_EVENT_NAME, {
705
706
  loaded,
706
707
  total,
707
708
  progress,
708
- isComplete: loaded === total
709
+ isComplete: loaded === total,
710
+ sourceLanguage,
711
+ targetLanguage
709
712
  });
710
713
  }
711
714
  subscribeToModelDownloadProgressEvent(callback) {
@@ -715,7 +718,29 @@ var EventHandler = class {
715
718
  loaded: detail.data?.loaded ?? 0,
716
719
  total: detail.data?.total ?? 0,
717
720
  progress: detail.data?.progress ?? 0,
718
- isComplete: detail.data?.isComplete ?? false
721
+ isComplete: detail.data?.isComplete ?? false,
722
+ sourceLanguage: detail.data?.sourceLanguage ?? "",
723
+ targetLanguage: detail.data?.targetLanguage ?? ""
724
+ })
725
+ );
726
+ }
727
+ /**
728
+ * Publishes a request for the user to consent to downloading a specific
729
+ * source -> target language model. Like the download-progress event this is
730
+ * NOT tool-scoped — a language model is shared across the whole session.
731
+ */
732
+ publishModelConsentRequiredEvent(sourceLanguage, targetLanguage) {
733
+ this.aiTranslationEvents.publish(MODEL_CONSENT_REQUIRED_EVENT_NAME, {
734
+ sourceLanguage,
735
+ targetLanguage
736
+ });
737
+ }
738
+ subscribeToModelConsentRequiredEvent(callback) {
739
+ return this.aiTranslationEvents.subscribe(
740
+ MODEL_CONSENT_REQUIRED_EVENT_NAME,
741
+ (detail) => callback({
742
+ sourceLanguage: detail.data?.sourceLanguage ?? "",
743
+ targetLanguage: detail.data?.targetLanguage ?? ""
719
744
  })
720
745
  );
721
746
  }
@@ -724,6 +749,111 @@ var EventHandler = class {
724
749
  }
725
750
  };
726
751
 
752
+ // src/utils/modelDownloadGate.ts
753
+ var ModelDownloadGateImpl = class _ModelDownloadGateImpl {
754
+ eventHandler = null;
755
+ ready = /* @__PURE__ */ new Set();
756
+ pending = /* @__PURE__ */ new Map();
757
+ resolvers = /* @__PURE__ */ new Map();
758
+ rejecters = /* @__PURE__ */ new Map();
759
+ getEventHandler() {
760
+ if (!this.eventHandler) {
761
+ this.eventHandler = new EventHandler("__chrome_model__");
762
+ }
763
+ return this.eventHandler;
764
+ }
765
+ static key(sourceLanguage, targetLanguage) {
766
+ return `${sourceLanguage}->${targetLanguage}`;
767
+ }
768
+ /**
769
+ * Resolves once the model for `sourceLanguage -> targetLanguage` is downloaded
770
+ * and ready. Pauses (stays pending) until the user confirms the download and
771
+ * it completes. Re-prompts are deduped per model.
772
+ */
773
+ ensureModelReady(sourceLanguage, targetLanguage) {
774
+ const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
775
+ if (this.ready.has(key)) {
776
+ return Promise.resolve();
777
+ }
778
+ const existing = this.pending.get(key);
779
+ if (existing) {
780
+ return existing;
781
+ }
782
+ const promise = new Promise((resolve, reject) => {
783
+ this.resolvers.set(key, resolve);
784
+ this.rejecters.set(key, reject);
785
+ });
786
+ this.pending.set(key, promise);
787
+ this.getEventHandler().publishModelConsentRequiredEvent(
788
+ sourceLanguage,
789
+ targetLanguage
790
+ );
791
+ return promise;
792
+ }
793
+ /**
794
+ * Marks a model as ready and resolves any waiter. Resolved exclusively when
795
+ * `downloadModel` resolves (wired in the Provider). This guarantees Chrome's
796
+ * `Translator.create()` has returned and the translator instance is cached in
797
+ * `chromeTranslatorRegistry`, so the resumed translation reuses it instead of
798
+ * calling `Translator.create()` again outside the user gesture (which throws
799
+ * `NotAllowedError`). It is deliberately NOT resolved on the intermediate
800
+ * `MODEL_DOWNLOAD_PROGRESS` `isComplete` event, which fires before
801
+ * `Translator.create()` returns.
802
+ */
803
+ markReady(sourceLanguage, targetLanguage) {
804
+ const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
805
+ this.ready.add(key);
806
+ const resolve = this.resolvers.get(key);
807
+ if (resolve) {
808
+ resolve();
809
+ }
810
+ this.clearPending(key);
811
+ }
812
+ /**
813
+ * Marks a model download as failed and rejects any waiter so the paused
814
+ * translation run can move past this text instead of hanging forever. Called
815
+ * when `downloadModel` rejects (e.g. Chrome returns 404 for a pair it
816
+ * reported as downloadable). The pair is NOT cached as ready, so it can be
817
+ * re-attempted later.
818
+ */
819
+ markFailed(sourceLanguage, targetLanguage, error) {
820
+ const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
821
+ const reject = this.rejecters.get(key);
822
+ if (reject) {
823
+ reject(
824
+ error instanceof Error ? error : new Error(`Model download failed for ${key}`)
825
+ );
826
+ }
827
+ this.clearPending(key);
828
+ }
829
+ isReady(sourceLanguage, targetLanguage) {
830
+ return this.ready.has(
831
+ _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage)
832
+ );
833
+ }
834
+ /**
835
+ * Clears all gate state. Intended for test teardown so cached "ready" models
836
+ * do not leak between cases.
837
+ */
838
+ reset() {
839
+ this.ready.clear();
840
+ this.pending.clear();
841
+ this.resolvers.clear();
842
+ this.rejecters.clear();
843
+ }
844
+ clearPending(key) {
845
+ this.resolvers.delete(key);
846
+ this.rejecters.delete(key);
847
+ this.pending.delete(key);
848
+ }
849
+ };
850
+ var modelDownloadGate = new ModelDownloadGateImpl();
851
+
852
+ // src/utils/language.ts
853
+ function normalizeLanguage(language) {
854
+ return language.split("-")[0] ?? language;
855
+ }
856
+
727
857
  // src/utils/renderVersionManager.ts
728
858
  var RenderVersionManager = class {
729
859
  version = 0;
@@ -780,27 +910,30 @@ var TranslationManager = class {
780
910
  this.translationProgress.total = queueManager.queueSize(
781
911
  this.currentTranslatorStrategy
782
912
  );
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();
913
+ try {
914
+ do {
915
+ for (const batch of queueManager.generateBatches(
916
+ this.translator.getConfig()
917
+ )) {
918
+ batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
919
+ const result = await this.translator.processTranslations(
920
+ batch.map(
921
+ (entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
922
+ )
923
+ );
924
+ this.setTranslationProgress(batch.length);
925
+ await this.updateDatabaseWithTranslations(result);
926
+ await this.notifyTranslationCompleted();
927
+ }
928
+ } while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
929
+ this.resetTranslationProgress();
930
+ this.publishTranslationProgress();
931
+ } finally {
932
+ if (this.currentTranslatorStrategy === "frontend_translations") {
933
+ globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
934
+ } else {
935
+ globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
796
936
  }
797
- } while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
798
- this.resetTranslationProgress();
799
- this.publishTranslationProgress();
800
- if (this.currentTranslatorStrategy === "frontend_translations") {
801
- globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
802
- } else {
803
- globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
804
937
  }
805
938
  }
806
939
  async updateDatabaseWithTranslations(translationResult) {
@@ -970,6 +1103,16 @@ var Client = class {
970
1103
  return this.createTranslationErrorResponse(requests, errorMessage);
971
1104
  }
972
1105
  }
1106
+ /**
1107
+ * No-op: backend translation does not manage on-device models, so there is
1108
+ * nothing to download client-side. The parameters are kept to match the
1109
+ * {@link TranslatorClientAdapter.downloadModel} signature and make the no-op
1110
+ * intent explicit to callers.
1111
+ */
1112
+ async downloadModel(sourceLanguage, targetLanguage) {
1113
+ void sourceLanguage;
1114
+ void targetLanguage;
1115
+ }
973
1116
  createTranslationErrorResponse(requests, errorMessage) {
974
1117
  return {
975
1118
  responseBody: requests.map((req) => ({
@@ -1089,6 +1232,7 @@ var ChromeTranslator = class _ChromeTranslator {
1089
1232
  }
1090
1233
  }
1091
1234
  static async init(sourceLanguage, targetLanguage) {
1235
+ this.ensureRegistry();
1092
1236
  if (globalThis.chromeTranslatorRegistry.has(
1093
1237
  this.generateKey(sourceLanguage, targetLanguage)
1094
1238
  )) {
@@ -1103,7 +1247,30 @@ var ChromeTranslator = class _ChromeTranslator {
1103
1247
  new _ChromeTranslator(translator)
1104
1248
  );
1105
1249
  }
1106
- static async createTranslator(source, target) {
1250
+ /**
1251
+ * Downloads the on-device model for a source -> target language pair. Must be
1252
+ * invoked from a user gesture (e.g. a click handler), since Chrome requires an
1253
+ * active gesture to start a model download.
1254
+ */
1255
+ static async downloadModel(sourceLanguage, targetLanguage) {
1256
+ await this.init(
1257
+ normalizeLanguage(sourceLanguage),
1258
+ normalizeLanguage(targetLanguage)
1259
+ );
1260
+ }
1261
+ /**
1262
+ * Creates a Chrome translator instance for the pair.
1263
+ *
1264
+ * @param publishProgress - When true, `downloadprogress` events are forwarded
1265
+ * as `MODEL_DOWNLOAD_PROGRESS` events (driving the download-progress popup).
1266
+ * This should only be true for an explicit, consent-driven download
1267
+ * (`init`/`downloadModel`). When creating an already-downloaded
1268
+ * (`available`) model in the translate loop, Chrome still emits
1269
+ * `downloadprogress` while loading the pack from disk into memory; forwarding
1270
+ * those would wrongly show a "downloading" popup for a model that is already
1271
+ * downloaded, so the translate-loop path passes `false`.
1272
+ */
1273
+ static async createTranslator(source, target, publishProgress = true) {
1107
1274
  const key = this.generateKey(source, target);
1108
1275
  let resolveReady;
1109
1276
  const readyPromise = new Promise((resolve) => {
@@ -1120,11 +1287,15 @@ var ChromeTranslator = class _ChromeTranslator {
1120
1287
  const loaded = e.loaded ?? 0;
1121
1288
  const total = e.total ?? 0;
1122
1289
  const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
1123
- getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1124
- loaded,
1125
- total,
1126
- progress
1127
- );
1290
+ if (publishProgress) {
1291
+ getModelDownloadEventHandler().publishModelDownloadProgressEvent(
1292
+ loaded,
1293
+ total,
1294
+ progress,
1295
+ source,
1296
+ target
1297
+ );
1298
+ }
1128
1299
  if (total > 0 && loaded >= total) {
1129
1300
  resolveReady();
1130
1301
  }
@@ -1141,26 +1312,27 @@ var ChromeTranslator = class _ChromeTranslator {
1141
1312
  }
1142
1313
  static async translate(text, targetLanguage) {
1143
1314
  this.ensureRegistry();
1315
+ const normalizedTarget = normalizeLanguage(targetLanguage);
1316
+ let sourceLanguage = normalizedTarget;
1144
1317
  try {
1145
1318
  const detector = await ChromeLanguageDetector.getInstance();
1146
- const sourceLanguage = await detector.detectLanguage(text);
1147
- const key = this.generateKey(sourceLanguage, targetLanguage);
1148
- if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
1319
+ sourceLanguage = normalizeLanguage(await detector.detectLanguage(text));
1320
+ if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(normalizedTarget)) {
1149
1321
  return {
1150
1322
  translation: text,
1151
1323
  sourceLanguage,
1152
1324
  success: false,
1153
1325
  retryable: false,
1154
- errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${targetLanguage}`
1326
+ errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${normalizedTarget}`
1155
1327
  };
1156
1328
  }
1157
- const existingInstance = globalThis.chromeTranslatorRegistry.get(key);
1158
- if (!existingInstance) {
1159
- const translatorCapabilities = await Translator.availability({
1329
+ const key = this.generateKey(sourceLanguage, normalizedTarget);
1330
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1331
+ const availability = await Translator.availability({
1160
1332
  sourceLanguage,
1161
- targetLanguage
1333
+ targetLanguage: normalizedTarget
1162
1334
  });
1163
- if (translatorCapabilities === "unavailable") {
1335
+ if (availability === "unavailable") {
1164
1336
  return {
1165
1337
  translation: text,
1166
1338
  sourceLanguage,
@@ -1169,47 +1341,94 @@ var ChromeTranslator = class _ChromeTranslator {
1169
1341
  errorMessage: "Translation not supported"
1170
1342
  };
1171
1343
  }
1172
- const chromeTranslator = await this.createTranslator(
1173
- sourceLanguage,
1174
- targetLanguage
1175
- );
1176
- if (!globalThis.chromeTranslatorRegistry.has(key)) {
1177
- globalThis.chromeTranslatorRegistry.set(
1178
- key,
1179
- new _ChromeTranslator(chromeTranslator)
1180
- );
1344
+ if (availability !== "available") {
1345
+ const isUserActivationActive = typeof navigator !== "undefined" && (navigator.userActivation?.isActive ?? false);
1346
+ if (isUserActivationActive) {
1347
+ await _ChromeTranslator.downloadModel(
1348
+ sourceLanguage,
1349
+ normalizedTarget
1350
+ );
1351
+ modelDownloadGate.markReady(sourceLanguage, normalizedTarget);
1352
+ } else {
1353
+ await modelDownloadGate.ensureModelReady(
1354
+ sourceLanguage,
1355
+ normalizedTarget
1356
+ );
1357
+ }
1181
1358
  }
1182
1359
  }
1183
- const instance = globalThis.chromeTranslatorRegistry.get(key);
1184
- await this.waitForReady(key);
1185
- this.translatorReadyPromises.delete(key);
1186
- const translation = await instance.translator?.translate(text);
1187
- if (!translation) {
1188
- return {
1189
- translation: text,
1190
- sourceLanguage,
1191
- success: false,
1192
- errorMessage: "Translation failed",
1193
- retryable: false
1194
- };
1195
- }
1196
- return {
1197
- translation,
1360
+ return await this.createInstanceAndTranslate(
1361
+ text,
1198
1362
  sourceLanguage,
1199
- success: true,
1200
- retryable: true
1201
- };
1363
+ normalizedTarget
1364
+ );
1202
1365
  } catch (error) {
1366
+ const isGestureRequired = error instanceof DOMException && error.name === "NotAllowedError";
1367
+ if (isGestureRequired) {
1368
+ try {
1369
+ await modelDownloadGate.ensureModelReady(
1370
+ sourceLanguage,
1371
+ normalizedTarget
1372
+ );
1373
+ return await this.createInstanceAndTranslate(
1374
+ text,
1375
+ sourceLanguage,
1376
+ normalizedTarget
1377
+ );
1378
+ } catch (retryError) {
1379
+ void retryError;
1380
+ }
1381
+ }
1203
1382
  const message = error instanceof Error ? error.message : "Unknown error";
1204
1383
  return {
1205
1384
  translation: text,
1206
- sourceLanguage: targetLanguage,
1385
+ sourceLanguage,
1207
1386
  success: false,
1208
1387
  errorMessage: message,
1209
- retryable: true
1388
+ retryable: false,
1389
+ requiresGesture: isGestureRequired
1210
1390
  };
1211
1391
  }
1212
1392
  }
1393
+ /**
1394
+ * Creates the translator instance for the pair if it is not already cached,
1395
+ * waits for the model to be ready, and translates the text. The model is
1396
+ * expected to already be downloaded (or downloadable without a gesture) by
1397
+ * the time this is called.
1398
+ */
1399
+ static async createInstanceAndTranslate(text, sourceLanguage, targetLanguage) {
1400
+ const key = this.generateKey(sourceLanguage, targetLanguage);
1401
+ if (!globalThis.chromeTranslatorRegistry.has(key)) {
1402
+ const chromeTranslator = await this.createTranslator(
1403
+ sourceLanguage,
1404
+ targetLanguage,
1405
+ false
1406
+ );
1407
+ globalThis.chromeTranslatorRegistry.set(
1408
+ key,
1409
+ new _ChromeTranslator(chromeTranslator)
1410
+ );
1411
+ }
1412
+ const instance = globalThis.chromeTranslatorRegistry.get(key);
1413
+ await this.waitForReady(key);
1414
+ this.translatorReadyPromises.delete(key);
1415
+ const translation = await instance.translator?.translate(text);
1416
+ if (!translation) {
1417
+ return {
1418
+ translation: text,
1419
+ sourceLanguage,
1420
+ success: false,
1421
+ errorMessage: "Translation failed",
1422
+ retryable: false
1423
+ };
1424
+ }
1425
+ return {
1426
+ translation,
1427
+ sourceLanguage,
1428
+ success: true,
1429
+ retryable: true
1430
+ };
1431
+ }
1213
1432
  static generateKey(sourceLanguage, targetLanguage) {
1214
1433
  return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
1215
1434
  }
@@ -1256,7 +1475,8 @@ var Client2 = class {
1256
1475
  translation: result.translation,
1257
1476
  sourceLanguage: result.sourceLanguage,
1258
1477
  isTranslated: result.success,
1259
- retryable: result.retryable
1478
+ retryable: result.retryable,
1479
+ requiresGesture: result.requiresGesture ?? false
1260
1480
  }
1261
1481
  ],
1262
1482
  targetLanguage: request.targetLanguage,
@@ -1271,6 +1491,9 @@ var Client2 = class {
1271
1491
  errorMessage
1272
1492
  };
1273
1493
  }
1494
+ async downloadModel(sourceLanguage, targetLanguage) {
1495
+ await ChromeTranslator.downloadModel(sourceLanguage, targetLanguage);
1496
+ }
1274
1497
  getConfig() {
1275
1498
  return this.config;
1276
1499
  }
@@ -1306,6 +1529,10 @@ var Client3 = class {
1306
1529
  const translator = this.getFrontendTranslator();
1307
1530
  return await translator.translateBatch(...args);
1308
1531
  }
1532
+ async downloadModel(sourceLanguage, targetLanguage) {
1533
+ const translator = this.getFrontendTranslator();
1534
+ await translator.downloadModel(sourceLanguage, targetLanguage);
1535
+ }
1309
1536
  };
1310
1537
 
1311
1538
  // src/translators/translator.ts
@@ -1320,6 +1547,17 @@ var Translator2 = class {
1320
1547
  const translatorClient = this.getTranslatorClient();
1321
1548
  return await translatorClient.translateBatch(translationRequests);
1322
1549
  }
1550
+ /**
1551
+ * Downloads the on-device model for a source -> target language pair via the
1552
+ * active translator client. Must be invoked from a user gesture for clients
1553
+ * (e.g. Chrome) that require one to start a model download.
1554
+ */
1555
+ async downloadModel(sourceLanguage, targetLanguage) {
1556
+ await this.getTranslatorClient().downloadModel(
1557
+ sourceLanguage,
1558
+ targetLanguage
1559
+ );
1560
+ }
1323
1561
  setTranslatorClient(config2) {
1324
1562
  if (config2.getToolConfig().strategy === "frontend_translations") {
1325
1563
  this.translatorClient = new Client3(config2);
@@ -1438,6 +1676,9 @@ function AITranslationInnerProvider(props) {
1438
1676
  } = props;
1439
1677
  const [translationProgress, setTranslationProgress] = useState(null);
1440
1678
  const [modelDownloadProgress, setModelDownloadProgress] = useState(null);
1679
+ const [modelConsent, setModelConsent] = useState(
1680
+ null
1681
+ );
1441
1682
  const [config2, setConfig] = useState(new Config());
1442
1683
  const translator = useRef(new Translator2(config2));
1443
1684
  const eventHandler = useRef(new EventHandler(tool));
@@ -1489,6 +1730,14 @@ function AITranslationInnerProvider(props) {
1489
1730
  );
1490
1731
  return () => unsubscribe();
1491
1732
  }, []);
1733
+ useEffect(() => {
1734
+ const unsubscribe = eventHandler.current.subscribeToModelConsentRequiredEvent(
1735
+ (request) => {
1736
+ setModelConsent(request);
1737
+ }
1738
+ );
1739
+ return () => unsubscribe();
1740
+ }, []);
1492
1741
  useEffect(() => {
1493
1742
  if (!isFetched || !remoteConfig) return;
1494
1743
  const toolConfig = remoteConfig[tool] ?? remoteConfig;
@@ -1505,9 +1754,23 @@ function AITranslationInnerProvider(props) {
1505
1754
  config2.setCompanyId(companyId);
1506
1755
  config2.setProjectId(projectId);
1507
1756
  const ait = useCallback(
1508
- async (text) => enableAIT ? await aitFunction(text, translationRegistry.current, config2, tool) : await Promise.resolve(text),
1757
+ async (text) => {
1758
+ if (!enableAIT) {
1759
+ return text;
1760
+ }
1761
+ return aitFunction(text, translationRegistry.current, config2, tool);
1762
+ },
1509
1763
  [tool, config2, enableAIT]
1510
1764
  );
1765
+ const confirmModelDownload = useCallback(() => {
1766
+ if (!modelConsent) return;
1767
+ if (config2.getToolConfig().strategy !== "frontend_translations") return;
1768
+ const { sourceLanguage, targetLanguage } = modelConsent;
1769
+ setModelConsent(null);
1770
+ translator.current.downloadModel(sourceLanguage, targetLanguage).then(() => modelDownloadGate.markReady(sourceLanguage, targetLanguage)).catch(
1771
+ (error) => modelDownloadGate.markFailed(sourceLanguage, targetLanguage, error)
1772
+ );
1773
+ }, [modelConsent, config2]);
1511
1774
  const contextValue = {
1512
1775
  ait,
1513
1776
  locale,
@@ -1516,6 +1779,8 @@ function AITranslationInnerProvider(props) {
1516
1779
  renderVersion: renderVersionManager.getVersion(),
1517
1780
  translationProgress,
1518
1781
  modelDownloadProgress,
1782
+ modelConsent,
1783
+ confirmModelDownload,
1519
1784
  tool,
1520
1785
  onTrackAnalyticsEvent,
1521
1786
  page,
@@ -1550,8 +1815,8 @@ var BUTTON_TYPE = {
1550
1815
  var ACTION = {
1551
1816
  CLICKED: "clicked"
1552
1817
  };
1553
- function buildObject(pageContext, buttonType) {
1554
- return `${pageContext}_${buttonType}_button`;
1818
+ function buildObject(buttonType) {
1819
+ return `${buttonType}_button`;
1555
1820
  }
1556
1821
  function buildEventKey(parts) {
1557
1822
  const { scope, tool, object, action } = parts;
@@ -1568,7 +1833,7 @@ function buildAnalyticEvent(params) {
1568
1833
  additionalProperties
1569
1834
  } = params;
1570
1835
  const resolvedAction = action ?? ACTION.CLICKED;
1571
- const object = buildObject(pageContext, buttonType);
1836
+ const object = buildObject(buttonType);
1572
1837
  const key = buildEventKey({
1573
1838
  scope,
1574
1839
  tool,
@@ -1577,6 +1842,7 @@ function buildAnalyticEvent(params) {
1577
1842
  });
1578
1843
  const properties = {
1579
1844
  ...baseProperties,
1845
+ [`${buttonType}_button_location`]: pageContext,
1580
1846
  ...additionalProperties
1581
1847
  };
1582
1848
  return { key, properties };
@@ -1862,7 +2128,7 @@ var CustomizableAITranslateText = ({
1862
2128
 
1863
2129
  // src/utils/featureFlag.ts
1864
2130
  import { isFederalZone, getProcoreZone } from "@procore/web-sdk-mfe-utils";
1865
- var AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
2131
+ var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
1866
2132
  var getAITranslationLDId = (domain) => {
1867
2133
  const { environment, zone } = getProcoreZone(domain);
1868
2134
  if (isFederalZone(zone)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@procore/ai-translations",
3
- "version": "0.8.0",
3
+ "version": "0.9.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",