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

[RFC] React Intl v2 #162

Closed
ericf opened this Issue Sep 11, 2015 · 193 comments

Comments

Projects
None yet
@ericf
Copy link
Collaborator

ericf commented Sep 11, 2015

UPDATED: 2015-11-11

React Intl v2 has been in development for several months and the current release is v2.0.0-beta-1 — the first v2 beta release — v2 has been promoted from preview release to beta because we feel it's now feature complete and ready to move forward towards a release candidate once the test suite has been filled out and the docs have been updated.

With v1 being out almost a year we've received tons of great feedback from everyone using React Intl and with these changes we're addressing 20+ issues that have been raised — they're label: fixed-by-v2. While 20-some issues doesn't seem like a lot, many of these are very long discussions fleshing out better ways to approach i18n in React and web apps in general. With v2 we're rethinking what it means to internationalize software…

The Big Idea

ECMA 402 has the following definition of internationalization:

"Internationalization of software means designing it such that it supports or can be easily adapted to support the needs of users speaking different languages and having different cultural expectations [...]"

The usual implementation looks something like this:

  1. Extract strings from source code
  2. Put all strings in a single, large, strings file
  3. Use identifiers in source code to reference strings

This ends up leading to an unpleasant dev experience and problems:

  • Jumping between source and strings files
  • Cruft accumulation, hard to cull strings once added
  • Missing context for translators on how strings are used in the UI

There's a great discussion in #89 on the problems listed above and how to resolve them. With v2, we've rethought this premise of needing to define strings external to the React Components where they're used…

Implementation of Internationalization with React Intl v2:

  1. Declare default messages/strings in source code (React Components)
  2. Use tooling to extract them at build-time

This new approach leads to a more pleasant dev experience and the following benefits:

  • Strings are colocated with where they're used in the UI
  • Delete a file, its strings are cleaned up too (no obsolete translations)
  • Provides a place to describe the context to translators

Default Message Declaration and Extraction

React Intl v2 has a new message descriptor concept which is used to define your app's default messages/strings:

  • id: A unique, stable identifier for the message
  • description: Context for the translator about how it's used in the UI
  • defaultMessage: The default message (probably in English)

Declaring Default Messages

The <FormattedMessage> component's props now map to a message descriptor, plus any values to format the message with:

<FormattedMessage
    id="greeting"
    description="Welcome greeting to the user"
    defaultMessage="Hello, {name}! How are you today?"
    values={{name: this.props.name}}
/>

defineMessages() can also be used to pre-declare messages which can then be formatted now or later:

const messages = defineMessages(
    greeting: {
        id: 'greeting',
        description: 'Welcome greeting to the user',
        defaultMessage: 'Hello, {name}! How are you today?',
    }
});

<FormattedMessage {...messages.greeting} values={{name: this.props.name}} />

Extracting Default Messages

Now that your app's default messages can be declared and defined inside the React components where they're used, you'll need a way to extract them.

The extraction works via a Babel plugin: babel-plugin-react-intl. This plugin will visit all of your JavaScript (ES6) modules looking for ones which import either: FormattedMessage, FormattedHTMLMessage, or defineMessage from "react-intl". When it finds one of these being used, it will extract the default message descriptors into a JSON file and leave the source untouched.

Using the greeting example above, the Babel plugin will create a JSON file with the following contents:

[
    {
        "id": "greeting",
        "description": "Welcome greeting to the user",
        "defaultMessage": "Hello, {name}! How are you today?"
    }
]

With the extracted message descriptors you can now aggregate and process them however you'd like to prepare them for your translators.

Providing Translations to React Intl

Once all of your app's default messages have been translated, you can provide them to React Intl via the new <IntlProvider> component (which you'd normally wrap around your entire app):

const esMessages = {
    "greeting": "¡Hola, {name}! ¿Cómo estás hoy?"
};

ReactDOM.render(
    <IntlProvider locale="es" messages={esMessages}>
        <App />
    </IntlProvider>,
    document.getElementById('container')
);

