Bug 1634042, Part 1: migrate page action titles to fluent. r=Gijs,fluent-reviewers,flod
authorEmma Malysz <emalysz@mozilla.com>
Thu, 24 Sep 2020 17:29:16 +0000
changeset 550201 b0c42fffcbc6a6e8ffb1fab4210b5f226295b945
parent 550200 68112bc6b12163894ed527f66d65214b1caeb8a1
child 550202 451025ce801661cfadcebc2595743fc35fe8d037
push id37809
push userapavel@mozilla.com
push dateFri, 25 Sep 2020 03:37:48 +0000
treeherdermozilla-central@4846ccf88574 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, fluent-reviewers, flod
bugs1634042
milestone83.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 1634042, Part 1: migrate page action titles to fluent. r=Gijs,fluent-reviewers,flod Differential Revision: https://phabricator.services.mozilla.com/D87822
browser/base/content/browser-pageActions.js
browser/base/content/browser-places.js
browser/base/content/browser.xhtml
browser/base/content/test/pageActions/browser_page_action_menu.js
browser/components/pocket/content/SaveToPocket.jsm
browser/locales/en-US/browser/browser.ftl
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/PageActions.jsm
python/l10n/fluent_migrations/bug_1634042_page_action_menu.py
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -577,17 +577,17 @@ var BrowserPageActions = {
         this[this._updateMethods[name]](action, panelNode, urlbarNode, value);
       }
     }
   },
 
   _updateMethods: {
     disabled: "_updateActionDisabled",
     iconURL: "_updateActionIconURL",
-    title: "_updateActionTitle",
+    title: "_updateActionLabeling",
     tooltip: "_updateActionTooltip",
     wantsSubview: "_updateActionWantsSubview",
   },
 
   _updateActionDisabled(
     action,
     panelNode,
     urlbarNode,
@@ -626,45 +626,51 @@ var BrowserPageActions = {
         panelNode.style.setProperty(prop, value);
       }
       if (urlbarNode) {
         urlbarNode.style.setProperty(prop, value);
       }
     }
   },
 
