Internationalization in React without any external package
Published: 2020-04-19 06:05:23

Internationalization is the process of making an application multi-locale, an application that supports more than one language.

There is already a popular package for it `react-intl`. But to understand the concept and make our grasp on React as well as javascript, we will try to implement it without any external library.


To follow this tutorial, you need to have knowledge of some of the concepts like `Higher Order Function` and `ContextAPI`. If you struggle there, I suggest you to get a hold on them for better understanding, or you can just follow this tutorial, we will get things working anyway.


{% stackblitz react-locale-imlementation view=preview %}

### Features

- Localization based on JSON translation files
- Persistant choice

Our solution will be easy, simple and efficient. So lets dive right into it.

### Project overview

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zjl1mhx0jn70b7ijrevc.png)

- components - this directory contains all the components for our app
- _contexts - this directory contains all the contexts related logic
- _locales - this directory contains all the JSON translation files
- index.js - the starting point of our app

Let's try to understand the workflow here, we will create a Locale context that will manage the locale of our app and wrap the app in it.
We will then use a special function that is provided by the context wherever we will feel a need to make the content multilingual.

We will pass the string to be handled in the special function which will return the translated string.

That's it.

#### Locale 

Let's have a look at the translation json files at first, the content of `hi.locale.json` is -

```json
{
  "hello": "नमस्कार"
}
```

as you can see, this file contains only one translation, i.e. for hello. so whenever our app's current mode is set to language `Hindi`, all hello wrapped in the special function would be turned to `नमस्कार`.

To add more translations we just have to add more key-value pairs.

#### Context

This directory contains the logic for contexts, it contains only one file `locale.context.js`, let's have a look at its content.

```react
import React, { Component } from 'react';

export const LocaleContext = React.createContext(null);

const withLocale = Component => props => (
    <LocaleContext.Consumer>
        {
            state => <Component {...props} lang={state} />
        }
    </LocaleContext.Consumer>
);

export { withLocale };
```

This file contains two exports, first one is the `LocaleContext` and the second one is a `Higher Order Component` that wraps any given component in a `LocaleContext.Consumer`, so that we can use a special `handle` function to handle the string conversion.

#### Components

This directory contains all the components (only two) that are used in our app.

- Home Component
- Locale Component

#### Locale Component

Let's start with the locale component - 

```react
import React from "react";
import { LocaleContext } from "../_contexts/locale.context";
import en from "../_locales/en.locale";
import hi from "../_locales/hi.locale";
import ta from "../_locales/ta.locale";

const translations = {
  en,
  hi,
  ta
};

class Locale extends React.Component {
  constructor(props) {
    super(props);
    // get saved langcode from localstorage
    let langCode = localStorage.getItem("langCode") || "en";

    // validate the langcode with available languages
    if (!Object.keys(translations).includes(langCode)) {
      langCode = "en";
    }

    this.state = {
      current: langCode
    };
    this.handle = this.handle.bind(this);
  }

  handle(s) {
    return translations[this.state.current][s.toLowerCase()] || s;
  }

  render() {
    return (
      <LocaleContext.Provider
        value={{
          current: this.state.current,
          handle: this.handle
        }}
      >
        <div className="LocaleSelector">
          <select
            defaultValue={this.state.current}
            onChange={event => {
              const langCode = event.target.value;
              this.setState({ current: langCode }, () => {
                localStorage.setItem("langCode", langCode);
              });
            }}
          >
            {Object.keys(translations).map(lang => (
              <option key={lang} value={lang}>
                {lang.toUpperCase()}
              </option>
            ))}
          </select>
        </div>
        {this.props.children}
      </LocaleContext.Provider>
    );
  }
}

export { Locale };
```

Let's start understanding what is happening here, in this component, we start off by importing react, and then the locale-files.

Next, we create a translations object, this object is has a key role in whole process, the keys in this object are lang code and values are the relative locale JSONs, which now are loaded as object themselves.

Next is the locale component itself, in the constructor, we are loading last saved preference from `localstorage`, if none found we set it as `en` for default.

The next line is a validation check, incase some language is set to current from `localstorage`, for eg `hindi`, but our application now is not supporting `hindi`, this check just makes "en" as current `langCode` incase the last language choice in now invalid.

After constructor, there is a method defined named `handle`, this is the special function that we were talking about, the `handle` takes in a string, finds if the translation of the string is available in currently selected language and then return the translation, or the string itself if the translation is not available.

Next comes that `render` method, we initialize the `LocaleContext.Provider` and pass it an object as value.

```javascript
{
     current: this.state.current,
     handle: this.handle
}
```

The current key holds the currently selected langcode.  
The handle method is also passed so that any consumer can use it to translate.

Next we have the locale select drop down - 

```react

        <div className="LocaleSelector">
          <select
            defaultValue={this.state.current}
            onChange={event => {
              const langCode = event.target.value;
              this.setState({ current: langCode }, () => {
                localStorage.setItem("langCode", langCode);
              });
            }}
          >
            {Object.keys(translations).map(lang => (
              <option key={lang} value={lang}>
                {lang.toUpperCase()}
              </option>
            ))}
          </select>
        </div>
```

The options for `select` tag are populated by the translations object, it also has an `onChange` handler which sets the currently selected `langCode` to state and also updates its value in `localstorage` for latter use.

Just below it is - 

```react
        {this.props.children}
```

This line of code is necessary to render the children of our `LocaleProvider`, we will see what this is about when we react `index.js`.


#### HomeComponent

This is the actual component that will make the use of our internationalization -

```react
import React from 'react';
import {withLocale} from '../_contexts/locale.context';

function HomeComponent({ lang }) {
  return (
    <div className="Home">{lang.handle('hello')}</div>
  );
}

export const Home = withLocale(HomeComponent);
```

Here, we import React, then the `Higher Order Component` `withLocale`.

We declare `HomeComponent` with takes in `prop` containing `lang` object, this `lang` object is the value of `LocaleContext` that will be provided by `withLocale` function.

Inside `HomeComponent` we are using the special `handle` function with handles the translation and passing it `hello`, remember `hello` is a key in our localization files.

at the last line we are export `HomeComponent` wrapped in `withLocale`, any component that would like to access the `handle` function must be wrapped in `withLocale` or the `LocaleContext.Consumer` itself.


#### index.js

The index.js is the files that joins all functionality together.

```react
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import { Locale } from './_components/locale.component';
import { Home } from './_components/home.component';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Locale>
          <Home />
        </Locale>
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));
```

We initialize the app in here, Provide `Locale` component with child `Home`, `Home` will replace `{this.props.children}` in `Locale` component.

And that's pretty much it.


{% stackblitz react-locale-imlementation view=preview %}

Thanks for reading.