10. Multi-Language Support
10.1 Supported Languages
| Language | Code | Native Name | Translation Keys | Status |
|---|---|---|---|---|
| English | en |
English | 45+ | Complete (Default) |
| Hindi | hi |
?????? | 45+ | Complete |
| Spanish | es |
Español | 45+ | Complete |
| French | fr |
Français | 45+ | Complete |
| German | de |
Deutsch | 45+ | Complete |
Translation Coverage: 45+ keys × 5 languages = 225+ translations
10.2 Translation System Architecture
File Structure
packages/desktop-app/src/
+-- i18n/
¦ +-- config.ts # i18n configuration
¦ +-- translations/
¦ ¦ +-- en.json # English translations
¦ ¦ +-- hi.json # Hindi translations
¦ ¦ +-- es.json # Spanish translations
¦ ¦ +-- fr.json # French translations
¦ ¦ +-- de.json # German translations
¦ +-- index.ts # Export all translations
+-- contexts/
+-- LanguageContext.tsx # Language state management
10.3 Translation Keys & Content
File: packages/desktop-app/src/i18n/translations/en.json
Complete English Translation File
{
"app": {
"title": "Currency Denomination Calculator",
"subtitle": "Optimize your cash distribution"
},
"navigation": {
"calculator": "Calculator",
"history": "History",
"bulkUpload": "Bulk Upload",
"settings": "Settings"
},
"calculator": {
"title": "Denomination Calculator",
"amountLabel": "Enter Amount",
"amountPlaceholder": "Enter amount...",
"currencyLabel": "Currency",
"modeLabel": "Optimization Mode",
"calculateButton": "Calculate",
"calculating": "Calculating...",
"results": {
"title": "Breakdown Results",
"totalNotes": "Total Notes",
"totalCoins": "Total Coins",
"totalDenominations": "Total Denominations",
"denomination": "Denomination",
"type": "Type",
"count": "Count",
"totalValue": "Total Value",
"copyButton": "Copy to Clipboard",
"exportCSV": "Export CSV",
"exportJSON": "Export JSON"
}
},
"history": {
"title": "Calculation History",
"quickAccess": "Quick Access",
"fullHistory": "Full History",
"filterByCurrency": "Filter by Currency",
"dateRange": "Date Range",
"clearFilters": "Clear Filters",
"noHistory": "No calculations yet",
"columns": {
"id": "ID",
"date": "Date",
"amount": "Amount",
"currency": "Currency",
"mode": "Mode",
"denoms": "Denominations",
"actions": "Actions"
},
"actions": {
"view": "View Details",
"delete": "Delete",
"export": "Export"
}
},
"bulkUpload": {
"title": "Bulk Upload & Processing",
"downloadTemplate": "Download CSV Template",
"dragDrop": "Drag & drop your file here",
"orClickBrowse": "or click to browse",
"chooseFile": "Choose File",
"supportedFormats": "Supported: CSV, PDF, Word, Images",
"selectedFile": "Selected File:",
"fileFormat": "Format:",
"fileSize": "Size:",
"removeFile": "Remove File",
"saveToHistory": "Save to history",
"uploadButton": "Upload & Process",
"processing": "Processing Data...",
"results": {
"processedFile": "Processed File:",
"processedAt": "Processed:",
"summary": {
"total": "Total",
"success": "Success",
"failed": "Failed",
"time": "Time"
},
"uploadAnother": "Upload Another",
"exportCSV": "Export CSV",
"exportJSON": "Export JSON"
}
},
"settings": {
"title": "Settings",
"appearance": {
"title": "Appearance",
"theme": "Theme",
"light": "Light",
"dark": "Dark",
"system": "System"
},
"language": {
"title": "Language & Region",
"label": "Language"
},
"preferences": {
"title": "Default Preferences",
"defaultCurrency": "Default Currency",
"defaultMode": "Default Optimization Mode",
"autoSave": "Auto-save to history"
},
"dataManagement": {
"title": "Data Management",
"historyStats": "Total calculations:",
"databaseSize": "Database size:",
"exportAll": "Export All History",
"clearAll": "Clear All History",
"resetSettings": "Reset All Settings"
},
"saveButton": "Save Settings",
"resetButton": "Reset"
},
"currencies": {
"INR": "Indian Rupee",
"USD": "US Dollar",
"EUR": "Euro",
"GBP": "British Pound"
},
"modes": {
"greedy": "Greedy (Standard)",
"balanced": "Balanced",
"minimize_large": "Minimize Large",
"minimize_small": "Minimize Small"
},
"errors": {
"amountRequired": "Amount is required",
"amountInvalid": "Invalid amount format",
"amountTooLarge": "Amount too large (max 15 digits)",
"amountNegative": "Amount must be greater than 0",
"fileRequired": "Please select a file",
"fileInvalid": "Unsupported file type",
"fileTooLarge": "File too large",
"uploadFailed": "Upload failed. Please try again.",
"networkError": "Network error. Please check your connection.",
"serverError": "Server error. Please contact support."
},
"success": {
"calculated": "Calculation completed successfully",
"saved": "Saved to history",
"copied": "Copied to clipboard",
"exported": "Exported successfully",
"uploaded": "File uploaded successfully",
"deleted": "Deleted successfully",
"settingsUpdated": "Settings updated"
}
}
Hindi Translation Sample
File: packages/desktop-app/src/i18n/translations/hi.json
{
"app": {
"title": "?????? ????????? ?????????",
"subtitle": "???? ???? ????? ?? ???????? ????"
},
"navigation": {
"calculator": "?????????",
"history": "??????",
"bulkUpload": "???? ?????",
"settings": "????????"
},
"calculator": {
"title": "????????? ?????????",
"amountLabel": "???? ???? ????",
"amountPlaceholder": "???? ???? ????...",
"currencyLabel": "??????",
"modeLabel": "??????? ???",
"calculateButton": "???? ????",
"calculating": "???? ?? ??? ??..."
}
}
10.4 Language Context Implementation
File: packages/desktop-app/src/contexts/LanguageContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import en from '../i18n/translations/en.json';
import hi from '../i18n/translations/hi.json';
import es from '../i18n/translations/es.json';
import fr from '../i18n/translations/fr.json';
import de from '../i18n/translations/de.json';
type Language = 'en' | 'hi' | 'es' | 'fr' | 'de';
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: string) => string;
}
const translations = { en, hi, es, fr, de };
const LanguageContext = createContext(undefined);
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [language, setLanguageState] = useState('en');
// Load saved language from localStorage
useEffect(() => {
const saved = localStorage.getItem('language') as Language;
if (saved && translations[saved]) {
setLanguageState(saved);
}
}, []);
// Save language changes
const setLanguage = (lang: Language) => {
setLanguageState(lang);
localStorage.setItem('language', lang);
};
// Translation function with nested key support
const t = (key: string): string => {
const keys = key.split('.');
let value: any = translations[language];
for (const k of keys) {
value = value?.[k];
}
return value || key; // Return key if translation not found
};
return (
{children}
);
};
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within LanguageProvider');
}
return context;
};
10.5 Usage in Components
Example 1: CalculatorPage.tsx
import { useLanguage } from '../contexts/LanguageContext';
const CalculatorPage = () => {
const { t } = useLanguage();
return (
{t('calculator.title')}
{results && (
{t('calculator.results.title')}
{t('calculator.results.totalNotes')}: {results.summary.total_notes}
{t('calculator.results.totalCoins')}: {results.summary.total_coins}
)}
);
};
Example 2: SettingsPage.tsx (Language Selector)
const SettingsPage = () => {
const { language, setLanguage, t } = useLanguage();
return (
{t('settings.language.title')}
{t('settings.language.description')}
);
};
Example 3: BulkUploadPage.tsx (Dynamic Messages)
const BulkUploadPage = () => {
const { t } = useLanguage();
const [uploadStatus, setUploadStatus] = useState<'idle' | 'uploading' | 'success' | 'error'>('idle');
const getStatusMessage = () => {
switch (uploadStatus) {
case 'uploading':
return t('bulkUpload.processing');
case 'success':
return t('success.uploaded');
case 'error':
return t('errors.uploadFailed');
default:
return t('bulkUpload.dragDrop');
}
};
return (
{t('bulkUpload.title')}
{getStatusMessage()}
);
};
Example 4: Error Messages
const validateAmount = (amount: string) => {
const { t } = useLanguage();
if (!amount) {
return { valid: false, error: t('errors.amountRequired') };
}
if (isNaN(Number(amount))) {
return { valid: false, error: t('errors.amountInvalid') };
}
if (Number(amount) <= 0) {
return { valid: false, error: t('errors.amountNegative') };
}
if (amount.length > 15) {
return { valid: false, error: t('errors.amountTooLarge') };
}
return { valid: true };
};
10.6 App-Level Integration
Wrap App with LanguageProvider
File: packages/desktop-app/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { LanguageProvider } from './contexts/LanguageContext';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
);
10.7 RTL (Right-to-Left) Support
Future Enhancement: Planned for Arabic/Hebrew support
Planned Implementation
const LanguageProvider: React.FC = ({ children }) => {
const [language, setLanguage] = useState('en');
// Detect RTL languages
const isRTL = ['ar', 'he'].includes(language);
useEffect(() => {
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
document.documentElement.lang = language;
}, [isRTL, language]);
return (
{children}
);
};
RTL CSS Support
/* Automatic RTL adjustments */
[dir="rtl"] .sidebar {
right: 0;
left: auto;
}
[dir="rtl"] .text-left {
text-align: right;
}
[dir="rtl"] .ml-2 {
margin-left: 0;
margin-right: 0.5rem;
}
10.8 Translation Management
Adding a New Language
Steps:
- Create new translation file:
src/i18n/translations/{code}.json - Copy structure from
en.json - Translate all 45+ keys
- Import in
LanguageContext.tsx - Add to
Languagetype andtranslationsobject - Add option to language selector in Settings
Translation Validation
// Utility to check for missing translations
function validateTranslations() {
const baseKeys = Object.keys(flattenObject(en));
const languages = ['hi', 'es', 'fr', 'de'];
languages.forEach(lang => {
const langKeys = Object.keys(flattenObject(translations[lang]));
const missing = baseKeys.filter(key => !langKeys.includes(key));
if (missing.length > 0) {
console.warn(`Missing translations in ${lang}:`, missing);
}
});
}
function flattenObject(obj: any, prefix = ''): Record {
return Object.keys(obj).reduce((acc, key) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(acc, flattenObject(obj[key], fullKey));
} else {
acc[fullKey] = obj[key];
}
return acc;
}, {} as Record);
}
10.9 Backend Language Support (Future)
Planned: Backend API responses can also be localized
Accept-Language Header
# FastAPI endpoint with language support
@router.get("/api/v1/metadata")
async def get_metadata(accept_language: str = Header(default='en')):
"""Return localized metadata."""
language = accept_language[:2] # Extract language code
metadata = {
'currencies': get_localized_currencies(language),
'modes': get_localized_modes(language)
}
return metadata
def get_localized_currencies(lang: str) -> Dict:
"""Get currency names in specified language."""
translations = {
'en': {'INR': 'Indian Rupee', 'USD': 'US Dollar', ...},
'hi': {'INR': '?????? ?????', 'USD': '??????? ????', ...},
# ...
}
return translations.get(lang, translations['en'])
? Section Complete
This section covers complete multi-language support including 5 languages (45+ keys each = 225+ translations), React Context implementation, nested key support, localStorage persistence, component usage examples, RTL support planning, and translation management utilities.