Note: In v2 things have been simplified to use a flat messages object. Please let us know if you think this would be problematic. (See: #193 (comment))

Try: The Translations example in the repo to see how all this works (be sure to check the contents of the build/ dir after you run the build.)

Automatic Translation Fallbacks

Another great benefit to come out of this approach is automatic fallback to the default message if a translation is missing or something goes wrong when formatting the translated message. A major pain-point we faced at Yahoo which every app experienced was not wanting to wait for new translations to be finished before deploying, or placeholders like {name} getting translated to {nombre} accidentally.

Message formatting in v2 now follows this algorithm:

  1. Try to format the translated message
  2. If that fails, try to format the default message
  3. If either the translated or default message was formatted, return it.
  4. Otherwise, fallback to the unformatted message or its id.

Other Major Changes

For v2, React Intl has been completely re-thought re-written here are the highlights of the major changes:

Simpler Model for Single-Language Apps

React Intl is useful for all apps, even those which only need to support one language. In v2 we've created a simpler model developer's building single-language apps to integrate React Intl. The message formatting features in React Intl are the most complex and are most useful for multi-language apps, but all apps will use pluralization.

In v2 the lower-level pluralization features that message formatting are built on are now exposed as a first-class feature. This allows a single-language app to have pluralization support without the complexity of messages:

<p>
    Hello <b>{name}</b>, you have {' '}
    <FormattedNumber value={unreadCount} /> {' '}
    <FormattedPlural value={unreadCount}
        one="message"
        other="messages"
    />.
</p>

You can think of <FormattedPlural> like a switch statement on its value, with: zero, one, two, few, and many props as cases and other as the default case. This matches the standard ICU Pluralization rules.

Note: Both cardinal and ordinal formatting are supported via the style prop, cardinal is the default.

Try: The Hello World example in the repo to see <FormattedPlural> in action.

Components

<IntlProvider>

This is the new top-level component which your app's root component should be a child of. It replaces the adding the mixin to your app's root component and provides React Intl's API to decedents via React's component context. It takes the following props to configure the intl API and context (all optional):

  • locale: The user's current locale (now singular, defaults to "en")
  • formats: Object of custom named format options for the current locale
  • messages: {id: 'translation'} collection of translated messages for the current locale
  • defaultLocale: The app's default locale used in message formatting fallbacks (defaults to "en")
  • defaultFormats: Object of custom named format options for the defaultLocale
  • initialNow: A reference time for "now" used on initial render of <FormattedRelative> components.

For this component to work the context needs to be setup properly. In React 0.14 context switched to parent-based from owner-based, so <IntlProvider> must be your app's parent/ancestor in React 0.14. React Intl v2 will not support React 0.13.

<IntlProvider>
    <App />
</IntlProvider>

Note: How there's no defaultMessages prop, that's because it's assumed the default message descriptors live co-located to where the messages are being formatted.

See: The Providing Translations to React Intl section above for how it's used.

Function-As-Child Support

There have been many discussions around customizing the rendering of the <Formatted*> components around styling, supporting extras props, and changing the <span> elements that they return. We think of <Fromatted*> components as representations of text.

Our guidance thus far has been to wrap them and style the wrapper. Thinking forward to a single React Intl for both React [Web] and React Native, we want to be more flexible. Also, issues come when rendering a <span> inside an SVG tree, and requires a <tspan>. To remedy this, in v2 all <Formatted*> components support function-as-child, which receives a React node type value. Which enables the following:

let now = Date.now();

<FormattedDate value={now}>
    {(formattedNow) => (
        <time dateTime={now} className="fancy-date">{formattedNow}</time>
    )}
</FormattedDate>

Of course you can always do the following instead, and its valid (and recommended for this example):

let now = Date.now();

<time dateTime={now} className="fancy-date">
    <FormattedDate value={now} />
</time>

The above will yield an inner <span>, and that's okay here. But sometimes it's not okay, e.g. when rending an <option> you should use the function-as-child pattern because you don't want the extra <span> since it'll be rendered as literal text:

let num = 10000;

<FormattedNumber value={num}>
    {(formattedNum) => (
        <option value={num}>{formattedNum}</option>
    )}
</FormattedNumber>

This pattern can work well for targeted use-cases, but sometimes you just want to call an API to format some data and get a string back, e.g., when rending formatted messages in title or aria attributes; this is where using the new API might be a better choice…

New API, No More Mixin

The IntlMixin is gone! And there's a new API to replace it.

The API works very similar to the one provided by the old mixin, but it now live's on this.context.intl and is created by the <IntlProvider> component and can be passed to your custom components via props by wrapping custom components with injectIntl(). It contains all of the config values passed as props to <IntlProvider> plus the following format*(), all of which return strings:

  • formatDate(value, [options])
  • formatTime(value, [options])
  • formatRelative(value, [options])
  • formatNumber(value, [options])
  • formatPlural(value, [options])
  • formatMessage(messageDescriptor, [values])
  • formatHTMLMessage(messageDescriptor, [values])

These functions are all bound to the props and state of the <IntlProvider> and are used under the hood by the <Formatted*> components. This means the formatMessage() function implements the automatic translation fallback algorithm (explained above).

Accessing the API via injectIntl()

This function is used to wrap a component and will inject the intl context object created by the <IntlProvider> as a prop on the wrapped component. Using the HOC factory function alleviates the need for context to be a part of the public API.

When you need to use React Intl's API in your component, you can wrap with with injectIntl() (e.g. when you need to format data that will be used in an ARIA attribute and you can't the a <Formatted*> component). To make sure its of the correct object-shape, React Intl v2 has an intlShape module export. Here's how you access and use the API:

