Bug 1743347 - [devtools] Move whyPaused.* string to a ftl file in devtools/shared. r=bomsy,jdescottes,fluent-reviewers,flod
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Thu, 02 Dec 2021 07:10:27 +0000
changeset 600890 6eaac86a49224c5818897ddb47466da5c52f4a53
parent 600889 0e8787dbc04c79f8fd8036177b147cc783e82d68
child 600891 41993db6c7a1b1e65d71e01a81777517de5bae1d
push id154033
push usernchevobbe@mozilla.com
push dateThu, 02 Dec 2021 07:12:57 +0000
treeherderautoland@90369a230cb4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbomsy, jdescottes, fluent-reviewers, flod
bugs1743347, 1743155
milestone96.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 1743347 - [devtools] Move whyPaused.* string to a ftl file in devtools/shared. r=bomsy,jdescottes,fluent-reviewers,flod Since those strings can be consumed by the PausedDebuggerOverlay, on the server, we need to put them in devtools/shared. As this will create some work for the l10n team, we migrate them to Fluent so we're feeding 2 birds with 1 scone. The `<LocalizationProvider>` is added directly in `<WhyPaused>` (and not in `<App>`, as it's messing up with the React context (See Bug 1743155). Some snapshots are updated. It's unfortunate that we don't see the translated strings there anymore, but we can't have nice things in Jest. Differential Revision: https://phabricator.services.mozilla.com/D132260
devtools/client/debugger/panel.js
devtools/client/debugger/src/components/App.js
devtools/client/debugger/src/components/SecondaryPanes/WhyPaused.js
devtools/client/debugger/src/components/test/WhyPaused.spec.js
devtools/client/debugger/src/components/test/__snapshots__/WhyPaused.spec.js.snap
devtools/client/debugger/src/main.js
devtools/client/debugger/src/utils/bootstrap.js
devtools/client/debugger/src/utils/pause/why.js
devtools/client/framework/test/metrics/browser_metrics_debugger.js
devtools/client/locales/en-US/debugger.properties
devtools/server/actors/highlighters/paused-debugger.js
devtools/shared/constants.js
devtools/shared/locales/en-US/debugger-paused-reasons.ftl
devtools/shared/locales/jar.mn
python/l10n/fluent_migrations/bug_1743347_devtools_debugger_whyPaused.py
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -1,13 +1,16 @@
 /* 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/>. */
 
 const { MultiLocalizationHelper } = require("devtools/shared/l10n");
