Bug 1509977 - Port bugs 1469696, 1505594, 1493711, 1505594, 1509080 and 1488442. r=jorgk

Bug 1469696: Support installing official language packs from AMO
Bug 1505594: Removing a requested locale puts it ahead of its label in the available dropdown
Bug 1493711: Pref off downloading langpacks outside of release
Bug 1505594: Put removed browser locale in the right order
Bug 1509080: Clear language change confirmation content on hide
Bug 1488442: Support disabled language packs in multilingual UI
This commit is contained in:
Richard Marti 2018-11-26 17:50:51 +01:00
Родитель d077228c6b
Коммит 40325e2253
8 изменённых файлов: 372 добавлений и 56 удалений

Просмотреть файл

@ -804,3 +804,6 @@ pref("intl.regional_prefs.use_os_locales", true);
// Multi-lingual preferences
pref("intl.multilingual.enabled", false);
// We don't support yet language pack download from ATN
pref("intl.multilingual.downloadEnabled", false);

Просмотреть файл

@ -39,6 +39,7 @@
<link rel="localization" href="branding/brand.ftl"/>
<link rel="localization" href="messenger/preferences/preferences.ftl"/>
<link rel="localization" href="messenger/preferences/fonts.ftl"/>
<link rel="localization" href="messenger/preferences/languages.ftl"/>
</linkset>
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>

Просмотреть файл

@ -194,7 +194,7 @@
<button id="manageMessengerLanguagesButton"
class="accessory-button"
data-l10n-id="manage-messenger-languages-button"
oncommand="gAdvancedPane.showMessengerLanguages()"/>
oncommand="gAdvancedPane.showMessengerLanguages({search: false})"/>
</hbox>
</vbox>
<hbox id="confirmMessengerLanguage"

Просмотреть файл

