diff --git a/lighthouse-core/lib/i18n.js b/lighthouse-core/lib/i18n.js index 75a6f5bd7cce..bb18a8814139 100644 --- a/lighthouse-core/lib/i18n.js +++ b/lighthouse-core/lib/i18n.js @@ -131,6 +131,22 @@ function getDefaultLocale() { return 'en-US'; } +/** + * @param {LH.Locale} locale + * @return {LH.I18NRendererStrings} + */ +function getRendererFormattedStrings(locale) { + const icuMessageIds = Object.keys(LOCALES[locale]).filter(f => f.includes('core/report/html/')); + const strings = {}; + for (const icuMessageId of icuMessageIds) { + const [filename, varName] = icuMessageId.split(' | '); + if (!filename.endsWith('util.js')) throw new Error(`Unexpected message: ${icuMessageId}`); + strings[varName] = LOCALES[locale][icuMessageId].message; + } + + return strings; +} + /** * @param {string} filename * @param {Record} fileStrings @@ -208,13 +224,14 @@ function replaceIcuMessageInstanceIds(lhr, locale) { const icuMessagePaths = {}; replaceInObject(lhr, icuMessagePaths); - lhr.i18n = {icuMessagePaths}; + return icuMessagePaths; } module.exports = { _formatPathAsString, UIStrings, getDefaultLocale, + getRendererFormattedStrings, createMessageInstanceIdFn, replaceIcuMessageInstanceIds, }; diff --git a/lighthouse-core/lib/locales/en-US.json b/lighthouse-core/lib/locales/en-US.json index ef621e4826b8..49ce49628a19 100644 --- a/lighthouse-core/lib/locales/en-US.json +++ b/lighthouse-core/lib/locales/en-US.json @@ -43,5 +43,41 @@ }, "lighthouse-core/lib/i18n.js | displayValueWastedMs": { "message": "Potential savings of {wastedMs, number, milliseconds} ms" + }, + "lighthouse-core/report/html/renderer/util.js | varianceDisclaimer": { + "message": "Values are estimated and may vary." + }, + "lighthouse-core/report/html/renderer/util.js | opportunityResourceColumnLabel": { + "message": "Resource to optimize" + }, + "lighthouse-core/report/html/renderer/util.js | opportunitySavingsColumnLabel": { + "message": "Estimated Savings" + }, + "lighthouse-core/report/html/renderer/util.js | errorMissingAuditInfo": { + "message": "Report error: no audit information" + }, + "lighthouse-core/report/html/renderer/util.js | errorLabel": { + "message": "Error!" + }, + "lighthouse-core/report/html/renderer/util.js | warningHeader": { + "message": "Warnings: " + }, + "lighthouse-core/report/html/renderer/util.js | auditGroupExpandTooltip": { + "message": "Show audits" + }, + "lighthouse-core/report/html/renderer/util.js | passedAuditsGroupTitle": { + "message": "Passed audits" + }, + "lighthouse-core/report/html/renderer/util.js | notApplicableAuditsGroupTitle": { + "message": "Not applicable" + }, + "lighthouse-core/report/html/renderer/util.js | manualAuditsGroupTitle": { + "message": "Additional items to manually check" + }, + "lighthouse-core/report/html/renderer/util.js | toplevelWarningsMessage": { + "message": "There were issues affecting this run of Lighthouse:" + }, + "lighthouse-core/report/html/renderer/util.js | scorescaleLabel": { + "message": "Score scale:" } } diff --git a/lighthouse-core/lib/locales/en-XA.json b/lighthouse-core/lib/locales/en-XA.json index a14708926426..3693b8ba1032 100644 --- a/lighthouse-core/lib/locales/en-XA.json +++ b/lighthouse-core/lib/locales/en-XA.json @@ -43,5 +43,41 @@ }, "lighthouse-core/lib/i18n.js | displayValueWastedMs": { "message": "P̂ót̂én̂t́îál̂ śâv́îńĝś ôf́ {wastedMs, number, milliseconds} m̂ś" + }, + "lighthouse-core/report/html/renderer/util.js | varianceDisclaimer": { + "message": "V̂ál̂úêś âŕê éŝt́îḿât́êd́ âńd̂ ḿâý v̂ár̂ý." + }, + "lighthouse-core/report/html/renderer/util.js | opportunityResourceColumnLabel": { + "message": "R̂éŝóûŕĉé t̂ó ôṕt̂ím̂íẑé" + }, + "lighthouse-core/report/html/renderer/util.js | opportunitySavingsColumnLabel": { + "message": "Êśt̂ím̂át̂éd̂ Śâv́îńĝś" + }, + "lighthouse-core/report/html/renderer/util.js | errorMissingAuditInfo": { + "message": "R̂ép̂ór̂t́ êŕr̂ór̂: ńô áûd́ît́ îńf̂ór̂ḿât́îón̂" + }, + "lighthouse-core/report/html/renderer/util.js | errorLabel": { + "message": "Êŕr̂ór̂!" + }, + "lighthouse-core/report/html/renderer/util.js | warningHeader": { + "message": "Ŵár̂ńîńĝś: " + }, + "lighthouse-core/report/html/renderer/util.js | auditGroupExpandTooltip": { + "message": "Ŝh́ôẃ âúd̂ít̂ś" + }, + "lighthouse-core/report/html/renderer/util.js | passedAuditsGroupTitle": { + "message": "P̂áŝśêd́ âúd̂ít̂ś" + }, + "lighthouse-core/report/html/renderer/util.js | notApplicableAuditsGroupTitle": { + "message": "N̂ót̂ áp̂ṕl̂íĉáb̂ĺê" + }, + "lighthouse-core/report/html/renderer/util.js | manualAuditsGroupTitle": { + "message": "Âd́d̂ít̂íôńâĺ ît́êḿŝ t́ô ḿâńûál̂ĺŷ ćĥéĉḱ" + }, + "lighthouse-core/report/html/renderer/util.js | toplevelWarningsMessage": { + "message": "T̂h́êŕê ẃêŕê íŝśûéŝ áf̂f́êćt̂ín̂ǵ t̂h́îś r̂ún̂ óf̂ Ĺîǵĥt́ĥóûśê:" + }, + "lighthouse-core/report/html/renderer/util.js | scorescaleLabel": { + "message": "Ŝćôŕê śĉál̂é:" } } diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js index 4daf750cb076..8697925360a1 100644 --- a/lighthouse-core/report/html/renderer/category-renderer.js +++ b/lighthouse-core/report/html/renderer/category-renderer.js @@ -75,10 +75,10 @@ class CategoryRenderer { if (audit.result.scoreDisplayMode === 'error') { auditEl.classList.add(`lh-audit--error`); const textEl = this.dom.find('.lh-audit__display-text', auditEl); - textEl.textContent = 'Error!'; + textEl.textContent = Util.UIStrings.errorLabel; textEl.classList.add('tooltip-boundary'); const tooltip = this.dom.createChildOf(textEl, 'div', 'tooltip tooltip--error'); - tooltip.textContent = audit.result.errorMessage || 'Report error: no audit information'; + tooltip.textContent = audit.result.errorMessage || Util.UIStrings.errorMissingAuditInfo; } else if (audit.result.explanation) { const explEl = this.dom.createChildOf(titleEl, 'div', 'lh-audit-explanation'); explEl.textContent = audit.result.explanation; @@ -89,9 +89,9 @@ class CategoryRenderer { // Add list of warnings or singular warning const warningsEl = this.dom.createChildOf(titleEl, 'div', 'lh-warnings'); if (warnings.length === 1) { - warningsEl.textContent = `Warning: ${warnings.join('')}`; + warningsEl.textContent = `${Util.UIStrings.warningHeader} ${warnings.join('')}`; } else { - warningsEl.textContent = 'Warnings: '; + warningsEl.textContent = Util.UIStrings.warningHeader; const warningsUl = this.dom.createChildOf(warningsEl, 'ul'); for (const warning of warnings) { const item = this.dom.createChildOf(warningsUl, 'li'); @@ -158,7 +158,7 @@ class CategoryRenderer { const itemCountEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__itemcount'); if (expandable) { const chevronEl = summmaryEl.appendChild(this._createChevron()); - chevronEl.title = 'Show audits'; + chevronEl.title = Util.UIStrings.auditGroupExpandTooltip; } if (group.description) { @@ -169,6 +169,7 @@ class CategoryRenderer { headerEl.textContent = group.title; if (opts.itemCount) { + // TODO(i18n): support multiple locales here itemCountEl.textContent = `${opts.itemCount} audits`; } return groupEl; @@ -211,7 +212,7 @@ class CategoryRenderer { */ renderPassedAuditsSection(elements) { const passedElem = this.renderAuditGroup({ - title: `Passed audits`, + title: Util.UIStrings.passedAuditsGroupTitle, }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); passedElem.classList.add('lh-passed-audits'); elements.forEach(elem => passedElem.appendChild(elem)); @@ -224,7 +225,7 @@ class CategoryRenderer { */ _renderNotApplicableAuditsSection(elements) { const notApplicableElem = this.renderAuditGroup({ - title: `Not applicable`, + title: Util.UIStrings.notApplicableAuditsGroupTitle, }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); notApplicableElem.classList.add('lh-audit-group--not-applicable'); elements.forEach(elem => notApplicableElem.appendChild(elem)); @@ -237,7 +238,7 @@ class CategoryRenderer { * @return {Element} */ _renderManualAudits(manualAudits, manualDescription) { - const group = {title: 'Additional items to manually check', description: manualDescription}; + const group = {title: Util.UIStrings.manualAuditsGroupTitle, description: manualDescription}; const auditGroupElem = this.renderAuditGroup(group, {expandable: true, itemCount: manualAudits.length}); auditGroupElem.classList.add('lh-audit-group--manual'); @@ -282,7 +283,7 @@ class CategoryRenderer { percentageEl.textContent = scoreOutOf100.toString(); if (category.score === null) { percentageEl.textContent = '?'; - percentageEl.title = 'Errors occurred while auditing'; + percentageEl.title = Util.UIStrings.errorLabel; } this.dom.find('.lh-gauge__label', tmpl).textContent = category.title; diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index 3b25314a097b..bcbd70978020 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -128,7 +128,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { }); const estValuesEl = this.dom.createChildOf(metricsColumn2El, 'div', 'lh-metrics__disclaimer lh-metrics__disclaimer'); - estValuesEl.textContent = 'Values are estimated and may vary.'; + estValuesEl.textContent = Util.UIStrings.varianceDisclaimer; metricAuditsEl.classList.add('lh-audit-group--metrics'); element.appendChild(metricAuditsEl); @@ -156,6 +156,12 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale); const groupEl = this.renderAuditGroup(groups['load-opportunities'], {expandable: false}); const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity-header', this.templateContext); + + this.dom.find('.lh-load-opportunity__col--one', tmpl).textContent = + Util.UIStrings.opportunityResourceColumnLabel; + this.dom.find('.lh-load-opportunity__col--two', tmpl).textContent = + Util.UIStrings.opportunitySavingsColumnLabel; + const headerEl = this.dom.find('.lh-load-opportunity__header', tmpl); groupEl.appendChild(headerEl); opportunityAudits.forEach((item, i) => diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js index de990d25994e..3ad4925e7b06 100644 --- a/lighthouse-core/report/html/renderer/report-renderer.js +++ b/lighthouse-core/report/html/renderer/report-renderer.js @@ -35,6 +35,11 @@ class ReportRenderer { renderReport(report, container) { // If any mutations happen to the report within the renderers, we want the original object untouched const clone = /** @type {LH.ReportResult} */ (JSON.parse(JSON.stringify(report))); + // Mutate the UIStrings if necessary (while saving originals) + const clonedStrings = JSON.parse(JSON.stringify(Util.UIStrings)); + if (clone.i18n && clone.i18n.rendererFormattedStrings) { + ReportRenderer.updateAllUIStrings(clone.i18n.rendererFormattedStrings); + } // TODO(phulce): we all agree this is technical debt we should fix if (typeof clone.categories !== 'object') throw new Error('No categories provided.'); @@ -43,6 +48,10 @@ class ReportRenderer { container.textContent = ''; // Remove previous report. container.appendChild(this._renderReport(clone)); + + // put the UIStrings back into original state + ReportRenderer.updateAllUIStrings(clonedStrings); + return /** @type {Element} **/ (container); } @@ -126,6 +135,9 @@ class ReportRenderer { } const container = this._dom.cloneTemplate('#tmpl-lh-warnings--toplevel', this._templateContext); + const message = this._dom.find('.lh-warnings__msg', container); + message.textContent = Util.UIStrings.toplevelWarningsMessage; + const warnings = this._dom.find('ul', container); for (const warningString of report.runWarnings) { const warning = warnings.appendChild(this._dom.createElement('li')); @@ -187,6 +199,8 @@ class ReportRenderer { if (scoreHeader) { const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext); + this._dom.find('.lh-scorescale-label', scoreScale).textContent = + Util.UIStrings.scorescaleLabel; scoresContainer.appendChild(scoreHeader); scoresContainer.appendChild(scoreScale); } @@ -213,8 +227,21 @@ class ReportRenderer { }); } } + + /** + * @param {LH.I18NRendererStrings} rendererFormattedStrings + */ + static updateAllUIStrings(rendererFormattedStrings) { + // TODO(i18n): don't mutate these here but on the LHR and pass that around everywhere + for (const [key, value] of Object.entries(rendererFormattedStrings)) { + Util.UIStrings[key] = value; + } + } } +/** @type {LH.I18NRendererStrings} */ +ReportRenderer._UIStringsStash = {}; + if (typeof module !== 'undefined' && module.exports) { module.exports = ReportRenderer; } else { diff --git a/lighthouse-core/report/html/renderer/util.js b/lighthouse-core/report/html/renderer/util.js index 87fc3a73978e..0bdc243906d7 100644 --- a/lighthouse-core/report/html/renderer/util.js +++ b/lighthouse-core/report/html/renderer/util.js @@ -378,6 +378,23 @@ class Util { } } +Util.UIStrings = { + varianceDisclaimer: 'Values are estimated and may vary.', + opportunityResourceColumnLabel: 'Resource to optimize', + opportunitySavingsColumnLabel: 'Estimated Savings', + + errorMissingAuditInfo: 'Report error: no audit information', + errorLabel: 'Error!', + warningHeader: 'Warnings: ', + auditGroupExpandTooltip: 'Show audits', + passedAuditsGroupTitle: 'Passed audits', + notApplicableAuditsGroupTitle: 'Not applicable', + manualAuditsGroupTitle: 'Additional items to manually check', + + toplevelWarningsMessage: 'There were issues affecting this run of Lighthouse:', + scorescaleLabel: 'Score scale:', +}; + if (typeof module !== 'undefined' && module.exports) { module.exports = Util; } else { diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html index 770432f86343..087ad79f0ab5 100644 --- a/lighthouse-core/report/html/templates.html +++ b/lighthouse-core/report/html/templates.html @@ -1,7 +1,7 @@ @@ -9,7 +9,7 @@