Skip to content

Commit

Permalink
core(i18n): initial utility library (#5644)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhulce authored and paulirish committed Jul 17, 2018
1 parent e94be2c commit 589162b
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 23 deletions.
5 changes: 5 additions & 0 deletions lighthouse-cli/cli-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function getFlags(manualArgv) {
],
'Configuration:')
.describe({
'locale': 'The locale/language the report should be formatted in',
'enable-error-reporting':
'Enables error reporting, overriding any saved preference. --no-enable-error-reporting will do the opposite. More: https://git.io/vFFTO',
'blocked-url-patterns': 'Block any network requests to the specified URL patterns',
Expand Down Expand Up @@ -118,6 +119,10 @@ function getFlags(manualArgv) {
'disable-storage-reset', 'disable-device-emulation', 'save-assets', 'list-all-audits',
'list-trace-categories', 'view', 'verbose', 'quiet', 'help',
])
.choices('locale', [
'en-US', // English
'en-XA', // Accented English, good for testing
])
.choices('output', printer.getValidOutputOptions())
.choices('throttling-method', ['devtools', 'provided', 'simulate'])
.choices('preset', ['full', 'perf', 'mixed-content'])
Expand Down
34 changes: 22 additions & 12 deletions lighthouse-core/audits/byte-efficiency/render-blocking-resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'use strict';

const Audit = require('../audit');
const i18n = require('../../lib/i18n');
const BaseNode = require('../../lib/dependency-graph/base-node');
const ByteEfficiencyAudit = require('./byte-efficiency-audit');
const UnusedCSS = require('./unused-css-rules');
Expand All @@ -25,6 +26,19 @@ const NetworkRequest = require('../../lib/network-request');
// to possibly be non-blocking (and they have minimal impact anyway).
const MINIMUM_WASTED_MS = 50;

const UIStrings = {
title: 'Eliminate render-blocking resources',
description: 'Resources are blocking the first paint of your page. Consider ' +
'delivering critical JS/CSS inline and deferring all non-critical ' +
'JS/styles. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
displayValue: `{itemCount, plural,
one {1 resource}
other {# resources}
} delayed first paint by {timeInMs, number, milliseconds} ms`,
};

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

/**
* Given a simulation's nodeTimings, return an object with the nodes/timing keyed by network URL
* @param {LH.Gatherer.Simulation.Result['nodeTimings']} nodeTimings
Expand Down Expand Up @@ -52,12 +66,9 @@ class RenderBlockingResources extends Audit {
static get meta() {
return {
id: 'render-blocking-resources',
title: 'Eliminate render-blocking resources',
title: str_(UIStrings.title),
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
description:
'Resources are blocking the first paint of your page. Consider ' +
'delivering critical JS/CSS inline and deferring all non-critical ' +
'JS/styles. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).',
description: str_(UIStrings.description),
// This audit also looks at CSSUsage but has a graceful fallback if it failed, so do not mark
// it as a "requiredArtifact".
// TODO: look into adding an `optionalArtifacts` property that captures this
Expand Down Expand Up @@ -198,17 +209,15 @@ class RenderBlockingResources extends Audit {
const {results, wastedMs} = await RenderBlockingResources.computeResults(artifacts, context);

let displayValue = '';
if (results.length > 1) {
displayValue = `${results.length} resources delayed first paint by ${wastedMs}ms`;
} else if (results.length === 1) {
displayValue = `${results.length} resource delayed first paint by ${wastedMs}ms`;
if (results.length > 0) {
displayValue = str_(UIStrings.displayValue, {timeInMs: wastedMs, itemCount: results.length});
}

/** @type {LH.Result.Audit.OpportunityDetails['headings']} */
const headings = [
{key: 'url', valueType: 'url', label: 'URL'},
{key: 'totalBytes', valueType: 'bytes', label: 'Size (KB)'},
{key: 'wastedMs', valueType: 'timespanMs', label: 'Download Time (ms)'},
{key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
{key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnSize)},
{key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedTime)},
];

const details = Audit.makeOpportunityDetails(headings, results, wastedMs);
Expand All @@ -223,3 +232,4 @@ class RenderBlockingResources extends Audit {
}

module.exports = RenderBlockingResources;
module.exports.UIStrings = UIStrings;
18 changes: 13 additions & 5 deletions lighthouse-core/audits/metrics/interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
'use strict';

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

const UIStrings = {
title: 'Time to Interactive',
description: 'Interactive marks the time at which the page is fully interactive. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive).',
};

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

