refactor(guest): retire legacy guest app and move shared modules
This commit is contained in:
124
resources/js/shared/guest/i18n/LocaleContext.tsx
Normal file
124
resources/js/shared/guest/i18n/LocaleContext.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
}
|
||||
1885
resources/js/shared/guest/i18n/messages.ts
Normal file
1885
resources/js/shared/guest/i18n/messages.ts
Normal file
File diff suppressed because it is too large
Load Diff
49
resources/js/shared/guest/i18n/useTranslation.ts
Normal file
49
resources/js/shared/guest/i18n/useTranslation.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { translate, DEFAULT_LOCALE, type LocaleCode } from './messages';
|
||||
import { useLocale } from './LocaleContext';
|
||||
|
||||
type ReplacementValues = Record<string, string | number>;
|
||||
|
||||
export type TranslateFn = {
|
||||
(key: string): string;
|
||||
(key: string, fallback: string): string;
|
||||
(key: string, replacements: ReplacementValues): string;
|
||||
(key: string, replacements: ReplacementValues, fallback: string): string;
|
||||
};
|
||||
|
||||
function resolveTranslation(locale: LocaleCode, key: string, fallback?: string): string {
|
||||
return translate(locale, key) ?? translate(DEFAULT_LOCALE, key) ?? fallback ?? key;
|
||||
}
|
||||
|
||||
function applyReplacements(value: string, replacements?: ReplacementValues): string {
|
||||
if (!replacements) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Object.entries(replacements).reduce((acc, [token, replacement]) => {
|
||||
const pattern = new RegExp(`\\{${token}\\}`, 'g');
|
||||
return acc.replace(pattern, String(replacement));
|
||||
}, value);
|
||||
}
|
||||
|
||||
export function useTranslation() {
|
||||
const { locale } = useLocale();
|
||||
|
||||
const t = React.useCallback<TranslateFn>((key: string, arg2?: ReplacementValues | string, arg3?: string) => {
|
||||
let replacements: ReplacementValues | undefined;
|
||||
let fallback: string | undefined;
|
||||
|
||||
if (typeof arg2 === 'string' || arg2 === undefined) {
|
||||
fallback = arg2 ?? arg3;
|
||||
} else {
|
||||
replacements = arg2;
|
||||
fallback = arg3;
|
||||
}
|
||||
|
||||
const raw = resolveTranslation(locale, key, fallback);
|
||||
|
||||
return applyReplacements(raw, replacements);
|
||||
}, [locale]);
|
||||
|
||||
return React.useMemo(() => ({ t, locale }), [t, locale]);
|
||||
}
|
||||
Reference in New Issue
Block a user