DEV Community

Cover image for How to Make Any React App Multilingual — From Local App to Global Product
Keshav Chauhan
Keshav Chauhan

Posted on

How to Make Any React App Multilingual — From Local App to Global Product

🚀 The Problem That Changed Everything

Picture this: You've just launched your beautiful React app. Users love it. The metrics look great. Then you check your analytics and realize something shocking — 67% of your potential users bounced within 10 seconds.

Why?

They couldn't read it.

This happened to me when I built a campus event platform. We had users from diverse linguistic backgrounds — Hindi, Bengali, Tamil, Telugu speakers — but our app only spoke English. We weren't just losing users; we were actively excluding them.

That's when I realized: Multilingual support isn't a feature. It's a growth decision.

This is the story of how we transformed our local campus tool into a platform that serves thousands of users across language barriers. And I'm going to show you exactly how to do it — step by step, with real code, real patterns, and real product thinking.

💡 What You'll Build: By the end of this article, you'll have a production-ready multilingual React app with language switching, AI-powered translation, and scalability patterns used by global products.


🌍 Why Multilingual Support is a Product Game-Changer

Let's talk numbers and reality:

The Global Internet Speaks Many Languages

  • Only 25.9% of internet users speak English as their primary language
  • 75% of users prefer to buy products in their native language
  • Apps with multilingual support see 2-3x higher engagement in non-English markets
  • 89% of users are more likely to return to a site if content is in their language

It's Not Just Translation — It's Trust

When users see content in their native language, something psychological happens:

  • They feel included and respected
  • They trust your product more
  • They engage deeper and stay longer
  • They recommend it to their community

Localization vs Translation: Understanding the Difference

Aspect Translation Localization
Scope Word-for-word conversion Cultural adaptation
Focus Language only Language + context + UX
Example "Submit" → "सबमिट करें" "Submit" → "जमा करें" (more natural)
Dates 02/08/2026 08/02/2026 (DD/MM/YYYY in India)
Currency $100 ₹8,300

Real Impact: When we added Hindi and Bengali support to our campus platform:

  • User signups increased by 183% in the first month
  • Session duration went from 2.3 mins to 7.1 mins
  • Support tickets about "confusion" dropped by 64%

🧠 How Multilingual Systems Actually Work

Before we code, let's understand the architecture. Multilingual systems are simpler than you think.

The Core Concept: Keys, Not Strings

Instead of hardcoding text:

// ❌ Don't do this
<button>Submit</button>
Enter fullscreen mode Exit fullscreen mode

You use translation keys:

// ✅ Do this
<button>{t('common.submit')}</button>
Enter fullscreen mode Exit fullscreen mode

The Translation Flow

┌─────────────────────────────────────────────────────────┐
│  1. User selects language (Hindi)                       │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  2. App loads corresponding translation file             │
│     (hi.json: {"common.submit": "जमा करें"})            │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  3. Translation library replaces keys with values        │
│     t('common.submit') → "जमा करें"                     │
└────────────────┬────────────────────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────────────────────┐
│  4. React re-renders UI with translated text            │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Understanding Locales

A locale is a language + region identifier:

  • en = English (generic)
  • en-US = English (United States)
  • en-GB = English (United Kingdom)
  • hi = Hindi
  • bn = Bengali

For most apps, language-only codes (en, hi, bn) work fine. Use region codes when you need specific variations (like pt-BR for Brazilian Portuguese vs pt-PT for European Portuguese).

Static vs Dynamic Translation

Static Translation: UI elements that never change

  • Button labels, navigation menus, form fields
  • Stored in JSON files, loaded at runtime
  • Fast and cacheable

Dynamic Translation: User-generated or database content

  • Event descriptions, user bios, posts
  • Translated via AI APIs (like Lingo.dev)
  • Stored in database with locale tags

🛠️ Our Tech Stack

Here's what we're building with:

Core Technologies

