Files
kan/apps/web/src/providers/lingui.tsx
Henry dd061c6d00 feat: localisation and multi-lang support (#95)
* feat: setup localisation with lingui

* Fix hydration and locale issues

* Add missing translations and tweak selector styling

* Extend language support

* Update lang support

* Extend lang support

* Fix build and add dyanmic imports
2025-06-25 21:30:24 +01:00

89 lines
2.3 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 langCode = browserLang.split("-")[0];
if (langCode && availableLocales.includes(langCode.toLowerCase())) {
return langCode.toLowerCase() as Locale;
}
}
return defaultLocale;
}
export function LinguiProviderWrapper({
children,
initialLocale = defaultLocale,
}: LinguiProviderProps) {
const [locale, setLocale] = useState<Locale>(defaultLocale);
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>
);
}