+const {
+  FluentL10n,
+} = require("devtools/client/shared/fluent-l10n/fluent-l10n");
 
 loader.lazyRequireGetter(
   this,
   "openContentLink",
   "devtools/client/shared/link",
   true
 );
 loader.lazyRequireGetter(
@@ -40,28 +43,34 @@ async function getNodeFront(gripOrFront,
   const inspectorFront = await toolbox.target.getFront("inspector");
   return inspectorFront.getNodeFrontFromNodeGrip(gripOrFront);
 }
 
 class DebuggerPanel {
   constructor(iframeWindow, toolbox, commands) {
     this.panelWin = iframeWindow;
     this.panelWin.L10N = L10N;
+
     this.toolbox = toolbox;
     this.commands = commands;
   }
 
   async open() {
+    // whypaused-* strings are in devtools/shared as they're used in the PausedDebuggerOverlay as well
+    const fluentL10n = new FluentL10n();
+    await fluentL10n.init(["devtools/shared/debugger-paused-reasons.ftl"]);
+
     const {
       actions,
       store,
       selectors,
       client,
     } = await this.panelWin.Debugger.bootstrap({
       commands: this.commands,
+      fluentBundles: fluentL10n.getBundles(),
       resourceCommand: this.toolbox.resourceCommand,
       workers: {
         sourceMaps: this.toolbox.sourceMapService,
         evaluationsParser: this.toolbox.parserService,
       },
       panel: this,
     });
 
--- a/devtools/client/debugger/src/components/App.js
+++ b/devtools/client/debugger/src/components/App.js
@@ -57,16 +57,17 @@ class App extends Component {
       shortcutsModalEnabled: false,
       startPanelSize: 0,
       endPanelSize: 0,
     };
   }
 
   getChildContext() {
     return {
+      fluentBundles: this.props.fluentBundles,
       toolboxDoc: this.props.toolboxDoc,
       shortcuts,
       l10n: L10N,
     };
   }
 
   componentDidMount() {
     horizontalLayoutBreakpoint.addListener(this.onLayoutChange);
@@ -295,16 +296,17 @@ class App extends Component {
     );
   }
 }
 
 App.childContextTypes = {
   toolboxDoc: PropTypes.object,
   shortcuts: PropTypes.object,
   l10n: PropTypes.object,
+  fluentBundles: PropTypes.array,
 };
 
 const mapStateToProps = state => ({
   selectedSource: getSelectedSource(state),
   startPanelCollapsed: getPaneCollapse(state, "start"),
   endPanelCollapsed: getPaneCollapse(state, "end"),
   activeSearch: getActiveSearch(state),
   quickOpenEnabled: getQuickOpenEnabled(state),
--- a/devtools/client/debugger/src/components/SecondaryPanes/WhyPaused.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/WhyPaused.js
@@ -1,13 +1,19 @@
 /* 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/>. */
 
+const {
+  LocalizationProvider,
+  Localized,
+} = require("devtools/client/shared/vendor/fluent-react");
+
 import React, { PureComponent } from "react";
+import PropTypes from "prop-types";
 import { connect } from "../../utils/connect";
 import AccessibleImage from "../shared/AccessibleImage";
 import actions from "../../actions";
 
 import Reps from "devtools/client/shared/components/reps/index";
 const {
   REPS: { Rep },
   MODE,
@@ -93,19 +99,23 @@ class WhyPaused extends PureComponent {
 
       return (
         <div>
           <div className="message">{why.message}</div>
           <div className="mutationNode">
             {ancestorRep}
             {ancestorGrip ? (
               <span className="why-paused-ancestor">
-                {action === "remove"
-                  ? L10N.getStr("whyPaused.mutationBreakpointRemoved")
-                  : L10N.getStr("whyPaused.mutationBreakpointAdded")}
+                <Localized
+                  id={
+                    action === "remove"
+                      ? "whypaused-mutation-breakpoint-removed"
+                      : "whypaused-mutation-breakpoint-added"
+                  }
+                ></Localized>
                 {targetRep}
               </span>
             ) : (
               targetRep
             )}
           </div>
         </div>
       );
@@ -115,38 +125,46 @@ class WhyPaused extends PureComponent {
       return <div className="message">{message}</div>;
     }
 
     return null;
   }
 
   render() {
     const { endPanelCollapsed, why } = this.props;
+    const { fluentBundles } = this.context;
     const reason = getPauseReason(why);
 
     if (!why || !reason || endPanelCollapsed) {
       return <div className={this.state.hideWhyPaused} />;
     }
-
     return (
-      <div className="pane why-paused">
-        <div>
-          <div className="info icon">
-            <AccessibleImage className="info" />
-          </div>
-          <div className="pause reason">
-            {L10N.getStr(reason)}
-            {this.renderMessage(why)}
+      // We're rendering the LocalizationProvider component from here and not in an upper
+      // component because it does set a new context, overriding the context that we set
+      // in the first place in <App>, which breaks some components.
+      // This should be fixed in Bug 1743155.
+      <LocalizationProvider bundles={fluentBundles || []}>
+        <div className="pane why-paused">
+          <div>
+            <div className="info icon">
+              <AccessibleImage className="info" />
+            </div>
+            <div className="pause reason">
+              <Localized id={reason}></Localized>
+              {this.renderMessage(why)}
+            </div>
           </div>
         </div>
-      </div>
+      </LocalizationProvider>
     );
   }
 }
 
+WhyPaused.contextTypes = { fluentBundles: PropTypes.array };
+
 const mapStateToProps = state => ({
   endPanelCollapsed: getPaneCollapse(state, "end"),
   why: getWhy(state, getCurrentThread(state)),
 });
 
 export default connect(mapStateToProps, {
   openElementInInspector: actions.openElementInInspectorCommand,
   highlightDomElement: actions.highlightDomElement,
--- a/devtools/client/debugger/src/components/test/WhyPaused.spec.js
+++ b/devtools/client/debugger/src/components/test/WhyPaused.spec.js
@@ -3,17 +3,16 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 import React from "react";
 import { shallow } from "enzyme";
 import WhyPaused from "../SecondaryPanes/WhyPaused.js";
 
 function render(why, delay) {
   const props = { why, delay };
-
   const component = shallow(<WhyPaused.WrappedComponent {...props} />);
 
   return { component, props };
 }
 
 describe("WhyPaused", () => {
   it("should pause reason with message", () => {
     const why = {
--- a/devtools/client/debugger/src/components/test/__snapshots__/WhyPaused.spec.js.snap
+++ b/devtools/client/debugger/src/components/test/__snapshots__/WhyPaused.spec.js.snap
@@ -1,85 +1,103 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`WhyPaused should pause reason with message 1`] = `
-<div
-  className="pane why-paused"
+<LocalizationProvider
+  bundles={Array []}
 >
-  <div>
-    <div
-      className="info icon"
-    >
-      <AccessibleImage
-        className="info"
-      />
-    </div>
-    <div
-      className="pause reason"
-    >
-      Paused on breakpoint
+  <div
+    className="pane why-paused"
+  >
+    <div>
       <div
-        className="message"
+        className="info icon"
+      >
+        <AccessibleImage
+          className="info"
+        />
+      </div>
+      <div
+        className="pause reason"
       >
-        bla is hit
+        <Localized
+          id="whypaused-breakpoint"
+        />
+        <div
+          className="message"
+        >
+          bla is hit
+        </div>
       </div>
     </div>
   </div>
-</div>
+</LocalizationProvider>
 `;
 
 exports[`WhyPaused should show an empty div when there is no pause reason 1`] = `
 <div
   className=""
 />
 `;
 
 exports[`WhyPaused should show pause reason with exception details 1`] = `
-<div
-  className="pane why-paused"
+<LocalizationProvider
+  bundles={Array []}
 >
-  <div>
-    <div
-      className="info icon"
-    >
-      <AccessibleImage
-        className="info"
-      />
-    </div>
-    <div
-      className="pause reason"
-    >
-      Paused on exception
+  <div
+    className="pane why-paused"
+  >
+    <div>
       <div
-        className="message warning"
+        className="info icon"
+      >
+        <AccessibleImage
+          className="info"
+        />
+      </div>
+      <div
+        className="pause reason"
       >
-        ReferenceError: o is not defined
+        <Localized
+          id="whypaused-exception"
+        />
+        <div
+          className="message warning"
+        >
+          ReferenceError: o is not defined
+        </div>
       </div>
     </div>
   </div>
-</div>
+</LocalizationProvider>
 `;
 
 exports[`WhyPaused should show pause reason with exception string 1`] = `
-<div
-  className="pane why-paused"
+<LocalizationProvider
+  bundles={Array []}
 >
-  <div>
-    <div
-      className="info icon"
-    >
-      <AccessibleImage
-        className="info"
-      />
-    </div>
-    <div
-      className="pause reason"
-    >
-      Paused on exception
+  <div
+    className="pane why-paused"
+  >
+    <div>
       <div
-        className="message warning"
+        className="info icon"
+      >
+        <AccessibleImage
+          className="info"
+        />
+      </div>
+      <div
+        className="pause reason"
       >
-        Not Available
+        <Localized
+          id="whypaused-exception"
+        />
+        <div
+          className="message warning"
+        >
+          Not Available
+        </div>
       </div>
     </div>
   </div>
-</div>
+</LocalizationProvider>
 `;
--- a/devtools/client/debugger/src/main.js
+++ b/devtools/client/debugger/src/main.js
@@ -67,16 +67,17 @@ async function loadInitialState() {
     breakpoints,
     eventListenerBreakpoints,
     sources,
   };
 }
 
 export async function bootstrap({
   commands,
+  fluentBundles,
   resourceCommand,
   workers: panelWorkers,
   panel,
 }) {
   verifyPrefSchema();
 
   const initialState = await loadInitialState();
   const workers = bootstrapWorkers(panelWorkers);
@@ -103,17 +104,20 @@ export async function bootstrap({
     store,
     actions,
     selectors,
     workers,
     targetCommand: commands.targetCommand,
     client: firefox.clientCommands,
   });
 
-  bootstrapApp(store, panel);
+  bootstrapApp(store, panel.getToolboxStore(), {
+    fluentBundles,
+    toolboxDoc: panel.panelWin.parent.document,
+  });
   await connected;
   return { store, actions, selectors, client: firefox.clientCommands };
 }
 
 export async function destroy() {
   firefox.onDisconnect();
   unmountRoot();
   teardownWorkers();
--- a/devtools/client/debugger/src/utils/bootstrap.js
+++ b/devtools/client/debugger/src/utils/bootstrap.js
@@ -59,32 +59,39 @@ export function bootstrapWorkers(panelWo
 }
 
 export function teardownWorkers() {
   prettyPrint.stop();
   parser.stop();
   search.stop();
 }
 
-export function bootstrapApp(store, panel) {
+/**
+ * Create and mount the root App component.
+ *
+ * @param {ReduxStore} store
+ * @param {ReduxStore} toolboxStore
+ * @param {Object} appComponentAttributes
+ * @param {Array} appComponentAttributes.fluentBundles
+ * @param {Document} appComponentAttributes.toolboxDoc
+ */
+export function bootstrapApp(store, toolboxStore, appComponentAttributes = {}) {
   const mount = getMountElement();
   if (!mount) {
     return;
   }
 
-  const toolboxDoc = panel.panelWin.parent.document;
-
   ReactDOM.render(
     React.createElement(
       Provider,
       { store },
       React.createElement(
         ToolboxProvider,
-        { store: panel.getToolboxStore() },
-        React.createElement(App, { toolboxDoc })
+        { store: toolboxStore },
+        React.createElement(App, appComponentAttributes)
       )
     ),
     mount
   );
 }
 
 function getMountElement() {
   return document.querySelector("#mount");
--- a/devtools/client/debugger/src/utils/pause/why.js
+++ b/devtools/client/debugger/src/utils/pause/why.js
@@ -1,49 +1,25 @@
 /* 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/>. */
 
-// Map protocol pause "why" reason to a valid L10N key
-// These are the known unhandled reasons:
-// "breakpointConditionThrown", "clientEvaluated"
-// "interrupted", "attached"
-const reasons = {
-  debuggerStatement: "whyPaused.debuggerStatement",
-  breakpoint: "whyPaused.breakpoint",
-  exception: "whyPaused.exception",
-  resumeLimit: "whyPaused.resumeLimit",
-  breakpointConditionThrown: "whyPaused.breakpointConditionThrown",
-  eventBreakpoint: "whyPaused.eventBreakpoint",
-  getWatchpoint: "whyPaused.getWatchpoint",
-  setWatchpoint: "whyPaused.setWatchpoint",
-  mutationBreakpoint: "whyPaused.mutationBreakpoint",
-  interrupted: "whyPaused.interrupted",
-
-  // V8
-  DOM: "whyPaused.breakpoint",
-  EventListener: "whyPaused.pauseOnDOMEvents",
-  XHR: "whyPaused.XHR",
-  promiseRejection: "whyPaused.promiseRejection",
-  assert: "whyPaused.assert",
-  debugCommand: "whyPaused.debugCommand",
-  other: "whyPaused.other",
-};
+import { DEBUGGER_PAUSED_REASONS_L10N_MAPPING } from "devtools/shared/constants";
 
 export function getPauseReason(why) {
   if (!why) {
     return null;
   }
 
   const reasonType = why.type;
-  if (!reasons[reasonType]) {
+  if (!DEBUGGER_PAUSED_REASONS_L10N_MAPPING[reasonType]) {
     console.log("Please file an issue: reasonType=", reasonType);
   }
 
-  return reasons[reasonType];
+  return DEBUGGER_PAUSED_REASONS_L10N_MAPPING[reasonType];
 }
 
 export function isException(why) {
   return why?.type === "exception";
 }
 
 export function isInterrupted(why) {
   return why?.type === "interrupted";
--- a/devtools/client/framework/test/metrics/browser_metrics_debugger.js
+++ b/devtools/client/framework/test/metrics/browser_metrics_debugger.js
@@ -28,16 +28,17 @@ add_task(async function() {
     toolboxBrowserLoader.loader,
     debuggerLoader.loader,
   ];
 
   const allowedDupes = [
     "@loader/unload.js",
     "@loader/options.js",
     "chrome.js",
+    "resource://devtools/client/shared/vendor/fluent-react.js",
     "resource://devtools/client/shared/vendor/react-dom.js",
     "resource://devtools/client/shared/vendor/react.js",
     "resource://devtools/client/debugger/dist/vendors.js",
     "resource://devtools/client/shared/vendor/react-prop-types.js",
     "resource://devtools/client/shared/vendor/react-dom-factories.js",
     "resource://devtools/client/shared/vendor/react-redux.js",
     "resource://devtools/client/shared/vendor/redux.js",
     "resource://devtools/client/debugger/src/workers/parser/index.js",
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -823,101 +823,16 @@ symbolSearch.searchModifier.regex=Regex
 symbolSearch.searchModifier.caseSensitive=Case sensitive
 
 # LOCALIZATION NOTE(symbolSearch.searchModifier.wholeWord): A search option
 # when searching text in a file
 symbolSearch.searchModifier.wholeWord=Whole word
 
 experimental=This is an experimental feature
 
-# LOCALIZATION NOTE (whyPaused.debuggerStatement): The text that is displayed
-# in a info block explaining how the debugger is currently paused due to a `debugger`
-# statement in the code
-whyPaused.debuggerStatement=Paused on debugger statement
-
-# LOCALIZATION NOTE (whyPaused.breakpoint): The text that is displayed
-# in a info block explaining how the debugger is currently paused on a breakpoint
-whyPaused.breakpoint=Paused on breakpoint
-
-# LOCALIZATION NOTE (whyPaused.eventBreakpoint): The text that is displayed
-# in a info block explaining how the debugger is currently paused on an event
-# breakpoint.
-whyPaused.eventBreakpoint=Paused on event breakpoint
-
-# LOCALIZATION NOTE (whyPaused.exception): The text that is displayed
-# in a info block explaining how the debugger is currently paused on an exception
-whyPaused.exception=Paused on exception
-
-# LOCALIZATION NOTE (whyPaused.mutationBreakpoint): The text that is displayed
-# in a info block explaining how the debugger is currently paused on a
-# DOM mutation breakpoint
-whyPaused.mutationBreakpoint=Paused on DOM mutation
-
-# LOCALIZATION NOTE (whyPaused.mutationBreakpointAdded): The text that
-# is displayed to describe an added node which triggers a subtree modification
-whyPaused.mutationBreakpointAdded=Added:
-
-# LOCALIZATION NOTE (whyPaused.mutationBreakpointRemoved): The text that
-# is displayed to describe a removed node which triggers a subtree modification
-whyPaused.mutationBreakpointRemoved=Removed:
-
-# LOCALIZATION NOTE (whyPaused.interrupted): The text that is displayed
-# in a info block explaining how the debugger is currently paused at
-# a JS execution
-whyPaused.interrupted=Paused at Execution
-
-# LOCALIZATION NOTE (whyPaused.resumeLimit): The text that is displayed
-# in a info block explaining how the debugger is currently paused while stepping
-# in or out of the stack
-whyPaused.resumeLimit=Paused while stepping
-
-# LOCALIZATION NOTE (whyPaused.pauseOnDOMEvents): The text that is displayed
-# in a info block explaining how the debugger is currently paused on a
-# dom event
-whyPaused.pauseOnDOMEvents=Paused on event listener
-
-# LOCALIZATION NOTE (whyPaused.breakpointConditionThrown): The text that is displayed
-# in an info block when evaluating a conditional breakpoint throws an error
-whyPaused.breakpointConditionThrown=Error with conditional breakpoint
-
-# LOCALIZATION NOTE (whyPaused.XHR): The text that is displayed
-# in a info block explaining how the debugger is currently paused on an
-# xml http request
-whyPaused.XHR=Paused on XMLHttpRequest
-
-# LOCALIZATION NOTE (whyPaused.promiseRejection): The text that is displayed
-# in a info block explaining how the debugger is currently paused on a
-# promise rejection
-whyPaused.promiseRejection=Paused on promise rejection
-
-# LOCALIZATION NOTE (whyPaused.getWatchpoint): The text that is displayed
-# in a info block explaining how the debugger is currently paused at a
-# watchpoint on an object property
-whyPaused.getWatchpoint=Paused on property get
-
-# LOCALIZATION NOTE (whyPaused.setWatchpoint): The text that is displayed
-# in an info block explaining how the debugger is currently paused at a
-# watchpoint on an object property
-whyPaused.setWatchpoint=Paused on property set
-
-# LOCALIZATION NOTE (whyPaused.assert): The text that is displayed
-# in a info block explaining how the debugger is currently paused on an
-# assert
-whyPaused.assert=Paused on assertion
-
-# LOCALIZATION NOTE (whyPaused.debugCommand): The text that is displayed
-# in a info block explaining how the debugger is currently paused on a
-# debugger statement
-whyPaused.debugCommand=Paused on debugged function
-
-# LOCALIZATION NOTE (whyPaused.other): The text that is displayed
-# in a info block explaining how the debugger is currently paused on an event
-# listener breakpoint set
-whyPaused.other=Debugger paused
-
 # LOCALIZATION NOTE (ctrl): The text that is used for documenting
 # keyboard shortcuts that use the control key
 ctrl=Ctrl
 
 # LOCALIZATION NOTE (anonymousFunction): this string is used to display
 # JavaScript functions that have no given name - they are said to be
 # anonymous.
 anonymousFunction=<anonymous>
--- a/devtools/server/actors/highlighters/paused-debugger.js
+++ b/devtools/server/actors/highlighters/paused-debugger.js
@@ -3,22 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   CanvasFrameAnonymousContentHelper,
 } = require("devtools/server/actors/highlighters/utils/markup");
 
-loader.lazyGetter(this, "L10N", () => {
-  const { LocalizationHelper } = require("devtools/shared/l10n");
-  const STRINGS_URI = "devtools/client/locales/debugger.properties";
-  return new LocalizationHelper(STRINGS_URI);
+loader.lazyGetter(this, "PausedReasonsBundle", () => {
+  return new Localization(
+    ["devtools/shared/debugger-paused-reasons.ftl"],
+    true
+  );
 });
 
+loader.lazyRequireGetter(
+  this,
+  "DEBUGGER_PAUSED_REASONS_L10N_MAPPING",
+  "devtools/shared/constants",
+  true
+);
+
 /**
  * The PausedDebuggerOverlay is a class that displays a semi-transparent mask on top of
  * the whole page and a toolbar at the top of the page.
  * This is used to signal to users that script execution is current paused.
  * The toolbar is used to display the reason for the pause in script execution as well as
  * buttons to resume or step through the program.
  */
 function PausedDebuggerOverlay(highlighterEnv, options = {}) {
@@ -201,40 +209,33 @@ PausedDebuggerOverlay.prototype = {
     return this.markup.getElement(this.ID_CLASS_PREFIX + id);
   },
 
   show(reason) {
     if (this.env.isXUL || !reason) {
       return false;
     }
 
-    try {
-      reason = L10N.getStr(`whyPaused.${reason}`);
-    } catch (e) {
-      // This is a temporary workaround (See Bug 1591025).
-      // This actors relies on a client side properties file. This file will not
-      // be available when debugging Firefox for Android / Gecko View.
-      // The highlighter also shows buttons that use client only images and are
-      // therefore invisible when remote debugging a mobile Firefox.
-      return false;
-    }
-
     // Only track mouse movement when the the overlay is shown
     // Prevents mouse tracking when the user isn't paused
     const { pageListenerTarget } = this.env;
     pageListenerTarget.addEventListener("mousemove", this);
 
     // Show the highlighter's root element.
     const root = this.getElement("root");
     root.removeAttribute("hidden");
     root.setAttribute("overlay", "true");
 
     // Set the text to appear in the toolbar.
     const toolbar = this.getElement("toolbar");
-    this.getElement("reason").setTextContent(reason);
+    this.getElement("reason").setTextContent(
+      PausedReasonsBundle.formatValueSync(
+        DEBUGGER_PAUSED_REASONS_L10N_MAPPING[reason]
+      )
+    );
     toolbar.removeAttribute("hidden");
 
     // When the debugger pauses execution in a page, events will not be delivered
     // to any handlers added to elements on that page. So here we use the
     // document's setSuppressedEventListener interface to still be able to act on mouse
     // events (they'll be handled by the `handleEvent` method)
     this.env.window.document.setSuppressedEventListener(this);
     return true;
@@ -245,11 +246,14 @@ PausedDebuggerOverlay.prototype = {
       return;
     }
 
     const { pageListenerTarget } = this.env;
     pageListenerTarget.removeEventListener("mousemove", this);
 
     // Hide the overlay.
     this.getElement("root").setAttribute("hidden", "true");
+    // Remove the hover state
+    this.getElement("step-button-wrapper").classList.remove("hover");
+    this.getElement("resume-button-wrapper").classList.remove("hover");
   },
 };
 exports.PausedDebuggerOverlay = PausedDebuggerOverlay;
--- a/devtools/shared/constants.js
+++ b/devtools/shared/constants.js
@@ -118,21 +118,49 @@ const COMPATIBILITY_ISSUE_TYPE = {
 const ELEMENT_STYLE = 100;
 
 /* WebConsole Panel ========================================================= */
 
 const MESSAGE_CATEGORY = {
   CSS_PARSER: "CSS Parser",
 };
 
+/* Debugger ============================================================= */
+
+// Map protocol pause "why" reason to a valid L10N key (in devtools/shared/locales/en-US/debugger-paused-reasons.ftl)
+const DEBUGGER_PAUSED_REASONS_L10N_MAPPING = {
+  debuggerStatement: "whypaused-debugger-statement",
+  breakpoint: "whypaused-breakpoint",
+  exception: "whypaused-exception",
+  resumeLimit: "whypaused-resume-limit",
+  breakpointConditionThrown: "whypaused-breakpoint-condition-thrown",
+  eventBreakpoint: "whypaused-event-breakpoint",
+  getWatchpoint: "whypaused-get-watchpoint",
+  setWatchpoint: "whypaused-set-watchpoint",
+  mutationBreakpoint: "whypaused-mutation-breakpoint",
+  interrupted: "whypaused-interrupted",
+
+  // V8
+  DOM: "whypaused-breakpoint",
+  EventListener: "whypaused-pause-on-dom-events",
+  XHR: "whypaused-xhr",
+  promiseRejection: "whypaused-promise-rejection",
+  assert: "whypaused-assert",
+  debugCommand: "whypaused-debug-command",
+  other: "whypaused-other",
+};
+
+/* Exports ============================================================= */
+
 module.exports = {
   accessibility: {
     AUDIT_TYPE,
     ISSUE_TYPE,
     SCORES,
     SIMULATION_TYPE,
   },
   COMPATIBILITY_ISSUE_TYPE,
+  DEBUGGER_PAUSED_REASONS_L10N_MAPPING,
   MESSAGE_CATEGORY,
   style: {
     ELEMENT_STYLE,
   },
 };
new file mode 100644
--- /dev/null
+++ b/devtools/shared/locales/en-US/debugger-paused-reasons.ftl
@@ -0,0 +1,85 @@
+# 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/.
+
+### These strings are used inside the Debugger which is available from the Web
+### Developer sub-menu -> 'Debugger', as well as in the "Paused Debugger
+### Overlay" that is displayed in the content page when it pauses.
+
+### The correct localization of this file might be to keep it in
+### English, or another language commonly spoken among web developers.
+### You want to make that choice consistent across the developer tools.
+### A good criteria is the language in which you'd find the best
+### documentation on web development on the web.
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused due to a `debugger` statement in the code
+whypaused-debugger-statement = Paused on debugger statement
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on a breakpoint
+whypaused-breakpoint = Paused on breakpoint
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on an event breakpoint.
+whypaused-event-breakpoint = Paused on event breakpoint
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on an exception
+whypaused-exception = Paused on exception
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on a DOM mutation breakpoint
+whypaused-mutation-breakpoint = Paused on DOM mutation
+
+# The text that is displayed to describe an added node which triggers a subtree
+# modification
+whypaused-mutation-breakpoint-added = Added:
+
+# The text that is displayed to describe a removed node which triggers a subtree
+# modification
+whypaused-mutation-breakpoint-removed = Removed:
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused at a JS execution
+whypaused-interrupted = Paused at Execution
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused while stepping in or out of the stack
+whypaused-resume-limit = Paused while stepping
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on a dom event
+whypaused-pause-on-dom-events = Paused on event listener
+
+# The text that is displayed in an info block when evaluating a conditional
+# breakpoint throws an error
+whypaused-breakpoint-condition-thrown = Error with conditional breakpoint
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on an xml http request
+whypaused-xhr = Paused on XMLHttpRequest
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on a promise rejection
+whypaused-promise-rejection = Paused on promise rejection
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused at a watchpoint on an object property
+whypaused-get-watchpoint = Paused on property get
+
+# The text that is displayed in an info block explaining how the debugger is
+# currently paused at a watchpoint on an object property
+whypaused-set-watchpoint = Paused on property set
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on an assert
+whypaused-assert = Paused on assertion
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on a debugger statement
+whypaused-debug-command = Paused on debugged function
+
+# The text that is displayed in a info block explaining how the debugger is
+# currently paused on an event listener breakpoint set
+whypaused-other = Debugger paused
--- a/devtools/shared/locales/jar.mn
+++ b/devtools/shared/locales/jar.mn
@@ -1,8 +1,11 @@
 #filter substitution
 # 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/.
 
+[localization] @AB_CD@.jar:
+    devtools/shared (%*.ftl)
+
 @AB_CD@.jar:
 %   locale devtools-shared @AB_CD@ %locale/@AB_CD@/devtools/shared/
-    locale/@AB_CD@/devtools/shared/ (%*)
+    locale/@AB_CD@/devtools/shared/ (%*.properties)
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1743347_devtools_debugger_whyPaused.py
@@ -0,0 +1,92 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import COPY
+
+
+def migrate(ctx):
+    """Bug 1743347 - Move whyPaused.* strings to Fluent, part {index}."""
+
+    source = "devtools/client/debugger.properties"
+    target = "devtools/shared/debugger-paused-reasons.ftl"
+    ctx.add_transforms(
+        target,
+        target,
+        [
+            FTL.Message(
+                id=FTL.Identifier("whypaused-debugger-statement"),
+                value=COPY(source, "whyPaused.debuggerStatement"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-breakpoint"),
+                value=COPY(source, "whyPaused.breakpoint"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-event-breakpoint"),
+                value=COPY(source, "whyPaused.eventBreakpoint"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-exception"),
+                value=COPY(source, "whyPaused.exception"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-mutation-breakpoint"),
+                value=COPY(source, "whyPaused.mutationBreakpoint"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-mutation-breakpoint-added"),
+                value=COPY(source, "whyPaused.mutationBreakpointAdded"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-mutation-breakpoint-removed"),
+                value=COPY(source, "whyPaused.mutationBreakpointRemoved"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-interrupted"),
+                value=COPY(source, "whyPaused.interrupted"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-resume-limit"),
+                value=COPY(source, "whyPaused.resumeLimit"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-pause-on-dom-events"),
+                value=COPY(source, "whyPaused.pauseOnDOMEvents"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-breakpoint-condition-thrown"),
+                value=COPY(source, "whyPaused.breakpointConditionThrown"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-xhr"),
+                value=COPY(source, "whyPaused.XHR"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-promise-rejection"),
+                value=COPY(source, "whyPaused.promiseRejection"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-get-watchpoint"),
+                value=COPY(source, "whyPaused.getWatchpoint"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-set-watchpoint"),
+                value=COPY(source, "whyPaused.setWatchpoint"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-assert"),
+                value=COPY(source, "whyPaused.assert"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-debug-command"),
+                value=COPY(source, "whyPaused.debugCommand"),
+            ),
+            FTL.Message(
+                id=FTL.Identifier("whypaused-other"),
+                value=COPY(source, "whyPaused.other"),
+            ),
+        ],
+    )