Skip to content

React Bindings Discussion

Staś Małolepszy edited this page Feb 2, 2018 · 7 revisions

This page lists a number of ideas for improvements and future plans for fluent-react.

Future plans: Extraction

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>

Discussion

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>) }.

Future plans: Custom wrappers

<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.

LocalizationProvider

  • In order to support async fallback we need messages to be an async iterable which in turn requires a different syntax in getMessageContext: for await (… of …). We might end up with a separate class, AsyncLocalization in order to support this.

Localized

  • 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?