import * as Localization from 'expo-localization'; import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import { getItemSync, setItem } from '@/utils/kvStore'; import en from './en'; import zh from './zh'; export const LANGUAGE_PREFERENCE_KEY = 'app_language_preference'; export const SUPPORTED_LANGUAGES = ['zh', 'en'] as const; export type AppLanguage = typeof SUPPORTED_LANGUAGES[number]; const fallbackLanguage: AppLanguage = 'zh'; const resources = { zh: { translation: zh, }, en: { translation: en, }, }; export const isSupportedLanguage = (language?: string | null): language is AppLanguage => { if (!language) return false; return SUPPORTED_LANGUAGES.some((code) => language === code || language.startsWith(`${code}-`)); }; export const getNormalizedLanguage = (language?: string | null): AppLanguage => { if (!language) return fallbackLanguage; const normalized = SUPPORTED_LANGUAGES.find((code) => language === code || language.startsWith(`${code}-`)); return normalized ?? fallbackLanguage; }; const getStoredLanguage = (): AppLanguage | null => { try { const stored = getItemSync?.(LANGUAGE_PREFERENCE_KEY) as AppLanguage | null | undefined; if (stored && isSupportedLanguage(stored)) { return stored; } } catch (error) { // ignore storage errors and fall back to device preference } return null; }; const getDeviceLanguage = (): AppLanguage | null => { try { const locales = Localization.getLocales(); const preferred = locales.find((locale) => locale.languageCode && isSupportedLanguage(locale.languageCode)); return preferred?.languageCode as AppLanguage | undefined || null; } catch (error) { return null; } }; const initialLanguage = getStoredLanguage() ?? getDeviceLanguage() ?? fallbackLanguage; void i18n.use(initReactI18next).init({ compatibilityJSON: 'v4', resources, lng: initialLanguage, fallbackLng: fallbackLanguage, interpolation: { escapeValue: false, }, returnNull: false, }); export const changeAppLanguage = async (language: AppLanguage) => { const nextLanguage = isSupportedLanguage(language) ? language : fallbackLanguage; await i18n.changeLanguage(nextLanguage); await setItem(LANGUAGE_PREFERENCE_KEY, nextLanguage); }; export default i18n;