Learn how to translate React apps with react-i18next using JSON files, plurals, and dynamic content. Set up continuous localization with PTC to automate your translation workflow.
This guide shows you how to:
- Set up react-i18next and load translation files automatically
- Handle dynamic content (variables, plurals, formatted text)
- Translate your JSON files and deploy your app
You can follow along with a complete example on GitHub.
React Internationalization (with Commit Examples)
1
Set Up Your React App
IThis tutorial uses a fresh React + TypeScript app built with Vite. If you’re adding translations to an existing app, skip to Step 2.
To create a new React + TypeScript app, run:
npm create vite@latest react-localization-demo -- --template react-ts
cd react-localization-demo
npm install
npm run devThis starts a development server and opens the default Vite + React page in your browser.
Commit Reference: Create React project with Vite and TypeScript
2
Install and Configure react-i18next
Install the libraries:
npm install i18next react-i18next
```
### Create Your Translation File
Create the following folder structure:
```
src/
i18n/
locales/
en.json
Add your English strings to src/i18n/locales/en.json:
{
"welcome": "Welcome",
"description": "This is a localization demo.",
"clickMe": "Click me"
}Each key (welcome, description, clickMe) maps to the text you want to display in the UI.
Update Your Component
Now, convert your component from hardcoded text to use translations. Here’s a typical React component with hardcoded strings:
function App() {
return (
<div>
<h1>Welcome</h1>
<p>This is a localization demo.</p>
<button onClick={() => alert('Click me')}>Click me</button>
</div>
);
}Replace the hardcoded text with the useTranslation hook in src/App.tsx:
import { useTranslation } from 'react-i18next';
function App() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
<button onClick={() => alert(t('clickMe'))}>{t('clickMe')}</button>
</div>
);
}
export default App;The useTranslation() hook gives your component access to:
t()– looks up a string by keyi18n– lets you change languages programmatically
Commit Reference: Create the initial English file for translation
3
Initialize i18next
Create src/i18n/index.ts to configure i18next and load translation files automatically.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
type TranslationResources = string | { [k: string]: TranslationResources } | TranslationResources[];
const modules = import.meta.glob<{default: Record<string, TranslationResources>}>('./locales/*.json', { eager: true })
const resources: Record<string, { translation: Record<string, TranslationResources> }> = {};
for (const path in modules) {
const lang = path.match(/\.\/locales\/(.*)\.json$/)?.[1];
if (lang) {
resources[lang] = { translation: modules[path].default };
}
}
i18n
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
interpolation: { escapeValue: false },
});
export default i18n;This setup is useful because:
- Every
*.jsonfile you add tosrc/i18n/locales/becomes available automatically. - When PTC creates new language files (for example
ar.json), they’ll be auto-loaded by this configuration — no imports or code changes needed.
Import i18n in Your Entry Point
Before React renders your app, i18next needs a chance to initialize. Open src/main.tsx and import the i18n configuration:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import './i18n' // ← Add this import!
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
If you skip this step, t() will just return translation keys instead of translated text.
Commit Reference: Set up i18n configuration
4
Handle Dynamic Content
So far, you’ve only used static strings like “Welcome” and “Click me”. But real apps need dynamic content: greeting users by name, showing counts, and including links or formatting inside translated text.
Interpolation (Variables)
To include variables in translations, use {{variableName}} syntax in your JSON and pass the value when calling t().
Update src/i18n/locales/en.json:
{
"welcome": "Welcome",
"userGreeting": "Welcome back, {{firstName}}!",
"description": "This is a localization demo.",
"clickMe": "Click me"
}
Use it in your component:
import { useTranslation } from 'react-i18next';
function App() {
const { t } = useTranslation();
const user = { firstName: 'Sarah' };
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('userGreeting', { firstName: user.firstName })}</p>
<p>{t('description')}</p>
<button onClick={() => alert(t('clickMe'))}>{t('clickMe')}</button>
</div>
);
}
export default App;
The second argument to t() is an object with the values you want to substitute. You can have as many variables as you need—just make sure each one appears in your translations.
Plurals
Some text changes based on a number. For example, “1 new message” vs “3 new messages”. i18next handles this automatically when you provide plural forms.
Update src/i18n/locales/en.json:
{
"welcome": "Welcome",
"userGreeting": "Welcome back, {{firstName}}!",
"description": "This is a localization demo.",
"clickMe": "Click me",
"newMessages_one": "You have {{count}} new message.",
"newMessages_other": "You have {{count}} new messages."
}
For pluralization to work, the interpolated variable must be named count. i18next uses this value to choose the correct plural form for each language.
Use it in your component:
import { useTranslation } from 'react-i18next';
function App() {
const { t } = useTranslation();
const user = { firstName: 'Sarah' };
const [messageCount, setMessageCount] = useState<number>(1);
const handleIncrement = () => {
setMessageCount(messageCount + 1);
};
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('userGreeting', { firstName: user.firstName })}</p>
<p>{t('newMessages', { count: messageCount })}</p>
<p>{t('description')}</p>
<button onClick={handleIncrement}>{t('clickMe')}</button>
</div>
);
}
export default App;
i18next will automatically choose newMessages (singular) when count is 1, and newMessages_other (plural) for any other number.
Formatting and Links with Trans
For translations containing HTML elements (links, bold text, etc.), use the Trans component.
Import Trans:
import { useTranslation, Trans } from 'react-i18next';Add a new key to src/i18n/locales/en.json:
{
"welcome": "Welcome",
"userGreeting": "Welcome back, {{firstName}}!",
"newMessages_one": "You have {{count}} new message.",
"newMessages_other": "You have {{count}} new messages.",
"description": "This is a localization demo.",
"termsText": "I agree to the <1>Terms of Service</1> and <3>Privacy Policy</3>.",
"clickMe": "Click me"
}
Notice the <1> and <3> tags. These are placeholders that map to child components by index (starting from 0).
Use the Trans component in your JSX:
import { useTranslation, Trans } from 'react-i18next';
function App() {
const { t } = useTranslation();
const user = { firstName: "Sarah" };
const [messageCount, setMessageCount] = useState<number>(1);
const handleIncrement = () => {
setMessageCount(messageCount + 1);
};
return (
<div>
<h1>{t("welcome")}</h1>
<p>{t("userGreeting", { firstName: user.firstName })}</p>
<p>{t("newMessages", { count: messageCount })}</p>
<p>{t("description")}</p>
<p>
<Trans i18nKey="termsText">
I agree to the <a href="/terms">Terms of Service</a> and
<a href="/privacy">Privacy Policy</a>.
</Trans>
</p>
<button onClick={handleIncrement}>{t("clickMe")}</button>
</div>
);
}
export default App;
Translate the JSON Files in Your React App
At this point, your app is fully internationalization-ready. Now it’s time to translate your en.json file.
You can translate manually, but as you add translation files, this can get hard to manage. That’s why we’re using our Private Translation Cloud (PTC)— it creates context-aware AI translations and can integrate with your GitHub repository for continuous localization.
PTC always starts with a free trial that lets you translate 2,500 words into up to 2 languages. After that, you pay as you go with no subscription—you only pay for what you translate.
For example, translating 10,000 words into 1 language costs just over €22. The more you translate, the less you pay per word.
To start translating:
- Follow the steps in the Getting Started Guide to create a new project.
- In the setup wizard, select your source file (in our case,
src/i18n/locales/en.json) - Choose your output path:
src/i18n/locales/{{lang}}.json
This pattern creates one file per language (e.g., fr.json, de.json) using the same folder structure your app already expects.
When your translations are ready, PTC opens a pull request with the new language files. Because of the auto-loading configuration you set up in Step 3, these files work immediately—no code changes needed.
Commit Reference: PTC ReactLocalizationDemo : Automatic Translations
Test Your Translated React App with a Language Switcher
Now that you have a translation file, add a simple language switcher to verify everything works.
Update src/App.tsx to add language switcher buttons:
import { useTranslation } from 'react-i18next';
function App() {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('userGreeting', { firstName: user.firstName })}</p>
<p>{t('newMessages', { count: messageCount })}</p>
<p>{t('description')}</p>
<p>
<Trans i18nKey="termsText">
I agree to the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy Policy</a>.
</Trans>
</p>
<button onClick={() => alert(t('clickMe'))}>{t('clickMe')}</button>
<button onClick={() => i18n.changeLanguage('en')}>EN</button>
<button onClick={() => i18n.changeLanguage('fr')}>FR</button>
</div>
);
}
Run your app (npm run dev) and click the EN / FR buttons. When you run the app in French, the layout should look exactly the same as in English — only the language of the text changes.
Commit Reference: Add a language switcher to test the translation
Next Steps: Optimizing Your Translated React App
Your React app now has full multi-language support. If you’re deploying to production or supporting many languages, these optimizations can improve performance and user experience.
Auto-Detect User Language
By default, your app starts in English and requires users to switch languages manually. With automatic language detection, i18next checks the browser, URL, or saved preferences and loads the correct language instantly.
- Install the language detector:
npm install i18next-browser-languagedetector- Update
src/i18n/index.ts:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
type TranslationResources = string | { [k: string]: TranslationResources } | TranslationResources[];
const modules = import.meta.glob<{default: Record<string, TranslationResources>}>('./locales/*.json', { eager: true })
const resources: Record<string, { translation: Record<string, TranslationResources> }> = {};
for (const path in modules) {
const lang = path.match(/\.\/locales\/(.*)\.json$/)?.[1];
if (lang) {
resources[lang] = { translation: modules[path].default };
}
}
i18n
.use(LanguageDetector) // detect browser/localStorage/etc.
.use(initReactI18next)
.init({
resources,
fallbackLng: "en",
supportedLngs: ["en", "fr", "de", "ar"], // adjust to your languages
interpolation: { escapeValue: false },
detection: {
order: ["querystring", "localStorage", "cookie", "navigator"],
caches: ["localStorage", "cookie"],
}
});
export default i18n;
Users will now automatically see the app in their preferred language when available.
Lazy-Load Translations
Right now, i18next loads every translation file at startup. For apps with many languages, lazy loading reduces your initial bundle size by downloading only the language files the user needs.
- Install the HTTP backend:
npm install i18next-http-backend- Update
src/i18n/index.ts:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
i18n
.use(Backend) // load JSON over HTTP
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
supportedLngs: ["en", "fr"],
interpolation: { escapeValue: false },
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json"
}
});
export default i18n;
- Move your translation files to the public directory:
public/
locales/
en/
translation.json
fr/
translation.jsonWith this structure, i18next will fetch translation files over HTTP instead of bundling them. When a user switches to French, i18next fetches /locales/fr/translation.json on-demand.

Translate Your React App with PTC
Your app is ready for AI localization. Use PTC to translate your JSON files automatically. Your first 2,500 words are free.


Translate React with AI
Get context-aware translations in minutes
Upload files, or automate via API or Git integration