import i18n, { LOCALE_STORAGE_KEY } from "@/config/localization/i18n"; import { getStorageItem, setStorageItem } from "@/utils/storage"; import { getLocales } from "expo-localization"; import { PropsWithChildren, createContext, createElement, useCallback, useContext, useEffect, useMemo, useState, } from "react"; type SupportedLocale = "en" | "vi"; type I18nContextValue = { t: typeof i18n.t; locale: SupportedLocale; setLocale: (locale: SupportedLocale) => Promise; isLoaded: boolean; }; const I18nContext = createContext(undefined); const SUPPORTED_LOCALES: SupportedLocale[] = ["en", "vi"]; const resolveSupportedLocale = ( locale: string | null | undefined ): SupportedLocale => { if (!locale) { return "en"; } const normalized = locale.split("-")[0]?.toLowerCase() as SupportedLocale; if (normalized && SUPPORTED_LOCALES.includes(normalized)) { return normalized; } return "en"; }; export const I18nProvider = ({ children }: PropsWithChildren) => { const [locale, setLocaleState] = useState( resolveSupportedLocale(i18n.locale) ); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { const loadLocale = async () => { try { const savedLocale = await getStorageItem(LOCALE_STORAGE_KEY); const deviceLocale = getLocales()[0]?.languageCode; const localeToUse = resolveSupportedLocale(savedLocale ?? deviceLocale); if (localeToUse !== i18n.locale) { i18n.locale = localeToUse; } setLocaleState(localeToUse); } catch (error) { console.error("Error loading locale preference:", error); } finally { setIsLoaded(true); } }; void loadLocale(); }, []); const updateLocale = useCallback((nextLocale: SupportedLocale) => { if (i18n.locale !== nextLocale) { i18n.locale = nextLocale; } setLocaleState(nextLocale); }, []); const setLocale = useCallback( async (nextLocale: SupportedLocale) => { if (!SUPPORTED_LOCALES.includes(nextLocale)) { console.warn(`Unsupported locale: ${nextLocale}`); return; } try { updateLocale(nextLocale); await setStorageItem(LOCALE_STORAGE_KEY, nextLocale); } catch (error) { console.error("Error setting locale:", error); } }, [updateLocale] ); const translate = useCallback( (...args: Parameters) => i18n.t(...args), [locale] ); const value = useMemo( () => ({ t: translate, locale, setLocale, isLoaded, }), [locale, setLocale, translate, isLoaded] ); return createElement(I18nContext.Provider, { value }, children); }; export const useI18n = () => { const context = useContext(I18nContext); if (!context) { throw new Error("useI18n must be used within an I18nProvider"); } return context; };