Bug 971048 - Run language detection on webpages and display infobar when language is not the current UI locale, r=felipe.

This commit is contained in:
Florian Quèze 2014-04-17 00:02:33 +02:00
parent 4647de30f2
commit d6b7a6b944
6 changed files with 142 additions and 67 deletions

View File

@ -1425,6 +1425,8 @@ pref("browser.cache.auto_delete_cache_version", 1);
// -1 means no experiment is run and we use the preferred value for frecency (6h) // -1 means no experiment is run and we use the preferred value for frecency (6h)
pref("browser.cache.frecency_experiment", 0); pref("browser.cache.frecency_experiment", 0);
pref("browser.translation.detectLanguage", false);
// Telemetry experiments settings. // Telemetry experiments settings.
pref("experiments.enabled", false); pref("experiments.enabled", false);
pref("experiments.manifest.fetchIntervalSeconds", 86400); pref("experiments.manifest.fetchIntervalSeconds", 86400);

View File

@ -147,6 +147,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"); "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Translation",
"resource:///modules/translation/Translation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions", XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
"resource:///modules/SitePermissions.jsm"); "resource:///modules/SitePermissions.jsm");
@ -1033,6 +1036,7 @@ var gBrowserInit = {
gFormSubmitObserver.init(); gFormSubmitObserver.init();
gRemoteTabsUI.init(); gRemoteTabsUI.init();
gPageStyleMenu.init(); gPageStyleMenu.init();
LanguageDetectionListener.init();
// Initialize the full zoom setting. // Initialize the full zoom setting.
// We do this before the session restore service gets initialized so we can // We do this before the session restore service gets initialized so we can
@ -5342,6 +5346,15 @@ function setStyleDisabled(disabled) {
} }
var LanguageDetectionListener = {
init: function() {
window.messageManager.addMessageListener("LanguageDetection:Result", msg => {
Translation.languageDetected(msg.target, msg.data);
});
}
};
var BrowserOffline = { var BrowserOffline = {
_inited: false, _inited: false,

View File

@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler", XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm"); "resource:///modules/ContentLinkHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm"); "resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils", XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
@ -387,3 +389,49 @@ let PageStyleHandler = {
}, },
}; };
PageStyleHandler.init(); PageStyleHandler.init();
let TranslationHandler = {
init: function() {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
},
/* nsIWebProgressListener implementation */
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (!aWebProgress.isTopLevel ||
!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP))
return;
let url = aRequest.name;
if (!url.startsWith("http://") && !url.startsWith("https://"))
return;
// Grab a 60k sample of text from the page.
let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
.createInstance(Ci.nsIDocumentEncoder);
encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
let string = encoder.encodeToStringWithMaxLength(60 * 1024);
// Language detection isn't reliable on very short strings.
if (string.length < 100)
return;
LanguageDetector.detectLanguage(string).then(result => {
if (result.confident)
sendAsyncMessage("LanguageDetection:Result", result.language);
});
},
// Unused methods.
onProgressChange: function() {},
onLocationChange: function() {},
onStatusChange: function() {},
onSecurityChange: function() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
if (Services.prefs.getBoolPref("browser.translation.detectLanguage"))
TranslationHandler.init();

View File

