Bug 894949 - Defect - Plural form strings are excessively complex for both devs and translators. f=flod, r=mbrubeck
authorJonathan Wilde <jwilde@jwilde.me>
Thu, 18 Jul 2013 18:28:24 -0700
changeset 152217 2acba061b8492e4db1f07a74fdb075728a7e23f0
parent 152216 fb1a587df374450b5167efd1aad1e28384f975fb
child 152218 973361ec4fb57b05f5f5b1859f44dbdf37c26660
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs894949
milestone25.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 894949 - Defect - Plural form strings are excessively complex for both devs and translators. f=flod, r=mbrubeck
browser/metro/base/content/TopSites.js
browser/metro/base/content/appbar.js
browser/metro/base/content/bindings/grid.xml
browser/metro/base/content/bookmarks.js
browser/metro/base/content/browser.xul
browser/metro/base/content/history.js
browser/metro/locales/en-US/chrome/browser.dtd
browser/metro/locales/en-US/chrome/browser.properties
--- a/browser/metro/base/content/TopSites.js
+++ b/browser/metro/base/content/TopSites.js
@@ -262,18 +262,16 @@ TopSitesView.prototype = Util.extend(Obj
       // default: no action
     }
     if (nextContextActions.size) {
       // at next tick, re-populate the context appbar
       setTimeout(function(){
         // fire a MozContextActionsChange event to update the context appbar
         let event = document.createEvent("Events");
         event.actions = [...nextContextActions];
-        event.noun = tileGroup.contextNoun;
-        event.qty = selectedTiles.length;
         event.initEvent("MozContextActionsChange", true, false);
         tileGroup.dispatchEvent(event);
       },0);
     }
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type){
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -40,20 +40,19 @@ var Appbar = {
           this.activeTileset.clearSelection();
         }
         this.clearContextualActions();
         this.activeTileset = null;
         break;
 
       case 'MozContextActionsChange':
         let actions = aEvent.actions;
-        let noun = aEvent.noun;
-        let qty = aEvent.qty;
+        let setName = aEvent.target.contextSetName;
         // could transition in old, new buttons?
-        this.showContextualActions(actions, noun, qty);
+        this.showContextualActions(actions, setName);
         break;
 
       case "selectionchange":
         let nodeName = aEvent.target.nodeName;
         if ('richgrid' === nodeName) {
           this._onTileSelectionChanged(aEvent);
         }
         break;
@@ -156,24 +155,25 @@ var Appbar = {
       activeTileset.dispatchEvent(event);
       if (!event.defaultPrevented) {
         activeTileset.clearSelection();
         Elements.contextappbar.dismiss();
       }
     }
   },
 
