-
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
misc(treemap): i18n #12441
misc(treemap): i18n #12441
Conversation
/** Text for an option in a dropdown menu, when selected the current view of the app is set to all scripts. */ | ||
treemapAllScripts: 'All Scripts', | ||
/** Label for a column where the values are URLs, JS module names, or arbitrary identifiers. For simplicity, just 'name' is used. */ | ||
treemapName: '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.
Name
or URL / Module
?
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.
which column is this in treemap app exactly? it's been awhile since I've used it should we make it part of the vercel deployments on PRs?
Identifier
perhaps?
lighthouse-treemap/app/src/main.js
Outdated
@@ -377,7 +391,7 @@ class TreemapViewer { | |||
if (!dataRow.unusedBytes) return ''; | |||
|
|||
const percent = Math.floor(100 * dataRow.unusedBytes / dataRow.resourceBytes); | |||
return `${percent}% bytes unused`; | |||
return `${Util.i18n.formatNumber(percent)}% bytes unused`; |
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 don't think we have a good way to handle this; thoughts? It's just a tooltip...
should we do the "nearly correct" thing and just string concat this formatted number w/ a Util.i18n.bytesUnused ?
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.
oof, um what about filling with a prefilled value and replacing in the report?
e.g. ask translators to do {percentUnused}% bytes unused
and for rendererFormattedStrings
we prefill percentUnused
to be 123456789
or some specific predetermined number?
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.
but how would we do the value replacement at runtime? can we do something like
const data = replaceIcuMessages({string: str_(myStringWithPlaceholders, {value: 123}})
// get string from data ....
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 IDK, but either way I think we can punt for now :)
Maybe we start an issue with i18n frontend use cases? This happens in the report to some extent too we just chopped off the explanations to make them less helpful.
lighthouse-treemap/app/src/main.js
Outdated
@@ -458,10 +478,11 @@ class TreemapViewer { | |||
]; | |||
|
|||
if (bytes !== undefined && total !== undefined) { | |||
let str = `${TreemapUtil.formatBytes(bytes)} (${Math.round(bytes / total * 100)}%)`; | |||
const percentStr = `${Util.i18n.formatNumber(Math.round(bytes / total * 100))}%`; |
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.
is concating % here OK?
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'm not sure, good question, but there's a lot of concat going on here with the partitionByStr:
and then parts.join
too so I'm not sure that's even our biggest issue. Since we're best effort on this already, seems fine file an issue to solve this report use 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.
for plain % (not concatenating to another string), there's new Intl.NumberFormat(locale, {style: 'percent'}).format(value)
. You have to be careful with report formatting stuff because Safari formatting support has really lagged the other browsers (and Node), but 'percent'
has been supported for a while 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.
but then this value isn't just ##%, it's put into let str = `${TreemapUtil.formatBytes(bytes)} (${percentStr})`;
, so agree with the 🤷 :)
Can you talk a bit about the downsides of this approach? 8 pretty short strings seems not so bad. It seems like the hardest part would be replicating
|
Rough estimate: at 100 bytes per string per locale, and 8 strings, that is 50 * 800 = 40 kB. Currently the bundle is ~200 kB (on disk). Is that acceptable? As for implementation, I suppose it wouldn't be too bad. Instead of const i18n = new I18n(report.configSettings.locale, {
// Set missing renderer strings to default (english) values.
...Util.UIStrings,
...report.i18n.rendererFormattedStrings,
}); we just do
where |
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.
Big picture wise, it feels really weird to have another app's strings just hanging out in the LHR on the chance that someone has source maps and then wants to open them up in the treemap viewer.
- an extra round trip to get strings based off the LHR/useragent before we can render the app
This really doesn't sound that bad to me. This would be a super small payload, could be loaded speculatively first based on user's locale (possibly even better than LHR's locale?), and on a warm connection even on moderate 3G get these in 300ms.
I'm not expecting treemap viewer to be the best mobile experience anyway (I mean the LH mobile report experience is already awful, the metrics get cutoff so you can't even see the most important values)
Was there an underlying issue that this approach was more difficult to accomplish or more work? I wouldn't let this extra round trip stop separation of these strings for treemap :)
/** Text for an option in a dropdown menu, when selected the current view of the app is set to all scripts. */ | ||
treemapAllScripts: 'All Scripts', | ||
/** Label for a column where the values are URLs, JS module names, or arbitrary identifiers. For simplicity, just 'name' is used. */ | ||
treemapName: '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.
which column is this in treemap app exactly? it's been awhile since I've used it should we make it part of the vercel deployments on PRs?
Identifier
perhaps?
treemapName: 'Name', | ||
/** Label for a value associated with how many bytes a URL/file is, on-disk. */ | ||
treemapResourceBytes: 'Resource Bytes', | ||
/** Label for a value associated with how many bytes a URL/file is, over-network. */ |
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 one doesn't seem right
/** Label for a value associated with how many bytes a URL/file is, over-network. */ | |
/** Label for a value stating how many bytes of a URL/file were not used. */ |
maybe?
treemapAllScripts: 'All Scripts', | ||
/** Label for a column where the values are URLs, JS module names, or arbitrary identifiers. For simplicity, just 'name' is used. */ | ||
treemapName: 'Name', | ||
/** Label for a value associated with how many bytes a URL/file is, on-disk. */ |
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'm not sure I understand the on disk distinction. Just saying it's the uncompressed size?
@@ -636,7 +636,7 @@ if (require.main === module) { | |||
|
|||
if (collisions > 0) { | |||
console.log(`MEANING COLLISION: ${collisions} string(s) have the same content.`); | |||
assert.equal(collisions, 30, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings.join('\n')}`); | |||
assert.equal(collisions, 32, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings.join('\n')}`); |
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.
name, and....?
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
lighthouse-treemap/app/src/main.js
Outdated
@@ -377,7 +391,7 @@ class TreemapViewer { | |||
if (!dataRow.unusedBytes) return ''; | |||
|
|||
const percent = Math.floor(100 * dataRow.unusedBytes / dataRow.resourceBytes); | |||
return `${percent}% bytes unused`; | |||
return `${Util.i18n.formatNumber(percent)}% bytes unused`; |
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.
oof, um what about filling with a prefilled value and replacing in the report?
e.g. ask translators to do {percentUnused}% bytes unused
and for rendererFormattedStrings
we prefill percentUnused
to be 123456789
or some specific predetermined number?
lighthouse-treemap/app/src/main.js
Outdated
@@ -458,10 +478,11 @@ class TreemapViewer { | |||
]; | |||
|
|||
if (bytes !== undefined && total !== undefined) { | |||
let str = `${TreemapUtil.formatBytes(bytes)} (${Math.round(bytes / total * 100)}%)`; | |||
const percentStr = `${Util.i18n.formatNumber(Math.round(bytes / total * 100))}%`; |
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'm not sure, good question, but there's a lot of concat going on here with the partitionByStr:
and then parts.join
too so I'm not sure that's even our biggest issue. Since we're best effort on this already, seems fine file an issue to solve this report use case? 🤷
lighthouse-treemap/app/src/main.js
Outdated
...report.i18n.rendererFormattedStrings, | ||
}); | ||
Util.i18n = i18n; | ||
Util.reportJson = report; |
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.
why do we need this? I thought this was just for the performance category renderer
|
@@ -636,7 +636,7 @@ if (require.main === module) { | |||
|
|||
if (collisions > 0) { | |||
console.log(`MEANING COLLISION: ${collisions} string(s) have the same content.`); | |||
assert.equal(collisions, 30, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings.join('\n')}`); | |||
assert.equal(collisions, 32, `The number of duplicate strings have changed, update this assertion if that is expected, or reword strings. Collisions: ${collisionStrings.join('\n')}`); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
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.
Ik probeer het. Maar kost me veel tijd
waiting for feedback on #12441 (comment) @patrickhulce I believe I commented this at the exact time you submitted your review, so likely you missed it. |
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.
LGTM, just the minor concern about the collision check undefined
strangeness
build/build-treemap.js
Outdated
function buildLocales() { | ||
const locales = require('../lighthouse-core/lib/i18n/locales.js'); | ||
const clonedLocales = JSON.parse(JSON.stringify(locales)); | ||
|
||
for (const lhlMessages of Object.values(clonedLocales)) { | ||
for (const key of Object.keys(lhlMessages)) { | ||
if (!key.startsWith('lighthouse-treemap')) { | ||
delete lhlMessages[key]; | ||
} | ||
} | ||
} | ||
|
||
return 'const locales =' + JSON.stringify(clonedLocales, 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.
just for fun since we can use fromEntries
, what are your thoughts on the readability of the below compared to the delete
?
function buildLocales() { | |
const locales = require('../lighthouse-core/lib/i18n/locales.js'); | |
const clonedLocales = JSON.parse(JSON.stringify(locales)); | |
for (const lhlMessages of Object.values(clonedLocales)) { | |
for (const key of Object.keys(lhlMessages)) { | |
if (!key.startsWith('lighthouse-treemap')) { | |
delete lhlMessages[key]; | |
} | |
} | |
} | |
return 'const locales =' + JSON.stringify(clonedLocales, null, 2) + ';'; | |
} | |
function buildLocales() { | |
const locales = Object.entries(require('../lighthouse-core/lib/i18n/locales.js')); | |
const treemapLocales = locales.map(([locale, messageMap]) => { | |
const lhlMessages = Object.entries(messageMap); | |
const treemapMessages = lhlMessages.filter(([key]) => key.startsWith('lighthouse-treemap')); | |
return [locale, Object.fromEntries(treemapMessages)]; | |
}); | |
return 'const locales =' + JSON.stringify(Object.fromEntries(treemapLocales), null, 2) + ';'; | |
} |
(not serious review, just curious) :)
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.
because it's nested, i prefer the current approach
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.
haha, agree with @connorjclark on this one :)
build/build-treemap.js
Outdated
} | ||
} | ||
|
||
return 'const locales =' + JSON.stringify(clonedLocales, 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.
with something this specially injected, WDYT about the name being a little more in your face?
BUILD_INJECTED_LOCALES
/__buildInjectedLocales__
/sOmEtHiNgElSe
?
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 don't think the cloak and dagger is necessary here, since we control the code that runs here (and it's not a 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.
I'm mostly interested in improving the experience for someone reading locales
referenced without a definition and wondering where in the world it comes from. If you tried to grep locales
in our codebase you'd get like 160 results. With localesInjectedAtBuildTime
you'd get 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.
A comment then?
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.
self-describing variable names >> comments :)
but yes a comment would somewhat address my concern too.
@@ -121,7 +122,10 @@ class TreemapViewer { | |||
} | |||
|
|||
for (const [group, depthOneNodes] of Object.entries(this.depthOneNodesByGroup)) { | |||
makeOption({type: 'group', value: group}, `All ${group}`); | |||
const allLabel = { |
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.
what's the purpose of this object? are there about to be many types 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.
maybe, i don't know, I'm being careful 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.
is there something dangerous that could happen by using the string directly? ;)
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 have just one group
now (scripts).
there's also a small part of me thinking someone will send us their own treemap data, and their own groups. unlikely, but....
anyhow, the object will be needed regardless as soon as we have a second group (however that turns out–I think resource summary
will be a group? idk yet)
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.
aight, sg
lighthouse-treemap/app/src/main.js
Outdated
const i18n = new I18n(options.lhr.configSettings.locale, { | ||
// Set missing renderer strings to default (english) values. | ||
...TreemapUtil.UIStrings, | ||
...getStrings(locales[options.lhr.configSettings.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.
does this handle all that fallback business? I can't remember off the top of my head if the configSettings
locale is normalized to always match our defined locale (e.g. for user-provided de-CH
which matches our de
key, will this work or miss?)
EDIT: seems like we should be fine
settingsWithFlags.locale = locale; |
build/build-treemap.js
Outdated
@@ -8,6 +8,21 @@ | |||
const fs = require('fs'); | |||
const GhPagesApp = require('./gh-pages-app.js'); | |||
|
|||
function buildLocales() { |
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 jsdoc comment on this function would be appreciated so you don't have to know the structure of locales.js
and the files it requires and what the significance of key.startsWith('lighthouse-treemap')
is to know what this is doing to the locale files :)
build/build-treemap.js
Outdated
function buildLocales() { | ||
const locales = require('../lighthouse-core/lib/i18n/locales.js'); | ||
const clonedLocales = JSON.parse(JSON.stringify(locales)); | ||
|
||
for (const lhlMessages of Object.values(clonedLocales)) { | ||
for (const key of Object.keys(lhlMessages)) { | ||
if (!key.startsWith('lighthouse-treemap')) { | ||
delete lhlMessages[key]; | ||
} | ||
} | ||
} | ||
|
||
return 'const locales =' + JSON.stringify(clonedLocales, 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.
haha, agree with @connorjclark on this one :)
|
||
/** | ||
* @template T |
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.
parameterizing on strings
when none of the I18n
object uses strings except the getter for strings
works for this PR but makes me think it really shouldn't be in this object at all and should just be a global (or on Util
directly), but we can save that for a future report refactor PR :)
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.
Moving to Util/TreemapUtil SGTM, and so does worrying about it in a future refactor ;)
assert.ok(val in Util.UIStrings, `Invalid data-i18n value of: "${val}" not found.`); | ||
} | ||
|
||
// Do the same for the strings in treemap app. |
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 this move to a treemap test since report-ui-features
isn't involved?
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, FWIW, if it's just the single string, could just set it manually and avoid the loop here and in main.js
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's just so much BLEGH to do re: jsdom setup... altho I guess it could go into the puppeteer page?
if it's just the single string,
I think there will be more later (if we do the settings cog).
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's just so much BLEGH to do re: jsdom setup
normally true but I think it's all self contained in this case. Free test for you:
const assert = require('assert').strict;
const fs = require('fs');
const jsdom = require('jsdom');
describe('data-i18n', () => {
it('should have only valid data-i18n values in treemap html', () => {
const TreemapUtil = require('../../../../../lighthouse-treemap/app/src/util.js');
const TREEMAP_INDEX = fs.readFileSync(__dirname +
'/../../../../../lighthouse-treemap/app/index.html', 'utf8');
const dom = new jsdom.JSDOM(TREEMAP_INDEX);
for (const node of dom.window.document.querySelectorAll('[data-i18n]')) {
const val = node.getAttribute('data-i18n');
assert.ok(val in TreemapUtil.UIStrings, `Invalid data-i18n value of: "${val}" not found.`);
}
});
});
though the paths will have to be updated :)
lighthouse-treemap/app/src/util.js
Outdated
|
||
TreemapUtil.UIStrings = { | ||
/** Label for a button that alternates between showing or hiding a table. */ | ||
toggleTable: 'Toggle Table', |
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.
in the main report we tend to be more self documenting with these, e.g.toggleTableButtonLabel
for this. It seems like a good approach generally?
I guess I'm less concerned about 'toggleTable'
and more about things like 'all'
.
lighthouse-treemap/app/src/util.js
Outdated
/** Label for a value associated with how many bytes a URL/file is, over-network. */ | ||
unusedBytes: 'Unused Bytes', |
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.
did this description and string diverge?
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.
wait yeah, I had these exact same comments already, did those get those in the move?
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 strings moved from Util to TreemapUtil, so github prob hid ur comments
havent fixed the original comments yet :P
lighthouse-treemap/app/src/main.js
Outdated
const [filename, varName] = icuMessageId.split(' | '); | ||
if (!filename.endsWith('util.js')) throw new Error(`Unexpected message: ${icuMessageId}`); | ||
|
||
const key = /** @type {keyof LH.I18NRendererStrings} */ (varName); |
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.
definitely not LH.I18NRendererStrings
:)
Can chopping off the path be moved to the build step? If we don't use them anyways, it would certainly cut down that 40KiB to something much smaller :)
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.
good idea
Co-authored-by: Brendan Kenny <bckenny@gmail.com>
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.
still missing the straggler OG comments, but I'll approve :)
build/build-treemap.js
Outdated
for (const lhlMessages of Object.values(clonedLocales)) { | ||
for (const icuMessageId of Object.keys(lhlMessages)) { | ||
const lhlMessage = lhlMessages[icuMessageId]; | ||
delete lhlMessages[icuMessageId]; |
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.
ok, c'mon y'all now it's really not a clone 😆 still no construction love?
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.
lol ok i surrender
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 renamed this to strings
since it is different from the real locales
lighthouse-treemap/app/src/util.js
Outdated
tableColumnName: 'Name', | ||
/** Label for column giving the size of a file in bytes. */ | ||
resourceBytesLabel: 'Resource Bytes', | ||
/** Label for a value associated with how many bytes a URL/file is, over-network. */ |
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.
bump
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.
Still lgtm
Strings added viaConsidered an approach where the app would work out its own strings, but that would require 1) an extra round trip to get strings based off the LHR/useragent before we can render the app or 2) bundle strings for all languages into the bundle.rendererFormattedStrings
.UsingrendererFormattedStrings
also keeps things simpler.