const Ci = Components.interfaces; const Cc = Components.classes; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource:///modules/LocaleRepository.jsm"); let stringPrefs = [ { selector: "#continue-in-button", pref: "continueIn", data: ["CURRENT_LOCALE"] }, { selector: "#change-language", pref: "choose", data: null }, { selector: "#picker-title", pref: "chooseLanguage", data: null }, { selector: "#continue-button", pref: "continue", data: null }, { selector: "#cancel-button", pref: "cancel", data: null }, { selector: "#installing-message", pref: "installing", data: ["CURRENT_LOCALE"] }, { selector: "#cancel-install-button", pref: "cancel", data: null }, { selector: "#installing-error", pref: "installerror", data: null }, { selector: "#install-continue", pref: "continue", data: null }, { selector: "#loading-label", pref: "loading", data: null } ]; let LocaleUI = { _strings: null, get strings() { if (!this._strings) this._strings = Services.strings.createBundle("chrome://browser/locale/localepicker.properties"); return this._strings; }, set strings(aVal) { this._strings = aVal; }, get mainPage() { delete this.mainPage; return this.mainPage = document.getElementById("main-page"); }, get pickerpage() { delete this.pickerpage; return this.pickerpage = document.getElementById("picker-page"); }, get installerPage() { delete this.installerPage; return this.installerPage = document.getElementById("installer-page"); }, get deck() { delete this.deck; return this.deck = document.getElementById("language-deck"); }, _availableLocales: null, get availableLocales() { if (!this._availableLocales) { let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); chrome.QueryInterface(Ci.nsIToolkitChromeRegistry); let strings = Services.strings.createBundle("chrome://browser/content/languages.properties"); let availableLocales = chrome.getLocalesForPackage("browser"); this._availableLocales = []; while (availableLocales.hasMore()) { let locale = availableLocales.getNext(); let label = locale; try { label = strings.GetStringFromName(locale); } catch (e) { } this._availableLocales.push({ addon: { id: locale, name: label, targetLocale: locale }}); } } return this._availableLocales; }, pendingInstall: null, // used to cancel an install get selectedPanel() { return this.deck.selectedPanel; }, set selectedPanel(aPanel) { this.deck.selectedPanel = aPanel; }, get list() { delete this.list; return this.list = document.getElementById("language-list"); }, _createItem: function lp__createItem(aId, aText, aLocale) { let item = document.createElement("richlistitem"); item.setAttribute("id", aId); let description = document.createElement("description"); description.appendChild(document.createTextNode(aText)); description.setAttribute('flex', 1); item.appendChild(description); item.setAttribute("locale", getTargetLocale(aLocale.addon)); if (aLocale) { item.locale = aLocale.addon; let checkbox = document.createElement("image"); checkbox.classList.add("checkbox"); item.appendChild(checkbox); } else { item.classList.add("message"); } return item; }, addLocales: function lp_addLocales(aLocales) { let fragment = document.createDocumentFragment(); let selectedItem = null; let bestMatch = NO_MATCH; for each (let locale in aLocales) { let targetLocale = getTargetLocale(locale.addon); if (document.querySelector('[locale="' + targetLocale + '"]')) continue; let item = this._createItem(targetLocale, "\u202A" + locale.addon.name + "\u202C", locale); let match = localesMatch(targetLocale, this.locale); if (match > bestMatch) { bestMatch = match; selectedItem = item; } fragment.appendChild(item); } this.list.appendChild(fragment); if (selectedItem && !this.list.selectedItem); this.list.selectedItem = selectedItem; }, loadLocales: function() { while (this.list.firstChild) this.list.removeChild(this.list.firstChild); this.addLocales(this.availableLocales); LocaleRepository.getLocales(this.addLocales.bind(this)); }, showPicker: function() { LocaleUI.selectedPanel = LocaleUI.pickerpage; LocaleUI.loadLocales(); }, closePicker: function() { if (this.pendingInstall) { Services.prefs.setBoolPref("intl.locale.matchOS", false); Services.prefs.setCharPref("general.useragent.locale", getTargetLocale(this.pendingInstall)); } if (window.opener) this.closeWindow(); else this.selectedPanel = this.mainPage; }, _locale: "", set locale(aVal) { Services.prefs.setBoolPref("intl.locale.matchOS", false); Services.prefs.setCharPref("general.useragent.locale", aVal); this._locale = aVal; this.strings = null; this.updateStrings(); }, get locale() { return this._locale; }, set installStatus(aVal) { this.installerPage.selectedPanel = document.getElementById("installer-page-" + aVal); }, clearInstallError: function() { this.installStatus = "installing"; this.selectedPanel = this.pickerpage; }, selectLocale: function(aEvent) { let locale = this.list.selectedItem.locale; if (locale.install) { LocaleUI.strings = new FakeStringBundle(locale); this.updateStrings(locale); } else { this.locale = getTargetLocale(locale); if (this.pendingInstall) this.pendingInstall = null; } }, installAddon: function lp_installAddon() { let locale = LocaleUI.list.selectedItem.locale; if (locale.install) { LocaleUI.pendingInstall = locale; LocaleUI.selectedPanel = LocaleUI.installerPage; locale.install.addListener(installListener); locale.install.install(); } else { this.closeWindow(); } }, goBack: function goBack() { switch (this.selectedPanel) { case this.mainPage: // Do nothing on the "Loading..." screen. break; case this.pickerpage: this.cancelPicker(); break; case this.installerPage: this.cancelInstall(); this.showPicker(); break; } }, cancelPicker: function() { if (this.pendingInstall) this.pendingInstall = null; // restore the last known "good" locale this.locale = this.defaultLocale; this.closePicker(); }, closeWindow : function() { var buildID = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).platformBuildID; Services.prefs.setCharPref("extensions.compatability.locales.buildid", buildID); // Trying to close this window and open a new one results in a corrupt UI. if (!window.opener && LocaleUI.pendingInstall) { // a new locale was installed, restart the browser let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); if (cancelQuit.data == false) { let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eForceQuit); return; } } // just open the window let argString = null; if (window.arguments) { argString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); argString.data = window.arguments.join(","); } if (!Services.wm.getMostRecentWindow("navigator:browser")) Services.ww.openWindow(window, "chrome://browser/content/browser.xul", "_blank", "chrome,dialog=no,all", argString); window.close(); }, cancelInstall: function () { if (LocaleUI.pendingInstall) { let addonInstall = LocaleUI.pendingInstall.install; try { addonInstall.cancel(); } catch(ex) { } LocaleUI.pendingInstall = null; } }, updateStrings: function (aAddon) { stringPrefs.forEach(function(aPref) { if (!aPref.element) aPref.element = document.querySelector(aPref.selector); let string = ""; try { string = getString(aPref.pref, aPref.data, aAddon); } catch(ex) { } aPref.element.textContent = string; }); } } // Gets the target locale for an addon // For now this returns the targetLocale, although if and addon doesn't // specify a targetLocale we could attempt to guess the locale from the addon's name function getTargetLocale(aAddon) { return aAddon.targetLocale; } // Gets a particular string for the passed in locale // Parameters: aStringName - The name of the string property to get // aDataset - an array of properties to use in a formatted string // aAddon - An addon to attempt to get dataset properties from function getString(aStringName, aDataSet, aAddon) { if (aDataSet) { let databundle = aDataSet.map(function(aData) { switch (aData) { case "CURRENT_LOCALE" : if (aAddon) return aAddon.name; try { return LocaleUI.strings.GetStringFromName("name"); } catch(ex) { return null; } break; default : } return ""; }); if (databundle.some(function(aItem) aItem === null)) throw("String not found"); return LocaleUI.strings.formatStringFromName(aStringName, databundle, databundle.length); } return LocaleUI.strings.GetStringFromName(aStringName); } let installListener = { onNewInstall: function(install) { }, onDownloadStarted: function(install) { }, onDownloadProgress: function(install) { }, onDownloadEnded: function(install) { }, onDownloadCancelled: function(install) { LocaleUI.cancelInstall(); LocaleUI.showPicker(); }, onDownloadFailed: function(install) { LocaleUI.cancelInstall(); LocaleUI.installStatus = "error"; }, onInstallStarted: function(install) { }, onInstallEnded: function(install, addon) { LocaleUI.locale = getTargetLocale(LocaleUI.pendingInstall); LocaleUI.closeWindow(); }, onInstallCancelled: function(install) { LocaleUI.cancelInstall(); LocaleUI.showPicker(); }, onInstallFailed: function(install) { LocaleUI.cancelInstall(); LocaleUI.installStatus = "error"; }, onExternalInstall: function(install, existingAddon, needsRestart) { } } const PERFECT_MATCH = 2; const GOOD_MATCH = 1; const NO_MATCH = 0; //Compares two locales of the form AB or AB-CD //returns GOOD_MATCH if AB == AB in both locales, PERFECT_MATCH if AB-CD == AB-CD function localesMatch(aLocale1, aLocale2) { if (aLocale1 == aLocale2) return PERFECT_MATCH; let short1 = aLocale1.split("-")[0]; let short2 = aLocale2.split("-")[0]; return (short1 == short2) ? GOOD_MATCH : NO_MATCH; } function start() { let mouseModule = new MouseModule(); // if we have gotten this far, we can assume that we don't have anything matching the system // locale and we should show the locale picker LocaleUI.mainPage.setAttribute("mode", "loading"); let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry); chrome.QueryInterface(Ci.nsIToolkitChromeRegistry); LocaleUI._locale = chrome.getSelectedLocale("browser"); LocaleUI.updateStrings(); // if we haven't gotten the list of available locales from AMO within 5 seconds, we give up // users can try downloading the list again by selecting "Choose another locale" let timeout = setTimeout(function() { LocaleUI.mainPage.removeAttribute("mode"); timeout = null; }, 5000); // update the page strings and show the correct page LocaleUI.defaultLocale = LocaleUI._locale; window.addEventListener("resize", resizeHandler, false); // if we have an opener, we are probably coming from the prefs pane // and can jump straight to the list of languages if (window.opener) { LocaleUI.updateStrings(); LocaleUI.showPicker(); resizeHandler(); return; } // Look on AMO for something that matches the system locale LocaleRepository.getLocales(function lp_initalDownload(aLocales) { if (!LocaleUI.mainPage.hasAttribute("mode")) return; clearTimeout(timeout); LocaleUI.mainPage.removeAttribute("mode"); let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].getService(Ci.nsILocaleService); let currentLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE"); if (Services.prefs.prefHasUserValue("general.useragent.locale")) { currentLocale = Services.prefs.getCharPref("general.useragent.locale"); } let match = NO_MATCH; let matchingLocale = null; for each (let locale in aLocales) { let targetLocale = getTargetLocale(locale.addon); let newMatch = localesMatch(targetLocale, currentLocale); if (newMatch > match) { matchingLocale = locale; match = newMatch; } } if (matchingLocale) { // if we found something, try to install it automatically AddonManager.getAddonByID(matchingLocale.addon.id, function (aAddon) { // if this locale is already installed, but is user disabled, // bail out of here. if (aAddon && aAddon.userDisabled) { Services.prefs.clearUserPref("general.useragent.locale"); LocaleUI.closeWindow(); return; } // if we found something, try to install it automatically LocaleUI.strings = new FakeStringBundle(matchingLocale.addon); LocaleUI.updateStrings(); LocaleUI.pendingInstall = matchingLocale.addon; LocaleUI.selectedPanel = LocaleUI.installerPage; matchingLocale.addon.install.addListener(installListener); matchingLocale.addon.install.install(); }); } }); } function resizeHandler() { let elements = document.getElementsByClassName("window-width"); for (let i = 0; i < elements.length; i++) elements[i].setAttribute("width", Math.min(800, window.innerWidth)); } function FakeStringBundle(aAddon) { let regex = /(.*)(?:=)(.*)/g; this.strings = {}; let res = regex.exec(aAddon.strings); while (res) { this.strings[res[1].trim()] = res[2].trim(); res = regex.exec(aAddon.strings); } } FakeStringBundle.prototype = { formatStringFromName: function(aName, aParams, aLength) { let txt = this.strings[aName]; for (var i = 0; i < aLength; i++) { txt = txt.replace("%S", aParams[i]); } return txt; }, GetStringFromName: function(aName) { return this.strings[aName]; } }