Skip to content

Commit 1eb11c3

Browse files
committed
feat: add language setting
1 parent ee6c2bd commit 1eb11c3

File tree

9 files changed

+536
-394
lines changed

9 files changed

+536
-394
lines changed

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"motion": "^12.31.1",
3232
"radix-ui": "^1.4.3",
3333
"react": "^18.2.0",
34+
"react-country-flag": "^3.1.0",
3435
"react-dom": "^18.2.0",
3536
"react-dropzone": "^14.3.8",
3637
"react-router-dom": "^7.12.0",
Lines changed: 123 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,131 @@
1-
import { useAppTranslation } from '../i18n/hooks'
2-
import { Select, SelectValue, SelectContent, SelectItem } from './ui/select'
3-
import { buttonVariants } from './ui/button'
4-
import type { ButtonProps } from '@base-ui/react'
5-
import { Select as RootSelect } from '@base-ui/react/select'
6-
import { cn } from '@/lib/utils'
1+
import { useAppTranslation } from "../i18n/hooks";
2+
import ReactCountryFlag from "react-country-flag";
3+
import {
4+
Combobox,
5+
ComboboxEmpty,
6+
ComboboxInput,
7+
ComboboxItem,
8+
ComboboxList,
9+
ComboboxPopup,
10+
ComboboxTrigger,
11+
ComboboxValue,
12+
} from "@/components/ui/combobox";
13+
import { Button, buttonVariants } from "./ui/button";
14+
import type { ButtonProps } from "@base-ui/react";
15+
import { LazyIcon } from "./icons";
16+
import { cn } from "../lib/utils";
17+
import { useTranslation } from "../i18n";
718

819
const LANGUAGES = [
9-
{ value: 'ar', label: 'العربية' },
10-
{ value: 'bn', label: 'বাংলা' },
11-
{ value: 'cs', label: 'Čeština' },
12-
{ value: 'de', label: 'Deutsch' },
13-
{ value: 'en', label: 'English' },
14-
{ value: 'es', label: 'Español' },
15-
{ value: 'fa', label: 'فارسی' },
16-
{ value: 'fr', label: 'Français' },
17-
{ value: 'hi', label: 'हिन्दी' },
18-
{ value: 'hu', label: 'Magyar' },
19-
{ value: 'it', label: 'Italiano' },
20-
{ value: 'ja', label: '日本語' },
21-
{ value: 'ko', label: '한국어' },
22-
{ value: 'no', label: 'Norsk' },
23-
{ value: 'pl', label: 'Polski' },
24-
{ value: 'pt-BR', label: 'Português' },
25-
{ value: 'ru', label: 'Русский' },
26-
{ value: 'sr', label: 'Српски' },
27-
{ value: 'th', label: 'Thai' },
28-
{ value: 'tr', label: 'Türkçe' },
29-
{ value: 'uk', label: 'Українська' },
30-
{ value: 'zh-CN', label: '简体中文' },
31-
{ value: 'zh-TW', label: '繁體中文' },
32-
]
20+
{ value: "ar", label: "العربية", countryCode: "SA" },
21+
{ value: "bn", label: "বাংলা", countryCode: "BD" },
22+
{ value: "cs", label: "Čeština", countryCode: "CZ" },
23+
{ value: "de", label: "Deutsch", countryCode: "DE" },
24+
{ value: "en", label: "English", countryCode: "US" },
25+
{ value: "es", label: "Español", countryCode: "ES" },
26+
{ value: "fa", label: "فارسی", countryCode: "IR" },
27+
{ value: "fr", label: "Français", countryCode: "FR" },
28+
{ value: "hi", label: "हिन्दी", countryCode: "IN" },
29+
{ value: "hu", label: "Magyar", countryCode: "HU" },
30+
{ value: "it", label: "Italiano", countryCode: "IT" },
31+
{ value: "ja", label: "日本語", countryCode: "JP" },
32+
{ value: "ko", label: "한국어", countryCode: "KR" },
33+
{ value: "no", label: "Norsk", countryCode: "NO" },
34+
{ value: "pl", label: "Polski", countryCode: "PL" },
35+
{ value: "pt-BR", label: "Português", countryCode: "BR" },
36+
{ value: "ru", label: "Русский", countryCode: "RU" },
37+
{ value: "sr", label: "Српски", countryCode: "RS" },
38+
{ value: "th", label: "Thai", countryCode: "TH" },
39+
{ value: "tr", label: "Türkçe", countryCode: "TR" },
40+
{ value: "uk", label: "Українська", countryCode: "UA" },
41+
{ value: "zh-CN", label: "简体中文", countryCode: "CN" },
42+
{ value: "zh-TW", label: "繁體中文", countryCode: "TW" },
43+
];
3344

