Repository: GoogleChrome/lighthouse
Pull request #5730 (2018-07-26 18:18:58)

report(i18n): localize CRC renderer strings
A lot of the text is actually just the path and origin of each request so there wasn't actually that much here.

brendankenny: Slight change to make translation easier.

before:
![screen shot 2018-07-30 at 15 42 58](https://user-images.githubusercontent.com/316891/43427640-4da76d42-940f-11e8-9749-9bed51b34f2e.png)

after:
![screen shot 2018-07-30 at 15 46 15](https://user-images.githubusercontent.com/316891/43427711-b8bd5fb0-940f-11e8-8090-b9d4643c513d.png)


Patch:
diff --git a/lighthouse-core/audits/accessibility/manual/interactive-element-affordance.js b/lighthouse-core/audits/accessibility/manual/interactive-element-affordance.js
new file mode 100644
index 000000000..309a1fd64
--- /dev/null
+++ b/lighthouse-core/audits/accessibility/manual/interactive-element-affordance.js
@@ -0,0 +1,28 @@
+
+/**
+ * @license Copyright 2018 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ */
+'use strict';
+
+const ManualAudit = require('../../manual/manual-audit');
+
+/**
+ * @fileoverview Manual A11y audit for interactive element affordance.
+ */
+
+class InteractiveElementAffordance extends ManualAudit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return Object.assign({
+      id: 'interactive-element-affordance',
+      description: 'Interactive elements, such as links and buttons, should indicate their state and be distinguishable from non-interactive elements. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#interactive_elements_like_links_and_buttons_should_indicate_their_purpose_and_state).',
+      title: 'Interactive elements indicate their purpose and state',
+    }, super.partialMeta);
+  }
+}
+
+module.exports = InteractiveElementAffordance;
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index 5521c28f5..f582044cf 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -171,6 +171,7 @@ module.exports = {
     'accessibility/manual/focus-traps',
     'accessibility/manual/focusable-controls',
     'accessibility/manual/heading-levels',
+    'accessibility/manual/interactive-element-affordance',
     'accessibility/manual/logical-tab-order',
     'accessibility/manual/managed-focus',
     'accessibility/manual/offscreen-content-hidden',
@@ -382,6 +383,7 @@ module.exports = {
         // Manual audits
         {id: 'logical-tab-order', weight: 0},
         {id: 'focusable-controls', weight: 0},
+        {id: 'interactive-element-affordance', weight: 0},
         {id: 'managed-focus', weight: 0},
         {id: 'focus-traps', weight: 0},
         {id: 'custom-controls-labels', weight: 0},
diff --git a/lighthouse-core/lib/locales/en-US.json b/lighthouse-core/lib/locales/en-US.json
index 5b20f79a5..cc37da30c 100644
--- a/lighthouse-core/lib/locales/en-US.json
+++ b/lighthouse-core/lib/locales/en-US.json
@@ -438,5 +438,13 @@
   "lighthouse-core/report/html/renderer/util.js | scorescaleLabel": {
     "message": "Score scale:",
     "description": "Label preceding a pictorial explanation of the scoring scale: 0-50 is red (bad), 50-90 is orange (ok), 90-100 is green (good). These colors are used throughout the report to provide context for how good/bad a particular result is."
+  },
+  "lighthouse-core/report/html/renderer/util.js | crcInitialNavigation": {
+    "message": "Initial Navigation",
+    "description": "String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com)."
+  },
+  "lighthouse-core/report/html/renderer/util.js | crcLongestDurationLabel": {
+    "message": "Maximum critical path latency:",
+    "description": "Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms"
   }
 }
diff --git a/lighthouse-core/lib/locales/en-XA.json b/lighthouse-core/lib/locales/en-XA.json
index 8777020f0..ecb4f1ed7 100644
--- a/lighthouse-core/lib/locales/en-XA.json
+++ b/lighthouse-core/lib/locales/en-XA.json
@@ -307,11 +307,5 @@
   },
   "lighthouse-core/report/html/renderer/util.js | toplevelWarningsMessage": {
     "message": "[Ţĥéŕé ŵéŕé îššûéš åƒƒéçţîñĝ ţĥîš ŕûñ öƒ Ļîĝĥţĥöûšé: one two three four five six seven eight nine ten eleven]"
-  },
-  "lighthouse-core/report/html/renderer/util.js | varianceDisclaimer": {
-    "message": "[Våļûéš åŕé éšţîmåţéð åñð måý våŕý. one two three four five six seven]"
-  },
-  "lighthouse-core/report/html/renderer/util.js | warningHeader": {
-    "message": "[Ŵåŕñîñĝš:  one two]"
   }
 }
