Skip to content

Commit e6ff332

Browse files
committed
feat(i18n): switch language on mobile
1 parent d9b8b8e commit e6ff332

File tree

3 files changed

+95
-56
lines changed

3 files changed

+95
-56
lines changed

src/GZCTF/ClientApp/src/components/AppHeader.tsx

Lines changed: 91 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Burger, Group, Menu, useMantineColorScheme, AppShell } from '@mantine/core'
1+
import { Burger, Group, Menu, useMantineColorScheme, AppShell, ActionIcon } from '@mantine/core'
22
import { createStyles } from '@mantine/emotion'
33
import {
44
mdiAccountCircleOutline,
55
mdiAccountGroupOutline,
66
mdiCached,
77
mdiLogout,
8+
mdiTranslate,
89
mdiWeatherNight,
910
mdiWeatherSunny,
1011
} from '@mdi/js'
@@ -13,6 +14,7 @@ import { FC, useState } from 'react'
1314
import { useTranslation } from 'react-i18next'
1415
import { Link, useLocation, useNavigate } from 'react-router-dom'
1516
import LogoHeader from '@Components/LogoHeader'
17+
import { LanguageMap, SupportedLanguages, useLanguage } from '@Utils/I18n'
1618
import { useIsMobile } from '@Utils/ThemeOverride'
1719
import { clearLocalCache } from '@Utils/useConfig'
1820
import { useLogOut, useUser } from '@Utils/useUser'
@@ -32,6 +34,15 @@ const useHeaderStyles = createStyles((theme, _, u) => ({
3234
backgroundColor: theme.colors.light[0],
3335
},
3436
},
37+
button: {
38+
[u.dark]: {
39+
color: theme.colors.gray[0],
40+
},
41+
42+
[u.light]: {
43+
color: theme.colors.gray[7],
44+
},
45+
},
3546
}))
3647

