Skip to content

Commit b8bcfee

Browse files
authored
core(i18n): fix path bug resulting in invalid i18n id via npx (#14314)
1 parent 03674e9 commit b8bcfee

File tree

2 files changed

+47
-33
lines changed

2 files changed

+47
-33
lines changed

core/lib/i18n/i18n.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ function lookupLocale(locales, possibleLocales) {
173173
function createIcuMessageFn(filename, fileStrings) {
174174
if (filename.startsWith('file://')) filename = url.fileURLToPath(filename);
175175

176+
// In the common case, `filename` is an absolute path that needs to be transformed
177+
// to be relative to LH_ROOT. In other cases, `filename` might be the exact i18n identifier
178+
// already (see: stack-packs.js, or bundled lighthouse).
179+
if (path.isAbsolute(filename)) filename = path.relative(LH_ROOT, filename);
180+
176181
/**
177182
* Combined so fn can access both caller's strings and i18n.UIStrings shared across LH.
178183
* @type {Record<string, string>}
@@ -189,8 +194,12 @@ function createIcuMessageFn(filename, fileStrings) {
189194
const keyname = Object.keys(mergedStrings).find(key => mergedStrings[key] === message);
190195
if (!keyname) throw new Error(`Could not locate: ${message}`);
191196

192-
const filenameToLookup = keyname in fileStrings ? filename : getModulePath(import.meta);
193-
const unixStyleFilename = path.relative(LH_ROOT, filenameToLookup).replace(/\\/g, '/');
197+
// `message` can be a UIString defined within the provided `fileStrings`, or it could be
198+
// one of the common strings found in `i18n.UIStrings`.
199+
const filenameToLookup = keyname in fileStrings ?
200+
filename :
201+
path.relative(LH_ROOT, getModulePath(import.meta));
202+
const unixStyleFilename = filenameToLookup.replace(/\\/g, '/');
194203
const i18nId = `${unixStyleFilename} | ${keyname}`;
195204

196205
return {

shared/test/localization/format-test.js

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as constants from '../../../core/config/constants.js';
1111
import * as format from '../../localization/format.js';
1212
import {locales} from '../../localization/locales.js';
1313
import {getModuleDirectory, getModulePath} from '../../../esm-utils.js';
14+
import {LH_ROOT} from '../../../root.js';
1415

1516
const moduleDir = getModuleDirectory(import.meta);
1617
const modulePath = getModulePath(import.meta);
@@ -185,37 +186,41 @@ describe('format', () => {
185186
expect(formattedStr).toEqual('en-XZ cuerda!');
186187
});
187188

188-
it('overwrites existing locale strings', async () => {
189-
const filename = 'core/audits/is-on-https.js';
190-
const {UIStrings} = await import('../../../core/audits/is-on-https.js');
191-
const str_ = i18n.createIcuMessageFn(filename, UIStrings);
192-
193-
// To start with, we get back the intended string..
194-
const origTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
195-
expect(origTitle).toEqual('Usa HTTPS');
196-
const origFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
197-
expect(origFailureTitle).toEqual('No usa HTTPS');
198-
199-
// Now we declare and register the new string...
200-
const localeData = {
201-
'core/audits/is-on-https.js | title': {
202-
'message': 'new string for es-419 uses https!',
203-
},
204-
};
205-
format.registerLocaleData('es-419', localeData);
206-
207-
// And confirm that's what is returned
208-
const newTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
209-
expect(newTitle).toEqual('new string for es-419 uses https!');
210-
211-
// Meanwhile another string that wasn't set in registerLocaleData just falls back to english
212-
const newFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
213-
expect(newFailureTitle).toEqual('Does not use HTTPS');
214-
215-
// Restore overwritten strings to avoid messing with other tests
216-
locales['es-419'] = clonedLocales['es-419'];
217-
const title = format.getFormatted(str_(UIStrings.title), 'es-419');
218-
expect(title).toEqual('Usa HTTPS');
189+
[
190+
{label: 'relative path', filename: 'core/audits/is-on-https.js'},
191+
{label: 'absolute path', filename: `${LH_ROOT}/core/audits/is-on-https.js`},
192+
].forEach(({label, filename}) => {
193+
it(`overwrites existing locale strings: ${label}`, async () => {
194+
const {UIStrings} = await import('../../../core/audits/is-on-https.js');
195+
const str_ = i18n.createIcuMessageFn(filename, UIStrings);
196+
197+
// To start with, we get back the intended string..
198+
const origTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
199+
expect(origTitle).toEqual('Usa HTTPS');
200+
const origFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
201+
expect(origFailureTitle).toEqual('No usa HTTPS');
202+
203+
// Now we declare and register the new string...
204+
const localeData = {
205+
'core/audits/is-on-https.js | title': {
206+
'message': 'new string for es-419 uses https!',
207+
},
208+
};
209+
format.registerLocaleData('es-419', localeData);
210+
211+
// And confirm that's what is returned
212+
const newTitle = format.getFormatted(str_(UIStrings.title), 'es-419');
213+
expect(newTitle).toEqual('new string for es-419 uses https!');
214+
215+
// Meanwhile another string that wasn't set in registerLocaleData just falls back to english
216+
const newFailureTitle = format.getFormatted(str_(UIStrings.failureTitle), 'es-419');
217+
expect(newFailureTitle).toEqual('Does not use HTTPS');
218+
219+
// Restore overwritten strings to avoid messing with other tests
220+
locales['es-419'] = clonedLocales['es-419'];
221+
const title = format.getFormatted(str_(UIStrings.title), 'es-419');
222+
expect(title).toEqual('Usa HTTPS');
223+
});
219224
});
220225
});
221226

0 commit comments

Comments
 (0)