diff --git a/lighthouse-core/report/html/renderer/crc-details-renderer.js b/lighthouse-core/report/html/renderer/crc-details-renderer.js
index a9c6fbb74..0ab374132 100644
--- a/lighthouse-core/report/html/renderer/crc-details-renderer.js
+++ b/lighthouse-core/report/html/renderer/crc-details-renderer.js
@@ -126,11 +126,11 @@ class CriticalRequestChainRenderer {
     dom.find('.crc-node__tree-hostname', treevalEl).textContent = hostname ? `(${hostname})` : '';
 
     if (!segment.hasChildren) {
+      const {startTime, endTime, transferSize} = segment.node.request;
       const span = dom.createElement('span', 'crc-node__chain-duration');
-      span.textContent = ' - ' + Util.chainDuration(
-          segment.node.request.startTime, segment.node.request.endTime) + 'ms, ';
+      span.textContent = ' - ' + Util.formatMilliseconds((endTime - startTime) * 1000) + ', ';
       const span2 = dom.createElement('span', 'crc-node__chain-duration');
-      span2.textContent = Util.formatBytesToKB(segment.node.request.transferSize, 0.01);
+      span2.textContent = Util.formatBytesToKB(transferSize, 0.01);
 
       treevalEl.appendChild(span);
       treevalEl.appendChild(span2);
@@ -168,11 +168,11 @@ class CriticalRequestChainRenderer {
     const containerEl = dom.find('.lh-crc', tmpl);
 
     // Fill in top summary.
+    dom.find('.crc-initial-nav', tmpl).textContent = Util.UIStrings.crcInitialNavigation;
+    dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
+        Util.UIStrings.crcLongestDurationLabel;
     dom.find('.lh-crc__longest_duration', tmpl).textContent =
-        Util.formatNumber(details.longestChain.duration) + 'ms';
-    dom.find('.lh-crc__longest_length', tmpl).textContent = details.longestChain.length.toString();
-    dom.find('.lh-crc__longest_transfersize', tmpl).textContent =
-        Util.formatBytesToKB(details.longestChain.transferSize);
+        Util.formatMilliseconds(details.longestChain.duration);
 
     // Construct visual tree.
     const root = CriticalRequestChainRenderer.initTree(details.chains);
diff --git a/lighthouse-core/report/html/renderer/util.js b/lighthouse-core/report/html/renderer/util.js
index 16f450d1a..e34c24ebc 100644
--- a/lighthouse-core/report/html/renderer/util.js
+++ b/lighthouse-core/report/html/renderer/util.js
@@ -308,15 +308,6 @@ class Util {
     };
   }
 
-  /**
-   * @param {number} startTime
-   * @param {number} endTime
-   * @return {string}
-   */
-  static chainDuration(startTime, endTime) {
-    return Util.formatNumber((endTime - startTime) * 1000);
-  }
-
   /**
    * @param {LH.Config.Settings} settings
    * @return {Array<{name: string, description: string}>}
@@ -447,6 +438,11 @@ Util.UIStrings = {
   toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:',
   /** Label preceding a pictorial explanation of the scoring scale: 0-50 is red (bad), 50-90 is orange (ok), 90-100 is green (good). These colors are used throughout the report to provide context for how good/bad a particular result is. */
   scorescaleLabel: 'Score scale:',
+
+  /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */
+  crcInitialNavigation: 'Initial Navigation',
+  /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */
+  crcLongestDurationLabel: 'Maximum critical path latency:',
 };
 
 if (typeof module !== 'undefined' && module.exports) {
diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html
index 121bd2081..ac7817d3b 100644
--- a/lighthouse-core/report/html/templates.html
+++ b/lighthouse-core/report/html/templates.html
@@ -618,15 +618,17 @@ limitations under the License.
         color: #595959;
         font-style: italic;
       }
+      .lh-crc__summary-value {
+        margin-bottom: 10px;
+      }
     </style>
     <div>
-      <!-- TODO(i18n): remove CRC sentences or localize -->
-      Longest chain: <b class="lh-crc__longest_duration"><!-- fill me: longestChain.duration --></b>
-      over <b class="lh-crc__longest_length"><!-- fill me: longestChain.length --></b> requests, totalling
-      <b class="lh-crc__longest_transfersize"><!-- fill me: longestChain.length --></b>
+      <div class="lh-crc__summary-value">
+        <span class="lh-crc__longest_duration_label"></span> <b class="lh-crc__longest_duration"></b>
+      </div>
     </div>
     <div class="lh-crc">
-      <div class="crc-initial-nav">Initial Navigation</div>
+      <div class="crc-initial-nav"></div>
       <!-- stamp for each chain -->
       <template id="tmpl-lh-crc__chains">
         <div class="crc-node">
diff --git a/lighthouse-extension/app/src/lighthouse-background.js b/lighthouse-extension/app/src/lighthouse-background.js
index 740e52b6e..e6de6f144 100644
--- a/lighthouse-extension/app/src/lighthouse-background.js
+++ b/lighthouse-extension/app/src/lighthouse-background.js
@@ -9,6 +9,7 @@ const RawProtocol = require('../../../lighthouse-core/gather/connections/raw');
 const Runner = require('../../../lighthouse-core/runner');
 const Config = require('../../../lighthouse-core/config/config');
 const defaultConfig = require('../../../lighthouse-core/config/default-config.js');
+const i18n = require('../../../lighthouse-core/lib/i18n');
 const log = require('lighthouse-logger');
 
 /** @typedef {import('../../../lighthouse-core/gather/connections/connection.js')} Connection */
@@ -65,7 +66,9 @@ function runLighthouseInWorker(port, url, options, categoryIDs) {
  * @return {Array<{title: string, id: string}>}
  */
 function getDefaultCategories() {
-  return Config.getCategories(defaultConfig);
+  const categories = Config.getCategories(defaultConfig);
+  categories.forEach(cat => cat.title = i18n.getFormatted(cat.title, 'en-US'));
+  return categories;
 }
 
 /** @param {(status: [string, string, string]) => void} listenCallback */


Test patch:
diff --git a/lighthouse-core/test/report/html/renderer/crc-details-renderer-test.js b/lighthouse-core/test/report/html/renderer/crc-details-renderer-test.js
index d07539b6b..18b883103 100644
--- a/lighthouse-core/test/report/html/renderer/crc-details-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/crc-details-renderer-test.js
@@ -103,7 +103,7 @@ describe('DetailsRenderer', () => {
     assert.equal(chains[1].querySelector('.crc-node__tree-file').textContent, '/b.js');
     assert.equal(chains[1].querySelector('.crc-node__tree-hostname').textContent, '(example.com)');
     const durationNodes = chains[1].querySelectorAll('.crc-node__chain-duration');
-    assert.equal(durationNodes[0].textContent, ' - 5,000ms, ');
+    assert.equal(durationNodes[0].textContent, ' - 5,000\xa0ms, ');
     // Note: actual transferSize is 2000 bytes but formatter formats to KBs.
     assert.equal(durationNodes[1].textContent, '1.95\xa0KB');
   });
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 7d695dc79..8ce08f9e1 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -1624,6 +1624,14 @@
       "scoreDisplayMode": "manual",
       "rawValue": false
     },
+    "interactive-element-affordance": {
+      "id": "interactive-element-affordance",
+      "title": "Interactive elements indicate their purpose and state",
+      "description": "Interactive elements, such as links and buttons, should indicate their state and be distinguishable from non-interactive elements. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#interactive_elements_like_links_and_buttons_should_indicate_their_purpose_and_state).",
+      "score": null,
+      "scoreDisplayMode": "manual",
+      "rawValue": false
+    },
     "logical-tab-order": {
       "id": "logical-tab-order",
       "title": "The page has a logical tab order",
@@ -3157,6 +3165,10 @@
           "id": "focusable-controls",
           "weight": 0
         },
+        {
+          "id": "interactive-element-affordance",
+          "weight": 0
+        },
         {
           "id": "managed-focus",
           "weight": 0
@@ -3403,7 +3415,9 @@
       "notApplicableAuditsGroupTitle": "Not applicable",
       "manualAuditsGroupTitle": "Additional items to manually check",
       "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:",
-      "scorescaleLabel": "Score scale:"
+      "scorescaleLabel": "Score scale:",
+      "crcInitialNavigation": "Initial Navigation",
+      "crcLongestDurationLabel": "Maximum critical path latency:"
     },
     "icuMessagePaths": {
       "lighthouse-core/audits/metrics/first-contentful-paint.js | title": [
diff --git a/lighthouse-extension/test/popup-test.js b/lighthouse-extension/test/popup-test.js
index 7806e6752..5196ed5ee 100644
--- a/lighthouse-extension/test/popup-test.js
+++ b/lighthouse-extension/test/popup-test.js
@@ -13,6 +13,21 @@ const puppeteer = require('../../node_modules/puppeteer/index.js');
 
 const lighthouseExtensionPath = path.resolve(__dirname, '../dist');
 
+const defaultCategoriesStub = [
+  {
+    id: 'performance',
+    title: 'Performance',
+  },
+  {
+    id: 'pwa',
+    title: 'Progressive Web App',
+  },
+  {
+    id: 'seo',
+    title: 'SEO',
+  },
+];
+
 describe('Lighthouse chrome popup', function() {
   // eslint-disable-next-line no-console
   console.log('\n✨ Be sure to have recently run this: yarn build-extension');
@@ -32,7 +47,7 @@ describe('Lighthouse chrome popup', function() {
     });
 
     page = await browser.newPage();
-    await page.evaluateOnNewDocument(() => {
+    await page.evaluateOnNewDocument((defaultCategoriesStub) => {
       const backgroundMock = {
         isRunning: () => false,
         listenForStatus: () => {},
@@ -40,7 +55,7 @@ describe('Lighthouse chrome popup', function() {
           selectedCategories: [],
           useDevTools: false,
         }),
-        getDefaultCategories: () => [],
+        getDefaultCategories: () => defaultCategoriesStub,
       };
 
       Object.defineProperty(chrome, 'tabs', {
@@ -59,7 +74,7 @@ describe('Lighthouse chrome popup', function() {
           },
         }),
       });
-    });
+    }, defaultCategoriesStub);
 
     page.on('pageerror', err => {
       pageErrors.push(err);
@@ -92,4 +107,15 @@ describe('Lighthouse chrome popup', function() {
     assert.equal(titleText, 'Lighthouse');
     assert.equal(urlText, 'http://example.com');
   });
+
+
+  it('should populate the category checkboxes correctly', async function() {
+    const checkboxTitles = await page.$$eval('li label', els => els.map(e => e.textContent));
+    const checkboxValues = await page.$$eval('li label input', els => els.map(e => e.value));
+
+    for (const {title, id} of defaultCategoriesStub) {
+      assert.ok(checkboxTitles.includes(title));
+      assert.ok(checkboxValues.includes(id));
+    }
+  });
 });