3748
const AppHeader: FC = () => {
@@ -48,73 +59,99 @@ const AppHeader: FC = () => {
4859
const isMobile = useIsMobile()
4960

5061
const { t } = useTranslation()
62+
const { setLanguage, supportedLanguages } = useLanguage()
5163

5264
return (
5365
<AppShell.Header hidden={!isMobile} h={isMobile ? 60 : 0} className={headerClasses.header}>
5466
<Group h="100%" p="0 1rem" justify="space-between" wrap="nowrap">
5567
<LogoHeader onClick={() => navigate('/')} />
56-
<Menu shadow="md" opened={opened} onClose={() => setOpened(false)} width={200} offset={13}>
57-
<Menu.Target>
58-
<Burger opened={opened} onClick={() => setOpened((o) => !o)} />
59-
</Menu.Target>
60-
<Menu.Dropdown>
61-
{user && !error ? (
62-
<>
63-
<Menu.Item
64-
component={Link}
65-
to="/teams"
66-
leftSection={<Icon path={mdiAccountGroupOutline} size={1} />}
67-
>
68-
{t('common.tab.team')}
68+
<Group justify="flex-end" wrap="nowrap">
69+
<Menu position="bottom-end" offset={24} width={160}>
70+
<Menu.Target>
71+
<ActionIcon className={headerClasses.button}>
72+
<Icon path={mdiTranslate} size={1} />
73+
</ActionIcon>
74+
</Menu.Target>
75+
76+
<Menu.Dropdown>
77+
{supportedLanguages.map((lang: SupportedLanguages) => (
78+
<Menu.Item key={lang} fw={500} fz="md" onClick={() => setLanguage(lang)}>
79+
{LanguageMap[lang] ?? lang}
6980
</Menu.Item>
81+
))}
82+
</Menu.Dropdown>
83+
</Menu>
84+
<Menu
85+
shadow="md"
86+
opened={opened}
87+
onClose={() => setOpened(false)}
88+
width={200}
89+
offset={13}
90+
>
91+
<Menu.Target>
92+
<Burger opened={opened} onClick={() => setOpened((o) => !o)} />
93+
</Menu.Target>
94+
<Menu.Dropdown>
95+
{user && !error ? (
96+
<>
97+
<Menu.Item
98+
component={Link}
99+
to="/teams"
100+
leftSection={<Icon path={mdiAccountGroupOutline} size={1} />}
101+
>
102+
{t('common.tab.team')}
103+
</Menu.Item>
104+
<Menu.Item
105+
component={Link}
106+
to="/account/profile"
107+
leftSection={<Icon path={mdiAccountCircleOutline} size={1} />}
108+
>
109+
{t('common.tab.account.profile')}
110+
</Menu.Item>
111+
<Menu.Item
112+
onClick={clearLocalCache}
113+
leftSection={<Icon path={mdiCached} size={1} />}
114+
>
115+
{t('common.tab.account.clean_cache')}
116+
</Menu.Item>
117+
<Menu.Item
118+
color="red"
119+
onClick={logout}
120+
leftSection={<Icon path={mdiLogout} size={1} />}
121+
>
122+
{t('common.tab.account.logout')}
123+
</Menu.Item>
124+
</>
125+
) : (
70126
<Menu.Item
71127
component={Link}
72-
to="/account/profile"
128+
to={`/account/login?from=${location.pathname}`}
73129
leftSection={<Icon path={mdiAccountCircleOutline} size={1} />}
74130
>
75-
{t('common.tab.account.profile')}
76-
</Menu.Item>
77-
<Menu.Item
78-
onClick={clearLocalCache}
79-
leftSection={<Icon path={mdiCached} size={1} />}
80-
>
81-
{t('common.tab.account.clean_cache')}
82-
</Menu.Item>
83-
<Menu.Item
84-
color="red"
85-
onClick={logout}
86-
leftSection={<Icon path={mdiLogout} size={1} />}
87-
>
88-
{t('common.tab.account.logout')}
131+
{t('common.tab.account.login')}
89132
</Menu.Item>
90-
</>
91-
) : (
133+
)}
134+
<Menu.Divider />
92135
<Menu.Item
93-
component={Link}
94-
to={`/account/login?from=${location.pathname}`}
95-
leftSection={<Icon path={mdiAccountCircleOutline} size={1} />}
136+
leftSection={
137+
colorScheme === 'dark' ? (
138+
<Icon path={mdiWeatherSunny} size={1} />
139+
) : (
140+
<Icon path={mdiWeatherNight} size={1} />
141+
)
142+
}
143+
onClick={() => toggleColorScheme()}
96144
>
97-
{t('common.tab.account.login')}
145+
{t('common.tab.theme.switch_to', {
146+
theme:
147+
colorScheme === 'dark'
148+
? t('common.tab.theme.light')
149+
: t('common.tab.theme.dark'),
150+
})}
98151
</Menu.Item>
99-
)}
100-
<Menu.Divider />
101-
<Menu.Item
102-
leftSection={
103-
colorScheme === 'dark' ? (
104-
<Icon path={mdiWeatherSunny} size={1} />
105-
) : (
106-
<Icon path={mdiWeatherNight} size={1} />
107-
)
108-
}
109-
onClick={() => toggleColorScheme()}
110-
>
111-
{t('common.tab.theme.switch_to', {
112-
theme:
113-
colorScheme === 'dark' ? t('common.tab.theme.light') : t('common.tab.theme.dark'),
114-
})}
115-
</Menu.Item>
116-
</Menu.Dropdown>
117-
</Menu>
152+
</Menu.Dropdown>
153+
</Menu>
154+
</Group>
118155
</Group>
119156
</AppShell.Header>
120157
)

src/GZCTF/ClientApp/src/components/AppNavbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ const AppNavbar: FC = () => {
205205

206206
<Menu.Dropdown>
207207
{supportedLanguages.map((lang: SupportedLanguages) => (
208-
<Menu.Item key={lang} onClick={() => setLanguage(lang)}>
208+
<Menu.Item key={lang} fw={500} fz="md" onClick={() => setLanguage(lang)}>
209209
{LanguageMap[lang] ?? lang}
210210
</Menu.Item>
211211
))}

src/GZCTF/ClientApp/src/components/ErrorFallback.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ import { Button, Center, Text, Stack, Title, useMantineTheme, Textarea, Group }
22
import { FC } from 'react'
33
import { FallbackProps } from 'react-error-boundary'
44
import { useTranslation } from 'react-i18next'
5+
import { useIsMobile } from '@Utils/ThemeOverride'
56
import { clearLocalCache } from '@Utils/useConfig'
67

78
const ErrorFallback: FC<FallbackProps> = ({ error, resetErrorBoundary }: FallbackProps) => {
89
const theme = useMantineTheme()
910
const { t } = useTranslation()
11+
const isMobile = useIsMobile()
1012

1113
return (
1214
<Center h="100vh" px="15%">
13-
<Stack maw="60rem" miw="30rem" w="70%" gap="sm">
15+
<Stack maw="60rem" miw={isMobile ? '92vw' : '30rem'} w="70%" gap="sm">
1416
<Title fw="bold" order={1} c={theme.primaryColor}>
1517
# {t('common.error.encountered')}
1618
</Title>

0 commit comments

Comments
 (0)