-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Comments
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? |
By 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 |
@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 @g-p-g you're still free to use dots ( Also, the |
@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 👍 |
This looks pretty great to me! I'll probably pull down the RC for my next project. +1 on killing the mixin! |
@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. |
@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. |
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 |
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. |
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. |
@SimenB I don't see anywhere that ES5 property getters are used in the source or generated code. 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. |
@ericf the compiled code uses getters Using EDIT: Apparently not... It fixes the |
@SimenB ah yeah, the module re-export syntax would compile to that. Your best bet right now would be to bundle I'll look into improving this, and maybe doing a Rollup build for |
@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 |
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. |
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: 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 |
Hello
But I can't figure out how to change the Basically I want to allow the user to change the language by selecting it from a Can this be done without a full page reload? |
@BerndWessels, sure, just call |
@dallonf Thanks, but I am still a bit lost. The
Maybe I just don't understand yet how to access the properties of outer/parent elements like the |
@BerndWessels The answer to both of your questions is to call 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 |
@dallonf OK, Thank you. I'll give it a try. |
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: 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? |
@eflarup There are docs here about the ICU Message Syntax: http://formatjs.io/guides/message-syntax/
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
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 |
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. |
@eflarup I'm not sure what to tell you. React Intl uses ICU Message syntax. |
Hi In V2 how do I handle optional (null) values in For example
and
BUT unfortunately See that there is an unwanted whitespace between |
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 |
@m4nuC Thank you! Just not sure how to give that to my translator? Does that confuse the babel-plugin-relay-intl? |
@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) |
Hello, I have a question regarding the re-use of intl messages. Say I have 2 components,
How do I use the same message in the other component? Just using this:
does not work (should not :) ). So, what is the correct way to do this? Also, because the I see that in the |
Please stop using tickets with hundreds of people attached for technical support |
Update: v2.0.0-rc-1I've released This release is expected to be the last one before
Thanks everyone for helping with v2 and testing things out and providing feedback along the way! |
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:
The usual implementation looks something like this:
This ends up leading to an unpleasant dev experience and problems:
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:
This new approach leads to a more pleasant dev experience and the following benefits:
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 messagedescription
: Context for the translator about how it's used in the UIdefaultMessage
: The default message (probably in English)Declaring Default Messages
The
<FormattedMessage>
component's props now map to a message descriptor, plus anyvalues
to format the message with:defineMessages()
can also be used to pre-declare messages which can then be formatted now or later: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 whichimport
either:FormattedMessage
,FormattedHTMLMessage
, ordefineMessage
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: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):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:
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
:You can think of
<FormattedPlural>
like aswitch
statement on itsvalue
, with:zero
,one
,two
,few
, andmany
props ascase
s andother
as thedefault
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 currentlocale
messages
:{id: 'translation'}
collection of translated messages for the currentlocale
defaultLocale
: The app's default locale used in message formatting fallbacks (defaults to"en"
)defaultFormats
: Object of custom named format options for thedefaultLocale
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.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 Reactnode
type value. Which enables the following:Of course you can always do the following instead, and its valid (and recommended for this example):
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: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
oraria
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 viaprops
by wrapping custom components withinjectIntl()
. It contains all of the config values passed as props to<IntlProvider>
plus the followingformat*()
, 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
andstate
of the<IntlProvider>
and are used under the hood by the<Formatted*>
components. This means theformatMessage()
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 anintlShape
moduleexport
. Here's how you access and use the API:Stabilized "now" Time and "ticking" Relative Times
<IntlProvider>
uses aninitialNow
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 callingReactDOM.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 callsReact.render()
.Relatives times formatted via
<FormattedRelative>
will now "tick" and stay up to date over time. The<FormattedRelative>
has aninitialNow
prop to match and override the same prop on<IntlProvider>
. It also has a newupdateInterval
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 publicaddLocaleData()
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:
Now when this file is bundled, it will include React Intl with
en
,es
, andfr
locale data.Note: The
dist/locale-data/
has UMD files which expose the data at:ReactIntlLocaleData.<lang>
. Previously the locale data files would automatically calladdLocaleData()
on theReactIntl
global. This decouples the loading of the locale data files from the loading of the library and allows them to be loadedasync
.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.
shouldComponentUpdate()
is needed.min.js
filesTesting 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!
The text was updated successfully, but these errors were encountered: