Bug 1555788 - Migrate about preferences home content to use fluent r=fluent-reviewers,Mardak,pdahiya,flod
authorJeane Carlos <volbohel@gmail.com>
Tue, 02 Jul 2019 17:25:55 +0000
changeset 543832 7c5b35e851d6d8a15ddb66991e8de06912be8bbe
parent 543831 e349eb0585f7835d6837532fef35019c73523cbf
child 543833 f6b0141f7b1fe8bef141302f0556725ec6ba5695
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfluent-reviewers, Mardak, pdahiya, flod
bugs1555788
milestone69.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 1555788 - Migrate about preferences home content to use fluent r=fluent-reviewers,Mardak,pdahiya,flod Differential Revision: https://phabricator.services.mozilla.com/D35278
browser/components/newtab/lib/AboutPreferences.jsm
browser/components/newtab/lib/SectionsManager.jsm
browser/components/newtab/locales-src/en-US/strings.properties
browser/components/newtab/test/unit/lib/AboutPreferences.test.js
browser/components/preferences/in-content/preferences.xul
browser/locales/en-US/browser/preferences/preferences.ftl
python/l10n/fluent_migrations/bug_1555788_newtab.py
--- a/browser/components/newtab/lib/AboutPreferences.jsm
+++ b/browser/components/newtab/lib/AboutPreferences.jsm
@@ -1,53 +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/. */
 "use strict";
 
 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"]);
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const PREFERENCES_LOADED_EVENT = "home-pane-loaded";
 
 // These "section" objects are formatted in a way to be similar to the ones from
 // SectionsManager to construct the preferences view.
 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",
   },
   {
     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,
     rowsPref: "topSitesRows",
   },
 ];
 
 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",
   },
 ];
 
 // This CSS is added to the whole about:preferences page
 const CUSTOM_CSS = `
 #homeContentsGroup checkbox[src] .checkbox-icon {
@@ -144,30 +143,21 @@ this.AboutPreferences = class AboutPrefe
    * Render preferences to an about:preferences content window with the provided
    * strings and preferences structure.
    */
   renderPreferences({document, Preferences, gHomePane}, strings, prefStructure) {
     // Helper to create a new element and append it
     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}`;
       element.setAttribute("preference", fullPref);
       Preferences.add({id: fullPref, type});
 
       // Prevent changing the UI if the preference can't be changed
       element.disabled = Preferences.get(fullPref).locked;
