I18n Advanced Guide
Master advanced patterns and techniques for the Zustic i18n library.
State Management
The i18n system uses Zustic core for state management. Understand the internal states:
States Overview
{
// Current language
lan: 'en' | 'es' | 'fr';
// Translation data
data: TranslationObject | null;
// Loading states
isInitialLoading: boolean; // First load
isUpdating: boolean; // Language switch
}
State Flow Diagram
┌─────────────────────────────────────────────┐
│ Initial App Load │
│ isInitialLoading = true │
│ data = null │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Load initialLan Translations │
│ Fetch/Load resource(initialLan) │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Data Loaded Successfully │
│ isInitialLoading = false │
│ data = { ... translations ... } │
└─────────────────────────────────────────────┘
│
│ updateTranslation('es')
▼
┌─────────────────────────────────────────────┐
│ Language Switch in Progress │
│ isUpdating = true │
│ lan = 'es' (updated) │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ New Language Translations Loaded │
│ isUpdating = false │
│ data = { ... new translations ... } │
└─────────────────────────────────────────────┘
Request De-duplication
The library automatically handles concurrent requests using request IDs:
// Multiple rapid language changes
updateTranslation('es'); // Request ID: 1
updateTranslation('fr'); // Request ID: 2
updateTranslation('de'); // Request ID: 3
// Only the last one (ID: 3) will update state
// Previous requests are ignored if they complete after
This prevents race conditions where slower requests overwrite faster ones:
// Example: Slow 'es' fetch vs fast 'fr' fetch
updateTranslation('es'); // Takes 2 seconds
updateTranslation('fr'); // Takes 100ms
// Even if 'es' finishes after 'fr', 'fr' wins
// The result will be French translations
Context Provider Pattern
Share translations across your app without prop drilling:
// TranslationProvider.tsx
'use client';
import { ReactNode, createContext, useContext } from 'react';
import { useTranslation } from './i18n';
type TranslationContextType = ReturnType<typeof useTranslation>;
const TranslationContext = createContext<TranslationContextType | null>(null);
export function TranslationProvider({ children }: { children: ReactNode }) {
const translation = useTranslation();
return (
<TranslationContext.Provider value={translation}>
{children}
</TranslationContext.Provider>
);
}
export function useTranslationContext() {
const context = useContext(TranslationContext);
if (!context) {
throw new Error('useTranslationContext must be used within TranslationProvider');
}
return context;
}
// Usage in app.tsx
'use client';
import { TranslationProvider } from './TranslationProvider';
import { Layout } from './Layout';
export default function App() {
return (
<TranslationProvider>
<Layout />
</TranslationProvider>
);
}
// In any component
export function Header() {
const { t } = useTranslationContext();
return <h1>{t('common.welcome')}</h1>;
}
Type-Safe Keys
Full TypeScript support ensures you never use wrong translation keys:
// ✅ Correct - IDE shows autocomplete
t('common.welcome')
// ❌ Error - TypeScript catches this
t('invalid.key') // Type error!
// ✅ Full nested key support
t('pages.home.title')
t('ui.forms.email.label')
Generic Component with Type Safety
interface LocalizedTextProps<T> {
keyPath: TranslationKey<T>;
className?: string;
}
export function LocalizedText<T>({ keyPath, className }: LocalizedTextProps<T>) {
const { t } = useTranslation();
return (
<span className={className}>
{t(keyPath)}
</span>
);
}
// Usage - fully type-safe
<LocalizedText keyPath="common.welcome" />
Performance Optimization
1. Lazy Load Translations
export const useTranslation = createI18n({
initialLan: 'en',
resource: async (lan) => {
// Only load when needed
const module = await import(`./translations/${lan}.js`);
return module.default;
},
});
2. Cache Fetched Translations
const cache = new Map<string, any>();
export const useTranslation = createI18n({
initialLan: 'en',
resource: async (lan) => {
// Return cached if available
if (cache.has(lan)) {
return cache.get(lan);
}
// Fetch and cache
const response = await fetch(`/api/translations/${lan}`);
const data = await response.json();
cache.set(lan, data);
return data;
},
});
3. Preload Common Languages
// In your app initialization
async function preloadTranslations() {
// Preload top 3 languages
await Promise.all([
fetch('/api/translations/en'),
fetch('/api/translations/es'),
fetch('/api/translations/fr'),
]);
}
// Call in app startup
preloadTranslations().catch(console.error);
4. Memoize Components
import { memo } from 'react';
const LanguageSwitcher = memo(function LanguageSwitcher() {
const { lan, updateTranslation, isUpdating } = useTranslation();
return (
<select
value={lan}
onChange={(e) => updateTranslation(e.target.value)}
disabled={isUpdating}
>
{/* Options */}
</select>
);
});
export default LanguageSwitcher;
SEO-Friendly URL Routes
Implement language in URL path:
// app/[lang]/page.tsx
'use client';
import { useTranslation } from '@/i18n';
import { useRouter } from 'next/navigation';
interface PageProps {
params: {
lang: string;
};
}
export default function Page({ params }: PageProps) {
const { lan, updateTranslation, t } = useTranslation();
const router = useRouter();
const handleLanguageChange = (newLan: string) => {
updateTranslation(newLan as any);
// Update URL
router.push(`/${newLan}`);
// Update meta
document.documentElement.lang = newLan;
};
return (
<main>
<h1>{t('pages.home.title')}</h1>
<select value={lan} onChange={(e) => handleLanguageChange(e.target.value)}>
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
</select>
</main>
);
}
Error Handling & Fallbacks
Graceful Error Handling
'use client';
import { useTranslation } from './i18n';
import { useState, useEffect } from 'react';
export function App() {
const { t, isInitialLoading, lan } = useTranslation();
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Monitor loading state
if (isInitialLoading) {
// Set timeout for failed loads
const timer = setTimeout(() => {
setError('Failed to load translations');
}, 5000);
return () => clearTimeout(timer);
}
}, [isInitialLoading]);
if (error) {
return (
<div className="error-screen">
<h1>⚠️ Error</h1>
<p>{error}</p>
<button onClick={() => window.location.reload()}>
Retry
</button>
</div>
);
}
if (isInitialLoading) {
return <div className="loading">⏳ Loading...</div>;
}
return <main>{/* Your app */}</main>;
}
Fallback Translations
const fallbackTranslations = {
en: {
common: { welcome: 'Welcome' },
},
};
export const useTranslation = createI18n({
initialLan: 'en',
resource: async (lan) => {
try {
const response = await fetch(`/api/translations/${lan}`);
if (!response.ok) throw new Error('Failed to load');
return response.json();
} catch (error) {
console.error(`Failed to load ${lan}, using fallback`);
// Return fallback
return fallbackTranslations[lan] || fallbackTranslations.en;
}
},
});
Debugging Translations
Log Missing Translations
// i18n.ts
function createDebugI18n<T, L>(params: I18nParams<T, L>) {
const hook = createI18n(params);
return () => {
const result = hook();
const originalT = result.t;
// Wrap translation function
result.t = (key: any) => {
const translation = originalT(key);
// If key equals translation, it's missing
if (translation === key) {
console.warn(`Missing translation: ${key}`);
}
return translation;
};
return result;
};
}
// Usage
export const useTranslation = createDebugI18n({
initialLan: 'en',
resource: (lan) => translations[lan],
});
Translation Coverage Report
export function reportTranslationCoverage(translations: any, language: string) {
const flatKeys: string[] = [];
function flatten(obj: any, prefix = '') {
for (const key in obj) {
const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'object' && value !== null) {
flatten(value, fullKey);
} else {
flatKeys.push(fullKey);
}
}
}
flatten(translations);
console.log(`${language}: ${flatKeys.length} translations`);
return flatKeys;
}
// Usage
const enKeys = reportTranslationCoverage(translations.en, 'English');
const esKeys = reportTranslationCoverage(translations.es, 'Spanish');
const missing = enKeys.filter(key => !esKeys.includes(key));
if (missing.length > 0) {
console.warn('Missing Spanish translations:', missing);
}
Advanced Patterns
Dynamic Language Based on Locale
// Get user's preferred language from browser
function getBrowserLanguage(): Language {
const lang = navigator.language.split('-')[0];
const supported: Language[] = ['en', 'es', 'fr'];
if (supported.includes(lang as Language)) {
return lang as Language;
}
return 'en'; // Default fallback
}
export const useTranslation = createI18n({
initialLan: getBrowserLanguage(),
resource: (lan) => translations[lan],
});
Pluralization Support
// translations.ts
export const en = {
items: {
singular: '{count} item',
plural: '{count} items',
},
};
// Helper function
function pluralize(key: string, count: number): string {
const { t } = useTranslation();
const isPlural = count !== 1;
const translationKey = isPlural ? `${key}.plural` : `${key}.singular`;
return t(translationKey as any).replace('{count}', String(count));
}
// Usage
<p>{pluralize('items', 5)}</p> // "5 items"
<p>{pluralize('items', 1)}</p> // "1 item"
Number & Date Localization
export function useLocalization() {
const { lan } = useTranslation();
const formatNumber = (num: number) => {
return new Intl.NumberFormat(lan).format(num);
};
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat(lan).format(date);
};
const formatCurrency = (amount: number, currency = 'USD') => {
return new Intl.NumberFormat(lan, {
style: 'currency',
currency,
}).format(amount);
};
return { formatNumber, formatDate, formatCurrency };
}
// Usage
function Product({ price }: { price: number }) {
const { formatCurrency } = useLocalization();
return <span>{formatCurrency(price)}</span>;
}
Server-Side Rendering (SSR)
Next.js SSR Example
// i18n.ts
import { createI18n } from 'zustic/i18n';
type Language = 'en' | 'es' | 'fr';
export const useTranslation = createI18n<any, Language>({
initialLan: 'en',
resource: async (lan) => {
// On server: import directly
if (typeof window === 'undefined') {
const translations = await import(`./translations/${lan}.json`);
return translations.default;
}
// On client: fetch
const response = await fetch(`/api/translations/${lan}`);
return response.json();
},
});
// page.tsx - Server Component
import { useTranslation } from '@/i18n';
export async function generateMetadata() {
const { t } = useTranslation();
return {
title: t('pages.home.title'),
description: t('pages.home.description'),
};
}
export default function Page() {
const { t } = useTranslation();
return (
<>
<h1>{t('pages.home.title')}</h1>
<p>{t('pages.home.description')}</p>
</>
);
}
Performance Metrics
Bundle Size
- Minified: ~1.8KB
- Gzipped: ~0.8KB
Runtime Performance
- Initial Load: Depends on resource function (typically 50-500ms)
- Language Switch:
<50ms - Translation Lookup: O(1) - instant
- Memory: ~100 bytes per translation set
Next Steps
- Best Practices - Learn dos and don'ts
- Examples - See more real-world use cases
- API Reference - Detailed API documentation