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 617427 9bdc922716e80b3438bfe7ac01ac38f821299ba5
parent 617426 a323aee88b0aebdd5c56c154615d7456ea21d527
child 617428 e52caa844728f5db71e1875f9de00c91c414b8f4
push id71048
push userbmo:emilio+bugs@crisal.io
push dateFri, 28 Jul 2017 12:58:45 +0000
reviewerslchang, MattN
bugs1375799
milestone56.0a1
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