All files / src/contexts LocaleContext.tsx

26.82% Statements 11/41
0% Branches 0/5
12.5% Functions 1/8
28.2% Lines 11/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 7876x 76x 76x 76x   76x               76x           76x   76x 32x     76x                                                                                         76x              
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import * as Localization from 'expo-localization';
import * as SecureStore from 'expo-secure-store';
import i18n, { isSupportedLocale, resolveLocale, type Locale } from '@/i18n';
 
const LOCALE_STORAGE_KEY = 'preferred_locale';
 
interface LocaleContextValue {
  locale: Locale;
  isHydrating: boolean;
  setLocale: (next: Locale) => Promise<void>;
}
 
const LocaleContext = createContext<LocaleContextValue | null>(null);
 
/**
 * Singleton mirror of the active locale so non-React code (e.g. the api layer)
 * can read it for the Accept-Language header without a provider.
 */
let currentLocale: Locale = 'en';
 
export function getCurrentLocale(): Locale {
  return currentLocale;
}
 
export function LocaleProvider({ children }: { children: React.ReactNode }) {
  const [locale, setLocaleState] = useState<Locale>('en');
  const [isHydrating, setIsHydrating] = useState(true);
 
  // Load persisted locale on mount, falling back to device locale.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const stored = await SecureStore.getItemAsync(LOCALE_STORAGE_KEY);
        const initial = isSupportedLocale(stored)
          ? stored
          : resolveLocale(Localization.getLocales()[0]?.languageTag);
        Iif (cancelled) return;
        currentLocale = initial;
        await i18n.changeLanguage(initial);
        setLocaleState(initial);
      } finally {
        Iif (!cancelled) setIsHydrating(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);
 
  const setLocale = useCallback(async (next: Locale) => {
    currentLocale = next;
    await i18n.changeLanguage(next);
    setLocaleState(next);
    try {
      await SecureStore.setItemAsync(LOCALE_STORAGE_KEY, next);
    } catch {
      // Persistence is best-effort; the in-memory locale is already updated.
    }
  }, []);
 
  const value = useMemo<LocaleContextValue>(
    () => ({ locale, isHydrating, setLocale }),
    [locale, isHydrating, setLocale],
  );
 
  return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
}
 
export function useLocale(): LocaleContextValue {
  const ctx = useContext(LocaleContext);
  Iif (!ctx) {
    throw new Error('useLocale must be used within a LocaleProvider');
  }
  return ctx;
}