i18n tweaks / layout fixes

This commit is contained in:
Henry Dollman
2024-10-29 18:08:55 -04:00
parent e64fad9584
commit f6e391f8a9
7 changed files with 120 additions and 107 deletions

View File

@@ -9,12 +9,7 @@ import {
DialogTrigger, DialogTrigger,
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip' import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
import { import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
@@ -51,7 +46,9 @@ export function AddSystemButton({ className }: { className?: string }) {
} }
function copyInstallCommand(port: string) { function copyInstallCommand(port: string) {
copyToClipboard(`curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/supplemental/scripts/install-agent.sh -o install-agent.sh && chmod +x install-agent.sh && ./install-agent.sh -p ${port} -k "${publicKey}"`) copyToClipboard(
`curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/supplemental/scripts/install-agent.sh -o install-agent.sh && chmod +x install-agent.sh && ./install-agent.sh -p ${port} -k "${publicKey}"`
)
} }
async function handleSubmit(e: SubmitEvent) { async function handleSubmit(e: SubmitEvent) {
@@ -77,10 +74,11 @@ export function AddSystemButton({ className }: { className?: string }) {
className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')} className={cn('flex gap-1 max-xs:h-[2.4rem]', className, isReadOnlyUser() && 'hidden')}
> >
<PlusIcon className="h-4 w-4 -ml-1" /> <PlusIcon className="h-4 w-4 -ml-1" />
{t('add')}<span className="hidden xs:inline">{t('system')}</span> {t('add')}
<span className="hidden sm:inline">{t('system')}</span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="w-[90%] sm:max-w-[425px] rounded-lg"> <DialogContent className="w-[90%] sm:max-w-[440px] rounded-lg">
<Tabs defaultValue="docker"> <Tabs defaultValue="docker">
<DialogHeader> <DialogHeader>
<DialogTitle className="mb-2">{t('add_system.add_new_system')}</DialogTitle> <DialogTitle className="mb-2">{t('add_system.add_new_system')}</DialogTitle>
@@ -93,14 +91,16 @@ export function AddSystemButton({ className }: { className?: string }) {
<TabsContent value="docker"> <TabsContent value="docker">
<DialogDescription className={'mb-4'}> <DialogDescription className={'mb-4'}>
{t('add_system.dialog_des_1')}{' '} {t('add_system.dialog_des_1')}{' '}
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code> {t('add_system.dialog_des_2')} <code className="bg-muted px-1 rounded-sm">docker-compose.yml</code>{' '}
{t('add_system.dialog_des_2')}
</DialogDescription> </DialogDescription>
</TabsContent> </TabsContent>
{/* Binary */} {/* Binary */}
<TabsContent value="binary"> <TabsContent value="binary">
<DialogDescription className={'mb-4'}> <DialogDescription className={'mb-4'}>
{t('add_system.dialog_des_1')}{' '} {t('add_system.dialog_des_1')}{' '}
<code className="bg-muted px-1 rounded-sm">install command</code> {t('add_system.dialog_des_2')} <code className="bg-muted px-1 rounded-sm">install command</code>{' '}
{t('add_system.dialog_des_2')}
</DialogDescription> </DialogDescription>
</TabsContent> </TabsContent>
<form onSubmit={handleSubmit as any}> <form onSubmit={handleSubmit as any}>
@@ -161,7 +161,7 @@ export function AddSystemButton({ className }: { className?: string }) {
</div> </div>
{/* Docker */} {/* Docker */}
<TabsContent value="docker"> <TabsContent value="docker">
<DialogFooter className="flex justify-end gap-2"> <DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
<Button <Button
type="button" type="button"
variant={'ghost'} variant={'ghost'}
@@ -174,7 +174,7 @@ export function AddSystemButton({ className }: { className?: string }) {
</TabsContent> </TabsContent>
{/* Binary */} {/* Binary */}
<TabsContent value="binary"> <TabsContent value="binary">
<DialogFooter className="flex justify-end gap-2"> <DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
<Button <Button
type="button" type="button"
variant={'ghost'} variant={'ghost'}

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { GlobeIcon, Languages } from 'lucide-react' import { LanguagesIcon } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { import {
@@ -10,19 +10,20 @@ import {
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import languages from '../lib/languages.json' import languages from '../lib/languages.json'
import { cn } from '@/lib/utils'
export function LangToggle() { export function LangToggle() {
const { i18n } = useTranslation(); const { i18n } = useTranslation()
useEffect(() => { useEffect(() => {
document.documentElement.lang = i18n.language; document.documentElement.lang = i18n.language
}, [i18n.language]); }, [i18n.language])
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant={'ghost'} size="icon"> <Button variant={'ghost'} size="icon" className="hidden 450:flex">
<GlobeIcon className="absolute h-[1.2rem] w-[1.2rem]" /> <LanguagesIcon className="absolute h-[1.2rem] w-[1.2rem]" />
<span className="sr-only">Language</span> <span className="sr-only">Language</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@@ -30,7 +31,7 @@ export function LangToggle() {
{languages.map(({ lang, label }) => ( {languages.map(({ lang, label }) => (
<DropdownMenuItem <DropdownMenuItem
key={lang} key={lang}
className={lang === i18n.language ? 'font-bold' : ''} className={cn('pl-4', lang === i18n.language ? 'font-bold' : '')}
onClick={() => i18n.changeLanguage(lang)} onClick={() => i18n.changeLanguage(lang)}
> >
{label} {label}

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next'
const SystemsTable = lazy(() => import('../systems-table/systems-table')) const SystemsTable = lazy(() => import('../systems-table/systems-table'))
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
export default function () { export default function () {
const { t } = useTranslation() const { t } = useTranslation()
@@ -78,13 +78,13 @@ export default function () {
> >
<info.icon className="h-4 w-4" /> <info.icon className="h-4 w-4" />
<AlertTitle> <AlertTitle>
{alert.sysname} {info.name} {alert.sysname} {t(info.name)}
</AlertTitle> </AlertTitle>
<AlertDescription> <AlertDescription>
{t('active_des', { {t('home.active_des', {
value: alert.value, value: alert.value,
unit: info.unit, unit: info.unit,
minutes: alert.min minutes: alert.min,
})} })}
</AlertDescription> </AlertDescription>
<Link <Link
@@ -108,7 +108,7 @@ export default function () {
<CardDescription> <CardDescription>
{t('home.subtitle_1')}{' '} {t('home.subtitle_1')}{' '}
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"> <kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-0.5 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
<span className="text-xs">{isMac ? '⌘' : "Ctrl"}</span>K <span className="text-xs">{isMac ? '⌘' : 'Ctrl'}</span>K
</kbd>{' '} </kbd>{' '}
{t('home.subtitle_2')} {t('home.subtitle_2')}
</CardDescription> </CardDescription>

View File

@@ -9,7 +9,7 @@ import {
} from '@/components/ui/select' } from '@/components/ui/select'
import { chartTimeData } from '@/lib/utils' import { chartTimeData } from '@/lib/utils'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { LoaderCircleIcon, SaveIcon } from 'lucide-react' import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from 'lucide-react'
import { UserSettings } from '@/types' import { UserSettings } from '@/types'
import { saveSettings } from './layout' import { saveSettings } from './layout'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
@@ -21,8 +21,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
useEffect(() => { useEffect(() => {
document.documentElement.lang = i18n.language; document.documentElement.lang = i18n.language
}, [i18n.language]); }, [i18n.language])
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@@ -47,7 +47,10 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
<form onSubmit={handleSubmit} className="space-y-5"> <form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-2"> <div className="space-y-2">
<div className="mb-4"> <div className="mb-4">
<h3 className="mb-1 text-lg font-medium">{t('settings.general.language.title')}</h3> <h3 className="mb-1 text-lg font-medium flex items-center gap-2">
<LanguagesIcon className="h-4 w-4" />
{t('settings.general.language.title')}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
{t('settings.general.language.subtitle_1')}{' '} {t('settings.general.language.subtitle_1')}{' '}
<a href="https://crowdin.com/project/beszel" className="link" target="_blank"> <a href="https://crowdin.com/project/beszel" className="link" target="_blank">
@@ -59,7 +62,7 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
<Label className="block" htmlFor="lang"> <Label className="block" htmlFor="lang">
{t('settings.general.language.preferred_language')} {t('settings.general.language.preferred_language')}
</Label> </Label>
<Select defaultValue={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}> <Select value={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
<SelectTrigger id="lang"> <SelectTrigger id="lang">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@@ -72,9 +75,12 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<Separator />
<div className="space-y-2"> <div className="space-y-2">
<div className="mb-4"> <div className="mb-4">
<h3 className="mb-1 text-lg font-medium">{t('settings.general.chart_options.title')}</h3> <h3 className="mb-1 text-lg font-medium">
{t('settings.general.chart_options.title')}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
{t('settings.general.chart_options.subtitle')} {t('settings.general.chart_options.subtitle')}
</p> </p>

View File

@@ -9,7 +9,7 @@
"continue": "Continue", "continue": "Continue",
"home": { "home": {
"active_alerts": "Active Alerts", "active_alerts": "Active Alerts",
"active_des": "Exceeds {{value}}{{unit}} average in last {{minutes}} minutes", "active_des": "Exceeds {{value}}{{unit}} average in last {{minutes}} min",
"subtitle_1": "Updated in real time. Press", "subtitle_1": "Updated in real time. Press",
"subtitle_2": "to open the command palette." "subtitle_2": "to open the command palette."
}, },

View File

@@ -51,6 +51,7 @@ import { AddSystemButton } from './components/add-system.tsx'
import './lib/i18n.ts' import './lib/i18n.ts'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { TFunction } from 'i18next'
// const ServerDetail = lazy(() => import('./components/routes/system.tsx')) // const ServerDetail = lazy(() => import('./components/routes/system.tsx'))
const CommandPalette = lazy(() => import('./components/command-palette.tsx')) const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
@@ -114,6 +115,84 @@ const App = () => {
} }
} }
const Navbar = (t: TFunction<'translation', undefined>) => {
return (
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
<Link href="/" aria-label="Home" className={'p-2 pl-0'}>
<Logo className="h-[1.15em] fill-foreground" />
</Link>
<div className={'flex ml-auto items-center'}>
<LangToggle />
<ModeToggle />
<Link
href="/settings/general"
aria-label="Settings"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<SettingsIcon className="h-[1.2rem] w-[1.2rem]" />
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
aria-label="User Actions"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<UserIcon className="h-[1.2rem] w-[1.2rem]" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align={isReadOnlyUser() ? 'end' : 'center'} className="min-w-44">
<DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{isAdmin() && (
<>
<DropdownMenuItem asChild>
<a href="/_/" target="_blank">
<UsersIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.users')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/collections?collectionId=2hz5ncl8tizk5nx" target="_blank">
<ServerIcon className="mr-2.5 h-4 w-4" />
<span>{t('systems')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/logs" target="_blank">
<LogsIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.logs')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/settings/backups" target="_blank">
<DatabaseBackupIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.backups')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/settings/auth-providers" target="_blank">
<LockKeyholeIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.auth_providers')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
</DropdownMenuGroup>
<DropdownMenuItem onSelect={() => pb.authStore.clear()}>
<LogOutIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.log_out')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AddSystemButton className="ml-2" />
</div>
</div>
)
}
const Layout = () => { const Layout = () => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -130,81 +209,7 @@ const Layout = () => {
return ( return (
<> <>
<div className="container"> <div className="container">{Navbar(t)}</div>
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
<Link href="/" aria-label="Home" className={'p-2 pl-0'}>
<Logo className="h-[1.15em] fill-foreground" />
</Link>
<div className={'flex ml-auto items-center'}>
<LangToggle />
<ModeToggle />
<Link
href="/settings/general"
aria-label="Settings"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<SettingsIcon className="h-[1.2rem] w-[1.2rem]" />
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
aria-label="User Actions"
className={cn('', buttonVariants({ variant: 'ghost', size: 'icon' }))}
>
<UserIcon className="h-[1.2rem] w-[1.2rem]" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align={isReadOnlyUser() ? 'end' : 'center'} className="min-w-44">
<DropdownMenuLabel>{pb.authStore.model?.email}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{isAdmin() && (
<>
<DropdownMenuItem asChild>
<a href="/_/" target="_blank">
<UsersIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.users')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/collections?collectionId=2hz5ncl8tizk5nx" target="_blank">
<ServerIcon className="mr-2.5 h-4 w-4" />
<span>{t('systems')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/logs" target="_blank">
<LogsIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.logs')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/settings/backups" target="_blank">
<DatabaseBackupIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.backups')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="/_/#/settings/auth-providers" target="_blank">
<LockKeyholeIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.auth_providers')}</span>
</a>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
</DropdownMenuGroup>
<DropdownMenuItem onSelect={() => pb.authStore.clear()}>
<LogOutIcon className="mr-2.5 h-4 w-4" />
<span>{t('user_dm.log_out')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AddSystemButton className="ml-2" />
</div>
</div>
</div>
<div className="container mb-14 relative"> <div className="container mb-14 relative">
<App /> <App />
<Suspense> <Suspense>

View File

@@ -24,6 +24,7 @@ module.exports = {
}, },
screens: { screens: {
xs: '425px', xs: '425px',
450: '450px',
}, },
colors: { colors: {
green: { green: {