Bug 1760033 - Convert aboutReader.html to Fluent. r=Gijs,fluent-reviewers,geckoview-reviewers,flod,m_kato

The data included in the `Reader:AddButton` message used by SaveToPocket.jsm is slightly modified, as it now includes a localization identifier rather than a preformatted label.

Differential Revision: https://phabricator.services.mozilla.com/D158575
This commit is contained in:
Eemeli Aro 2022-10-13 16:28:31 +00:00
parent 7f9dd7905a
commit f8f531d202
8 changed files with 323 additions and 197 deletions

View File

@ -25,9 +25,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
var EXPORTED_SYMBOLS = ["SaveToPocket"];
const gStrings = Services.strings.createBundle(
"chrome://global/locale/aboutReader.properties"
);
var PocketCustomizableWidget = {
init() {
lazy.CustomizableUI.createWidget({
@ -125,10 +122,8 @@ var SaveToPocket = {
_readerButtonData: {
id: "pocket-button",
l10nId: "about-reader-toolbar-savetopocket",
telemetryId: "save-to-pocket",
label: gStrings.formatStringFromName("readerView.savetopocket.label", [
"Pocket",
]),
image: "chrome://global/skin/icons/pocket.svg",
},

View File

@ -0,0 +1,170 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE
from fluent.migrate.transforms import COPY, PLURALS, REPLACE, REPLACE_IN_TEXT
def migrate(ctx):
"""Bug 1760033 - Convert aboutReader.html to Fluent, part {index}."""
source = "toolkit/chrome/global/aboutReader.properties"
target = "toolkit/toolkit/about/aboutReader.ftl"
ctx.add_transforms(
target,
target,
[
FTL.Message(
id=FTL.Identifier("about-reader-loading"),
value=COPY(source, "aboutReader.loading2"),
),
FTL.Message(
id=FTL.Identifier("about-reader-load-error"),
value=COPY(source, "aboutReader.loadError"),
),
FTL.Message(
id=FTL.Identifier("about-reader-color-scheme-light"),
value=COPY(source, "aboutReader.colorScheme.light"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.colorschemelight"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-color-scheme-dark"),
value=COPY(source, "aboutReader.colorScheme.dark"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.colorschemedark"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-color-scheme-sepia"),
value=COPY(source, "aboutReader.colorScheme.sepia"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.colorschemesepia"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-color-scheme-auto"),
value=COPY(source, "aboutReader.colorScheme.auto"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.colorschemeauto"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-estimated-read-time"),
value=PLURALS(
source,
"aboutReader.estimatedReadTimeRange1",
VARIABLE_REFERENCE("rangePlural"),
foreach=lambda n: REPLACE_IN_TEXT(
n,
{
# most locales
"#1-#2": VARIABLE_REFERENCE("range"),
# bg
"#1#2": VARIABLE_REFERENCE("range"),
# bo
"#1་ནས་#2": VARIABLE_REFERENCE("range"),
# ja ja-JP-mac
"#1 #2": VARIABLE_REFERENCE("range"),
# kab
"#1 -#2": VARIABLE_REFERENCE("range"),
# pl sk
"#1 — #2": VARIABLE_REFERENCE("range"),
},
),
),
),
FTL.Message(
id=FTL.Identifier("about-reader-font-type-serif"),
value=COPY(source, "aboutReader.fontType.serif"),
),
FTL.Message(
id=FTL.Identifier("about-reader-font-type-sans-serif"),
value=COPY(source, "aboutReader.fontType.sans-serif"),
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-minus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.minus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-plus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.plus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-contentwidthminus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.contentwidthminus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-contentwidthplus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.contentwidthplus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-lineheightminus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.lineheightminus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-lineheightplus"),
attributes=[
FTL.Attribute(
id=FTL.Identifier("title"),
value=COPY(source, "aboutReader.toolbar.lineheightplus"),
)
],
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-close"),
value=COPY(source, "aboutReader.toolbar.close"),
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-type-controls"),
value=COPY(source, "aboutReader.toolbar.typeControls"),
),
FTL.Message(
id=FTL.Identifier("about-reader-toolbar-savetopocket"),
value=REPLACE(
source,
"readerView.savetopocket.label",
{"%1$S": TERM_REFERENCE("pocket-brand-name")},
),
),
],
)

View File

@ -243,12 +243,8 @@ class PrintingChild extends JSWindowActorChild {
// Display reader content element
readerContent.style.display = "block";
} else {
let aboutReaderStrings = Services.strings.createBundle(
"chrome://global/locale/aboutReader.properties"
);
let errorMessage = aboutReaderStrings.GetStringFromName(
"aboutReader.loadError"
);
const l10n = new Localization(["toolkit/about/aboutReader.ftl"], true);
const errorMessage = l10n.formatValueSync("about-reader-load-error");
document.title = errorMessage;

View File

@ -13,6 +13,10 @@ const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
@ -23,20 +27,29 @@ ChromeUtils.defineModuleGetter(
"NarrateControls",
"resource://gre/modules/narrate/NarrateControls.jsm"
);
ChromeUtils.defineModuleGetter(
lazy,
"PluralForm",
"resource://gre/modules/PluralForm.jsm"
);
ChromeUtils.defineModuleGetter(
lazy,
"NimbusFeatures",
"resource://nimbus/ExperimentAPI.jsm"
);
var gStrings = Services.strings.createBundle(
"chrome://global/locale/aboutReader.properties"
XPCOMUtils.defineLazyGetter(
lazy,
"numberFormat",
() => new Services.intl.NumberFormat(undefined)
);
XPCOMUtils.defineLazyGetter(
lazy,
"pluralRules",
() => new Services.intl.PluralRules(undefined)
);
const COLORSCHEME_L10N_IDS = {
light: "about-reader-color-scheme-light",
dark: "about-reader-color-scheme-dark",
sepia: "about-reader-color-scheme-sepia",
auto: "about-reader-color-scheme-auto",
};
Services.telemetry.setEventRecordingEnabled("readermode", true);
@ -156,11 +169,7 @@ var AboutReader = function(
Services.obs.addObserver(this, "inner-window-destroyed");
this._setupButton(
"close-button",
this._onReaderClose.bind(this),
"aboutReader.toolbar.close"
);
this._setupButton("close-button", this._onReaderClose.bind(this));
// we're ready for any external setup, send a signal for that.
this._actor.sendAsyncMessage("Reader:OnSetup");
@ -168,14 +177,12 @@ var AboutReader = function(
let colorSchemeValues = JSON.parse(
Services.prefs.getCharPref("reader.color_scheme.values")
);
let colorSchemeOptions = colorSchemeValues.map(value => {
return {
name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
groupName: "color-scheme",
value,
itemClass: value + "-button",
};
});
let colorSchemeOptions = colorSchemeValues.map(value => ({
l10nId: COLORSCHEME_L10N_IDS[value],
groupName: "color-scheme",
value,
itemClass: value + "-button",
}));
let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
this._setupSegmentedButton(
@ -186,21 +193,18 @@ var AboutReader = function(
);
this._setColorSchemePref(colorScheme);
let styleButton = this._doc.querySelector(".style-button");
this._setButtonTip(styleButton, "aboutReader.toolbar.typeControls");
// See bug 1637089.
// let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
let fontTypeOptions = [
{
name: gStrings.GetStringFromName("aboutReader.fontType.sans-serif"),
l10nId: "about-reader-font-type-sans-serif",
groupName: "font-type",
value: "sans-serif",
itemClass: "sans-serif-button",
},
{
name: gStrings.GetStringFromName("aboutReader.fontType.serif"),
l10nId: "about-reader-font-type-serif",
groupName: "font-type",
value: "serif",
itemClass: "serif-button",
@ -231,30 +235,6 @@ var AboutReader = function(
}
this._loadArticle(docContentType);
let dropdown = this._toolbarElement;
let elemL10nMap = {
".minus-button": "minus",
".plus-button": "plus",
".content-width-minus-button": "contentwidthminus",
".content-width-plus-button": "contentwidthplus",
".line-height-minus-button": "lineheightminus",
".line-height-plus-button": "lineheightplus",
".light-button": "colorschemelight",
".dark-button": "colorschemedark",
".sepia-button": "colorschemesepia",
".auto-button": "colorschemeauto",
};
for (let [selector, stringID] of Object.entries(elemL10nMap)) {
dropdown
.querySelector(selector)
?.setAttribute(
"title",
gStrings.GetStringFromName("aboutReader.toolbar." + stringID)
);
}
};
AboutReader.prototype = {
@ -331,30 +311,28 @@ AboutReader.prototype = {
));
},
receiveMessage(message) {
switch (message.name) {
receiveMessage({ data, name }) {
const doc = this._doc;
switch (name) {
case "Reader:AddButton": {
if (
message.data.id &&
message.data.image &&
!this._doc.getElementsByClassName(message.data.id)[0]
) {
let btn = this._doc.createElement("button");
btn.dataset.buttonid = message.data.id;
btn.dataset.telemetryId = `reader-${message.data.telemetryId}`;
btn.className = "toolbar-button " + message.data.id;
let tip = this._doc.createElement("span");
if (data.id && data.image && !doc.getElementsByClassName(data.id)[0]) {
let btn = doc.createElement("button");
btn.dataset.buttonid = data.id;
btn.dataset.telemetryId = `reader-${data.telemetryId}`;
btn.className = "toolbar-button " + data.id;
btn.setAttribute("aria-labelledby", "label-" + data.id);
let tip = doc.createElement("span");
tip.className = "hover-label";
tip.textContent = message.data.label;
tip.id = "label-" + data.id;
doc.l10n.setAttributes(tip, data.l10nId);
btn.append(tip);
btn.setAttribute("aria-label", message.data.label);
btn.style.backgroundImage = "url('" + message.data.image + "')";
if (message.data.width && message.data.height) {
btn.style.backgroundSize = `${message.data.width}px ${message.data.height}px`;
btn.style.backgroundImage = "url('" + data.image + "')";
if (data.width && data.height) {
btn.style.backgroundSize = `${data.width}px ${data.height}px`;
}
let tb = this._toolbarElement;
tb.appendChild(btn);
this._setupButton(message.data.id, button => {
this._setupButton(data.id, button => {
this._actor.sendAsyncMessage(
"Reader:Clicked-" + button.dataset.buttonid,
{ article: this._article }
@ -364,8 +342,8 @@ AboutReader.prototype = {
break;
}
case "Reader:RemoveButton": {
if (message.data.id) {
let btn = this._doc.getElementsByClassName(message.data.id)[0];
if (data.id) {
let btn = doc.getElementsByClassName(data.id)[0];
if (btn) {
btn.remove();
}
@ -993,32 +971,20 @@ AboutReader.prototype = {
this._toolbarElement.setAttribute("articledir", article.dir || "ltr");
},
_formatReadTime(slowEstimate, fastEstimate) {
let displayStringKey = "aboutReader.estimatedReadTimeRange1";
// only show one reading estimate when they are the same value
if (slowEstimate == fastEstimate) {
displayStringKey = "aboutReader.estimatedReadTimeValue1";
}
return lazy.PluralForm.get(
slowEstimate,
gStrings.GetStringFromName(displayStringKey)
)
.replace("#1", fastEstimate)
.replace("#2", slowEstimate);
},
_showError() {
this._headerElement.classList.remove("reader-show-element");
this._contentElement.classList.remove("reader-show-element");
let errorMessage = gStrings.GetStringFromName("aboutReader.loadError");
this._messageElement.textContent = errorMessage;
this._doc.l10n.setAttributes(
this._messageElement,
"about-reader-load-error"
);
this._doc.l10n.setAttributes(
this._doc.getElementById("reader-title"),
"about-reader-load-error"
);
this._messageElement.style.display = "block";
this._doc.title = errorMessage;
this._doc.documentElement.dataset.isError = true;
this._error = true;
@ -1071,9 +1037,19 @@ AboutReader.prototype = {
this._creditsElement.textContent = article.byline;
this._titleElement.textContent = article.title;
this._readTimeElement.textContent = this._formatReadTime(
article.readingTimeMinsSlow,
article.readingTimeMinsFast
const slow = article.readingTimeMinsSlow;
const fast = article.readingTimeMinsFast;
this._doc.l10n.setAttributes(
this._readTimeElement,
"about-reader-estimated-read-time",
{
range: lazy.numberFormat.formatRange(fast, slow),
rangePlural:
slow === fast
? lazy.pluralRules.select(fast) // workaround for https://github.com/tc39/proposal-intl-numberformat-v3/issues/64
: lazy.pluralRules.selectRange(fast, slow),
}
);
// If a document title was not provided in the constructor, we'll fall back
@ -1141,8 +1117,9 @@ AboutReader.prototype = {
this._headerElement.classList.remove("reader-show-element");
this._contentElement.classList.remove("reader-show-element");
this._messageElement.textContent = gStrings.GetStringFromName(
"aboutReader.loading2"
this._doc.l10n.setAttributes(
this._messageElement,
"about-reader-loading"
);
this._messageElement.classList.add("reader-show-element");
}, 300);
@ -1169,9 +1146,9 @@ AboutReader.prototype = {
segmentedButton.appendChild(radioButton);
let item = doc.createElement("label");
item.textContent = option.name;
item.htmlFor = radioButton.id;
item.classList.add(option.itemClass);
doc.l10n.setAttributes(item, option.l10nId);
segmentedButton.appendChild(item);
@ -1200,12 +1177,8 @@ AboutReader.prototype = {
}
},
_setupButton(id, callback, titleEntity) {
_setupButton(id, callback) {
let button = this._doc.querySelector("." + id);
if (titleEntity) {
this._setButtonTip(button, titleEntity);
}
button.removeAttribute("hidden");
button.addEventListener(
"click",
@ -1221,19 +1194,6 @@ AboutReader.prototype = {
);
},
/**
* Sets a tooltip-style label on a button.
* @param Localizable string providing UI element usage tip.
*/
_setButtonTip(button, titleEntity) {
let tip = this._doc.createElement("span");
let localizedString = gStrings.GetStringFromName(titleEntity);
tip.textContent = localizedString;
tip.className = "hover-label";
button.setAttribute("aria-label", localizedString);
button.append(tip);
},
_toggleDropdownClicked(event) {
let dropdown = event.target.closest(".dropdown");

View File

@ -6,11 +6,14 @@
<html>
<head>
<title id="reader-title"></title>
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src data: *; media-src *; object-src 'none'" />
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
<meta name="viewport" content="width=device-width; user-scalable=0" />
<link rel="stylesheet" href="chrome://global/skin/aboutReader.css" type="text/css"/>
<link rel="stylesheet" href="chrome://global/skin/aboutReaderPocket.css" type="text/css"/>
<link rel="localization" href="toolkit/about/aboutReader.ftl"/>
<link rel="localization" href="browser/branding/brandings.ftl"/>
</head>
<body>
@ -19,28 +22,32 @@
<div id="toolbar" class="toolbar-container">
<div class="toolbar reader-toolbar">
<div class="reader-controls">
<button class="close-button toolbar-button" data-telemetry-id="reader-close"></button>
<button class="close-button toolbar-button" aria-labelledby="toolbar-close" data-telemetry-id="reader-close">
<span class="hover-label" id="toolbar-close" data-l10n-id="about-reader-toolbar-close"></span>
</button>
<ul class="dropdown style-dropdown">
<li>
<button class="dropdown-toggle toolbar-button style-button" data-telemetry-id="reader-type-controls"></button>
<button class="dropdown-toggle toolbar-button style-button" aria-labelledby="toolbar-type-controls" data-telemetry-id="reader-type-controls">
<span class="hover-label" id="toolbar-type-controls" data-l10n-id="about-reader-toolbar-type-controls"></span>
</button>
</li>
<li class="dropdown-popup">
<div class="dropdown-arrow"></div>
<div class="font-type-buttons radiorow"></div>
<div class="font-size-buttons buttonrow">
<button class="minus-button"></button>
<button class="minus-button" data-l10n-id="about-reader-toolbar-minus"></button>
<span class="font-size-value"></span>
<button class="plus-button"/>
<button class="plus-button" data-l10n-id="about-reader-toolbar-plus"></button>
</div>
<div class="content-width-buttons buttonrow">
<button class="content-width-minus-button"></button>
<button class="content-width-minus-button" data-l10n-id="about-reader-toolbar-contentwidthminus"></button>
<span class="content-width-value"></span>
<button class="content-width-plus-button"/>
<button class="content-width-plus-button" data-l10n-id="about-reader-toolbar-contentwidthplus"></button>
</div>
<div class="line-height-buttons buttonrow">
<button class="line-height-minus-button"></button>
<button class="line-height-minus-button" data-l10n-id="about-reader-toolbar-lineheightminus"></button>
<span class="line-height-value"></span>
<button class="line-height-plus-button"/>
<button class="line-height-plus-button" data-l10n-id="about-reader-toolbar-lineheightplus"></button>
</div>
<div class="color-scheme-buttons radiorow"></div>
</li>

View File

@ -30,9 +30,10 @@ add_task(async function() {
".reader-estimated-time"
);
ok(readingTimeElement, "Reading time element should be in document");
is(
readingTimeElement.textContent,
"9-12 minutes",
const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
is(args.rangePlural, "other", "Reading time should be '9-12 minutes'");
ok(
/\b9\b.*\b12\b/.test(args.range),
"Reading time should be '9-12 minutes'"
);
});
@ -61,11 +62,9 @@ add_task(async function() {
".reader-estimated-time"
);
ok(readingTimeElement, "Reading time element should be in document");
is(
readingTimeElement.textContent,
"1 minute",
"Reading time should be '1 minute'"
);
const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
is(args.rangePlural, "one", "Reading time should be '~1 minute'");
ok(/\b1\b/.test(args.range), "Reading time should be '~1 minute'");
});
}
);
@ -93,11 +92,9 @@ add_task(async function() {
".reader-estimated-time"
);
ok(readingTimeElement, "Reading time element should be in document");
is(
readingTimeElement.textContent,
"3 minutes",
"Reading time should be '3 minutes'"
);
const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
is(args.rangePlural, "other", "Reading time should be '~3 minutes'");
ok(/\b3\b/.test(args.range), "Reading time should be '~3 minutes'");
});
}
);

View File

@ -2,49 +2,10 @@
# 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 NOTE (aboutReader.loading2):
# Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
aboutReader.loading2=Loading…
aboutReader.loadError=Failed to load article from page
aboutReader.colorScheme.light=Light
aboutReader.colorScheme.dark=Dark
aboutReader.colorScheme.sepia=Sepia
aboutReader.colorScheme.auto=Auto
# LOCALIZATION NOTE (aboutReader.estimatedReadTimeValue1): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of minutes it is estimated to take to read the article
# example: `3 minutes`
aboutReader.estimatedReadTimeValue1=#1 minute;#1 minutes
#LOCALIZATION NOTE (aboutReader.estimatedReadTimeRange1): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# When there is some uncertainty in how long the article will take to read show a range of
# minutes it is expected to take.
# #1 is the number of minutes it is estimated to take to read the article for a fast reader
# #2 is the number of minutes it is estimated to take to read the article for a slow reader
# #2 is the variable used to determine the plural form to use.
# example: `5-8 minutes`
aboutReader.estimatedReadTimeRange1=#1-#2 minute;#1-#2 minutes
# LOCALIZATION NOTE (aboutReader.fontType.serif, aboutReader.fontType.sans-serif):
# These are the styles of typeface that are options in the reader view controls.
aboutReader.fontType.serif=Serif
aboutReader.fontType.sans-serif=Sans-serif
# LOCALIZATION NOTE (aboutReader.fontTypeSample): String used to sample font types.
# For followup see: https://bugzilla.mozilla.org/show_bug.cgi?id=1637089
aboutReader.fontTypeSample=Aa
aboutReader.toolbar.close=Close Reader View
aboutReader.toolbar.typeControls=Type controls
# This is used as a label for the Reader View toolbar button.
# This is a label used for the Save to Pocket option in the toolbar.
# %S is replaced by Pocket.
readerView.savetopocket.label=Save To %S
# This is a label used for done option in the toolbar
readerView.done.label=Done
# These are used for the Reader View toolbar button and the menuitem within the
@ -53,15 +14,3 @@ readerView.enter=Enter Reader View
readerView.enter.accesskey=R
readerView.close=Close Reader View
readerView.close.accesskey=R
# These are used as tooltips in Type Control
aboutReader.toolbar.minus = Decrease Font Size
aboutReader.toolbar.plus = Increase Font Size
aboutReader.toolbar.contentwidthminus = Decrease Content Width
aboutReader.toolbar.contentwidthplus = Increase Content Width
aboutReader.toolbar.lineheightminus = Decrease Line Height
aboutReader.toolbar.lineheightplus = Increase Line Height
aboutReader.toolbar.colorschemelight = Color Scheme Light
aboutReader.toolbar.colorschemedark = Color Scheme Dark
aboutReader.toolbar.colorschemesepia = Color Scheme Sepia
aboutReader.toolbar.colorschemeauto = Color Scheme Auto

View File

@ -0,0 +1,52 @@
# 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/.
about-reader-loading = Loading…
about-reader-load-error = Failed to load article from page
about-reader-color-scheme-light = Light
.title = Color Scheme Light
about-reader-color-scheme-dark = Dark
.title = Color Scheme Dark
about-reader-color-scheme-sepia = Sepia
.title = Color Scheme Sepia
about-reader-color-scheme-auto = Auto
.title = Color Scheme Auto
# An estimate for how long it takes to read an article,
# expressed as a range covering both slow and fast readers.
# Variables:
# $rangePlural (String): The plural category of the range, using the same set as for numbers.
# $range (String): The range of minutes as a localised string. Examples: "3-7", "~1".
about-reader-estimated-read-time =
{ $rangePlural ->
[one] { $range } minute
*[other] { $range } minutes
}
## These are used as tooltips in Type Control
about-reader-toolbar-minus =
.title = Decrease Font Size
about-reader-toolbar-plus =
.title = Increase Font Size
about-reader-toolbar-contentwidthminus =
.title = Decrease Content Width
about-reader-toolbar-contentwidthplus =
.title = Increase Content Width
about-reader-toolbar-lineheightminus =
.title = Decrease Line Height
about-reader-toolbar-lineheightplus =
.title = Increase Line Height
## These are the styles of typeface that are options in the reader view controls.
about-reader-font-type-serif = Serif
about-reader-font-type-sans-serif = Sans-serif
## Reader View toolbar buttons
about-reader-toolbar-close = Close Reader View
about-reader-toolbar-type-controls = Type controls
about-reader-toolbar-savetopocket = Save To { -pocket-brand-name }