DEV Community

Cover image for ๐—›๐—ผ๐˜„ ๐˜๐—ผ ๐—”๐—ฐ๐—ต๐—ถ๐—ฒ๐˜ƒ๐—ฒ ๐—Ÿ๐—ผ๐—ฐ๐—ฎ๐—น๐—ถ๐˜€๐—ฎ๐˜๐—ถ๐—ผ๐—ป ๐—ถ๐—ป ๐—ฅ๐—ฒ๐—ฎ๐—ฐ๐˜.๐—ท๐˜€
Kiran
Kiran

Posted on

๐—›๐—ผ๐˜„ ๐˜๐—ผ ๐—”๐—ฐ๐—ต๐—ถ๐—ฒ๐˜ƒ๐—ฒ ๐—Ÿ๐—ผ๐—ฐ๐—ฎ๐—น๐—ถ๐˜€๐—ฎ๐˜๐—ถ๐—ผ๐—ป ๐—ถ๐—ป ๐—ฅ๐—ฒ๐—ฎ๐—ฐ๐˜.๐—ท๐˜€

๐ŸŒ ๐—›๐—ผ๐˜„ ๐˜๐—ผ ๐—”๐—ฐ๐—ต๐—ถ๐—ฒ๐˜ƒ๐—ฒ ๐—Ÿ๐—ผ๐—ฐ๐—ฎ๐—น๐—ถ๐˜€๐—ฎ๐˜๐—ถ๐—ผ๐—ป ๐—ถ๐—ป ๐—ฅ๐—ฒ๐—ฎ๐—ฐ๐˜.๐—ท๐˜€ (Step by Step)

Your React app works perfectly โ€” in one language.
The moment you go global? Everything breaks. ๐Ÿ˜ถ

Here's the complete step-by-step localisation setup ๐Ÿ‘‡


๐Ÿง  ๐—ช๐—ต๐—ฎ๐˜ ๐—ช๐—ฒ'๐—ฟ๐—ฒ ๐—•๐˜‚๐—ถ๐—น๐—ฑ๐—ถ๐—ป๐—ด

๐Ÿ‘‰ Multi-language React app
๐Ÿ‘‰ Auto language detection
๐Ÿ‘‰ Dynamic language switching
๐Ÿ‘‰ Formatted dates, numbers & currencies

English โ†’ French โ†’ Japanese โ†’ Arabic (RTL)
All from one codebase. Zero rewrites.
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ 1๏ธโƒฃ ๐—œ๐—ป๐˜€๐˜๐—ฎ๐—น๐—น ๐——๐—ฒ๐—ฝ๐—ฒ๐—ป๐—ฑ๐—ฒ๐—ป๐—ฐ๐—ถ๐—ฒ๐˜€

npm install i18next react-i18next
npm install i18next-browser-languagedetector
npm install i18next-http-backend
Enter fullscreen mode Exit fullscreen mode

โœ” i18next โ€” core translation engine
โœ” react-i18next โ€” React bindings & hooks
โœ” browser-languagedetector โ€” auto detects user's language
โœ” http-backend โ€” lazy loads translation files


๐Ÿ—‚๏ธ 2๏ธโƒฃ ๐—™๐—ผ๐—น๐—ฑ๐—ฒ๐—ฟ ๐—ฆ๐˜๐—ฟ๐˜‚๐—ฐ๐˜๐˜‚๐—ฟ๐—ฒ

๐Ÿ‘‰ Keep translations outside your components

src/
โ”œโ”€โ”€ i18n/
โ”‚   โ”œโ”€โ”€ index.js          โ† i18n config
โ”‚   โ””โ”€โ”€ locales/
โ”‚       โ”œโ”€โ”€ en/
โ”‚       โ”‚   โ””โ”€โ”€ translation.json
โ”‚       โ”œโ”€โ”€ fr/
โ”‚       โ”‚   โ””โ”€โ”€ translation.json
โ”‚       โ””โ”€โ”€ ja/
โ”‚           โ””โ”€โ”€ translation.json
Enter fullscreen mode Exit fullscreen mode

โœ” One folder per language
โœ” Same key structure across all files
โœ” Easy to hand off to translators