@@ -178,35 +168,36 @@ this.AboutPreferences = class AboutPrefe
       `href="data:text/css,${encodeURIComponent(CUSTOM_CSS)}" type="text/css"`),
       document.documentElement);
 
     // Insert a new group immediately after the homepage one
     const homeGroup = document.getElementById("homepageGroup");
     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 => {
       const {
         id,
         pref: prefData,
         icon = "webextension",
         maxRows,
         rowsPref,
         shouldHidePref,
       } = sectionData;
       const {
         feed: name,
-        titleString,
+        titleString = {},
         descString,
         nestedPrefs = [],
       } = prefData || {};
 
       // Don't show any sections that we don't want to expose in preferences UI
       if (shouldHidePref) {
         return;
       }
@@ -215,74 +206,74 @@ this.AboutPreferences = class AboutPrefe
       const iconUrl = !icon.search(/^(chrome|moz-extension|resource):/) ? icon :
         `resource://activity-stream/data/content/assets/glyph-${icon}-16.svg`;
 
       // Add the main preference for turning on/off a section
       const sectionVbox = createAppend("vbox", contentsGroup);
       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
       if (id === "topstories") {
         const sponsoredHbox = createAppend("hbox", sectionVbox);
         sponsoredHbox.setAttribute("align", "center");
         sponsoredHbox.appendChild(checkbox);
         checkbox.classList.add("tail-with-learn-more");
 
         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)
       const detailVbox = createAppend("vbox", sectionVbox);
       detailVbox.classList.add("indent");
       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) {
           const detailHbox = createAppend("hbox", detailVbox);
           detailHbox.setAttribute("align", "center");
           label.setAttribute("flex", 1);
           detailHbox.appendChild(label);
 
           // Add box so the search tooltip is positioned correctly
           const tooltipBox = createAppend("hbox", detailHbox);
 
           // Add appropriate number of localized entries to the dropdown
           const menulist = createAppend("menulist", tooltipBox);
           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");
         }
       }
 
       const subChecks = [];
       const fullName = `browser.newtabpage.activity-stream.${sectionData.pref.feed}`;
       const pref = Preferences.get(fullName);
 
       // Add a checkbox pref for any nested preferences
       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;
       });
 
       // Disable any nested checkboxes if the parent pref is not enabled.
       pref.on("change", () => {
         subChecks.forEach(subcheck => {
--- a/browser/components/newtab/lib/SectionsManager.jsm
+++ b/browser/components/newtab/lib/SectionsManager.jsm
@@ -14,33 +14,38 @@ ChromeUtils.defineModuleGetter(this, "Pl
  * Generators for built in sections, keyed by the pref name for their feed.
  * Built in sections may depend on options stored as serialised JSON in the pref
  * `${feed_pref_name}.options`.
  */
 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",
     icon: options.provider_icon,
     title: {id: "newtab-section-header-pocket", values: {provider: options.provider_name}},
     learnMore: {
       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",
     compactCards: false,
     rowsPref: "section.topstories.rows",
     maxRows: 4,
     availableLinkMenuOptions: ["CheckBookmarkOrArchive", "CheckSavedToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
     emptyState: {
@@ -48,30 +53,30 @@ const BUILT_IN_SECTIONS = {
       icon: "check",
     },
     shouldSendImpressionStats: true,
     dedupeFrom: ["highlights"],
   }),
   "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,
     eventSource: "HIGHLIGHTS",
     icon: "highlights",
     title: {id: "newtab-section-header-highlights"},
     compactCards: true,
     rowsPref: "section.highlights.rows",
--- a/browser/components/newtab/locales-src/en-US/strings.properties
+++ b/browser/components/newtab/locales-src/en-US/strings.properties
@@ -1,47 +1,8 @@
-# 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
 firstrun_content=Get your bookmarks, history, passwords and other settings on all your devices.
 firstrun_learn_more_link=Learn more about Firefox Accounts
 
 # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
 # firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
--- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
+++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
@@ -148,16 +148,22 @@ describe("AboutPreferences Feed", () => 
     let node;
     let strings;
     let prefStructure;
     let Preferences;
     let gHomePane;
     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),
         insertBefore: sandbox.stub().returnsArg(0),
         querySelector: sandbox.stub().returns({appendChild: sandbox.stub()}),
       },
       Preferences,
       gHomePane,
@@ -178,52 +184,37 @@ describe("AboutPreferences Feed", () => 
       Preferences = {
         add: sandbox.stub(),
         get: sandbox.stub().returns({
           on: sandbox.stub(),
         }),
       };
       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", () => {
       it("should add a pref to the global", () => {
         prefStructure = [{pref: {feed: "feed"}}];
 
         testRender();
 
@@ -263,34 +254,34 @@ describe("AboutPreferences Feed", () => 
     });
     describe("title line", () => {
       it("should render a title", () => {
         const titleString = "the_title";
         prefStructure = [{pref: {titleString}}];
 
         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);
       });
     });
     describe("description line", () => {
       it("should render a description", () => {
         const descString = "the_desc";
         prefStructure = [{pref: {descString}}];
 
         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"}}];
 
         testRender();
 
         assert.calledWith(node.setAttribute, "value", 1);
         assert.calledWith(node.setAttribute, "value", 2);
@@ -300,17 +291,17 @@ describe("AboutPreferences Feed", () => 
     describe("nested prefs", () => {
       const titleString = "im_nested";
       beforeEach(() => {
         prefStructure = [{pref: {nestedPrefs: [{titleString}]}}];
       });
       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();
 
         assert.calledOnce(Preferences.get().on);
         assert.calledWith(Preferences.get().on, "change");
       });
       it("should default node disabled to false", async () => {
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -21,16 +21,17 @@
 <page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
       xmlns:html="http://www.w3.org/1999/xhtml"
       role="document"
       data-l10n-id="pref-page"
       data-l10n-attrs="title">
 
   <linkset>
     <html:link rel="localization" href="branding/brand.ftl"/>
+    <html:link rel="localization" href="browser/branding/brandings.ftl"/>
     <html:link rel="localization" href="browser/branding/sync-brand.ftl"/>
     <html:link rel="localization" href="browser/preferences/preferences.ftl"/>
     <!-- Used by fontbuilder.js -->
     <html:link rel="localization" href="browser/preferences/fonts.ftl"/>
 
     <!-- Links below are only used for search-l10n-ids into subdialogs -->
     <html:link rel="localization" href="browser/preferences/blocklists.ftl"/>
     <html:link rel="localization" href="browser/preferences/clearSiteData.ftl"/>
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -493,16 +493,63 @@ use-current-pages =
            *[other] Use Current Pages
         }
     .accesskey = C
 
 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
 search-bar-hidden =
     .label = Use the address bar for search and navigation
 search-bar-shown =
     .label = Add search bar in toolbar
 
new file mode 100644
--- /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")
+                                }
+                            )
+                        )
+                    )
+                ]
+            )
+        ]
+    )