translate new element to English

This commit is contained in:
yoan
2024-09-28 07:46:55 +08:00
48 changed files with 1532 additions and 806 deletions

View File

@@ -0,0 +1,33 @@
import { Languages } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function LocaleToggle() {
const { i18n } = useTranslation()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Languages className="h-[1.2rem] w-[1.2rem] dark:text-white" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Object.keys(i18n.store.data).map(key => (
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
{i18n.store.data[key].name as string}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -1,4 +1,5 @@
import { Moon, Sun } from "lucide-react";
import { useTranslation } from 'react-i18next'
import { Button } from "@/components/ui/button";
import {
@@ -11,6 +12,8 @@ import { useTheme } from "./ThemeProvider";
export function ThemeToggle() {
const { setTheme } = useTheme();
const { t } = useTranslation();
return (
<DropdownMenu>
@@ -23,13 +26,13 @@ export function ThemeToggle() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
{t('theme.light')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
{t('theme.dark')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
{t('theme.system')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -29,12 +30,13 @@ const AccessAliyunForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
accessKeyId: z.string().min(1).max(64),
accessSecretId: z.string().min(1).max(64),
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: AliyunConfig = {
@@ -47,7 +49,7 @@ const AccessAliyunForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "aliyun",
accessKeyId: config.accessKeyId,
accessSecretId: config.accessKeySecret,
@@ -111,9 +113,9 @@ const AccessAliyunForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -126,7 +128,7 @@ const AccessAliyunForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -141,7 +143,7 @@ const AccessAliyunForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -156,9 +158,9 @@ const AccessAliyunForm = ({
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKeyId</FormLabel>
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKeyId" {...field} />
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -171,9 +173,9 @@ const AccessAliyunForm = ({
name="accessSecretId"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKeySecret</FormLabel>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKeySecret" {...field} />
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -184,7 +186,7 @@ const AccessAliyunForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -28,11 +29,12 @@ const AccessCloudflareForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
dnsApiToken: z.string().min(1).max(64),
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: CloudflareConfig = {
@@ -44,7 +46,7 @@ const AccessCloudflareForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "cloudflare",
dnsApiToken: config.dnsApiToken,
},
@@ -106,9 +108,9 @@ const AccessCloudflareForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -121,7 +123,7 @@ const AccessCloudflareForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -136,7 +138,7 @@ const AccessCloudflareForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -151,9 +153,9 @@ const AccessCloudflareForm = ({
name="dnsApiToken"
render={({ field }) => (
<FormItem>
<FormLabel>CLOUD_DNS_API_TOKEN</FormLabel>
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel>
<FormControl>
<Input placeholder="请输入CLOUD_DNS_API_TOKEN" {...field} />
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -162,7 +164,7 @@ const AccessCloudflareForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -8,6 +8,7 @@ import {
import { ScrollArea } from "@/components/ui/scroll-area";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import AccessTencentForm from "./AccessTencentForm";
@@ -47,6 +48,7 @@ export function AccessEdit({
className,
}: TargetConfigEditProps) {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const typeKeys = Array.from(accessTypeMap.keys());
@@ -157,25 +159,24 @@ export function AccessEdit({
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{op == "add" ? "添加" : "编辑"}</DialogTitle>
<DialogTitle>{op == "add" ? t('access.add') : t('access.edit')}</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[80vh]">
<div className="container py-3">
<Label></Label>
<Label>{t('access.type')}</Label>
<Select
onValueChange={(val) => {
console.log(val);
setConfigType(val);
}}
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder="请选择服务商" />
<SelectValue placeholder={t('access.type.not.empty')} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel></SelectLabel>
<SelectLabel>{t('access.type')}</SelectLabel>
{typeKeys.map((key) => (
<SelectItem value={key} key={key}>
<div
@@ -188,7 +189,7 @@ export function AccessEdit({
src={accessTypeMap.get(key)?.[1]}
className="h-6 w-6"
/>
<div>{accessTypeMap.get(key)?.[0]}</div>
<div>{t(accessTypeMap.get(key)?.[0] || '')}</div>
</div>
</SelectItem>
))}

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -33,12 +34,13 @@ const AccessGodaddyFrom = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1).max(64),
apiSecret: z.string().min(1).max(64),
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: GodaddyConfig = {
@@ -51,7 +53,7 @@ const AccessGodaddyFrom = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "godaddy",
apiKey: config.apiKey,
apiSecret: config.apiSecret,
@@ -115,9 +117,9 @@ const AccessGodaddyFrom = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -130,7 +132,7 @@ const AccessGodaddyFrom = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -145,7 +147,7 @@ const AccessGodaddyFrom = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -160,9 +162,9 @@ const AccessGodaddyFrom = ({
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>GODADDY_API_KEY</FormLabel>
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入GODADDY_API_KEY" {...field} />
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -175,9 +177,9 @@ const AccessGodaddyFrom = ({
name="apiSecret"
render={({ field }) => (
<FormItem>
<FormLabel>GODADDY_API_SECRET</FormLabel>
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel>
<FormControl>
<Input placeholder="请输入GODADDY_API_SECRET" {...field} />
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -186,7 +188,7 @@ const AccessGodaddyFrom = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -25,6 +25,7 @@ import { update } from "@/repository/access_group";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { useState } from "react";
import { useTranslation } from "react-i18next";
type AccessGroupEditProps = {
className?: string;
@@ -35,9 +36,10 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { reloadAccessGroups } = useConfig();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const formSchema = z.object({
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
const form = useForm<z.infer<typeof formSchema>>({
@@ -78,14 +80,13 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogTitle>{t('access.group.add')}</DialogTitle>
</DialogHeader>
<div className="container py-3">
<Form {...form}>
<form
onSubmit={(e) => {
console.log(e);
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
@@ -96,9 +97,9 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('access.group.name')}</FormLabel>
<FormControl>
<Input placeholder="请输入组名" {...field} type="text" />
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" />
</FormControl>
<FormMessage />
@@ -107,7 +108,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -29,6 +29,7 @@ import { Group } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const AccessGroupList = () => {
const {
@@ -39,8 +40,7 @@ const AccessGroupList = () => {
const { toast } = useToast();
const navigate = useNavigate();
const { t } = useTranslation();
const handleRemoveClick = async (id: string) => {
try {
@@ -48,7 +48,7 @@ const AccessGroupList = () => {
reloadAccessGroups();
} catch (e) {
toast({
title: "删除失败",
title: t('delete.failed'),
description: getErrMessage(e),
variant: "destructive",
});
@@ -69,10 +69,10 @@ const AccessGroupList = () => {
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
{t('access.group.domain.empty')}
</div>
<AccessGroupEdit
trigger={<Button></Button>}
trigger={<Button>{t('access.group.add')}</Button>}
className="mt-3"
/>
</div>
@@ -86,9 +86,7 @@ const AccessGroupList = () => {
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{accessGroup.expand ? accessGroup.expand.access.length : 0}
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
@@ -123,7 +121,7 @@ const AccessGroupList = () => {
<Group size={40} />
</div>
<div className="ml-2">
使
{t('access.group.empty')}
</div>
</div>
</>
@@ -151,7 +149,7 @@ const AccessGroupList = () => {
);
}}
>
{t('access.all')}
</Button>
</div>
</Show>
@@ -159,14 +157,14 @@ const AccessGroupList = () => {
<Show
when={
!accessGroup.expand ||
accessGroup.expand.access.length == 0
accessGroup.expand.access.length == 0
? true
: false
}
>
<div>
<Button size="sm" onClick={handleAddAccess}>
{t('access.add')}
</Button>
</div>
</Show>
@@ -175,21 +173,21 @@ const AccessGroupList = () => {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
{t('delete')}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">
{t('access.group.delete')}
</AlertDialogTitle>
<AlertDialogDescription>
{t('access.group.delete.confirm')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">
{t('cancel')}
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
@@ -198,7 +196,7 @@ const AccessGroupList = () => {
);
}}
>
{t('confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -28,11 +29,12 @@ const AccessNamesiloForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1).max(64),
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: NamesiloConfig = {
@@ -44,14 +46,13 @@ const AccessNamesiloForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "namesilo",
apiKey: config.apiKey,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
const req: Access = {
id: data.id as string,
name: data.name,
@@ -106,9 +107,9 @@ const AccessNamesiloForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -121,7 +122,7 @@ const AccessNamesiloForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -136,7 +137,7 @@ const AccessNamesiloForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -151,9 +152,9 @@ const AccessNamesiloForm = ({
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>NAMESILO_API_KEY</FormLabel>
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入NAMESILO_API_KEY" {...field} />
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -162,7 +163,7 @@ const AccessNamesiloForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -29,12 +30,13 @@ const AccessQiniuForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
accessKey: z.string().min(1).max(64),
secretKey: z.string().min(1).max(64),
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64),
});
let config: QiniuConfig = {
@@ -47,7 +49,7 @@ const AccessQiniuForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "qiniu",
accessKey: config.accessKey,
secretKey: config.secretKey,
@@ -111,9 +113,9 @@ const AccessQiniuForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -126,7 +128,7 @@ const AccessQiniuForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -141,7 +143,7 @@ const AccessQiniuForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -156,9 +158,9 @@ const AccessQiniuForm = ({
name="accessKey"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKey</FormLabel>
<FormLabel>{t('access.form.access.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKey" {...field} />
<Input placeholder={t('access.form.access.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -171,9 +173,9 @@ const AccessQiniuForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>SecretKey</FormLabel>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretKey" {...field} />
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -184,7 +186,7 @@ const AccessQiniuForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -24,6 +24,7 @@ import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { readFileContent } from "@/lib/file";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Select,
SelectContent,
@@ -53,6 +54,7 @@ const AccessSSHForm = ({
const fileInputRef = useRef<HTMLInputElement | null>(null);
const [fileName, setFileName] = useState("");
const { t } = useTranslation();
const originGroup = data ? (data.group ? data.group : "") : "";
@@ -62,26 +64,27 @@ const AccessSSHForm = ({
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
host: z.string().refine(
(str) => {
return ipReg.test(str) || domainReg.test(str);
},
{
message: "请输入正确的域名或IP",
message: "zod.rule.ssh.host",
}
),
group: z.string().optional(),
port: z.string().min(1).max(5),
username: z.string().min(1).max(64),
password: z.string().min(0).max(64),
key: z.string().min(0).max(20480),
port: z.string().min(1, 'access.form.ssh.port.not.empty').max(5, t('zod.rule.string.max', { max: 5 })),
username: z.string().min(1, 'username.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
password: z.string().min(0, 'password.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
key: z.string().min(0, 'access.form.ssh.key.not.empty').max(20480, t('zod.rule.string.max', { max: 20480 })),
keyFile: z.any().optional(),
command: z.string().min(1).max(2048),
preCommand: z.string().min(0).max(2048).optional(),
certPath: z.string().min(0).max(2048),
keyPath: z.string().min(0).max(2048),
preCommand: z.string().min(0).max(2048).optional(),
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
});
let config: SSHConfig = {
@@ -102,7 +105,7 @@ const AccessSSHForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "ssh",
group: data?.group,
host: config.host,
@@ -223,9 +226,9 @@ const AccessSSHForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -239,12 +242,12 @@ const AccessSSHForm = ({
render={({ field }) => (
<FormItem>
<FormLabel className="w-full flex justify-between">
<div>( ssh )</div>
<div>{t('access.form.ssh.group.label')}</div>
<AccessGroupEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t('add')}
</div>
}
/>
@@ -259,7 +262,7 @@ const AccessSSHForm = ({
}}
>
<SelectTrigger>
<SelectValue placeholder="请选择分组" />
<SelectValue placeholder={t('access.group.not.empty')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="emptyId">
@@ -299,7 +302,7 @@ const AccessSSHForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -314,7 +317,7 @@ const AccessSSHForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -329,9 +332,9 @@ const AccessSSHForm = ({
name="host"
render={({ field }) => (
<FormItem className="grow">
<FormLabel>HOST</FormLabel>
<FormLabel>{t('access.form.ssh.host')}</FormLabel>
<FormControl>
<Input placeholder="请输入Host" {...field} />
<Input placeholder={t('access.form.ssh.host.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -344,10 +347,10 @@ const AccessSSHForm = ({
name="port"
render={({ field }) => (
<FormItem>
<FormLabel>SSH端口</FormLabel>
<FormLabel>{t('access.form.ssh.port')}</FormLabel>
<FormControl>
<Input
placeholder="请输入Port"
placeholder={t('access.form.ssh.port.not.empty')}
{...field}
type="number"
/>
@@ -364,9 +367,9 @@ const AccessSSHForm = ({
name="username"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('username')}</FormLabel>
<FormControl>
<Input placeholder="请输入用户名" {...field} />
<Input placeholder={t('username.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -379,10 +382,10 @@ const AccessSSHForm = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('password')}</FormLabel>
<FormControl>
<Input
placeholder="请输入密码"
placeholder={t('password.not.empty')}
{...field}
type="password"
/>
@@ -398,9 +401,9 @@ const AccessSSHForm = ({
name="key"
render={({ field }) => (
<FormItem hidden>
<FormLabel>Key使</FormLabel>
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入Key" {...field} />
<Input placeholder={t('access.form.ssh.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -413,7 +416,7 @@ const AccessSSHForm = ({
name="keyFile"
render={({ field }) => (
<FormItem>
<FormLabel>Key使</FormLabel>
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
<FormControl>
<div>
<Button
@@ -423,10 +426,10 @@ const AccessSSHForm = ({
className="w-48"
onClick={handleSelectFileClick}
>
{fileName ? fileName : "请选择文件"}
{fileName ? fileName : t('access.form.ssh.key.file.not.empty')}
</Button>
<Input
placeholder="请输入Key"
placeholder={t('access.form.ssh.key.not.empty')}
{...field}
ref={fileInputRef}
className="hidden"
@@ -447,9 +450,9 @@ const AccessSSHForm = ({
name="certPath"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
<FormControl>
<Input placeholder="请输入证书上传路径" {...field} />
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -462,9 +465,9 @@ const AccessSSHForm = ({
name="keyPath"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
<FormControl>
<Input placeholder="请输入私钥上传路径" {...field} />
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -492,9 +495,9 @@ const AccessSSHForm = ({
name="command"
render={({ field }) => (
<FormItem>
<FormLabel>Command</FormLabel>
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
<FormControl>
<Textarea placeholder="请输入要执行的命令" {...field} />
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -505,7 +508,7 @@ const AccessSSHForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -28,12 +29,13 @@ const AccessTencentForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
secretId: z.string().min(1).max(64),
secretKey: z.string().min(1).max(64),
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: TencentConfig = {
@@ -46,7 +48,7 @@ const AccessTencentForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "tencent",
secretId: config.secretId,
secretKey: config.secretKey,
@@ -108,9 +110,9 @@ const AccessTencentForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -123,7 +125,7 @@ const AccessTencentForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -138,7 +140,7 @@ const AccessTencentForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -153,9 +155,9 @@ const AccessTencentForm = ({
name="secretId"
render={({ field }) => (
<FormItem>
<FormLabel>SecretId</FormLabel>
<FormLabel>{t('access.form.secret.id')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretId" {...field} />
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -168,9 +170,9 @@ const AccessTencentForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>SecretKey</FormLabel>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretKey" {...field} />
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -179,7 +181,7 @@ const AccessTencentForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -28,11 +29,12 @@ const WebhookForm = ({
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
url: z.string().url(),
url: z.string().url('zod.rule.url'),
});
let config: WebhookConfig = {
@@ -44,14 +46,13 @@ const WebhookForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "webhook",
url: config.url,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
const req: Access = {
id: data.id as string,
name: data.name,
@@ -106,9 +107,9 @@ const WebhookForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -121,7 +122,7 @@ const WebhookForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -136,7 +137,7 @@ const WebhookForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -151,9 +152,9 @@ const WebhookForm = ({
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook Url</FormLabel>
<FormLabel>{t('access.form.webhook.url')}</FormLabel>
<FormControl>
<Input placeholder="请输入Webhook Url" {...field} />
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -162,7 +163,7 @@ const WebhookForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,3 +1,8 @@
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"
import { Separator } from "../ui/separator";
type DeployProgressProps = {
@@ -6,80 +11,63 @@ type DeployProgressProps = {
};
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
let rs = <> </>;
const { t } = useTranslation();
let step = 0;
if (phase === "check") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-red-600"> </div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
}
step = 1
} else if (phase === "apply") {
step = 2
} else if (phase === "deploy") {
step = 3
}
if (phase === "apply") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-red-600"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
}
}
if (phase === "deploy") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-red-600"></div>
</div>
);
}
}
return rs;
return (
<div className="flex items-center">
<div className={
cn(
"text-xs text-nowrap",
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 1 ? "text-green-600" : "",
)
}>
{t('deploy.progress.check')}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 1 ? "bg-green-600" : "",
)
} />
<div className={
cn(
"text-xs text-nowrap",
step < 2 ? "text-muted-foreground" : "",
step === 2 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 2 ? "text-green-600" : "",
)
}>
{t('deploy.progress.apply')}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 2 ? "bg-green-600" : "",
)
} />
<div className={
cn(
"text-xs text-nowrap",
step < 3 ? "text-muted-foreground" : "",
step === 3 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 3 ? "text-green-600" : "",
)
}>
{t('deploy.progress.deploy')}
</div>
</div>
)
};
export default DeployProgress;

View File

@@ -10,6 +10,7 @@ import {
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
Form,
FormControl,
@@ -39,9 +40,10 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
} = useConfig();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const formSchema = z.object({
email: z.string().email(),
email: z.string().email("email.valid.message"),
});
const form = useForm<z.infer<typeof formSchema>>({
@@ -54,7 +56,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const onSubmit = async (data: z.infer<typeof formSchema>) => {
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
form.setError("email", {
message: "邮箱已存在",
message: "email.already.exist",
});
return;
}
@@ -100,7 +102,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogTitle>{t('email.add')}</DialogTitle>
</DialogHeader>
<div className="container py-3">
@@ -118,9 +120,9 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('email')}</FormLabel>
<FormControl>
<Input placeholder="请输入邮箱" {...field} type="email" />
<Input placeholder={t('email.not.empty.message')} {...field} type="email" />
</FormControl>
<FormMessage />
@@ -129,7 +131,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,9 +1,12 @@
import { BookOpen } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Separator } from "../ui/separator";
import { version } from "@/domain/version";
const Version = () => {
const { t } = useTranslation()
return (
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
<div className=""></div>
@@ -14,7 +17,7 @@ const Version = () => {
className="flex items-center"
>
<BookOpen size={16} />
<div className="ml-1"></div>
<div className="ml-1">{t('document')}</div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a

View File

@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next'
type DingTalkSetting = {
id: string;
@@ -17,6 +18,7 @@ type DingTalkSetting = {
const DingTalk = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
id: config.id ?? "",
@@ -70,15 +72,15 @@ const DingTalk = () => {
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
variant: "destructive",
});
}
@@ -100,7 +102,7 @@ const DingTalk = () => {
}}
/>
<Input
placeholder="加签的签名"
placeholder={t('access.form.ding.access.token.placeholder')}
className="mt-2"
value={dingtalk.data.secret}
onChange={(e) => {
@@ -127,7 +129,7 @@ const DingTalk = () => {
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
@@ -136,7 +138,7 @@ const DingTalk = () => {
handleSaveClick();
}}
>
{t('save')}
</Button>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import {
} from "@/domain/settings";
import { getSetting, update } from "@/repository/settings";
import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next'
const NotifyTemplate = () => {
const [id, setId] = useState("");
@@ -17,6 +18,7 @@ const NotifyTemplate = () => {
]);
const { toast } = useToast();
const { t } = useTranslation();
useEffect(() => {
const featchData = async () => {
@@ -66,8 +68,8 @@ const NotifyTemplate = () => {
}
toast({
title: "保存成功",
description: "通知模板保存成功",
title: t('save.succeed'),
description: t('setting.notify.template.save.succeed'),
});
};
@@ -81,7 +83,7 @@ const NotifyTemplate = () => {
/>
<div className="text-muted-foreground text-sm mt-1">
, COUNT:即将过期张数
{t('setting.notify.template.variables.tips.title')}
</div>
<Textarea
@@ -92,10 +94,10 @@ const NotifyTemplate = () => {
}}
></Textarea>
<div className="text-muted-foreground text-sm mt-1">
, COUNT:即将过期张数DOMAINS:域名列表
{t('setting.notify.template.variables.tips.content')}
</div>
<div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}></Button>
<Button onClick={handleSaveClick}>{t('save')}</Button>
</div>
</div>
);

View File

@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { useTranslation } from "react-i18next";
type TelegramSetting = {
id: string;
@@ -17,6 +18,7 @@ type TelegramSetting = {
const Telegram = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [telegram, setTelegram] = useState<TelegramSetting>({
id: config.id ?? "",
@@ -70,15 +72,15 @@ const Telegram = () => {
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
variant: "destructive",
});
}
@@ -128,7 +130,7 @@ const Telegram = () => {
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
@@ -137,7 +139,7 @@ const Telegram = () => {
handleSaveClick();
}}
>
{t('save')}
</Button>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { isValidURL } from "@/lib/url";
import { useTranslation } from 'react-i18next'
type WebhookSetting = {
id: string;
@@ -18,6 +19,7 @@ type WebhookSetting = {
const Webhook = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [webhook, setWebhook] = useState<WebhookSetting>({
id: config.id ?? "",
@@ -59,8 +61,8 @@ const Webhook = () => {
webhook.data.url = webhook.data.url.trim();
if (!isValidURL(webhook.data.url)) {
toast({
title: "保存失败",
description: "Url格式不正确",
title: t('save.failed'),
description: t('setting.notify.config.save.failed.url.not.valid'),
variant: "destructive",
});
return;
@@ -79,15 +81,15 @@ const Webhook = () => {
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
variant: "destructive",
});
}
@@ -123,7 +125,7 @@ const Webhook = () => {
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
@@ -132,7 +134,7 @@ const Webhook = () => {
handleSaveClick();
}}
>
{t('save')}
</Button>
</div>
</div>

View File

@@ -12,6 +12,7 @@ import {
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { useTranslation } from "react-i18next"
const Form = FormProvider
@@ -145,7 +146,9 @@ const FormMessage = React.forwardRef<
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
const { t } = useTranslation()
const body = error ? t(String(error?.message)) : children
if (!body) {
return null

View File

@@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
import { useTranslation } from "react-i18next";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
@@ -62,33 +63,41 @@ PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span></span>
</PaginationLink>
);
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>{t('pagination.prev')}</span>
</PaginationLink>
)
};
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span></span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>{t('pagination.next')}</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
};
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({