-  showContextualActions: function(aVerbs, aNoun, aQty) {
+  showContextualActions: function(aVerbs, aSetName) {
     // When the appbar is not visible, we want the icons to refresh right away
     let immediate = !Elements.contextappbar.isShowing;
 
-    if (aVerbs.length)
+    if (aVerbs.length) {
       Elements.contextappbar.show();
-    else
+    } else {
       Elements.contextappbar.hide();
+    }
 
     // Look up all of the buttons for the verbs that should be visible.
     let idsToVisibleVerbs = new Map();
     for (let verb of aVerbs) {
       let id = verb + "-selected-button";
       if (!document.getElementById(id)) {
         throw new Error("Appbar.showContextualActions: no button for " + verb);
       }
@@ -182,17 +182,17 @@ var Appbar = {
 
     // Sort buttons into 2 buckets - needing showing and needing hiding.
     let toHide = [], toShow = [];
     let buttons = Elements.contextappbar.getElementsByTagName("toolbarbutton");
     for (let button of buttons) {
       let verb = idsToVisibleVerbs.get(button.id);
       if (verb != undefined) {
         // Button should be visible, and may or may not be showing.
-        this._updateContextualActionLabel(button, verb, aNoun, aQty);
+        this._updateContextualActionLabel(button, verb, aSetName);
         if (button.hidden) {
           toShow.push(button);
         }
       } else if (!button.hidden) {
         // Button is visible, but shouldn't be.
         toHide.push(button);
       }
     }
@@ -218,28 +218,22 @@ var Appbar = {
       }
     });
   },
 
   clearContextualActions: function() {
     this.showContextualActions([]);
   },
 
-  _updateContextualActionLabel: function(aBtnNode, aVerb, aNoun, aQty) {
-    // True if action modifies the noun for the grid (bookmark, top site, etc.),
-    // causing the label to be pluralized by the number of selected items.
-    let modifiesNoun = aBtnNode.getAttribute("modifies-noun") == "true";
-    if (modifiesNoun && (!aNoun || isNaN(aQty))) {
-      throw new Error("Appbar._updateContextualActionLabel: " +
-                      "missing noun/quantity for " + aVerb);
-    }
-
-    let labelName = "contextAppbar." + aVerb + (modifiesNoun ? "." + aNoun : "");
-    let label = Strings.browser.GetStringFromName(labelName);
-    aBtnNode.label = modifiesNoun ? PluralForm.get(aQty, label) : label;
+  _updateContextualActionLabel: function(aButton, aVerb, aSetName) {
+    // True if the action's label string contains the set name and
+    // thus has to be selected based on the list passed in.
+    let usesSetName = aButton.hasAttribute("label-uses-set-name");
+    let name = "contextAppbar2." + aVerb + (usesSetName ? "." + aSetName : "");
+    aButton.label = Strings.browser.GetStringFromName(name);
   },
 
   _onTileSelectionChanged: function _onTileSelectionChanged(aEvent){
     let activeTileset = aEvent.target;
 
     // deselect tiles in other tile groups
     if (this.activeTileset && this.activeTileset !== activeTileset) {
       this.activeTileset.clearSelection();
@@ -250,20 +244,18 @@ var Appbar = {
     // ask the view for the list verbs/action-names it thinks are
     // appropriate for the tiles selected
     let contextActions = activeTileset.contextActions;
     let verbs = [v for (v of contextActions)];
 
     // fire event with these verbs as payload
     let event = document.createEvent("Events");
     event.actions = verbs;
-    event.noun = activeTileset.contextNoun;
-    event.qty = activeTileset.selectedItems.length;
     event.initEvent("MozContextActionsChange", true, false);
-    Elements.contextappbar.dispatchEvent(event);
+    activeTileset.dispatchEvent(event);
 
     if (verbs.length) {
       Elements.contextappbar.show(); // should be no-op if we're already showing
     } else {
       Elements.contextappbar.dismiss();
     }
   },
 
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -116,18 +116,18 @@
               return;
             // we'll republish this as a selectionchange event on the grid
             aEvent.stopPropagation();
             this.toggleItemSelection(aItem);
           ]]>
         </body>
       </method>
 
-      <property name="contextNoun" readonly="true"
-                onget="return this.getAttribute('noun');"/>
+      <property name="contextSetName" readonly="true"
+                onget="return this.getAttribute('set-name');"/>
 
       <property name="contextActions">
         <getter>
           <![CDATA[
             // return the subset of verbs that apply to all selected tiles
             let tileNodes = this.selectedItems;
             if (!tileNodes.length) {
               return new Set();
--- a/browser/metro/base/content/bookmarks.js
+++ b/browser/metro/base/content/bookmarks.js
@@ -289,18 +289,16 @@ BookmarksView.prototype = Util.extend(Ob
         aEvent.preventDefault();
 
         // at next tick, re-populate the context appbar.
         setTimeout(function(){
           // fire a MozContextActionsChange event to update the context appbar
           let event = document.createEvent("Events");
           // we need the restore button to show (the tile node will go away though)
           event.actions = ["restore"];
-          event.noun = tileGroup.contextNoun;
-          event.qty = selectedTiles.length;
           event.initEvent("MozContextActionsChange", true, false);
           tileGroup.dispatchEvent(event);
         }, 0);
         break;
 
       case "restore":
         // clear toRemove and let _sendNeedsRefresh update the items.
         this._toRemove = null;
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -191,41 +191,41 @@
         </hbox>
 
         <!-- Start UI -->
         <hbox id="start-container" flex="1" observes="bcast_windowState" class="meta content-height content-width">
           <!-- portrait/landscape/filled view -->
 
           <scrollbox id="start-scrollbox" observes="bcast_preciseInput" flex="1">
             <vbox id="start-topsites" class="meta-section">
-              <label class="meta-section-title wide-title" value="&startTopSitesHeader.label;"/>
+              <label class="meta-section-title wide-title" value="&topSitesHeader.label;"/>
               <label class="meta-section-title narrow-title" value="&snappedTopSitesHeader.label;"
                 onclick="StartUI.onNarrowTitleClick('start-topsites-grid')"/>
-              <richgrid id="start-topsites-grid" noun="topsite" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" flex="1" expanded="true"/>
+              <richgrid id="start-topsites-grid" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" flex="1" expanded="true"/>
             </vbox>
 
             <vbox id="start-bookmarks" class="meta-section">
-              <label class="meta-section-title wide-title" value="&startBookmarksHeader.label;"/>
+              <label class="meta-section-title wide-title" value="&bookmarksHeader.label;"/>
               <label class="meta-section-title narrow-title" value="&snappedBookmarksHeader.label;"
                 onclick="StartUI.onNarrowTitleClick('start-bookmarks-grid')"/>
-              <richgrid id="start-bookmarks-grid" noun="bookmark" seltype="multiple" flex="1"/>
+              <richgrid id="start-bookmarks-grid" set-name="bookmarks" seltype="multiple" flex="1"/>
             </vbox>
 
             <vbox id="start-history" class="meta-section">
-              <label class="meta-section-title wide-title" value="&startHistoryHeader.label;"/>
-              <label class="meta-section-title narrow-title" value="&snappedHistoryHeader.label;"
+              <label class="meta-section-title wide-title" value="&recentHistoryHeader.label;"/>
+              <label class="meta-section-title narrow-title" value="&snappedRecentHistoryHeader.label;"
                 onclick="StartUI.onNarrowTitleClick('start-history-grid')"/>
-              <richgrid id="start-history-grid" noun="history" seltype="multiple" flex="1"/>
+              <richgrid id="start-history-grid" set-name="recentHistory" seltype="multiple" flex="1"/>
             </vbox>
 
             <vbox id="start-remotetabs" class="meta-section">
-              <label class="meta-section-title wide-title" value="&startRemoteTabsHeader.label;"/>
+              <label class="meta-section-title wide-title" value="&remoteTabsHeader.label;"/>
               <label id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" value="&snappedRemoteTabsHeader.label;"
                 onclick="StartUI.onNarrowTitleClick('start-remotetabs-grid')"/>
-              <richgrid id="start-remotetabs-grid" noun="tab" seltype="multiple" flex="1"/>
+              <richgrid id="start-remotetabs-grid" set-name="remoteTabs" seltype="multiple" flex="1"/>
             </vbox>
 
             <!-- Spacer to take extra space in snapped mode. -->
             <spacer flex="999"/>
           </scrollbox>
 
         </hbox>
       </vbox> <!-- end tray -->
@@ -306,26 +306,26 @@
           hidden="true" observes="bcast_windowState">
       <hbox id="panel-header">
         <toolbarbutton id="panel-close-button" class="appbar-primary"
                        command="cmd_panel"/>
       </hbox>
 
       <deck id="panel-items" selectedIndex="0" flex="1" >
         <scrollbox id="bookmarks-container" flex="1">
-          <richgrid id="bookmarks-list" noun="bookmark" class="canSnapTiles"
-                    seltype="multiple" flex="1"/>
+          <richgrid id="bookmarks-list" class="canSnapTiles"
+                    set-name="bookmarks" seltype="multiple" flex="1"/>
         </scrollbox>
         <scrollbox id="history-container" flex="1">
-          <richgrid id="history-list" noun="history" class="canSnapTiles"
-                    seltype="multiple" flex="1"/>
+          <richgrid id="history-list" class="canSnapTiles"
+                    set-name="recentHistory" seltype="multiple" flex="1"/>
         </scrollbox>
         <scrollbox id="remotetabs-container" flex="1">
-          <richgrid id="remotetabs-list" noun="tab" class="canSnapTiles"
-                    seltype="single" flex="1"/>
+          <richgrid id="remotetabs-list" class="canSnapTiles"
+                    set-name="remoteTabs" seltype="single" flex="1"/>
         </scrollbox>
         <vbox id="console-container" flex="1">
           <vbox id="console-header" class="panel-list">
             <label class="panel-header" value="&consoleHeader.label;"/>
             <hbox align="center">
               <label value="&consoleCodeEval.label;"
                      control="console-eval-textbox"/>
               <textbox id="console-eval-textbox" class="toolbar search-bar"
@@ -369,28 +369,28 @@
       <button class="next-button" command="cmd_findNext"/>
       <spacer flex="1"/>
       <button id="findbar-close" class="close-button" command="cmd_findClose"/>
     </appbar>
 
     <!-- Context button bar -->
     <appbar id="contextappbar">
       <toolbar id="contextualactions-tray" labelled="true" flex="1">
+        <toolbarbutton id="pin-selected-button" class="appbar-secondary"
+                       label-uses-set-name="true" hidden="true" fade="true"
+                       oncommand="Appbar.dispatchContextualAction('pin')"/>
+        <toolbarbutton id="unpin-selected-button" class="appbar-secondary"
+                       label-uses-set-name="true" hidden="true" fade="true"
+                       oncommand="Appbar.dispatchContextualAction('unpin')"/>
         <toolbarbutton id="delete-selected-button" class="appbar-secondary"
-                       modifies-noun="true" hidden="true" fade="true"
+                       hidden="true" fade="true"
                        oncommand="Appbar.dispatchContextualAction('delete')"/>
         <toolbarbutton id="restore-selected-button" class="appbar-secondary"
-                       modifies-noun="true" hidden="true" fade="true"
+                       hidden="true" fade="true"
                        oncommand="Appbar.dispatchContextualAction('restore')"/>
-        <toolbarbutton id="pin-selected-button" class="appbar-secondary"
-                       modifies-noun="true" hidden="true" fade="true"
-                       oncommand="Appbar.dispatchContextualAction('pin')"/>
-        <toolbarbutton id="unpin-selected-button" class="appbar-secondary"
-                       modifies-noun="true" hidden="true" fade="true"
-                       oncommand="Appbar.dispatchContextualAction('unpin')"/>
         <toolbarbutton id="clear-selected-button" class="appbar-secondary"
                        hidden="true" fade="true"
                        oncommand="Appbar.dispatchContextualAction('clear')"/>
       </toolbar>
     </appbar>
 
     <autoscroller class="autoscroller" id="autoscrollerid"/>
 
--- a/browser/metro/base/content/history.js
+++ b/browser/metro/base/content/history.js
@@ -143,18 +143,16 @@ HistoryView.prototype = Util.extend(Obje
         aEvent.preventDefault();
 
         // at next tick, re-populate the context appbar.
         setTimeout(function(){
           // fire a MozContextActionsChange event to update the context appbar
           let event = document.createEvent("Events");
           // we need the restore button to show (the tile node will go away though)
           event.actions = ["restore"];
-          event.noun = tileGroup.contextNoun;
-          event.qty = selectedTiles.length;
           event.initEvent("MozContextActionsChange", true, false);
           tileGroup.dispatchEvent(event);
         }, 0);
         break;
 
       case "restore":
         // clear toRemove and let _sendNeedsRefresh update the items.
         this._toRemove = null;
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -1,58 +1,59 @@
 <!-- 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/. -->
 
+<!-- NAVBAR AND AUTOCOMPLETE -->
+
 <!ENTITY urlbar.emptytext      "Enter Search or Address">
 <!ENTITY urlbar.accesskey      "d">
 
 <!ENTITY back.label            "Back">
 <!ENTITY forward.label         "Forward">
 <!ENTITY newtab.label          "New Tab">
 <!ENTITY closetab.label        "Close Tab">
 
+<!ENTITY autocompleteResultsHeader.label  "Your Results">
+<!ENTITY autocompleteSearchesHeader.label "Internet Searches">
+
 <!ENTITY appbarErrorConsole.label   "Open error console">
 <!ENTITY appbarJSShell.label        "Open JavaScript shell">
 <!ENTITY appbarFindInPage2.label    "Find in page">
 <!ENTITY appbarViewOnDesktop2.label "View on desktop">
 
-<!ENTITY startTopSitesHeader.label        "Top Sites">
-<!ENTITY startBookmarksHeader.label       "Bookmarks">
-<!ENTITY startHistoryHeader.label         "Recent History">
-<!ENTITY startRemoteTabsHeader.label      "Tabs from Other Devices">
+<!ENTITY topSitesHeader.label        "Top Sites">
+<!ENTITY bookmarksHeader.label       "Bookmarks">
+<!ENTITY recentHistoryHeader.label   "Recent History">
+<!ENTITY remoteTabsHeader.label      "Tabs from Other Devices">
 
 <!-- LOCALIZATION NOTE (snappedRemoteTabsHeader.label): shortened version of startRemoteTabsHeader.label.
      Needs to be two words or shorter to fit in narrow vertical space.-->
 <!-- LOCALIZATION NOTE (snappedRemoteTabsHeader.label,
                         snappedBookmarksHeader.label,
                         snappedHistoryHeader.label,
                         snappedTopSitesHeader.label )
       The '>' character is not part of the name, but is an indicator of more content. Please do not localize the '>' -->
 <!ENTITY snappedRemoteTabsHeader.label    "Remote Tabs >">
 <!ENTITY snappedBookmarksHeader.label     "Bookmarks >">
-<!ENTITY snappedHistoryHeader.label       "Recent History >">
+<!ENTITY snappedRecentHistoryHeader.label "Recent History >">
 <!ENTITY snappedTopSitesHeader.label      "Top Sites >">
 
-<!ENTITY autocompleteResultsHeader.label  "Your Results">
-<!ENTITY autocompleteSearchesHeader.label "Internet Searches"> 
-
 <!ENTITY downloadsHeader.label     "Downloads">
 <!ENTITY downloadShowPage.label    "Go to Page">
 <!ENTITY downloadShow2.label       "Find">
 <!ENTITY downloadOpen2.label       "Open">
 <!ENTITY downloadCancel.label      "Cancel">
 <!ENTITY downloadPause.label       "Pause">
 <!ENTITY downloadResume.label      "Resume">
 <!ENTITY downloadRetry.label       "Retry">
 <!ENTITY downloadRemove.label      "Remove">
 <!ENTITY downloadDelete.label      "Delete">
 <!ENTITY downloadFailed.label      "Failed">
 
-<!ENTITY bookmarksHeader.label     "Bookmarks">
 <!ENTITY allBookmarks.label        "See all bookmarks">
 
 <!ENTITY consoleHeader.label       "Error Console">
 <!ENTITY consoleAll.label          "All">
 <!ENTITY consoleErrors.label       "Errors">
 <!ENTITY consoleWarnings.label     "Warnings">
 <!ENTITY consoleMessages.label     "Messages">
 <!ENTITY consoleCodeEval.label     "Code:">
--- a/browser/metro/locales/en-US/chrome/browser.properties
+++ b/browser/metro/locales/en-US/chrome/browser.properties
@@ -1,50 +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/.
 
+# LOCALIZATION NOTE : FILE Capitalized phrases like "Top Sites", "Bookmarks",
+# and "Recent History" are typically used as proper noun phrases to refer to
+# the specific visual set (usually displayed as a grid) of top sites,
+# bookmarks, etc. displayed on screen, rather than those concepts in general.
+# Buttons (like with contextAppbar2.pin.topSites) refer to actions against
+# the specific on-screen sets with similarly-named headings.
+
 # Default search engine
 browser.search.defaultenginename=Bing
 
 # Search engine order (order displayed in the search bar dropdown)s
 browser.search.order.1=Bing
 browser.search.order.2=Google
 browser.search.order.3=Yahoo
 
 # LOCALIZATION NOTE (browser.search.contextTextSearchLabel2): search context
 # menu item text will be: |Search (browser.search.defaultenginename) for "string"|
 browser.search.contextTextSearchLabel2=Search %S for "%S"
 
-# Contextual Appbar - button labels
-# LOCALIZATION NOTE: All of the following represent actions that can be done
-# to items (top sites, bookmarks, history items) that are selected by the user
-# on the start screen. Each string is a semicolon list of plural forms.
-contextAppbar.delete.topsite=Delete top site;Delete top sites
-contextAppbar.restore.topsite=Restore top site;Restore top sites
-contextAppbar.pin.topsite=Pin top site;Pin top sites
-contextAppbar.unpin.topsite=Unpin top site;Unpin top sites
-contextAppbar.delete.bookmark=Delete bookmark;Delete bookmarks
-contextAppbar.restore.bookmark=Restore bookmark;Restore bookmarks
-contextAppbar.pin.bookmark=Pin bookmark;Pin bookmarks
-contextAppbar.unpin.bookmark=Unpin bookmark;Unpin bookmarks
-contextAppbar.delete.history=Delete page;Delete pages
-contextAppbar.restore.history=Restore page;Restore pages
-contextAppbar.pin.history=Pin page;Pin pages
-contextAppbar.unpin.history=Unpin page;Unpin pages
-contextAppbar.delete.tab=Delete tab;Delete tabs
-contextAppbar.restore.tab=Restore tab;Restore tabs
-contextAppbar.pin.tab=Pin tab;Pin tabs
-contextAppbar.unpin.tab=Unpin page;Unpin tabs
-contextAppbar.delete.download=Delete download;Delete downloads
-contextAppbar.restore.download=Restore download;Restore downloads
-contextAppbar.pin.download=Pin download;Pin downloads
-contextAppbar.unpin.download=Unpin page;Unpin downloads
-# LOCALIZATION NOTE (contextAppbar.clear): Unselects items without modification.
-contextAppbar.clear=Clear selection
+# Contextual Appbar - Button Labels
+
+contextAppbar2.pin.topSites=Pin to Top Sites
+contextAppbar2.pin.bookmarks=Pin to Bookmarks
+contextAppbar2.pin.recentHistory=Pin to Recent History
+contextAppbar2.pin.downloads=Pin to Downloads
+
+contextAppbar2.unpin.topSites=Unpin from Top Sites
+contextAppbar2.unpin.bookmarks=Unpin from Bookmarks
+contextAppbar2.unpin.recentHistory=Unpin from Recent History
+contextAppbar2.unpin.downloads=Unpin from Downloads
+
+# LOCALIZATION NOTE (contextAppbar2.delete): Deletes selected pages.
+contextAppbar2.delete=Delete
+
+# LOCALIZATION NOTE (contextAppbar2.restore): Undoes a previous deletion.
+# Button with this label only appears immediately after a deletion.
+contextAppbar2.restore=Undo delete
+
+# LOCALIZATION NOTE (contextAppbar2.clear): Unselects pages without modification.
+contextAppbar2.clear=Clear selection
 
 # Settings Charms
 aboutCharm1=About
 optionsCharm=Options
 syncCharm=Sync
 helpOnlineCharm=Help (online)
 
 # General