← Back to Blog

How to Add Multi-Language Support to Your React App

Your React app works great. Users love it. But they're all reading it in one language.

Adding multi-language support (i18n) to React is often treated as a big project — complex routing, translation files to maintain, third-party libraries with steep learning curves. It doesn't have to be. In this tutorial, we'll add real-time translation to a React app using the SocketsIO Translation API, covering everything from a simple hook to a production-ready language switcher.

By the end you'll have:

Why Not Just Use react-i18next?

The standard approach is react-i18next with static JSON translation files. That works well when you have a fixed set of UI strings and a team of translators. But it breaks down when:

API-based translation handles all of these cases. The tradeoff is latency and cost — which is why caching matters.

Setup: Get Your API Key

Sign up at socketsio.com/signup — you get 500K free characters per month, no credit card required. Copy your API key from the dashboard.

For this tutorial, store it in your .env file:

REACT_APP_SOCKETSIO_KEY=your-api-key-here

Step 1: The Translation Service

First, create a simple service module that wraps the SocketsIO API. This keeps API calls out of your components and makes caching easy to add later.

// src/services/translation.js

const API_KEY = process.env.REACT_APP_SOCKETSIO_KEY;
const API_URL = 'https://api.socketsio.com/v1';

// Simple in-memory cache: "text|targetLang" → translated text
const cache = new Map();

export async function translate(text, targetLang, sourceLang = 'auto') {
  const cacheKey = `${text}|${targetLang}`;
  if (cache.has(cacheKey)) return cache.get(cacheKey);

  const res = await fetch(`${API_URL}/translate`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ q: text, target: targetLang, source: sourceLang }),
  });

  if (!res.ok) throw new Error(`Translation failed: ${res.status}`);

  const data = await res.json();
  const translated = data.data.translations[0].translatedText;

  cache.set(cacheKey, translated);
  return translated;
}

export async function detectLanguage(text) {
  const res = await fetch(`${API_URL}/detect`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
    },
    body: JSON.stringify({ q: text }),
  });
  const data = await res.json();
  return data.data.detections[0][0].language;
}

Step 2: The useTranslation Hook

Now wrap the service in a React hook. The hook takes a string and a target language, and returns the translated version along with loading/error state.

// src/hooks/useTranslation.js
import { useState, useEffect } from 'react';
import { translate } from '../services/translation';

export function useTranslation(text, targetLang) {
  const [translated, setTranslated] = useState(text);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    // No translation needed for default language
    if (!targetLang || targetLang === 'en') {
      setTranslated(text);
      return;
    }

    let cancelled = false;
    setLoading(true);

    translate(text, targetLang)
      .then(result => { if (!cancelled) { setTranslated(result); setLoading(false); } })
      .catch(err => { if (!cancelled) { setError(err); setLoading(false); } });

    return () => { cancelled = true; };
  }, [text, targetLang]);

  return { translated, loading, error };
}

Step 3: Language Context

To share the selected language across your entire app, use React Context:

// src/context/LanguageContext.js
import React, { createContext, useContext, useState } from 'react';

const LanguageContext = createContext();

export function LanguageProvider({ children }) {
  // Detect browser language, fall back to 'en'
  const browserLang = navigator.language?.split('-')[0] || 'en';
  const [language, setLanguage] = useState(
    localStorage.getItem('sio_lang') || browserLang
  );

  const changeLanguage = (lang) => {
    setLanguage(lang);
    localStorage.setItem('sio_lang', lang);
  };

  return (
    <LanguageContext.Provider value={{ language, changeLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
}

export const useLanguage = () => useContext(LanguageContext);

Wrap your app in the provider:

// src/index.js
import { LanguageProvider } from './context/LanguageContext';

ReactDOM.render(
  <LanguageProvider>
    <App />
  </LanguageProvider>,
  document.getElementById('root')
);

Step 4: The Language Switcher Component

// src/components/LanguageSwitcher.jsx
import { useLanguage } from '../context/LanguageContext';

const LANGUAGES = [
  { code: 'en', name: 'English' },
  { code: 'zh', name: '中文' },
  { code: 'es', name: 'Español' },
  { code: 'fr', name: 'Français' },
  { code: 'de', name: 'Deutsch' },
  { code: 'ja', name: '日本語' },
  { code: 'ko', name: '한국어' },
  { code: 'ar', name: 'العربية' },
  { code: 'pt', name: 'Português' },
  { code: 'ru', name: 'Русский' },
];

export function LanguageSwitcher() {
  const { language, changeLanguage } = useLanguage();

  return (
    <select
      value={language}
      onChange={e => changeLanguage(e.target.value)}
      style={{ background: '#1e2433', color: '#fff', border: '1px solid #2d3748', borderRadius: '6px', padding: '6px 12px' }}
    >
      {LANGUAGES.map(lang => (
        <option key={lang.code} value={lang.code}>{lang.name}</option>
      ))}
    </select>
  );
}

Step 5: Using It in Components

Now put it all together. Here's a product card that translates its content based on the selected language:

// src/components/ProductCard.jsx
import { useTranslation } from '../hooks/useTranslation';
import { useLanguage } from '../context/LanguageContext';

export function ProductCard({ title, description, price }) {
  const { language } = useLanguage();
  const { translated: tTitle, loading: l1 } = useTranslation(title, language);
  const { translated: tDesc, loading: l2 } = useTranslation(description, language);

  return (
    <div className="product-card">
      <h3 style={{ opacity: l1 ? 0.5 : 1 }}>{tTitle}</h3>
      <p style={{ opacity: l2 ? 0.5 : 1 }}>{tDesc}</p>
      <span className="price">{price}</span>
    </div>
  );
}

Step 6: Batch Translation for Performance

If you're translating many strings at once (e.g., an entire page of product listings), use the batch endpoint to avoid making dozens of individual API calls:

// Translate multiple strings in one API call
export async function translateBatch(texts, targetLang) {
  const uncached = texts.filter(t => !cache.has(`${t}|${targetLang}`));

  if (uncached.length > 0) {
    const res = await fetch(`${API_URL}/translate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
      body: JSON.stringify({ q: uncached, target: targetLang }),
    });
    const data = await res.json();

    uncached.forEach((text, i) => {
      cache.set(`${text}|${targetLang}`, data.data.translations[i].translatedText);
    });
  }

  return texts.map(t => cache.get(`${t}|${targetLang}`));
}

Cost Estimate

A typical React app with 50 UI strings averaging 50 characters each = 2,500 characters per language switch. At SocketsIO's pricing of $2/million characters, translating your entire UI into a new language costs $0.005 — half a cent. With caching, most users will never trigger a fresh API call at all.

ScenarioCharactersCost (SocketsIO)Cost (Google)
UI strings (50 strings)2,500$0.005$0.05
Product page (10 products)5,000$0.01$0.10
Blog post (~1,500 words)10,000$0.02$0.20
Full catalog (1,000 products)500,000$1.00$10.00

Summary

In under 30 minutes you've added real-time multi-language support to your React app:

The full code is available in the socketsio-js GitHub repository under examples/react-i18n/.

Ready to add multi-language support to your React app?

500K free characters/month. No credit card required. Works with React, Vue, Angular, or plain JS.

Get Your Free API Key →
← Back to Blog