import React, {Component, PropTypes} from 'react';
import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';

const messages = defineMessages({
    label: {
        id: 'send_button.label',
        defaultMessage: 'Send',
    },
    tooltip: {
        id: 'send_button.tooltip',
        defaultMessage: 'Send the message'
    }
});

class SendButton extends Component {
    render() {
        const {formatMessage} = this.props.intl;

        return (
            <button 
                onClick={this.props.onClick}
                title={formatMessage(messages.tooltip)}
            >
                <FormattedMessage {...messages.label} />
            </button>
        );
    }
}

SendButton.propTypes = {
    intl   : intlShape.isRequired,
    onClick: PropTypes.func.isRequired,
};

export default injectIntl(SendButton);

Stabilized "now" Time and "ticking" Relative Times

<IntlProvider> uses an initialNow prop to stabilize the reference time when formatting relative times during the initial render. This prop should be set when rendering a universal/isomorphic React app on the server and client so the initial client render will match the server's checksum.

On the server, Date.now() should be captured before calling ReactDOM.renderToString() and passed to <IntlProvider>. This "now" value needs to be serialized to the client so it can also pass the same value to <IntlProvider> when it calls React.render().

Relatives times formatted via <FormattedRelative> will now "tick" and stay up to date over time. The <FormattedRelative> has an initialNow prop to match and override the same prop on <IntlProvider>. It also has a new updateInterval prop which accepts a number of milliseconds for the maximum speed at which relative times should be updated (defaults to 10 seconds).

Special care has been taken in the scheduling algorithm to display accurate information while reducing unnecessary re-renders. The algorithm will update the relative time at its next "interesting" moment; e.g., "1 minute ago" to "2 minutes ago" will use a delay of 60 seconds even if updateInterval is set to 1 second.

See: #186

Locale Data as Modules

React Intl requires that locale data be loaded and added to the library in order to support a locale. Previously this was done via modules which caused side-effects by automatically adding data to React Intl when they were loaded. This anti-pattern has been replaced with modules that export the locale data, and a new public addLocaleData() function which registers the locale data with the library.

This new approach will make it much simpler for developers whose apps only support a couple locales and they just want to bundle the locale data for those locales with React Intl and their app code. Doing would look this like:

import {addLocaleData} from 'react-intl';
import en from 'react-intl/locale-data/en';
import es from 'react-intl/locale-data/es';
import fr from 'react-intl/locale-data/fr';

addLocaleData(en);
addLocaleData(es);
addLocaleData(fr);

Now when this file is bundled, it will include React Intl with en, es, and fr locale data.

Note: The dist/locale-data/ has UMD files which expose the data at: ReactIntlLocaleData.<lang>. Previously the locale data files would automatically call addLocaleData() on the ReactIntl global. This decouples the loading of the locale data files from the loading of the library and allows them to be loaded async.

