Bug 1485418 - Land CFR UI Strings r=flod,k88hudson
authorAndrei Oprea <andrei.br92@gmail.com>
Thu, 30 Aug 2018 19:06:11 +0000
changeset 491876 c14b6edd88634fe65402dbf3d5b404ed1c83fc4c
parent 491875 513621c77f125e2c2198e7e71d064ebb3f13bb74
child 491877 f4ca43d3fc1b14fc56a13a3be6ce877873196ed4
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflod, k88hudson
bugs1485418
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1485418 - Land CFR UI Strings r=flod,k88hudson Land UI strings that are not part of the messages UI Spec https://mozilla.invisionapp.com/share/YWLSGDZGUSF#/screens Differential Revision: https://phabricator.services.mozilla.com/D4003
browser/components/newtab/lib/CFRPageActions.jsm
browser/locales/en-US/browser/newtab/asrouter.ftl
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -1,13 +1,15 @@
 /* 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 { Localization } = ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
+
 const POPUP_NOTIFICATION_ID = "contextual-feature-recommendation";
 
 const DELAY_BEFORE_EXPAND_MS = 1000;
 const DURATION_OF_EXPAND_MS = 5000;
 
 /**
  * A WeakMap from browsers to {host, recommendation} pairs. Recommendations are
  * defined in the ExtensionDoorhanger.schema.json.
@@ -38,26 +40,30 @@ class PageAction {
     // Please use dispatchUserAction instead.
     this._dispatchToASRouter = dispatchToASRouter;
 
     this._popupStateChange = this._popupStateChange.bind(this);
     this._collapse = this._collapse.bind(this);
     this._handleClick = this._handleClick.bind(this);
     this.dispatchUserAction = this.dispatchUserAction.bind(this);
 
+    this._l10n = new Localization([
+      "browser/newtab/asrouter.ftl"
+    ]);
+
     // Saved timeout IDs for scheduled state changes, so they can be cancelled
     this.stateTransitionTimeoutIDs = [];
 
     this.container.onclick = this._handleClick;
   }
 
-  async show(notificationText, shouldExpand = false) {
+  async show(notification_text, shouldExpand = false) {
     this.container.hidden = false;
 
-    this.label.value = notificationText;
+    this.label.value = await this.getStrings({string: notification_text});
 
     // Wait for layout to flush to avoid a synchronous reflow then calculate the
     // label width. We can safely get the width even though the recommendation is
     // collapsed; the label itself remains full width (with its overflow hidden)
     await this.window.promiseDocumentFlushed;
     const [{width}] = this.label.getClientRects();
     this.urlbar.style.setProperty("--cfr-label-width", `${width}px`);
 
@@ -124,20 +130,48 @@ class PageAction {
   // the popup, e.g. by hitting <esc>
   _popupStateChange(state) {
     if (["dismissed", "removed"].includes(state)) {
       this._collapse();
     }
   }
 
   /**
+   * getStrings - Handles getting the localized strings vs message overrides.
+   *              If string_id is not defined it assumes you passed in an override
+   *              message and it just returns it.
+   *              If hasAttributes is true it will try to fetch and reduce them
+   *              into an object with key and value.
+   */
+  async getStrings({string, hasAttributes}) {
+    if (!string.string_id) {
+      return string;
+    }
+
+    const [localeStrings] = await this._l10n.formatMessages([{id: string.string_id}]);
+
+    if (hasAttributes && localeStrings.attributes) {
+      const attributes = localeStrings.attributes.reduce((acc, attribute) => {
+        acc[attribute.name] = attribute.value;
+        return acc;
+      }, {});
+      return {
+        value: localeStrings.value,
+        attributes
+      };
+    } else {
+      return localeStrings.value;
+    }
+  }
+
+  /**
    * Respond to a user click on the recommendation by showing a doorhanger/
    * popup notification
    */
