i18next and React application localization in 3 steps

Jakub Pomykała
Jakub Pomykała
Last updated: April 28, 20266 min read
i18next and React application localization in 3 steps

i18next is one of the most widely used internationalization libraries in the JavaScript ecosystem. Combined with react-i18next, it gives React applications a clean API for loading translations, handling pluralization, and switching languages at runtime.

This guide is a practical quick-start for plain React (Vite) projects. If you are building with Next.js App Router and need server components, middleware-based locale detection, and hreflang tags, see how to localize a React app using i18next — that guide covers the full App Router setup.

For the conceptual background before diving into library specifics, the complete technical guide to internationalization and software localization is a good foundation.

Create a sample project

npm create vite@latest simplelocalize-i18next-example -- --template react-ts
cd simplelocalize-i18next-example
npm install

Step 1: Install dependencies

npm install react-i18next i18next i18next-http-backend i18next-browser-languagedetector
  • i18next — core library
  • react-i18next — React bindings (hooks and components)
  • i18next-http-backend — fetches translation JSON files over HTTP at runtime
  • i18next-browser-languagedetector — detects the user's preferred language from the browser

Step 2: Configure i18next

Create src/i18n.ts and import it once at the app entry point:

// src/i18n.ts
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
 
i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    debug: import.meta.env.DEV,
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
    interpolation: {
      escapeValue: false, // React handles XSS
    },
  });
 
export default i18n;

Because this walkthrough uses a Vite template, import.meta.env.DEV is the correct development flag for the debug option.

Import it before any component renders:

// src/main.tsx
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import './i18n';
import App from './App';
 
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Suspense fallback={<div>Loading…</div>}>
      <App />
    </Suspense>
  </React.StrictMode>
);

The Suspense boundary is required: i18next-http-backend fetches translation files asynchronously, so the first render needs a fallback while the files load.

Create your source translation file at public/locales/en/translation.json:

{
  "home": "Home",
  "rooms": "Rooms",
  "contact": "Contact",
  "welcome": "Welcome to Pillow Hostel",
  "availableRooms_one": "Only {{count}} room left",
  "availableRooms_other": "{{count}} rooms available"
}

Step 3: Use translations in components

// src/App.tsx
import { useTranslation } from 'react-i18next';
 
function App() {
  const { t, i18n } = useTranslation();
 
  return (
    <div>
      <h1>{t('welcome')}</h1>
      <nav>
        <a href="/">{t('home')}</a>
        <a href="/rooms">{t('rooms')}</a>
        <a href="/contact">{t('contact')}</a>
      </nav>
 
      <p>{t('availableRooms', { count: 1 })}</p>
      <p>{t('availableRooms', { count: 5 })}</p>
 
      <div>
        <button onClick={() => i18n.changeLanguage('en')}>English</button>
        <button onClick={() => i18n.changeLanguage('es')}>Español</button>
        <button onClick={() => i18n.changeLanguage('pl')}>Polski</button>
      </div>
    </div>
  );
}
 
export default App;

i18n.changeLanguage triggers a fetch for the new language's JSON file if it has not been loaded yet, then re-renders all components using useTranslation.

That is the working app. Everything below is about scaling the workflow.

Managing translations with SimpleLocalize

Editing one JSON file per language by hand breaks down quickly. SimpleLocalize gives your team — developers, translators, reviewers — a shared workspace for managing translation keys and keeping files in sync.

Install the CLI

# macOS / Linux / Windows (WSL)
curl -s https://get.simplelocalize.io/2.10/install | bash
 
# npm
npm install @simplelocalize/cli

Configure simplelocalize.yml

# simplelocalize.yml
apiKey: YOUR_PROJECT_API_KEY
uploadFormat: single-language-json
uploadLanguageKey: en
uploadPath: ./public/locales/en/translation.json
uploadOptions:
  - REPLACE_TRANSLATION_IF_FOUND
downloadFormat: single-language-json
downloadLanguageKeys: ['es', 'pl', 'de']
downloadPath: ./public/locales/{lang}/translation.json

Daily workflow

# Push English source strings to SimpleLocalize
simplelocalize upload
 
# Translate in the editor or trigger auto-translation
simplelocalize auto-translate
 
# Pull completed translations back into the project
simplelocalize download

This keeps your API key out of client-side code and gives you explicit control over when new keys enter the translation platform. To run this automatically on every deploy, see localization workflow for developers: from CLI setup to CI/CD automation.

If you want translators to push updates to production without a code deployment, point i18next-http-backend at the SimpleLocalize Translation Hosting CDN instead of your local public/ directory.

Troubleshooting common issues

Even with a perfect setup, i18n can be tricky. Here are the most common issues developers face when using i18next with React.

1. Translations are not loading (404 Not Found)

If you see errors in the console saying GET /locales/en/translation.json 404, check your public folder structure.

  • Vite specific: Ensure your files are in public/locales/... and not src/public/....
  • Path check: If your app is hosted on a subpath (e.g., company.com/app/), you may need to adjust the loadPath in i18n.ts to include the base path.

2. "Keys" are showing instead of "Translations"

If your UI displays welcome instead of "Welcome to Pillow Hostel", i18next cannot find the key.

  • Namespace mismatch: By default, i18next looks for a file named translation.json. If you named your file common.json, you must specify it: useTranslation('common').
  • Case sensitivity: i18next keys are case-sensitive. Welcome and welcome are different keys.

3. Suspense errors or "flickering" text

If your app crashes with a "component suspended" error, it's because i18next-http-backend is still fetching the JSON files.

  • Solution: Ensure your <App /> is wrapped in a <Suspense> block in main.tsx.
  • Alternative: Set useSuspense: false in the react section of your i18n configuration, but be prepared to handle the ready state manually in your components.

4. CLI "Unauthorized" errors

If the SimpleLocalize CLI fails to upload or download:

  • API Key: Double-check your apiKey in simplelocalize.yml. It should be your Project API Key, not your personal profile token.
  • IP Whitelisting: If you are behind a corporate VPN, ensure your network allows outgoing traffic to api.simplelocalize.io.

5. Pluralization not working

If you pass a count but always see the same string:

  • Suffix check: Ensure your keys use the correct underscore suffix (e.g., _one and _other).
  • Language Rules: Remember that different languages have different plural rules. For example, Arabic has six different plural forms. Use the SimpleLocalize editor to see exactly which suffixes are required for your target languages.

What comes next

Once you have the basics working, you can explore more advanced features of i18next and react-i18next:

The live demo and complete source code are available on GitHub.

React Localization with i18next
React Localization with i18next
Jakub Pomykała
Jakub Pomykała
Founder of SimpleLocalize

Get started with SimpleLocalize

  • All-in-one localization platform
  • Web-based translation editor for your team
  • Auto-translation, QA-checks, AI and more
  • See how easily you can start localizing your product.
  • Powerful API, hosting, integrations and developer tools
  • Unmatched customer support
Start for free
No credit card required5-minute setup
"The product
and support
are fantastic."
Laars Buur|CTO
"The support is
blazing fast,
thank you Jakub!"
Stefan|Developer
"Interface that
makes any dev
feel at home!"
Dario De Cianni|CTO
"Excellent app,
saves my time
and money"
Dmitry Melnik|Developer