Frontend Engineering/
Lesson

Once you understand the concepts from the previous lesson, you need a library that actually implements them. React i18next is the overwhelming choice in production React apps, it handles everything from basic string lookup to complex plural rules, RTLWhat is rtl?Right-to-Left - a text direction used by Arabic, Hebrew, and Persian scripts that requires layout mirroring in addition to text translation. support, and lazy loadingWhat is lazy loading?Deferring the loading of a resource like an image or component until the moment it's actually needed, speeding up the initial page load.. Let's walk through how to use it properly.

Installation and setup

You need two packages: i18next (the core engine) and react-i18next (the React bindings). A couple of optional plugins make your life easier in production.

npm install react-i18next i18next

# Optional but useful in production
npm install i18next-http-backend          # loads translations via HTTP
npm install i18next-browser-languagedetector  # auto-detects user's language

Configuring i18nWhat is i18n?Short for internationalization (18 letters between i and n) - structuring your code so it can support multiple languages and regions.

Create a dedicated i18n.js file that you import once at the app root. This is where you wire up plugins and set your options.

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import enCommon from './locales/en/common.json';
import frCommon from './locales/fr/common.json';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    debug: process.env.NODE_ENV === 'development',
    defaultNS: 'common',
    resources: {
      en: { common: enCommon },
      fr: { common: frCommon }
    },
    detection: {
      order: ['localStorage', 'navigator', 'htmlTag'],
      caches: ['localStorage']
    },
    interpolation: {
      escapeValue: false, // React already handles XSS
    },
  });

export default i18n;

Wrapping your app

Import i18n.js in your entry point. The I18nextProvider makes the i18n instance available to every component in the tree.

import './i18n'; // side-effect import initializes i18next
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';

ReactDOM.render(
  <I18nextProvider i18n={i18n}>
    <App />
  </I18nextProvider>,
  document.getElementById('root')
);
02

The useTranslation hookWhat is hook?A special function in React (starting with "use") that lets you add state, side effects, or other React features to a component without writing a class.

This is what you'll use in 90% of your components. It returns t for translating strings and i18n for accessing the active language or switching languages.

import { useTranslation } from 'react-i18next';

function Welcome() {
  const { t, i18n } = useTranslation();

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('hello', { name: 'Marie' })}</p>

      <button onClick={() => i18n.changeLanguage('en')}>English</button>
      <button onClick={() => i18n.changeLanguage('fr')}>Français</button>
    </div>
  );
}

InterpolationWhat is interpolation?Inserting dynamic values like usernames or counts into a text template, replacing placeholders with actual data at display time.

Dynamic values go into your JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. as {{variableName}} placeholders. You pass the actual values as a second argument to t.

json
{
  "welcome": "Hello, {{name}}!",
  "balance": "You have {{amount}} {{currency}}"
}
function Greeting({ userName, balance }) {
  const { t } = useTranslation();

  return (
    <div>
      <p>{t('welcome', { name: userName })}</p>
      <p>{t('balance', { amount: balance, currency: '€' })}</p>
    </div>
  );
}

Plurals

English has two plural forms (one / other). Many languages have more. You define plural variants with suffixed keys and pass count, i18next picks the right one automatically based on the active language's plural rules.

json
{
  "item_zero": "No items",
  "item_one": "One item",
  "item_other": "{{count}} items"
}
function ItemCount({ count }) {
  const { t } = useTranslation();

  return <p>{t('item', { count })}</p>;
  // count=0  → "No items"
  // count=1  → "One item"
  // count=5  → "5 items"
}
Plural key suffixes (_zero, _one, _other, _few, _many) come from the CLDR plural rules standard. Arabic, for example, has six plural forms. i18next handles the lookup logic, you just need to define the keys.

Context

Context keys let you provide grammatically different translations for the same logical string. A common use case is gendered language.

json
{
  "greeting": "Hello",
  "greeting_male": "Hello sir",
  "greeting_female": "Hello madam"
}
function Greeting({ gender }) {
  const { t } = useTranslation();
  return <p>{t('greeting', { context: gender })}</p>;
}
03

The Trans component for JSXWhat is jsx?A syntax that lets you write HTML-like markup inside JavaScript, which React converts into actual DOM elements. in translations

Sometimes a translated string needs to contain markup, a link, a bold word, or an icon. You cannot do that with plain t(). The Trans component solves this by letting you embed real React elements inside the translation.

import { Trans } from 'react-i18next';

// locales/en/common.json
// { "terms": "I agree to the <1>terms of service</1> and <3>privacy policy</3>" }

