Exposing API using React provider
Tags: i18n, react, internationalization, localisation
Published: 2020-06-30 15:23:47

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.

```sh
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" }
      ]
    };
  }
}
```

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.

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

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

```sh

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);
  };

```
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. 

```sh
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>
      );
    }
  };
```

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](https://github.com/devAbhijeet/react-i18n-app)

{% codesandbox blue-brook-xgg5v %}