diff --git a/devtools/client/framework/components/MeatballMenu.js b/devtools/client/framework/components/MeatballMenu.js index 52043740d03e..afa6b92dc810 100644 --- a/devtools/client/framework/components/MeatballMenu.js +++ b/devtools/client/framework/components/MeatballMenu.js @@ -80,7 +80,9 @@ class MeatballMenu extends PureComponent { // Function to turn the disable pop-up autohide behavior on / off. toggleNoAutohide: PropTypes.func, - // Localization interface. + // Bug 1709191 - The help shortcut key is localized without Fluent, and still needs + // to be migrated. This is the only remaining use of the legacy L10N object. + // Everything else should prefer the Fluent API. L10N: PropTypes.object.isRequired, // Callback function that will be invoked any time the component contents @@ -122,22 +124,22 @@ class MeatballMenu extends PureComponent { for (const hostType of this.props.hostTypes) { // This is more verbose than it needs to be but lets us easily search for // l10n entities. - let l10nkey; + let l10nID; switch (hostType.position) { case "window": - l10nkey = "toolbox.meatballMenu.dock.separateWindow.label"; + l10nID = "toolbox-meatball-menu-dock-separate-window-label"; break; case "bottom": - l10nkey = "toolbox.meatballMenu.dock.bottom.label"; + l10nID = "toolbox-meatball-menu-dock-bottom-label"; break; case "left": - l10nkey = "toolbox.meatballMenu.dock.left.label"; + l10nID = "toolbox-meatball-menu-dock-left-label"; break; case "right": - l10nkey = "toolbox.meatballMenu.dock.right.label"; + l10nID = "toolbox-meatball-menu-dock-right-label"; break; default: @@ -149,7 +151,7 @@ class MeatballMenu extends PureComponent { MenuItem({ id: `toolbox-meatball-menu-dock-${hostType.position}`, key: `dock-${hostType.position}`, - label: this.props.L10N.getStr(l10nkey), + l10nID, onClick: hostType.switchHost, checked: hostType.position === this.props.currentHostType, className: "iconic", @@ -163,14 +165,14 @@ class MeatballMenu extends PureComponent { // Split console if (this.props.currentToolId !== "webconsole") { - const l10nkey = this.props.isSplitConsoleActive - ? "toolbox.meatballMenu.hideconsole.label" - : "toolbox.meatballMenu.splitconsole.label"; + const l10nID = this.props.isSplitConsoleActive + ? "toolbox-meatball-menu-hideconsole-label" + : "toolbox-meatball-menu-splitconsole-label"; items.push( MenuItem({ id: "toolbox-meatball-menu-splitconsole", key: "splitconsole", - label: this.props.L10N.getStr(l10nkey), + l10nID, accelerator: "Esc", onClick: this.props.toggleSplitConsole, className: "iconic", @@ -187,9 +189,7 @@ class MeatballMenu extends PureComponent { MenuItem({ id: "toolbox-meatball-menu-noautohide", key: "noautohide", - label: this.props.L10N.getStr( - "toolbox.meatballMenu.noautohide.label" - ), + l10nID: "toolbox-meatball-menu-noautohide-label", type: "checkbox", checked: this.props.disableAutohide, onClick: this.props.toggleNoAutohide, @@ -203,7 +203,9 @@ class MeatballMenu extends PureComponent { MenuItem({ id: "toolbox-meatball-menu-settings", key: "settings", - label: this.props.L10N.getStr("toolbox.meatballMenu.settings.label"), + l10nID: "toolbox-meatball-menu-settings-label", + // Bug 1709191 - The help key is localized without Fluent, and still needs to + // be migrated. accelerator: this.props.L10N.getStr("toolbox.help.key"), onClick: this.props.toggleOptions, className: "iconic", @@ -217,9 +219,7 @@ class MeatballMenu extends PureComponent { MenuItem({ id: "toolbox-meatball-menu-documentation", key: "documentation", - label: this.props.L10N.getStr( - "toolbox.meatballMenu.documentation.label" - ), + l10nID: "toolbox-meatball-menu-documentation-label", onClick: openDevToolsDocsLink, }) ); @@ -229,7 +229,7 @@ class MeatballMenu extends PureComponent { MenuItem({ id: "toolbox-meatball-menu-community", key: "community", - label: this.props.L10N.getStr("toolbox.meatballMenu.community.label"), + l10nID: "toolbox-meatball-menu-community-label", onClick: openCommunityLink, }) ); diff --git a/devtools/client/framework/components/ToolboxToolbar.js b/devtools/client/framework/components/ToolboxToolbar.js index ecd0c05a4c8e..bfbb6dc0e7b3 100644 --- a/devtools/client/framework/components/ToolboxToolbar.js +++ b/devtools/client/framework/components/ToolboxToolbar.js @@ -10,7 +10,6 @@ const { const dom = require("devtools/client/shared/vendor/react-dom-factories"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const { div, button } = dom; - const DebugTargetInfo = createFactory( require("devtools/client/framework/components/DebugTargetInfo") ); @@ -36,6 +35,11 @@ loader.lazyGetter(this, "MenuList", function() { require("devtools/client/shared/components/menu/MenuList") ); }); +loader.lazyGetter(this, "LocalizationProvider", function() { + return createFactory( + require("devtools/client/shared/vendor/fluent-react").LocalizationProvider + ); +}); loader.lazyRequireGetter( this, @@ -119,6 +123,8 @@ class ToolboxToolbar extends Component { runtimeInfo: PropTypes.object.isRequired, targetType: PropTypes.string.isRequired, }), + // The loaded Fluent localization bundles. + fluentBundles: PropTypes.array.isRequired, }; } @@ -466,7 +472,7 @@ class ToolboxToolbar extends Component { * render functions for how each of the sections is rendered. */ render() { - const { L10N, debugTargetData, toolbox } = this.props; + const { L10N, debugTargetData, toolbox, fluentBundles } = this.props; const classnames = ["devtools-tabbar"]; const startButtons = this.renderToolboxButtonsStart(); const endButtons = this.renderToolboxButtonsEnd(); @@ -494,7 +500,10 @@ class ToolboxToolbar extends Component { ? DebugTargetInfo({ debugTargetData, L10N, toolbox }) : null; - return div({}, debugTargetInfo, toolbar); + return LocalizationProvider( + { bundles: fluentBundles }, + div({}, debugTargetInfo, toolbar) + ); } } diff --git a/devtools/client/framework/test/metrics/browser_metrics_inspector.js b/devtools/client/framework/test/metrics/browser_metrics_inspector.js index 41db12cdbf94..319c60de93ae 100644 --- a/devtools/client/framework/test/metrics/browser_metrics_inspector.js +++ b/devtools/client/framework/test/metrics/browser_metrics_inspector.js @@ -33,6 +33,7 @@ add_task(async function() { "resource://devtools/client/shared/vendor/react-dom-factories.js", "resource://devtools/client/shared/vendor/react-prop-types.js", "resource://devtools/client/shared/vendor/redux.js", + "resource://devtools/client/shared/vendor/fluent-react.js", ]); runMetricsTest({ diff --git a/devtools/client/framework/test/metrics/browser_metrics_webconsole.js b/devtools/client/framework/test/metrics/browser_metrics_webconsole.js index cc24bfebbbce..c39690f3299e 100644 --- a/devtools/client/framework/test/metrics/browser_metrics_webconsole.js +++ b/devtools/client/framework/test/metrics/browser_metrics_webconsole.js @@ -39,6 +39,7 @@ add_task(async function() { "resource://devtools/client/shared/components/menu/MenuButton.js", "resource://devtools/client/shared/components/menu/MenuItem.js", "resource://devtools/client/shared/components/menu/MenuList.js", + "resource://devtools/client/shared/vendor/fluent-react.js", "resource://devtools/client/shared/vendor/react.js", "resource://devtools/client/shared/vendor/react-dom.js", "resource://devtools/client/shared/vendor/react-prop-types.js", diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 08117d16308c..e7c5c4a9aeb2 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -33,6 +33,10 @@ var Telemetry = require("devtools/client/shared/telemetry"); const { getUnicodeUrl } = require("devtools/client/shared/unicode-url"); var { DOMHelpers } = require("devtools/shared/dom-helpers"); const { KeyCodes } = require("devtools/client/shared/keycodes"); +const { + FluentL10n, +} = require("devtools/client/shared/fluent-l10n/fluent-l10n"); + var Startup = Cc["@mozilla.org/devtools/startup-clh;1"].getService( Ci.nsISupports ).wrappedJSObject; @@ -845,6 +849,12 @@ Toolbox.prototype = { */ open: function() { return async function() { + // Kick off async loading the Fluent bundles. + const fluentL10n = new FluentL10n(); + const fluentInitPromise = fluentL10n.init([ + "devtools/client/toolbox.ftl", + ]); + const isToolboxURL = this.win.location.href.startsWith(this._URL); if (isToolboxURL) { // Update the URL so that onceDOMReady watch for the right url. @@ -944,7 +954,9 @@ Toolbox.prototype = { // Get the DOM element to mount the ToolboxController to. this._componentMount = this.doc.getElementById("toolbox-toolbar-mount"); - this._mountReactComponent(); + await fluentInitPromise; + + this._mountReactComponent(fluentL10n.getBundles()); this._buildDockOptions(); this._buildTabs(); @@ -1889,10 +1901,11 @@ Toolbox.prototype = { } }, - _mountReactComponent: function() { + _mountReactComponent(fluentBundles) { // Ensure the toolbar doesn't try to render until the tool is ready. const element = this.React.createElement(this.ToolboxController, { L10N, + fluentBundles, currentToolId: this.currentToolId, selectTool: this.selectTool, toggleOptions: this.toggleOptions, diff --git a/devtools/client/locales/en-US/toolbox.ftl b/devtools/client/locales/en-US/toolbox.ftl new file mode 100644 index 000000000000..5330bea9135a --- /dev/null +++ b/devtools/client/locales/en-US/toolbox.ftl @@ -0,0 +1,29 @@ +# 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 messages are used in the DevTools toolbox. + +## These labels are shown in the "..." menu in the toolbox, and represent different +## commands such as the docking of DevTools, toggling features, and viewing some +## external links. Some of the commands have the keyboard shortcut shown next to +## the label. + +toolbox-meatball-menu-dock-bottom-label = Dock to Bottom +toolbox-meatball-menu-dock-left-label = Dock to Left +toolbox-meatball-menu-dock-right-label = Dock to Right +toolbox-meatball-menu-dock-separate-window-label = Separate Window + +toolbox-meatball-menu-splitconsole-label = Show Split Console +toolbox-meatball-menu-hideconsole-label = Hide Split Console + +toolbox-meatball-menu-settings-label = Settings +toolbox-meatball-menu-documentation-label = Documentation… +toolbox-meatball-menu-community-label = Community… + +# This menu item is only available in the browser toolbox. It forces the popups/panels +# to stay visible on blur, which is primarily useful for addon developers and Firefox +# contributors. +toolbox-meatball-menu-noautohide-label = Disable Popup Auto-Hide + +## diff --git a/devtools/client/locales/en-US/toolbox.properties b/devtools/client/locales/en-US/toolbox.properties index 18e2814926d8..bba00ac7e4df 100644 --- a/devtools/client/locales/en-US/toolbox.properties +++ b/devtools/client/locales/en-US/toolbox.properties @@ -168,42 +168,6 @@ toolbox.showFrames.key=Alt+Down # for the "..." button on the developer tools toolbox. toolbox.meatballMenu.button.tooltip=Customize Developer Tools and Get Help -# LOCALIZATION NOTE (toolbox.meatballMenu.dock.*.label): These labels are shown -# in the "..." menu in the toolbox and represent the different arrangements for -# docking (or undocking) the developer tools toolbox. -toolbox.meatballMenu.dock.bottom.label=Dock to Bottom -toolbox.meatballMenu.dock.left.label=Dock to Left -toolbox.meatballMenu.dock.right.label=Dock to Right -toolbox.meatballMenu.dock.separateWindow.label=Separate Window - -# LOCALIZATION NOTE (toolbox.meatballMenu.{splitconsole,hideconsole}.label): -# These are the labels in the "..." menu in the toolbox for toggling the split -# console window. -# The keyboard shortcut will be shown to the side of the label. -toolbox.meatballMenu.splitconsole.label=Show Split Console -toolbox.meatballMenu.hideconsole.label=Hide Split Console - -# LOCALIZATION NOTE (toolbox.meatballMenu.noautohide.label): This is the label -# in the "..." menu in the toolbox to force the popups/panels to stay visible on -# blur. -# This is only visible in the browser toolbox as it is meant for -# addon developers and Firefox contributors. -toolbox.meatballMenu.noautohide.label=Disable Popup Auto-Hide - -# LOCALIZATION NOTE (toolbox.meatballMenu.settings.label): This is the label for -# the item in the "..." menu in the toolbox that brings up the Settings -# (Options) panel. -# The keyboard shortcut will be shown to the side of the label. -toolbox.meatballMenu.settings.label=Settings - -# LOCALIZATION NOTE (toolbox.meatballMenu.documentation.label): This is the -# label for the Documentation menu item. -toolbox.meatballMenu.documentation.label=Documentation… - -# LOCALIZATION NOTE (toolbox.meatballMenu.community.label): This is the label -# for the Community menu item. -toolbox.meatballMenu.community.label=Community… - # LOCALIZATION NOTE (toolbox.closebutton.tooltip): This is the tooltip for # the close button the developer tools toolbox. toolbox.closebutton.tooltip=Close Developer Tools diff --git a/devtools/client/shared/components/menu/MenuItem.js b/devtools/client/shared/components/menu/MenuItem.js index eb0579516297..26e48acf34e4 100644 --- a/devtools/client/shared/components/menu/MenuItem.js +++ b/devtools/client/shared/components/menu/MenuItem.js @@ -8,12 +8,16 @@ // A command in a menu. const { + createFactory, createRef, PureComponent, } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); const { button, li, span } = dom; +loader.lazyGetter(this, "Localized", () => + createFactory(require("devtools/client/shared/vendor/fluent-react").Localized) +); class MenuItem extends PureComponent { static get propTypes() { @@ -46,8 +50,11 @@ class MenuItem extends PureComponent { // An optional ID to be assigned to the item. id: PropTypes.string, - // The item label. - label: PropTypes.string.isRequired, + // The item label for use with legacy localization systems. + label: PropTypes.string, + + // The Fluent ID for localizing the label. + l10nID: PropTypes.string, // An optional callback to be invoked when the item is selected. onClick: PropTypes.func, @@ -153,11 +160,32 @@ class MenuItem extends PureComponent { attr["aria-checked"] = true; } - const textLabel = span( - { key: "label", className: "label", ref: this.labelRef }, - this.props.label - ); - const children = [textLabel]; + const children = []; + const className = "label"; + + // Add the text label. + if (this.props.l10nID) { + // Fluent localized label. + children.push( + Localized( + { id: this.props.l10nID, key: "label" }, + span({ className, ref: this.labelRef }) + ) + ); + } else { + children.push( + span({ key: "label", className, ref: this.labelRef }, this.props.label) + ); + } + + if (this.props.l10nID && this.props.label) { + console.warn( + " should only take either an l10nID or a label, not both" + ); + } + if (!this.props.l10nID && !this.props.label) { + console.warn(" requires either an l10nID, or a label prop."); + } if (typeof this.props.accelerator !== "undefined") { const acceleratorLabel = span( diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_split.js b/devtools/client/webconsole/test/browser/browser_webconsole_split.js index 539c3d36edc5..eb365d7abca6 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_split.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_split.js @@ -5,10 +5,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for splitting"; -const { LocalizationHelper } = require("devtools/shared/l10n"); -const L10N = new LocalizationHelper( - "devtools/client/locales/toolbox.properties" -); // Test is slow on Linux EC2 instances - Bug 962931 requestLongerTimeout(4); @@ -122,8 +118,7 @@ add_task(async function() { let label; if (menuItem && menuItem.querySelector(".label")) { label = - menuItem.querySelector(".label").textContent === - L10N.getStr("toolbox.meatballMenu.hideconsole.label") + menuItem.querySelector(".label").textContent === "Hide Split Console" ? "hide" : "split"; } diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_split_persist.js b/devtools/client/webconsole/test/browser/browser_webconsole_split_persist.js index 5b90184b7a8c..52adb7aeda56 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_split_persist.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_split_persist.js @@ -5,11 +5,6 @@ // Test that the split console state is persisted. -const { LocalizationHelper } = require("devtools/shared/l10n"); -const L10N = new LocalizationHelper( - "devtools/client/locales/toolbox.properties" -); - const TEST_URI = "data:text/html;charset=utf-8,

Web Console test for splitting

"; @@ -121,8 +116,7 @@ async function doesMenuSayHide(toolbox) { const result = menuItem && menuItem.querySelector(".label") && - menuItem.querySelector(".label").textContent === - L10N.getStr("toolbox.meatballMenu.hideconsole.label"); + menuItem.querySelector(".label").textContent === "Hide Split Console"; toolbox.doc.addEventListener( "popuphidden", diff --git a/python/l10n/fluent_migrations/bug_1580599_toolbox.py b/python/l10n/fluent_migrations/bug_1580599_toolbox.py new file mode 100644 index 000000000000..102981e72bf1 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1580599_toolbox.py @@ -0,0 +1,58 @@ +# 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 1580599 - Convert toolbox.properties to Fluent, part {index}.""" + + source = "devtools/client/toolbox.properties" + target = "devtools/client/toolbox.ftl" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-dock-bottom-label"), + value=COPY(source, "toolbox.meatballMenu.dock.bottom.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-dock-left-label"), + value=COPY(source, "toolbox.meatballMenu.dock.left.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-dock-right-label"), + value=COPY(source, "toolbox.meatballMenu.dock.right.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-dock-separate-window-label"), + value=COPY(source, "toolbox.meatballMenu.dock.separateWindow.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-splitconsole-label"), + value=COPY(source, "toolbox.meatballMenu.splitconsole.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-hideconsole-label"), + value=COPY(source, "toolbox.meatballMenu.hideconsole.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-noautohide-label"), + value=COPY(source, "toolbox.meatballMenu.noautohide.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-settings-label"), + value=COPY(source, "toolbox.meatballMenu.settings.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-documentation-label"), + value=COPY(source, "toolbox.meatballMenu.documentation.label"), + ), + FTL.Message( + id=FTL.Identifier("toolbox-meatball-menu-community-label"), + value=COPY(source, "toolbox.meatballMenu.community.label"), + ), + ], + )