From fbe42f77963f6f09b45416f26f308aac901c38c9 Mon Sep 17 00:00:00 2001 From: Jeane Carlos Date: Tue, 2 Jul 2019 17:25:55 +0000 Subject: [PATCH] Bug 1555788 - Migrate about preferences home content to use fluent r=fluent-reviewers,Mardak,pdahiya,flod Differential Revision: https://phabricator.services.mozilla.com/D35278 --HG-- extra : moz-landing-system : lando --- .../newtab/lib/AboutPreferences.jsm | 55 +++---- .../components/newtab/lib/SectionsManager.jsm | 25 ++-- .../locales-src/en-US/strings.properties | 39 ----- .../test/unit/lib/AboutPreferences.test.js | 53 +++---- .../preferences/in-content/preferences.xul | 1 + .../en-US/browser/preferences/preferences.ftl | 47 ++++++ .../fluent_migrations/bug_1555788_newtab.py | 138 ++++++++++++++++++ 7 files changed, 246 insertions(+), 112 deletions(-) create mode 100644 python/l10n/fluent_migrations/bug_1555788_newtab.py diff --git a/browser/components/newtab/lib/AboutPreferences.jsm b/browser/components/newtab/lib/AboutPreferences.jsm index 5b4d477387e5..d3d02d0dfc30 100644 --- a/browser/components/newtab/lib/AboutPreferences.jsm +++ b/browser/components/newtab/lib/AboutPreferences.jsm @@ -5,7 +5,6 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -ChromeUtils.defineModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); const {actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm"); XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]); @@ -19,7 +18,7 @@ const PREFS_BEFORE_SECTIONS = [ id: "search", pref: { feed: "showSearch", - titleString: "prefs_search_header", + titleString: "home-prefs-search-header", }, icon: "chrome://browser/skin/search-glass.svg", }, @@ -27,8 +26,8 @@ const PREFS_BEFORE_SECTIONS = [ id: "topsites", pref: { feed: "feeds.topsites", - titleString: "settings_pane_topsites_header", - descString: "prefs_topsites_description", + titleString: "home-prefs-topsites-header", + descString: "home-prefs-topsites-description", }, icon: "topsites", maxRows: 4, @@ -41,8 +40,8 @@ const PREFS_AFTER_SECTIONS = [ id: "snippets", pref: { feed: "feeds.snippets", - titleString: "settings_pane_snippets_header", - descString: "prefs_snippets_description", + titleString: "home-prefs-snippets-header", + descString: "home-prefs-snippets-description", }, icon: "info", }, @@ -149,20 +148,11 @@ this.AboutPreferences = class AboutPreferences { const createAppend = (tag, parent, options) => parent.appendChild( document.createXULElement(tag, options)); - // Helper to get strings and format with values if necessary - const formatString = id => { - if (typeof id !== "object") { - return strings[id] || id; - } - let string = strings[id.id] || JSON.stringify(id); - if (id.values) { - Object.entries(id.values).forEach(([key, val]) => { - string = string.replace(new RegExp(`{${key}}`, "g"), val); - }); - } - return string; - }; + // Helper to get fluentIDs sometimes encase in an object + const getString = message => + typeof message !== "object" ? message : message.id; + // Helper to link a UI element to a preference for updating const linkPref = (element, name, type) => { const fullPref = `browser.newtabpage.activity-stream.${name}`; @@ -183,11 +173,12 @@ this.AboutPreferences = class AboutPreferences { const contentsGroup = homeGroup.insertAdjacentElement("afterend", homeGroup.cloneNode()); contentsGroup.id = "homeContentsGroup"; contentsGroup.setAttribute("data-subcategory", "contents"); - createAppend("label", contentsGroup) - .appendChild(document.createElementNS(HTML_NS, "h2")) - .textContent = formatString("prefs_home_header"); - createAppend("description", contentsGroup) - .textContent = formatString("prefs_home_description"); + const homeHeader = createAppend("label", contentsGroup) + .appendChild(document.createElementNS(HTML_NS, "h2")); + document.l10n.setAttributes(homeHeader, "home-prefs-content-header"); + + const homeDescription = createAppend("description", contentsGroup); + document.l10n.setAttributes(homeDescription, "home-prefs-content-description"); // Add preferences for each section prefStructure.forEach(sectionData => { @@ -201,7 +192,7 @@ this.AboutPreferences = class AboutPreferences { } = sectionData; const { feed: name, - titleString, + titleString = {}, descString, nestedPrefs = [], } = prefData || {}; @@ -220,8 +211,9 @@ this.AboutPreferences = class AboutPreferences { sectionVbox.setAttribute("data-subcategory", id); const checkbox = createAppend("checkbox", sectionVbox); checkbox.classList.add("section-checkbox"); - checkbox.setAttribute("label", formatString(titleString)); checkbox.setAttribute("src", iconUrl); + document.l10n.setAttributes(checkbox, getString(titleString), titleString.values); + linkPref(checkbox, name, "bool"); // Specially add a link for stories @@ -233,8 +225,8 @@ this.AboutPreferences = class AboutPreferences { const link = createAppend("label", sponsoredHbox, {is: "text-link"}); link.classList.add("learn-sponsored"); - link.setAttribute("href", sectionData.learnMore.link.href); - link.textContent = formatString(sectionData.learnMore.link.id); + link.setAttribute("href", sectionData.pref.learnMore.link.href); + document.l10n.setAttributes(link, sectionData.pref.learnMore.link.id); } // Add more details for the section (e.g., description, more prefs) @@ -243,7 +235,7 @@ this.AboutPreferences = class AboutPreferences { if (descString) { const label = createAppend("label", detailVbox); label.classList.add("indent"); - label.textContent = formatString(descString); + document.l10n.setAttributes(label, getString(descString)); // Add a rows dropdown if we have a pref to control and a maximum if (rowsPref && maxRows) { @@ -260,9 +252,8 @@ this.AboutPreferences = class AboutPreferences { menulist.setAttribute("crop", "none"); const menupopup = createAppend("menupopup", menulist); for (let num = 1; num <= maxRows; num++) { - const plurals = formatString({id: "prefs_section_rows_option", values: {num}}); const item = createAppend("menuitem", menupopup); - item.setAttribute("label", PluralForm.get(num, plurals)); + document.l10n.setAttributes(item, "home-prefs-sections-rows-option", { num }); item.setAttribute("value", num); } linkPref(menulist, rowsPref, "int"); @@ -277,7 +268,7 @@ this.AboutPreferences = class AboutPreferences { nestedPrefs.forEach(nested => { const subcheck = createAppend("checkbox", detailVbox); subcheck.classList.add("indent"); - subcheck.setAttribute("label", formatString(nested.titleString)); + document.l10n.setAttributes(subcheck, nested.titleString); linkPref(subcheck, nested.name, "bool"); subChecks.push(subcheck); subcheck.disabled = !pref._value; diff --git a/browser/components/newtab/lib/SectionsManager.jsm b/browser/components/newtab/lib/SectionsManager.jsm index e81c09414d52..74c35a739ef4 100644 --- a/browser/components/newtab/lib/SectionsManager.jsm +++ b/browser/components/newtab/lib/SectionsManager.jsm @@ -19,13 +19,19 @@ const BUILT_IN_SECTIONS = { "feeds.section.topstories": options => ({ id: "topstories", pref: { - titleString: {id: "header_recommended_by", values: {provider: options.provider_name}}, - descString: {id: "prefs_topstories_description2"}, + titleString: {id: "home-prefs-recommended-by-header", values: {provider: options.provider_name} }, + descString: {id: "home-prefs-recommended-by-description"}, nestedPrefs: options.show_spocs ? [{ name: "showSponsored", - titleString: "prefs_topstories_options_sponsored_label", + titleString: "home-prefs-recommended-by-option-sponsored-stories", icon: "icon-info", }] : [], + learnMore: { + link: { + href: "https://getpocket.com/firefox/new_tab_learn_more", + id: "home-prefs-recommended-by-learn-more", + }, + }, }, shouldHidePref: options.hidden, eventSource: "TOP_STORIES", @@ -35,7 +41,6 @@ const BUILT_IN_SECTIONS = { link: { href: "https://getpocket.com/firefox/new_tab_learn_more", message: {id: "newtab-pocket-how-it-works"}, - id: "pocket_how_it_works", }, }, privacyNoticeURL: "https://www.mozilla.org/privacy/firefox/#suggest-relevant-content", @@ -53,20 +58,20 @@ const BUILT_IN_SECTIONS = { "feeds.section.highlights": options => ({ id: "highlights", pref: { - titleString: {id: "settings_pane_highlights_header"}, - descString: {id: "prefs_highlights_description"}, + titleString: {id: "home-prefs-highlights-header"}, + descString: {id: "home-prefs-highlights-description"}, nestedPrefs: [{ name: "section.highlights.includeVisited", - titleString: "prefs_highlights_options_visited_label", + titleString: "home-prefs-highlights-option-visited-pages", }, { name: "section.highlights.includeBookmarks", - titleString: "settings_pane_highlights_options_bookmarks", + titleString: "home-prefs-highlights-options-bookmarks", }, { name: "section.highlights.includeDownloads", - titleString: "prefs_highlights_options_download_label", + titleString: "home-prefs-highlights-option-most-recent-download", }, { name: "section.highlights.includePocket", - titleString: "prefs_highlights_options_pocket_label", + titleString: "home-prefs-highlights-option-saved-to-pocket", }], }, shouldHidePref: false, diff --git a/browser/components/newtab/locales-src/en-US/strings.properties b/browser/components/newtab/locales-src/en-US/strings.properties index 06d819e73a3d..f62d8559df4a 100644 --- a/browser/components/newtab/locales-src/en-US/strings.properties +++ b/browser/components/newtab/locales-src/en-US/strings.properties @@ -1,42 +1,3 @@ -# LOCALIZATION NOTE(header_recommended_by): This is followed by the name -# of the corresponding content provider. -header_recommended_by=Recommended by {provider} - -# LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences -# for a "Firefox Home" section. "Firefox" should be treated as a brand and kept -# in English, while "Home" should be localized matching the about:preferences -# sidebar mozilla-central string for the panel that has preferences related to -# what is shown for the homepage, new windows, and new tabs. -prefs_home_header=Firefox Home Content -prefs_home_description=Choose what content you want on your Firefox Home screen. - -prefs_content_discovery_description=Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web. - -# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of -# plural forms used in a drop down of multiple row options (1 row, 2 rows). -# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals -prefs_section_rows_option={num} row;{num} rows -prefs_search_header=Web Search -prefs_topsites_description=The sites you visit most -prefs_topstories_description2=Great content from around the web, personalized for you -prefs_topstories_options_sponsored_label=Sponsored Stories -prefs_topstories_sponsored_learn_more=Learn more -prefs_highlights_description=A selection of sites that you’ve saved or visited -prefs_highlights_options_visited_label=Visited Pages -prefs_highlights_options_download_label=Most Recent Download -prefs_highlights_options_pocket_label=Pages Saved to Pocket -prefs_snippets_description=Updates from Mozilla and Firefox -settings_pane_topsites_header=Top Sites -settings_pane_highlights_header=Highlights -settings_pane_highlights_options_bookmarks=Bookmarks -# LOCALIZATION NOTE(settings_pane_snippets_header): For the "Snippets" feature -# traditionally on about:home. Alternative translation options: "Small Note" or -# something that expresses the idea of "a small message, shortened from -# something else, and non-essential but also not entirely trivial and useless." -settings_pane_snippets_header=Snippets - -pocket_how_it_works=How it works - # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the # firstrun of the browser, they give an introduction to Firefox and Sync. firstrun_title=Take Firefox with You diff --git a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js index 226ac339f27d..60932ab1741a 100644 --- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js +++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js @@ -153,6 +153,12 @@ describe("AboutPreferences Feed", () => { const testRender = () => instance.renderPreferences({ document: { createXULElement: sandbox.stub().returns(node), + l10n: { + setAttributes(el, id , args) { + el.setAttribute("data-l10n-id", id); + el.setAttribute("data-l10n-args", JSON.stringify(args)); + }, + }, createProcessingInstruction: sandbox.stub(), createElementNS: sandbox.stub().callsFake((NS, el) => node), getElementById: sandbox.stub().returns(node), @@ -183,42 +189,27 @@ describe("AboutPreferences Feed", () => { }; gHomePane = {toggleRestoreDefaultsBtn: sandbox.stub()}; }); - describe("#formatString", () => { - it("should fall back to string id if missing", () => { - testRender(); - - assert.equal(node.textContent, "prefs_home_description"); - }); - it("should use provided plain string", () => { - strings = {prefs_home_description: "hello"}; + describe("#getString", () => { + it("should not fail if titleString is not provided", () => { + prefStructure = [{pref: {}}]; testRender(); - - assert.equal(node.textContent, "hello"); + assert.calledWith(node.setAttribute, "data-l10n-id", sinon.match.typeOf("undefined")); }); - it("should fall back to string object if missing", () => { - const titleString = {id: "foo"}; + it("should return the string id if titleString is just a string", () => { + const titleString = "foo"; prefStructure = [{pref: {titleString}}]; testRender(); - - assert.calledWith(node.setAttribute, "label", JSON.stringify(titleString)); + assert.calledWith(node.setAttribute, "data-l10n-id", titleString); }); - it("should use provided string object id", () => { - strings = {foo: "bar"}; - prefStructure = [{pref: {titleString: {id: "foo"}}}]; + it("should set id and args if titleString is an object with id and values", () => { + const titleString = {id: "foo", values: { provider: "bar"}}; + prefStructure = [{pref: {titleString}}]; testRender(); - - assert.calledWith(node.setAttribute, "label", "bar"); - }); - it("should use values in string object", () => { - strings = {foo: "l{n}{n}t"}; - prefStructure = [{pref: {titleString: {id: "foo", values: {n: 3}}}}]; - - testRender(); - - assert.calledWith(node.setAttribute, "label", "l33t"); + assert.calledWith(node.setAttribute, "data-l10n-id", titleString.id); + assert.calledWith(node.setAttribute, "data-l10n-args", JSON.stringify(titleString.values)); }); }); describe("#linkPref", () => { @@ -268,11 +259,11 @@ describe("AboutPreferences Feed", () => { testRender(); - assert.calledWith(node.setAttribute, "label", titleString); + assert.calledWith(node.setAttribute, "data-l10n-id", titleString); }); it("should add a link for top stories", () => { const href = "https://disclaimer/"; - prefStructure = [{learnMore: {link: {href}}, id: "topstories", pref: {feed: "feed"}}]; + prefStructure = [{id: "topstories", pref: {feed: "feed", learnMore: {link: {href}}}}]; testRender(); assert.calledWith(node.setAttribute, "href", href); @@ -285,7 +276,7 @@ describe("AboutPreferences Feed", () => { testRender(); - assert.equal(node.textContent, descString); + assert.calledWith(node.setAttribute, "data-l10n-id", descString); }); it("should render rows dropdown with appropriate number", () => { prefStructure = [{rowsPref: "row_pref", maxRows: 3, pref: {descString: "foo"}}]; @@ -305,7 +296,7 @@ describe("AboutPreferences Feed", () => { it("should render a nested pref", () => { testRender(); - assert.calledWith(node.setAttribute, "label", titleString); + assert.calledWith(node.setAttribute, "data-l10n-id", titleString); }); it("should add a change event", () => { testRender(); diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul index 839881cda067..0e20705dee26 100644 --- a/browser/components/preferences/in-content/preferences.xul +++ b/browser/components/preferences/in-content/preferences.xul @@ -26,6 +26,7 @@ + diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl index 2d28b9577d7a..dea78f07b927 100644 --- a/browser/locales/en-US/browser/preferences/preferences.ftl +++ b/browser/locales/en-US/browser/preferences/preferences.ftl @@ -498,6 +498,53 @@ choose-bookmark = .label = Use Bookmark… .accesskey = B +## Home Section - Firefox Home Content Customization + +home-prefs-content-header = Firefox Home Content +home-prefs-content-description = Choose what content you want on your Firefox Home screen. +home-prefs-content-discovery-description = Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web. + +home-prefs-search-header = + .label = Web Search +home-prefs-topsites-header = + .label = Top Sites +home-prefs-topsites-description = The sites you visit most + +# Variables: +# $provider (String): Name of the corresponding content provider, e.g "Pocket". +home-prefs-recommended-by-header = + .label = Recommended by { $provider } +home-prefs-recommended-by-description = Great content from around the web, personalized for you +home-prefs-recommended-by-learn-more = How it works +home-prefs-recommended-by-option-sponsored-stories = + .label = Sponsored Stories + +home-prefs-highlights-header = + .label = Highlights +home-prefs-highlights-description = A selection of sites that you’ve saved or visited +home-prefs-highlights-option-visited-pages = + .label = Visited Pages +home-prefs-highlights-options-bookmarks = + .label = Bookmarks +home-prefs-highlights-option-most-recent-download = + .label = Most Recent Download +home-prefs-highlights-option-saved-to-pocket = + .label = Pages Saved to { -pocket-brand-name } + +# For the "Snippets" feature traditionally on about:home. +# Alternative translation options: "Small Note" or something that +# expresses the idea of "a small message, shortened from something else, +# and non-essential but also not entirely trivial and useless. +home-prefs-snippets-header = + .label = Snippets +home-prefs-snippets-description = Updates from { -vendor-short-name } and { -brand-product-name } +home-prefs-sections-rows-option = + .label = + { $num -> + [one] { $num } row + *[other] { $num } rows + } + ## Search Section search-bar-header = Search Bar diff --git a/python/l10n/fluent_migrations/bug_1555788_newtab.py b/python/l10n/fluent_migrations/bug_1555788_newtab.py new file mode 100644 index 000000000000..0c985337edd8 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1555788_newtab.py @@ -0,0 +1,138 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE +from fluent.migrate import REPLACE, REPLACE_IN_TEXT, PLURALS + +TARGET_FILE = 'browser/browser/preferences/preferences.ftl' +SOURCE_FILE = 'browser/browser/preferences/preferences.ftl' + +""" +From mozilla-central directory: +``` +cd .. +git clone hg::https://hg.mozilla.org/l10n/fluent-migration +cd fluent-migration +pip install -e . +cd .. +hg clone https://hg.mozilla.org/l10n/gecko-strings +``` +NB: gecko-strings needs to be cloned with mercurial not git-cinnabar +Testing from mozilla-central directory: +``` +PYTHONPATH=./python/l10n/fluent_migrations migrate-l10n bug_1555788_newtab --lang en-US --reference-dir . \ + --localization-dir ../gecko-strings +diff -B -w browser/locales/en-US/browser/preferences/preferences.ftl ../gecko-strings/browser/browser/preferences/preferences.ftl +``` +""" + +def migrate(ctx): + """Bug 1555788 - Migrate about preferences home content to use fluent, part {index}""" + ctx.add_transforms( + TARGET_FILE, + SOURCE_FILE, + transforms_from( +""" +home-prefs-content-header = { COPY(from_path, "prefs_home_header") } +home-prefs-content-description = { COPY(from_path, "prefs_home_description") } +home-prefs-content-discovery-description = { COPY(from_path, "prefs_content_discovery_description") } + +home-prefs-search-header = + .label = { COPY(from_path, "prefs_search_header") } + +home-prefs-highlights-header = + .label = { COPY(from_path, "settings_pane_highlights_header") } +home-prefs-highlights-description = { COPY(from_path, "prefs_highlights_description") } +home-prefs-highlights-option-visited-pages = + .label = { COPY(from_path, "prefs_highlights_options_visited_label") } +home-prefs-highlights-option-most-recent-download = + .label = { COPY(from_path, "prefs_highlights_options_download_label") } +home-prefs-highlights-options-bookmarks = + .label = { COPY(from_path, "settings_pane_highlights_options_bookmarks") } +home-prefs-snippets-header = + .label = { COPY(from_path, "settings_pane_snippets_header") } + +home-prefs-topsites-description = { COPY(from_path, "prefs_topsites_description") } +home-prefs-topsites-header = + .label = { COPY(from_path, "settings_pane_topsites_header") } + +home-prefs-recommended-by-description = { COPY(from_path, "prefs_topstories_description2") } +home-prefs-recommended-by-learn-more = { COPY(from_path, "pocket_how_it_works") } +home-prefs-recommended-by-option-sponsored-stories = + .label = { COPY(from_path, "prefs_topstories_options_sponsored_label") } + +""", from_path="browser/chrome/browser/activity-stream/newtab.properties" + ) + ) + + ctx.add_transforms( + TARGET_FILE, + SOURCE_FILE, + [ + FTL.Message( + id=FTL.Identifier("home-prefs-recommended-by-header"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/activity-stream/newtab.properties", + "header_recommended_by", + { + "{provider}": VARIABLE_REFERENCE("provider") + } + ) + ) + ] + ), + FTL.Message( + id=FTL.Identifier("home-prefs-highlights-option-saved-to-pocket"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/activity-stream/newtab.properties", + "prefs_highlights_options_pocket_label", + { + "Pocket": TERM_REFERENCE("pocket-brand-name") + }, + ) + ) + ] + ), + FTL.Message( + id=FTL.Identifier("home-prefs-snippets-description"), + value=REPLACE( + "browser/chrome/browser/activity-stream/newtab.properties", + "prefs_snippets_description", + { + "Mozilla": TERM_REFERENCE("vendor-short-name"), + "Firefox": TERM_REFERENCE("brand-product-name") + }, + ) + ), + FTL.Message( + id=FTL.Identifier("home-prefs-sections-rows-option"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + "browser/chrome/browser/activity-stream/newtab.properties", + "prefs_section_rows_option", + VARIABLE_REFERENCE("num"), + lambda text: REPLACE_IN_TEXT( + text, + { + "{num}": VARIABLE_REFERENCE("num") + } + ) + ) + ) + ] + ) + ] + )