lighthouse 9.5.0-dev.20230121 → 9.5.0-dev.20230123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/core/audits/bf-cache.js +1 -1
  2. package/core/audits/screenshot-thumbnails.d.ts +2 -1
  3. package/core/audits/screenshot-thumbnails.js +14 -9
  4. package/core/computed/processed-navigation.d.ts +12 -6
  5. package/core/computed/processed-navigation.js +19 -6
  6. package/core/util.cjs +42 -9
  7. package/core/util.d.cts +114 -2
  8. package/dist/report/bundle.esm.js +199 -261
  9. package/dist/report/flow.js +23 -23
  10. package/dist/report/standalone.js +17 -17
  11. package/flow-report/assets/standalone-flow-template.html +1 -1
  12. package/flow-report/src/i18n/i18n.d.ts +103 -100
  13. package/flow-report/src/i18n/i18n.tsx +12 -8
  14. package/flow-report/src/sidebar/sidebar.tsx +1 -1
  15. package/flow-report/src/summary/category.tsx +1 -1
  16. package/flow-report/src/topbar.tsx +4 -28
  17. package/package.json +2 -2
  18. package/readme.md +1 -1
  19. package/report/assets/standalone-template.html +1 -1
  20. package/report/assets/styles.css +3 -3
  21. package/report/assets/templates.html +4 -28
  22. package/report/renderer/category-renderer.js +9 -9
  23. package/report/renderer/components.js +119 -202
  24. package/report/renderer/crc-details-renderer.js +4 -3
  25. package/report/renderer/{i18n.d.ts → i18n-formatter.d.ts} +3 -9
  26. package/report/renderer/{i18n.js → i18n-formatter.js} +2 -11
  27. package/report/renderer/performance-category-renderer.js +4 -4
  28. package/report/renderer/report-renderer.js +14 -18
  29. package/report/renderer/report-ui-features.js +6 -6
  30. package/report/renderer/snippet-renderer.js +1 -1
  31. package/report/renderer/util.d.ts +115 -3
  32. package/report/renderer/util.js +42 -9
  33. package/report/test/renderer/category-renderer-test.js +2 -2
  34. package/report/test/renderer/crc-details-renderer-test.js +2 -2
  35. package/report/test/renderer/details-renderer-test.js +2 -2
  36. package/report/test/renderer/dom-test.js +2 -2
  37. package/report/test/renderer/element-screenshot-renderer-test.js +2 -2
  38. package/report/test/renderer/{i18n-test.js → i18n-formatter-test.js} +15 -21
  39. package/report/test/renderer/performance-category-renderer-test.js +2 -2
  40. package/report/test/renderer/pwa-category-renderer-test.js +2 -2
  41. package/report/test/renderer/snippet-renderer-test.js +2 -2
  42. package/report/test/renderer/util-test.js +2 -2
  43. package/report/test-assets/lhr-3.0.0.json +2332 -0
  44. package/report/test-assets/lhr-4.3.0.json +4754 -0
  45. package/report/test-assets/lhr-5.0.0.json +5994 -0
  46. package/report/test-assets/lhr-6.0.0.json +7222 -0
  47. package/report/test-assets/lhr-8.5.0.json +8679 -0
  48. package/shared/localization/locales/en-US.json +1 -1
  49. package/shared/localization/locales/en-XL.json +1 -1
