-
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
i18n: introduce script to swap in new locale to LHR #8755
Conversation
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.
@@ -0,0 +1,28 @@ | |||
/** | |||
* @license Copyright 2018 Google Inc. All Rights Reserved. |
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.
2019 :)
Object.entries(icuMessagePaths).forEach(([icuMessageId, messageInstancesInLHR]) => { | ||
for (const instance of messageInstancesInLHR) { | ||
// The path that _formatPathAsString() generated | ||
const path = /** @type {LH.I18NMessageValuesEntry} */ (instance).path || /** @type {string} */ (instance); |
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 typeof
check this instead of the type assertions?
for (const instance of messageInstancesInLHR) { | ||
// The path that _formatPathAsString() generated | ||
const path = /** @type {LH.I18NMessageValuesEntry} */ (instance).path || /** @type {string} */ (instance); | ||
const values = /** @type {LH.I18NMessageValuesEntry} */ (instance).values || undefined; |
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.
probably worth commenting that values
are the string template values we need to replace
lighthouse-core/lib/i18n/i18n.js
Outdated
const icuMessageIdRegex = /(.* \| .*)$/; | ||
if (!icuMessageIdRegex.test(icuMessageId)) throw new Error('This is not an ICU message ID'); | ||
|
||
const {formattedString} = _formatIcuMessage(locale, icuMessageId, '', 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.
empty fallback message concerns me a bit, maybe we should have _formatIcuMessage
throw if we can't find the icuMessageId and the fallback message is undefined? Maybe we should use a placeholder string that's non-empty?
not sure what to do there 🤷♂
const i18n = require('./i18n.js'); | ||
|
||
/** | ||
* Replaces all strings within an LHR with ones from a different 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.
I wonder if this might be worth being the place where we explain how the icuMessagePaths
property is setup and meant to be used?
|
||
/* eslint-disable no-console, max-len */ | ||
|
||
const _set = require('lodash.set'); |
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.
😍
// when using accented english, force the use of a different locale for number formatting | ||
const localeForMessageFormat = locale === 'en-XA' ? 'de-DE' : locale; | ||
// pre-process values for the message format like KB and milliseconds | ||
const valuesForMessageFormat = _preprocessMessageValues(icuMessage, values); | ||
const valuesForMessageFormat = _preprocessMessageValues(messageForMessageFormat, 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.
@patrickhulce note this change.. it was using the fallback message instead of the one pulled from the locales files. which seemed odd. 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.
it was using the fallback message instead of the one pulled from the locales files. which seemed odd. right?
It is weird. As long as there isn't an old mismatched translation for the locale it shouldn't matter, but agreed that checking that the values
will actually be able to go into the string we want them to (and preparing them to do so) is the right thing to do.
Mismatched translations could become a problem at some point. If we've updated a string in en-US.json
and it has different values
than the not-yet-updated strings in all the other locales, I'm pretty sure that will either throw in _preprocessMessageValues
or below in the formatter.
Maybe we should have a check in string collection that deletes strings in other locales if the expected values
don't match anymore.
fwiw if you check out https://lighthouse-git-arabic.googlechrome.now.sh/ you can see where i'm going with this. |
we should add this:
either just for these now deployments, or directly in the 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.
it does seem like we have a missing abstraction that could unify both this and the regular translation process nicely. Basically to be able to pass icuMessagePaths
to a method that populates _icuMessageInstanceMap
from it, then run i18n.replaceIcuMessageInstanceIds()
on the LHR exactly as we do in Runner
.
But this is self contained enough (and refactorable if we ever feel compelled to do so) it seems fine to move forward with
} else { | ||
path = /** @type {LH.I18NMessageValuesEntry} */ (instance).path; | ||
// `values` are the string template values to be used. eg. `values: {wastedBytes: 9028}` | ||
values = /** @type {LH.I18NMessageValuesEntry} */ (instance).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.
don't need these casts anymore
|
||
/* eslint-env jest */ | ||
|
||
describe('swap-locale', () => { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
// when using accented english, force the use of a different locale for number formatting | ||
const localeForMessageFormat = locale === 'en-XA' ? 'de-DE' : locale; | ||
// pre-process values for the message format like KB and milliseconds | ||
const valuesForMessageFormat = _preprocessMessageValues(icuMessage, values); | ||
const valuesForMessageFormat = _preprocessMessageValues(messageForMessageFormat, 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.
it was using the fallback message instead of the one pulled from the locales files. which seemed odd. right?
It is weird. As long as there isn't an old mismatched translation for the locale it shouldn't matter, but agreed that checking that the values
will actually be able to go into the string we want them to (and preparing them to do so) is the right thing to do.
Mismatched translations could become a problem at some point. If we've updated a string in en-US.json
and it has different values
than the not-yet-updated strings in all the other locales, I'm pretty sure that will either throw in _preprocessMessageValues
or below in the formatter.
Maybe we should have a check in string collection that deletes strings in other locales if the expected values
don't match anymore.
const formattedStr = i18n.formatMessageFromIdWithValues(locale, icuMessageId, values); | ||
// Write string back into the LHR | ||
_set(lhr, path, formattedStr); | ||
} catch (e) {} |
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.
unconditional catch here seems excessive since all problems would be silenced. Some wouldn't be interesting (e.g. string from removed audit isn't available in new locale), but some could be a real problem. Maybe at least log the issue?
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 at least log the issue?
or log the ones that aren't 'No ICU message string to format'
if that gets excessive
lighthouse-core/lib/i18n/i18n.js
Outdated
* @param {*} [values] | ||
* @return {string} | ||
*/ | ||
function formatMessageFromIdWithValues(locale, icuMessageId, 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.
what about
function formatMessageFromIdWithValues(locale, icuMessageId, values) { | |
function getFormattedFromIdAndValues(locale, icuMessageId, values) { |
to match getFormatted
(param order for getFormatted
vs _formatIcuMessage
is unfortunate but whatever)
// Renderer formatted strings | ||
expect(lhrEn.i18n.rendererFormattedStrings.notApplicableAuditsGroupTitle).toEqual('Not applicable'); | ||
expect(lhrEs.i18n.rendererFormattedStrings.notApplicableAuditsGroupTitle).toEqual('No aplicable'); | ||
}); |
This comment was marked as outdated.
This comment was marked as outdated.
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.
looks great to me!
* which will be used in the replacement within `i18n._formatIcuMessage()` | ||
* | ||
* An example: | ||
* "icuMessagePaths": { |
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.
nit: unindent this 2 spaces so it's easy to tell its nested? :)
Object.entries(icuMessagePaths).forEach(([icuMessageId, messageInstancesInLHR]) => { | ||
for (const instance of messageInstancesInLHR) { | ||
// The path that _formatPathAsString() generated | ||
let path; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
/* eslint-disable max-len */ | ||
// Renderer formatted strings | ||
expect(lhrEn.i18n.rendererFormattedStrings.notApplicableAuditsGroupTitle).toEqual('Not applicable'); | ||
expect(lhrEs.i18n.rendererFormattedStrings.notApplicableAuditsGroupTitle).toEqual('No aplicable'); |
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 didn't even notice this was translated 😆 is there a better example?
types/lhr.d.ts
Outdated
@@ -8,7 +8,8 @@ import LHError = require('../lighthouse-core/lib/lh-error.js'); | |||
|
|||
declare global { | |||
module LH { | |||
export type I18NMessageEntry = string | {path: string, values: any}; | |||
export type I18NMessageValuesEntry = {path: string, values: any}; |
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 we should type values as Record<string, string | number>
?
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
Co-Authored-By: Brendan Kenny <bckenny@gmail.com>
Co-Authored-By: Brendan Kenny <bckenny@gmail.com>
ready for another look |
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.
looks pretty good to me! just a few primetime cleanup nits
*/ | ||
'use strict'; | ||
|
||
/* eslint-disable no-console, max-len */ |
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.
probably shouldn't leave these on if this is for real now :)
maybe we have a script version that does the console.log
ing?
|
||
// via Spanish | ||
const lhrEnEsRT = swapLocale(swapLocale(lhrEn, 'es'), 'en-US'); | ||
expect(lhrEnEsRT).toMatchObject(lhrEn); |
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 toEqual
, right? or do things get deleted
}); | ||
|
||
if (missingIcuMessageIds.length) { | ||
console.error(`No message in locale (${locale}) found for:\n`, missingIcuMessageIds); |
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 these get returned as warnings
if we split the logging into a script?
// Write string back into the LHR | ||
_set(lhr, path, formattedStr); | ||
} catch (err) { | ||
if (err.message === 'No ICU message string to format') { |
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 like it will get out of date and we won't notice :)
any way you can think of to make this a little more foolproof? export the string in i18n maybe? a flag on the error? 🤷♂
beforeEach(() => { | ||
// silence console.error spam about messages not found | ||
// eslint-disable-next-line no-console | ||
console.error = jest.fn(); |
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.
hopefully won't need this one :)
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 make the CI happy!
const _set = require('lodash.set'); | ||
|
||
const i18n = require('./i18n.js'); | ||
const LHError = require('../lh-error.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.
Appveyor is mad about this unused import.
A Googler has manually verified that the CLAs look good. (Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.) ℹ️ Googlers: Go here for more info. |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
A Googler has manually verified that the CLAs look good. (Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.) ℹ️ Googlers: Go here for more info. |
icuMessagePaths
has been sitting there waiting for us for a while! Finally we can put it to use.It's written as isomorphic code (lol), assuming we may want to do this clientside or whatever... But the initial motivation was to generate reports in all locales in our Now deployments. That PR will followup this one.