gecko/mobile/chrome/content/localePicker.js

468 lines
15 KiB
JavaScript

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, locale.addon.name, 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];
}
}