--- a/devtools/client/application/initializer.js
+++ b/devtools/client/application/initializer.js
@@ -9,16 +9,18 @@ const require = BrowserLoader({
baseURI: "resource://devtools/client/application/",
window,
}).require;
const { createFactory } = require("devtools/client/shared/vendor/react");
const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
+const { L10nRegistry } = require("resource://gre/modules/L10nRegistry.jsm");
+const Services = require("Services");
const { configureStore } = require("./src/create-store");
const actions = require("./src/actions/index");
const App = createFactory(require("./src/components/App"));
/**
* Global Application object in this panel. This object is expected by panel.js and is
@@ -46,21 +48,41 @@ window.Application = {
this.client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
this.client.addListener("registration-changed", this.updateWorkers);
this.client.addListener("processListChanged", this.updateWorkers);
this.toolbox.target.on("navigate", this.updateDomain);
this.updateDomain();
await this.updateWorkers();
+ const messageContexts = await this.createMessageContexts();
+
// Render the root Application component.
- const app = App({ client: this.client, serviceContainer });
+ const app = App({ client: this.client, messageContexts, serviceContainer });
render(Provider({ store: this.store }, app), this.mount);
},
+ /**
+ * Retrieve message contexts for the current locales, and return them as an array of
+ * MessageContext elements.
+ */
+ async createMessageContexts() {
+ const locales = Services.locale.getAppLocalesAsBCP47();
+ let generator = L10nRegistry.generateContexts(locales, ["devtools/application.ftl"]);
+
+ // Return value of generateContexts is a generator and should be converted to
+ // a sync iterable before using it with React.
+ let contexts = [];
+ for await (let message of generator) {
+ contexts.push(message);
+ }
+
+ return contexts;
+ },
+
async updateWorkers() {
let { service } = await this.client.mainRoot.listAllWorkers();
this.actions.updateWorkers(service);
},
updateDomain() {
this.actions.updateDomain(this.toolbox.target.url);
},
--- a/devtools/client/application/src/components/App.js
+++ b/devtools/client/application/src/components/App.js
@@ -4,45 +4,51 @@
"use strict";
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { createFactory, Component } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { main } = require("devtools/client/shared/vendor/react-dom-factories");
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
+
const WorkerList = createFactory(require("./WorkerList"));
const WorkerListEmpty = createFactory(require("./WorkerListEmpty"));
/**
* This is the main component for the application panel.
*/
class App extends Component {
static get propTypes() {
return {
client: PropTypes.object.isRequired,
workers: PropTypes.object.isRequired,
serviceContainer: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
+ messageContexts: PropTypes.array.isRequired,
};
}
render() {
- let { workers, domain, client, serviceContainer } = this.props;
+ let { workers, domain, client, serviceContainer, messageContexts } = this.props;
// Filter out workers from other domains
workers = workers.filter((x) => new URL(x.url).hostname === domain);
const isEmpty = workers.length === 0;
return (
- main(
- { className: `application ${isEmpty ? "application--empty" : ""}` },
- isEmpty
- ? WorkerListEmpty({ serviceContainer })
- : WorkerList({ workers, client })
+ LocalizationProvider(
+ { messages: messageContexts },
+ main(
+ { className: `application ${isEmpty ? "application--empty" : ""}` },
+ isEmpty ? WorkerListEmpty({ serviceContainer })
+ : WorkerList({ workers, client })
+ )
)
);
}
}
// Exports
module.exports = connect(
--- a/devtools/client/application/src/components/Worker.css
+++ b/devtools/client/application/src/components/Worker.css
@@ -68,13 +68,8 @@
.worker__data > * {
margin: 0;
}
.worker__data__updated {
color: var(--theme-body-color-alt);
}
-
-.worker__unregister-button {
- /* TODO: remove this once/if we have proper capitalization in the strings file */
- text-transform: capitalize;
-}
--- a/devtools/client/application/src/components/Worker.js
+++ b/devtools/client/application/src/components/Worker.js
@@ -1,29 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Component } = require("devtools/client/shared/vendor/react");
+const { createFactory, Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { a, br, button, dd, dl, dt, header, li, section, span, time } =
require("devtools/client/shared/vendor/react-dom-factories");
-const Services = require("Services");
const { getUnicodeUrl, getUnicodeUrlPath } = require("devtools/client/shared/unicode-url");
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const Localized = createFactory(FluentReact.Localized);
+
loader.lazyRequireGetter(this, "DebuggerClient",
"devtools/shared/client/debugger-client", true);
loader.lazyRequireGetter(this, "gDevToolsBrowser",
"devtools/client/framework/devtools-browser", true);
-const Strings = Services.strings.createBundle(
- "chrome://devtools/locale/aboutdebugging.properties");
-
/**
* This component is dedicated to display a worker, more accurately a service worker, in
* the list of workers displayed in the application panel. It displays information about
* the worker as well as action links and buttons to interact with the worker (e.g. debug,
* unregister, update etc...).
*/
class Worker extends Component {
static get propTypes() {
@@ -111,65 +110,88 @@ class Worker extends Component {
return getUnicodeUrlPath(parts[parts.length - 1]);
}
render() {
let { worker } = this.props;
let status = this.getServiceWorkerStatus();
const unregisterButton = this.isActive() ?
- button({
- onClick: this.unregister,
- className: "devtools-button worker__unregister-button js-unregister-button",
- "data-standalone": true
- },
- Strings.GetStringFromName("unregister"))
- : null;
+ Localized(
+ { id: "serviceworker-worker-unregister" },
+ button({
+ onClick: this.unregister,
+ className: "devtools-button worker__unregister-button js-unregister-button",
+ "data-standalone": true
+ })
+ ) : null;
const debugLinkDisabled = this.isRunning() ? "" : "disabled";
- const debugLink = a({
- onClick: this.isRunning() ? this.debug : null,
- title: this.isRunning() ? null : "Only running service workers can be debugged",
- className: `${debugLinkDisabled} worker__debug-link js-debug-link`
+ const debugLink = Localized({
+ id: "serviceworker-worker-debug",
+ // The localized title is only displayed if the debug link is disabled.
+ attrs: { title: !this.isRunning() }
},
- Strings.GetStringFromName("debug"));
+ a({
+ onClick: this.isRunning() ? this.debug : null,
+ className: `${debugLinkDisabled} worker__debug-link js-debug-link`
+ })
+ );
const startLink = !this.isRunning() ?
- a({ onClick: this.start, className: "worker__start-link" },
- Strings.GetStringFromName("start"))
- : null;
+ Localized(
+ { id: "serviceworker-worker-start" },
+ a({
+ onClick: this.start,
+ className: "worker__start-link"
+ })
+ ) : null;
- const lastUpdated = worker.lastUpdateTime
- ? span({ className: "worker__data__updated" },
- "Updated ",
- time({ className: "js-sw-updated"},
- new Date(worker.lastUpdateTime / 1000).toLocaleString()))
- : null;
+ const lastUpdated = worker.lastUpdateTime ?
+ Localized(
+ {
+ id: "serviceworker-worker-updated",
+ // XXX: $date should normally be a Date object, but we pass the timestamp as a
+ // workaround. See Bug 1465718. worker.lastUpdateTime is in microseconds,
+ // convert to a valid timestamp in milliseconds by dividing by 1000.
+ "$date": worker.lastUpdateTime / 1000,
+ time: time({ className: "js-sw-updated" })
+ },
+ span({ className: "worker__data__updated" })
+ ) : null;
return li({ className: "worker js-sw-container" },
header(
{ className: "worker__header" },
span({ title: worker.scope, className: "worker__scope js-sw-scope" },
this.formatScope(worker.scope)),
section(
{ className: "worker__controls" },
unregisterButton),
),
dl(
{ className: "worker__data" },
- dt({ className: "worker__meta-name" }, "Source"),
+ Localized({ id: "serviceworker-worker-source" },
+ dt({ className: "worker__meta-name" })
+ ),
dd({},
span({ title: worker.scope, className: "worker__source-url js-source-url" },
this.formatSource(worker.url)),
debugLink,
lastUpdated ? br({}) : null,
lastUpdated ? lastUpdated : null),
- dt({ className: "worker__meta-name" }, "Status"),
+ Localized({ id: "serviceworker-worker-status" },
+ dt({ className: "worker__meta-name" })
+ ),
dd({},
- Strings.GetStringFromName(status).toLowerCase(),
- startLink)
+ Localized(
+ { id: "serviceworker-worker-status-" + status },
+ span({}),
+ ),
+ startLink
+ )
)
);
}
}
module.exports = Worker;
--- a/devtools/client/application/src/components/WorkerList.css
+++ b/devtools/client/application/src/components/WorkerList.css
@@ -3,14 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.aboutdebugging-plug {
text-align: right;
padding: 1rem 0;
}
.aboutdebugging-plug__link {
- margin-right: 0;
+ margin: 0;
}
.workers-container {
flex-grow: 1;
}
--- a/devtools/client/application/src/components/WorkerList.js
+++ b/devtools/client/application/src/components/WorkerList.js
@@ -5,16 +5,19 @@
"use strict";
const { openTrustedLink } = require("devtools/client/shared/link");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { createFactory, Component } = require("devtools/client/shared/vendor/react");
const { a, article, footer, h1, ul } = require("devtools/client/shared/vendor/react-dom-factories");
const Worker = createFactory(require("./Worker"));
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const Localized = createFactory(FluentReact.Localized);
+
/**
* This component handles the list of service workers displayed in the application panel
* and also displays a suggestion to use about debugging for debugging other service
* workers.
*/
class WorkerList extends Component {
static get propTypes() {
return {
@@ -23,30 +26,38 @@ class WorkerList extends Component {
};
}
render() {
const { workers, client } = this.props;
return [
article({ className: "workers-container" },
- h1({}, "Service Workers"),
+ Localized(
+ { id: "serviceworker-list-header" },
+ h1({})
+ ),
ul({},
workers.map(worker => Worker({
client,
debugDisabled: false,
worker,
})))
),
- footer({ className: "aboutdebugging-plug" },
- "See about:debugging for Service Workers from other domains",
- a({ className: "aboutdebugging-plug__link",
- onClick: () => openTrustedLink("about:debugging#workers") },
- "Open about:debugging"
- )
+ Localized(
+ {
+ id: "serviceworker-list-aboutdebugging",
+ a: a(
+ {
+ className: "aboutdebugging-plug__link",
+ onClick: () => openTrustedLink("about:debugging#workers")
+ }
+ )
+ },
+ footer({ className: "aboutdebugging-plug" })
)
];
}
}
// Exports
module.exports = WorkerList;
--- a/devtools/client/application/src/components/WorkerListEmpty.js
+++ b/devtools/client/application/src/components/WorkerListEmpty.js
@@ -1,18 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { openWebLink, openTrustedLink } = require("devtools/client/shared/link");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const { Component } = require("devtools/client/shared/vendor/react");
+const { createFactory, Component } = require("devtools/client/shared/vendor/react");
const { a, article, h1, li, p, ul } = require("devtools/client/shared/vendor/react-dom-factories");
+
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const Localized = createFactory(FluentReact.Localized);
+
const DOC_URL = "https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers" +
"?utm_source=devtools&utm_medium=sw-panel-blank";
/**
* This component displays help information when no service workers are found for the
* current target.
*/
class WorkerListEmpty extends Component {
@@ -36,55 +40,46 @@ class WorkerListEmpty extends Component
openDocumentation() {
openWebLink(DOC_URL);
}
render() {
return article(
{ className: "worker-list-empty" },
- h1(
- { className: "worker-list-empty__title" },
- "You need to register a Service Worker to inspect it here.",
- a(
- { className: "external-link", onClick: () => this.openDocumentation() },
- "Learn More"
- )
+ Localized({
+ id: "serviceworker-empty-intro",
+ a: a({ className: "external-link", onClick: () => this.openDocumentation() })
+ },
+ h1({ className: "worker-list-empty__title" })
),
- p(
- {},
- `If the current page should have a service worker, ` +
- `here are some things you can try:`,
+ Localized(
+ { id: "serviceworker-empty-suggestions" },
+ p({})
),
ul(
{ className: "worker-list-empty__tips" },
- li(
- { className: "worker-list-empty__tips__item" },
- "Look for errors in the Console.",
- a(
- { className: "link", onClick: () => this.switchToConsole() },
- "Open the Console"
- )
+ Localized({
+ id: "serviceworker-empty-suggestions-console",
+ a: a({ className: "link", onClick: () => this.switchToConsole() })
+ },
+ li({ className: "worker-list-empty__tips__item" })
),
- li(
- { className: "worker-list-empty__tips__item" },
- "Step through you Service Worker registration and look for exceptions.",
- a(
- { className: "link", onClick: () => this.switchToDebugger()},
- "Open the Debugger"
- )
+ Localized({
+ id: "serviceworker-empty-suggestions-debugger",
+ a: a({ className: "link", onClick: () => this.switchToDebugger() })
+ },
+ li({ className: "worker-list-empty__tips__item" })
),
- li(
- { className: "worker-list-empty__tips__item" },
- "Inspect Service Workers from other domains.",
- a(
- { className: "external-link", onClick: () => this.openAboutDebugging() },
- "Open about:debugging"
- )
- )
+ Localized({
+ id: "serviceworker-empty-suggestions-aboutdebugging",
+ a: a({ className: "link", onClick: () => this.openAboutDebugging() })
+ },
+ li({ className: "worker-list-empty__tips__item" })
+ ),
)
);
}
}
// Exports
module.exports = WorkerListEmpty;
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/application.ftl
@@ -0,0 +1,75 @@
+# 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 Application panel which is available
+### by setting the preference `devtools-application-enabled` to true.
+
+### 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.
+
+# Header for the list of Service Workers displayed in the application panel for the current page.
+serviceworker-list-header = Service Workers
+
+# Text displayed next to the list of Service Workers to encourage users to check out
+# about:debugging to see all registered Service Workers.
+serviceworker-list-aboutdebugging = Open <a>about:debugging</a> for Service Workers from other domains
+
+# Text for the button to unregister a Service Worker. Displayed for active Service Workers.
+serviceworker-worker-unregister = Unregister
+
+# Text for the debug link displayed for an already started Service Worker. Clicking on the
+# link opens a new devtools toolbox for this service worker. The title attribute is only
+# displayed when the link is disabled.
+serviceworker-worker-debug = Debug
+ .title = Only running service workers can be debugged
+
+# Text for the start link displayed for a registered but not running Service Worker.
+# Clicking on the link will attempt to start the service worker.
+serviceworker-worker-start = Start
+
+# Text displayed for the updated time of the service worker. The <time> element will
+# display the last update time of the service worker script.
+serviceworker-worker-updated = Updated <time>{ DATETIME($date, month: "long", year: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric") }</time>
+
+# Text displayed next to the URL for the source of the service worker (e-g. "Source my/path/to/worker-js")
+serviceworker-worker-source = Source
+
+# Text displayed next to the current status of the service worker.
+serviceworker-worker-status = Status
+
+## Service Worker status strings: all serviceworker-worker-status-* strings are also
+## defined in aboutdebugging.properties and should be synchronized with them.
+
+# Service Worker status. A running service worker is registered, currently executed, can
+# be debugged and stopped.
+serviceworker-worker-status-running = Running
+
+# Service Worker status. A stopped service worker is registered but not currently active.
+serviceworker-worker-status-stopped = Stopped
+
+# Service Worker status. A registering service worker is not yet registered and cannot be
+# started or debugged.
+serviceworker-worker-status-registering = Registering
+
+# Text displayed when no service workers are visible for the current page. Clicking on the
+# link will open https://developer-mozilla-org/docs/Web/API/Service_Worker_API/Using_Service_Workers
+serviceworker-empty-intro = You need to register a Service Worker to inspect it here. <a>Learn more</a>
+
+# Text displayed when there are no Service Workers to display for the current page,
+# introducing hints to debug Service Worker issues.
+serviceworker-empty-suggestions = If the current page should have a service worker, here are some things you can try
+
+# Suggestion to check for errors in the Console to investigate why a service worker is not
+# registered. Clicking on the link opens the webconsole.
+serviceworker-empty-suggestions-console = Look for errors in the Console. <a>Open the Console</a>
+
+# Suggestion to use the debugger to investigate why a service worker is not registered.
+# Clicking on the link will switch from the Application panel to the debugger.
+serviceworker-empty-suggestions-debugger = Step through your Service Worker registration and look for exceptions. <a>Open the Debugger</a>
+
+# Suggestion to go to about:debugging in order to see Service Workers for all domains.
+# Clicking on the link will open about:debugging in a new tab.
+serviceworker-empty-suggestions-aboutdebugging = Inspect Service Workers from other domains. <a>Open about:debugging</a>
--- a/devtools/client/locales/jar.mn
+++ b/devtools/client/locales/jar.mn
@@ -1,8 +1,13 @@
#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 (en-US/**/*.ftl)
+
@AB_CD@.jar:
% locale devtools @AB_CD@ %locale/@AB_CD@/devtools/client/
- locale/@AB_CD@/devtools/client/ (%*)
+ locale/@AB_CD@/devtools/client/ (%*.properties)
+ locale/@AB_CD@/devtools/client/ (%*.dtd)
+