Bug 1469684 - Allow switching between installed locales r=flod,jaws
authorMark Striemer <mstriemer@mozilla.com>
Thu, 21 Jun 2018 17:16:25 -0500
changeset 428801 ff16973184c057c56849ec0bba80742205ea05df
parent 428800 57adf58922e0bcaccdde06aa739a0d290cce9f98
child 428802 c3309f635b9028f3475f1498b60780cb55a9d1f2
push id34345
push userrgurzau@mozilla.com
push dateSat, 28 Jul 2018 09:44:50 +0000
treeherdermozilla-central@8a1379826329 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflod, jaws
bugs1469684
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1469684 - Allow switching between installed locales r=flod,jaws MozReview-Commit-ID: 1MkkZUfpJ8O
browser/app/profile/firefox.js
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/main.xul
browser/locales/en-US/browser/preferences/preferences.ftl
browser/themes/shared/incontentprefs/preferences.inc.css
toolkit/themes/shared/in-content/common.inc.css
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1719,8 +1719,11 @@ pref("app.normandy.shieldLearnMoreUrl", 
 pref("app.shield.optoutstudies.enabled", true);
 #else
 pref("app.shield.optoutstudies.enabled", false);
 #endif
 
 // Savant Shield study preferences
 pref("shield.savant.enabled", false);
 pref("shield.savant.loglevel", "warn");
+
+// Multi-lingual preferences
+pref("intl.multilingual.enabled", false);
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -9,16 +9,18 @@
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Downloads.jsm");
 ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 ChromeUtils.import("resource:///modules/ShellService.jsm");
 ChromeUtils.import("resource:///modules/TransientPrefs.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
+ChromeUtils.import("resource://gre/modules/L10nRegistry.jsm");
+ChromeUtils.import("resource://gre/modules/Localization.jsm");
 ChromeUtils.defineModuleGetter(this, "CloudStorage",
   "resource://gre/modules/CloudStorage.jsm");
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   gCategoryManager: ["@mozilla.org/categorymanager;1", "nsICategoryManager"],
   gHandlerService: ["@mozilla.org/uriloader/handler-service;1", "nsIHandlerService"],
   gMIMEService: ["@mozilla.org/mime;1", "nsIMIMEService"],
 });
@@ -228,16 +230,27 @@ if (AppConstants.MOZ_UPDATER) {
     ]);
   }
 }
 
 // A promise that resolves when the list of application handlers is loaded.
 // We store this in a global so tests can await it.
 var promiseLoadHandlersList;
 