@@ -136,7 +136,8 @@ class CriticalRequestChainRenderer {
136
136
  if (!segment.hasChildren) {
137
137
  const {startTime, endTime, transferSize} = segment.node.request;
138
138
  const span = dom.createElement('span', 'lh-crc-node__chain-duration');
139
- span.textContent = ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
139
+ span.textContent =
140
+ ' - ' + Util.i18n.formatMilliseconds((endTime - startTime) * 1000) + ', ';
140
141
  const span2 = dom.createElement('span', 'lh-crc-node__chain-duration');
141
142
  span2.textContent = Util.i18n.formatBytesToKiB(transferSize, 0.01);
142
143
 
@@ -177,9 +178,9 @@ class CriticalRequestChainRenderer {
177
178
  const containerEl = dom.find('.lh-crc', tmpl);
178
179
 
179
180
  // Fill in top summary.
180
- dom.find('.lh-crc-initial-nav', tmpl).textContent = Util.i18n.strings.crcInitialNavigation;
181
+ dom.find('.lh-crc-initial-nav', tmpl).textContent = Util.strings.crcInitialNavigation;
181
182
  dom.find('.lh-crc__longest_duration_label', tmpl).textContent =
182
- Util.i18n.strings.crcLongestDurationLabel;
183
+ Util.strings.crcLongestDurationLabel;
183
184
  dom.find('.lh-crc__longest_duration', tmpl).textContent =
184
185
  Util.i18n.formatMilliseconds(details.longestChain.duration);
185
186
 
@@ -1,16 +1,10 @@
1
- /**
2
- * @template T
3
- */
4
- export class I18n<T> {
1
+ export class I18nFormatter {
5
2
  /**
6
3
  * @param {LH.Locale} locale
7
- * @param {T} strings
8
4
  */
9
- constructor(locale: LH.Locale, strings: T);
5
+ constructor(locale: LH.Locale);
10
6
  _locale: "en-US" | "en" | "en-AU" | "en-GB" | "en-IE" | "en-SG" | "en-ZA" | "en-IN" | "ar-XB" | "ar" | "bg" | "ca" | "cs" | "da" | "de" | "el" | "en-XL" | "es" | "es-419" | "es-AR" | "es-BO" | "es-BR" | "es-BZ" | "es-CL" | "es-CO" | "es-CR" | "es-CU" | "es-DO" | "es-EC" | "es-GT" | "es-HN" | "es-MX" | "es-NI" | "es-PA" | "es-PE" | "es-PR" | "es-PY" | "es-SV" | "es-US" | "es-UY" | "es-VE" | "fi" | "fil" | "fr" | "he" | "hi" | "hr" | "hu" | "gsw" | "id" | "in" | "it" | "iw" | "ja" | "ko" | "lt" | "lv" | "mo" | "nl" | "nb" | "no" | "pl" | "pt" | "pt-PT" | "ro" | "ru" | "sk" | "sl" | "sr" | "sr-Latn" | "sv" | "ta" | "te" | "th" | "tl" | "tr" | "uk" | "vi" | "zh" | "zh-HK" | "zh-TW";
11
- _strings: T;
12
7
  _cachedNumberFormatters: Map<any, any>;
13
- get strings(): T;
14
8
  /**
15
9
  * @param {number} number
16
10
  * @param {number|undefined} granularity
@@ -103,4 +97,4 @@ export class I18n<T> {
103
97
  */
104
98
  formatDuration(timeInMilliseconds: number): string;
105
99
  }
106
- //# sourceMappingURL=i18n.d.ts.map
100
+ //# sourceMappingURL=i18n-formatter.d.ts.map
@@ -9,27 +9,18 @@ const NBSP2 = '\xa0';
9
9
  const KiB = 1024;
10
10
  const MiB = KiB * KiB;
11
11
 
12
- /**
13
- * @template T
14
- */
15
- export class I18n {
12
+ export class I18nFormatter {
16
13
  /**
17
14
  * @param {LH.Locale} locale
18
- * @param {T} strings
19
15
  */
20
- constructor(locale, strings) {
16
+ constructor(locale) {
21
17
  // When testing, use a locale with more exciting numeric formatting.
22
18
  if (locale === 'en-XA') locale = 'de';
23
19
 
24
20
  this._locale = locale;
25
- this._strings = strings;
26
21
  this._cachedNumberFormatters = new Map();
27
22
  }
28
23
 
29
- get strings() {
30
- return this._strings;
31
- }
32
-
33
24
  /**
34
25
  * @param {number} number
35
26
  * @param {number|undefined} granularity
@@ -178,7 +178,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
178
178
  * @override
179
179
  */
180
180
  render(category, groups, options) {
181
- const strings = Util.i18n.strings;
181
+ const strings = Util.strings;
182
182
  const element = this.dom.createElement('div', 'lh-category');
183
183
  element.id = category.id;
184
184
  element.append(this.renderCategoryHeader(category, groups, options));
@@ -200,8 +200,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
200
200
  labelEl.htmlFor = checkboxId;
201
201
  const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show');
202
202
  const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide');
203
- showEl.textContent = Util.i18n.strings.expandView;
204
- hideEl.textContent = Util.i18n.strings.collapseView;
203
+ showEl.textContent = Util.strings.expandView;
204
+ hideEl.textContent = Util.strings.collapseView;
205
205
 
206
206
  const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container');
207
207
  metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl);
@@ -333,7 +333,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
333
333
  renderMetricAuditFilter(filterableMetrics, categoryEl) {
334
334
  const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter');
335
335
  const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text');
336
- textEl.textContent = Util.i18n.strings.showRelevantAudits;
336
+ textEl.textContent = Util.strings.showRelevantAudits;
337
337
 
338
338
  const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([
339
339
  ({acronym: 'All'}),
@@ -23,7 +23,7 @@
23
23
  import {CategoryRenderer} from './category-renderer.js';
24
24
  import {DetailsRenderer} from './details-renderer.js';
25
25
  import {ElementScreenshotRenderer} from './element-screenshot-renderer.js';
26
- import {I18n} from './i18n.js';
26
+ import {I18nFormatter} from './i18n-formatter.js';
27
27
  import {PerformanceCategoryRenderer} from './performance-category-renderer.js';
28
28
  import {PwaCategoryRenderer} from './pwa-category-renderer.js';
29
29
  import {Util} from './util.js';
@@ -108,7 +108,7 @@ export class ReportRenderer {
108
108
 
109
109
  this._renderMetaBlock(report, footer);
110
110
 
111
- this._dom.find('.lh-footer__version_issue', footer).textContent = Util.i18n.strings.footerIssue;
111
+ this._dom.find('.lh-footer__version_issue', footer).textContent = Util.strings.footerIssue;
112
112
  this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion;
113
113
  return footer;
114
114
  }
@@ -128,15 +128,15 @@ export class ReportRenderer {
128
128
  const axeVersion = report.environment.credits?.['axe-core'];
129
129
 
130
130
  const devicesTooltipTextLines = [
131
- `${Util.i18n.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}`,
132
- `${Util.i18n.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}`,
131
+ `${Util.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}`,
132
+ `${Util.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}`,
133
133
  ];
134
134
  if (envValues.screenEmulation) {
135
135
  devicesTooltipTextLines.push(
136
- `${Util.i18n.strings.runtimeSettingsScreenEmulation}: ${envValues.screenEmulation}`);
136
+ `${Util.strings.runtimeSettingsScreenEmulation}: ${envValues.screenEmulation}`);
137
137
  }
138
138
  if (axeVersion) {
139
- devicesTooltipTextLines.push(`${Util.i18n.strings.runtimeSettingsAxeVersion}: ${axeVersion}`);
139
+ devicesTooltipTextLines.push(`${Util.strings.runtimeSettingsAxeVersion}: ${axeVersion}`);
140
140
  }
141
141
 
142
142
  // [CSS icon class, textContent, tooltipText]
@@ -147,16 +147,16 @@ export class ReportRenderer {
147
147
  `${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`,
148
148
  devicesTooltipTextLines.join('\n')],
149
149
  ['samples-one',
150
- Util.i18n.strings.runtimeSingleLoad,
151
- Util.i18n.strings.runtimeSingleLoadTooltip],
150
+ Util.strings.runtimeSingleLoad,
151
+ Util.strings.runtimeSingleLoadTooltip],
152
152
  ['stopwatch',
153
- Util.i18n.strings.runtimeAnalysisWindow],
153
+ Util.strings.runtimeAnalysisWindow],
154
154
  ['networkspeed',
155
155
  `${envValues.summary}`,
156
- `${Util.i18n.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`],
156
+ `${Util.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`],
157
157
  ['chrome',
158
158
  `Using ${chromeVer}` + (channel ? ` with ${channel}` : ''),
159
- `${Util.i18n.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`],
159
+ `${Util.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`],
160
160
  ];
161
161
 
162
162
  const metaItemsEl = this._dom.find('.lh-meta__items', footer);
@@ -184,7 +184,7 @@ export class ReportRenderer {
184
184
 
185
185
  const container = this._dom.createComponent('warningsToplevel');
186
186
  const message = this._dom.find('.lh-warnings__msg', container);
187
- message.textContent = Util.i18n.strings.toplevelWarningsMessage;
187
+ message.textContent = Util.strings.toplevelWarningsMessage;
188
188
 
189
189
  const warnings = [];
190
190
  for (const warningString of report.runWarnings) {
@@ -260,12 +260,8 @@ export class ReportRenderer {
260
260
  * @return {!DocumentFragment}
261
261
  */
262
262
  _renderReport(report) {
263
- const i18n = new I18n(report.configSettings.locale, {
264
- // Set missing renderer strings to default (english) values.
265
- ...Util.UIStrings,
266
- ...report.i18n.rendererFormattedStrings,
267
- });
268
- Util.i18n = i18n;
263
+ Util.applyStrings(report.i18n.rendererFormattedStrings);
264
+ Util.i18n = new I18nFormatter(report.configSettings.locale);
269
265
  Util.reportJson = report;
270
266
 
271
267
  const detailsRenderer = new DetailsRenderer(this._dom, {
@@ -106,7 +106,7 @@ export class ReportUIFeatures {
106
106
  this.json.audits['script-treemap-data'] && this.json.audits['script-treemap-data'].details;
107
107
  if (showTreemapApp) {
108
108
  this.addButton({
109
- text: Util.i18n.strings.viewTreemapLabel,
109
+ text: Util.strings.viewTreemapLabel,
110
110
  icon: 'treemap',
111
111
  onClick: () => openTreemap(this.json),
112
112
  });
@@ -115,8 +115,8 @@ export class ReportUIFeatures {
115
115
  if (this._opts.onViewTrace) {
116
116
  this.addButton({
117
117
  text: lhr.configSettings.throttlingMethod === 'simulate' ?
118
- Util.i18n.strings.viewOriginalTraceLabel :
119
- Util.i18n.strings.viewTraceLabel,
118
+ Util.strings.viewOriginalTraceLabel :
119
+ Util.strings.viewTraceLabel,
120
120
  onClick: () => this._opts.onViewTrace?.(),
121
121
  });
122
122
  }
@@ -130,8 +130,8 @@ export class ReportUIFeatures {
130
130
  // These strings are guaranteed to (at least) have a default English string in Util.UIStrings,
131
131
  // so this cannot be undefined as long as `report-ui-features.data-i18n` test passes.
132
132
  const i18nKey = node.getAttribute('data-i18n');
133
- const i18nAttr = /** @type {keyof typeof Util.i18n.strings} */ (i18nKey);
134
- node.textContent = Util.i18n.strings[i18nAttr];
133
+ const i18nAttr = /** @type {keyof typeof Util.strings} */ (i18nKey);
134
+ node.textContent = Util.strings[i18nAttr];
135
135
  }
136
136
  }
137
137
 
@@ -273,7 +273,7 @@ export class ReportUIFeatures {
273
273
  this._dom.find('.lh-3p-filter-count', filterTemplate).textContent =
274
274
  `${thirdPartyRows.length}`;
275
275
  this._dom.find('.lh-3p-ui-string', filterTemplate).textContent =
276
- Util.i18n.strings.thirdPartyResourcesLabel;
276
+ Util.strings.thirdPartyResourcesLabel;
277
277
 
278
278
  const allThirdParty = thirdPartyRows.length === rowEls.length;
279
279
  const allFirstParty = !thirdPartyRows.length;
@@ -104,7 +104,7 @@ export class SnippetRenderer {
104
104
  const {
105
105
  snippetCollapseButtonLabel,
106
106
  snippetExpandButtonLabel,
107
- } = Util.i18n.strings;
107
+ } = Util.strings;
108
108
  dom.find(
109
109
  '.lh-snippet__btn-label-collapse',
110
110
  header
@@ -1,7 +1,119 @@
1
- export type I18n<T> = import('./i18n').I18n<T>;
1
+ export type I18nFormatter = import('./i18n-formatter').I18nFormatter;
2
2
  export class Util {
3
- /** @type {I18n<typeof UIStrings>} */
4
- static i18n: I18n<typeof UIStrings>;
3
+ /** @type {I18nFormatter} */
4
+ static i18n: I18nFormatter;
5
+ static strings: {
6
+ /** 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. */
7
+ varianceDisclaimer: string;
8
+ /** Text link pointing to an interactive calculator that explains Lighthouse scoring. The link text should be fairly short. */
9
+ calculatorLink: string;
10
+ /** 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. */
11
+ showRelevantAudits: string;
12
+ /** 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. */
13
+ opportunityResourceColumnLabel: string;
14
+ /** 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. */
15
+ opportunitySavingsColumnLabel: string;
16
+ /** An error string displayed next to a particular audit when it has errored, but not provided any specific error message. */
17
+ errorMissingAuditInfo: string;
18
+ /** 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). */
19
+ errorLabel: string;
20
+ /** 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. */
21
+ warningHeader: string;
22
+ /** 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. */
23
+ warningAuditsGroupTitle: string;
24
+ /** 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. */
25
+ passedAuditsGroupTitle: string;
26
+ /** 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. */
27
+ notApplicableAuditsGroupTitle: string;
28
+ /** 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. */
29
+ manualAuditsGroupTitle: string;
30
+ /** 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. */
31
+ toplevelWarningsMessage: string;
32
+ /** 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). */
33
+ crcInitialNavigation: string;
34
+ /** 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 */
35
+ crcLongestDurationLabel: string;
36
+ /** Label for button that shows all lines of the snippet when clicked */
37
+ snippetExpandButtonLabel: string;
38
+ /** Label for button that only shows a few lines of the snippet when clicked */
39
+ snippetCollapseButtonLabel: string;
40
+ /** 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. */
41
+ lsPerformanceCategoryDescription: string;
42
+ /** 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. */
43
+ labDataTitle: string;
44
+ /** 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. */
45
+ thirdPartyResourcesLabel: string;
46
+ /** 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.) */
47
+ viewTreemapLabel: string;
48
+ /** This label is for a button that will show the user a trace of the page. */
49
+ viewTraceLabel: string;
50
+ /** This label is for a button that will show the user a trace of the page. */
51
+ viewOriginalTraceLabel: string;
52
+ /** Option in a dropdown menu that opens a small, summary report in a print dialog. */
53
+ dropdownPrintSummary: string;
54
+ /** Option in a dropdown menu that opens a full Lighthouse report in a print dialog. */
55
+ dropdownPrintExpanded: string;
56
+ /** Option in a dropdown menu that copies the Lighthouse JSON object to the system clipboard. */
57
+ dropdownCopyJSON: string;
58
+ /** Option in a dropdown menu that saves the Lighthouse report HTML locally to the system as a '.html' file. */
59
+ dropdownSaveHTML: string;
60
+ /** Option in a dropdown menu that saves the Lighthouse JSON object to the local system as a '.json' file. */
61
+ dropdownSaveJSON: string;
62
+ /** Option in a dropdown menu that opens the current report in the Lighthouse Viewer Application. */
63
+ dropdownViewer: string;
64
+ /** Option in a dropdown menu that saves the current report as a new GitHub Gist. */
65
+ dropdownSaveGist: string;
66
+ /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */
67
+ dropdownDarkTheme: string;
68
+ /** 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. */
69
+ runtimeSettingsDevice: string;
70
+ /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */
71
+ runtimeSettingsNetworkThrottling: string;
72
+ /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/
73
+ runtimeSettingsCPUThrottling: string;
74
+ /** 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. */
75
+ runtimeSettingsUANetwork: string;
76
+ /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */
77
+ runtimeSettingsBenchmark: string;
78
+ /** 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 */
79
+ runtimeSettingsAxeVersion: string;
80
+ /** 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' */
81
+ runtimeSettingsScreenEmulation: string;
82
+ /** Label for button to create an issue against the Lighthouse GitHub project. */
83
+ footerIssue: string;
84
+ /** Descriptive explanation for emulation setting when no device emulation is set. */
85
+ runtimeNoEmulation: string;
86
+ /** Descriptive explanation for emulation setting when emulating a Moto G4 mobile device. */
87
+ runtimeMobileEmulation: string;
88
+ /** Descriptive explanation for emulation setting when emulating a generic desktop form factor, as opposed to a mobile-device like form factor. */
89
+ runtimeDesktopEmulation: string;
90
+ /** Descriptive explanation for a runtime setting that is set to an unknown value. */
91
+ runtimeUnknown: string;
92
+ /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */
93
+ runtimeSingleLoad: string;
94
+ /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */
95
+ runtimeAnalysisWindow: string;
96
+ /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */
97
+ runtimeSingleLoadTooltip: string;
98
+ /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
99
+ throttlingProvided: string;
100
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
101
+ show: string;
102
+ /** Label for an interactive control that will reveal or hide a group of content. This control toggles between the text 'Show' and 'Hide'. */
103
+ hide: string;
104
+ /** 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'. */
105
+ expandView: string;
106
+ /** 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'. */
107
+ collapseView: string;
108
+ /** Label indicating that Lighthouse throttled the page to emulate a slow 4G network connection. */
109
+ runtimeSlow4g: string;
110
+ /** Label indicating that Lighthouse throttled the page using custom throttling settings. */
111
+ runtimeCustom: string;
112
+ };
113
+ /**
114
+ * @param {Record<string, string>} providedStrings
115
+ */
116
+ static applyStrings(providedStrings: Record<string, string>): void;
5
117
  static get PASS_THRESHOLD(): number;
6
118
  static get MS_DISPLAY_VALUE(): string;
7
119
  /**
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- /** @template T @typedef {import('./i18n').I18n<T>} I18n */
18
+ /** @typedef {import('./i18n-formatter').I18nFormatter} I18nFormatter */
19
19
 
20
20
  const ELLIPSIS = '\u2026';
21
21
  const NBSP = '\xa0';
@@ -38,9 +38,21 @@ const listOfTlds = [
38
38
  ];
39
39
 
40
40
  class Util {
41
- /** @type {I18n<typeof UIStrings>} */
41
+ /** @type {I18nFormatter} */
42
42
  // @ts-expect-error: Is set in report renderer.
43
43
  static i18n = null;
44
+ static strings = /** @type {typeof UIStrings} */ ({});
45
+
46
+ /**
47
+ * @param {Record<string, string>} providedStrings
48
+ */
49
+ static applyStrings(providedStrings) {
50
+ this.strings = {
51
+ // Set missing renderer strings to default (english) values.
52
+ ...UIStrings,
53
+ ...providedStrings,
54
+ };
55
+ }
44
56
 
45
57
  static get PASS_THRESHOLD() {
46
58
  return PASS_THRESHOLD;
@@ -150,6 +162,10 @@ class Util {
150
162
  }
151
163
  }
152
164
  }
165
+
166
+ // TODO: convert printf-style displayValue.
167
+ // Added: #5099, v3
168
+ // Removed: #6767, v4
153
169
  }
154
170
  }
155
171
 
@@ -212,6 +228,20 @@ class Util {
212
228
  });
213
229
  }
214
230
 
231
+ // Add some minimal stuff so older reports still work.
232
+ if (!clone.environment) {
233
+ // @ts-expect-error
234
+ clone.environment = {benchmarkIndex: 0};
235
+ }
236
+ if (!clone.configSettings.screenEmulation) {
237
+ // @ts-expect-error
238
+ clone.configSettings.screenEmulation = {};
239
+ }
240
+ if (!clone.i18n) {
241
+ // @ts-expect-error
242
+ clone.i18n = {};
243
+ }
244
+
215
245
  // In 10.0, full-page-screenshot became a top-level property on the LHR.
216
246
  if (clone.audits['full-page-screenshot']) {
217
247
  const details = /** @type {LH.Result.FullPageScreenshot=} */ (
@@ -507,7 +537,7 @@ class Util {
507
537
 
508
538
  switch (settings.throttlingMethod) {
509
539
  case 'provided':
510
- summary = networkThrottling = cpuThrottling = Util.i18n.strings.throttlingProvided;
540
+ summary = networkThrottling = cpuThrottling = Util.strings.throttlingProvided;
511
541
  break;
512
542
  case 'devtools': {
513
543
  const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
@@ -522,7 +552,8 @@ class Util {
522
552
  throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
523
553
  throttling.uploadThroughputKbps === 750 * 0.9;
524
554
  };
525
- summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
555
+ summary = isSlow4G() ?
556
+ Util.strings.runtimeSlow4g : Util.strings.runtimeCustom;
526
557
  break;
527
558
  }
528
559
  case 'simulate': {
@@ -535,11 +566,12 @@ class Util {
535
566
  const isSlow4G = () => {
536
567
  return rttMs === 150 && throughputKbps === 1.6 * 1024;
537
568
  };
538
- summary = isSlow4G() ? Util.i18n.strings.runtimeSlow4g : Util.i18n.strings.runtimeCustom;
569
+ summary = isSlow4G() ?
570
+ Util.strings.runtimeSlow4g : Util.strings.runtimeCustom;
539
571
  break;
540
572
  }
541
573
  default:
542
- summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
574
+ summary = cpuThrottling = networkThrottling = Util.strings.runtimeUnknown;
543
575
  }
544
576
 
545
577
  // devtools-entry.js always sets `screenEmulation.disabled` when using mobile emulation,
@@ -552,11 +584,11 @@ class Util {
552
584
  settings.formFactor === 'mobile' :
553
585
  settings.screenEmulation.mobile;
554
586
 
555
- let deviceEmulation = Util.i18n.strings.runtimeMobileEmulation;
587
+ let deviceEmulation = Util.strings.runtimeMobileEmulation;
556
588
  if (isScreenEmulationDisabled) {
557
- deviceEmulation = Util.i18n.strings.runtimeNoEmulation;
589
+ deviceEmulation = Util.strings.runtimeNoEmulation;
558
590
  } else if (!isScreenEmulationMobile) {
559
- deviceEmulation = Util.i18n.strings.runtimeDesktopEmulation;
591
+ deviceEmulation = Util.strings.runtimeDesktopEmulation;
560
592
  }
561
593
 
562
594
  const screenEmulation = isScreenEmulationDisabled ?
@@ -801,6 +833,7 @@ const UIStrings = {
801
833
  runtimeCustom: 'Custom throttling',
802
834
  };
803
835
  Util.UIStrings = UIStrings;
836
+ Util.strings = {...UIStrings};
804
837
 
805
838
  export {
806
839
  Util,
@@ -9,7 +9,7 @@ import assert from 'assert/strict';
9
9
  import jsdom from 'jsdom';
10
10
 
11
11
  import {Util} from '../../renderer/util.js';
12
- import {I18n} from '../../renderer/i18n.js';
12
+ import {I18nFormatter} from '../../renderer/i18n-formatter.js';
13
13
  import {DOM} from '../../renderer/dom.js';
14
14
  import {DetailsRenderer} from '../../renderer/details-renderer.js';
15
15
  import {CategoryRenderer} from '../../renderer/category-renderer.js';
@@ -22,7 +22,7 @@ describe('CategoryRenderer', () => {
22
22
  let sampleResults;
23
23
 
24
24
  before(() => {
25
- Util.i18n = new I18n('en', {...Util.UIStrings});
25
+ Util.i18n = new I18nFormatter('en');
26
26
 
27
27
  const {document} = new jsdom.JSDOM().window;
28
28
  const dom = new DOM(document);
@@ -9,7 +9,7 @@ import assert from 'assert/strict';
9
9
  import jsdom from 'jsdom';
10
10
 
11
11
  import {Util} from '../../renderer/util.js';
12
- import {I18n} from '../../renderer/i18n.js';
12
+ import {I18nFormatter} from '../../renderer/i18n-formatter.js';
13
13
  import {DOM} from '../../renderer/dom.js';
14
14
  import {DetailsRenderer} from '../../renderer/details-renderer.js';
15
15
  import {CriticalRequestChainRenderer} from '../../renderer/crc-details-renderer.js';
@@ -73,7 +73,7 @@ describe('DetailsRenderer', () => {
73
73
  let detailsRenderer;
74
74
 
75
75
  before(() => {
76
- Util.i18n = new I18n('en', {...Util.UIStrings});
76
+ Util.i18n = new I18nFormatter('en');
77
77
 
78
78
  const {document} = new jsdom.JSDOM().window;
79
79
  dom = new DOM(document);
@@ -10,7 +10,7 @@ import jsdom from 'jsdom';
10
10
 
11
11
  import {DOM} from '../../renderer/dom.js';
12
12
  import {Util} from '../../renderer/util.js';
13
- import {I18n} from '../../renderer/i18n.js';
13
+ import {I18nFormatter} from '../../renderer/i18n-formatter.js';
14
14
  import {DetailsRenderer} from '../../renderer/details-renderer.js';
15
15
 
16
16
  describe('DetailsRenderer', () => {
@@ -23,7 +23,7 @@ describe('DetailsRenderer', () => {
23
23
  }
24
24
 
25
25
  before(() => {
26
- Util.i18n = new I18n('en', {...Util.UIStrings});
26
+ Util.i18n = new I18nFormatter('en');
27
27
  createRenderer();
28
28
  });
29
29
 
@@ -11,7 +11,7 @@ import jsdom from 'jsdom';
11
11
 
12
12
  import {DOM} from '../../renderer/dom.js';
13
13
  import {Util} from '../../renderer/util.js';
14
- import {I18n} from '../../renderer/i18n.js';
14
+ import {I18nFormatter} from '../../renderer/i18n-formatter.js';
15
15
 
16
16
  describe('DOM', () => {
17
17
  /** @type {DOM} */
@@ -20,7 +20,7 @@ describe('DOM', () => {
20
20
  let nativeCreateObjectURL;
21
21
 
22
22
  before(() => {
23
- Util.i18n = new I18n('en', {...Util.UIStrings});
23
+ Util.i18n = new I18nFormatter('en');
24
24
  window = new jsdom.JSDOM().window;
25
25
 
26
26
  // The Node version of URL.createObjectURL isn't compatible with the jsdom blob type,
@@ -8,7 +8,7 @@ import jsdom from 'jsdom';
8
8
 
9
9
  import {ElementScreenshotRenderer} from '../../renderer/element-screenshot-renderer.js';
10
10
  import {Util} from '../../renderer/util.js';
11
- import {I18n} from '../../renderer/i18n.js';
11
+ import {I18nFormatter} from '../../renderer/i18n-formatter.js';
12
12
  import {DOM} from '../../renderer/dom.js';
13
13
 
14
14
  /**
@@ -27,7 +27,7 @@ describe('ElementScreenshotRenderer', () => {
27
27
  let dom;
28
28
 
29
29
  before(() => {
30
- Util.i18n = new I18n('en', {...Util.UIStrings});
30
+ Util.i18n = new I18nFormatter('en');
31
31
 
32
32
  const {document} = new jsdom.JSDOM().window;
33
33
  dom = new DOM(document);