mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 19:22:44 +08:00
chore: i18n (#5276)
* chore: notice i18n * feat: add script to clean up unused i18n keys * chore: cleanup i18n keys * refactor(i18n/proxies): migrate proxies UI to structured locale keys * chore: i18n for rule module * chore: i18n for profile module * chore: i18n for connections module * chore: i18n for settings module * chore: i18n for verge settings * chore: i18n for theme settings * chore: i18n for theme * chore(i18n): components.home.* * chore(i18n): remove unused i18n keys * chore(i18n): components.profile.* * chore(i18n): components.connection * chore(i18n): pages.logs.* * chore(i18n): pages.*.provider * chore(i18n): components.settings.externalCors.* * chore(i18n): components.settings.clash.* * chore(i18n): components.settings.liteMode.* * chore(i18n): components.settings.backup.* * chore(i18n): components.settings.clash.port.* * chore(i18n): components.settings.misc.* * chore(i18n): components.settings.update.* * chore(i18n): components.settings.sysproxy.* * chore(i18n): components.settings.sysproxy.* * chore(i18n): pages.profiles.notices/components.providers.notices * refactor(notice): unify showNotice usage * refactor(notice): add typed showNotice shortcuts, centralize defaults, and simplify subscriptions * refactor: unify showNotice usage * refactor(notice): unify showNotice API * refactor(notice): unify showNotice usage * chore(i18n): components.test.* * chore(i18n): components.settings.dns.* * chore(i18n): components.home.clashInfo.* * chore(i18n): components.home.systemInfo.* * chore(i18n): components.home.ipInfo/traffic.* * chore(i18n): navigation.* * refactor(i18n): remove pages.* namespace and migrate route texts under module-level page keys * chore(i18n): common.* * chore(i18n): common.* * fix: change error handling in patch_profiles_config to return false when a switch is in progress * fix: improve error handling in patch_profiles_config to prevent requests during profile switching * fix: change error handling in patch_profiles_config to return false when a switch is in progress fix: ensure CURRENT_SWITCHING_PROFILE is reset after config updates in perform_config_update and patch_profiles_config * chore(i18n): restructure root-level locale keys into namespaces * chore(i18n): add missing i18n keys * docs: i18n guide * chore: adjust i18n * refactor(i18n): align UI actions and status labels with common keys * refactor(i18n): unify two-name locale namespaces * refactor(i18n/components): unify locale keys and update component references * chore(i18n): add shared and entities namespaces to all locale files * refactor(i18n): consolidate shared and entity namespaces across features * chore(deps): update npm dependencies to ^7.3.5 (#5310) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * refactor(i18n): migrate shared editor modes and consolidate entities namespaces * tmp * refactor(i18n): flatten locales and move theme/validation strings * docs: CONTRIBUTING_i18n.md * refactor(i18n): restructure feedback and profile namespaces for better organization * refactor(i18n): unify settings locale structure and update references * refactor(i18n): reorganize locale keys for home, proxies, rules, connections, logs, unlock, and tests * refactor(i18n/feedback/layout): unify shared toasts & normalize layout namespace * refactor(i18n): centralize common UI strings in shared * refactor(i18n): flatten headers and unify locale schema * refactor(i18n): consolidate duplicate per-feature translations into shared namespace * refactor(i18n): split locales into per-namespace files * style: lint * refactor(i18n): unify unlock UI translations under tests namespace * feat(i18n): add type-checked translation keys * style: eslint import order * feat(i18n): replace ad-hoc loader with rust-i18n backend bundles * chore(prebuild): remove locale-copy step * fix(i18n, notice): propagate runtime params and update cleanup script path * fix(i18n,notice): make locale formatting idempotent and guard early notice translations * fix(i18n): resolve locale aliases and match OS codes correctly * fix(unlock): use i18next-compatible double-brace interpolation in failure notice * fix(i18n): route unlock error notices through translation keys * fix(i18n): i18n types * feat(i18n): localize upgrade notice for Clash core viewer * fix(notice): ensure runtime overrides apply to prefix translations * chore(i18n): replace literal notices with translation keys * chore(i18n): types * chore(i18n): regen typings before formatting to keep keys in sync * chore(i18n): simply labels * chore(i18n): adjust translation * chore: remove eslint-plugin-i18next * chore(i18n): add/refine Korean translations across frontend scopes and Rust backend (#5341) * chore(i18n): translate settings.json (missed in previous pass) (#5343) * chore(i18n): add/refine Korean translations across frontend scopes and Rust backend * chore(i18n): add/refine Korean translations across frontend scopes and Rust backend * fix(i18n-tauri): quote placeholder-leading value in ko.yml to prevent rust_i18n parse panic * chore(i18n): translate settings.json (forgot to include previously) --------- Co-authored-by: rozan <34974262+thelojan@users.noreply.github.com>
This commit is contained in:
co-authored by
renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
rozan
parent
c1a2e99f63
commit
c8aa72186e
@@ -32,7 +32,7 @@ export const ClashInfoCard = () => {
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Core Version")}
|
||||
{t("home.components.clashInfo.fields.coreVersion")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{clashVersion || "-"}
|
||||
@@ -41,7 +41,7 @@ export const ClashInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("System Proxy Address")}
|
||||
{t("home.components.clashInfo.fields.systemProxyAddress")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{systemProxyAddress}
|
||||
@@ -50,7 +50,7 @@ export const ClashInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Mixed Port")}
|
||||
{t("home.components.clashInfo.fields.mixedPort")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{clashConfig.mixedPort || "-"}
|
||||
@@ -59,7 +59,7 @@ export const ClashInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Uptime")}
|
||||
{t("home.components.clashInfo.fields.uptime")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{formattedUptime}
|
||||
@@ -68,7 +68,7 @@ export const ClashInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Rules Count")}
|
||||
{t("home.components.clashInfo.fields.rulesCount")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{rules.length}
|
||||
@@ -87,7 +87,7 @@ export const ClashInfoCard = () => {
|
||||
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("Clash Info")}
|
||||
title={t("home.components.clashInfo.title")}
|
||||
icon={<DeveloperBoardOutlined />}
|
||||
iconColor="warning"
|
||||
action={null}
|
||||
|
||||
@@ -12,6 +12,31 @@ import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { patchClashMode } from "@/services/cmds";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
|
||||
const CLASH_MODES = ["rule", "global", "direct"] as const;
|
||||
type ClashMode = (typeof CLASH_MODES)[number];
|
||||
|
||||
const isClashMode = (mode: string): mode is ClashMode =>
|
||||
(CLASH_MODES as readonly string[]).includes(mode);
|
||||
|
||||
const MODE_META: Record<
|
||||
ClashMode,
|
||||
{ label: TranslationKey; description: TranslationKey }
|
||||
> = {
|
||||
rule: {
|
||||
label: "home.components.clashMode.labels.rule",
|
||||
description: "home.components.clashMode.descriptions.rule",
|
||||
},
|
||||
global: {
|
||||
label: "home.components.clashMode.labels.global",
|
||||
description: "home.components.clashMode.descriptions.global",
|
||||
},
|
||||
direct: {
|
||||
label: "home.components.clashMode.labels.direct",
|
||||
description: "home.components.clashMode.descriptions.direct",
|
||||
},
|
||||
};
|
||||
|
||||
export const ClashModeCard = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -19,19 +44,21 @@ export const ClashModeCard = () => {
|
||||
const { clashConfig, refreshClashConfig } = useAppData();
|
||||
|
||||
// 支持的模式列表
|
||||
const modeList = useMemo(() => ["rule", "global", "direct"] as const, []);
|
||||
const modeList = CLASH_MODES;
|
||||
|
||||
// 直接使用API返回的模式,不维护本地状态
|
||||
const currentMode = clashConfig?.mode?.toLowerCase();
|
||||
const currentModeKey =
|
||||
typeof currentMode === "string" && isClashMode(currentMode)
|
||||
? currentMode
|
||||
: undefined;
|
||||
|
||||
const modeDescription = useMemo(() => {
|
||||
if (typeof currentMode === "string" && currentMode.length > 0) {
|
||||
return t(
|
||||
`${currentMode[0].toLocaleUpperCase()}${currentMode.slice(1)} Mode Description`,
|
||||
);
|
||||
if (currentModeKey) {
|
||||
return t(MODE_META[currentModeKey].description);
|
||||
}
|
||||
return t("Core communication error");
|
||||
}, [currentMode, t]);
|
||||
return t("home.components.clashMode.errors.communication");
|
||||
}, [currentModeKey, t]);
|
||||
|
||||
// 模式图标映射
|
||||
const modeIcons = useMemo(
|
||||
@@ -44,8 +71,8 @@ export const ClashModeCard = () => {
|
||||
);
|
||||
|
||||
// 切换模式的处理函数
|
||||
const onChangeMode = useLockFn(async (mode: string) => {
|
||||
if (mode === currentMode) return;
|
||||
const onChangeMode = useLockFn(async (mode: ClashMode) => {
|
||||
if (mode === currentModeKey) return;
|
||||
if (verge?.auto_close_connection) {
|
||||
closeAllConnections();
|
||||
}
|
||||
@@ -60,7 +87,7 @@ export const ClashModeCard = () => {
|
||||
});
|
||||
|
||||
// 按钮样式
|
||||
const buttonStyles = (mode: string) => ({
|
||||
const buttonStyles = (mode: ClashMode) => ({
|
||||
cursor: "pointer",
|
||||
px: 2,
|
||||
py: 1.2,
|
||||
@@ -68,8 +95,8 @@ export const ClashModeCard = () => {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 1,
|
||||
bgcolor: mode === currentMode ? "primary.main" : "background.paper",
|
||||
color: mode === currentMode ? "primary.contrastText" : "text.primary",
|
||||
bgcolor: mode === currentModeKey ? "primary.main" : "background.paper",
|
||||
color: mode === currentModeKey ? "primary.contrastText" : "text.primary",
|
||||
borderRadius: 1.5,
|
||||
transition: "all 0.2s ease-in-out",
|
||||
position: "relative",
|
||||
@@ -82,7 +109,7 @@ export const ClashModeCard = () => {
|
||||
transform: "translateY(1px)",
|
||||
},
|
||||
"&::after":
|
||||
mode === currentMode
|
||||
mode === currentModeKey
|
||||
? {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
@@ -128,7 +155,7 @@ export const ClashModeCard = () => {
|
||||
{modeList.map((mode) => (
|
||||
<Paper
|
||||
key={mode}
|
||||
elevation={mode === currentMode ? 2 : 0}
|
||||
elevation={mode === currentModeKey ? 2 : 0}
|
||||
onClick={() => onChangeMode(mode)}
|
||||
sx={buttonStyles(mode)}
|
||||
>
|
||||
@@ -137,10 +164,10 @@ export const ClashModeCard = () => {
|
||||
variant="body2"
|
||||
sx={{
|
||||
textTransform: "capitalize",
|
||||
fontWeight: mode === currentMode ? 600 : 400,
|
||||
fontWeight: mode === currentModeKey ? 600 : 400,
|
||||
}}
|
||||
>
|
||||
{t(mode)}
|
||||
{t(MODE_META[mode].label)}
|
||||
</Typography>
|
||||
</Paper>
|
||||
))}
|
||||
|
||||
@@ -811,11 +811,11 @@ export const CurrentProxyCard = () => {
|
||||
const getSortTooltip = (): string => {
|
||||
switch (sortType) {
|
||||
case 0:
|
||||
return t("Sort by default");
|
||||
return t("proxies.page.tooltips.sortDefault");
|
||||
case 1:
|
||||
return t("Sort by delay");
|
||||
return t("proxies.page.tooltips.sortDelay");
|
||||
case 2:
|
||||
return t("Sort by name");
|
||||
return t("proxies.page.tooltips.sortName");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -823,7 +823,7 @@ export const CurrentProxyCard = () => {
|
||||
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("Current Node")}
|
||||
title={t("home.components.currentProxy.title")}
|
||||
icon={
|
||||
<Tooltip
|
||||
title={
|
||||
@@ -840,7 +840,9 @@ export const CurrentProxyCard = () => {
|
||||
iconColor={currentProxy ? "primary" : undefined}
|
||||
action={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<Tooltip title={t("Delay check")}>
|
||||
<Tooltip
|
||||
title={t("home.components.currentProxy.actions.refreshDelay")}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
@@ -868,7 +870,7 @@ export const CurrentProxyCard = () => {
|
||||
sx={{ borderRadius: 1.5 }}
|
||||
endIcon={<ChevronRight fontSize="small" />}
|
||||
>
|
||||
{t("Label-Proxies")}
|
||||
{t("layout.components.navigation.tabs.proxies")}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
@@ -906,7 +908,7 @@ export const CurrentProxyCard = () => {
|
||||
{isGlobalMode && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={t("Global Mode")}
|
||||
label={t("home.components.currentProxy.labels.globalMode")}
|
||||
color="primary"
|
||||
sx={{ mr: 0.5 }}
|
||||
/>
|
||||
@@ -914,7 +916,7 @@ export const CurrentProxyCard = () => {
|
||||
{isDirectMode && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={t("Direct Mode")}
|
||||
label={t("home.components.currentProxy.labels.directMode")}
|
||||
color="success"
|
||||
sx={{ mr: 0.5 }}
|
||||
/>
|
||||
@@ -954,12 +956,14 @@ export const CurrentProxyCard = () => {
|
||||
size="small"
|
||||
sx={{ mb: 1.5 }}
|
||||
>
|
||||
<InputLabel id="proxy-group-select-label">{t("Group")}</InputLabel>
|
||||
<InputLabel id="proxy-group-select-label">
|
||||
{t("home.components.currentProxy.labels.group")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="proxy-group-select-label"
|
||||
value={state.selection.group}
|
||||
onChange={handleGroupChange}
|
||||
label={t("Group")}
|
||||
label={t("home.components.currentProxy.labels.group")}
|
||||
disabled={isGlobalMode || isDirectMode}
|
||||
>
|
||||
{state.proxyData.groups.map((group) => (
|
||||
@@ -972,12 +976,14 @@ export const CurrentProxyCard = () => {
|
||||
|
||||
{/* 代理节点选择器 */}
|
||||
<FormControl fullWidth variant="outlined" size="small" sx={{ mb: 0 }}>
|
||||
<InputLabel id="proxy-select-label">{t("Proxy")}</InputLabel>
|
||||
<InputLabel id="proxy-select-label">
|
||||
{t("home.components.currentProxy.labels.proxy")}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="proxy-select-label"
|
||||
value={state.selection.proxy}
|
||||
onChange={handleProxyChange}
|
||||
label={t("Proxy")}
|
||||
label={t("home.components.currentProxy.labels.proxy")}
|
||||
disabled={isDirectMode}
|
||||
renderValue={renderProxyValue}
|
||||
MenuProps={{
|
||||
@@ -1033,7 +1039,7 @@ export const CurrentProxyCard = () => {
|
||||
) : (
|
||||
<Box sx={{ textAlign: "center", py: 4 }}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{t("No active proxy node")}
|
||||
{t("home.components.currentProxy.labels.noActiveNode")}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -851,7 +851,9 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
|
||||
// 获取时间范围文本
|
||||
const getTimeRangeText = useCallback(() => {
|
||||
return t("{{time}} Minutes", { time: timeRange });
|
||||
return t("home.components.traffic.patterns.minutes", {
|
||||
time: timeRange,
|
||||
});
|
||||
}, [timeRange, t]);
|
||||
|
||||
return (
|
||||
@@ -934,7 +936,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
textAlign: "right",
|
||||
}}
|
||||
>
|
||||
{t("Upload")}
|
||||
{t("home.components.traffic.legends.upload")}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -944,7 +946,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
textAlign: "right",
|
||||
}}
|
||||
>
|
||||
{t("Download")}
|
||||
{t("home.components.traffic.legends.download")}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -219,42 +219,42 @@ export const EnhancedTrafficStats = () => {
|
||||
() => [
|
||||
{
|
||||
icon: <ArrowUpwardRounded fontSize="small" />,
|
||||
title: t("Upload Speed"),
|
||||
title: t("home.components.traffic.metrics.uploadSpeed"),
|
||||
value: parsedData.up,
|
||||
unit: `${parsedData.upUnit}/s`,
|
||||
color: "secondary" as const,
|
||||
},
|
||||
{
|
||||
icon: <ArrowDownwardRounded fontSize="small" />,
|
||||
title: t("Download Speed"),
|
||||
title: t("home.components.traffic.metrics.downloadSpeed"),
|
||||
value: parsedData.down,
|
||||
unit: `${parsedData.downUnit}/s`,
|
||||
color: "primary" as const,
|
||||
},
|
||||
{
|
||||
icon: <LinkRounded fontSize="small" />,
|
||||
title: t("Active Connections"),
|
||||
title: t("home.components.traffic.metrics.activeConnections"),
|
||||
value: parsedData.connectionsCount,
|
||||
unit: "",
|
||||
color: "success" as const,
|
||||
},
|
||||
{
|
||||
icon: <CloudUploadRounded fontSize="small" />,
|
||||
title: t("Uploaded"),
|
||||
title: t("shared.labels.uploaded"),
|
||||
value: parsedData.uploadTotal,
|
||||
unit: parsedData.uploadTotalUnit,
|
||||
color: "secondary" as const,
|
||||
},
|
||||
{
|
||||
icon: <CloudDownloadRounded fontSize="small" />,
|
||||
title: t("Downloaded"),
|
||||
title: t("shared.labels.downloaded"),
|
||||
value: parsedData.downloadTotal,
|
||||
unit: parsedData.downloadTotalUnit,
|
||||
color: "primary" as const,
|
||||
},
|
||||
{
|
||||
icon: <MemoryRounded fontSize="small" />,
|
||||
title: t("Memory Usage"),
|
||||
title: t("home.components.traffic.metrics.memoryUsage"),
|
||||
value: parsedData.inuse,
|
||||
unit: parsedData.inuseUnit,
|
||||
color: "error" as const,
|
||||
|
||||
@@ -111,7 +111,7 @@ const ProfileDetails = ({
|
||||
noWrap
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<span style={{ flexShrink: 0 }}>{t("From")}: </span>
|
||||
<span style={{ flexShrink: 0 }}>{t("shared.labels.from")}: </span>
|
||||
{current.home ? (
|
||||
<Link
|
||||
component="button"
|
||||
@@ -186,7 +186,7 @@ const ProfileDetails = ({
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={onUpdateProfile}
|
||||
>
|
||||
{t("Update Time")}:{" "}
|
||||
{t("shared.labels.updateTime")}:{" "}
|
||||
<Box component="span" fontWeight="medium">
|
||||
{dayjs(current.updated * 1000).format("YYYY-MM-DD HH:mm")}
|
||||
</Box>
|
||||
@@ -199,7 +199,7 @@ const ProfileDetails = ({
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<SpeedOutlined fontSize="small" color="action" />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Used / Total")}:{" "}
|
||||
{t("shared.labels.usedTotal")}:{" "}
|
||||
<Box component="span" fontWeight="medium">
|
||||
{parseTraffic(usedTraffic)} /{" "}
|
||||
{parseTraffic(current.extra.total)}
|
||||
@@ -211,7 +211,7 @@ const ProfileDetails = ({
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<EventOutlined fontSize="small" color="action" />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Expire Time")}:{" "}
|
||||
{t("shared.labels.expireTime")}:{" "}
|
||||
<Box component="span" fontWeight="medium">
|
||||
{parseExpire(current.extra.expire)}
|
||||
</Box>
|
||||
@@ -266,10 +266,10 @@ const EmptyProfile = ({ onClick }: { onClick: () => void }) => {
|
||||
sx={{ fontSize: 60, color: "primary.main", mb: 2 }}
|
||||
/>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{t("Import")} {t("Profiles")}
|
||||
{t("profiles.page.actions.import")} {t("profiles.page.title")}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Click to import subscription")}
|
||||
{t("profiles.components.card.labels.clickToImport")}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
@@ -296,8 +296,8 @@ export const HomeProfileCard = ({
|
||||
|
||||
// 刷新首页数据
|
||||
refreshAll();
|
||||
} catch (err: any) {
|
||||
showNotice("error", err.message || err.toString(), 3000);
|
||||
} catch (err) {
|
||||
showNotice.error(err, 3000);
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
@@ -310,7 +310,7 @@ export const HomeProfileCard = ({
|
||||
|
||||
// 卡片标题
|
||||
const cardTitle = useMemo(() => {
|
||||
if (!current) return t("Profiles");
|
||||
if (!current) return t("profiles.page.title");
|
||||
|
||||
if (!current.home) return current.name;
|
||||
|
||||
@@ -363,7 +363,7 @@ export const HomeProfileCard = ({
|
||||
endIcon={<StorageOutlined fontSize="small" />}
|
||||
sx={{ borderRadius: 1.5 }}
|
||||
>
|
||||
{t("Label-Profiles")}
|
||||
{t("layout.components.navigation.tabs.proxies")}
|
||||
</Button>
|
||||
);
|
||||
}, [current, goToProfiles, t]);
|
||||
|
||||
@@ -68,8 +68,12 @@ export const IpInfoCard = () => {
|
||||
const data = await getIpInfo();
|
||||
setIpInfo(data);
|
||||
setCountdown(IP_REFRESH_SECONDS);
|
||||
} catch (err: any) {
|
||||
setError(err.message || t("Failed to get IP info"));
|
||||
} catch (err) {
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t("home.components.ipInfo.errors.load"),
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -114,7 +118,7 @@ export const IpInfoCard = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("IP Information")}
|
||||
title={t("home.components.ipInfo.title")}
|
||||
icon={<LocationOnOutlined />}
|
||||
iconColor="info"
|
||||
action={
|
||||
@@ -137,7 +141,7 @@ export const IpInfoCard = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("IP Information")}
|
||||
title={t("home.components.ipInfo.title")}
|
||||
icon={<LocationOnOutlined />}
|
||||
iconColor="info"
|
||||
action={
|
||||
@@ -160,7 +164,7 @@ export const IpInfoCard = () => {
|
||||
{error}
|
||||
</Typography>
|
||||
<Button onClick={fetchIpInfo} sx={{ mt: 2 }}>
|
||||
{t("Retry")}
|
||||
{t("shared.actions.retry")}
|
||||
</Button>
|
||||
</Box>
|
||||
</EnhancedCard>
|
||||
@@ -170,7 +174,7 @@ export const IpInfoCard = () => {
|
||||
// 渲染正常数据
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("IP Information")}
|
||||
title={t("home.components.ipInfo.title")}
|
||||
icon={<LocationOnOutlined />}
|
||||
iconColor="info"
|
||||
action={
|
||||
@@ -222,7 +226,7 @@ export const IpInfoCard = () => {
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
{ipInfo?.country || t("Unknown")}
|
||||
{ipInfo?.country || t("home.components.ipInfo.labels.unknown")}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -232,7 +236,7 @@ export const IpInfoCard = () => {
|
||||
color="text.secondary"
|
||||
sx={{ flexShrink: 0 }}
|
||||
>
|
||||
{t("IP")}:
|
||||
{t("home.components.ipInfo.labels.ip")}:
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -266,20 +270,29 @@ export const IpInfoCard = () => {
|
||||
</Box>
|
||||
|
||||
<InfoItem
|
||||
label={t("ASN")}
|
||||
label={t("home.components.ipInfo.labels.asn")}
|
||||
value={ipInfo?.asn ? `AS${ipInfo.asn}` : "N/A"}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 右侧:组织、ISP和位置信息 */}
|
||||
<Box sx={{ width: "60%", overflow: "auto" }}>
|
||||
<InfoItem label={t("ISP")} value={ipInfo?.isp} />
|
||||
<InfoItem label={t("ORG")} value={ipInfo?.asn_organization} />
|
||||
<InfoItem
|
||||
label={t("Location")}
|
||||
label={t("home.components.ipInfo.labels.isp")}
|
||||
value={ipInfo?.isp}
|
||||
/>
|
||||
<InfoItem
|
||||
label={t("home.components.ipInfo.labels.org")}
|
||||
value={ipInfo?.asn_organization}
|
||||
/>
|
||||
<InfoItem
|
||||
label={t("home.components.ipInfo.labels.location")}
|
||||
value={[ipInfo?.city, ipInfo?.region].filter(Boolean).join(", ")}
|
||||
/>
|
||||
<InfoItem label={t("Timezone")} value={ipInfo?.timezone} />
|
||||
<InfoItem
|
||||
label={t("home.components.ipInfo.labels.timezone")}
|
||||
value={ipInfo?.timezone}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -297,7 +310,7 @@ export const IpInfoCard = () => {
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption">
|
||||
{t("Auto refresh")}: {countdown}s
|
||||
{t("home.components.ipInfo.labels.autoRefresh")}: {countdown}s
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
|
||||
@@ -147,8 +147,8 @@ export const ProxyTunCard: FC = () => {
|
||||
|
||||
const { enable_tun_mode } = verge ?? {};
|
||||
|
||||
const handleError = (err: Error) => {
|
||||
showNotice("error", err.message || err.toString());
|
||||
const handleError = (err: unknown) => {
|
||||
showNotice.error(err);
|
||||
};
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
@@ -160,18 +160,18 @@ export const ProxyTunCard: FC = () => {
|
||||
if (activeTab === "system") {
|
||||
return {
|
||||
text: systemProxyActualState
|
||||
? t("System Proxy Enabled")
|
||||
: t("System Proxy Disabled"),
|
||||
tooltip: t("System Proxy Info"),
|
||||
? t("home.components.proxyTun.status.systemProxyEnabled")
|
||||
: t("home.components.proxyTun.status.systemProxyDisabled"),
|
||||
tooltip: t("home.components.proxyTun.tooltips.systemProxy"),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
text: !isTunModeAvailable
|
||||
? t("TUN Mode Service Required")
|
||||
? t("home.components.proxyTun.status.tunModeServiceRequired")
|
||||
: enable_tun_mode
|
||||
? t("TUN Mode Enabled")
|
||||
: t("TUN Mode Disabled"),
|
||||
tooltip: t("TUN Mode Intercept Info"),
|
||||
? t("home.components.proxyTun.status.tunModeEnabled")
|
||||
: t("home.components.proxyTun.status.tunModeDisabled"),
|
||||
tooltip: t("home.components.proxyTun.tooltips.tunMode"),
|
||||
};
|
||||
}
|
||||
}, [
|
||||
@@ -198,14 +198,14 @@ export const ProxyTunCard: FC = () => {
|
||||
isActive={activeTab === "system"}
|
||||
onClick={() => handleTabChange("system")}
|
||||
icon={ComputerRounded}
|
||||
label={t("System Proxy")}
|
||||
label={t("settings.sections.system.toggles.systemProxy")}
|
||||
hasIndicator={systemProxyActualState}
|
||||
/>
|
||||
<TabButton
|
||||
isActive={activeTab === "tun"}
|
||||
onClick={() => handleTabChange("tun")}
|
||||
icon={TroubleshootRounded}
|
||||
label={t("Tun Mode")}
|
||||
label={t("settings.sections.system.toggles.tunMode")}
|
||||
hasIndicator={enable_tun_mode && isTunModeAvailable}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -236,7 +236,11 @@ export const ProxyTunCard: FC = () => {
|
||||
>
|
||||
<ProxyControlSwitches
|
||||
onError={handleError}
|
||||
label={activeTab === "system" ? t("System Proxy") : t("Tun Mode")}
|
||||
label={
|
||||
activeTab === "system"
|
||||
? t("settings.sections.system.toggles.systemProxy")
|
||||
: t("settings.sections.system.toggles.tunMode")
|
||||
}
|
||||
noRightPadding={true}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -174,13 +174,15 @@ export const SystemInfoCard = () => {
|
||||
try {
|
||||
const info = await checkUpdate();
|
||||
if (!info?.available) {
|
||||
showNotice("success", t("Currently on the Latest Version"));
|
||||
showNotice.success(
|
||||
"settings.components.verge.advanced.notifications.latestVersion",
|
||||
);
|
||||
} else {
|
||||
showNotice("info", t("Update Available"), 2000);
|
||||
showNotice.info("shared.feedback.notifications.updateAvailable", 2000);
|
||||
goToSettings();
|
||||
}
|
||||
} catch (err: any) {
|
||||
showNotice("error", err.message || err.toString());
|
||||
} catch (err) {
|
||||
showNotice.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -217,11 +219,11 @@ export const SystemInfoCard = () => {
|
||||
<>
|
||||
<AdminPanelSettingsOutlined
|
||||
sx={{ color: "primary.main", fontSize: 16 }}
|
||||
titleAccess={t("Administrator Mode")}
|
||||
titleAccess={t("home.components.systemInfo.badges.adminMode")}
|
||||
/>
|
||||
<DnsOutlined
|
||||
sx={{ color: "success.main", fontSize: 16, ml: 0.5 }}
|
||||
titleAccess={t("Service Mode")}
|
||||
titleAccess={t("home.components.systemInfo.badges.serviceMode")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -229,21 +231,21 @@ export const SystemInfoCard = () => {
|
||||
return (
|
||||
<AdminPanelSettingsOutlined
|
||||
sx={{ color: "primary.main", fontSize: 16 }}
|
||||
titleAccess={t("Administrator Mode")}
|
||||
titleAccess={t("home.components.systemInfo.badges.adminMode")}
|
||||
/>
|
||||
);
|
||||
} else if (isSidecarMode) {
|
||||
return (
|
||||
<ExtensionOutlined
|
||||
sx={{ color: "info.main", fontSize: 16 }}
|
||||
titleAccess={t("Sidecar Mode")}
|
||||
titleAccess={t("home.components.systemInfo.badges.sidecarMode")}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<DnsOutlined
|
||||
sx={{ color: "success.main", fontSize: 16 }}
|
||||
titleAccess={t("Service Mode")}
|
||||
titleAccess={t("home.components.systemInfo.badges.serviceMode")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -254,13 +256,13 @@ export const SystemInfoCard = () => {
|
||||
if (isAdminMode) {
|
||||
// 判断是否同时处于服务模式
|
||||
if (!isSidecarMode) {
|
||||
return t("Administrator + Service Mode");
|
||||
return t("home.components.systemInfo.badges.adminServiceMode");
|
||||
}
|
||||
return t("Administrator Mode");
|
||||
return t("home.components.systemInfo.badges.adminMode");
|
||||
} else if (isSidecarMode) {
|
||||
return t("Sidecar Mode");
|
||||
return t("home.components.systemInfo.badges.sidecarMode");
|
||||
} else {
|
||||
return t("Service Mode");
|
||||
return t("home.components.systemInfo.badges.serviceMode");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -269,11 +271,15 @@ export const SystemInfoCard = () => {
|
||||
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("System Info")}
|
||||
title={t("home.components.systemInfo.title")}
|
||||
icon={<InfoOutlined />}
|
||||
iconColor="error"
|
||||
action={
|
||||
<IconButton size="small" onClick={goToSettings} title={t("Settings")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={goToSettings}
|
||||
title={t("home.components.systemInfo.actions.settings")}
|
||||
>
|
||||
<SettingsOutlined fontSize="small" />
|
||||
</IconButton>
|
||||
}
|
||||
@@ -281,7 +287,7 @@ export const SystemInfoCard = () => {
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("OS Info")}
|
||||
{t("home.components.systemInfo.fields.osInfo")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{systemState.osInfo}
|
||||
@@ -294,19 +300,23 @@ export const SystemInfoCard = () => {
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Auto Launch")}
|
||||
{t("home.components.systemInfo.fields.autoLaunch")}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
{isAdminMode && (
|
||||
<Tooltip
|
||||
title={t("Administrator mode may not support auto launch")}
|
||||
title={t("home.components.systemInfo.tooltips.autoLaunchAdmin")}
|
||||
>
|
||||
<WarningOutlined sx={{ color: "warning.main", fontSize: 20 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Chip
|
||||
size="small"
|
||||
label={autoLaunchEnabled ? t("Enabled") : t("Disabled")}
|
||||
label={
|
||||
autoLaunchEnabled
|
||||
? t("shared.statuses.enabled")
|
||||
: t("shared.statuses.disabled")
|
||||
}
|
||||
color={autoLaunchEnabled ? "success" : "default"}
|
||||
variant={autoLaunchEnabled ? "filled" : "outlined"}
|
||||
onClick={toggleAutoLaunch}
|
||||
@@ -321,7 +331,7 @@ export const SystemInfoCard = () => {
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Running Mode")}
|
||||
{t("home.components.systemInfo.fields.runningMode")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -336,7 +346,7 @@ export const SystemInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Last Check Update")}
|
||||
{t("home.components.systemInfo.fields.lastCheckUpdate")}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -354,7 +364,7 @@ export const SystemInfoCard = () => {
|
||||
<Divider />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("Verge Version")}
|
||||
{t("home.components.systemInfo.fields.vergeVersion")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
v{appVersion}
|
||||
|
||||
@@ -173,16 +173,16 @@ export const TestCard = () => {
|
||||
|
||||
return (
|
||||
<EnhancedCard
|
||||
title={t("Website Tests")}
|
||||
title={t("home.components.tests.title")}
|
||||
icon={<NetworkCheck />}
|
||||
action={
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<Tooltip title={t("Test All")} arrow>
|
||||
<Tooltip title={t("tests.page.actions.testAll")} arrow>
|
||||
<IconButton size="small" onClick={handleTestAll}>
|
||||
<NetworkCheck fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("Create Test")} arrow>
|
||||
<Tooltip title={t("tests.modals.test.title.create")} arrow>
|
||||
<IconButton size="small" onClick={handleCreateTest}>
|
||||
<Add fontSize="small" />
|
||||
</IconButton>
|
||||
|
||||
Reference in New Issue
Block a user