Added language preference system
This commit is contained in:
parent
3cf890d70d
commit
fb88e97825
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue