diff --git a/src/hooks/lang.ts b/src/hooks/lang.ts new file mode 100644 index 0000000..219038b --- /dev/null +++ b/src/hooks/lang.ts @@ -0,0 +1,33 @@ +import { ExternalStore } from "@snort/shared"; +import { useSyncExternalStore } from "react"; + +export const DefaultLocale = "en"; + +class LangStore extends ExternalStore { + setLang(lang: string) { + localStorage.setItem("lang", lang); + this.notifyChange(); + } + + takeSnapshot(): string { + return localStorage.getItem("lang") ?? getLocale(); + } +} + +const LangSelector = new LangStore(); + +export function useLang() { + const store = useSyncExternalStore( + c => LangSelector.hook(c), + () => LangSelector.snapshot() + ); + + return { + lang: store, + setLang: (l: string) => LangSelector.setLang(l), + }; +} + +export const getLocale = () => { + return (navigator.languages && navigator.languages[0]) ?? navigator.language ?? DefaultLocale; +}; diff --git a/src/index.css b/src/index.css index b389137..1411e7e 100644 --- a/src/index.css +++ b/src/index.css @@ -106,6 +106,14 @@ a { width: -moz-available; } +.uppercase { + text-transform: uppercase; +} + +.pointer { + cursor: pointer; +} + .btn { border: none; outline: none; diff --git a/src/intl.tsx b/src/intl.tsx index 965f139..2318420 100644 --- a/src/intl.tsx +++ b/src/intl.tsx @@ -1,10 +1,9 @@ +import { DefaultLocale, useLang } from "hooks/lang"; import { useEffect, useState, type ReactNode } from "react"; import { IntlProvider as ReactIntlProvider } from "react-intl"; import enMessages from "translations/en.json"; -const DefaultLocale = "en-US"; - async function importLang(code: string) { const src = await import(`translations/${code}.json`); const typed = src.default as Record; @@ -12,6 +11,13 @@ async function importLang(code: string) { return Object.fromEntries(ent) as Record; } +export const AllLocales = [ + "en", + "de", + "es", + "th" +] + const getMessages = (locale: string) => { const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0]; @@ -36,7 +42,7 @@ const getMessages = (locale: string) => { }; export const IntlProvider = ({ children }: { children: ReactNode }) => { - const locale = getLocale(); + const { lang: locale } = useLang(); const [messages, setMessages] = useState>(enMessages); useEffect(() => { @@ -49,7 +55,3 @@ export const IntlProvider = ({ children }: { children: ReactNode }) => { ); }; - -export const getLocale = () => { - return (navigator.languages && navigator.languages[0]) ?? navigator.language ?? DefaultLocale; -}; diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx index 2859b5c..772becc 100644 --- a/src/pages/layout.tsx +++ b/src/pages/layout.tsx @@ -13,13 +13,43 @@ import { Profile } from "element/profile"; import { NewStreamDialog } from "element/new-stream"; import { LoginSignup } from "element/login-signup"; import { Login } from "index"; +import { useLang } from "hooks/lang"; +import { AllLocales } from "intl"; export function LayoutPage() { const navigate = useNavigate(); const login = useLogin(); const [showLogin, setShowLogin] = useState(false); + const { lang, setLang } = useLang(); + useLoginEvents(login?.pubkey, true); + function langSelector() { + return ( + +
+ +
+
+ {lang.includes("-") ? lang.split("-")[0] : lang} +
+ + } + align="end" + gap={5}> + {AllLocales.sort().map(l => setLang(l)} key={l}> + {new Intl.DisplayNames([l], { + type: "language", + }).of(l)} + )} + +
+ ); + } + function loggedIn() { if (!login) return; @@ -95,6 +125,7 @@ export function LayoutPage() { */}
{/* Future menu items go here */}
+ {langSelector()} {loggedIn()} {loggedOut()}