initial
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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, unknown>): 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, unknown>) => string;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nState | undefined>(undefined);
|
||||
|
||||
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [preference, setPreferenceState] = useState<LanguagePreference>('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<I18nState>(
|
||||
() => ({
|
||||
language,
|
||||
preference,
|
||||
setPreference,
|
||||
t: (key: string, options?: Record<string, unknown>) => {
|
||||
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 <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
||||
}
|
||||
|
||||
export function useI18n(): I18nState {
|
||||
const ctx = useContext(I18nContext);
|
||||
if (!ctx) throw new Error('useI18n must be used within <I18nProvider>');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function useT() {
|
||||
return useI18n().t;
|
||||
}
|
||||
Reference in New Issue
Block a user