From b770045791c87ddfded8c60074bb24b727e19a79 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 19 Jan 2023 16:22:31 -0800 Subject: [PATCH 1/3] report: rename report i18n to formatter, move strings to Util --- core/util.cjs | 293 +++++++++--------- flow-report/src/i18n/i18n.tsx | 24 +- flow-report/src/sidebar/sidebar.tsx | 2 +- flow-report/src/summary/category.tsx | 2 +- report/renderer/category-renderer.js | 18 +- report/renderer/crc-details-renderer.js | 11 +- report/renderer/details-renderer.js | 10 +- report/renderer/{i18n.js => formatter.js} | 13 +- .../renderer/performance-category-renderer.js | 10 +- report/renderer/report-renderer.js | 34 +- report/renderer/report-ui-features.js | 12 +- report/renderer/snippet-renderer.js | 2 +- report/renderer/util.js | 293 +++++++++--------- .../test/renderer/category-renderer-test.js | 6 +- .../renderer/crc-details-renderer-test.js | 6 +- report/test/renderer/details-renderer-test.js | 6 +- report/test/renderer/dom-test.js | 6 +- .../element-screenshot-renderer-test.js | 6 +- .../{i18n-test.js => formatter-test.js} | 34 +- .../performance-category-renderer-test.js | 6 +- .../renderer/pwa-category-renderer-test.js | 6 +- report/test/renderer/snippet-renderer-test.js | 6 +- report/test/renderer/util-test.js | 6 +- treemap/app/src/main.js | 55 ++-- treemap/app/src/util.js | 19 +- 25 files changed, 456 insertions(+), 430 deletions(-) rename report/renderer/{i18n.js => formatter.js} (97%) rename report/test/renderer/{i18n-test.js => formatter-test.js} (87%) diff --git a/core/util.cjs b/core/util.cjs index 2997aae0da46..02d69377be2c 100644 --- a/core/util.cjs +++ b/core/util.cjs @@ -19,7 +19,7 @@ * limitations under the License. */ -/** @template T @typedef {import('./i18n').I18n} I18n */ +/** @typedef {import('./formatter').Formatter} Formatter */ const ELLIPSIS = '\u2026'; const NBSP = '\xa0'; @@ -41,10 +41,145 @@ const listOfTlds = [ 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', ]; +/** + * Report-renderer-specific strings. + */ +const UIStrings = { + /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ + varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', + /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ + calculatorLink: 'See calculator.', + /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ + showRelevantAudits: 'Show audits relevant to:', + /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ + opportunityResourceColumnLabel: 'Opportunity', + /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ + opportunitySavingsColumnLabel: 'Estimated Savings', + + /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ + errorMissingAuditInfo: 'Report error: no audit information', + /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ + errorLabel: 'Error!', + /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ + warningHeader: 'Warnings: ', + /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ + warningAuditsGroupTitle: 'Passed audits but with warnings', + /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + passedAuditsGroupTitle: 'Passed audits', + /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + notApplicableAuditsGroupTitle: 'Not applicable', + /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + manualAuditsGroupTitle: 'Additional items to manually check', + + /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ + toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', + + /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ + crcInitialNavigation: 'Initial Navigation', + /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ + crcLongestDurationLabel: 'Maximum critical path latency:', + + /** Label for button that shows all lines of the snippet when clicked */ + snippetExpandButtonLabel: 'Expand snippet', + /** Label for button that only shows a few lines of the snippet when clicked */ + snippetCollapseButtonLabel: 'Collapse snippet', + + /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ + lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', + /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ + labDataTitle: 'Lab Data', + + /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourcesLabel: 'Show 3rd-party resources', + /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ + viewTreemapLabel: 'View Treemap', + /** This label is for a button that will show the user a trace of the page. */ + viewTraceLabel: 'View Trace', + /** This label is for a button that will show the user a trace of the page. */ + viewOriginalTraceLabel: 'View Original Trace', + + /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ + dropdownPrintSummary: 'Print Summary', + /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ + dropdownPrintExpanded: 'Print Expanded', + /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ + dropdownCopyJSON: 'Copy JSON', + /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ + dropdownSaveHTML: 'Save as HTML', + /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ + dropdownSaveJSON: 'Save as JSON', + /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ + dropdownViewer: 'Open in Viewer', + /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ + dropdownSaveGist: 'Save as Gist', + /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ + dropdownDarkTheme: 'Toggle Dark Theme', + + /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ + runtimeSettingsDevice: 'Device', + /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ + runtimeSettingsNetworkThrottling: 'Network throttling', + /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ + runtimeSettingsCPUThrottling: 'CPU throttling', + /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ + runtimeSettingsUANetwork: 'User agent (network)', + /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ + runtimeSettingsBenchmark: 'CPU/Memory Power', + /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ + runtimeSettingsAxeVersion: 'Axe version', + /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ + runtimeSettingsScreenEmulation: 'Screen emulation', + + /** Label for button to create an issue against the Lighthouse GitHub project. */ + footerIssue: 'File an issue', + + /** Descriptive explanation for emulation setting when no device emulation is set. */ + runtimeNoEmulation: 'No emulation', + /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ + runtimeMobileEmulation: 'Emulated Moto G4', + /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ + runtimeDesktopEmulation: 'Emulated Desktop', + /** Descriptive explanation for a runtime setting that is set to an unknown value. */ + runtimeUnknown: 'Unknown', + /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ + runtimeSingleLoad: 'Single page load', + /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ + runtimeAnalysisWindow: 'Initial page load', + /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ + runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len + + /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ + throttlingProvided: 'Provided by environment', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + show: 'Show', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + hide: 'Hide', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + expandView: 'Expand view', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + collapseView: 'Collapse view', + /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ + runtimeSlow4g: 'Slow 4G throttling', + /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ + runtimeCustom: 'Custom throttling', +}; + class Util { - /** @type {I18n} */ + /** @type {Formatter} */ // @ts-expect-error: Is set in report renderer. - static i18n = null; + static formatter = null; + static strings = {...UIStrings}; + + /** + * @param {Record} providedStrings + */ + static applyStrings(providedStrings) { + this.strings = { + // Set missing renderer strings to default (english) values. + ...UIStrings, + ...providedStrings, + }; + } static get PASS_THRESHOLD() { return PASS_THRESHOLD; @@ -511,39 +646,41 @@ class Util { switch (settings.throttlingMethod) { case 'provided': - summary = networkThrottling = cpuThrottling = Util.i18n.strings.throttlingProvided; + summary = networkThrottling = cpuThrottling = Util.strings.throttlingProvided; break; case 'devtools': { const {cpuSlowdownMultiplier, requestLatencyMs} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; - networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + - `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` + - `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; + cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; + networkThrottling = `${Util.formatter.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + + `${Util.formatter.formatKbps(throttling.downloadThroughputKbps)} down, ` + + `${Util.formatter.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; const isSlow4G = () => { return requestLatencyMs === 150 * 3.75 && throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 && throttling.uploadThroughputKbps === 750 * 0.9; }; - summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom; + summary = isSlow4G() ? + Util.strings.runtimeSlow4g : Util.strings.runtimeCustom; break; } case 'simulate': { const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; - networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` + - `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`; + cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; + networkThrottling = `${Util.formatter.formatMilliseconds(rttMs)} TCP RTT, ` + + `${Util.formatter.formatKbps(throughputKbps)} throughput (Simulated)`; const isSlow4G = () => { return rttMs === 150 && throughputKbps === 1.6 * 1024; }; - summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom; + summary = isSlow4G() ? + Util.strings.runtimeSlow4g : Util.strings.runtimeCustom; break; } default: - summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown; + summary = cpuThrottling = networkThrottling = Util.strings.runtimeUnknown; } // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation, @@ -556,11 +693,11 @@ class Util { settings.formFactor === 'mobile' : settings.screenEmulation.mobile; - let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation; + let deviceEmulation = Util.strings.runtimeMobileEmulation; if (isScreenEmulationDisabled) { - deviceEmulation = Util.i18n.strings.runtimeNoEmulation; + deviceEmulation = Util.strings.runtimeNoEmulation; } else if (!isScreenEmulationMobile) { - deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation; + deviceEmulation = Util.strings.runtimeDesktopEmulation; } const screenEmulation = isScreenEmulationDisabled ? @@ -682,128 +819,6 @@ Util.resetUniqueSuffix = () => { svgSuffix = 0; }; -/** - * Report-renderer-specific strings. - */ -const UIStrings = { - /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ - varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', - /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ - calculatorLink: 'See calculator.', - /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ - showRelevantAudits: 'Show audits relevant to:', - /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ - opportunityResourceColumnLabel: 'Opportunity', - /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ - opportunitySavingsColumnLabel: 'Estimated Savings', - - /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ - errorMissingAuditInfo: 'Report error: no audit information', - /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ - errorLabel: 'Error!', - /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ - warningHeader: 'Warnings: ', - /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ - warningAuditsGroupTitle: 'Passed audits but with warnings', - /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - passedAuditsGroupTitle: 'Passed audits', - /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - notApplicableAuditsGroupTitle: 'Not applicable', - /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - manualAuditsGroupTitle: 'Additional items to manually check', - - /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ - toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', - - /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ - crcInitialNavigation: 'Initial Navigation', - /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ - crcLongestDurationLabel: 'Maximum critical path latency:', - - /** Label for button that shows all lines of the snippet when clicked */ - snippetExpandButtonLabel: 'Expand snippet', - /** Label for button that only shows a few lines of the snippet when clicked */ - snippetCollapseButtonLabel: 'Collapse snippet', - - /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ - lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', - /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ - labDataTitle: 'Lab Data', - - /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ - thirdPartyResourcesLabel: 'Show 3rd-party resources', - /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ - viewTreemapLabel: 'View Treemap', - /** This label is for a button that will show the user a trace of the page. */ - viewTraceLabel: 'View Trace', - /** This label is for a button that will show the user a trace of the page. */ - viewOriginalTraceLabel: 'View Original Trace', - - /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ - dropdownPrintSummary: 'Print Summary', - /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ - dropdownPrintExpanded: 'Print Expanded', - /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ - dropdownCopyJSON: 'Copy JSON', - /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ - dropdownSaveHTML: 'Save as HTML', - /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ - dropdownSaveJSON: 'Save as JSON', - /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ - dropdownViewer: 'Open in Viewer', - /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ - dropdownSaveGist: 'Save as Gist', - /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ - dropdownDarkTheme: 'Toggle Dark Theme', - - /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ - runtimeSettingsDevice: 'Device', - /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ - runtimeSettingsNetworkThrottling: 'Network throttling', - /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ - runtimeSettingsCPUThrottling: 'CPU throttling', - /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ - runtimeSettingsUANetwork: 'User agent (network)', - /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ - runtimeSettingsBenchmark: 'CPU/Memory Power', - /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ - runtimeSettingsAxeVersion: 'Axe version', - /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ - runtimeSettingsScreenEmulation: 'Screen emulation', - - /** Label for button to create an issue against the Lighthouse GitHub project. */ - footerIssue: 'File an issue', - - /** Descriptive explanation for emulation setting when no device emulation is set. */ - runtimeNoEmulation: 'No emulation', - /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ - runtimeMobileEmulation: 'Emulated Moto G4', - /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ - runtimeDesktopEmulation: 'Emulated Desktop', - /** Descriptive explanation for a runtime setting that is set to an unknown value. */ - runtimeUnknown: 'Unknown', - /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ - runtimeSingleLoad: 'Single page load', - /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ - runtimeAnalysisWindow: 'Initial page load', - /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ - runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len - - /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ - throttlingProvided: 'Provided by environment', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - show: 'Show', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - hide: 'Hide', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - expandView: 'Expand view', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - collapseView: 'Collapse view', - /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ - runtimeSlow4g: 'Slow 4G throttling', - /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ - runtimeCustom: 'Custom throttling', -}; Util.UIStrings = UIStrings; module.exports = { diff --git a/flow-report/src/i18n/i18n.tsx b/flow-report/src/i18n/i18n.tsx index bd350c89654a..4e1c6d594f25 100644 --- a/flow-report/src/i18n/i18n.tsx +++ b/flow-report/src/i18n/i18n.tsx @@ -8,13 +8,16 @@ import {createContext, FunctionComponent} from 'preact'; import {useContext, useMemo} from 'preact/hooks'; import {formatMessage} from '../../../shared/localization/format'; -import {I18n} from '../../../report/renderer/i18n'; +import {Formatter} from '../../../report/renderer/formatter'; import {UIStrings} from './ui-strings'; import {useFlowResult} from '../util'; import strings from './localized-strings.js'; import {Util} from '../../../report/renderer/util'; -const I18nContext = createContext(new I18n('en-US', {...Util.UIStrings, ...UIStrings})); +const I18nContext = createContext({ + formatter: new Formatter('en-US'), + strings: {...Util.UIStrings, ...UIStrings}, +}); function useLhrLocale() { const flowResult = useFlowResult(); @@ -50,10 +53,8 @@ function useStringFormatter() { const I18nProvider: FunctionComponent = ({children}) => { const {locale, lhrStrings} = useLhrLocale(); - const i18n = useMemo(() => { - const i18n = new I18n(locale, { - // Set any missing lhr strings to default (english) values. - ...Util.UIStrings, + const formatter = useMemo(() => { + Util.applyStrings({ // Preload with strings from the first lhr. // Used for legacy report components imported into the flow report. ...lhrStrings, @@ -64,14 +65,17 @@ const I18nProvider: FunctionComponent = ({children}) => { }); // Initialize renderer util i18n for strings rendered in wrapped components. - // TODO: Don't attach global i18n to `Util`. - Util.i18n = i18n; + // TODO: Don't attach global formatter to `Util`. + Util.formatter = new Formatter(locale); - return i18n; + return { + formatter: Util.formatter, + strings: Util.strings as typeof UIStrings & typeof Util.UIStrings, + }; }, [locale, lhrStrings]); return ( - + {children} ); diff --git a/flow-report/src/sidebar/sidebar.tsx b/flow-report/src/sidebar/sidebar.tsx index 9edd387ff3ac..a425a90aba93 100644 --- a/flow-report/src/sidebar/sidebar.tsx +++ b/flow-report/src/sidebar/sidebar.tsx @@ -77,7 +77,7 @@ const SidebarHeader: FunctionComponent<{title: string, date: string}> = ({title, return (
{title}
-
{i18n.formatDateTime(date)}
+
{i18n.formatter.formatDateTime(date)}
); }; diff --git a/flow-report/src/summary/category.tsx b/flow-report/src/summary/category.tsx index f438664a0c79..01003bfe9588 100644 --- a/flow-report/src/summary/category.tsx +++ b/flow-report/src/summary/category.tsx @@ -132,7 +132,7 @@ const SummaryTooltip: FunctionComponent<{ { !displayAsFraction && category.score !== null && <> · - {i18n.formatInteger(category.score * 100)} + {i18n.formatter.formatInteger(category.score * 100)} } diff --git a/report/renderer/category-renderer.js b/report/renderer/category-renderer.js index 61a2f437008f..405d7bfa99e2 100644 --- a/report/renderer/category-renderer.js +++ b/report/renderer/category-renderer.js @@ -39,10 +39,10 @@ export class CategoryRenderer { */ get _clumpTitles() { return { - warning: Util.i18n.strings.warningAuditsGroupTitle, - manual: Util.i18n.strings.manualAuditsGroupTitle, - passed: Util.i18n.strings.passedAuditsGroupTitle, - notApplicable: Util.i18n.strings.notApplicableAuditsGroupTitle, + warning: Util.strings.warningAuditsGroupTitle, + manual: Util.strings.manualAuditsGroupTitle, + passed: Util.strings.passedAuditsGroupTitle, + notApplicable: Util.strings.notApplicableAuditsGroupTitle, }; } @@ -62,7 +62,7 @@ export class CategoryRenderer { * @return {!Element} */ populateAuditValues(audit, component) { - const strings = Util.i18n.strings; + const strings = Util.strings; const auditEl = this.dom.find('.lh-audit', component); auditEl.id = audit.result.id; const scoreDisplayMode = audit.result.scoreDisplayMode; @@ -334,8 +334,8 @@ export class CategoryRenderer { el.append(descriptionEl); } - this.dom.find('.lh-clump-toggletext--show', el).textContent = Util.i18n.strings.show; - this.dom.find('.lh-clump-toggletext--hide', el).textContent = Util.i18n.strings.hide; + this.dom.find('.lh-clump-toggletext--show', el).textContent = Util.strings.show; + this.dom.find('.lh-clump-toggletext--hide', el).textContent = Util.strings.hide; clumpElement.classList.add(`lh-clump--${clumpId.toLowerCase()}`); return el; @@ -393,7 +393,7 @@ export class CategoryRenderer { percentageEl.textContent = scoreOutOf100.toString(); if (category.score === null) { percentageEl.textContent = '?'; - percentageEl.title = Util.i18n.strings.errorLabel; + percentageEl.title = Util.strings.errorLabel; } // Render a numerical score if the category has applicable audits, or no audits whatsoever. @@ -402,7 +402,7 @@ export class CategoryRenderer { } else { wrapper.classList.add(`lh-gauge__wrapper--not-applicable`); percentageEl.textContent = '-'; - percentageEl.title = Util.i18n.strings.notApplicableAuditsGroupTitle; + percentageEl.title = Util.strings.notApplicableAuditsGroupTitle; } this.dom.find('.lh-gauge__label', tmpl).textContent = category.title; diff --git a/report/renderer/crc-details-renderer.js b/report/renderer/crc-details-renderer.js index 1a0afdcd8bec..67157b34a412 100644 --- a/report/renderer/crc-details-renderer.js +++ b/report/renderer/crc-details-renderer.js @@ -136,9 +136,10 @@ class CriticalRequestChainRenderer { if (!segment.hasChildren) { const {startTime, endTime, transferSize} = segment.node.request; const span = dom.createElement('span', 'lh-crc-node__chain-duration'); - span.textContent = ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', '; + span.textContent = + ' - ' + Util.formatter.formatMilliseconds((endTime - startTime) * 1000) + ', '; const span2 = dom.createElement('span', 'lh-crc-node__chain-duration'); - span2.textContent = Util.i18n.formatBytesToKiB(transferSize, 0.01); + span2.textContent = Util.formatter.formatBytesToKiB(transferSize, 0.01); treevalEl.append(span, span2); } @@ -177,11 +178,11 @@ class CriticalRequestChainRenderer { const containerEl = dom.find('.lh-crc', tmpl); // Fill in top summary. - dom.find('.lh-crc-initial-nav', tmpl).textContent = Util.i18n.strings.crcInitialNavigation; + dom.find('.lh-crc-initial-nav', tmpl).textContent = Util.strings.crcInitialNavigation; dom.find('.lh-crc__longest_duration_label', tmpl).textContent = - Util.i18n.strings.crcLongestDurationLabel; + Util.strings.crcLongestDurationLabel; dom.find('.lh-crc__longest_duration', tmpl).textContent = - Util.i18n.formatMilliseconds(details.longestChain.duration); + Util.formatter.formatMilliseconds(details.longestChain.duration); // Construct visual tree. const root = CRCRenderer.initTree(details.chains); diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index bc4aa925b9bd..29e73e965d0c 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -77,9 +77,9 @@ export class DetailsRenderer { */ _renderBytes(details) { // TODO: handle displayUnit once we have something other than 'KiB' - const value = Util.i18n.formatBytesToKiB(details.value, details.granularity || 0.1); + const value = Util.formatter.formatBytesToKiB(details.value, details.granularity || 0.1); const textEl = this._renderText(value); - textEl.title = Util.i18n.formatBytes(details.value); + textEl.title = Util.formatter.formatBytes(details.value); return textEl; } @@ -90,9 +90,9 @@ export class DetailsRenderer { _renderMilliseconds(details) { let value; if (details.displayUnit === 'duration') { - value = Util.i18n.formatDuration(details.value); + value = Util.formatter.formatDuration(details.value); } else { - value = Util.i18n.formatMilliseconds(details.value, details.granularity || 10); + value = Util.formatter.formatMilliseconds(details.value, details.granularity || 10); } return this._renderText(value); @@ -171,7 +171,7 @@ export class DetailsRenderer { * @return {Element} */ _renderNumeric(details) { - const value = Util.i18n.formatNumber(details.value, details.granularity || 0.1); + const value = Util.formatter.formatNumber(details.value, details.granularity || 0.1); const element = this._dom.createElement('div', 'lh-numeric'); element.textContent = value; return element; diff --git a/report/renderer/i18n.js b/report/renderer/formatter.js similarity index 97% rename from report/renderer/i18n.js rename to report/renderer/formatter.js index eaeef0a8ada3..bf82df865ed2 100644 --- a/report/renderer/i18n.js +++ b/report/renderer/formatter.js @@ -9,27 +9,18 @@ const NBSP2 = '\xa0'; const KiB = 1024; const MiB = KiB * KiB; -/** - * @template T - */ -export class I18n { +export class Formatter { /** * @param {LH.Locale} locale - * @param {T} strings */ - constructor(locale, strings) { + constructor(locale) { // When testing, use a locale with more exciting numeric formatting. if (locale === 'en-XA') locale = 'de'; this._locale = locale; - this._strings = strings; this._cachedNumberFormatters = new Map(); } - get strings() { - return this._strings; - } - /** * @param {number} number * @param {number|undefined} granularity diff --git a/report/renderer/performance-category-renderer.js b/report/renderer/performance-category-renderer.js index abe0649972fd..41cb38c945fc 100644 --- a/report/renderer/performance-category-renderer.js +++ b/report/renderer/performance-category-renderer.js @@ -77,7 +77,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element); const sparklineWidthPct = `${details.overallSavingsMs / scale * 100}%`; this.dom.find('div.lh-sparkline__bar', element).style.width = sparklineWidthPct; - displayEl.textContent = Util.i18n.formatSeconds(details.overallSavingsMs, 0.01); + displayEl.textContent = Util.formatter.formatSeconds(details.overallSavingsMs, 0.01); // Set [title] tooltips if (audit.result.displayValue) { @@ -178,7 +178,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { * @override */ render(category, groups, options) { - const strings = Util.i18n.strings; + const strings = Util.strings; const element = this.dom.createElement('div', 'lh-category'); element.id = category.id; element.append(this.renderCategoryHeader(category, groups, options)); @@ -200,8 +200,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { labelEl.htmlFor = checkboxId; const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show'); const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide'); - showEl.textContent = Util.i18n.strings.expandView; - hideEl.textContent = Util.i18n.strings.collapseView; + showEl.textContent = Util.strings.expandView; + hideEl.textContent = Util.strings.collapseView; const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container'); metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl); @@ -333,7 +333,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { renderMetricAuditFilter(filterableMetrics, categoryEl) { const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter'); const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text'); - textEl.textContent = Util.i18n.strings.showRelevantAudits; + textEl.textContent = Util.strings.showRelevantAudits; const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([ ({acronym: 'All'}), diff --git a/report/renderer/report-renderer.js b/report/renderer/report-renderer.js index ce40ada033c0..8d44334aa343 100644 --- a/report/renderer/report-renderer.js +++ b/report/renderer/report-renderer.js @@ -23,7 +23,7 @@ import {CategoryRenderer} from './category-renderer.js'; import {DetailsRenderer} from './details-renderer.js'; import {ElementScreenshotRenderer} from './element-screenshot-renderer.js'; -import {I18n} from './i18n.js'; +import {Formatter} from './formatter.js'; import {PerformanceCategoryRenderer} from './performance-category-renderer.js'; import {PwaCategoryRenderer} from './pwa-category-renderer.js'; import {Util} from './util.js'; @@ -108,7 +108,7 @@ export class ReportRenderer { this._renderMetaBlock(report, footer); - this._dom.find('.lh-footer__version_issue', footer).textContent = Util.i18n.strings.footerIssue; + this._dom.find('.lh-footer__version_issue', footer).textContent = Util.strings.footerIssue; this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion; return footer; } @@ -128,35 +128,35 @@ export class ReportRenderer { const axeVersion = report.environment.credits?.['axe-core']; const devicesTooltipTextLines = [ - `${Util.i18n.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}`, - `${Util.i18n.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}`, + `${Util.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}`, + `${Util.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}`, ]; if (envValues.screenEmulation) { devicesTooltipTextLines.push( - `${Util.i18n.strings.runtimeSettingsScreenEmulation}: ${envValues.screenEmulation}`); + `${Util.strings.runtimeSettingsScreenEmulation}: ${envValues.screenEmulation}`); } if (axeVersion) { - devicesTooltipTextLines.push(`${Util.i18n.strings.runtimeSettingsAxeVersion}: ${axeVersion}`); + devicesTooltipTextLines.push(`${Util.strings.runtimeSettingsAxeVersion}: ${axeVersion}`); } // [CSS icon class, textContent, tooltipText] const metaItems = [ ['date', - `Captured at ${Util.i18n.formatDateTime(report.fetchTime)}`], + `Captured at ${Util.formatter.formatDateTime(report.fetchTime)}`], ['devices', `${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`, devicesTooltipTextLines.join('\n')], ['samples-one', - Util.i18n.strings.runtimeSingleLoad, - Util.i18n.strings.runtimeSingleLoadTooltip], + Util.strings.runtimeSingleLoad, + Util.strings.runtimeSingleLoadTooltip], ['stopwatch', - Util.i18n.strings.runtimeAnalysisWindow], + Util.strings.runtimeAnalysisWindow], ['networkspeed', `${envValues.summary}`, - `${Util.i18n.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`], + `${Util.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`], ['chrome', `Using ${chromeVer}` + (channel ? ` with ${channel}` : ''), - `${Util.i18n.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`], + `${Util.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`], ]; const metaItemsEl = this._dom.find('.lh-meta__items', footer); @@ -184,7 +184,7 @@ export class ReportRenderer { const container = this._dom.createComponent('warningsToplevel'); const message = this._dom.find('.lh-warnings__msg', container); - message.textContent = Util.i18n.strings.toplevelWarningsMessage; + message.textContent = Util.strings.toplevelWarningsMessage; const warnings = []; for (const warningString of report.runWarnings) { @@ -260,12 +260,8 @@ export class ReportRenderer { * @return {!DocumentFragment} */ _renderReport(report) { - const i18n = new I18n(report.configSettings.locale, { - // Set missing renderer strings to default (english) values. - ...Util.UIStrings, - ...report.i18n.rendererFormattedStrings, - }); - Util.i18n = i18n; + Util.applyStrings(report.i18n.rendererFormattedStrings); + Util.formatter = new Formatter(report.configSettings.locale); Util.reportJson = report; const detailsRenderer = new DetailsRenderer(this._dom, { diff --git a/report/renderer/report-ui-features.js b/report/renderer/report-ui-features.js index bf5b05af4c38..e5bfd608f48a 100644 --- a/report/renderer/report-ui-features.js +++ b/report/renderer/report-ui-features.js @@ -106,7 +106,7 @@ export class ReportUIFeatures { this.json.audits['script-treemap-data'] && this.json.audits['script-treemap-data'].details; if (showTreemapApp) { this.addButton({ - text: Util.i18n.strings.viewTreemapLabel, + text: Util.strings.viewTreemapLabel, icon: 'treemap', onClick: () => openTreemap(this.json), }); @@ -115,8 +115,8 @@ export class ReportUIFeatures { if (this._opts.onViewTrace) { this.addButton({ text: lhr.configSettings.throttlingMethod === 'simulate' ? - Util.i18n.strings.viewOriginalTraceLabel : - Util.i18n.strings.viewTraceLabel, + Util.strings.viewOriginalTraceLabel : + Util.strings.viewTraceLabel, onClick: () => this._opts.onViewTrace?.(), }); } @@ -130,8 +130,8 @@ export class ReportUIFeatures { // These strings are guaranteed to (at least) have a default English string in Util.UIStrings, // so this cannot be undefined as long as `report-ui-features.data-i18n` test passes. const i18nKey = node.getAttribute('data-i18n'); - const i18nAttr = /** @type {keyof typeof Util.i18n.strings} */ (i18nKey); - node.textContent = Util.i18n.strings[i18nAttr]; + const i18nAttr = /** @type {keyof typeof Util.strings} */ (i18nKey); + node.textContent = Util.strings[i18nAttr]; } } @@ -273,7 +273,7 @@ export class ReportUIFeatures { this._dom.find('.lh-3p-filter-count', filterTemplate).textContent = `${thirdPartyRows.length}`; this._dom.find('.lh-3p-ui-string', filterTemplate).textContent = - Util.i18n.strings.thirdPartyResourcesLabel; + Util.strings.thirdPartyResourcesLabel; const allThirdParty = thirdPartyRows.length === rowEls.length; const allFirstParty = !thirdPartyRows.length; diff --git a/report/renderer/snippet-renderer.js b/report/renderer/snippet-renderer.js index 97cecdc6b534..a2133095cf1b 100644 --- a/report/renderer/snippet-renderer.js +++ b/report/renderer/snippet-renderer.js @@ -104,7 +104,7 @@ export class SnippetRenderer { const { snippetCollapseButtonLabel, snippetExpandButtonLabel, - } = Util.i18n.strings; + } = Util.strings; dom.find( '.lh-snippet__btn-label-collapse', header diff --git a/report/renderer/util.js b/report/renderer/util.js index 1a77d5d18a4f..4fef060d6ede 100644 --- a/report/renderer/util.js +++ b/report/renderer/util.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/** @template T @typedef {import('./i18n').I18n} I18n */ +/** @typedef {import('./formatter').Formatter} Formatter */ const ELLIPSIS = '\u2026'; const NBSP = '\xa0'; @@ -37,10 +37,145 @@ const listOfTlds = [ 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', ]; +/** + * Report-renderer-specific strings. + */ +const UIStrings = { + /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ + varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', + /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ + calculatorLink: 'See calculator.', + /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ + showRelevantAudits: 'Show audits relevant to:', + /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ + opportunityResourceColumnLabel: 'Opportunity', + /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ + opportunitySavingsColumnLabel: 'Estimated Savings', + + /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ + errorMissingAuditInfo: 'Report error: no audit information', + /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ + errorLabel: 'Error!', + /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ + warningHeader: 'Warnings: ', + /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ + warningAuditsGroupTitle: 'Passed audits but with warnings', + /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + passedAuditsGroupTitle: 'Passed audits', + /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + notApplicableAuditsGroupTitle: 'Not applicable', + /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + manualAuditsGroupTitle: 'Additional items to manually check', + + /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ + toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', + + /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ + crcInitialNavigation: 'Initial Navigation', + /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ + crcLongestDurationLabel: 'Maximum critical path latency:', + + /** Label for button that shows all lines of the snippet when clicked */ + snippetExpandButtonLabel: 'Expand snippet', + /** Label for button that only shows a few lines of the snippet when clicked */ + snippetCollapseButtonLabel: 'Collapse snippet', + + /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ + lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', + /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ + labDataTitle: 'Lab Data', + + /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourcesLabel: 'Show 3rd-party resources', + /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ + viewTreemapLabel: 'View Treemap', + /** This label is for a button that will show the user a trace of the page. */ + viewTraceLabel: 'View Trace', + /** This label is for a button that will show the user a trace of the page. */ + viewOriginalTraceLabel: 'View Original Trace', + + /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ + dropdownPrintSummary: 'Print Summary', + /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ + dropdownPrintExpanded: 'Print Expanded', + /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ + dropdownCopyJSON: 'Copy JSON', + /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ + dropdownSaveHTML: 'Save as HTML', + /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ + dropdownSaveJSON: 'Save as JSON', + /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ + dropdownViewer: 'Open in Viewer', + /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ + dropdownSaveGist: 'Save as Gist', + /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ + dropdownDarkTheme: 'Toggle Dark Theme', + + /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ + runtimeSettingsDevice: 'Device', + /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ + runtimeSettingsNetworkThrottling: 'Network throttling', + /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ + runtimeSettingsCPUThrottling: 'CPU throttling', + /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ + runtimeSettingsUANetwork: 'User agent (network)', + /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ + runtimeSettingsBenchmark: 'CPU/Memory Power', + /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ + runtimeSettingsAxeVersion: 'Axe version', + /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ + runtimeSettingsScreenEmulation: 'Screen emulation', + + /** Label for button to create an issue against the Lighthouse GitHub project. */ + footerIssue: 'File an issue', + + /** Descriptive explanation for emulation setting when no device emulation is set. */ + runtimeNoEmulation: 'No emulation', + /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ + runtimeMobileEmulation: 'Emulated Moto G4', + /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ + runtimeDesktopEmulation: 'Emulated Desktop', + /** Descriptive explanation for a runtime setting that is set to an unknown value. */ + runtimeUnknown: 'Unknown', + /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ + runtimeSingleLoad: 'Single page load', + /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ + runtimeAnalysisWindow: 'Initial page load', + /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ + runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len + + /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ + throttlingProvided: 'Provided by environment', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + show: 'Show', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + hide: 'Hide', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + expandView: 'Expand view', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + collapseView: 'Collapse view', + /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ + runtimeSlow4g: 'Slow 4G throttling', + /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ + runtimeCustom: 'Custom throttling', +}; + class Util { - /** @type {I18n} */ + /** @type {Formatter} */ // @ts-expect-error: Is set in report renderer. - static i18n = null; + static formatter = null; + static strings = {...UIStrings}; + + /** + * @param {Record} providedStrings + */ + static applyStrings(providedStrings) { + this.strings = { + // Set missing renderer strings to default (english) values. + ...UIStrings, + ...providedStrings, + }; + } static get PASS_THRESHOLD() { return PASS_THRESHOLD; @@ -507,39 +642,41 @@ class Util { switch (settings.throttlingMethod) { case 'provided': - summary = networkThrottling = cpuThrottling = Util.i18n.strings.throttlingProvided; + summary = networkThrottling = cpuThrottling = Util.strings.throttlingProvided; break; case 'devtools': { const {cpuSlowdownMultiplier, requestLatencyMs} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; - networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + - `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` + - `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; + cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; + networkThrottling = `${Util.formatter.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + + `${Util.formatter.formatKbps(throttling.downloadThroughputKbps)} down, ` + + `${Util.formatter.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; const isSlow4G = () => { return requestLatencyMs === 150 * 3.75 && throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 && throttling.uploadThroughputKbps === 750 * 0.9; }; - summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom; + summary = isSlow4G() ? + Util.strings.runtimeSlow4g : Util.strings.runtimeCustom; break; } case 'simulate': { const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; - networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` + - `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`; + cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; + networkThrottling = `${Util.formatter.formatMilliseconds(rttMs)} TCP RTT, ` + + `${Util.formatter.formatKbps(throughputKbps)} throughput (Simulated)`; const isSlow4G = () => { return rttMs === 150 && throughputKbps === 1.6 * 1024; }; - summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom; + summary = isSlow4G() ? + Util.strings.runtimeSlow4g : Util.strings.runtimeCustom; break; } default: - summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown; + summary = cpuThrottling = networkThrottling = Util.strings.runtimeUnknown; } // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation, @@ -552,11 +689,11 @@ class Util { settings.formFactor === 'mobile' : settings.screenEmulation.mobile; - let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation; + let deviceEmulation = Util.strings.runtimeMobileEmulation; if (isScreenEmulationDisabled) { - deviceEmulation = Util.i18n.strings.runtimeNoEmulation; + deviceEmulation = Util.strings.runtimeNoEmulation; } else if (!isScreenEmulationMobile) { - deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation; + deviceEmulation = Util.strings.runtimeDesktopEmulation; } const screenEmulation = isScreenEmulationDisabled ? @@ -678,128 +815,6 @@ Util.resetUniqueSuffix = () => { svgSuffix = 0; }; -/** - * Report-renderer-specific strings. - */ -const UIStrings = { - /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ - varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', - /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ - calculatorLink: 'See calculator.', - /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ - showRelevantAudits: 'Show audits relevant to:', - /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ - opportunityResourceColumnLabel: 'Opportunity', - /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ - opportunitySavingsColumnLabel: 'Estimated Savings', - - /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ - errorMissingAuditInfo: 'Report error: no audit information', - /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ - errorLabel: 'Error!', - /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ - warningHeader: 'Warnings: ', - /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ - warningAuditsGroupTitle: 'Passed audits but with warnings', - /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - passedAuditsGroupTitle: 'Passed audits', - /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - notApplicableAuditsGroupTitle: 'Not applicable', - /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - manualAuditsGroupTitle: 'Additional items to manually check', - - /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ - toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', - - /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ - crcInitialNavigation: 'Initial Navigation', - /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ - crcLongestDurationLabel: 'Maximum critical path latency:', - - /** Label for button that shows all lines of the snippet when clicked */ - snippetExpandButtonLabel: 'Expand snippet', - /** Label for button that only shows a few lines of the snippet when clicked */ - snippetCollapseButtonLabel: 'Collapse snippet', - - /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ - lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', - /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ - labDataTitle: 'Lab Data', - - /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ - thirdPartyResourcesLabel: 'Show 3rd-party resources', - /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ - viewTreemapLabel: 'View Treemap', - /** This label is for a button that will show the user a trace of the page. */ - viewTraceLabel: 'View Trace', - /** This label is for a button that will show the user a trace of the page. */ - viewOriginalTraceLabel: 'View Original Trace', - - /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ - dropdownPrintSummary: 'Print Summary', - /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ - dropdownPrintExpanded: 'Print Expanded', - /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ - dropdownCopyJSON: 'Copy JSON', - /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ - dropdownSaveHTML: 'Save as HTML', - /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ - dropdownSaveJSON: 'Save as JSON', - /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ - dropdownViewer: 'Open in Viewer', - /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ - dropdownSaveGist: 'Save as Gist', - /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ - dropdownDarkTheme: 'Toggle Dark Theme', - - /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ - runtimeSettingsDevice: 'Device', - /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ - runtimeSettingsNetworkThrottling: 'Network throttling', - /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ - runtimeSettingsCPUThrottling: 'CPU throttling', - /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ - runtimeSettingsUANetwork: 'User agent (network)', - /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ - runtimeSettingsBenchmark: 'CPU/Memory Power', - /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ - runtimeSettingsAxeVersion: 'Axe version', - /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ - runtimeSettingsScreenEmulation: 'Screen emulation', - - /** Label for button to create an issue against the Lighthouse GitHub project. */ - footerIssue: 'File an issue', - - /** Descriptive explanation for emulation setting when no device emulation is set. */ - runtimeNoEmulation: 'No emulation', - /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ - runtimeMobileEmulation: 'Emulated Moto G4', - /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ - runtimeDesktopEmulation: 'Emulated Desktop', - /** Descriptive explanation for a runtime setting that is set to an unknown value. */ - runtimeUnknown: 'Unknown', - /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ - runtimeSingleLoad: 'Single page load', - /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ - runtimeAnalysisWindow: 'Initial page load', - /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ - runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len - - /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ - throttlingProvided: 'Provided by environment', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - show: 'Show', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - hide: 'Hide', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - expandView: 'Expand view', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - collapseView: 'Collapse view', - /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ - runtimeSlow4g: 'Slow 4G throttling', - /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ - runtimeCustom: 'Custom throttling', -}; Util.UIStrings = UIStrings; export { diff --git a/report/test/renderer/category-renderer-test.js b/report/test/renderer/category-renderer-test.js index 978fbff1880b..22ca755f2570 100644 --- a/report/test/renderer/category-renderer-test.js +++ b/report/test/renderer/category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {CategoryRenderer} from '../../renderer/category-renderer.js'; @@ -22,7 +22,7 @@ describe('CategoryRenderer', () => { let sampleResults; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -33,7 +33,7 @@ describe('CategoryRenderer', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('renders an audit', () => { diff --git a/report/test/renderer/crc-details-renderer-test.js b/report/test/renderer/crc-details-renderer-test.js index 7019d80f2783..e56290a7d58a 100644 --- a/report/test/renderer/crc-details-renderer-test.js +++ b/report/test/renderer/crc-details-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {CriticalRequestChainRenderer} from '../../renderer/crc-details-renderer.js'; @@ -73,7 +73,7 @@ describe('DetailsRenderer', () => { let detailsRenderer; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); @@ -81,7 +81,7 @@ describe('DetailsRenderer', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('renders tree structure', () => { diff --git a/report/test/renderer/details-renderer-test.js b/report/test/renderer/details-renderer-test.js index d14a67b544a0..b89aea6ac1b2 100644 --- a/report/test/renderer/details-renderer-test.js +++ b/report/test/renderer/details-renderer-test.js @@ -10,7 +10,7 @@ import jsdom from 'jsdom'; import {DOM} from '../../renderer/dom.js'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; describe('DetailsRenderer', () => { @@ -23,12 +23,12 @@ describe('DetailsRenderer', () => { } before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); createRenderer(); }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); describe('render', () => { diff --git a/report/test/renderer/dom-test.js b/report/test/renderer/dom-test.js index 2af538e7bf00..2e34c46374f5 100644 --- a/report/test/renderer/dom-test.js +++ b/report/test/renderer/dom-test.js @@ -11,7 +11,7 @@ import jsdom from 'jsdom'; import {DOM} from '../../renderer/dom.js'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; describe('DOM', () => { /** @type {DOM} */ @@ -20,7 +20,7 @@ describe('DOM', () => { let nativeCreateObjectURL; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); window = new jsdom.JSDOM().window; // The Node version of URL.createObjectURL isn't compatible with the jsdom blob type, @@ -33,7 +33,7 @@ describe('DOM', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; URL.createObjectURL = nativeCreateObjectURL; }); diff --git a/report/test/renderer/element-screenshot-renderer-test.js b/report/test/renderer/element-screenshot-renderer-test.js index 502a4e52f359..7c760dde9357 100644 --- a/report/test/renderer/element-screenshot-renderer-test.js +++ b/report/test/renderer/element-screenshot-renderer-test.js @@ -8,7 +8,7 @@ import jsdom from 'jsdom'; import {ElementScreenshotRenderer} from '../../renderer/element-screenshot-renderer.js'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; /** @@ -27,7 +27,7 @@ describe('ElementScreenshotRenderer', () => { let dom; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); @@ -35,7 +35,7 @@ describe('ElementScreenshotRenderer', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('renders screenshot', () => { diff --git a/report/test/renderer/i18n-test.js b/report/test/renderer/formatter-test.js similarity index 87% rename from report/test/renderer/i18n-test.js rename to report/test/renderer/formatter-test.js index 67da529cfd68..700491923df1 100644 --- a/report/test/renderer/i18n-test.js +++ b/report/test/renderer/formatter-test.js @@ -6,19 +6,13 @@ import assert from 'assert/strict'; -import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; - -// 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 -import '../../../core/lib/i18n/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; const NBSP = '\xa0'; describe('util helpers', () => { it('formats a number', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); assert.strictEqual(i18n.formatNumber(10), '10'); assert.strictEqual(i18n.formatNumber(100.01), '100.01'); assert.strictEqual(i18n.formatNumber(13000.456), '13,000.456'); @@ -46,7 +40,7 @@ describe('util helpers', () => { }); it('formats a date', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); const timestamp = i18n.formatDateTime('2017-04-28T23:07:51.189Z'); assert.ok( timestamp.includes('Apr 27, 2017') || @@ -56,7 +50,7 @@ describe('util helpers', () => { }); it('formats bytes', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); assert.equal(i18n.formatBytesToKiB(100), `0.098${NBSP}KiB`); assert.equal(i18n.formatBytesToKiB(100, 0.1), `0.1${NBSP}KiB`); assert.equal(i18n.formatBytesToKiB(2000, 0.1), `2.0${NBSP}KiB`); @@ -64,7 +58,7 @@ describe('util helpers', () => { }); it('formats bytes with different granularities', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); let granularity = 10; assert.strictEqual(i18n.formatBytes(15.0, granularity), `20${NBSP}bytes`); @@ -88,7 +82,7 @@ describe('util helpers', () => { }); it('formats bytes with invalid granularity', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); const granularity = 0.5; const originalWarn = console.warn; @@ -103,7 +97,7 @@ describe('util helpers', () => { }); it('formats kibibytes with different granularities', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); let granularity = 10; assert.strictEqual(i18n.formatBytesToKiB(5 * 1024, granularity), `10${NBSP}KiB`); @@ -121,7 +115,7 @@ describe('util helpers', () => { }); it('formats ms', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); assert.equal(i18n.formatMilliseconds(123, 10), `120${NBSP}ms`); assert.equal(i18n.formatMilliseconds(2456.5, 0.1), `2,456.5${NBSP}ms`); assert.equal(i18n.formatMilliseconds(0.000001), `0${NBSP}ms`); @@ -129,21 +123,21 @@ describe('util helpers', () => { }); it('formats a duration', () => { - const i18n = new I18n('en', {...Util.UIStrings}); + const i18n = new Formatter('en'); assert.equal(i18n.formatDuration(60 * 1000), '1m'); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), '1h 5s'); assert.equal(i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), '1d 4h 5s'); }); it('formats a duration based on locale', () => { - let i18n = new I18n('de', {...Util.UIStrings}); + let i18n = new Formatter('de'); assert.equal(i18n.formatDuration(60 * 1000), `1${NBSP}Min.`); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `1${NBSP}Std. 5${NBSP}Sek.`); assert.equal( i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), `1${NBSP}T 4${NBSP}Std. 5${NBSP}Sek.`); // Yes, this is actually backwards (s h d). - i18n = new I18n('ar', {...Util.UIStrings}); + i18n = new Formatter('ar'); /* eslint-disable no-irregular-whitespace */ assert.equal(i18n.formatDuration(60 * 1000), `١${NBSP}د`); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `١${NBSP}س ٥${NBSP}ث`); @@ -155,7 +149,7 @@ describe('util helpers', () => { // Requires full-icu or Intl polyfill. const number = 12346.858558; - const i18n = new I18n('de', {...Util.UIStrings}); + const i18n = new Formatter('de'); assert.strictEqual(i18n.formatNumber(number), '12.346,859'); assert.strictEqual(i18n.formatBytesToKiB(number, 0.1), `12,1${NBSP}KiB`); assert.strictEqual(i18n.formatMilliseconds(number, 10), `12.350${NBSP}ms`); @@ -166,7 +160,7 @@ describe('util helpers', () => { // Requires full-icu or Intl polyfill. const number = 12346.858558; - const i18n = new I18n('en-XA', {...Util.UIStrings}); + const i18n = new Formatter('en-XA'); assert.strictEqual(i18n.formatNumber(number), '12.346,859'); assert.strictEqual(i18n.formatBytesToKiB(number, 0.1), `12,1${NBSP}KiB`); assert.strictEqual(i18n.formatMilliseconds(number, 100), `12.300${NBSP}ms`); @@ -174,7 +168,7 @@ describe('util helpers', () => { }); it('should not crash on unknown locales', () => { - const i18n = new I18n('unknown-mystery-locale', {...Util.UIStrings}); + const i18n = new Formatter('unknown-mystery-locale'); const timestamp = i18n.formatDateTime('2017-04-28T23:07:51.189Z'); assert.ok( timestamp.includes('Apr 27, 2017') || diff --git a/report/test/renderer/performance-category-renderer-test.js b/report/test/renderer/performance-category-renderer-test.js index 340680e07b30..8e8f53f45cc3 100644 --- a/report/test/renderer/performance-category-renderer-test.js +++ b/report/test/renderer/performance-category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {PerformanceCategoryRenderer} from '../../renderer/performance-category-renderer.js'; @@ -23,7 +23,7 @@ describe('PerfCategoryRenderer', () => { let sampleResults; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -36,7 +36,7 @@ describe('PerfCategoryRenderer', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('renders the category header', () => { diff --git a/report/test/renderer/pwa-category-renderer-test.js b/report/test/renderer/pwa-category-renderer-test.js index f575c3cbf6be..3734a8fc7d9a 100644 --- a/report/test/renderer/pwa-category-renderer-test.js +++ b/report/test/renderer/pwa-category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {PwaCategoryRenderer} from '../../renderer/pwa-category-renderer.js'; @@ -23,7 +23,7 @@ describe('PwaCategoryRenderer', () => { let sampleResults; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -40,7 +40,7 @@ describe('PwaCategoryRenderer', () => { }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('renders the regular audits', () => { diff --git a/report/test/renderer/snippet-renderer-test.js b/report/test/renderer/snippet-renderer-test.js index 56a0574a9961..47c3258965cb 100644 --- a/report/test/renderer/snippet-renderer-test.js +++ b/report/test/renderer/snippet-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {DOM} from '../../renderer/dom.js'; import {SnippetRenderer} from '../../renderer/snippet-renderer.js'; @@ -55,13 +55,13 @@ describe('DetailsRenderer', () => { let dom; before(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); }); after(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); function renderSnippet(details) { diff --git a/report/test/renderer/util-test.js b/report/test/renderer/util-test.js index 6df01c86e217..487ba8670c8d 100644 --- a/report/test/renderer/util-test.js +++ b/report/test/renderer/util-test.js @@ -7,18 +7,18 @@ import assert from 'assert/strict'; import {Util} from '../../renderer/util.js'; -import {I18n} from '../../renderer/i18n.js'; +import {Formatter} from '../../renderer/formatter.js'; import {readJson} from '../../../core/test/test-utils.js'; const sampleResult = readJson('../../../core/test/results/sample_v2.json', import.meta); describe('util helpers', () => { beforeEach(() => { - Util.i18n = new I18n('en', {...Util.UIStrings}); + Util.formatter = new Formatter('en'); }); afterEach(() => { - Util.i18n = undefined; + Util.formatter = undefined; }); it('calculates a score ratings', () => { diff --git a/treemap/app/src/main.js b/treemap/app/src/main.js index 3cb7c74bc7b9..56a51056a4e1 100644 --- a/treemap/app/src/main.js +++ b/treemap/app/src/main.js @@ -11,7 +11,7 @@ import {TreemapUtil} from './util.js'; import {DragAndDrop} from '../../../viewer/app/src/drag-and-drop.js'; import {GithubApi} from '../../../viewer/app/src/github-api.js'; -import {I18n} from '../../../report/renderer/i18n.js'; +import {Formatter} from '../../../report/renderer/formatter.js'; import {TextEncoding} from '../../../report/renderer/text-encoding.js'; import {Logger} from '../../../report/renderer/logger.js'; @@ -156,7 +156,7 @@ class TreemapViewer { for (const [group, depthOneNodes] of Object.entries(this.depthOneNodesByGroup)) { const allLabel = { - scripts: TreemapUtil.i18n.strings.allScriptsDropdownLabel, + scripts: TreemapUtil.strings.allScriptsDropdownLabel, }[group] || `All ${group}`; makeOption({type: 'group', value: group}, allLabel); for (const depthOneNode of depthOneNodes) { @@ -268,8 +268,8 @@ class TreemapViewer { return { id: 'unused-bytes', - label: TreemapUtil.i18n.strings.unusedBytesLabel, - subLabel: TreemapUtil.i18n.formatBytesWithBestUnit(root.unusedBytes), + label: TreemapUtil.strings.unusedBytesLabel, + subLabel: TreemapUtil.formatter.formatBytesWithBestUnit(root.unusedBytes), enabled: true, }; } @@ -328,8 +328,9 @@ class TreemapViewer { return { id: 'duplicate-modules', - label: TreemapUtil.i18n.strings.duplicateModulesLabel, - subLabel: enabled ? TreemapUtil.i18n.formatBytesWithBestUnit(potentialByteSavings) : 'N/A', + label: TreemapUtil.strings.duplicateModulesLabel, + subLabel: enabled ? + TreemapUtil.formatter.formatBytesWithBestUnit(potentialByteSavings) : 'N/A', highlights, enabled, }; @@ -340,8 +341,9 @@ class TreemapViewer { viewModes.push({ id: 'all', - label: TreemapUtil.i18n.strings.allLabel, - subLabel: TreemapUtil.i18n.formatBytesWithBestUnit(this.currentTreemapRoot.resourceBytes), + label: TreemapUtil.strings.allLabel, + subLabel: TreemapUtil.formatter.formatBytesWithBestUnit( + this.currentTreemapRoot.resourceBytes), enabled: true, }); @@ -488,7 +490,7 @@ class TreemapViewer { const value = dataRow[field]; if (value === undefined) return ''; - return TreemapUtil.i18n.formatBytes(value); + return TreemapUtil.formatter.formatBytes(value); }; return fn; }; @@ -509,7 +511,7 @@ class TreemapViewer { if (!dataRow.unusedBytes) return ''; const percent = dataRow.unusedBytes / dataRow.resourceBytes; - return `${TreemapUtil.i18n.formatPercent(percent)} bytes unused`; + return `${TreemapUtil.formatter.formatPercent(percent)} bytes unused`; }; const gridEl = document.createElement('div'); @@ -531,20 +533,20 @@ class TreemapViewer { ], columns: [ // eslint-disable-next-line max-len - {title: TreemapUtil.i18n.strings.tableColumnName, field: 'name', widthGrow: 5, tooltip: makeNameTooltip}, + {title: TreemapUtil.strings.tableColumnName, field: 'name', widthGrow: 5, tooltip: makeNameTooltip}, // eslint-disable-next-line max-len - {title: TreemapUtil.i18n.strings.resourceBytesLabel, field: 'resourceBytes', headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('resourceBytes'), formatter: cell => { + {title: TreemapUtil.strings.resourceBytesLabel, field: 'resourceBytes', headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('resourceBytes'), formatter: cell => { const value = cell.getValue(); - return TreemapUtil.i18n.formatBytesWithBestUnit(value); + return TreemapUtil.formatter.formatBytesWithBestUnit(value); }}, // eslint-disable-next-line max-len - {title: TreemapUtil.i18n.strings.unusedBytesLabel, field: 'unusedBytes', widthGrow: 1, sorterParams: {alignEmptyValues: 'bottom'}, headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('unusedBytes'), formatter: cell => { + {title: TreemapUtil.strings.unusedBytesLabel, field: 'unusedBytes', widthGrow: 1, sorterParams: {alignEmptyValues: 'bottom'}, headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('unusedBytes'), formatter: cell => { const value = cell.getValue(); if (value === undefined) return ''; - return TreemapUtil.i18n.formatBytesWithBestUnit(value); + return TreemapUtil.formatter.formatBytesWithBestUnit(value); }}, // eslint-disable-next-line max-len - {title: TreemapUtil.i18n.strings.coverageColumnName, widthGrow: 3, headerSort: false, tooltip: makeCoverageTooltip, formatter: cell => { + {title: TreemapUtil.strings.coverageColumnName, widthGrow: 3, headerSort: false, tooltip: makeCoverageTooltip, formatter: cell => { /** @type {typeof data[number]} */ const dataRow = cell.getRow().getData(); @@ -592,8 +594,8 @@ class TreemapViewer { makeCaption(node) { const partitionBy = this.currentViewMode.partitionBy || 'resourceBytes'; const partitionByStr = { - resourceBytes: TreemapUtil.i18n.strings.resourceBytesLabel, - unusedBytes: TreemapUtil.i18n.strings.unusedBytesLabel, + resourceBytes: TreemapUtil.strings.resourceBytesLabel, + unusedBytes: TreemapUtil.strings.unusedBytesLabel, }[partitionBy]; const bytes = node[partitionBy]; const total = this.currentTreemapRoot[partitionBy]; @@ -604,8 +606,8 @@ class TreemapViewer { if (bytes !== undefined && total !== undefined) { const percent = total === 0 ? 1 : bytes / total; - const percentStr = TreemapUtil.i18n.formatPercent(percent); - let str = `${TreemapUtil.i18n.formatBytesWithBestUnit(bytes)} (${percentStr})`; + const percentStr = TreemapUtil.formatter.formatPercent(percent); + let str = `${TreemapUtil.formatter.formatBytesWithBestUnit(bytes)} (${percentStr})`; // Only add label for bytes on the root node. if (node === this.currentTreemapRoot) { str = `${partitionByStr}: ${str}`; @@ -775,13 +777,10 @@ class LighthouseTreemap { const locale = options.lhr.configSettings.locale; document.documentElement.lang = locale; - const i18n = new I18n(locale, { - // Set missing renderer strings to default (english) values. - ...TreemapUtil.UIStrings, - // `strings` is generated in build/build-treemap.js - ...strings[options.lhr.configSettings.locale], - }); - TreemapUtil.i18n = i18n; + + // `strings` is generated in build/build-treemap.js + TreemapUtil.applyStrings(strings[options.lhr.configSettings.locale]); + TreemapUtil.formatter = new Formatter(locale); // Fill in all i18n data. for (const node of document.querySelectorAll('[data-i18n]')) { @@ -789,7 +788,7 @@ class LighthouseTreemap { // so this cannot be undefined as long as `report-ui-features.data-i18n` test passes. const i18nAttr = /** @type {keyof typeof TreemapUtil['UIStrings']} */ ( node.getAttribute('data-i18n')); - node.textContent = TreemapUtil.i18n.strings[i18nAttr]; + node.textContent = TreemapUtil.strings[i18nAttr]; } if (treemapViewer) { diff --git a/treemap/app/src/util.js b/treemap/app/src/util.js index a3bcd6f4a4c0..79a511830d76 100644 --- a/treemap/app/src/util.js +++ b/treemap/app/src/util.js @@ -8,7 +8,7 @@ /** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElementByTagName */ /** @template {string} T @typedef {import('typed-query-selector/parser').ParseSelector} ParseSelector */ -/** @template T @typedef {import('../../../report/renderer/i18n').I18n} I18n */ +/** @typedef {import('../../../report/renderer/formatter').Formatter} Formatter */ const UIStrings = { /** Label for a button that alternates between showing or hiding a table. */ @@ -30,11 +30,22 @@ const UIStrings = { }; class TreemapUtil { - /** @type {I18n} */ + /** @type {Formatter} */ // @ts-expect-error: Is set in main. - static i18n = null; - + static formatter = null; static UIStrings = UIStrings; + static strings = {...UIStrings}; + + /** + * @param {Record} providedStrings + */ + static applyStrings(providedStrings) { + this.strings = { + // Set missing renderer strings to default (english) values. + ...UIStrings, + ...providedStrings, + }; + } /** * @param {LH.Treemap.Node} node From 510bde15c1d9f4a99803ee3c2b318e15ec333299 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 19 Jan 2023 16:29:58 -0800 Subject: [PATCH 2/3] avoid moving UIString declaration --- core/util.cjs | 248 ++++++++++++++++++++-------------------- report/renderer/util.js | 248 ++++++++++++++++++++-------------------- 2 files changed, 248 insertions(+), 248 deletions(-) diff --git a/core/util.cjs b/core/util.cjs index 02d69377be2c..c14de61abcad 100644 --- a/core/util.cjs +++ b/core/util.cjs @@ -41,134 +41,11 @@ const listOfTlds = [ 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', ]; -/** - * Report-renderer-specific strings. - */ -const UIStrings = { - /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ - varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', - /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ - calculatorLink: 'See calculator.', - /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ - showRelevantAudits: 'Show audits relevant to:', - /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ - opportunityResourceColumnLabel: 'Opportunity', - /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ - opportunitySavingsColumnLabel: 'Estimated Savings', - - /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ - errorMissingAuditInfo: 'Report error: no audit information', - /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ - errorLabel: 'Error!', - /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ - warningHeader: 'Warnings: ', - /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ - warningAuditsGroupTitle: 'Passed audits but with warnings', - /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - passedAuditsGroupTitle: 'Passed audits', - /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - notApplicableAuditsGroupTitle: 'Not applicable', - /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - manualAuditsGroupTitle: 'Additional items to manually check', - - /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ - toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', - - /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ - crcInitialNavigation: 'Initial Navigation', - /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ - crcLongestDurationLabel: 'Maximum critical path latency:', - - /** Label for button that shows all lines of the snippet when clicked */ - snippetExpandButtonLabel: 'Expand snippet', - /** Label for button that only shows a few lines of the snippet when clicked */ - snippetCollapseButtonLabel: 'Collapse snippet', - - /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ - lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', - /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ - labDataTitle: 'Lab Data', - - /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ - thirdPartyResourcesLabel: 'Show 3rd-party resources', - /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ - viewTreemapLabel: 'View Treemap', - /** This label is for a button that will show the user a trace of the page. */ - viewTraceLabel: 'View Trace', - /** This label is for a button that will show the user a trace of the page. */ - viewOriginalTraceLabel: 'View Original Trace', - - /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ - dropdownPrintSummary: 'Print Summary', - /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ - dropdownPrintExpanded: 'Print Expanded', - /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ - dropdownCopyJSON: 'Copy JSON', - /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ - dropdownSaveHTML: 'Save as HTML', - /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ - dropdownSaveJSON: 'Save as JSON', - /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ - dropdownViewer: 'Open in Viewer', - /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ - dropdownSaveGist: 'Save as Gist', - /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ - dropdownDarkTheme: 'Toggle Dark Theme', - - /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ - runtimeSettingsDevice: 'Device', - /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ - runtimeSettingsNetworkThrottling: 'Network throttling', - /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ - runtimeSettingsCPUThrottling: 'CPU throttling', - /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ - runtimeSettingsUANetwork: 'User agent (network)', - /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ - runtimeSettingsBenchmark: 'CPU/Memory Power', - /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ - runtimeSettingsAxeVersion: 'Axe version', - /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ - runtimeSettingsScreenEmulation: 'Screen emulation', - - /** Label for button to create an issue against the Lighthouse GitHub project. */ - footerIssue: 'File an issue', - - /** Descriptive explanation for emulation setting when no device emulation is set. */ - runtimeNoEmulation: 'No emulation', - /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ - runtimeMobileEmulation: 'Emulated Moto G4', - /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ - runtimeDesktopEmulation: 'Emulated Desktop', - /** Descriptive explanation for a runtime setting that is set to an unknown value. */ - runtimeUnknown: 'Unknown', - /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ - runtimeSingleLoad: 'Single page load', - /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ - runtimeAnalysisWindow: 'Initial page load', - /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ - runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len - - /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ - throttlingProvided: 'Provided by environment', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - show: 'Show', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - hide: 'Hide', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - expandView: 'Expand view', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - collapseView: 'Collapse view', - /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ - runtimeSlow4g: 'Slow 4G throttling', - /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ - runtimeCustom: 'Custom throttling', -}; - class Util { /** @type {Formatter} */ // @ts-expect-error: Is set in report renderer. static formatter = null; - static strings = {...UIStrings}; + static strings = /** @type {typeof UIStrings} */ ({}); /** * @param {Record} providedStrings @@ -819,7 +696,130 @@ Util.resetUniqueSuffix = () => { svgSuffix = 0; }; +/** + * Report-renderer-specific strings. + */ +const UIStrings = { + /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ + varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', + /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ + calculatorLink: 'See calculator.', + /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ + showRelevantAudits: 'Show audits relevant to:', + /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ + opportunityResourceColumnLabel: 'Opportunity', + /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ + opportunitySavingsColumnLabel: 'Estimated Savings', + + /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ + errorMissingAuditInfo: 'Report error: no audit information', + /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ + errorLabel: 'Error!', + /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ + warningHeader: 'Warnings: ', + /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ + warningAuditsGroupTitle: 'Passed audits but with warnings', + /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + passedAuditsGroupTitle: 'Passed audits', + /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + notApplicableAuditsGroupTitle: 'Not applicable', + /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + manualAuditsGroupTitle: 'Additional items to manually check', + + /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ + toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', + + /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ + crcInitialNavigation: 'Initial Navigation', + /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ + crcLongestDurationLabel: 'Maximum critical path latency:', + + /** Label for button that shows all lines of the snippet when clicked */ + snippetExpandButtonLabel: 'Expand snippet', + /** Label for button that only shows a few lines of the snippet when clicked */ + snippetCollapseButtonLabel: 'Collapse snippet', + + /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ + lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', + /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ + labDataTitle: 'Lab Data', + + /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourcesLabel: 'Show 3rd-party resources', + /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ + viewTreemapLabel: 'View Treemap', + /** This label is for a button that will show the user a trace of the page. */ + viewTraceLabel: 'View Trace', + /** This label is for a button that will show the user a trace of the page. */ + viewOriginalTraceLabel: 'View Original Trace', + + /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ + dropdownPrintSummary: 'Print Summary', + /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ + dropdownPrintExpanded: 'Print Expanded', + /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ + dropdownCopyJSON: 'Copy JSON', + /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ + dropdownSaveHTML: 'Save as HTML', + /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ + dropdownSaveJSON: 'Save as JSON', + /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ + dropdownViewer: 'Open in Viewer', + /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ + dropdownSaveGist: 'Save as Gist', + /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ + dropdownDarkTheme: 'Toggle Dark Theme', + + /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ + runtimeSettingsDevice: 'Device', + /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ + runtimeSettingsNetworkThrottling: 'Network throttling', + /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ + runtimeSettingsCPUThrottling: 'CPU throttling', + /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ + runtimeSettingsUANetwork: 'User agent (network)', + /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ + runtimeSettingsBenchmark: 'CPU/Memory Power', + /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ + runtimeSettingsAxeVersion: 'Axe version', + /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ + runtimeSettingsScreenEmulation: 'Screen emulation', + + /** Label for button to create an issue against the Lighthouse GitHub project. */ + footerIssue: 'File an issue', + + /** Descriptive explanation for emulation setting when no device emulation is set. */ + runtimeNoEmulation: 'No emulation', + /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ + runtimeMobileEmulation: 'Emulated Moto G4', + /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ + runtimeDesktopEmulation: 'Emulated Desktop', + /** Descriptive explanation for a runtime setting that is set to an unknown value. */ + runtimeUnknown: 'Unknown', + /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ + runtimeSingleLoad: 'Single page load', + /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ + runtimeAnalysisWindow: 'Initial page load', + /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ + runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len + + /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ + throttlingProvided: 'Provided by environment', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + show: 'Show', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + hide: 'Hide', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + expandView: 'Expand view', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + collapseView: 'Collapse view', + /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ + runtimeSlow4g: 'Slow 4G throttling', + /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ + runtimeCustom: 'Custom throttling', +}; Util.UIStrings = UIStrings; +Util.strings = {...UIStrings}; module.exports = { Util, diff --git a/report/renderer/util.js b/report/renderer/util.js index 4fef060d6ede..019b693714e4 100644 --- a/report/renderer/util.js +++ b/report/renderer/util.js @@ -37,134 +37,11 @@ const listOfTlds = [ 'web', 'spb', 'blog', 'jus', 'kiev', 'mil', 'wi', 'qc', 'ca', 'bel', 'on', ]; -/** - * Report-renderer-specific strings. - */ -const UIStrings = { - /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ - varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', - /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ - calculatorLink: 'See calculator.', - /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ - showRelevantAudits: 'Show audits relevant to:', - /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ - opportunityResourceColumnLabel: 'Opportunity', - /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ - opportunitySavingsColumnLabel: 'Estimated Savings', - - /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ - errorMissingAuditInfo: 'Report error: no audit information', - /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ - errorLabel: 'Error!', - /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ - warningHeader: 'Warnings: ', - /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ - warningAuditsGroupTitle: 'Passed audits but with warnings', - /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - passedAuditsGroupTitle: 'Passed audits', - /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - notApplicableAuditsGroupTitle: 'Not applicable', - /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ - manualAuditsGroupTitle: 'Additional items to manually check', - - /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ - toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', - - /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ - crcInitialNavigation: 'Initial Navigation', - /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ - crcLongestDurationLabel: 'Maximum critical path latency:', - - /** Label for button that shows all lines of the snippet when clicked */ - snippetExpandButtonLabel: 'Expand snippet', - /** Label for button that only shows a few lines of the snippet when clicked */ - snippetCollapseButtonLabel: 'Collapse snippet', - - /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ - lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', - /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ - labDataTitle: 'Lab Data', - - /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ - thirdPartyResourcesLabel: 'Show 3rd-party resources', - /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ - viewTreemapLabel: 'View Treemap', - /** This label is for a button that will show the user a trace of the page. */ - viewTraceLabel: 'View Trace', - /** This label is for a button that will show the user a trace of the page. */ - viewOriginalTraceLabel: 'View Original Trace', - - /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ - dropdownPrintSummary: 'Print Summary', - /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ - dropdownPrintExpanded: 'Print Expanded', - /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ - dropdownCopyJSON: 'Copy JSON', - /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ - dropdownSaveHTML: 'Save as HTML', - /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ - dropdownSaveJSON: 'Save as JSON', - /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ - dropdownViewer: 'Open in Viewer', - /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ - dropdownSaveGist: 'Save as Gist', - /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ - dropdownDarkTheme: 'Toggle Dark Theme', - - /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ - runtimeSettingsDevice: 'Device', - /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ - runtimeSettingsNetworkThrottling: 'Network throttling', - /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ - runtimeSettingsCPUThrottling: 'CPU throttling', - /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ - runtimeSettingsUANetwork: 'User agent (network)', - /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ - runtimeSettingsBenchmark: 'CPU/Memory Power', - /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ - runtimeSettingsAxeVersion: 'Axe version', - /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ - runtimeSettingsScreenEmulation: 'Screen emulation', - - /** Label for button to create an issue against the Lighthouse GitHub project. */ - footerIssue: 'File an issue', - - /** Descriptive explanation for emulation setting when no device emulation is set. */ - runtimeNoEmulation: 'No emulation', - /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ - runtimeMobileEmulation: 'Emulated Moto G4', - /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ - runtimeDesktopEmulation: 'Emulated Desktop', - /** Descriptive explanation for a runtime setting that is set to an unknown value. */ - runtimeUnknown: 'Unknown', - /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ - runtimeSingleLoad: 'Single page load', - /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ - runtimeAnalysisWindow: 'Initial page load', - /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ - runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len - - /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ - throttlingProvided: 'Provided by environment', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - show: 'Show', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ - hide: 'Hide', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - expandView: 'Expand view', - /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ - collapseView: 'Collapse view', - /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ - runtimeSlow4g: 'Slow 4G throttling', - /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ - runtimeCustom: 'Custom throttling', -}; - class Util { /** @type {Formatter} */ // @ts-expect-error: Is set in report renderer. static formatter = null; - static strings = {...UIStrings}; + static strings = /** @type {typeof UIStrings} */ ({}); /** * @param {Record} providedStrings @@ -815,7 +692,130 @@ Util.resetUniqueSuffix = () => { svgSuffix = 0; }; +/** + * Report-renderer-specific strings. + */ +const UIStrings = { + /** Disclaimer shown to users below the metric values (First Contentful Paint, Time to Interactive, etc) to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. */ + varianceDisclaimer: 'Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.', + /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */ + calculatorLink: 'See calculator.', + /** Label preceding a radio control for filtering the list of audits. The radio choices are various performance metrics (FCP, LCP, TBT), and if chosen, the audits in the report are hidden if they are not relevant to the selected metric. */ + showRelevantAudits: 'Show audits relevant to:', + /** Column heading label for the listing of opportunity audits. Each audit title represents an opportunity. There are only 2 columns, so no strict character limit. */ + opportunityResourceColumnLabel: 'Opportunity', + /** Column heading label for the estimated page load savings of opportunity audits. Estimated Savings is the total amount of time (in seconds) that Lighthouse computed could be reduced from the total page load time, if the suggested action is taken. There are only 2 columns, so no strict character limit. */ + opportunitySavingsColumnLabel: 'Estimated Savings', + + /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */ + errorMissingAuditInfo: 'Report error: no audit information', + /** A label, shown next to an audit title or metric title, indicating that there was an error computing it. The user can hover on the label to reveal a tooltip with the extended error message. Translation should be short (< 20 characters). */ + errorLabel: 'Error!', + /** This label is shown above a bulleted list of warnings. It is shown directly below an audit that produced warnings. Warnings describe situations the user should be aware of, as Lighthouse was unable to complete all the work required on this audit. For example, The 'Unable to decode image (biglogo.jpg)' warning may show up below an image encoding audit. */ + warningHeader: 'Warnings: ', + /** Section heading shown above a list of passed audits that contain warnings. Audits under this section do not negatively impact the score, but Lighthouse has generated some potentially actionable suggestions that should be reviewed. This section is expanded by default and displays after the failing audits. */ + warningAuditsGroupTitle: 'Passed audits but with warnings', + /** Section heading shown above a list of audits that are passing. 'Passed' here refers to a passing grade. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + passedAuditsGroupTitle: 'Passed audits', + /** Section heading shown above a list of audits that do not apply to the page. For example, if an audit is 'Are images optimized?', but the page has no images on it, the audit will be marked as not applicable. This is neither passing or failing. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + notApplicableAuditsGroupTitle: 'Not applicable', + /** Section heading shown above a list of audits that were not computed by Lighthouse. They serve as a list of suggestions for the user to go and manually check. For example, Lighthouse can't automate testing cross-browser compatibility, so that is listed within this section, so the user is reminded to test it themselves. This section is collapsed by default, as the user should be focusing on the failed audits instead. Users can click this heading to reveal the list. */ + manualAuditsGroupTitle: 'Additional items to manually check', + + /** Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI. */ + toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', + + /** String of text shown in a graphical representation of the flow of network requests for the web page. This label represents the initial network request that fetches an HTML page. This navigation may be redirected (eg. Initial navigation to http://example.com redirects to https://www.example.com). */ + crcInitialNavigation: 'Initial Navigation', + /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */ + crcLongestDurationLabel: 'Maximum critical path latency:', + + /** Label for button that shows all lines of the snippet when clicked */ + snippetExpandButtonLabel: 'Expand snippet', + /** Label for button that only shows a few lines of the snippet when clicked */ + snippetCollapseButtonLabel: 'Collapse snippet', + + /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */ + lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.', + /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site. */ + labDataTitle: 'Lab Data', + + /** This label is for a checkbox above a table of items loaded by a web page. The checkbox is used to show or hide third-party (or "3rd-party") resources in the table, where "third-party resources" refers to items loaded by a web page from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourcesLabel: 'Show 3rd-party resources', + /** This label is for a button that opens a new tab to a webapp called "Treemap", which is a nested visual representation of a heierarchy of data related to the reports (script bytes and coverage, resource breakdown, etc.) */ + viewTreemapLabel: 'View Treemap', + /** This label is for a button that will show the user a trace of the page. */ + viewTraceLabel: 'View Trace', + /** This label is for a button that will show the user a trace of the page. */ + viewOriginalTraceLabel: 'View Original Trace', + + /** Option in a dropdown menu that opens a small, summary report in a print dialog. */ + dropdownPrintSummary: 'Print Summary', + /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */ + dropdownPrintExpanded: 'Print Expanded', + /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */ + dropdownCopyJSON: 'Copy JSON', + /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */ + dropdownSaveHTML: 'Save as HTML', + /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */ + dropdownSaveJSON: 'Save as JSON', + /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */ + dropdownViewer: 'Open in Viewer', + /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */ + dropdownSaveGist: 'Save as Gist', + /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */ + dropdownDarkTheme: 'Toggle Dark Theme', + + /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */ + runtimeSettingsDevice: 'Device', + /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */ + runtimeSettingsNetworkThrottling: 'Network throttling', + /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/ + runtimeSettingsCPUThrottling: 'CPU throttling', + /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */ + runtimeSettingsUANetwork: 'User agent (network)', + /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */ + runtimeSettingsBenchmark: 'CPU/Memory Power', + /** Label for a row in a table that shows the version of the Axe library used. Example row values: 2.1.0, 3.2.3 */ + runtimeSettingsAxeVersion: 'Axe version', + /** Label for a row in a table that shows the screen resolution and DPR that was emulated for the Lighthouse run. Example values: '800x600, DPR: 3' */ + runtimeSettingsScreenEmulation: 'Screen emulation', + + /** Label for button to create an issue against the Lighthouse GitHub project. */ + footerIssue: 'File an issue', + + /** Descriptive explanation for emulation setting when no device emulation is set. */ + runtimeNoEmulation: 'No emulation', + /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */ + runtimeMobileEmulation: 'Emulated Moto G4', + /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */ + runtimeDesktopEmulation: 'Emulated Desktop', + /** Descriptive explanation for a runtime setting that is set to an unknown value. */ + runtimeUnknown: 'Unknown', + /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */ + runtimeSingleLoad: 'Single page load', + /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */ + runtimeAnalysisWindow: 'Initial page load', + /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */ + runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len + + /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */ + throttlingProvided: 'Provided by environment', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + show: 'Show', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */ + hide: 'Hide', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + expandView: 'Expand view', + /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Expand view' and 'Collapse view'. */ + collapseView: 'Collapse view', + /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */ + runtimeSlow4g: 'Slow 4G throttling', + /** Label indicating that Lighthouse throttled the page using custom throttling settings. */ + runtimeCustom: 'Custom throttling', +}; Util.UIStrings = UIStrings; +Util.strings = {...UIStrings}; export { Util, From f9a7bddf7e3f64808de6f6dccecc7092184f49f2 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 23 Jan 2023 11:57:37 -0800 Subject: [PATCH 3/3] keep Util.i18n name --- core/util.cjs | 20 ++++++------- flow-report/src/i18n/i18n.tsx | 12 ++++---- report/renderer/crc-details-renderer.js | 6 ++-- report/renderer/details-renderer.js | 10 +++---- .../{formatter.js => i18n-formatter.js} | 2 +- .../renderer/performance-category-renderer.js | 2 +- report/renderer/report-renderer.js | 6 ++-- report/renderer/util.js | 20 ++++++------- .../test/renderer/category-renderer-test.js | 6 ++-- .../renderer/crc-details-renderer-test.js | 6 ++-- report/test/renderer/details-renderer-test.js | 6 ++-- report/test/renderer/dom-test.js | 6 ++-- .../element-screenshot-renderer-test.js | 6 ++-- ...rmatter-test.js => i18n-formatter-test.js} | 30 +++++++++---------- .../performance-category-renderer-test.js | 6 ++-- .../renderer/pwa-category-renderer-test.js | 6 ++-- report/test/renderer/snippet-renderer-test.js | 6 ++-- report/test/renderer/util-test.js | 6 ++-- treemap/app/src/main.js | 22 +++++++------- treemap/app/src/util.js | 6 ++-- 20 files changed, 95 insertions(+), 95 deletions(-) rename report/renderer/{formatter.js => i18n-formatter.js} (99%) rename report/test/renderer/{formatter-test.js => i18n-formatter-test.js} (91%) diff --git a/core/util.cjs b/core/util.cjs index c14de61abcad..41faec335071 100644 --- a/core/util.cjs +++ b/core/util.cjs @@ -19,7 +19,7 @@ * limitations under the License. */ -/** @typedef {import('./formatter').Formatter} Formatter */ +/** @typedef {import('./i18n-formatter').I18nFormatter} I18nFormatter */ const ELLIPSIS = '\u2026'; const NBSP = '\xa0'; @@ -42,9 +42,9 @@ const listOfTlds = [ ]; class Util { - /** @type {Formatter} */ + /** @type {I18nFormatter} */ // @ts-expect-error: Is set in report renderer. - static formatter = null; + static i18n = null; static strings = /** @type {typeof UIStrings} */ ({}); /** @@ -528,10 +528,10 @@ class Util { case 'devtools': { const {cpuSlowdownMultiplier, requestLatencyMs} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; - networkThrottling = `${Util.formatter.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + - `${Util.formatter.formatKbps(throttling.downloadThroughputKbps)} down, ` + - `${Util.formatter.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; + cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; + networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + + `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` + + `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; const isSlow4G = () => { return requestLatencyMs === 150 * 3.75 && @@ -545,9 +545,9 @@ class Util { case 'simulate': { const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; - networkThrottling = `${Util.formatter.formatMilliseconds(rttMs)} TCP RTT, ` + - `${Util.formatter.formatKbps(throughputKbps)} throughput (Simulated)`; + cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; + networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` + + `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`; const isSlow4G = () => { return rttMs === 150 && throughputKbps === 1.6 * 1024; diff --git a/flow-report/src/i18n/i18n.tsx b/flow-report/src/i18n/i18n.tsx index 4e1c6d594f25..a2091cf2f518 100644 --- a/flow-report/src/i18n/i18n.tsx +++ b/flow-report/src/i18n/i18n.tsx @@ -8,14 +8,14 @@ import {createContext, FunctionComponent} from 'preact'; import {useContext, useMemo} from 'preact/hooks'; import {formatMessage} from '../../../shared/localization/format'; -import {Formatter} from '../../../report/renderer/formatter'; +import {I18nFormatter} from '../../../report/renderer/i18n-formatter'; import {UIStrings} from './ui-strings'; import {useFlowResult} from '../util'; import strings from './localized-strings.js'; import {Util} from '../../../report/renderer/util'; const I18nContext = createContext({ - formatter: new Formatter('en-US'), + formatter: new I18nFormatter('en-US'), strings: {...Util.UIStrings, ...UIStrings}, }); @@ -53,7 +53,7 @@ function useStringFormatter() { const I18nProvider: FunctionComponent = ({children}) => { const {locale, lhrStrings} = useLhrLocale(); - const formatter = useMemo(() => { + const i18n = useMemo(() => { Util.applyStrings({ // Preload with strings from the first lhr. // Used for legacy report components imported into the flow report. @@ -66,16 +66,16 @@ const I18nProvider: FunctionComponent = ({children}) => { // Initialize renderer util i18n for strings rendered in wrapped components. // TODO: Don't attach global formatter to `Util`. - Util.formatter = new Formatter(locale); + Util.i18n = new I18nFormatter(locale); return { - formatter: Util.formatter, + formatter: Util.i18n, strings: Util.strings as typeof UIStrings & typeof Util.UIStrings, }; }, [locale, lhrStrings]); return ( - + {children} ); diff --git a/report/renderer/crc-details-renderer.js b/report/renderer/crc-details-renderer.js index 67157b34a412..e524c3939a59 100644 --- a/report/renderer/crc-details-renderer.js +++ b/report/renderer/crc-details-renderer.js @@ -137,9 +137,9 @@ class CriticalRequestChainRenderer { const {startTime, endTime, transferSize} = segment.node.request; const span = dom.createElement('span', 'lh-crc-node__chain-duration'); span.textContent = - ' - ' + Util.formatter.formatMilliseconds((endTime - startTime) * 1000) + ', '; + ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', '; const span2 = dom.createElement('span', 'lh-crc-node__chain-duration'); - span2.textContent = Util.formatter.formatBytesToKiB(transferSize, 0.01); + span2.textContent = Util.i18n.formatBytesToKiB(transferSize, 0.01); treevalEl.append(span, span2); } @@ -182,7 +182,7 @@ class CriticalRequestChainRenderer { dom.find('.lh-crc__longest_duration_label', tmpl).textContent = Util.strings.crcLongestDurationLabel; dom.find('.lh-crc__longest_duration', tmpl).textContent = - Util.formatter.formatMilliseconds(details.longestChain.duration); + Util.i18n.formatMilliseconds(details.longestChain.duration); // Construct visual tree. const root = CRCRenderer.initTree(details.chains); diff --git a/report/renderer/details-renderer.js b/report/renderer/details-renderer.js index 29e73e965d0c..bc4aa925b9bd 100644 --- a/report/renderer/details-renderer.js +++ b/report/renderer/details-renderer.js @@ -77,9 +77,9 @@ export class DetailsRenderer { */ _renderBytes(details) { // TODO: handle displayUnit once we have something other than 'KiB' - const value = Util.formatter.formatBytesToKiB(details.value, details.granularity || 0.1); + const value = Util.i18n.formatBytesToKiB(details.value, details.granularity || 0.1); const textEl = this._renderText(value); - textEl.title = Util.formatter.formatBytes(details.value); + textEl.title = Util.i18n.formatBytes(details.value); return textEl; } @@ -90,9 +90,9 @@ export class DetailsRenderer { _renderMilliseconds(details) { let value; if (details.displayUnit === 'duration') { - value = Util.formatter.formatDuration(details.value); + value = Util.i18n.formatDuration(details.value); } else { - value = Util.formatter.formatMilliseconds(details.value, details.granularity || 10); + value = Util.i18n.formatMilliseconds(details.value, details.granularity || 10); } return this._renderText(value); @@ -171,7 +171,7 @@ export class DetailsRenderer { * @return {Element} */ _renderNumeric(details) { - const value = Util.formatter.formatNumber(details.value, details.granularity || 0.1); + const value = Util.i18n.formatNumber(details.value, details.granularity || 0.1); const element = this._dom.createElement('div', 'lh-numeric'); element.textContent = value; return element; diff --git a/report/renderer/formatter.js b/report/renderer/i18n-formatter.js similarity index 99% rename from report/renderer/formatter.js rename to report/renderer/i18n-formatter.js index bf82df865ed2..bc0cb36f604d 100644 --- a/report/renderer/formatter.js +++ b/report/renderer/i18n-formatter.js @@ -9,7 +9,7 @@ const NBSP2 = '\xa0'; const KiB = 1024; const MiB = KiB * KiB; -export class Formatter { +export class I18nFormatter { /** * @param {LH.Locale} locale */ diff --git a/report/renderer/performance-category-renderer.js b/report/renderer/performance-category-renderer.js index 41cb38c945fc..67f5f28a77a9 100644 --- a/report/renderer/performance-category-renderer.js +++ b/report/renderer/performance-category-renderer.js @@ -77,7 +77,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { this.dom.find('span.lh-audit__display-text, div.lh-audit__display-text', element); const sparklineWidthPct = `${details.overallSavingsMs / scale * 100}%`; this.dom.find('div.lh-sparkline__bar', element).style.width = sparklineWidthPct; - displayEl.textContent = Util.formatter.formatSeconds(details.overallSavingsMs, 0.01); + displayEl.textContent = Util.i18n.formatSeconds(details.overallSavingsMs, 0.01); // Set [title] tooltips if (audit.result.displayValue) { diff --git a/report/renderer/report-renderer.js b/report/renderer/report-renderer.js index 8d44334aa343..7c55149a7c14 100644 --- a/report/renderer/report-renderer.js +++ b/report/renderer/report-renderer.js @@ -23,7 +23,7 @@ import {CategoryRenderer} from './category-renderer.js'; import {DetailsRenderer} from './details-renderer.js'; import {ElementScreenshotRenderer} from './element-screenshot-renderer.js'; -import {Formatter} from './formatter.js'; +import {I18nFormatter} from './i18n-formatter.js'; import {PerformanceCategoryRenderer} from './performance-category-renderer.js'; import {PwaCategoryRenderer} from './pwa-category-renderer.js'; import {Util} from './util.js'; @@ -142,7 +142,7 @@ export class ReportRenderer { // [CSS icon class, textContent, tooltipText] const metaItems = [ ['date', - `Captured at ${Util.formatter.formatDateTime(report.fetchTime)}`], + `Captured at ${Util.i18n.formatDateTime(report.fetchTime)}`], ['devices', `${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`, devicesTooltipTextLines.join('\n')], @@ -261,7 +261,7 @@ export class ReportRenderer { */ _renderReport(report) { Util.applyStrings(report.i18n.rendererFormattedStrings); - Util.formatter = new Formatter(report.configSettings.locale); + Util.i18n = new I18nFormatter(report.configSettings.locale); Util.reportJson = report; const detailsRenderer = new DetailsRenderer(this._dom, { diff --git a/report/renderer/util.js b/report/renderer/util.js index 019b693714e4..f2cd0123ddc0 100644 --- a/report/renderer/util.js +++ b/report/renderer/util.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/** @typedef {import('./formatter').Formatter} Formatter */ +/** @typedef {import('./i18n-formatter').I18nFormatter} I18nFormatter */ const ELLIPSIS = '\u2026'; const NBSP = '\xa0'; @@ -38,9 +38,9 @@ const listOfTlds = [ ]; class Util { - /** @type {Formatter} */ + /** @type {I18nFormatter} */ // @ts-expect-error: Is set in report renderer. - static formatter = null; + static i18n = null; static strings = /** @type {typeof UIStrings} */ ({}); /** @@ -524,10 +524,10 @@ class Util { case 'devtools': { const {cpuSlowdownMultiplier, requestLatencyMs} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; - networkThrottling = `${Util.formatter.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + - `${Util.formatter.formatKbps(throttling.downloadThroughputKbps)} down, ` + - `${Util.formatter.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; + cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; + networkThrottling = `${Util.i18n.formatMilliseconds(requestLatencyMs)} HTTP RTT, ` + + `${Util.i18n.formatKbps(throttling.downloadThroughputKbps)} down, ` + + `${Util.i18n.formatKbps(throttling.uploadThroughputKbps)} up (DevTools)`; const isSlow4G = () => { return requestLatencyMs === 150 * 3.75 && @@ -541,9 +541,9 @@ class Util { case 'simulate': { const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling; // eslint-disable-next-line max-len - cpuThrottling = `${Util.formatter.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; - networkThrottling = `${Util.formatter.formatMilliseconds(rttMs)} TCP RTT, ` + - `${Util.formatter.formatKbps(throughputKbps)} throughput (Simulated)`; + cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; + networkThrottling = `${Util.i18n.formatMilliseconds(rttMs)} TCP RTT, ` + + `${Util.i18n.formatKbps(throughputKbps)} throughput (Simulated)`; const isSlow4G = () => { return rttMs === 150 && throughputKbps === 1.6 * 1024; diff --git a/report/test/renderer/category-renderer-test.js b/report/test/renderer/category-renderer-test.js index 22ca755f2570..cecb44611806 100644 --- a/report/test/renderer/category-renderer-test.js +++ b/report/test/renderer/category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {CategoryRenderer} from '../../renderer/category-renderer.js'; @@ -22,7 +22,7 @@ describe('CategoryRenderer', () => { let sampleResults; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -33,7 +33,7 @@ describe('CategoryRenderer', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('renders an audit', () => { diff --git a/report/test/renderer/crc-details-renderer-test.js b/report/test/renderer/crc-details-renderer-test.js index e56290a7d58a..a95eb5d9e54b 100644 --- a/report/test/renderer/crc-details-renderer-test.js +++ b/report/test/renderer/crc-details-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {CriticalRequestChainRenderer} from '../../renderer/crc-details-renderer.js'; @@ -73,7 +73,7 @@ describe('DetailsRenderer', () => { let detailsRenderer; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); @@ -81,7 +81,7 @@ describe('DetailsRenderer', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('renders tree structure', () => { diff --git a/report/test/renderer/details-renderer-test.js b/report/test/renderer/details-renderer-test.js index b89aea6ac1b2..e32fc33bec43 100644 --- a/report/test/renderer/details-renderer-test.js +++ b/report/test/renderer/details-renderer-test.js @@ -10,7 +10,7 @@ import jsdom from 'jsdom'; import {DOM} from '../../renderer/dom.js'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; describe('DetailsRenderer', () => { @@ -23,12 +23,12 @@ describe('DetailsRenderer', () => { } before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); createRenderer(); }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); describe('render', () => { diff --git a/report/test/renderer/dom-test.js b/report/test/renderer/dom-test.js index 2e34c46374f5..a7446204a310 100644 --- a/report/test/renderer/dom-test.js +++ b/report/test/renderer/dom-test.js @@ -11,7 +11,7 @@ import jsdom from 'jsdom'; import {DOM} from '../../renderer/dom.js'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; describe('DOM', () => { /** @type {DOM} */ @@ -20,7 +20,7 @@ describe('DOM', () => { let nativeCreateObjectURL; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); window = new jsdom.JSDOM().window; // The Node version of URL.createObjectURL isn't compatible with the jsdom blob type, @@ -33,7 +33,7 @@ describe('DOM', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; URL.createObjectURL = nativeCreateObjectURL; }); diff --git a/report/test/renderer/element-screenshot-renderer-test.js b/report/test/renderer/element-screenshot-renderer-test.js index 7c760dde9357..83ec400cb443 100644 --- a/report/test/renderer/element-screenshot-renderer-test.js +++ b/report/test/renderer/element-screenshot-renderer-test.js @@ -8,7 +8,7 @@ import jsdom from 'jsdom'; import {ElementScreenshotRenderer} from '../../renderer/element-screenshot-renderer.js'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; /** @@ -27,7 +27,7 @@ describe('ElementScreenshotRenderer', () => { let dom; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); @@ -35,7 +35,7 @@ describe('ElementScreenshotRenderer', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('renders screenshot', () => { diff --git a/report/test/renderer/formatter-test.js b/report/test/renderer/i18n-formatter-test.js similarity index 91% rename from report/test/renderer/formatter-test.js rename to report/test/renderer/i18n-formatter-test.js index 700491923df1..3af5e67c4bf6 100644 --- a/report/test/renderer/formatter-test.js +++ b/report/test/renderer/i18n-formatter-test.js @@ -6,13 +6,13 @@ import assert from 'assert/strict'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; const NBSP = '\xa0'; -describe('util helpers', () => { +describe('i18n formatter', () => { it('formats a number', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); assert.strictEqual(i18n.formatNumber(10), '10'); assert.strictEqual(i18n.formatNumber(100.01), '100.01'); assert.strictEqual(i18n.formatNumber(13000.456), '13,000.456'); @@ -40,7 +40,7 @@ describe('util helpers', () => { }); it('formats a date', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); const timestamp = i18n.formatDateTime('2017-04-28T23:07:51.189Z'); assert.ok( timestamp.includes('Apr 27, 2017') || @@ -50,7 +50,7 @@ describe('util helpers', () => { }); it('formats bytes', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); assert.equal(i18n.formatBytesToKiB(100), `0.098${NBSP}KiB`); assert.equal(i18n.formatBytesToKiB(100, 0.1), `0.1${NBSP}KiB`); assert.equal(i18n.formatBytesToKiB(2000, 0.1), `2.0${NBSP}KiB`); @@ -58,7 +58,7 @@ describe('util helpers', () => { }); it('formats bytes with different granularities', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); let granularity = 10; assert.strictEqual(i18n.formatBytes(15.0, granularity), `20${NBSP}bytes`); @@ -82,7 +82,7 @@ describe('util helpers', () => { }); it('formats bytes with invalid granularity', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); const granularity = 0.5; const originalWarn = console.warn; @@ -97,7 +97,7 @@ describe('util helpers', () => { }); it('formats kibibytes with different granularities', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); let granularity = 10; assert.strictEqual(i18n.formatBytesToKiB(5 * 1024, granularity), `10${NBSP}KiB`); @@ -115,7 +115,7 @@ describe('util helpers', () => { }); it('formats ms', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); assert.equal(i18n.formatMilliseconds(123, 10), `120${NBSP}ms`); assert.equal(i18n.formatMilliseconds(2456.5, 0.1), `2,456.5${NBSP}ms`); assert.equal(i18n.formatMilliseconds(0.000001), `0${NBSP}ms`); @@ -123,21 +123,21 @@ describe('util helpers', () => { }); it('formats a duration', () => { - const i18n = new Formatter('en'); + const i18n = new I18nFormatter('en'); assert.equal(i18n.formatDuration(60 * 1000), '1m'); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), '1h 5s'); assert.equal(i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), '1d 4h 5s'); }); it('formats a duration based on locale', () => { - let i18n = new Formatter('de'); + let i18n = new I18nFormatter('de'); assert.equal(i18n.formatDuration(60 * 1000), `1${NBSP}Min.`); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `1${NBSP}Std. 5${NBSP}Sek.`); assert.equal( i18n.formatDuration(28 * 60 * 60 * 1000 + 5000), `1${NBSP}T 4${NBSP}Std. 5${NBSP}Sek.`); // Yes, this is actually backwards (s h d). - i18n = new Formatter('ar'); + i18n = new I18nFormatter('ar'); /* eslint-disable no-irregular-whitespace */ assert.equal(i18n.formatDuration(60 * 1000), `١${NBSP}د`); assert.equal(i18n.formatDuration(60 * 60 * 1000 + 5000), `١${NBSP}س ٥${NBSP}ث`); @@ -149,7 +149,7 @@ describe('util helpers', () => { // Requires full-icu or Intl polyfill. const number = 12346.858558; - const i18n = new Formatter('de'); + const i18n = new I18nFormatter('de'); assert.strictEqual(i18n.formatNumber(number), '12.346,859'); assert.strictEqual(i18n.formatBytesToKiB(number, 0.1), `12,1${NBSP}KiB`); assert.strictEqual(i18n.formatMilliseconds(number, 10), `12.350${NBSP}ms`); @@ -160,7 +160,7 @@ describe('util helpers', () => { // Requires full-icu or Intl polyfill. const number = 12346.858558; - const i18n = new Formatter('en-XA'); + const i18n = new I18nFormatter('en-XA'); assert.strictEqual(i18n.formatNumber(number), '12.346,859'); assert.strictEqual(i18n.formatBytesToKiB(number, 0.1), `12,1${NBSP}KiB`); assert.strictEqual(i18n.formatMilliseconds(number, 100), `12.300${NBSP}ms`); @@ -168,7 +168,7 @@ describe('util helpers', () => { }); it('should not crash on unknown locales', () => { - const i18n = new Formatter('unknown-mystery-locale'); + const i18n = new I18nFormatter('unknown-mystery-locale'); const timestamp = i18n.formatDateTime('2017-04-28T23:07:51.189Z'); assert.ok( timestamp.includes('Apr 27, 2017') || diff --git a/report/test/renderer/performance-category-renderer-test.js b/report/test/renderer/performance-category-renderer-test.js index 8e8f53f45cc3..6dc02e0ef10c 100644 --- a/report/test/renderer/performance-category-renderer-test.js +++ b/report/test/renderer/performance-category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {PerformanceCategoryRenderer} from '../../renderer/performance-category-renderer.js'; @@ -23,7 +23,7 @@ describe('PerfCategoryRenderer', () => { let sampleResults; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -36,7 +36,7 @@ describe('PerfCategoryRenderer', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('renders the category header', () => { diff --git a/report/test/renderer/pwa-category-renderer-test.js b/report/test/renderer/pwa-category-renderer-test.js index 3734a8fc7d9a..b6b7e4c34cbd 100644 --- a/report/test/renderer/pwa-category-renderer-test.js +++ b/report/test/renderer/pwa-category-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; import {DetailsRenderer} from '../../renderer/details-renderer.js'; import {PwaCategoryRenderer} from '../../renderer/pwa-category-renderer.js'; @@ -23,7 +23,7 @@ describe('PwaCategoryRenderer', () => { let sampleResults; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; const dom = new DOM(document); @@ -40,7 +40,7 @@ describe('PwaCategoryRenderer', () => { }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('renders the regular audits', () => { diff --git a/report/test/renderer/snippet-renderer-test.js b/report/test/renderer/snippet-renderer-test.js index 47c3258965cb..2447ce094f6f 100644 --- a/report/test/renderer/snippet-renderer-test.js +++ b/report/test/renderer/snippet-renderer-test.js @@ -9,7 +9,7 @@ import assert from 'assert/strict'; import jsdom from 'jsdom'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {DOM} from '../../renderer/dom.js'; import {SnippetRenderer} from '../../renderer/snippet-renderer.js'; @@ -55,13 +55,13 @@ describe('DetailsRenderer', () => { let dom; before(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); const {document} = new jsdom.JSDOM().window; dom = new DOM(document); }); after(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); function renderSnippet(details) { diff --git a/report/test/renderer/util-test.js b/report/test/renderer/util-test.js index 487ba8670c8d..cfbf485f72ef 100644 --- a/report/test/renderer/util-test.js +++ b/report/test/renderer/util-test.js @@ -7,18 +7,18 @@ import assert from 'assert/strict'; import {Util} from '../../renderer/util.js'; -import {Formatter} from '../../renderer/formatter.js'; +import {I18nFormatter} from '../../renderer/i18n-formatter.js'; import {readJson} from '../../../core/test/test-utils.js'; const sampleResult = readJson('../../../core/test/results/sample_v2.json', import.meta); describe('util helpers', () => { beforeEach(() => { - Util.formatter = new Formatter('en'); + Util.i18n = new I18nFormatter('en'); }); afterEach(() => { - Util.formatter = undefined; + Util.i18n = undefined; }); it('calculates a score ratings', () => { diff --git a/treemap/app/src/main.js b/treemap/app/src/main.js index 56a51056a4e1..5a377fdd8b6d 100644 --- a/treemap/app/src/main.js +++ b/treemap/app/src/main.js @@ -11,7 +11,7 @@ import {TreemapUtil} from './util.js'; import {DragAndDrop} from '../../../viewer/app/src/drag-and-drop.js'; import {GithubApi} from '../../../viewer/app/src/github-api.js'; -import {Formatter} from '../../../report/renderer/formatter.js'; +import {I18nFormatter} from '../../../report/renderer/i18n-formatter.js'; import {TextEncoding} from '../../../report/renderer/text-encoding.js'; import {Logger} from '../../../report/renderer/logger.js'; @@ -269,7 +269,7 @@ class TreemapViewer { return { id: 'unused-bytes', label: TreemapUtil.strings.unusedBytesLabel, - subLabel: TreemapUtil.formatter.formatBytesWithBestUnit(root.unusedBytes), + subLabel: TreemapUtil.i18n.formatBytesWithBestUnit(root.unusedBytes), enabled: true, }; } @@ -330,7 +330,7 @@ class TreemapViewer { id: 'duplicate-modules', label: TreemapUtil.strings.duplicateModulesLabel, subLabel: enabled ? - TreemapUtil.formatter.formatBytesWithBestUnit(potentialByteSavings) : 'N/A', + TreemapUtil.i18n.formatBytesWithBestUnit(potentialByteSavings) : 'N/A', highlights, enabled, }; @@ -342,7 +342,7 @@ class TreemapViewer { viewModes.push({ id: 'all', label: TreemapUtil.strings.allLabel, - subLabel: TreemapUtil.formatter.formatBytesWithBestUnit( + subLabel: TreemapUtil.i18n.formatBytesWithBestUnit( this.currentTreemapRoot.resourceBytes), enabled: true, }); @@ -490,7 +490,7 @@ class TreemapViewer { const value = dataRow[field]; if (value === undefined) return ''; - return TreemapUtil.formatter.formatBytes(value); + return TreemapUtil.i18n.formatBytes(value); }; return fn; }; @@ -511,7 +511,7 @@ class TreemapViewer { if (!dataRow.unusedBytes) return ''; const percent = dataRow.unusedBytes / dataRow.resourceBytes; - return `${TreemapUtil.formatter.formatPercent(percent)} bytes unused`; + return `${TreemapUtil.i18n.formatPercent(percent)} bytes unused`; }; const gridEl = document.createElement('div'); @@ -537,13 +537,13 @@ class TreemapViewer { // eslint-disable-next-line max-len {title: TreemapUtil.strings.resourceBytesLabel, field: 'resourceBytes', headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('resourceBytes'), formatter: cell => { const value = cell.getValue(); - return TreemapUtil.formatter.formatBytesWithBestUnit(value); + return TreemapUtil.i18n.formatBytesWithBestUnit(value); }}, // eslint-disable-next-line max-len {title: TreemapUtil.strings.unusedBytesLabel, field: 'unusedBytes', widthGrow: 1, sorterParams: {alignEmptyValues: 'bottom'}, headerSortStartingDir: 'desc', tooltip: makeBytesTooltip('unusedBytes'), formatter: cell => { const value = cell.getValue(); if (value === undefined) return ''; - return TreemapUtil.formatter.formatBytesWithBestUnit(value); + return TreemapUtil.i18n.formatBytesWithBestUnit(value); }}, // eslint-disable-next-line max-len {title: TreemapUtil.strings.coverageColumnName, widthGrow: 3, headerSort: false, tooltip: makeCoverageTooltip, formatter: cell => { @@ -606,8 +606,8 @@ class TreemapViewer { if (bytes !== undefined && total !== undefined) { const percent = total === 0 ? 1 : bytes / total; - const percentStr = TreemapUtil.formatter.formatPercent(percent); - let str = `${TreemapUtil.formatter.formatBytesWithBestUnit(bytes)} (${percentStr})`; + const percentStr = TreemapUtil.i18n.formatPercent(percent); + let str = `${TreemapUtil.i18n.formatBytesWithBestUnit(bytes)} (${percentStr})`; // Only add label for bytes on the root node. if (node === this.currentTreemapRoot) { str = `${partitionByStr}: ${str}`; @@ -780,7 +780,7 @@ class LighthouseTreemap { // `strings` is generated in build/build-treemap.js TreemapUtil.applyStrings(strings[options.lhr.configSettings.locale]); - TreemapUtil.formatter = new Formatter(locale); + TreemapUtil.i18n = new I18nFormatter(locale); // Fill in all i18n data. for (const node of document.querySelectorAll('[data-i18n]')) { diff --git a/treemap/app/src/util.js b/treemap/app/src/util.js index 79a511830d76..2fb3e86efb4a 100644 --- a/treemap/app/src/util.js +++ b/treemap/app/src/util.js @@ -8,7 +8,7 @@ /** @typedef {HTMLElementTagNameMap & {[id: string]: HTMLElement}} HTMLElementByTagName */ /** @template {string} T @typedef {import('typed-query-selector/parser').ParseSelector} ParseSelector */ -/** @typedef {import('../../../report/renderer/formatter').Formatter} Formatter */ +/** @typedef {import('../../../report/renderer/i18n-formatter').I18nFormatter} I18nFormatter */ const UIStrings = { /** Label for a button that alternates between showing or hiding a table. */ @@ -30,9 +30,9 @@ const UIStrings = { }; class TreemapUtil { - /** @type {Formatter} */ + /** @type {I18nFormatter} */ // @ts-expect-error: Is set in main. - static formatter = null; + static i18n = null; static UIStrings = UIStrings; static strings = {...UIStrings};