Source: app/hoc/LocaleProvider.js

import React from 'react';
import PropTypes from 'prop-types';
import {
  getMessage,
  hasMessage,
  postProcessMessage,
  preProcessMessage,
} from '../util/locale/messageFormatUtil';

/**
 * Provides a localization function getLocale to child components
 */
class LocaleProvider extends React.Component {
  constructor(props) {
    super(props);

    this.getMessage = this.getMessage.bind(this);
    this.hasMessage = this.hasMessage.bind(this);
    this.messages = null;
    this.state = {
      localeReady: true,
    };

    this.initMessages();
  }

  getChildContext() {
    return {
      getMessage: this.getMessage,
      hasMessage: this.hasMessage,
    };
  }

  /**
   * Gets a locale message formatted by MessageFormat
   *
   * Additional processing is enabled in this function that allows for more advanced formatting.
   * You can now pass objects (jsx) or functions as param values, which makes the return value JSX
   * wrapped into a <span>.
   *
   * When using functions, you can wrap a piece of copy between two variable placeholders, which
   * will be passed to that function as argument.
   *
   * @param id The id of the message to get
   * @param params Parameters to pass to the MessageFormat compiler for this message
   *
   * @example
   *
   *  foo {date} bar
   *  getMessage('foo.bar', { date: <time>2017</time> });
   *
   *  foo {link}http://www.google.com/{_link}
   *  getMessage('foo.bar', { link: link => <a href={link}>{link}</a> });
   *
   *  click {link}http://www.google.com/|here{_link}
   *  getMessage('foo.bar', { link: (url, text) => <a href={url}>{text}</a> });
   */
  getMessage(id, params = {}) {
    let message = id;

    const { updatedParams, ...preProcessValues } = preProcessMessage(params);

    try {
      message = getMessage(this.messages, id, updatedParams);
      return postProcessMessage(message, preProcessValues);
    } catch (e) {
      console.error(`Error getting locale message: ${e.message}`); // eslint-disable-line no-console
    }
    return message;
  }

  /**
   * Checks if a message in the given MessageFormat messages bundle exists
   * @param id The id of the message to get
   * @returns {boolean} true when the message exists
   */
  hasMessage(id) {
    return hasMessage(this.messages, id);
  }

  /**
   * Loads the locale from the getMessages function passed in props.
   * Supports both a synchronous and asynchronous return value from getMessages.
   * If the getMessages function returns a promise, we will set localeReady on this
   * component's state to false so child components do not get rendered until the
   * locale has completed loading.
   *
   * When loading is complete, calls the onLocaleReady callback on props, if
   * it is provided.
   */
  initMessages() {
    const getMessages = this.props.getMessages();

    if (typeof getMessages.then === 'function') {
      // getMessages is async.
      this.state({
        localeReady: false,
      });

      getMessages.then(messages => {
        this.messages = messages;

        if (this.props.onLocaleReady) {
          this.props.onLocaleReady(this.messages);
        }

        this.state({
          localeReady: true,
        });
      });
    } else {
      // getMessages is synchronous
      this.messages = getMessages;

      if (this.props.onLocaleReady) {
        this.props.onLocaleReady(this.messages);
      }
    }
  }

  render() {
    return this.state.localeReady ? this.props.children : <div className="loading-locales" />;
  }
}

LocaleProvider.propTypes = {
  children: PropTypes.node,
  /* Function that retrieves the messages for a given locale id */
  getMessages: PropTypes.func.isRequired,
  /* callback function that will be called with the loaded locale definitions when ready */
  onLocaleReady: PropTypes.func,
};

LocaleProvider.childContextTypes = {
  getMessage: PropTypes.func.isRequired,
  hasMessage: PropTypes.func.isRequired,
};

export default LocaleProvider;