@ -548,9 +548,18 @@ var gAdvancedPane = {
},
initMessengerLocale() {
let localeCodes = Services.locale.availableLocales;
let localeNames = Services.intl.getLocaleDisplayNames(undefined, localeCodes);
let locales = localeCodes.map((code, i) => ({code, name: localeNames[i]}));
gAdvancedPane.setMessengerLocales(Services.locale.requestedLocale);
},
/**
* Update the available list of locales and select the locale that the user
* is "selecting". This could be the currently requested locale or a locale
* that the user would like to switch to after confirmation.
*/
async setMessengerLocales(selected) {
let available = Services.locale.availableLocales;
let localeNames = Services.intl.getLocaleDisplayNames(undefined, available);
let locales = available.map((code, i) => ({code, name: localeNames[i]}));
locales.sort((a, b) => a.name > b.name);
let fragment = document.createDocumentFragment();
@ -560,31 +569,50 @@ var gAdvancedPane = {
menuitem.setAttribute("label", name);
fragment.appendChild(menuitem);
}
// Add an option to search for more languages if downloading is supported.
if (Services.prefs.getBoolPref("intl.multilingual.downloadEnabled")) {
let menuitem = document.createXULElement("menuitem");
menuitem.id = "defaultBrowserLanguageSearch";
menuitem.setAttribute(
"label", await document.l10n.formatValue("messenger-languages-search"));
menuitem.setAttribute("value", "search");
menuitem.addEventListener("command", () => {
gMainPane.showBrowserLanguages({search: true});
});
fragment.appendChild(menuitem);
}
let menulist = document.getElementById("defaultMessengerLanguage");
let menupopup = menulist.querySelector("menupopup");
menupopup.textContent = "";
menupopup.appendChild(fragment);
menulist.value = Services.locale.requestedLocale;
menulist.value = selected;
document.getElementById("messengerLanguagesBox").hidden = false;
},
showMessengerLanguages() {
showMessengerLanguages({search}) {
let opts = {selected: gAdvancedPane.selectedLocales, search};
gSubDialog.open(
"chrome://messenger/content/preferences/messengerLanguages.xul",
null, this.requestingLocales, this.messengerLanguagesClosed);
null, opts, this.messengerLanguagesClosed);
},
/* Show or hide the confirm change message bar based on the updated ordering. */
messengerLanguagesClosed() {
let requesting = this.gMessengerLanguagesDialog.requestedLocales;
let requested = Services.locale.requestedLocales;
let defaultMessengerLanguage = document.getElementById("defaultMessengerLanguage");
if (requesting && requesting.join(",") != requested.join(",")) {
gAdvancedPane.showConfirmLanguageChangeMessageBar(requesting);
defaultMessengerLanguage.value = requesting[0];
let selected = this.gMessengerLanguagesDialog.selected;
let active = Services.locale.appLocalesAsBCP47;
// Prepare for changing the locales if they are different than the current locales.
if (selected && selected.join(",") != active.join(",")) {
gAdvancedPane.showConfirmLanguageChangeMessageBar(selected);
gAdvancedPane.setMessengerLocales(selected[0]);
return;
}
defaultMessengerLanguage.value = Services.locale.requestedLocale;
// They matched, so we can reset the UI.
gAdvancedPane.setMessengerLocales(Services.locale.appLocaleAsBCP47);
gAdvancedPane.hideConfirmLanguageChangeMessageBar();
},
@ -633,13 +661,14 @@ var gAdvancedPane = {
}
messageBar.hidden = false;
this.requestingLocales = locales;
this.selectedLocales = locales;
},
hideConfirmLanguageChangeMessageBar() {
let messageBar = document.getElementById("confirmMessengerLanguage");
messageBar.hidden = true;
messageBar.querySelector(".message-bar-button").removeAttribute("locales");
let contentContainer = messageBar.querySelector(".message-bar-content-container");
contentContainer.textContent = "";
this.requestingLocales = null;
},
@ -663,10 +692,14 @@ var gAdvancedPane = {
/* Show or hide the confirm change message bar based on the new locale. */
onMessengerLanguageChange(event) {
let locale = event.target.value;
if (locale == Services.locale.requestedLocale) {
if (locale == "search") {
return;
} else if (locale == Services.locale.appLocaleAsBCP47) {
this.hideConfirmLanguageChangeMessageBar();
return;
}
let locales = Array.from(new Set([
locale,
...Services.locale.requestedLocales,

Просмотреть файл

@ -4,6 +4,23 @@
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
ChromeUtils.defineModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
/* This dialog provides an interface for managing what language the browser is
* displayed in.
*
* There is a list of "requested" locales and a list of "available" locales. The
* requested locales must be installed and enabled. Available locales could be
* installed and enabled, or fetched from the AMO language tools API.
*
* If a langpack is disabled, there is no way to determine what locale it is for and
* it will only be listed as available if that locale is also available on AMO and
* the user has opted to search for more languages.
*/
class OrderedListBox {
constructor({richlistbox, upButton, downButton, removeButton, onRemove}) {
this.richlistbox = richlistbox;
@ -128,13 +145,18 @@ class OrderedListBox {
}
class SortedItemSelectList {
constructor({menulist, button, onSelect}) {
constructor({menulist, button, onSelect, onChange, compareFn}) {
this.menulist = menulist;
this.popup = menulist.firstElementChild;
this.button = button;
this.compareFn = compareFn;
this.items = [];
menulist.addEventListener("command", () => {
button.disabled = !menulist.selectedItem;
if (menulist.selectedItem) {
onChange(this.items[menulist.selectedIndex]);
}
});
button.addEventListener("command", () => {
if (!menulist.selectedItem) return;
@ -144,18 +166,19 @@ class SortedItemSelectList {
menulist.setAttribute("label", menulist.getAttribute("placeholder"));
button.disabled = true;
menulist.disabled = menulist.itemCount == 0;
menulist.selectedIndex = -1;
onSelect(item);
});
}
setItems(items) {
this.items = items.sort((a, b) => a.label > b.label);
this.items = items.sort(this.compareFn);
this.populate();
}
populate() {
let {items, menulist, popup} = this;
let {button, items, menulist, popup} = this;
popup.textContent = "";
let frag = document.createDocumentFragment();
@ -166,6 +189,8 @@ class SortedItemSelectList {
menulist.setAttribute("label", menulist.getAttribute("placeholder"));
menulist.disabled = menulist.itemCount == 0;
menulist.selectedIndex = -1;
button.disabled = true;
}
/**
@ -174,27 +199,51 @@ class SortedItemSelectList {
* @param {object} item The item to insert.
*/
addItem(item) {
let {items, menulist, popup} = this;
let i;
let {compareFn, items, menulist, popup} = this;
// Find the index of the item to insert before.
for (i = 0; i < items.length && items[i].label < item.label; i++)
;
let i = items.findIndex(el => compareFn(el, item) >= 0);
items.splice(i, 0, item);
popup.insertBefore(this.createItem(item), menulist.getItemAtIndex(i));
menulist.disabled = menulist.itemCount == 0;
}
createItem({label, value}) {
createItem({label, value, className, disabled}) {
let item = document.createElement("menuitem");
item.value = value;
item.setAttribute("label", label);
if (className)
item.classList.add(className);
if (disabled)
item.setAttribute("disabled", "true");
return item;
}
/**
* Disable the inputs and set a data-l10n-id on the menulist. This can be
* reverted with `enableWithMessageId()`.
*/
disableWithMessageId(messageId) {
this.menulist.setAttribute("data-l10n-id", messageId);
this.menulist.setAttribute("image", "chrome://global/skin/icons/loading.png");
this.menulist.disabled = true;
this.button.disabled = true;
}
/**
* Enable the inputs and set a data-l10n-id on the menulist. This can be
* reverted with `disableWithMessageId()`.
*/
enableWithMessageId(messageId) {
this.menulist.setAttribute("data-l10n-id", messageId);
this.menulist.removeAttribute("image");
this.menulist.disabled = this.menulist.itemCount == 0;
this.button.disabled = !this.menulist.selectedItem;
}
}
function getLocaleDisplayInfo(localeCodes) {
let availableLocales = new Set(Services.locale.availableLocales);
let packagedLocales = new Set(Services.locale.packagedLocales);
let localeNames = Services.intl.getLocaleDisplayNames(undefined, localeCodes);
return localeCodes.map((code, i) => {
@ -203,51 +252,241 @@ function getLocaleDisplayInfo(localeCodes) {
label: localeNames[i],
value: code,
canRemove: !packagedLocales.has(code),
installed: availableLocales.has(code),
};
});
}
function compareItems(a, b) {
// Sort by installed.
if (a.installed != b.installed) {
return a.installed ? -1 : 1;
// The search label is always last.
} else if (a.value == "search") {
return 1;
} else if (b.value == "search") {
return -1;
// If both items are locales, sort by label.
} else if (a.value && b.value) {
return a.label.localeCompare(b.label);
// One of them is a label, put it first.
} else if (a.value) {
return 1;
}
return -1;
}
var gMessengerLanguagesDialog = {
_availableLocales: null,
_requestedLocales: null,
requestedLocales: null,
_selectedLocales: null,
selectedLocales: null,
get downloadEnabled() {
// Downloading langpacks isn't always supported, check the pref.
return Services.prefs.getBoolPref("intl.multilingual.downloadEnabled");
},
beforeAccept() {
this.requestedLocales = this._requestedLocales.items.map(item => item.value);
this.selected = this.getSelectedLocales();
return true;
},
onLoad() {
// Maintain the previously requested locales even if we cancel out.
this.requestedLocales = window.arguments[0];
async onLoad() {
// Maintain the previously selected locales even if we cancel out.
let {selected, search} = window.arguments[0] || {};
this.selectedLocales = selected;
let requested = this.requestedLocales || Services.locale.requestedLocales;
let requestedSet = new Set(requested);
let available = Services.locale.availableLocales
.filter(locale => !requestedSet.has(locale));
// This is a list of available locales that the user selected. It's more
// restricted than the Intl notion of `requested` as it only contains
// locale codes for which we have matching locales available.
// The first time this dialog is opened, populate with appLocalesAsBCP47.
let selectedLocales = this.selectedLocales || Services.locale.appLocalesAsBCP47;
let selectedLocaleSet = new Set(selectedLocales);
let available = Services.locale.availableLocales;
let availableSet = new Set(available);
// Filter selectedLocales since the user may select a locale when it is
// available and then disable it.
selectedLocales = selectedLocales.filter(locale => availableSet.has(locale));
// Nothing in available should be in selectedSet.
available = available.filter(locale => !selectedLocaleSet.has(locale));
this.initSelectedLocales(selectedLocales);
await this.initAvailableLocales(available, search);
this.initRequestedLocales(requested);
this.initAvailableLocales(available);
this.initialized = true;
},
initRequestedLocales(requested) {
this._requestedLocales = new OrderedListBox({
richlistbox: document.getElementById("requestedLocales"),
initSelectedLocales(selectedLocales) {
this._selectedLocales = new OrderedListBox({
richlistbox: document.getElementById("selectedLocales"),
upButton: document.getElementById("up"),
downButton: document.getElementById("down"),
removeButton: document.getElementById("remove"),
onRemove: (item) => this._availableLocales.addItem(item),
onRemove: (item) => this.selectedLocaleRemoved(item),
});
this._requestedLocales.setItems(getLocaleDisplayInfo(requested));
this._selectedLocales.setItems(getLocaleDisplayInfo(selectedLocales));
},
initAvailableLocales(available) {
async initAvailableLocales(available, search) {
this._availableLocales = new SortedItemSelectList({
menulist: document.getElementById("availableLocales"),
button: document.getElementById("add"),
onSelect: (item) => this._requestedLocales.addItem(item),
compareFn: compareItems,
onSelect: (item) => this.availableLanguageSelected(item),
onChange: (item) => {
this.hideError();
if (item.value == "search") {
this.loadLocalesFromAMO();
}
},
});
this._availableLocales.setItems(getLocaleDisplayInfo(available));
// Populate the list with the installed locales even if the user is
// searching in case the download fails.
await this.loadLocalesFromInstalled(available);
// If the user opened this from the "Search for more languages" option,
// search AMO for available locales.
if (search) {
return this.loadLocalesFromATN();
}
return undefined;
},
async loadLocalesFromATN() {
if (!this.downloadEnabled) {
return;
}
// Disable the dropdown while we hit the network.
this._availableLocales.disableWithMessageId("messenger-languages-searching");
// Fetch the available langpacks from AMO.
let availableLangpacks;
try {
availableLangpacks = await AddonRepository.getAvailableLangpacks();
} catch (e) {
this.showError();
return;
}
// Store the available langpack info for later use.
this.availableLangpacks = new Map();
for (let {target_locale, url, hash} of availableLangpacks) {
this.availableLangpacks.set(target_locale, {url, hash});
}
// Remove the installed locales from the available ones.
let installedLocales = new Set(Services.locale.availableLocales);
let notInstalledLocales = availableLangpacks
.filter(({target_locale}) => !installedLocales.has(target_locale))
.map(lang => lang.target_locale);
// Create the rows for the remote locales.
let availableItems = getLocaleDisplayInfo(notInstalledLocales);
availableItems.push({
label: await document.l10n.formatValue("messenger-languages-available-label"),
className: "label-item",
disabled: true,
installed: false,
});
// Remove the search option and add the remote locales.
let items = this._availableLocales.items;
items.pop();
items = items.concat(availableItems);
// Update the dropdown and enable it again.
this._availableLocales.setItems(items);
this._availableLocales.enableWithMessageId("messenger-languages-select-language");
},
async loadLocalesFromInstalled(available) {
let items;
if (available.length > 0) {
items = getLocaleDisplayInfo(available);
items.push(await this.createInstalledLabel());
} else {
items = [];
}
if (this.downloadEnabled) {
items.push({
label: await document.l10n.formatValue("messenger-languages-search"),
value: "search",
});
}
this._availableLocales.setItems(items);
},
async availableLanguageSelected(item) {
let available = new Set(Services.locale.availableLocales);
if (available.has(item.value)) {
this._selectedLocales.addItem(item);
if (available.size == this._selectedLocales.items.length) {
// Remove the installed label, they're all installed.
this._availableLocales.items.shift();
this._availableLocales.setItems(this._availableLocales.items);
}
} else if (this.availableLangpacks.has(item.value)) {
this._availableLocales.disableWithMessageId("messenger-languages-downloading");
let {url, hash} = this.availableLangpacks.get(item.value);
let install = await AddonManager.getInstallForURL(
url, "application/x-xpinstall", hash);
try {
await install.install();
} catch (e) {
this.showError();
return;
}
item.installed = true;
this._selectedLocales.addItem(item);
this._availableLocales.enableWithMessageId("messenger-languages-select-language");
} else {
this.showError();
}
},
showError() {
document.querySelectorAll(".warning-message-separator")
.forEach(separator => separator.classList.add("thin"));
document.getElementById("warning-message").hidden = false;
this._availableLocales.enableWithMessageId("messenger-languages-select-language");
},
hideError() {
document.querySelectorAll(".warning-message-separator")
.forEach(separator => separator.classList.remove("thin"));
document.getElementById("warning-message").hidden = true;
},
getSelectedLocales() {
return this._selectedLocales.items.map(item => item.value);
},
async selectedLocaleRemoved(item) {
this._availableLocales.addItem(item);
// If the item we added is at the top of the list, it needs the label.
if (this._availableLocales.items[0] == item) {
this._availableLocales.addItem(await this.createInstalledLabel());
}
},
async createInstalledLabel() {
return {
label: await document.l10n.formatValue("messenger-languages-installed-label"),
className: "label-item",
disabled: true,
installed: true,
};
},
};

Просмотреть файл

@ -35,27 +35,32 @@
</columns>
<rows>
<row flex="1">
<richlistbox id="requestedLocales" flex="1"/>
<richlistbox id="selectedLocales" flex="1"/>
<vbox>
<button id="up" disabled="true" data-l10n-id="languages-customize-moveup"/>
<button id="down" disabled="true" data-l10n-id="languages-customize-movedown"/>
<button id="remove" disabled="true" data-l10n-id="languages-customize-remove"/>
<button id="up" class="action-button" disabled="true" data-l10n-id="languages-customize-moveup"/>
<button id="down" class="action-button" disabled="true" data-l10n-id="languages-customize-movedown"/>
<button id="remove" class="action-button" disabled="true" data-l10n-id="languages-customize-remove"/>
</vbox>
</row>
<row>
<menulist id="availableLocales"
class="available-locales-list"
data-l10n-id="languages-customize-select-language"
data-l10n-attrs="placeholder">
data-l10n-id="messenger-languages-select-language"
data-l10n-attrs="placeholder,label">
<menupopup/>
</menulist>
<button id="add"
class="add-browser-language"
class="add-messenger-language action-button"
data-l10n-id="languages-customize-add"
disabled="true"/>
</row>
</rows>
</grid>
<separator/>
<separator class="warning-message-separator"/>
<hbox id="warning-message" class="message-bar message-bar-warning" hidden="true">
<image class="message-bar-icon"/>
<description class="message-bar-description" data-l10n-id="messenger-languages-error"/>
</hbox>
<separator class="warning-message-separator"/>
</vbox>
</dialog>

Просмотреть файл

@ -26,3 +26,20 @@ messenger-languages-window =
.style = width: 40em
messenger-languages-description = { -brand-short-name } will display the first language as your default and will display alternate languages if necessary in the order they appear.
messenger-languages-search = Search for more languages…
messenger-languages-searching =
.label = Searching for languages…
messenger-languages-downloading =
.label = Downloading…
messenger-languages-select-language =
.label = Select a language to add…
.placeholder = Select a language to add…
messenger-languages-installed-label = Installed languages
messenger-languages-available-label = Available languages
messenger-languages-error = { -brand-short-name } can't update your languages right now. Check that you are connected to the internet or try again.

Просмотреть файл

@ -420,13 +420,31 @@ richlistbox:focus > richlistitem[selected="true"] {
min-width: 20em;
}
#MessengerLanguagesDialog > .dialog-button-box > .dialog-button[dlgtype="accept"] {
margin-inline-end: 0;
}
#availableLocales {
margin: 0;
margin-inline-end: 4px;
}
.add-browser-language {
margin: 0 4px;
#warning-message > .message-bar-description {
width: 32em;
}
.add-messenger-language {
margin: 0;
margin-inline-start: 4px;
}
.action-button {
margin-inline-end: 0;
}
/* Menulist styles */
.label-item {
font-size: .8em;
}
/**