3445
export function LanguageSwitcher(props: ButtonProps) {
35-
const { i18n } = useAppTranslation()
46+
const { i18n } = useAppTranslation();
47+
const { t } = useTranslation();
3648

37-
const currentLanguage =
38-
LANGUAGES.find((lang) => lang.value === i18n.language) || LANGUAGES[0]
49+
const currentLanguage =
50+
LANGUAGES.find((lang) => lang.value === i18n.language) || LANGUAGES[0];
3951

40-
const changeLanguage = (lng: string) => {
41-
i18n.changeLanguage(lng)
42-
window.dispatchEvent(new Event('languagechange'))
43-
}
52+
const changeLanguage = (lng: string) => {
53+
i18n.changeLanguage(lng);
54+
window.dispatchEvent(new Event("languagechange"));
55+
};
4456

45-
return (
46-
<Select
47-
// items={LANGUAGES}
48-
value={currentLanguage}
49-
onValueChange={(item) => {
50-
return item && changeLanguage(item?.value)
51-
}}
52-
>
53-
<RootSelect.Trigger
54-
{...props}
55-
className={cn(
56-
buttonVariants({ variant: 'ghost', size: 'sm' }),
57-
props.className
58-
)}
59-
>
60-
<SelectValue />
61-
</RootSelect.Trigger>
62-
<SelectContent sideOffset={10} className="max-h-[30vh]">
63-
{LANGUAGES.map((lang) => (
64-
<SelectItem value={lang} key={lang.value}>
65-
{lang.label}
66-
</SelectItem>
67-
))}
68-
</SelectContent>
69-
</Select>
70-
)
57+
return (
58+
<Combobox
59+
items={LANGUAGES}
60+
value={currentLanguage}
61+
defaultInputValue={""}
62+
onValueChange={(item) => {
63+
return item && changeLanguage(item?.value);
64+
}}
65+
>
66+
<ComboboxTrigger aria-label="Select an item" {...props}>
67+
<div
68+
className={cn(
69+
buttonVariants({ variant: "outline" }),
70+
"justify-between",
71+
props.className,
72+
)}
73+
>
74+
<ComboboxValue />
75+
<LazyIcon
76+
name="CaretDown"
77+
className="-me-1 text-muted-foreground"
78+
/>
79+
</div>
80+
</ComboboxTrigger>
81+
<ComboboxPopup>
82+
<div className="border-b p-2">
83+
<ComboboxInput
84+
className="rounded-md before:rounded-[calc(var(--radius-md)-1px)]"
85+
placeholder="e.g. English"
86+
showTrigger={false}
87+
startAddon={
88+
<LazyIcon
89+
name="MagnifyingGlass"
90+
className="mx-2 text-muted-foreground"
91+
/>
92+
}
93+
/>
94+
</div>
95+
<ComboboxEmpty className="gap-4">
96+
<LazyIcon
97+
name="FunnelSimpleX"
98+
weight="duotone"
99+
size="48"
100+
className="text-muted-foreground opacity-50"
101+
/>
102+
{t("settings.language.noResults")}
103+
</ComboboxEmpty>
104+
<ComboboxList>
105+
{(item: {
106+
value: string;
107+
label: string;
108+
countryCode?: string;
109+
}) => (
110+
<ComboboxItem key={item.value} value={item}>
111+
{item.countryCode && (
112+
<ReactCountryFlag
113+
countryCode={item.countryCode}
114+
svg
115+
style={{
116+
width: "1.5em",
117+
height: "1.5em",
118+
verticalAlign: "middle",
119+
marginRight: "0.5em",
120+
borderRadius: "0.25em",
121+
}}
122+
/>
123+
)}
124+
{item.label}
125+
</ComboboxItem>
126+
)}
127+
</ComboboxList>
128+
</ComboboxPopup>
129+
</Combobox>
130+
);
71131
}

web-app/src/components/icons.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@ export type IconName =
88
| "BellRinging"
99
| "Network"
1010
| "House"
11-
| "CheckCircle";
11+
| "CheckCircle"
12+
| "XCircle"
13+
| "User"
14+
| "Users"
15+
| "MagnifyingGlass"
16+
| "CaretDown"
17+
| "CaretUp"
18+
| "CaretLeft"
19+
| "Hexagon"
20+
| "FunnelSimpleX"
21+
| "CaretRight";
1222

1323
export function LazyIcon(props: IconProps & { name: IconName }) {
1424
const Icon = P[props.name];

web-app/src/components/settings/language-select/index.ts

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useTranslation } from "../../../i18n";
2+
import { LanguageSwitcher } from "../../LanguageSwitcher";
3+
import {
4+
Frame,
5+
FrameDescription,
6+
FramePanel,
7+
FrameTitle,
8+
} from "../../ui/frame";
9+
10+
export function LanguageSelect() {
11+
const { t } = useTranslation();
12+
return (
13+
<Frame>
14+
<FramePanel className="flex items-center justify-between">
15+
<div className="flex-1">
16+
<FrameTitle>{t("settings.language.title")}</FrameTitle>
17+
<FrameDescription>
18+
{t("settings.language.description")}
19+
</FrameDescription>
20+
</div>
21+
<LanguageSwitcher className="w-40" />
22+
</FramePanel>
23+
</Frame>
24+
);
25+
}

0 commit comments

Comments
 (0)