Lang selector

This commit is contained in:
Kieran 2023-09-01 13:35:07 +01:00
parent 5a90164922
commit 0c06f88a2d
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
4 changed files with 81 additions and 7 deletions

33
src/hooks/lang.ts Normal file
View File

@ -0,0 +1,33 @@
import { ExternalStore } from "@snort/shared";
import { useSyncExternalStore } from "react";
export const DefaultLocale = "en";
class LangStore extends ExternalStore<string> {
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;
};

View File

@ -106,6 +106,14 @@ a {
width: -moz-available; width: -moz-available;
} }
.uppercase {
text-transform: uppercase;
}
.pointer {
cursor: pointer;
}
.btn { .btn {
border: none; border: none;
outline: none; outline: none;

View File

@ -1,10 +1,9 @@
import { DefaultLocale, useLang } from "hooks/lang";
import { useEffect, useState, type ReactNode } from "react"; import { useEffect, useState, type ReactNode } from "react";
import { IntlProvider as ReactIntlProvider } from "react-intl"; import { IntlProvider as ReactIntlProvider } from "react-intl";
import enMessages from "translations/en.json"; import enMessages from "translations/en.json";
const DefaultLocale = "en-US";
async function importLang(code: string) { async function importLang(code: string) {
const src = await import(`translations/${code}.json`); const src = await import(`translations/${code}.json`);
const typed = src.default as Record<string, { defaultMessage: string }>; const typed = src.default as Record<string, { defaultMessage: string }>;
@ -12,6 +11,13 @@ async function importLang(code: string) {
return Object.fromEntries(ent) as Record<string, string>; return Object.fromEntries(ent) as Record<string, string>;
} }
export const AllLocales = [
"en",
"de",
"es",
"th"
]
const getMessages = (locale: string) => { const getMessages = (locale: string) => {
const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0]; const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0];
@ -36,7 +42,7 @@ const getMessages = (locale: string) => {
}; };
export const IntlProvider = ({ children }: { children: ReactNode }) => { export const IntlProvider = ({ children }: { children: ReactNode }) => {
const locale = getLocale(); const { lang: locale } = useLang();
const [messages, setMessages] = useState<Record<string, string>>(enMessages); const [messages, setMessages] = useState<Record<string, string>>(enMessages);
useEffect(() => { useEffect(() => {
@ -49,7 +55,3 @@ export const IntlProvider = ({ children }: { children: ReactNode }) => {
</ReactIntlProvider> </ReactIntlProvider>
); );
}; };
export const getLocale = () => {
return (navigator.languages && navigator.languages[0]) ?? navigator.language ?? DefaultLocale;
};

View File

@ -13,13 +13,43 @@ import { Profile } from "element/profile";
import { NewStreamDialog } from "element/new-stream"; import { NewStreamDialog } from "element/new-stream";
import { LoginSignup } from "element/login-signup"; import { LoginSignup } from "element/login-signup";
import { Login } from "index"; import { Login } from "index";
import { useLang } from "hooks/lang";
import { AllLocales } from "intl";
export function LayoutPage() { export function LayoutPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const login = useLogin(); const login = useLogin();
const [showLogin, setShowLogin] = useState(false); const [showLogin, setShowLogin] = useState(false);
const { lang, setLang } = useLang();
useLoginEvents(login?.pubkey, true); useLoginEvents(login?.pubkey, true);
function langSelector() {
return (
<Menu
menuClassName="ctx-menu"
menuButton={
<div className="flex f-center g24">
<div className="flag">
</div>
<div className="uppercase pointer">
<b>{lang.includes("-") ? lang.split("-")[0] : lang}</b>
</div>
</div>
}
align="end"
gap={5}>
{AllLocales.sort().map(l => <MenuItem onClick={() => setLang(l)} key={l}>
{new Intl.DisplayNames([l], {
type: "language",
}).of(l)}
</MenuItem>)}
</Menu>
);
}
function loggedIn() { function loggedIn() {
if (!login) return; if (!login) return;
@ -95,6 +125,7 @@ export function LayoutPage() {
</div>*/} </div>*/}
<div className="f-grow">{/* Future menu items go here */}</div> <div className="f-grow">{/* Future menu items go here */}</div>
<div className="header-right"> <div className="header-right">
{langSelector()}
{loggedIn()} {loggedIn()}
{loggedOut()} {loggedOut()}
</div> </div>