import { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; // Supported languages export type Language = 'en' | 'es'; // Translation key type - allows nested keys like 'nav.home' type TranslationKey = string; // Translation values can be strings or nested objects type TranslationValue = string | { [key: string]: TranslationValue }; // Translations structure export type Translations = { [key: string]: TranslationValue; }; interface LanguageContextType { /** Current language */ language: Language; /** Set the current language */ setLanguage: (lang: Language) => void; /** Translate a key - supports dot notation for nested keys */ t: (key: TranslationKey, params?: Record) => string; /** Check if current language is RTL (for future Arabic support, etc.) */ isRTL: boolean; } const LanguageContext = createContext(undefined); // Import translations import en from '@/i18n/en'; import es from '@/i18n/es'; const translations: Record = { en, es }; /** * Get nested value from object using dot notation * e.g., getNestedValue({ nav: { home: 'Home' } }, 'nav.home') => 'Home' */ function getNestedValue(obj: Translations, path: string): string | undefined { const keys = path.split('.'); let current: TranslationValue = obj; for (const key of keys) { if (current && typeof current === 'object' && key in current) { current = current[key]; } else { return undefined; } } return typeof current === 'string' ? current : undefined; } /** * Replace parameters in translation string * e.g., interpolate('Hello {{name}}!', { name: 'World' }) => 'Hello World!' */ function interpolate(str: string, params?: Record): string { if (!params) return str; return str.replace(/\{\{(\w+)\}\}/g, (_, key) => { return params[key]?.toString() ?? `{{${key}}}`; }); } interface LanguageProviderProps { children: ReactNode; } export function LanguageProvider({ children }: LanguageProviderProps) { const [storedLanguage, setStoredLanguage] = useLocalStorage('app-language', 'en'); const [language, setLanguageState] = useState(storedLanguage); // Sync with localStorage useEffect(() => { setLanguageState(storedLanguage); }, [storedLanguage]); const setLanguage = useCallback((lang: Language) => { setLanguageState(lang); setStoredLanguage(lang); // Update HTML lang attribute for accessibility document.documentElement.lang = lang; }, [setStoredLanguage]); // Set initial HTML lang attribute useEffect(() => { document.documentElement.lang = language; }, [language]); const t = useCallback((key: TranslationKey, params?: Record): string => { // Try to get translation in current language const translation = getNestedValue(translations[language], key); if (translation) { return interpolate(translation, params); } // Fallback to English if (language !== 'en') { const fallback = getNestedValue(translations.en, key); if (fallback) { return interpolate(fallback, params); } } // Return key if no translation found (helps identify missing translations) console.warn(`Missing translation for key: ${key}`); return key; }, [language]); // Currently no RTL languages supported, but ready for future const isRTL = false; return ( {children} ); } /** * Hook to access translation functions and language state */ export function useTranslation() { const context = useContext(LanguageContext); if (context === undefined) { throw new Error('useTranslation must be used within a LanguageProvider'); } return context; } /** * Hook to get just the translation function (convenience) */ export function useT() { const { t } = useTranslation(); return t; }