зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1710546 - Firefox Translations integration r=mossop,mixedpuppy,mhoye
Bundle Firefox Translation as a builtin pref'd off addon in Nightly only Differential Revision: https://phabricator.services.mozilla.com/D114810
This commit is contained in:
Родитель
9f010baa14
Коммит
cfd82965b8
|
@ -218,3 +218,6 @@ tools/update-packaging/**/*refs.js
|
||||||
|
|
||||||
# Ignore backgroundtasks preferences files.
|
# Ignore backgroundtasks preferences files.
|
||||||
toolkit/components/backgroundtasks/defaults
|
toolkit/components/backgroundtasks/defaults
|
||||||
|
|
||||||
|
# Ignore pre-generated webpack and typescript transpiled files for translations
|
||||||
|
browser/extensions/translations/extension/
|
||||||
|
|
|
@ -2583,3 +2583,8 @@ pref("first-startup.timeout", 30000);
|
||||||
// are expected to go away once a standardized alternative becomes
|
// are expected to go away once a standardized alternative becomes
|
||||||
// available.
|
// available.
|
||||||
pref("svg.context-properties.content.allowed-domains", "profile.accounts.firefox.com,profile.stage.mozaws.net");
|
pref("svg.context-properties.content.allowed-domains", "profile.accounts.firefox.com,profile.stage.mozaws.net");
|
||||||
|
|
||||||
|
// Preference that allows individual users to disable Firefox Translations.
|
||||||
|
#ifdef NIGHTLY_BUILD
|
||||||
|
pref("extensions.translations.disabled", true);
|
||||||
|
#endif
|
||||||
|
|
|
@ -1975,6 +1975,35 @@ BrowserGlue.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Set up a listener to enable/disable the translation extension
|
||||||
|
// based on its preference.
|
||||||
|
_monitorTranslationsPref() {
|
||||||
|
const PREF = "extensions.translations.disabled";
|
||||||
|
const ID = "firefox-translations@mozilla.org";
|
||||||
|
const _checkTranslationsPref = async () => {
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
let disabled = Services.prefs.getBoolPref(PREF, false);
|
||||||
|
if (!addon && disabled) {
|
||||||
|
// not installed, bail out early.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!disabled) {
|
||||||
|
// first time install of addon and install on firefox update
|
||||||
|
addon =
|
||||||
|
(await AddonManager.maybeInstallBuiltinAddon(
|
||||||
|
ID,
|
||||||
|
"0.4.0",
|
||||||
|
"resource://builtin-addons/translations/"
|
||||||
|
)) || addon;
|
||||||
|
await addon.enable();
|
||||||
|
} else if (addon) {
|
||||||
|
await addon.disable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Services.prefs.addObserver(PREF, _checkTranslationsPref);
|
||||||
|
_checkTranslationsPref();
|
||||||
|
},
|
||||||
|
|
||||||
_monitorHTTPSOnlyPref() {
|
_monitorHTTPSOnlyPref() {
|
||||||
const PREF_ENABLED = "dom.security.https_only_mode";
|
const PREF_ENABLED = "dom.security.https_only_mode";
|
||||||
const PREF_WAS_ENABLED = "dom.security.https_only_mode_ever_enabled";
|
const PREF_WAS_ENABLED = "dom.security.https_only_mode_ever_enabled";
|
||||||
|
@ -2172,6 +2201,9 @@ BrowserGlue.prototype = {
|
||||||
this._monitorHTTPSOnlyPref();
|
this._monitorHTTPSOnlyPref();
|
||||||
this._monitorIonPref();
|
this._monitorIonPref();
|
||||||
this._monitorIonStudies();
|
this._monitorIonStudies();
|
||||||
|
if (AppConstants.NIGHTLY_BUILD) {
|
||||||
|
this._monitorTranslationsPref();
|
||||||
|
}
|
||||||
|
|
||||||
FirefoxMonitor.init();
|
FirefoxMonitor.init();
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,3 +12,8 @@ DIRS += [
|
||||||
"report-site-issue",
|
"report-site-issue",
|
||||||
"pictureinpicture",
|
"pictureinpicture",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG["NIGHTLY_BUILD"]:
|
||||||
|
DIRS += [
|
||||||
|
"translations",
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
This folder contains the source files for Firefox Translations, which utilizes
|
||||||
|
the proceedings from Project Bergamot [1].
|
||||||
|
|
||||||
|
The feature is developed as a webextension [2] which utilizes a neural machine
|
||||||
|
translation decoder [3] ported to WebAssembly in order to process the
|
||||||
|
in page translation. The translation models [4] are downloaded on-demand by the
|
||||||
|
extension, so there's no need to bundle them too.
|
||||||
|
|
||||||
|
The folder `extension_src` contains the entire code of the extension's xpi and
|
||||||
|
the wasm module (which lies inside the folder wasm), and is automatically
|
||||||
|
generated by the `import_xpi.py` script, which is responsibile for cloning
|
||||||
|
the extension repo [2], build it, and generate the `jar.mn` package containing
|
||||||
|
all the pertinent files necessary for running it.
|
||||||
|
|
||||||
|
For any questions, reach out to anatal@mozilla.com.
|
||||||
|
|
||||||
|
[1] https://browser.mt/
|
||||||
|
[2] https://github.com/mozilla-extensions/bergamot-browser-extension
|
||||||
|
[3] https://github.com/mozilla/bergamot-translator/
|
||||||
|
[4] https://github.com/mozilla-applied-ml/bergamot-models
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"extensionName": {
|
||||||
|
"message": "Firefox Translations",
|
||||||
|
"description": "Name of the extension"
|
||||||
|
},
|
||||||
|
"extensionDescription": {
|
||||||
|
"message": "Neural Machine Translation for the browser.",
|
||||||
|
"description": "Description of the extension."
|
||||||
|
},
|
||||||
|
"currentlyDownloadingLanguageModel": {
|
||||||
|
"message": "Currently downloading language model",
|
||||||
|
"description": "Informs the user that the translations feature is currently downloading the language model files."
|
||||||
|
},
|
||||||
|
"detailedDownloadProgress": {
|
||||||
|
"message": "$PERCENTAGE$% out of $MB$ mb downloaded",
|
||||||
|
"description": "Informs the user how the language model file downloading is progressing.",
|
||||||
|
"placeholders": {
|
||||||
|
"percentage": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "50"
|
||||||
|
},
|
||||||
|
"mb": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "22.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currentlyLoadingLanguageModel": {
|
||||||
|
"message": "Currently loading language model",
|
||||||
|
"description": "Informs the user that the translations feature is currently loading the language model files into memory."
|
||||||
|
},
|
||||||
|
"loadedLanguageModel": {
|
||||||
|
"message": "Language model loaded",
|
||||||
|
"description": "Informs the user that the translations feature is has loaded the language model files into memory."
|
||||||
|
},
|
||||||
|
"partsLeftToTranslate": {
|
||||||
|
"message": "Parts left to translate: $NUM$",
|
||||||
|
"description": "Informs the user that the translations feature has $NUM$ parts left to translate.",
|
||||||
|
"placeholders": {
|
||||||
|
"num": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"extensionName": {
|
||||||
|
"message": "Traducciones de Firefox",
|
||||||
|
"description": "Name of the extension"
|
||||||
|
},
|
||||||
|
"extensionDescription": {
|
||||||
|
"message": "Traducción de la Máquina Neural para el navegador.",
|
||||||
|
"description": "Description of the extension."
|
||||||
|
},
|
||||||
|
"currentlyDownloadingLanguageModel": {
|
||||||
|
"message": "Actualmente descargando el modelo de idioma",
|
||||||
|
"description": "Informs the user that the translations feature is currently downloading the language model files."
|
||||||
|
},
|
||||||
|
"detailedDownloadProgress": {
|
||||||
|
"message": "$PERCENTAGE$% de $MB$ mb descargado",
|
||||||
|
"description": "Informs the user how the language model file downloading is progressing.",
|
||||||
|
"placeholders": {
|
||||||
|
"percentage": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "50"
|
||||||
|
},
|
||||||
|
"mb": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "22.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currentlyLoadingLanguageModel": {
|
||||||
|
"message": "Actualmente cargar el modelo de idioma",
|
||||||
|
"description": "Informs the user that the translations feature is currently loading the language model files into memory."
|
||||||
|
},
|
||||||
|
"loadedLanguageModel": {
|
||||||
|
"message": "Modelo de lenguaje cargado",
|
||||||
|
"description": "Informs the user that the translations feature is has loaded the language model files into memory."
|
||||||
|
},
|
||||||
|
"partsLeftToTranslate": {
|
||||||
|
"message": "Partes que quedan para traducir: $NUM$",
|
||||||
|
"description": "Informs the user that the translations feature has $NUM$ parts left to translate.",
|
||||||
|
"placeholders": {
|
||||||
|
"num": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,44 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global ExtensionAPI, ExtensionCommon, Services */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.extensionPreferences = class extends ExtensionAPI {
|
||||||
|
getAPI() {
|
||||||
|
const { Preferences } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/Preferences.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { ExtensionUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/ExtensionUtils.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { ExtensionError } = ExtensionUtils;
|
||||||
|
const telemetryInactivityThresholdInSecondsOverridePrefName = `extensions.translations.telemetryInactivityThresholdInSecondsOverride`;
|
||||||
|
return {
|
||||||
|
experiments: {
|
||||||
|
extensionPreferences: {
|
||||||
|
async getTelemetryInactivityThresholdInSecondsOverridePref() {
|
||||||
|
try {
|
||||||
|
const value = Preferences.get(
|
||||||
|
telemetryInactivityThresholdInSecondsOverridePrefName,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return parseFloat(value);
|
||||||
|
} catch (error) {
|
||||||
|
// Surface otherwise silent or obscurely reported errors
|
||||||
|
console.error(error.message, error.stack);
|
||||||
|
throw new ExtensionError(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "experiments.extensionPreferences",
|
||||||
|
"description": "Enables read-only access to specific Extensions-related about:config preferences",
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "getTelemetryInactivityThresholdInSecondsOverridePref",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Get the `extensions.translations.telemetryInactivityThresholdInSecondsOverride` preference's value",
|
||||||
|
"parameters": [],
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global ExtensionAPI, ExtensionCommon, Services */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.telemetryEnvironment = class extends ExtensionAPI {
|
||||||
|
getAPI(context) {
|
||||||
|
const { TelemetryController } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/TelemetryController.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { TelemetryEnvironment } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/TelemetryEnvironment.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These attributes are already sent as part of the telemetry ping envelope
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
const collectTelemetryEnvironmentBasedAttributes = () => {
|
||||||
|
const environment = TelemetryEnvironment.currentEnvironment;
|
||||||
|
// console.debug("TelemetryEnvironment.currentEnvironment", environment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
systemMemoryMb: environment.system.memoryMB,
|
||||||
|
systemCpuCount: environment.system.cpu.count,
|
||||||
|
systemCpuCores: environment.system.cpu.cores,
|
||||||
|
systemCpuVendor: environment.system.cpu.vendor,
|
||||||
|
systemCpuFamily: environment.system.cpu.family,
|
||||||
|
systemCpuModel: environment.system.cpu.model,
|
||||||
|
systemCpuStepping: environment.system.cpu.stepping,
|
||||||
|
systemCpuL2cacheKB: environment.system.cpu.l2cacheKB,
|
||||||
|
systemCpuL3cacheKB: environment.system.cpu.l3cacheKB,
|
||||||
|
systemCpuSpeedMhz: environment.system.cpu.speedMHz,
|
||||||
|
systemCpuExtensions: environment.system.cpu.extensions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
experiments: {
|
||||||
|
telemetryEnvironment: {
|
||||||
|
async getTranslationRelevantFxTelemetryMetrics() {
|
||||||
|
await TelemetryController.promiseInitialized();
|
||||||
|
const telemetryEnvironmentBasedAttributes = collectTelemetryEnvironmentBasedAttributes();
|
||||||
|
// console.debug("telemetryEnvironmentBasedAttributes", telemetryEnvironmentBasedAttributes);
|
||||||
|
return {
|
||||||
|
...telemetryEnvironmentBasedAttributes,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "experiments.telemetryEnvironment",
|
||||||
|
"description": "Enables read-only access to the translation-relevant info from the current telemetry environment",
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "getTranslationRelevantFxTelemetryMetrics",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Get the translation-relevant info from the current telemetry environment",
|
||||||
|
"parameters": [],
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global ExtensionAPI, ExtensionCommon, Services */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.telemetryPreferences = class extends ExtensionAPI {
|
||||||
|
getAPI(context) {
|
||||||
|
const EventManager = ExtensionCommon.EventManager;
|
||||||
|
const uploadEnabledPrefName = `datareporting.healthreport.uploadEnabled`;
|
||||||
|
const cachedClientIDPrefName = `toolkit.telemetry.cachedClientID`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
experiments: {
|
||||||
|
telemetryPreferences: {
|
||||||
|
onUploadEnabledPrefChange: new EventManager({
|
||||||
|
context,
|
||||||
|
name: "telemetryPreferences.onUploadEnabledPrefChange",
|
||||||
|
register: fire => {
|
||||||
|
const callback = () => {
|
||||||
|
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
||||||
|
};
|
||||||
|
Services.prefs.addObserver(uploadEnabledPrefName, callback);
|
||||||
|
return () => {
|
||||||
|
Services.prefs.removeObserver(uploadEnabledPrefName, callback);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).api(),
|
||||||
|
async getUploadEnabledPref() {
|
||||||
|
return Services.prefs.getBoolPref(uploadEnabledPrefName, undefined);
|
||||||
|
},
|
||||||
|
onCachedClientIDPrefChange: new EventManager({
|
||||||
|
context,
|
||||||
|
name: "telemetryPreferences.onCachedClientIDPrefChange",
|
||||||
|
register: fire => {
|
||||||
|
const callback = () => {
|
||||||
|
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
||||||
|
};
|
||||||
|
Services.prefs.addObserver(cachedClientIDPrefName, callback);
|
||||||
|
return () => {
|
||||||
|
Services.prefs.removeObserver(cachedClientIDPrefName, callback);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}).api(),
|
||||||
|
async getCachedClientIDPref() {
|
||||||
|
return Services.prefs.getStringPref(
|
||||||
|
cachedClientIDPrefName,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "experiments.telemetryPreferences",
|
||||||
|
"description": "Enables read-only access to specific Telemetry-related about:config preferences",
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"name": "onUploadEnabledPrefChange",
|
||||||
|
"type": "function",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onCachedClientIDPrefChange",
|
||||||
|
"type": "function",
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "getUploadEnabledPref",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Get the uploadEnabled preference's value",
|
||||||
|
"parameters": [],
|
||||||
|
"async": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getCachedClientIDPref",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Get the cachedClientID preference's value",
|
||||||
|
"parameters": [],
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,322 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global TranslationBrowserChromeUiNotificationManager */
|
||||||
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(TranslationBrowserChromeUi)" }]*/
|
||||||
|
|
||||||
|
const { setTimeout, clearTimeout } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/Timer.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const TranslationInfoBarStates = {
|
||||||
|
STATE_OFFER: 0,
|
||||||
|
STATE_TRANSLATING: 1,
|
||||||
|
STATE_TRANSLATED: 2,
|
||||||
|
STATE_ERROR: 3,
|
||||||
|
STATE_UNAVAILABLE: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TranslationBrowserChromeUi {
|
||||||
|
constructor(Services, browser, context, apiEventEmitter, tabId) {
|
||||||
|
this.Services = Services;
|
||||||
|
this.uiState = null;
|
||||||
|
this.browser = browser;
|
||||||
|
this.context = context;
|
||||||
|
this.translationInfoBarShown = false;
|
||||||
|
this.shouldShowTranslationProgressTimer = undefined;
|
||||||
|
this.importTranslationNotification();
|
||||||
|
|
||||||
|
// The manager instance is injected into the translation notification bar and handles events from therein
|
||||||
|
this.translationBrowserChromeUiNotificationManager = new TranslationBrowserChromeUiNotificationManager(
|
||||||
|
browser,
|
||||||
|
apiEventEmitter,
|
||||||
|
tabId,
|
||||||
|
TranslationInfoBarStates,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get notificationBox() {
|
||||||
|
return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
|
||||||
|
}
|
||||||
|
|
||||||
|
importTranslationNotification() {
|
||||||
|
const chromeWin = this.browser.ownerGlobal;
|
||||||
|
|
||||||
|
// As a workaround to be able to load updates for the translation notification on extension reload
|
||||||
|
// we use the current unix timestamp as part of the element id.
|
||||||
|
// TODO: Restrict use of Date.now() as cachebuster to development mode only
|
||||||
|
chromeWin.now = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
chromeWin.customElements.setElementCreationCallback(
|
||||||
|
`translation-notification-${chromeWin.now}`,
|
||||||
|
() => {
|
||||||
|
this.Services.scriptloader.loadSubScript(
|
||||||
|
this.context.extension.getURL(
|
||||||
|
"experiment-apis/translateUi/content/translation-notification.js",
|
||||||
|
) +
|
||||||
|
"?cachebuster=" +
|
||||||
|
chromeWin.now,
|
||||||
|
chromeWin,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
"Error occurred when attempting to load the translation notification script, but we continue nevertheless",
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUiStateUpdate(uiState) {
|
||||||
|
// Set all values before showing a new translation infobar.
|
||||||
|
this.translationBrowserChromeUiNotificationManager.uiState = uiState;
|
||||||
|
this.setInfobarState(uiState.infobarState);
|
||||||
|
this.updateTranslationProgress(uiState);
|
||||||
|
if (this.shouldShowInfoBar(this.browser.contentPrincipal)) {
|
||||||
|
this.showTranslationInfoBarIfNotAlreadyShown();
|
||||||
|
} else {
|
||||||
|
this.hideTranslationInfoBarIfShown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs infobarState with the inner infobar state variable of the infobar
|
||||||
|
* @param val
|
||||||
|
*/
|
||||||
|
setInfobarState(val) {
|
||||||
|
const notif = this.notificationBox.getNotificationWithValue("translation");
|
||||||
|
if (notif) {
|
||||||
|
notif.state = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the infobar element of the current translation progress
|
||||||
|
*/
|
||||||
|
updateTranslationProgress(uiState) {
|
||||||
|
// Don't bother updating translation progress if not currently translating
|
||||||
|
if (
|
||||||
|
this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.infobarState !== TranslationInfoBarStates.STATE_TRANSLATING
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const notif = this.notificationBox.getNotificationWithValue("translation");
|
||||||
|
if (notif) {
|
||||||
|
const {
|
||||||
|
modelDownloading,
|
||||||
|
translationDurationMs,
|
||||||
|
localizedTranslationProgressText,
|
||||||
|
} = uiState;
|
||||||
|
|
||||||
|
// Always cancel ongoing timers so that we start from a clean state
|
||||||
|
if (this.shouldShowTranslationProgressTimer) {
|
||||||
|
clearTimeout(this.shouldShowTranslationProgressTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show progress if translation has been going on for at least 3 seconds
|
||||||
|
// or we are currently downloading a model
|
||||||
|
let shouldShowTranslationProgress;
|
||||||
|
const thresholdMsAfterWhichToShouldTranslationProgress = 3000;
|
||||||
|
if (
|
||||||
|
translationDurationMs >=
|
||||||
|
thresholdMsAfterWhichToShouldTranslationProgress ||
|
||||||
|
modelDownloading
|
||||||
|
) {
|
||||||
|
shouldShowTranslationProgress = true;
|
||||||
|
} else {
|
||||||
|
// Use a timer to show the translation progress after the threshold
|
||||||
|
this.shouldShowTranslationProgressTimer = setTimeout(() => {
|
||||||
|
notif.updateTranslationProgress(
|
||||||
|
true,
|
||||||
|
localizedTranslationProgressText,
|
||||||
|
);
|
||||||
|
clearTimeout(this.shouldShowTranslationProgressTimer);
|
||||||
|
}, thresholdMsAfterWhichToShouldTranslationProgress - translationDurationMs);
|
||||||
|
// Don't show until then
|
||||||
|
shouldShowTranslationProgress = false;
|
||||||
|
}
|
||||||
|
notif.updateTranslationProgress(
|
||||||
|
shouldShowTranslationProgress,
|
||||||
|
localizedTranslationProgressText,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldShowInfoBar(principal) {
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
TranslationInfoBarStates.STATE_OFFER,
|
||||||
|
TranslationInfoBarStates.STATE_TRANSLATING,
|
||||||
|
TranslationInfoBarStates.STATE_TRANSLATED,
|
||||||
|
TranslationInfoBarStates.STATE_ERROR,
|
||||||
|
].includes(
|
||||||
|
this.translationBrowserChromeUiNotificationManager.uiState.infobarState,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show the infobar if we have no language detection results yet
|
||||||
|
if (
|
||||||
|
!this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.detectedLanguageResults
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show the infobar if we couldn't confidently detect the language
|
||||||
|
if (
|
||||||
|
!this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.detectedLanguageResults.confident
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should never show the infobar for this language.
|
||||||
|
const neverForLangs = this.Services.prefs.getCharPref(
|
||||||
|
"browser.translation.neverForLanguages",
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
neverForLangs
|
||||||
|
.split(",")
|
||||||
|
.includes(
|
||||||
|
this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.detectedLanguageResults.language,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// TranslationTelemetry.recordAutoRejectedTranslationOffer();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or if we should never show the infobar for this domain.
|
||||||
|
const perms = this.Services.perms;
|
||||||
|
if (
|
||||||
|
perms.testExactPermissionFromPrincipal(principal, "translate") ===
|
||||||
|
perms.DENY_ACTION
|
||||||
|
) {
|
||||||
|
// TranslationTelemetry.recordAutoRejectedTranslationOffer();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideURLBarIcon() {
|
||||||
|
const chromeWin = this.browser.ownerGlobal;
|
||||||
|
const PopupNotifications = chromeWin.PopupNotifications;
|
||||||
|
const removeId = this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.originalShown
|
||||||
|
? "translated"
|
||||||
|
: "translate";
|
||||||
|
const notification = PopupNotifications.getNotification(
|
||||||
|
removeId,
|
||||||
|
this.browser,
|
||||||
|
);
|
||||||
|
if (notification) {
|
||||||
|
PopupNotifications.remove(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showURLBarIcon() {
|
||||||
|
const chromeWin = this.browser.ownerGlobal;
|
||||||
|
const PopupNotifications = chromeWin.PopupNotifications;
|
||||||
|
const removeId = this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.originalShown
|
||||||
|
? "translated"
|
||||||
|
: "translate";
|
||||||
|
const notification = PopupNotifications.getNotification(
|
||||||
|
removeId,
|
||||||
|
this.browser,
|
||||||
|
);
|
||||||
|
if (notification) {
|
||||||
|
PopupNotifications.remove(notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = (topic /* , aNewBrowser */) => {
|
||||||
|
if (topic === "swapping") {
|
||||||
|
const infoBarVisible = this.notificationBox.getNotificationWithValue(
|
||||||
|
"translation",
|
||||||
|
);
|
||||||
|
if (infoBarVisible) {
|
||||||
|
this.showTranslationInfoBar();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topic !== "showing") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
||||||
|
"translation",
|
||||||
|
);
|
||||||
|
if (translationNotification) {
|
||||||
|
translationNotification.close();
|
||||||
|
} else {
|
||||||
|
this.showTranslationInfoBar();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addId = this.translationBrowserChromeUiNotificationManager.uiState
|
||||||
|
.originalShown
|
||||||
|
? "translate"
|
||||||
|
: "translated";
|
||||||
|
PopupNotifications.show(
|
||||||
|
this.browser,
|
||||||
|
addId,
|
||||||
|
null,
|
||||||
|
addId + "-notification-icon",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{ dismissed: true, eventCallback: callback },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTranslationInfoBarIfNotAlreadyShown() {
|
||||||
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
||||||
|
"translation",
|
||||||
|
);
|
||||||
|
if (!translationNotification && !this.translationInfoBarShown) {
|
||||||
|
this.showTranslationInfoBar();
|
||||||
|
}
|
||||||
|
this.showURLBarIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
hideTranslationInfoBarIfShown() {
|
||||||
|
const translationNotification = this.notificationBox.getNotificationWithValue(
|
||||||
|
"translation",
|
||||||
|
);
|
||||||
|
if (translationNotification) {
|
||||||
|
translationNotification.close();
|
||||||
|
}
|
||||||
|
this.hideURLBarIcon();
|
||||||
|
this.translationInfoBarShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
showTranslationInfoBar() {
|
||||||
|
console.debug("showTranslationInfoBar");
|
||||||
|
this.translationInfoBarShown = true;
|
||||||
|
const notificationBox = this.notificationBox;
|
||||||
|
const chromeWin = this.browser.ownerGlobal;
|
||||||
|
const notif = notificationBox.appendNotification(
|
||||||
|
"",
|
||||||
|
"translation",
|
||||||
|
null,
|
||||||
|
notificationBox.PRIORITY_INFO_HIGH,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
`translation-notification-${chromeWin.now}`,
|
||||||
|
);
|
||||||
|
notif.init(this.translationBrowserChromeUiNotificationManager);
|
||||||
|
this.translationBrowserChromeUiNotificationManager.infobarDisplayed(
|
||||||
|
notif._getSourceLang(),
|
||||||
|
notif._getTargetLang(),
|
||||||
|
);
|
||||||
|
return notif;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(TranslationBrowserChromeUiNotificationManager)" }]*/
|
||||||
|
|
||||||
|
class TranslationBrowserChromeUiNotificationManager {
|
||||||
|
constructor(browser, apiEventEmitter, tabId, TranslationInfoBarStates) {
|
||||||
|
this.uiState = null;
|
||||||
|
this.TranslationInfoBarStates = TranslationInfoBarStates;
|
||||||
|
this.apiEventEmitter = apiEventEmitter;
|
||||||
|
this.tabId = tabId;
|
||||||
|
this.browser = browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
infobarDisplayed(from, to) {
|
||||||
|
console.log("infobarDisplayed", { from, to });
|
||||||
|
this.apiEventEmitter.emit("onInfoBarDisplayed", this.tabId, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
toLanguageChanged(from, newTo) {
|
||||||
|
console.log("toLanguageChanged", { from, newTo });
|
||||||
|
this.apiEventEmitter.emit("onSelectTranslateTo", this.tabId, from, newTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromLanguageChanged(newFrom, to) {
|
||||||
|
console.log("fromLanguageChanged", { newFrom, to });
|
||||||
|
this.apiEventEmitter.emit("onSelectTranslateFrom", this.tabId, newFrom, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
infobarClosed(from, to) {
|
||||||
|
console.log("infobarClosed", { from, to });
|
||||||
|
this.apiEventEmitter.emit("onInfoBarClosed", this.tabId, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
neverForLanguage(from, to) {
|
||||||
|
console.log("neverForLanguage", { from, to });
|
||||||
|
this.apiEventEmitter.emit(
|
||||||
|
"onNeverTranslateSelectedLanguage",
|
||||||
|
this.tabId,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
neverForSite(from, to) {
|
||||||
|
console.log("neverForSite", { from, to });
|
||||||
|
this.apiEventEmitter.emit("onNeverTranslateThisSite", this.tabId, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
showOriginalContent(from, to) {
|
||||||
|
console.log("showOriginalContent", { from, to });
|
||||||
|
this.apiEventEmitter.emit(
|
||||||
|
"onShowOriginalButtonPressed",
|
||||||
|
this.tabId,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTranslatedContent(from, to) {
|
||||||
|
console.log("showTranslatedContent", { from, to });
|
||||||
|
this.apiEventEmitter.emit(
|
||||||
|
"onShowTranslatedButtonPressed",
|
||||||
|
this.tabId,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
translate(from, to) {
|
||||||
|
console.log("translate", { from, to });
|
||||||
|
this.apiEventEmitter.emit("onTranslateButtonPressed", this.tabId, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
notNow(from, to) {
|
||||||
|
console.log("notNow", { from, to });
|
||||||
|
this.apiEventEmitter.emit("onNotNowButtonPressed", this.tabId, from, to);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* eslint-env commonjs */
|
||||||
|
/* eslint no-unused-vars: off */
|
||||||
|
/* eslint no-inner-declarations: off */
|
||||||
|
/* eslint no-console: ["warn", { allow: ["info", "warn", "error"] }] */
|
||||||
|
/* global ExtensionAPI */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.translateUi = class extends ExtensionAPI {
|
||||||
|
getAPI(context) {
|
||||||
|
const { Services } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/Services.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
/* global TranslationBrowserChromeUiManager */
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
context.extension.getURL(
|
||||||
|
"experiment-apis/translateUi/TranslationBrowserChromeUiManager.js",
|
||||||
|
) +
|
||||||
|
"?cachebuster=" +
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
/* global TranslationBrowserChromeUi */
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
context.extension.getURL(
|
||||||
|
"experiment-apis/translateUi/TranslationBrowserChromeUi.js",
|
||||||
|
) +
|
||||||
|
"?cachebuster=" +
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { ExtensionCommon } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/ExtensionCommon.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { EventManager, EventEmitter } = ExtensionCommon;
|
||||||
|
|
||||||
|
const apiEventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
const { ExtensionUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/ExtensionUtils.jsm",
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { ExtensionError } = ExtensionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boilerplate-reducing factory method translating between
|
||||||
|
* apiEventEmitter.emit("translateUi.onFoo", ...args)
|
||||||
|
* and the actual web extension event being emitted
|
||||||
|
*
|
||||||
|
* @param {string} eventRef the event reference, eg "onFoo"
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const eventManagerFactory = eventRef => {
|
||||||
|
const eventId = `translateUi.${eventRef}`;
|
||||||
|
return new EventManager({
|
||||||
|
context,
|
||||||
|
name: eventId,
|
||||||
|
register: fire => {
|
||||||
|
const listener = (event, ...args) => fire.async(...args);
|
||||||
|
apiEventEmitter.on(eventRef, listener);
|
||||||
|
return () => {
|
||||||
|
apiEventEmitter.off(eventRef, listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { tabManager } = context.extension;
|
||||||
|
const translationBrowserChromeUiInstancesByTabId = new Map();
|
||||||
|
const getTranslationBrowserChromeUiInstanceByTabId = tabId => {
|
||||||
|
if (translationBrowserChromeUiInstancesByTabId.has(tabId)) {
|
||||||
|
return translationBrowserChromeUiInstancesByTabId.get(tabId);
|
||||||
|
}
|
||||||
|
const tab = tabManager.get(tabId);
|
||||||
|
const { browser } = tab;
|
||||||
|
const translationBrowserChromeUi = new TranslationBrowserChromeUi(
|
||||||
|
Services,
|
||||||
|
browser,
|
||||||
|
context,
|
||||||
|
apiEventEmitter,
|
||||||
|
tabId,
|
||||||
|
);
|
||||||
|
translationBrowserChromeUiInstancesByTabId.set(
|
||||||
|
tabId,
|
||||||
|
translationBrowserChromeUi,
|
||||||
|
);
|
||||||
|
return translationBrowserChromeUi;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
experiments: {
|
||||||
|
translateUi: {
|
||||||
|
/* Start reacting to translation state updates */
|
||||||
|
start: async function start() {
|
||||||
|
try {
|
||||||
|
console.log("Called start()");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Inactivating legacy built-in translation feature (by setting browser.translation.ui.show and browser.translation.detectLanguage to false)",
|
||||||
|
);
|
||||||
|
Services.prefs.setBoolPref(`browser.translation.ui.show`, false);
|
||||||
|
Services.prefs.setBoolPref(
|
||||||
|
`browser.translation.detectLanguage`,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
} catch (error) {
|
||||||
|
// Surface otherwise silent or obscurely reported errors
|
||||||
|
console.error(error.message, error.stack);
|
||||||
|
throw new ExtensionError(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Set current ui state */
|
||||||
|
setUiState: async function setUiState(tabId, uiState) {
|
||||||
|
try {
|
||||||
|
// console.log("Called setUiState(tabId, uiState)", {tabId,uiState});
|
||||||
|
const translationBrowserChromeUi = getTranslationBrowserChromeUiInstanceByTabId(
|
||||||
|
tabId,
|
||||||
|
);
|
||||||
|
translationBrowserChromeUi.onUiStateUpdate(uiState);
|
||||||
|
return undefined;
|
||||||
|
} catch (error) {
|
||||||
|
// Surface otherwise silent or obscurely reported errors
|
||||||
|
console.error(error.message, error.stack);
|
||||||
|
throw new ExtensionError(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Stop reacting to translation state updates */
|
||||||
|
stop: async function stop() {
|
||||||
|
try {
|
||||||
|
console.log("Called stop()");
|
||||||
|
return undefined;
|
||||||
|
} catch (error) {
|
||||||
|
// Surface otherwise silent or obscurely reported errors
|
||||||
|
console.error(error.message, error.stack);
|
||||||
|
throw new ExtensionError(error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Event boilerplate with listeners that forwards all but the first argument to the web extension event */
|
||||||
|
onInfoBarDisplayed: eventManagerFactory("onInfoBarDisplayed").api(),
|
||||||
|
onSelectTranslateTo: eventManagerFactory("onSelectTranslateTo").api(),
|
||||||
|
onSelectTranslateFrom: eventManagerFactory(
|
||||||
|
"onSelectTranslateFrom",
|
||||||
|
).api(),
|
||||||
|
onInfoBarClosed: eventManagerFactory("onInfoBarClosed").api(),
|
||||||
|
onNeverTranslateSelectedLanguage: eventManagerFactory(
|
||||||
|
"onNeverTranslateSelectedLanguage",
|
||||||
|
).api(),
|
||||||
|
onNeverTranslateThisSite: eventManagerFactory(
|
||||||
|
"onNeverTranslateThisSite",
|
||||||
|
).api(),
|
||||||
|
onShowOriginalButtonPressed: eventManagerFactory(
|
||||||
|
"onShowOriginalButtonPressed",
|
||||||
|
).api(),
|
||||||
|
onShowTranslatedButtonPressed: eventManagerFactory(
|
||||||
|
"onShowTranslatedButtonPressed",
|
||||||
|
).api(),
|
||||||
|
onTranslateButtonPressed: eventManagerFactory(
|
||||||
|
"onTranslateButtonPressed",
|
||||||
|
).api(),
|
||||||
|
onNotNowButtonPressed: eventManagerFactory(
|
||||||
|
"onNotNowButtonPressed",
|
||||||
|
).api(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,449 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global MozElements */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window.MozTranslationNotification = class extends MozElements.Notification {
|
||||||
|
static get markup() {
|
||||||
|
return `
|
||||||
|
<hbox anonid="details" align="center" flex="1">
|
||||||
|
<image class="messageImage"/>
|
||||||
|
<panel anonid="welcomePanel" class="translation-welcome-panel" type="arrow" align="start">
|
||||||
|
<image class="translation-welcome-logo"/>
|
||||||
|
<vbox flex="1" class="translation-welcome-content">
|
||||||
|
<description class="translation-welcome-headline" anonid="welcomeHeadline"/>
|
||||||
|
<description class="translation-welcome-body" anonid="welcomeBody"/>
|
||||||
|
<hbox align="center">
|
||||||
|
<label anonid="learnMore" class="plain" onclick="openTrustedLinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();" is="text-link"/>
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<button anonid="thanksButton" onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</panel>
|
||||||
|
<deck anonid="translationStates" selectedIndex="0">
|
||||||
|
<hbox class="translate-offer-box" align="center">
|
||||||
|
<label value="&translation.thisPageIsIn.label;"/>
|
||||||
|
<menulist class="notification-button" anonid="detectedLanguage">
|
||||||
|
<menupopup/>
|
||||||
|
</menulist>
|
||||||
|
<label value="&translation.translateThisPage.label;"/>
|
||||||
|
<button class="notification-button primary" label="&translation.translate.button;" anonid="translate" oncommand="this.closest('notification').translate();"/>
|
||||||
|
<button class="notification-button" label="&translation.notNow.button;" anonid="notNow" oncommand="this.closest('notification').notNow();"/>
|
||||||
|
</hbox>
|
||||||
|
<vbox class="translating-box" pack="center">
|
||||||
|
<hbox><label value="&translation.translatingContent.label;"/><label anonid="progress-label" value=""/></hbox>
|
||||||
|
</vbox>
|
||||||
|
<hbox class="translated-box" align="center">
|
||||||
|
<label value="&translation.translatedFrom.label;"/>
|
||||||
|
<menulist class="notification-button" anonid="fromLanguage" oncommand="this.closest('notification').fromLanguageChanged();">
|
||||||
|
<menupopup/>
|
||||||
|
</menulist>
|
||||||
|
<label value="&translation.translatedTo.label;"/>
|
||||||
|
<menulist class="notification-button" anonid="toLanguage" oncommand="this.closest('notification').toLanguageChanged();">
|
||||||
|
<menupopup/>
|
||||||
|
</menulist>
|
||||||
|
<label value="&translation.translatedToSuffix.label;"/>
|
||||||
|
<button anonid="showOriginal" class="notification-button" label="&translation.showOriginal.button;" oncommand="this.closest('notification').showOriginal();"/>
|
||||||
|
<button anonid="showTranslation" class="notification-button" label="&translation.showTranslation.button;" oncommand="this.closest('notification').showTranslation();"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox class="translation-error" align="center">
|
||||||
|
<label value="&translation.errorTranslating.label;"/>
|
||||||
|
<button class="notification-button" label="&translation.tryAgain.button;" anonid="tryAgain" oncommand="this.closest('notification').translate();"/>
|
||||||
|
</hbox>
|
||||||
|
<vbox class="translation-unavailable" pack="center">
|
||||||
|
<label value="&translation.serviceUnavailable.label;"/>
|
||||||
|
</vbox>
|
||||||
|
</deck>
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<button type="menu" class="notification-button" anonid="options" label="&translation.options.menu;">
|
||||||
|
<menupopup class="translation-menupopup" onpopupshowing="this.closest('notification').optionsShowing();">
|
||||||
|
<menuitem anonid="neverForLanguage" oncommand="this.closest('notification').neverForLanguage();"/>
|
||||||
|
<menuitem anonid="neverForSite" oncommand="this.closest('notification').neverForSite();" label="&translation.options.neverForSite.label;" accesskey="&translation.options.neverForSite.accesskey;"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem oncommand="openPreferences('paneGeneral');" label="&translation.options.preferences.label;" accesskey="&translation.options.preferences.accesskey;"/>
|
||||||
|
</menupopup>
|
||||||
|
</button>
|
||||||
|
</hbox>
|
||||||
|
<toolbarbutton anonid="closeButton" ondblclick="event.stopPropagation();"
|
||||||
|
class="messageCloseButton close-icon tabbable"
|
||||||
|
tooltiptext="&closeNotification.tooltip;"
|
||||||
|
oncommand="this.parentNode.closeCommand();"/>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get entities() {
|
||||||
|
return [
|
||||||
|
"chrome://global/locale/notification.dtd",
|
||||||
|
"chrome://browser/locale/translation.dtd",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.appendChild(this.constructor.fragment);
|
||||||
|
|
||||||
|
for (const [propertyName, selector] of [
|
||||||
|
["details", "[anonid=details]"],
|
||||||
|
["messageImage", ".messageImage"],
|
||||||
|
["spacer", "[anonid=spacer]"],
|
||||||
|
]) {
|
||||||
|
this[propertyName] = this.querySelector(selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTranslationProgress(
|
||||||
|
shouldShowTranslationProgress,
|
||||||
|
localizedTranslationProgressText,
|
||||||
|
) {
|
||||||
|
const progressLabelValue = shouldShowTranslationProgress
|
||||||
|
? localizedTranslationProgressText
|
||||||
|
: "";
|
||||||
|
this._getAnonElt("progress-label").setAttribute(
|
||||||
|
"value",
|
||||||
|
progressLabelValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set state(val) {
|
||||||
|
const deck = this._getAnonElt("translationStates");
|
||||||
|
|
||||||
|
const activeElt = document.activeElement;
|
||||||
|
if (activeElt && deck.contains(activeElt)) {
|
||||||
|
activeElt.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
let stateName;
|
||||||
|
for (const name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
|
||||||
|
if (Translation["STATE_" + name] === val) {
|
||||||
|
stateName = name.toLowerCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setAttribute("state", stateName);
|
||||||
|
|
||||||
|
if (val === this.translation.TranslationInfoBarStates.STATE_TRANSLATED) {
|
||||||
|
this._handleButtonHiding();
|
||||||
|
}
|
||||||
|
|
||||||
|
deck.selectedIndex = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
get state() {
|
||||||
|
return this._getAnonElt("translationStates").selectedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(translationBrowserChromeUiNotificationManager) {
|
||||||
|
this.translation = translationBrowserChromeUiNotificationManager;
|
||||||
|
|
||||||
|
const sortByLocalizedName = function(list) {
|
||||||
|
const names = Services.intl.getLanguageDisplayNames(undefined, list);
|
||||||
|
return list
|
||||||
|
.map((code, i) => [code, names[i]])
|
||||||
|
.sort((a, b) => a[1].localeCompare(b[1]));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fill the lists of supported source languages.
|
||||||
|
const detectedLanguage = this._getAnonElt("detectedLanguage");
|
||||||
|
const fromLanguage = this._getAnonElt("fromLanguage");
|
||||||
|
const sourceLanguages = sortByLocalizedName(
|
||||||
|
this.translation.uiState.supportedSourceLanguages,
|
||||||
|
);
|
||||||
|
for (const [code, name] of sourceLanguages) {
|
||||||
|
detectedLanguage.appendItem(name, code);
|
||||||
|
fromLanguage.appendItem(name, code);
|
||||||
|
}
|
||||||
|
detectedLanguage.value = this.translation.uiState.detectedLanguageResults.language;
|
||||||
|
|
||||||
|
// translatedFrom is only set if we have already translated this page.
|
||||||
|
if (translationBrowserChromeUiNotificationManager.uiState.translatedFrom) {
|
||||||
|
fromLanguage.value =
|
||||||
|
translationBrowserChromeUiNotificationManager.uiState.translatedFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the list of supported target languages.
|
||||||
|
const toLanguage = this._getAnonElt("toLanguage");
|
||||||
|
const targetLanguages = sortByLocalizedName(
|
||||||
|
this.translation.uiState.supportedTargetLanguages,
|
||||||
|
);
|
||||||
|
for (const [code, name] of targetLanguages) {
|
||||||
|
toLanguage.appendItem(name, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationBrowserChromeUiNotificationManager.uiState.translatedTo) {
|
||||||
|
toLanguage.value =
|
||||||
|
translationBrowserChromeUiNotificationManager.uiState.translatedTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationBrowserChromeUiNotificationManager.uiState.infobarState) {
|
||||||
|
this.state =
|
||||||
|
translationBrowserChromeUiNotificationManager.uiState.infobarState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// The welcome popup/notification is currently disabled
|
||||||
|
const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
|
||||||
|
if (
|
||||||
|
Services.prefs.prefHasUserValue(kWelcomePref) ||
|
||||||
|
this.translation.browser !== gBrowser.selectedBrowser
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addEventListener(
|
||||||
|
"transitionend",
|
||||||
|
function() {
|
||||||
|
// These strings are hardcoded because they need to reach beta
|
||||||
|
// without riding the trains.
|
||||||
|
const localizedStrings = {
|
||||||
|
en: [
|
||||||
|
"Hey look! It's something new!",
|
||||||
|
"Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
|
||||||
|
"Learn more.",
|
||||||
|
"Thanks",
|
||||||
|
],
|
||||||
|
"es-AR": [
|
||||||
|
"\xA1Mir\xE1! \xA1Hay algo nuevo!",
|
||||||
|
"Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
|
||||||
|
"Conoc\xE9 m\xE1s.",
|
||||||
|
"Gracias",
|
||||||
|
],
|
||||||
|
"es-ES": [
|
||||||
|
"\xA1Mira! \xA1Hay algo nuevo!",
|
||||||
|
"Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
|
||||||
|
"M\xE1s informaci\xF3n.",
|
||||||
|
"Gracias",
|
||||||
|
],
|
||||||
|
pl: [
|
||||||
|
"Sp\xF3jrz tutaj! To co\u015B nowego!",
|
||||||
|
"Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
|
||||||
|
"Dowiedz si\u0119 wi\u0119cej",
|
||||||
|
"Dzi\u0119kuj\u0119",
|
||||||
|
],
|
||||||
|
tr: [
|
||||||
|
"Bak\u0131n, burada yeni bir \u015Fey var!",
|
||||||
|
"Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
|
||||||
|
"Daha fazla bilgi al\u0131n.",
|
||||||
|
"Te\u015Fekk\xFCrler",
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
"Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
|
||||||
|
"Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
|
||||||
|
"T\xECm hi\u1EC3u th\xEAm.",
|
||||||
|
"C\u1EA3m \u01A1n",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let locale = Services.locale.appLocaleAsBCP47;
|
||||||
|
if (!(locale in localizedStrings)) {
|
||||||
|
locale = "en";
|
||||||
|
}
|
||||||
|
const strings = localizedStrings[locale];
|
||||||
|
|
||||||
|
this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
|
||||||
|
this._getAnonElt("welcomeBody").textContent = strings[1];
|
||||||
|
this._getAnonElt("learnMore").setAttribute("value", strings[2]);
|
||||||
|
this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
|
||||||
|
|
||||||
|
// TODO: Figure out why this shows a strangely rendered popup at the corner of the window instead next to the URL bar
|
||||||
|
const panel = this._getAnonElt("welcomePanel");
|
||||||
|
panel.openPopup(
|
||||||
|
this._getAnonElt("messageImage"),
|
||||||
|
"bottomcenter topleft",
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.prefs.setBoolPref(kWelcomePref, true);
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAnonElt(anonId) {
|
||||||
|
return this.querySelector("[anonid=" + anonId + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
fromLanguageChanged() {
|
||||||
|
this.translation.fromLanguageChanged(
|
||||||
|
this._getSourceLang(),
|
||||||
|
this._getTargetLang(),
|
||||||
|
);
|
||||||
|
this.translate();
|
||||||
|
}
|
||||||
|
|
||||||
|
toLanguageChanged() {
|
||||||
|
this.translation.toLanguageChanged(
|
||||||
|
this._getSourceLang(),
|
||||||
|
this._getTargetLang(),
|
||||||
|
);
|
||||||
|
this.translate();
|
||||||
|
}
|
||||||
|
|
||||||
|
translate() {
|
||||||
|
const from = this._getSourceLang();
|
||||||
|
const to = this._getTargetLang();
|
||||||
|
|
||||||
|
// Initiate translation
|
||||||
|
this.translation.translate(from, to);
|
||||||
|
|
||||||
|
// Store the values used in the translation in the from and to inputs
|
||||||
|
if (
|
||||||
|
this.translation.uiState.infobarState ===
|
||||||
|
this.translation.TranslationInfoBarStates.STATE_OFFER
|
||||||
|
) {
|
||||||
|
this._getAnonElt("fromLanguage").value = from;
|
||||||
|
this._getAnonElt("toLanguage").value = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be called when the infobar should be closed per user's wish (e.g.
|
||||||
|
* by clicking the notification's close button, the not now button or choosing never to translate)
|
||||||
|
*/
|
||||||
|
closeCommand() {
|
||||||
|
const from = this._getSourceLang();
|
||||||
|
const to = this._getTargetLang();
|
||||||
|
this.close();
|
||||||
|
this.translation.infobarClosed(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be called when the infobar should be closed per user's wish
|
||||||
|
* by clicking the Not now button
|
||||||
|
*/
|
||||||
|
notNow() {
|
||||||
|
this.translation.notNow(this._getSourceLang(), this._getTargetLang());
|
||||||
|
this.closeCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleButtonHiding() {
|
||||||
|
const originalShown = this.translation.uiState.originalShown;
|
||||||
|
this._getAnonElt("showOriginal").hidden = originalShown;
|
||||||
|
this._getAnonElt("showTranslation").hidden = !originalShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
showOriginal() {
|
||||||
|
this.translation.showOriginalContent(
|
||||||
|
this._getSourceLang(),
|
||||||
|
this._getTargetLang(),
|
||||||
|
);
|
||||||
|
this._handleButtonHiding();
|
||||||
|
}
|
||||||
|
|
||||||
|
showTranslation() {
|
||||||
|
this.translation.showTranslatedContent(
|
||||||
|
this._getSourceLang(),
|
||||||
|
this._getTargetLang(),
|
||||||
|
);
|
||||||
|
this._handleButtonHiding();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getSourceLang() {
|
||||||
|
let lang;
|
||||||
|
if (
|
||||||
|
this.translation.uiState.infobarState ===
|
||||||
|
this.translation.TranslationInfoBarStates.STATE_OFFER
|
||||||
|
) {
|
||||||
|
lang = this._getAnonElt("detectedLanguage").value;
|
||||||
|
} else {
|
||||||
|
lang = this._getAnonElt("fromLanguage").value;
|
||||||
|
|
||||||
|
// If we have never attempted to translate the page before the
|
||||||
|
// service became unavailable, "fromLanguage" isn't set.
|
||||||
|
if (
|
||||||
|
!lang &&
|
||||||
|
this.translation.uiState.infobarState ===
|
||||||
|
this.translation.TranslationInfoBarStates.STATE_UNAVAILABLE
|
||||||
|
) {
|
||||||
|
lang = this.translation.uiState.defaultTargetLanguage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!lang) {
|
||||||
|
throw new Error("Source language is not defined");
|
||||||
|
}
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTargetLang() {
|
||||||
|
return (
|
||||||
|
this._getAnonElt("toLanguage").value ||
|
||||||
|
this.translation.uiState.defaultTargetLanguage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsShowing() {
|
||||||
|
const lang = this._getSourceLang();
|
||||||
|
|
||||||
|
// Get the source language name.
|
||||||
|
const langName = Services.intl.getLanguageDisplayNames(undefined, [
|
||||||
|
lang,
|
||||||
|
])[0];
|
||||||
|
|
||||||
|
// Set the label and accesskey on the menuitem.
|
||||||
|
const bundle = Services.strings.createBundle(
|
||||||
|
"chrome://browser/locale/translation.properties",
|
||||||
|
);
|
||||||
|
let item = this._getAnonElt("neverForLanguage");
|
||||||
|
const kStrId = "translation.options.neverForLanguage";
|
||||||
|
item.setAttribute(
|
||||||
|
"label",
|
||||||
|
bundle.formatStringFromName(kStrId + ".label", [langName]),
|
||||||
|
);
|
||||||
|
item.setAttribute(
|
||||||
|
"accesskey",
|
||||||
|
bundle.GetStringFromName(kStrId + ".accesskey"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// We may need to disable the menuitems if they have already been used.
|
||||||
|
// Check if translation is already disabled for this language:
|
||||||
|
const neverForLangs = Services.prefs.getCharPref(
|
||||||
|
"browser.translation.neverForLanguages",
|
||||||
|
);
|
||||||
|
item.disabled = neverForLangs.split(",").includes(lang);
|
||||||
|
|
||||||
|
// Check if translation is disabled for the domain:
|
||||||
|
const principal = this.translation.browser.contentPrincipal;
|
||||||
|
const perms = Services.perms;
|
||||||
|
item = this._getAnonElt("neverForSite");
|
||||||
|
item.disabled =
|
||||||
|
perms.testExactPermissionFromPrincipal(principal, "translate") ===
|
||||||
|
perms.DENY_ACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
neverForLanguage() {
|
||||||
|
const kPrefName = "browser.translation.neverForLanguages";
|
||||||
|
const sourceLang = this._getSourceLang();
|
||||||
|
|
||||||
|
let val = Services.prefs.getCharPref(kPrefName);
|
||||||
|
if (val) {
|
||||||
|
val += ",";
|
||||||
|
}
|
||||||
|
val += sourceLang;
|
||||||
|
|
||||||
|
Services.prefs.setCharPref(kPrefName, val);
|
||||||
|
|
||||||
|
this.translation.neverForLanguage(
|
||||||
|
this._getSourceLang(),
|
||||||
|
this._getTargetLang(),
|
||||||
|
);
|
||||||
|
this.closeCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
neverForSite() {
|
||||||
|
const principal = this.translation.browser.contentPrincipal;
|
||||||
|
const perms = Services.perms;
|
||||||
|
perms.addFromPrincipal(principal, "translate", perms.DENY_ACTION);
|
||||||
|
|
||||||
|
this.translation.neverForSite(this._getSourceLang(), this._getTargetLang());
|
||||||
|
this.closeCommand();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
customElements.define(
|
||||||
|
`translation-notification-${window.now}`,
|
||||||
|
window.MozTranslationNotification,
|
||||||
|
{
|
||||||
|
extends: "notification",
|
||||||
|
},
|
||||||
|
);
|
|
@ -0,0 +1,100 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "experiments.translateUi",
|
||||||
|
"description": "Provides browser chrome UI that reacts to translation states and fires events on user interaction",
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "start",
|
||||||
|
"type": "function",
|
||||||
|
"async": true,
|
||||||
|
"description": "Start reacting to translation state updates",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setUiState",
|
||||||
|
"type": "function",
|
||||||
|
"async": true,
|
||||||
|
"description": "Set current ui state",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "tabId",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uiState",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stop",
|
||||||
|
"type": "function",
|
||||||
|
"async": true,
|
||||||
|
"description": "Stop reacting to translation state updates",
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"name": "onInfoBarDisplayed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onSelectTranslateTo",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onSelectTranslateFrom",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onInfoBarClosed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onNeverTranslateSelectedLanguage",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onNeverTranslateThisSite",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onShowOriginalButtonPressed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onShowTranslatedButtonPressed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onTranslateButtonPressed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onNotNowButtonPressed",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Foo",
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 750 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.7 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
##### This file was automatically generated by the import_xpi.py script ####
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
browser.jar:
|
||||||
|
% resource builtin-addons %builtin-addons/ contentaccessible=yes
|
||||||
|
builtin-addons/translations/ (**)
|
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Firefox Translations",
|
||||||
|
"description": "__MSG_extensionDescription__",
|
||||||
|
"version": "0.4.0",
|
||||||
|
"incognito": "spanning",
|
||||||
|
"default_locale": "en_US",
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"commons.js",
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"js": [
|
||||||
|
"commons.js"
|
||||||
|
],
|
||||||
|
"matches": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"all_frames": true,
|
||||||
|
"run_at": "document_idle",
|
||||||
|
"match_about_blank": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"js": [
|
||||||
|
"dom-translation-content-script.js"
|
||||||
|
],
|
||||||
|
"matches": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"all_frames": true,
|
||||||
|
"run_at": "document_idle",
|
||||||
|
"match_about_blank": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"<all_urls>",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"icons": {
|
||||||
|
"16": "icons/translation.16x16.png",
|
||||||
|
"32": "icons/translation.32x32.png"
|
||||||
|
},
|
||||||
|
"hidden": false,
|
||||||
|
"experiment_apis": {
|
||||||
|
"extensionPreferences": {
|
||||||
|
"schema": "./experiment-apis/extensionPreferences/schema.json",
|
||||||
|
"parent": {
|
||||||
|
"scopes": [
|
||||||
|
"addon_parent"
|
||||||
|
],
|
||||||
|
"script": "./experiment-apis/extensionPreferences/api.js",
|
||||||
|
"paths": [
|
||||||
|
[
|
||||||
|
"experiments",
|
||||||
|
"extensionPreferences"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"telemetryEnvironment": {
|
||||||
|
"schema": "./experiment-apis/telemetryEnvironment/schema.json",
|
||||||
|
"parent": {
|
||||||
|
"scopes": [
|
||||||
|
"addon_parent"
|
||||||
|
],
|
||||||
|
"script": "./experiment-apis/telemetryEnvironment/api.js",
|
||||||
|
"paths": [
|
||||||
|
[
|
||||||
|
"experiments",
|
||||||
|
"telemetryEnvironment"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"telemetryPreferences": {
|
||||||
|
"schema": "./experiment-apis/telemetryPreferences/schema.json",
|
||||||
|
"parent": {
|
||||||
|
"scopes": [
|
||||||
|
"addon_parent"
|
||||||
|
],
|
||||||
|
"script": "./experiment-apis/telemetryPreferences/api.js",
|
||||||
|
"paths": [
|
||||||
|
[
|
||||||
|
"experiments",
|
||||||
|
"telemetryPreferences"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translateUi": {
|
||||||
|
"schema": "./experiment-apis/translateUi/schema.json",
|
||||||
|
"parent": {
|
||||||
|
"scopes": [
|
||||||
|
"addon_parent"
|
||||||
|
],
|
||||||
|
"script": "./experiment-apis/translateUi/api.js",
|
||||||
|
"paths": [
|
||||||
|
[
|
||||||
|
"experiments",
|
||||||
|
"translateUi"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "firefox-translations@mozilla.org",
|
||||||
|
"strict_min_version": "90.0a1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Двоичные данные
browser/extensions/translations/extension/wasm/bergamot-translator-worker.wasm
Normal file
Двоичные данные
browser/extensions/translations/extension/wasm/bergamot-translator-worker.wasm
Normal file
Двоичный файл не отображается.
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Двоичный файл не отображается.
|
@ -0,0 +1,106 @@
|
||||||
|
# script to pull and import Firefox Translations's extension source code
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
from zipfile import ZipFile
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if not os.path.exists("import_xpi.py"):
|
||||||
|
sys.exit("This script is intended to be executed from its local folder")
|
||||||
|
|
||||||
|
have_xpi = "N"
|
||||||
|
local_xpi_file = (
|
||||||
|
"bergamot-browser-extension-src/dist/production/firefox/"
|
||||||
|
"firefox-infobar-ui/firefox-translations-0.4.0.xpi"
|
||||||
|
)
|
||||||
|
if os.path.isfile(local_xpi_file):
|
||||||
|
have_xpi = input(
|
||||||
|
"Extension xpi exists. Press Y to use it or any other key to rebuild it."
|
||||||
|
)
|
||||||
|
|
||||||
|
if have_xpi.lower() != "y":
|
||||||
|
# deleting old files if any
|
||||||
|
shutil.rmtree("bergamot-browser-extension-src", ignore_errors=True)
|
||||||
|
# cloning the extension
|
||||||
|
subprocess.call(
|
||||||
|
(
|
||||||
|
"git clone -b v0.4.0 "
|
||||||
|
"https://github.com/mozilla-extensions/bergamot-browser-extension/ "
|
||||||
|
"bergamot-browser-extension-src "
|
||||||
|
).split()
|
||||||
|
)
|
||||||
|
# setting up the repo
|
||||||
|
subprocess.call("yarn install".split(), cwd="bergamot-browser-extension-src")
|
||||||
|
# pulling bergamot-translator submodule, the repo containing the port of the
|
||||||
|
# neural machine translation engine to wasm
|
||||||
|
subprocess.call(
|
||||||
|
"git submodule update --init --recursive".split(),
|
||||||
|
cwd="bergamot-browser-extension-src",
|
||||||
|
)
|
||||||
|
# build the wasm nmt module
|
||||||
|
subprocess.call(
|
||||||
|
"./bergamot-translator/build-wasm.sh".split(),
|
||||||
|
cwd="bergamot-browser-extension-src",
|
||||||
|
)
|
||||||
|
# import the generated wasm module to the extension
|
||||||
|
subprocess.call(
|
||||||
|
"./import-bergamot-translator.sh ./bergamot-translator/build-wasm/".split(),
|
||||||
|
cwd="bergamot-browser-extension-src",
|
||||||
|
)
|
||||||
|
# build the final xpi
|
||||||
|
subprocess.call(
|
||||||
|
"yarn build:firefox-infobar-ui".split(), cwd="bergamot-browser-extension-src"
|
||||||
|
)
|
||||||
|
|
||||||
|
shutil.rmtree("extension", ignore_errors=True)
|
||||||
|
os.mkdir("extension")
|
||||||
|
file_exceptions = [
|
||||||
|
"META-INF",
|
||||||
|
".md",
|
||||||
|
"BRANCH",
|
||||||
|
"COMMITHASH",
|
||||||
|
"LASTCOMMITDATETIME",
|
||||||
|
"VERSION",
|
||||||
|
".map",
|
||||||
|
".yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
fo = open("extension/jar.mn", "w")
|
||||||
|
fo.write(
|
||||||
|
"##### This file was automatically generated by the import_xpi.py script ####\n"
|
||||||
|
)
|
||||||
|
fo.write("# This Source Code Form is subject to the terms of the Mozilla Public\n")
|
||||||
|
fo.write("# License, v. 2.0. If a copy of the MPL was not distributed with this\n")
|
||||||
|
fo.write("# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n")
|
||||||
|
fo.write("browser.jar:\n")
|
||||||
|
fo.write("% resource builtin-addons %builtin-addons/ contentaccessible=yes\n")
|
||||||
|
fo.write(" builtin-addons/translations/ (**)")
|
||||||
|
fo.write("\n")
|
||||||
|
fo.close()
|
||||||
|
|
||||||
|
def isValidFile(filename):
|
||||||
|
for exception in file_exceptions:
|
||||||
|
if exception in filename:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
file_set = set()
|
||||||
|
# read xpi files
|
||||||
|
with ZipFile(local_xpi_file, "r") as zip:
|
||||||
|
namelist = zip.namelist()
|
||||||
|
cleared_namelist = []
|
||||||
|
for filename in namelist:
|
||||||
|
if isValidFile(filename):
|
||||||
|
full_file_path = zip.extract(filename, "extension")
|
||||||
|
if filename.endswith(".js"):
|
||||||
|
filename = "browser/extensions/translations/{}".format(full_file_path)
|
||||||
|
subprocess.call(
|
||||||
|
str(
|
||||||
|
"./mach lint --linter license {} --fix".format(filename)
|
||||||
|
).split(),
|
||||||
|
cwd="../../../",
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Import finalized successfully")
|
|
@ -0,0 +1,4 @@
|
||||||
|
with Files("**"):
|
||||||
|
BUG_COMPONENT = ("Firefox", "Translations")
|
||||||
|
|
||||||
|
JAR_MANIFESTS += ["extension/jar.mn"]
|
|
@ -174,4 +174,9 @@ browser/chrome/browser/skin/classic/browser/zoom-in.svg
|
||||||
|
|
||||||
# Bug 1709445 - De-duplicate history icons
|
# Bug 1709445 - De-duplicate history icons
|
||||||
browser/chrome/browser/skin/classic/browser/history.svg
|
browser/chrome/browser/skin/classic/browser/history.svg
|
||||||
browser/chrome/browser/skin/classic/browser/places/history.svg
|
browser/chrome/browser/skin/classic/browser/places/history.svg
|
||||||
|
|
||||||
|
# Bug 1710546 - Bundle Firefox extension as a builtin addon Nighly only
|
||||||
|
# We plan to remove this duplicity after Firefox Translations become pref'd on
|
||||||
|
browser/chrome/browser/builtin-addons/translations/wasm/cld-worker.js.mem
|
||||||
|
browser/modules/translation/cld-worker.js.mem
|
||||||
|
|
Загрузка…
Ссылка в новой задаче