import React, { createContext, useContext, useReducer, useEffect, ReactNode, } from "react"; import { SupportedLanguage, ChatbotTranslations, translations, languages, DEFAULT_LANGUAGE, detectBrowserLanguage, t, isRTL, adjustTextLength, } from "./i18n-config"; // I18n State Interface interface I18nState { currentLanguage: SupportedLanguage; translations: ChatbotTranslations; isRTL: boolean; isLoading: boolean; } // I18n Actions type I18nAction = | { type: "SET_LANGUAGE"; payload: SupportedLanguage } | { type: "SET_LOADING"; payload: boolean }; // I18n Context Interface interface I18nContextType extends I18nState { changeLanguage: (language: SupportedLanguage) => void; t: (key: keyof ChatbotTranslations) => string; supportedLanguages: SupportedLanguage[]; formatText: (text: string) => { isTruncated: boolean; displayText: string; maxLength: number; }; } // Create Context const I18nContext = createContext(undefined); // I18n Reducer function i18nReducer(state: I18nState, action: I18nAction): I18nState { switch (action.type) { case "SET_LANGUAGE": return { ...state, currentLanguage: action.payload, translations: translations[action.payload], isRTL: isRTL(action.payload), isLoading: false, }; case "SET_LOADING": return { ...state, isLoading: action.payload, }; default: return state; } } // I18n Provider Props interface I18nProviderProps { children: ReactNode; defaultLanguage?: SupportedLanguage; enableAutoDetect?: boolean; enablePersistence?: boolean; } // Storage Keys const STORAGE_KEY = "chatbot-language"; // I18n Provider Component export function I18nProvider({ children, defaultLanguage, enableAutoDetect = true, enablePersistence = true, }: I18nProviderProps) { // Determine initial language const getInitialLanguage = (): SupportedLanguage => { // 1. Check localStorage if persistence is enabled if (enablePersistence && typeof window !== "undefined") { const stored = localStorage.getItem(STORAGE_KEY) as SupportedLanguage; if (stored && Object.keys(translations).includes(stored)) { return stored; } } // 2. Use provided default language if (defaultLanguage) { return defaultLanguage; } // 3. Auto-detect browser language if enabled if (enableAutoDetect) { return detectBrowserLanguage(); } // 4. Fall back to DEFAULT_LANGUAGE return DEFAULT_LANGUAGE; }; const initialLanguage = getInitialLanguage(); // Initialize state const [state, dispatch] = useReducer(i18nReducer, { currentLanguage: initialLanguage, translations: translations[initialLanguage], isRTL: isRTL(initialLanguage), isLoading: false, }); // Change language function const changeLanguage = React.useCallback( (language: SupportedLanguage) => { if (!Object.keys(translations).includes(language)) { console.warn( `Language '${language}' is not supported. Falling back to '${DEFAULT_LANGUAGE}'.` ); language = DEFAULT_LANGUAGE; } dispatch({ type: "SET_LOADING", payload: true }); // Simulate loading time for language switching setTimeout(() => { dispatch({ type: "SET_LANGUAGE", payload: language }); // Persist language preference if (enablePersistence && typeof window !== "undefined") { try { localStorage.setItem(STORAGE_KEY, language); } catch (error) { console.warn("Failed to save language preference:", error); } } }, 100); }, [enablePersistence] ); // Translation function const translate = React.useCallback( (key: keyof ChatbotTranslations): string => { return t(key, state.currentLanguage); }, [state.currentLanguage] ); // Text formatting function const formatText = React.useCallback( (text: string) => { return adjustTextLength(text, state.currentLanguage); }, [state.currentLanguage] ); // Apply RTL direction to document useEffect(() => { if (typeof document !== "undefined") { const chatbotElements = document.querySelectorAll("[data-chatbot]"); chatbotElements.forEach((element) => { (element as HTMLElement).dir = state.isRTL ? "rtl" : "ltr"; }); } }, [state.isRTL]); // Context value const contextValue: I18nContextType = { ...state, changeLanguage, t: translate, supportedLanguages: Object.keys(translations) as SupportedLanguage[], formatText, }; return ( {children} ); } // Custom hook to use I18n context export function useI18n(): I18nContextType { const context = useContext(I18nContext); if (context === undefined) { throw new Error("useI18n must be used within an I18nProvider"); } return context; } // Language selector component interface LanguageSelectorProps { className?: string; showFlags?: boolean; showNativeNames?: boolean; onLanguageChange?: (language: SupportedLanguage) => void; } export function LanguageSelector({ className = "", showFlags = true, showNativeNames = true, onLanguageChange, }: LanguageSelectorProps) { const { currentLanguage, changeLanguage, supportedLanguages, isRTL } = useI18n(); const handleLanguageChange = (language: SupportedLanguage) => { changeLanguage(language); onLanguageChange?.(language); }; return (
); } // Utility component for RTL-aware text rendering interface RTLTextProps { children: ReactNode; className?: string; tag?: keyof JSX.IntrinsicElements; } export function RTLText({ children, className = "", tag: Tag = "span", }: RTLTextProps) { const { isRTL } = useI18n(); return ( {children} ); }