clash-verge-rev/src/components/home/clash-mode-card.tsx
Sline c8aa72186e
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>
2025-11-08 19:40:38 +08:00

194 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
DirectionsRounded,
LanguageRounded,
MultipleStopRounded,
} from "@mui/icons-material";
import { Box, Paper, Stack, Typography } from "@mui/material";
import { useLockFn } from "ahooks";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
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();
const { verge } = useVerge();
const { clashConfig, refreshClashConfig } = useAppData();
// 支持的模式列表
const modeList = CLASH_MODES;
// 直接使用API返回的模式不维护本地状态
const currentMode = clashConfig?.mode?.toLowerCase();
const currentModeKey =
typeof currentMode === "string" && isClashMode(currentMode)
? currentMode
: undefined;
const modeDescription = useMemo(() => {
if (currentModeKey) {
return t(MODE_META[currentModeKey].description);
}
return t("home.components.clashMode.errors.communication");
}, [currentModeKey, t]);
// 模式图标映射
const modeIcons = useMemo(
() => ({
rule: <MultipleStopRounded fontSize="small" />,
global: <LanguageRounded fontSize="small" />,
direct: <DirectionsRounded fontSize="small" />,
}),
[],
);
// 切换模式的处理函数
const onChangeMode = useLockFn(async (mode: ClashMode) => {
if (mode === currentModeKey) return;
if (verge?.auto_close_connection) {
closeAllConnections();
}
try {
await patchClashMode(mode);
// 使用共享的刷新方法
refreshClashConfig();
} catch (error) {
console.error("Failed to change mode:", error);
}
});
// 按钮样式
const buttonStyles = (mode: ClashMode) => ({
cursor: "pointer",
px: 2,
py: 1.2,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 1,
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",
overflow: "visible",
"&:hover": {
transform: "translateY(-1px)",
boxShadow: 1,
},
"&:active": {
transform: "translateY(1px)",
},
"&::after":
mode === currentModeKey
? {
content: '""',
position: "absolute",
bottom: -16,
left: "50%",
width: 2,
height: 16,
bgcolor: "primary.main",
transform: "translateX(-50%)",
}
: {},
});
// 描述样式
const descriptionStyles = {
width: "95%",
textAlign: "center",
color: "text.secondary",
p: 0.8,
borderRadius: 1,
borderColor: "primary.main",
borderWidth: 1,
borderStyle: "solid",
backgroundColor: "background.paper",
wordBreak: "break-word",
hyphens: "auto",
};
return (
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
{/* 模式选择按钮组 */}
<Stack
direction="row"
spacing={1}
sx={{
display: "flex",
justifyContent: "center",
py: 1,
position: "relative",
zIndex: 2,
}}
>
{modeList.map((mode) => (
<Paper
key={mode}
elevation={mode === currentModeKey ? 2 : 0}
onClick={() => onChangeMode(mode)}
sx={buttonStyles(mode)}
>
{modeIcons[mode]}
<Typography
variant="body2"
sx={{
textTransform: "capitalize",
fontWeight: mode === currentModeKey ? 600 : 400,
}}
>
{t(MODE_META[mode].label)}
</Typography>
</Paper>
))}
</Stack>
{/* 说明文本区域 */}
<Box
sx={{
width: "100%",
my: 1,
position: "relative",
display: "flex",
justifyContent: "center",
overflow: "visible",
}}
>
<Typography variant="caption" component="div" sx={descriptionStyles}>
{modeDescription}
</Typography>
</Box>
</Box>
);
};