/**
* @fileoverview This audit identifies the time the page is "consistently interactive".
Expand All @@ -21,9 +29,8 @@ class InteractiveMetric extends Audit {
static get meta() {
return {
id: 'interactive',
title: 'Time to Interactive',
description: 'Interactive marks the time at which the page is fully interactive. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive).',
title: str_(UIStrings.title),
description: str_(UIStrings.description),
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
requiredArtifacts: ['traces', 'devtoolsLogs'],
};
Expand Down Expand Up @@ -69,7 +76,7 @@ class InteractiveMetric extends Audit {
context.options.scoreMedian
),
rawValue: timeInMs,
displayValue: [Util.MS_DISPLAY_VALUE, timeInMs],
displayValue: str_(i18n.UIStrings.ms, {timeInMs}),
extendedInfo: {
value: extendedInfo,
},
Expand All @@ -78,3 +85,4 @@ class InteractiveMetric extends Audit {
}

module.exports = InteractiveMetric;
module.exports.UIStrings = UIStrings;
1 change: 1 addition & 0 deletions lighthouse-core/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const defaultSettings = {

// the following settings have no defaults but we still want ensure that `key in settings`
// in config will work in a typechecked way
locale: null, // default determined by the intl library
blockedUrlPatterns: null,
additionalTraceCategories: null,
extraHeaders: null,
Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const Runner = require('./runner');
const log = require('lighthouse-logger');
const ChromeProtocol = require('./gather/connections/cri.js');
const i18n = require('./lib/i18n');
const Config = require('./config/config');

/*
Expand All @@ -34,6 +35,7 @@ const Config = require('./config/config');
async function lighthouse(url, flags, configJSON) {
// TODO(bckenny): figure out Flags types.
flags = flags || /** @type {LH.Flags} */ ({});
i18n.setLocale(flags.locale);

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

const path = require('path');
const MessageFormat = require('intl-messageformat').default;
const MessageParser = require('intl-messageformat-parser');
const LOCALES = require('./locales');

let locale = MessageFormat.defaultLocale;

const LH_ROOT = path.join(__dirname, '../../');

try {
// Node usually doesn't come with the locales we want built-in, so load the polyfill.
// In browser environments, we won't need the polyfill, and this will throw so wrap in try/catch.

// @ts-ignore
const IntlPolyfill = require('intl');
// @ts-ignore
Intl.NumberFormat = IntlPolyfill.NumberFormat;
// @ts-ignore
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
} catch (_) {}

const UIStrings = {
ms: '{timeInMs, number, milliseconds}\xa0ms',
columnURL: 'URL',
columnSize: 'Size (KB)',
columnWastedTime: 'Potential Savings (ms)',
};

const formats = {
number: {
milliseconds: {
maximumFractionDigits: 0,
},
},
};

/**
* @param {string} msg
* @param {Record<string, *>} values
*/
function preprocessMessageValues(msg, values) {
const parsed = MessageParser.parse(msg);
// Round all milliseconds to 10s place
parsed.elements
.filter(el => el.format && el.format.style === 'milliseconds')
.forEach(el => (values[el.id] = Math.round(values[el.id] / 10) * 10));

// Replace all the bytes with KB
parsed.elements
.filter(el => el.format && el.format.style === 'bytes')
.forEach(el => (values[el.id] = values[el.id] / 1024));
}