@ -11,42 +11,11 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
/* Create an object keeping the information related to translation for this.Translation = {
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
* - supportedSourceLanguages, array of supported source language codes
* - supportedTargetLanguages, array of supported target language codes
* - detectedLanguage, code of the language detected on the web page.
* - defaultTargetLanguage, code of the language to use by default for
* translation.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
this.Translation = function(aBrowser) {
this.browser = aBrowser;
};
this.Translation.prototype = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"], supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"], supportedTargetLanguages: ["en", "pl", "tr", "vi"],
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
_defaultTargetLanguage: "", _defaultTargetLanguage: "",
get defaultTargetLanguage() { get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) { if (!this._defaultTargetLanguage) {
@ -58,6 +27,43 @@ this.Translation.prototype = {
return this._defaultTargetLanguage; return this._defaultTargetLanguage;
}, },
languageDetected: function(aBrowser, aDetectedLanguage) {
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) != -1 &&
aDetectedLanguage != this.defaultTargetLanguage) {
if (!aBrowser.translationUI)
aBrowser.translationUI = new TranslationUI(aBrowser);
aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
}
}
};
/* TranslationUI objects keep the information related to translation for
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
* - detectedLanguage, code of the language detected on the web page.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
function TranslationUI(aBrowser) {
this.browser = aBrowser;
}
TranslationUI.prototype = {
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
get doc() this.browser.contentDocument, get doc() this.browser.contentDocument,
translate: function(aFrom, aTo) { translate: function(aFrom, aTo) {

View File

@ -29,8 +29,6 @@ function waitForCondition(condition, nextTest, errorMsg) {
} }
var TranslationStub = { var TranslationStub = {
__proto__: Translation.prototype,
translate: function(aFrom, aTo) { translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING; this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom; this.translatedFrom = aFrom;
@ -54,6 +52,14 @@ var TranslationStub = {
} }
}; };
function showTranslationUI(aDetectedLanguage) {
let browser = gBrowser.selectedBrowser;
Translation.languageDetected(browser, aDetectedLanguage);
let ui = browser.translationUI;
for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
ui[name] = TranslationStub[name];
return ui.notificationBox.getNotificationWithValue("translation");
}
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
@ -83,35 +89,35 @@ function checkURLBarIcon(aExpectTranslated = false) {
function run_tests(aFinishCallback) { function run_tests(aFinishCallback) {
info("Show an info bar saying the current page is in French"); info("Show an info bar saying the current page is in French");
let notif = TranslationStub.showTranslationUI("fr"); let notif = showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation"); is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed"); is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
checkURLBarIcon(); checkURLBarIcon();
info("Click the 'Translate' button"); info("Click the 'Translate' button");
notif._getAnonElt("translate").click(); notif._getAnonElt("translate").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state"); is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called"); ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct"); is(notif.translation.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct"); is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
checkURLBarIcon(); checkURLBarIcon();
info("Make the translation fail and check we are in the error state."); info("Make the translation fail and check we are in the error state.");
TranslationStub.failTranslation(); notif.translation.failTranslation();
is(notif.state, TranslationStub.STATE_ERROR, "infobar in the error state"); is(notif.state, notif.translation.STATE_ERROR, "infobar in the error state");
checkURLBarIcon(); checkURLBarIcon();
info("Click the try again button"); info("Click the try again button");
notif._getAnonElt("tryAgain").click(); notif._getAnonElt("tryAgain").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state"); is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called"); ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct"); is(notif.translation.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct"); is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
checkURLBarIcon(); checkURLBarIcon();
info("Make the translation succeed and check we are in the 'translated' state."); info("Make the translation succeed and check we are in the 'translated' state.");
TranslationStub.finishTranslation(); notif.translation.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATED, "infobar in the translated state"); is(notif.state, notif.translation.STATE_TRANSLATED, "infobar in the translated state");
checkURLBarIcon(true); checkURLBarIcon(true);
info("Test 'Show original' / 'Show Translation' buttons."); info("Test 'Show original' / 'Show Translation' buttons.");
@ -137,45 +143,45 @@ function run_tests(aFinishCallback) {
let from = notif._getAnonElt("fromLanguage"); let from = notif._getAnonElt("fromLanguage");
from.value = "es"; from.value = "es";
from.doCommand(); from.doCommand();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state"); is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called"); ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct"); is(notif.translation.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "to language correct"); is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "to language correct");
// We want to show the 'translated' icon while re-translating, // We want to show the 'translated' icon while re-translating,
// because we are still displaying the previous translation. // because we are still displaying the previous translation.
checkURLBarIcon(true); checkURLBarIcon(true);
TranslationStub.finishTranslation(); notif.translation.finishTranslation();
checkURLBarIcon(true); checkURLBarIcon(true);
info("Check that changing the target language causes a re-translation"); info("Check that changing the target language causes a re-translation");
let to = notif._getAnonElt("toLanguage"); let to = notif._getAnonElt("toLanguage");
to.value = "pl"; to.value = "pl";
to.doCommand(); to.doCommand();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state"); is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called"); ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct"); is(notif.translation.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, "pl", "to language correct"); is(notif.translation.translatedTo, "pl", "to language correct");
checkURLBarIcon(true); checkURLBarIcon(true);
TranslationStub.finishTranslation(); notif.translation.finishTranslation();
checkURLBarIcon(true); checkURLBarIcon(true);
// Cleanup. // Cleanup.
notif.close(); notif.close();
info("Reopen the info bar to check that it's possible to override the detected language."); info("Reopen the info bar to check that it's possible to override the detected language.");
notif = TranslationStub.showTranslationUI("fr"); notif = showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation"); is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed"); is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
// Change the language and click 'Translate' // Change the language and click 'Translate'
notif._getAnonElt("detectedLanguage").value = "ja"; notif._getAnonElt("detectedLanguage").value = "ja";
notif._getAnonElt("translate").click(); notif._getAnonElt("translate").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state"); is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called"); ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "ja", "from language correct"); is(notif.translation.translatedFrom, "ja", "from language correct");
notif.close(); notif.close();
info("Reopen to check the 'Not Now' button closes the notification."); info("Reopen to check the 'Not Now' button closes the notification.");
notif = TranslationStub.showTranslationUI("fr"); notif = showTranslationUI("fr");
let notificationBox = gBrowser.getNotificationBox(); let notificationBox = gBrowser.getNotificationBox();
ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification"); ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
notif._getAnonElt("notNow").click(); notif._getAnonElt("notNow").click();

View File

@ -102,7 +102,7 @@
// Fill the lists of supported source languages. // Fill the lists of supported source languages.
let detectedLanguage = this._getAnonElt("detectedLanguage"); let detectedLanguage = this._getAnonElt("detectedLanguage");
let fromLanguage = this._getAnonElt("fromLanguage"); let fromLanguage = this._getAnonElt("fromLanguage");
for (let code of this.translation.supportedSourceLanguages) { for (let code of Translation.supportedSourceLanguages) {
let name = bundle.GetStringFromName(code); let name = bundle.GetStringFromName(code);
detectedLanguage.appendItem(name, code); detectedLanguage.appendItem(name, code);
fromLanguage.appendItem(name, code); fromLanguage.appendItem(name, code);
@ -115,7 +115,7 @@
// Fill the list of supporter target languages. // Fill the list of supporter target languages.
let toLanguage = this._getAnonElt("toLanguage"); let toLanguage = this._getAnonElt("toLanguage");
for (let code of this.translation.supportedTargetLanguages) for (let code of Translation.supportedTargetLanguages)
toLanguage.appendItem(bundle.GetStringFromName(code), code); toLanguage.appendItem(bundle.GetStringFromName(code), code);
if (aTranslation.translatedTo) if (aTranslation.translatedTo)
@ -143,7 +143,7 @@
this._getAnonElt("fromLanguage").value = this._getAnonElt("fromLanguage").value =
this._getAnonElt("detectedLanguage").value; this._getAnonElt("detectedLanguage").value;
this._getAnonElt("toLanguage").value = this._getAnonElt("toLanguage").value =
this.translation.defaultTargetLanguage; Translation.defaultTargetLanguage;
} }
this._handleButtonHiding(false); this._handleButtonHiding(false);