diff --git a/ui/src/components/ThemeToggle.tsx b/ui/src/components/ThemeToggle.tsx index 4ec3d9c2..a7729482 100644 --- a/ui/src/components/ThemeToggle.tsx +++ b/ui/src/components/ThemeToggle.tsx @@ -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 ( @@ -23,13 +26,13 @@ export function ThemeToggle() { setTheme("light")}> - 浅色 + {t('theme.light')} setTheme("dark")}> - 暗黑 + {t('theme.dark')} setTheme("system")}> - 系统 + {t('theme.system')} diff --git a/ui/src/components/certimate/DeployProgress.tsx b/ui/src/components/certimate/DeployProgress.tsx index 863da792..b96dc2b9 100644 --- a/ui/src/components/certimate/DeployProgress.tsx +++ b/ui/src/components/certimate/DeployProgress.tsx @@ -1,3 +1,5 @@ +import { useTranslation } from "react-i18next"; + import { Separator } from "../ui/separator"; type DeployProgressProps = { @@ -6,26 +8,40 @@ type DeployProgressProps = { }; const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => { + const { t } = useTranslation(); + let rs = <> ; if (phase === "check") { if (phaseSuccess) { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } else { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } @@ -35,21 +51,33 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => { if (phaseSuccess) { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } else { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } @@ -59,21 +87,33 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => { if (phaseSuccess) { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } else { rs = (
-
检查
+
+ {t('deploy.progress.check')} +
-
获取
+
+ {t('deploy.progress.apply')} +
-
部署
+
+ {t('deploy.progress.deploy')} +
); } diff --git a/ui/src/components/notify/DingTalk.tsx b/ui/src/components/notify/DingTalk.tsx index 2e2a58f6..37b3f320 100644 --- a/ui/src/components/notify/DingTalk.tsx +++ b/ui/src/components/notify/DingTalk.tsx @@ -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({ 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", }); } @@ -127,7 +129,7 @@ const DingTalk = () => { }); }} /> - +
@@ -136,7 +138,7 @@ const DingTalk = () => { handleSaveClick(); }} > - 保存 + {t('save')}
diff --git a/ui/src/components/notify/NotifyTemplate.tsx b/ui/src/components/notify/NotifyTemplate.tsx index 2a761b08..2412ee05 100644 --- a/ui/src/components/notify/NotifyTemplate.tsx +++ b/ui/src/components/notify/NotifyTemplate.tsx @@ -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 = () => { />
- 可选的变量, COUNT:即将过期张数 + {t('setting.notify.template.variables.tips.title')}
- 可选的变量, COUNT:即将过期张数,DOMAINS:域名列表 + {t('setting.notify.template.variables.tips.content')}
- +
); diff --git a/ui/src/components/notify/Telegram.tsx b/ui/src/components/notify/Telegram.tsx index 50919463..339a24d6 100644 --- a/ui/src/components/notify/Telegram.tsx +++ b/ui/src/components/notify/Telegram.tsx @@ -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({ 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 = () => { }); }} /> - +
@@ -137,7 +139,7 @@ const Telegram = () => { handleSaveClick(); }} > - 保存 + {t('save')}
diff --git a/ui/src/components/notify/Webhook.tsx b/ui/src/components/notify/Webhook.tsx index f2844a7a..af8468c7 100644 --- a/ui/src/components/notify/Webhook.tsx +++ b/ui/src/components/notify/Webhook.tsx @@ -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({ 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 = () => { }); }} /> - +
@@ -132,7 +134,7 @@ const Webhook = () => { handleSaveClick(); }} > - 保存 + {t('save')}
diff --git a/ui/src/components/ui/form.tsx b/ui/src/components/ui/form.tsx index 4603f8b3..d91f6b89 100644 --- a/ui/src/components/ui/form.tsx +++ b/ui/src/components/ui/form.tsx @@ -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 >(({ 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 diff --git a/ui/src/components/ui/pagination.tsx b/ui/src/components/ui/pagination.tsx index d6b1fcf1..bbaa801d 100644 --- a/ui/src/components/ui/pagination.tsx +++ b/ui/src/components/ui/pagination.tsx @@ -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">) => ( @@ -138,7 +141,7 @@ export default function Dashboard() { )} > - 控制面板 + {t('dashboard')} - 域名列表 + {t('domain.management.name')} - 授权管理 + {t('menu.auth.management')} - 部署历史 + {t('deployment.log.name')}
+ @@ -182,16 +200,16 @@ const Dashboard = () => { ) : ( <>
-
域名
+
{t('domain')}
-
状态
-
阶段
-
最近执行时间
+
{t('deployment.log.status')}
+
{t('deployment.log.stage')}
+
{t('deployment.log.last.execution.time')}
-
操作
+
{t('operation')}
- 部署历史 + {t('deployment.log.name')}
{deployments?.map((deployment) => ( @@ -218,14 +236,14 @@ const Dashboard = () => { {deployment.expand.domain?.domain}-{deployment.id} - 部署详情 + {t('deployment.log.detail')}
diff --git a/ui/src/pages/domains/Edit.tsx b/ui/src/pages/domains/Edit.tsx index af03eccb..3a4c4230 100644 --- a/ui/src/pages/domains/Edit.tsx +++ b/ui/src/pages/domains/Edit.tsx @@ -38,6 +38,7 @@ import EmailsEdit from "@/components/certimate/EmailsEdit"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { EmailsSetting } from "@/domain/settings"; +import { useTranslation } from "react-i18next"; const Edit = () => { const { @@ -47,6 +48,7 @@ const Edit = () => { const [domain, setDomain] = useState(); const location = useLocation(); + const { t } = useTranslation(); const [tab, setTab] = useState<"base" | "advance">("base"); @@ -69,15 +71,15 @@ const Edit = () => { const formSchema = z.object({ id: z.string().optional(), domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { - message: "请输入正确的域名", + message: t('domain.management.edit.domain.verify.tips'), }), email: z.string().email().optional(), access: z.string().regex(/^[a-zA-Z0-9]+$/, { - message: "请选择DNS服务商授权配置", + message: t('domain.management.edit.dns.verify.tips'), }), targetAccess: z.string().optional(), targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, { - message: "请选择部署服务类型", + message: t('domain.management.edit.target.type.verify.tips'), }), variables: z.string().optional(), group: z.string().optional(), diff --git a/ui/src/pages/domains/Home.tsx b/ui/src/pages/domains/Home.tsx index 162eaf64..5150a178 100644 --- a/ui/src/pages/domains/Home.tsx +++ b/ui/src/pages/domains/Home.tsx @@ -35,11 +35,13 @@ import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip"; import { Earth } from "lucide-react"; import { useEffect, useState } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; +import { useTranslation, Trans } from "react-i18next"; const Home = () => { const toast = useToast(); const navigate = useNavigate(); + const { t } = useTranslation() const location = useLocation(); const query = new URLSearchParams(location.search); @@ -127,23 +129,22 @@ const Home = () => { await save(domain); toast.toast({ - title: "操作成功", - description: "已发起部署,请稍后查看部署日志。", + title: t('operation.succeed'), + description: t('domain.management.start.deploy.succeed.tips'), }); } catch (e) { toast.toast({ - title: "执行失败", + title: t('domain.management.execution.failed'), description: ( - <> - 执行失败,请查看 + // 这里的 text 只是占位作用,实际文案在 src/i18n/locales/[lang].json + + text1 - 部署日志 - - 查看详情。 - + >text2 + text3 + ), variant: "destructive", }); @@ -175,8 +176,10 @@ const Home = () => {
-
域名列表
- +
{t('domain.management.name')}
+
{!domains.length ? ( @@ -187,26 +190,26 @@ const Home = () => {
- 请添加域名开始部署证书吧。 + {t('domain.management.empty')}
) : ( <>
-
域名
-
有效期限
-
最近执行状态
-
最近执行阶段
-
最近执行时间
-
是否启用
-
操作
+
{t('domain')}
+
{t('domain.management.expiry.date')}
+
{t('domain.management.last.execution.status')}
+
{t('domain.management.last.execution.stage')}
+
{t('domain.management.last.execution.time')}
+
{t('domain.management.enable')}
+
{t('operation')}
- 域名 + {t('domain')}
{domains.map((domain) => ( @@ -221,8 +224,8 @@ const Home = () => {
{domain.expiredAt ? ( <> -
有效期90天
-
{getDate(domain.expiredAt)}到期
+
{t('domain.management.expiry.date1', { date: 90 })}
+
{t('domain.management.expiry.date2', { date: getDate(domain.expiredAt) })}
) : ( "---" @@ -266,7 +269,7 @@ const Home = () => {
- {domain.enabled ? "禁用" : "启用"} + {domain.enabled ? t('disable') : t('enable')}
@@ -278,7 +281,7 @@ const Home = () => { className="p-0" onClick={() => handleHistoryClick(domain.id)} > - 部署历史 + {t('deployment.log.name')} @@ -287,7 +290,7 @@ const Home = () => { className="p-0" onClick={() => handleRightNowClick(domain)} > - 立即部署 + {t('domain.management.start.deploying')} @@ -304,7 +307,7 @@ const Home = () => { className="p-0" onClick={() => handleForceClick(domain)} > - 强行部署 + {t('domain.management.forced.deployment')} @@ -315,7 +318,7 @@ const Home = () => { className="p-0" onClick={() => handleDownloadClick(domain)} > - 下载 + {t('download')} @@ -325,24 +328,24 @@ const Home = () => { - 删除域名 + {t('domain.delete')} - 确定要删除域名吗? + {t('domain.management.delete.confirm')} - 取消 + {t('cancel')} { handleDeleteClick(domain.id); }} > - 确认 + {t('confirm')} @@ -354,7 +357,7 @@ const Home = () => { className="p-0" onClick={() => handleEditClick(domain.id)} > - 编辑 + {t('edit')} )} diff --git a/ui/src/pages/history/History.tsx b/ui/src/pages/history/History.tsx index 3db4db4e..94dc7569 100644 --- a/ui/src/pages/history/History.tsx +++ b/ui/src/pages/history/History.tsx @@ -17,11 +17,13 @@ import { list } from "@/repository/deployment"; import { Smile } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; const History = () => { const navigate = useNavigate(); const [deployments, setDeployments] = useState(); const [searchParams] = useSearchParams(); + const { t } = useTranslation(); const domain = searchParams.get("domain"); useEffect(() => { @@ -38,11 +40,11 @@ const History = () => { return ( -
部署历史
+
{t('deployment.log.name')}
{!deployments?.length ? ( <> - 暂无数据 + {t('no.data')}
@@ -50,7 +52,7 @@ const History = () => {
{" "} - 你暂未创建任何部署,请先添加域名进行部署吧! + {t('deployment.log.empty')}
@@ -59,7 +61,7 @@ const History = () => { navigate("/"); }} > - 添加域名 + {t('domain.add')}
@@ -68,16 +70,16 @@ const History = () => { ) : ( <>
-
域名
+
{t('domain')}
-
状态
-
阶段
-
最近执行时间
+
{t('deployment.log.status')}
+
{t('deployment.log.stage')}
+
{t('deployment.log.last.execution.time')}
-
操作
+
{t('operation')}
- 部署历史 + {t('deployment.log.name')}
{deployments?.map((deployment) => ( @@ -104,14 +106,14 @@ const History = () => { {deployment.expand.domain?.domain}-{deployment.id} - 部署详情 + {t('deployment.log.detail')}
diff --git a/ui/src/pages/login/Login.tsx b/ui/src/pages/login/Login.tsx index d023a9b9..9df8aa3c 100644 --- a/ui/src/pages/login/Login.tsx +++ b/ui/src/pages/login/Login.tsx @@ -1,3 +1,8 @@ +import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { z } from "zod"; +import { useTranslation } from 'react-i18next' + import { Button } from "@/components/ui/button"; import { Form, @@ -11,20 +16,19 @@ import { Input } from "@/components/ui/input"; import { getErrMessage } from "@/lib/error"; import { getPb } from "@/repository/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { useNavigate } from "react-router-dom"; -import { z } from "zod"; const formSchema = z.object({ username: z.string().email({ - message: "请输入正确的邮箱地址", + message: "login.username.no.empty.message", }), password: z.string().min(10, { - message: "密码至少10个字符", + message: "login.password.length.message", }), }); const Login = () => { + const { t } = useTranslation() + const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -61,7 +65,7 @@ const Login = () => { name="username" render={({ field }) => ( - 用户名 + {t('username')} @@ -76,7 +80,7 @@ const Login = () => { name="password" render={({ field }) => ( - 密码 + {t('password')} @@ -86,7 +90,7 @@ const Login = () => { )} />
- +
diff --git a/ui/src/pages/setting/Account.tsx b/ui/src/pages/setting/Account.tsx index 65e6e62c..c839f70f 100644 --- a/ui/src/pages/setting/Account.tsx +++ b/ui/src/pages/setting/Account.tsx @@ -15,16 +15,18 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { z } from "zod"; const formSchema = z.object({ - email: z.string().email("请输入正确的邮箱"), + email: z.string().email("setting.account.email.valid.message"), }); const Account = () => { const { toast } = useToast(); const navigate = useNavigate(); + const { t } = useTranslation(); const [changed, setChanged] = useState(false); @@ -43,8 +45,8 @@ const Account = () => { getPb().authStore.clear(); toast({ - title: "修改账户邮箱功", - description: "请重新登录", + title: t("setting.account.email.change.succeed"), + description: t("setting.account.log.back.in"), }); setTimeout(() => { navigate("/login"); @@ -52,7 +54,7 @@ const Account = () => { } catch (e) { const message = getErrMessage(e); toast({ - title: "修改账户邮箱失败", + title: t("setting.account.email.change.failed"), description: message, variant: "destructive", }); @@ -72,10 +74,10 @@ const Account = () => { name="email" render={({ field }) => ( - 邮箱 + {t('email')} { @@ -92,10 +94,10 @@ const Account = () => {
{changed ? ( - + ) : ( )}
diff --git a/ui/src/pages/setting/Notify.tsx b/ui/src/pages/setting/Notify.tsx index ecdec18d..a727611d 100644 --- a/ui/src/pages/setting/Notify.tsx +++ b/ui/src/pages/setting/Notify.tsx @@ -9,15 +9,18 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { NotifyProvider } from "@/providers/notify"; +import { useTranslation } from "react-i18next"; const Notify = () => { + const { t } = useTranslation(); + return ( <>
- 模板 + {t('template')} diff --git a/ui/src/pages/setting/Password.tsx b/ui/src/pages/setting/Password.tsx index 8733a5cf..c72b15c0 100644 --- a/ui/src/pages/setting/Password.tsx +++ b/ui/src/pages/setting/Password.tsx @@ -14,29 +14,31 @@ import { getPb } from "@/repository/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { z } from "zod"; const formSchema = z .object({ oldPassword: z.string().min(10, { - message: "密码至少10个字符", + message: "setting.password.length.message", }), newPassword: z.string().min(10, { - message: "密码至少10个字符", + message: "setting.password.length.message", }), confirmPassword: z.string().min(10, { - message: "密码至少10个字符", + message: "setting.password.length.message", }), }) .refine((data) => data.newPassword === data.confirmPassword, { - message: "两次密码不一致", + message: "setting.password.not.match", path: ["confirmPassword"], }); const Password = () => { const { toast } = useToast(); const navigate = useNavigate(); + const { t } = useTranslation(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -66,8 +68,8 @@ const Password = () => { getPb().authStore.clear(); toast({ - title: "修改密码成功", - description: "请重新登录", + title: t('setting.password.change.succeed'), + description: t("setting.account.log.back.in"), }); setTimeout(() => { navigate("/login"); @@ -75,7 +77,7 @@ const Password = () => { } catch (e) { const message = getErrMessage(e); toast({ - title: "修改密码失败", + title: t('setting.password.change.failed'), description: message, variant: "destructive", }); @@ -95,9 +97,9 @@ const Password = () => { name="oldPassword" render={({ field }) => ( - 当前密码 + {t('setting.password.current.password')} - + @@ -110,7 +112,7 @@ const Password = () => { name="newPassword" render={({ field }) => ( - 新密码 + {t('setting.password.new.password')} { name="confirmPassword" render={({ field }) => ( - 确认密码 + {t('setting.password.confirm.password')} { )} />
- +