๐Ÿ“ 3๏ธโƒฃ ๐—–๐—ฟ๐—ฒ๐—ฎ๐˜๐—ฒ ๐—ง๐—ฟ๐—ฎ๐—ป๐˜€๐—น๐—ฎ๐˜๐—ถ๐—ผ๐—ป ๐—™๐—ถ๐—น๐—ฒ๐˜€

๐Ÿ‘‰ Same keys, different values per language

// locales/en/translation.json
{
  "welcome": "Welcome, {{name}}!",
  "logout": "Logout",
  "items_one": "{{count}} item",
  "items_other": "{{count}} items",
  "errors": {
    "required": "This field is required"
  }
}

// locales/fr/translation.json
{
  "welcome": "Bienvenue, {{name}}!",
  "logout": "Se dรฉconnecter",
  "items_one": "{{count}} article",
  "items_other": "{{count}} articles",
  "errors": {
    "required": "Ce champ est obligatoire"
  }
}
Enter fullscreen mode Exit fullscreen mode

โœ” Use nested keys for grouping
โœ” {{variable}} syntax for dynamic values
โœ” _one / _other suffix for plurals


โš™๏ธ 4๏ธโƒฃ ๐—–๐—ผ๐—ป๐—ณ๐—ถ๐—ด๐˜‚๐—ฟ๐—ฒ ๐—ถ๐Ÿญ๐Ÿด๐—ป

// src/i18n/index.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';

i18n
  .use(HttpBackend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',       // default if language not found
    debug: false,

    detection: {
      order: ['localStorage', 'navigator'],
      cacheUserLanguage: true  // remembers user choice
    },

    backend: {
      loadPath: '/locales/{{lng}}/translation.json'
    },

    interpolation: {
      escapeValue: false       // React handles XSS
    }
  });

export default i18n;
Enter fullscreen mode Exit fullscreen mode

โœ” fallbackLng โ€” safety net if translation key missing
โœ” cacheUserLanguage โ€” persists language in localStorage
โœ” loadPath โ€” lazy loads only needed language file


๐Ÿš€ 5๏ธโƒฃ ๐—–๐—ผ๐—ป๐—ป๐—ฒ๐—ฐ๐˜ ๐˜๐—ผ ๐—ฅ๐—ฒ๐—ฎ๐—ฐ๐˜ ๐—”๐—ฝ๐—ฝ

// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './i18n/index.js';   // โ† import before App renders

ReactDOM.createRoot(document.getElementById('root'))
  .render(<App />);
Enter fullscreen mode Exit fullscreen mode

โœ” Import i18n before App renders
โœ” No Provider needed โ€” i18next is global
โœ” Translations load automatically on mount


๐Ÿ”ค 6๏ธโƒฃ ๐—จ๐˜€๐—ฒ ๐—ง๐—ฟ๐—ฎ๐—ป๐˜€๐—น๐—ฎ๐˜๐—ถ๐—ผ๐—ป๐˜€ ๐—ถ๐—ป ๐—–๐—ผ๐—บ๐—ฝ๐—ผ๐—ป๐—ฒ๐—ป๐˜๐˜€

import { useTranslation } from 'react-i18next';

