-
Notifications
You must be signed in to change notification settings - Fork 80
React Bindings Discussion
This page lists a number of ideas for improvements and future plans for fluent-react
.
The text content of the wrapped element isn't currently used and serves as visual help for the developer. In the future it might be used to extract the source copy. It must be a simple string expression. For simple translations without interpolations, the following two components yield the same result:
<Localized id="hello">
<p>Hello, world!</p>
</Localized>
<Localized id="hello">
<p>{'Hello, world!'}</p>
</Localized>
For translations with interpolated arguments, only the latter syntax is valid:
<Localized id="hello" $username={name}>
<p>{'Hello, { $username }'}</p>
</Localized>
By putting the text in a simple string expression we're saving React's cycles: <p>Hello, { name }</p>
would be evaluated by React before being passed to Localized
where it would be replaced by the translation. It also isn't the proper Fluent syntax for this message as external arguments need to be prefixed with a $
in FTL. Lastly there's a naming mismatch between the JSX variable name
and the Fluent argument username
.
Because the text content of the <p>
element isn't used at runtime, the following is also valid:
<Localized id="hello" $username={name}>
<p />
</Localized>
As an alternative, there could be an orig
or source
prop which takes the source copy:
<Localized id="hello" $username={name} orig="Hello, {$name}">
<p />
</Localized>
Is there a way to use regular JSX as localized contents and make the extraction smarter? Something like the following would look more familiar to React developers:
<Localized id="hello" $username={name}>
<p>Hello, { name }</p>
</Localized>
Putting <p>Hello, { name }</p>
as the content looks good on paper and it makes it such that this content can be used as an ultimate fallback, but it has a number of shortcomings:
-
{ name }
is evaluated before the localization logic happens. If you have<p>Hello, { name.toUpperCase() }</p>
,toUpperCase
will be called before the element makes it to<Localized>
.<Localized>
will then replace the content with the translation and hand it off to React for rendering. So the rendering happens only once but the descendant elements are evaluated nonetheless. -
{ name }
is not the proper Fluent syntax for this message; it should be{ $username }
. It's a coincidence that JSX also uses braces for interpolated expressions. We could probably solve this with additional logic in the extraction tool. -
If the message needs plurals in the source language, you can't express them in JSX, and you need the Fluent syntax. Same with variables which need to be passed through Fluent's number or date formatting. These could be special-cases that require the use of simple string expressions.
-
It may be misleading: it's regular JSX so developers might be tempted to put more logic in it; think
{items.map(item => <MyItem>{item}</MyItem>) }
.
<Localized>
can be wrapped by the users of the library to create element-specific wrappers:
<LocalizedP id="hello" />
{'Hello, world!'}
</LocalizedP>
<LocalizedSpan id="hello">
{'Hello, world!'}
</LocalizedSpan>
This could be used to easily define allowed and forbidden attributes for custom components.
Given the following traditional HTML, how might we localize it with fluent-react
?
<p>
By using out product, you are bound by <OutboundLink
to="https://www.google.com/intl/en/policies/terms" target="_blank">Google’s Terms
of Service</OutboundLink>. Consult <OutboundLink
to="https://www.google.com/policies/privacy" target="_blank">Google Privacy
Policy</OutboundLink> for more information.
</p>
As explained in the README, it's already possible to localize this in fluent-react
by passing the <OutboundLink>
elements as arguments to the translation. If the links' text content or title
attributes need to be localized, you can wrap them in <Localized>
before passing them as args. This approach works but requires the creation of additional translation identifiers.
Can we devise something similar to L20n's DOM overlays?
If the translation includes HTML-like tags, we could parse it using the <template>
element and match the resulting DOM tree against the children
of the wrapped components, in this case, <p>
:
<LocalizedOverlay id="hello" $username={name}>
<p>
<a href />
</p>
</LocalizedOverlay>
The HTML parser is very understanding and correctly creates elements that it doesn't know about. Alternatively, we could use <Localized>
with a special overlay
prop or maybe we could infer from the children if we need to run the overlay logic.
<Localized id="hello" $username={name} overlay={true}>
<p>
<a href />
</p>
</Localized>
This is hard because the overlaid elements would normally be interpolated into the source copy. A couple of ideas:
- Source copy is the first child in the wrapped component.
```javascript
<LocalizedOverlay id="hello" $username={name}>
<p>
{'Hello <a>{ $username }</a>'}
<a href />
</p>
</LocalizedOverlay>
```
- Special-purpose component for the source copy.
```javascript
<LocalizedOverlay id="hello" $username={name}>
<SourceMessage>
{'Hello <a>{ $username }</a>'}
</SourceMessage>
<p>
<a href />
</p>
</LocalizedOverlay>
```
- Source copy is a prop.
```javascript
<Localized id="hello" $username={name} orig="Hello <a>{ $username }</a>">
<p>
<a href />
</p>
</Localized>
```
Here's what the real-life example from above could look like:
<Localized id="about-bound-terms">
<p>
{`
By using our product, you are bound by
<OutboundLink>Google’s Terms of Service</OutboundLink>.
Consult <OutboundLink>Google Privacy Policy</OutboundLink>
for more information.
`}
<OutboundLink to="https://www.google.com/intl/en/policies/terms" target="_blank" />
<OutboundLink to="https://www.google.com/policies/privacy" target="_blank" />
</p>
</Localized>
- In order to support async fallback we need
messages
to be an async iterable which in turn requires a different syntax ingetMessageContext
:for await (… of …)
. We might end up with a separate class,AsyncLocalization
in order to support this.
- Can it cache its
message
? - Can it request a fallback from the provider if the
message
doesn't exist? Async? - Perhaps format the message in
componentWillReceiveProps
and store it in the<Localized>
's state?