<script async src="/path/to/react-intl/dist/react-intl.min.js"></script>
<script async src="/path/to/react-intl/dist/locale-data/fr.js"></script>
<script>
    window.addEventListener('load', function () {
        ReactIntl.addLocaleData(ReactIntlLocaleData.fr);
    });
</script>

Todos

This is just a preview release so there's still more work to do until the v2 final release, but we've already begun integrating this code into Yahoo apps that use React Intl.

  • Finish unit tests
  • Add perf tests to determine if shouldComponentUpdate() is needed
  • Create 1.0 -> 2.0 Upgrade Guide
  • Update docs and examples on http://formatjs.io/ website
  • Only support only React 0.14+? Yes
  • Improve build, try to get source maps working on .min.js files
  • Remove all TODO comments in code

Testing and Feedback

We'd love for you to try out this early version of React Intl v2 and give us feedback, it'll be much appreciated!

$ npm install react-intl@next
@jbrantly

This comment has been minimized.

Copy link

jbrantly commented Sep 11, 2015

IMO, a large part of the pain is in maintaining the stable string identifiers. What happens if two components use the same identifier but a different message? Do you need to "namespace" the identifiers, eg: "mycomponent-greeting"?

Is it not possible to get rid of the identifier, and instead use something like defaultMessage+description as the identifier?

@g-p-g

This comment has been minimized.

Copy link

g-p-g commented Sep 11, 2015

By flat messages do you mean it no longer supports {a: {b: message}} ? Are you dropping that for performance reasons? To me it's a lot better to support that as it helps understanding where the message is used, i.e. something like {pageX: {featureY: text}} describes that text is used in some page X at a place where feature X is present.

