Repository: GoogleChrome/lighthouse
Pull request #9280 (2019-06-25 19:14:30)

misc(build): generate report variants in deployment
Currently our builder just gives us a single sample LHR report. But we'd like to see it in other languages and in a more devtools-like view.

e.g. https://lighthouse-git-deployvariants.googlechrome.now.sh/

![image](https://user-images.githubusercontent.com/39191/60126381-b57cc700-9742-11e9-840f-ea18eddc7f54.png)

I have another PR waiting that adds an LHR/report with lots of errors.


Patch:
diff --git a/.build-for-now.sh b/.build-for-now.sh
deleted file mode 100644
index 07e7accd0..000000000
--- a/.build-for-now.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-##
-# @license Copyright 2019 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.
-##
-
-set -x
-
-# Manually install this one report dependency. Avoid installing all deps (for speed)
-rm package.json
-yarn add details-element-polyfill@2.2.0
-
-# Create dist if it's not already there
-mkdir -p dist
-
-# Generate the report and place as dist/index.html
-node lighthouse-core/scripts/build-report-for-autodeployment.js
diff --git a/lighthouse-core/audits/deprecations.js b/lighthouse-core/audits/deprecations.js
index 238ee9ddb..f7eb7b14b 100644
--- a/lighthouse-core/audits/deprecations.js
+++ b/lighthouse-core/audits/deprecations.js
@@ -12,7 +12,28 @@
  */
 
 const Audit = require('./audit.js');
-const Util = require('../report/html/renderer/util.js');
+const i18n = require('../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the use of deprecated APIs. This descriptive title is shown to users when the page does not use deprecated APIs. */
+  title: 'Avoids deprecated APIs',
+  /** Title of a Lighthouse audit that provides detail on the use of deprecated APIs. This descriptive title is shown to users when the page uses deprecated APIs. */
+  failureTitle: 'Uses deprecated APIs',
+  /** Description of a Lighthouse audit that tells the user why they should not use deprecated APIs on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Deprecated APIs will eventually be removed from the browser. ' +
+      '[Learn more](https://www.chromestatus.com/features#deprecated).',
+  /** [ICU Syntax] Label for the audit identifying the number of warnings generated by using deprecated APIs. */
+  displayValue: `{itemCount, plural,
+    =1 {1 warning found}
+    other {# warnings found}
+    }`,
+  /** Header of the table column which displays the warning message describing use of a deprecated API by code running in the web page. */
+  columnDeprecate: 'Deprecation / Warning',
+  /** Table column header for line of code (eg. 432) that is using a deprecated API. */
+  columnLine: 'Line',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class Deprecations extends Audit {
   /**
@@ -21,10 +42,9 @@ class Deprecations extends Audit {
   static get meta() {
     return {
       id: 'deprecations',
-      title: 'Avoids deprecated APIs',
-      failureTitle: 'Uses deprecated APIs',
-      description: 'Deprecated APIs will eventually be removed from the browser. ' +
-          '[Learn more](https://www.chromestatus.com/features#deprecated).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages'],
     };
   }
@@ -47,17 +67,15 @@ class Deprecations extends Audit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'value', itemType: 'code', text: 'Deprecation / Warning'},
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'lineNumber', itemType: 'text', text: 'Line'},
+      {key: 'value', itemType: 'code', text: str_(UIStrings.columnDeprecate)},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'lineNumber', itemType: 'text', text: str_(UIStrings.columnLine)},
     ];
     const details = Audit.makeTableDetails(headings, deprecations);
 
     let displayValue = '';
-    if (deprecations.length > 1) {
-      displayValue = `${Util.formatNumber(deprecations.length)} warnings found`;
-    } else if (deprecations.length === 1) {
-      displayValue = `${deprecations.length} warning found`;
+    if (deprecations.length > 0) {
+      displayValue = str_(UIStrings.displayValue, {itemCount: deprecations.length});
     }
 
     return {
@@ -72,3 +90,4 @@ class Deprecations extends Audit {
 }
 
 module.exports = Deprecations;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/appcache-manifest.js b/lighthouse-core/audits/dobetterweb/appcache-manifest.js
index f475db34c..1f133a89a 100644
--- a/lighthouse-core/audits/dobetterweb/appcache-manifest.js
+++ b/lighthouse-core/audits/dobetterweb/appcache-manifest.js
@@ -11,6 +11,21 @@
 'use strict';
 
 const Audit = require('../audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the use of the Application Cache API. This descriptive title is shown to users when they do not use the Application Cache API. */
+  title: 'Avoids Application Cache',
+  /** Title of a Lighthouse audit that provides detail on the use of the Application Cache API. This descriptive title is shown to users when they do use the Application Cache API, which is considered bad practice. */
+  failureTitle: 'Uses Application Cache',
+  /** Description of a Lighthouse audit that tells the user why they should not use the Application Cache API. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Application Cache is deprecated. ' +
+    '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/appcache).',
+  /** Label for the audit identifying uses of the Application Cache. */
+  displayValue: 'Found "{AppCacheManifest}"',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class AppCacheManifestAttr extends Audit {
   /**
@@ -19,10 +34,9 @@ class AppCacheManifestAttr extends Audit {
   static get meta() {
     return {
       id: 'appcache-manifest',
-      title: 'Avoids Application Cache',
-      failureTitle: 'Uses Application Cache',
-      description: 'Application Cache is deprecated. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/appcache).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['AppCacheManifest'],
     };
   }
@@ -33,7 +47,8 @@ class AppCacheManifestAttr extends Audit {
    */
   static audit(artifacts) {
     const usingAppcache = artifacts.AppCacheManifest !== null;
-    const displayValue = usingAppcache ? `Found "${artifacts.AppCacheManifest}"` : '';
+    const displayValue = usingAppcache ?
+      str_(UIStrings.displayValue, {AppCacheManifest: artifacts.AppCacheManifest}) : '';
 
     return {
       score: usingAppcache ? 0 : 1,
@@ -43,3 +58,4 @@ class AppCacheManifestAttr extends Audit {
 }
 
 module.exports = AppCacheManifestAttr;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/doctype.js b/lighthouse-core/audits/dobetterweb/doctype.js
index 4ba11830d..48f38df9c 100644
--- a/lighthouse-core/audits/dobetterweb/doctype.js
+++ b/lighthouse-core/audits/dobetterweb/doctype.js
@@ -6,6 +6,28 @@
 'use strict';
 
 const Audit = require('../audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the doctype of a page. This descriptive title is shown to users when the pages's doctype is set to HTML. */
+  title: 'Page has the HTML doctype',
+  /** Title of a Lighthouse audit that provides detail on the doctype of a page. This descriptive title is shown to users when the page's doctype is not set to HTML. */
+  failureTitle: 'Page lacks the HTML doctype, thus triggering quirks-mode',
+  /** Description of a Lighthouse audit that tells the user why they should define an HTML doctype. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Specifying a doctype prevents the browser ' +
+    'from switching to quirks-mode. Read more on the ' +
+    '[MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)',
+  /** Explanatory message stating that the document has no doctype. */
+  explanationNoDoctype: 'Document must contain a doctype',
+  /** Explanatory message stating that the publicId field is not empty. */
+  explanationPublicId: 'Expected publicId to be an empty string',
+  /** Explanatory message stating that the systemId field is not empty. */
+  explanationSystemId: 'Expected systemId to be an empty string',
+  /** Explanatory message stating that the doctype is set, but is not "html" and is therefore invalid. */
+  explanationBadDoctype: 'Doctype name must be the lowercase string `html`',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class Doctype extends Audit {
   /**
@@ -14,11 +36,9 @@ class Doctype extends Audit {
   static get meta() {
     return {
       id: 'doctype',
-      title: 'Page has the HTML doctype',
-      failureTitle: 'Page is missing the HTML doctype',
-      description: 'Specifying a doctype prevents the browser from switching to quirks-mode.' +
-                'Read more on the ' +
-                '[MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['Doctype'],
     };
   }
@@ -31,7 +51,7 @@ class Doctype extends Audit {
     if (!artifacts.Doctype) {
       return {
         score: 0,
-        explanation: 'Document must contain a doctype',
+        explanation: str_(UIStrings.explanationNoDoctype),
       };
     }
 
@@ -43,14 +63,14 @@ class Doctype extends Audit {
     if (doctypePublicId !== '') {
       return {
         score: 0,
-        explanation: 'Expected publicId to be an empty string',
+        explanation: str_(UIStrings.explanationPublicId),
       };
     }
 
     if (doctypeSystemId !== '') {
       return {
         score: 0,
-        explanation: 'Expected systemId to be an empty string',
+        explanation: str_(UIStrings.explanationSystemId),
       };
     }
 
@@ -64,10 +84,11 @@ class Doctype extends Audit {
     } else {
       return {
         score: 0,
-        explanation: 'Doctype name must be the lowercase string `html`',
+        explanation: str_(UIStrings.explanationBadDoctype),
       };
     }
   }
 }
 
 module.exports = Doctype;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
index 7333085ce..f7ad647bb 100644
--- a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
+++ b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
@@ -7,6 +7,27 @@
 
 const URL = require('../../lib/url-shim.js');
 const Audit = require('../audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the cross-origin links that the web page contains, and whether the links can be considered safe. This descriptive title is shown to users when all links are safe. */
+  title: 'Links to cross-origin destinations are safe',
+  /** Title of a Lighthouse audit that provides detail on the cross-origin links that the web page contains, and whether the links can be considered safe. This descriptive title is shown to users when not all links can be considered safe. */
+  failureTitle: 'Links to cross-origin destinations are unsafe',
+  /** Description of a Lighthouse audit that tells the user why and how they should secure cross-origin links. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Add `rel="noopener"` or `rel="noreferrer"` to any external links to improve ' +
+    'performance and prevent security vulnerabilities. ' +
+    '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
+  /** Warning that some links' destinations cannot be determined and therefore the audit cannot evaluate the link's safety. */
+  warning: 'Unable to determine the destination for anchor ({anchorHTML}). ' +
+    'If not used as a hyperlink, consider removing target=_blank.',
+  /** Label for a column in a data table; entries will be the target attribute of a link. Each entry is either an empty string or a string like `_blank`.  */
+  columnTarget: 'Target',
+  /** Label for a column in a data table; entries will be the values of the html "rel" attribute from link in a page.  */
+  columnRel: 'Rel',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class ExternalAnchorsUseRelNoopenerAudit extends Audit {
   /**
@@ -15,11 +36,9 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
   static get meta() {
     return {
       id: 'external-anchors-use-rel-noopener',
-      title: 'Links to cross-origin destinations are safe',
-      failureTitle: 'Links to cross-origin destinations are unsafe',
-      description: 'Add `rel="noopener"` or `rel="noreferrer"` to any external links to improve ' +
-          'performance and prevent security vulnerabilities. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['URL', 'AnchorElements'],
     };
   }
@@ -40,8 +59,7 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
         try {
           return new URL(anchor.href).host !== pageHost;
         } catch (err) {
-          warnings.push(`Unable to determine the destination for anchor (${anchor.outerHTML}). ` +
-            'If not used as a hyperlink, consider removing target=_blank.');
+          warnings.push(str_(UIStrings.warning, {anchorHTML: anchor.outerHTML}));
           return true;
         }
       })
@@ -59,9 +77,9 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'href', itemType: 'url', text: 'URL'},
-      {key: 'target', itemType: 'text', text: 'Target'},
-      {key: 'rel', itemType: 'text', text: 'Rel'},
+      {key: 'href', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'target', itemType: 'text', text: str_(UIStrings.columnTarget)},
+      {key: 'rel', itemType: 'text', text: str_(UIStrings.columnRel)},
     ];
 
     const details = Audit.makeTableDetails(headings, failingAnchors);
@@ -78,3 +96,4 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
 }
 
 module.exports = ExternalAnchorsUseRelNoopenerAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/geolocation-on-start.js b/lighthouse-core/audits/dobetterweb/geolocation-on-start.js
index e773c3594..208040e26 100644
--- a/lighthouse-core/audits/dobetterweb/geolocation-on-start.js
+++ b/lighthouse-core/audits/dobetterweb/geolocation-on-start.js
@@ -12,6 +12,20 @@
 'use strict';
 
 const ViolationAudit = require('../violation-audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on geolocation permission requests while the page is loading. This descriptive title is shown to users when the page does not ask for geolocation permissions on load. */
+  title: 'Avoids requesting the geolocation permission on page load',
+  /** Title of a Lighthouse audit that provides detail on geolocation permissions requests. This descriptive title is shown to users when the page does ask for geolocation permissions on load. */
+  failureTitle: 'Requests the geolocation permission on page load',
+  /** Description of a Lighthouse audit that tells the user why they should not ask for geolocation permissions on load. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Users are mistrustful of or confused by sites that request their ' +
+    'location without context. Consider tying the request to a user action instead. ' +
+    '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class GeolocationOnStart extends ViolationAudit {
   /**
@@ -20,11 +34,9 @@ class GeolocationOnStart extends ViolationAudit {
   static get meta() {
     return {
       id: 'geolocation-on-start',
-      title: 'Avoids requesting the geolocation permission on page load',
-      failureTitle: 'Requests the geolocation permission on page load',
-      description: 'Users are mistrustful of or confused by sites that request their ' +
-          'location without context. Consider tying the request to user gestures instead. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages'],
     };
   }
@@ -39,8 +51,8 @@ class GeolocationOnStart extends ViolationAudit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'label', itemType: 'text', text: 'Location'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'label', itemType: 'text', text: str_(i18n.UIStrings.columnLocation)},
     ];
     // TODO(bckenny): there should actually be a ts error here. results[0].stackTrace
     // should violate the results type. Shouldn't be removed from details items regardless.
@@ -57,3 +69,4 @@ class GeolocationOnStart extends ViolationAudit {
 }
 
 module.exports = GeolocationOnStart;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/js-libraries.js b/lighthouse-core/audits/dobetterweb/js-libraries.js
index cc255b179..684feb719 100644
--- a/lighthouse-core/audits/dobetterweb/js-libraries.js
+++ b/lighthouse-core/audits/dobetterweb/js-libraries.js
@@ -11,6 +11,20 @@
 'use strict';
 
 const Audit = require('../audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the Javascript libraries that are used on the page. */
+  title: 'Detected JavaScript libraries',
+  /** Description of a Lighthouse audit that tells the user what this audit is detecting. This is displayed after a user expands the section to see more. No character length limits. */
+  description: 'All front-end JavaScript libraries detected on the page.',
+  /** Label for a column in a data table; entries will be the names of the detected Javascript libraries.  */
+  columnName: 'Name',
+  /** Label for a column in a data table; entries will be the version numbers of the detected Javascript libraries.  */
+  columnVersion: 'Version',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class JsLibrariesAudit extends Audit {
   /**
@@ -19,8 +33,8 @@ class JsLibrariesAudit extends Audit {
   static get meta() {
     return {
       id: 'js-libraries',
-      title: 'Detected JavaScript libraries',
-      description: 'All front-end JavaScript libraries detected on the page.',
+      title: str_(UIStrings.title),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['Stacks'],
     };
   }
@@ -40,8 +54,8 @@ class JsLibrariesAudit extends Audit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'name', itemType: 'text', text: 'Name'},
-      {key: 'version', itemType: 'text', text: 'Version'},
+      {key: 'name', itemType: 'text', text: str_(UIStrings.columnName)},
+      {key: 'version', itemType: 'text', text: str_(UIStrings.columnVersion)},
     ];
     const details = Audit.makeTableDetails(headings, libDetails, {});
 
@@ -53,3 +67,4 @@ class JsLibrariesAudit extends Audit {
 }
 
 module.exports = JsLibrariesAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/no-document-write.js b/lighthouse-core/audits/dobetterweb/no-document-write.js
index 6dca43f3b..77368e048 100644
--- a/lighthouse-core/audits/dobetterweb/no-document-write.js
+++ b/lighthouse-core/audits/dobetterweb/no-document-write.js
@@ -11,6 +11,20 @@
 'use strict';
 
 const ViolationAudit = require('../violation-audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the page's use of the `document.write` API. This descriptive title is shown to users when the page does not use `document.write`. */
+  title: 'Avoids `document.write()`',
+  /** Title of a Lighthouse audit that provides detail on the page's use of the `document.write` API. This descriptive title is shown to users when the page does use `document.write`. */
+  failureTitle: 'Uses `document.write()`',
+  /** Description of a Lighthouse audit that tells the user why they should avoid `document.write`. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'For users on slow connections, external scripts dynamically injected via ' +
+      '`document.write()` can delay page load by tens of seconds. ' +
+      '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class NoDocWriteAudit extends ViolationAudit {
   /**
@@ -19,11 +33,9 @@ class NoDocWriteAudit extends ViolationAudit {
   static get meta() {
     return {
       id: 'no-document-write',
-      title: 'Avoids `document.write()`',
-      failureTitle: 'Uses `document.write()`',
-      description: 'For users on slow connections, external scripts dynamically injected via ' +
-          '`document.write()` can delay page load by tens of seconds. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages'],
     };
   }
@@ -37,8 +49,8 @@ class NoDocWriteAudit extends ViolationAudit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'label', itemType: 'text', text: 'Location'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'label', itemType: 'text', text: str_(i18n.UIStrings.columnLocation)},
     ];
     // TODO(bckenny): see TODO in geolocation-on-start
     const details = ViolationAudit.makeTableDetails(headings, results);
@@ -54,3 +66,4 @@ class NoDocWriteAudit extends ViolationAudit {
 }
 
 module.exports = NoDocWriteAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js
index 772f9c37e..ec3036e10 100644
--- a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js
+++ b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js
@@ -16,9 +16,49 @@ const Audit = require('../audit.js');
 const Sentry = require('../../lib/sentry.js');
 const semver = require('semver');
 const snykDatabase = require('../../../third-party/snyk/snapshot.json');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on Javascript libraries the page uses. This descriptive title is shown to users when all Javascript libraries are free of known security vulnerabilities. */
+  title: 'Avoids front-end JavaScript libraries' +
+    ' with known security vulnerabilities',
+  /** Title of a Lighthouse audit that provides detail on Javascript libraries the page uses. This descriptive title is shown to users when some detected Javascript libraries have known security vulnerabilities. */
+  failureTitle: 'Includes front-end JavaScript libraries' +
+    ' with known security vulnerabilities',
+  /** Description of a Lighthouse audit that tells the user why they should be concerned about the third party Javascript libraries that they use. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Some third-party scripts may contain known security vulnerabilities ' +
+    'that are easily identified and exploited by attackers. ' +
+    '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/vulnerabilities).',
+  /** [ICU Syntax] Label for the audit identifying the number of vulnerable Javascript libraries found. */
+  displayValue: `{itemCount, plural,
+    =1 {1 vulnerability detected}
+    other {# vulnerabilities detected}
+    }`,
+  /** Label for a column in a data table; entries will be the version numbers of the Javascript libraries found.  */
+  columnVersion: 'Library Version',
+  /** Label for a column in a data table; entries will be the counts of JavaScript-library vulnerabilities found.  */
+  columnVuln: 'Vulnerability Count',
+  /** Label for a column in a data table; entries will be the severity of the vulnerabilities found within a Javascript library. */
+  columnSeverity: 'Highest Severity',
+  /** Table row value for the severity of a small, or low impact Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high. */
+  rowSeverityLow: 'Low',
+  /** Table row value for the severity of a Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high. */
+  rowSeverityMedium: 'Medium',
+  /** Table row value for the severity of a high impact, or dangerous Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high. */
+  rowSeverityHigh: 'High',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 const SEMVER_REGEX = /^(\d+\.\d+\.\d+)[^-0-9]+/;
 
+/** @type {Object<string, string>} */
+const rowMap = {
+  'low': str_(UIStrings.rowSeverityLow),
+  'medium': str_(UIStrings.rowSeverityMedium),
+  'high': str_(UIStrings.rowSeverityHigh),
+};
+
 /** @typedef {{npm: Object<string, Array<{id: string, severity: string, semver: {vulnerable: Array<string>}}>>}} SnykDB */
 /** @typedef {{severity: string, numericSeverity: number, library: string, url: string}} Vulnerability */
 
@@ -29,13 +69,9 @@ class NoVulnerableLibrariesAudit extends Audit {
   static get meta() {
     return {
       id: 'no-vulnerable-libraries',
-      title: 'Avoids front-end JavaScript libraries'
-        + ' with known security vulnerabilities',
-      failureTitle: 'Includes front-end JavaScript libraries'
-        + ' with known security vulnerabilities',
-      description: 'Some third-party scripts may contain known security vulnerabilities ' +
-        'that are easily identified and exploited by attackers. ' +
-        '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/vulnerabilities).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['Stacks'],
     };
   }
@@ -110,7 +146,7 @@ class NoVulnerableLibrariesAudit extends Audit {
 
     const vulns = matchingVulns.map(vuln => {
       return {
-        severity: vuln.severity,
+        severity: rowMap[vuln.severity],
         numericSeverity: this.severityMap[vuln.severity],
         library: `${lib.name}@${normalizedVersion}`,
         url: 'https://snyk.io/vuln/' + vuln.id,
@@ -156,7 +192,7 @@ class NoVulnerableLibrariesAudit extends Audit {
 
       let highestSeverity;
       if (vulns.length > 0) {
-        highestSeverity = this.highestSeverity(vulns).replace(/^\w/, l => l.toUpperCase());
+        highestSeverity = this.highestSeverity(vulns);
 
         vulnerabilityResults.push({
           highestSeverity,
@@ -179,17 +215,15 @@ class NoVulnerableLibrariesAudit extends Audit {
     });
 
     let displayValue = '';
-    if (totalVulns > 1) {
-      displayValue = `${totalVulns} vulnerabilities detected`;
-    } else if (totalVulns === 1) {
-      displayValue = `${totalVulns} vulnerability detected`;
+    if (totalVulns > 0) {
+      displayValue = str_(UIStrings.displayValue, {itemCount: totalVulns});
     }
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'detectedLib', itemType: 'link', text: 'Library Version'},
-      {key: 'vulnCount', itemType: 'text', text: 'Vulnerability Count'},
-      {key: 'highestSeverity', itemType: 'text', text: 'Highest Severity'},
+      {key: 'detectedLib', itemType: 'link', text: str_(UIStrings.columnVersion)},
+      {key: 'vulnCount', itemType: 'text', text: str_(UIStrings.columnVuln)},
+      {key: 'highestSeverity', itemType: 'text', text: str_(UIStrings.columnSeverity)},
     ];
     const details = Audit.makeTableDetails(headings, vulnerabilityResults, {});
 
@@ -206,3 +240,4 @@ class NoVulnerableLibrariesAudit extends Audit {
 }
 
 module.exports = NoVulnerableLibrariesAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/notification-on-start.js b/lighthouse-core/audits/dobetterweb/notification-on-start.js
index cbf73a65c..25559a96c 100644
--- a/lighthouse-core/audits/dobetterweb/notification-on-start.js
+++ b/lighthouse-core/audits/dobetterweb/notification-on-start.js
@@ -12,6 +12,20 @@
 'use strict';
 
 const ViolationAudit = require('../violation-audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the page's notification permission requests. This descriptive title is shown to users when the page does not ask for notification permission on load. */
+  title: 'Avoids requesting the notification permission on page load',
+  /** Title of a Lighthouse audit that provides detail on the page's notification permission requests. This descriptive title is shown to users when the page does ask for notification permission on load. */
+  failureTitle: 'Requests the notification permission on page load',
+  /** Description of a Lighthouse audit that tells the user why they should not ask for notification permission on load. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Users are mistrustful of or confused by sites that request to send ' +
+      'notifications without context. Consider tying the request to user gestures ' +
+      'instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class NotificationOnStart extends ViolationAudit {
   /**
@@ -20,11 +34,9 @@ class NotificationOnStart extends ViolationAudit {
   static get meta() {
     return {
       id: 'notification-on-start',
-      title: 'Avoids requesting the notification permission on page load',
-      failureTitle: 'Requests the notification permission on page load',
-      description: 'Users are mistrustful of or confused by sites that request to send ' +
-          'notifications without context. Consider tying the request to user gestures ' +
-          'instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages'],
     };
   }
@@ -38,8 +50,8 @@ class NotificationOnStart extends ViolationAudit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'label', itemType: 'text', text: 'Location'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'label', itemType: 'text', text: str_(i18n.UIStrings.columnLocation)},
     ];
     // TODO(bckenny): see TODO in geolocation-on-start
     const details = ViolationAudit.makeTableDetails(headings, results);
@@ -55,3 +67,4 @@ class NotificationOnStart extends ViolationAudit {
 }
 
 module.exports = NotificationOnStart;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js
index be42e2a0e..b417988ca 100644
--- a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js
+++ b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js
@@ -6,6 +6,21 @@
 'use strict';
 
 const Audit = require('../audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page allows pasting of content into password fields. */
+  title: 'Allows users to paste into password fields',
+  /** Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page does not allow pasting of content into password fields. */
+  failureTitle: 'Prevents users to paste into password fields',
+  /** Description of a Lighthouse audit that tells the user why they should allow pasting of content into password fields. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Preventing password pasting undermines good security policy. ' +
+      '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/password-pasting).',
+  /** Table column header for the HTML elements that do not allow pasting of content. */
+  columnFailingElem: 'Failing Elements',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class PasswordInputsCanBePastedIntoAudit extends Audit {
   /**
@@ -14,10 +29,9 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
   static get meta() {
     return {
       id: 'password-inputs-can-be-pasted-into',
-      title: 'Allows users to paste into password fields',
-      failureTitle: 'Prevents users to paste into password fields',
-      description: 'Preventing password pasting undermines good security policy. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/password-pasting).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['PasswordInputsWithPreventedPaste'],
     };
   }
@@ -39,7 +53,7 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'node', itemType: 'node', text: 'Failing Elements'},
+      {key: 'node', itemType: 'node', text: str_(UIStrings.columnFailingElem)},
     ];
 
     return {
@@ -53,3 +67,4 @@ class PasswordInputsCanBePastedIntoAudit extends Audit {
 }
 
 module.exports = PasswordInputsCanBePastedIntoAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/uses-http2.js b/lighthouse-core/audits/dobetterweb/uses-http2.js
index 1df6e6f5e..73d2524fb 100644
--- a/lighthouse-core/audits/dobetterweb/uses-http2.js
+++ b/lighthouse-core/audits/dobetterweb/uses-http2.js
@@ -13,8 +13,27 @@
 
 const URL = require('../../lib/url-shim.js');
 const Audit = require('../audit.js');
-const Util = require('../../report/html/renderer/util.js');
 const NetworkRecords = require('../../computed/network-records.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on whether the webpage uses HTTP/2 for resources it requests over the network. This descriptive title is shown to users when the page uses HTTP/2 for its requests. */
+  title: 'Uses HTTP/2 for its own resources',
+  /** Title of a Lighthouse audit that provides detail on whether the webpage uses HTTP/2 for resources it requests over the network. This descriptive title is shown to users when the page does not use HTTP/2 for its requests. */
+  failureTitle: 'Does not use HTTP/2 for all of its resources',
+  /** Description of a Lighthouse audit that tells the user why they should use HTTP/2. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'HTTP/2 offers many benefits over HTTP/1.1, including binary headers, ' +
+      'multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).',
+  /** [ICU Syntax] Label identifying the number of network requests that were not served with HTTP/2. */
+  displayValue: `{itemCount, plural,
+    =1 {1 request not served via HTTP/2}
+    other {# requests not served via HTTP/2}
+    }`,
+  /**  Label for a column in a data table; entries in the column will be the HTTP Protocol used to make a network request. */
+  columnProtocol: 'Protocol',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class UsesHTTP2Audit extends Audit {
   /**
@@ -23,10 +42,9 @@ class UsesHTTP2Audit extends Audit {
   static get meta() {
     return {
       id: 'uses-http2',
-      title: 'Uses HTTP/2 for its own resources',
-      failureTitle: 'Does not use HTTP/2 for all of its resources',
-      description: 'HTTP/2 offers many benefits over HTTP/1.1, including binary headers, ' +
-          'multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['URL', 'devtoolsLogs'],
     };
   }
@@ -64,17 +82,14 @@ class UsesHTTP2Audit extends Audit {
       });
 
       let displayValue = '';
-      if (resources.length > 1) {
-        displayValue =
-          `${Util.formatNumber(resources.length)} requests not served via HTTP/2`;
-      } else if (resources.length === 1) {
-        displayValue = `${resources.length} request not served via HTTP/2`;
+      if (resources.length > 0) {
+        displayValue = str_(UIStrings.displayValue, {itemCount: resources.length});
       }
 
       /** @type {LH.Audit.Details.Table['headings']} */
       const headings = [
-        {key: 'url', itemType: 'url', text: 'URL'},
-        {key: 'protocol', itemType: 'text', text: 'Protocol'},
+        {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+        {key: 'protocol', itemType: 'text', text: str_(UIStrings.columnProtocol)},
       ];
       const details = Audit.makeTableDetails(headings, resources);
 
@@ -93,3 +108,4 @@ class UsesHTTP2Audit extends Audit {
 }
 
 module.exports = UsesHTTP2Audit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js b/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js
index bd53fe587..24e476c0a 100644
--- a/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js
+++ b/lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js
@@ -12,6 +12,20 @@
 'use strict';
 
 const ViolationAudit = require('../violation-audit.js');
+const i18n = require('../../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the page's use of passive event listeners used to improve the scrolling performance of the page. This descriptive title is shown to users when the page does use passive listeners. */
+  title: 'Uses passive listeners to improve scrolling performance',
+  /** Title of a Lighthouse audit that provides detail on the page's use of passive event listeners used to improve the scrolling performance of the page. This descriptive title is shown to users when the page does not use passive listeners. */
+  failureTitle: 'Does not use passive listeners to improve scrolling performance',
+  /** Description of a Lighthouse audit that tells the user why they should use passive event listeners on the page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Consider marking your touch and wheel event listeners as `passive` ' +
+      'to improve your page\'s scroll performance. ' +
+      '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class PassiveEventsAudit extends ViolationAudit {
   /**
@@ -20,11 +34,9 @@ class PassiveEventsAudit extends ViolationAudit {
   static get meta() {
     return {
       id: 'uses-passive-event-listeners',
-      title: 'Uses passive listeners to improve scrolling performance',
-      failureTitle: 'Does not use passive listeners to improve scrolling performance',
-      description: 'Consider marking your touch and wheel event listeners as `passive` ' +
-          'to improve your page\'s scroll performance. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages'],
     };
   }
@@ -38,8 +50,8 @@ class PassiveEventsAudit extends ViolationAudit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'label', itemType: 'text', text: 'Location'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'label', itemType: 'text', text: str_(i18n.UIStrings.columnLocation)},
     ];
     // TODO(bckenny): see TODO in geolocation-on-start
     const details = ViolationAudit.makeTableDetails(headings, results);
@@ -55,3 +67,4 @@ class PassiveEventsAudit extends ViolationAudit {
 }
 
 module.exports = PassiveEventsAudit;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/errors-in-console.js b/lighthouse-core/audits/errors-in-console.js
index c73fb2da9..22b6afae4 100644
--- a/lighthouse-core/audits/errors-in-console.js
+++ b/lighthouse-core/audits/errors-in-console.js
@@ -11,6 +11,21 @@
  */
 
 const Audit = require('./audit.js');
+const i18n = require('../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when no browser errors were logged into the devtools console. */
+  title: 'No browser errors logged to the console',
+  /** Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when browser errors occurred and were logged into the devtools console. */
+  failureTitle: 'Browser errors were logged to the console',
+  /** Description of a Lighthouse audit that tells the user why errors being logged to the devtools console are a cause for concern and so should be fixed. This is displayed after a user expands the section to see more. No character length limits. */
+  description: 'Errors logged to the console indicate unresolved problems. ' +
+    'They can come from network request failures and other browser concerns.',
+  /**  Label for a column in a data table; entries in the column will be the descriptions of logged browser errors. */
+  columnDesc: 'Description',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 class ErrorLogs extends Audit {
   /**
@@ -19,10 +34,9 @@ class ErrorLogs extends Audit {
   static get meta() {
     return {
       id: 'errors-in-console',
-      title: 'No browser errors logged to the console',
-      description: 'Errors logged to the console indicate unresolved problems. ' +
-        'They can come from network request failures and other browser concerns.',
-      failureTitle: 'Browser errors were logged to the console',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ConsoleMessages', 'RuntimeExceptions'],
     };
   }
@@ -62,8 +76,8 @@ class ErrorLogs extends Audit {
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'description', itemType: 'code', text: 'Description'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'description', itemType: 'code', text: str_(UIStrings.columnDesc)},
     ];
 
     const details = Audit.makeTableDetails(headings, tableRows);
@@ -78,3 +92,4 @@ class ErrorLogs extends Audit {
 }
 
 module.exports = ErrorLogs;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/image-aspect-ratio.js b/lighthouse-core/audits/image-aspect-ratio.js
index 51791f57d..8dd975f97 100644
--- a/lighthouse-core/audits/image-aspect-ratio.js
+++ b/lighthouse-core/audits/image-aspect-ratio.js
@@ -12,8 +12,27 @@
 'use strict';
 
 const Audit = require('./audit.js');
-
 const URL = require('../lib/url-shim.js');
+const i18n = require('../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when all images use correct aspect ratios. */
+  title: 'Displays images with correct aspect ratio',
+  /** Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when not all images use correct aspect ratios. */
+  failureTitle: 'Displays images with incorrect aspect ratio',
+  /** Description of a Lighthouse audit that tells the user why they should maintain the correct aspect ratios for all images. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'Image display dimensions should match natural aspect ratio. ' +
+    '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/aspect-ratio).',
+  /** Warning that the size information for an image was nonsensical. `url` will be replaced with the url of that image. */
+  warningCompute: 'Invalid image sizing information {url}',
+  /**  Label for a column in a data table; entries in the column will be the numeric aspect ratio of an image as displayed in a web page. */
+  columnDisplayed: 'Aspect Ratio (Displayed)',
+  /**  Label for a column in a data table; entries in the column will be the numeric aspect ratio of the raw (actual) image. */
+  columnActual: 'Aspect Ratio (Actual)',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
+
 const THRESHOLD_PX = 2;
 
 /** @typedef {Required<LH.Artifacts.ImageElement>} WellDefinedImage */
@@ -25,10 +44,9 @@ class ImageAspectRatio extends Audit {
   static get meta() {
     return {
       id: 'image-aspect-ratio',
-      title: 'Displays images with correct aspect ratio',
-      failureTitle: 'Displays images with incorrect aspect ratio',
-      description: 'Image display dimensions should match natural aspect ratio. ' +
-        '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/aspect-ratio).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['ImageElements'],
     };
   }
@@ -47,7 +65,7 @@ class ImageAspectRatio extends Audit {
 
     if (!Number.isFinite(actualAspectRatio) ||
       !Number.isFinite(displayedAspectRatio)) {
-      return new Error(`Invalid image sizing information ${url}`);
+      return new Error(str_(UIStrings.warningCompute, {url}));
     }
 
     return {
@@ -99,9 +117,9 @@ class ImageAspectRatio extends Audit {
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
       {key: 'url', itemType: 'thumbnail', text: ''},
-      {key: 'url', itemType: 'url', text: 'URL'},
-      {key: 'displayedAspectRatio', itemType: 'text', text: 'Aspect Ratio (Displayed)'},
-      {key: 'actualAspectRatio', itemType: 'text', text: 'Aspect Ratio (Actual)'},
+      {key: 'url', itemType: 'url', text: str_(i18n.UIStrings.columnURL)},
+      {key: 'displayedAspectRatio', itemType: 'text', text: str_(UIStrings.columnDisplayed)},
+      {key: 'actualAspectRatio', itemType: 'text', text: str_(UIStrings.columnActual)},
     ];
 
     return {
@@ -113,3 +131,4 @@ class ImageAspectRatio extends Audit {
 }
 
 module.exports = ImageAspectRatio;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/audits/is-on-https.js b/lighthouse-core/audits/is-on-https.js
index 98af7bbf5..9b1d245fd 100644
--- a/lighthouse-core/audits/is-on-https.js
+++ b/lighthouse-core/audits/is-on-https.js
@@ -7,8 +7,30 @@
 
 const Audit = require('./audit.js');
 const URL = require('../lib/url-shim.js');
-const Util = require('../report/html/renderer/util.js');
 const NetworkRecords = require('../computed/network-records.js');
+const i18n = require('../lib/i18n/i18n.js');
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that provides detail on the useage of HTTPS on a page. This descriptive title is shown to users when all requests on a page are fufilled using HTTPS. */
+  title: 'Uses HTTPS',
+  /** Title of a Lighthouse audit that provides detail on the useage of HTTPS on a page. This descriptive title is shown to users when some, or all, requests on the page use HTTP instead of HTTPS. */
+  failureTitle: 'Does not use HTTPS',
+  /** Description of a Lighthouse audit that tells the user *why* HTTPS use is important. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. */
+  description: 'All sites should be protected with HTTPS, even ones that don\'t handle ' +
+      'sensitive data. HTTPS prevents intruders from tampering with or passively listening ' +
+      'in on the communications between your app and your users, and is a prerequisite for ' +
+      'HTTP/2 and many new web platform APIs. ' +
+      '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).',
+  /** [ICU Syntax] Label identifying the number of insecure network requests found by an audit of a web page. */
+  displayValue: `{itemCount, plural,
+    =1 {1 insecure request found}
+    other {# insecure requests found}
+    }`,
+  /** Label for a column in a data table; entries in the column will be the URLs of insecure (non-HTTPS) network requests. */
+  columnInsecureURL: 'Insecure URL',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
 
 const SECURE_SCHEMES = ['data', 'https', 'wss', 'blob', 'chrome', 'chrome-extension', 'about'];
 const SECURE_DOMAINS = ['localhost', '127.0.0.1'];
@@ -20,13 +42,9 @@ class HTTPS extends Audit {
   static get meta() {
     return {
       id: 'is-on-https',
-      title: 'Uses HTTPS',
-      failureTitle: 'Does not use HTTPS',
-      description: 'All sites should be protected with HTTPS, even ones that don\'t handle ' +
-          'sensitive data. HTTPS prevents intruders from tampering with or passively listening ' +
-          'in on the communications between your app and your users, and is a prerequisite for ' +
-          'HTTP/2 and many new web platform APIs. ' +
-          '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
       requiredArtifacts: ['devtoolsLogs'],
     };
   }
@@ -53,18 +71,16 @@ class HTTPS extends Audit {
           .filter(record => !HTTPS.isSecureRecord(record))
           .map(record => URL.elideDataURI(record.url));
 
+      const items = Array.from(new Set(insecureURLs)).map(url => ({url}));
+
       let displayValue = '';
-      if (insecureURLs.length > 1) {
-        displayValue = `${Util.formatNumber(insecureURLs.length)} insecure requests found`;
-      } else if (insecureURLs.length === 1) {
-        displayValue = `${insecureURLs.length} insecure request found`;
+      if (items.length > 0) {
+        displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
       }
 
-      const items = Array.from(new Set(insecureURLs)).map(url => ({url}));
-
       /** @type {LH.Audit.Details.Table['headings']} */
       const headings = [
-        {key: 'url', itemType: 'url', text: 'Insecure URL'},
+        {key: 'url', itemType: 'url', text: str_(UIStrings.columnInsecureURL)},
       ];
 
       return {
@@ -80,3 +96,4 @@ class HTTPS extends Audit {
 }
 
 module.exports = HTTPS;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index d569564e8..93ab96ddf 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -94,6 +94,8 @@ const UIStrings = {
   seoCrawlingGroupTitle: 'Crawling and Indexing',
   /* Description of the navigation section within the Search Engine Optimization (SEO) category. Within this section are audits with descriptive titles that highlight ways to make a website accessible to search engine crawlers. */
   seoCrawlingGroupDescription: 'To appear in search results, crawlers need access to your app.',
+  /** Title of the Best Practices category of audits. This is displayed at the top of a list of audits focused on topics related to following web development best practices and accepted guidelines. Also used as a label of a score gauge; try to limit to 20 characters. */
+  bestPracticesCategoryTitle: 'Best Practices',
   /** Title of the Fast and Reliable section of the web app category. Within this section are audits that check if the web site loaded quickly and can reliably load even if the internet connection is very slow or goes offline. */
   pwaFastReliableGroupTitle: 'Fast and reliable',
   /** Title of the Installable section of the web app category. Within this section are audits that check if Chrome supports installing the web site as an app on their device. */
@@ -463,7 +465,7 @@ const defaultConfig = {
       ],
     },
     'best-practices': {
-      title: 'Best Practices',
+      title: str_(UIStrings.bestPracticesCategoryTitle),
       auditRefs: [
         {id: 'appcache-manifest', weight: 1},
         {id: 'is-on-https', weight: 1},
diff --git a/lighthouse-core/lib/i18n/en-US.json b/lighthouse-core/lib/i18n/en-US.json
index a3adf30df..95ed40853 100644
--- a/lighthouse-core/lib/i18n/en-US.json
+++ b/lighthouse-core/lib/i18n/en-US.json
@@ -599,6 +599,74 @@
     "message": "Minimize Critical Requests Depth",
     "description": "Imperative title of a Lighthouse audit that tells the user to reduce the depth of critical network requests to enhance initial load of a page. Critical request chains are series of dependent network requests that are important for page rendering. For example, here's a 4-request-deep chain: The biglogo.jpg image is required, but is requested via the styles.css style code, which is requested by the initialize.js javascript, which is requested by the page's HTML. This is displayed in a list of audit titles that Lighthouse generates."
   },
+  "lighthouse-core/audits/deprecations.js | columnDeprecate": {
+    "message": "Deprecation / Warning",
+    "description": "Header of the table column which displays the warning message describing use of a deprecated API by code running in the web page."
+  },
+  "lighthouse-core/audits/deprecations.js | columnLine": {
+    "message": "Line",
+    "description": "Table column header for line of code (eg. 432) that is using a deprecated API."
+  },
+  "lighthouse-core/audits/deprecations.js | description": {
+    "message": "Deprecated APIs will eventually be removed from the browser. [Learn more](https://www.chromestatus.com/features#deprecated).",
+    "description": "Description of a Lighthouse audit that tells the user why they should not use deprecated APIs on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/deprecations.js | displayValue": {
+    "message": "{itemCount, plural,\n    =1 {1 warning found}\n    other {# warnings found}\n    }",
+    "description": "[ICU Syntax] Label for the audit identifying the number of warnings generated by using deprecated APIs."
+  },
+  "lighthouse-core/audits/deprecations.js | failureTitle": {
+    "message": "Uses deprecated APIs",
+    "description": "Title of a Lighthouse audit that provides detail on the use of deprecated APIs. This descriptive title is shown to users when the page uses deprecated APIs."
+  },
+  "lighthouse-core/audits/deprecations.js | title": {
+    "message": "Avoids deprecated APIs",
+    "description": "Title of a Lighthouse audit that provides detail on the use of deprecated APIs. This descriptive title is shown to users when the page does not use deprecated APIs."
+  },
+  "lighthouse-core/audits/dobetterweb/appcache-manifest.js | description": {
+    "message": "Application Cache is deprecated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/appcache).",
+    "description": "Description of a Lighthouse audit that tells the user why they should not use the Application Cache API. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/appcache-manifest.js | displayValue": {
+    "message": "Found \"{AppCacheManifest}\"",
+    "description": "Label for the audit identifying uses of the Application Cache."
+  },
+  "lighthouse-core/audits/dobetterweb/appcache-manifest.js | failureTitle": {
+    "message": "Uses Application Cache",
+    "description": "Title of a Lighthouse audit that provides detail on the use of the Application Cache API. This descriptive title is shown to users when they do use the Application Cache API, which is considered bad practice."
+  },
+  "lighthouse-core/audits/dobetterweb/appcache-manifest.js | title": {
+    "message": "Avoids Application Cache",
+    "description": "Title of a Lighthouse audit that provides detail on the use of the Application Cache API. This descriptive title is shown to users when they do not use the Application Cache API."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | description": {
+    "message": "Specifying a doctype prevents the browser from switching to quirks-mode. Read more on the [MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)",
+    "description": "Description of a Lighthouse audit that tells the user why they should define an HTML doctype. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | explanationBadDoctype": {
+    "message": "Doctype name must be the lowercase string `html`",
+    "description": "Explanatory message stating that the doctype is set, but is not \"html\" and is therefore invalid."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | explanationNoDoctype": {
+    "message": "Document must contain a doctype",
+    "description": "Explanatory message stating that the document has no doctype."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | explanationPublicId": {
+    "message": "Expected publicId to be an empty string",
+    "description": "Explanatory message stating that the publicId field is not empty."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | explanationSystemId": {
+    "message": "Expected systemId to be an empty string",
+    "description": "Explanatory message stating that the systemId field is not empty."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | failureTitle": {
+    "message": "Page lacks the HTML doctype, thus triggering quirks-mode",
+    "description": "Title of a Lighthouse audit that provides detail on the doctype of a page. This descriptive title is shown to users when the page's doctype is not set to HTML."
+  },
+  "lighthouse-core/audits/dobetterweb/doctype.js | title": {
+    "message": "Page has the HTML doctype",
+    "description": "Title of a Lighthouse audit that provides detail on the doctype of a page. This descriptive title is shown to users when the pages's doctype is set to HTML."
+  },
   "lighthouse-core/audits/dobetterweb/dom-size.js | columnElement": {
     "message": "Element",
     "description": "Table column header for the DOM element. Each DOM element is described with its HTML representation."
@@ -639,6 +707,186 @@
     "message": "Avoids an excessive DOM size",
     "description": "Title of a diagnostic audit that provides detail on the size of the web page's DOM. The size of a DOM is characterized by the total number of DOM elements and greatest DOM depth. This descriptive title is shown to users when the amount is acceptable and no user action is required."
   },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | columnRel": {
+    "message": "Rel",
+    "description": "Label for a column in a data table; entries will be the values of the html \"rel\" attribute from link in a page."
+  },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | columnTarget": {
+    "message": "Target",
+    "description": "Label for a column in a data table; entries will be the target attribute of a link. Each entry is either an empty string or a string like `_blank`."
+  },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | description": {
+    "message": "Add `rel=\"noopener\"` or `rel=\"noreferrer\"` to any external links to improve performance and prevent security vulnerabilities. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).",
+    "description": "Description of a Lighthouse audit that tells the user why and how they should secure cross-origin links. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | failureTitle": {
+    "message": "Links to cross-origin destinations are unsafe",
+    "description": "Title of a Lighthouse audit that provides detail on the cross-origin links that the web page contains, and whether the links can be considered safe. This descriptive title is shown to users when not all links can be considered safe."
+  },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | title": {
+    "message": "Links to cross-origin destinations are safe",
+    "description": "Title of a Lighthouse audit that provides detail on the cross-origin links that the web page contains, and whether the links can be considered safe. This descriptive title is shown to users when all links are safe."
+  },
+  "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | warning": {
+    "message": "Unable to determine the destination for anchor ({anchorHTML}). If not used as a hyperlink, consider removing target=_blank.",
+    "description": "Warning that some links' destinations cannot be determined and therefore the audit cannot evaluate the link's safety."
+  },
+  "lighthouse-core/audits/dobetterweb/geolocation-on-start.js | description": {
+    "message": "Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to a user action instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).",
+    "description": "Description of a Lighthouse audit that tells the user why they should not ask for geolocation permissions on load. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/geolocation-on-start.js | failureTitle": {
+    "message": "Requests the geolocation permission on page load",
+    "description": "Title of a Lighthouse audit that provides detail on geolocation permissions requests. This descriptive title is shown to users when the page does ask for geolocation permissions on load."
+  },
+  "lighthouse-core/audits/dobetterweb/geolocation-on-start.js | title": {
+    "message": "Avoids requesting the geolocation permission on page load",
+    "description": "Title of a Lighthouse audit that provides detail on geolocation permission requests while the page is loading. This descriptive title is shown to users when the page does not ask for geolocation permissions on load."
+  },
+  "lighthouse-core/audits/dobetterweb/js-libraries.js | columnName": {
+    "message": "Name",
+    "description": "Label for a column in a data table; entries will be the names of the detected Javascript libraries."
+  },
+  "lighthouse-core/audits/dobetterweb/js-libraries.js | columnVersion": {
+    "message": "Version",
+    "description": "Label for a column in a data table; entries will be the version numbers of the detected Javascript libraries."
+  },
+  "lighthouse-core/audits/dobetterweb/js-libraries.js | description": {
+    "message": "All front-end JavaScript libraries detected on the page.",
+    "description": "Description of a Lighthouse audit that tells the user what this audit is detecting. This is displayed after a user expands the section to see more. No character length limits."
+  },
+  "lighthouse-core/audits/dobetterweb/js-libraries.js | title": {
+    "message": "Detected JavaScript libraries",
+    "description": "Title of a Lighthouse audit that provides detail on the Javascript libraries that are used on the page."
+  },
+  "lighthouse-core/audits/dobetterweb/no-document-write.js | description": {
+    "message": "For users on slow connections, external scripts dynamically injected via `document.write()` can delay page load by tens of seconds. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).",
+    "description": "Description of a Lighthouse audit that tells the user why they should avoid `document.write`. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/no-document-write.js | failureTitle": {
+    "message": "Uses `document.write()`",
+    "description": "Title of a Lighthouse audit that provides detail on the page's use of the `document.write` API. This descriptive title is shown to users when the page does use `document.write`."
+  },
+  "lighthouse-core/audits/dobetterweb/no-document-write.js | title": {
+    "message": "Avoids `document.write()`",
+    "description": "Title of a Lighthouse audit that provides detail on the page's use of the `document.write` API. This descriptive title is shown to users when the page does not use `document.write`."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnSeverity": {
+    "message": "Highest Severity",
+    "description": "Label for a column in a data table; entries will be the severity of the vulnerabilities found within a Javascript library."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnVersion": {
+    "message": "Library Version",
+    "description": "Label for a column in a data table; entries will be the version numbers of the Javascript libraries found."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnVuln": {
+    "message": "Vulnerability Count",
+    "description": "Label for a column in a data table; entries will be the counts of JavaScript-library vulnerabilities found."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | description": {
+    "message": "Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/vulnerabilities).",
+    "description": "Description of a Lighthouse audit that tells the user why they should be concerned about the third party Javascript libraries that they use. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | displayValue": {
+    "message": "{itemCount, plural,\n    =1 {1 vulnerability detected}\n    other {# vulnerabilities detected}\n    }",
+    "description": "[ICU Syntax] Label for the audit identifying the number of vulnerable Javascript libraries found."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | failureTitle": {
+    "message": "Includes front-end JavaScript libraries with known security vulnerabilities",
+    "description": "Title of a Lighthouse audit that provides detail on Javascript libraries the page uses. This descriptive title is shown to users when some detected Javascript libraries have known security vulnerabilities."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | rowSeverityHigh": {
+    "message": "High",
+    "description": "Table row value for the severity of a high impact, or dangerous Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | rowSeverityLow": {
+    "message": "Low",
+    "description": "Table row value for the severity of a small, or low impact Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | rowSeverityMedium": {
+    "message": "Medium",
+    "description": "Table row value for the severity of a Javascript vulnerability.  Part of a ranking scale in the form: low, medium, high."
+  },
+  "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | title": {
+    "message": "Avoids front-end JavaScript libraries with known security vulnerabilities",
+    "description": "Title of a Lighthouse audit that provides detail on Javascript libraries the page uses. This descriptive title is shown to users when all Javascript libraries are free of known security vulnerabilities."
+  },
+  "lighthouse-core/audits/dobetterweb/notification-on-start.js | description": {
+    "message": "Users are mistrustful of or confused by sites that request to send notifications without context. Consider tying the request to user gestures instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).",
+    "description": "Description of a Lighthouse audit that tells the user why they should not ask for notification permission on load. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/notification-on-start.js | failureTitle": {
+    "message": "Requests the notification permission on page load",
+    "description": "Title of a Lighthouse audit that provides detail on the page's notification permission requests. This descriptive title is shown to users when the page does ask for notification permission on load."
+  },
+  "lighthouse-core/audits/dobetterweb/notification-on-start.js | title": {
+    "message": "Avoids requesting the notification permission on page load",
+    "description": "Title of a Lighthouse audit that provides detail on the page's notification permission requests. This descriptive title is shown to users when the page does not ask for notification permission on load."
+  },
+  "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": {
+    "message": "Failing Elements",
+    "description": "Table column header for the HTML elements that do not allow pasting of content."
+  },
+  "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": {
+    "message": "Preventing password pasting undermines good security policy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/password-pasting).",
+    "description": "Description of a Lighthouse audit that tells the user why they should allow pasting of content into password fields. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | failureTitle": {
+    "message": "Prevents users to paste into password fields",
+    "description": "Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page does not allow pasting of content into password fields."
+  },
+  "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | title": {
+    "message": "Allows users to paste into password fields",
+    "description": "Title of a Lighthouse audit that provides detail on the ability to paste into password fields. This descriptive title is shown to users when the page allows pasting of content into password fields."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-http2.js | columnProtocol": {
+    "message": "Protocol",
+    "description": "Label for a column in a data table; entries in the column will be the HTTP Protocol used to make a network request."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-http2.js | description": {
+    "message": "HTTP/2 offers many benefits over HTTP/1.1, including binary headers, multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).",
+    "description": "Description of a Lighthouse audit that tells the user why they should use HTTP/2. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-http2.js | displayValue": {
+    "message": "{itemCount, plural,\n    =1 {1 request not served via HTTP/2}\n    other {# requests not served via HTTP/2}\n    }",
+    "description": "[ICU Syntax] Label identifying the number of network requests that were not served with HTTP/2."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-http2.js | failureTitle": {
+    "message": "Does not use HTTP/2 for all of its resources",
+    "description": "Title of a Lighthouse audit that provides detail on whether the webpage uses HTTP/2 for resources it requests over the network. This descriptive title is shown to users when the page does not use HTTP/2 for its requests."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-http2.js | title": {
+    "message": "Uses HTTP/2 for its own resources",
+    "description": "Title of a Lighthouse audit that provides detail on whether the webpage uses HTTP/2 for resources it requests over the network. This descriptive title is shown to users when the page uses HTTP/2 for its requests."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js | description": {
+    "message": "Consider marking your touch and wheel event listeners as `passive` to improve your page's scroll performance. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).",
+    "description": "Description of a Lighthouse audit that tells the user why they should use passive event listeners on the page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js | failureTitle": {
+    "message": "Does not use passive listeners to improve scrolling performance",
+    "description": "Title of a Lighthouse audit that provides detail on the page's use of passive event listeners used to improve the scrolling performance of the page. This descriptive title is shown to users when the page does not use passive listeners."
+  },
+  "lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js | title": {
+    "message": "Uses passive listeners to improve scrolling performance",
+    "description": "Title of a Lighthouse audit that provides detail on the page's use of passive event listeners used to improve the scrolling performance of the page. This descriptive title is shown to users when the page does use passive listeners."
+  },
+  "lighthouse-core/audits/errors-in-console.js | columnDesc": {
+    "message": "Description",
+    "description": "Label for a column in a data table; entries in the column will be the descriptions of logged browser errors."
+  },
+  "lighthouse-core/audits/errors-in-console.js | description": {
+    "message": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns.",
+    "description": "Description of a Lighthouse audit that tells the user why errors being logged to the devtools console are a cause for concern and so should be fixed. This is displayed after a user expands the section to see more. No character length limits."
+  },
+  "lighthouse-core/audits/errors-in-console.js | failureTitle": {
+    "message": "Browser errors were logged to the console",
+    "description": "Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when browser errors occurred and were logged into the devtools console."
+  },
+  "lighthouse-core/audits/errors-in-console.js | title": {
+    "message": "No browser errors logged to the console",
+    "description": "Title of a Lighthouse audit that provides detail on browser errors. This descriptive title is shown to users when no browser errors were logged into the devtools console."
+  },
   "lighthouse-core/audits/font-display.js | description": {
     "message": "Leverage the font-display CSS feature to ensure text is user-visible while webfonts are loading. [Learn more](https://developers.google.com/web/updates/2016/02/font-display).",
     "description": "Description of a Lighthouse audit that tells the user *why* they should use the font-display CSS feature. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
@@ -655,6 +903,50 @@
     "message": "Lighthouse was unable to automatically check the font-display value for the following URL: {fontURL}.",
     "description": "A warning message that is shown when Lighthouse couldn't automatically check some of the page's fonts and that the user will need to manually check it."
   },
+  "lighthouse-core/audits/image-aspect-ratio.js | columnActual": {
+    "message": "Aspect Ratio (Actual)",
+    "description": "Label for a column in a data table; entries in the column will be the numeric aspect ratio of the raw (actual) image."
+  },
+  "lighthouse-core/audits/image-aspect-ratio.js | columnDisplayed": {
+    "message": "Aspect Ratio (Displayed)",
+    "description": "Label for a column in a data table; entries in the column will be the numeric aspect ratio of an image as displayed in a web page."
+  },
+  "lighthouse-core/audits/image-aspect-ratio.js | description": {
+    "message": "Image display dimensions should match natural aspect ratio. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/aspect-ratio).",
+    "description": "Description of a Lighthouse audit that tells the user why they should maintain the correct aspect ratios for all images. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/image-aspect-ratio.js | failureTitle": {
+    "message": "Displays images with incorrect aspect ratio",
+    "description": "Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when not all images use correct aspect ratios."
+  },
+  "lighthouse-core/audits/image-aspect-ratio.js | title": {
+    "message": "Displays images with correct aspect ratio",
+    "description": "Title of a Lighthouse audit that provides detail on the aspect ratios of all images on the page. This descriptive title is shown to users when all images use correct aspect ratios."
+  },
+  "lighthouse-core/audits/image-aspect-ratio.js | warningCompute": {
+    "message": "Invalid image sizing information {url}",
+    "description": "Warning that the size information for an image was nonsensical. `url` will be replaced with the url of that image."
+  },
+  "lighthouse-core/audits/is-on-https.js | columnInsecureURL": {
+    "message": "Insecure URL",
+    "description": "Label for a column in a data table; entries in the column will be the URLs of insecure (non-HTTPS) network requests."
+  },
+  "lighthouse-core/audits/is-on-https.js | description": {
+    "message": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).",
+    "description": "Description of a Lighthouse audit that tells the user *why* HTTPS use is important. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
+  },
+  "lighthouse-core/audits/is-on-https.js | displayValue": {
+    "message": "{itemCount, plural,\n    =1 {1 insecure request found}\n    other {# insecure requests found}\n    }",
+    "description": "[ICU Syntax] Label identifying the number of insecure network requests found by an audit of a web page."
+  },
+  "lighthouse-core/audits/is-on-https.js | failureTitle": {
+    "message": "Does not use HTTPS",
+    "description": "Title of a Lighthouse audit that provides detail on the useage of HTTPS on a page. This descriptive title is shown to users when some, or all, requests on the page use HTTP instead of HTTPS."
+  },
+  "lighthouse-core/audits/is-on-https.js | title": {
+    "message": "Uses HTTPS",
+    "description": "Title of a Lighthouse audit that provides detail on the useage of HTTPS on a page. This descriptive title is shown to users when all requests on a page are fufilled using HTTPS."
+  },
   "lighthouse-core/audits/load-fast-enough-for-pwa.js | description": {
     "message": "A fast page load over a cellular network ensures a good mobile user experience. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).",
     "description": "Description of a Lighthouse audit that tells the user *why* they need to load fast enough on mobile networks. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."
@@ -1171,6 +1463,10 @@
     "message": "Tables and lists",
     "description": "Title of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to improve the experience of reading tabular or list data using assistive technology."
   },
+  "lighthouse-core/config/default-config.js | bestPracticesCategoryTitle": {
+    "message": "Best Practices",
+    "description": "Title of the Best Practices category of audits. This is displayed at the top of a list of audits focused on topics related to following web development best practices and accepted guidelines. Also used as a label of a score gauge; try to limit to 20 characters."
+  },
   "lighthouse-core/config/default-config.js | budgetsGroupDescription": {
     "message": "Performance budgets set standards for the performance of your site.",
     "description": "Description of the Budgets section of the Performance category. Within this section the budget results are displayed."
@@ -1269,7 +1565,11 @@
   },
   "lighthouse-core/lib/i18n/i18n.js | columnCacheTTL": {
     "message": "Cache TTL",
-    "description": "Label for the TTL column in data tables, entries will be the time to live value of the cache header on a web resource"
+    "description": "Label for a column in a data table; entries will be the time to live value of the cache header on a web resource."
+  },
+  "lighthouse-core/lib/i18n/i18n.js | columnLocation": {
+    "message": "Location",
+    "description": "Label for a column in a data table; entries will be the location of a specific line of code in a file, in the format \"line: 102\"."
   },
   "lighthouse-core/lib/i18n/i18n.js | columnRequests": {
     "message": "Requests",
@@ -1281,11 +1581,11 @@
   },
   "lighthouse-core/lib/i18n/i18n.js | columnSize": {
     "message": "Size",
-    "description": "Label for the size column in data tables, entries will be the size of a web resource in kilobytes"
+    "description": "Label for a column in a data table; entries will be the size of a web resource in kilobytes."
   },
   "lighthouse-core/lib/i18n/i18n.js | columnTimeSpent": {
     "message": "Time Spent",
-    "description": "Label for the time spent column in data tables, entries will be the number of milliseconds spent during a particular activity"
+    "description": "Label for a column in a data table; entries will be the number of milliseconds spent during a particular activity."
   },
   "lighthouse-core/lib/i18n/i18n.js | columnTransferSize": {
     "message": "Transfer Size",
@@ -1293,15 +1593,15 @@
   },
   "lighthouse-core/lib/i18n/i18n.js | columnURL": {
     "message": "URL",
-    "description": "Label for the URL column in data tables, entries will be the URL of a web resource"
+    "description": "Label for a column in a data table; entries will be the URL of a web resource"
   },
   "lighthouse-core/lib/i18n/i18n.js | columnWastedBytes": {
     "message": "Potential Savings",
-    "description": "Label for the wasted bytes column in data tables, entries will be the number of kilobytes the user could reduce their page by if they implemented the suggestions"
+    "description": "Label for a column in a data table; entries will be the number of kilobytes the user could reduce their page by if they implemented the suggestions."
   },
   "lighthouse-core/lib/i18n/i18n.js | columnWastedMs": {
     "message": "Potential Savings",
-    "description": "Label for the wasted bytes column in data tables, entries will be the number of milliseconds the user could reduce page load by if they implemented the suggestions"
+    "description": "Label for a column in a data table; entries will be the number of milliseconds the user could reduce page load by if they implemented the suggestions."
   },
   "lighthouse-core/lib/i18n/i18n.js | displayValueByteSavings": {
     "message": "Potential savings of {wastedBytes, number, bytes} KB",
diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js
index 3433bf79b..36476f29b 100644
--- a/lighthouse-core/lib/i18n/i18n.js
+++ b/lighthouse-core/lib/i18n/i18n.js
@@ -44,23 +44,25 @@ const UIStrings = {
   displayValueByteSavings: 'Potential savings of {wastedBytes, number, bytes}\xa0KB',
   /** Label shown per-audit to show how many milliseconds faster the page load could be if the user implemented the suggestions. The `{wastedMs}` placeholder will be replaced with the time duration, shown in milliseconds (e.g. 140 ms) */
   displayValueMsSavings: 'Potential savings of {wastedMs, number, milliseconds}\xa0ms',
-  /** Label for the URL column in data tables, entries will be the URL of a web resource */
+  /** Label for a column in a data table; entries will be the URL of a web resource */
   columnURL: 'URL',
-  /** Label for the size column in data tables, entries will be the size of a web resource in kilobytes */
+  /** Label for a column in a data table; entries will be the size of a web resource in kilobytes. */
   columnSize: 'Size',
-  /** Label for the TTL column in data tables, entries will be the time to live value of the cache header on a web resource */
+  /** Label for a column in a data table; entries will be the time to live value of the cache header on a web resource. */
   columnCacheTTL: 'Cache TTL',
-  /** Label for the wasted bytes column in data tables, entries will be the number of kilobytes the user could reduce their page by if they implemented the suggestions */
+  /** Label for a column in a data table; entries will be the number of kilobytes the user could reduce their page by if they implemented the suggestions. */
   columnWastedBytes: 'Potential Savings',
-  /** Label for the wasted bytes column in data tables, entries will be the number of milliseconds the user could reduce page load by if they implemented the suggestions */
+  /** Label for a column in a data table; entries will be the number of milliseconds the user could reduce page load by if they implemented the suggestions. */
   columnWastedMs: 'Potential Savings',
-  /** Label for the time spent column in data tables, entries will be the number of milliseconds spent during a particular activity */
+  /** Label for a column in a data table; entries will be the number of milliseconds spent during a particular activity. */
   columnTimeSpent: 'Time Spent',
+  /** Label for a column in a data table; entries will be the location of a specific line of code in a file, in the format "line: 102". */
+  columnLocation: 'Location',
   /** Label for a column in a data table; entries will be types of resources loaded over the network, e.g. "Scripts", "Third-Party", "Stylesheet". */
   columnResourceType: 'Resource Type',
-  /** Label for a column in a data table; entries will be the number of network requests done by a webpage.*/
+  /** Label for a column in a data table; entries will be the number of network requests done by a webpage. */
   columnRequests: 'Requests',
-  /** Label for a column in a data table; entries will be the number of kilobytes transferred to load a set of files.*/
+  /** Label for a column in a data table; entries will be the number of kilobytes transferred to load a set of files. */
   columnTransferSize: 'Transfer Size',
   /** Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page. */
   totalResourceType: 'Total',
diff --git a/lighthouse-core/runner.js b/lighthouse-core/runner.js
index c96a9e6a5..a798eda2e 100644
--- a/lighthouse-core/runner.js
+++ b/lighthouse-core/runner.js
@@ -237,6 +237,7 @@ class Runner {
         gatherMode: undefined,
         auditMode: undefined,
         output: undefined,
+        channel: undefined,
         budgets: undefined,
       };
       const normalizedGatherSettings = Object.assign({}, artifacts.settings, overrides);
diff --git a/lighthouse-core/scripts/build-report-for-autodeployment.js b/lighthouse-core/scripts/build-report-for-autodeployment.js
index f4534436e..5bfab5d56 100644
--- a/lighthouse-core/scripts/build-report-for-autodeployment.js
+++ b/lighthouse-core/scripts/build-report-for-autodeployment.js
@@ -10,10 +10,14 @@
 /* eslint-disable no-console */
 const fs = require('fs');
 const path = require('path');
+const mkdirp = require('mkdirp').sync;
+const swapLocale = require('../lib/i18n/swap-locale.js');
 
 const ReportGenerator = require('../../lighthouse-core/report/report-generator.js');
 const lhr = /** @type {LH.Result} */ (require('../../lighthouse-core/test/results/sample_v2.json'));
 
+const DIST = path.join(__dirname, `../../dist`);
+
 // Add a plugin to demo plugin rendering.
 lhr.categories['lighthouse-plugin-someplugin'] = {
   id: 'lighthouse-plugin-someplugin',
@@ -22,8 +26,26 @@ lhr.categories['lighthouse-plugin-someplugin'] = {
   auditRefs: [],
 };
 
-console.log('🕒 Generating report for sample_v2.json...');
-const html = ReportGenerator.generateReport(lhr, 'html');
-const filename = path.join(__dirname, '../../dist/index.html');
-fs.writeFileSync(filename, html, {encoding: 'utf-8'});
-console.log('✅', filename, 'written.');
+(async function() {
+  const filenameToLhr = {
+    'english': lhr,
+    'espanol': swapLocale(lhr, 'es').lhr,
+    'arabic': swapLocale(lhr, 'ar').lhr,
+  };
+
+  mkdirp(DIST);
+
+  // Generate and write reports
+  Object.entries(filenameToLhr).forEach(([filename, lhr]) => {
+    let html = ReportGenerator.generateReportHtml(lhr);
+    for (const variant of ['', '-devtools']) {
+      if (variant === '-devtools') {
+        html = html.replace(`"lh-root lh-vars"`, `"lh-root lh-vars lh-devtools"`);
+      }
+      const filepath = `${DIST}/${filename}${variant}/index.html`;
+      mkdirp(path.dirname(filepath));
+      fs.writeFileSync(filepath, html, {encoding: 'utf-8'});
+      console.log('✅', filepath, 'written.');
+    }
+  });
+})();
diff --git a/now.json b/now.json
index fd1f9ca74..02b220f9d 100644
--- a/now.json
+++ b/now.json
@@ -2,7 +2,7 @@
   "version": 2,
   "builds": [
     {
-      "src": ".build-for-now.sh",
+      "src": "package.json",
       "use": "@now/static-build"
     }
   ],
diff --git a/package.json b/package.json
index cceb6d70d..556bc7f4c 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "chrome": "node lighthouse-core/scripts/manual-chrome-launcher.js",
     "fast": "yarn start --emulated-form-factor=none --throttlingMethod=provided",
     "deploy-viewer": "yarn build-viewer --deploy",
+    "now-build": "node lighthouse-core/scripts/build-report-for-autodeployment.js",
     "bundlesize": "bundlesize",
     "timing-trace": "node lighthouse-core/scripts/generate-timing-trace.js",
     "changelog": "conventional-changelog --config ./build/changelog-generator/index.js --infile changelog.md --same-file",
@@ -122,7 +123,6 @@
     "isomorphic-fetch": "^2.2.1",
     "jest": "^24.3.0",
     "jsdom": "^12.2.0",
-    "lodash.set": "^4.3.2",
     "make-dir": "^1.3.0",
     "npm-run-posix-or-windows": "^2.0.2",
     "nyc": "^13.3.0",
@@ -152,6 +152,7 @@
     "jsonlint-mod": "^1.7.4",
     "lighthouse-logger": "^1.2.0",
     "lodash.isequal": "^4.5.0",
+    "lodash.set": "^4.3.2",
     "lookup-closest-locale": "6.0.4",
     "metaviewport-parser": "0.2.0",
     "mkdirp": "0.5.1",
diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json
index baf0bb7ad..0c03c90c9 100644
--- a/proto/sample_v2_round_trip.json
+++ b/proto/sample_v2_round_trip.json
@@ -492,7 +492,7 @@
             "title": "Definition list items are wrapped in `<dl>` elements"
         },
         "doctype": {
-            "description": "Specifying a doctype prevents the browser from switching to quirks-mode.Read more on the [MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)",
+            "description": "Specifying a doctype prevents the browser from switching to quirks-mode. Read more on the [MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)",
             "id": "doctype",
             "score": 1.0,
             "scoreDisplayMode": "binary",
@@ -810,7 +810,7 @@
             "title": "`<frame>` or `<iframe>` elements have a title"
         },
         "geolocation-on-start": {
-            "description": "Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to user gestures instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).",
+            "description": "Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to a user action instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).",
             "details": {
                 "headings": [
                     {


Test patch:
diff --git a/lighthouse-core/test/audits/deprecations-test.js b/lighthouse-core/test/audits/deprecations-test.js
index 2fbb0b8b5..ab51ae8a1 100644
--- a/lighthouse-core/test/audits/deprecations-test.js
+++ b/lighthouse-core/test/audits/deprecations-test.js
@@ -31,7 +31,7 @@ describe('Console deprecations audit', () => {
       ],
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.displayValue, '1 warning found');
+    expect(auditResult.displayValue).toBeDisplayString('1 warning found');
     assert.equal(auditResult.details.items.length, 1);
     assert.equal(auditResult.details.items[0].url, '');
     assert.equal(auditResult.details.items[0].lineNumber, undefined);
@@ -67,7 +67,7 @@ describe('Console deprecations audit', () => {
       ],
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.displayValue, '2 warnings found');
+    expect(auditResult.displayValue).toBeDisplayString('2 warnings found');
     assert.equal(auditResult.details.items.length, 2);
     assert.equal(auditResult.details.items[0].url, URL);
     assert.equal(auditResult.details.items[0].lineNumber, 123);
diff --git a/lighthouse-core/test/audits/dobetterweb/doctype-test.js b/lighthouse-core/test/audits/dobetterweb/doctype-test.js
index 1c3479617..71d386e7b 100644
--- a/lighthouse-core/test/audits/dobetterweb/doctype-test.js
+++ b/lighthouse-core/test/audits/dobetterweb/doctype-test.js
@@ -16,7 +16,7 @@ describe('DOBETTERWEB: doctype audit', () => {
       Doctype: null,
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.explanation, 'Document must contain a doctype');
+    expect(auditResult.explanation).toBeDisplayString('Document must contain a doctype');
   });
 
   it('fails when the value of the name attribute is a value other then lowercase "html"', () => {
@@ -28,7 +28,8 @@ describe('DOBETTERWEB: doctype audit', () => {
       },
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.explanation, 'Doctype name must be the lowercase string `html`');
+    expect(auditResult.explanation).toBeDisplayString(
+      'Doctype name must be the lowercase string `html`');
   });
 
   it('fails when the value of the name attribute is not the lowercase string "html"', () => {
@@ -40,7 +41,8 @@ describe('DOBETTERWEB: doctype audit', () => {
       },
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.explanation, 'Doctype name must be the lowercase string `html`');
+    expect(auditResult.explanation).toBeDisplayString(
+      'Doctype name must be the lowercase string `html`');
   });
 
   it('fails when the publicId attribute is not an empty string', () => {
@@ -52,7 +54,7 @@ describe('DOBETTERWEB: doctype audit', () => {
       },
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.explanation, 'Expected publicId to be an empty string');
+    expect(auditResult.explanation).toBeDisplayString('Expected publicId to be an empty string');
   });
 
   it('fails when the systemId attribute is not an empty string', () => {
@@ -64,7 +66,7 @@ describe('DOBETTERWEB: doctype audit', () => {
       },
     });
     assert.equal(auditResult.score, 0);
-    assert.equal(auditResult.explanation, 'Expected systemId to be an empty string');
+    expect(auditResult.explanation).toBeDisplayString('Expected systemId to be an empty string');
   });
 
   it('succeeds when document contains a doctype, and the name value is "html"', () => {
diff --git a/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js b/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js
index 41279193c..ef65ff78c 100644
--- a/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js
+++ b/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js
@@ -37,7 +37,7 @@ describe('Avoids front-end JavaScript libraries with known vulnerabilities', ()
     assert.equal(auditResult.score, 0);
     assert.equal(auditResult.details.items.length, 1);
     assert.equal(auditResult.extendedInfo.jsLibs.length, 3);
-    assert.equal(auditResult.details.items[0].highestSeverity, 'High');
+    expect(auditResult.details.items[0].highestSeverity).toBeDisplayString('High');
     assert.equal(auditResult.details.items[0].detectedLib.type, 'link');
     assert.equal(auditResult.details.items[0].detectedLib.text, 'angular@1.1.4');
     assert.equal(auditResult.details.items[0].detectedLib.url, 'https://snyk.io/vuln/npm:angular?lh=1.1.4&utm_source=lighthouse&utm_medium=ref&utm_campaign=audit');
@@ -58,16 +58,11 @@ describe('Avoids front-end JavaScript libraries with known vulnerabilities', ()
       Stacks[0],
       mockSnykDb
     );
-    expect(vulns).toMatchInlineSnapshot(`
-Array [
-  Object {
-    "library": "Badlib@3.0.0",
-    "numericSeverity": 2,
-    "severity": "medium",
-    "url": "https://snyk.io/vuln/badlibvuln:12345",
-  },
-]
-`);
+
+    expect(vulns[0].severity).toBeDisplayString('Medium');
+    assert.equal(vulns[0].library, 'Badlib@3.0.0');
+    assert.equal(vulns[0].numericSeverity, 2);
+    assert.equal(vulns[0].url, 'https://snyk.io/vuln/badlibvuln:12345');
   });
 
   it('handles ill-specified versions', () => {
diff --git a/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js b/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js
index 3e4f51565..bc9929365 100644
--- a/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js
+++ b/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js
@@ -29,15 +29,15 @@ describe('Resources are fetched over http/2', () => {
     return UsesHTTP2Audit.audit(getArtifacts(networkRecords, URL), {computedCache: new Map()}).then(
       auditResult => {
         assert.equal(auditResult.score, 0);
-        assert.ok(auditResult.displayValue.match('3 requests not'));
+        expect(auditResult.displayValue).toBeDisplayString('3 requests not served via HTTP/2');
         assert.equal(auditResult.details.items.length, 3);
         assert.equal(
           auditResult.details.items[0].url,
           'https://webtide.com/wp-content/plugins/wp-pagenavi/pagenavi-css.css?ver=2.70'
         );
         const headers = auditResult.details.headings;
-        assert.equal(headers[0].text, 'URL', 'table headings are correct and in order');
-        assert.equal(headers[1].text, 'Protocol', 'table headings are correct and in order');
+        expect(headers[0].text).toBeDisplayString('URL');
+        expect(headers[1].text).toBeDisplayString('Protocol');
       }
     );
   });
@@ -46,7 +46,7 @@ describe('Resources are fetched over http/2', () => {
     const entryWithHTTP1 = networkRecords.slice(1, 2);
     return UsesHTTP2Audit.audit(getArtifacts(entryWithHTTP1, URL), {computedCache: new Map()}).then(
       auditResult => {
-        assert.ok(auditResult.displayValue.match('1 request not'));
+        expect(auditResult.displayValue).toBeDisplayString('1 request not served via HTTP/2');
       }
     );
   });
@@ -78,7 +78,7 @@ describe('Resources are fetched over http/2', () => {
       computedCache: new Map(),
     }).then(auditResult => {
       assert.equal(auditResult.score, 0);
-      assert.ok(auditResult.displayValue.match('1 request not'));
+      expect(auditResult.displayValue).toBeDisplayString('1 request not served via HTTP/2');
       // Protocol is http/1.0 which we don't mark as fetched fetchedViaServiceWorker on line 73.
       assert.equal(
         auditResult.details.items[0].url,
diff --git a/lighthouse-core/test/audits/is-on-https-test.js b/lighthouse-core/test/audits/is-on-https-test.js
index d53717d89..7bdc837eb 100644
--- a/lighthouse-core/test/audits/is-on-https-test.js
+++ b/lighthouse-core/test/audits/is-on-https-test.js
@@ -28,7 +28,7 @@ describe('Security: HTTPS audit', () => {
       {url: 'https://google.com/', parsedURL: {scheme: 'https', host: 'google.com'}},
     ]), {computedCache: new Map()}).then(result => {
       assert.strictEqual(result.score, 0);
-      assert.ok(result.displayValue.includes('requests found'));
+      expect(result.displayValue).toBeDisplayString('2 insecure requests found');
       assert.strictEqual(result.extendedInfo.value.length, 2);
     });
   });
@@ -40,7 +40,7 @@ describe('Security: HTTPS audit', () => {
       {url: 'https://google.com/', parsedURL: {scheme: 'https', host: 'google.com'}},
     ]), {computedCache: new Map()}).then(result => {
       assert.strictEqual(result.score, 0);
-      assert.ok(result.displayValue.includes('request found'));
+      expect(result.displayValue).toBeDisplayString('1 insecure request found');
       assert.deepEqual(result.extendedInfo.value[0], {url: 'http://insecure.com/image.jpeg'});
     });
   });
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 3e22f468b..ea1424095 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -2637,7 +2637,7 @@
     "doctype": {
       "id": "doctype",
       "title": "Page has the HTML doctype",
-      "description": "Specifying a doctype prevents the browser from switching to quirks-mode.Read more on the [MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)",
+      "description": "Specifying a doctype prevents the browser from switching to quirks-mode. Read more on the [MDN Web Docs page](https://developer.mozilla.org/en-US/docs/Glossary/Doctype)",
       "score": 1,
       "scoreDisplayMode": "binary"
     },
@@ -2746,7 +2746,7 @@
     "geolocation-on-start": {
       "id": "geolocation-on-start",
       "title": "Requests the geolocation permission on page load",
-      "description": "Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to user gestures instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).",
+      "description": "Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to a user action instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).",
       "score": 0,
       "scoreDisplayMode": "binary",
       "details": {
@@ -5117,6 +5117,23 @@
       "warningHeader": "Warnings: "
     },
     "icuMessagePaths": {
+      "lighthouse-core/audits/is-on-https.js | failureTitle": [
+        "audits[is-on-https].title"
+      ],
+      "lighthouse-core/audits/is-on-https.js | description": [
+        "audits[is-on-https].description"
+      ],
+      "lighthouse-core/audits/is-on-https.js | displayValue": [
+        {
+          "values": {
+            "itemCount": 1
+          },
+          "path": "audits[is-on-https].displayValue"
+        }
+      ],
+      "lighthouse-core/audits/is-on-https.js | columnInsecureURL": [
+        "audits[is-on-https].details.headings[0].text"
+      ],
       "lighthouse-core/audits/metrics/first-contentful-paint.js | title": [
         "audits[first-contentful-paint].title"
       ],
@@ -5223,6 +5240,35 @@
       "lighthouse-core/audits/metrics/max-potential-fid.js | description": [
         "audits[max-potential-fid].description"
       ],
+      "lighthouse-core/audits/errors-in-console.js | failureTitle": [
+        "audits[errors-in-console].title"
+      ],
+      "lighthouse-core/audits/errors-in-console.js | description": [
+        "audits[errors-in-console].description"
+      ],
+      "lighthouse-core/lib/i18n/i18n.js | columnURL": [
+        "audits[errors-in-console].details.headings[0].text",
+        "audits[image-aspect-ratio].details.headings[1].text",
+        "audits.deprecations.details.headings[1].text",
+        "audits[bootup-time].details.headings[0].text",
+        "audits[network-rtt].details.headings[0].text",
+        "audits[network-server-latency].details.headings[0].text",
+        "audits[uses-long-cache-ttl].details.headings[0].text",
+        "audits[total-byte-weight].details.headings[0].text",
+        "audits[render-blocking-resources].details.headings[0].label",
+        "audits[unminified-javascript].details.headings[0].label",
+        "audits[uses-webp-images].details.headings[1].label",
+        "audits[uses-text-compression].details.headings[0].label",
+        "audits[external-anchors-use-rel-noopener].details.headings[0].text",
+        "audits[geolocation-on-start].details.headings[0].text",
+        "audits[no-document-write].details.headings[0].text",
+        "audits[notification-on-start].details.headings[0].text",
+        "audits[uses-http2].details.headings[0].text",
+        "audits[uses-passive-event-listeners].details.headings[0].text"
+      ],
+      "lighthouse-core/audits/errors-in-console.js | columnDesc": [
+        "audits[errors-in-console].details.headings[1].text"
+      ],
       "lighthouse-core/audits/time-to-first-byte.js | title": [
         "audits[time-to-first-byte].title"
       ],
@@ -5281,6 +5327,38 @@
       "lighthouse-core/audits/apple-touch-icon.js | description": [
         "audits[apple-touch-icon].description"
       ],
+      "lighthouse-core/audits/image-aspect-ratio.js | failureTitle": [
+        "audits[image-aspect-ratio].title"
+      ],
+      "lighthouse-core/audits/image-aspect-ratio.js | description": [
+        "audits[image-aspect-ratio].description"
+      ],
+      "lighthouse-core/audits/image-aspect-ratio.js | columnDisplayed": [
+        "audits[image-aspect-ratio].details.headings[2].text"
+      ],
+      "lighthouse-core/audits/image-aspect-ratio.js | columnActual": [
+        "audits[image-aspect-ratio].details.headings[3].text"
+      ],
+      "lighthouse-core/audits/deprecations.js | failureTitle": [
+        "audits.deprecations.title"
+      ],
+      "lighthouse-core/audits/deprecations.js | description": [
+        "audits.deprecations.description"
+      ],
+      "lighthouse-core/audits/deprecations.js | displayValue": [
+        {
+          "values": {
+            "itemCount": 3
+          },
+          "path": "audits.deprecations.displayValue"
+        }
+      ],
+      "lighthouse-core/audits/deprecations.js | columnDeprecate": [
+        "audits.deprecations.details.headings[0].text"
+      ],
+      "lighthouse-core/audits/deprecations.js | columnLine": [
+        "audits.deprecations.details.headings[2].text"
+      ],
       "lighthouse-core/audits/mainthread-work-breakdown.js | title": [
         "audits[mainthread-work-breakdown].title"
       ],
@@ -5301,17 +5379,6 @@
       "lighthouse-core/audits/bootup-time.js | description": [
         "audits[bootup-time].description"
       ],
-      "lighthouse-core/lib/i18n/i18n.js | columnURL": [
-        "audits[bootup-time].details.headings[0].text",
-        "audits[network-rtt].details.headings[0].text",
-        "audits[network-server-latency].details.headings[0].text",
-        "audits[uses-long-cache-ttl].details.headings[0].text",
-        "audits[total-byte-weight].details.headings[0].text",
-        "audits[render-blocking-resources].details.headings[0].label",
-        "audits[unminified-javascript].details.headings[0].label",
-        "audits[uses-webp-images].details.headings[1].label",
-        "audits[uses-text-compression].details.headings[0].label"
-      ],
       "lighthouse-core/audits/bootup-time.js | columnTotal": [
         "audits[bootup-time].details.headings[1].text"
       ],
@@ -5828,6 +5895,26 @@
       "lighthouse-core/audits/byte-efficiency/efficient-animated-content.js | description": [
         "audits[efficient-animated-content].description"
       ],
+      "lighthouse-core/audits/dobetterweb/appcache-manifest.js | failureTitle": [
+        "audits[appcache-manifest].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/appcache-manifest.js | description": [
+        "audits[appcache-manifest].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/appcache-manifest.js | displayValue": [
+        {
+          "values": {
+            "AppCacheManifest": "clock.appcache"
+          },
+          "path": "audits[appcache-manifest].displayValue"
+        }
+      ],
+      "lighthouse-core/audits/dobetterweb/doctype.js | title": [
+        "audits.doctype.title"
+      ],
+      "lighthouse-core/audits/dobetterweb/doctype.js | description": [
+        "audits.doctype.description"
+      ],
       "lighthouse-core/audits/dobetterweb/dom-size.js | title": [
         "audits[dom-size].title"
       ],
@@ -5860,6 +5947,120 @@
       "lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMWidth": [
         "audits[dom-size].details.items[2].statistic"
       ],
+      "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | failureTitle": [
+        "audits[external-anchors-use-rel-noopener].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | description": [
+        "audits[external-anchors-use-rel-noopener].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | warning": [
+        {
+          "values": {
+            "anchorHTML": "<a target=\"blank\">Hello</a>"
+          },
+          "path": "audits[external-anchors-use-rel-noopener].warnings[0]"
+        }
+      ],
+      "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | columnTarget": [
+        "audits[external-anchors-use-rel-noopener].details.headings[1].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js | columnRel": [
+        "audits[external-anchors-use-rel-noopener].details.headings[2].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/geolocation-on-start.js | failureTitle": [
+        "audits[geolocation-on-start].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/geolocation-on-start.js | description": [
+        "audits[geolocation-on-start].description"
+      ],
+      "lighthouse-core/lib/i18n/i18n.js | columnLocation": [
+        "audits[geolocation-on-start].details.headings[1].text",
+        "audits[no-document-write].details.headings[1].text",
+        "audits[notification-on-start].details.headings[1].text",
+        "audits[uses-passive-event-listeners].details.headings[1].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-document-write.js | failureTitle": [
+        "audits[no-document-write].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-document-write.js | description": [
+        "audits[no-document-write].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | failureTitle": [
+        "audits[no-vulnerable-libraries].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | description": [
+        "audits[no-vulnerable-libraries].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | displayValue": [
+        {
+          "values": {
+            "itemCount": 2
+          },
+          "path": "audits[no-vulnerable-libraries].displayValue"
+        }
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnVersion": [
+        "audits[no-vulnerable-libraries].details.headings[0].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnVuln": [
+        "audits[no-vulnerable-libraries].details.headings[1].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | columnSeverity": [
+        "audits[no-vulnerable-libraries].details.headings[2].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js | rowSeverityMedium": [
+        "audits[no-vulnerable-libraries].details.items[0].highestSeverity"
+      ],
+      "lighthouse-core/audits/dobetterweb/js-libraries.js | title": [
+        "audits[js-libraries].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/js-libraries.js | description": [
+        "audits[js-libraries].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/js-libraries.js | columnName": [
+        "audits[js-libraries].details.headings[0].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/js-libraries.js | columnVersion": [
+        "audits[js-libraries].details.headings[1].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/notification-on-start.js | failureTitle": [
+        "audits[notification-on-start].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/notification-on-start.js | description": [
+        "audits[notification-on-start].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | failureTitle": [
+        "audits[password-inputs-can-be-pasted-into].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | description": [
+        "audits[password-inputs-can-be-pasted-into].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js | columnFailingElem": [
+        "audits[password-inputs-can-be-pasted-into].details.headings[0].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-http2.js | failureTitle": [
+        "audits[uses-http2].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-http2.js | description": [
+        "audits[uses-http2].description"
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-http2.js | displayValue": [
+        {
+          "values": {
+            "itemCount": 15
+          },
+          "path": "audits[uses-http2].displayValue"
+        }
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-http2.js | columnProtocol": [
+        "audits[uses-http2].details.headings[1].text"
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js | failureTitle": [
+        "audits[uses-passive-event-listeners].title"
+      ],
+      "lighthouse-core/audits/dobetterweb/uses-passive-event-listeners.js | description": [
+        "audits[uses-passive-event-listeners].description"
+      ],
       "lighthouse-core/audits/seo/meta-description.js | failureTitle": [
         "audits[meta-description].title"
       ],
@@ -5960,6 +6161,9 @@
       "lighthouse-core/config/default-config.js | a11yCategoryManualDescription": [
         "categories.accessibility.manualDescription"
       ],
+      "lighthouse-core/config/default-config.js | bestPracticesCategoryTitle": [
+        "categories[best-practices].title"
+      ],
       "lighthouse-core/config/default-config.js | seoCategoryTitle": [
         "categories.seo.title"
       ],