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
- 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)
This is amazing. Thank you so much brother.