function TermsAgreement() {
  return (
    <p>
      <Trans i18nKey="terms">
        I agree to the
        <a href="/terms">terms of service</a>
        and
        <a href="/privacy">privacy policy</a>
      </Trans>
    </p>
  );
}

The numbers in angle brackets (<1>, <3>) refer to the position of each child element. Translators work with the string including the numbered tags but never see your actual component code.

04

Language switching

Calling i18n.changeLanguage(code) is all it takes. React i18next re-renders any component that uses useTranslation automatically.

import { useTranslation } from 'react-i18next';

function LanguageSwitcher() {
  const { i18n } = useTranslation();

  const languages = [
    { code: 'en', label: 'English' },
    { code: 'fr', label: 'Français' },
    { code: 'es', label: 'Español' },
  ];

  return (
    <div>
      {languages.map(({ code, label }) => (
        <button
          key={code}
          onClick={() => i18n.changeLanguage(code)}
          disabled={i18n.language === code}
        >
          {label}
        </button>
      ))}
    </div>
  );
}
05

Lazy loadingWhat is lazy loading?Deferring the loading of a resource like an image or component until the moment it's actually needed, speeding up the initial page load. translations

For large apps with many languages, you do not want to bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. all translation files upfront. Load them on demand when the user switches languages.

async function loadLanguage(language) {
  const translations = await import(`./locales/${language}/common.json`);
  i18n.addResourceBundle(language, 'common', translations.default);
  i18n.changeLanguage(language);
}
Do not forget to call i18n.addResourceBundle before changeLanguage. If you switch first, i18next will briefly show fallback strings while the bundle loads.
06

Quick reference

FeatureAPIExample
Basic translationt('key')t('welcome')
Interpolationt('key', { var: val })t('hello', { name: 'Ana' })
Pluralst('key', { count: n })t('item', { count: 3 })
Contextt('key', { context: 'male' })t('greeting', { context: gender })
JSX in translation<Trans i18nKey="...">See Trans example above
Switch languagei18n.changeLanguage(code)i18n.changeLanguage('fr')
Current languagei18n.language'en'
javascript
// Complete React i18next example

// ========== i18n.js ==========
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import enCommon from './locales/en/common.json';
import enUser from './locales/en/user.json';
import frCommon from './locales/fr/common.json';
import frUser from './locales/fr/user.json';
import esCommon from './locales/es/common.json';
import esUser from './locales/es/user.json';

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        common: enCommon,
        user: enUser
      },
      fr: {
        common: frCommon,
        user: frUser
      },
      es: {
        common: esCommon,
        user: esUser
      }
    },
    fallbackLng: 'en',
    defaultNS: 'common',
    detection: {
      order: ['localStorage', 'navigator'],
      caches: ['localStorage']
    },
    interpolation: {
      escapeValue: false
    }
  });

export default i18n;

// ========== locales/fr/common.json ==========
{
  "app": {
    "title": "Mon Application",
    "welcome": "Bienvenue, {{name}} !",
    "items": {
      "zero": "Aucun article",
      "one": "Un article",
      "other": "{{count}} articles"
    }
  },
  "buttons": {
    "save": "Enregistrer",
    "cancel": "Annuler",
    "delete": "Supprimer",
    "loading": "Chargement..."
  },
  "errors": {
    "required": "Ce champ est requis",
    "email": "Veuillez entrer un email valide",
    "generic": "Une erreur est survenue"
  },
  "nav": {
    "home": "Accueil",
    "profile": "Profil",
    "settings": "Paramètres"
  }
}

// ========== App.jsx ==========
import { useTranslation, Trans } from 'react-i18next';
import './i18n';

function App() {
  return (
    <div>
      <Header />
      <Main />
      <Footer />
    </div>
  );
}

function Header() {
  const { t, i18n } = useTranslation('common');

  return (
    <header>
      <h1>{t('app.title')}</h1>
      <nav>
        <a href="/">{t('nav.home')}</a>
        <a href="/profile">{t('nav.profile')}</a>
        <button onClick={() => i18n.changeLanguage(i18n.language === 'en' ? 'fr' : 'en')}>
          {i18n.language === 'en' ? 'FR' : 'EN'}
        </button>
      </nav>
    </header>
  );
}

function ItemList({ count }) {
  const { t } = useTranslation('common');

  return (
    <div>
      <p>{t('app.items', { count })}</p>
      <button>{t('buttons.save')}</button>
    </div>
  );
}

function Terms() {
  return (
    <p>
      <Trans i18nKey="terms" ns="common">
        By continuing, you agree to our <1>terms</1> and <3>privacy policy</3>
      </Trans>
    </p>
  );
}

export default App;