Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n: add registerLocaleData() method #9638

Merged
merged 3 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lighthouse-core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ lighthouse.traceCategories = require('./gather/driver.js').traceCategories;
lighthouse.Audit = require('./audits/audit.js');
lighthouse.Gatherer = require('./gather/gatherers/gatherer.js');
lighthouse.NetworkRecords = require('./computed/network-records.js');
lighthouse.registerLocaleData = require('./lib/i18n/i18n.js').registerLocaleData;

module.exports = lighthouse;
14 changes: 14 additions & 0 deletions lighthouse-core/lib/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,19 @@ function replaceIcuMessageInstanceIds(inputObject, locale) {
return icuMessagePaths;
}

/** @typedef {import('./locales').LhlMessages} LhlMessages */

/**
* Populate the i18n string lookup dict with locale data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we link off to that design and/or mention how this is expected to be used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Populate the i18n string lookup dict with locale data
* Populate the i18n string lookup dict with locale data.

* Used when the host environment selects the locale and serves lighthouse the intended locale file
* @see https://docs.google.com/document/d/1jnt3BqKB-4q3AE94UWFA0Gqspx8Sd_jivlB7gQMlmfk/edit
Copy link
Member

@brendankenny brendankenny Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of this is seen by users of the LH module, though. Does that matter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whatcha mean? it's exposed at the module level..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's exposed at the module level..

The doc strings, I mean. They aren't in the module-level file and tools like vscode don't forward the jsdocs

* @param {LH.Locale} locale
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this threatens the little LH.Locale house of cards types. We should either limit to overriding existing locales (verify that locale here is actually of type LH.Locale) or get rid of LH.Locale and type all our locale names as string. There might be some side effects of the second option, though, if we depend on that behaving well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should either limit to overriding existing locales

in reality this will only be used to override existing locales

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in reality this will only be used to override existing locales

This is partly what #9638 (comment) is touching on. I don't think we can treat it as a private api

* @param {LhlMessages} lhlMessages
Copy link
Member

@brendankenny brendankenny Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly we should shape check this (a la plugins/budgets.json). All our good locale error handling code assumes the type is correct (and isn't such good error handling code if it isn't)

*/
function registerLocaleData(locale, lhlMessages) {
LOCALES[locale] = lhlMessages;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little afraid to make LOCALES a mutable singleton like this now, but it's hard to think through the consequences and if they matter. #9296 and #9043 (and wherever the field-performance plugin feedback is) point at the fact that people likely will use this outside of devtools, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little afraid to make LOCALES a mutable singleton like this now, but it's hard to think through the consequences and if they matter. #9296 and #9043 (and wherever the field-performance plugin feedback is) point at the fact that people likely will use this outside of devtools, though.

if we don't want to expose this to anyone but devtools (I believe @exterkamp also preferred not to do that), another option (sorry I didn't think of until right now) is something like
https://github.com/GoogleChrome/lighthouse/blob/b2dfb06221249d86dab667c241303b53c66203ed/clients/devtools-report-assets.js
basically rewriting locales.js to instead access the assets in the module. We could be clever instead of having to do it manually

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could be clever instead of having to do it manually

@connorjclark could even get to use a proxy to rewrite any getter on the locales.js object into a request for the resource :)

}

module.exports = {
_formatPathAsString,
_ICUMsgNotFoundMsg,
Expand All @@ -485,4 +498,5 @@ module.exports = {
replaceIcuMessageInstanceIds,
isIcuMessage,
collectAllCustomElementsFromICU,
registerLocaleData,
};
59 changes: 59 additions & 0 deletions lighthouse-core/test/lib/i18n/i18n-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ describe('i18n', () => {
});

describe('#getFormatted', () => {
it('returns the formatted string', () => {
const UIStrings = {testMessage: 'happy test'};
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
const formattedStr = i18n.getFormatted(str_(UIStrings.testMessage), 'en');
expect(formattedStr).toEqual('happy test');
});


it('returns the formatted string with replacements', () => {
const UIStrings = {testMessage: 'replacement test ({errorCode})'};
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
const formattedStr = i18n.getFormatted(str_(UIStrings.testMessage, {errorCode: 'BOO'}), 'en');
expect(formattedStr).toEqual('replacement test (BOO)');
});

it('throws an error for invalid locales', () => {
// Populate a string to try to localize to a bad locale.
const UIStrings = {testMessage: 'testy test'};
Expand All @@ -98,6 +113,50 @@ describe('i18n', () => {
});
});

describe('#registerLocaleData', () => {
it('installs new locale strings', () => {
const localeData = {
'lighthouse-core/test/lib/i18n/i18n-test.js | testString': {
'message': 'en-XZ cuerda!',
},
};
i18n.registerLocaleData('en-XZ', localeData);

const UIStrings = {testString: 'en-US string!'};
const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
const formattedStr = i18n.getFormatted(str_(UIStrings.testString), 'en-XZ');
expect(formattedStr).toEqual('en-XZ cuerda!');
});

it('overwrites existing locale strings', () => {
const filename = 'lighthouse-core/audits/is-on-https.js';
const UIStrings = require('../../../../lighthouse-core/audits/is-on-https.js').UIStrings;
const str_ = i18n.createMessageInstanceIdFn(filename, UIStrings);

// To start with, we get back the intended string..
const origTitle = i18n.getFormatted(str_(UIStrings.title), 'es-419');
expect(origTitle).toEqual('Usa HTTPS');
const origFailureTitle = i18n.getFormatted(str_(UIStrings.failureTitle), 'es-419');
expect(origFailureTitle).toEqual('No usa HTTPS');

// Now we declare and register the new string...
const localeData = {
'lighthouse-core/audits/is-on-https.js | title': {
'message': 'es-419 uses https!',
},
};
i18n.registerLocaleData('es-419', localeData);
Copy link
Member

@brendankenny brendankenny Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes es-419 for all tests run after this, FWIW. It's makes for a more fragile test, but should we reset after it's done?


// And confirm that's what is returned
const newTitle = i18n.getFormatted(str_(UIStrings.title), 'es-419');
expect(newTitle).toEqual('es-419 uses https!');

// Meanwhile another string that wasn't set in registerLocaleData just falls back to english
const newFailureTitle = i18n.getFormatted(str_(UIStrings.failureTitle), 'es-419');
expect(newFailureTitle).toEqual('Does not use HTTPS');
});
});

describe('Message values are properly formatted', () => {
// Message strings won't be in locale files, so will fall back to values given here.
const UIStrings = {
Expand Down