diff --git a/lighthouse-cli/cli-flags.js b/lighthouse-cli/cli-flags.js index d0d7b5345f2d..3c4e3345fa47 100644 --- a/lighthouse-cli/cli-flags.js +++ b/lighthouse-cli/cli-flags.js @@ -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', @@ -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']) diff --git a/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js b/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js index 65511d93e707..4abf287c0850 100644 --- a/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +++ b/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js @@ -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'); @@ -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 @@ -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 @@ -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); @@ -223,3 +232,4 @@ class RenderBlockingResources extends Audit { } module.exports = RenderBlockingResources; +module.exports.UIStrings = UIStrings; diff --git a/lighthouse-core/audits/metrics/interactive.js b/lighthouse-core/audits/metrics/interactive.js index 3300082db495..672d73f3e4d6 100644 --- a/lighthouse-core/audits/metrics/interactive.js +++ b/lighthouse-core/audits/metrics/interactive.js @@ -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". @@ -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'], }; @@ -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, }, @@ -78,3 +85,4 @@ class InteractiveMetric extends Audit { } module.exports = InteractiveMetric; +module.exports.UIStrings = UIStrings; diff --git a/lighthouse-core/config/constants.js b/lighthouse-core/config/constants.js index cc14ba761635..7ae3772a5d8d 100644 --- a/lighthouse-core/config/constants.js +++ b/lighthouse-core/config/constants.js @@ -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, diff --git a/lighthouse-core/index.js b/lighthouse-core/index.js index 7b91a8b0dce7..063d40399b89 100644 --- a/lighthouse-core/index.js +++ b/lighthouse-core/index.js @@ -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'); /* @@ -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'; diff --git a/lighthouse-core/lib/i18n.js b/lighthouse-core/lib/i18n.js new file mode 100644 index 000000000000..192fa7f4b927 --- /dev/null +++ b/lighthouse-core/lib/i18n.js @@ -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} 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} 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; + }, +}; diff --git a/lighthouse-core/lib/locales/en-US.json b/lighthouse-core/lib/locales/en-US.json new file mode 100644 index 000000000000..ebe0d04af942 --- /dev/null +++ b/lighthouse-core/lib/locales/en-US.json @@ -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)" + } +} diff --git a/lighthouse-core/lib/locales/en-XA.json b/lighthouse-core/lib/locales/en-XA.json new file mode 100644 index 000000000000..22ae187819ae --- /dev/null +++ b/lighthouse-core/lib/locales/en-XA.json @@ -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̂ś)" + } +} diff --git a/lighthouse-core/lib/locales/index.js b/lighthouse-core/lib/locales/index.js new file mode 100644 index 000000000000..9f70b5f3ca6b --- /dev/null +++ b/lighthouse-core/lib/locales/index.js @@ -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'), +}; diff --git a/lighthouse-core/scripts/i18n/collect-strings.js b/lighthouse-core/scripts/i18n/collect-strings.js new file mode 100644 index 000000000000..72bbae1c29f1 --- /dev/null +++ b/lighthouse-core/scripts/i18n/collect-strings.js @@ -0,0 +1,103 @@ +#!/usr/bin/env node +/** + * @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'; + +/* eslint-disable no-console, max-len */ + +const fs = require('fs'); +const path = require('path'); + +const LH_ROOT = path.join(__dirname, '../../../'); + +const ignoredPathComponents = [ + '/.git', + '/scripts', + '/node_modules', + '/renderer', + '/test/', + '-test.js', +]; + +/** + * @param {string} dir + * @param {Record} strings + */ +function collectAllStringsInDir(dir, strings = {}) { + for (const name of fs.readdirSync(dir)) { + const fullPath = path.join(dir, name); + const relativePath = path.relative(LH_ROOT, fullPath); + if (ignoredPathComponents.some(p => fullPath.includes(p))) continue; + + if (fs.statSync(fullPath).isDirectory()) { + collectAllStringsInDir(fullPath, strings); + } else { + if (name.endsWith('.js')) { + console.log('Collecting from', relativePath); + const mod = require(fullPath); + if (!mod.UIStrings) continue; + for (const [key, value] of Object.entries(mod.UIStrings)) { + strings[`${relativePath}!#${key}`] = value; + } + } + } + } + + return strings; +} + +/** + * @param {Record} strings + */ +function createPsuedoLocaleStrings(strings) { + const psuedoLocalizedStrings = {}; + for (const [key, string] of Object.entries(strings)) { + const psuedoLocalizedString = []; + let braceCount = 0; + let useHatForAccentMark = true; + for (let i = 0; i < string.length; i++) { + const char = string.substr(i, 1); + psuedoLocalizedString.push(char); + // Don't touch the characters inside braces + if (char === '{') { + braceCount++; + } else if (char === '}') { + braceCount--; + } else if (braceCount === 0) { + if (/[a-z]/i.test(char)) { + psuedoLocalizedString.push(useHatForAccentMark ? `\u0302` : `\u0301`); + useHatForAccentMark = !useHatForAccentMark; + } + } + } + + psuedoLocalizedStrings[key] = psuedoLocalizedString.join(''); + } + + return psuedoLocalizedStrings; +} + +/** + * @param {LH.Locale} locale + * @param {Record} strings + */ +function writeStringsToLocaleFormat(locale, strings) { + const fullPath = path.join(LH_ROOT, `lighthouse-core/lib/locales/${locale}.json`); + const output = {}; + for (const [key, message] of Object.entries(strings)) { + output[key] = {message}; + } + + fs.writeFileSync(fullPath, JSON.stringify(output, null, 2)); +} + +const strings = collectAllStringsInDir(path.join(LH_ROOT, 'lighthouse-core')); +const psuedoLocalizedStrings = createPsuedoLocaleStrings(strings); +console.log('Collected!'); + +writeStringsToLocaleFormat('en-US', strings); +writeStringsToLocaleFormat('en-XA', psuedoLocalizedStrings); +console.log('Written to disk!'); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 935b0e736b70..48c4c77ee4eb 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -276,10 +276,7 @@ "score": 0.78, "scoreDisplayMode": "numeric", "rawValue": 4927.278, - "displayValue": [ - "%10d ms", - 4927.278 - ] + "displayValue": "4,930 ms" }, "user-timings": { "id": "user-timings", @@ -1923,7 +1920,7 @@ "score": 0.46, "scoreDisplayMode": "numeric", "rawValue": 1129, - "displayValue": "5 resources delayed first paint by 1129ms", + "displayValue": "5 resources delayed first paint by 1,130 ms", "details": { "type": "opportunity", "headings": [ @@ -1940,7 +1937,7 @@ { "key": "wastedMs", "valueType": "timespanMs", - "label": "Download Time (ms)" + "label": "Potential Savings (ms)" } ], "items": [ @@ -2774,6 +2771,7 @@ "gatherMode": false, "disableStorageReset": false, "disableDeviceEmulation": false, + "locale": null, "blockedUrlPatterns": null, "additionalTraceCategories": null, "extraHeaders": null, diff --git a/lighthouse-extension/gulpfile.js b/lighthouse-extension/gulpfile.js index 646f6c610dc2..20ed862e37f6 100644 --- a/lighthouse-extension/gulpfile.js +++ b/lighthouse-extension/gulpfile.js @@ -125,6 +125,7 @@ gulp.task('browserify-lighthouse', () => { // scripts will need some additional transforms, ignores and requires… bundle.ignore('source-map') .ignore('debug/node') + .ignore('intl') .ignore('raven') .ignore('mkdirp') .ignore('rimraf') diff --git a/package.json b/package.json index 1574b0c3e12d..0dfe7984c8e8 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@types/configstore": "^2.1.1", "@types/css-font-loading-module": "^0.0.2", "@types/inquirer": "^0.0.35", + "@types/intl-messageformat": "^1.3.0", "@types/jpeg-js": "^0.3.0", "@types/lodash.isequal": "^4.5.2", "@types/node": "*", @@ -96,6 +97,7 @@ "gulp": "^3.9.1", "gulp-replace": "^0.5.4", "gulp-util": "^3.0.7", + "intl": "^1.2.5", "jest": "^23.2.0", "jsdom": "^9.12.0", "mocha": "^3.2.0", @@ -118,6 +120,8 @@ "esprima": "^4.0.0", "http-link-header": "^0.8.0", "inquirer": "^3.3.0", + "intl-messageformat": "^2.2.0", + "intl-messageformat-parser": "^1.4.0", "jpeg-js": "0.1.2", "js-library-detector": "^4.3.1", "lighthouse-logger": "^1.0.0", diff --git a/typings/externs.d.ts b/typings/externs.d.ts index 8e04d0dbfab8..895c677a17aa 100644 --- a/typings/externs.d.ts +++ b/typings/externs.d.ts @@ -52,10 +52,13 @@ declare global { cpuSlowdownMultiplier?: number } + export type Locale = 'en-US'|'en-XA'; + export type OutputMode = 'json' | 'html' | 'csv'; interface SharedFlagsSettings { output?: OutputMode|OutputMode[]; + locale?: Locale | null; maxWaitForLoad?: number; blockedUrlPatterns?: string[] | null; additionalTraceCategories?: string | null; diff --git a/typings/intl-messageformat-parser/index.d.ts b/typings/intl-messageformat-parser/index.d.ts new file mode 100644 index 000000000000..0db5d1fed74b --- /dev/null +++ b/typings/intl-messageformat-parser/index.d.ts @@ -0,0 +1,10 @@ +declare module 'intl-messageformat-parser' { + interface Element { + type: 'messageTextElement'|'argumentElement'; + id: string + value?: string + format?: null | {type: string; style?: string}; + } + function parse(message: string): {elements: Element[]}; + export {parse}; +} diff --git a/yarn.lock b/yarn.lock index 6e8c0da55e8f..aaeb04969b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,6 +55,10 @@ "@types/rx" "*" "@types/through" "*" +"@types/intl-messageformat@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/intl-messageformat/-/intl-messageformat-1.3.0.tgz#6af7144802b13d62ade9ad68f8420cdb33b75916" + "@types/jpeg-js@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/jpeg-js/-/jpeg-js-0.3.0.tgz#5971f0aa50900194e9565711c265c10027385e89" @@ -2961,6 +2965,20 @@ interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" +intl-messageformat-parser@1.4.0, intl-messageformat-parser@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075" + +intl-messageformat@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.2.0.tgz#345bcd46de630b7683330c2e52177ff5eab484fc" + dependencies: + intl-messageformat-parser "1.4.0" + +intl@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" + invariant@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.1.tgz#b097010547668c7e337028ebe816ebe36c8a8d54"