Bug 1375799 - [Form Autofill] Use localized strings instead of hardcoded strings. r=lchang draft
authorScott Wu <scottcwwu@gmail.com>
Thu, 29 Jun 2017 16:23:44 -0700
changeset 614260 14cc60cc87a160dcadff4fdeced0e0ea40ddf527
parent 614015 5928d905c0bc0b28f5488b236444c7d7991cf8d4
child 638829 d74f93322f52c8fed377c50e2aa70b6c47ea9982
push id69973
push userbmo:scwwu@mozilla.com
push dateMon, 24 Jul 2017 11:26:19 +0000
reviewerslchang
bugs1375799
milestone56.0a1
Bug 1375799 - [Form Autofill] Use localized strings instead of hardcoded strings. r=lchang MozReview-Commit-ID: DgWLFN2EDc8
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/editProfile.js
browser/extensions/formautofill/content/editProfile.xhtml
browser/extensions/formautofill/content/manageProfiles.js
browser/extensions/formautofill/content/manageProfiles.xhtml
browser/extensions/formautofill/locale/en-US/formautofill.properties
browser/extensions/formautofill/skin/shared/editProfile.css
--- 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 profile.
    * 2. First pass try to find exact match.
    * 3. Second pass try to identify values from profile value and options,
    *    and look for a match.
    * @param   {DOMElement} selectEl
    * @param   {object} profile
    * @param   {string} fieldName
    * @returns {DOMElement}
    */
   findSelectOption(selectEl, profile, fieldName) {
     let value = profile[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/${profile.country}`] ||
-                  this.addressData["data/US"];
+    let dataset = this.getCountryAddressData(profile.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/editProfile.js
+++ b/browser/extensions/formautofill/content/editProfile.js
@@ -1,40 +1,70 @@
 /* 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 = {
+      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(REGIONS_BUNDLE_URI, this._elements.country);
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  },
+
+  /**
    * Asks FormAutofillParent to save or update an address.
    * @param  {object} data
    *         {
    *           {string} guid [optional]
    *           {object} address
    *         }
    */
   saveAddress(data) {
@@ -155,9 +185,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/editProfile.xhtml
+++ b/browser/extensions/formautofill/content/editProfile.xhtml
@@ -1,68 +1,74 @@
 <?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>Profile Autofill - Edit Profile</title>
+  <title data-localization="editDialogTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editProfile.css" />
   <link rel="stylesheet" href="chrome://formautofill/skin/editProfile.css" />
   <script src="chrome://formautofill/content/editProfile.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
+    const dialog = window.dialog;
+    dialog.formatForm(dialog._address && dialog._address.country);
+  ]]></script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.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_PROFILE_URL = "chrome://formautofill/content/editProfile.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, "manageProfiles");
 
@@ -52,16 +53,20 @@ ManageProfileDialog.prototype = {
   },
 
   uninit() {
     log.debug("uninit");
     this.detachEventListeners();
     this._elements = null;
   },
 
+  localizeMarkup() {
+    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 @@ ManageProfileDialog.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 ManageProfileDialog();
+window.dialog = new ManageProfileDialog();
--- a/browser/extensions/formautofill/content/manageProfiles.xhtml
+++ b/browser/extensions/formautofill/content/manageProfiles.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>Form Autofill - Manage Addresses</title>
+  <title data-localization="manageDialogTitle"/>
   <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
   <link rel="stylesheet" href="chrome://formautofill/content/manageProfiles.css" />
   <script src="chrome://formautofill/content/manageProfiles.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="profiles" 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.localizeMarkup();
+  </script>
 </body>
 </html>
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locale/en-US/formautofill.properties
@@ -26,8 +26,28 @@ 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 = Form Autofill - Manage Addresses
+addressListHeader = Addresses
+remove = Remove
+add = Add
+edit = Edit
+
+editDialogTitle = Profile Autofill - Edit Profile
+givenName = First Name
+additionalName = Middle Name
+familyName = Last Name
+streetAddress = Street Address
+city = City
+province = Province
+state = State
+postalCode = Postal Code
+zip = Zip Code
+country = Country
+cancel = Cancel
+save = Save
--- a/browser/extensions/formautofill/skin/shared/editProfile.css
+++ b/browser/extensions/formautofill/skin/shared/editProfile.css
@@ -22,16 +22,17 @@ label {
 
 label > span {
   box-sizing: border-box;
   flex: 0 0 8em;
   padding-inline-end: 0.5em;
   align-self: center;
   text-align: end;
   -moz-user-select: none;
+  text-transform: capitalize;
 }
 
 input,
 select {
   box-sizing: border-box;
   flex: 1 0 auto;
   width: calc(50% - 8em);
 }