Tool Purpose Why This One?
React Frontend framework Component-based, perfect for dynamic UIs
i18next Translation library Most mature, 10k+ stars, production-proven
react-i18next React bindings for i18next Hooks-based API, SSR support
i18next-browser-languagedetector Auto-detect user language UX boost — users see their language instantly

Optional Power-Ups

  • Lingo.dev — AI translation for dynamic content
  • i18next-http-backend — Load translations from API/CDN
  • React Context — Manage language state globally

Why i18next Over react-intl?

Both are excellent, but i18next wins for:

  • Flexibility: Works outside React (Node.js, vanilla JS)
  • Ecosystem: Massive plugin ecosystem
  • Interpolation: Advanced placeholder and formatting features
  • TypeScript: Better type safety out of the box

⚙️ Step-by-Step Implementation

Alright, let's build this thing. I'll show you the exact code we used in production.

Step 1: Install Dependencies

First, let's grab what we need:

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

Or with yarn:

yarn add i18next react-i18next i18next-browser-languagedetector
Enter fullscreen mode Exit fullscreen mode

What each package does:

  • i18next — Core translation engine
  • react-i18next — React hooks and components
  • i18next-browser-languagedetector — Auto-detect browser language

Step 2: Folder Structure

Organization matters. Here's our production structure:

src/
├── i18n/
│   ├── locales/
│   │   ├── en/
│   │   │   ├── common.json
│   │   │   ├── dashboard.json
│   │   │   └── errors.json
│   │   ├── hi/
│   │   │   ├── common.json
│   │   │   ├── dashboard.json
│   │   │   └── errors.json
│   │   └── bn/
│   │       ├── common.json
│   │       ├── dashboard.json
│   │       └── errors.json
│   └── config.js
├── components/
│   ├── LanguageSwitcher.jsx
│   └── ...
└── App.jsx
Enter fullscreen mode Exit fullscreen mode

Why namespace by feature?

  • Easier to maintain as app grows
  • Lazy load only what you need
  • Different teams can own different namespaces

Step 3: Create Translation Files

Let's create our translation JSON files. Start with common UI elements.

src/i18n/locales/en/common.json

{
  "welcome": "Welcome to EventHub",
  "tagline": "Discover amazing events on campus",
  "buttons": {
    "submit": "Submit",
    "cancel": "Cancel",
    "save": "Save",
    "login": "Log In",
    "signup": "Sign Up"
  },
  "navigation": {
    "home": "Home",
    "events": "Events",
    "profile": "Profile",
    "settings": "Settings"
  },
  "errors": {
    "required": "This field is required",
    "invalid_email": "Please enter a valid email",
    "network_error": "Network error. Please try again."
  }
}
Enter fullscreen mode Exit fullscreen mode

src/i18n/locales/hi/common.json

{
  "welcome": "EventHub में आपका स्वागत है",
  "tagline": "कैंपस पर अद्भुत इवेंट्स खोजें",
  "buttons": {
    "submit": "जमा करें",
    "cancel": "रद्द करें",
    "save": "सहेजें",
    "login": "लॉग इन करें",
    "signup": "साइन अप करें"
  },
  "navigation": {
    "home": "होम",
    "events": "इवेंट्स",
    "profile": "प्रोफ़ाइल",
    "settings": "सेटिंग्स"
  },
  "errors": {
    "required": "यह फ़ील्ड आवश्यक है",
    "invalid_email": "कृपया एक मान्य ईमेल दर्ज करें",
    "network_error": "नेटवर्क त्रुटि। कृपया पुनः प्रयास करें।"
  }
}
Enter fullscreen mode Exit fullscreen mode

src/i18n/locales/bn/common.json

