Hacker News new | ask | show | jobs
by stasm 2616 days ago
This is a great point and something that we've seen come up very often in building UIs. The good practice which we recommend to developers at Mozilla is to avoid splitting or nesting messages, because it makes it harder for translators to see the entire translation at once.

We've taken a layered approach to designing Fluent: what we're announcing today is the 1.0 of the syntax and file format specification. The implementations are still maturing towards 1.0 quality, but let me quickly describe what our current thinking is.

For JavaScript, we're working on low-level library which implements a parser of Fluent files, and offers an agnostic API for formatting translations. On top of it we hope to see an ecosystem of glue-code libraries, or bindings, each satisfying the needs of a different use-case or framework.

I've been working on one such binding library called fluent-react. It's still in its 0.x days, but it's already used in a number of Mozilla projects (e.g. in Firefox DevTools). In fluent-react translations can contain limited markup. During rendering, the markup is sanitized and then matched against props defined by the developer in the source code, in a way that overlays the translation onto the source's structure. Hence, this feature is called Overlays. See https://github.com/projectfluent/fluent.js/wiki/React-Overla....

Here's how you could re-implement your example using fluent-react. Note that the <a>'s href is only defined in the prop to the Localized component.

    <Localized
        id="confirm"
        $clickCount={7}
        a={<a href="..."></a>}
    >
        {"Please <a>click here {$clickCount ->
            [one] 1 time
           *[other] {$clickCount} times
        }</a> to confirm."}
    </Localized>
I'd love to get more feedback on ideas in fluent-react. Please feel free to reach out if you have more questions!
5 comments

I've seen a few libraries that use a similar approach of parsing strings for pseudo-elements and then matching them with React elements to avoid splitting up messages, but I've always felt a lot of resistance towards adopting something like that because it means incurring the runtime cost of parsing a string for elements when you can easily have hundreds or thousands of messages being rendered at once. (Call it a premature optimization if you must, but I've been bitten enough times in the past for adopting libraries/approaches that scaled poorly performance wise and had to pay the cost in untimely, painful refactors.)

I feel there's a fundamental impedance mismatch here because we're defining messages as strings but the rest of our UI as React components. I described here a potentially different component-oriented approach as an attempt to get rid of this impedance mismatch: https://news.ycombinator.com/item?id=19681129

I'd love to hear some thoughts on that approach from folks with more real-world experience working with i18n than I do (which is not a whole lot sadly, given the nature of the kinds of projects I've worked on in the past).

I’ve been trying to solve this problem in the resource4j library for Java, which can cache rendered strings, but in the end it’s always a memory vs performance problem. I didn’t publish any artificial benchmarks, but in couple real projects (resource4j+thymeleaf) performance impact was usually negligible.
> `[one] 1 time *[other] {$clickCount} times`

How does this work for languages that have more complex pluralization rules?

E.g. in Russian it's "1 раз", "2 раза", "11 раз", "12 раз", "22 раза" and "55 раз" - the case depends on the number ending, with exceptions for 11, 12, 13 and 14.

That's a great question!

Fluent relies on Unicode Plural Rules [0] which allow us to handle all (as far as Unicode knows) pluralization rules for cardinal and ordinal (and range) categories :)

[0] http://cldr.unicode.org/index/cldr-spec/plural-rules

It's up to the localizer to define variants corresponding to the language's plural categories. For Russian, that's (one, few, many). Interestingly, this particular example could simplified to (few, *), because "раз" is good for both 1, 5, 11, 55, etc. See https://projectfluent.org/play/?id=7d22f87c04b23b86d9f9149d5... for an example of this in action.

Authoring tools can help here, too. Pontoon, Mozilla's translation management system, pre-populates plural variants based on the number of plural categories defined in Unicode's CLDR.

Would this also allow for translating e.g. the <a>'s `title` attribute, or e.g. an `aria-label`?
It's something that I definitely plan to add. There's even an open issue about it! https://github.com/projectfluent/fluent.js/issues/185
Great! It sounds like a very cool project to work on :)
What's stopping you from doing that now? Just add a new translation for those labels in the source document and bind it to the attribute tag.
You should take a look at js-lingui. Child components are automatically converted to symbols by way of a babel-macro (no run time parsing of complex translations).
Why fluent-react and not (in addition to) fluent-web-components? :(
One advantage I can imagine is that you can prerender the React components, outputting e.g. plain HTML. With tools like e.g. React Static, that means you can somewhat ergonomically generate different static websites for different languages, avoiding the runtime costs of looking up the correct strings.
Using the Svelte JS library (https://svelte.technology) you can have both: server-side rendered components [1] and compile to web components (custom elements) [2]

Another advantage is that the components compile to vanilla JavaScript, so we don't rely on a runtime library to run the application.

[1] https://svelte.technology/guide#server-side-rendering

[2] https://svelte.technology/guide#custom-elements