-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Added count param to phrase param and return plural form accordingly #48229
Added count param to phrase param and return plural form accordingly #48229
Conversation
src/languages/types.ts
Outdated
|
||
type TranslationBase = {[key: string]: TranslationBaseValue | TranslationBase}; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
type ArgumentType<T, R> = T extends (arg: infer A, ...args: any[]) => R ? A : unknown; |
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.
@ZhenjaHorbach Can you explain this please?
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 just helper type
Which I used for checking first parameter
And basically it is used to determine the type of the first parameter
And if the first parameter is not an object
We will get a TS error
@shubham1206agra Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
@shubham1206agra |
Assume this needs no design review? If so, let me know and I'll check it out :) |
Oh sorry 😅 But thanks ! |
@ZhenjaHorbach Have you enforced the single parameter condition in function in translation? Since I was able to add another parameter without any errors. |
@shubham1206agra |
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 are removing the possibility to call translate('bla', 'param', 'other')
. That's fine, but my fear is that other unmerged PRs could be using that format and we would not catch that, right?
Can you update the links in https://github.com/Expensify/App/#internationalization and add a small explanation of the pluralization feature?
src/libs/Localize/index.ts
Outdated
phraseKey: TKey, | ||
fallbackLanguage: 'en' | 'es' | null = null, | ||
...phraseParameters: PhraseParameters<Phrase<TKey>> | ||
path: TPath, |
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 I think I liked more phraseKey
then path
, NAB
src/libs/Localize/index.ts
Outdated
return translatedPhrase(...phraseParameters); | ||
/** | ||
* | ||
* is Plain object is for checking if the phraseTranslated output |
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 Plain object is
is this referring to a method or what?
phraseTranslated
I think you mean translatedPhrase
?
src/libs/Localize/index.ts
Outdated
/** | ||
* | ||
* is Plain object is for checking if the phraseTranslated output | ||
* is an object then further check if it include the count param or not |
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 an object then further check if it include the count param or not | |
* is an object then further check if it includes the count param or not |
src/libs/Localize/index.ts
Outdated
* | ||
* is Plain object is for checking if the phraseTranslated output | ||
* is an object then further check if it include the count param or not | ||
* OR before checking the plain object output, we can check if we have the count |
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 100% what exactly is the counterpart of this OR... the last if here, both last ifs?
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
Sorry
These were draft comments 😅
I agree that they should be updated
src/libs/Localize/index.ts
Outdated
} | ||
|
||
const phraseObject = parameters[0]; | ||
if (phraseObject && typeof phraseObject === 'object' && 'count' in phraseObject && typeof phraseObject.count === '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.
I don't think we need to check 'count' in phraseObject
since if count is not defined, typeof would return undefined and this code would work anyway
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 think we need some additional clarification to prevent Property 'count' does not exist on type 'object'.
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't we change the type of phraseObject
to allow for an optional count
?
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.
But I'm not sure that is a good idea update TranslationParameters type for count
Why?
Plus we use such a condition to write count key into phraseObject
Which we could remove if we added it as an optional param, no?
src/libs/Localize/index.ts
Outdated
if (phraseObject && typeof phraseObject === 'object' && 'count' in phraseObject && typeof phraseObject.count === 'number') { | ||
const pluralRule = new Intl.PluralRules(language).select(phraseObject.count); | ||
|
||
const pluralResult = pluralRule in translateResult && translateResult[pluralRule]; |
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 we need the pluralRule in translateResult
I think the code would work exactly the same without it, no?
src/libs/Localize/index.ts
Outdated
return translateResult.other(phraseObject.count); | ||
} | ||
|
||
throw new Error(`Invalid plural form for '${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.
Throw early (ie: invert this if condition and throw)
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 think it's ok !
Because this error symbolizes that all the previous conditions were not pass
As esle
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 all previous conditions? There's only one of them.
We prefer early throw/return in general, as it makes the code easier to read and follow
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.
Updated !
Oh, can you also pick some existing translation, for example these ones and change them to use the new pluralization methods? |
Actually a good question |
This will be done by me in #45892 |
Not sure I follow. Are you saying that if you call
Ah nice! |
Ah nice, thanks for checking and explaining! |
And I fixed other comments 😃 |
src/languages/en.ts
Outdated
deleteRates: ({count}: DistanceRateOperationsParams) => `Delete ${Str.pluralize('rate', 'rates', count)}`, | ||
deleteRates: () => ({ | ||
one: 'Delete rate', | ||
other: () => `Delete rates`, |
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 is this a function?
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.
Since it will be easy to implement, and we need to pass the count somehow.
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 same as the others, you could just have a string here 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.
Yes
I also thought about it 5 minutes ago 😅
I will merge changes soon
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.
Now it will work!
messages: () => ({ | ||
zero: 'No messages', | ||
one: 'One message', | ||
two: 'Two messages', | ||
few: (count) => `${count} messages`, | ||
many: (count) => `You have ${count} messages`, | ||
other: (count) => `You have ${count} unread messages`, | ||
}) | ||
|
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 crap, did not really notice this before... does the object containing the messages always need to be a function or can it also be:
messages: () => ({ | |
zero: 'No messages', | |
one: 'One message', | |
two: 'Two messages', | |
few: (count) => `${count} messages`, | |
many: (count) => `You have ${count} messages`, | |
other: (count) => `You have ${count} unread messages`, | |
}) | |
messages: { | |
zero: 'No messages', | |
one: 'One message', | |
two: 'Two messages', | |
few: (count) => `${count} messages`, | |
many: (count) => `You have ${count} messages`, | |
other: (count) => `You have ${count} unread messages`, | |
} | |
There's really no need for a function there...
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.
No, since it will be impossible to differentiate between messages
and messages.one
translation key in a flat object.
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 implementation looks like any other object with translations
And not a specific translation with plural forms
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.
No, since it will be impossible to differentiate between messages and messages.one translation key in a flat object.
I don't get this
This implementation looks like any other object with translations
And not a specific translation with plural forms
Yeah, so?
Anyway, it would be nice if we supported the simple object notation too, but not going to block on that since this is a big PR, we've been waiting a long time for it and adding the function is not that bad.
src/languages/es.ts
Outdated
deleteRates: ({count}: DistanceRateOperationsParams) => `Eliminar ${Str.pluralize('tasa', 'tasas', count)}`, | ||
deleteRates: () => ({ | ||
one: 'Eliminar tasa', | ||
other: () => `Eliminar tasas`, |
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 here
@iwiznia If everything is ok, could you approve it again and merge it? |
We are skipping ESLint checks because this PR is touching 76 unrelated files and fixing all the previously existing lint errors would make this PR riskier. Thanks so much for this, great job!!! |
@iwiznia looks like this was merged without a test passing. Please add a note explaining why this was done and remove the |
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
🚀 Deployed to staging by https://github.com/iwiznia in version: 9.0.41-0 🚀
|
🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.0.41-10 🚀
|
Details
This PR added a count parameter to the phrase parameter to return the plural form when necessary. Also In this PR we
updated the types to support PluralizeValues and refactored the code so that all translation functions now use a single object containing all the necessary parameters
Fixed Issues
#38614
PROPOSAL:
Tests
Offline tests
QA Steps
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
android.mov
Android: mWeb Chrome
android-web.mov
iOS: Native
ios.mov
iOS: mWeb Safari
ios-web.mov
MacOS: Chrome / Safari
web.mov
MacOS: Desktop
desktop.mov