React-Intl how to switch locale and messages from variable

With a new React Context API it is quite easy to do. Create a wrapper:

import React from "react";
import Types from "prop-types";
import { IntlProvider, addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";
import de from "react-intl/locale-data/de";
import deTranslation from "../../lang/de";
import enTranslation from "../../lang/en";

addLocaleData([...en, ...de]);

const Context = React.createContext();

class IntlProviderWrapper extends React.Component {
  constructor(...args) {
    super(...args);

    this.switchToEnglish = () =>
      this.setState({ locale: "en", messages: enTranslation });

    this.switchToDeutsch = () =>
      this.setState({ locale: "de", messages: deTranslation });

    // pass everything in state to avoid creating object inside render method (like explained in the documentation)
    this.state = {
      locale: "en",
      messages: enTranslation,
      switchToEnglish: this.switchToEnglish, 
      switchToDeutsch: this.switchToDeutsch 
    };
  }

  render() {
    const { children } = this.props;
    const { locale, messages } = this.state;
    return (
      <Context.Provider value={this.state}>
        <IntlProvider
          key={locale}
          locale={locale}
          messages={messages}
          defaultLocale="en"
        >
          {children}
        </IntlProvider>
      </Context.Provider>
    );
  }
}

export { IntlProviderWrapper, Context as IntlContext };

And then use that Provider and Consumer:

import { Provider } from "react-redux";
import {  IntlProviderWrapper } from "./IntlContext";

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <IntlProviderWrapper>
          ...
        </IntlProviderWrapper>
      </Provider>
    );
  }
}

somewhere in the app:

import React from "react";
import { Text, Button } from "native-base";
import { IntlContext } from "../IntlContext";

const LanguageSwitch = () => (
  <IntlContext.Consumer>
    {({ switchToEnglish, switchToDeutsch }) => (
      <React.Fragment>
        <button onClick={switchToEnglish}>
          English
        </button>
        <button onClick={switchToDeutsch}>
          Deutsch
        </button>
      </React.Fragment>
    )}
  </IntlContext.Consumer>
);

// with hooks
const LanguageSwitch2 = () => {
  const { switchToEnglish, switchToDeutsch } = React.useContext(IntlContext);
  return (
    <>
      <button onClick={switchToEnglish}>English</button>
      <button onClick={switchToDeutsch}>Deutsch</button>
    </>
  );
};

export default LanguageSwitch;

Example on CodeSandbox

Here is the relevant repository with a more general solution.

Note: at the moment react-intl is still using an old context API but in the future solution like this might work out-of-the-box.


It works if you remove all from root:

ReactDOM.render(<TodoApp />, document.getElementById('root'));

But now we change TodoApp component like this:

1) We add 'locale' as component state and import React-Intl:

import { IntlProvider, addLocaleData } from 'react-intl';
import intlEN from 'react-intl/locale-data/en';
import intlES from 'react-intl/locale-data/es';
import intlMessagesES from './../i18n/locales/es.json';
import intlMessagesEN from './../i18n/locales/en.json';

addLocaleData([...intlEN, ...intlES]);

/* Define your default translations */
let i18nConfig = {
    locale: 'en',
    messages: intlMessagesEN
};

2) Change our changeLanguage function (this time called 'onChangeLanguage'), this function receives 'lang' from a subComponent language selector:

onChangeLanguage(lang) {
        switch (lang) {
            case 'ES': i18nConfig.messages = intlMessagesES; break;
            case 'EN': i18nConfig.messages = intlMessagesEN; break;
            default: i18nConfig.messages = intlMessagesEN; break;
        }
        this.setState({ locale: lang });
        i18nConfig.locale = lang;
}

And finally render:

render() {
        return (
            <IntlProvider key={ i18nConfig.locale } locale={ i18nConfig.locale }  messages={ i18nConfig.messages }>
                <div>
                    <Header onChangeLanguage={this.onChangeLanguage} />
                    // Other components ...
                </div>
            </IntlProvider>
        );
    }

If someone doesn't understand at all, ask in comments! Thanks to @TomásEhrich