Bug 1375799 - (Part 1) [Form Autofill] Use localized strings instead of hardcoded strings. r=lchang,MattN
authorScott Wu <scottcwwu@gmail.com>
Thu, 29 Jun 2017 16:23:44 -0700
changeset 420324 9bdc922716e80b3438bfe7ac01ac38f821299ba5
parent 420323 a323aee88b0aebdd5c56c154615d7456ea21d527
child 420325 e52caa844728f5db71e1875f9de00c91c414b8f4
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslchang, MattN
bugs1375799
milestone56.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 1375799 - (Part 1) [Form Autofill] Use localized strings instead of hardcoded strings. r=lchang,MattN MozReview-Commit-ID: DgWLFN2EDc8
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/editAddress.js
browser/extensions/formautofill/content/editAddress.xhtml
browser/extensions/formautofill/content/formautofill.xml
browser/extensions/formautofill/content/manageAddresses.js
browser/extensions/formautofill/content/manageAddresses.xhtml
browser/extensions/formautofill/locale/en-US/formautofill.properties
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -6,16 +6,17 @@
 
 this.EXPORTED_SYMBOLS = ["FormAutofillUtils"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 const ADDRESS_REFERENCES = "chrome://formautofill/content/addressReferences.js";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 this.FormAutofillUtils = {
   get AUTOFILL_FIELDS_THRESHOLD() { return 3; },
 
   _fieldNameInfo: {
     "name": "name",
     "given-name": "name",
     "additional-name": "name",
@@ -197,41 +198,47 @@ this.FormAutofillUtils = {
   loadDataFromScript(url, sandbox = {}) {
     let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                          .getService(Ci.mozIJSSubScriptLoader);
     scriptLoader.loadSubScript(url, sandbox, "utf-8");
     return sandbox;
   },
 
   /**
+   * Get country address data. Fallback to US if not found.
+   * @param   {string} country
+   * @returns {object}
+   */
+  getCountryAddressData(country) {
+    // Load the addressData if needed
+    if (!this._addressDataLoaded) {
+      Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
+      this._addressDataLoaded = true;
+    }
+    return this.addressData[`data/${country}`] || this.addressData["data/US"];
+  },
+
+  /**
    * Find the option element from select element.
    * 1. Try to find the locale using the country from address.
    * 2. First pass try to find exact match.
    * 3. Second pass try to identify values from address value and options,
    *    and look for a match.
    * @param   {DOMElement} selectEl
    * @param   {object} address
    * @param   {string} fieldName
    * @returns {DOMElement}
    */
   findSelectOption(selectEl, address, fieldName) {
     let value = address[fieldName];
     if (!value) {
       return null;
     }
 
-    // Load the addressData if needed
-    if (!this._addressDataLoaded) {
-      Object.assign(this, this.loadDataFromScript(ADDRESS_REFERENCES));
-      this._addressDataLoaded = true;
-    }
-
-    // Set dataset to "data/US" as fallback
-    let dataset = this.addressData[`data/${address.country}`] ||
-                  this.addressData["data/US"];
+    let dataset = this.getCountryAddressData(address.country);
     let collator = new Intl.Collator(dataset.lang, {sensitivity: "base", ignorePunctuation: true});
 
     for (let option of selectEl.options) {
       if (this.strCompare(value, option.value, collator) ||
           this.strCompare(value, option.text, collator)) {
         return option;
       }
     }
@@ -298,12 +305,43 @@ this.FormAutofillUtils = {
    * @param   {string} a
    * @param   {string} b
    * @param   {object} collator
    * @returns {boolean}
    */
   strCompare(a = "", b = "", collator) {
     return !collator.compare(a, b);
   },
+
+  /**
+   * Get formatting information of a given country
+   * @param   {string} country
+   * @returns {object}
+   *         {
+   *           {string} addressLevel1Label
+   *           {string} postalCodeLabel
+   *         }
+   */
+  getFormFormat(country) {
+    const dataset = this.getCountryAddressData(country);
+    return {
+      "addressLevel1Label": dataset.state_name_type || "province",
+      "postalCodeLabel": dataset.zip_name_type || "postalCode",
+    };
+  },
+
+  /**
+   * Localize elements with "data-localization" attribute
+   * @param   {string} bundleURI
+   * @param   {DOMElement} root
+   */
+  localizeMarkup(bundleURI, root) {
+    const bundle = Services.strings.createBundle(bundleURI);
+    let elements = root.querySelectorAll("[data-localization]");
+    for (let element of elements) {
+      element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
+      element.removeAttribute("data-localization");
+    }
+  },
 };
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
--- a/browser/extensions/formautofill/content/editAddress.js
+++ b/browser/extensions/formautofill/content/editAddress.js
@@ -1,40 +1,78 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
+const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 function EditDialog() {
   this._address = window.arguments && window.arguments[0];
   window.addEventListener("DOMContentLoaded", this, {once: true});
 }
 
 EditDialog.prototype = {
-  init() {
-    this._elements = {
+  get _elements() {
+    if (this._elementRefs) {
+      return this._elementRefs;
+    }
+    this._elementRefs = {
+      title: document.querySelector("title"),
+      addressLevel1Label: document.querySelector("#address-level1-container > span"),
+      postalCodeLabel: document.querySelector("#postal-code-container > span"),
+      country: document.getElementById("country"),
       controlsContainer: document.getElementById("controls-container"),
       cancel: document.getElementById("cancel"),
       save: document.getElementById("save"),
     };
+    return this._elementRefs;
+  },
+
+  set _elements(refs) {
+    this._elementRefs = refs;
+  },
+
+  init() {
     this.attachEventListeners();
   },
 
   uninit() {
     this.detachEventListeners();
     this._elements = null;
   },
 
   /**
+   * Format the form based on country. The address-level1 and postal-code labels
+   * should be specific to the given country.
+   * @param  {string} country
+   */
+  formatForm(country) {
+    // TODO: Use fmt to show/hide and order fields (Bug 1383687)
+    const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
+    this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
+    this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  },
+
+  localizeDocument() {
+    if (this._address) {
+      this._elements.title.dataset.localization = "editDialogTitle";
+    }
+    FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
+    this.formatForm(this._address && this._address.country);
+  },
+
+  /**
    * Asks FormAutofillParent to save or update an address.
    * @param  {object} data
    *         {
    *           {string} guid [optional]
    *           {object} address
    *         }
    */
   saveAddress(data) {
@@ -72,17 +110,16 @@ EditDialog.prototype = {
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "DOMContentLoaded": {
         this.init();
         if (this._address) {
-          document.title = "Edit Address";
           this.loadInitialValues(this._address);
         }
         break;
       }
       case "click": {
         this.handleClick(event);
         break;
       }
@@ -156,9 +193,9 @@ EditDialog.prototype = {
    */
   detachEventListeners() {
     window.removeEventListener("keypress", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     document.removeEventListener("input", this);
   },
 };
 
-new EditDialog();
+window.dialog = new EditDialog();
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -1,68 +1,73 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-  <title>Add New Address</title>
+  <title data-localization="addNewDialogTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css" />
   <link rel="stylesheet" href="chrome://formautofill/skin/editAddress.css" />
   <script src="chrome://formautofill/content/editAddress.js"></script>
 </head>
 <body>
   <form autocomplete="off">
     <label id="given-name-container">
-      <span>First Name</span>
+      <span data-localization="givenName"/>
       <input id="given-name" type="text"/>
     </label>
     <label id="additional-name-container">
-      <span>Middle Name</span>
+      <span data-localization="additionalName"/>
       <input id="additional-name" type="text"/>
     </label>
     <label id="family-name-container">
-      <span>Last Name</span>
+      <span data-localization="familyName"/>
       <input id="family-name" type="text"/>
     </label>
     <label id="organization-container">
-      <span>Company</span>
+      <span data-localization="organization"/>
       <input id="organization" type="text"/>
     </label>
     <label id="street-address-container">
-      <span>Street Address</span>
+      <span data-localization="streetAddress"/>
       <textarea id="street-address" rows="3"/>
     </label>
     <label id="address-level2-container">
-      <span>City/Town</span>
+      <span data-localization="city"/>
       <input id="address-level2" type="text"/>
     </label>
     <label id="address-level1-container">
-      <span>State/Province</span>
+      <span/>
       <input id="address-level1" type="text"/>
     </label>
     <label id="postal-code-container">
-      <span>Zip/Postal</span>
+      <span/>
       <input id="postal-code" type="text"/>
     </label>
     <label id="country-container">
-      <span>Country</span>
+      <span data-localization="country"/>
       <select id="country">
         <option/>
-        <option value="US">United States</option>
+        <option value="US" data-localization="us"/>
       </select>
     </label>
     <label id="email-container">
-      <span>Email</span>
+      <span data-localization="email"/>
       <input id="email" type="email"/>
     </label>
     <label id="tel-container">
-      <span>Phone</span>
+      <span data-localization="tel"/>
       <input id="tel" type="tel"/>
     </label>
   </form>
   <div id="controls-container">
-    <button id="cancel">Cancel</button>
-    <button id="save" disabled="disabled">Save</button>
+    <button id="cancel" data-localization="cancel"/>
+    <button id="save" disabled="disabled" data-localization="save"/>
   </div>
+  <script type="application/javascript"><![CDATA[
+    "use strict";
+    // Localize strings before DOMContentLoaded to prevent flash
+    window.dialog.localizeDocument();
+  ]]></script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -174,25 +174,26 @@
            *
            * @private
            * @param {string|string[]} categories
            *        A list of categories that used to generate the message.
            * @param {boolean} hasExtraCategories
            *        Used to determine if it has the extra categories other than the focued category. If
            *        the value is true, we show "Also fill ...", otherwise, show "Fill ..." only.
            */
+          const namespace = "category.";
           this._updateText = (categories = this._allFieldCategories, hasExtraCategories = true) => {
             let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2";
             let sep = this._stringBundle.GetStringFromName("fieldNameSeparator");
             // Show the categories in certain order to conform with the spec.
             let orderedCategoryList = ["address", "name", "organization", "tel", "email"];
             let showCategories = hasExtraCategories ?
               orderedCategoryList.filter(category => categories.includes(category) && category != this._focusedCategory) :
               [this._focusedCategory];
-            let categoriesText = showCategories.map(this._stringBundle.GetStringFromName).join(sep);
+            let categoriesText = showCategories.map(category => this._stringBundle.GetStringFromName(namespace + category)).join(sep);
 
             this._warningTextBox.textContent = this._stringBundle.formatStringFromName(warningTextTmplKey,
               [categoriesText], 1);
             this.parentNode.parentNode.adjustHeight();
           };
 
           /**
            * A handler for updating warning message once selectedIndex has been changed.
--- a/browser/extensions/formautofill/content/manageAddresses.js
+++ b/browser/extensions/formautofill/content/manageAddresses.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
+const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
 
@@ -52,16 +53,20 @@ ManageAddressDialog.prototype = {
   },
 
   uninit() {
     log.debug("uninit");
     this.detachEventListeners();
     this._elements = null;
   },
 
+  localizeDocument() {
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  },
+
   /**
    * Load addresses and render them.
    *
    * @returns {promise}
    */
   loadAddresses() {
     return this.getRecords({collectionName: "addresses"}).then(addresses => {
       log.debug("addresses:", addresses);
@@ -297,9 +302,9 @@ ManageAddressDialog.prototype = {
     window.removeEventListener("keypress", this);
     this._elements.addresses.removeEventListener("change", this);
     this._elements.addresses.removeEventListener("click", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 };
 
-new ManageAddressDialog();
+window.dialog = new ManageAddressDialog();
--- a/browser/extensions/formautofill/content/manageAddresses.xhtml
+++ b/browser/extensions/formautofill/content/manageAddresses.xhtml
@@ -1,29 +1,34 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
-  <title>Saved Addresses</title>
+  <title data-localization="manageDialogTitle"/>
   <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
   <link rel="stylesheet" href="chrome://formautofill/content/manageAddresses.css" />
   <script src="chrome://formautofill/content/manageAddresses.js"></script>
 </head>
 <body>
   <p style="padding-left: 30px; background: url(chrome://browser/skin/warning.svg) no-repeat left center">
     Autofill of addresses is only ready for testing with United States addresses on &lt;input&gt;s and some &lt;select&gt; elements.
     Improvements to form field type detection are in progress.
     <a href="https://luke-chang.github.io/autofill-demo/basic.html" target="_blank">Demo page</a>
   </p>
   <fieldset>
-    <legend>Addresses</legend>
+    <legend data-localization="addressListHeader"/>
     <select id="addresses" size="9" multiple="multiple"/>
   </fieldset>
   <div id="controls-container">
-    <button id="remove" disabled="disabled">Remove</button>
-    <button id="add">Add…</button>
-    <button id="edit" disabled="disabled">Edit…</button>
+    <button id="remove" disabled="disabled" data-localization="remove"/>
+    <button id="add" data-localization="add"/>
+    <button id="edit" disabled="disabled" data-localization="edit"/>
   </div>
+  <script type="application/javascript">
+    "use strict";
+    // Localize strings before DOMContentLoaded to prevent flash
+    window.dialog.localizeDocument();
+  </script>
 </body>
 </html>
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locale/en-US/formautofill.properties
@@ -13,21 +13,45 @@ changeAutofillOptionsOSX = Change Form A
 updateAddressMessage = Would you like to update your address with this new information?
 createAddressLabel = Create New Address
 updateAddressLabel = Update Address
 openAutofillMessagePanel = Open Form Autofill message panel
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionShort = More Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 autocompleteFooterOptionOSXShort = Preferences
-address = address
-name = name
-organization = company
-tel = phone
-email = email
+category.address = address
+category.name = name
+category.organization = company
+category.tel = phone
+category.email = email
 # LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
 fieldNameSeparator = ,\u0020
 # LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
 # text that is displayed for informing users what categories are about to be filled.
 # "%S" will be replaced with a list generated from the pre-defined categories.
 # The text would be e.g. Also fill company, phone, email
 phishingWarningMessage = Also autofills %S
 phishingWarningMessage2 = Autofills %S
+
+manageDialogTitle = Saved Addresses
+addressListHeader = Addresses
+remove = Remove
+add = Add…
+edit = Edit…
+
+addNewDialogTitle = Add New Address
+editDialogTitle = Edit Address
+givenName = First Name
+additionalName = Middle Name
+familyName = Last Name
+organization = Company
+streetAddress = Street Address
+city = City
+province = Province
+state = State
+postalCode = Postal Code
+zip = Zip Code
+country = Country
+tel = Phone
+email = Email
+cancel = Cancel
+save = Save