58 lines
1.5 KiB
TypeScript
58 lines
1.5 KiB
TypeScript
"use client";
|
||
|
||
import {
|
||
createContext,
|
||
useContext,
|
||
useSyncExternalStore,
|
||
type ReactNode,
|
||
} from "react";
|
||
|
||
import { syncPreferredLanguage } from "@/i18n";
|
||
|
||
const I18nHydrationContext = createContext(false);
|
||
const hydrationListeners = new Set<() => void>();
|
||
let hydrationReady = false;
|
||
let hydrationSyncPromise: Promise<void> | null = null;
|
||
|
||
function ensurePreferredLanguageSynced(): void {
|
||
if (hydrationSyncPromise) return;
|
||
|
||
hydrationSyncPromise = syncPreferredLanguage()
|
||
.catch(() => undefined)
|
||
.then(() => {
|
||
hydrationReady = true;
|
||
hydrationListeners.forEach((listener) => listener());
|
||
});
|
||
}
|
||
|
||
function subscribeI18nHydration(onStoreChange: () => void): () => void {
|
||
hydrationListeners.add(onStoreChange);
|
||
ensurePreferredLanguageSynced();
|
||
return () => hydrationListeners.delete(onStoreChange);
|
||
}
|
||
|
||
function getI18nHydratedSnapshot(): boolean {
|
||
return hydrationReady;
|
||
}
|
||
|
||
function getI18nHydratedServerSnapshot(): boolean {
|
||
return false;
|
||
}
|
||
|
||
/** hydration 完成前保持 DEFAULT_LANGUAGE,与 SSR 一致;完成后再同步用户偏好语言。 */
|
||
export function I18nHydrationProvider({ children }: { children: ReactNode }) {
|
||
const hydrated = useSyncExternalStore(
|
||
subscribeI18nHydration,
|
||
getI18nHydratedSnapshot,
|
||
getI18nHydratedServerSnapshot,
|
||
);
|
||
|
||
return (
|
||
<I18nHydrationContext.Provider value={hydrated}>{children}</I18nHydrationContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useI18nHydrated(): boolean {
|
||
return useContext(I18nHydrationContext);
|
||
}
|