-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
core(i18n): initial utility library #5644
Conversation
} delayed first paint by {timeInMs, number, milliseconds} ms` | ||
}; | ||
|
||
const i18n = i18nUtils.createStringFormatter(__filename, UI_STRINGS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a formatting function that has context bundled in explicitly via __filename
and UI_STRINGS
{key: 'url', valueType: 'url', label: 'URL'}, | ||
{key: 'totalBytes', valueType: 'bytes', label: 'Size (KB)'}, | ||
{key: 'wastedMs', valueType: 'timespanMs', label: 'Download Time (ms)'}, | ||
{key: 'url', valueType: 'url', label: i18n(i18nUtils.UI_STRINGS.columnURL)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
global shared UI_STRINGS can be used too
lighthouse-core/index.js
Outdated
@@ -34,6 +35,7 @@ const Config = require('./config/config'); | |||
async function lighthouse(url, flags, configJSON) { | |||
// TODO(bckenny): figure out Flags types. | |||
flags = flags || /** @type {LH.Flags} */ ({}); | |||
i18n.setLocale(flags.locale || 'en-US'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all it takes to swap locales at runtime is a single call here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can use something like this https://github.com/brandonhorst/javascript-environment-lang/blob/master/index.js before we fall back to english. (well i suppose it wouldn't ever fall back)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh yeah this was me being lazy for WIP, I'd have setLocale
just use the default locale determined by MessageFormat.defaultLocale
when this is undefined
lighthouse-core/lib/i18n.js
Outdated
if (!keyname) throw new Error(`Could not locate: ${msg}`); | ||
|
||
const parsed = MessageParser.parse(msg); | ||
parsed.elements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by forcing the use of our own format function first, we can do things like preserve our fancy rounding logic for milliseconds, automatically convert bytes to KB, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg. i like it. let's split into its own fn
'lighthouse-extension', | ||
] | ||
|
||
function collectAllStringsInDir(dir) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simple script recurses the directories looking for strings
fs.writeFileSync(path.join(LH_ROOT, 'lighthouse-core/lib/locales/en-US.js'), 'module.exports = ' + JSON.stringify(strings, null, 2) + ';') | ||
|
||
const gibberish = {} | ||
for (const [key, value] of Object.entries(strings)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's easy to generate other locales
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this feels great so far
const fs = require('fs') | ||
const path = require('path') | ||
|
||
const LH_ROOT = path.join(__dirname, '../../') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets put this file in scripts/i18n/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lighthouse-core/index.js
Outdated
@@ -34,6 +35,7 @@ const Config = require('./config/config'); | |||
async function lighthouse(url, flags, configJSON) { | |||
// TODO(bckenny): figure out Flags types. | |||
flags = flags || /** @type {LH.Flags} */ ({}); | |||
i18n.setLocale(flags.locale || 'en-US'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can use something like this https://github.com/brandonhorst/javascript-environment-lang/blob/master/index.js before we fall back to english. (well i suppose it wouldn't ever fall back)
|
||
fs.writeFileSync(path.join(LH_ROOT, 'lighthouse-core/lib/locales/en-US.js'), 'module.exports = ' + JSON.stringify(strings, null, 2) + ';') | ||
|
||
const gibberish = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap into a createPsuedoLocaleStrings
fn (or 'gibberish')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
collectAllStringsInDir(path.join(LH_ROOT, 'lighthouse-core')) | ||
console.log('Done!') | ||
|
||
fs.writeFileSync(path.join(LH_ROOT, 'lighthouse-core/lib/locales/en-US.js'), 'module.exports = ' + JSON.stringify(strings, null, 2) + ';') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template literal for this multiline thang
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
const mod = require(fullPath) | ||
if (!mod.UI_STRINGS) continue | ||
for (const [key, value] of Object.entries(mod.UI_STRINGS)) { | ||
strings[`${relativePath}!#${key}`] = value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdyt about just adopting the basic chrome i18n format here:
"relativePathAndKey": {
"message": "stringValue",
// "description": "..."
},
Like.. we could translate again into that format, but perhaps its nicer to just start with it here.
also it looks like git diff doesnt handle small changes in these strings files well. considers the WHOLE block to be a change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah sg, done
lighthouse-core/lib/i18n.js
Outdated
* @param {string} filename | ||
* @param {Record<string, string>} localStrings | ||
*/ | ||
createStringFormatter(filename, localStrings) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
localStrings => fileStrings ? or moduleStrings
local had me thinking local to the i18n file for a sec..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally 👍 done
lighthouse-core/lib/i18n.js
Outdated
createStringFormatter(filename, localStrings) { | ||
const mergedStrings = {...UI_STRINGS, ...localStrings}; | ||
/** @param {string} msg @param {*} [values] */ | ||
const format = (msg, values) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatFn (or w/e) to differentiate from all the other 'format's in this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lighthouse-core/lib/i18n.js
Outdated
const lookupKey = path.relative(LH_ROOT, filenameToLookup) + '!#' + keyname; | ||
const localeStrings = LOCALES[locale] || {}; | ||
const localeForMessageFormat = locale === 'gibberish' ? 'de-DE' : locale; | ||
const formatter = new MessageFormat(localeStrings[lookupKey] || msg, localeForMessageFormat, formats); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we prefer to pull it from the locale file, but if its not present we fall back to the msg passed in? can you comment why we want that fallback behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totes
lighthouse-core/lib/i18n.js
Outdated
const filenameToLookup = keyname in UI_STRINGS ? __filename : filename; | ||
const lookupKey = path.relative(LH_ROOT, filenameToLookup) + '!#' + keyname; | ||
const localeStrings = LOCALES[locale] || {}; | ||
const localeForMessageFormat = locale === 'gibberish' ? 'de-DE' : locale; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
g3 uses "en-XA" for psuedo locales. (and also en-XB and en-XC)
"en-XA", # Accented English
i'm okay doing that replacement here, though we could just switch to 'en-XA' everywhere now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect, sg 👍
lighthouse-core/lib/i18n.js
Outdated
if (!keyname) throw new Error(`Could not locate: ${msg}`); | ||
|
||
const parsed = MessageParser.parse(msg); | ||
parsed.elements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg. i like it. let's split into its own fn
lighthouse-core/lib/i18n.js
Outdated
.forEach(el => (values[el.id] = values[el.id] / 1024)); | ||
|
||
const filenameToLookup = keyname in UI_STRINGS ? __filename : filename; | ||
const lookupKey = path.relative(LH_ROOT, filenameToLookup) + '!#' + keyname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw is !#
a convention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not that I know of, just seemed like some good characters that won't appear in a valid path and look good for separating things :)
@@ -1940,7 +1937,7 @@ | |||
{ | |||
"key": "wastedMs", | |||
"valueType": "timespanMs", | |||
"label": "Download Time (ms)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And so the string consolidation begins :)
@@ -39,6 +39,7 @@ const defaultSettings = { | |||
|
|||
// the following settings have no defaults but we still want ensure that `key in settings` | |||
// in config will work in a typechecked way | |||
locale: null, // default determined by the intl library |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I'm not 100% sure about this anymore. It might make more sense to default to en-US
and then explicitly warn when folks try to change to a locale we don't have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@paulirish WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need to block this PR on this decision IMO.
but we should be sniffing the user's locale somewhere, right? in both the CLI and browser case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah sg we can punt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah sg we can punt
Shall we land this so we can start expanding our coverage? |
{key: 'url', valueType: 'url', label: 'URL'}, | ||
{key: 'totalBytes', valueType: 'bytes', label: 'Size (KB)'}, | ||
{key: 'wastedMs', valueType: 'timespanMs', label: 'Download Time (ms)'}, | ||
{key: 'url', valueType: 'url', label: i18n(i18nUtils.UIStrings.columnURL)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming-wise, how about: uiS(i18n.UIStrings.columnURL)
bikeshedding wise, "i18n" feels most like a namespace so i hesitate to use it for a method name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chatted outside and went with str_
for simplicity and easy future refactoring :)
let's! |
just need an approval ;) |
|
||
/** | ||
* @param {string} msg | ||
* @param {Record<string, *>} values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*
could be more specific? (just numbers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess only numbers touched inside this function, but could have other types passed in (just not for ms or bytes), so that wouldn't work. We could probably get more clever here, though, since only a subset of them are going to support being preprocessed. Otherwise we lose all checking on these
createStringFormatter(filename, fileStrings) { | ||
const mergedStrings = {...UIStrings, ...fileStrings}; | ||
|
||
/** @param {string} msg @param {*} [values] */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
const localeString = localeStrings[lookupKey] && localeStrings[lookupKey].message; | ||
// fallback to the original english message if we couldn't find a message in the specified locale | ||
// better to have an english message than no message at all, in some number cases it won't even matter | ||
const messageForMessageFormat = localeString || msg; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we differentiate between cases where there is no particular string for that locale vs there are no strings at all for that locale? If a particular string is missing in a locale should that be an error?
/** | ||
* @param {LH.Locale|null} [newLocale] | ||
*/ | ||
setLocale(newLocale) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
singletons :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I guess the only way to avoid is all translations could be done in the style of #5655 and just use the default locale until report generation time)
if (!newLocale) return; | ||
locale = newLocale; | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need tests for this :)
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. | ||
*/ | ||
// @ts-nocheck |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
problem here?
a slightly different take on the dynamic approach we ditched earlier.
I wanted to pitch this one more time because I ran into 2 issues with the static approach that felt 😝
also @paulirish likes this one more :)