import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { I18n } from 'i18n-js'; import { getLocales } from 'expo-localization'; import * as SecureStore from 'expo-secure-store'; import en from './en'; import it from './it'; // English and Italian. Follows the phone's locale unless overridden in Profile. export type AppLanguage = 'en' | 'it'; /** 'system' means follow the phone. */ export type LanguagePreference = AppLanguage | 'system'; export const SUPPORTED_LANGUAGES: AppLanguage[] = ['en', 'it']; const PREF_KEY = 'casadoc.language'; const i18n = new I18n({ en, it }); i18n.defaultLocale = 'en'; i18n.enableFallback = true; /** Resolve the phone's locale to a language we actually ship. */ export function deviceLanguage(): AppLanguage { const tags = getLocales(); for (const l of tags) { const code = (l.languageCode ?? '').toLowerCase(); if (code === 'it') return 'it'; if (code === 'en') return 'en'; } return 'en'; // neither Italian nor English → English } function resolve(pref: LanguagePreference): AppLanguage { return pref === 'system' ? deviceLanguage() : pref; } // Applied immediately, so translation calls before the provider mounts work. i18n.locale = deviceLanguage(); /** For non-React code; inside components use `useT()` so it re-renders. */ export function t(key: string, options?: Record): string { return i18n.t(key, options as any); } /** Current BCP-47-ish locale, for Intl date/number formatting. */ export function currentLocale(): string { return i18n.locale === 'it' ? 'it-IT' : 'en-GB'; } interface I18nState { language: AppLanguage; preference: LanguagePreference; setPreference: (pref: LanguagePreference) => void; t: (key: string, options?: Record) => string; } const I18nContext = createContext(undefined); export function I18nProvider({ children }: { children: React.ReactNode }) { const [preference, setPreferenceState] = useState('system'); const [ready, setReady] = useState(false); useEffect(() => { (async () => { try { const saved = await SecureStore.getItemAsync(PREF_KEY); if (saved === 'en' || saved === 'it' || saved === 'system') { setPreferenceState(saved); i18n.locale = resolve(saved); } } catch { // fall back to the system language } finally { setReady(true); } })(); }, []); const setPreference = useCallback((pref: LanguagePreference) => { setPreferenceState(pref); i18n.locale = resolve(pref); SecureStore.setItemAsync(PREF_KEY, pref).catch(() => { // a lost preference only means we fall back to the phone's language }); }, []); const language = resolve(preference); const value = useMemo( () => ({ language, preference, setPreference, t: (key: string, options?: Record) => { i18n.locale = language; return i18n.t(key, options as any); }, }), [language, preference, setPreference], ); // Avoids a flash of the wrong language before the preference loads. if (!ready) return null; return {children}; } export function useI18n(): I18nState { const ctx = useContext(I18nContext); if (!ctx) throw new Error('useI18n must be used within '); return ctx; } export function useT() { return useI18n().t; }