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:
- A
useTranslationReact hook that translates any string on demand - A language switcher component supporting 195 languages
- Client-side caching to avoid re-translating the same content
- Automatic user language detection
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:
- You have user-generated content that can't be pre-translated
- You're adding new languages after launch and don't want to redo all translation files
- You need to translate dynamic data from your API (product names, descriptions, reviews)
- You want to support 100+ languages without maintaining 100+ JSON files
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.
| Scenario | Characters | Cost (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:
- ✅
translate()service with in-memory caching - ✅
useTranslation()hook for any component - ✅
LanguageContextfor app-wide language state - ✅
LanguageSwitchercomponent - ✅ Batch translation for performance
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 →