Skip to content

Commit

Permalink
report: remove Util.UIStrings mutation, add I18n renderer class (#10153)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Jan 7, 2020
1 parent 5dcb666 commit 81c6e92
Show file tree
Hide file tree
Showing 32 changed files with 327 additions and 275 deletions.
15 changes: 8 additions & 7 deletions lighthouse-core/audits/dobetterweb/dom-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
'use strict';

const Audit = require('../audit.js');
const Util = require('../../report/html/renderer/util.js');
const i18n = require('../../lib/i18n/i18n.js');
const I18n = require('../../report/html/renderer/i18n.js');
const i18n_ = require('../../lib/i18n/i18n.js');

const UIStrings = {
/** 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. */
Expand Down Expand Up @@ -44,8 +44,7 @@ const UIStrings = {
statisticDOMWidth: 'Maximum Child Elements',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

const str_ = i18n_.createMessageInstanceIdFn(__filename, UIStrings);

class DOMSize extends Audit {
/**
Expand Down Expand Up @@ -97,28 +96,30 @@ class DOMSize extends Audit {
{key: 'value', itemType: 'numeric', text: str_(UIStrings.columnValue)},
];

const i18n = new I18n(context.settings.locale);

/** @type {LH.Audit.Details.Table['items']} */
const items = [
{
statistic: str_(UIStrings.statisticDOMElements),
element: '',
value: Util.formatNumber(stats.totalBodyElements),
value: i18n.formatNumber(stats.totalBodyElements),
},
{
statistic: str_(UIStrings.statisticDOMDepth),
element: {
type: 'code',
value: stats.depth.snippet,
},
value: Util.formatNumber(stats.depth.max),
value: i18n.formatNumber(stats.depth.max),
},
{
statistic: str_(UIStrings.statisticDOMWidth),
element: {
type: 'code',
value: stats.width.snippet,
},
value: Util.formatNumber(stats.width.max),
value: i18n.formatNumber(stats.width.max),
},
];

Expand Down
5 changes: 3 additions & 2 deletions lighthouse-core/audits/mixed-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

const Audit = require('./audit.js');
const URL = require('../lib/url-shim.js');
const Util = require('../report/html/renderer/util.js');
const I18n = require('../report/html/renderer/i18n.js');
const NetworkRecords = require('../computed/network-records.js');

/**
Expand Down Expand Up @@ -126,7 +126,8 @@ class MixedContent extends Audit {
upgradeableResources.push(resource);
}

const displayValue = `${Util.formatNumber(upgradeableResources.length)}
const i18n = new I18n(context.settings.locale);
const displayValue = `${i18n.formatNumber(upgradeableResources.length)}
${upgradeableResources.length === 1 ? 'request' : 'requests'}`;

/** @type {LH.Audit.Details.Table['headings']} */
Expand Down
6 changes: 4 additions & 2 deletions lighthouse-core/audits/predictive-perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'use strict';

const Audit = require('./audit.js');
const Util = require('../report/html/renderer/util.js');
const I18n = require('../report/html/renderer/i18n.js');

const LanternFcp = require('../computed/metrics/lantern-first-contentful-paint.js');
const LanternFmp = require('../computed/metrics/lantern-first-meaningful-paint.js');
Expand Down Expand Up @@ -92,10 +92,12 @@ class PredictivePerf extends Audit {
SCORING_MEDIAN
);

const i18n = new I18n(context.settings.locale);

return {
score,
numericValue: values.roughEstimateOfTTI,
displayValue: Util.formatMilliseconds(values.roughEstimateOfTTI),
displayValue: i18n.formatMilliseconds(values.roughEstimateOfTTI),
details: {
type: 'debugdata',
// TODO: Consider not nesting values under `items`.
Expand Down
7 changes: 4 additions & 3 deletions lighthouse-core/lib/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,13 @@ function getRendererFormattedStrings(locale) {
if (!localeMessages) throw new Error(`Unsupported locale '${locale}'`);

const icuMessageIds = Object.keys(localeMessages).filter(f => f.includes('core/report/html/'));
/** @type {LH.I18NRendererStrings} */
const strings = {};
const strings = /** @type {LH.I18NRendererStrings} */ ({});
for (const icuMessageId of icuMessageIds) {
const [filename, varName] = icuMessageId.split(' | ');
if (!filename.endsWith('util.js')) throw new Error(`Unexpected message: ${icuMessageId}`);
strings[varName] = localeMessages[icuMessageId].message;

const key = /** @type {keyof LH.I18NRendererStrings} */ (varName);
strings[key] = localeMessages[icuMessageId].message;
}

return strings;
Expand Down
1 change: 1 addition & 0 deletions lighthouse-core/report/html/html-report-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const REPORT_JAVASCRIPT = [
fs.readFileSync(__dirname + '/renderer/performance-category-renderer.js', 'utf8'),
fs.readFileSync(__dirname + '/renderer/pwa-category-renderer.js', 'utf8'),
fs.readFileSync(__dirname + '/renderer/report-renderer.js', 'utf8'),
fs.readFileSync(__dirname + '/renderer/i18n.js', 'utf8'),
].join(';\n');
const REPORT_CSS = fs.readFileSync(__dirname + '/report-styles.css', 'utf8');
const REPORT_TEMPLATES = fs.readFileSync(__dirname + '/templates.html', 'utf8');
Expand Down
19 changes: 10 additions & 9 deletions lighthouse-core/report/html/renderer/category-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ class CategoryRenderer {
*/
get _clumpTitles() {
return {
warning: Util.UIStrings.warningAuditsGroupTitle,
manual: Util.UIStrings.manualAuditsGroupTitle,
passed: Util.UIStrings.passedAuditsGroupTitle,
notApplicable: Util.UIStrings.notApplicableAuditsGroupTitle,
warning: Util.i18n.strings.warningAuditsGroupTitle,
manual: Util.i18n.strings.manualAuditsGroupTitle,
passed: Util.i18n.strings.passedAuditsGroupTitle,
notApplicable: Util.i18n.strings.notApplicableAuditsGroupTitle,
};
}

Expand All @@ -68,6 +68,7 @@ class CategoryRenderer {
* @return {Element}
*/
populateAuditValues(audit, tmpl) {
const strings = Util.i18n.strings;
const auditEl = this.dom.find('.lh-audit', tmpl);
auditEl.id = audit.result.id;
const scoreDisplayMode = audit.result.scoreDisplayMode;
Expand Down Expand Up @@ -115,10 +116,10 @@ class CategoryRenderer {
if (audit.result.scoreDisplayMode === 'error') {
auditEl.classList.add(`lh-audit--error`);
const textEl = this.dom.find('.lh-audit__display-text', auditEl);
textEl.textContent = Util.UIStrings.errorLabel;
textEl.textContent = strings.errorLabel;
textEl.classList.add('tooltip-boundary');
const tooltip = this.dom.createChildOf(textEl, 'div', 'tooltip tooltip--error');
tooltip.textContent = audit.result.errorMessage || Util.UIStrings.errorMissingAuditInfo;
tooltip.textContent = audit.result.errorMessage || strings.errorMissingAuditInfo;
} else if (audit.result.explanation) {
const explEl = this.dom.createChildOf(titleEl, 'div', 'lh-audit-explanation');
explEl.textContent = audit.result.explanation;
Expand All @@ -128,7 +129,7 @@ class CategoryRenderer {

// Add list of warnings or singular warning
const warningsEl = this.dom.createChildOf(titleEl, 'div', 'lh-warnings');
this.dom.createChildOf(warningsEl, 'span').textContent = Util.UIStrings.warningHeader;
this.dom.createChildOf(warningsEl, 'span').textContent = strings.warningHeader;
if (warnings.length === 1) {
warningsEl.appendChild(this.dom.document().createTextNode(warnings.join('')));
} else {
Expand Down Expand Up @@ -287,7 +288,7 @@ class CategoryRenderer {

const summaryInnerEl = this.dom.find('.lh-audit-group__summary', clumpElement);
const chevronEl = summaryInnerEl.appendChild(this._createChevron());
chevronEl.title = Util.UIStrings.auditGroupExpandTooltip;
chevronEl.title = Util.i18n.strings.auditGroupExpandTooltip;

const headerEl = this.dom.find('.lh-audit-group__header', clumpElement);
const title = this._clumpTitles[clumpId];
Expand Down Expand Up @@ -345,7 +346,7 @@ class CategoryRenderer {
percentageEl.textContent = scoreOutOf100.toString();
if (category.score === null) {
percentageEl.textContent = '?';
percentageEl.title = Util.UIStrings.errorLabel;
percentageEl.title = Util.i18n.strings.errorLabel;
}

this.dom.find('.lh-gauge__label', tmpl).textContent = category.title;
Expand Down
10 changes: 5 additions & 5 deletions lighthouse-core/report/html/renderer/crc-details-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ class CriticalRequestChainRenderer {
if (!segment.hasChildren) {
const {startTime, endTime, transferSize} = segment.node.request;
const span = dom.createElement('span', 'crc-node__chain-duration');
span.textContent = ' - ' + Util.formatMilliseconds((endTime - startTime) * 1000) + ', ';
span.textContent = ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
const span2 = dom.createElement('span', 'crc-node__chain-duration');
span2.textContent = Util.formatBytesToKB(transferSize, 0.01);
span2.textContent = Util.i18n.formatBytesToKB(transferSize, 0.01);

treevalEl.appendChild(span);
treevalEl.appendChild(span2);
Expand Down Expand Up @@ -172,11 +172,11 @@ class CriticalRequestChainRenderer {
const containerEl = dom.find('.lh-crc', tmpl);

// Fill in top summary.
dom.find('.crc-initial-nav', tmpl).textContent = Util.UIStrings.crcInitialNavigation;
dom.find('.crc-initial-nav', tmpl).textContent = Util.i18n.strings.crcInitialNavigation;
dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
Util.UIStrings.crcLongestDurationLabel;
Util.i18n.strings.crcLongestDurationLabel;
dom.find('.lh-crc__longest_duration', tmpl).textContent =
Util.formatMilliseconds(details.longestChain.duration);
Util.i18n.formatMilliseconds(details.longestChain.duration);

// Construct visual tree.
const root = CRCRenderer.initTree(details.chains);
Expand Down
7 changes: 3 additions & 4 deletions lighthouse-core/report/html/renderer/details-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class DetailsRenderer {
* @param {DOM} dom
*/
constructor(dom) {
/** @type {DOM} */
this._dom = dom;
/** @type {ParentNode} */
this._templateContext; // eslint-disable-line no-unused-expressions
Expand Down Expand Up @@ -76,7 +75,7 @@ class DetailsRenderer {
*/
_renderBytes(details) {
// TODO: handle displayUnit once we have something other than 'kb'
const value = Util.formatBytesToKB(details.value, details.granularity);
const value = Util.i18n.formatBytesToKB(details.value, details.granularity);
return this._renderText(value);
}

Expand All @@ -85,9 +84,9 @@ class DetailsRenderer {
* @return {Element}
*/
_renderMilliseconds(details) {
let value = Util.formatMilliseconds(details.value, details.granularity);
let value = Util.i18n.formatMilliseconds(details.value, details.granularity);
if (details.displayUnit === 'duration') {
value = Util.formatDuration(details.value);
value = Util.i18n.formatDuration(details.value);
}

return this._renderText(value);
Expand Down
131 changes: 131 additions & 0 deletions lighthouse-core/report/html/renderer/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @license Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

/* globals self, URL */

// Not named `NBSP` because that creates a duplicate identifier (util.js).
const NBSP2 = '\xa0';

class I18n {
/**
* @param {LH.Locale} locale
* @param {LH.I18NRendererStrings=} strings
*/
constructor(locale, strings) {
// When testing, use a locale with more exciting numeric formatting.
if (locale === 'en-XA') locale = 'de';

this._numberDateLocale = locale;
this._numberFormatter = new Intl.NumberFormat(locale);
this._strings = /** @type {LH.I18NRendererStrings} */ (strings || {});
}

get strings() {
return this._strings;
}

/**
* Format number.
* @param {number} number
* @param {number=} granularity Number of decimal places to include. Defaults to 0.1.
* @return {string}
*/
formatNumber(number, granularity = 0.1) {
const coarseValue = Math.round(number / granularity) * granularity;
return this._numberFormatter.format(coarseValue);
}

/**
* @param {number} size
* @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
* @return {string}
*/
formatBytesToKB(size, granularity = 0.1) {
const kbs = this._numberFormatter.format(Math.round(size / 1024 / granularity) * granularity);
return `${kbs}${NBSP2}KB`;
}

/**
* @param {number} ms
* @param {number=} granularity Controls how coarse the displayed value is, defaults to 10
* @return {string}
*/
formatMilliseconds(ms, granularity = 10) {
const coarseTime = Math.round(ms / granularity) * granularity;
return `${this._numberFormatter.format(coarseTime)}${NBSP2}ms`;
}

/**
* @param {number} ms
* @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1
* @return {string}
*/
formatSeconds(ms, granularity = 0.1) {
const coarseTime = Math.round(ms / 1000 / granularity) * granularity;
return `${this._numberFormatter.format(coarseTime)}${NBSP2}s`;
}

/**
* Format time.
* @param {string} date
* @return {string}
*/
formatDateTime(date) {
/** @type {Intl.DateTimeFormatOptions} */
const options = {
month: 'short', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: 'numeric', timeZoneName: 'short',
};
let formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);

// Force UTC if runtime timezone could not be detected.
// See https://github.com/GoogleChrome/lighthouse/issues/1056
const tz = formatter.resolvedOptions().timeZone;
if (!tz || tz.toLowerCase() === 'etc/unknown') {
options.timeZone = 'UTC';
formatter = new Intl.DateTimeFormat(this._numberDateLocale, options);
}
return formatter.format(new Date(date));
}
/**
* Converts a time in milliseconds into a duration string, i.e. `1d 2h 13m 52s`
* @param {number} timeInMilliseconds
* @return {string}
*/
formatDuration(timeInMilliseconds) {
let timeInSeconds = timeInMilliseconds / 1000;
if (Math.round(timeInSeconds) === 0) {
return 'None';
}

/** @type {Array<string>} */
const parts = [];
const unitLabels = /** @type {Object<string, number>} */ ({
d: 60 * 60 * 24,
h: 60 * 60,
m: 60,
s: 1,
});

Object.keys(unitLabels).forEach(label => {
const unit = unitLabels[label];
const numberOfUnits = Math.floor(timeInSeconds / unit);
if (numberOfUnits > 0) {
timeInSeconds -= numberOfUnits * unit;
parts.push(`${numberOfUnits}\xa0${label}`);
}
});

return parts.join(' ');
}
}

if (typeof module !== 'undefined' && module.exports) {
module.exports = I18n;
} else {
self.I18n = I18n;
}
Loading

0 comments on commit 81c6e92

Please sign in to comment.