Files
fotospiel-app/resources/js/guest/i18n/LocaleContext.tsx
2025-10-17 15:00:07 +02:00

125 lines
3.5 KiB
TypeScript

import React from 'react';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES, type LocaleCode, isLocaleCode } from './messages';
export interface LocaleContextValue {
locale: LocaleCode;
setLocale: (next: LocaleCode) => void;
resetLocale: () => void;
hydrated: boolean;
defaultLocale: LocaleCode;
storageKey: string;
availableLocales: typeof SUPPORTED_LOCALES;
}
const LocaleContext = React.createContext<LocaleContextValue | undefined>(undefined);
function sanitizeLocale(value: string | null | undefined, fallback: LocaleCode = DEFAULT_LOCALE): LocaleCode {
if (value && isLocaleCode(value)) {
return value;
}
return fallback;
}
export interface LocaleProviderProps {
children: React.ReactNode;
defaultLocale?: LocaleCode;
storageKey?: string;
}
export function LocaleProvider({
children,
defaultLocale = DEFAULT_LOCALE,
storageKey = 'guestLocale_global',
}: LocaleProviderProps) {
const resolvedDefault = sanitizeLocale(defaultLocale, DEFAULT_LOCALE);
const [locale, setLocaleState] = React.useState<LocaleCode>(resolvedDefault);
const [userLocale, setUserLocale] = React.useState<LocaleCode | null>(null);
const [hydrated, setHydrated] = React.useState(false);
React.useEffect(() => {
setHydrated(false);
if (typeof window === 'undefined') {
setLocaleState(resolvedDefault);
setUserLocale(null);
setHydrated(true);
return;
}
let stored: string | null = null;
try {
stored = window.localStorage.getItem(storageKey);
} catch (error) {
console.warn('Failed to read stored locale', error);
}
const nextLocale = sanitizeLocale(stored, resolvedDefault);
setLocaleState(nextLocale);
setUserLocale(isLocaleCode(stored) ? stored : null);
setHydrated(true);
}, [storageKey, resolvedDefault]);
React.useEffect(() => {
if (!hydrated || userLocale !== null) {
return;
}
setLocaleState(resolvedDefault);
}, [hydrated, userLocale, resolvedDefault]);
const setLocale = React.useCallback(
(next: LocaleCode) => {
const safeLocale = sanitizeLocale(next, resolvedDefault);
setLocaleState(safeLocale);
setUserLocale(safeLocale);
if (typeof window !== 'undefined') {
try {
window.localStorage.setItem(storageKey, safeLocale);
} catch (error) {
console.warn('Failed to persist locale', error);
}
}
},
[storageKey, resolvedDefault],
);
const resetLocale = React.useCallback(() => {
setUserLocale(null);
setLocaleState(resolvedDefault);
if (typeof window !== 'undefined') {
try {
window.localStorage.removeItem(storageKey);
} catch (error) {
console.warn('Failed to clear stored locale', error);
}
}
}, [resolvedDefault, storageKey]);
const value = React.useMemo<LocaleContextValue>(
() => ({
locale,
setLocale,
resetLocale,
hydrated,
defaultLocale: resolvedDefault,
storageKey,
availableLocales: SUPPORTED_LOCALES,
}),
[locale, setLocale, resetLocale, hydrated, resolvedDefault, storageKey],
);
return <LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>;
}
export function useLocale(): LocaleContextValue {
const ctx = React.useContext(LocaleContext);
if (!ctx) {
throw new Error('useLocale must be used within a LocaleProvider');
}
return ctx;
}
export function useOptionalLocale(): LocaleContextValue | undefined {
return React.useContext(LocaleContext);
}