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)
pref("browser.cache.frecency_experiment", 0);
pref("browser.translation.detectLanguage", false);
// Telemetry experiments settings.
pref("experiments.enabled", false);
pref("experiments.manifest.fetchIntervalSeconds", 86400);

View File

@ -147,6 +147,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Translation",
"resource:///modules/translation/Translation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
"resource:///modules/SitePermissions.jsm");
@ -1033,6 +1036,7 @@ var gBrowserInit = {
gFormSubmitObserver.init();
gRemoteTabsUI.init();
gPageStyleMenu.init();
LanguageDetectionListener.init();
// Initialize the full zoom setting.
// 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 = {
_inited: false,

View File

@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
@ -387,3 +389,49 @@ let PageStyleHandler = {
},
};
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/Services.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
* 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 = {
this.Translation = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
_defaultTargetLanguage: "",
get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) {
@ -58,6 +27,43 @@ this.Translation.prototype = {
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,
translate: function(aFrom, aTo) {

View File

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

View File

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