DEV Community

Shubham Singh Chahar
Shubham Singh Chahar

Posted on

Internationalization in React without any external package

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.

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

  • 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 -

{
  "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.

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 -

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.

{
     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 -


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

        {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 -

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.

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.

Thanks for reading.

Top comments (1)

Collapse
 
sangeethkc profile image
SANGEETH

This is amazing. Thank you so much brother.