Introduction:
I've recently been updating our internal frontend codebase at work. One task was to add internationalization (i18n). Although we'd been using i18n in the old project for a while, this was my first time setting it up myself. It seemed like a good opportunity to write down what I learned and how I implemented it.
The Main Tools: i18next and react-i18next
When I looked into internationalization for React applications, i18next and react-i18next were the combination I saw recommended most often.
i18next
i18next is an extensible internationalization library for handling translations. It works across frameworks, making it an option for both frontend and backend applications. Its core features include language detection, language switching, and managing text in JSON translation files.
It also supports plugins. For example, a language detector can identify the user's preferred language, while an HTTP loader can fetch translation files dynamically from the server.
react-i18next
react-i18next wraps i18next for React, making it easier to integrate into a React application. It provides tools such as the useTranslation hook and the withTranslation higher-order component, so we can work with translations in a way that fits React.
Project Requirements
Once I understood what the tools did, I worked through the project's requirements. Based on how we'd used i18n in the old project, we needed at least the following:
- Switch between Chinese and English: Users should be able to change the interface language at any time.
- Set the initial language from the browser's language: Let the application choose an appropriate language automatically for a smoother experience.
- Organize translation files with namespaces: Different feature modules should have their own translation files to keep things manageable, such as a shared
commonnamespace and an application-specificappnamespace.
Installation and Configuration
Installing the Packages
First, install the tools we need. These are the plugins I used in this project:
yarn add i18next i18next-browser-languagedetector i18next-http-backend react-i18next
i18next-http-backend: Loads translation files from the server. It lets us manage translations separately and load them dynamically for the language we need.i18next-browser-languagedetector: Detects the user's browser language, for example through URL query parameters or browser language settings.
Configuring i18n
Next, configure i18next to meet those requirements. Following the official documentation, I used this setup:
// i18n.ts
import { initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
// Initialize i18next
i18n
.use(HttpBackend) // Load translation files with HttpBackend
.use(LanguageDetector) // Use the language detector
.use(initReactI18next) // Initialize React i18next
.init({
// lng: 'zh-TW', // Default language
fallbackLng: 'zh-TW', // Default language if detection fails
detection: {
order: ['querystring', 'navigator'], // Detect the language from query parameters first, then browser settings
caches: [], // Do not cache the language in localStorage or cookies
},
backend: {
loadPath: '/translations/{{ns}}/{{lng}}.json', // Translation file path
},
ns: ['common'], // Namespaces to use
defaultNS: 'common', // Default namespace
interpolation: {
escapeValue: false, // React already handles XSS protection, so no extra escaping is needed
},
debug: import.meta.env.DEV, // Enable debug output during development
});
export default i18n;
Here, I configured the language detection order and namespaces. For example, I used common and app to keep translations modular, so different feature modules could be managed separately. Then we just need to import this file in the application entry point, such as main.tsx, to make i18n's translation features available throughout the components.
For the full list of configuration options and their definitions, see @i18next documentation - Configuration Options.
Preparing Translation Files
With the configuration in place, we can create the translation files. I set the path to '/translations/{{ns}}/{{lng}}.json', so I created this directory structure:
public/translations
└── common
├── en.json
└── zh-TW.json
Each JSON file contains translations as key-value pairs. This zh-TW.json example keeps the Chinese translations: login means “Log in,” and logout means “Log out.”
// zh-TW.json
{
"login": "登入",
"logout": "登出",
...
}
Using react-i18next
Working with Translations
The most common way to use translations in react-i18next is through the useTranslation hook:
import React from 'react';
import { useTranslation } from 'react-i18next';
const MyComponent: React.FC = () => {
const { t } = useTranslation('app'); // Use the 'app' namespace
return <h1>{t('welcome_message')}</h1>;
};
export default MyComponent;
Here, useTranslation gives us the t function. It looks up the text for a key in the current language, so the rendered component's text changes along with the language.
You can also specify a namespace, such as useTranslation('common') or useTranslation('app'). This helps organize translations for different parts of the application. If you don't specify one, i18n uses the default namespace.
There are other APIs, including the withTranslation HOC and the Translation render prop. I use them less often, so I won't go into them here.
Switching Languages
Language switching is another core part of internationalization. I used i18next's changeLanguage method to let users change the language at any time:
import React from 'react';
import { useTranslation } from 'react-i18next';
const LanguageSwitcher: React.FC = () => {
const { i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('zh')}>Chinese</button>
</div>
);
};
export default LanguageSwitcher;
