Added language preference system

This commit is contained in:
DrMint 2022-04-10 23:18:52 +02:00
parent 3cf890d70d
commit fb88e97825
6 changed files with 261 additions and 171 deletions

View File

@ -14,6 +14,7 @@ import {
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useSwipeable } from "react-swipeable"; import { useSwipeable } from "react-swipeable";
import { ImageQuality } from "./Img"; import { ImageQuality } from "./Img";
import OrderableList from "./OrderableList";
import MainPanel from "./Panels/MainPanel"; import MainPanel from "./Panels/MainPanel";
import Popup from "./Popup"; import Popup from "./Popup";
import Select from "./Select"; import Select from "./Select";
@ -250,33 +251,48 @@ export default function AppLayout(props: Props): JSX.Element {
</span> </span>
</div> </div>
<Popup
state={appLayout.languagePanelOpen}
setState={appLayout.setLanguagePanelOpen}
>
<h2 className="text-2xl">{langui.select_language}</h2>
<div className="flex flex-wrap flex-row gap-2 mobile:flex-col">
{router.locales?.map((locale) => (
<Button
key={locale}
active={locale === router.locale}
href={router.asPath}
locale={locale}
onClick={() => appLayout.setLanguagePanelOpen(false)}
>
{prettyLanguage(locale, languages)}
</Button>
))}
</div>
</Popup>
<Popup <Popup
state={appLayout.configPanelOpen} state={appLayout.configPanelOpen}
setState={appLayout.setConfigPanelOpen} setState={appLayout.setConfigPanelOpen}
> >
<h2 className="text-2xl">{langui.settings}</h2> <h2 className="text-2xl">{langui.settings}</h2>
<div className="mt-4 grid gap-8 place-items-center text-center desktop:grid-cols-2"> <div className="mt-4 grid gap-16 justify-items-center text-center desktop:grid-cols-[auto_auto]">
{router.locales && (
<div>
<h3 className="text-xl">{langui.languages}</h3>
{appLayout.preferredLanguages && (
<OrderableList
items={
appLayout.preferredLanguages.length > 0
? new Map(
appLayout.preferredLanguages.map((locale) => [
locale,
prettyLanguage(locale, languages),
])
)
: new Map(
router.locales.map((locale) => [
locale,
prettyLanguage(locale, languages),
])
)
}
onChange={(items) => {
const preferredLanguages = [...items].map(
([code]) => code
);
console.log(router.asPath);
appLayout.setPreferredLanguages(preferredLanguages);
router.push(router.asPath, router.asPath, {
locale: preferredLanguages[0],
});
}}
/>
)}
</div>
)}
<div className="grid gap-8 place-items-center text-center desktop:grid-cols-2">
<div> <div>
<h3 className="text-xl">{langui.theme}</h3> <h3 className="text-xl">{langui.theme}</h3>
<div className="flex flex-row"> <div className="flex flex-row">
@ -337,7 +353,9 @@ export default function AppLayout(props: Props): JSX.Element {
className="rounded-r-none" className="rounded-r-none"
onClick={() => onClick={() =>
appLayout.setFontSize( appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize / 1.05 : 1 / 1.05 appLayout.fontSize
? appLayout.fontSize / 1.05
: 1 / 1.05
) )
} }
> >
@ -347,16 +365,21 @@ export default function AppLayout(props: Props): JSX.Element {
className="rounded-l-none rounded-r-none border-x-0" className="rounded-l-none rounded-r-none border-x-0"
onClick={() => appLayout.setFontSize(1)} onClick={() => appLayout.setFontSize(1)}
> >
{((appLayout.fontSize ?? 1) * 100).toLocaleString(undefined, { {((appLayout.fontSize ?? 1) * 100).toLocaleString(
undefined,
{
maximumFractionDigits: 0, maximumFractionDigits: 0,
})} }
)}
% %
</Button> </Button>
<Button <Button
className="rounded-l-none" className="rounded-l-none"
onClick={() => onClick={() =>
appLayout.setFontSize( appLayout.setFontSize(
appLayout.fontSize ? appLayout.fontSize * 1.05 : 1 * 1.05 appLayout.fontSize
? appLayout.fontSize * 1.05
: 1 * 1.05
) )
} }
> >
@ -399,6 +422,7 @@ export default function AppLayout(props: Props): JSX.Element {
/> />
</div> </div>
</div> </div>
</div>
</Popup> </Popup>
</div> </div>
</div> </div>

View File

@ -10,7 +10,7 @@ interface Props {
locale?: string; locale?: string;
target?: "_blank"; target?: "_blank";
onClick?: MouseEventHandler<HTMLDivElement>; onClick?: MouseEventHandler<HTMLDivElement>;
draggable?: boolean draggable?: boolean;
} }
export default function Button(props: Props): JSX.Element { export default function Button(props: Props): JSX.Element {

View File

@ -0,0 +1,69 @@
import { arrayMove } from "queries/helpers";
import { useEffect, useState } from "react";
interface Props {
className?: string;
items: Map<string, string>;
onChange?: (items: Map<string, string>) => void;
}
export default function LanguageSwitcher(props: Props): JSX.Element {
const [items, setItems] = useState<Map<string, string>>(props.items);
useEffect(() => {
props.onChange?.(items);
}, [items]);
return (
<div className="grid gap-2">
{[...items].map(([key, value], index) => (
<>
{index === 0 ? (
<p>Primary language</p>
) : index === 1 ? (
<p>Secondary languages</p>
) : (
""
)}
<div
onDragStart={(event) => {
const source = event.target as HTMLElement;
const sourceIndex = source.parentElement
? Array.from(source.parentElement.children)
.filter((element) => element.tagName === "DIV")
.indexOf(source)
: -1;
event.dataTransfer.setData("text", sourceIndex.toString());
}}
onDragOver={(event) => {
event.preventDefault();
}}
onDrop={(event) => {
event.preventDefault();
const target = event.target as HTMLElement;
const targetIndex = target.parentElement
? Array.from(target.parentElement.children)
.filter((element) => element.tagName === "DIV")
.indexOf(target)
: -1;
const sourceIndex = parseInt(
event.dataTransfer.getData("text"),
10
);
const newItems = arrayMove([...items], sourceIndex, targetIndex);
setItems(new Map(newItems));
}}
className="grid place-content-center place-items-center
border-[1px] transition-all hover:text-light hover:bg-dark
hover:drop-shadow-shade-lg border-dark bg-light text-dark
rounded-full px-4 pt-[0.4rem] pb-[0.5rem] cursor-grab select-none"
key={key}
draggable
>
{value}
</div>
</>
))}
</div>
);
}

View File

@ -89,26 +89,6 @@ export default function MainPanel(props: Props): JSX.Element {
</Button> </Button>
</ToolTip> </ToolTip>
{router.locale && (
<ToolTip
content={<h3 className="text-2xl">{langui.change_language}</h3>}
placement="right"
className="text-left"
disabled={!appLayout.mainPanelReduced}
>
<Button
onClick={() => appLayout.setLanguagePanelOpen(true)}
className={
appLayout.mainPanelReduced && isDesktop
? ""
: "!py-0.5 !px-2.5 !text-sm"
}
>
{router.locale.toUpperCase()}
</Button>
</ToolTip>
)}
{/* <ToolTip {/* <ToolTip
content={<h3 className="text-2xl">{langui.open_search}</h3>} content={<h3 className="text-2xl">{langui.open_search}</h3>}
placement="right" placement="right"

View File

@ -4,7 +4,6 @@ import React, { ReactNode, useContext } from "react";
interface AppLayoutState { interface AppLayoutState {
subPanelOpen: boolean | undefined; subPanelOpen: boolean | undefined;
languagePanelOpen: boolean | undefined;
configPanelOpen: boolean | undefined; configPanelOpen: boolean | undefined;
mainPanelReduced: boolean | undefined; mainPanelReduced: boolean | undefined;
mainPanelOpen: boolean | undefined; mainPanelOpen: boolean | undefined;
@ -14,10 +13,8 @@ interface AppLayoutState {
dyslexic: boolean | undefined; dyslexic: boolean | undefined;
currency: string | undefined; currency: string | undefined;
playerName: string | undefined; playerName: string | undefined;
preferredLanguages: string[] | undefined;
setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>; setSubPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setLanguagePanelOpen: React.Dispatch<
React.SetStateAction<boolean | undefined>
>;
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>; setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setMainPanelReduced: React.Dispatch< setMainPanelReduced: React.Dispatch<
React.SetStateAction<boolean | undefined> React.SetStateAction<boolean | undefined>
@ -31,12 +28,14 @@ interface AppLayoutState {
setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>; setDyslexic: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>; setCurrency: React.Dispatch<React.SetStateAction<string | undefined>>;
setPlayerName: React.Dispatch<React.SetStateAction<string | undefined>>; setPlayerName: React.Dispatch<React.SetStateAction<string | undefined>>;
setPreferredLanguages: React.Dispatch<
React.SetStateAction<string[] | undefined>
>;
} }
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
const initialState: AppLayoutState = { const initialState: AppLayoutState = {
subPanelOpen: false, subPanelOpen: false,
languagePanelOpen: false,
configPanelOpen: false, configPanelOpen: false,
mainPanelReduced: false, mainPanelReduced: false,
mainPanelOpen: false, mainPanelOpen: false,
@ -46,8 +45,8 @@ const initialState: AppLayoutState = {
dyslexic: false, dyslexic: false,
currency: "USD", currency: "USD",
playerName: "", playerName: "",
preferredLanguages: [],
setSubPanelOpen: () => {}, setSubPanelOpen: () => {},
setLanguagePanelOpen: () => {},
setMainPanelReduced: () => {}, setMainPanelReduced: () => {},
setMainPanelOpen: () => {}, setMainPanelOpen: () => {},
setDarkMode: () => {}, setDarkMode: () => {},
@ -57,6 +56,7 @@ const initialState: AppLayoutState = {
setDyslexic: () => {}, setDyslexic: () => {},
setCurrency: () => {}, setCurrency: () => {},
setPlayerName: () => {}, setPlayerName: () => {},
setPreferredLanguages: () => {},
}; };
/* eslint-enable @typescript-eslint/no-empty-function */ /* eslint-enable @typescript-eslint/no-empty-function */
@ -77,10 +77,6 @@ export function AppContextProvider(props: Props): JSX.Element {
boolean | undefined boolean | undefined
>("subPanelOpen", initialState.subPanelOpen); >("subPanelOpen", initialState.subPanelOpen);
const [languagePanelOpen, setLanguagePanelOpen] = useStateWithLocalStorage<
boolean | undefined
>("languagePanelOpen", initialState.languagePanelOpen);
const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage< const [configPanelOpen, setConfigPanelOpen] = useStateWithLocalStorage<
boolean | undefined boolean | undefined
>("configPanelOpen", initialState.configPanelOpen); >("configPanelOpen", initialState.configPanelOpen);
@ -115,11 +111,14 @@ export function AppContextProvider(props: Props): JSX.Element {
string | undefined string | undefined
>("playerName", initialState.playerName); >("playerName", initialState.playerName);
const [preferredLanguages, setPreferredLanguages] = useStateWithLocalStorage<
string[] | undefined
>("preferredLanguages", initialState.preferredLanguages);
return ( return (
<AppContext.Provider <AppContext.Provider
value={{ value={{
subPanelOpen, subPanelOpen,
languagePanelOpen,
configPanelOpen, configPanelOpen,
mainPanelReduced, mainPanelReduced,
mainPanelOpen, mainPanelOpen,
@ -129,8 +128,8 @@ export function AppContextProvider(props: Props): JSX.Element {
dyslexic, dyslexic,
currency, currency,
playerName, playerName,
preferredLanguages,
setSubPanelOpen, setSubPanelOpen,
setLanguagePanelOpen,
setConfigPanelOpen, setConfigPanelOpen,
setMainPanelReduced, setMainPanelReduced,
setMainPanelOpen, setMainPanelOpen,
@ -140,6 +139,7 @@ export function AppContextProvider(props: Props): JSX.Element {
setDyslexic, setDyslexic,
setCurrency, setCurrency,
setPlayerName, setPlayerName,
setPreferredLanguages,
}} }}
> >
{props.children} {props.children}

View File

@ -274,6 +274,18 @@ export function prettyLanguage(
return result; return result;
} }
export function prettyLanguageToCode(
prettyLanguage: string,
languages: AppStaticProps["languages"]
): string {
let result = prettyLanguage;
languages.forEach((language) => {
if (language?.attributes?.localized_name === prettyLanguage)
result = language.attributes.code;
});
return result;
}
export function prettyTestWarning( export function prettyTestWarning(
router: NextRouter, router: NextRouter,
message: string, message: string,
@ -463,3 +475,8 @@ export function getVideoThumbnailURL(uid: string): string {
export function getVideoFile(uid: string): string { export function getVideoFile(uid: string): string {
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`; return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`;
} }
export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr;
}