+// Load the preferences string bundle for a given locale.
+function getBundleForLocale(locale) {
+  function generateContexts(resourceIds) {
+    return L10nRegistry.generateContexts([locale], resourceIds);
+  }
+  return new Localization([
+    "browser/preferences/preferences.ftl",
+    "branding/brand.ftl",
+  ], generateContexts);
+}
+
 var gNodeToObjectMap = new WeakMap();
 
 var gMainPane = {
   // The set of types the app knows how to handle.  A hash of HandlerInfoWrapper
   // objects, indexed by type.
   _handledTypes: {},
 
   // The list of types we can show, sorted by the sort column/direction.
@@ -333,16 +346,20 @@ var gMainPane = {
 
     let connectionSettingsLink = document.getElementById("connectionSettingsLearnMore");
     let connectionSettingsUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") +
                                 "prefs-connection-settings";
     connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
     this.updateProxySettingsUI();
     initializeProxyUI(gMainPane);
 
+    if (Services.prefs.getBoolPref("intl.multilingual.enabled")) {
+      gMainPane.initBrowserLocale();
+    }
+
     if (AppConstants.platform == "win") {
       // Functionality for "Show tabs in taskbar" on Windows 7 and up.
       try {
         let ver = parseFloat(Services.sysinfo.getProperty("version"));
         let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
         showTabsInTaskbar.hidden = ver < 6.1;
       } catch (ex) { }
     }
@@ -758,16 +775,70 @@ var gMainPane = {
       checkbox.removeAttribute("disabled");
       newValue = startupPref.value === this.STARTUP_PREF_RESTORE_SESSION;
     }
     if (checkbox.checked !== newValue) {
       checkbox.checked = newValue;
     }
   },
 
+  initBrowserLocale() {
+    let localeCodes = Services.locale.getAvailableLocales();
+    let localeNames = Services.intl.getLocaleDisplayNames(undefined, localeCodes);
+    let locales = localeCodes.map((code, i) => ({code, name: localeNames[i]}));
+    locales.sort((a, b) => a.name > b.name);
+
+    let fragment = document.createDocumentFragment();
+    for (let {code, name} of locales) {
+      let menuitem = document.createElement("menuitem");
+      menuitem.setAttribute("value", code);
+      menuitem.setAttribute("label", name);
+      fragment.appendChild(menuitem);
+    }
+    let menulist = document.getElementById("defaultBrowserLanguage");
+    let menupopup = menulist.querySelector("menupopup");
+    menupopup.appendChild(fragment);
+    menulist.value = Services.locale.getRequestedLocale();
+
+    document.getElementById("browserLanguagesBox").hidden = false;
+  },
+
+  /* Show the confirmation message bar to allow a restart into the new language. */
+  async onBrowserLanguageChange(event) {
+    let locale = event.target.value;
+    let messageBar = document.getElementById("confirmBrowserLanguage");
+    if (locale == Services.locale.getRequestedLocale()) {
+      messageBar.hidden = true;
+      return;
+    }
+    // Set the text in the message bar for the new locale.
+    let newBundle = getBundleForLocale(locale);
+    let description = messageBar.querySelector("description");
+    description.textContent = await newBundle.formatValue(
+      "confirm-browser-language-change-description");
+    let button = messageBar.querySelector("button");
+    button.setAttribute(
+      "label", await newBundle.formatValue(
+        "confirm-browser-language-change-button"));
+    messageBar.hidden = false;
+  },
+
+  /* Confirm the locale change and restart the browser in the new locale. */
+  confirmBrowserLanguageChange() {
+    let locale = document.getElementById("defaultBrowserLanguage").value;
+    Services.locale.setRequestedLocales([locale]);
+
+    // Restart with the new locale.
+    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+    Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+    if (!cancelQuit.data) {
+      Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+    }
+  },
+
   onBrowserRestoreSessionChange(event) {
     const value = event.target.checked;
     const startupPref = Preferences.get("browser.startup.page");
     let newValue;
 
     if (value) {
       // We need to restore the blank homepage setting in our other pref
       if (startupPref.value === this.STARTUP_PREF_BLANK) {
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -278,16 +278,30 @@
     </hbox>
   </vbox>
 </groupbox>
 
 <!-- Languages -->
 <groupbox id="languagesGroup" data-category="paneGeneral" hidden="true">
   <caption><label data-l10n-id="language-header"/></caption>
 
+  <vbox id="browserLanguagesBox" align="start" hidden="true">
+    <description flex="1" controls="chooseBrowserLanguage" data-l10n-id="choose-browser-language-description"/>
+    <menulist id="defaultBrowserLanguage" class="accessory-button" oncommand="gMainPane.onBrowserLanguageChange(event)" flex="1">
+      <menupopup/>
+    </menulist>
+  </vbox>
+  <hbox id="confirmBrowserLanguage" class="message-bar" align="center" hidden="true">
+    <image class="message-bar-icon"/>
+    <hbox class="message-bar-content" align="center" flex="1">
+      <description flex="1"/>
+      <button class="message-bar-button" oncommand="gMainPane.confirmBrowserLanguageChange()"/>
+    </hbox>
+  </hbox>
+
   <hbox id="languagesBox" align="center">
     <description flex="1" control="chooseLanguage" data-l10n-id="choose-language-description"/>
     <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
     <hbox>
       <button id="chooseLanguage"
               class="accessory-button"
               data-l10n-id="choose-button"
               search-l10n-ids="
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -246,16 +246,20 @@ colors-settings =
 language-header = Language
 
 choose-language-description = Choose your preferred language for displaying pages
 
 choose-button =
     .label = Choose…
     .accesskey = o
 
+choose-browser-language-description = Choose the languages used to display menus, messages, and notifications from { -brand-short-name }.
+confirm-browser-language-change-description = Restart { -brand-short-name } to apply these changes
+confirm-browser-language-change-button = Apply and Restart
+
 translate-web-pages =
     .label = Translate web content
     .accesskey = T
 
 # The <img> element is replaced by the logo of the provider
 # used to provide machine translations for web pages.
 translate-attribution = Translations by <img data-l10n-name="logo"/>
 
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -902,8 +902,12 @@ menulist[indicator=true] > menupopup men
   visibility: hidden;
 }
 
 /* Proxy port input */
 
 .proxy-port-input {
   width: calc(5ch + 22px); /* 5 chars + 11px padding on both sides */
 }
+
+#defaultBrowserLanguage {
+  min-width: 20em;
+}
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -36,16 +36,21 @@
   --in-content-link-color-active: #003eaa;
   --in-content-link-color-visited: #0a8dff;
   --in-content-primary-button-background: #0a84ff;
   --in-content-primary-button-background-hover: #0060df;
   --in-content-primary-button-background-active: #003EAA;
   --in-content-table-background: #ebebeb;
   --in-content-table-border-dark-color: #d1d1d1;
   --in-content-table-header-background: #0a84ff;
+  --grey-20: #ededf0;
+  --grey-90: #0c0c0d;
+  --grey-90-a10: rgba(12, 12, 13, 0.1);
+  --grey-90-a20: rgba(12, 12, 13, 0.2);
+  --grey-90-a30: rgba(12, 12, 13, 0.3);
 }
 
 html|html,
 xul|page,
 xul|window {
   font: message-box;
   -moz-appearance: none;
   background-color: var(--in-content-page-background);
@@ -840,8 +845,46 @@ xul|treechildren::-moz-tree-cell-text,
 xul|treechildren::-moz-tree-image {
   color: var(--in-content-text-color);
 }
 
 xul|treechildren::-moz-tree-cell-text(selected),
 xul|treechildren::-moz-tree-image(selected) {
   color: var(--in-content-selected-text);
 }
+
+/* Message bars */
+.message-bar {
+  background-color: var(--grey-20);
+  border-radius: 4px;
+  color: var(--grey-90);
+  min-height: 32px;
+  padding: 0 4px;
+}
+
+/* The message-bar-button styles have extra specificity to override
+ * the defaults for buttons. */
+.message-bar > hbox > .message-bar-button {
+  background-color: var(--grey-90-a10);
+  border: none;
+  border-radius: 2px;
+  height: 24px;
+  margin-inline-start: 8px;
+  padding: 0 8px;
+}
+
+.message-bar > hbox > .message-bar-button:hover {
+  background-color: var(--grey-90-a20);
+}
+
+.message-bar > hbox > .message-bar-button:active {
+  background-color: var(--grey-90-a30);
+}
+
+.message-bar-icon {
+  list-style-image: url("chrome://browser/skin/identity-icon.svg");
+  width: 24px;
+  height: 24px;
+  padding: 4px;
+  margin-inline-end: 4px;
+  -moz-context-properties: fill;
+  fill: currentColor;
+}