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 ff16973184c0
parent 428800 57adf58922e0
child 428802 c3309f635b90
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;
+}