The Performance Crisis in Modern i18n
If you're using i18next with TypeScript, you've probably felt the pain. Despite performance improvements, the reality is sobering:
tested on Apple M1
TypeScript compilation: Each 1,000 translation keys adds ~1 second totscbuild time
IDE responsiveness: Type hints slow down by 0.3+ seconds with large dictionaries
Bundle size: i18next weighs 41.6 kB (13.2 kB gzip) before you even add translations
Runtime performance: Custom DSL parsing becomes a bottleneck at scale
Real developers are feeling this pain:
"We had to remove i18n typing entirely due to CI memory overflow with ~3k translations" - Production developer
"Removing i18next improved our SSR performance by 3x without losing functionality" - Performance engineer
But here's the thing: modern JavaScript has everything we need built-in.
Why Go Native?
The Internationalization API has matured significantly. We have:
Intl.NumberFormatfor numbers, currencies, units
Intl.DateTimeFormatfor dates and times
Intl.PluralRulesfor pluralization logic
Intl.RelativeTimeFormatfor "2 days ago" formatting
These APIs are zero-cost, tree-shakeable, and blazing fast.
The Solution: A 5-File i18n System
Here's a complete internationalization system that's simpler, faster, and more maintainable than traditional libraries:
1. Language Detection & Management
// translations/lang.ts
import { cookie } from "../cookie";
const LANGS = {
en: "en",
ru: "ru",
} as const;
export type LANGS = keyof typeof LANGS;
const userLang = cookie.get("lang") ?? window.navigator.language;
export const LANG = userLang in LANGS ? (userLang as LANGS) : "en";
export const changeLang = (lang: string) => {
cookie.set("lang", lang);
window.location.reload();
};
// Native formatters - zero overhead
export const degree = new Intl.NumberFormat(LANG, {
style: "unit",
unit: "degree",
unitDisplay: "long",
});
2. Dynamic Translation Loading
// translations/index.ts
import { LANG } from "./lang";
export * from "./lang";
const vocabModule = {
en: () => import("./en"),
ru: () => import("./ru"),
} as const;
// Only load the language we need
export const { vocab: t } = await vocabModule[LANG]();
3. Type-Safe Translation Files
// translations/en.ts
import { degree } from "./lang";
export const vocab = {
hi: "Hello",
temperature: (n: number) => `Temperature is ${degree.format(n)}`,
};
export type Vocab = typeof vocab;
4. Simple Cookie Utility
// cookie.ts
const getCookieRec = () =>
Object.fromEntries(document.cookie.split('; ').map((rec) => rec.split('=')));
export const cookie = {
get(name: string): string | undefined {
return getCookieRec()[name];
},
set(name: string, value: string) {
document.cookie = `${name}=${value}`;
},
};
5. Usage in Components
// App.tsx
import { useState } from 'react';
import { LANG, changeLang, t } from './translations';
export function App() {
const [count, setCount] = useState(0);
return (
<main>
<p>{t.hi}</p>
<p>
<button onClick={() => setCount((s) => s + 1)}>{count}</button>
</p>
<p>{t.temperature(count)}</p>
<p>
<select value={LANG} onChange={(e) => changeLang(e.target.value)}>
{['ru', 'en'].map((lang) => (
<option key={lang} value={lang}>{lang}</option>
))}
</select>
</p>
</main>
);
}
The Benefits
✅ Blazing Fast Types: Direct object access, no complex mapping
✅ Zero Runtime Overhead: No DSL parsing, no library weight
✅ Automatic Code Splitting: Only load translations you need
✅ Full Type Safety: TypeScript infers everything automatically
✅ Native Formatting: Leverage browser APIs for numbers, dates, plurals
✅ Simple API: t.key instead of t('key')
✅ SSR out of the box: no additional setup for SSR
✅ Framework agnostic: use with Svelte, React, Vue or jQuery 😁
Advanced Patterns
Pluralization with Intl.PluralRules
For complex plural forms, use the native Intl.PluralRules API:
const pluralRules = new Intl.PluralRules('en-US');
const messages = {
zero: 'No items',
one: '1 item',
other: '{count} items'
};
function pluralize(count: number) {
const rule = pluralRules.select(count);
return messages[rule].replace('{count}', count.toString());
}
Namespace Support
Create subdirectories for different feature areas:
const vocabModule = {
en: () => import("./en"),
ru: () => import("./ru"),
} as const;
const authModule = {
en: () => import("./auth/en"),
ru: () => import("./auth/ru"),
} as const;
The Trade-off
The main downside: Translations live in code, making it harder for non-technical team members to edit them. This isn't always a problem - many teams prefer developer-controlled translations for better version control and review processes.
For teams that need non-technical editing, consider:
- Build-time generation from external sources
- Git-based workflows with translation management tools
- Hybrid approaches for different content types
Try It Out
This approach has transformed how I think about internationalization. Sometimes the best solution isn't the most popular one - it's the one that leverages what's already built into the platform.
What's your experience with i18n performance? Have you found other lightweight alternatives? Share your thoughts in the comments!
SOCIAL SHARE CARD GENERATOR