Skip to content

Commit 97c08d2

Browse files
committed
feat(i18n): 为多个组件添加多语言支持,替换硬编码文本为国际化字符串,包括服务卡片、系统设置、隧道管理和网络质量卡片
1 parent 90940f8 commit 97c08d2

11 files changed

Lines changed: 289 additions & 98 deletions

File tree

web/src/components/services/service-card-variants.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
faUserSlash,
2626
faSync,
2727
} from "@fortawesome/free-solid-svg-icons";
28+
import { useTranslation } from "react-i18next";
2829
import { formatBytes } from "@/lib/utils";
2930

3031
// 服务类型定义
@@ -92,6 +93,8 @@ export function GlassmorphismCard({
9293
onNavigate,
9394
onAction,
9495
}: ServiceCardProps) {
96+
const { t } = useTranslation("services");
97+
9598
return (
9699
<div className="group relative">
97100
{/* 背景光晕效果 */}
@@ -133,59 +136,59 @@ export function GlassmorphismCard({
133136
<FontAwesomeIcon icon={faEllipsisVertical} />
134137
</Button>
135138
</DropdownTrigger>
136-
<DropdownMenu aria-label="服务操作" onAction={onAction}>
137-
<DropdownSection showDivider title="实例操作">
139+
<DropdownMenu aria-label={t("card.menu.ariaLabel")} onAction={onAction}>
140+
<DropdownSection showDivider title={t("card.menu.instanceActions")}>
138141
<DropdownItem
139142
key="start"
140143
className="text-success"
141144
startContent={<FontAwesomeIcon fixedWidth icon={faPlay} />}
142145
>
143-
启动
146+
{t("actions.start")}
144147
</DropdownItem>
145148
<DropdownItem
146149
key="stop"
147150
className="text-warning"
148151
startContent={<FontAwesomeIcon fixedWidth icon={faStop} />}
149152
>
150-
停止
153+
{t("actions.stop")}
151154
</DropdownItem>
152155
<DropdownItem
153156
key="restart"
154157
className="text-primary"
155158
startContent={<FontAwesomeIcon fixedWidth icon={faRotateRight} />}
156159
>
157-
重启
160+
{t("actions.restart")}
158161
</DropdownItem>
159162
<DropdownItem
160163
key="delete"
161164
className="text-danger"
162165
color="danger"
163166
startContent={<FontAwesomeIcon fixedWidth icon={faTrash} />}
164167
>
165-
删除
168+
{t("actions.delete")}
166169
</DropdownItem>
167170
</DropdownSection>
168-
<DropdownSection title="服务操作">
171+
<DropdownSection title={t("card.menu.serviceActions")}>
169172
<DropdownItem
170173
key="sync"
171174
className="text-primary"
172175
startContent={<FontAwesomeIcon fixedWidth icon={faSync} />}
173176
>
174-
同步
177+
{t("actions.sync")}
175178
</DropdownItem>
176179
<DropdownItem
177180
key="rename"
178181
startContent={<FontAwesomeIcon fixedWidth icon={faEdit} />}
179182
>
180-
重命名
183+
{t("actions.rename")}
181184
</DropdownItem>
182185
<DropdownItem
183186
key="dissolve"
184187
className="text-danger"
185188
color="warning"
186189
startContent={<FontAwesomeIcon fixedWidth icon={faUserSlash} />}
187190
>
188-
解散
191+
{t("actions.dissolve")}
189192
</DropdownItem>
190193
</DropdownSection>
191194
</DropdownMenu>
@@ -244,7 +247,7 @@ export function GlassmorphismCard({
244247
className="flex items-center gap-2 p-2 rounded-lg bg-white/50 dark:bg-gray-800/30 backdrop-blur-sm border border-white/20"
245248
>
246249
<span className="text-[11px] font-medium text-gray-500 dark:text-gray-400 w-10">
247-
入口
250+
{t("card.entry")}
248251
</span>
249252
<span className="text-[11px] font-mono font-semibold text-gray-900 dark:text-white flex-1 truncate">
250253
{formatHost(service.entranceHost)}:{service.entrancePort}
@@ -254,7 +257,7 @@ export function GlassmorphismCard({
254257
className="flex items-center gap-2 p-2 rounded-lg bg-white/50 dark:bg-gray-800/30 backdrop-blur-sm border border-white/20"
255258
>
256259
<span className="text-[11px] font-medium text-gray-500 dark:text-gray-400 w-10">
257-
出口
260+
{t("card.exit")}
258261
</span>
259262
<span className="text-[11px] font-mono font-semibold text-gray-900 dark:text-white flex-1 truncate">
260263
{formatHost(service.exitHost)}:{service.exitPort}
@@ -271,7 +274,7 @@ export function GlassmorphismCard({
271274
<span className="text-white text-[10px] font-bold"></span>
272275
</div>
273276
<div className="flex flex-col">
274-
<span className="text-[9px] text-gray-500 dark:text-gray-400 leading-tight">上传</span>
277+
<span className="text-[9px] text-gray-500 dark:text-gray-400 leading-tight">{t("card.upload")}</span>
275278
<span className="text-[11px] font-mono font-semibold text-gray-900 dark:text-white leading-tight">
276279
{formatBytes(service.totalTx || 0)}
277280
</span>
@@ -283,7 +286,7 @@ export function GlassmorphismCard({
283286
<span className="text-white text-[10px] font-bold"></span>
284287
</div>
285288
<div className="flex flex-col">
286-
<span className="text-[9px] text-gray-500 dark:text-gray-400 leading-tight">下载</span>
289+
<span className="text-[9px] text-gray-500 dark:text-gray-400 leading-tight">{t("card.download")}</span>
287290
<span className="text-[11px] font-mono font-semibold text-gray-900 dark:text-white leading-tight">
288291
{formatBytes(service.totalRx || 0)}
289292
</span>

web/src/components/settings/system-settings.tsx

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@ import React, { forwardRef, useImperativeHandle } from "react";
1313
import { z } from "zod";
1414
import { useForm } from "react-hook-form";
1515
import { zodResolver } from "@hookform/resolvers/zod";
16+
import { useTranslation } from "react-i18next";
1617

17-
// 定义表单验证 schema
18-
const systemSettingsSchema = z.object({
19-
systemName: z.string().min(1, "系统名称不能为空"),
20-
language: z.enum(["zh", "en"]),
21-
maxConnections: z.number().int().min(1).max(1000),
22-
connectionTimeout: z.number().int().min(1).max(3600),
23-
logLevel: z.enum(["debug", "info", "warn", "error", "event"]),
24-
logRetentionDays: z.number().int().min(1).max(365),
25-
autoBackup: z.boolean(),
26-
backupInterval: z.enum(["daily", "weekly", "monthly"]),
27-
backupRetention: z.number().int().min(1).max(100),
28-
});
18+
// 定义表单验证 schema (将在组件内创建以使用翻译)
19+
const createSystemSettingsSchema = (t: (key: string) => string) =>
20+
z.object({
21+
systemName: z.string().min(1, t("system.form.validation.systemNameRequired")),
22+
language: z.enum(["zh", "en"]),
23+
maxConnections: z.number().int().min(1).max(1000),
24+
connectionTimeout: z.number().int().min(1).max(3600),
25+
logLevel: z.enum(["debug", "info", "warn", "error", "event"]),
26+
logRetentionDays: z.number().int().min(1).max(365),
27+
autoBackup: z.boolean(),
28+
backupInterval: z.enum(["daily", "weekly", "monthly"]),
29+
backupRetention: z.number().int().min(1).max(100),
30+
});
2931

30-
type SystemSettingsForm = z.infer<typeof systemSettingsSchema>;
32+
type SystemSettingsForm = z.infer<ReturnType<typeof createSystemSettingsSchema>>;
3133

3234
// 定义组件 ref 类型
3335
export type SystemSettingsRef = {
@@ -36,6 +38,8 @@ export type SystemSettingsRef = {
3638
};
3739

3840
const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
41+
const { t } = useTranslation("settings");
42+
3943
// 初始化表单
4044
const {
4145
register,
@@ -45,7 +49,7 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
4549
reset,
4650
formState: { errors },
4751
} = useForm<SystemSettingsForm>({
48-
resolver: zodResolver(systemSettingsSchema),
52+
resolver: zodResolver(createSystemSettingsSchema(t)),
4953
defaultValues: {
5054
systemName: "NodePass",
5155
language: "zh",
@@ -85,29 +89,29 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
8589
<CardBody className="gap-6">
8690
{/* 基础设置 */}
8791
<div className="space-y-4">
88-
<h3 className="text-lg font-medium">基础设置</h3>
92+
<h3 className="text-lg font-medium">{t("system.form.basicSettings.title")}</h3>
8993
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
9094
<div className="space-y-2">
91-
<label className="text-sm text-default-700">系统名称</label>
95+
<label className="text-sm text-default-700">{t("system.form.basicSettings.systemName")}</label>
9296
<Input
9397
{...register("systemName")}
9498
errorMessage={errors.systemName?.message}
9599
isInvalid={!!errors.systemName}
96-
placeholder="输入系统名称"
100+
placeholder={t("system.form.basicSettings.systemNamePlaceholder")}
97101
variant="bordered"
98102
/>
99103
</div>
100104
<div className="space-y-2">
101-
<label className="text-sm text-default-700">系统语言</label>
105+
<label className="text-sm text-default-700">{t("system.form.basicSettings.systemLanguage")}</label>
102106
<Select
103107
selectedKeys={[watch("language")]}
104108
variant="bordered"
105109
onChange={(e) =>
106110
setValue("language", e.target.value as "zh" | "en")
107111
}
108112
>
109-
<SelectItem key="zh">简体中文</SelectItem>
110-
<SelectItem key="en">English</SelectItem>
113+
<SelectItem key="zh">{t("system.form.basicSettings.zhCN")}</SelectItem>
114+
<SelectItem key="en">{t("system.form.basicSettings.enUS")}</SelectItem>
111115
</Select>
112116
</div>
113117
</div>
@@ -117,13 +121,13 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
117121

118122
{/* 性能设置 */}
119123
<div className="space-y-4">
120-
<h3 className="text-lg font-medium">性能设置</h3>
124+
<h3 className="text-lg font-medium">{t("system.form.performanceSettings.title")}</h3>
121125
<div className="space-y-4">
122126
<div className="flex items-center justify-between">
123127
<div>
124-
<p className="font-medium">最大连接数</p>
128+
<p className="font-medium">{t("system.form.performanceSettings.maxConnections.title")}</p>
125129
<p className="text-sm text-default-500">
126-
单个隧道允许的最大并发连接数
130+
{t("system.form.performanceSettings.maxConnections.description")}
127131
</p>
128132
</div>
129133
<Input
@@ -137,9 +141,9 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
137141
</div>
138142
<div className="flex items-center justify-between">
139143
<div>
140-
<p className="font-medium">连接超时时间</p>
144+
<p className="font-medium">{t("system.form.performanceSettings.connectionTimeout.title")}</p>
141145
<p className="text-sm text-default-500">
142-
空闲连接的超时时间(秒)
146+
{t("system.form.performanceSettings.connectionTimeout.description")}
143147
</p>
144148
</div>
145149
<Input
@@ -158,13 +162,13 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
158162

159163
{/* 日志设置 */}
160164
<div className="space-y-4">
161-
<h3 className="text-lg font-medium">日志设置</h3>
165+
<h3 className="text-lg font-medium">{t("system.form.logSettings.title")}</h3>
162166
<div className="space-y-4">
163167
<div className="flex items-center justify-between">
164168
<div>
165-
<p className="font-medium">日志级别</p>
169+
<p className="font-medium">{t("system.form.logSettings.logLevel.title")}</p>
166170
<p className="text-sm text-default-500">
167-
设置系统日志记录的详细程度
171+
{t("system.form.logSettings.logLevel.description")}
168172
</p>
169173
</div>
170174
<Select
@@ -192,8 +196,8 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
192196
</div>
193197
<div className="flex items-center justify-between">
194198
<div>
195-
<p className="font-medium">日志保留天数</p>
196-
<p className="text-sm text-default-500">系统日志的保留时间</p>
199+
<p className="font-medium">{t("system.form.logSettings.logRetentionDays.title")}</p>
200+
<p className="text-sm text-default-500">{t("system.form.logSettings.logRetentionDays.description")}</p>
197201
</div>
198202
<Input
199203
{...register("logRetentionDays", { valueAsNumber: true })}
@@ -211,13 +215,13 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
211215

212216
{/* 备份设置 */}
213217
<div className="space-y-4">
214-
<h3 className="text-lg font-medium">备份设置</h3>
218+
<h3 className="text-lg font-medium">{t("system.form.backupSettings.title")}</h3>
215219
<div className="space-y-4">
216220
<div className="flex items-center justify-between">
217221
<div>
218-
<p className="font-medium">自动备份</p>
222+
<p className="font-medium">{t("system.form.backupSettings.autoBackup.title")}</p>
219223
<p className="text-sm text-default-500">
220-
定期备份系统配置和数据
224+
{t("system.form.backupSettings.autoBackup.description")}
221225
</p>
222226
</div>
223227
<Switch
@@ -229,9 +233,9 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
229233
<>
230234
<div className="flex items-center justify-between">
231235
<div>
232-
<p className="font-medium">备份周期</p>
236+
<p className="font-medium">{t("system.form.backupSettings.backupInterval.title")}</p>
233237
<p className="text-sm text-default-500">
234-
自动备份的时间间隔
238+
{t("system.form.backupSettings.backupInterval.description")}
235239
</p>
236240
</div>
237241
<Select
@@ -245,16 +249,16 @@ const SystemSettings = forwardRef<SystemSettingsRef>((props, ref) => {
245249
)
246250
}
247251
>
248-
<SelectItem key="daily">每天</SelectItem>
249-
<SelectItem key="weekly">每周</SelectItem>
250-
<SelectItem key="monthly">每月</SelectItem>
252+
<SelectItem key="daily">{t("system.form.backupSettings.backupInterval.daily")}</SelectItem>
253+
<SelectItem key="weekly">{t("system.form.backupSettings.backupInterval.weekly")}</SelectItem>
254+
<SelectItem key="monthly">{t("system.form.backupSettings.backupInterval.monthly")}</SelectItem>
251255
</Select>
252256
</div>
253257
<div className="flex items-center justify-between">
254258
<div>
255-
<p className="font-medium">保留备份数</p>
259+
<p className="font-medium">{t("system.form.backupSettings.backupRetention.title")}</p>
256260
<p className="text-sm text-default-500">
257-
最多保留的备份文件数量
261+
{t("system.form.backupSettings.backupRetention.description")}
258262
</p>
259263
</div>
260264
<Input

0 commit comments

Comments
 (0)