Exposing API using React provider

devabhijeet

Abhijeet Yadav

Posted on June 30, 2020

Exposing API using React provider

This post is third in the series of react i18n integration.

To summarise the previous post, we added the necessary config to initialise react-i18n.

Now let's see how the provider, using this config, exposes API's that are consumed by our react components.

Our provider should have following functionality

  • get all supported locale list
  • change language
  • load namespace
  • query the current language

The changeLanguage and loadNameSpaces function provided by react-i18n are asynchronous since it requires making XHR call. To make sure our application is rendered only after the above calls are resolved we maintain a boolean isTReady and set it accordingly.

Let's look at the API's now. Start by pasting following code.

import React from "react";
import PropType from "prop-types";
import { withTranslation } from "react-i18next";


let TranslationContext;

const { Provider, Consumer } = (TranslationContext = React.createContext({
  currentLanguage: "",
  isTReady: false,
  localeList: [],
  setTReady: () => {},
  loadNameSpaces: () => {},
  changeLanguage: () => {},
  hasNameSpaceLoaded: () => {},
  setEntityPreferredLocale: () => {}
}));

class TranslationServiceProvider extends React.Component {
  constructor(props) {
    super(props);
    const { tReady } = this.props;

    this.state = {
      currentLanguage: "",
      isTReady: tReady,
      localeList: [
        { id: 1, name: "English", locale: "en" },
        { id: 2, name: "Spanish", locale: "es" }
      ]
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have initialised our state. Let's go ahead and add the languageChanged subscriber call inside componentDidMount. The subscriber makes sure to to set isTReady to false when the locales are being fetched. We also set the currentLanguage to the one detected by the plugin.

componentDidMount() {
    const { i18n } = this.props;
    i18n.on("languageChanged", () => {
      this.setTReady(false);
    });
    this.setLang(
      i18n && i18n.language && i18n.language.slice(0, 2).toLocaleLowerCase()
    );
  }
Enter fullscreen mode Exit fullscreen mode

After we are done adding the subscriber, we need API's to changeLanguage and loadNameSpaces.


setLang = lang => {
    this.setState({
      currentLanguage: lang
    });
  };

  setTReady = ready => {
    this.setState({
      isTReady: ready
    });
  };

  loadNameSpaces = ns => {
    const { i18n } = this.props;
    this.setTReady(false);
    i18n.loadNamespaces(ns).then(() => {
      i18n.setDefaultNamespace(ns);
      this.setTReady(true);
    });
  };

  changeLanguage = lang => {
    const { i18n } = this.props;
    this.setTReady(false);
    return i18n.changeLanguage(lang).then(() => {
      this.setTReady(true);
      if (lang) {
        this.setLang(lang.toLocaleLowerCase().slice(0, 2));
      }
    });
  };

  hasNameSpaceLoaded = ns => {
    const { i18n } = this.props;
    return i18n.options.ns.indexOf(ns) > -1;
  };

  setEntityPreferredLocale = locale => {
    this.changeLanguage(locale);
  };

Enter fullscreen mode Exit fullscreen mode

The above API's takes care of loadingNameSpaces, changeLanguage and checking if the namespaces was already loaded using hasNameSpaceLoaded.

This essentially completed a major chunk of out i18n application. The only thing left now is to encapsulate our Component inside TranslationServiceProvider

Our application component subscribes to above API using a HOC.

import React from "react";
import { TranslationConsumer } from "../providers/TranslationServiceProvider";

export const TranslationServiceHelper = Component =>
  class extends React.Component {
    render() {
      return (
        <TranslationConsumer>
          {context => (
            <Component
              {...this.props}
              {...this.state}
              localeList={context.localeList}
              currentLanguage={context.currentLanguage}
              isTReady={context.isTReady}
              loadNameSpaces={context.loadNameSpaces}
              changeLanguage={context.changeLanguage}
              hasNameSpaceLoaded={context.hasNameSpaceLoaded}
              setEntityPreferredLocale={context.setEntityPreferredLocale}
            />
          )}
        </TranslationConsumer>
      );
    }
  };
Enter fullscreen mode Exit fullscreen mode

In the next post we will take a look at how our components consume provider methods. Since, a component can be a class based or functional, our provider should account for both components.

Find the github repo containing all codes mentioned in this series here

💖 💪 🙅 🚩
devabhijeet
Abhijeet Yadav

Posted on June 30, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related