I feel that including a description and a flat key is a step backward as it's very verbose and I'm not aware of tools supporting that (I'm guessing yahoo has something for that, but what about everyone else?). At that point I would prefer to follow @jbrantly suggestion of not having manually defined identifiers. Also, the generated file could be very similar to .pot files, where you use msgid for the original message and msgstr for the translated one.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Sep 12, 2015

@jbrantly @g-p-g Using the default message as the identifier was my original thought too, but I was persuaded away from that idea by @tinganho's comment; and instead went for stable, user-defined ids. The namespacing issue is still real, and it would be great to figure out a way to automate this to avoid id conflicts, but it's very easy to catch duplicate ids statically at build time and throw an error. If your app is split into multiple packages, I'd recommend namepsacing the package's message ids with the package name, or using nested <IntlProvider> elements to only expose the package to its own messages.

@g-p-g you're still free to use dots (.) in your identifiers and it should have no observable difference if you think of the messages collection as opaque. "pageX.featureY" is completely fine to have as an id in the flat messages collection and yields the same result as looking up the value in a nested messages collection.

Also, the description field in the message descriptor is completely optional, you can leave it out, or only use it when you need to disambiguate the meaning of a default string for a translator. I feel the tooling to aggregate and process the JSON files with the extracted message descriptors will be app-specific and is easy to write. At Yahoo we're aggregating these descriptors into a collection and converting the data into a different format for our translation system.

@johanneslumpe

This comment has been minimized.

Copy link

johanneslumpe commented Sep 12, 2015

@ericf Those updates are HUGE! A lot of pain points addressed - I like this very much. Not sure when I get around to play with it but I feels like a major, major improvement - good work 👍

@dallonf

This comment has been minimized.

Copy link

dallonf commented Sep 12, 2015

This looks pretty great to me! I'll probably pull down the RC for my next project. +1 on killing the mixin!

@g-p-g

This comment has been minimized.

Copy link

g-p-g commented Sep 12, 2015

@ericf sure you can leave the dots, but the typical applications (crowdin/oneskyapp) will not handle that in the same way as nested objects when displaying. v2 looks very close to how gettext works when you consider the features default translation text, text in the source, some context for the text; except it's more verbose to achieve the same.

I'm not that excited for so much breakage in this part, the other parts (IntlProvider, no more mixins) look good so thank you and everyone else that have been building react-intl.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Sep 12, 2015

sure you can leave the dots, but the typical applications (crowdin/oneskyapp) will not handle that in the same way as nested objects when displaying.

@g-p-g can you explain more how the identifiers are interpreted by these translation vendors? My goal with the message descriptors is provide and extract enough information that can be formatted for translations services, and once the translations are returned, they can be put into the runtime's format.

I'm totally on-board with trying to cut down the verbosity of message declaration via the tooling. This is a preview release so I could get feedback like that and keep iterating on these ideas to hone v2 into something that works for a lot of people. I will think about ways to automatically create or transform the message ids to provide greater context for translations and namespacing to avoid collisions.

@glenjamin

This comment has been minimized.

Copy link

glenjamin commented Sep 12, 2015

It's worth noting that converting from nested to flat is a pretty simple function to write, so you can do something like

module.exports = require('react-intl').flattenTranslations({
  blah: {
    blah: {
      blah: {
      }
    }
  }
});

This all looks pretty sensible to me, the use-case we have for mapping server error codes into translations should fit quite nicely as a map of code: defineMessage().

@bunkat

This comment has been minimized.

Copy link

bunkat commented Sep 12, 2015

The extraction process doesn't work very well for those of us that wrap the FormattedMessage component in our own component instead of using it directly.

We do this to reduce the exposure of using react-intl in case we want to move to a different localization strategy in the future. Otherwise you end up with FormattedMessage spread throughout the entire project that would need to be fixed. We also use it to make the formatters easier to use in our particular project.

We can definitely still do things the old way which isn't that big of a problem.

@SimenB

This comment has been minimized.

Copy link

SimenB commented Jan 11, 2016

Could this be made IE8-compatible? It'd have to use some loose mode (for modules) and drop the usage of getters.

I see you've added the es3-transforms, but those doesn't work properly yet (can be worked around by consumer though). Getters are not polyfillable in IE8 though.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 11, 2016

@SimenB I don't see anywhere that ES5 property getters are used in the source or generated code. Object.defineProperty() is used, but it never defines a getter/setter. React Intl requires that the parts of O.dP that can be polyfilled, are — just like React requires.

Yeah I'm tracking the Babel ES3 plugin issues. Now that the unit tests have been filled out, I plan to start building some integration tests which will include testing the various builds and bundling of React Intl and testing in various runtimes, including IE8. Hopefully around this time, the Babel ES3 plugins will be fixed and I'll be able to do another v2 release.

@SimenB

This comment has been minimized.

Copy link

SimenB commented Jan 12, 2016

@ericf the compiled code uses getters
image

Using loose mode with modules (for babel) fixes it.

EDIT: Apparently not... It fixes the __esModule assignment, but it still uses getters for the exports (unlike in babel@5).

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 12, 2016

@SimenB ah yeah, the module re-export syntax would compile to that. Your best bet right now would be to bundle dist/react-intl.js and ship it to the browser as it doesn't use getters or incompatible ES3 members like .default.

I'll look into improving this, and maybe doing a Rollup build for lib/ as well.

@johannesnagl

This comment has been minimized.

Copy link

johannesnagl commented Jan 12, 2016

@SimenB IE8 is a dead end - not only has MS discontinued all support, React is doing it as well! http://facebook.github.io/react/blog/2016/01/12/discontinuing-ie8-support.html

@SimenB

This comment has been minimized.

Copy link

SimenB commented Jan 12, 2016

Yeah, I expected that. Unfortunately IE8 is still used in enterprise where the app I work on is currently being used, so we're stuck for now.

@johannesnagl

This comment has been minimized.

Copy link

johannesnagl commented Jan 12, 2016

i know that the situation in some companies is quite annoying, but i'm very convinced that frameworks should adopt to new standards/reality rather than holding new technology back. there are quite a few examples of frameworks/libs that suffered a loss in popularity due to oldish stuff and not enough progress.

maybe the "enterprise mode for ie8 in ie11" is an option:
https://technet.microsoft.com/en-us/library/dn640687.aspx

if you're stuck with IE8, than you should freeze to react 0.14 and the old react-intl.js library.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 12, 2016

if you're stuck with IE8, than you should freeze to react 0.14 and the old react-intl.js library.

@johannesnagl No, this is not our guidance. IE8 will be supported with React 0.14 and React Intl v2. As I pointed out above, you can simply use the dist/ file today while I improve a couple things in the build to make sure the code bundled with Browserify/Webpack builds works out of the box without extra configuration.

@BerndWessels

This comment has been minimized.

Copy link

BerndWessels commented Jan 12, 2016

Hello
I have wrapped my App as proposed for V2 with the <IntlProvider> like this:

import {IntlProvider, addLocaleData} from 'react-intl';
import intlDE from 'react-intl/lib/locale-data/de';
import intlEN from 'react-intl/lib/locale-data/en';
import intlMessagesDE from '../public/assets/translations/de.json';
import intlMessagesEN from '../public/assets/translations/en.json';

addLocaleData(intlDE);
addLocaleData(intlEN);

ReactDOM.render(
  <IntlProvider locale="de" messages={intlMessagesDE}>
    <RelayRouter
      history={browserHistory}
      routes={routes}
    />
  </IntlProvider>,
  document.getElementById('root')
);

But I can't figure out how to change the locale and the messages at runtime.

Basically I want to allow the user to change the language by selecting it from a <select> dropdown. That should change the locale and the messages to the selected language and update the UI.

Can this be done without a full page reload?

@dallonf

This comment has been minimized.

Copy link

dallonf commented Jan 12, 2016

@BerndWessels, sure, just call ReactDOM.render again with the new locale and messages. React is awesome like that and will diff the result rather than re-render the page.

@BerndWessels

This comment has been minimized.

Copy link

BerndWessels commented Jan 12, 2016

@dallonf Thanks, but I am still a bit lost.

The <IntlProvider> wraps my app. Somewhere within my app I will have a <select> with multiple language options. Now the user selects a different language:

  1. how do I change the value for the locale and the messages properties of the IntlProvider ?
  2. how do I force the app to re-render without fetching all the data from the Relay/GraphQL endpoint again ?
    All without global window scope hacks if possible - since the handler for the <select> will be somewhere within my application.

Maybe I just don't understand yet how to access the properties of outer/parent elements like the IntlProvider from within a child component.

@dallonf

This comment has been minimized.

Copy link

dallonf commented Jan 12, 2016

@BerndWessels The answer to both of your questions is to call ReactDOM.render() again with the same JSX and same parameters, but different values for locale and messages. The rest will just work; React will not recreate your app, just change the props.

As to how you call that again when something is triggered within your app, that's an architectural question that I can't really help you with. The simplest answer is to call a global function. Second simplest is to bubble up an onLanguageChange event to the original file. And then of course there's Flux-like answers where you emit an event.

@BerndWessels

This comment has been minimized.

Copy link

BerndWessels commented Jan 12, 2016

@dallonf OK, Thank you. I'll give it a try.

@eflarup

This comment has been minimized.

Copy link

eflarup commented Jan 12, 2016

I have looked at the various comments/questions about plurals, and I am also somewhat confused about how exactly this is supposed to work. I am looking at the examples/translations code, which has this sample string:
"greeting.welcome_message": "Welcome {name}, you have received {unreadCount, plural, =0 {no new messages} one {{formattedUnreadCount} new message} other {{formattedUnreadCount} new messages}} since {formattedLastLoginTime}.",

Am I right in assuming that either the developer has to include all the possible plural formats needed to support all target languages (in the example above only 3 are included, while 6 would be required to support all languages), or each language translator has to insert the plural formats needed for their language?
And that in either case, translators are expected to be able to understand the syntax in messages like the one above?

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 13, 2016

@eflarup There are docs here about the ICU Message Syntax: http://formatjs.io/guides/message-syntax/

Am I right in assuming that either the developer has to include all the possible plural formats needed to support all target languages (in the example above only 3 are included, while 6 would be required to support all languages), or each language translator has to insert the plural formats needed for their language?

This depends on your translators. At Yahoo we ran into issues with this where the translators only change what was there and didn't add additional plural categories where important. We talked with them to get it resolved… but you could also fill out all of the missing plural categories with the value from other in your build process. The downside here is potential additional cost.

And that in either case, translators are expected to be able to understand the syntax in messages like the one above?

Compared with any other specialized syntax, the Unicode ICU Message Syntax and CLDR plural rules are the industry standard. It might be the case that you have to convert the messages into a specialized format for the translation service you use. You can do so using the intl-messageformat-parser package directly which creates an AST that you can compile into some other translation file format.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 13, 2016

@SimenB I refactored the package layout and build in #282 which should fix the IE8 issues.

@eflarup

This comment has been minimized.

Copy link

eflarup commented Jan 13, 2016

@ericf

Compared with any other specialized syntax, the Unicode ICU Message Syntax and CLDR plural rules are the industry standard.

I don't completely agree with that. CLDR plural rules, yes, obviously, since they just codify the actual language rules. But gettext produces pluralformat messages that are a good deal more translator-friendly, it's been around for a while, and is widely used.

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Jan 13, 2016

@eflarup I'm not sure what to tell you. React Intl uses ICU Message syntax.

@BerndWessels

This comment has been minimized.

Copy link

BerndWessels commented Jan 14, 2016

Hi

In V2 how do I handle optional (null) values in <FormattedMessage> ?

For example

values = {{firstName: 'Bernd', lastName: 'Wessels'}}
should show
Hello Bernd Wessels! How are you?

and

values = {{firstName: 'Bernd', lastName: null}}
should show
Hello Bernd! How are you?

BUT unfortunately defaultMessage="Hello {firstName} {lastName}! How are you?"
shows
Hello Bernd ! How are you?

See that there is an unwanted whitespace between Bernd and ! ?

@m4nuC

This comment has been minimized.

Copy link

m4nuC commented Jan 14, 2016

@BerndWessels

BUT unfortunately defaultMessage="Hello {firstName} {lastName}! How are you?"
shows
Hello Bernd ! How are you?

It seems like null is handled properly in this case (it doesn't throw). The white space in your message is hard coded so will remain there no matter what the value for lastName is. You can put some logic to do something like to go around this problem:
const defaultMsg = "Hello " + firstName + ( lastName ? ' ' + lastName : '' );

@BerndWessels

This comment has been minimized.

Copy link

BerndWessels commented Jan 14, 2016

@m4nuC Thank you! Just not sure how to give that to my translator? Does that confuse the babel-plugin-relay-intl?

@m4nuC

This comment has been minimized.

Copy link

m4nuC commented Jan 14, 2016

@BerndWessels Right, my bad, what I proposed doesn't make sense in that case. Isn't there something you could do with the ICU syntax? (quite foreign to it hence the question)

@kumarharsh

This comment has been minimized.

Copy link

kumarharsh commented Jan 18, 2016

Hello, I have a question regarding the re-use of intl messages. Say I have 2 components, Tooltip and Select. Both require the same i18n-formatted string, say something like:

    <FormattedMessage
                id='ui.widget.cycleOffsetSelector.timeCycle.label'
                defaultMessage="This {cycle}"
                values={{cycle: props.cycle}}
              />

How do I use the same message in the other component? Just using this:

    <FormattedMessage
                id='ui.widget.cycleOffsetSelector.timeCycle.label'
                values={{cycle: props.cycle}}
              />

does not work (should not :) ). So, what is the correct way to do this?
Do I have to keep these shared messages somewhere globally?

Also, because the props.cycle in my example is actually a time-period like day, week, month, etc, I need to translate that text too before passing it in the FormattedMessage. What is the correct way to do these kind of things?

I see that in the lib\locale-data\[lang].js, there are already values defined for day, week, month, etc. Is there a way to use those in my app directly?

@StoneCypher

This comment has been minimized.

Copy link

StoneCypher commented Jan 18, 2016

Please stop using tickets with hundreds of people attached for technical support

@yahoo yahoo locked and limited conversation to collaborators Jan 18, 2016

@ericf

This comment has been minimized.

Copy link
Collaborator

ericf commented Mar 9, 2016

Update: v2.0.0-rc-1

I've released v2.0.0-rc-1!

This release is expected to be the last one before v2.0.0 final. The reason for the release candidate is to have you test it before the final release is cut in case there are some major issues that need to be fixed. While everyone is testing this release, we'll be working on finishing up the docs.

$ npm install react-intl@next

Thanks everyone for helping with v2 and testing things out and providing feedback along the way!

@ericf ericf closed this Mar 9, 2016

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.