React-intl multi language app: changing languages and translations storage

qwe asd picture qwe asd · Mar 3, 2016 · Viewed 19.4k times · Source

I have react-router app and would like to add i18n. In react-intl example root component wrapped in IntlProvider:

ReactDOM.render(
<IntlProvider locale="en">
    <App />
</IntlProvider>,
document.getElementById('container')

);

But there is only one locale. How to update app for adding other languages and how is the best way to store translations?

Answer

ScienceSamovar picture ScienceSamovar · Nov 15, 2016

I have faced the same problem and this is what I found out:

To change language I used solution provided here, which is basically binding IntlProvider to ReduxStore with Connect function. Also don't forget to include key to re-render components on language change. This is basically all the code:

This is ConnectedIntlProvider.js, just binds default IntlProvider(notice the key prop that is missing in original comment on github)

import { connect } from 'react-redux';
import { IntlProvider } from 'react-intl';

// This function will map the current redux state to the props for the component that it is "connected" to.
// When the state of the redux store changes, this function will be called, if the props that come out of
// this function are different, then the component that is wrapped is re-rendered.
function mapStateToProps(state) {
  const { lang, messages } = state.locales;
  return { locale: lang, key: lang, messages };
}

export default connect(mapStateToProps)(IntlProvider);

And then in your entry point file:

// index.js (your top-level file)

import ConnectedIntlProvider from 'ConnectedIntlProvider';

const store = applyMiddleware(thunkMiddleware)(createStore)(reducers);

ReactDOM.render((
  <Provider store={store}>
    <ConnectedIntlProvider>
      <Router history={createHistory()}>{routes}</Router>
    </ConnectedIntlProvider>
  </Provider>
), document.getElementById( APP_DOM_CONTAINER ));

Next thing to do is to just implement reducer for managing locale and make action creators to change languages on demand.

As for the best way to store translations - I found many discussions on this topic and situation seems to be quite confused, honestly I am quite baffled that makers of react-intl focus so much on date and number formats and forget about translation. So, I don't know the absolutely correct way to handle it, but this is what I do:

Create folder "locales" and inside create bunch of files like "en.js", "fi.js", "ru.js", etc. Basically all languages you work with.
In every file export json object with translations like this:

export const ENGLISH_STATE = {
  lang: 'en',
  messages: {
      'app.header.title': 'Awesome site',
      'app.header.subtitle': 'check it out',
      'app.header.about': 'About',
      'app.header.services': 'services',
      'app.header.shipping': 'Shipping & Payment',
  }
}

Other files have exact same structure but with translated strings inside.
Then in reducer that is responsible for language change import all the states from these files and load them into redux store as soon as action to change language is dispatched. Your component created in previous step will propagate changes to IntlProvider and new locale will take place. Output it on page using <FormattedMessage> or intl.formatMessage({id: 'app.header.title'})}, read more on that at their github wiki.
They have some DefineMessages function there, but honestly I couldn't find any good information how to use it, basically you can forget about it and be OK.