-  _updateActionTitle(
+  _updateActionLabeling(
     action,
     panelNode,
     urlbarNode,
     title = action.getTitle(window)
   ) {
-    if (!title) {
-      // `title` is a required action property, but the bookmark action's is an
-      // empty string since its actual title is set via
-      // BookmarkingUI.updateBookmarkPageMenuItem().  The purpose of this early
-      // return is to ignore that empty title.
-      return;
-    }
+    let tabCount = gBrowser.selectedTabs.length;
     if (panelNode) {
-      panelNode.setAttribute("label", title);
+      if (action.panelFluentID) {
+        document.l10n.setAttributes(panelNode, action.panelFluentID, {
+          tabCount,
+        });
+      } else {
+        panelNode.setAttribute("label", title);
+      }
     }
     if (urlbarNode) {
       // Some actions (e.g. Save Page to Pocket) have a wrapper node with the
       // actual controls inside that wrapper. The wrapper is semantically
       // meaningless, so it doesn't get reflected in the accessibility tree.
       // In these cases, we don't want to set aria-label because that will
       // force the element to be exposed to accessibility.
       if (urlbarNode.nodeName != "hbox") {
         urlbarNode.setAttribute("aria-label", title);
       }
       // tooltiptext falls back to the title, so update it too if necessary.
       let tooltip = action.getTooltip(window);
-      if (!tooltip && title) {
-        urlbarNode.setAttribute("tooltiptext", title);
+      if (!tooltip) {
+        if (action.urlbarFluentID) {
+          document.l10n.setAttributes(urlbarNode, action.urlbarFluentID, {
+            tabCount,
+          });
+        } else {
+          urlbarNode.setAttribute("tooltiptext", title);
+        }
       }
     }
   },
 
   _updateActionTooltip(
     action,
     panelNode,
     urlbarNode,
@@ -999,42 +1005,19 @@ var BrowserPageActions = {
     this._contextAction = null;
 
     BrowserAddonUI.removeAddon(action.extensionID, "pageAction");
   },
 
   _contextAction: null,
 
   /**
-   * Titles for a few of the built-in actions are defined in DTD, but the
-   * actions are created in JS.  So what we do is for each title, set an
-   * attribute in markup on the main page action panel whose value is the DTD
-   * string.  In gBuiltInActions, where the built-in actions are defined, we set
-   * the action's initial title to the name of this attribute.  Then when the
-   * action is set up, we get the action's current title, and then get the
-   * attribute on the main panel whose name is that title.  If the attribute
-   * exists, then its value is the actual title, and we update the action with
-   * this title.  Otherwise the action's title has already been set up in this
-   * manner.
-   *
-   * @param  action (PageActions.Action, required)
-   *         The action whose title you're setting.
-   */
-  takeActionTitleFromPanel(action) {
-    let titleOrAttrNameOnPanel = action.getTitle();
-    let attrValueOnPanel = this.panelNode.getAttribute(titleOrAttrNameOnPanel);
-    if (attrValueOnPanel) {
-      this.panelNode.removeAttribute(titleOrAttrNameOnPanel);
-      action.setTitle(attrValueOnPanel);
-    }
-  },
-
-  /**
-   * This is similar to takeActionTitleFromPanel, except it sets an attribute on
-   * a DOM node instead of setting the title on an action.  The point is to map
+   * We use this to set an attribute on the DOM node. If the attribute exists,
+   * then we get the panel node's attribute and set it on the DOM node. Otherwise,
+   * we get the title string and update the attribute with that value. The point is to map
    * attributes on the node to strings on the main panel.  Use this for DOM
    * nodes that don't correspond to actions, like buttons in subviews.
    *
    * @param  node (DOM node, required)
    *         The node you're setting up.
    * @param  attrName (string, required)
    *         The name of the attribute *on the node you're setting up*.
    */
@@ -1081,48 +1064,49 @@ function showBrowserPageActionFeedback(a
   });
 }
 
 // built-in actions below //////////////////////////////////////////////////////
 
 // bookmark
 BrowserPageActions.bookmark = {
   onShowingInPanel(buttonNode) {
-    // Do nothing.
+    if (buttonNode.label == "null") {
+      BookmarkingUI.updateBookmarkPageMenuItem();
+    }
   },
 
   onCommand(event, buttonNode) {
     PanelMultiView.hidePopup(BrowserPageActions.panelNode);
     BookmarkingUI.onStarCommand(event);
   },
 };
 
 // pin tab
 BrowserPageActions.pinTab = {
   updateState() {
     let action = PageActions.actionForID("pinTab");
     let { pinned } = gBrowser.selectedTab;
+    let fluentID;
     if (pinned) {
-      action.setTitle(
-        BrowserPageActions.panelNode.getAttribute("unpinTab-title")
-      );
+      fluentID = "page-action-unpin-tab";
     } else {
-      action.setTitle(
-        BrowserPageActions.panelNode.getAttribute("pinTab-title")
-      );
+      fluentID = "page-action-pin-tab";
     }
 
     let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
     if (panelButton) {
+      document.l10n.setAttributes(panelButton, fluentID + "-panel");
       panelButton.toggleAttribute("pinned", pinned);
     }
     let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(
       action.id
     );
     if (urlbarButton) {
+      document.l10n.setAttributes(urlbarButton, fluentID + "-urlbar");
       urlbarButton.toggleAttribute("pinned", pinned);
     }
   },
 
   onCommand(event, buttonNode) {
     if (gBrowser.selectedTab.pinned) {
       gBrowser.unpinTab(gBrowser.selectedTab);
     } else {
@@ -1155,40 +1139,30 @@ BrowserPageActions.launchSSB = {
     // open the SSB to the current page.
     ssb.launch(gBrowser.selectedBrowser.currentURI);
     gBrowser.removeTab(gBrowser.selectedTab, { closeWindowWithLastTab: false });
   },
 };
 
 // copy URL
 BrowserPageActions.copyURL = {
-  onBeforePlacedInWindow(browserWindow) {
-    let action = PageActions.actionForID("copyURL");
-    BrowserPageActions.takeActionTitleFromPanel(action);
-  },
-
   onCommand(event, buttonNode) {
     PanelMultiView.hidePopup(BrowserPageActions.panelNode);
     Cc["@mozilla.org/widget/clipboardhelper;1"]
       .getService(Ci.nsIClipboardHelper)
       .copyString(
         gURLBar.makeURIReadable(gBrowser.selectedBrowser.currentURI).displaySpec
       );
     let action = PageActions.actionForID("copyURL");
     showBrowserPageActionFeedback(action, event);
   },
 };
 
 // email link
 BrowserPageActions.emailLink = {
-  onBeforePlacedInWindow(browserWindow) {
-    let action = PageActions.actionForID("emailLink");
-    BrowserPageActions.takeActionTitleFromPanel(action);
-  },
-
   onCommand(event, buttonNode) {
     PanelMultiView.hidePopup(BrowserPageActions.panelNode);
     MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
   },
 };
 
 // send to device
 BrowserPageActions.sendToDevice = {
@@ -1198,33 +1172,43 @@ BrowserPageActions.sendToDevice = {
       this._updateTitle();
     });
   },
 
   // The action's title in this window depends on the number of tabs that are
   // selected.
   _updateTitle() {
     let action = PageActions.actionForID("sendToDevice");
-    let string = gBrowserBundle.GetStringFromName(
-      "pageAction.sendTabsToDevice.label"
+    let tabCount = gBrowser.selectedTabs.length;
+
+    let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
+    if (panelButton) {
+      document.l10n.setAttributes(panelButton, action.panelFluentID, {
+        tabCount,
+      });
+    }
+    let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(
+      action.id
     );
-    let tabCount = gBrowser.selectedTabs.length;
-    let title = PluralForm.get(tabCount, string).replace("#1", tabCount);
-    action.setTitle(title, window);
+    if (urlbarButton) {
+      document.l10n.setAttributes(urlbarButton, action.urlbarFluentID, {
+        tabCount,
+      });
+    }
   },
 
   onSubviewPlaced(panelViewNode) {
     let bodyNode = panelViewNode.querySelector(".panel-subview-body");
     let notReady = document.createXULElement("toolbarbutton");
     notReady.classList.add(
       "subviewbutton",
       "subviewbutton-iconic",
       "pageAction-sendToDevice-notReady"
     );
-    notReady.setAttribute("label", "sendToDevice-notReadyTitle");
+    document.l10n.setAttributes(notReady, "page-action-send-tab-not-ready");
     notReady.setAttribute("disabled", "true");
     bodyNode.appendChild(notReady);
     for (let node of bodyNode.children) {
       BrowserPageActions.takeNodeAttributeFromPanel(node, "title");
       BrowserPageActions.takeNodeAttributeFromPanel(node, "shortcut");
     }
   },
 
@@ -1348,21 +1332,16 @@ BrowserPageActions.shareURL = {
     let currentURI = gURLBar.makeURIReadable(browser.currentURI).displaySpec;
     this._windowsUIUtils.shareUrl(currentURI, browser.contentTitle);
   },
 
   onShowingInPanel(buttonNode) {
     this._cached = false;
   },
 
-  onBeforePlacedInWindow(browserWindow) {
-    let action = PageActions.actionForID("shareURL");
-    BrowserPageActions.takeActionTitleFromPanel(action);
-  },
-
   onShowingSubview(panelViewNode) {
     let bodyNode = panelViewNode.querySelector(".panel-subview-body");
 
     // We cache the providers + the UI if the user selects the share
     // panel multiple times while the panel is open.
     if (this._cached && bodyNode.children.length) {
       return;
     }
@@ -1393,20 +1372,17 @@ BrowserPageActions.shareURL = {
       item.setAttribute("share-name", share.name);
       item.setAttribute("image", share.image);
       item.classList.add("subviewbutton", "subviewbutton-iconic");
       item.addEventListener("command", onCommand);
       fragment.appendChild(item);
     });
 
     let item = document.createXULElement("toolbarbutton");
-    item.setAttribute(
-      "label",
-      BrowserPageActions.panelNode.getAttribute("shareMore-label")
-    );
+    document.l10n.setAttributes(item, "page-action-share-more-panel");
     item.classList.add(
       "subviewbutton",
       "subviewbutton-iconic",
       "share-more-button"
     );
     item.addEventListener("command", onCommand);
     fragment.appendChild(item);
 
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1941,21 +1941,30 @@ var BookmarkingUI = {
         // a newer l10n id pending to be set.
         if (this._latestMenuItemL10nId != menuItemL10nId) {
           return;
         }
 
         // We assume that menuItemL10nId has a single attribute.
         let label = l10n[0].attributes[0].value;
 
-        // Update the title and the starred state for the page action panel.
-        PageActions.actionForID(PageActions.ACTION_ID_BOOKMARK).setTitle(
-          label,
-          window
+        // Update the label, tooltip, and the starred state for the
+        // page action panel.
+        let panelButton = BrowserPageActions.panelButtonNodeForActionID(
+          PageActions.ACTION_ID_BOOKMARK
         );
+        if (panelButton) {
+          panelButton.setAttribute("label", label);
+        }
+        let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(
+          PageActions.ACTION_ID_BOOKMARK
+        );
+        if (urlbarButton) {
+          urlbarButton.setAttribute("tooltiptext", label);
+        }
       });
     }
   },
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     // Don't handle events for submenus.
     if (event.target != event.currentTarget) {
       return;
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -554,25 +554,17 @@
     <panel id="pageActionPanel"
            class="cui-widget-panel panel-no-padding"
            role="group"
            type="arrow"
            hidden="true"
            flip="slide"
            position="bottomcenter topright"
            tabspecific="true"
-           noautofocus="true"
-           pinTab-title="&pinTab.label;"
-           unpinTab-title="&unpinTab.label;"
-           pocket-title="&saveToPocketCmd.label;"
-           copyURL-title="&pageAction.copyLink.label;"
-           emailLink-title="&emailPageCmd.label;"
-           sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
-           shareURL-title="&pageAction.shareUrl.label;"
-           shareMore-label="&pageAction.shareMore.label;">
+           noautofocus="true">
       <panelmultiview id="pageActionPanelMultiView"
                       mainViewId="pageActionPanelMainView"
                       viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionContextMenu"
                    class="PanelUI-subView">
           <vbox class="panel-subview-body"/>
         </panelview>
--- a/browser/base/content/test/pageActions/browser_page_action_menu.js
+++ b/browser/base/content/test/pageActions/browser_page_action_menu.js
@@ -185,18 +185,18 @@ add_task(async function pinTabFromPanel(
     Assert.equal(pinTabButton.label, "Pin Tab");
     let hiddenPromise = promisePageActionPanelHidden();
     EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
     await hiddenPromise;
 
     Assert.ok(gBrowser.selectedTab.pinned, "Tab was pinned");
 
     // Open the panel and click Unpin Tab.
+    await promisePageActionPanelOpen();
     Assert.equal(pinTabButton.label, "Unpin Tab");
-    await promisePageActionPanelOpen();
 
     hiddenPromise = promisePageActionPanelHidden();
     EventUtils.synthesizeMouseAtCenter(pinTabButton, {});
     await hiddenPromise;
 
     Assert.ok(!gBrowser.selectedTab.pinned, "Tab was unpinned");
   });
 });
@@ -882,20 +882,16 @@ add_task(async function sendToDevice_tit
         // be "Send Tab to Device".
         await promisePageActionPanelOpen();
         let sendToDeviceButton = document.getElementById(
           "pageAction-panel-sendToDevice"
         );
         Assert.ok(!sendToDeviceButton.disabled);
 
         Assert.equal(sendToDeviceButton.label, "Send Tab to Device");
-        Assert.equal(
-          PageActions.actionForID("sendToDevice").getTitle(window),
-          "Send Tab to Device"
-        );
 
         // Hide the panel.
         let hiddenPromise = promisePageActionPanelHidden();
         BrowserPageActions.panelNode.hidePopup();
         await hiddenPromise;
 
         // Add the other tab to the selection.
         gBrowser.addToMultiSelectedTabs(
@@ -903,20 +899,16 @@ add_task(async function sendToDevice_tit
           { isLastMultiSelectChange: true }
         );
 
         // Open the panel again.  Now the action's title should be "Send 2 Tabs to
         // Device".
         await promisePageActionPanelOpen();
         Assert.ok(!sendToDeviceButton.disabled);
         Assert.equal(sendToDeviceButton.label, "Send 2 Tabs to Device");
-        Assert.equal(
-          PageActions.actionForID("sendToDevice").getTitle(window),
-          "Send 2 Tabs to Device"
-        );
 
         // Hide the panel.
         hiddenPromise = promisePageActionPanelHidden();
         BrowserPageActions.panelNode.hidePopup();
         await hiddenPromise;
 
         cleanUp();
 
--- a/browser/components/pocket/content/SaveToPocket.jsm
+++ b/browser/components/pocket/content/SaveToPocket.jsm
@@ -49,27 +49,24 @@ var PocketPageAction = {
 
   init() {
     let id = "pocket";
     this.pageAction = PageActions.actionForID(id);
     if (!this.pageAction) {
       this.pageAction = PageActions.addAction(
         new PageActions.Action({
           id,
-          title: "pocket-title",
+          panelFluentID: "page-action-pocket-panel",
+          urlbarFluentID: "urlbar-pocket-button",
           pinnedToUrlbar: true,
           wantsIframe: true,
           urlbarIDOverride: "pocket-button",
           anchorIDOverride: "pocket-button",
           _insertBeforeActionID: PageActions.ACTION_ID_PIN_TAB,
           _urlbarNodeInMarkup: true,
-          onBeforePlacedInWindow(window) {
-            let action = PageActions.actionForID("pocket");
-            window.BrowserPageActions.takeActionTitleFromPanel(action);
-          },
           onIframeShowing(iframe, panel) {
             Pocket.onShownInPhotonPageActionPanel(panel, iframe);
 
             let doc = panel.ownerDocument;
             let urlbarNode = doc.getElementById("pocket-button");
             if (!urlbarNode) {
               return;
             }
--- a/browser/locales/en-US/browser/browser.ftl
+++ b/browser/locales/en-US/browser/browser.ftl
@@ -159,16 +159,61 @@ page-action-add-to-urlbar =
     .label = Add to Address Bar
 page-action-manage-extension =
     .label = Manage Extension…
 page-action-remove-from-urlbar =
     .label = Remove from Address Bar
 page-action-remove-extension =
     .label = Remove Extension
 
+## Page Action menu
+
+# Variables
+# $tabCount (integer) - Number of tabs selected
+page-action-send-tabs-panel =
+  .label = { $tabCount ->
+      [1] Send Tab to Device
+     *[other] Send { $tabCount } Tabs to Device
+  }
+page-action-send-tabs-urlbar =
+  .tooltiptext = { $tabCount ->
+      [1] Send Tab to Device
+     *[other] Send { $tabCount } Tabs to Device
+  }
+page-action-pocket-panel =
+  .label = Save Page to { -pocket-brand-name }
+page-action-copy-url-panel =
+  .label = Copy Link
+page-action-copy-url-urlbar =
+  .tooltiptext = Copy Link
+page-action-email-link-panel =
+  .label = Email Link…
+page-action-email-link-urlbar =
+  .tooltiptext = Email Link…
+page-action-share-url-panel =
+  .label = Share
+page-action-share-url-urlbar =
+  .tooltiptext = Share
+page-action-share-more-panel =
+  .label = More…
+page-action-send-tab-not-ready =
+  .label = Syncing Devices…
+# "Pin" is being used as a metaphor for expressing the fact that these tabs
+# are "pinned" to the left edge of the tabstrip. Really we just want the
+# string to express the idea that this is a lightweight and reversible
+# action that keeps your tab where you can reach it easily.
+page-action-pin-tab-panel =
+  .label = Pin Tab
+page-action-pin-tab-urlbar =
+  .tooltiptext = Pin Tab
+page-action-unpin-tab-panel =
+  .label = Unpin Tab
+page-action-unpin-tab-urlbar =
+  .tooltiptext = Unpin Tab
+
 ## Auto-hide Context Menu
 
 full-screen-autohide =
     .label = Hide Toolbars
     .accesskey = H
 full-screen-exit =
     .label = Exit Full Screen Mode
     .accesskey = F
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -5,24 +5,16 @@
 <!-- LOCALIZATION NOTE : FILE This file contains the browser main menu items -->
 <!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
 
 <!ENTITY appmenu.tooltip                     "Open menu">
 <!ENTITY navbarOverflow.label                "More tools…">
 
 <!-- Tab context menu -->
 
-<!-- LOCALIZATION NOTE (pinTab.label, unpinTab.label): "Pin" is being
-used as a metaphor for expressing the fact that these tabs are "pinned" to the
-left edge of the tabstrip. Really we just want the string to express the idea
-that this is a lightweight and reversible action that keeps your tab where you
-can reach it easily. -->
-<!ENTITY  pinTab.label                       "Pin Tab">
-<!ENTITY  unpinTab.label                     "Unpin Tab">
-
 <!ENTITY  listAllTabs.label      "List all tabs">
 
 <!ENTITY tabCmd.label "New Tab">
 <!ENTITY openFileCmd.label "Open File…">
 <!ENTITY printCmd.label "Print…">
 
 <!ENTITY taskManagerCmd.label "Task Manager">
 
@@ -182,25 +174,20 @@ this container is a toolbar. This avoids
 
 <!-- LOCALIZATION NOTE (searchInput.placeholder):
      This string is displayed in the search box when the input field is empty. -->
 <!ENTITY searchInput.placeholder      "Search">
 <!ENTITY searchIcon.tooltip           "Search">
 
 <!ENTITY openLinkCmdInTab.accesskey   "T">
 
-<!ENTITY pageAction.copyLink.label    "Copy Link">
-
 <!-- LOCALIZATION NOTE(pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
   "Pocket" is a brand name. -->
-<!ENTITY saveToPocketCmd.label        "Save Page to Pocket">
 <!ENTITY pocketMenuitem.label         "View Pocket List">
 
-<!ENTITY emailPageCmd.label           "Email Link…">
-
 <!ENTITY fullZoom.label                 "Zoom">
 
 <!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
 
 <!ENTITY quitApplicationCmdWin2.label       "Exit">
 <!ENTITY quitApplicationCmdWin2.accesskey   "x">
 <!ENTITY quitApplicationCmdWin2.tooltip     "Exit &brandShorterName;">
 <!ENTITY quitApplicationCmd.label       "Quit">
@@ -238,18 +225,15 @@ this container is a toolbar. This avoids
 
 <!ENTITY updateAvailable.panelUI.label "Download &brandShorterName; update">
 <!ENTITY updateManual.panelUI.label "Download a fresh copy of &brandShorterName;">
 <!ENTITY updateUnsupported.panelUI.label "You cannot perform further updates">
 <!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
 
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
-<!ENTITY pageAction.shareUrl.label "Share">
-<!ENTITY pageAction.shareMore.label "More…">
-
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
 
 <!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
      display a tooltip for accessibility indicator in toolbar/tabbar. It is also
      used as a textual label for the indicator used by assistive technology
      users. -->
 <!ENTITY accessibilityIndicator.tooltip "Accessibility Features Enabled">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -945,22 +945,16 @@ playTabs.accesskey = y
 
 # LOCALIZATION NOTE (sendTabsToDevice.label):
 # Semi-colon list of plural forms.
 # See: https://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the number of tabs sent to the device.
 sendTabsToDevice.label = Send Tab to Device;Send #1 Tabs to Device
 sendTabsToDevice.accesskey = n
 
-# LOCALIZATION NOTE (pageAction.sendTabsToDevice.label):
-# Semi-colon list of plural forms.
-# See: https://developer.mozilla.org/en/docs/Localization_and_Plurals
-# #1 is the number of tabs sent to the device.
-pageAction.sendTabsToDevice.label = Send Tab to Device;Send #1 Tabs to Device
-
 # LOCALIZATION NOTE (pendingCrashReports2.label): Semi-colon list of plural forms
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the number of pending crash reports
 pendingCrashReports2.label = You have an unsent crash report;You have #1 unsent crash reports
 pendingCrashReports.viewAll = View
 pendingCrashReports.send = Send
 pendingCrashReports.alwaysSend = Always Send
 
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -444,18 +444,18 @@ var PageActions = {
  * that window, then the global state will be returned.
  *
  * `options` is a required object with the following properties.  Regarding the
  * properties discussed in the previous paragraph, the values in `options` set
  * global state.
  *
  * @param id (string, required)
  *        The action's ID.  Treat this like the ID of a DOM node.
- * @param title (string, required)
- *        The action's title.
+ * @param title (string, optional)
+ *        The action's title. It is optional for built in actions.
  * @param anchorIDOverride (string, optional)
  *        Pass a string to override the node to which the action's activated-
  *        action panel is anchored.
  * @param disabled (bool, optional)
  *        Pass true to cause the action to be disabled initially in all browser
  *        windows.  False by default.
  * @param extensionID (string, optional)
  *        If the action lives in an extension, pass its ID.
@@ -520,37 +520,42 @@ var PageActions = {
  *        Called when the action's subview is added to its parent panel in a
  *        browser window:
  *        onSubviewPlaced(panelViewNode)
  *        * panelViewNode: The subview's panelview node.
  * @param onSubviewShowing (function, optional)
  *        Called when the action's subview is showing in a browser window:
  *        onSubviewShowing(panelViewNode)
  *        * panelViewNode: The subview's panelview node.
+ * @param panelFluentID (string, optional)
+ *        The action panel node's corresponding fluent attribute used for its label.
  * @param pinnedToUrlbar (bool, optional)
  *        Pass true to pin the action to the urlbar.  An action is shown in the
  *        urlbar if it's pinned and not disabled.  False by default.
  * @param tooltip (string, optional)
  *        The action's button tooltip text.
  * @param urlbarIDOverride (string, optional)
  *        Usually the ID of the action's button in the urlbar will be generated
  *        automatically.  Pass a string for this property to override that with
  *        your own ID.
+ * @param urlbarFluentID (string, optional)
+ *        The action urlbar node's corresponding fluent attribute used for its
+ *        tooltip.
  * @param wantsIframe (bool, optional)
  *        Pass true to make an action that shows an iframe in a panel when
  *        clicked.
  * @param wantsSubview (bool, optional)
  *        Pass true to make an action that shows a panel subview when clicked.
  * @param disablePrivateBrowsing (bool, optional)
  *        Pass true to prevent the action from showing in a private browsing window.
  */
 function Action(options) {
   setProperties(this, options, {
     id: true,
-    title: !options._isSeparator,
+    title: false,
     anchorIDOverride: false,
     disabled: false,
     extensionID: false,
     iconURL: false,
     isBadged: false,
     labelForHistogram: false,
     onBeforePlacedInWindow: false,
     onCommand: false,
@@ -559,19 +564,21 @@ function Action(options) {
     onIframeShowing: false,
     onLocationChange: false,
     onPlacedInPanel: false,
     onPlacedInUrlbar: false,
     onRemovedFromWindow: false,
     onShowingInPanel: false,
     onSubviewPlaced: false,
     onSubviewShowing: false,
+    panelFluentID: false,
     pinnedToUrlbar: false,
     tooltip: false,
     urlbarIDOverride: false,
+    urlbarFluentID: false,
     wantsIframe: false,
     wantsSubview: false,
     disablePrivateBrowsing: false,
 
     // private
 
     // (string, optional)
     // The ID of another action before which to insert this new action in the
@@ -633,16 +640,38 @@ Action.prototype = {
 
   /**
    * The action's ID (string)
    */
   get id() {
     return this._id;
   },
 
+  /**
+   * The action panel node's fluent ID (string)
+   */
+  get panelFluentID() {
+    return this._panelFluentID;
+  },
+
+  set panelFluentID(id) {
+    this._panelFluentID = id;
+  },
+
+  /**
+   * The action urlbar node's fluent ID (string)
+   */
+  get urlbarFluentID() {
+    return this._urlbarFluentID;
+  },
+
+  set urlbarFluentID(id) {
+    this._urlbarFluentID = id;
+  },
+
   get disablePrivateBrowsing() {
     return !!this._disablePrivateBrowsing;
   },
 
   /**
    * Verifies that the action can be shown in a private window.  For
    * extensions, verifies the extension has access to the window.
    */
@@ -728,17 +757,18 @@ Action.prototype = {
     let cssURL = urls ? escapeCSSURL(urls) : null;
     return Object.freeze({
       "--pageAction-image-16px": cssURL,
       "--pageAction-image-32px": cssURL,
     });
   },
 
   /**
-   * The action's title (string)
+   * The action's title (string). Note, built in actions will
+   * not have a title property.
    */
   getTitle(browserWindow = null) {
     return this._getProperties(browserWindow).title;
   },
   setTitle(value, browserWindow = null) {
     return this._setProperty("title", value, browserWindow);
   },
 
@@ -1125,32 +1155,28 @@ PageActions.PREF_PERSISTED_ACTIONS = PRE
 // want to keep track of), make sure to also update Histograms.json for the
 // new actions.
 var gBuiltInActions = [
   // bookmark
   {
     id: ACTION_ID_BOOKMARK,
     urlbarIDOverride: "star-button-box",
     _urlbarNodeInMarkup: true,
-    // The title is set by BookmarkingUI.updateBookmarkPageMenuItem().
-    title: "",
     pinnedToUrlbar: true,
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).bookmark.onShowingInPanel(buttonNode);
     },
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).bookmark.onCommand(event, buttonNode);
     },
   },
 
   // pin tab
   {
     id: ACTION_ID_PIN_TAB,
-    // The title is set in browser-pageActions.js.
-    title: "",
     onBeforePlacedInWindow(browserWindow) {
       function handlePinEvent() {
         browserPageActions(browserWindow).pinTab.updateState();
       }
       function handleWindowUnload() {
         for (let event of ["TabPinned", "TabUnpinned"]) {
           browserWindow.removeEventListener(event, handlePinEvent);
         }
@@ -1181,46 +1207,37 @@ var gBuiltInActions = [
   {
     id: ACTION_ID_BOOKMARK_SEPARATOR,
     _isSeparator: true,
   },
 
   // copy URL
   {
     id: "copyURL",
-    title: "copyURL-title",
-    onBeforePlacedInWindow(browserWindow) {
-      browserPageActions(browserWindow).copyURL.onBeforePlacedInWindow(
-        browserWindow
-      );
-    },
+    panelFluentID: "page-action-copy-url-panel",
+    urlbarFluentID: "page-action-copy-url-urlbar",
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).copyURL.onCommand(event, buttonNode);
     },
   },
 
   // email link
   {
     id: "emailLink",
-    title: "emailLink-title",
-    onBeforePlacedInWindow(browserWindow) {
-      browserPageActions(browserWindow).emailLink.onBeforePlacedInWindow(
-        browserWindow
-      );
-    },
+    panelFluentID: "page-action-email-link-panel",
+    urlbarFluentID: "page-action-email-link-urlbar",
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).emailLink.onCommand(event, buttonNode);
     },
   },
 
   // add search engine
   {
     id: "addSearchEngine",
     // The title is set in browser-pageActions.js.
-    title: "",
     isBadged: true,
     _transient: true,
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).addSearchEngine.onShowingInPanel();
     },
     onCommand(event, buttonNode) {
       browserPageActions(buttonNode).addSearchEngine.onCommand(
         event,
@@ -1234,19 +1251,20 @@ var gBuiltInActions = [
     },
   },
 ];
 
 // send to device
 if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
   gBuiltInActions.push({
     id: "sendToDevice",
+    panelFluentID: "page-action-send-tabs-panel",
     // The actual title is set by each window, per window, and depends on the
     // number of tabs that are selected.
-    title: "sendToDevice",
+    urlbarFluentID: "page-action-send-tabs-urlbar",
     onBeforePlacedInWindow(browserWindow) {
       browserPageActions(browserWindow).sendToDevice.onBeforePlacedInWindow(
         browserWindow
       );
     },
     onLocationChange(browserWindow) {
       browserPageActions(browserWindow).sendToDevice.onLocationChange();
     },
@@ -1277,45 +1295,37 @@ if (SiteSpecificBrowserService.isEnabled
     },
   });
 }
 
 // share URL
 if (AppConstants.platform == "macosx") {
   gBuiltInActions.push({
     id: "shareURL",
-    title: "shareURL-title",
+    panelFluentID: "page-action-share-url-panel",
+    urlbarFluentID: "page-action-share-url-urlbar",
     onShowingInPanel(buttonNode) {
       browserPageActions(buttonNode).shareURL.onShowingInPanel(buttonNode);
     },
-    onBeforePlacedInWindow(browserWindow) {
-      browserPageActions(browserWindow).shareURL.onBeforePlacedInWindow(
-        browserWindow
-      );
-    },
     wantsSubview: true,
     onSubviewShowing(panelViewNode) {
       browserPageActions(panelViewNode).shareURL.onShowingSubview(
         panelViewNode
       );
     },
   });
 }
 
 if (AppConstants.isPlatformAndVersionAtLeast("win", "6.4")) {
   gBuiltInActions.push(
     // Share URL
     {
       id: "shareURL",
-      title: "shareURL-title",
-      onBeforePlacedInWindow(buttonNode) {
-        browserPageActions(buttonNode).shareURL.onBeforePlacedInWindow(
-          buttonNode
-        );
-      },
+      panelFluentID: "page-action-share-url-panel",
+      urlbarFluentID: "page-action-share-url-urlbar",
       onCommand(event, buttonNode) {
         browserPageActions(buttonNode).shareURL.onCommand(event, buttonNode);
       },
     }
   );
 }
 
 /**
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1634042_page_action_menu.py
@@ -0,0 +1,114 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate import COPY, PLURALS, REPLACE, REPLACE_IN_TEXT
+from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE, transforms_from
+
+
+def migrate(ctx):
+    """Bug 1634042 - Adding page action labels to fluent, part {index}."""
+
+    ctx.add_transforms(
+        "browser/browser/browser.ftl",
+        "browser/browser/browser.ftl",
+        transforms_from("""
+page-action-pin-tab-panel =
+    .label = { COPY(from_path, "pinTab.label") }
+page-action-pin-tab-urlbar =
+    .tooltiptext = { COPY(from_path, "pinTab.label") }
+page-action-unpin-tab-panel =
+    .label = { COPY(from_path, "unpinTab.label") }
+page-action-unpin-tab-urlbar =
+    .tooltiptext = { COPY(from_path, "unpinTab.label") }
+page-action-copy-url-panel =
+    .label = { COPY(from_path, "pageAction.copyLink.label") }
+page-action-copy-url-urlbar =
+    .tooltiptext = { COPY(from_path, "pageAction.copyLink.label") }
+page-action-email-link-panel =
+    .label = { COPY(from_path, "emailPageCmd.label") }
+page-action-email-link-urlbar =
+    .tooltiptext = { COPY(from_path, "emailPageCmd.label") }
+page-action-share-url-panel =
+    .label = { COPY(from_path, "pageAction.shareUrl.label") }
+page-action-share-url-urlbar =
+    .tooltiptext = { COPY(from_path, "pageAction.shareUrl.label") }
+page-action-share-more-panel =
+    .label = { COPY(from_path, "pageAction.shareMore.label") }
+page-action-send-tab-not-ready =
+    .label = { COPY(from_path, "sendToDevice.syncNotReady.label") }
+""", from_path="browser/chrome/browser/browser.dtd"))
+    ctx.add_transforms(
+        "browser/browser/browser.ftl",
+        "browser/browser/browser.ftl",
+        [
+		FTL.Message(
+		    id=FTL.Identifier("page-action-pocket-panel"),
+		    attributes=[
+		    	FTL.Attribute(
+		    		id=FTL.Identifier("label"),
+				    value=REPLACE(
+				        "browser/chrome/browser/browser.dtd",
+				        "saveToPocketCmd.label",
+				        {
+				            "Pocket": TERM_REFERENCE("pocket-brand-name"),
+				        },
+				    )
+				)
+			]
+		)
+		]
+	)
+    ctx.add_transforms(
+        "browser/browser/browser.ftl",
+        "browser/browser/browser.ftl",
+        [
+		FTL.Message(
+		    id=FTL.Identifier("page-action-send-tabs-panel"),
+		    attributes=[
+		    	FTL.Attribute(
+		    		id=FTL.Identifier("label"),
+				    value=PLURALS(
+				        "browser/chrome/browser/browser.properties",
+				        "pageAction.sendTabsToDevice.label",
+				        VARIABLE_REFERENCE("tabCount"),
+				        lambda text: REPLACE_IN_TEXT(
+				            text,
+				            {
+				                "#1": VARIABLE_REFERENCE("tabCount")
+				            }
+				        )
+				    )
+				)
+		    ]
+		)
+		]
+	)
+    ctx.add_transforms(
+        "browser/browser/browser.ftl",
+        "browser/browser/browser.ftl",
+        [
+		FTL.Message(
+		    id=FTL.Identifier("page-action-send-tabs-urlbar"),
+		    attributes=[
+		    	FTL.Attribute(
+		    		id=FTL.Identifier("tooltiptext"),
+				    value=PLURALS(
+				        "browser/chrome/browser/browser.properties",
+				        "pageAction.sendTabsToDevice.label",
+				        VARIABLE_REFERENCE("tabCount"),
+				        lambda text: REPLACE_IN_TEXT(
+				            text,
+				            {
+				                "#1": VARIABLE_REFERENCE("tabCount")
+				            }
+				        )
+				    )
+				)
+		    ]
+		)
+		]
+	)