-
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): localize strings at end of run #5655
Conversation
lighthouse-core/lib/i18n.js
Outdated
keyUsages.push({key, template, values}); | ||
formattedStringUsages.set(key, keyUsages); | ||
|
||
return `${key}#${keyUsages.length - 1}`; |
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.
just a reference is returned here instead of the real string
lighthouse-core/lib/i18n.js
Outdated
const occurrences = templateLogRecord.occurrences || [] | ||
occurrences.push({values: usage.values, path: currentPath}) | ||
|
||
const {message, template} = formatTemplate(locale, templateKey, usage.template, usage.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.
we do the real formatting here in the specified locale
} | ||
] | ||
}, | ||
"lighthouse-core/lib/i18n.js!#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.
a log keeps track of all the strings we used
"values": { | ||
"timeInMs": 4927.278 | ||
}, | ||
"path": [ |
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.
as well as the path in the LHR of where you can find them to replace them
@@ -3416,5 +3416,144 @@ | |||
"title": "Crawling and Indexing", | |||
"description": "To appear in search results, crawlers need access to your app." | |||
} | |||
}, | |||
"localeLog": { |
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 will get big, but maybe it can be an optional thing?
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.
Yah +1 to this being an option that's off by default.
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 like where things are going here. :D
@@ -406,3 +414,7 @@ module.exports = { | |||
}, | |||
}, | |||
}; | |||
|
|||
Object.defineProperty(module.exports, 'UIStrings', { |
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.
can you add a comment on why we export it all fancy like this?
( i dont totally grok why.. :)
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.
sure :)
it's because config data gets cloned and validated and whatnot so we want a getter and not put the real strings on the actual object
@@ -3416,5 +3416,144 @@ | |||
"title": "Crawling and Indexing", | |||
"description": "To appear in search results, crawlers need access to your app." | |||
} | |||
}, | |||
"localeLog": { |
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.
Yah +1 to this being an option that's off by default.
} | ||
] | ||
}, | ||
"lighthouse-core/lib/i18n.js!#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.
how about JSONPath?
"lighthouse-core/lib/i18n.js!#columnURL": {
"occurrences": [
{
"path": [
"audits",
"render-blocking-resources",
"details",
"headings",
"0",
"label"
]
}
]
},
could become:
"lighthouse-core/lib/i18n.js!#columnURL": {
"occurrences": ["$.audits['render-blocking-resources'].details.headings[0].label"],
},
obviously it's more dense, but I think its a lot more readable.
(actually looks like lodash's get also supports this style, anyhow.)
We don't really need ALL of jsonpath, just the basics.
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.
do you want JSONPath or what lodash get supports? ;)
I'm on board for this 👍
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 should've said, "I'm on board for this, so long as we use someone else's getter code :)"
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! based on their docs, the string form with lodash get lgtm
3df449a
to
f555090
Compare
|
||
/* eslint-disable max-len */ | ||
const UIStrings = { |
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.
FWIW @brendankenny I think these are the only strings we really need for performance section, so it's relatively small
if you want to be noodling on a much better config solution before we start on the other categories, nows a good time :)
lighthouse-core/lib/i18n.js
Outdated
*/ | ||
|
||
/** @type {Map<string, StringUsage[]>} */ | ||
const formattedStringUsages = new Map(); |
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 the new singleton, we might want to put some disclaimers into README somewhere about YMMV using LH as a long-lived module not in a worker. CLI/DevTools/Extension all don't really care about these types of concerns so inevitably we'll make some trade-offs not great for node module users (example: #5683)
I think this is ready for ready for re-review. I'm going to throw one more thought out there as I was thinking about report strings. instead of lhr.localization = {
log: {...},
uiMessages: {},
} thoughts? |
typings/lhr.d.ts
Outdated
@@ -6,6 +6,12 @@ | |||
|
|||
declare global { | |||
module LH { | |||
export type LocaleLogEntry = string | {path: string, values: any}; | |||
|
|||
export interface LocaleLog { |
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 bikeshed plz? 😁
Doesn't feel like a log. More of a map
? or just locations
? how about.......
lhr.i18n.messageLocations
lhr.i18n.uiMessages
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.
how about
lhr.i18n.messages
I has saving uiMessages
for the strings that the renderer will use but are in the LHR anywhere otherwise
:)
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.
yup sg
lighthouse-core/lib/i18n.js
Outdated
} | ||
} | ||
|
||
return pathAsString; | ||
} | ||
|
||
module.exports = { |
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.
the export style is a little funky here. starts halfway and nests these other fns, but not formatPathAsString
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 was whatever is used inside the module gets defined outside everything else is inline, I'll just move it all out though
lighthouse-core/lib/i18n.js
Outdated
* @param {string} template | ||
* @param {*} [values] | ||
*/ | ||
function formatTemplate(locale, templateKey, template, 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.
function _formatTemplate
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
} | ||
|
||
/** @param {string[]} path */ | ||
function formatPathAsString(path) { |
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.
also with a _ prefix? (even though we export for testing)?
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 formatter = new MessageFormat(messageForMessageFormat, localeForMessageFormat, formats); | ||
return formatter.format(values); | ||
const unixStyleFilename = path.relative(LH_ROOT, filenameToLookup).replace(/\\/g, '/'); | ||
const key = unixStyleFilename + '!#' + 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.
thinking we should use stronger delimiters. How about |
.. e.g.
"lighthouse-core/audits/metrics/interactive.js | description"
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, done
lighthouse-core/lib/i18n.js
Outdated
const unixStyleFilename = path.relative(LH_ROOT, filenameToLookup).replace(/\\/g, '/'); | ||
const key = unixStyleFilename + '!#' + keyname; | ||
const keyUsages = formattedStringUsages.get(key) || []; | ||
keyUsages.push({key, template, 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.
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.
how did you get that? are you looking at the test output? the tests invoke FCP .meta
lots of times so that's probably what you're seeing
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.
yup it's the getter of meta, as you suspected. we'll have to dedupe.
lighthouse-core/lib/i18n.js
Outdated
return formatter.format(values); | ||
const unixStyleFilename = path.relative(LH_ROOT, filenameToLookup).replace(/\\/g, '/'); | ||
const key = unixStyleFilename + '!#' + keyname; | ||
const keyUsages = formattedStringUsages.get(key) || []; |
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.
(related to next comment, but..)
why are there multiple usages of a given string ID? Well I guess i'd expect it of the i18n ones, but not typically the strings held in the audits, right?
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 it'd be unexpected for more than 1 audit title to be used, but for strings that will be used for table cells it will be expected
lighthouse-core/lib/i18n.js
Outdated
keyUsages.push({key, template, values}); | ||
formattedStringUsages.set(key, keyUsages); | ||
|
||
return `${key}#${keyUsages.length - 1}`; |
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.
similar to previous one. giving this some space.. ${key} # {length - 1}
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 done
lighthouse-core/lib/i18n.js
Outdated
/** | ||
* @param {*} objectInLHR | ||
* @param {LH.LocaleLog} log | ||
* @param {string[]} path |
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.
only for testing?
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.
are you referring to why path is an array of strings in this function signature? it's so that we can easily push onto it and convert the name at each step rather than have two functions that create the full string from an array and one that builds incrementally
lighthouse-core/lib/i18n.js
Outdated
setLocale(newLocale) { | ||
if (!newLocale) return; | ||
locale = newLocale; | ||
replaceLocaleStringReferences(lhr, 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.
naming
okay we have:
string IDs?
string references?
object paths?
i'm also seeing: template key, key name, locale string reference, keyusages / usages.
can we make these names a bit more concrete and consistent?
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 this is a bit confusing, I'll do a pass to try to make them all consistent
alright @paulirish I think the names have been sorted out except this area https://github.com/GoogleChrome/lighthouse/pull/5655/files#diff-7e3be24bffdebd4c067d12d0596858f9R171 now that it's not log and we also have usages saved in our |
16b49ce
to
24b4e24
Compare
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.
New namessss
formatter 👍
uiStrings 👍
rename 'template'.
kill 'localeString'
rename message in _formatTemplate => formattedString
createStringFormatter -> createMessageInstanceIdFn
maybe 'usages' -> 'instances', but nobigdeal
icuMessage
icuMessageId
icuMessageInstanceId
formattedString
icuMessageInstanceIdMap. Map(<icuMessageID, [*]>)
lhr.i18n.icuMessagePaths
lhr.i18n.rendererFormattedStrings
typings/lhr.d.ts
Outdated
@@ -6,6 +6,12 @@ | |||
|
|||
declare global { | |||
module LH { | |||
export type LocaleLogEntry = string | {path: string, values: any}; | |||
|
|||
export interface LocaleLog { |
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.
yup sg
lighthouse-core/lib/i18n.js
Outdated
const unixStyleFilename = path.relative(LH_ROOT, filenameToLookup).replace(/\\/g, '/'); | ||
const key = unixStyleFilename + '!#' + keyname; | ||
const keyUsages = formattedStringUsages.get(key) || []; | ||
keyUsages.push({key, template, 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.
yup it's the getter of meta, as you suspected. we'll have to dedupe.
lighthouse-core/lib/i18n.js
Outdated
* @param {string} templateID | ||
* @param {string} template | ||
* @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.
can you add a @return
lighthouse-core/lib/i18n.js
Outdated
@@ -43,57 +41,150 @@ const formats = { | |||
|
|||
/** | |||
* @param {string} msg | |||
* @param {Record<string, *>} values | |||
* @param {Record<string, *>} [values] | |||
*/ | |||
function preprocessMessageValues(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.
_preprocessMessageValues
lighthouse-core/lib/i18n.js
Outdated
const currentPathInLHR = pathInLHR.concat([property]); | ||
|
||
// Check to see if the value in the LHR looks like a string reference. If it is, replace it. | ||
if (typeof value === 'string' && /.* \| .* # \d+$/.test(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.
wanna throw in the param groups that you have on 177 so these look more similar?
@brendankenny u want to take a pass? |
lighthouse-core/lib/i18n.js
Outdated
@@ -20,6 +19,8 @@ try { | |||
|
|||
// @ts-ignore | |||
const IntlPolyfill = require('intl'); | |||
if (!IntlPolyfill.NumberFormat) throw new Error('Invalid polyfill'); |
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 FYI this was my fix to what you discovered in #5702
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.
ah cool. nice.
The msg is a little weird though. Seems like this case is described as: "Polyfill is a no-op due to existing implementation". The fact that we exit via throwing is a little random. can we do a IIFE and return instead?
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 IFFE makes a lot more sense, done 👍
👍 |
An example of how we might be able to "fix" the strings before shipping the LHR, basically the i18n formatting function just returns a reference to the string to be used which is stored in a map, then at the end before returning, runner asks i18n to replace all the references in the LHR with the string of the appropriate locale. BOOM done! a log is kept of what replacements were made and added to the LHR as
localeLog
so that later the report could be translated again without re-running.