fix: i18n (#71)

* fix: block_identifier text

* fix: i18n

* chore: auto-fix linting and formatting issues

* fix: block button

* fix: ConfirmBlock text

* fix: i18n

---------

Co-authored-by: hamster1963 <hamster1963@users.noreply.github.com>
This commit is contained in:
仓鼠
2024-12-23 00:38:40 +08:00
committed by GitHub
parent 4d12682cdf
commit 5e3db19ed6
8 changed files with 172 additions and 16 deletions

View File

@@ -21,6 +21,12 @@ interface ButtonGroupProps<E, U> {
delete: { fn: (id: E[]) => Promise<void>; id: E; mutate: KeyedMutator<U> } delete: { fn: (id: E[]) => Promise<void>; id: E; mutate: KeyedMutator<U> }
} }
interface BlockButtonGroupProps<E, U> {
className?: string
children?: React.ReactNode
block: { fn: (id: E[]) => Promise<void>; id: E; mutate: KeyedMutator<U> }
}
export function ActionButtonGroup<E, U>({ export function ActionButtonGroup<E, U>({
className, className,
children, children,
@@ -66,3 +72,49 @@ export function ActionButtonGroup<E, U>({
</div> </div>
) )
} }
export function BlockButtonGroup<E, U>({
className,
children,
block: { fn, id, mutate },
}: BlockButtonGroupProps<E, U>) {
const { t } = useTranslation()
const handleBlock = async () => {
try {
await fn([id])
} catch (error: any) {
toast(t("Error"), {
description: error.message,
})
}
await mutate()
}
return (
<div className={className}>
{children}
<AlertDialog>
<AlertDialogTrigger asChild>
<IconButton variant="destructive" icon="ban" />
</AlertDialogTrigger>
<AlertDialogContent className="sm:max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle>{t("ConfirmBlock")}</AlertDialogTitle>
<AlertDialogDescription>
{t("Results.ThisOperationIsUnrecoverable")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
<AlertDialogAction
className={buttonVariants({ variant: "destructive" })}
onClick={handleBlock}
>
{t("Confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}

View File

@@ -21,6 +21,12 @@ interface ButtonGroupProps<E, U> {
delete: { fn: (id: E[]) => Promise<void>; id: E[]; mutate: KeyedMutator<U> } delete: { fn: (id: E[]) => Promise<void>; id: E[]; mutate: KeyedMutator<U> }
} }
interface ButtonBlockGroupProps<E, U> {
className?: string
children?: React.ReactNode
block: { fn: (id: E[]) => Promise<void>; id: E[]; mutate: KeyedMutator<U> }
}
export function HeaderButtonGroup<E, U>({ export function HeaderButtonGroup<E, U>({
className, className,
children, children,
@@ -83,3 +89,66 @@ export function HeaderButtonGroup<E, U>({
</div> </div>
) )
} }
export function HeaderBlockButtonGroup<E, U>({
className,
children,
block: { fn, id, mutate },
}: ButtonBlockGroupProps<E, U>) {
const { t } = useTranslation()
const handleBlock = async () => {
try {
await fn(id)
} catch (error: any) {
toast(t("Error"), {
description: error.message,
})
}
await mutate()
}
return (
<div className={className}>
{id.length < 1 ? (
<>
<IconButton
variant="destructive"
icon="ban"
onClick={() => {
toast(t("Error"), {
description: t("Results.NoRowsAreSelected"),
})
}}
/>
{children}
</>
) : (
<>
<AlertDialog>
<AlertDialogTrigger asChild>
<IconButton variant="destructive" icon="trash" />
</AlertDialogTrigger>
<AlertDialogContent className="sm:max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle>{t("ConfirmBlock")}</AlertDialogTitle>
<AlertDialogDescription>
{t("Results.ThisOperationIsUnrecoverable")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
<AlertDialogAction
className={buttonVariants({ variant: "destructive" })}
onClick={handleBlock}
>
{t("Confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{children}
</>
)}
</div>
)
}

View File

@@ -1,5 +1,6 @@
import { Button, ButtonProps } from "@/components/ui/button" import { Button, ButtonProps } from "@/components/ui/button"
import { import {
BanIcon,
Check, Check,
CircleArrowUp, CircleArrowUp,
Clipboard, Clipboard,
@@ -29,6 +30,7 @@ export interface IconButtonProps extends ButtonProps {
| "download" | "download"
| "upload" | "upload"
| "menu" | "menu"
| "ban"
} }
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => { export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
@@ -77,6 +79,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
case "menu": { case "menu": {
return <Menu /> return <Menu />
} }
case "ban": {
return <BanIcon />
}
} }
})()} })()}
</Button> </Button>

View File

@@ -164,5 +164,15 @@
"CommunityThemeDescription": "This theme is provided by the community, use at your own risk", "CommunityThemeDescription": "This theme is provided by the community, use at your own risk",
"Cancel": "Cancel", "Cancel": "Cancel",
"EnableDDNS": "Enable DDNS", "EnableDDNS": "Enable DDNS",
"PushSuccessful": "Push if Successful" "PushSuccessful": "Push if Successful",
"GrpcAuthFailed": "gRPC authentication failed",
"APITokenInvalid": "API token is invalid",
"UserInvalid": "User is invalid",
"BlockByUser": "Blocked by user",
"BlockIdentifier": "Block identifier",
"UserId": "User ID",
"ConnectedAt": "Connected at",
"OnlineUser": "Online User",
"Total": "Total",
"ConfirmBlock": "Confirm Block"
} }

View File

@@ -164,5 +164,15 @@
"CommunityThemeDescription": "社区主题未经官方审计,需自行甄别风险", "CommunityThemeDescription": "社区主题未经官方审计,需自行甄别风险",
"Cancel": "取消", "Cancel": "取消",
"EnableDDNS": "启用 DDNS", "EnableDDNS": "启用 DDNS",
"PushSuccessful": "推送成功的通知" "PushSuccessful": "推送成功的通知",
"GrpcAuthFailed": "gRPC 认证失败",
"APITokenInvalid": "API 令牌无效",
"UserInvalid": "用户无效",
"BlockByUser": "被用户封禁",
"BlockIdentifier": "封禁标识",
"UserId": "用户 ID",
"ConnectedAt": "连接时间",
"OnlineUser": "在线用户",
"Total": "总数",
"ConfirmBlock": "确认封禁"
} }

View File

@@ -164,5 +164,15 @@
"CommunityThemeDescription": "社群主題未經官方審計,需自行甄別風險", "CommunityThemeDescription": "社群主題未經官方審計,需自行甄別風險",
"Cancel": "取消", "Cancel": "取消",
"EnableDDNS": "啟用 DDNS", "EnableDDNS": "啟用 DDNS",
"PushSuccessful": "推送成功的通知" "PushSuccessful": "推送成功的通知",
"GrpcAuthFailed": "gRPC 認證失敗",
"APITokenInvalid": "API 令牌無效",
"UserInvalid": "使用者無效",
"BlockByUser": "被使用者封鎖",
"BlockIdentifier": "封鎖標識符",
"UserId": "使用者 ID",
"ConnectedAt": "連接時間",
"OnlineUser": "線上使用者",
"Total": "總數",
"ConfirmBlock": "確認封鎖"
} }

View File

@@ -1,7 +1,7 @@
import { swrFetcher } from "@/api/api" import { swrFetcher } from "@/api/api"
import { blockUser } from "@/api/online-user" import { blockUser } from "@/api/online-user"
import { ActionButtonGroup } from "@/components/action-button-group" import { BlockButtonGroup } from "@/components/action-button-group"
import { HeaderButtonGroup } from "@/components/header-button-group" import { HeaderBlockButtonGroup } from "@/components/header-button-group"
import { SettingsTab } from "@/components/settings-tab" import { SettingsTab } from "@/components/settings-tab"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
import { import {
@@ -104,16 +104,16 @@ export default function OnlineUserPage() {
cell: ({ row }) => { cell: ({ row }) => {
const s = row.original const s = row.original
return ( return (
<ActionButtonGroup <BlockButtonGroup
className="flex gap-2" className="flex gap-2"
delete={{ block={{
fn: blockUser, fn: blockUser,
id: s.ip ?? "", id: s.ip ?? "",
mutate: mutate, mutate: mutate,
}} }}
> >
<></> <></>
</ActionButtonGroup> </BlockButtonGroup>
) )
}, },
}, },
@@ -242,16 +242,16 @@ export default function OnlineUserPage() {
<SettingsTab className="mt-6 w-full" /> <SettingsTab className="mt-6 w-full" />
<div className="flex mt-4 mb-4"> <div className="flex mt-4 mb-4">
{isAdmin && ( {isAdmin && (
<HeaderButtonGroup <HeaderBlockButtonGroup
className="flex-2 flex gap-2 ml-auto" className="flex-2 flex gap-2 ml-auto"
delete={{ block={{
fn: blockUser, fn: blockUser,
id: selectedRows.map((r) => r.original.ip ?? ""), id: selectedRows.map((r) => r.original.ip ?? ""),
mutate: mutate, mutate: mutate,
}} }}
> >
<></> <></>
</HeaderButtonGroup> </HeaderBlockButtonGroup>
)} )}
</div> </div>
<Table> <Table>

View File

@@ -96,11 +96,11 @@ export default function WAFPage() {
cell: ({ row }) => <span>{wafBlockReasons[row.original.block_reason] || ""}</span>, cell: ({ row }) => <span>{wafBlockReasons[row.original.block_reason] || ""}</span>,
}, },
{ {
header: t("LastBlockIdentifier"), header: t("BlockIdentifier"),
accessorKey: "lastBlockIdentifier", accessorKey: "BlockIdentifier",
accessorFn: (row) => ( accessorFn: (row) => {
<span>{wafBlockIdentifiers[row.block_identifier] || row.block_identifier}</span> return wafBlockIdentifiers[row.block_identifier] || row.block_identifier
), },
}, },
{ {
header: t("LastBlockTime"), header: t("LastBlockTime"),