module.exports = {
UIStrings,
/**
* @param {string} filename
* @param {Record<string, string>} fileStrings
*/
createStringFormatter(filename, fileStrings) {
const mergedStrings = {...UIStrings, ...fileStrings};

/** @param {string} msg @param {*} [values] */
const formatFn = (msg, values) => {
const keyname = Object.keys(mergedStrings).find(key => mergedStrings[key] === msg);
if (!keyname) throw new Error(`Could not locate: ${msg}`);
preprocessMessageValues(msg, values);

const filenameToLookup = keyname in UIStrings ? __filename : filename;
const lookupKey = path.relative(LH_ROOT, filenameToLookup) + '!#' + keyname;
const localeStrings = LOCALES[locale] || {};
const localeString = localeStrings[lookupKey] && localeStrings[lookupKey].message;
// fallback to the original english message if we couldn't find a message in the specified locale
// better to have an english message than no message at all, in some number cases it won't even matter
const messageForMessageFormat = localeString || msg;
// when using accented english, force the use of a different locale for number formatting
const localeForMessageFormat = locale === 'en-XA' ? 'de-DE' : locale;

const formatter = new MessageFormat(messageForMessageFormat, localeForMessageFormat, formats);
return formatter.format(values);
};

return formatFn;
},
/**
* @param {LH.Locale|null} [newLocale]
*/
setLocale(newLocale) {
if (!newLocale) return;
locale = newLocale;
},
};
29 changes: 29 additions & 0 deletions lighthouse-core/lib/locales/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#title": {
"message": "Eliminate render-blocking resources"
},
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#description": {
"message": "Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources)."
},
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#displayValue": {
"message": "{itemCount, plural,\n one {1 resource}\n other {# resources}\n } delayed first paint by {timeInMs, number, milliseconds} ms"
},
"lighthouse-core/audits/metrics/interactive.js!#title": {
"message": "Time to Interactive"
},
"lighthouse-core/audits/metrics/interactive.js!#description": {
"message": "Interactive marks the time at which the page is fully interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive)."
},
"lighthouse-core/lib/i18n.js!#ms": {
"message": "{timeInMs, number, milliseconds} ms"
},
"lighthouse-core/lib/i18n.js!#columnURL": {
"message": "URL"
},
"lighthouse-core/lib/i18n.js!#columnSize": {
"message": "Size (KB)"
},
"lighthouse-core/lib/i18n.js!#columnWastedTime": {
"message": "Potential Savings (ms)"
}
}
29 changes: 29 additions & 0 deletions lighthouse-core/lib/locales/en-XA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#title": {
"message": "Êĺîḿîńât́ê ŕêńd̂ér̂-b́l̂óĉḱîńĝ ŕêśôúr̂ćêś"
},
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#description": {
"message": "R̂éŝóûŕĉéŝ ár̂é b̂ĺôćk̂ín̂ǵ t̂h́ê f́îŕŝt́ p̂áîńt̂ óf̂ ýôúr̂ ṕâǵê. Ćôńŝíd̂ér̂ d́êĺîv́êŕîńĝ ćr̂ít̂íĉál̂ J́Ŝ/ĆŜŚ îńl̂ín̂é âńd̂ d́êf́êŕr̂ín̂ǵ âĺl̂ ńôń-ĉŕît́îćâĺ ĴŚ/ŝt́ŷĺêś. [L̂éâŕn̂ ḿôŕê](h́t̂t́p̂ś://d̂év̂él̂óp̂ér̂ś.ĝóôǵl̂é.ĉóm̂/ẃêb́/t̂óôĺŝ/ĺîǵĥt́ĥóûśê/áûd́ît́ŝ/b́l̂óĉḱîńĝ-ŕêśôúr̂ćêś)."
},
"lighthouse-core/audits/byte-efficiency/render-blocking-resources.js!#displayValue": {
"message": "{itemCount, plural,\n one {1 resource}\n other {# resources}\n } d̂él̂áŷéd̂ f́îŕŝt́ p̂áîńt̂ b́ŷ {timeInMs, number, milliseconds} ḿŝ"
},
"lighthouse-core/audits/metrics/interactive.js!#title": {
"message": "T̂ím̂é t̂ó Îńt̂ér̂áĉt́îv́ê"
},
"lighthouse-core/audits/metrics/interactive.js!#description": {
"message": "Îńt̂ér̂áĉt́îv́ê ḿâŕk̂ś t̂h́ê t́îḿê át̂ ẃĥíĉh́ t̂h́ê ṕâǵê íŝ f́ûĺl̂ý îńt̂ér̂áĉt́îv́ê. [Ĺêár̂ń m̂ór̂é](ĥt́t̂ṕŝ://d́êv́êĺôṕêŕŝ.ǵôóĝĺê.ćôḿ/ŵéb̂/t́ôól̂ś/l̂íĝh́t̂h́ôúŝé/âúd̂ít̂ś/ĉón̂śîśt̂én̂t́l̂ý-îńt̂ér̂áĉt́îv́ê)."
},
"lighthouse-core/lib/i18n.js!#ms": {
"message": "{timeInMs, number, milliseconds} m̂ś"
},
"lighthouse-core/lib/i18n.js!#columnURL": {
"message": "ÛŔL̂"
},
"lighthouse-core/lib/i18n.js!#columnSize": {
"message": "Ŝíẑé (K̂B́)"
},
"lighthouse-core/lib/i18n.js!#columnWastedTime": {
"message": "P̂ót̂én̂t́îál̂ Śâv́îńĝś (m̂ś)"
}
}
11 changes: 11 additions & 0 deletions lighthouse-core/lib/locales/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @license Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
// @ts-nocheck
'use strict';

module.exports = {
'en-XA': require('./en-XA.json'),
};
Loading

0 comments on commit 589162b

Please sign in to comment.