Skip to content

Commit

Permalink
core(i18n): improve Intl polyfill and use it in Util (#9584)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendankenny authored and paulirish committed Nov 6, 2019
1 parent b8603e2 commit 556f5c0
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 25 deletions.
28 changes: 16 additions & 12 deletions lighthouse-core/lib/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@ const MESSAGE_INSTANCE_ID_REGEX = /(.* \| .*) # (\d+)$/;
const MESSAGE_INSTANCE_ID_QUICK_REGEX = / # \d+$/;

(() => {
// Node usually doesn't come with the locales we want built-in, so load the polyfill if we can.
// Node without full-icu doesn't come with the locales we want built-in. Load the polyfill if needed.
// See https://nodejs.org/api/intl.html#intl_options_for_building_node_js

try {
// @ts-ignore
const IntlPolyfill = require('intl');
// In browser environments where we don't need the polyfill, this won't exist
if (!IntlPolyfill.NumberFormat) return;
// Conditionally polyfills itself. Bundler removes this dep, so this will be a no-op in browsers.
// @ts-ignore
require('intl-pluralrules');

// @ts-ignore
const IntlPolyfill = require('intl');

// The bundler also removes this dep, so there's nothing to do if it's empty.
if (!IntlPolyfill.NumberFormat) return;

// Check if global implementation supports a minimum set of locales.
const minimumLocales = ['en', 'es', 'ru', 'zh'];
const supportedLocales = Intl.NumberFormat.supportedLocalesOf(minimumLocales);

if (supportedLocales.length !== minimumLocales.length) {
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;

// Intl.PluralRules polyfilled on require().
// @ts-ignore
require('intl-pluralrules');
} catch (_) {
log.warn('i18n', 'Failed to install `intl` polyfill');
}
})();

Expand Down
22 changes: 14 additions & 8 deletions lighthouse-core/report/html/renderer/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Util {
*/
static formatNumber(number, granularity = 0.1) {
const coarseValue = Math.round(number / granularity) * granularity;
return coarseValue.toLocaleString(Util.numberDateLocale);
return Util.numberFormatter.format(coarseValue);
}

/**
Expand All @@ -201,8 +201,7 @@ class Util {
* @return {string}
*/
static formatBytesToKB(size, granularity = 0.1) {
const kbs = (Math.round(size / 1024 / granularity) * granularity)
.toLocaleString(Util.numberDateLocale);
const kbs = Util.numberFormatter.format(Math.round(size / 1024 / granularity) * granularity);
return `${kbs}${NBSP}KB`;
}

Expand All @@ -213,7 +212,7 @@ class Util {
*/
static formatMilliseconds(ms, granularity = 10) {
const coarseTime = Math.round(ms / granularity) * granularity;
return `${coarseTime.toLocaleString(Util.numberDateLocale)}${NBSP}ms`;
return `${Util.numberFormatter.format(coarseTime)}${NBSP}ms`;
}

/**
Expand All @@ -223,7 +222,7 @@ class Util {
*/
static formatSeconds(ms, granularity = 0.1) {
const coarseTime = Math.round(ms / 1000 / granularity) * granularity;
return `${coarseTime.toLocaleString(Util.numberDateLocale)}${NBSP}s`;
return `${Util.numberFormatter.format(coarseTime)}${NBSP}s`;
}

/**
Expand Down Expand Up @@ -556,10 +555,11 @@ class Util {
* @param {LH.Locale} locale
*/
static setNumberDateLocale(locale) {
Util.numberDateLocale = locale;

// When testing, use a locale with more exciting numeric formatting
if (Util.numberDateLocale === 'en-XA') Util.numberDateLocale = 'de';
if (locale === 'en-XA') locale = 'de';

Util.numberDateLocale = locale;
Util.numberFormatter = new Intl.NumberFormat(locale);
}

/**
Expand Down Expand Up @@ -618,6 +618,12 @@ class Util {
*/
Util.numberDateLocale = 'en';

/**
* This value stays in sync with Util.numberDateLocale.
* @type {Intl.NumberFormat}
*/
Util.numberFormatter = new Intl.NumberFormat(Util.numberDateLocale);

/**
* Report-renderer-specific strings.
* @type {LH.I18NRendererStrings}
Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/test/lib/i18n/swap-locale-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ describe('swap-locale', () => {
// Renderer formatted strings
expect(lhrEn.i18n.rendererFormattedStrings.labDataTitle).toEqual('Lab Data');
expect(lhrEs.i18n.rendererFormattedStrings.labDataTitle).toEqual('Datos de prueba');

// Formatted numbers in placeholders.
expect(lhrEn.audits['render-blocking-resources'].displayValue)
.toEqual('Potential savings of 1,130 ms');
expect(lhrEs.audits['render-blocking-resources'].displayValue)
.toEqual('Ahorro potencial de 1.130 ms');
});

it('can roundtrip back to english correctly', () => {
Expand Down
30 changes: 25 additions & 5 deletions lighthouse-core/test/report/html/renderer/util-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const assert = require('assert');
const Util = require('../../../../report/html/renderer/util.js');
const sampleResult = require('../../../results/sample_v2.json');

// Require i18n to make sure Intl is polyfilled in Node without full-icu for testing.
// When Util is run in a browser, Intl will be supplied natively (IE11+).
// eslint-disable-next-line no-unused-vars
const i18n = require('../../../../lib/i18n/i18n.js');

const NBSP = '\xa0';

/* eslint-env jest */
Expand Down Expand Up @@ -47,25 +52,40 @@ describe('util helpers', () => {
assert.equal(Util.formatDuration(28 * 60 * 60 * 1000 + 5000), `1${NBSP}d 4${NBSP}h 5${NBSP}s`);
});

// TODO: need ICU support in node on Travis/Appveyor
it.skip('formats based on locale', () => {
it('formats numbers based on locale', () => {
// Requires full-icu or Intl polyfill.
const number = 12346.858558;

const originalLocale = Util.numberDateLocale;
Util.setNumberDateLocale('de');
assert.strictEqual(Util.formatNumber(number), '12.346,9');
Util.setNumberDateLocale(originalLocale); // reset
assert.strictEqual(Util.formatBytesToKB(number), `12,1${NBSP}KB`);
assert.strictEqual(Util.formatMilliseconds(number), `12.350${NBSP}ms`);
assert.strictEqual(Util.formatSeconds(number), `12,3${NBSP}s`);

Util.setNumberDateLocale(originalLocale); // reset locale
assert.strictEqual(Util.formatNumber(number), '12,346.9');
assert.strictEqual(Util.formatBytesToKB(number), `12.1${NBSP}KB`);
assert.strictEqual(Util.formatMilliseconds(number), `12,350${NBSP}ms`);
assert.strictEqual(Util.formatSeconds(number), `12.3${NBSP}s`);
});

it.skip('uses decimal comma with en-XA test locale', () => {
it('uses decimal comma with en-XA test locale', () => {
// Requires full-icu or Intl polyfill.
const number = 12346.858558;

const originalLocale = Util.numberDateLocale;
Util.setNumberDateLocale('en-XA');
assert.strictEqual(Util.formatNumber(number), '12.346,9');
Util.setNumberDateLocale(originalLocale); // reset
assert.strictEqual(Util.formatBytesToKB(number), `12,1${NBSP}KB`);
assert.strictEqual(Util.formatMilliseconds(number), `12.350${NBSP}ms`);
assert.strictEqual(Util.formatSeconds(number), `12,3${NBSP}s`);

Util.setNumberDateLocale(originalLocale); // reset locale
assert.strictEqual(Util.formatNumber(number), '12,346.9');
assert.strictEqual(Util.formatBytesToKB(number), `12.1${NBSP}KB`);
assert.strictEqual(Util.formatMilliseconds(number), `12,350${NBSP}ms`);
assert.strictEqual(Util.formatSeconds(number), `12.3${NBSP}s`);
});

it('calculates a score ratings', () => {
Expand Down

0 comments on commit 556f5c0

Please sign in to comment.