This article was originally published on my personal blog
Internationalization, or i18n, is supporting different languages in your website or app. It allows you to gain users from different parts of the world, which leads to growing your website's traffic.
In this tutorial, we'll learn how to internationalize a React website including translating content and changing the layout's direction based on the language chosen.
You can find the full code for this tutorial in this GitHub repository.
Setup Website
First, we'll set up the React website with Create React App (CRA).
Run the following command:
npx create-react-app react-i18n-tutorial
Once that is done, change the directory to the project:
cd react-i18n-tutorial
You can then start the server:
npm start
Install Dependencies
The easiest way to internationalize a React app is to use the library i18next. i18next is an internationalization framework written in Javascript that can be used with many languages and frameworks, but most importantly with React.
Run the following command to install i18next:
npm install react-i18next i18next --save
In addition, we need to install i18next-http-backend which allows us to fetch translations from a directory, and i18next-browser-languagedetector which allows us to detect the user's language:
npm i i18next-http-backend i18next-browser-languagedetector
Last, we'll install React Bootstrap for simple styling:
npm install react-bootstrap@next bootstrap@5.1.0
Create the Main Page
We'll create the main page of the website before working on the internationalization.
Navigation Bar
We first need the Navigation component. Create src/components/Navigation.js
with the following content:
import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";
function Navigation () {
return (
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand href="#">React i18n</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<NavDropdown title="Language" id="basic-nav-dropdown">
<NavDropdown.Item href="#">English</NavDropdown.Item>
<NavDropdown.Item href="#">العربية</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
}
export default Navigation;
Heading
Then, we'll create src/components/Greeting.js
with the following content:
function Greeting () {
return (
<h1>Hello</h1>
);
}
export default Greeting;
Text
Next, we'll create src/components/Text.js
with the following content:
function Text () {
return (
<p>Thank you for visiting our website.</p>
)
}
export default Text;
Finally, we need to show these components on the website. Change the content of src/App.js
:
import React from 'react';
import { Container } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import Greeting from './components/Greeting';
import Loading from './components/Loading';
import Navigation from './components/Navigation';
import Text from './components/Text';
function App() {
return (
<>
<Navigation />
<Container>
<Greeting />
<Text />
</Container>
</>
);
}
export default App;
Run the server now, if it isn't running already. You'll see a simple website with a navigation bar and some text.
Configuring i18next
The first step of internationalizing React with i18next is to configure and initialize it.
Create src/i18n.js
with the following content:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from 'i18next-http-backend';
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
i18n
.use(Backend)
.use(I18nextBrowserLanguageDetector)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
fallbackLng: 'en',
debug: true,
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;
We're first importing i18n
from i18next
. Then, we're adding i18next-http-backend
and i18next-browser-languagedetector
as plugins to i18n
. We're also adding initReactI18next
as a plugin to ensure that i18next
works with React.
Next, we're initializing i18n
by passing it an object of options. There are many options you can pass to the initializer, but we're passing 3 only.
fallbackLng
acts as the default language in i18n if no language is detected. Language is detected either from the user's preferred language, or a language they previously chose when using the website.
debug
enables debug messages in the console. This should not be used in production.
As for escapeValue
in interpolation
, we're setting it to false since React already escapes all strings and is safe from Cross-Site Scripting (XSS).
Adding the Translation Files
By default, i18next-http-backend
looks for translation files in public/locales/{language}/translation.json
, where {language}
would be the code of the language chosen. For example, en for English.
In this tutorial, we'll have 2 languages on our website, English and Arabic. So, we'll create the directory locales
and inside we'll create 2 directories en
and ar
.
Then, create the file translation.json
inside en
:
{
"greeting": "Hello",
"text": "Thank you for visiting our website.",
"language": "Language"
}
This will create 3 translation keys. When these keys are used, the string value that the key corresponds to will be outputted based on the chosen language. So, each language file should have the same keys but with the values translated to that language.
Next, we'll create the file translation.json
inside ar
:
{
"greeting": "مرحبا",
"text": "شكرا لزيارة موقعنا",
"language": " اللغة"
}
Using the i18n Instance
The next step is importing the file with the settings we just created in App.js
:
import i18n from './i18n';
Next, to make sure that the components are rendered once i18next and the translation files have been loaded, we need to surround our components with Suspense from React:
<Suspense fallback={<Loading />}>
<Navigation />
<Container>
<Greeting />
<Text />
</Container>
</Suspense>
As you can see, we're passing a new component Loading
as a fallback while i18next loads with the translation files. So, we need to create src/components/Loading.js
with the following content:
import { Spinner } from "react-bootstrap";
function Loading () {
return (
<Spinner animation="border" role="status">
<span className="visually-hidden">Loading...</span>
</Spinner>
)
}
export default Loading;
Now, we're able to translate strings in the App
components and its sub-components.
Translating Strings with useTranslation
There are different ways you can translate strings in i18next, and one of them is using useTranslation
hook. With this hook, you'll get the translation function which you can use to translate strings.
We'll start by translating the Greeting
component. Add the following at the beginning of the component:
function Greeting () {
const { t } = useTranslation();
...
}
Then, inside the returned JSX, instead of just placing the text "Hello", we'll replace it with the translation function t
that we received from useTranslation
:
return (
<h1>{t('greeting')}</h1>
);
Note how we're passing the translation function a key that we added in the translation.json
files for each of the languages. i18next will fetch the value based on the current language.
We'll do the same thing for the Text
component:
import { useTranslation } from "react-i18next";
function Text () {
const { t } = useTranslation();
return (
<p>{t('text')}</p>
)
}
export default Text;
Finally, we'll translate the text "Language" inside the Navigation
component:
<NavDropdown title={t('language')} id="basic-nav-dropdown">
If you open the website now, you'll see that nothing has changed. The text is still in English.
Although technically nothing has changed, considering we are using the translation function passing it the keys instead of the actual strings and it's outputting the correct strings, that means that i18next is loading the translations and is displaying the correct language.
If we try to change the language using the dropdown in the navigation bar, nothing will happen. We need to change the language based on the language clicked.
Changing the Language of the Website
The user should be able to change the language of a website. To manage and change the current language of the website, we need to create a context that's accessible by all the parts of the app.
Creating a context eliminates the need to pass a state through different components and levels.
Create the file src/LocaleContext.js
with the following content:
import React from "react";
const defaultValue = {
locale: 'en',
setLocale: () => {}
}
export default React.createContext(defaultValue);
Then, create the state locale
inside src/App.js
:
function App() {
const [locale, setLocale] = useState(i18n.language);
As you can see, we're passing i18n.language
as an initial value. The language
property represents the current language chosen.
However, as it takes time for i18n to load with the translations, the initial value will be undefined
. So, we need to listen to the languageChanged
event that i18n
triggers when the language is first loaded and when it changes:
i18n.on('languageChanged', (lng) => setLocale(i18n.language));
Finally, we need to surround the returned JSX with the provider of the context:
<LocaleContext.Provider value={{locale, setLocale}}>
<Suspense fallback={<Loading />}>
<Navigation />
<Container>
<Greeting />
<Text />
</Container>
</Suspense>
</LocaleContext.Provider>
Now, we can access the locale and its setter from any of the subcomponents.
To change the language, we need to have a listener function for the click events on the dropdown links.
In src/components/Navigation.js
get the locale state from the context at the beginning of the function:
const { locale } = useContext(LocaleContext);
Then, add a listener component that will change the language in i18n
:
function changeLocale (l) {
if (locale !== l) {
i18n.changeLanguage(l);
}
}
Finally, we'll bind the listener to the click event for both of the dropdown links:
<NavDropdown.Item href="#" onClick={() => changeLocale('en')}>English</NavDropdown.Item>
<NavDropdown.Item href="#" onClick={() => changeLocale('ar')}>العربية</NavDropdown.Item>
If you go on the website and try to change the language, you'll see that the language changes successfully based on what you choose. Also, if you try changing the language then refreshing the page, you'll see that the chosen language will persist.
Changing the Location of the Translation Files
As mentioned earlier, the default location of the translation files is in public/locales/{language}/translation.json
. However, this can be changed.
To change the default location, change this line in src/i18n.js
:
.use(Backend)
To the following:
.use(new Backend(null, {
loadPath: '/translations/{{lng}}/{{ns}}.json'
}))
Where the loadPath
is relative to public
. So, if you use the above path it means the translation files should be in a directory called translations
.
{{lng}}
refers to the language, for example, en
. {{ns}}
refers to the namespace, which by default is translation
.
You can also provide a function as a value of loadPath
which takes the language as the first parameter and the namespace as the second parameter.
Changing Document Direction
The next essential part of internationalization and localization is supporting different directions based on the languages you support.
If you have Right-to-Left (RTL) languages, you should be able to change the direction of the document when the RTL language is chosen.
If you use our website as an example, you'll see that although the text is translated when the Arabic language is chosen, the direction is still Left-to-Right (LTR).
This is not related to i18next as this is done through CSS. In this tutorial, we'll see how we can use RTL in Bootstrap 5 to support RTL languages.
The first thing we need to do is adding the dir
and lang
attributes to the <html>
tag of the document. To do that, we need to install React Helmet:
npm i react-helmet
Then, inside Suspense
in the returned JSX of the App
component add the following:
<Helmet htmlAttributes={{
lang: locale,
dir: locale === 'en' ? 'ltr' : 'rtl'
}} />
This will change the lang
and dir
attributes of <html>
based on the value of the locale.
The next thing we need to do is surround the Bootstrap components with ThemeProvider
which is a component from react-bootstrap
:
<ThemeProvider dir={locale === 'en' ? 'ltr' : 'rtl'}>
<Navigation />
<Container>
<Greeting />
<Text />
</Container>
</ThemeProvider>
As you can see we're passing it the dir
prop with the direction based on the locale. This is necessary as react-bootstrap
will load the necessary stylesheet based on whether the current direction is rtl
or ltr
.
Finally, we need to change the class name of Nav
in the Navigation
component:
<Nav className={locale === 'en' ? 'ms-auto' : 'me-auto'}>
This is only necessary since there seems to be a problem in the support for ms-auto
when switching to RTL.
If you try opening the website now and changing the language to Arabic, you'll see that the direction of the document is changed as well.
Conclusion
i18next facilitates internationalizing your React app, as well as other frameworks and languages. By internationalizing your app or website, you are inviting more users from around the world to use it.
The main parts of internationalization are translating the content, supporting the direction of the chosen language in your website's stylesheets, and remembering the user's choice. Using i18next you're able to easily translate the content as well as remembering the user's choice.
Top comments (4)
Nice post! Where does i18next stores the selected lang? Is it in localstorage or backend?
It stores the selected lang on localStorage with 'i18nextLng' key.
thanks
I'd like to know your view on the react-intl package if you have used it.