lighthouse 8.6.0-dev.20211013 → 8.6.0-dev.20211017
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.
- package/dist/report/flow.js +172 -0
- package/dist/report/standalone.js +227 -0
- package/flow-report/src/i18n/i18n.tsx +8 -0
- package/flow-report/src/i18n/ui-strings.js +48 -0
- package/flow-report/src/summary/category.tsx +9 -12
- package/flow-report/src/summary/summary.tsx +5 -5
- package/flow-report/test/summary/category-test.tsx +9 -5
- package/flow-report/test/summary/summary-test.tsx +1 -1
- package/flow-report/tsconfig.json +1 -0
- package/lighthouse-core/config/config-helpers.js +1 -0
- package/lighthouse-core/lib/i18n/i18n.js +19 -8
- package/package.json +1 -1
- package/shared/localization/format.js +50 -17
- package/shared/localization/locales/en-US.json +18 -0
- package/shared/localization/locales/en-XL.json +18 -0
- package/shared/localization/locales.js +6 -0
- package/shared/test/localization/format-test.js +44 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import {createContext, FunctionComponent} from 'preact';
|
|
8
8
|
import {useContext, useMemo} from 'preact/hooks';
|
|
9
9
|
|
|
10
|
+
import {formatMessage} from '../../../shared/localization/format';
|
|
10
11
|
import {I18n} from '../../../report/renderer/i18n';
|
|
11
12
|
import {UIStrings} from './ui-strings';
|
|
12
13
|
import {useLocale} from '../util';
|
|
@@ -25,6 +26,13 @@ export function useUIStrings() {
|
|
|
25
26
|
return i18n.strings;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
export function useStringFormatter() {
|
|
30
|
+
const locale = useLocale();
|
|
31
|
+
return (str: string, values?: Record<string, string|number>) => {
|
|
32
|
+
return formatMessage(str, values, locale);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export const I18nProvider: FunctionComponent = ({children}) => {
|
|
29
37
|
const locale = useLocale();
|
|
30
38
|
const i18n = useMemo(() => new I18n(locale, {
|
|
@@ -57,6 +57,30 @@ export const UIStrings = {
|
|
|
57
57
|
ratingFail: 'Poor',
|
|
58
58
|
/** Rating indicating that a report category rating could not be calculated because of an error. */
|
|
59
59
|
ratingError: 'Error',
|
|
60
|
+
/**
|
|
61
|
+
* @description Label indicating the number of Lighthouse reports that evaluate a web page as it loads.
|
|
62
|
+
* @example {2} numNavigation
|
|
63
|
+
*/
|
|
64
|
+
navigationReportCount: `{numNavigation, plural,
|
|
65
|
+
=1 {{numNavigation} navigation report}
|
|
66
|
+
other {{numNavigation} navigation reports}
|
|
67
|
+
}`,
|
|
68
|
+
/**
|
|
69
|
+
* @description Label indicating the number of Lighthouse reports that evaluate a web page over a period of time.
|
|
70
|
+
* @example {2} numTimespan
|
|
71
|
+
*/
|
|
72
|
+
timespanReportCount: `{numTimespan, plural,
|
|
73
|
+
=1 {{numTimespan} timespan report}
|
|
74
|
+
other {{numTimespan} timespan reports}
|
|
75
|
+
}`,
|
|
76
|
+
/**
|
|
77
|
+
* @description Label indicating the number of Lighthouse reports that evaluate a web page at a single point in time.
|
|
78
|
+
* @example {2} numSnapshot
|
|
79
|
+
*/
|
|
80
|
+
snapshotReportCount: `{numSnapshot, plural,
|
|
81
|
+
=1 {{numSnapshot} snapshot report}
|
|
82
|
+
other {{numSnapshot} snapshot reports}
|
|
83
|
+
}`,
|
|
60
84
|
/** Label for a button that saves a Lighthouse report to disk. */
|
|
61
85
|
save: 'Save',
|
|
62
86
|
/** Label for a button that toggles the help modal with explanations on how to interpret the Lighthouse flow report. */
|
|
@@ -83,4 +107,28 @@ export const UIStrings = {
|
|
|
83
107
|
helpUseCaseSnapshot1: 'Find accessibility issues in single page applications or complex forms.',
|
|
84
108
|
/** Example use case for how Lighthouse can be applied in practice. Appears in a list with other examples. */
|
|
85
109
|
helpUseCaseSnapshot2: 'Evaluate best practices of menus and UI elements hidden behind interaction.',
|
|
110
|
+
/**
|
|
111
|
+
* @description Label indicating the number of Lighthouse audits that passed.
|
|
112
|
+
* @example {2} numPassed
|
|
113
|
+
*/
|
|
114
|
+
passedAuditCount: `{numPassed, plural,
|
|
115
|
+
=1 {{numPassed} audit passed}
|
|
116
|
+
other {{numPassed} audits passed}
|
|
117
|
+
}`,
|
|
118
|
+
/**
|
|
119
|
+
* @description Label indicating the number of Lighthouse audits that are possible to pass for a page.
|
|
120
|
+
* @example {2} numPassableAudits
|
|
121
|
+
*/
|
|
122
|
+
passableAuditCount: `{numPassableAudits, plural,
|
|
123
|
+
=1 {{numPassableAudits} passable audit}
|
|
124
|
+
other {{numPassableAudits} passable audits}
|
|
125
|
+
}`,
|
|
126
|
+
/**
|
|
127
|
+
* @description Label indicating the number of Lighthouse audits that are informative.
|
|
128
|
+
* @example {2} numInformative
|
|
129
|
+
*/
|
|
130
|
+
informativeAuditCount: `{numInformative, plural,
|
|
131
|
+
=1 {{numInformative} informative audit}
|
|
132
|
+
other {{numInformative} informative audits}
|
|
133
|
+
}`,
|
|
86
134
|
};
|
|
@@ -9,7 +9,7 @@ import {FunctionComponent} from 'preact';
|
|
|
9
9
|
import {Util} from '../../../report/renderer/util';
|
|
10
10
|
import {Separator} from '../common';
|
|
11
11
|
import {CategoryScore} from '../wrappers/category-score';
|
|
12
|
-
import {useUIStrings} from '../i18n/i18n';
|
|
12
|
+
import {useStringFormatter, useUIStrings} from '../i18n/i18n';
|
|
13
13
|
|
|
14
14
|
import type {UIStringsType} from '../i18n/ui-strings';
|
|
15
15
|
|
|
@@ -35,6 +35,7 @@ export const SummaryTooltip: FunctionComponent<{
|
|
|
35
35
|
gatherMode: LH.Result.GatherMode
|
|
36
36
|
}> = ({category, gatherMode}) => {
|
|
37
37
|
const strings = useUIStrings();
|
|
38
|
+
const str_ = useStringFormatter();
|
|
38
39
|
const {
|
|
39
40
|
numPassed,
|
|
40
41
|
numPassableAudits,
|
|
@@ -69,18 +70,14 @@ export const SummaryTooltip: FunctionComponent<{
|
|
|
69
70
|
}
|
|
70
71
|
</div>
|
|
71
72
|
<div className="SummaryTooltip__fraction">
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
73
|
+
<span>{str_(strings.passedAuditCount, {numPassed})}</span>
|
|
74
|
+
<span> / </span>
|
|
75
|
+
<span>{str_(strings.passableAuditCount, {numPassableAudits})}</span>
|
|
76
76
|
</div>
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{`${numInformative} informative audits`}
|
|
82
|
-
</div> :
|
|
83
|
-
null
|
|
77
|
+
{numInformative !== 0 &&
|
|
78
|
+
<div className="SummaryTooltip__informative">
|
|
79
|
+
{str_(strings.informativeAuditCount, {numInformative})}
|
|
80
|
+
</div>
|
|
84
81
|
}
|
|
85
82
|
</div>
|
|
86
83
|
);
|
|
@@ -11,7 +11,7 @@ import {FlowSegment, FlowStepThumbnail, Separator} from '../common';
|
|
|
11
11
|
import {getModeDescription, useFlowResult} from '../util';
|
|
12
12
|
import {Util} from '../../../report/renderer/util';
|
|
13
13
|
import {SummaryCategory} from './category';
|
|
14
|
-
import {useUIStrings} from '../i18n/i18n';
|
|
14
|
+
import {useStringFormatter, useUIStrings} from '../i18n/i18n';
|
|
15
15
|
|
|
16
16
|
const DISPLAYED_CATEGORIES = ['performance', 'accessibility', 'best-practices', 'seo'];
|
|
17
17
|
const THUMBNAIL_WIDTH = 50;
|
|
@@ -108,6 +108,7 @@ const SummaryFlow: FunctionComponent = () => {
|
|
|
108
108
|
export const SummaryHeader: FunctionComponent = () => {
|
|
109
109
|
const flowResult = useFlowResult();
|
|
110
110
|
const strings = useUIStrings();
|
|
111
|
+
const str_ = useStringFormatter();
|
|
111
112
|
|
|
112
113
|
let numNavigation = 0;
|
|
113
114
|
let numTimespan = 0;
|
|
@@ -126,11 +127,10 @@ export const SummaryHeader: FunctionComponent = () => {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
// TODO(FLOW-I18N): Placeholder format.
|
|
130
130
|
const subtitleCounts = [];
|
|
131
|
-
if (numNavigation) subtitleCounts.push(
|
|
132
|
-
if (numTimespan) subtitleCounts.push(
|
|
133
|
-
if (numSnapshot) subtitleCounts.push(
|
|
131
|
+
if (numNavigation) subtitleCounts.push(str_(strings.navigationReportCount, {numNavigation}));
|
|
132
|
+
if (numTimespan) subtitleCounts.push(str_(strings.timespanReportCount, {numTimespan}));
|
|
133
|
+
if (numSnapshot) subtitleCounts.push(str_(strings.snapshotReportCount, {numSnapshot}));
|
|
134
134
|
const subtitle = subtitleCounts.join(' · ');
|
|
135
135
|
|
|
136
136
|
return (
|
|
@@ -44,7 +44,8 @@ describe('SummaryTooltip', () => {
|
|
|
44
44
|
|
|
45
45
|
expect(root.getByText('Average')).toBeTruthy();
|
|
46
46
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
47
|
-
expect(root.getByText('2 audits passed
|
|
47
|
+
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
48
|
+
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
it('renders tooltip without rating', async () => {
|
|
@@ -65,7 +66,8 @@ describe('SummaryTooltip', () => {
|
|
|
65
66
|
|
|
66
67
|
expect(() => root.getByText(/^(Average|Good|Poor)$/)).toThrow();
|
|
67
68
|
expect(() => root.getByText(/^[0-9]+$/)).toThrow();
|
|
68
|
-
expect(root.getByText('2 audits passed
|
|
69
|
+
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
70
|
+
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
it('renders scored category tooltip with score', async () => {
|
|
@@ -86,7 +88,8 @@ describe('SummaryTooltip', () => {
|
|
|
86
88
|
|
|
87
89
|
expect(root.getByText('Good')).toBeTruthy();
|
|
88
90
|
expect(root.getByText('100')).toBeTruthy();
|
|
89
|
-
expect(root.getByText('2 audits passed
|
|
91
|
+
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
92
|
+
expect(root.getByText('3 passable audits')).toBeTruthy();
|
|
90
93
|
});
|
|
91
94
|
|
|
92
95
|
it('renders informative audit count if any', async () => {
|
|
@@ -107,7 +110,8 @@ describe('SummaryTooltip', () => {
|
|
|
107
110
|
|
|
108
111
|
expect(root.getByText('Good')).toBeTruthy();
|
|
109
112
|
expect(root.getByText('100')).toBeTruthy();
|
|
110
|
-
expect(root.getByText('2 audits passed
|
|
111
|
-
expect(root.getByText('
|
|
113
|
+
expect(root.getByText('2 audits passed')).toBeTruthy();
|
|
114
|
+
expect(root.getByText('2 passable audits')).toBeTruthy();
|
|
115
|
+
expect(root.getByText('1 informative audit')).toBeTruthy();
|
|
112
116
|
});
|
|
113
117
|
});
|
|
@@ -34,7 +34,7 @@ describe('SummaryHeader', () => {
|
|
|
34
34
|
const lhrCounts = root.getByText(/·/);
|
|
35
35
|
expect(root.getByText('Summary')).toBeTruthy();
|
|
36
36
|
expect(lhrCounts.textContent).toEqual(
|
|
37
|
-
'2 navigation reports · 1 timespan
|
|
37
|
+
'2 navigation reports · 1 timespan report · 1 snapshot report'
|
|
38
38
|
);
|
|
39
39
|
});
|
|
40
40
|
});
|
|
@@ -283,6 +283,7 @@ function resolveSettings(settingsJson = {}, overrides = undefined) {
|
|
|
283
283
|
// If a locale is requested in flags or settings, use it. A typical CLI run will not have one,
|
|
284
284
|
// however `lookupLocale` will always determine which of our supported locales to use (falling
|
|
285
285
|
// back if necessary).
|
|
286
|
+
// TODO: could do more work to sniff out the user's locale
|
|
286
287
|
const locale = i18n.lookupLocale((overrides && overrides.locale) || settingsJson.locale);
|
|
287
288
|
|
|
288
289
|
// Fill in missing settings with defaults
|
|
@@ -12,9 +12,11 @@ const lookupClosestLocale = require('lookup-closest-locale');
|
|
|
12
12
|
const {getAvailableLocales} = require('../../../shared/localization/format.js');
|
|
13
13
|
const log = require('lighthouse-logger');
|
|
14
14
|
const {LH_ROOT} = require('../../../root.js');
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const {
|
|
16
|
+
isIcuMessage,
|
|
17
|
+
formatMessage,
|
|
18
|
+
DEFAULT_LOCALE,
|
|
19
|
+
} = require('../../../shared/localization/format.js');
|
|
18
20
|
|
|
19
21
|
const UIStrings = {
|
|
20
22
|
/** Used to show the duration in milliseconds that something lasted. The `{timeInMs}` placeholder will be replaced with the time duration, shown in milliseconds (e.g. 63 ms) */
|
|
@@ -118,23 +120,32 @@ const UIStrings = {
|
|
|
118
120
|
* - supported locales in Intl formatters
|
|
119
121
|
*
|
|
120
122
|
* If `locale` isn't provided or one could not be found, DEFAULT_LOCALE is returned.
|
|
123
|
+
*
|
|
124
|
+
* By default any of the locales Lighthouse has strings for can be returned, but this
|
|
125
|
+
* can be overriden with `possibleLocales`, useful e.g. when Lighthouse is bundled and
|
|
126
|
+
* only DEFAULT_LOCALE is available, but `possibleLocales` can be used to select a
|
|
127
|
+
* locale available to be downloaded on demand.
|
|
121
128
|
* @param {string|string[]=} locales
|
|
129
|
+
* @param {Array<string>=} possibleLocales
|
|
122
130
|
* @return {LH.Locale}
|
|
123
131
|
*/
|
|
124
|
-
function lookupLocale(locales) {
|
|
125
|
-
//
|
|
132
|
+
function lookupLocale(locales, possibleLocales) {
|
|
133
|
+
// TODO: lookupLocale may need to be split into two functions, one that canonicalizes
|
|
134
|
+
// locales and one that looks up the best locale filename for a given locale.
|
|
135
|
+
// e.g. `en-IE` is canonical, but uses `en-GB.json`. See TODO in locales.js
|
|
136
|
+
|
|
126
137
|
if (typeof Intl !== 'object') {
|
|
138
|
+
// If Node was built with `--with-intl=none`, `Intl` won't exist.
|
|
127
139
|
throw new Error('Lighthouse must be run in Node with `Intl` support. See https://nodejs.org/api/intl.html for help');
|
|
128
140
|
}
|
|
129
141
|
|
|
130
|
-
// TODO: could do more work to sniff out the user's locale
|
|
131
142
|
const canonicalLocales = Intl.getCanonicalLocales(locales);
|
|
132
143
|
|
|
133
144
|
// Filter by what's available in this runtime.
|
|
134
145
|
const availableLocales = Intl.NumberFormat.supportedLocalesOf(canonicalLocales);
|
|
135
146
|
|
|
136
147
|
// Get available locales and transform into object to match `lookupClosestLocale`'s API.
|
|
137
|
-
const localesWithMessages = getAvailableLocales();
|
|
148
|
+
const localesWithMessages = possibleLocales || getAvailableLocales();
|
|
138
149
|
const localesWithmessagesObj = /** @type {Record<LH.Locale, LhlMessages>} */ (
|
|
139
150
|
Object.fromEntries(localesWithMessages.map(l => [l, {}])));
|
|
140
151
|
|
|
@@ -182,7 +193,7 @@ function createIcuMessageFn(filename, fileStrings) {
|
|
|
182
193
|
return {
|
|
183
194
|
i18nId,
|
|
184
195
|
values,
|
|
185
|
-
formattedDefault:
|
|
196
|
+
formattedDefault: formatMessage(message, values, DEFAULT_LOCALE),
|
|
186
197
|
};
|
|
187
198
|
};
|
|
188
199
|
|
package/package.json
CHANGED
|
@@ -13,6 +13,8 @@ const {isObjectOfUnknownValues, isObjectOrArrayOfUnknownValues} = require('../ty
|
|
|
13
13
|
/** Contains available locales with messages. May be an empty object if bundled. */
|
|
14
14
|
const LOCALE_MESSAGES = require('./locales.js');
|
|
15
15
|
|
|
16
|
+
const DEFAULT_LOCALE = 'en-US';
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* The locale tags for the localized messages available to Lighthouse on disk.
|
|
18
20
|
* When bundled, these will be inlined by brfs.
|
|
@@ -169,7 +171,7 @@ function _preformatValues(messageFormatter, values, lhlMessage) {
|
|
|
169
171
|
* @param {LH.Locale} locale
|
|
170
172
|
* @return {string}
|
|
171
173
|
*/
|
|
172
|
-
function
|
|
174
|
+
function formatMessage(message, values = {}, locale) {
|
|
173
175
|
// When using accented english, force the use of a different locale for number formatting.
|
|
174
176
|
const localeForMessageFormat = (locale === 'en-XA' || locale === 'en-XL') ? 'de-DE' : locale;
|
|
175
177
|
|
|
@@ -189,19 +191,21 @@ function _formatMessage(message, values = {}, locale) {
|
|
|
189
191
|
* @return {string}
|
|
190
192
|
*/
|
|
191
193
|
function _localizeIcuMessage(icuMessage, locale) {
|
|
192
|
-
const localeMessages =
|
|
193
|
-
if (!localeMessages) throw new Error(`Unsupported locale '${locale}'`);
|
|
194
|
+
const localeMessages = _getLocaleMessages(locale);
|
|
194
195
|
const localeMessage = localeMessages[icuMessage.i18nId];
|
|
195
196
|
|
|
196
|
-
//
|
|
197
|
-
// message in the specified locale.
|
|
198
|
-
//
|
|
199
|
-
//
|
|
197
|
+
// Use the DEFAULT_LOCALE fallback (usually the original english message) if we couldn't
|
|
198
|
+
// find a message in the specified locale. Possible reasons:
|
|
199
|
+
// - string drift between Lighthouse versions
|
|
200
|
+
// - in a bundle stripped of locale files but running in the DEFAULT_LOCALE
|
|
201
|
+
// - new strings haven't been updated yet in a local dev run
|
|
202
|
+
// Better to have an english message than no message at all; in some cases it
|
|
203
|
+
// won't even matter.
|
|
200
204
|
if (!localeMessage) {
|
|
201
205
|
return icuMessage.formattedDefault;
|
|
202
206
|
}
|
|
203
207
|
|
|
204
|
-
return
|
|
208
|
+
return formatMessage(localeMessage.message, icuMessage.values, locale);
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
/**
|
|
@@ -209,9 +213,10 @@ function _localizeIcuMessage(icuMessage, locale) {
|
|
|
209
213
|
* @return {Record<string, string>}
|
|
210
214
|
*/
|
|
211
215
|
function getRendererFormattedStrings(locale) {
|
|
212
|
-
const localeMessages =
|
|
213
|
-
if (!localeMessages) throw new Error(`Unsupported locale '${locale}'`);
|
|
216
|
+
const localeMessages = _getLocaleMessages(locale);
|
|
214
217
|
|
|
218
|
+
// If `localeMessages` is empty in the bundled and DEFAULT_LOCALE case, this
|
|
219
|
+
// will be empty and the report will fall back to the util UIStrings for these.
|
|
215
220
|
const icuMessageIds = Object.keys(localeMessages).filter(f => f.startsWith('report/'));
|
|
216
221
|
/** @type {Record<string, string>} */
|
|
217
222
|
const strings = {};
|
|
@@ -349,12 +354,38 @@ function replaceIcuMessages(inputObject, locale) {
|
|
|
349
354
|
return icuMessagePaths;
|
|
350
355
|
}
|
|
351
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Returns the locale messages for the given `locale`, if they exist.
|
|
359
|
+
* Throws if an unsupported locale.
|
|
360
|
+
*
|
|
361
|
+
* NOTE: If DEFAULT_LOCALE is requested and this is inside a bundle with locale
|
|
362
|
+
* messages stripped, an empty object will be returned. Default fallbacks will need to handle that case.
|
|
363
|
+
* @param {LH.Locale} locale
|
|
364
|
+
* @return {import('./locales').LhlMessages}
|
|
365
|
+
*/
|
|
366
|
+
function _getLocaleMessages(locale) {
|
|
367
|
+
const localeMessages = LOCALE_MESSAGES[locale];
|
|
368
|
+
if (!localeMessages) {
|
|
369
|
+
if (locale === DEFAULT_LOCALE) {
|
|
370
|
+
// If the default locale isn't in LOCALE_MESSAGES, this is likely executing
|
|
371
|
+
// in a bundle. Let the caller use the fallbacks available.
|
|
372
|
+
return {};
|
|
373
|
+
}
|
|
374
|
+
throw new Error(`Unsupported locale '${locale}'`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return localeMessages;
|
|
378
|
+
}
|
|
379
|
+
|
|
352
380
|
/**
|
|
353
381
|
* Returns whether the `requestedLocale` can be used.
|
|
354
382
|
* @param {LH.Locale} requestedLocale
|
|
355
383
|
* @return {boolean}
|
|
356
384
|
*/
|
|
357
385
|
function hasLocale(requestedLocale) {
|
|
386
|
+
// The default locale is always supported through `IcuMessage.formattedDefault`.
|
|
387
|
+
if (requestedLocale === DEFAULT_LOCALE) return true;
|
|
388
|
+
|
|
358
389
|
const hasIntlSupport = Intl.NumberFormat.supportedLocalesOf([requestedLocale]).length > 0;
|
|
359
390
|
const hasMessages = Boolean(LOCALE_MESSAGES[requestedLocale]);
|
|
360
391
|
|
|
@@ -362,8 +393,9 @@ function hasLocale(requestedLocale) {
|
|
|
362
393
|
}
|
|
363
394
|
|
|
364
395
|
/**
|
|
365
|
-
* Returns a list of canonical locales
|
|
366
|
-
*
|
|
396
|
+
* Returns a list of canonical locales, as defined by the existent message files.
|
|
397
|
+
* In practice, each of these may have aliases in the full list returned by
|
|
398
|
+
* `getAvailableLocales()`.
|
|
367
399
|
* TODO: create a CanonicalLocale type
|
|
368
400
|
* @return {Array<string>}
|
|
369
401
|
*/
|
|
@@ -375,13 +407,13 @@ function getCanonicalLocales() {
|
|
|
375
407
|
* Returns a list of available locales.
|
|
376
408
|
* - if full build, this includes all canonical locales, aliases, and any locale added
|
|
377
409
|
* via `registerLocaleData`.
|
|
378
|
-
* - if bundled and locale messages have been stripped (locales.js shimmed), this includes
|
|
379
|
-
*
|
|
380
|
-
* from `registerLocaleData`.
|
|
410
|
+
* - if bundled and locale messages have been stripped (locales.js shimmed), this includes
|
|
411
|
+
* only DEFAULT_LOCALE and any locales from `registerLocaleData`.
|
|
381
412
|
* @return {Array<LH.Locale>}
|
|
382
413
|
*/
|
|
383
414
|
function getAvailableLocales() {
|
|
384
|
-
|
|
415
|
+
const localesWithMessages = new Set([...Object.keys(LOCALE_MESSAGES), DEFAULT_LOCALE]);
|
|
416
|
+
return /** @type {Array<LH.Locale>} */ ([...localesWithMessages].sort());
|
|
385
417
|
}
|
|
386
418
|
|
|
387
419
|
/**
|
|
@@ -407,6 +439,7 @@ function getIcuMessageIdParts(i18nMessageId) {
|
|
|
407
439
|
}
|
|
408
440
|
|
|
409
441
|
module.exports = {
|
|
442
|
+
DEFAULT_LOCALE,
|
|
410
443
|
_formatPathAsString,
|
|
411
444
|
collectAllCustomElementsFromICU,
|
|
412
445
|
isIcuMessage,
|
|
@@ -415,7 +448,7 @@ module.exports = {
|
|
|
415
448
|
replaceIcuMessages,
|
|
416
449
|
hasLocale,
|
|
417
450
|
registerLocaleData,
|
|
418
|
-
|
|
451
|
+
formatMessage,
|
|
419
452
|
getIcuMessageIdParts,
|
|
420
453
|
getAvailableLocales,
|
|
421
454
|
getCanonicalLocales,
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseTimespan2": {
|
|
60
60
|
"message": "Discover performance opportunities to improve the experience for long-lived pages and single-page applications."
|
|
61
61
|
},
|
|
62
|
+
"flow-report/src/i18n/ui-strings.js | informativeAuditCount": {
|
|
63
|
+
"message": "{numInformative, plural,\n =1 {{numInformative} informative audit}\n other {{numInformative} informative audits}\n }"
|
|
64
|
+
},
|
|
62
65
|
"flow-report/src/i18n/ui-strings.js | mobile": {
|
|
63
66
|
"message": "Mobile"
|
|
64
67
|
},
|
|
@@ -71,6 +74,15 @@
|
|
|
71
74
|
"flow-report/src/i18n/ui-strings.js | navigationReport": {
|
|
72
75
|
"message": "Navigation report"
|
|
73
76
|
},
|
|
77
|
+
"flow-report/src/i18n/ui-strings.js | navigationReportCount": {
|
|
78
|
+
"message": "{numNavigation, plural,\n =1 {{numNavigation} navigation report}\n other {{numNavigation} navigation reports}\n }"
|
|
79
|
+
},
|
|
80
|
+
"flow-report/src/i18n/ui-strings.js | passableAuditCount": {
|
|
81
|
+
"message": "{numPassableAudits, plural,\n =1 {{numPassableAudits} passable audit}\n other {{numPassableAudits} passable audits}\n }"
|
|
82
|
+
},
|
|
83
|
+
"flow-report/src/i18n/ui-strings.js | passedAuditCount": {
|
|
84
|
+
"message": "{numPassed, plural,\n =1 {{numPassed} audit passed}\n other {{numPassed} audits passed}\n }"
|
|
85
|
+
},
|
|
74
86
|
"flow-report/src/i18n/ui-strings.js | ratingAverage": {
|
|
75
87
|
"message": "Average"
|
|
76
88
|
},
|
|
@@ -95,6 +107,9 @@
|
|
|
95
107
|
"flow-report/src/i18n/ui-strings.js | snapshotReport": {
|
|
96
108
|
"message": "Snapshot report"
|
|
97
109
|
},
|
|
110
|
+
"flow-report/src/i18n/ui-strings.js | snapshotReportCount": {
|
|
111
|
+
"message": "{numSnapshot, plural,\n =1 {{numSnapshot} snapshot report}\n other {{numSnapshot} snapshot reports}\n }"
|
|
112
|
+
},
|
|
98
113
|
"flow-report/src/i18n/ui-strings.js | summary": {
|
|
99
114
|
"message": "Summary"
|
|
100
115
|
},
|
|
@@ -107,6 +122,9 @@
|
|
|
107
122
|
"flow-report/src/i18n/ui-strings.js | timespanReport": {
|
|
108
123
|
"message": "Timespan report"
|
|
109
124
|
},
|
|
125
|
+
"flow-report/src/i18n/ui-strings.js | timespanReportCount": {
|
|
126
|
+
"message": "{numTimespan, plural,\n =1 {{numTimespan} timespan report}\n other {{numTimespan} timespan reports}\n }"
|
|
127
|
+
},
|
|
110
128
|
"flow-report/src/i18n/ui-strings.js | title": {
|
|
111
129
|
"message": "Lighthouse User Flow Report"
|
|
112
130
|
},
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
"flow-report/src/i18n/ui-strings.js | helpUseCaseTimespan2": {
|
|
60
60
|
"message": "D̂íŝćôv́êŕ p̂ér̂f́ôŕm̂án̂ćê óp̂ṕôŕt̂ún̂ít̂íêś t̂ó îḿp̂ŕôv́ê t́ĥé êx́p̂ér̂íêńĉé f̂ór̂ ĺôńĝ-ĺîv́êd́ p̂áĝéŝ án̂d́ ŝín̂ǵl̂é-p̂áĝé âṕp̂ĺîćât́îón̂ś."
|
|
61
61
|
},
|
|
62
|
+
"flow-report/src/i18n/ui-strings.js | informativeAuditCount": {
|
|
63
|
+
"message": "{numInformative, plural,\n =1 {{numInformative} îńf̂ór̂ḿât́îv́ê áûd́ît́}\n other {{numInformative} îńf̂ór̂ḿât́îv́ê áûd́ît́ŝ}\n }"
|
|
64
|
+
},
|
|
62
65
|
"flow-report/src/i18n/ui-strings.js | mobile": {
|
|
63
66
|
"message": "M̂ób̂íl̂é"
|
|
64
67
|
},
|
|
@@ -71,6 +74,15 @@
|
|
|
71
74
|
"flow-report/src/i18n/ui-strings.js | navigationReport": {
|
|
72
75
|
"message": "N̂áv̂íĝát̂íôń r̂ép̂ór̂t́"
|
|
73
76
|
},
|
|
77
|
+
"flow-report/src/i18n/ui-strings.js | navigationReportCount": {
|
|
78
|
+
"message": "{numNavigation, plural,\n =1 {{numNavigation} n̂áv̂íĝát̂íôń r̂ép̂ór̂t́}\n other {{numNavigation} n̂áv̂íĝát̂íôń r̂ép̂ór̂t́ŝ}\n }"
|
|
79
|
+
},
|
|
80
|
+
"flow-report/src/i18n/ui-strings.js | passableAuditCount": {
|
|
81
|
+
"message": "{numPassableAudits, plural,\n =1 {{numPassableAudits} p̂áŝśâb́l̂é âúd̂ít̂}\n other {{numPassableAudits} ṕâśŝáb̂ĺê áûd́ît́ŝ}\n }"
|
|
82
|
+
},
|
|
83
|
+
"flow-report/src/i18n/ui-strings.js | passedAuditCount": {
|
|
84
|
+
"message": "{numPassed, plural,\n =1 {{numPassed} âúd̂ít̂ ṕâśŝéd̂}\n other {{numPassed} áûd́ît́ŝ ṕâśŝéd̂}\n }"
|
|
85
|
+
},
|
|
74
86
|
"flow-report/src/i18n/ui-strings.js | ratingAverage": {
|
|
75
87
|
"message": "Âv́êŕâǵê"
|
|
76
88
|
},
|
|
@@ -95,6 +107,9 @@
|
|
|
95
107
|
"flow-report/src/i18n/ui-strings.js | snapshotReport": {
|
|
96
108
|
"message": "Ŝńâṕŝh́ôt́ r̂ép̂ór̂t́"
|
|
97
109
|
},
|
|
110
|
+
"flow-report/src/i18n/ui-strings.js | snapshotReportCount": {
|
|
111
|
+
"message": "{numSnapshot, plural,\n =1 {{numSnapshot} ŝńâṕŝh́ôt́ r̂ép̂ór̂t́}\n other {{numSnapshot} ŝńâṕŝh́ôt́ r̂ép̂ór̂t́ŝ}\n }"
|
|
112
|
+
},
|
|
98
113
|
"flow-report/src/i18n/ui-strings.js | summary": {
|
|
99
114
|
"message": "Ŝúm̂ḿâŕŷ"
|
|
100
115
|
},
|
|
@@ -107,6 +122,9 @@
|
|
|
107
122
|
"flow-report/src/i18n/ui-strings.js | timespanReport": {
|
|
108
123
|
"message": "T̂ím̂éŝṕâń r̂ép̂ór̂t́"
|
|
109
124
|
},
|
|
125
|
+
"flow-report/src/i18n/ui-strings.js | timespanReportCount": {
|
|
126
|
+
"message": "{numTimespan, plural,\n =1 {{numTimespan} t̂ím̂éŝṕâń r̂ép̂ór̂t́}\n other {{numTimespan} t̂ím̂éŝṕâń r̂ép̂ór̂t́ŝ}\n }"
|
|
127
|
+
},
|
|
110
128
|
"flow-report/src/i18n/ui-strings.js | title": {
|
|
111
129
|
"message": "L̂íĝh́t̂h́ôúŝé Ûśêŕ F̂ĺôẃ R̂ép̂ór̂t́"
|
|
112
130
|
},
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
* Google locale inheritance rules: https://goto.google.com/ccssm
|
|
13
13
|
* CLDR language aliases: https://www.unicode.org/cldr/charts/latest/supplemental/aliases.html
|
|
14
14
|
* CLDR locale inheritance: https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/parentLocales.json
|
|
15
|
+
*
|
|
16
|
+
* For Lighthouse bundles that shouldn't include locale data, the recommended pattern
|
|
17
|
+
* is to replace the default export of this file with `{}` so that no locale messages
|
|
18
|
+
* are included. Strings will work normally through the IcuMessage.formattedDefault
|
|
19
|
+
* fallback, and locale messages can be added on demand (e.g. dynamically fetched)
|
|
20
|
+
* through `format.registerLocaleData()`.
|
|
15
21
|
*/
|
|
16
22
|
|
|
17
23
|
// TODO(paulirish): Centralize locale inheritance (combining this & i18n.lookupLocale()), adopt cldr parentLocale rules.
|
|
@@ -9,10 +9,54 @@ const path = require('path');
|
|
|
9
9
|
|
|
10
10
|
const format = require('../../localization/format.js');
|
|
11
11
|
const i18n = require('../../../lighthouse-core/lib/i18n/i18n.js');
|
|
12
|
+
const constants = require('../../../lighthouse-core/config/constants.js');
|
|
13
|
+
const locales = require('../../localization/locales.js');
|
|
12
14
|
|
|
13
15
|
/* eslint-env jest */
|
|
14
16
|
|
|
15
17
|
describe('format', () => {
|
|
18
|
+
describe('DEFAULT_LOCALE', () => {
|
|
19
|
+
it('is the same as the default config locale', () => {
|
|
20
|
+
expect(format.DEFAULT_LOCALE).toBe(constants.defaultSettings.locale);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('#getAvailableLocales', () => {
|
|
25
|
+
it('has all the available locales', () => {
|
|
26
|
+
const availableLocales = format.getAvailableLocales();
|
|
27
|
+
for (const locale of ['en', 'es', 'ru', 'zh']) {
|
|
28
|
+
expect(availableLocales).toContain(locale);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rawLocales = Object.keys(locales).sort();
|
|
32
|
+
expect(availableLocales.sort()).toEqual(rawLocales);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('contains the default locale', () => {
|
|
36
|
+
expect(format.getAvailableLocales()).toContain(format.DEFAULT_LOCALE);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('#getCanonicalLocales', () => {
|
|
41
|
+
it('contains some canonical locales', () => {
|
|
42
|
+
const canonicalLocales = format.getCanonicalLocales();
|
|
43
|
+
for (const locale of ['en-US', 'es', 'ru', 'zh']) {
|
|
44
|
+
expect(canonicalLocales).toContain(locale);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('is a subset of the available locales', () => {
|
|
49
|
+
const canonicalLocales = format.getCanonicalLocales();
|
|
50
|
+
const availableLocales = format.getAvailableLocales();
|
|
51
|
+
|
|
52
|
+
for (const canonicalLocale of canonicalLocales) {
|
|
53
|
+
expect(availableLocales).toContain(canonicalLocale);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
expect(canonicalLocales.length).toBeLessThan(availableLocales.length);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
16
60
|
describe('#_formatPathAsString', () => {
|
|
17
61
|
it('handles simple paths', () => {
|
|
18
62
|
expect(format._formatPathAsString(['foo'])).toBe('foo');
|