110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { I18nProvider } from "@lingui/react";
|
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
|
|
import type { Locale } from "~/locales";
|
|
import { defaultLocale, locales } from "~/locales";
|
|
import { activateLocale, i18n, initializeI18n } from "~/utils/i18n";
|
|
|
|
interface LinguiContextType {
|
|
locale: Locale;
|
|
setLocale: (locale: Locale) => void;
|
|
availableLocales: Locale[];
|
|
}
|
|
|
|
const LinguiContext = createContext<LinguiContextType | undefined>(undefined);
|
|
|
|
export function useLinguiContext() {
|
|
const context = useContext(LinguiContext);
|
|
if (!context) {
|
|
throw new Error("useLinguiContext must be used within a LinguiProvider");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
interface LinguiProviderProps {
|
|
children: ReactNode;
|
|
initialLocale?: Locale;
|
|
}
|
|
|
|
function detectBrowserLocale(availableLocales: readonly string[]): Locale {
|
|
if (typeof navigator === "undefined") {
|
|
return defaultLocale;
|
|
}
|
|
|
|
const browserLanguages = navigator.languages || [navigator.language];
|
|
|
|
for (const browserLang of browserLanguages) {
|
|
const normalizedLocale = browserLang.toLowerCase();
|
|
const exactLocale = availableLocales.find(
|
|
(availableLocale) => availableLocale.toLowerCase() === normalizedLocale,
|
|
);
|
|
|
|
if (exactLocale) {
|
|
return exactLocale as Locale;
|
|
}
|
|
|
|
const languageCode = normalizedLocale.split("-")[0];
|
|
|
|
if (languageCode === "zh" && availableLocales.includes("zh-CN")) {
|
|
return "zh-CN";
|
|
}
|
|
|
|
if (languageCode === "pt" && availableLocales.includes("ptbr")) {
|
|
return "ptbr";
|
|
}
|
|
|
|
const baseLocale = availableLocales.find(
|
|
(availableLocale) => availableLocale.toLowerCase() === languageCode,
|
|
);
|
|
|
|
if (baseLocale) {
|
|
return baseLocale as Locale;
|
|
}
|
|
}
|
|
|
|
return defaultLocale;
|
|
}
|
|
|
|
export function LinguiProviderWrapper({
|
|
children,
|
|
initialLocale = defaultLocale,
|
|
}: LinguiProviderProps) {
|
|
const [locale, setLocale] = useState<Locale>(initialLocale);
|
|
const [isHydrated, setIsHydrated] = useState(false);
|
|
|
|
initializeI18n();
|
|
|
|
useEffect(() => {
|
|
const savedLocale = localStorage.getItem("locale") as Locale;
|
|
|
|
if (savedLocale && locales.includes(savedLocale)) {
|
|
setLocale(savedLocale);
|
|
} else {
|
|
const detectedLocale = detectBrowserLocale(locales);
|
|
setLocale(detectedLocale);
|
|
}
|
|
setIsHydrated(true);
|
|
}, [initialLocale]);
|
|
|
|
useEffect(() => {
|
|
if (isHydrated && locale !== defaultLocale) {
|
|
activateLocale(locale).then(() => {
|
|
localStorage.setItem("locale", locale);
|
|
});
|
|
} else if (isHydrated) {
|
|
localStorage.setItem("locale", locale);
|
|
}
|
|
}, [locale, isHydrated]);
|
|
|
|
return (
|
|
<LinguiContext.Provider
|
|
value={{ locale, setLocale, availableLocales: [...locales] }}
|
|
>
|
|
<I18nProvider i18n={i18n} key={locale}>
|
|
{children}
|
|
</I18nProvider>
|
|
</LinguiContext.Provider>
|
|
);
|
|
}
|