As fluent-react is an early prototype, I still have many open
questions about its design.  I'll summarize them below in hope that
the React community can help me find the best path forward.


    1. Use-cases

I wrote fluent-react with the assumption that everything in the UI is
expressed as a React element, be it DOM or Component.  In what cases
does this assumption break?  For instance, do you keep default
placeholders in your app's or component's state?

What we learned during the Firefox OS days is that almost every time
you're tempted to store a translation value somewhere, you may just as
well store the identifier of the translation and let the localization
library format the translation later.

Does this hold for React as well?  One of the examples I wrote
demonstrate the idea behind this:

    
https://github.com/projectfluent/fluent.js/blob/236c270971ab2ee88b7d3357860d065ec068a985/fluent-react/examples/text-input/src/App.js#L25-L33

Instead of storing unlocalizable "stranger" in the state, there are
two LocalizedElements, one for when the name is known and one for when
the name is unknown.  This has two benefits: the localizer can
customize the unknown-name case to make it sound more natural, and
both options exist in the source code which allows for automatic
extraction of localizable copy.

I'd like to know if this would work in large React apps.


    2. Pluggable components

I don't know what to call them, but what I mean are small components
that can be installed via npm and used in your app. Is it safe to
assume that most of them can be localized by passing props?  An
example of this approach is React Toolbox's Snackbar:

    http://react-toolbox.com/#/components/snackbar

    <Snackbar
        action='Nice'
        label='A new developer started using React Toolbox'
        …
    />

With fluent-react, I'd localize it via:

    <LocalizedElement id="snackbar-nice">
        <Snackbar
            action='Nice'
            label='A new developer started using React Toolbox'
            …
        />
    </LocalizedElement>

And the Fluent translation would look like this:

    snackbar-nice
        .action = Nice
        .label = A new developer started using React Toolbox

One counter-example which I was able to find also comes from React
Toolbox: it's the Dialog component.

    http://react-toolbox.com/#/components/dialog

Its `actions` prop is an array of objects with localizable properties:

    actions = [
        { label: "Cancel", onClick: … },
        { label: "Save", onClick: … }
    ];

    <Dialog
        actions={this.actions}
        title='My awesome dialog'
        …
    >
        …
    </Dialog>

My recommendation would be to change the API of Dialog and make
`actions` accept React elements, which could then be wrapped in
LocalizedElement themselves. Something like:

    actions = [
        <LocalizedElement id="dialog-action-cancel">
            <DialogAction onClick={…}>Cancel</DialogAction>
        </LocalizedElement>
        <LocalizedElement>
            <DialogAction onClick={…}>Save</DialogAction>
        </LocalizedElement>
    ];

    <Dialog
        actions={this.actions}
        title='My awesome dialog'
        …
    >
        …
    </Dialog>

An alternative solution would be to use a custom Dialog component in
which the number of actions is predefined and localize the actions via
props.

Can you think of other components that wouldn't work well with the
localization-via-props approach?


    3. Fetching translations

There are many different approaches to loading the right translations
in the app.  I ran a quick Twitter poll yesterday and from the ~100
responses I got so far, it looks like the most common approach is to
do an async fetch on the clientside once the preferred language of the
user is known.

    https://twitter.com/stas/status/835483537105170432

Right now, fluent-react's LocalizationProvider takes a
`requestMessages` prop which is a function returning a promise.  I'd
like to simplify this and use a pattern that I think is common in
React: use wrapping and lifecycle methods to fetch the translations
any way you want.

    import { LocalizationProvider } from 'fluent-react';
    import negotiateLanguages from 'fluent-langneg';

    // Provide your own implementation.
    import fetchMessages from './lib/l10n';

    class MyLocalizationProvider extends Component {
        constructor(props) {
            super(props);
            this.state = {
                locales: negotiateLanguages(props.requested),
                messages: ''
            };
        }

        componentWillMount() {
            fetchMessages(this.state.locales).then(
                messages => this.setState({ messages })
            );
        }

        render() {
            const { children } = this.props;
            const { locales, messages } = this.state;

            if (!messages) {
                // Show a loader?
                return null;
            }

            return (
                <LocalizationProvider locales={locales} messages={messages}>
                    {children}
                </LocalizationProvider>
            );
        }
    }

