Bug 1516617 - Port bug 1507595: Convert about:support to Fluent. r=jorgk
authorRichard Marti <richard.marti@gmail.com>
Fri, 28 Dec 2018 21:02:13 +0100
changeset 33221 e1df268e0c14
parent 33220 468c27d25750
child 33222 af79aa826c3a
push id2368
push userclokep@gmail.com
push dateMon, 28 Jan 2019 21:12:50 +0000
treeherdercomm-beta@56d23c07d815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorgk
bugs1516617, 1507595
Bug 1516617 - Port bug 1507595: Convert about:support to Fluent. r=jorgk
mail/components/about-support/content/aboutSupport.js
mail/components/about-support/content/aboutSupport.xhtml
mail/themes/shared/mail/aboutSupport.css
--- a/mail/components/about-support/content/aboutSupport.js
+++ b/mail/components/about-support/content/aboutSupport.js
@@ -4,16 +4,17 @@
 
 /* This file is a copy of mozilla/toolkit/content/aboutSupport.js with
    modifications for TB. */
 
 /* globals AboutSupportPlatform, populateAccountsSection, sendViaEmail */
 
 "use strict";
 
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Troubleshoot.jsm");
 ChromeUtils.import("resource://gre/modules/ResetProfile.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 // added for TB
 ChromeUtils.import("resource:///modules/MailServices.jsm");
 // end of TB addition
 
@@ -34,109 +35,126 @@ var CLASS_DATA_UIONLY = "data-uionly";
 var CLASS_DATA_PRIVATE = "data-private";
 
 // Any nodes marked with this class will only be displayed when the user chooses
 // to not display private data.
 var CLASS_DATA_PUBLIC = "data-public";
 // end of TB addition
 window.addEventListener("load", function onload(event) {
   try {
-  window.removeEventListener("load", onload);
-  Troubleshoot.snapshot(function(snapshot) {
-    for (let prop in snapshotFormatters)
-      snapshotFormatters[prop](snapshot[prop]);
-  });
-  populateActionBox();
-  setupEventListeners();
+    window.removeEventListener("load", onload);
+    Troubleshoot.snapshot(async function(snapshot) {
+      for (let prop in snapshotFormatters)
+        await snapshotFormatters[prop](snapshot[prop]);
+    });
+    populateActionBox();
+    setupEventListeners();
   } catch (e) {
     Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
   }
   // added for TB
   populateAccountsSection();
 });
 
+// Fluent uses lisp-case IDs so this converts
+// the SentenceCase info IDs to lisp-case.
+const FLUENT_IDENT_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
+function toFluentID(str) {
+  if (!FLUENT_IDENT_REGEX.test(str)) {
+    return null;
+  }
+  return str.toString().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
+}
+
 // Each property in this object corresponds to a property in Troubleshoot.jsm's
 // snapshot data.  Each function is passed its property's corresponding data,
 // and it's the function's job to update the page with it.
 var snapshotFormatters = {
 
-  application(data) {
-    let strings = stringBundle();
+  async application(data) {
     $("application-box").textContent = data.name;
     $("useragent-box").textContent = data.userAgent;
     $("os-box").textContent = data.osVersion;
     $("supportLink").href = data.supportURL;
     let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
     if (data.vendor)
       version += " (" + data.vendor + ")";
     $("version-box").textContent = version;
     $("buildid-box").textContent = data.buildID;
     if (data.updateChannel)
       $("updatechannel-box").textContent = data.updateChannel;
 
-    let statusText = strings.GetStringFromName("multiProcessStatus.unknown");
+    let statusTextId = "multi-process-status-unknown";
 
     // Whitelist of known values with string descriptions:
     switch (data.autoStartStatus) {
       case 0:
       case 1:
       case 2:
       case 4:
       case 6:
       case 7:
       case 8:
-        statusText = strings.GetStringFromName("multiProcessStatus." + data.autoStartStatus);
+        statusTextId = "multi-process-status-" + data.autoStartStatus;
         break;
     }
 
-    $("multiprocess-box").textContent = strings.formatStringFromName("multiProcessWindows",
-      [data.numRemoteWindows, data.numTotalWindows, statusText], 3);
+    document.l10n.setAttributes($("multiprocess-box-process-count"),
+                                "multi-process-windows",
+                                {
+                                  remoteWindows: data.numRemoteWindows,
+                                  totalWindows: data.numTotalWindows,
+                                });
+    document.l10n.setAttributes($("multiprocess-box-status"), statusTextId);
 
     if (data.remoteAutoStart) {
       $("contentprocesses-box").textContent = data.currentContentProcesses +
                                               "/" +
                                               data.maxContentProcesses;
     } else {
       $("contentprocesses-row").hidden = true;
     }
 
     if (Services.policies) {
-      let policiesText = "";
+      let policiesStrId = "";
       let aboutPolicies = "about:policies";
       switch (data.policiesStatus) {
         case Services.policies.INACTIVE:
-          policiesText = strings.GetStringFromName("policies.inactive");
+          policiesStrId = "policies-inactive";
           break;
 
         case Services.policies.ACTIVE:
-          policiesText = strings.GetStringFromName("policies.active");
+          policiesStrId = "policies-active";
           aboutPolicies += "#active";
           break;
 
         default:
-          policiesText = strings.GetStringFromName("policies.error");
+          policiesStrId = "policies-error";
           aboutPolicies += "#errors";
           break;
       }
 
       if (data.policiesStatus != Services.policies.INACTIVE) {
-        let activePolicies = $.new("a", policiesText, null, {href: aboutPolicies});
+        let activePolicies = $.new("a", null, null, {
+          href: aboutPolicies,
+        });
+        document.l10n.setAttributes(activePolicies, policiesStrId);
         $("policies-status").appendChild(activePolicies);
       } else {
-        $("policies-status").textContent = policiesText;
+        document.l10n.setAttributes($("policies-status"), policiesStrId);
       }
     } else {
       $("policies-status-row").hidden = true;
     }
 
     let keyGoogleFound = data.keyGoogleFound ? "found" : "missing";
-    $("key-google-box").textContent = strings.GetStringFromName(keyGoogleFound);
+    document.l10n.setAttributes($("key-google-box"), keyGoogleFound);
 
     let keyMozillaFound = data.keyMozillaFound ? "found" : "missing";
-    $("key-mozilla-box").textContent = strings.GetStringFromName(keyMozillaFound);
+    document.l10n.setAttributes($("key-mozilla-box"), keyMozillaFound);
 
     $("safemode-box").textContent = data.safeMode;
 
     // added for TB
     // Add profile path as private info into the page.
     let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
     let profElem = document.getElementById("profile-dir-button").parentNode;
     let profDirNode = document.getElementById("profile-dir-box");
@@ -164,68 +182,61 @@ var snapshotFormatters = {
     }
     // end of TB addition
   },
 
   crashes(data) {
     if (!AppConstants.MOZ_CRASHREPORTER)
       return;
 
-    let strings = stringBundle();
     let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
-    $("crashes-title").textContent =
-      PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
-                .replace("#1", daysRange);
+    document.l10n.setAttributes($("crashes-title"), "report-crash-for-days", { days: daysRange });
     let reportURL;
     try {
       reportURL = Services.prefs.getCharPref("breakpad.reportURL");
       // Ignore any non http/https urls
       if (!/^https?:/i.test(reportURL))
         reportURL = null;
     } catch (e) { }
     if (!reportURL) {
       $("crashes-noConfig").style.display = "block";
       $("crashes-noConfig").classList.remove("no-copy");
       return;
     }
     $("crashes-allReports").style.display = "block";
     $("crashes-allReports").classList.remove("no-copy");
 
     if (data.pending > 0) {
-      $("crashes-allReportsWithPending").textContent =
-        PluralForm.get(data.pending, strings.GetStringFromName("pendingReports"))
-                  .replace("#1", data.pending);
+      document.l10n.setAttributes($("crashes-allReportsWithPending"), "pending-reports", { reports: data.pending });
     }
 
     let dateNow = new Date();
     $.append($("crashes-tbody"), data.submitted.map(function(crash) {
       let date = new Date(crash.date);
       let timePassed = dateNow - date;
-      let formattedDate;
+      let formattedDateStrId;
+      let formattedDateStrArgs;
       if (timePassed >= 24 * 60 * 60 * 1000) {
         let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
-        let daysPassedString = strings.GetStringFromName("crashesTimeDays");
-        formattedDate = PluralForm.get(daysPassed, daysPassedString)
-                                  .replace("#1", daysPassed);
+        formattedDateStrId = "crashes-time-days";
+        formattedDateStrArgs = { days: daysPassed };
       } else if (timePassed >= 60 * 60 * 1000) {
         let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
-        let hoursPassedString = strings.GetStringFromName("crashesTimeHours");
-        formattedDate = PluralForm.get(hoursPassed, hoursPassedString)
-                                  .replace("#1", hoursPassed);
+        formattedDateStrId = "crashes-time-hours";
+        formattedDateStrArgs = { hours: hoursPassed };
       } else {
         let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
-        let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes");
-        formattedDate = PluralForm.get(minutesPassed, minutesPassedString)
-                                  .replace("#1", minutesPassed);
+        formattedDateStrId = "crashes-time-minutes";
+        formattedDateStrArgs = { minutes: minutesPassed };
       }
       return $.new("tr", [
         $.new("td", [
           $.new("a", crash.id, null, {href: reportURL + crash.id}),
         ]),
-        $.new("td", formattedDate),
+        $.new("td", null, null, {"data-l10n-id": formattedDateStrId, "data-l10n-args": formattedDateStrArgs}),
       ]);
     }));
   },
 
   extensions(data) {
     $.append($("extensions-tbody"), data.map(function(extension) {
       return $.new("tr", [
         $.new("td", extension.name),
@@ -280,82 +291,66 @@ var snapshotFormatters = {
           $.new("td", name, "pref-name"),
           $.new("td", String(value).substr(0, 120), "pref-value"),
         ]);
       }
     ));
   },
 
   /* eslint-disable complexity */
-  graphics(data) {
-    let strings = stringBundle();
-
-    function localizedMsg(msgArray) {
-      let nameOrMsg = msgArray.shift();
-      if (msgArray.length) {
-        // formatStringFromName logs an NS_ASSERTION failure otherwise that says
-        // "use GetStringFromName".  Lame.
-        try {
-          return strings.formatStringFromName(nameOrMsg, msgArray,
-                                              msgArray.length);
-        } catch (err) {
-          // Throws if nameOrMsg is not a name in the bundle.  This shouldn't
-          // actually happen though, since msgArray.length > 1 => nameOrMsg is a
-          // name in the bundle, not a message, and the remaining msgArray
-          // elements are parameters.
-          return nameOrMsg;
-        }
+  async graphics(data) {
+    function localizedMsg(msg) {
+      if (typeof msg == "object" && msg.key) {
+        return document.l10n.formatValue(msg.key, msg.args);
       }
-      try {
-        return strings.GetStringFromName(nameOrMsg);
-      } catch (err) {
-        // Throws if nameOrMsg is not a name in the bundle.
+      let msgId = toFluentID(msg);
+      if (msgId) {
+        return document.l10n.formatValue(msgId);
       }
-      return nameOrMsg;
+      return "";
     }
 
     // Read APZ info out of data.info, stripping it out in the process.
     let apzInfo = [];
     let formatApzInfo = function(info) {
       let out = [];
       for (let type of ["Wheel", "Touch", "Drag", "Keyboard", "Autoscroll"]) {
         let key = "Apz" + type + "Input";
 
         if (!(key in info))
           continue;
 
         delete info[key];
 
-        let message = localizedMsg([type.toLowerCase() + "Enabled"]);
-        out.push(message);
+        out.push(toFluentID(type.toLowerCase() + "Enabled"));
       }
 
       return out;
     };
 
     // Create a <tr> element with key and value columns.
     //
     // @key      Text in the key column. Localized automatically, unless starts with "#".
     // @value    Text in the value column. Not localized.
     function buildRow(key, value) {
-      let title;
-      if (key[0] == "#") {
-        title = key.substr(1);
-      } else {
-        try {
-          title = strings.GetStringFromName(key);
-        } catch (e) {
-          title = key;
-        }
-      }
+      let title = key[0] == "#" ? key.substr(1) : key;
+      let keyStrId = toFluentID(key);
+      let valueStrId = Array.isArray(value) ? null : toFluentID(value);
       let td = $.new("td", value);
       td.style["white-space"] = "pre-wrap";
+      if (valueStrId) {
+        document.l10n.setAttributes(td, valueStrId);
+      }
 
+      let th = $.new("th", title, "column");
+      if (!key.startsWith("#")) {
+        document.l10n.setAttributes(th, keyStrId);
+      }
       return $.new("tr", [
-        $.new("th", title, "column"),
+        th,
         td,
       ]);
     }
 
     // @where    The name in "graphics-<name>-tbody", of the element to append to.
     // @trs      Array of row elements.
     function addRows(where, trs) {
       $.append($("graphics-" + where + "-tbody"), trs);
@@ -363,17 +358,17 @@ var snapshotFormatters = {
 
     // Build and append a row.
     //
     // @where    The name in "graphics-<name>-tbody", of the element to append to.
     function addRow(where, key, value) {
       addRows(where, [buildRow(key, value)]);
     }
     if (data.clearTypeParameters !== undefined) {
-      addRow("diagnostics", "clearTypeParameters", data.clearTypeParameters);
+      addRow("diagnostics", "clear-type-parameters", data.clearTypeParameters);
     }
     if ("info" in data) {
       apzInfo = formatApzInfo(data.info);
 
       let trs = sortedArrayFromObject(data.info).map(function([prop, val]) {
         return $.new("tr", [
           $.new("th", prop, "column"),
           $.new("td", String(val)),
@@ -391,34 +386,34 @@ var snapshotFormatters = {
       let gpuProcessKillButton = null;
       if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) {
         gpuProcessKillButton = $.new("button");
 
         gpuProcessKillButton.addEventListener("click", function() {
           windowUtils.terminateGPUProcess();
         });
 
-        gpuProcessKillButton.textContent = strings.GetStringFromName("gpuProcessKillButton");
+        document.l10n.setAttributes(gpuProcessKillButton, "gpu-process-kill-button");
       }
 
-      addRow("diagnostics", "GPUProcessPid", gpuProcessPid);
+      addRow("diagnostics", "gpu-process-pid", [new Text(gpuProcessPid)]);
       if (gpuProcessKillButton) {
-        addRow("diagnostics", "GPUProcess", [gpuProcessKillButton]);
+        addRow("diagnostics", "gpu-process", [gpuProcessKillButton]);
       }
     }
 
     if ((AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) && AppConstants.platform != "macosx") {
       let gpuDeviceResetButton = $.new("button");
 
       gpuDeviceResetButton.addEventListener("click", function() {
         windowUtils.triggerDeviceReset();
       });
 
-      gpuDeviceResetButton.textContent = strings.GetStringFromName("gpuDeviceResetButton");
-      addRow("diagnostics", "Device Reset", [gpuDeviceResetButton]);
+      document.l10n.setAttributes(gpuDeviceResetButton, "gpu-device-reset-button");
+      addRow("diagnostics", "gpu-device-reset", [gpuDeviceResetButton]);
     }
 
     // graphics-failures-tbody tbody
     if ("failures" in data) {
       // If indices is there, it should be the same length as failures,
       // (see Troubleshoot.jsm) but we check anyway:
       if ("indices" in data && data.failures.length == data.indices.length) {
         let combined = [];
@@ -448,95 +443,104 @@ var snapshotFormatters = {
       $("graphics-failures-tbody").style.display = "none";
     }
 
     // Add a new row to the table, and take the key (or keys) out of data.
     //
     // @where        Table section to add to.
     // @key          Data key to use.
     // @colKey       The localization key to use, if different from key.
-    function addRowFromKey(where, key, colKey) {
+    async function addRowFromKey(where, key, colKey) {
       if (!(key in data))
         return;
       colKey = colKey || key;
 
       let value;
       let messageKey = key + "Message";
       if (messageKey in data) {
-        value = localizedMsg(data[messageKey]);
+        value = await localizedMsg(data[messageKey]);
         delete data[messageKey];
       } else {
         value = data[key];
       }
       delete data[key];
 
       if (value) {
-        addRow(where, colKey, value);
+        addRow(where, colKey, [new Text(value)]);
       }
     }
 
     // graphics-features-tbody
-
     let compositor = "";
     if (data.windowLayerManagerRemote) {
       compositor = data.windowLayerManagerType;
       if (data.windowUsingAdvancedLayers) {
         compositor += " (Advanced Layers)";
       }
     } else {
-      compositor = "BasicLayers (" + strings.GetStringFromName("mainThreadNoOMTC") + ")";
+      let noOMTCString = await document.l10n.formatValue("main-thread-no-omtc");
+      compositor = "BasicLayers (" + noOMTCString + ")";
     }
-    addRow("features", "compositing", compositor);
+    addRow("features", "compositing", [new Text(compositor)]);
     delete data.windowLayerManagerRemote;
     delete data.windowLayerManagerType;
     delete data.numTotalWindows;
     delete data.numAcceleratedWindows;
     delete data.numAcceleratedWindowsMessage;
     delete data.windowUsingAdvancedLayers;
 
     addRow("features", "asyncPanZoom",
            apzInfo.length
-           ? apzInfo.join("; ")
-           : localizedMsg(["apzNone"]));
-    addRowFromKey("features", "webgl1WSIInfo");
-    addRowFromKey("features", "webgl1Renderer");
-    addRowFromKey("features", "webgl1Version");
-    addRowFromKey("features", "webgl1DriverExtensions");
-    addRowFromKey("features", "webgl1Extensions");
-    addRowFromKey("features", "webgl2WSIInfo");
-    addRowFromKey("features", "webgl2Renderer");
-    addRowFromKey("features", "webgl2Version");
-    addRowFromKey("features", "webgl2DriverExtensions");
-    addRowFromKey("features", "webgl2Extensions");
-    addRowFromKey("features", "supportsHardwareH264", "hardwareH264");
-    addRowFromKey("features", "direct2DEnabled", "#Direct2D");
-    addRowFromKey("features", "usesTiling");
-    // addRowFromKey("features", "contentUsesTiling"); - For content processes in Firefox.
-    addRowFromKey("features", "offMainThreadPaintEnabled");
-    addRowFromKey("features", "offMainThreadPaintWorkerCount");
+           ? (await document.l10n.formatValues(apzInfo.map(id => { return {id}; }))).join("; ")
+           : await localizedMsg("apz-none"));
+    let featureKeys = [
+      "webgl1WSIInfo",
+      "webgl1Renderer",
+      "webgl1Version",
+      "webgl1DriverExtensions",
+      "webgl1Extensions",
+      "webgl2WSIInfo",
+      "webgl2Renderer",
+      "webgl2Version",
+      "webgl2DriverExtensions",
+      "webgl2Extensions",
+      ["supportsHardwareH264", "hardware-h264"],
+      ["direct2DEnabled", "#Direct2D"],
+      "usesTiling",
+      "contentUsesTiling",
+      "offMainThreadPaintEnabled",
+      "offMainThreadPaintWorkerCount",
+    ];
+    for (let feature of featureKeys) {
+      if (Array.isArray(feature)) {
+        await addRowFromKey("features", feature[0], feature[1]);
+        continue;
+      }
+      await addRowFromKey("features", feature);
+    }
 
     if ("directWriteEnabled" in data) {
       let message = data.directWriteEnabled;
       if ("directWriteVersion" in data)
         message += " (" + data.directWriteVersion + ")";
-      addRow("features", "#DirectWrite", message);
+      await addRow("features", "#DirectWrite", [new Text(message)]);
       delete data.directWriteEnabled;
       delete data.directWriteVersion;
     }
 
     // Adapter tbodies.
     let adapterKeys = [
-      ["adapterDescription", "gpuDescription"],
-      ["adapterVendorID", "gpuVendorID"],
-      ["adapterDeviceID", "gpuDeviceID"],
-      ["driverVersion", "gpuDriverVersion"],
-      ["driverDate", "gpuDriverDate"],
-      ["adapterDrivers", "gpuDrivers"],
-      ["adapterSubsysID", "gpuSubsysID"],
-      ["adapterRAM", "gpuRAM"],
+      ["adapterDescription", "gpu-description"],
+      ["adapterVendorID", "gpu-vendor-id"],
+      ["adapterDeviceID", "gpu-device-id"],
+      ["driverVersion", "gpu-driver-version"],
+      ["driverDate", "gpu-driver-date"],
+      ["adapterDrivers", "gpu-drivers"],
+      ["adapterSubsysID", "gpu-subsys-id"],
+      ["adapterRAM", "gpu-ram"],
     ];
 
     function showGpu(id, suffix) {
       function get(prop) {
         return data[prop + suffix];
       }
 
       let trs = [];
@@ -551,17 +555,18 @@ var snapshotFormatters = {
         $("graphics-" + id + "-tbody").style.display = "none";
         return;
       }
 
       let active = "yes";
       if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) {
         active = "no";
       }
-      addRow(id, "gpuActive", strings.GetStringFromName(active));
+
+      addRow(id, "gpu-active", active);
       addRows(id, trs);
     }
     showGpu("gpu-1", "");
     showGpu("gpu-2", "2");
 
     // Remove adapter keys.
     for (let [prop /* key */] of adapterKeys) {
       delete data[prop];
@@ -589,36 +594,37 @@ var snapshotFormatters = {
             continue;
 
           let contents;
           if (entry.message.length > 0 && entry.message[0] == "#") {
             // This is a failure ID. See nsIGfxInfo.idl.
             let m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message);
             if (m) {
               let bugSpan = $.new("span");
-              bugSpan.textContent = strings.GetStringFromName("blocklistedBug") + "; ";
+              document.l10n.setAttributes(bugSpan, "blocklisted-bug");
 
               let bugHref = $.new("a");
               bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
-              bugHref.textContent = strings.formatStringFromName("bugLink", [m[1]], 1);
+              document.l10n.setAttributes(bugHref, "bug-link", { bugNumber: m[1]});
 
               contents = [bugSpan, bugHref];
             } else {
-              contents = strings.formatStringFromName(
-                "unknownFailure", [entry.message.substr(1)], 1);
+              let unknownFailure = $.new("span");
+              document.l10n.setAttributes(unknownFailure, "unknown-failure", { failureCode: entry.message.substr(1) });
+              contents = [unknownFailure];
             }
           } else {
             contents = entry.status + " by " + entry.type + ": " + entry.message;
           }
 
           trs.push($.new("tr", [
             $.new("td", contents),
           ]));
         }
-        addRow("decisions", feature.name, [$.new("table", trs)]);
+        addRow("decisions", "#" + feature.name, [$.new("table", trs)]);
       }
     } else {
       $("graphics-decisions-tbody").style.display = "none";
     }
 
     if (featureLog.fallbacks.length) {
       for (let fallback of featureLog.fallbacks) {
         addRow("workarounds", fallback.name, fallback.message);
@@ -634,43 +640,39 @@ var snapshotFormatters = {
       for (let guard of crashGuards) {
         let resetButton = $.new("button");
         let onClickReset = function() {
           Services.prefs.setIntPref(guard.prefName, 0);
           resetButton.removeEventListener("click", onClickReset);
           resetButton.disabled = true;
         };
 
-        resetButton.textContent = strings.GetStringFromName("resetOnNextRestart");
+        document.l10n.setAttributes(resetButton, "reset-on-next-restart");
         resetButton.addEventListener("click", onClickReset);
 
         addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
       }
     } else {
       $("graphics-crashguards-tbody").style.display = "none";
     }
 
     // Now that we're done, grab any remaining keys in data and drop them into
     // the diagnostics section.
     for (let key in data) {
       let value = data[key];
-      if (Array.isArray(value)) {
-        value = localizedMsg(value);
-      }
       addRow("diagnostics", key, value);
     }
   },
   /* eslint-enable complexity */
 
   media(data) {
-    let strings = stringBundle();
-
     function insertBasicInfo(key, value) {
       function createRow(key, value) {
-        let th = $.new("th", strings.GetStringFromName(key), "column");
+        let th = $.new("th", null, "column");
+        document.l10n.setAttributes(th, key);
         let td = $.new("td", value);
         td.style["white-space"] = "pre-wrap";
         td.colSpan = 8;
         return $.new("tr", [th, td]);
       }
       $.append($("media-info-tbody"), [createRow(key, value)]);
     }
 
@@ -749,19 +751,19 @@ var snapshotFormatters = {
       let rows = [];
       for (let dev of devices) {
         rows.push(createDeviceInfoRow(dev));
       }
       $.append($("media-" + side + "-devices-tbody"), rows);
     }
 
     // Basic information
-    insertBasicInfo("audioBackend", data.currentAudioBackend);
-    insertBasicInfo("maxAudioChannels", data.currentMaxAudioChannels);
-    insertBasicInfo("sampleRate", data.currentPreferredSampleRate);
+    insertBasicInfo("audio-backend", data.currentAudioBackend);
+    insertBasicInfo("max-audio-channels", data.currentMaxAudioChannels);
+    insertBasicInfo("sample-rate", data.currentPreferredSampleRate);
 
     // Output devices information
     insertDeviceInfo("output", data.audioOutputDevices);
 
     // Input devices information
     insertDeviceInfo("input", data.audioInputDevices);
   },
 
@@ -780,22 +782,21 @@ var snapshotFormatters = {
 
     let a11yInstantiator = $("a11y-instantiator");
     if (a11yInstantiator) {
       a11yInstantiator.textContent = data.instantiator;
     }
   },
 
   libraryVersions(data) {
-    let strings = stringBundle();
     let trs = [
       $.new("tr", [
         $.new("th", ""),
-        $.new("th", strings.GetStringFromName("minLibVersions")),
-        $.new("th", strings.GetStringFromName("loadedLibVersions")),
+        $.new("th", null, null, {"data-l10n-id": "min-lib-versions"}),
+        $.new("th", null, null, {"data-l10n-id": "loaded-lib-versions"}),
       ]),
     ];
     sortedArrayFromObject(data).forEach(
       function([name, val]) {
         trs.push($.new("tr", [
           $.new("td", name),
           $.new("td", val.minVersion),
           $.new("td", val.version),
@@ -815,48 +816,50 @@ var snapshotFormatters = {
     // Clear the no-copy class
     $("prefs-user-js-section").className = "";
   },
 
   sandbox(data) {
     if (!AppConstants.MOZ_SANDBOX)
       return;
 
-    let strings = stringBundle();
     let tbody = $("sandbox-tbody");
     for (let key in data) {
       // Simplify the display a little in the common case.
       if (key === "hasPrivilegedUserNamespaces" &&
           data[key] === data.hasUserNamespaces) {
         continue;
       }
       if (key === "syscallLog") {
         // Not in this table.
         continue;
       }
+      let keyStrId = toFluentID(key);
+      let th = $.new("th", null, "column");
+      document.l10n.setAttributes(th, keyStrId);
       tbody.appendChild($.new("tr", [
-        $.new("th", strings.GetStringFromName(key), "column"),
+        th,
         $.new("td", data[key]),
       ]));
     }
 
     if ("syscallLog" in data) {
       let syscallBody = $("sandbox-syscalls-tbody");
       let argsHead = $("sandbox-syscalls-argshead");
       for (let syscall of data.syscallLog) {
         if (argsHead.colSpan < syscall.args.length) {
           argsHead.colSpan = syscall.args.length;
         }
+        let procTypeStrId = toFluentID(syscall.procType);
         let cells = [
           $.new("td", syscall.index, "integer"),
           $.new("td", syscall.msecAgo / 1000),
           $.new("td", syscall.pid, "integer"),
           $.new("td", syscall.tid, "integer"),
-          $.new("td", strings.GetStringFromName("sandboxProcType." +
-                                                syscall.procType)),
+          $.new("td", null, null, {"data-l10n-id": "sandbox-proc-type-" + procTypeStrId}),
           $.new("td", syscall.syscall, "integer"),
         ];
         for (let arg of syscall.args) {
           cells.push($.new("td", arg, "integer"));
         }
         syscallBody.appendChild($.new("tr", cells));
       }
     }
@@ -878,40 +881,51 @@ var snapshotFormatters = {
       JSON.stringify(data.osPrefs.systemLocales);
     $("intl-osprefs-regionalprefs").textContent =
       JSON.stringify(data.osPrefs.regionalPrefsLocales);
   },
 };
 
 var $ = document.getElementById.bind(document);
 
-$.new = function(tag, textContentOrChildren, className, attributes) {
+$.new = function $_new(tag, textContentOrChildren, className, attributes) { // eslint-disable-line func-names
   let elt = document.createElement(tag);
-  if (className)
+  if (className) {
     elt.className = className;
+  }
   if (attributes) {
-    for (let attrName in attributes)
+    if (attributes["data-l10n-id"]) {
+      let args = attributes.hasOwnProperty("data-l10n-args") ?
+        attributes["data-l10n-args"] :
+        undefined;
+      document.l10n.setAttributes(elt,
+                                  attributes["data-l10n-id"],
+                                  args);
+      delete attributes["data-l10n-id"];
+      if (args) {
+        delete attributes["data-l10n-args"];
+      }
+    }
+
+    for (let attrName in attributes) {
       elt.setAttribute(attrName, attributes[attrName]);
+    }
   }
-  if (Array.isArray(textContentOrChildren))
+  if (Array.isArray(textContentOrChildren)) {
     this.append(elt, textContentOrChildren);
-  else
+  } else if (textContentOrChildren) {
     elt.textContent = String(textContentOrChildren);
+  }
   return elt;
 };
 
-$.append = function(parent, children) {
+$.append = function $_append(parent, children) { // eslint-disable-line func-names
   children.forEach(c => parent.appendChild(c));
 };
 
-function stringBundle() {
-  return Services.strings.createBundle(
-           "chrome://global/locale/aboutSupport.properties");
-}
-
 function assembleFromGraphicsFailure(i, data) {
   // Only cover the cases we have today; for example, we do not have
   // log failures that assert and we assume the log level is 1/error.
   let message = data.failures[i];
   let index = data.indices[i];
   let what = "";
   if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
     // Non-asserting log failure - the message is substring(14)
@@ -939,47 +953,47 @@ function sortedArrayFromObject(obj) {
   tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2));
   return tuples;
 }
 
 function copyRawDataToClipboard(button) {
   if (button)
     button.disabled = true;
   try {
-    Troubleshoot.snapshot(function(snapshot) {
+    Troubleshoot.snapshot(async function(snapshot) {
       if (button)
         button.disabled = false;
       let str = Cc["@mozilla.org/supports-string;1"].
                 createInstance(Ci.nsISupportsString);
       str.data = JSON.stringify(snapshot, undefined, 2);
       let transferable = Cc["@mozilla.org/widget/transferable;1"].
                          createInstance(Ci.nsITransferable);
       transferable.init(getLoadContext());
       transferable.addDataFlavor("text/unicode");
       transferable.setTransferData("text/unicode", str, str.data.length * 2);
       Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
       if (AppConstants.platform == "android") {
         // Present a snackbar notification.
         ChromeUtils.import("resource://gre/modules/Snackbars.jsm");
-        Snackbars.show(stringBundle().GetStringFromName("rawDataCopied"),
-                       Snackbars.LENGTH_SHORT);
+        let rawDataCopiedString = await document.l10n.formatValue("raw-data-copied");
+        Snackbars.show(rawDataCopiedString, Snackbars.LENGTH_SHORT);
       }
     });
   } catch (err) {
     if (button)
       button.disabled = false;
     throw err;
   }
 }
 
 function getLoadContext() {
   return window.docShell.QueryInterface(Ci.nsILoadContext);
 }
 
-function copyContentsToClipboard() {
+async function copyContentsToClipboard() {
   // Get the HTML and text representations for the important part of the page.
   let contentsDiv = $("contents");
   let dataHtml = contentsDiv.innerHTML;
   let dataText = createTextForElement(contentsDiv);
 
   // We can't use plain strings, we have to use nsSupportsString.
   let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
   let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
@@ -1000,18 +1014,18 @@ function copyContentsToClipboard() {
   transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
 
   // Store the data into the clipboard.
   Services.clipboard.setData(transferable, null, Services.clipboard.kGlobalClipboard);
 
   if (AppConstants.platform == "android") {
     // Present a snackbar notification.
     ChromeUtils.import("resource://gre/modules/Snackbars.jsm");
-    Snackbars.show(stringBundle().GetStringFromName("textCopied"),
-                   Snackbars.LENGTH_SHORT);
+    let textCopiedString = await document.l10n.formatValue("text-copied");
+    Snackbars.show(textCopiedString, Snackbars.LENGTH_SHORT);
   }
 }
 
 // Return the plain text representation of an element.  Do a little bit
 // of pretty-printing to make it human-readable.
 function createTextForElement(elem) {
   let serializer = new Serializer();
   let text = serializer.serialize(elem);
@@ -1061,19 +1075,18 @@ Serializer.prototype = {
     // all other elements
 
     let hasText = false;
     for (let child of elem.childNodes) {
       if (child.nodeType == Node.TEXT_NODE) {
         let text = this._nodeText(child);
         this._appendText(text);
         hasText = hasText || !!text.trim();
-      } else if (child.nodeType == Node.ELEMENT_NODE) {
+      } else if (child.nodeType == Node.ELEMENT_NODE)
         this._serializeElement(child);
-      }
     }
 
     // For headings, draw a "line" underneath them so they stand out.
     if (/^h[0-9]+$/.test(elem.localName)) {
       let headerText = (this._currentLine || "").trim();
       if (headerText) {
         this._startNewLine();
         this._appendText("-".repeat(headerText.length));
--- a/mail/components/about-support/content/aboutSupport.xhtml
+++ b/mail/components/about-support/content/aboutSupport.xhtml
@@ -3,306 +3,240 @@
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- This file is a copy of mozilla/toolkit/content/aboutSupport.xhtml with
      modifications for TB. -->
 
 <!DOCTYPE html [
   <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
-  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
-  <!ENTITY % aboutSupportDTD SYSTEM "chrome://global/locale/aboutSupport.dtd"> %aboutSupportDTD;
   <!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd"> %resetProfileDTD;
 <!-- Added for TB -->
   <!ENTITY % aboutSupportMailDTD SYSTEM "chrome://messenger/locale/aboutSupportMail.dtd"> %aboutSupportMailDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
-    <title>&aboutSupport.pageTitle;</title>
+    <title data-l10n-id="page-title"/>
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://branding/content/icon48.png"/>
     <link rel="stylesheet" href="chrome://global/skin/aboutSupport.css"
           type="text/css"/>
 <!-- Added for TB -->
     <link rel="stylesheet" href="chrome://messenger/skin/aboutSupport.css"
           type="text/css"/>
     <!-- Private data is hidden by default. -->
     <link rel="stylesheet" href="chrome://messenger/content/about-support/hide-private.css"
           type="text/css" id="about-support-private"/>
 <!-- End of TB addition -->
     <script type="application/javascript"
             src="chrome://messenger/content/about-support/aboutSupport.js"/>
+    <link rel="localization" href="branding/brand.ftl"/>
+    <link rel="localization" href="toolkit/about/aboutSupport.ftl"/>
 <!-- Added for TB -->
     <script type="application/javascript"
             src="chrome://messenger/content/about-support/accounts.js"/>
     <script type="application/javascript"
             src="chrome://messenger/content/about-support/export.js"/>
 <!-- End of TB addition -->
   </head>
 
-  <body dir="&locale.dir;" class="wide-container">
+  <body class="wide-container">
 
 #ifndef ANDROID
     <div id="action-box" class="notice-box">
       <div id="reset-box">
         <h3>&refreshProfile.title;</h3>
         <button id="reset-box-button">
           &refreshProfile.button.label;
         </button>
       </div>
       <div id="safe-mode-box">
-        <h3>&aboutSupport.safeModeTitle;</h3>
-        <button id="restart-in-safe-mode-button">
-          &aboutSupport.restartInSafeMode.label;
-        </button>
+        <h3 data-l10n-id="safe-mode-title"/>
+        <button id="restart-in-safe-mode-button" data-l10n-id="restart-in-safe-mode-label"/>
       </div>
     </div>
 #endif
 
-    <h1>
-      &aboutSupport.pageTitle;
-    </h1>
+    <h1 data-l10n-id="page-title"/>
 
-    <div class="page-subtitle">
-        &aboutSupport.pageSubtitle;
+    <div class="page-subtitle" data-l10n-id="page-subtitle">
+      <a id="supportLink" data-l10n-name="support-link"></a>
     </div>
 
     <div>
 <!-- Not used on TB
-      <button id="copy-raw-data-to-clipboard">
-        &aboutSupport.copyRawDataToClipboard.label;
-      </button>
+      <button id="copy-raw-data-to-clipboard" data-l10n-id="copy-raw-data-to-clipboard-label"/>
  -->
-      <button id="copy-to-clipboard">
-        &aboutSupport.copyTextToClipboard.label;
-      </button>
+      <button id="copy-to-clipboard" data-l10n-id="copy-text-to-clipboard-label"/>
 <!-- Added for TB -->
       <button id="send-via-email">
         &aboutSupport.sendViaEmail.label;
       </button>
       <input type="checkbox" id="check-show-private-data" class="data-uionly"
              onchange="onShowPrivateDataChange(this);" />
       <label for="check-show-private-data">&aboutSupport.showPrivateData.mainText;
       <span class="gray-text">&aboutSupport.showPrivateData.explanationText;</span></label>
 <!-- End of TB addition -->
     </div>
 
     <div id="contents">
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.appBasicsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="app-basics-title"/>
 
       <table>
         <tbody>
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsName;
-            </th>
+            <th class="column" data-l10n-id="app-basics-name"/>
 
             <td id="application-box">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsVersion;
-            </th>
+            <th class="column" data-l10n-id="app-basics-version"/>
 
             <td id="version-box">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsBuildID;
-              </th>
+            <th class="column" data-l10n-id="app-basics-build-id"/>
             <td id="buildid-box"></td>
           </tr>
 
 #ifndef ANDROID
 #ifdef MOZ_UPDATER
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsUpdateHistory;
-            </th>
+            <th class="column" data-l10n-id="app-basics-update-history"/>
 
             <td>
-              <button id="show-update-history-button">
-                &aboutSupport.appBasicsShowUpdateHistory;
-              </button>
+              <button id="show-update-history-button" data-l10n-id="app-basics-show-update-history"/>
             </td>
           </tr>
 #endif
 #endif
 
 #ifdef MOZ_UPDATER
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsUpdateChannel;
-            </th>
+            <th class="column" data-l10n-id="app-basics-update-channel"/>
             <td id="updatechannel-box"></td>
           </tr>
 #endif
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsUserAgent;
-            </th>
+            <th class="column" data-l10n-id="app-basics-user-agent"/>
 
             <td id="useragent-box">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsOS;
-            </th>
+            <th class="column" data-l10n-id="app-basics-os"/>
 
             <td id="os-box">
             </td>
           </tr>
 
           <tr id="profile-row" class="no-copy">
-            <th class="column">
-#ifdef XP_WIN
-              &aboutSupport.appBasicsProfileDirWinMac;
-#else
-#ifdef XP_MACOSX
-              &aboutSupport.appBasicsProfileDirWinMac;
-#else
-              &aboutSupport.appBasicsProfileDir;
-#endif
-#endif
-            </th>
+            <th class="column" data-l10n-id="app-basics-profile-dir"/>
 
             <td>
-              <button id="profile-dir-button">
-#ifdef XP_WIN
-                &aboutSupport.showWin2.label;
-#else
-#ifdef XP_MACOSX
-                &aboutSupport.showMac.label;
-#else
-                &aboutSupport.showDir.label;
-#endif
-#endif
-               </button>
+              <button id="profile-dir-button" data-l10n-id="show-dir-label"/>
                <span id="profile-dir-box">
                </span>
             </td>
           </tr>
 
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsBuildConfig;
-            </th>
+            <th class="column" data-l10n-id="app-basics-build-config"/>
 
             <td>
-              <a href="about:buildconfig" target="_blank">about:buildconfig</a>
+              <a href="about:buildconfig">about:buildconfig</a>
             </td>
           </tr>
 
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsMemoryUse;
-            </th>
+            <th class="column" data-l10n-id="app-basics-memory-use"/>
 
             <td>
-              <a href="about:memory" target="_blank">about:memory</a>
+              <a href="about:memory">about:memory</a>
             </td>
           </tr>
 
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsPerformance;
-            </th>
+            <th class="column" data-l10n-id="app-basics-performance"/>
 
             <td>
-              <a href="about:performance" target="_blank">about:performance</a>
+              <a href="about:performance">about:performance</a>
             </td>
           </tr>
 
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsServiceWorkers;
-            </th>
+            <th class="column" data-l10n-id="app-basics-service-workers"/>
 
             <td>
-              <a href="about:serviceworkers" target="_blank">about:serviceworkers</a>
+              <a href="about:serviceworkers">about:serviceworkers</a>
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsMultiProcessSupport;
-            </th>
+            <th class="column" data-l10n-id="app-basics-multi-process-support"/>
 
             <td id="multiprocess-box">
+              <span id="multiprocess-box-process-count"/>
+              <span id="multiprocess-box-status" data-l10n-id="multi-process-status-unknown"/>
             </td>
           </tr>
 
           <tr id="contentprocesses-row">
-            <th class="column">
-              &aboutSupport.appBasicsProcessCount;
-            </th>
+            <th class="column" data-l10n-id="app-basics-process-count"/>
 
             <td id="contentprocesses-box">
             </td>
           </tr>
 
           <tr id="policies-status-row">
-            <th class="column">
-              &aboutSupport.enterprisePolicies;
-            </th>
+            <th class="column" data-l10n-id="app-basics-enterprise-policies"/>
 
             <td id="policies-status">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsKeyGoogle;
-            </th>
+            <th class="column" data-l10n-id="app-basics-key-google"/>
 
             <td id="key-google-box">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsKeyMozilla;
-            </th>
+            <th class="column" data-l10n-id="app-basics-key-mozilla"/>
 
             <td id="key-mozilla-box">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.appBasicsSafeMode;
-            </th>
+            <th class="column" data-l10n-id="app-basics-safe-mode"/>
 
             <td id="safemode-box">
             </td>
           </tr>
 
 #ifndef ANDROID
           <tr class="no-copy">
-            <th class="column">
-              &aboutSupport.appBasicsProfiles;
-            </th>
+            <th class="column" data-l10n-id="app-basics-profiles"/>
 
             <td>
-              <a href="about:profiles" target="_blank">about:profiles</a>
+              <a href="about:profiles">about:profiles</a>
             </td>
           </tr>
 #endif
 
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
@@ -369,550 +303,390 @@
 
         <tbody id="accounts-tbody">
         </tbody>
       </table>
 <!-- End of TB addition -->
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 #ifdef MOZ_CRASHREPORTER
-      <h2 class="major-section" id="crashes-title">
-        &aboutSupport.crashes.title;
-      </h2>
+
+      <h2 class="major-section" id="crashes-title" data-l10n-id="crashes-title"/>
 
       <table id="crashes-table">
         <thead>
           <tr>
-            <th>
-              &aboutSupport.crashes.id;
-            </th>
-            <th>
-              &aboutSupport.crashes.sendDate;
-            </th>
+            <th data-l10n-id="crashes-id"/>
+            <th data-l10n-id="crashes-send-date"/>
           </tr>
         </thead>
         <tbody id="crashes-tbody">
         </tbody>
       </table>
       <p id="crashes-allReports" class="hidden no-copy">
-        <a href="about:crashes" id="crashes-allReportsWithPending" class="block" target="_blank">&aboutSupport.crashes.allReports;</a>
+        <a href="about:crashes" id="crashes-allReportsWithPending"
+           class="block" data-l10n-id="crashes-all-reports"/>
       </p>
-      <p id="crashes-noConfig" class="hidden no-copy">&aboutSupport.crashes.noConfig;</p>
+      <p id="crashes-noConfig" class="hidden no-copy" data-l10n-id="crashes-no-config"/>
 
 #endif
       <!-- - - - - - - - - - - - - - - - - - - - - -->
       <!-- Not used by TB
-      <h2 class="major-section">
-        &aboutSupport.featuresTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="features-title"/>
 
       <table>
         <thead>
           <tr>
-            <th>
-              &aboutSupport.featureName;
-            </th>
-            <th>
-              &aboutSupport.featureVersion;
-            </th>
-            <th>
-              &aboutSupport.featureId;
-            </th>
+            <th data-l10n-id="features-name"/>
+            <th data-l10n-id="features-version"/>
+            <th data-l10n-id="features-id"/>
           </tr>
         </thead>
         <tbody id="features-tbody">
         </tbody>
       </table>
       -->
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.extensionsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="extensions-title"/>
 
-      <table id="extensions-table">
+      <table>
         <thead>
           <tr>
-            <th>
-              &aboutSupport.extensionName;
-            </th>
-            <th>
-              &aboutSupport.extensionVersion;
-            </th>
-            <th>
-              &aboutSupport.extensionEnabled;
-            </th>
-            <th>
-              &aboutSupport.extensionId;
-            </th>
+            <th data-l10n-id="extensions-name"/>
+            <th data-l10n-id="extensions-version"/>
+            <th data-l10n-id="extensions-enabled"/>
+            <th data-l10n-id="extensions-id"/>
           </tr>
         </thead>
         <tbody id="extensions-tbody">
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section" id="security-software-title">
-        &aboutSupport.securitySoftwareTitle;
-      </h2>
+      <h2 class="major-section" id="security-software-title" data-l10n-id="security-software-title"/>
 
       <table id="security-software-table">
         <thead>
           <tr>
-            <th>
-              &aboutSupport.securitySoftwareType;
-            </th>
-            <th>
-              &aboutSupport.securitySoftwareName;
-            </th>
+            <th data-l10n-id="security-software-type"/>
+            <th data-l10n-id="security-software-name"/>
           </tr>
         </thead>
         <tbody>
           <tr>
-            <th class="column">
-              &aboutSupport.securitySoftwareAntivirus;
-            </th>
+            <th class="column" data-l10n-id="security-software-antivirus"/>
 
             <td id="security-software-antivirus">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.securitySoftwareAntiSpyware;
-            </th>
+            <th class="column" data-l10n-id="security-software-antispyware"/>
 
             <td id="security-software-antispyware">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.securitySoftwareFirewall;
-            </th>
+            <th class="column" data-l10n-id="security-software-firewall"/>
 
             <td id="security-software-firewall">
             </td>
           </tr>
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.graphicsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="graphics-title"/>
 
       <table>
         <tbody id="graphics-features-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsFeaturesTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-features-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-tbody">
         </tbody>
 
         <tbody id="graphics-gpu-1-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsGPU1Title;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-gpu1-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-gpu-2-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsGPU2Title;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-gpu2-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-diagnostics-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsDiagnosticsTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-diagnostics-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-decisions-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsDecisionLogTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-decision-log-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-crashguards-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsCrashGuardsTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-crash-guards-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-workarounds-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsWorkaroundsTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-workarounds-title"/>
           </tr>
         </tbody>
 
         <tbody id="graphics-failures-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.graphicsFailureLogTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="graphics-failure-log-title"/>
           </tr>
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.mediaTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="media-title"/>
       <table>
         <tbody id="media-info-tbody">
         </tbody>
 
         <tbody id="media-output-devices-tbody">
           <tr>
-            <th colspan="9" class="title-column">
-              &aboutSupport.mediaOutputDevicesTitle;
-            </th>
+            <th colspan="9" class="title-column" data-l10n-id="media-output-devices-title"/>
           </tr>
           <tr>
-            <th>
-              &aboutSupport.mediaDeviceName;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceGroup;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceVendor;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceState;
-            </th>
-            <th>
-              &aboutSupport.mediaDevicePreferred;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceFormat;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceChannels;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceRate;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceLatency;
-            </th>
+            <th data-l10n-id="media-device-name"/>
+            <th data-l10n-id="media-device-group"/>
+            <th data-l10n-id="media-device-vendor"/>
+            <th data-l10n-id="media-device-state"/>
+            <th data-l10n-id="media-device-preferred"/>
+            <th data-l10n-id="media-device-format"/>
+            <th data-l10n-id="media-device-channels"/>
+            <th data-l10n-id="media-device-rate"/>
+            <th data-l10n-id="media-device-latency"/>
           </tr>
         </tbody>
 
         <tbody id="media-input-devices-tbody">
           <tr>
-            <th colspan="9" class="title-column">
-              &aboutSupport.mediaInputDevicesTitle;
-            </th>
+            <th colspan="9" class="title-column" data-l10n-id="media-input-devices-title"/>
           </tr>
           <tr>
-            <th>
-              &aboutSupport.mediaDeviceName;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceGroup;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceVendor;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceState;
-            </th>
-            <th>
-              &aboutSupport.mediaDevicePreferred;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceFormat;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceChannels;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceRate;
-            </th>
-            <th>
-              &aboutSupport.mediaDeviceLatency;
-            </th>
+            <th data-l10n-id="media-device-name"/>
+            <th data-l10n-id="media-device-group"/>
+            <th data-l10n-id="media-device-vendor"/>
+            <th data-l10n-id="media-device-state"/>
+            <th data-l10n-id="media-device-preferred"/>
+            <th data-l10n-id="media-device-format"/>
+            <th data-l10n-id="media-device-channels"/>
+            <th data-l10n-id="media-device-rate"/>
+            <th data-l10n-id="media-device-latency"/>
           </tr>
         </tbody>
 
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.modifiedKeyPrefsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="modified-key-prefs-title"/>
 
       <table class="prefs-table">
         <thead class="no-copy">
-          <th class="name">
-            &aboutSupport.modifiedPrefsName;
-          </th>
+          <th class="name" data-l10n-id="modified-prefs-name"/>
 
-          <th class="value">
-            &aboutSupport.modifiedPrefsValue;
-          </th>
+          <th class="value" data-l10n-id="modified-prefs-value"/>
         </thead>
 
         <tbody id="prefs-tbody">
         </tbody>
       </table>
 
       <section id="prefs-user-js-section" class="hidden no-copy">
-        <h3>&aboutSupport.userJSTitle;</h3>
-        <p>&aboutSupport.userJSDescription;</p>
+        <h3 data-l10n-id="user-js-title"/>
+        <p data-l10n-id="user-js-description">
+          <a id="prefs-user-js-link" data-l10n-name="user-js-link"></a>
+        </p>
       </section>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.lockedKeyPrefsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="locked-key-prefs-title"/>
 
       <table class="prefs-table">
         <thead class="no-copy">
-          <th class="name">
-            &aboutSupport.lockedPrefsName;
-          </th>
+          <th class="name" data-l10n-id="locked-prefs-name"/>
 
-          <th class="value">
-            &aboutSupport.lockedPrefsValue;
-          </th>
+          <th class="value" data-l10n-id="locked-prefs-value"/>
         </thead>
 
         <tbody id="locked-prefs-tbody">
         </tbody>
       </table>
 
+#ifndef ANDROID
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
-      <h2 class="major-section">
-        &aboutSupport.placeDatabaseTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="place-database-title"/>
 
       <table>
         <tr class="no-copy">
-          <th class="column">
-            &aboutSupport.placeDatabaseIntegrity;
-          </th>
+          <th class="column" data-l10n-id="place-database-integrity"/>
 
           <td>
-            <button id="verify-place-integrity-button">
-              &aboutSupport.placeDatabaseVerifyIntegrity;
-            </button>
+            <button id="verify-place-integrity-button" data-l10n-id="place-database-verify-integrity"/>
             <pre id="verify-place-result" class="hidden no-copy"></pre>
           </td>
         </tr>
       </table>
+#endif
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
-      <h2 class="major-section">
-        &aboutSupport.jsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="js-title"/>
 
       <table>
         <tbody>
           <tr>
-            <th class="column">
-              &aboutSupport.jsIncrementalGC;
-            </th>
+            <th class="column" data-l10n-id="js-incremental-gc"/>
 
             <td id="javascript-incremental-gc">
             </td>
           </tr>
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
-      <h2 class="major-section">
-        &aboutSupport.a11yTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="a11y-title"/>
 
       <table>
         <tbody>
           <tr>
-            <th class="column">
-              &aboutSupport.a11yActivated;
-            </th>
+            <th class="column" data-l10n-id="a11y-activated"/>
 
             <td id="a11y-activated">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.a11yForceDisabled;
-            </th>
+            <th class="column" data-l10n-id="a11y-force-disabled"/>
 
             <td id="a11y-force-disabled">
             </td>
           </tr>
 #if defined(XP_WIN)
           <tr>
-            <th class="column">
-              &aboutSupport.a11yHandlerUsed;
-            </th>
+            <th class="column" data-l10n-id="a11y-handler-used"/>
 
             <td id="a11y-handler-used">
             </td>
           </tr>
 
           <tr>
-            <th class="column">
-              &aboutSupport.a11yInstantiator;
-            </th>
+            <th class="column" data-l10n-id="a11y-instantiator"/>
 
             <td id="a11y-instantiator">
             </td>
           </tr>
 #endif
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
-      <h2 class="major-section">
-        &aboutSupport.libraryVersionsTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="library-version-title"/>
 
       <table>
         <tbody id="libversions-tbody">
         </tbody>
       </table>
 
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
 #if defined(MOZ_SANDBOX)
-      <h2 class="major-section" id="sandbox">
-	&aboutSupport.sandboxTitle;
-      </h2>
+      <h2 class="major-section" id="sandbox" data-l10n-id="sandbox-title"/>
 
       <table>
 	<tbody id="sandbox-tbody">
 	</tbody>
       </table>
 
 #if defined(XP_LINUX)
-      <h4>&aboutSupport.sandboxSyscallLogTitle;</h4>
+      <h4 data-l10n-id="sandbox-sys-call-log-title"/>
       <table>
 	<thead>
 	  <tr>
-	    <th>
-	      &aboutSupport.sandboxSyscallIndex;
-	    </th>
-	    <th>
-	      &aboutSupport.sandboxSyscallAge;
-	    </th>
-	    <th>
-	      &aboutSupport.sandboxSyscallPID;
-	    </th>
-	    <th>
-	      &aboutSupport.sandboxSyscallTID;
-	    </th>
-	    <th>
-	      &aboutSupport.sandboxSyscallProcType;
-	    </th>
-	    <th>
-	      &aboutSupport.sandboxSyscallNumber;
-	    </th>
-	    <th id="sandbox-syscalls-argshead">
-	      &aboutSupport.sandboxSyscallArgs;
-	    </th>
+	    <th data-l10n-id="sandbox-sys-call-index"/>
+	    <th data-l10n-id="sandbox-sys-call-age"/>
+	    <th data-l10n-id="sandbox-sys-call-pid"/>
+	    <th data-l10n-id="sandbox-sys-call-tid"/>
+	    <th data-l10n-id="sandbox-sys-call-proc-type"/>
+	    <th data-l10n-id="sandbox-sys-call-number"/>
+	    <th id="sandbox-syscalls-argshead" data-l10n-id="sandbox-sys-call-args"/>
 	    </tr>
 	</thead>
 	<tbody id="sandbox-syscalls-tbody">
 	</tbody>
       </table>
 #endif
 #endif
 
-      <h2 class="major-section">
-        &aboutSupport.intlTitle;
-      </h2>
+      <h2 class="major-section" data-l10n-id="intl-title"/>
 
       <table>
         <tbody id="intl-localeservice-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.intlAppTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="intl-app-title"/>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlLocalesRequested;
-            </th>
+            <th class="column" data-l10n-id="intl-locales-requested"/>
             <td id="intl-locale-requested">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlLocalesAvailable;
-            </th>
+            <th class="column" data-l10n-id="intl-locales-available"/>
             <td id="intl-locale-available">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlLocalesSupported;
-            </th>
+            <th class="column" data-l10n-id="intl-locales-supported"/>
             <td id="intl-locale-supported">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlRegionalPrefs;
-            </th>
+            <th class="column" data-l10n-id="intl-regional-prefs"/>
             <td id="intl-locale-regionalprefs">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlLocalesDefault;
-            </th>
+            <th class="column" data-l10n-id="intl-locales-default"/>
             <td id="intl-locale-default">
             </td>
           </tr>
         </tbody>
         <tbody id="intl-ospreferences-tbody">
           <tr>
-            <th colspan="2" class="title-column">
-              &aboutSupport.intlOSTitle;
-            </th>
+            <th colspan="2" class="title-column" data-l10n-id="intl-os-title"/>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlOSPrefsSystemLocales;
-            </th>
+            <th class="column" data-l10n-id="intl-os-prefs-system-locales"/>
             <td id="intl-osprefs-systemlocales">
             </td>
           </tr>
           <tr>
-            <th class="column">
-              &aboutSupport.intlRegionalPrefs;
-            </th>
+            <th class="column" data-l10n-id="intl-regional-prefs"/>
             <td id="intl-osprefs-regionalprefs">
             </td>
           </tr>
         </tbody>
       </table>
       <!-- - - - - - - - - - - - - - - - - - - - - -->
 
     </div>
--- a/mail/themes/shared/mail/aboutSupport.css
+++ b/mail/themes/shared/mail/aboutSupport.css
@@ -6,17 +6,19 @@
   display: none;
 }
 
 #safe-mode-box > h3 {
   margin-top: 0;
 }
 
 #check-show-private-data {
-  margin-bottom: -0.5em;
+  margin-inline-start: 10px;
+  margin-inline-end: 3px;
+  vertical-align: text-bottom;
 }
 
 .gray-text {
   color: #888;
 }
 
 .thead-level2 > th {
   background-color: #ebebeb;