{
  "welcome": "EventHub-এ স্বাগতম",
  "tagline": "ক্যাম্পাসে অসাধারণ ইভেন্ট আবিষ্কার করুন",
  "buttons": {
    "submit": "জমা দিন",
    "cancel": "বাতিল",
    "save": "সংরক্ষণ করুন",
    "login": "লগ ইন করুন",
    "signup": "সাইন আপ করুন"
  },
  "navigation": {
    "home": "হোম",
    "events": "ইভেন্টসমূহ",
    "profile": "প্রোফাইল",
    "settings": "সেটিংস"
  },
  "errors": {
    "required": "এই ক্ষেত্রটি আবশ্যক",
    "invalid_email": "দয়া করে একটি বৈধ ইমেল লিখুন",
    "network_error": "নেটওয়ার্ক ত্রুটি। অনুগ্রহ করে আবার চেষ্টা করুন।"
  }
}
Enter fullscreen mode Exit fullscreen mode

💡 Pro Tip: Use nested keys for organization, but don't go more than 3 levels deep. common.buttons.submit is clear. app.ui.forms.buttons.actions.submit is overkill.


Step 4: Configure i18next

Now let's set up the translation engine.

src/i18n/config.js

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

// Import translations
import enCommon from './locales/en/common.json';
import hiCommon from './locales/hi/common.json';
import bnCommon from './locales/bn/common.json';

// Translation resources
const resources = {
  en: {
    common: enCommon,
  },
  hi: {
    common: hiCommon,
  },
  bn: {
    common: bnCommon,
  },
};

i18n
  // Detect user language
  .use(LanguageDetector)
  // Pass the i18n instance to react-i18next
  .use(initReactI18next)
  // Initialize i18next
  .init({
    resources,
    fallbackLng: 'en', // Fallback language if translation missing
    defaultNS: 'common', // Default namespace
    interpolation: {
      escapeValue: false, // React already escapes values
    },
    detection: {
      // Order of language detection methods
      order: ['localStorage', 'navigator'],
      caches: ['localStorage'], // Cache user's language choice
    },
  });

export default i18n;
Enter fullscreen mode Exit fullscreen mode

Configuration Breakdown:

  • fallbackLng: 'en' — If Hindi translation is missing a key, show English instead of breaking
  • defaultNS: 'common' — Default namespace, so you can write t('welcome') instead of t('common:welcome')
  • detection.order — Check localStorage first (user's saved preference), then browser language
  • detection.caches — Remember user's choice in localStorage

Step 5: Wrap Your React App

Now we initialize i18n when the app starts.

src/index.jsx or src/main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './i18n/config'; // Initialize i18n

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

That's it! Just import the config file. The initReactI18next plugin makes translations available to all components.


Step 6: Use Translations in Components

Time to make your UI multilingual.

src/components/HomePage.jsx

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

const HomePage = () => {
  const { t } = useTranslation('common');

  return (
    <div className="home-page">
      <h1>{t('welcome')}</h1>
      <p>{t('tagline')}</p>

      <div className="actions">
        <button className="btn-primary">
          {t('buttons.login')}
        </button>
        <button className="btn-secondary">
          {t('buttons.signup')}
        </button>
      </div>
    </div>
  );
};

export default HomePage;
Enter fullscreen mode Exit fullscreen mode

With Variables (Interpolation):

// In your JSON: "greeting": "Hello, name!"

const { t } = useTranslation();

return <h1>{t('greeting', { name: 'Keshav' })}</h1>;
// Output: "Hello, Keshav!" (or "नमस्ते, Keshav!" in Hindi)
Enter fullscreen mode Exit fullscreen mode

Pluralization:

// en/common.json
{
  "event_count": "count event",
  "event_count_plural": "count events"
}
Enter fullscreen mode Exit fullscreen mode
<p>{t('event_count', { count: 5 })}</p>
// Output: "5 events"

<p>{t('event_count', { count: 1 })}</p>
// Output: "1 event"
Enter fullscreen mode Exit fullscreen mode

Step 7: Build a Language Switcher UI

Let's give users control.

src/components/LanguageSwitcher.jsx

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

const languages = [
  { code: 'en', name: 'English', flag: '🇬🇧' },
  { code: 'hi', name: 'हिंदी', flag: '🇮🇳' },
  { code: 'bn', name: 'বাংলা', flag: '🇧🇩' },
];

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

  const changeLanguage = (langCode) => {
    i18n.changeLanguage(langCode);
  };

  return (
    <div className="language-switcher">
      <select
        value={i18n.language}
        onChange={(e) => changeLanguage(e.target.value)}
        className="language-select"
      >
        {languages.map((lang) => (
          <option key={lang.code} value={lang.code}>
            {lang.flag} {lang.name}
          </option>
        ))}
      </select>
    </div>
  );
};