You'd use like this:

    ReactDOM.render(
        <MyLocalizationProvider requested={navigator.languages}>
            <App />
        </MyLocalizationProvider>,
        document.getElementById('root')
    );

Is this too much boilerplate to ask the developers to write?  I'd like
to allow maximum flexibility so that a large variety of use-cases and
setups can be supported.  For instance, it would be easy to have a
small sync LocalizationProvider for the word "Loading" in all
supported languages, and a bigger async one for other messages.

There's also a question of runtime fallback.  What should happen when
a translation is missing in the current language?  We could make
LocalizedElement request the translation asynchronously from
LocalizationProvider, although that might lead to having a lot of
Promises in the running code.  I'd need to measure the performance
impact of such approach.  It could be introduced later, too, available
as a flag on LocalizationProvider (e.g. runtimeFallback={true}).

Another alternative is to make this a responsibility of the build
setup: before publishing, any translations could be merged with the
default language to fill in the gaps.  For even better support of
non-standard fallback orders (i.e. not falling back directly to
English), ServiceWorkers could be used to perform the merging
"build-step" for translations.


    4. Naming

Lastly, is LocalizedElement a good name?  Throught this name I wanted
to emphasize that it takes a single React element as a child.  It's a
bit long, especially if it's going to be used a lot.  Perhaps
something simple like Localized or Localizable would be enough?


Thanks for reading all of this and for trying fluent-react out!
Staś

On Thu, Feb 23, 2017 at 10:18 PM, Staś Małolepszy <[email protected]> wrote:
> Hi all --
>
> Fluent is the new name for the low-level API powering L20n.  Fluent is
> small and very unopinionated. It makes it easy to integrate it with
> other stacks.
>
> I wrote a prototype of React bindings for Fluent:
>
>     https://github.com/projectfluent/fluent.js/tree/master/fluent-react
>
> It takes advantage of the composition pattern to wrap elements and
> components and provide translations for them:
>
>     <LocalizedElement id="welcome" $username={name}>
>         <p>{'Welcome, {$username}'}</p>
>     </LocalizedElement>
>
> It's an early version and the API will likely change. I would love to
> get feedback from people familiar with React. Bonus points for working
> on an app which ships in multiple languages :)
>
> The README in the repo explains the basics.  I wrote a number of small
> examples to demonstrate what's currently implemented:
>
>     
> https://github.com/projectfluent/fluent.js/tree/master/fluent-react/examples
>
> Lastly, there's a rather lengthy design / braindump doc on the wiki:
>
>     https://github.com/projectfluent/fluent.js/wiki/React-bindings-for-Fluent
>
> Hope you like it!
> Staś
>
>
> * * *
>
> Context & History:
>
> Nearly a year ago we had an in-depth discussion about how we might
> integrate L20n into React:
>
>     https://groups.google.com/forum/#!topic/mozilla.tools.l10n/XtxHgBEokCA
>
> One take-away from that thread was that perhaps we could just let L20n
> and React live alongside each other. After all, L20n uses its own
> MutationObserver to detect changes to the native DOM and could
> localize DOM elements after React has rendered them.
>
> Today we can say that it wasn't the best idea: React and L20n can race
> against each other or cause the content to be rendered twice. L20n
> also removes <!-- react-text --> comments from the DOM which may cause
> trouble with React's reconciliation algorithm.
>
> In January I wrote a post in this newsgroup introducing Project Fluent
>
>     https://groups.google.com/forum/#!topic/mozilla.tools.l10n/NPmsJD4IGjQ
>
> Fluent takes the low-level API from L20n and packages it separately so
> that it's easy to work with and integrate in larger codebases.  You
> can read more about Fluent and L20n on the wiki:
>
>     https://github.com/projectfluent/fluent/wiki/Fluent-and-L20n
>
> To learn more about Fluent, see http://projectfluent.io/.
_______________________________________________
tools-l10n mailing list
[email protected]
https://lists.mozilla.org/listinfo/tools-l10n

Reply via email to