@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.
- package/README.md +39 -5
- package/dist/legacy/index.d.mts +27 -2
- package/dist/legacy/index.d.ts +27 -2
- package/dist/legacy/index.js +345 -74
- package/dist/legacy/index.mjs +345 -74
- package/dist/modern/index.d.mts +27 -2
- package/dist/modern/index.d.ts +27 -2
- package/dist/modern/index.js +338 -73
- package/dist/modern/index.mjs +338 -73
- package/package.json +1 -1
package/dist/legacy/index.js
CHANGED
|
@@ -678,6 +678,7 @@ var TRANSLATION_COMPLETE_EVENT_NAME = "ai-translation-completed";
|
|
|
678
678
|
var RERENDER_EVENT_NAME = "ai-translations-component-rerender";
|
|
679
679
|
var TRANSLATION_PROGRESS_EVENT_NAME = "ai-translations-progress";
|
|
680
680
|
var MODEL_DOWNLOAD_PROGRESS_EVENT_NAME = "ai-translations-model-download-progress";
|
|
681
|
+
var MODEL_CONSENT_REQUIRED_EVENT_NAME = "ai-translations-model-consent-required";
|
|
681
682
|
|
|
682
683
|
// src/utils/eventHandler.ts
|
|
683
684
|
var EventHandler = class {
|
|
@@ -758,24 +759,51 @@ var EventHandler = class {
|
|
|
758
759
|
* Not tool-scoped — a Chrome language model is shared across the entire
|
|
759
760
|
* browser session, so all Provider instances receive it.
|
|
760
761
|
*/
|
|
761
|
-
publishModelDownloadProgressEvent(loaded, total, progress) {
|
|
762
|
+
publishModelDownloadProgressEvent(loaded, total, progress, sourceLanguage, targetLanguage) {
|
|
762
763
|
this.aiTranslationEvents.publish(MODEL_DOWNLOAD_PROGRESS_EVENT_NAME, {
|
|
763
764
|
loaded,
|
|
764
765
|
total,
|
|
765
766
|
progress,
|
|
766
|
-
isComplete: loaded === total
|
|
767
|
+
isComplete: loaded === total,
|
|
768
|
+
sourceLanguage,
|
|
769
|
+
targetLanguage
|
|
767
770
|
});
|
|
768
771
|
}
|
|
769
772
|
subscribeToModelDownloadProgressEvent(callback) {
|
|
770
773
|
return this.aiTranslationEvents.subscribe(
|
|
771
774
|
MODEL_DOWNLOAD_PROGRESS_EVENT_NAME,
|
|
772
775
|
(detail) => {
|
|
773
|
-
var _a, _b, _c, _d;
|
|
776
|
+
var _a, _b, _c, _d, _e, _f;
|
|
774
777
|
return callback({
|
|
775
778
|
loaded: ((_a = detail.data) == null ? void 0 : _a.loaded) ?? 0,
|
|
776
779
|
total: ((_b = detail.data) == null ? void 0 : _b.total) ?? 0,
|
|
777
780
|
progress: ((_c = detail.data) == null ? void 0 : _c.progress) ?? 0,
|
|
778
|
-
isComplete: ((_d = detail.data) == null ? void 0 : _d.isComplete) ?? false
|
|
781
|
+
isComplete: ((_d = detail.data) == null ? void 0 : _d.isComplete) ?? false,
|
|
782
|
+
sourceLanguage: ((_e = detail.data) == null ? void 0 : _e.sourceLanguage) ?? "",
|
|
783
|
+
targetLanguage: ((_f = detail.data) == null ? void 0 : _f.targetLanguage) ?? ""
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Publishes a request for the user to consent to downloading a specific
|
|
790
|
+
* source -> target language model. Like the download-progress event this is
|
|
791
|
+
* NOT tool-scoped — a language model is shared across the whole session.
|
|
792
|
+
*/
|
|
793
|
+
publishModelConsentRequiredEvent(sourceLanguage, targetLanguage) {
|
|
794
|
+
this.aiTranslationEvents.publish(MODEL_CONSENT_REQUIRED_EVENT_NAME, {
|
|
795
|
+
sourceLanguage,
|
|
796
|
+
targetLanguage
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
subscribeToModelConsentRequiredEvent(callback) {
|
|
800
|
+
return this.aiTranslationEvents.subscribe(
|
|
801
|
+
MODEL_CONSENT_REQUIRED_EVENT_NAME,
|
|
802
|
+
(detail) => {
|
|
803
|
+
var _a, _b;
|
|
804
|
+
return callback({
|
|
805
|
+
sourceLanguage: ((_a = detail.data) == null ? void 0 : _a.sourceLanguage) ?? "",
|
|
806
|
+
targetLanguage: ((_b = detail.data) == null ? void 0 : _b.targetLanguage) ?? ""
|
|
779
807
|
});
|
|
780
808
|
}
|
|
781
809
|
);
|
|
@@ -785,6 +813,113 @@ var EventHandler = class {
|
|
|
785
813
|
}
|
|
786
814
|
};
|
|
787
815
|
|
|
816
|
+
// src/utils/modelDownloadGate.ts
|
|
817
|
+
var ModelDownloadGateImpl = class _ModelDownloadGateImpl {
|
|
818
|
+
constructor() {
|
|
819
|
+
__publicField(this, "eventHandler", null);
|
|
820
|
+
__publicField(this, "ready", /* @__PURE__ */ new Set());
|
|
821
|
+
__publicField(this, "pending", /* @__PURE__ */ new Map());
|
|
822
|
+
__publicField(this, "resolvers", /* @__PURE__ */ new Map());
|
|
823
|
+
__publicField(this, "rejecters", /* @__PURE__ */ new Map());
|
|
824
|
+
}
|
|
825
|
+
getEventHandler() {
|
|
826
|
+
if (!this.eventHandler) {
|
|
827
|
+
this.eventHandler = new EventHandler("__chrome_model__");
|
|
828
|
+
}
|
|
829
|
+
return this.eventHandler;
|
|
830
|
+
}
|
|
831
|
+
static key(sourceLanguage, targetLanguage) {
|
|
832
|
+
return `${sourceLanguage}->${targetLanguage}`;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Resolves once the model for `sourceLanguage -> targetLanguage` is downloaded
|
|
836
|
+
* and ready. Pauses (stays pending) until the user confirms the download and
|
|
837
|
+
* it completes. Re-prompts are deduped per model.
|
|
838
|
+
*/
|
|
839
|
+
ensureModelReady(sourceLanguage, targetLanguage) {
|
|
840
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
841
|
+
if (this.ready.has(key)) {
|
|
842
|
+
return Promise.resolve();
|
|
843
|
+
}
|
|
844
|
+
const existing = this.pending.get(key);
|
|
845
|
+
if (existing) {
|
|
846
|
+
return existing;
|
|
847
|
+
}
|
|
848
|
+
const promise = new Promise((resolve, reject) => {
|
|
849
|
+
this.resolvers.set(key, resolve);
|
|
850
|
+
this.rejecters.set(key, reject);
|
|
851
|
+
});
|
|
852
|
+
this.pending.set(key, promise);
|
|
853
|
+
this.getEventHandler().publishModelConsentRequiredEvent(
|
|
854
|
+
sourceLanguage,
|
|
855
|
+
targetLanguage
|
|
856
|
+
);
|
|
857
|
+
return promise;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Marks a model as ready and resolves any waiter. Resolved exclusively when
|
|
861
|
+
* `downloadModel` resolves (wired in the Provider). This guarantees Chrome's
|
|
862
|
+
* `Translator.create()` has returned and the translator instance is cached in
|
|
863
|
+
* `chromeTranslatorRegistry`, so the resumed translation reuses it instead of
|
|
864
|
+
* calling `Translator.create()` again outside the user gesture (which throws
|
|
865
|
+
* `NotAllowedError`). It is deliberately NOT resolved on the intermediate
|
|
866
|
+
* `MODEL_DOWNLOAD_PROGRESS` `isComplete` event, which fires before
|
|
867
|
+
* `Translator.create()` returns.
|
|
868
|
+
*/
|
|
869
|
+
markReady(sourceLanguage, targetLanguage) {
|
|
870
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
871
|
+
this.ready.add(key);
|
|
872
|
+
const resolve = this.resolvers.get(key);
|
|
873
|
+
if (resolve) {
|
|
874
|
+
resolve();
|
|
875
|
+
}
|
|
876
|
+
this.clearPending(key);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Marks a model download as failed and rejects any waiter so the paused
|
|
880
|
+
* translation run can move past this text instead of hanging forever. Called
|
|
881
|
+
* when `downloadModel` rejects (e.g. Chrome returns 404 for a pair it
|
|
882
|
+
* reported as downloadable). The pair is NOT cached as ready, so it can be
|
|
883
|
+
* re-attempted later.
|
|
884
|
+
*/
|
|
885
|
+
markFailed(sourceLanguage, targetLanguage, error) {
|
|
886
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
887
|
+
const reject = this.rejecters.get(key);
|
|
888
|
+
if (reject) {
|
|
889
|
+
reject(
|
|
890
|
+
error instanceof Error ? error : new Error(`Model download failed for ${key}`)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
this.clearPending(key);
|
|
894
|
+
}
|
|
895
|
+
isReady(sourceLanguage, targetLanguage) {
|
|
896
|
+
return this.ready.has(
|
|
897
|
+
_ModelDownloadGateImpl.key(sourceLanguage, targetLanguage)
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Clears all gate state. Intended for test teardown so cached "ready" models
|
|
902
|
+
* do not leak between cases.
|
|
903
|
+
*/
|
|
904
|
+
reset() {
|
|
905
|
+
this.ready.clear();
|
|
906
|
+
this.pending.clear();
|
|
907
|
+
this.resolvers.clear();
|
|
908
|
+
this.rejecters.clear();
|
|
909
|
+
}
|
|
910
|
+
clearPending(key) {
|
|
911
|
+
this.resolvers.delete(key);
|
|
912
|
+
this.rejecters.delete(key);
|
|
913
|
+
this.pending.delete(key);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
var modelDownloadGate = new ModelDownloadGateImpl();
|
|
917
|
+
|
|
918
|
+
// src/utils/language.ts
|
|
919
|
+
function normalizeLanguage(language) {
|
|
920
|
+
return language.split("-")[0] ?? language;
|
|
921
|
+
}
|
|
922
|
+
|
|
788
923
|
// src/utils/renderVersionManager.ts
|
|
789
924
|
var RenderVersionManager = class {
|
|
790
925
|
constructor() {
|
|
@@ -843,27 +978,30 @@ var TranslationManager = class {
|
|
|
843
978
|
this.translationProgress.total = queueManager.queueSize(
|
|
844
979
|
this.currentTranslatorStrategy
|
|
845
980
|
);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
981
|
+
try {
|
|
982
|
+
do {
|
|
983
|
+
for (const batch of queueManager.generateBatches(
|
|
984
|
+
this.translator.getConfig()
|
|
985
|
+
)) {
|
|
986
|
+
batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
|
|
987
|
+
const result = await this.translator.processTranslations(
|
|
988
|
+
batch.map(
|
|
989
|
+
(entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
|
|
990
|
+
)
|
|
991
|
+
);
|
|
992
|
+
this.setTranslationProgress(batch.length);
|
|
993
|
+
await this.updateDatabaseWithTranslations(result);
|
|
994
|
+
await this.notifyTranslationCompleted();
|
|
995
|
+
}
|
|
996
|
+
} while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
|
|
997
|
+
this.resetTranslationProgress();
|
|
998
|
+
this.publishTranslationProgress();
|
|
999
|
+
} finally {
|
|
1000
|
+
if (this.currentTranslatorStrategy === "frontend_translations") {
|
|
1001
|
+
globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
1002
|
+
} else {
|
|
1003
|
+
globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
859
1004
|
}
|
|
860
|
-
} while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
|
|
861
|
-
this.resetTranslationProgress();
|
|
862
|
-
this.publishTranslationProgress();
|
|
863
|
-
if (this.currentTranslatorStrategy === "frontend_translations") {
|
|
864
|
-
globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
865
|
-
} else {
|
|
866
|
-
globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
867
1005
|
}
|
|
868
1006
|
}
|
|
869
1007
|
async updateDatabaseWithTranslations(translationResult) {
|
|
@@ -1033,6 +1171,16 @@ var Client = class {
|
|
|
1033
1171
|
return this.createTranslationErrorResponse(requests, errorMessage);
|
|
1034
1172
|
}
|
|
1035
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* No-op: backend translation does not manage on-device models, so there is
|
|
1176
|
+
* nothing to download client-side. The parameters are kept to match the
|
|
1177
|
+
* {@link TranslatorClientAdapter.downloadModel} signature and make the no-op
|
|
1178
|
+
* intent explicit to callers.
|
|
1179
|
+
*/
|
|
1180
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1181
|
+
void sourceLanguage;
|
|
1182
|
+
void targetLanguage;
|
|
1183
|
+
}
|
|
1036
1184
|
createTranslationErrorResponse(requests, errorMessage) {
|
|
1037
1185
|
return {
|
|
1038
1186
|
responseBody: requests.map((req) => ({
|
|
@@ -1152,6 +1300,7 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1152
1300
|
}
|
|
1153
1301
|
}
|
|
1154
1302
|
static async init(sourceLanguage, targetLanguage) {
|
|
1303
|
+
this.ensureRegistry();
|
|
1155
1304
|
if (globalThis.chromeTranslatorRegistry.has(
|
|
1156
1305
|
this.generateKey(sourceLanguage, targetLanguage)
|
|
1157
1306
|
)) {
|
|
@@ -1166,7 +1315,30 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1166
1315
|
new _ChromeTranslator(translator)
|
|
1167
1316
|
);
|
|
1168
1317
|
}
|
|
1169
|
-
|
|
1318
|
+
/**
|
|
1319
|
+
* Downloads the on-device model for a source -> target language pair. Must be
|
|
1320
|
+
* invoked from a user gesture (e.g. a click handler), since Chrome requires an
|
|
1321
|
+
* active gesture to start a model download.
|
|
1322
|
+
*/
|
|
1323
|
+
static async downloadModel(sourceLanguage, targetLanguage) {
|
|
1324
|
+
await this.init(
|
|
1325
|
+
normalizeLanguage(sourceLanguage),
|
|
1326
|
+
normalizeLanguage(targetLanguage)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Creates a Chrome translator instance for the pair.
|
|
1331
|
+
*
|
|
1332
|
+
* @param publishProgress - When true, `downloadprogress` events are forwarded
|
|
1333
|
+
* as `MODEL_DOWNLOAD_PROGRESS` events (driving the download-progress popup).
|
|
1334
|
+
* This should only be true for an explicit, consent-driven download
|
|
1335
|
+
* (`init`/`downloadModel`). When creating an already-downloaded
|
|
1336
|
+
* (`available`) model in the translate loop, Chrome still emits
|
|
1337
|
+
* `downloadprogress` while loading the pack from disk into memory; forwarding
|
|
1338
|
+
* those would wrongly show a "downloading" popup for a model that is already
|
|
1339
|
+
* downloaded, so the translate-loop path passes `false`.
|
|
1340
|
+
*/
|
|
1341
|
+
static async createTranslator(source, target, publishProgress = true) {
|
|
1170
1342
|
const key = this.generateKey(source, target);
|
|
1171
1343
|
let resolveReady;
|
|
1172
1344
|
const readyPromise = new Promise((resolve) => {
|
|
@@ -1183,11 +1355,15 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1183
1355
|
const loaded = e.loaded ?? 0;
|
|
1184
1356
|
const total = e.total ?? 0;
|
|
1185
1357
|
const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1358
|
+
if (publishProgress) {
|
|
1359
|
+
getModelDownloadEventHandler().publishModelDownloadProgressEvent(
|
|
1360
|
+
loaded,
|
|
1361
|
+
total,
|
|
1362
|
+
progress,
|
|
1363
|
+
source,
|
|
1364
|
+
target
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1191
1367
|
if (total > 0 && loaded >= total) {
|
|
1192
1368
|
resolveReady();
|
|
1193
1369
|
}
|
|
@@ -1205,26 +1381,27 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1205
1381
|
static async translate(text, targetLanguage) {
|
|
1206
1382
|
var _a;
|
|
1207
1383
|
this.ensureRegistry();
|
|
1384
|
+
const normalizedTarget = normalizeLanguage(targetLanguage);
|
|
1385
|
+
let sourceLanguage = normalizedTarget;
|
|
1208
1386
|
try {
|
|
1209
1387
|
const detector = await ChromeLanguageDetector.getInstance();
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
|
|
1388
|
+
sourceLanguage = normalizeLanguage(await detector.detectLanguage(text));
|
|
1389
|
+
if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(normalizedTarget)) {
|
|
1213
1390
|
return {
|
|
1214
1391
|
translation: text,
|
|
1215
1392
|
sourceLanguage,
|
|
1216
1393
|
success: false,
|
|
1217
1394
|
retryable: false,
|
|
1218
|
-
errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${
|
|
1395
|
+
errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${normalizedTarget}`
|
|
1219
1396
|
};
|
|
1220
1397
|
}
|
|
1221
|
-
const
|
|
1222
|
-
if (!
|
|
1223
|
-
const
|
|
1398
|
+
const key = this.generateKey(sourceLanguage, normalizedTarget);
|
|
1399
|
+
if (!globalThis.chromeTranslatorRegistry.has(key)) {
|
|
1400
|
+
const availability = await Translator.availability({
|
|
1224
1401
|
sourceLanguage,
|
|
1225
|
-
targetLanguage
|
|
1402
|
+
targetLanguage: normalizedTarget
|
|
1226
1403
|
});
|
|
1227
|
-
if (
|
|
1404
|
+
if (availability === "unavailable") {
|
|
1228
1405
|
return {
|
|
1229
1406
|
translation: text,
|
|
1230
1407
|
sourceLanguage,
|
|
@@ -1233,47 +1410,94 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1233
1410
|
errorMessage: "Translation not supported"
|
|
1234
1411
|
};
|
|
1235
1412
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1413
|
+
if (availability !== "available") {
|
|
1414
|
+
const isUserActivationActive = typeof navigator !== "undefined" && (((_a = navigator.userActivation) == null ? void 0 : _a.isActive) ?? false);
|
|
1415
|
+
if (isUserActivationActive) {
|
|
1416
|
+
await _ChromeTranslator.downloadModel(
|
|
1417
|
+
sourceLanguage,
|
|
1418
|
+
normalizedTarget
|
|
1419
|
+
);
|
|
1420
|
+
modelDownloadGate.markReady(sourceLanguage, normalizedTarget);
|
|
1421
|
+
} else {
|
|
1422
|
+
await modelDownloadGate.ensureModelReady(
|
|
1423
|
+
sourceLanguage,
|
|
1424
|
+
normalizedTarget
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1245
1427
|
}
|
|
1246
1428
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
this.translatorReadyPromises.delete(key);
|
|
1250
|
-
const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
|
|
1251
|
-
if (!translation) {
|
|
1252
|
-
return {
|
|
1253
|
-
translation: text,
|
|
1254
|
-
sourceLanguage,
|
|
1255
|
-
success: false,
|
|
1256
|
-
errorMessage: "Translation failed",
|
|
1257
|
-
retryable: false
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
translation,
|
|
1429
|
+
return await this.createInstanceAndTranslate(
|
|
1430
|
+
text,
|
|
1262
1431
|
sourceLanguage,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
};
|
|
1432
|
+
normalizedTarget
|
|
1433
|
+
);
|
|
1266
1434
|
} catch (error) {
|
|
1435
|
+
const isGestureRequired = error instanceof DOMException && error.name === "NotAllowedError";
|
|
1436
|
+
if (isGestureRequired) {
|
|
1437
|
+
try {
|
|
1438
|
+
await modelDownloadGate.ensureModelReady(
|
|
1439
|
+
sourceLanguage,
|
|
1440
|
+
normalizedTarget
|
|
1441
|
+
);
|
|
1442
|
+
return await this.createInstanceAndTranslate(
|
|
1443
|
+
text,
|
|
1444
|
+
sourceLanguage,
|
|
1445
|
+
normalizedTarget
|
|
1446
|
+
);
|
|
1447
|
+
} catch (retryError) {
|
|
1448
|
+
void retryError;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1267
1451
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1268
|
-
const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
|
|
1269
1452
|
return {
|
|
1270
1453
|
translation: text,
|
|
1271
|
-
sourceLanguage
|
|
1454
|
+
sourceLanguage,
|
|
1272
1455
|
success: false,
|
|
1273
1456
|
errorMessage: message,
|
|
1274
|
-
retryable:
|
|
1457
|
+
retryable: false,
|
|
1458
|
+
requiresGesture: isGestureRequired
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Creates the translator instance for the pair if it is not already cached,
|
|
1464
|
+
* waits for the model to be ready, and translates the text. The model is
|
|
1465
|
+
* expected to already be downloaded (or downloadable without a gesture) by
|
|
1466
|
+
* the time this is called.
|
|
1467
|
+
*/
|
|
1468
|
+
static async createInstanceAndTranslate(text, sourceLanguage, targetLanguage) {
|
|
1469
|
+
var _a;
|
|
1470
|
+
const key = this.generateKey(sourceLanguage, targetLanguage);
|
|
1471
|
+
if (!globalThis.chromeTranslatorRegistry.has(key)) {
|
|
1472
|
+
const chromeTranslator = await this.createTranslator(
|
|
1473
|
+
sourceLanguage,
|
|
1474
|
+
targetLanguage,
|
|
1475
|
+
false
|
|
1476
|
+
);
|
|
1477
|
+
globalThis.chromeTranslatorRegistry.set(
|
|
1478
|
+
key,
|
|
1479
|
+
new _ChromeTranslator(chromeTranslator)
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
const instance = globalThis.chromeTranslatorRegistry.get(key);
|
|
1483
|
+
await this.waitForReady(key);
|
|
1484
|
+
this.translatorReadyPromises.delete(key);
|
|
1485
|
+
const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
|
|
1486
|
+
if (!translation) {
|
|
1487
|
+
return {
|
|
1488
|
+
translation: text,
|
|
1489
|
+
sourceLanguage,
|
|
1490
|
+
success: false,
|
|
1491
|
+
errorMessage: "Translation failed",
|
|
1492
|
+
retryable: false
|
|
1275
1493
|
};
|
|
1276
1494
|
}
|
|
1495
|
+
return {
|
|
1496
|
+
translation,
|
|
1497
|
+
sourceLanguage,
|
|
1498
|
+
success: true,
|
|
1499
|
+
retryable: true
|
|
1500
|
+
};
|
|
1277
1501
|
}
|
|
1278
1502
|
static generateKey(sourceLanguage, targetLanguage) {
|
|
1279
1503
|
return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
|
|
@@ -1318,7 +1542,8 @@ var Client2 = class {
|
|
|
1318
1542
|
translation: result.translation,
|
|
1319
1543
|
sourceLanguage: result.sourceLanguage,
|
|
1320
1544
|
isTranslated: result.success,
|
|
1321
|
-
retryable: result.retryable
|
|
1545
|
+
retryable: result.retryable,
|
|
1546
|
+
requiresGesture: result.requiresGesture ?? false
|
|
1322
1547
|
}
|
|
1323
1548
|
],
|
|
1324
1549
|
targetLanguage: request.targetLanguage,
|
|
@@ -1333,6 +1558,9 @@ var Client2 = class {
|
|
|
1333
1558
|
errorMessage
|
|
1334
1559
|
};
|
|
1335
1560
|
}
|
|
1561
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1562
|
+
await ChromeTranslator.downloadModel(sourceLanguage, targetLanguage);
|
|
1563
|
+
}
|
|
1336
1564
|
getConfig() {
|
|
1337
1565
|
return this.config;
|
|
1338
1566
|
}
|
|
@@ -1373,6 +1601,10 @@ var Client3 = class {
|
|
|
1373
1601
|
const translator = this.getFrontendTranslator();
|
|
1374
1602
|
return await translator.translateBatch(...args);
|
|
1375
1603
|
}
|
|
1604
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1605
|
+
const translator = this.getFrontendTranslator();
|
|
1606
|
+
await translator.downloadModel(sourceLanguage, targetLanguage);
|
|
1607
|
+
}
|
|
1376
1608
|
};
|
|
1377
1609
|
|
|
1378
1610
|
// src/translators/translator.ts
|
|
@@ -1387,6 +1619,17 @@ var Translator2 = class {
|
|
|
1387
1619
|
const translatorClient = this.getTranslatorClient();
|
|
1388
1620
|
return await translatorClient.translateBatch(translationRequests);
|
|
1389
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Downloads the on-device model for a source -> target language pair via the
|
|
1624
|
+
* active translator client. Must be invoked from a user gesture for clients
|
|
1625
|
+
* (e.g. Chrome) that require one to start a model download.
|
|
1626
|
+
*/
|
|
1627
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1628
|
+
await this.getTranslatorClient().downloadModel(
|
|
1629
|
+
sourceLanguage,
|
|
1630
|
+
targetLanguage
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1390
1633
|
setTranslatorClient(config2) {
|
|
1391
1634
|
if (config2.getToolConfig().strategy === "frontend_translations") {
|
|
1392
1635
|
this.translatorClient = new Client3(config2);
|
|
@@ -1505,6 +1748,9 @@ function AITranslationInnerProvider(props) {
|
|
|
1505
1748
|
} = props;
|
|
1506
1749
|
const [translationProgress, setTranslationProgress] = (0, import_react.useState)(null);
|
|
1507
1750
|
const [modelDownloadProgress, setModelDownloadProgress] = (0, import_react.useState)(null);
|
|
1751
|
+
const [modelConsent, setModelConsent] = (0, import_react.useState)(
|
|
1752
|
+
null
|
|
1753
|
+
);
|
|
1508
1754
|
const [config2, setConfig] = (0, import_react.useState)(new Config());
|
|
1509
1755
|
const translator = (0, import_react.useRef)(new Translator2(config2));
|
|
1510
1756
|
const eventHandler = (0, import_react.useRef)(new EventHandler(tool));
|
|
@@ -1556,6 +1802,14 @@ function AITranslationInnerProvider(props) {
|
|
|
1556
1802
|
);
|
|
1557
1803
|
return () => unsubscribe();
|
|
1558
1804
|
}, []);
|
|
1805
|
+
(0, import_react.useEffect)(() => {
|
|
1806
|
+
const unsubscribe = eventHandler.current.subscribeToModelConsentRequiredEvent(
|
|
1807
|
+
(request) => {
|
|
1808
|
+
setModelConsent(request);
|
|
1809
|
+
}
|
|
1810
|
+
);
|
|
1811
|
+
return () => unsubscribe();
|
|
1812
|
+
}, []);
|
|
1559
1813
|
(0, import_react.useEffect)(() => {
|
|
1560
1814
|
if (!isFetched || !remoteConfig) return;
|
|
1561
1815
|
const toolConfig = remoteConfig[tool] ?? remoteConfig;
|
|
@@ -1572,9 +1826,23 @@ function AITranslationInnerProvider(props) {
|
|
|
1572
1826
|
config2.setCompanyId(companyId);
|
|
1573
1827
|
config2.setProjectId(projectId);
|
|
1574
1828
|
const ait = (0, import_react.useCallback)(
|
|
1575
|
-
async (text) =>
|
|
1829
|
+
async (text) => {
|
|
1830
|
+
if (!enableAIT) {
|
|
1831
|
+
return text;
|
|
1832
|
+
}
|
|
1833
|
+
return aitFunction(text, translationRegistry.current, config2, tool);
|
|
1834
|
+
},
|
|
1576
1835
|
[tool, config2, enableAIT]
|
|
1577
1836
|
);
|
|
1837
|
+
const confirmModelDownload = (0, import_react.useCallback)(() => {
|
|
1838
|
+
if (!modelConsent) return;
|
|
1839
|
+
if (config2.getToolConfig().strategy !== "frontend_translations") return;
|
|
1840
|
+
const { sourceLanguage, targetLanguage } = modelConsent;
|
|
1841
|
+
setModelConsent(null);
|
|
1842
|
+
translator.current.downloadModel(sourceLanguage, targetLanguage).then(() => modelDownloadGate.markReady(sourceLanguage, targetLanguage)).catch(
|
|
1843
|
+
(error) => modelDownloadGate.markFailed(sourceLanguage, targetLanguage, error)
|
|
1844
|
+
);
|
|
1845
|
+
}, [modelConsent, config2]);
|
|
1578
1846
|
const contextValue = {
|
|
1579
1847
|
ait,
|
|
1580
1848
|
locale,
|
|
@@ -1583,6 +1851,8 @@ function AITranslationInnerProvider(props) {
|
|
|
1583
1851
|
renderVersion: renderVersionManager.getVersion(),
|
|
1584
1852
|
translationProgress,
|
|
1585
1853
|
modelDownloadProgress,
|
|
1854
|
+
modelConsent,
|
|
1855
|
+
confirmModelDownload,
|
|
1586
1856
|
tool,
|
|
1587
1857
|
onTrackAnalyticsEvent,
|
|
1588
1858
|
page,
|
|
@@ -1617,8 +1887,8 @@ var BUTTON_TYPE = {
|
|
|
1617
1887
|
var ACTION = {
|
|
1618
1888
|
CLICKED: "clicked"
|
|
1619
1889
|
};
|
|
1620
|
-
function buildObject(
|
|
1621
|
-
return `${
|
|
1890
|
+
function buildObject(buttonType) {
|
|
1891
|
+
return `${buttonType}_button`;
|
|
1622
1892
|
}
|
|
1623
1893
|
function buildEventKey(parts) {
|
|
1624
1894
|
const { scope, tool, object, action } = parts;
|
|
@@ -1635,7 +1905,7 @@ function buildAnalyticEvent(params) {
|
|
|
1635
1905
|
additionalProperties
|
|
1636
1906
|
} = params;
|
|
1637
1907
|
const resolvedAction = action ?? ACTION.CLICKED;
|
|
1638
|
-
const object = buildObject(
|
|
1908
|
+
const object = buildObject(buttonType);
|
|
1639
1909
|
const key = buildEventKey({
|
|
1640
1910
|
scope,
|
|
1641
1911
|
tool,
|
|
@@ -1644,6 +1914,7 @@ function buildAnalyticEvent(params) {
|
|
|
1644
1914
|
});
|
|
1645
1915
|
const properties = {
|
|
1646
1916
|
...baseProperties,
|
|
1917
|
+
[`${buttonType}_button_location`]: pageContext,
|
|
1647
1918
|
...additionalProperties
|
|
1648
1919
|
};
|
|
1649
1920
|
return { key, properties };
|