@procore/ai-translations 0.8.1 → 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,93 @@ 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
- const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
1205
1383
  return {
1206
1384
  translation: text,
1207
- sourceLanguage: targetLanguage,
1385
+ sourceLanguage,
1208
1386
  success: false,
1209
1387
  errorMessage: message,
1210
- retryable: !isPermanentFailure
1388
+ retryable: false,
1389
+ requiresGesture: isGestureRequired
1390
+ };
1391
+ }
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
1211
1423
  };
1212
1424
  }
1425
+ return {
1426
+ translation,
1427
+ sourceLanguage,
1428
+ success: true,
1429
+ retryable: true
1430
+ };
1213
1431
  }
1214
1432
  static generateKey(sourceLanguage, targetLanguage) {
1215
1433
  return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
@@ -1257,7 +1475,8 @@ var Client2 = class {
1257
1475
  translation: result.translation,
1258
1476
  sourceLanguage: result.sourceLanguage,
1259
1477
  isTranslated: result.success,
1260
- retryable: result.retryable
1478
+ retryable: result.retryable,
1479
+ requiresGesture: result.requiresGesture ?? false
1261
1480
  }
1262
1481
  ],
1263
1482
  targetLanguage: request.targetLanguage,
@@ -1272,6 +1491,9 @@ var Client2 = class {
1272
1491
  errorMessage
1273
1492
  };
1274
1493
  }
1494
+ async downloadModel(sourceLanguage, targetLanguage) {
1495
+ await ChromeTranslator.downloadModel(sourceLanguage, targetLanguage);
1496
+ }
1275
1497
  getConfig() {
1276
1498
  return this.config;
1277
1499
  }
@@ -1307,6 +1529,10 @@ var Client3 = class {
1307
1529
  const translator = this.getFrontendTranslator();
1308
1530
  return await translator.translateBatch(...args);
1309
1531
  }
1532
+ async downloadModel(sourceLanguage, targetLanguage) {
1533
+ const translator = this.getFrontendTranslator();
1534
+ await translator.downloadModel(sourceLanguage, targetLanguage);
1535
+ }
1310
1536
  };
1311
1537
 
1312
1538
  // src/translators/translator.ts
@@ -1321,6 +1547,17 @@ var Translator2 = class {
1321
1547
  const translatorClient = this.getTranslatorClient();
1322
1548
  return await translatorClient.translateBatch(translationRequests);
1323
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
+ }
1324
1561
  setTranslatorClient(config2) {
1325
1562
  if (config2.getToolConfig().strategy === "frontend_translations") {
1326
1563
  this.translatorClient = new Client3(config2);
@@ -1439,6 +1676,9 @@ function AITranslationInnerProvider(props) {
1439
1676
  } = props;
1440
1677
  const [translationProgress, setTranslationProgress] = useState(null);
1441
1678
  const [modelDownloadProgress, setModelDownloadProgress] = useState(null);
1679
+ const [modelConsent, setModelConsent] = useState(
1680
+ null
1681
+ );
1442
1682
  const [config2, setConfig] = useState(new Config());
1443
1683
  const translator = useRef(new Translator2(config2));
1444
1684
  const eventHandler = useRef(new EventHandler(tool));
@@ -1490,6 +1730,14 @@ function AITranslationInnerProvider(props) {
1490
1730
  );
1491
1731
  return () => unsubscribe();
1492
1732
  }, []);
1733
+ useEffect(() => {
1734
+ const unsubscribe = eventHandler.current.subscribeToModelConsentRequiredEvent(
1735
+ (request) => {
1736
+ setModelConsent(request);
1737
+ }
1738
+ );
1739
+ return () => unsubscribe();
1740
+ }, []);
1493
1741
  useEffect(() => {
1494
1742
  if (!isFetched || !remoteConfig) return;
1495
1743
  const toolConfig = remoteConfig[tool] ?? remoteConfig;
@@ -1506,9 +1754,23 @@ function AITranslationInnerProvider(props) {
1506
1754
  config2.setCompanyId(companyId);
1507
1755
  config2.setProjectId(projectId);
1508
1756
  const ait = useCallback(
1509
- 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
+ },
1510
1763
  [tool, config2, enableAIT]
1511
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]);
1512
1774
  const contextValue = {
1513
1775
  ait,
1514
1776
  locale,
@@ -1517,6 +1779,8 @@ function AITranslationInnerProvider(props) {
1517
1779
  renderVersion: renderVersionManager.getVersion(),
1518
1780
  translationProgress,
1519
1781
  modelDownloadProgress,
1782
+ modelConsent,
1783
+ confirmModelDownload,
1520
1784
  tool,
1521
1785
  onTrackAnalyticsEvent,
1522
1786
  page,
@@ -1551,8 +1815,8 @@ var BUTTON_TYPE = {
1551
1815
  var ACTION = {
1552
1816
  CLICKED: "clicked"
1553
1817
  };
1554
- function buildObject(pageContext, buttonType) {
1555
- return `${pageContext}_${buttonType}_button`;
1818
+ function buildObject(buttonType) {
1819
+ return `${buttonType}_button`;
1556
1820
  }
1557
1821
  function buildEventKey(parts) {
1558
1822
  const { scope, tool, object, action } = parts;
@@ -1569,7 +1833,7 @@ function buildAnalyticEvent(params) {
1569
1833
  additionalProperties
1570
1834
  } = params;
1571
1835
  const resolvedAction = action ?? ACTION.CLICKED;
1572
- const object = buildObject(pageContext, buttonType);
1836
+ const object = buildObject(buttonType);
1573
1837
  const key = buildEventKey({
1574
1838
  scope,
1575
1839
  tool,
@@ -1578,6 +1842,7 @@ function buildAnalyticEvent(params) {
1578
1842
  });
1579
1843
  const properties = {
1580
1844
  ...baseProperties,
1845
+ [`${buttonType}_button_location`]: pageContext,
1581
1846
  ...additionalProperties
1582
1847
  };
1583
1848
  return { key, properties };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@procore/ai-translations",
3
- "version": "0.8.1",
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",