-
Notifications
You must be signed in to change notification settings - Fork 78
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
Migrate @fluent/react to TypeScript #458
Conversation
CC @huy-nguyen, @Seally, @Gregoor -- Would you be able to give me feedback on this PR? Thanks! |
React typings (wrongly) expect the return type of functional components to be a ReactElement, thus making () => "Hello" invalid. This is incorrect; as of React 16, it's possible to return plain strings from functional components and Component.render(). See DefinitelyTyped#18912 and TypeScript#21699. To work around this, annotate the return value of Localized to be of type ReactElement, and whenever a string or null are returned, wrap them in Fragments.
I had to work around the issue described in DefinitelyTyped/DefinitelyTyped#18912. React's typings currently expect the return type of |
I tested this in a few example projects. I think it's ready for review. I'd like to test it in a larger project, if possible, but I don't have good candidates 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.
Two general things that are on my mind:
- migration path: This does require all consumers of @fluent/react to change a lot (most?) of their
Localized
components, doesn't it? So this is a major version change? Is there room for allowing the old way and just throwing warnings for now, so that consumers can migrate gradually? - API / naming: I'm torn on this one. I like brevity but dislike abbreviations. You probably already made the right compromise here and it's not like I have a better one to offer. Just wanted to highlight it.
Exciting changes overall 🎉
@@ -51,8 +45,8 @@ function toArguments(props) { | |||
* translation is available. It also makes it easy to grep for strings in the | |||
* source code. | |||
*/ | |||
function Localized(props) { | |||
const { id, attrs, children: child = null } = props; | |||
export function Localized(props: LocalizedProps): ReactElement { |
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 not loosen the return type to ReactNode
instead, which includes ReactElement
but also allows child
to be returned without a wrapping call.
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 did that initially, but I ran into DefinitelyTyped/DefinitelyTyped#18912. See #458 (comment).
id: string; | ||
attrs?: Record<string, boolean>; | ||
children?: ReactNode; | ||
vars?: Record<string, FluentArgument>; |
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 out of curiosity: why vars
instead of args
?
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.
Hmm, I'm torn on this one. They're called variables in Fluent Syntax, but they're passed as arguments. In DOM, we use data-l10n-args
, too, but that's mostly an artifact of how we did it in Firefox OS. I think vars
is a more accurate name here, but args
might be a better choice because of the prior work and due to how developers think about them.
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.
My main concern would be to be consistent, and I'd rather change the resolvers as we change resolvers anyway. As opposed to changing the syntax.
So, vars
is my vote.
One could argue that you pass your variables as args. As in, when you pass them, they're args, when you store and reference them, they're variables, but that's not useful for anything, I think. (But that may be the historical reason why we have both.)
Thanks for the comments, @Gregoor!
Yes, it's a breaking change. We might want to release it as 1.0, to make that superclear. I'd also like to figure out the migration plan. We might be able to add the following index signature to
I went with |
@@ -850,8 +855,7 @@ foo = test <em>custom markup parser</em> | |||
function parseMarkup(str) { | |||
return [ | |||
{ | |||
TEXT_NODE: 3, | |||
nodeType: 3, | |||
nodeName: "#text", |
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 simplified the detection of text nodes relying on the fact that their nodeName
is guaranteed by the standard to be #text
.
I've finished upgrading
I think the remaining work is about naming. |
I'd like to go ahead with |
@Gregoor I'd like to make this a breaking change in the next release of The alternative would be to support both APIs for a period of time, but I'm afraid of having to maintain and test both APIs at the same time, plus their combinations. |
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 feeling confident in actually reviewing this, sorry.
I've got some raised eyebrows on the changes to vendor
. The files don't seem to match what's in the README anymore?
Good catch, I reverted them to their originals. |
See #376 for the general discussion about migrating fluent.js packages to TypeScript.
The migration of
@fluent/react
is more involved than the packages which I've migrated so far. I'll keep this PR in the draft state for the next couple of days because I'd like to test it in a TypeScript+React project.This PR changes the API of the
<Localized>
component. To be fair, I should have made it this way from the get go. I regret giving in to the temptation of the "cute" API design in which arguments to the translations are passed in as props prefixed with the dollar sign:<Localized $arg={...}>
. In this PR, the API changes to a more explicit<Localized vars={{arg: ...}}>
.The same goes for elements which are then matched with markup from the translation. Rather than passing them as separate props:
<Localized confirmButton={...}>
, this PR changes the API to be explicit again:<Localized elems={{confirmButton: ...}}>
.I'm not sure if I got all the
ReactNode
vs.ReactElement
vs.ComponentType
right. I'll need to test more and I'd appreciate help with testing!