How to Translate a React App with react-i18next
Set up react-i18next, translate en.json into 40+ languages with AI, then let PTC's (Private Translation Cloud) browser extension review the running React app on every release. This guide covers React internationalization end-to-end: setting up react-i18next, handling plurals and dynamic content, translating your en.json into 40+ languages with PTC, and switching languages at runtime. By the end you will have a working React localization setup ready to ship. For the standalone JSON translation tool overview, see translate JSON files online with AI.
What a localized React app looks like
A React app where:
- All user-facing strings live in JSON translation files under
src/i18n/locales/(orpublic/locales/). - A language switcher toggles between English, Spanish, French, German.
- Dynamic content (user names, counts, dates) is interpolated correctly per locale.
- Plurals follow each target language's rules.
- The translated
.jsonfiles are produced by PTC, not hand-written.
Step 1: Scaffold a React + TypeScript app with Vite
This guide uses a React + TypeScript app built with Vite. If you are adding react-i18next to an existing app, skip to Step 2.
npm create vite@latest react-localization-demo -- --template react-ts
cd react-localization-demo
npm install
npm run dev
This starts a development server and opens the default Vite + React page in your browser.
Step 2: Install react-i18next and replace hardcoded strings with t()
npm install i18next react-i18next
Create your React i18n 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"
}
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:
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;
useTranslation() returns:
t()- looks up a string by key.i18n- lets you change languages programmatically.
Step 3: Auto-load every locale JSON with import.meta.glob
Create src/i18n/index.ts to configure i18next and auto-load translation files. The import.meta.glob pattern means every .json file you add to src/i18n/locales/ is picked up automatically. No manual imports when you add new languages:
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;
Import i18n before your app renders. Open src/main.tsx and add the import:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import './i18n'; // <- add this
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
If you skip this step, t() will return translation keys instead of translated text.
Step 4: Interpolate variables, plurals, and links with i18next conventions
Interpolate variables with {{variableName}}
Use {{variableName}} syntax in your JSON and pass the value to t(). Update en.json:
{
"welcome": "Welcome",
"userGreeting": "Welcome back, {{firstName}}!",
"description": "This is a localization demo.",
"clickMe": "Click me"
}
const user = { firstName: 'Sarah' };
<p>{t('userGreeting', { firstName: user.firstName })}</p>
The second argument to t() is an object with the values you want to substitute. As many variables as you need.
Pluralize with _one / _other key suffixes
Use the _one / _other suffix pattern. The variable must be named count:
{
"newMessages_one": "You have {{count}} new message.",
"newMessages_other": "You have {{count}} new messages."
}
<p>{t('newMessages', { count: messageCount })}</p>
i18next selects the correct plural form automatically. It also handles languages with more than two plural forms (Polish, Arabic, Russian) without extra configuration.
Translate links and HTML with the Trans component
For translations containing HTML elements like links or bold text, use the Trans component:
{
"termsText": "I agree to the <1>Terms of Service</1> and <3>Privacy Policy</3>."
}
The <1> and <3> tags are index-based placeholders that map to child elements (counting from 0):
import { Trans } from 'react-i18next';
<Trans i18nKey="termsText">
I agree to the <a href="/terms">Terms of Service</a> and <a href="/privacy">Privacy Policy</a>.
</Trans>
This keeps your JSX elements in the component while still letting translators reorder surrounding text naturally.
Format dates, numbers, and currencies with Intl
Use Intl formatters rather than embedding format strings in your translations:
const formatDate = (date: Date, locale: string) =>
new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(date);
const formatCurrency = (amount: number, locale: string, currency: string) =>
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);
const { i18n } = useTranslation();
<p>{formatDate(new Date(), i18n.language)}</p>
<p>{formatCurrency(199.99, i18n.language, 'EUR')}</p>
Step 5: Translate the JSON files with PTC in 5 steps
You now have en.json and need to produce es.json, fr.json, de.json, etc. PTC is designed exactly for this.
- Start a PTC project and choose English as the source. The free trial covers 20,000 words into 2 languages, no credit card.
- Upload
en.json. PTC parses the nested structure, recognizes i18next placeholders ({{name}},{{count}}), and detects the plural suffixes (_one,_other). - Add a short description of your React app and its audience. PTC uses this context to match tone and terminology across every language.
- Pick target languages and confirm. PTC produces one translated
.jsonper target language, structurally identical to the source. Same keys, same nesting, same placeholders. Translated values. - Drop the files into
src/i18n/locales/. Because of theimport.meta.globauto-loading you set up in Step 3, the new files work immediately with no code changes.
Once you have translated your first file with PTC, you can move to an automated process. Connect your GitHub, GitLab, or Bitbucket repository via Git integration, or use the PTC API to plug translation into your CI/CD pipeline. New strings in en.json trigger automatic translation. PTC opens a PR with updated target-language files.
Step 6: Verify the setup with a language switcher
Add language buttons to verify your react-i18next setup is working:
import { useTranslation } from 'react-i18next';
function App() {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<button onClick={() => i18n.changeLanguage('en')}>EN</button>
<button onClick={() => i18n.changeLanguage('fr')}>FR</button>
</div>
);
}
i18n.changeLanguage() triggers a re-render of every component using useTranslation(), so the entire UI updates immediately. No page reload.
Auto-detect the user's language with i18next-browser-languagedetector
Install i18next-browser-languagedetector to automatically load the correct language from the user's browser settings, URL, or saved preferences:
npm install i18next-browser-languagedetector
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
supportedLngs: ['en', 'fr', 'de', 'ar'],
interpolation: { escapeValue: false },
detection: {
order: ['querystring', 'localStorage', 'cookie', 'navigator'],
caches: ['localStorage', 'cookie'],
},
});
Lazy-load translations with i18next-http-backend
By default, all translation files are bundled at build time. For apps with many languages, use i18next-http-backend to fetch only the language the user actually needs:
npm install i18next-http-backend
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
supportedLngs: ['en', 'fr'],
interpolation: { escapeValue: false },
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
});
Move your translation files to public/locales/ so they are served as static assets:
public/
locales/
en/translation.json
fr/translation.json
Switching to French now fetches /locales/fr/translation.json on demand rather than bundling it. The initial bundle stays lean regardless of how many languages you support.
Visual translation review of your running React app - ship without manual QA per release
After PTC translates your en.json, you still need to verify the running React app. A translated label may overflow a button in German. “Save” may translate as a noun in French when the UI needed a verb. A hardcoded English string outside t() will render untranslated regardless of how many languages you ship.
PTC's AI Visual QA replaces the manual QA pass. For browser-based apps like React, the right flavor is the browser extension. Install it once and record a short walkthrough of your React app's critical user flows (sign-in, main feature, settings). From then on, PTC replays the recording in every target language after every translation update, captures each screen, and inspects the rendered result:
- Fixes in the translation files when PTC controls them. PTC re-translates a wrong part of speech, picks a shorter synonym that fits a button, regenerates a plural form.
- Cursor / Claude Code prompts when the issue is in your component code. A hardcoded English string outside
t(), a sentence built by string concatenation instead oft('key', { var }), a missingTranscomponent for rich-text translations.
The result: review-as-a-CI-step. Every release ships with visual verification across every target language, with no human bottleneck.
Translate release notes, marketing pages, and customer emails
Your release notes, marketing pages, landing pages, and customer emails live outside en.json. PTC's Paste to Translate handles that copy in the same project. Paste the source text in the PTC dashboard, choose target languages, get back translations that use the same glossary and brand voice as your React UI strings.
Translate user comments, posts, and search results with the PTC API
User comments, posts, search results, and any other user-generated content your React app handles need translation as it arrives. The PTC REST API translates this content on demand with Bearer-token authentication, using the same glossary and brand voice as your en.json translations.
Now translate your en.json file
Start your free 30-day trial - 20,000 words into 2 languages, no credit card. Two paths:
- Manual: upload
src/i18n/locales/en.jsonon the JSON translation page. - CI-driven: connect your Git repo via the PTC API and translations happen automatically on every push.
Either way, install the browser extension and let AI Visual QA verify your running React app in every language before you ship.