export default LanguageSwitcher;
Enter fullscreen mode Exit fullscreen mode

Fancy Toggle Version:

const LanguageSwitcher = () => {
  const { i18n } = useTranslation();
  const [isOpen, setIsOpen] = React.useState(false);

  return (
    <div className="language-dropdown">
      <button 
        className="current-language"
        onClick={() => setIsOpen(!isOpen)}
      >
        <span className="flag">
          {languages.find(l => l.code === i18n.language)?.flag}
        </span>
        <span className="name">
          {languages.find(l => l.code === i18n.language)?.name}
        </span>
        <span className="arrow"></span>
      </button>

      {isOpen && (
        <div className="dropdown-menu">
          {languages.map((lang) => (
            <button
              key={lang.code}
              className={`dropdown-item ${i18n.language === lang.code ? 'active' : ''}`}
              onClick={() => {
                i18n.changeLanguage(lang.code);
                setIsOpen(false);
              }}
            >
              <span className="flag">{lang.flag}</span>
              <span className="name">{lang.name}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Add to your navbar:

import LanguageSwitcher from './components/LanguageSwitcher';

const Navbar = () => {
  const { t } = useTranslation();

  return (
    <nav className="navbar">
      <div className="nav-links">
        <a href="/">{t('navigation.home')}</a>
        <a href="/events">{t('navigation.events')}</a>
        <a href="/profile">{t('navigation.profile')}</a>
      </div>
      <LanguageSwitcher />
    </nav>
  );
};
Enter fullscreen mode Exit fullscreen mode

🎥 See It In Action

Here's what the experience looks like for users:

Language Switching Demo

laguage Switching

Watch the entire UI update instantly when switching from English to Hindi

UI Translation in Real-Time

UI Translation

UI Translation Demo

Every button, label, and message adapts to the selected language

Mobile Multilingual Experience

Mobile Multilingual

Mobile Multilingual View

Responsive design + multilingual = accessible to everyone

Dashboard Translation

Dashboard

Dashboard Translation

Complex interfaces like dashboards stay organized in any language

Adding New Languages

Languages

Adding New Language

Scalable architecture makes it easy to add more languages


🤖 AI-Powered Dynamic Translation

Static UI translations are great, but what about user-generated content?

The Dynamic Content Challenge

Your app probably has:

  • Event descriptions written by organizers
  • User bios in their preferred language
  • Comments and posts from the community
  • Notifications generated by the system

You can't pre-translate these. You need AI.

Integrating Lingo.dev for Dynamic Translation

Backend Setup (Node.js/Express example)

import Lingo from '@lingo-dev/sdk';

const lingo = new Lingo({
  apiKey: process.env.LINGO_API_KEY,
});

// Translate event description
app.post('/api/events', async (req, res) => {
  const { title, description, language } = req.body;

  // Store original content
  const event = await Event.create({
    title_en: title,
    description_en: description,
    created_by: req.user.id,
  });

  // Generate translations
  const translations = await lingo.translate({
    text: [title, description],
    targetLanguages: ['hi', 'bn', 'ta', 'te'],
    sourceLanguage: language || 'en',
  });

  // Store translations
  await event.update({
    title_hi: translations.hi[0],
    description_hi: translations.hi[1],
    title_bn: translations.bn[0],
    description_bn: translations.bn[1],
    // ... other languages
  });

  res.json({ success: true, event });
});
Enter fullscreen mode Exit fullscreen mode

Frontend: Fetch Content in User's Language

import { useTranslation } from 'react-i18next';

const EventCard = ({ event }) => {
  const { i18n } = useTranslation();
  const currentLang = i18n.language;

  // Get translation for current language, fallback to English
  const title = event[`title_${currentLang}`] || event.title_en;
  const description = event[`description_${currentLang}`] || event.description_en;

  return (
    <div className="event-card">
      <h3>{title}</h3>
      <p>{description}</p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Database Schema for Multilingual Content

CREATE TABLE events (
  id UUID PRIMARY KEY,

  -- English (source)
  title_en TEXT NOT NULL,
  description_en TEXT,

  -- Hindi
  title_hi TEXT,
  description_hi TEXT,

  -- Bengali
  title_bn TEXT,
  description_bn TEXT,

  -- Original language marker
  source_language VARCHAR(5) DEFAULT 'en',

  created_at TIMESTAMP DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

Alternative: JSON Column Approach

CREATE TABLE events (
  id UUID PRIMARY KEY,
  title JSONB, -- {"en": "...", "hi": "...", "bn": "..."}
  description JSONB,
  source_language VARCHAR(5) DEFAULT 'en',
  created_at TIMESTAMP DEFAULT NOW()
);
Enter fullscreen mode Exit fullscreen mode

Real-Time Translation Hook

For instant translations without page reload:

import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

const useTranslatedContent = (content) => {
  const { i18n } = useTranslation();
  const [translatedText, setTranslatedText] = useState(content);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const translateContent = async () => {
      if (i18n.language === 'en' || !content) {
        setTranslatedText(content);
        return;
      }

      setLoading(true);
      try {
        const response = await fetch('/api/translate', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            text: content,
            targetLang: i18n.language,
          }),
        });

        const { translatedText } = await response.json();
        setTranslatedText(translatedText);
      } catch (error) {
        console.error('Translation failed:', error);
        setTranslatedText(content); // Fallback to original
      } finally {
        setLoading(false);
      }
    };

    translateContent();
  }, [content, i18n.language]);

  return { translatedText, loading };
};

// Usage
const EventDescription = ({ description }) => {
  const { translatedText, loading } = useTranslatedContent(description);

  if (loading) return <p>Translating...</p>;

  return <p>{translatedText}</p>;
};
Enter fullscreen mode Exit fullscreen mode

💡 Pro Tips & Best Practices

Things I learned the hard way so you don't have to.

1. Always Have a Fallback Language

i18n.init({
  fallbackLng: 'en',
  // If translation missing in Hindi, show English
});
Enter fullscreen mode Exit fullscreen mode

Why it matters: Incomplete translations shouldn't break your UI. Users prefer seeing English over blank screens.


2. Lazy Load Translations for Performance

Don't load all languages upfront. Use i18next-http-backend:

import HttpBackend from 'i18next-http-backend';

i18n
  .use(HttpBackend)
  .init({
    backend: {
      loadPath: '/locales/lng/ns.json',
    },
    // Only load current language
    preload: [],
  });
Enter fullscreen mode Exit fullscreen mode

Impact: Initial bundle size reduced by 60-70% in our app.


3. Namespace Separation for Large Apps

const { t } = useTranslation(['common', 'dashboard']);

// From common namespace
<button>{t('common:buttons.save')}</button>

// From dashboard namespace
<h1>{t('dashboard:title')}</h1>
Enter fullscreen mode Exit fullscreen mode

Pattern:

  • common.json — Shared UI elements (buttons, labels)
  • dashboard.json — Dashboard-specific content
  • auth.json — Login/signup flows
  • errors.json — Error messages

4. RTL Language Support

For Arabic, Hebrew, Urdu:

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

const App = () => {
  const { i18n } = useTranslation();

  useEffect(() => {
    // RTL languages
    const rtlLanguages = ['ar', 'he', 'ur'];
    const isRTL = rtlLanguages.includes(i18n.language);

    document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
    document.documentElement.lang = i18n.language;
  }, [i18n.language]);

  return <div>...</div>;
};
Enter fullscreen mode Exit fullscreen mode

CSS adjustments:

/* Use logical properties for automatic RTL flipping */
.container {
  margin-inline-start: 20px; /* Becomes margin-right in RTL */
  padding-inline-end: 10px;  /* Becomes padding-left in RTL */
}
Enter fullscreen mode Exit fullscreen mode

5. SEO for Multilingual Apps

For better search rankings:

import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';

const EventPage = () => {
  const { i18n, t } = useTranslation();

  return (
    <>
      <Helmet>
        <html lang={i18n.language} />
        <title>{t('events.page_title')}</title>
        <meta name="description" content={t('events.page_description')} />

        {/* Alternate language versions */}
        <link rel="alternate" hrefLang="en" href="https://example.com/events" />
        <link rel="alternate" hrefLang="hi" href="https://example.com/hi/events" />
        <link rel="alternate" hrefLang="bn" href="https://example.com/bn/events" />
      </Helmet>

      {/* Page content */}
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

6. Context-Aware Translations

Same word, different context:

{
  "button.close": "Close",
  "door.close": "Closed",
  "relationship.close": "Close friend"
}
Enter fullscreen mode Exit fullscreen mode

Use descriptive keys, not generic ones.


7. Handle Missing Translations Gracefully

i18n.init({
  saveMissing: true, // Log missing keys
  missingKeyHandler: (lng, ns, key) => {
    console.warn(`Missing translation: ${lng}/${ns}/${key}`);
    // Send to analytics/monitoring
  },
});
Enter fullscreen mode Exit fullscreen mode

🧪 Testing Multilingual Apps

How do you QA 5 languages without speaking all of them?

1. Missing Key Detection

Create a test script:

import fs from 'fs';
import path from 'path';

const languages = ['en', 'hi', 'bn'];
const namespaces = ['common', 'dashboard'];

const findMissingKeys = () => {
  const enKeys = {};

  // Load all English keys
  namespaces.forEach(ns => {
    const content = fs.readFileSync(
      path.join(__dirname, `locales/en/${ns}.json`),
      'utf-8'
    );
    enKeys[ns] = JSON.parse(content);
  });

  // Check each language
  languages.slice(1).forEach(lang => {
    namespaces.forEach(ns => {
      const content = fs.readFileSync(
        path.join(__dirname, `locales/${lang}/${ns}.json`),
        'utf-8'
      );
      const langKeys = JSON.parse(content);

      // Find missing keys
      const missing = findMissing(enKeys[ns], langKeys);
      if (missing.length > 0) {
        console.log(`❌ ${lang}/${ns}: Missing keys:`, missing);
      }
    });
  });
};

const findMissing = (source, target, prefix = '') => {
  const missing = [];

  Object.keys(source).forEach(key => {
    const fullKey = prefix ? `${prefix}.${key}` : key;

    if (!(key in target)) {
      missing.push(fullKey);
    } else if (typeof source[key] === 'object') {
      missing.push(...findMissing(source[key], target[key], fullKey));
    }
  });

  return missing;
};

findMissingKeys();
Enter fullscreen mode Exit fullscreen mode

Run before deployment:

node scripts/check-translations.js
Enter fullscreen mode Exit fullscreen mode

2. Visual Regression Testing

Use Playwright or Cypress to screenshot each language:

import { test, expect } from '@playwright/test';

const languages = ['en', 'hi', 'bn'];

languages.forEach(lang => {
  test(`Homepage renders correctly in ${lang}`, async ({ page }) => {
    await page.goto('/');

    // Change language
    await page.selectOption('.language-select', lang);
    await page.waitForTimeout(500); // Wait for translation

    // Take screenshot
    await expect(page).toHaveScreenshot(`homepage-${lang}.png`);
  });
});
Enter fullscreen mode Exit fullscreen mode

3. Manual Testing Checklist

  • [ ] All UI elements translate
  • [ ] No layout breaks (especially with longer text)
  • [ ] Language switcher works from any page
  • [ ] Language persists on page reload
  • [ ] Fallback works if translation missing
  • [ ] Dynamic content translates (if applicable)
  • [ ] Forms validate in correct language
  • [ ] Error messages display in correct language

🌍 Scaling to Production

Adding More Languages

Once your architecture is solid, adding languages is easy:

  1. Create translation files:
mkdir src/i18n/locales/ta
cp src/i18n/locales/en/* src/i18n/locales/ta/
Enter fullscreen mode Exit fullscreen mode
  1. Translate the JSON files (manually or with AI)
  2. Update config:
import taCommon from './locales/ta/common.json';

const resources = {
  en: { common: enCommon },
  hi: { common: hiCommon },
  bn: { common: bnCommon },
  ta: { common: taCommon }, // Tamil added
};
Enter fullscreen mode Exit fullscreen mode
  1. Update language switcher:
const languages = [
  { code: 'en', name: 'English', flag: '🇬🇧' },
  { code: 'hi', name: 'हिंदी', flag: '🇮🇳' },
  { code: 'bn', name: 'বাংলা', flag: '🇧🇩' },
  { code: 'ta', name: 'தமிழ்', flag: '🇮🇳' }, // Tamil added
];
Enter fullscreen mode Exit fullscreen mode

That's it!


AI Translation Pipelines

Automate translation for new languages:

import Lingo from '@lingo-dev/sdk';
import fs from 'fs';

const lingo = new Lingo({ apiKey: process.env.LINGO_API_KEY });

const translateLanguage = async (targetLang) => {
  // Load English source
  const source = JSON.parse(
    fs.readFileSync('./locales/en/common.json', 'utf-8')
  );

  // Translate recursively
  const translated = await translateObject(source, targetLang);

  // Save
  fs.writeFileSync(
    `./locales/${targetLang}/common.json`,
    JSON.stringify(translated, null, 2)
  );

  console.log(`✅ Translated to ${targetLang}`);
};

const translateObject = async (obj, targetLang) => {
  const result = {};

  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === 'object') {
      result[key] = await translateObject(value, targetLang);
    } else {
      const response = await lingo.translate({
        text: value,
        targetLanguage: targetLang,
        sourceLanguage: 'en',
      });
      result[key] = response.translatedText;
    }
  }

  return result;
};

// Generate Tamil translations
translateLanguage('ta');
Enter fullscreen mode Exit fullscreen mode

CLI automation:

node scripts/translate.js --target ta
node scripts/translate.js --target te
node scripts/translate.js --target kn
Enter fullscreen mode Exit fullscreen mode

Translation Caching

For dynamic content, cache translations:

// Redis-based translation cache
import redis from 'redis';
const cache = redis.createClient();

const getTranslation = async (text, targetLang) => {
  const cacheKey = `translation:${targetLang}:${text.slice(0, 50)}`;

  // Check cache
  const cached = await cache.get(cacheKey);
  if (cached) return cached;

  // Translate
  const translated = await lingo.translate({ text, targetLang });

  // Cache for 7 days
  await cache.setex(cacheKey, 604800, translated);

  return translated;
};
Enter fullscreen mode Exit fullscreen mode

Impact:

  • 95% cache hit rate = 95% cost reduction
  • Sub-10ms response time for cached translations

🚀 Deployment & Performance

CDN Caching for Translation Files

Host translations on a CDN:

i18n.use(HttpBackend).init({
  backend: {
    loadPath: 'https://cdn.yourapp.com/locales/lng/ns.json',
    crossDomain: true,
  },
});
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Global edge caching — translations load fast worldwide
  • Version control/locales/v2/en/common.json
  • Independent updates — update translations without redeploying

Locale-Based Routing

For better SEO and user experience:

https://yourapp.com/en/events
https://yourapp.com/hi/events
https://yourapp.com/bn/events
Enter fullscreen mode Exit fullscreen mode

React Router setup:

import { useParams } from 'react-router-dom';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

const LocalizedApp = () => {
  const { lang } = useParams();
  const { i18n } = useTranslation();

  useEffect(() => {
    if (lang && i18n.language !== lang) {
      i18n.changeLanguage(lang);
    }
  }, [lang, i18n]);

  return <App />;
};

// Routes
<Routes>
  <Route path="/:lang/*" element={<LocalizedApp />} />
  <Route path="/*" element={<App />} />
</Routes>
Enter fullscreen mode Exit fullscreen mode

Environment-Based Configuration

// .env.development
REACT_APP_LINGO_API_KEY=dev_key_123
REACT_APP_SUPPORTED_LANGUAGES=en,hi

// .env.production
REACT_APP_LINGO_API_KEY=prod_key_xyz
REACT_APP_SUPPORTED_LANGUAGES=en,hi,bn,ta,te,kn,mr,gu,pa
Enter fullscreen mode Exit fullscreen mode
const supportedLanguages = process.env.REACT_APP_SUPPORTED_LANGUAGES.split(',');

i18n.init({
  supportedLngs: supportedLanguages,
  // ...
});
Enter fullscreen mode Exit fullscreen mode

Build Optimizations

Webpack config for code splitting:

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        i18n: {
          test: /[\\/]locales[\\/]/,
          name: 'i18n',
          chunks: 'all',
        },
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Result: Translation files loaded on-demand, not in main bundle.


🏁 The Product Impact: From Local to Global

Let me bring this back to why this matters.

Our Numbers After Going Multilingual

Metric Before After Change
User Signups 2,400/month 6,800/month +183%
Session Duration 2.3 mins 7.1 mins +209%
Bounce Rate 67% 31% -54%
Support Tickets 89/week 32/week -64%
User Retention (30-day) 23% 51% +122%

What Users Told Us

"Finally, an app that speaks my language. I can actually understand what's happening." — Priya, Hindi user

"I recommended this to my entire class because everyone can use it now." — Rahul, Bengali user

The Builder Lesson

Multilingual support isn't just a feature — it's a philosophy.

It says:

  • "We built this for you, not just at you"
  • "Your language and culture matter"
  • "Everyone deserves access"

When you build inclusively, you build better products. Period.


🎯 Quick Recap: What We Built

You now know how to:

✅ Set up i18next and React integration

✅ Organize translation files with namespaces

✅ Build language switchers

✅ Handle dynamic AI-powered translations

✅ Test multilingual apps

✅ Scale to dozens of languages

✅ Optimize for production

✅ Measure real product impact

Your Next Steps

  1. Start simple: Add 2-3 languages to your current project
  2. Get feedback: Ask users if they'd use other languages
  3. Iterate: Add languages based on demand, not assumptions
  4. Measure: Track engagement by language
  5. Share: Write about your experience

📚 Resources

Documentation

Tools

Community


💬 Let's Connect

Building something multilingual? Got questions? Want to share your experience?

I'd love to hear about it.

Find me:

  • GitHub: @Keshav833
  • LinkedIn: keshav1408

🙏 Final Thoughts

The internet was supposed to connect everyone. But language barriers keep it fragmented.

Every time you add a language to your app, you're giving someone access they didn't have before. You're saying "you belong here."

That's powerful.

So go build something that speaks to everyone. Not just in English, but in the languages that matter to your users.

Because a truly global product isn't just available globally.

It's accessible globally.


If this article helped you, share it with someone building their first multilingual app. Let's make the internet more inclusive, one translation at a time. 🌍


Tags: #React #i18n #Internationalization #WebDev #Multilingual #JavaScript #ProductEngineering #Accessibility #GlobalProducts #LingoDev

Top comments (2)

Collapse
 
sumitsaurabh927 profile image
Sumit Saurabh

Lovely!!!

Collapse
 
anandku06 profile image
Anand Kumar

Phenomenal!!!