function Dashboard() {
  const { t } = useTranslation();

  return (
    <div>
      {/* Basic translation */}
      <h1>{t('welcome', { name: 'John' })}</h1>

      {/* Nested key */}
      <span>{t('errors.required')}</span>

      {/* Pluralisation โ€” auto picks _one or _other */}
      <p>{t('items', { count: 5 })}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

โœ” t('key') โ€” translate a string
โœ” t('key', { var }) โ€” with dynamic values
โœ” t('key', { count }) โ€” automatic pluralisation


๐ŸŒ 7๏ธโƒฃ ๐—Ÿ๐—ฎ๐—ป๐—ด๐˜‚๐—ฎ๐—ด๐—ฒ ๐—ฆ๐˜„๐—ถ๐˜๐—ฐ๐—ต๐—ฒ๐—ฟ ๐—–๐—ผ๐—บ๐—ฝ๐—ผ๐—ป๐—ฒ๐—ป๐˜

import { useTranslation } from 'react-i18next';

const languages = [
  { code: 'en', label: 'English' },
  { code: 'fr', label: 'Franรงais' },
  { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž' },
  { code: 'ar', label: 'ุงู„ุนุฑุจูŠุฉ' }
];

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

  const changeLanguage = (code) => {
    i18n.changeLanguage(code);

    // Handle RTL languages
    document.dir = code === 'ar' ? 'rtl' : 'ltr';
  };

  return (
    <div>
      {languages.map(({ code, label }) => (
        <button
          key={code}
          onClick={() => changeLanguage(code)}
          className={i18n.language === code ? 'active' : ''}
        >
          {label}
        </button>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

โœ” i18n.changeLanguage() โ€” switches instantly
โœ” document.dir = 'rtl' โ€” flips layout for Arabic
โœ” Language saved in localStorage automatically


๐Ÿ“… 8๏ธโƒฃ ๐——๐—ฎ๐˜๐—ฒ๐˜€, ๐—ก๐˜‚๐—บ๐—ฏ๐—ฒ๐—ฟ๐˜€ & ๐—–๐˜‚๐—ฟ๐—ฟ๐—ฒ๐—ป๐—ฐ๐˜†

function PriceDisplay({ amount, date }) {
  const { i18n } = useTranslation();
  const locale = i18n.language;

  // Format currency
  const price = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: locale === 'ja' ? 'JPY' : 'USD'
  }).format(amount);

  // Format date
  const formattedDate = new Intl.DateTimeFormat(locale, {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }).format(date);

  return (
    <div>
      <p>{price}</p>
      <p>{formattedDate}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

โœ” Always use Intl API โ€” never manual formatting
โœ” Currency code changes per locale
โœ” Date format adapts automatically


โณ 9๏ธโƒฃ ๐—›๐—ฎ๐—ป๐—ฑ๐—น๐—ถ๐—ป๐—ด ๐—Ÿ๐—ผ๐—ฎ๐—ฑ๐—ถ๐—ป๐—ด ๐—ฆ๐˜๐—ฎ๐˜๐—ฒ

๐Ÿ‘‰ Translations load async โ€” handle the loading state

import { useTranslation } from 'react-i18next';
import { Suspense } from 'react';

// Wrap app in Suspense
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Dashboard />
    </Suspense>
  );
}

// Or check inside component
function Dashboard() {
  const { t, ready } = useTranslation();

  if (!ready) return <Spinner />;

  return <h1>{t('welcome')}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

โœ” ready โ€” true when translations are loaded
โœ” Suspense โ€” cleaner loading handling at app level
โœ” Never render untranslated content to users


๐Ÿšจ ๐—–๐—ผ๐—บ๐—บ๐—ผ๐—ป ๐— ๐—ถ๐˜€๐˜๐—ฎ๐—ธ๐—ฒ๐˜€

โŒ Hardcoding strings inside JSX
โŒ Not handling RTL layout for Arabic/Hebrew
โŒ Using English keys as translation keys t('Welcome back!')
โŒ Formatting dates manually instead of Intl API
โŒ Bundling all language files โ€” lazy load instead
โŒ Adding i18n after the app is built โ€” costs 10x more


๐Ÿ’ก ๐—ฆ๐—ฒ๐—ป๐—ถ๐—ผ๐—ฟ-๐—Ÿ๐—ฒ๐˜ƒ๐—ฒ๐—น ๐—œ๐—ป๐˜€๐—ถ๐—ด๐—ต๐˜

Translation is 10% of localisation. Dates, RTL, plurals, and culture are the other 90%.
Build the infrastructure on day one โ€” adding it later is a full rewrite.


๐ŸŽฏ ๐—œ๐—ป๐˜๐—ฒ๐—ฟ๐˜ƒ๐—ถ๐—ฒ๐˜„ ๐—ข๐—ป๐—ฒ-๐—Ÿ๐—ถ๐—ป๐—ฒ๐—ฟ

Localisation in React is achieved using react-i18next for translation management, the browser LanguageDetector for auto-detection, the Intl API for dates, numbers and currencies, and dynamic RTL switching via document.dir โ€” all configured once and consumed anywhere via the useTranslation hook.


#ReactJS #Localisation #i18n #Frontend #WebDevelopment #JavaScript #InterviewPrep #GlobalProduct #EngineeringMindset

Top comments (0)