React Localization in 15 Minutes (Because Life's Too Short for Complex i18n)
So I was building this React app last month and needed to add Spanish support. Figured it'd be quick - just translate some strings, right?
Wrong.
Spent 3 hours reading docs for various i18n libraries, configuring webpack loaders, setting up namespaces, and still couldn't get a simple "Hello" to show up in Spanish. There had to be a better way.
The Problem with Most i18n Solutions
Don't get me wrong - libraries like react-i18next are powerful. But for a lot of projects, they're overkill:
- Complex setup with multiple config files
- Learning curve for their specific syntax
- Bundle size impact
- Over-engineered for simple use cases
Sometimes you just want to fetch some translations and render them. That's it.
A Simpler Approach
Here's what I ended up building instead. The whole integration took about 10 minutes:
import { useState, useEffect } from 'react';
function App() {
const [translations, setTranslations] = useState({});
const [locale, setLocale] = useState('en');
useEffect(() => {
// Fetch translations from your API
fetch(`/api/translations/${locale}`)
.then(res => res.json())
.then(data => setTranslations(data));
}, [locale]);
return (
<div>
<nav>
<button onClick={() => setLocale('en')}>English</button>
<button onClick={() => setLocale('es')}>Español</button>
</nav>
<h1>{translations.welcome || 'Welcome'}</h1>
<p>{translations.description || 'Loading...'}</p>
</div>
);
}
That's it. No providers, no namespaces, no special syntax. Just fetch JSON and use it.
The Backend Part
For the /api/translations/${locale}
endpoint, I'm using Ling Arc which gives you clean JSON:
// pages/api/translations/[locale].js (Next.js)
export default async function handler(req, res) {
const { locale } = req.query;
const response = await fetch(
'https://api.lingarc.com/projects/abc123/translations/export',
{ headers: { 'X-API-Key': process.env.LINGARC_API_KEY } }
);
const translations = await response.json();
// Transform to flat object for the locale
const localized = translations.reduce((acc, item) => {
acc[item.key] = item.singular[locale] || item.singular.en;
return acc;
}, {});
res.json(localized);
}
You could use any translation service here - the point is keeping the React side simple.
Why This Works Better (For Me)
It's just JavaScript. No DSL to learn, no special components, no magic strings. If you know fetch()
and useState
, you're good.
Easy to debug. When something breaks, you can just console.log(translations)
and see exactly what you're working with.
Flexible. Want to cache translations? Add caching. Need pluralization? Handle it however you want. It's just data.
Small bundle. No extra dependencies beyond what React already gives you.
Making It Reusable
Here's a quick hook to clean it up:
function useTranslations(locale) {
const [translations, setTranslations] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/translations/${locale}`)
.then(res => res.json())
.then(data => {
setTranslations(data);
setLoading(false);
})
.catch(() => setLoading(false));
}, [locale]);
return { translations, loading };
}
// Usage
function MyComponent() {
const { translations, loading } = useTranslations('es');
if (loading) return <div>Loading...</div>;
return <h1>{translations.greeting}</h1>;
}
When NOT to Use This
This approach works great for:
- Small to medium apps
- Simple translation needs
- Teams that prefer explicit over magical
But stick with react-i18next if you need:
- Complex pluralization rules
- Interpolation with rich formatting
- Lazy loading of translation chunks
- ICU message format
Wrapping Up
Sometimes the simplest solution is just fetching JSON and using it. No shame in keeping things straightforward.
The whole setup took me 15 minutes vs the hours I spent wrestling with more complex solutions. Sometimes that's all you need.
Top comments (0)