-  _handleClick(event) {
+  async _handleClick(event) {
     const browser = this.window.gBrowser.selectedBrowser;
     if (!RecommendationMap.has(browser)) {
       // There's no recommendation for this browser, so the user shouldn't have
       // been able to click
       this.hide();
       return;
     }
     const {content} = RecommendationMap.get(browser);
@@ -146,39 +180,41 @@ class PageAction {
     // doorhanger is showing
     this._clearScheduledStateChanges();
 
     // A hacky way of setting the popup anchor outside the usual url bar icon box
     // See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
     browser.cfrpopupnotificationanchor = this.container;
 
     const {primary, secondary} = content.buttons;
+    const primaryBtnStrings = await this.getStrings({string: primary.label, hasAttributes: true});
+    const secondaryBtnStrings = await this.getStrings({string: secondary.label, hasAttributes: true});
 
     const mainAction = {
-      label: primary.label,
-      accessKey: primary.accessKey,
+      label: primaryBtnStrings.value,
+      accessKey: primaryBtnStrings.attributes.accesskey,
       callback: () => this.dispatchUserAction(primary.action)
     };
 
     const secondaryActions = [{
-      label: secondary.label,
-      accessKey: secondary.accessKey,
+      label: secondaryBtnStrings.value,
+      accessKey: secondaryBtnStrings.attributes.accesskey,
       callback: this._collapse
     }];
 
     const options = {
       popupIconURL: content.addon.icon,
       hideClose: true,
       eventCallback: this._popupStateChange
     };
 
     this.window.PopupNotifications.show(
       browser,
       POPUP_NOTIFICATION_ID,
-      content.text,
+      await this.getStrings({string: content.text}),
       "cfr",
       mainAction,
       secondaryActions,
       options
     );
   }
 }
 
@@ -202,17 +238,17 @@ const CFRPageActions = {
     if (!pageAction || browser !== win.gBrowser.selectedBrowser) {
       return;
     }
     if (RecommendationMap.has(browser)) {
       const {host, content} = RecommendationMap.get(browser);
       if (isHostMatch(browser, host)) {
         // The browser has a recommendation specified with this host, so show
         // the page action
-        pageAction.show(content.notification_text);
+        pageAction.show(this.getStrings({string: content.notification_text}));
       } else {
         // The user has navigated away from the specified host in the given
         // browser, so the recommendation is no longer valid and should be removed
         RecommendationMap.delete(browser);
         pageAction.hide();
       }
     } else {
       // There's no recommendation specified for this browser, so hide the page action
@@ -230,17 +266,17 @@ const CFRPageActions = {
   async forceRecommendation(browser, recommendation, dispatchToASRouter) {
     // If we are forcing via the Admin page, the browser comes in a different format
     const win = browser.browser.ownerGlobal;
     const {id, content} = recommendation;
     RecommendationMap.set(browser.browser, {id, content});
     if (!PageActionMap.has(win)) {
       PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
     }
-    await PageActionMap.get(win).show(recommendation.content.notification_text, true);
+    await PageActionMap.get(win).show(this.getStrings({string: recommendation.content.notification_text}), true);
     return true;
   },
 
   /**
    * Add a recommendation specific to the given browser and host.
    * @param browser             The browser for the recommendation
    * @param host                The host for the recommendation
    * @param recommendation      The recommendation to show
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/newtab/asrouter.ftl
@@ -0,0 +1,45 @@
+# 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/.
+
+cfr-doorhanger-extension-heading = Recommended Extension
+
+cfr-doorhanger-extension-sumo-link =
+  .tooltiptext = Why am I seeing this
+
+cfr-doorhanger-extension-cancel-button = Not Now
+  .accesskey = N
+
+cfr-doorhanger-extension-ok-button = Add Now
+  .accesskey = A
+
+cfr-doorhanger-extension-learn-more-link = Learn more
+
+# This string is used on a new line below the add-on name
+# Variables:
+#   $name (String) - Add-on author name
+cfr-doorhanger-extension-author = by { $name }
+
+# This is a notification displayed in the address bar.
+# When clicked it opens a panel with a message for the user.
+cfr-doorhanger-extension-notification = Recommendation
+
+## Add-on statistics
+## These strings are used to display the total number of
+## users and rating for an add-on. They are shown next to each other.
+
+# Variables:
+#   $total (Number) - The rating of the add-on from 1 to 5
+cfr-doorhanger-extension-rating =
+  .tooltiptext =
+    { $total ->
+        [one] { $total } star
+       *[other] { $total } stars
+    }
+# Variables:
+#   $total (Number) - The total number of users using the add-on
+cfr-doorhanger-extension-total-users =
+  { $total ->
+      [one] { $total } user
+     *[other] { $total } users
+  }