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:
Sline
2025-11-08 19:40:38 +08:00
committed by GitHub
co-authored by renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> rozan
parent c1a2e99f63
commit c8aa72186e
286 changed files with 24243 additions and 8867 deletions
+58 -1
View File
@@ -1,14 +1,17 @@
import { CloseRounded } from "@mui/icons-material";
import { Snackbar, Alert, IconButton, Box } from "@mui/material";
import React, { useSyncExternalStore } from "react";
import { useTranslation } from "react-i18next";
import {
subscribeNotices,
hideNotice,
getSnapshotNotices,
} from "@/services/noticeService";
import type { TranslationKey } from "@/types/generated/i18n-keys";
export const NoticeManager: React.FC = () => {
const { t } = useTranslation();
const currentNotices = useSyncExternalStore(
subscribeNotices,
getSnapshotNotices,
@@ -60,7 +63,61 @@ export const NoticeManager: React.FC = () => {
</IconButton>
}
>
{notice.message}
{notice.i18n
? (() => {
const params = (notice.i18n.params ?? {}) as Record<
string,
unknown
>;
const {
prefixKey,
prefixParams,
prefix,
message,
...restParams
} = params;
const prefixKeyParams =
prefixParams &&
typeof prefixParams === "object" &&
prefixParams !== null
? (prefixParams as Record<string, unknown>)
: undefined;
const resolvedPrefix =
typeof prefixKey === "string"
? t(prefixKey as TranslationKey, {
defaultValue: prefixKey,
...(prefixKeyParams ?? {}),
...restParams,
})
: typeof prefix === "string"
? prefix
: undefined;
const finalParams: Record<string, unknown> = {
...restParams,
};
if (resolvedPrefix !== undefined) {
finalParams.prefix = resolvedPrefix;
}
if (typeof message === "string") {
finalParams.message = message;
}
const defaultValue =
resolvedPrefix && typeof message === "string"
? `${resolvedPrefix} ${message}`
: typeof message === "string"
? message
: undefined;
return t(notice.i18n.key as TranslationKey, {
defaultValue,
...finalParams,
});
})()
: notice.message}
</Alert>
</Snackbar>
))}
+14 -5
View File
@@ -1,16 +1,25 @@
import { InboxRounded } from "@mui/icons-material";
import { alpha, Box, Typography } from "@mui/material";
import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import type { TranslationKey } from "@/types/generated/i18n-keys";
interface Props {
text?: React.ReactNode;
extra?: React.ReactNode;
text?: ReactNode;
textKey?: TranslationKey;
extra?: ReactNode;
}
export const BaseEmpty = (props: Props) => {
const { text = "Empty", extra } = props;
export const BaseEmpty = ({
text,
textKey = "shared.statuses.empty",
extra,
}: Props) => {
const { t } = useTranslation();
const resolvedText: ReactNode = text !== undefined ? text : t(textKey);
return (
<Box
sx={({ palette }) => ({
@@ -24,7 +33,7 @@ export const BaseEmpty = (props: Props) => {
})}
>
<InboxRounded sx={{ fontSize: "4em" }} />
<Typography sx={{ fontSize: "1.25em" }}>{t(`${text}`)}</Typography>
<Typography sx={{ fontSize: "1.25em" }}>{resolvedText}</Typography>
{extra}
</Box>
);
+6 -6
View File
@@ -135,7 +135,7 @@ export const BaseSearchBox = ({
if (useRegularExpression && value) {
const isValid = validateRegex(value);
if (!isValid) {
setErrorMessage(t("Invalid regular expression"));
setErrorMessage(t("shared.validation.invalidRegex"));
}
}
@@ -156,7 +156,7 @@ export const BaseSearchBox = ({
} else {
const value = inputRef.current?.value ?? "";
if (value && !validateRegex(value)) {
setErrorMessage(t("Invalid regular expression"));
setErrorMessage(t("shared.validation.invalidRegex"));
}
}
return next;
@@ -173,7 +173,7 @@ export const BaseSearchBox = ({
size="small"
variant="outlined"
spellCheck="false"
placeholder={placeholder ?? t("Filter conditions")}
placeholder={placeholder ?? t("shared.placeholders.filter")}
sx={{ input: { py: 0.65, px: 1.25 } }}
onChange={onChange}
error={!!errorMessage}
@@ -182,7 +182,7 @@ export const BaseSearchBox = ({
sx: { pr: 1 },
endAdornment: (
<Box display="flex">
<Tooltip title={t("Match Case")}>
<Tooltip title={t("shared.placeholders.matchCase")}>
<div>
<SvgIcon
component={matchCaseIcon}
@@ -192,7 +192,7 @@ export const BaseSearchBox = ({
/>
</div>
</Tooltip>
<Tooltip title={t("Match Whole Word")}>
<Tooltip title={t("shared.placeholders.matchWholeWord")}>
<div>
<SvgIcon
component={matchWholeWordIcon}
@@ -202,7 +202,7 @@ export const BaseSearchBox = ({
/>
</div>
</Tooltip>
<Tooltip title={t("Use Regular Expression")}>
<Tooltip title={t("shared.placeholders.useRegex")}>
<div>
<SvgIcon
component={useRegularExpressionIcon}
@@ -12,7 +12,7 @@ export const BaseStyledTextField = styled((props: TextFieldProps) => {
size="small"
variant="outlined"
spellCheck="false"
placeholder={t("Filter conditions")}
placeholder={t("shared.placeholders.filter")}
sx={{ input: { py: 0.65, px: 1.25 } }}
{...props}
/>
@@ -177,7 +177,7 @@ const TrafficErrorFallback: React.FC<TrafficErrorFallbackProps> = ({
<ErrorOutlineRounded sx={{ fontSize: 48, mb: 2, color: "error.main" }} />
<Typography variant="h6" gutterBottom>
{t("Traffic Statistics Error")}
{t("shared.feedback.errors.trafficStats")}
</Typography>
<Typography
@@ -186,18 +186,17 @@ const TrafficErrorFallback: React.FC<TrafficErrorFallbackProps> = ({
textAlign="center"
sx={{ mb: 2 }}
>
{t(
"The traffic statistics component encountered an error and has been disabled to prevent crashes.",
)}
{t("shared.feedback.errors.trafficStatsDescription")}
</Typography>
<Alert severity="error" sx={{ mb: 2, maxWidth: 400 }}>
<Typography variant="body2">
<strong>Error:</strong> {error?.message || "Unknown error"}
<strong>Error:</strong>{" "}
{error instanceof Error ? error.message : "Unknown error"}
</Typography>
{retryCount > 0 && (
<Typography variant="caption" display="block" sx={{ mt: 1 }}>
{t("Retry attempts")}: {retryCount}/{maxRetries}
{t("shared.labels.retryAttempts")}: {retryCount}/{maxRetries}
</Typography>
)}
</Alert>
@@ -211,12 +210,12 @@ const TrafficErrorFallback: React.FC<TrafficErrorFallbackProps> = ({
onClick={onRetry}
size="small"
>
{t("Retry")}
{t("shared.actions.retry")}
</Button>
)}
<Button variant="outlined" onClick={onRefresh} size="small">
{t("Refresh Page")}
{t("shared.actions.refreshPage")}
</Button>
<Button
@@ -225,7 +224,9 @@ const TrafficErrorFallback: React.FC<TrafficErrorFallbackProps> = ({
onClick={onToggleDetails}
size="small"
>
{showDetails ? t("Hide Details") : t("Show Details")}
{showDetails
? t("shared.actions.hideDetails")
: t("shared.actions.showDetails")}
</Button>
</Box>
+35 -16
View File
@@ -1,8 +1,8 @@
import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { useLockFn } from "ahooks";
import dayjs from "dayjs";
import { t } from "i18next";
import { useImperativeHandle, useState, type Ref } from "react";
import { useTranslation } from "react-i18next";
import { closeConnections } from "tauri-plugin-mihomo-api";
import parseTraffic from "@/utils/parse-traffic";
@@ -55,6 +55,7 @@ interface InnerProps {
}
const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const { t } = useTranslation();
const { metadata, rulePayload } = data;
const theme = useTheme();
const chains = [...data.chains].reverse().join(" / ");
@@ -67,34 +68,52 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
: metadata.remoteDestination;
const information = [
{ label: t("Host"), value: host },
{ label: t("Downloaded"), value: parseTraffic(data.download).join(" ") },
{ label: t("Uploaded"), value: parseTraffic(data.upload).join(" ") },
{ label: t("connections.components.fields.host"), value: host },
{
label: t("DL Speed"),
label: t("shared.labels.downloaded"),
value: parseTraffic(data.download).join(" "),
},
{
label: t("shared.labels.uploaded"),
value: parseTraffic(data.upload).join(" "),
},
{
label: t("connections.components.fields.dlSpeed"),
value: parseTraffic(data.curDownload ?? -1).join(" ") + "/s",
},
{
label: t("UL Speed"),
label: t("connections.components.fields.ulSpeed"),
value: parseTraffic(data.curUpload ?? -1).join(" ") + "/s",
},
{
label: t("Chains"),
label: t("connections.components.fields.chains"),
value: chains,
},
{ label: t("Rule"), value: rule },
{ label: t("connections.components.fields.rule"), value: rule },
{
label: t("Process"),
label: t("connections.components.fields.process"),
value: `${metadata.process}${metadata.processPath ? `(${metadata.processPath})` : ""}`,
},
{ label: t("Time"), value: dayjs(data.start).fromNow() },
{
label: t("Source"),
label: t("connections.components.fields.time"),
value: dayjs(data.start).fromNow(),
},
{
label: t("connections.components.fields.source"),
value: `${metadata.sourceIP}:${metadata.sourcePort}`,
},
{ label: t("Destination"), value: Destination },
{ label: t("DestinationPort"), value: `${metadata.destinationPort}` },
{ label: t("Type"), value: `${metadata.type}(${metadata.network})` },
{
label: t("connections.components.fields.destination"),
value: Destination,
},
{
label: t("connections.components.fields.destinationPort"),
value: `${metadata.destinationPort}`,
},
{
label: t("connections.components.fields.type"),
value: `${metadata.type}(${metadata.network})`,
},
];
const onDelete = useLockFn(async () => closeConnections(data.id));
@@ -118,13 +137,13 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
<Box sx={{ textAlign: "right" }}>
<Button
variant="contained"
title={t("Close Connection")}
title={t("connections.components.actions.closeConnection")}
onClick={() => {
onDelete();
onClose?.();
}}
>
{t("Close Connection")}
{t("connections.components.actions.closeConnection")}
</Button>
</Box>
</Box>
@@ -9,6 +9,7 @@ import {
} from "@mui/material";
import { useLockFn } from "ahooks";
import dayjs from "dayjs";
import { useTranslation } from "react-i18next";
import { closeConnections } from "tauri-plugin-mihomo-api";
import parseTraffic from "@/utils/parse-traffic";
@@ -33,6 +34,7 @@ export const ConnectionItem = (props: Props) => {
const { value, onShowDetail } = props;
const { id, metadata, chains, start, curUpload, curDownload } = value;
const { t } = useTranslation();
const onDelete = useLockFn(async () => closeConnections(id));
const showTraffic = curUpload! >= 100 || curDownload! >= 100;
@@ -42,7 +44,13 @@ export const ConnectionItem = (props: Props) => {
dense
sx={{ borderBottom: "1px solid var(--divider-color)" }}
secondaryAction={
<IconButton edge="end" color="inherit" onClick={onDelete}>
<IconButton
edge="end"
color="inherit"
onClick={onDelete}
title={t("connections.components.actions.closeConnection")}
aria-label={t("connections.components.actions.closeConnection")}
>
<CloseRounded />
</IconButton>
}
+12 -13
View File
@@ -162,13 +162,13 @@ export const ConnectionTable = (props: Props) => {
return [
{
field: "host",
headerName: t("Host"),
headerName: t("connections.components.fields.host"),
width: columnWidths["host"] || 220,
minWidth: 180,
},
{
field: "download",
headerName: t("Downloaded"),
headerName: t("shared.labels.downloaded"),
width: columnWidths["download"] || 88,
align: "right",
headerAlign: "right",
@@ -176,7 +176,7 @@ export const ConnectionTable = (props: Props) => {
},
{
field: "upload",
headerName: t("Uploaded"),
headerName: t("shared.labels.uploaded"),
width: columnWidths["upload"] || 88,
align: "right",
headerAlign: "right",
@@ -184,7 +184,7 @@ export const ConnectionTable = (props: Props) => {
},
{
field: "dlSpeed",
headerName: t("DL Speed"),
headerName: t("connections.components.fields.dlSpeed"),
width: columnWidths["dlSpeed"] || 88,
align: "right",
headerAlign: "right",
@@ -192,7 +192,7 @@ export const ConnectionTable = (props: Props) => {
},
{
field: "ulSpeed",
headerName: t("UL Speed"),
headerName: t("connections.components.fields.ulSpeed"),
width: columnWidths["ulSpeed"] || 88,
align: "right",
headerAlign: "right",
@@ -200,25 +200,25 @@ export const ConnectionTable = (props: Props) => {
},
{
field: "chains",
headerName: t("Chains"),
headerName: t("connections.components.fields.chains"),
width: columnWidths["chains"] || 340,
minWidth: 180,
},
{
field: "rule",
headerName: t("Rule"),
headerName: t("connections.components.fields.rule"),
width: columnWidths["rule"] || 280,
minWidth: 180,
},
{
field: "process",
headerName: t("Process"),
headerName: t("connections.components.fields.process"),
width: columnWidths["process"] || 220,
minWidth: 180,
},
{
field: "time",
headerName: t("Time"),
headerName: t("connections.components.fields.time"),
width: columnWidths["time"] || 120,
minWidth: 100,
align: "right",
@@ -229,19 +229,19 @@ export const ConnectionTable = (props: Props) => {
},
{
field: "source",
headerName: t("Source"),
headerName: t("connections.components.fields.source"),
width: columnWidths["source"] || 200,
minWidth: 130,
},
{
field: "remoteDestination",
headerName: t("Destination"),
headerName: t("connections.components.fields.destination"),
width: columnWidths["remoteDestination"] || 200,
minWidth: 130,
},
{
field: "type",
headerName: t("Type"),
headerName: t("connections.components.fields.type"),
width: columnWidths["type"] || 160,
minWidth: 100,
},
@@ -250,7 +250,6 @@ export const ConnectionTable = (props: Props) => {
const handleColumnResize = (params: GridColumnResizeParams) => {
const { colDef, width } = params;
console.log("Column resize:", colDef.field, width);
setColumnWidths((prev) => ({
...prev,
[colDef.field]: width,
+6 -6
View File
@@ -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}
+43 -16
View File
@@ -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>
))}
+19 -13
View File
@@ -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,
+10 -10
View File
@@ -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]);
+27 -14
View File
@@ -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"
+16 -12
View File
@@ -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>
+32 -22
View File
@@ -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}
+3 -3
View File
@@ -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>
+3 -3
View File
@@ -87,7 +87,7 @@ export const LayoutTraffic = () => {
<Box display="flex" flexDirection="column" gap={0.75}>
<Box
title={`${t("Upload Speed")}`}
title={`${t("home.components.traffic.metrics.uploadSpeed")}`}
{...boxStyle}
sx={{
...boxStyle.sx,
@@ -105,7 +105,7 @@ export const LayoutTraffic = () => {
</Box>
<Box
title={`${t("Download Speed")}`}
title={`${t("home.components.traffic.metrics.downloadSpeed")}`}
{...boxStyle}
sx={{
...boxStyle.sx,
@@ -124,7 +124,7 @@ export const LayoutTraffic = () => {
{displayMemory && (
<Box
title={`${t("Memory Usage")} `}
title={`${t("home.components.traffic.metrics.memoryUsage")} `}
{...boxStyle}
sx={{
cursor: "auto",
+2 -2
View File
@@ -35,10 +35,10 @@ export const ConfirmViewer = (props: Props) => {
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Cancel")}
{t("shared.actions.cancel")}
</Button>
<Button onClick={onConfirm} variant="contained">
{t("Confirm")}
{t("shared.actions.confirm")}
</Button>
</DialogActions>
</Dialog>
+16 -12
View File
@@ -97,7 +97,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
onClose,
} = props;
const resolvedTitle = title ?? t("Edit File");
const resolvedTitle = title ?? t("profiles.components.menu.editFile");
const resolvedInitialData = useMemo(
() => initialData ?? Promise.resolve(""),
[initialData],
@@ -130,8 +130,8 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
try {
currData.current = value;
onChange?.(prevData.current, currData.current);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -141,16 +141,16 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
onSave?.(prevData.current, currData.current);
}
onClose();
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
const handleClose = useLockFn(async () => {
try {
onClose();
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -200,7 +200,9 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
},
mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例
readOnly: readOnly, // 只读模式
readOnlyMessage: { value: t("ReadOnlyMessage") }, // 只读模式尝试编辑时的提示信息
readOnlyMessage: {
value: t("profiles.modals.editor.messages.readOnly"),
}, // 只读模式尝试编辑时的提示信息
renderValidationDecorations: "on", // 只读模式下显示校验信息
quickSuggestions: {
strings: true, // 字符串类型的建议
@@ -229,7 +231,7 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
size="medium"
color="inherit"
sx={{ display: readOnly ? "none" : "" }}
title={t("Format document")}
title={t("profiles.modals.editor.actions.format")}
onClick={() =>
editorRef.current
?.getAction("editor.action.formatDocument")
@@ -241,7 +243,9 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
<IconButton
size="medium"
color="inherit"
title={t(isMaximized ? "Minimize" : "Maximize")}
title={t(
isMaximized ? "shared.window.minimize" : "shared.window.maximize",
)}
onClick={() => appWindow.toggleMaximize().then(editorResize)}
>
{isMaximized ? <CloseFullscreenRounded /> : <OpenInFullRounded />}
@@ -251,11 +255,11 @@ export const EditorViewer = <T extends Language>(props: Props<T>) => {
<DialogActions>
<Button onClick={handleClose} variant="outlined">
{t(readOnly ? "Close" : "Cancel")}
{t(readOnly ? "shared.actions.close" : "shared.actions.cancel")}
</Button>
{!readOnly && (
<Button onClick={handleSave} variant="contained">
{t("Save")}
{t("shared.actions.save")}
</Button>
)}
</DialogActions>
+1 -1
View File
@@ -42,7 +42,7 @@ export const FileInput = (props: Props) => {
sx={{ flex: "none" }}
onClick={() => inputRef.current?.click()}
>
{t("Choose File")}
{t("profiles.components.fileInput.chooseFile")}
</Button>
<input
+156 -43
View File
@@ -57,6 +57,7 @@ import {
} from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import { useThemeMode } from "@/services/states";
import type { TranslationKey } from "@/types/generated/i18n-keys";
import getSystem from "@/utils/get-system";
import { BaseSearchBox } from "../base/base-search-box";
@@ -73,6 +74,24 @@ interface Props {
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
const PROXY_STRATEGY_LABEL_KEYS: Record<string, TranslationKey> = {
select: "proxies.components.enums.strategies.select",
"url-test": "proxies.components.enums.strategies.url-test",
fallback: "proxies.components.enums.strategies.fallback",
"load-balance": "proxies.components.enums.strategies.load-balance",
relay: "proxies.components.enums.strategies.relay",
};
const PROXY_POLICY_LABEL_KEYS: Record<string, TranslationKey> =
builtinProxyPolicies.reduce(
(acc, policy) => {
acc[policy] =
`proxies.components.enums.policies.${policy}` as TranslationKey;
return acc;
},
{} as Record<string, TranslationKey>,
);
const normalizeDeleteSeq = (input?: unknown): string[] => {
if (!Array.isArray(input)) {
return [];
@@ -121,6 +140,20 @@ export const GroupsEditorViewer = (props: Props) => {
const { mergeUid, proxiesUid, profileUid, property, open, onClose, onSave } =
props;
const { t } = useTranslation();
const translateStrategy = useCallback(
(value: string) =>
PROXY_STRATEGY_LABEL_KEYS[value]
? t(PROXY_STRATEGY_LABEL_KEYS[value])
: value,
[t],
);
const translatePolicy = useCallback(
(value: string) =>
PROXY_POLICY_LABEL_KEYS[value]
? t(PROXY_POLICY_LABEL_KEYS[value])
: value,
[t],
);
const themeMode = useThemeMode();
const [prevData, setPrevData] = useState("");
const [currData, setCurrData] = useState("");
@@ -369,7 +402,7 @@ export const GroupsEditorViewer = (props: Props) => {
const validateGroup = () => {
const group = formIns.getValues();
if (group.name === "") {
throw new Error(t("Group Name Required"));
throw new Error(t("profiles.modals.groupsEditor.errors.nameRequired"));
}
};
@@ -384,12 +417,12 @@ export const GroupsEditorViewer = (props: Props) => {
}
await saveProfileFile(property, nextData);
showNotice("success", t("Saved Successfully"));
showNotice.success("shared.feedback.notifications.saved");
setPrevData(nextData);
onSave?.(prevData, nextData);
onClose();
} catch (err: any) {
showNotice("error", err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -398,7 +431,7 @@ export const GroupsEditorViewer = (props: Props) => {
<DialogTitle>
{
<Box display="flex" justifyContent="space-between">
{t("Edit Groups")}
{t("profiles.modals.groupsEditor.title")}
<Box>
<Button
variant="contained"
@@ -407,7 +440,9 @@ export const GroupsEditorViewer = (props: Props) => {
setVisualization((prev) => !prev);
}}
>
{visualization ? t("Advanced") : t("Visualization")}
{visualization
? t("shared.editorModes.advanced")
: t("shared.editorModes.visualization")}
</Button>
</Box>
</Box>
@@ -436,7 +471,9 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Group Type")} />
<ListItemText
primary={t("profiles.modals.groupsEditor.fields.type")}
/>
<Autocomplete
size="small"
sx={{ width: "calc(100% - 150px)" }}
@@ -448,9 +485,10 @@ export const GroupsEditorViewer = (props: Props) => {
"relay",
]}
value={field.value}
getOptionLabel={translateStrategy}
renderOption={(props, option) => (
<li {...props} title={t(option)}>
{option}
<li {...props} title={translateStrategy(option)}>
{translateStrategy(option)}
</li>
)}
onChange={(_, value) => value && field.onChange(value)}
@@ -464,7 +502,9 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Group Name")} />
<ListItemText
primary={t("profiles.modals.groupsEditor.fields.name")}
/>
<TextField
autoComplete="new-password"
size="small"
@@ -481,7 +521,9 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Proxy Group Icon")} />
<ListItemText
primary={t("profiles.modals.groupsEditor.fields.icon")}
/>
<TextField
autoComplete="new-password"
size="small"
@@ -496,7 +538,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Use Proxies")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.proxies",
)}
/>
<Autocomplete
size="small"
sx={{
@@ -508,10 +554,11 @@ export const GroupsEditorViewer = (props: Props) => {
onChange={(_, value) => value && field.onChange(value)}
renderInput={(params) => <TextField {...params} />}
renderOption={(props, option) => (
<li {...props} title={t(option)}>
{option}
<li {...props} title={translatePolicy(option)}>
{translatePolicy(option)}
</li>
)}
getOptionLabel={translatePolicy}
/>
</Item>
)}
@@ -521,7 +568,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Use Provider")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.provider",
)}
/>
<Autocomplete
size="small"
sx={{ width: "calc(100% - 150px)" }}
@@ -539,7 +590,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Health Check Url")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.healthCheckUrl",
)}
/>
<TextField
autoComplete="new-password"
placeholder="https://cp.cloudflare.com/generate_204"
@@ -555,7 +610,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Expected Status")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.expectedStatus",
)}
/>
<TextField
autoComplete="new-password"
placeholder="*"
@@ -573,7 +632,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Interval")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.interval",
)}
/>
<TextField
autoComplete="new-password"
placeholder="300"
@@ -587,7 +650,7 @@ export const GroupsEditorViewer = (props: Props) => {
input: {
endAdornment: (
<InputAdornment position="end">
{t("seconds")}
{t("shared.units.seconds")}
</InputAdornment>
),
},
@@ -601,7 +664,7 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Timeout")} />
<ListItemText primary={t("shared.labels.timeout")} />
<TextField
autoComplete="new-password"
placeholder="5000"
@@ -615,7 +678,7 @@ export const GroupsEditorViewer = (props: Props) => {
input: {
endAdornment: (
<InputAdornment position="end">
{t("millis")}
{t("shared.units.milliseconds")}
</InputAdornment>
),
},
@@ -629,7 +692,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Max Failed Times")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.maxFailedTimes",
)}
/>
<TextField
autoComplete="new-password"
placeholder="5"
@@ -648,7 +715,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Interface Name")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.interfaceName",
)}
/>
<Autocomplete
size="small"
sx={{ width: "calc(100% - 150px)" }}
@@ -665,7 +736,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Routing Mark")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.routingMark",
)}
/>
<TextField
autoComplete="new-password"
type="number"
@@ -683,7 +758,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Filter")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.filter",
)}
/>
<TextField
autoComplete="new-password"
size="small"
@@ -698,7 +777,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Exclude Filter")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.excludeFilter",
)}
/>
<TextField
autoComplete="new-password"
size="small"
@@ -713,7 +796,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Exclude Type")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.excludeType",
)}
/>
<Autocomplete
multiple
options={[
@@ -759,7 +846,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Include All")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.includeAll",
)}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -769,7 +860,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Include All Proxies")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.includeAllProxies",
)}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -779,7 +874,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Include All Providers")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.fields.includeAllProviders",
)}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -789,7 +888,9 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Lazy")} />
<ListItemText
primary={t("profiles.modals.groupsEditor.toggles.lazy")}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -799,7 +900,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Disable UDP")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.toggles.disableUdp",
)}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -809,7 +914,11 @@ export const GroupsEditorViewer = (props: Props) => {
control={control}
render={({ field }) => (
<Item>
<ListItemText primary={t("Hidden")} />
<ListItemText
primary={t(
"profiles.modals.groupsEditor.toggles.hidden",
)}
/>
<Switch checked={field.value} {...field} />
</Item>
)}
@@ -825,16 +934,18 @@ export const GroupsEditorViewer = (props: Props) => {
validateGroup();
for (const item of [...prependSeq, ...groupList]) {
if (item.name === formIns.getValues().name) {
throw new Error(t("Group Name Already Exists"));
throw new Error(
t("profiles.modals.groupsEditor.errors.nameExists"),
);
}
}
setPrependSeq([formIns.getValues(), ...prependSeq]);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
}}
>
{t("Prepend Group")}
{t("profiles.modals.groupsEditor.actions.prepend")}
</Button>
</Item>
<Item>
@@ -847,16 +958,18 @@ export const GroupsEditorViewer = (props: Props) => {
validateGroup();
for (const item of [...appendSeq, ...groupList]) {
if (item.name === formIns.getValues().name) {
throw new Error(t("Group Name Already Exists"));
throw new Error(
t("profiles.modals.groupsEditor.errors.nameExists"),
);
}
}
setAppendSeq([...appendSeq, formIns.getValues()]);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
}}
>
{t("Append Group")}
{t("profiles.modals.groupsEditor.actions.append")}
</Button>
</Item>
</List>
@@ -1007,11 +1120,11 @@ export const GroupsEditorViewer = (props: Props) => {
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Cancel")}
{t("shared.actions.cancel")}
</Button>
<Button onClick={handleSave} variant="contained">
{t("Save")}
{t("shared.actions.save")}
</Button>
</DialogActions>
</Dialog>
+2 -2
View File
@@ -26,7 +26,7 @@ export const LogViewer = (props: Props) => {
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{t("Script Console")}</DialogTitle>
<DialogTitle>{t("profiles.modals.logViewer.title")}</DialogTitle>
<DialogContent
sx={{
@@ -62,7 +62,7 @@ export const LogViewer = (props: Props) => {
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Close")}
{t("shared.actions.close")}
</Button>
</DialogActions>
</Dialog>
+133 -46
View File
@@ -36,6 +36,7 @@ import {
} from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import { useLoadingCache, useSetLoadingCache } from "@/services/states";
import type { TranslationKey } from "@/types/generated/i18n-keys";
import parseTraffic from "@/utils/parse-traffic";
import { ProfileBox } from "./profile-box";
@@ -120,34 +121,46 @@ export const ProfileItem = (props: Props) => {
// 如果已经过期,显示"更新失败"
if (nextUpdateDate.isBefore(now)) {
setNextUpdateTime(t("Last Update failed"));
setNextUpdateTime(
t("profiles.components.profileItem.status.lastUpdateFailed"),
);
} else {
// 否则显示剩余时间
const diffMinutes = nextUpdateDate.diff(now, "minute");
if (diffMinutes < 60) {
if (diffMinutes <= 0) {
setNextUpdateTime(`${t("Next Up")} <1m`);
setNextUpdateTime(
`${t("profiles.components.profileItem.status.nextUp")} <1m`,
);
} else {
setNextUpdateTime(`${t("Next Up")} ${diffMinutes}m`);
setNextUpdateTime(
`${t("profiles.components.profileItem.status.nextUp")} ${diffMinutes}m`,
);
}
} else {
const hours = Math.floor(diffMinutes / 60);
const mins = diffMinutes % 60;
setNextUpdateTime(`${t("Next Up")} ${hours}h ${mins}m`);
setNextUpdateTime(
`${t("profiles.components.profileItem.status.nextUp")} ${hours}h ${mins}m`,
);
}
}
} else {
console.log(`返回的下次更新时间为空`);
setNextUpdateTime(t("No schedule"));
setNextUpdateTime(
t("profiles.components.profileItem.status.noSchedule"),
);
}
} catch (err) {
console.error(`获取下次更新时间出错:`, err);
setNextUpdateTime(t("Unknown"));
setNextUpdateTime(t("profiles.components.profileItem.status.unknown"));
}
} else {
console.log(`该配置未设置更新间隔或间隔为0`);
setNextUpdateTime(t("Auto update disabled"));
setNextUpdateTime(
t("profiles.components.profileItem.status.autoUpdateDisabled"),
);
}
});
@@ -312,8 +325,8 @@ export const ProfileItem = (props: Props) => {
setAnchorEl(null);
try {
await viewProfile(itemData.uid);
} catch (err: any) {
showNotice("error", err?.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -354,42 +367,95 @@ export const ProfileItem = (props: Props) => {
}
});
const urlModeMenu = (
hasHome ? [{ label: "Home", handler: onOpenHome, disabled: false }] : []
).concat([
{ label: "Select", handler: onForceSelect, disabled: false },
{ label: "Edit Info", handler: onEditInfo, disabled: false },
{ label: "Edit File", handler: onEditFile, disabled: false },
type ContextMenuItem = {
label: string;
handler: () => void;
disabled: boolean;
};
const menuLabels: Record<string, TranslationKey> = {
home: "profiles.components.menu.home",
select: "profiles.components.menu.select",
editInfo: "profiles.components.menu.editInfo",
editFile: "profiles.components.menu.editFile",
editRules: "profiles.components.menu.editRules",
editProxies: "profiles.components.menu.editProxies",
editGroups: "profiles.components.menu.editGroups",
extendConfig: "profiles.components.menu.extendConfig",
extendScript: "profiles.components.menu.extendScript",
openFile: "profiles.components.menu.openFile",
update: "profiles.components.menu.update",
updateViaProxy: "profiles.components.menu.updateViaProxy",
delete: "shared.actions.delete",
} as const;
const urlModeMenu: ContextMenuItem[] = [
...(hasHome
? [
{
label: menuLabels.home,
handler: onOpenHome,
disabled: false,
} satisfies ContextMenuItem,
]
: []),
{
label: "Edit Rules",
label: menuLabels.select,
handler: onForceSelect,
disabled: false,
},
{
label: menuLabels.editInfo,
handler: onEditInfo,
disabled: false,
},
{
label: menuLabels.editFile,
handler: onEditFile,
disabled: false,
},
{
label: menuLabels.editRules,
handler: onEditRules,
disabled: !option?.rules,
},
{
label: "Edit Proxies",
label: menuLabels.editProxies,
handler: onEditProxies,
disabled: !option?.proxies,
},
{
label: "Edit Groups",
label: menuLabels.editGroups,
handler: onEditGroups,
disabled: !option?.groups,
},
{
label: "Extend Config",
label: menuLabels.extendConfig,
handler: onEditMerge,
disabled: !option?.merge,
},
{
label: "Extend Script",
label: menuLabels.extendScript,
handler: onEditScript,
disabled: !option?.script,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{ label: "Update", handler: () => onUpdate(0), disabled: false },
{ label: "Update via proxy", handler: () => onUpdate(2), disabled: false },
{
label: "Delete",
label: menuLabels.openFile,
handler: onOpenFile,
disabled: false,
},
{
label: menuLabels.update,
handler: () => onUpdate(0),
disabled: false,
},
{
label: menuLabels.updateViaProxy,
handler: () => onUpdate(2),
disabled: false,
},
{
label: menuLabels.delete,
handler: () => {
setAnchorEl(null);
if (batchMode) {
@@ -403,39 +469,55 @@ export const ProfileItem = (props: Props) => {
},
disabled: false,
},
]);
const fileModeMenu = [
{ label: "Select", handler: onForceSelect, disabled: false },
{ label: "Edit Info", handler: onEditInfo, disabled: false },
{ label: "Edit File", handler: onEditFile, disabled: false },
];
const fileModeMenu: ContextMenuItem[] = [
{
label: "Edit Rules",
label: menuLabels.select,
handler: onForceSelect,
disabled: false,
},
{
label: menuLabels.editInfo,
handler: onEditInfo,
disabled: false,
},
{
label: menuLabels.editFile,
handler: onEditFile,
disabled: false,
},
{
label: menuLabels.editRules,
handler: onEditRules,
disabled: !option?.rules,
},
{
label: "Edit Proxies",
label: menuLabels.editProxies,
handler: onEditProxies,
disabled: !option?.proxies,
},
{
label: "Edit Groups",
label: menuLabels.editGroups,
handler: onEditGroups,
disabled: !option?.groups,
},
{
label: "Extend Config",
label: menuLabels.extendConfig,
handler: onEditMerge,
disabled: !option?.merge,
},
{
label: "Extend Script",
label: menuLabels.extendScript,
handler: onEditScript,
disabled: !option?.script,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{
label: "Delete",
label: menuLabels.openFile,
handler: onOpenFile,
disabled: false,
},
{
label: menuLabels.delete,
handler: () => {
setAnchorEl(null);
if (batchMode) {
@@ -599,7 +681,7 @@ export const ProfileItem = (props: Props) => {
{/* only if has url can it be updated */}
{hasUrl && (
<IconButton
title={t("Refresh")}
title={t("shared.actions.refresh")}
sx={{
position: "absolute",
p: "3px",
@@ -637,7 +719,10 @@ export const ProfileItem = (props: Props) => {
</Typography>
) : (
hasUrl && (
<Typography noWrap title={`${t("From")} ${from}`}>
<Typography
noWrap
title={`${t("shared.labels.from")} ${from}`}
>
{from}
</Typography>
)
@@ -657,8 +742,8 @@ export const ProfileItem = (props: Props) => {
textAlign="right"
title={
showNextUpdate
? t("Click to show last update time")
: `${t("Update Time")}: ${parseExpire(updated)}\n${t("Click to show next update")}`
? t("profiles.components.profileItem.tooltips.showLast")
: `${t("shared.labels.updateTime")}: ${parseExpire(updated)}\n${t("profiles.components.profileItem.tooltips.showNext")}`
}
sx={{
cursor: "pointer",
@@ -686,14 +771,16 @@ export const ProfileItem = (props: Props) => {
{/* the third line show extra info or last updated time */}
{hasExtra ? (
<Box sx={{ ...boxStyle, fontSize: 14 }}>
<span title={t("Used / Total")}>
<span title={t("shared.labels.usedTotal")}>
{parseTraffic(upload + download)} / {parseTraffic(total)}
</span>
<span title={t("Expire Time")}>{expire}</span>
<span title={t("shared.labels.expireTime")}>{expire}</span>
</Box>
) : (
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
<span title={t("Update Time")}>{parseExpire(updated)}</span>
<span title={t("shared.labels.updateTime")}>
{parseExpire(updated)}
</span>
</Box>
)}
<LinearProgress
@@ -728,7 +815,7 @@ export const ProfileItem = (props: Props) => {
(theme) => {
return {
color:
item.label === "Delete"
item.label === menuLabels.delete
? theme.palette.error.main
: undefined,
};
@@ -813,8 +900,8 @@ export const ProfileItem = (props: Props) => {
)}
<ConfirmViewer
title={t("Confirm deletion")}
message={t("This operation is not reversible")}
title={t("profiles.modals.confirmDelete.title")}
message={t("profiles.modals.confirmDelete.message")}
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
onConfirm={() => {
+20 -10
View File
@@ -47,16 +47,26 @@ export const ProfileMore = (props: Props) => {
setAnchorEl(null);
try {
await viewProfile(id);
} catch (err: any) {
showNotice("error", err?.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
const hasError = entries.some(([level]) => level === "exception");
const globalTitles: Record<Props["id"], string> = {
Merge: "profiles.components.more.global.merge",
Script: "profiles.components.more.global.script",
};
const chipLabels: Record<Props["id"], string> = {
Merge: "profiles.components.more.chips.merge",
Script: "profiles.components.more.chips.script",
};
const itemMenu = [
{ label: "Edit File", handler: onEditFile },
{ label: "Open File", handler: onOpenFile },
{ label: "profiles.components.menu.editFile", handler: onEditFile },
{ label: "profiles.components.menu.openFile", handler: onOpenFile },
];
const boxStyle = {
@@ -89,13 +99,13 @@ export const ProfileMore = (props: Props) => {
variant="h6"
component="h2"
noWrap
title={t(`Global ${id}`)}
title={t(globalTitles[id])}
>
{t(`Global ${id}`)}
{t(globalTitles[id])}
</Typography>
<Chip
label={id}
label={t(chipLabels[id])}
color="primary"
size="small"
variant="outlined"
@@ -111,7 +121,7 @@ export const ProfileMore = (props: Props) => {
size="small"
edge="start"
color="error"
title={t("Script Console")}
title={t("profiles.modals.logViewer.title")}
onClick={() => setLogOpen(true)}
>
<FeaturedPlayListRounded fontSize="inherit" />
@@ -122,7 +132,7 @@ export const ProfileMore = (props: Props) => {
size="small"
edge="start"
color="inherit"
title={t("Script Console")}
title={t("profiles.modals.logViewer.title")}
onClick={() => setLogOpen(true)}
>
<FeaturedPlayListRounded fontSize="inherit" />
@@ -170,7 +180,7 @@ export const ProfileMore = (props: Props) => {
{fileOpen && (
<EditorViewer
open={true}
title={`${t("Global " + id)}`}
title={t(globalTitles[id])}
initialData={readProfileFile(id)}
language={id === "Merge" ? "yaml" : "javascript"}
schema={id === "Merge" ? "clash" : undefined}
+46 -24
View File
@@ -144,9 +144,8 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
}
} catch {
// 首次创建/更新失败,尝试使用自身代理
showNotice(
"info",
t("Profile creation failed, retrying with Clash proxy..."),
showNotice.info(
"profiles.modals.profileForm.feedback.notifications.creationRetry",
);
// 使用自身代理的配置
@@ -170,9 +169,8 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
await patchProfile(form.uid, { option: originalOptions });
}
showNotice(
"success",
t("Profile creation succeeded with Clash proxy"),
showNotice.success(
"profiles.modals.profileForm.feedback.notifications.creationSuccess",
);
}
}
@@ -186,8 +184,8 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
setTimeout(() => {
onChange(isActivating);
}, 0);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
} finally {
setLoading(false);
}
@@ -220,10 +218,14 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
return (
<BaseDialog
open={open}
title={openType === "new" ? t("Create Profile") : t("Edit Profile")}
title={
openType === "new"
? t("profiles.modals.profileForm.title.create")
: t("profiles.modals.profileForm.title.edit")
}
contentSx={{ width: 375, pb: 0, maxHeight: "80%" }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={handleClose}
onCancel={handleClose}
onOk={handleOk}
@@ -234,8 +236,14 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
control={control}
render={({ field }) => (
<FormControl size="small" fullWidth sx={{ mt: 1, mb: 1 }}>
<InputLabel>{t("Type")}</InputLabel>
<Select {...field} autoFocus label={t("Type")}>
<InputLabel>
{t("profiles.modals.profileForm.fields.type")}
</InputLabel>
<Select
{...field}
autoFocus
label={t("profiles.modals.profileForm.fields.type")}
>
<MenuItem value="remote">Remote</MenuItem>
<MenuItem value="local">Local</MenuItem>
</Select>
@@ -247,7 +255,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
name="name"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t("Name")} />
<TextField {...text} {...field} label={t("shared.labels.name")} />
)}
/>
@@ -255,7 +263,11 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
name="desc"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t("Descriptions")} />
<TextField
{...text}
{...field}
label={t("profiles.modals.profileForm.fields.description")}
/>
)}
/>
@@ -269,7 +281,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
{...text}
{...field}
multiline
label={t("Subscription URL")}
label={t("profiles.modals.profileForm.fields.subscriptionUrl")}
/>
)}
/>
@@ -296,12 +308,12 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
{...field}
type="number"
placeholder="60"
label={t("HTTP Request Timeout")}
label={t("profiles.modals.profileForm.fields.httpTimeout")}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
{t("seconds")}
{t("shared.units.seconds")}
</InputAdornment>
),
},
@@ -321,11 +333,13 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
{...text}
{...field}
type="number"
label={t("Update Interval")}
label={t("profiles.modals.profileForm.fields.updateInterval")}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("mins")}</InputAdornment>
<InputAdornment position="end">
{t("shared.units.minutes")}
</InputAdornment>
),
},
}}
@@ -350,7 +364,9 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
control={control}
render={({ field }) => (
<StyledBox>
<InputLabel>{t("Use System Proxy")}</InputLabel>
<InputLabel>
{t("profiles.modals.profileForm.fields.useSystemProxy")}
</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
@@ -361,7 +377,9 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
control={control}
render={({ field }) => (
<StyledBox>
<InputLabel>{t("Use Clash Proxy")}</InputLabel>
<InputLabel>
{t("profiles.modals.profileForm.fields.useClashProxy")}
</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
@@ -372,7 +390,9 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
control={control}
render={({ field }) => (
<StyledBox>
<InputLabel>{t("Accept Invalid Certs (Danger)")}</InputLabel>
<InputLabel>
{t("profiles.modals.profileForm.fields.acceptInvalidCerts")}
</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
@@ -383,7 +403,9 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
control={control}
render={({ field }) => (
<StyledBox>
<InputLabel>{t("Allow Auto Update")}</InputLabel>
<InputLabel>
{t("profiles.modals.profileForm.fields.allowAutoUpdate")}
</InputLabel>
<Switch checked={field.value} {...field} color="primary" />
</StyledBox>
)}
@@ -163,11 +163,11 @@ export const ProxiesEditorViewer = (props: Props) => {
proxies.push(proxy);
names.push(proxy.name);
}
} catch (err: any) {
} catch (err) {
console.warn(
"[ProxiesEditorViewer] parseUri failed for line:",
uri,
err?.message || err,
err,
);
// 不阻塞主流程
}
@@ -263,11 +263,11 @@ export const ProxiesEditorViewer = (props: Props) => {
const handleSave = useLockFn(async () => {
try {
await saveProfileFile(property, currData);
showNotice("success", t("Saved Successfully"));
showNotice.success("shared.feedback.notifications.saved");
onSave?.(prevData, currData);
onClose();
} catch (err: any) {
showNotice("error", err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -276,7 +276,7 @@ export const ProxiesEditorViewer = (props: Props) => {
<DialogTitle>
{
<Box display="flex" justifyContent="space-between">
{t("Edit Proxies")}
{t("profiles.modals.proxiesEditor.title")}
<Box>
<Button
variant="contained"
@@ -285,7 +285,9 @@ export const ProxiesEditorViewer = (props: Props) => {
setVisualization((prev) => !prev);
}}
>
{visualization ? t("Advanced") : t("Visualization")}
{visualization
? t("shared.editorModes.advanced")
: t("shared.editorModes.visualization")}
</Button>
</Box>
</Box>
@@ -312,7 +314,9 @@ export const ProxiesEditorViewer = (props: Props) => {
<Item>
<TextField
autoComplete="new-password"
placeholder={t("Use newlines for multiple uri")}
placeholder={t(
"profiles.modals.proxiesEditor.placeholders.multiUri",
)}
fullWidth
rows={9}
multiline
@@ -332,7 +336,7 @@ export const ProxiesEditorViewer = (props: Props) => {
});
}}
>
{t("Prepend Proxy")}
{t("profiles.modals.proxiesEditor.actions.prepend")}
</Button>
</Item>
<Item>
@@ -346,7 +350,7 @@ export const ProxiesEditorViewer = (props: Props) => {
});
}}
>
{t("Append Proxy")}
{t("profiles.modals.proxiesEditor.actions.append")}
</Button>
</Item>
</List>
@@ -497,11 +501,11 @@ export const ProxiesEditorViewer = (props: Props) => {
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Cancel")}
{t("shared.actions.cancel")}
</Button>
<Button onClick={handleSave} variant="contained">
{t("Save")}
{t("shared.actions.save")}
</Button>
</DialogActions>
</Dialog>
+70 -29
View File
@@ -240,8 +240,24 @@ const rules: {
},
];
const RULE_TYPE_LABEL_KEYS: Record<string, string> = Object.fromEntries(
rules.map((rule) => [
rule.name,
`rules.modals.editor.ruleTypes.${rule.name}`,
]),
);
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
const PROXY_POLICY_LABEL_KEYS: Record<string, string> =
builtinProxyPolicies.reduce(
(acc, policy) => {
acc[policy] = `proxy.policies.${policy}`;
return acc;
},
{} as Record<string, string>,
);
export const RulesEditorViewer = (props: Props) => {
const { groupsUid, mergeUid, profileUid, property, open, onClose, onSave } =
props;
@@ -350,8 +366,8 @@ export const RulesEditorViewer = (props: Props) => {
{ forceQuotes: true },
),
);
} catch (e: any) {
showNotice("error", e?.message || e?.toString() || "YAML dump error");
} catch (error) {
showNotice.error(error ?? "YAML dump error");
}
};
let idleId: number | undefined;
@@ -462,10 +478,12 @@ export const RulesEditorViewer = (props: Props) => {
const validateRule = () => {
if ((ruleType.required ?? true) && !ruleContent) {
throw new Error(t("Rule Condition Required"));
throw new Error(
t("rules.modals.editor.form.validation.conditionRequired"),
);
}
if (ruleType.validator && !ruleType.validator(ruleContent)) {
throw new Error(t("Invalid Rule"));
throw new Error(t("rules.modals.editor.form.validation.invalidRule"));
}
const condition = (ruleType.required ?? true) ? ruleContent : "";
@@ -477,11 +495,11 @@ export const RulesEditorViewer = (props: Props) => {
const handleSave = useLockFn(async () => {
try {
await saveProfileFile(property, currData);
showNotice("success", t("Saved Successfully"));
showNotice.success("shared.feedback.notifications.saved");
onSave?.(prevData, currData);
onClose();
} catch (err: any) {
showNotice("error", err.toString());
showNotice.error(err);
}
});
@@ -490,7 +508,7 @@ export const RulesEditorViewer = (props: Props) => {
<DialogTitle>
{
<Box display="flex" justifyContent="space-between">
{t("Edit Rules")}
{t("rules.modals.editor.title")}
<Box>
<Button
variant="contained"
@@ -499,7 +517,9 @@ export const RulesEditorViewer = (props: Props) => {
setVisualization((prev) => !prev);
}}
>
{visualization ? t("Advanced") : t("Visualization")}
{visualization
? t("shared.editorModes.advanced")
: t("shared.editorModes.visualization")}
</Button>
</Box>
</Box>
@@ -518,26 +538,37 @@ export const RulesEditorViewer = (props: Props) => {
}}
>
<Item>
<ListItemText primary={t("Rule Type")} />
<ListItemText
primary={t("rules.modals.editor.form.labels.type")}
/>
<Autocomplete
size="small"
sx={{ minWidth: "240px" }}
renderInput={(params) => <TextField {...params} />}
options={rules}
value={ruleType}
getOptionLabel={(option) => option.name}
renderOption={(props, option) => (
<li {...props} title={t(option.name)}>
{option.name}
</li>
)}
getOptionLabel={(option) =>
t(RULE_TYPE_LABEL_KEYS[option.name] ?? option.name)
}
renderOption={(props, option) => {
const label = t(
RULE_TYPE_LABEL_KEYS[option.name] ?? option.name,
);
return (
<li {...props} title={label}>
{label}
</li>
);
}}
onChange={(_, value) => value && setRuleType(value)}
/>
</Item>
<Item
sx={{ display: !(ruleType.required ?? true) ? "none" : "" }}
>
<ListItemText primary={t("Rule Content")} />
<ListItemText
primary={t("rules.modals.editor.form.labels.content")}
/>
{ruleType.name === "RULE-SET" && (
<Autocomplete
@@ -574,24 +605,34 @@ export const RulesEditorViewer = (props: Props) => {
)}
</Item>
<Item>
<ListItemText primary={t("Proxy Policy")} />
<ListItemText
primary={t("rules.modals.editor.form.labels.proxyPolicy")}
/>
<Autocomplete
size="small"
sx={{ minWidth: "240px" }}
renderInput={(params) => <TextField {...params} />}
options={proxyPolicyList}
value={proxyPolicy}
renderOption={(props, option) => (
<li {...props} title={t(option)}>
{option}
</li>
)}
getOptionLabel={(option) =>
t(PROXY_POLICY_LABEL_KEYS[option] ?? option)
}
renderOption={(props, option) => {
const label = t(PROXY_POLICY_LABEL_KEYS[option] ?? option);
return (
<li {...props} title={label}>
{label}
</li>
);
}}
onChange={(_, value) => value && setProxyPolicy(value)}
/>
</Item>
{ruleType.noResolve && (
<Item>
<ListItemText primary={t("No Resolve")} />
<ListItemText
primary={t("rules.modals.editor.form.toggles.noResolve")}
/>
<Switch
checked={noResolve}
onChange={() => setNoResolve(!noResolve)}
@@ -609,11 +650,11 @@ export const RulesEditorViewer = (props: Props) => {
if (prependSeq.includes(raw)) return;
setPrependSeq([raw, ...prependSeq]);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
}}
>
{t("Prepend Rule")}
{t("rules.modals.editor.form.actions.prependRule")}
</Button>
</Item>
<Item>
@@ -627,11 +668,11 @@ export const RulesEditorViewer = (props: Props) => {
if (appendSeq.includes(raw)) return;
setAppendSeq([...appendSeq, raw]);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
}}
>
{t("Append Rule")}
{t("rules.modals.editor.form.actions.appendRule")}
</Button>
</Item>
</List>
@@ -776,11 +817,11 @@ export const RulesEditorViewer = (props: Props) => {
<DialogActions>
<Button onClick={onClose} variant="outlined">
{t("Cancel")}
{t("shared.actions.cancel")}
</Button>
<Button onClick={handleSave} variant="contained">
{t("Save")}
{t("shared.actions.save")}
</Button>
</DialogActions>
</Dialog>
+34 -17
View File
@@ -66,12 +66,17 @@ export const ProviderButton = () => {
await refreshProxy();
await refreshProxyProviders();
showNotice("success", `${name} 更新成功`);
} catch (err: any) {
showNotice(
"error",
`${name} 更新失败: ${err?.message || err.toString()}`,
showNotice.success(
"proxies.feedback.notifications.provider.updateSuccess",
{
name,
},
);
} catch (err) {
showNotice.error("proxies.feedback.notifications.provider.updateFailed", {
name,
message: String(err),
});
} finally {
// 清除更新状态
setUpdating((prev) => ({ ...prev, [name]: false }));
@@ -84,7 +89,7 @@ export const ProviderButton = () => {
// 获取所有provider的名称
const allProviders = Object.keys(proxyProviders || {});
if (allProviders.length === 0) {
showNotice("info", "没有可更新的代理提供者");
showNotice.info("proxies.feedback.notifications.provider.none");
return;
}
@@ -114,9 +119,11 @@ export const ProviderButton = () => {
await refreshProxy();
await refreshProxyProviders();
showNotice("success", "全部代理提供者更新成功");
} catch (err: any) {
showNotice("error", `更新失败: ${err?.message || err.toString()}`);
showNotice.success("proxies.feedback.notifications.provider.allUpdated");
} catch (err) {
showNotice.error("proxies.feedback.notifications.provider.genericError", {
message: String(err),
});
} finally {
// 清除所有更新状态
setUpdating({});
@@ -138,7 +145,7 @@ export const ProviderButton = () => {
onClick={() => setOpen(true)}
sx={{ mr: 1 }}
>
{t("Proxy Provider")}
{t("proxies.page.provider.title")}
</Button>
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
@@ -148,14 +155,17 @@ export const ProviderButton = () => {
justifyContent="space-between"
alignItems="center"
>
<Typography variant="h6">{t("Proxy Provider")}</Typography>
<Typography variant="h6">
{t("proxies.page.provider.title")}
</Typography>
<Box>
<Button
variant="contained"
size="small"
onClick={updateAllProviders}
aria-label={t("proxies.page.provider.actions.updateAll")}
>
{t("Update All")}
{t("proxies.page.provider.actions.updateAll")}
</Button>
</Box>
</Box>
@@ -246,7 +256,7 @@ export const ProviderButton = () => {
color="text.secondary"
noWrap
>
<small>{t("Update At")}: </small>
<small>{t("shared.labels.updateAt")}: </small>
{time.fromNow()}
</Typography>
</Box>
@@ -264,11 +274,17 @@ export const ProviderButton = () => {
justifyContent: "space-between",
}}
>
<span title={t("Used / Total") as string}>
<span
title={t("shared.labels.usedTotal") as string}
>
{parseTraffic(upload + download)} /{" "}
{parseTraffic(total)}
</span>
<span title={t("Expire Time") as string}>
<span
title={
t("shared.labels.expireTime") as string
}
>
{parseExpire(expire)}
</span>
</Box>
@@ -313,7 +329,8 @@ export const ProviderButton = () => {
"100%": { transform: "rotate(360deg)" },
},
}}
title={t("Update Provider") as string}
title={t("proxies.page.provider.actions.update")}
aria-label={t("proxies.page.provider.actions.update")}
>
<RefreshRounded />
</IconButton>
@@ -326,7 +343,7 @@ export const ProviderButton = () => {
<DialogActions>
<Button onClick={handleClose} variant="outlined">
{t("Close")}
{t("shared.actions.close")}
</Button>
</DialogActions>
</Dialog>
+20 -17
View File
@@ -156,7 +156,11 @@ const SortableItem = ({ proxy, index, onRemove }: SortableItemProps) => {
{proxy.delay !== undefined && (
<Chip
label={proxy.delay > 0 ? `${proxy.delay}ms` : t("timeout") || "超时"}
label={
proxy.delay > 0
? `${proxy.delay}ms`
: t("shared.labels.timeout") || "超时"
}
size="small"
color={
proxy.delay > 0 && proxy.delay < 200
@@ -299,7 +303,7 @@ export const ProxyChain = ({
// onUpdateChain([]);
} catch (error) {
console.error("Failed to disconnect from proxy chain:", error);
alert(t("Failed to disconnect from proxy chain") || "断开链式代理失败");
alert(t("proxies.page.chain.disconnectFailed") || "断开链式代理失败");
} finally {
setIsConnecting(false);
}
@@ -307,9 +311,7 @@ export const ProxyChain = ({
}
if (proxyChain.length < 2) {
alert(
t("Chain proxy requires at least 2 nodes") || "链式代理至少需要2个节点",
);
alert(t("proxies.page.chain.minimumNodes") || "链式代理至少需要2个节点");
return;
}
@@ -341,7 +343,7 @@ export const ProxyChain = ({
console.log("Successfully connected to proxy chain");
} catch (error) {
console.error("Failed to connect to proxy chain:", error);
alert(t("Failed to connect to proxy chain") || "连接链式代理失败");
alert(t("proxies.page.chain.connectFailed") || "连接链式代理失败");
} finally {
setIsConnecting(false);
}
@@ -471,7 +473,7 @@ export const ProxyChain = ({
mb: 2,
}}
>
<Typography variant="h6">{t("Chain Proxy Config")}</Typography>
<Typography variant="h6">{t("proxies.page.chain.header")}</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
{proxyChain.length > 0 && (
<IconButton
@@ -486,7 +488,9 @@ export const ProxyChain = ({
backgroundColor: theme.palette.error.light + "20",
},
}}
title={t("Delete Chain Config") || "删除链式配置"}
title={
t("proxies.page.actions.clearChainConfig") || "删除链式配置"
}
>
<DeleteIcon fontSize="small" />
</IconButton>
@@ -507,16 +511,16 @@ export const ProxyChain = ({
}}
title={
proxyChain.length < 2
? t("Chain proxy requires at least 2 nodes") ||
? t("proxies.page.chain.minimumNodes") ||
"链式代理至少需要2个节点"
: undefined
}
>
{isConnecting
? t("Connecting...") || "连接中..."
? t("proxies.page.actions.connecting") || "连接中..."
: isConnected
? t("Disconnect") || "断开"
: t("Connect") || "连接"}
? t("proxies.page.actions.disconnect") || "断开"
: t("proxies.page.actions.connect") || "连接"}
</Button>
</Box>
</Box>
@@ -526,10 +530,9 @@ export const ProxyChain = ({
sx={{ mb: 2 }}
>
{proxyChain.length === 1
? t(
"Chain proxy requires at least 2 nodes. Please add one more node.",
) || "链式代理至少需要2个节点,请再添加一个节点。"
: t("Click nodes in order to add to proxy chain") ||
? t("proxies.page.chain.minimumNodesHint") ||
"链式代理至少需要2个节点,请再添加一个节点。"
: t("proxies.page.chain.instruction") ||
"按顺序点击节点添加到代理链中"}
</Alert>
@@ -544,7 +547,7 @@ export const ProxyChain = ({
color: theme.palette.text.secondary,
}}
>
<Typography>{t("No proxy chain configured")}</Typography>
<Typography>{t("proxies.page.chain.empty")}</Typography>
</Box>
) : (
<DndContext
+4 -4
View File
@@ -239,7 +239,7 @@ export const ProxyGroups = (props: Props) => {
setProxyChain((prev) => {
// 检查是否已经存在相同名称的代理,防止重复添加
if (prev.some((item) => item.name === proxy.name)) {
const warningMessage = t("Proxy node already exists in chain");
const warningMessage = t("proxies.page.chain.duplicateNode");
setDuplicateWarning({
open: true,
message: warningMessage,
@@ -372,7 +372,7 @@ export const ProxyGroups = (props: Props) => {
}, [renderList]);
if (mode === "direct") {
return <BaseEmpty text={t("clash_mode_direct")} />;
return <BaseEmpty textKey="proxies.page.messages.directMode" />;
}
if (isChainMode) {
@@ -403,7 +403,7 @@ export const ProxyGroups = (props: Props) => {
variant="h6"
sx={{ fontWeight: 600, fontSize: "16px" }}
>
{t("Proxy Rules")}
{t("proxies.page.rules.title")}
</Typography>
{currentGroup && (
<Box
@@ -442,7 +442,7 @@ export const ProxyGroups = (props: Props) => {
variant="body2"
sx={{ mr: 0.5, fontSize: "12px" }}
>
{t("Select Rules")}
{t("proxies.page.rules.select")}
</Typography>
<ExpandMoreRounded fontSize="small" />
</IconButton>
+16 -10
View File
@@ -67,7 +67,7 @@ export const ProxyHead = ({
<IconButton
size="small"
color="inherit"
title={t("locate")}
title={t("proxies.page.tooltips.locate")}
onClick={onLocation}
>
<MyLocationRounded />
@@ -76,7 +76,7 @@ export const ProxyHead = ({
<IconButton
size="small"
color="inherit"
title={t("Delay check")}
title={t("proxies.page.tooltips.delayCheck")}
onClick={() => {
console.log(`[ProxyHead] 点击延迟测试按钮,组: ${groupName}`);
// Remind the user that it is custom test url
@@ -94,9 +94,11 @@ export const ProxyHead = ({
size="small"
color="inherit"
title={
[t("Sort by default"), t("Sort by delay"), t("Sort by name")][
sortType
]
[
t("proxies.page.tooltips.sortDefault"),
t("proxies.page.tooltips.sortDelay"),
t("proxies.page.tooltips.sortName"),
][sortType]
}
onClick={() =>
onHeadState({ sortType: ((sortType + 1) % 3) as ProxySortType })
@@ -110,7 +112,7 @@ export const ProxyHead = ({
<IconButton
size="small"
color="inherit"
title={t("Delay check URL")}
title={t("proxies.page.tooltips.delayCheckUrl")}
onClick={() =>
onHeadState({ textState: textState === "url" ? null : "url" })
}
@@ -125,7 +127,11 @@ export const ProxyHead = ({
<IconButton
size="small"
color="inherit"
title={showType ? t("Proxy basic") : t("Proxy detail")}
title={
showType
? t("proxies.page.tooltips.showBasic")
: t("proxies.page.tooltips.showDetail")
}
onClick={() => onHeadState({ showType: !showType })}
>
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
@@ -134,7 +140,7 @@ export const ProxyHead = ({
<IconButton
size="small"
color="inherit"
title={t("Filter")}
title={t("proxies.page.tooltips.filter")}
onClick={() =>
onHeadState({ textState: textState === "filter" ? null : "filter" })
}
@@ -154,7 +160,7 @@ export const ProxyHead = ({
value={filterText}
size="small"
variant="outlined"
placeholder={t("Filter conditions")}
placeholder={t("shared.placeholders.filter")}
onChange={(e) => onHeadState({ filterText: e.target.value })}
sx={{ ml: 0.5, flex: "1 1 auto", input: { py: 0.65, px: 1 } }}
/>
@@ -169,7 +175,7 @@ export const ProxyHead = ({
value={testUrl}
size="small"
variant="outlined"
placeholder={t("Delay check URL")}
placeholder={t("proxies.page.placeholders.delayCheckUrl")}
onChange={(e) => onHeadState({ testUrl: e.target.value })}
sx={{ ml: 0.5, flex: "1 1 auto", input: { py: 0.65, px: 1 } }}
/>
+3 -1
View File
@@ -271,7 +271,9 @@ export const ProxyItemMini = (props: Props) => {
<span
className={proxy.name === group.now ? "the-pin" : "the-unpin"}
title={
group.type === "URLTest" ? t("Delay check to cancel fixed") : ""
group.type === "URLTest"
? t("proxies.page.labels.delayCheckReset")
: ""
}
>
📌
+1 -1
View File
@@ -160,7 +160,7 @@ export const ProxyRender = (props: RenderProps) => {
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={t("Proxy Count")} arrow>
<Tooltip title={t("proxies.page.labels.proxyCount")} arrow>
<Chip
size="small"
label={`${group.all.length}`}
+25 -15
View File
@@ -58,12 +58,17 @@ export const ProviderButton = () => {
await refreshRules();
await refreshRuleProviders();
showNotice("success", `${name} 更新成功`);
} catch (err: any) {
showNotice(
"error",
`${name} 更新失败: ${err?.message || err.toString()}`,
showNotice.success(
"rules.feedback.notifications.provider.updateSuccess",
{
name,
},
);
} catch (err) {
showNotice.error("rules.feedback.notifications.provider.updateFailed", {
name,
message: String(err),
});
} finally {
// 清除更新状态
setUpdating((prev) => ({ ...prev, [name]: false }));
@@ -76,7 +81,7 @@ export const ProviderButton = () => {
// 获取所有provider的名称
const allProviders = Object.keys(ruleProviders || {});
if (allProviders.length === 0) {
showNotice("info", "没有可更新的规则提供者");
showNotice.info("rules.feedback.notifications.provider.none");
return;
}
@@ -106,9 +111,11 @@ export const ProviderButton = () => {
await refreshRules();
await refreshRuleProviders();
showNotice("success", "全部规则提供者更新成功");
} catch (err: any) {
showNotice("error", `更新失败: ${err?.message || err.toString()}`);
showNotice.success("rules.feedback.notifications.provider.allUpdated");
} catch (err) {
showNotice.error("rules.feedback.notifications.provider.genericError", {
message: String(err),
});
} finally {
// 清除所有更新状态
setUpdating({});
@@ -129,7 +136,7 @@ export const ProviderButton = () => {
startIcon={<StorageOutlined />}
onClick={() => setOpen(true)}
>
{t("Rule Provider")}
{t("rules.page.provider.trigger")}
</Button>
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
@@ -139,13 +146,15 @@ export const ProviderButton = () => {
justifyContent="space-between"
alignItems="center"
>
<Typography variant="h6">{t("Rule Providers")}</Typography>
<Typography variant="h6">
{t("rules.page.provider.dialogTitle")}
</Typography>
<Button
variant="contained"
size="small"
onClick={updateAllProviders}
>
{t("Update All")}
{t("rules.page.provider.actions.updateAll")}
</Button>
</Box>
</DialogTitle>
@@ -216,7 +225,7 @@ export const ProviderButton = () => {
color="text.secondary"
noWrap
>
<small>{t("Update At")}: </small>
<small>{t("shared.labels.updateAt")}: </small>
{time.fromNow()}
</Typography>
</Box>
@@ -246,6 +255,7 @@ export const ProviderButton = () => {
color="primary"
onClick={() => updateProvider(key)}
disabled={isUpdating}
aria-label={t("rules.page.provider.actions.update")}
sx={{
animation: isUpdating
? "spin 1s linear infinite"
@@ -255,7 +265,7 @@ export const ProviderButton = () => {
"100%": { transform: "rotate(360deg)" },
},
}}
title={t("Update Provider") as string}
title={t("rules.page.provider.actions.update")}
>
<RefreshRounded />
</IconButton>
@@ -268,7 +278,7 @@ export const ProviderButton = () => {
<DialogActions>
<Button onClick={handleClose} variant="outlined">
{t("Close")}
{t("shared.actions.close")}
</Button>
</DialogActions>
</Dialog>
@@ -84,22 +84,22 @@ export const BackupConfigViewer = memo(
if (!url) {
urlRef.current?.focus();
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("WebDAV URL Required"));
showNotice.error("settings.modals.backup.messages.webdavUrlRequired");
throw new Error(t("settings.modals.backup.messages.webdavUrlRequired"));
} else if (!isValidUrl(url)) {
urlRef.current?.focus();
showNotice("error", t("Invalid WebDAV URL"));
throw new Error(t("Invalid WebDAV URL"));
showNotice.error("settings.modals.backup.messages.invalidWebdavUrl");
throw new Error(t("settings.modals.backup.messages.invalidWebdavUrl"));
}
if (!username) {
usernameRef.current?.focus();
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("Username Required"));
showNotice.error("settings.modals.backup.messages.usernameRequired");
throw new Error(t("settings.modals.backup.messages.usernameRequired"));
}
if (!password) {
passwordRef.current?.focus();
showNotice("error", t("WebDAV URL Required"));
throw new Error(t("Password Required"));
showNotice.error("settings.modals.backup.messages.passwordRequired");
throw new Error(t("settings.modals.backup.messages.passwordRequired"));
}
};
@@ -112,11 +112,17 @@ export const BackupConfigViewer = memo(
data.username.trim(),
data.password,
).then(() => {
showNotice("success", t("WebDAV Config Saved"));
showNotice.success(
"settings.modals.backup.messages.webdavConfigSaved",
);
onSaveSuccess();
});
} catch (error) {
showNotice("error", t("WebDAV Config Save Failed", { error }), 3000);
showNotice.error(
"settings.modals.backup.messages.webdavConfigSaveFailed",
{ error },
3000,
);
} finally {
setLoading(false);
}
@@ -127,11 +133,13 @@ export const BackupConfigViewer = memo(
try {
setLoading(true);
await createWebdavBackup().then(async () => {
showNotice("success", t("Backup Created"));
showNotice.success("settings.modals.backup.messages.backupCreated");
await onBackupSuccess();
});
} catch (error) {
showNotice("error", t("Backup Failed", { error }));
showNotice.error("settings.modals.backup.messages.backupFailed", {
error,
});
} finally {
setLoading(false);
}
@@ -145,7 +153,7 @@ export const BackupConfigViewer = memo(
<Grid size={{ xs: 12 }}>
<TextField
fullWidth
label={t("WebDAV Server URL")}
label={t("settings.modals.backup.fields.webdavUrl")}
variant="outlined"
size="small"
{...register("url")}
@@ -157,7 +165,7 @@ export const BackupConfigViewer = memo(
</Grid>
<Grid size={{ xs: 6 }}>
<TextField
label={t("Username")}
label={t("settings.modals.backup.fields.username")}
variant="outlined"
size="small"
{...register("username")}
@@ -169,7 +177,7 @@ export const BackupConfigViewer = memo(
</Grid>
<Grid size={{ xs: 6 }}>
<TextField
label={t("Password")}
label={t("shared.labels.password")}
type={showPassword ? "text" : "password"}
variant="outlined"
size="small"
@@ -214,7 +222,7 @@ export const BackupConfigViewer = memo(
type="button"
onClick={handleSubmit(save)}
>
{t("Save")}
{t("shared.actions.save")}
</Button>
) : (
<>
@@ -225,7 +233,7 @@ export const BackupConfigViewer = memo(
type="button"
size="large"
>
{t("Backup")}
{t("settings.modals.backup.actions.backup")}
</Button>
<Button
variant="outlined"
@@ -233,7 +241,7 @@ export const BackupConfigViewer = memo(
type="button"
size="large"
>
{t("Refresh")}
{t("shared.actions.refresh")}
</Button>
</>
)}
@@ -75,7 +75,7 @@ export const BackupTableViewer = memo(
const handleRestore = useLockFn(async (filename: string) => {
await onRestore(filename).then(() => {
showNotice("success", t("Restore Success, App will restart in 1s"));
showNotice.success("settings.modals.backup.messages.restoreSuccess");
});
await restartApp();
});
@@ -92,10 +92,14 @@ export const BackupTableViewer = memo(
return;
}
await onExport(filename, savePath);
showNotice("success", t("Local Backup Exported"));
showNotice.success(
"settings.modals.backup.messages.localBackupExported",
);
} catch (error) {
console.error(error);
showNotice("error", t("Local Backup Export Failed"));
showNotice.error(
"settings.modals.backup.messages.localBackupExportFailed",
);
}
});
@@ -104,9 +108,15 @@ export const BackupTableViewer = memo(
<Table>
<TableHead>
<TableRow>
<TableCell>{t("Filename")}</TableCell>
<TableCell>{t("Backup Time")}</TableCell>
<TableCell align="right">{t("Actions")}</TableCell>
<TableCell>
{t("settings.modals.backup.table.filename")}
</TableCell>
<TableCell>
{t("settings.modals.backup.table.backupTime")}
</TableCell>
<TableCell align="right">
{t("settings.modals.backup.table.actions")}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
@@ -140,9 +150,13 @@ export const BackupTableViewer = memo(
<>
<IconButton
color="primary"
aria-label={t("Export")}
aria-label={t(
"settings.modals.backup.actions.export",
)}
size="small"
title={t("Export Backup")}
title={t(
"settings.modals.backup.actions.exportBackup",
)}
onClick={async (e: React.MouseEvent) => {
e.preventDefault();
await handleExport(file.filename);
@@ -159,13 +173,17 @@ export const BackupTableViewer = memo(
)}
<IconButton
color="secondary"
aria-label={t("Delete")}
aria-label={t("shared.actions.delete")}
size="small"
title={t("Delete Backup")}
title={t(
"settings.modals.backup.actions.deleteBackup",
)}
onClick={async (e: React.MouseEvent) => {
e.preventDefault();
const confirmed = await confirmAsync(
t("Confirm to delete this backup file?"),
t(
"settings.modals.backup.messages.confirmDelete",
),
);
if (confirmed) {
await handleDelete(file.filename);
@@ -181,14 +199,20 @@ export const BackupTableViewer = memo(
/>
<IconButton
color="primary"
aria-label={t("Restore")}
aria-label={t(
"settings.modals.backup.actions.restore",
)}
size="small"
title={t("Restore Backup")}
title={t(
"settings.modals.backup.actions.restoreBackup",
)}
disabled={!file.allow_apply}
onClick={async (e: React.MouseEvent) => {
e.preventDefault();
const confirmed = await confirmAsync(
t("Confirm to restore this backup file?"),
t(
"settings.modals.backup.messages.confirmRestore",
),
);
if (confirmed) {
await handleRestore(file.filename);
@@ -219,7 +243,7 @@ export const BackupTableViewer = memo(
color="textSecondary"
align="center"
>
{t("No Backups")}
{t("settings.modals.backup.table.noBackups")}
</Typography>
</Box>
</TableCell>
@@ -234,7 +258,7 @@ export const BackupTableViewer = memo(
rowsPerPage={DEFAULT_ROWS_PER_PAGE}
page={page}
onPageChange={onPageChange}
labelRowsPerPage={t("Rows per page")}
labelRowsPerPage={t("settings.modals.backup.table.rowsPerPage")}
/>
</TableContainer>
);
@@ -127,7 +127,6 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
setBackupFiles([]);
setTotal(0);
console.error(error);
// Notice.error(t("Failed to fetch backup files"));
} finally {
setIsLoading(false);
}
@@ -247,7 +246,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
return (
<BaseDialog
open={open}
title={t("Backup Setting")}
title={t("settings.modals.backup.title")}
contentSx={{
minWidth: { xs: 320, sm: 620 },
maxWidth: "unset",
@@ -278,11 +277,14 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Tabs
value={source}
onChange={handleChangeSource}
aria-label={t("Select Backup Target")}
aria-label={t("settings.modals.backup.actions.selectTarget")}
sx={{ mb: 2 }}
>
<Tab value="local" label={t("Local Backup")} />
<Tab value="webdav" label={t("WebDAV Backup")} />
<Tab value="local" label={t("settings.modals.backup.tabs.local")} />
<Tab
value="webdav"
label={t("settings.modals.backup.tabs.webdav")}
/>
</Tabs>
{source === "local" ? (
<LocalBackupActions
@@ -342,7 +344,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
backgroundColor: (theme) => theme.palette.background.paper,
}}
>
{t("Close")}
{t("shared.actions.close")}
</Button>
</Box>,
dialogPaper,
@@ -24,8 +24,16 @@ import { changeClashCore, restartCore } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
const VALID_CORE = [
{ name: "Mihomo", core: "verge-mihomo", chip: "Release Version" },
{ name: "Mihomo Alpha", core: "verge-mihomo-alpha", chip: "Alpha Version" },
{
name: "Mihomo",
core: "verge-mihomo",
chipKey: "settings.modals.clashCore.variants.release",
},
{
name: "Mihomo Alpha",
core: "verge-mihomo-alpha",
chipKey: "settings.modals.clashCore.variants.alpha",
},
];
export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
@@ -54,7 +62,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
const errorMsg = await changeClashCore(core);
if (errorMsg) {
showNotice("error", errorMsg);
showNotice.error(errorMsg);
setChangingCore(null);
return;
}
@@ -65,9 +73,9 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
mutate("getVersion");
setChangingCore(null);
}, 500);
} catch (err: any) {
} catch (err) {
setChangingCore(null);
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
});
@@ -75,11 +83,13 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
try {
setRestarting(true);
await restartCore();
showNotice("success", t(`Clash Core Restarted`));
showNotice.success(
t("settings.feedback.notifications.clash.restartSuccess"),
);
setRestarting(false);
} catch (err: any) {
} catch (err) {
setRestarting(false);
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
});
@@ -88,14 +98,16 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
setUpgrading(true);
await upgradeCore();
setUpgrading(false);
showNotice("success", t(`Core Version Updated`));
showNotice.success(
t("settings.feedback.notifications.clash.versionUpdated"),
);
} catch (err: any) {
setUpgrading(false);
const errMsg = err.response?.data?.message || err.toString();
const errMsg = err?.response?.data?.message ?? String(err);
const showMsg = errMsg.includes("already using latest version")
? "Already Using Latest Core Version"
? t("settings.feedback.notifications.clash.alreadyLatestVersion")
: errMsg;
showNotice("error", t(showMsg));
showNotice.info(showMsg);
}
});
@@ -104,7 +116,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between">
{t("Clash Core")}
{t("settings.sections.clash.form.fields.clashCore")}
<Box>
<LoadingButton
variant="contained"
@@ -116,7 +128,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
sx={{ marginRight: "8px" }}
onClick={onUpgrade}
>
{t("Upgrade")}
{t("shared.actions.upgrade")}
</LoadingButton>
<LoadingButton
variant="contained"
@@ -127,7 +139,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
disabled={upgrading}
onClick={onRestart}
>
{t("Restart")}
{t("shared.actions.restart")}
</LoadingButton>
</Box>
</Box>
@@ -141,7 +153,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
marginTop: "-8px",
}}
disableOk
cancelBtn={t("Close")}
cancelBtn={t("shared.actions.close")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
@@ -157,7 +169,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
{changingCore === each.core ? (
<CircularProgress size={20} sx={{ mr: 1 }} />
) : (
<Chip label={t(`${each.chip}`)} size="small" />
<Chip label={t(each.chipKey)} size="small" />
)}
</ListItemButton>
))}
@@ -69,11 +69,13 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Port settings saved"));
showNotice.success("settings.modals.clashPort.messages.saved");
},
onError: (e) => {
showNotice("error", e.message || t("Failed to save port settings"));
// showNotice("error", t("Failed to save port settings"));
onError: (error) => {
showNotice.error(
"settings.modals.clashPort.messages.saveFailed",
error,
);
},
},
);
@@ -150,7 +152,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
return (
<BaseDialog
open={open}
title={t("Port Config")}
title={t("settings.modals.clashPort.title")}
contentSx={{
width: 400,
}}
@@ -158,13 +160,13 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
loading ? (
<Stack direction="row" alignItems="center" spacing={1}>
<CircularProgress size={20} />
{t("Saving...")}
{t("shared.statuses.saving")}
</Stack>
) : (
t("Save")
t("shared.actions.save")
)
}
cancelBtn={t("Cancel")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
@@ -172,7 +174,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<List sx={{ width: "100%" }}>
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Mixed Port")}
primary={t("settings.modals.clashPort.fields.mixed")}
slotProps={{ primary: { sx: { fontSize: 12 } } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
@@ -188,7 +190,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<IconButton
size="small"
onClick={() => setMixedPort(generateRandomPort())}
title={t("Random Port")}
title={t("settings.modals.clashPort.actions.random")}
sx={{ mr: 0.5 }}
>
<Shuffle fontSize="small" />
@@ -204,7 +206,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Socks Port")}
primary={t("settings.modals.clashPort.fields.socks")}
slotProps={{ primary: { sx: { fontSize: 12 } } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
@@ -221,7 +223,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<IconButton
size="small"
onClick={() => setSocksPort(generateRandomPort())}
title={t("Random Port")}
title={t("settings.modals.clashPort.actions.random")}
disabled={!socksEnabled}
sx={{ mr: 0.5 }}
>
@@ -238,7 +240,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Http Port")}
primary={t("settings.modals.clashPort.fields.http")}
slotProps={{ primary: { sx: { fontSize: 12 } } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
@@ -255,7 +257,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<IconButton
size="small"
onClick={() => setHttpPort(generateRandomPort())}
title={t("Random Port")}
title={t("settings.modals.clashPort.actions.random")}
disabled={!httpEnabled}
sx={{ mr: 0.5 }}
>
@@ -273,7 +275,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
{OS !== "windows" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Redir Port")}
primary={t("settings.modals.clashPort.fields.redir")}
slotProps={{ primary: { sx: { fontSize: 12 } } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
@@ -290,7 +292,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<IconButton
size="small"
onClick={() => setRedirPort(generateRandomPort())}
title={t("Random Port")}
title={t("settings.modals.clashPort.actions.random")}
disabled={!redirEnabled}
sx={{ mr: 0.5 }}
>
@@ -309,7 +311,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
{OS === "linux" && (
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
<ListItemText
primary={t("Tproxy Port")}
primary={t("settings.modals.clashPort.fields.tproxy")}
slotProps={{ primary: { sx: { fontSize: 12 } } }}
/>
<div style={{ display: "flex", alignItems: "center" }}>
@@ -326,7 +328,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<IconButton
size="small"
onClick={() => setTproxyPort(generateRandomPort())}
title={t("Random Port")}
title={t("settings.modals.clashPort.actions.random")}
disabled={!tproxyEnabled}
sx={{ mr: 0.5 }}
>
@@ -27,8 +27,8 @@ export const ConfigViewer = forwardRef<DialogRef>((_, ref) => {
open={true}
title={
<Box display="flex" alignItems="center" gap={2}>
{t("Runtime Config")}
<Chip label={t("ReadOnly")} size="small" />
{t("settings.components.verge.advanced.fields.runtimeConfig")}
<Chip label={t("shared.labels.readOnly")} size="small" />
</Box>
}
initialData={Promise.resolve(runtimeConfig)}
@@ -56,12 +56,16 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
// 如果启用了外部控制器,则保存控制器地址和密钥
if (enableController) {
if (!controller.trim()) {
showNotice("error", t("Controller address cannot be empty"));
showNotice.error(
"settings.sections.externalController.messages.addressRequired",
);
return;
}
if (!secret.trim()) {
showNotice("error", t("Secret cannot be empty"));
showNotice.error(
"settings.sections.externalController.messages.secretRequired",
);
return;
}
@@ -71,12 +75,12 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
await patchInfo({ "external-controller": "" });
}
showNotice("success", t("Configuration saved successfully"));
showNotice.success("shared.feedback.notifications.common.saveSuccess");
setOpen(false);
} catch (err: any) {
showNotice(
"error",
err.message || t("Failed to save configuration"),
} catch (err) {
showNotice.error(
"shared.feedback.notifications.common.saveFailed",
err,
4000,
);
} finally {
@@ -93,7 +97,9 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
setTimeout(() => setCopySuccess(null));
} catch (err) {
console.warn("[ControllerViewer] copy to clipboard failed:", err);
showNotice("error", t("Failed to copy"));
showNotice.error(
"settings.sections.externalController.messages.copyFailed",
);
}
},
);
@@ -101,19 +107,19 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
return (
<BaseDialog
open={open}
title={t("External Controller")}
title={t("settings.sections.externalController.title")}
contentSx={{ width: 400 }}
okBtn={
isSaving ? (
<Box display="flex" alignItems="center" gap={1}>
<CircularProgress size={16} color="inherit" />
{t("Saving...")}
{t("shared.statuses.saving")}
</Box>
) : (
t("Save")
t("shared.actions.save")
)
}
cancelBtn={t("Cancel")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
@@ -126,7 +132,9 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
justifyContent: "space-between",
}}
>
<ListItemText primary={t("Enable External Controller")} />
<ListItemText
primary={t("settings.sections.externalController.fields.enable")}
/>
<Switch
edge="end"
checked={enableController}
@@ -142,7 +150,9 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
justifyContent: "space-between",
}}
>
<ListItemText primary={t("External Controller")} />
<ListItemText
primary={t("settings.sections.externalController.fields.address")}
/>
<Box display="flex" alignItems="center" gap={1}>
<TextField
size="small"
@@ -152,11 +162,15 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
pointerEvents: enableController ? "auto" : "none",
}}
value={controller}
placeholder="Required"
placeholder={t(
"settings.sections.externalController.placeholders.address",
)}
onChange={(e) => setController(e.target.value)}
disabled={isSaving || !enableController}
/>
<Tooltip title={t("Copy to clipboard")}>
<Tooltip
title={t("settings.sections.externalController.tooltips.copy")}
>
<IconButton
size="small"
onClick={() => handleCopyToClipboard(controller, "controller")}
@@ -176,7 +190,9 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
justifyContent: "space-between",
}}
>
<ListItemText primary={t("Core Secret")} />
<ListItemText
primary={t("settings.sections.externalController.fields.secret")}
/>
<Box display="flex" alignItems="center" gap={1}>
<TextField
size="small"
@@ -186,11 +202,15 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
pointerEvents: enableController ? "auto" : "none",
}}
value={secret}
placeholder={t("Recommended")}
placeholder={t(
"settings.sections.externalController.placeholders.secret",
)}
onChange={(e) => setSecret(e.target.value)}
disabled={isSaving || !enableController}
/>
<Tooltip title={t("Copy to clipboard")}>
<Tooltip
title={t("settings.sections.externalController.tooltips.copy")}
>
<IconButton
size="small"
onClick={() => handleCopyToClipboard(secret, "secret")}
@@ -211,8 +231,10 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
>
<Alert severity="success">
{copySuccess === "controller"
? t("Controller address copied to clipboard")
: t("Secret copied to clipboard")}
? t(
"settings.sections.externalController.messages.controllerCopied",
)
: t("settings.sections.externalController.messages.secretCopied")}
</Alert>
</Snackbar>
</BaseDialog>
+86 -58
View File
@@ -424,9 +424,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
skipYamlSyncRef.current = true;
updateValuesFromConfig(parsedYaml);
} catch {
showNotice("error", t("Invalid YAML format"));
showNotice.error("settings.modals.dns.errors.invalidYaml");
}
}, [yamlContent, t, updateValuesFromConfig]);
}, [yamlContent, updateValuesFromConfig]);
useEffect(() => {
if (skipYamlSyncRef.current) {
@@ -509,7 +509,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
// 使用YAML编辑器的值
const parsedConfig = yaml.load(yamlContent);
if (typeof parsedConfig !== "object" || parsedConfig === null) {
throw new Error(t("Invalid configuration"));
throw new Error(t("settings.modals.dns.errors.invalid"));
}
config = parsedConfig as Record<string, any>;
}
@@ -547,9 +547,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
}
}
showNotice(
"error",
t("DNS configuration error") + ": " + cleanErrorMsg,
showNotice.error(
"settings.modals.dns.messages.configError",
cleanErrorMsg,
);
return;
}
@@ -561,9 +561,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
}
setOpen(false);
showNotice("success", t("DNS settings saved"));
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.success("settings.modals.dns.messages.saved");
} catch (err) {
showNotice.error(err);
}
});
@@ -613,7 +613,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between" alignItems="center">
{t("DNS Overwrite")}
{t("settings.modals.dns.dialog.title")}
<Box display="flex" alignItems="center" gap={1}>
<Button
variant="outlined"
@@ -622,7 +622,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
startIcon={<RestartAltRounded />}
onClick={resetToDefaults}
>
{t("Reset to Default")}
{t("shared.actions.resetToDefault")}
</Button>
<Button
variant="contained"
@@ -631,7 +631,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
setVisualization((prev) => !prev);
}}
>
{visualization ? t("Advanced") : t("Visualization")}
{visualization
? t("shared.editorModes.advanced")
: t("shared.editorModes.visualization")}
</Button>
</Box>
</Box>
@@ -643,8 +645,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
? {}
: { padding: "0 24px", display: "flex", flexDirection: "column" }),
}}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
@@ -655,7 +657,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
color="warning.main"
sx={{ mb: 2, mt: 0, fontStyle: "italic" }}
>
{t("DNS Settings Warning")}
{t("settings.modals.dns.dialog.warning")}
</Typography>
{visualization ? (
@@ -664,11 +666,11 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="subtitle1"
sx={{ mt: 1, mb: 1, fontWeight: "bold" }}
>
{t("DNS Settings")}
{t("settings.modals.dns.sections.general")}
</Typography>
<Item>
<ListItemText primary={t("Enable DNS")} />
<ListItemText primary={t("settings.modals.dns.fields.enable")} />
<Switch
edge="end"
checked={values.enable}
@@ -677,7 +679,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Item>
<Item>
<ListItemText primary={t("DNS Listen")} />
<ListItemText primary={t("settings.modals.dns.fields.listen")} />
<TextField
size="small"
autoComplete="off"
@@ -689,7 +691,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Item>
<Item>
<ListItemText primary={t("Enhanced Mode")} />
<ListItemText
primary={t("settings.modals.dns.fields.enhancedMode")}
/>
<FormControl size="small" sx={{ width: 150 }}>
<Select
value={values.enhancedMode}
@@ -702,7 +706,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Item>
<Item>
<ListItemText primary={t("Fake IP Range")} />
<ListItemText
primary={t("settings.modals.dns.fields.fakeIpRange")}
/>
<TextField
size="small"
autoComplete="off"
@@ -714,7 +720,9 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Item>
<Item>
<ListItemText primary={t("Fake IP Filter Mode")} />
<ListItemText
primary={t("settings.modals.dns.fields.fakeIpFilterMode")}
/>
<FormControl size="small" sx={{ width: 150 }}>
<Select
value={values.fakeIpFilterMode}
@@ -728,8 +736,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("IPv6")}
secondary={t("Enable IPv6 DNS resolution")}
primary={t("settings.modals.dns.fields.ipv6.label")}
secondary={t("settings.modals.dns.fields.ipv6.description")}
/>
<Switch
edge="end"
@@ -740,8 +748,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("Prefer H3")}
secondary={t("DNS DOH使用HTTP/3")}
primary={t("settings.modals.dns.fields.preferH3.label")}
secondary={t("settings.modals.dns.fields.preferH3.description")}
/>
<Switch
edge="end"
@@ -752,8 +760,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("Respect Rules")}
secondary={t("DNS connections follow routing rules")}
primary={t("settings.modals.dns.fields.respectRules.label")}
secondary={t(
"settings.modals.dns.fields.respectRules.description",
)}
/>
<Switch
edge="end"
@@ -764,8 +774,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("Use Hosts")}
secondary={t("Enable to resolve hosts through hosts file")}
primary={t("settings.modals.dns.fields.useHosts.label")}
secondary={t("settings.modals.dns.fields.useHosts.description")}
/>
<Switch
edge="end"
@@ -776,8 +786,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("Use System Hosts")}
secondary={t("Enable to resolve hosts through system hosts file")}
primary={t("settings.modals.dns.fields.useSystemHosts.label")}
secondary={t(
"settings.modals.dns.fields.useSystemHosts.description",
)}
/>
<Switch
edge="end"
@@ -788,8 +800,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item>
<ListItemText
primary={t("Direct Nameserver Follow Policy")}
secondary={t("Whether to follow nameserver policy")}
primary={t("settings.modals.dns.fields.directPolicy.label")}
secondary={t(
"settings.modals.dns.fields.directPolicy.description",
)}
/>
<Switch
edge="end"
@@ -800,8 +814,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Default Nameserver")}
secondary={t("Default DNS servers used to resolve DNS servers")}
primary={t("settings.modals.dns.fields.defaultNameserver.label")}
secondary={t(
"settings.modals.dns.fields.defaultNameserver.description",
)}
/>
<TextField
fullWidth
@@ -817,8 +833,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Nameserver")}
secondary={t("List of DNS servers")}
primary={t("settings.modals.dns.fields.nameserver.label")}
secondary={t("settings.modals.dns.fields.nameserver.description")}
/>
<TextField
fullWidth
@@ -834,8 +850,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Fallback")}
secondary={t("List of fallback DNS servers")}
primary={t("settings.modals.dns.fields.fallback.label")}
secondary={t("settings.modals.dns.fields.fallback.description")}
/>
<TextField
fullWidth
@@ -851,8 +867,8 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Proxy Server Nameserver")}
secondary={t("Proxy Node Nameserver")}
primary={t("settings.modals.dns.fields.proxy.label")}
secondary={t("settings.modals.dns.fields.proxy.description")}
/>
<TextField
fullWidth
@@ -868,8 +884,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Direct Nameserver")}
secondary={t("Direct outbound Nameserver")}
primary={t("settings.modals.dns.fields.directNameserver.label")}
secondary={t(
"settings.modals.dns.fields.directNameserver.description",
)}
/>
<TextField
fullWidth
@@ -885,8 +903,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Fake IP Filter")}
secondary={t("Domains that skip fake IP resolution")}
primary={t("settings.modals.dns.fields.fakeIpFilter.label")}
secondary={t(
"settings.modals.dns.fields.fakeIpFilter.description",
)}
/>
<TextField
fullWidth
@@ -902,8 +922,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Nameserver Policy")}
secondary={t("Domain-specific DNS server")}
primary={t("settings.modals.dns.fields.nameserverPolicy.label")}
secondary={t(
"settings.modals.dns.fields.nameserverPolicy.description",
)}
/>
<TextField
fullWidth
@@ -921,13 +943,15 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="subtitle2"
sx={{ mt: 2, mb: 1, fontWeight: "bold" }}
>
{t("Fallback Filter Settings")}
{t("settings.modals.dns.sections.fallbackFilter")}
</Typography>
<Item>
<ListItemText
primary={t("GeoIP Filtering")}
secondary={t("Enable GeoIP filtering for fallback")}
primary={t("settings.modals.dns.fields.geoipFiltering.label")}
secondary={t(
"settings.modals.dns.fields.geoipFiltering.description",
)}
/>
<Switch
edge="end"
@@ -937,7 +961,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Item>
<Item>
<ListItemText primary={t("GeoIP Code")} />
<ListItemText primary={t("settings.modals.dns.fields.geoipCode")} />
<TextField
size="small"
autoComplete="off"
@@ -950,8 +974,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Fallback IP CIDR")}
secondary={t("IP CIDRs not using fallback servers")}
primary={t("settings.modals.dns.fields.fallbackIpCidr.label")}
secondary={t(
"settings.modals.dns.fields.fallbackIpCidr.description",
)}
/>
<TextField
fullWidth
@@ -967,8 +993,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Fallback Domain")}
secondary={t("Domains using fallback servers")}
primary={t("settings.modals.dns.fields.fallbackDomain.label")}
secondary={t(
"settings.modals.dns.fields.fallbackDomain.description",
)}
/>
<TextField
fullWidth
@@ -987,13 +1015,13 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="subtitle1"
sx={{ mt: 3, mb: 0, fontWeight: "bold" }}
>
{t("Hosts Settings")}
{t("settings.modals.dns.sections.hosts")}
</Typography>
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Hosts")}
secondary={t("Custom domain to IP or domain mapping")}
primary={t("settings.modals.dns.fields.hosts.label")}
secondary={t("settings.modals.dns.fields.hosts.description")}
/>
<TextField
fullWidth
@@ -140,10 +140,12 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
manual: true,
onSuccess: () => {
setOpen(false);
showNotice("success", t("Configuration saved successfully"));
showNotice.success(
"shared.feedback.notifications.common.saveSuccess",
);
},
onError: () => {
showNotice("error", t("Failed to save configuration"));
showNotice.error("shared.feedback.notifications.common.saveFailed");
},
},
);
@@ -181,10 +183,10 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
return (
<BaseDialog
open={open}
title={t("External Cors Configuration")}
title={t("settings.sections.externalCors.title")}
contentSx={{ width: 500 }}
okBtn={loading ? t("Saving...") : t("Save")}
cancelBtn={t("Cancel")}
okBtn={loading ? t("shared.statuses.saving") : t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={handleSave}
@@ -198,7 +200,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
width="100%"
>
<span style={{ fontWeight: "normal" }}>
{t("Allow private network access")}
{t("settings.sections.externalCors.fields.allowPrivateNetwork")}
</span>
<Switch
edge="end"
@@ -218,7 +220,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
<ListItem sx={{ padding: "8px 0" }}>
<div style={{ width: "100%" }}>
<div style={{ marginBottom: 8, fontWeight: "bold" }}>
{t("Allowed Origins")}
{t("settings.sections.externalCors.fields.allowedOrigins")}
</div>
{originEntries.map(({ origin, index, key }) => (
<div
@@ -235,7 +237,9 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
sx={{ fontSize: 14, marginRight: 2 }}
value={origin}
onChange={(e) => handleUpdateOrigin(index, e.target.value)}
placeholder={t("Please enter a valid url")}
placeholder={t(
"settings.sections.externalCors.placeholders.origin",
)}
inputProps={{ style: { fontSize: 14 } }}
/>
<Button
@@ -256,7 +260,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
onClick={handleAddOrigin}
sx={addButtonStyle}
>
{t("Add")}
{t("settings.sections.externalCors.actions.add")}
</Button>
<div
@@ -270,7 +274,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
<div
style={{ color: "#666", fontSize: 12, fontStyle: "italic" }}
>
{t("Always included origins: {{urls}}", {
{t("settings.sections.externalCors.messages.alwaysIncluded", {
urls: DEV_URLS.join(", "),
})}
</div>
+1 -1
View File
@@ -102,7 +102,7 @@ export const HotkeyInput = (props: Props) => {
<IconButton
size="small"
title={t("Delete")}
title={t("shared.actions.delete")}
color="inherit"
onClick={() => {
onChange([]);
+22 -8
View File
@@ -24,7 +24,19 @@ const HOTKEY_FUNC = [
"toggle_system_proxy",
"toggle_tun_mode",
"entry_lightweight_mode",
];
] as const;
const HOTKEY_FUNC_LABELS: Record<(typeof HOTKEY_FUNC)[number], string> = {
open_or_close_dashboard:
"settings.modals.hotkey.functions.openOrCloseDashboard",
clash_mode_rule: "settings.modals.hotkey.functions.rule",
clash_mode_global: "settings.modals.hotkey.functions.global",
clash_mode_direct: "settings.modals.hotkey.functions.direct",
toggle_system_proxy: "settings.modals.hotkey.functions.toggleSystemProxy",
toggle_tun_mode: "settings.modals.hotkey.functions.toggleTunMode",
entry_lightweight_mode:
"settings.modals.hotkey.functions.entryLightweightMode",
};
export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -81,24 +93,26 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
enable_global_hotkey: enableGlobalHotkey,
});
setOpen(false);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
return (
<BaseDialog
open={open}
title={t("Hotkey Setting")}
title={t("settings.modals.hotkey.title")}
contentSx={{ width: 450, maxHeight: 380 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<ItemWrapper style={{ marginBottom: 16 }}>
<Typography>{t("Enable Global Hotkey")}</Typography>
<Typography>
{t("settings.modals.hotkey.toggles.enableGlobal")}
</Typography>
<Switch
edge="end"
checked={enableGlobalHotkey}
@@ -108,7 +122,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
{HOTKEY_FUNC.map((func) => (
<ItemWrapper key={func}>
<Typography>{t(func)}</Typography>
<Typography>{t(HOTKEY_FUNC_LABELS[func])}</Typography>
<HotkeyInput
value={hotkeyMap[func] ?? []}
onChange={(v) => setHotkeyMap((m) => ({ ...m, [func]: v }))}
+86 -28
View File
@@ -104,7 +104,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
const onSwitchFormat = (_e: any, value: boolean) => value;
const onError = (err: any) => {
showNotice("error", err.message || err.toString());
showNotice.error(err);
};
const onChangeData = (patch: Partial<IVergeConfig>) => {
mutateVerge({ ...verge, ...patch }, false);
@@ -113,16 +113,20 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
return (
<BaseDialog
open={open}
title={t("Layout Setting")}
title={t("settings.components.verge.layout.title")}
contentSx={{ width: 450 }}
disableOk
cancelBtn={t("Close")}
cancelBtn={t("shared.actions.close")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<List>
<Item>
<ListItemText primary={t("Prefer System Titlebar")} />
<ListItemText
primary={t(
"settings.components.verge.layout.fields.preferSystemTitlebar",
)}
/>
<GuardState
value={decorated}
valueProps="checked"
@@ -137,7 +141,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
<Item>
<ListItemText primary={t("Traffic Graph")} />
<ListItemText
primary={t("settings.components.verge.layout.fields.trafficGraph")}
/>
<GuardState
value={verge?.traffic_graph ?? true}
valueProps="checked"
@@ -151,7 +157,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
<Item>
<ListItemText primary={t("Memory Usage")} />
<ListItemText
primary={t("settings.components.verge.layout.fields.memoryUsage")}
/>
<GuardState
value={verge?.enable_memory_usage ?? true}
valueProps="checked"
@@ -165,7 +173,11 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
<Item>
<ListItemText primary={t("Proxy Group Icon")} />
<ListItemText
primary={t(
"settings.components.verge.layout.fields.proxyGroupIcon",
)}
/>
<GuardState
value={verge?.enable_group_icon ?? true}
valueProps="checked"
@@ -182,9 +194,13 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
<span>{t("Hover Jump Navigator")}</span>
<span>
{t("settings.components.verge.layout.fields.hoverNavigator")}
</span>
<TooltipIcon
title={t("Hover Jump Navigator Info")}
title={t(
"settings.components.verge.layout.tooltips.hoverNavigator",
)}
sx={{ opacity: "0.7" }}
/>
</Box>
@@ -206,9 +222,15 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
<span>{t("Hover Jump Navigator Delay")}</span>
<span>
{t(
"settings.components.verge.layout.fields.hoverNavigatorDelay",
)}
</span>
<TooltipIcon
title={t("Hover Jump Navigator Delay Info")}
title={t(
"settings.components.verge.layout.tooltips.hoverNavigatorDelay",
)}
sx={{ opacity: "0.7" }}
/>
</Box>
@@ -241,7 +263,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
input: {
endAdornment: (
<InputAdornment position="end">
{t("millis")}
{t("shared.units.milliseconds")}
</InputAdornment>
),
},
@@ -256,7 +278,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
<Item>
<ListItemText primary={t("Nav Icon")} />
<ListItemText
primary={t("settings.components.verge.layout.fields.navIcon")}
/>
<GuardState
value={verge?.menu_icon ?? "monochrome"}
onCatch={onError}
@@ -265,16 +289,24 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
onGuard={(value) => patchVerge({ menu_icon: value })}
>
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
<MenuItem value="monochrome">{t("Monochrome")}</MenuItem>
<MenuItem value="colorful">{t("Colorful")}</MenuItem>
<MenuItem value="disable">{t("Disable")}</MenuItem>
<MenuItem value="monochrome">
{t("settings.components.verge.layout.options.icon.monochrome")}
</MenuItem>
<MenuItem value="colorful">
{t("settings.components.verge.layout.options.icon.colorful")}
</MenuItem>
<MenuItem value="disable">
{t("settings.components.verge.layout.options.icon.disable")}
</MenuItem>
</Select>
</GuardState>
</Item>
{OS === "macos" && (
<Item>
<ListItemText primary={t("Tray Icon")} />
<ListItemText
primary={t("settings.components.verge.layout.fields.trayIcon")}
/>
<GuardState
value={verge?.tray_icon ?? "monochrome"}
onCatch={onError}
@@ -286,15 +318,21 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
size="small"
sx={{ width: 140, "> div": { py: "7.5px" } }}
>
<MenuItem value="monochrome">{t("Monochrome")}</MenuItem>
<MenuItem value="colorful">{t("Colorful")}</MenuItem>
<MenuItem value="monochrome">
{t(
"settings.components.verge.layout.options.icon.monochrome",
)}
</MenuItem>
<MenuItem value="colorful">
{t("settings.components.verge.layout.options.icon.colorful")}
</MenuItem>
</Select>
</GuardState>
</Item>
)}
{/* {OS === "macos" && (
<Item>
<ListItemText primary={t("Enable Tray Speed")} />
<ListItemText primary={t("settings.components.verge.layout.fields.enableTraySpeed")} />
<GuardState
value={verge?.enable_tray_speed ?? false}
valueProps="checked"
@@ -309,7 +347,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
)} */}
{/* {OS === "macos" && (
<Item>
<ListItemText primary={t("Enable Tray Icon")} />
<ListItemText primary={t("settings.components.verge.layout.fields.enableTrayIcon")} />
<GuardState
value={
verge?.enable_tray_icon === false &&
@@ -328,7 +366,11 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
)} */}
<Item>
<ListItemText primary={t("Show Proxy Groups Inline")} />
<ListItemText
primary={t(
"settings.components.verge.layout.fields.showProxyGroupsInline",
)}
/>
<GuardState
value={verge?.tray_inline_proxy_groups ?? false}
valueProps="checked"
@@ -342,7 +384,11 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
<Item>
<ListItemText primary={t("Common Tray Icon")} />
<ListItemText
primary={t(
"settings.components.verge.layout.fields.commonTrayIcon",
)}
/>
<GuardState
value={verge?.common_tray_icon}
onCatch={onError}
@@ -384,13 +430,19 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
}
}}
>
{verge?.common_tray_icon ? t("Clear") : t("Browse")}
{verge?.common_tray_icon
? t("shared.actions.clear")
: t("settings.components.verge.basic.actions.browse")}
</Button>
</GuardState>
</Item>
<Item>
<ListItemText primary={t("System Proxy Tray Icon")} />
<ListItemText
primary={t(
"settings.components.verge.layout.fields.systemProxyTrayIcon",
)}
/>
<GuardState
value={verge?.sysproxy_tray_icon}
onCatch={onError}
@@ -430,13 +482,17 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
}
}}
>
{verge?.sysproxy_tray_icon ? t("Clear") : t("Browse")}
{verge?.sysproxy_tray_icon
? t("shared.actions.clear")
: t("settings.components.verge.basic.actions.browse")}
</Button>
</GuardState>
</Item>
<Item>
<ListItemText primary={t("Tun Tray Icon")} />
<ListItemText
primary={t("settings.components.verge.layout.fields.tunTrayIcon")}
/>
<GuardState
value={verge?.tun_tray_icon}
onCatch={onError}
@@ -474,7 +530,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
}
}}
>
{verge?.tun_tray_icon ? t("Clear") : t("Browse")}
{verge?.tun_tray_icon
? t("shared.actions.clear")
: t("settings.components.verge.basic.actions.browse")}
</Button>
</GuardState>
</Item>
@@ -45,25 +45,27 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
auto_light_weight_minutes: values.autoEnterLiteModeDelay,
});
setOpen(false);
} catch (err: any) {
showNotice("error", err.message || err.toString());
} catch (err) {
showNotice.error(err);
}
});
return (
<BaseDialog
open={open}
title={t("LightWeight Mode Settings")}
title={t("settings.modals.liteMode.title")}
contentSx={{ width: 450 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Enter LightWeight Mode Now")} />
<ListItemText
primary={t("settings.modals.liteMode.actions.enterNow")}
/>
<Typography
variant="button"
sx={{
@@ -73,17 +75,17 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
}}
onClick={async () => await entry_lightweight_mode()}
>
{t("Enable")}
{t("shared.actions.enable")}
</Typography>
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Auto Enter LightWeight Mode")}
primary={t("settings.modals.liteMode.toggles.autoEnter")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("Auto Enter LightWeight Mode Info")}
title={t("settings.modals.liteMode.tooltips.autoEnter")}
sx={{ opacity: "0.7" }}
/>
<Switch
@@ -99,7 +101,9 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
{values.autoEnterLiteMode && (
<>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Auto Enter LightWeight Mode Delay")} />
<ListItemText
primary={t("settings.modals.liteMode.fields.delay")}
/>
<TextField
autoComplete="off"
size="small"
@@ -119,7 +123,7 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
input: {
endAdornment: (
<InputAdornment position="end">
{t("mins")}
{t("shared.units.minutes")}
</InputAdornment>
),
},
@@ -133,10 +137,9 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
color="text.secondary"
sx={{ fontStyle: "italic" }}
>
{t(
"When closing the window, LightWeight Mode will be automatically activated after _n minutes",
{ n: values.autoEnterLiteModeDelay },
)}
{t("settings.modals.liteMode.messages.autoEnterHint", {
n: values.autoEnterLiteModeDelay,
})}
</Typography>
</ListItem>
</>
@@ -20,11 +20,13 @@ export const LocalBackupActions = memo(
try {
setLoading(true);
await createLocalBackup();
showNotice("success", t("Local Backup Created"));
showNotice.success(
"settings.modals.backup.messages.localBackupCreated",
);
await onBackupSuccess();
} catch (error) {
console.error(error);
showNotice("error", t("Local Backup Failed"));
showNotice.error("settings.modals.backup.messages.localBackupFailed");
} finally {
setLoading(false);
}
@@ -43,7 +45,7 @@ export const LocalBackupActions = memo(
<Grid container spacing={2}>
<Grid size={{ xs: 12, sm: 9 }}>
<Typography variant="body2" color="text.secondary">
{t("Local Backup Info")}
{t("settings.modals.backup.fields.info")}
</Typography>
</Grid>
<Grid size={{ xs: 12, sm: 3 }}>
@@ -60,7 +62,7 @@ export const LocalBackupActions = memo(
type="button"
size="large"
>
{t("Backup")}
{t("settings.modals.backup.actions.backup")}
</Button>
<Button
variant="outlined"
@@ -68,7 +70,7 @@ export const LocalBackupActions = memo(
type="button"
size="large"
>
{t("Refresh")}
{t("shared.actions.refresh")}
</Button>
</Stack>
</Grid>
+68 -29
View File
@@ -69,25 +69,27 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
auto_log_clean: values.autoLogClean as any,
});
setOpen(false);
} catch (err: any) {
showNotice("error", err.toString());
} catch (err) {
showNotice.error(err);
}
});
return (
<BaseDialog
open={open}
title={t("Miscellaneous")}
title={t("settings.modals.misc.title")}
contentSx={{ width: 450 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("App Log Level")} />
<ListItemText
primary={t("settings.modals.misc.fields.appLogLevel")}
/>
<Select
size="small"
sx={{ width: 100, "> div": { py: "7.5px" } }}
@@ -109,7 +111,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("App Log Max Size")}
primary={t("settings.modals.misc.fields.appLogMaxSize")}
sx={{ maxWidth: "fit-content" }}
/>
<TextField
@@ -130,7 +132,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("KB")}</InputAdornment>
<InputAdornment position="end">
{t("shared.units.kilobytes")}
</InputAdornment>
),
},
}}
@@ -139,7 +143,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("App Log Max Count")}
primary={t("settings.modals.misc.fields.appLogMaxCount")}
sx={{ maxWidth: "fit-content" }}
/>
<TextField
@@ -160,7 +164,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("Files")}</InputAdornment>
<InputAdornment position="end">
{t("shared.units.files")}
</InputAdornment>
),
},
}}
@@ -169,11 +175,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Auto Close Connections")}
primary={t("settings.modals.misc.fields.autoCloseConnections")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("Auto Close Connections Info")}
title={t("settings.modals.misc.tooltips.autoCloseConnections")}
sx={{ opacity: "0.7" }}
/>
<Switch
@@ -187,7 +193,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Auto Check Update")} />
<ListItemText
primary={t("settings.modals.misc.fields.autoCheckUpdate")}
/>
<Switch
edge="end"
checked={values.autoCheckUpdate}
@@ -199,11 +207,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Enable Builtin Enhanced")}
primary={t("settings.modals.misc.fields.enableBuiltinEnhanced")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("Enable Builtin Enhanced Info")}
title={t("settings.modals.misc.tooltips.enableBuiltinEnhanced")}
sx={{ opacity: "0.7" }}
/>
<Switch
@@ -217,7 +225,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Proxy Layout Columns")} />
<ListItemText
primary={t("settings.modals.misc.fields.proxyLayoutColumns")}
/>
<Select
size="small"
sx={{ width: 160, "> div": { py: "7.5px" } }}
@@ -230,7 +240,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
}
>
<MenuItem value={6} key={6}>
{t("Auto Columns")}
{t("settings.modals.misc.options.proxyLayoutColumns.auto")}
</MenuItem>
{[1, 2, 3, 4, 5].map((i) => (
<MenuItem value={i} key={i}>
@@ -241,7 +251,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Auto Log Clean")} />
<ListItemText
primary={t("settings.modals.misc.fields.autoLogClean")}
/>
<Select
size="small"
sx={{ width: 160, "> div": { py: "7.5px" } }}
@@ -255,11 +267,34 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
>
{/* 1: 1天, 2: 7天, 3: 30天, 4: 90天*/}
{[
{ key: t("Never Clean"), value: 0 },
{ key: t("Retain _n Days", { n: 1 }), value: 1 },
{ key: t("Retain _n Days", { n: 7 }), value: 2 },
{ key: t("Retain _n Days", { n: 30 }), value: 3 },
{ key: t("Retain _n Days", { n: 90 }), value: 4 },
{
key: t("settings.modals.misc.options.autoLogClean.never"),
value: 0,
},
{
key: t("settings.modals.misc.options.autoLogClean.retainDays", {
n: 1,
}),
value: 1,
},
{
key: t("settings.modals.misc.options.autoLogClean.retainDays", {
n: 7,
}),
value: 2,
},
{
key: t("settings.modals.misc.options.autoLogClean.retainDays", {
n: 30,
}),
value: 3,
},
{
key: t("settings.modals.misc.options.autoLogClean.retainDays", {
n: 90,
}),
value: 4,
},
].map((i) => (
<MenuItem key={i.value} value={i.value}>
{i.key}
@@ -270,11 +305,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Auto Delay Detection")}
primary={t("settings.modals.misc.fields.autoDelayDetection")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("Auto Delay Detection Info")}
title={t("settings.modals.misc.tooltips.autoDelayDetection")}
sx={{ opacity: "0.7" }}
/>
<Switch
@@ -289,11 +324,11 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Default Latency Test")}
primary={t("settings.modals.misc.fields.defaultLatencyTest")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("Default Latency Test Info")}
title={t("settings.modals.misc.tooltips.defaultLatencyTest")}
sx={{ opacity: "0.7" }}
/>
<TextField
@@ -312,7 +347,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Default Latency Timeout")} />
<ListItemText
primary={t("settings.modals.misc.fields.defaultLatencyTimeout")}
/>
<TextField
autoComplete="new-password"
size="small"
@@ -332,7 +369,9 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("millis")}</InputAdornment>
<InputAdornment position="end">
{t("shared.units.milliseconds")}
</InputAdornment>
),
},
}}
@@ -35,7 +35,7 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between">
{t("Network Interface")}
{t("settings.modals.networkInterface.title")}
<Box>
<Button
variant="contained"
@@ -51,7 +51,7 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
}
contentSx={{ width: 450 }}
disableOk
cancelBtn={t("Close")}
cancelBtn={t("shared.actions.close")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
@@ -66,13 +66,17 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
address.V4 && (
<AddressDisplay
key={address.V4.ip}
label={t("Ip Address")}
label={t(
"settings.modals.networkInterface.fields.ipAddress",
)}
content={address.V4.ip}
/>
),
)}
<AddressDisplay
label={t("Mac Address")}
label={t(
"settings.modals.networkInterface.fields.macAddress",
)}
content={item.mac_addr ?? ""}
/>
</>
@@ -84,13 +88,17 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
address.V6 && (
<AddressDisplay
key={address.V6.ip}
label={t("Ip Address")}
label={t(
"settings.modals.networkInterface.fields.ipAddress",
)}
content={address.V6.ip}
/>
),
)}
<AddressDisplay
label={t("Mac Address")}
label={t(
"settings.modals.networkInterface.fields.macAddress",
)}
content={item.mac_addr ?? ""}
/>
</>
@@ -109,8 +117,6 @@ const AddressDisplay = ({
label: string;
content: string;
}) => {
const { t } = useTranslation();
return (
<Box
sx={{
@@ -135,7 +141,9 @@ const AddressDisplay = ({
size="small"
onClick={async () => {
await writeText(content);
showNotice("success", t("Copy Success"));
showNotice.success(
"shared.feedback.notifications.common.copySuccess",
);
}}
>
<ContentCopyRounded sx={{ fontSize: "18px" }} />
@@ -21,13 +21,15 @@ export const PasswordInput = (props: Props) => {
return (
<Dialog open={true} maxWidth="xs" fullWidth>
<DialogTitle>{t("Please enter your root password")}</DialogTitle>
<DialogTitle>
{t("settings.modals.password.prompts.enterRoot")}
</DialogTitle>
<DialogContent>
<TextField
sx={{ mt: 1 }}
autoFocus
label={t("Password")}
label={t("shared.labels.password")}
fullWidth
size="small"
type="password"
@@ -42,7 +44,7 @@ export const PasswordInput = (props: Props) => {
onClick={async () => await onConfirm(passwd)}
variant="contained"
>
{t("Confirm")}
{t("shared.actions.confirm")}
</Button>
</DialogActions>
</Dialog>
+55 -32
View File
@@ -160,8 +160,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
mutate("getAutotemProxy"),
]);
}
} catch (err: any) {
showNotice("error", err.toString());
} catch (err) {
showNotice.error(err);
}
};
@@ -277,14 +277,11 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const onSave = useLockFn(async () => {
if (value.duration < 1) {
showNotice(
"error",
t("Proxy Daemon Duration Cannot be Less than 1 Second"),
);
showNotice.error("settings.modals.sysproxy.messages.durationTooShort");
return;
}
if (value.bypass && !validReg.test(value.bypass)) {
showNotice("error", t("Invalid Bypass Format"));
showNotice.error("settings.modals.sysproxy.messages.invalidBypass");
return;
}
@@ -301,7 +298,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
!ipv6Regex.test(value.proxy_host) &&
!hostnameRegex.test(value.proxy_host)
) {
showNotice("error", t("Invalid Proxy Host Format"));
showNotice.error("settings.modals.sysproxy.messages.invalidProxyHost");
return;
}
@@ -401,10 +398,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
console.warn("代理状态更新失败:", err);
}
}, 50);
} catch (err: any) {
} catch (err) {
console.error("配置保存失败:", err);
mutateVerge();
showNotice("error", err.toString());
showNotice.error(err);
// setOpen(true);
}
});
@@ -413,10 +410,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
return (
<BaseDialog
open={open}
title={t("System Proxy Setting")}
title={t("settings.modals.sysproxy.title")}
contentSx={{ width: 450, maxHeight: 565 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
@@ -424,28 +421,37 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
disableOk={saving}
>
<List>
<BaseFieldset label={t("Current System Proxy")} padding="15px 10px">
<BaseFieldset
label={t("settings.modals.sysproxy.fieldsets.currentStatus")}
padding="15px 10px"
>
<FlexBox>
<Typography className="label">{t("Enable status")}</Typography>
<Typography className="label">
{t("settings.modals.sysproxy.fields.enableStatus")}
</Typography>
<Typography className="value">
{value.pac
? autoproxy?.enable
? t("Enabled")
: t("Disabled")
? t("shared.statuses.enabled")
: t("shared.statuses.disabled")
: sysproxy?.enable
? t("Enabled")
: t("Disabled")}
? t("shared.statuses.enabled")
: t("shared.statuses.disabled")}
</Typography>
</FlexBox>
{!value.pac && (
<FlexBox>
<Typography className="label">{t("Server Addr")}</Typography>
<Typography className="label">
{t("settings.modals.sysproxy.fields.serverAddr")}
</Typography>
<Typography className="value">{getSystemProxyAddress}</Typography>
</FlexBox>
)}
{value.pac && (
<FlexBox>
<Typography className="label">{t("PAC URL")}</Typography>
<Typography className="label">
{t("settings.modals.sysproxy.fields.pacUrl")}
</Typography>
<Typography className="value">
{getCurrentPacUrl || "-"}
</Typography>
@@ -453,7 +459,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
)}
</BaseFieldset>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Proxy Host")} />
<ListItemText
primary={t("settings.modals.sysproxy.fields.proxyHost")}
/>
<Autocomplete
size="small"
sx={{ width: 150 }}
@@ -478,7 +486,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Use PAC Mode")} />
<ListItemText
primary={t("settings.modals.sysproxy.fields.usePacMode")}
/>
<Switch
edge="end"
disabled={!enabled}
@@ -489,10 +499,13 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Proxy Guard")}
primary={t("settings.modals.sysproxy.fields.proxyGuard")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon title={t("Proxy Guard Info")} sx={{ opacity: "0.7" }} />
<TooltipIcon
title={t("settings.modals.sysproxy.tooltips.proxyGuard")}
sx={{ opacity: "0.7" }}
/>
<Switch
edge="end"
disabled={!enabled}
@@ -503,7 +516,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Guard Duration")} />
<ListItemText
primary={t("settings.modals.sysproxy.fields.guardDuration")}
/>
<TextField
disabled={!enabled}
size="small"
@@ -524,7 +539,11 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
</ListItem>
{!value.pac && (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Always use Default Bypass")} />
<ListItemText
primary={t(
"settings.modals.sysproxy.fields.alwaysUseDefaultBypass",
)}
/>
<Switch
edge="end"
disabled={!enabled}
@@ -543,7 +562,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
{!value.pac && !value.use_default && (
<>
<ListItemText primary={t("Proxy Bypass")} />
<ListItemText
primary={t("settings.modals.sysproxy.fields.proxyBypass")}
/>
<TextField
error={value.bypass ? !validReg.test(value.bypass) : false}
disabled={!enabled}
@@ -561,7 +582,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
{!value.pac && value.use_default && (
<>
<ListItemText primary={t("Bypass")} />
<ListItemText
primary={t("settings.modals.sysproxy.fields.bypass")}
/>
<FlexBox>
<TextField
disabled={true}
@@ -578,7 +601,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
{value.pac && (
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
<ListItemText
primary={t("PAC Script Content")}
primary={t("settings.modals.sysproxy.fields.pacScriptContent")}
sx={{ padding: "3px 0" }}
/>
<Button
@@ -588,12 +611,12 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
setEditorOpen(true);
}}
>
{t("Edit")} PAC
{t("settings.modals.sysproxy.actions.editPac")}
</Button>
{editorOpen && (
<EditorViewer
open={true}
title={`${t("Edit")} PAC`}
title={t("settings.modals.sysproxy.actions.editPac")}
initialData={Promise.resolve(value.pac_content ?? "")}
language="javascript"
onSave={(_prev, curr) => {
@@ -23,7 +23,7 @@ export const ThemeModeSwitch = (props: Props) => {
onClick={() => onChange?.(mode)}
sx={{ textTransform: "capitalize" }}
>
{t(`theme.${mode}`)}
{t(`settings.sections.appearance.${mode}`)}
</Button>
))}
</ButtonGroup>
+56 -27
View File
@@ -9,7 +9,7 @@ import {
useTheme,
} from "@mui/material";
import { useLockFn } from "ahooks";
import { useImperativeHandle, useState } from "react";
import { useImperativeHandle, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef } from "@/components/base";
@@ -50,8 +50,8 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
try {
await patchVerge({ theme_setting: theme });
setOpen(false);
} catch (err: any) {
showNotice("error", err.toString());
} catch (err) {
showNotice.error(err);
}
});
@@ -62,9 +62,48 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
type ThemeKey = keyof typeof theme & keyof typeof defaultTheme;
const renderItem = (label: string, key: ThemeKey) => {
const fieldDefinitions: Array<{ labelKey: string; key: ThemeKey }> = useMemo(
() => [
{
labelKey: "settings.components.verge.theme.fields.primaryColor",
key: "primary_color",
},
{
labelKey: "settings.components.verge.theme.fields.secondaryColor",
key: "secondary_color",
},
{
labelKey: "settings.components.verge.theme.fields.primaryText",
key: "primary_text",
},
{
labelKey: "settings.components.verge.theme.fields.secondaryText",
key: "secondary_text",
},
{
labelKey: "settings.components.verge.theme.fields.infoColor",
key: "info_color",
},
{
labelKey: "settings.components.verge.theme.fields.warningColor",
key: "warning_color",
},
{
labelKey: "settings.components.verge.theme.fields.errorColor",
key: "error_color",
},
{
labelKey: "settings.components.verge.theme.fields.successColor",
key: "success_color",
},
],
[],
);
const renderItem = (labelKey: string, key: ThemeKey) => {
const label = t(labelKey);
return (
<Item>
<Item key={key}>
<ListItemText primary={label} />
<Round sx={{ background: theme[key] || dt[key] }} />
<TextField
@@ -81,33 +120,21 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
return (
<BaseDialog
open={open}
title={t("Theme Setting")}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
title={t("settings.components.verge.theme.title")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
contentSx={{ width: 400, maxHeight: 505, overflow: "auto", pb: 0 }}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List sx={{ pt: 0 }}>
{renderItem(t("Primary Color"), "primary_color")}
{renderItem(t("Secondary Color"), "secondary_color")}
{renderItem(t("Primary Text"), "primary_text")}
{renderItem(t("Secondary Text"), "secondary_text")}
{renderItem(t("Info Color"), "info_color")}
{renderItem(t("Warning Color"), "warning_color")}
{renderItem(t("Error Color"), "error_color")}
{renderItem(t("Success Color"), "success_color")}
{fieldDefinitions.map((field) => renderItem(field.labelKey, field.key))}
<Item>
<ListItemText primary={t("Font Family")} />
<ListItemText
primary={t("settings.components.verge.theme.fields.fontFamily")}
/>
<TextField
{...textProps}
value={theme.font_family ?? ""}
@@ -116,7 +143,9 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
/>
</Item>
<Item>
<ListItemText primary={t("CSS Injection")} />
<ListItemText
primary={t("settings.components.verge.theme.fields.cssInjection")}
/>
<Button
startIcon={<EditRounded />}
variant="outlined"
@@ -124,12 +153,12 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
setEditorOpen(true);
}}
>
{t("Edit")} CSS
{t("settings.components.verge.theme.actions.editCss")}
</Button>
{editorOpen && (
<EditorViewer
open={true}
title={`${t("Edit")} CSS`}
title={t("settings.components.verge.theme.dialogs.editCssTitle")}
initialData={Promise.resolve(theme.css_injection ?? "")}
language="css"
onSave={(_prev, curr) => {
+17 -15
View File
@@ -80,13 +80,13 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
);
try {
await enhanceProfiles();
showNotice("success", t("Settings Applied"));
showNotice.success("settings.modals.tun.messages.applied");
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
setOpen(false);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
});
@@ -95,7 +95,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between" gap={1}>
<Typography variant="h6">{t("Tun Mode")}</Typography>
<Typography variant="h6">{t("settings.modals.tun.title")}</Typography>
<Button
variant="outlined"
size="small"
@@ -128,20 +128,20 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
);
}}
>
{t("Reset to Default")}
{t("shared.actions.resetToDefault")}
</Button>
</Box>
}
contentSx={{ width: 450 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Stack")} />
<ListItemText primary={t("settings.modals.tun.fields.stack")} />
<StackModeSwitch
value={values.stack}
onChange={(value) => {
@@ -154,7 +154,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Device")} />
<ListItemText primary={t("settings.modals.tun.fields.device")} />
<TextField
autoComplete="new-password"
size="small"
@@ -171,7 +171,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Auto Route")} />
<ListItemText primary={t("settings.modals.tun.fields.autoRoute")} />
<Switch
edge="end"
checked={values.autoRoute}
@@ -180,7 +180,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Strict Route")} />
<ListItemText primary={t("settings.modals.tun.fields.strictRoute")} />
<Switch
edge="end"
checked={values.strictRoute}
@@ -189,7 +189,9 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Auto Detect Interface")} />
<ListItemText
primary={t("settings.modals.tun.fields.autoDetectInterface")}
/>
<Switch
edge="end"
checked={values.autoDetectInterface}
@@ -200,7 +202,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("DNS Hijack")} />
<ListItemText primary={t("settings.modals.tun.fields.dnsHijack")} />
<TextField
autoComplete="new-password"
size="small"
@@ -209,7 +211,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
spellCheck="false"
sx={{ width: 250 }}
value={values.dnsHijack.join(",")}
placeholder="Please use , to separate multiple DNS servers"
placeholder={t("settings.modals.tun.tooltips.dnsHijack")}
onChange={(e) =>
setValues((v) => ({ ...v, dnsHijack: e.target.value.split(",") }))
}
@@ -217,7 +219,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("MTU")} />
<ListItemText primary={t("settings.modals.tun.fields.mtu")} />
<TextField
autoComplete="new-password"
size="small"
@@ -58,12 +58,12 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
const onUpdate = useLockFn(async () => {
if (portableFlag) {
showNotice("error", t("Portable Updater Error"));
showNotice.error("settings.modals.update.messages.portableError");
return;
}
if (!updateInfo?.body) return;
if (breakChangeFlag) {
showNotice("error", t("Break Change Update Error"));
showNotice.error("settings.modals.update.messages.breakChangeError");
return;
}
if (updateState) return;
@@ -89,7 +89,7 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
await updateInfo.downloadAndInstall();
await relaunch();
} catch (err: any) {
showNotice("error", err?.message || err.toString());
showNotice.error(err);
} finally {
setUpdateState(false);
if (progressListener) {
@@ -113,7 +113,9 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between">
{`New Version v${updateInfo?.version}`}
{t("settings.modals.update.title", {
version: updateInfo?.version ?? "",
})}
<Box>
<Button
variant="contained"
@@ -124,14 +126,14 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
);
}}
>
{t("Go to Release Page")}
{t("settings.modals.update.actions.goToRelease")}
</Button>
</Box>
</Box>
}
contentSx={{ minWidth: 360, maxWidth: 400, height: "50vh" }}
okBtn={t("Update")}
cancelBtn={t("Cancel")}
okBtn={t("settings.modals.update.actions.update")}
cancelBtn={t("shared.actions.cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onUpdate}
+8 -6
View File
@@ -56,11 +56,13 @@ export const WebUIItem = (props: Props) => {
size="small"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
placeholder={t("Support %host, %port, %secret")}
placeholder={t(
"settings.modals.webUI.messages.supportedPlaceholders",
)}
/>
<IconButton
size="small"
title={t("Save")}
title={t("shared.actions.save")}
color="inherit"
onClick={() => {
onChange(editValue);
@@ -71,7 +73,7 @@ export const WebUIItem = (props: Props) => {
</IconButton>
<IconButton
size="small"
title={t("Cancel")}
title={t("shared.actions.cancel")}
color="inherit"
onClick={() => {
onCancel?.();
@@ -127,7 +129,7 @@ export const WebUIItem = (props: Props) => {
</Typography>
<IconButton
size="small"
title={t("Open URL")}
title={t("settings.modals.webUI.actions.openUrl")}
color="inherit"
onClick={() => onOpenUrl?.(value)}
>
@@ -135,7 +137,7 @@ export const WebUIItem = (props: Props) => {
</IconButton>
<IconButton
size="small"
title={t("Edit")}
title={t("shared.actions.edit")}
color="inherit"
onClick={() => {
setEditing(true);
@@ -146,7 +148,7 @@ export const WebUIItem = (props: Props) => {
</IconButton>
<IconButton
size="small"
title={t("Delete")}
title={t("shared.actions.delete")}
color="inherit"
onClick={onDelete}
>
@@ -92,7 +92,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
await openWebUrl(url);
} catch (e: any) {
showNotice("error", e.message || e.toString());
showNotice.error(e);
}
});
@@ -101,14 +101,14 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
open={open}
title={
<Box display="flex" justifyContent="space-between">
{t("Web UI")}
{t("settings.modals.webUI.title")}
<Button
variant="contained"
size="small"
disabled={editing}
onClick={() => setEditing(true)}
>
{t("New")}
{t("shared.actions.new")}
</Button>
</Box>
}
@@ -119,7 +119,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
overflowY: "auto",
userSelect: "text",
}}
cancelBtn={t("Close")}
cancelBtn={t("shared.actions.close")}
disableOk
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
@@ -128,7 +128,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
<BaseEmpty
extra={
<Typography mt={2} sx={{ fontSize: "12px" }}>
{t("Replace host, port, secret with %host, %port, %secret")}
{t("settings.modals.webUI.messages.placeholderInstruction")}
</Typography>
}
/>
+46 -25
View File
@@ -67,9 +67,11 @@ const SettingClash = ({ onError }: Props) => {
const onUpdateGeo = async () => {
try {
await updateGeo();
showNotice("success", t("GeoData Updated"));
showNotice.success(
"settings.feedback.notifications.clash.geoDataUpdated",
);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
};
@@ -86,14 +88,14 @@ const SettingClash = ({ onError }: Props) => {
} catch (err: any) {
setDnsSettingsEnabled(!enable);
localStorage.setItem("dns_settings_enabled", String(!enable));
showNotice("error", err.message || err.toString());
showNotice.error(err);
await patchVerge({ enable_dns_settings: !enable }).catch(() => {});
throw err;
}
});
return (
<SettingList title={t("Clash Setting")}>
<SettingList title={t("settings.sections.clash.title")}>
<WebUIViewer ref={webRef} />
<ClashPortViewer ref={portRef} />
<ControllerViewer ref={ctrlRef} />
@@ -103,10 +105,10 @@ const SettingClash = ({ onError }: Props) => {
<HeaderConfiguration ref={corsRef} />
<SettingItem
label={t("Allow Lan")}
label={t("settings.sections.clash.form.fields.allowLan")}
extra={
<TooltipIcon
title={t("Network Interface")}
title={t("settings.sections.clash.form.tooltips.networkInterface")}
color={"inherit"}
icon={LanRounded}
onClick={() => {
@@ -128,7 +130,7 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={t("DNS Overwrite")}
label={t("settings.sections.clash.form.fields.dnsOverwrite")}
extra={
<TooltipIcon
icon={SettingsRounded}
@@ -143,7 +145,7 @@ const SettingClash = ({ onError }: Props) => {
/>
</SettingItem>
<SettingItem label={t("IPv6")}>
<SettingItem label={t("settings.sections.clash.form.fields.ipv6")}>
<GuardState
value={ipv6 ?? false}
valueProps="checked"
@@ -157,10 +159,10 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={t("Unified Delay")}
label={t("settings.sections.clash.form.fields.unifiedDelay")}
extra={
<TooltipIcon
title={t("Unified Delay Info")}
title={t("settings.sections.clash.form.tooltips.unifiedDelay")}
sx={{ opacity: "0.7" }}
/>
}
@@ -178,9 +180,12 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={t("Log Level")}
label={t("settings.sections.clash.form.fields.logLevel")}
extra={
<TooltipIcon title={t("Log Level Info")} sx={{ opacity: "0.7" }} />
<TooltipIcon
title={t("settings.sections.clash.form.tooltips.logLevel")}
sx={{ opacity: "0.7" }}
/>
}
>
<GuardState
@@ -194,16 +199,26 @@ const SettingClash = ({ onError }: Props) => {
}}
>
<Select size="small" sx={{ width: 100, "> div": { py: "7.5px" } }}>
<MenuItem value="debug">Debug</MenuItem>
<MenuItem value="info">Info</MenuItem>
<MenuItem value="warning">Warn</MenuItem>
<MenuItem value="error">Error</MenuItem>
<MenuItem value="silent">Silent</MenuItem>
<MenuItem value="debug">
{t("settings.sections.clash.form.options.logLevel.debug")}
</MenuItem>
<MenuItem value="info">
{t("settings.sections.clash.form.options.logLevel.info")}
</MenuItem>
<MenuItem value="warning">
{t("settings.sections.clash.form.options.logLevel.warning")}
</MenuItem>
<MenuItem value="error">
{t("settings.sections.clash.form.options.logLevel.error")}
</MenuItem>
<MenuItem value="silent">
{t("settings.sections.clash.form.options.logLevel.silent")}
</MenuItem>
</Select>
</GuardState>
</SettingItem>
<SettingItem label={t("Port Config")}>
<SettingItem label={t("settings.sections.clash.form.fields.portConfig")}>
<TextField
autoComplete="new-password"
disabled={false}
@@ -218,10 +233,10 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={<>{t("External")}</>}
label={t("settings.sections.clash.form.fields.external")}
extra={
<TooltipIcon
title={t("External Cors Settings")}
title={t("settings.sections.externalCors.tooltips.open")}
icon={SettingsRounded}
onClick={(e) => {
e.stopPropagation();
@@ -234,10 +249,13 @@ const SettingClash = ({ onError }: Props) => {
}}
/>
<SettingItem onClick={() => webRef.current?.open()} label={t("Web UI")} />
<SettingItem
onClick={() => webRef.current?.open()}
label={t("settings.sections.clash.form.fields.webUI")}
/>
<SettingItem
label={t("Clash Core")}
label={t("settings.sections.clash.form.fields.clashCore")}
extra={
<TooltipIcon
icon={SettingsRounded}
@@ -251,17 +269,20 @@ const SettingClash = ({ onError }: Props) => {
{isWIN && (
<SettingItem
onClick={invoke_uwp_tool}
label={t("Open UWP tool")}
label={t("settings.sections.clash.form.fields.openUwpTool")}
extra={
<TooltipIcon
title={t("Open UWP tool Info")}
title={t("settings.sections.clash.form.tooltips.openUwpTool")}
sx={{ opacity: "0.7" }}
/>
}
/>
)}
<SettingItem onClick={onUpdateGeo} label={t("Update GeoData")} />
<SettingItem
onClick={onUpdateGeo}
label={t("settings.sections.clash.form.fields.updateGeoData")}
/>
</SettingList>
);
};
+18 -10
View File
@@ -41,20 +41,26 @@ const SettingSystem = ({ onError }: Props) => {
};
return (
<SettingList title={t("System Setting")}>
<SettingList title={t("settings.sections.system.title")}>
<SysproxyViewer ref={sysproxyRef} />
<TunViewer ref={tunRef} />
<ProxyControlSwitches label={t("Tun Mode")} onError={onError} />
<ProxyControlSwitches
label={t("settings.sections.system.toggles.tunMode")}
onError={onError}
/>
<ProxyControlSwitches label={t("System Proxy")} onError={onError} />
<ProxyControlSwitches
label={t("settings.sections.system.toggles.systemProxy")}
onError={onError}
/>
<SettingItem
label={t("Auto Launch")}
label={t("settings.sections.system.fields.autoLaunch")}
extra={
isAdminMode && (
<Tooltip
title={t("Administrator mode may not support auto launch")}
title={t("settings.sections.system.tooltips.autoLaunchAdmin")}
>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip>
@@ -72,9 +78,8 @@ const SettingSystem = ({ onError }: Props) => {
}}
onGuard={async (e) => {
if (isAdminMode) {
showNotice(
"info",
t("Administrator mode may not support auto launch"),
showNotice.info(
"settings.sections.system.tooltips.autoLaunchAdmin",
);
}
@@ -96,9 +101,12 @@ const SettingSystem = ({ onError }: Props) => {
</SettingItem>
<SettingItem
label={t("Silent Start")}
label={t("settings.sections.system.fields.silentStart")}
extra={
<TooltipIcon title={t("Silent Start Info")} sx={{ opacity: "0.7" }} />
<TooltipIcon
title={t("settings.sections.system.tooltips.silentStart")}
sx={{ opacity: "0.7" }}
/>
}
>
<GuardState
@@ -47,28 +47,36 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
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 {
updateRef.current?.open();
}
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
};
const onExportDiagnosticInfo = useCallback(async () => {
await exportDiagnosticInfo();
showNotice("success", t("Copy Success"), 1000);
}, [t]);
showNotice.success(
"shared.feedback.notifications.common.copySuccess",
1000,
);
}, []);
const copyVersion = useCallback(() => {
navigator.clipboard.writeText(`v${version}`).then(() => {
showNotice("success", t("Version copied to clipboard"), 1000);
showNotice.success(
"settings.components.verge.advanced.notifications.versionCopied",
1000,
);
});
}, [t]);
}, []);
return (
<SettingList title={t("Verge Advanced Setting")}>
<SettingList title={t("settings.components.verge.advanced.title")}>
<ThemeViewer ref={themeRef} />
<ConfigViewer ref={configRef} />
<HotkeyViewer ref={hotkeyRef} />
@@ -80,10 +88,10 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
<SettingItem
onClick={() => backupRef.current?.open()}
label={t("Backup Setting")}
label={t("settings.components.verge.advanced.fields.backupSetting")}
extra={
<TooltipIcon
title={t("Backup Setting Info")}
title={t("settings.components.verge.advanced.tooltips.backupInfo")}
sx={{ opacity: "0.7" }}
/>
}
@@ -91,33 +99,45 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
<SettingItem
onClick={() => configRef.current?.open()}
label={t("Runtime Config")}
label={t("settings.components.verge.advanced.fields.runtimeConfig")}
/>
<SettingItem
onClick={openAppDir}
label={t("Open Conf Dir")}
label={t("settings.components.verge.advanced.fields.openConfDir")}
extra={
<TooltipIcon
title={t("Open Conf Dir Info")}
title={t("settings.components.verge.advanced.tooltips.openConfDir")}
sx={{ opacity: "0.7" }}
/>
}
/>
<SettingItem onClick={openCoreDir} label={t("Open Core Dir")} />
<SettingItem onClick={openLogsDir} label={t("Open Logs Dir")} />
<SettingItem onClick={onCheckUpdate} label={t("Check for Updates")} />
<SettingItem onClick={openDevTools} label={t("Open Dev Tools")} />
<SettingItem
onClick={openCoreDir}
label={t("settings.components.verge.advanced.fields.openCoreDir")}
/>
<SettingItem
label={t("LightWeight Mode Settings")}
onClick={openLogsDir}
label={t("settings.components.verge.advanced.fields.openLogsDir")}
/>
<SettingItem
onClick={onCheckUpdate}
label={t("settings.components.verge.advanced.fields.checkUpdates")}
/>
<SettingItem
onClick={openDevTools}
label={t("settings.components.verge.advanced.fields.openDevTools")}
/>
<SettingItem
label={t("settings.components.verge.advanced.fields.liteModeSettings")}
extra={
<TooltipIcon
title={t("LightWeight Mode Info")}
title={t("settings.components.verge.advanced.tooltips.liteMode")}
sx={{ opacity: "0.7" }}
/>
}
@@ -128,11 +148,11 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
onClick={() => {
exitApp();
}}
label={t("Exit")}
label={t("settings.components.verge.advanced.fields.exit")}
/>
<SettingItem
label={t("Export Diagnostic Info")}
label={t("settings.components.verge.advanced.fields.exportDiagnostics")}
extra={
<TooltipIcon
icon={ContentCopyRounded}
@@ -142,12 +162,12 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
></SettingItem>
<SettingItem
label={t("Verge Version")}
label={t("settings.components.verge.advanced.fields.vergeVersion")}
extra={
<TooltipIcon
icon={ContentCopyRounded}
onClick={copyVersion}
title={t("Copy Version")}
title={t("settings.components.verge.advanced.actions.copyVersion")}
/>
}
>
+43 -20
View File
@@ -76,11 +76,14 @@ const SettingVergeBasic = ({ onError }: Props) => {
const onCopyClashEnv = useCallback(async () => {
await copyClashEnv();
showNotice("success", t("Copy Success"), 1000);
}, [t]);
showNotice.success(
"shared.feedback.notifications.common.copySuccess",
1000,
);
}, []);
return (
<SettingList title={t("Verge Basic Setting")}>
<SettingList title={t("settings.components.verge.basic.title")}>
<ThemeViewer ref={themeRef} />
<ConfigViewer ref={configRef} />
<HotkeyViewer ref={hotkeyRef} />
@@ -89,7 +92,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
<UpdateViewer ref={updateRef} />
<BackupViewer ref={backupRef} />
<SettingItem label={t("Language")}>
<SettingItem label={t("settings.components.verge.basic.fields.language")}>
<GuardState
value={language ?? "en"}
onCatch={onError}
@@ -107,7 +110,9 @@ const SettingVergeBasic = ({ onError }: Props) => {
</GuardState>
</SettingItem>
<SettingItem label={t("Theme Mode")}>
<SettingItem
label={t("settings.components.verge.basic.fields.themeMode")}
>
<GuardState
value={theme_mode}
onCatch={onError}
@@ -119,7 +124,9 @@ const SettingVergeBasic = ({ onError }: Props) => {
</SettingItem>
{OS !== "linux" && (
<SettingItem label={t("Tray Click Event")}>
<SettingItem
label={t("settings.components.verge.basic.fields.trayClickEvent")}
>
<GuardState
value={tray_event ?? "main_window"}
onCatch={onError}
@@ -128,18 +135,30 @@ const SettingVergeBasic = ({ onError }: Props) => {
onGuard={(e) => patchVerge({ tray_event: e })}
>
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
<MenuItem value="main_window">{t("Show Main Window")}</MenuItem>
<MenuItem value="tray_menu">{t("Show Tray Menu")}</MenuItem>
<MenuItem value="system_proxy">{t("System Proxy")}</MenuItem>
<MenuItem value="tun_mode">{t("Tun Mode")}</MenuItem>
<MenuItem value="disable">{t("Disable")}</MenuItem>
<MenuItem value="main_window">
{t(
"settings.components.verge.basic.trayOptions.showMainWindow",
)}
</MenuItem>
<MenuItem value="tray_menu">
{t("settings.components.verge.basic.trayOptions.showTrayMenu")}
</MenuItem>
<MenuItem value="system_proxy">
{t("settings.sections.system.toggles.systemProxy")}
</MenuItem>
<MenuItem value="tun_mode">
{t("settings.sections.system.toggles.tunMode")}
</MenuItem>
<MenuItem value="disable">
{t("settings.components.verge.basic.trayOptions.disable")}
</MenuItem>
</Select>
</GuardState>
</SettingItem>
)}
<SettingItem
label={t("Copy Env Type")}
label={t("settings.components.verge.basic.fields.copyEnvType")}
extra={
<TooltipIcon icon={ContentCopyRounded} onClick={onCopyClashEnv} />
}
@@ -161,7 +180,9 @@ const SettingVergeBasic = ({ onError }: Props) => {
</GuardState>
</SettingItem>
<SettingItem label={t("Start Page")}>
<SettingItem
label={t("settings.components.verge.basic.fields.startPage")}
>
<GuardState
value={start_page ?? "/"}
onCatch={onError}
@@ -181,7 +202,9 @@ const SettingVergeBasic = ({ onError }: Props) => {
</GuardState>
</SettingItem>
<SettingItem label={t("Startup Script")}>
<SettingItem
label={t("settings.components.verge.basic.fields.startupScript")}
>
<GuardState
value={startup_script ?? ""}
onCatch={onError}
@@ -214,7 +237,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
}
}}
>
{t("Browse")}
{t("settings.components.verge.basic.actions.browse")}
</Button>
{startup_script && (
<Button
@@ -223,7 +246,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
patchVerge({ startup_script: "" });
}}
>
{t("Clear")}
{t("shared.actions.clear")}
</Button>
)}
</>
@@ -234,22 +257,22 @@ const SettingVergeBasic = ({ onError }: Props) => {
<SettingItem
onClick={() => themeRef.current?.open()}
label={t("Theme Setting")}
label={t("settings.components.verge.basic.fields.themeSetting")}
/>
<SettingItem
onClick={() => layoutRef.current?.open()}
label={t("Layout Setting")}
label={t("settings.components.verge.basic.fields.layoutSetting")}
/>
<SettingItem
onClick={() => miscRef.current?.open()}
label={t("Miscellaneous")}
label={t("settings.components.verge.basic.fields.misc")}
/>
<SettingItem
onClick={() => hotkeyRef.current?.open()}
label={t("Hotkey Setting")}
label={t("settings.components.verge.basic.fields.hotkeySetting")}
/>
</SettingList>
);
+23 -16
View File
@@ -126,15 +126,15 @@ const ProxyControlSwitches = ({
const { enable_tun_mode, enable_system_proxy } = verge ?? {};
const showErrorNotice = useCallback(
(msg: string) => showNotice("error", t(msg)),
[t],
(msg: string) => showNotice.error(msg),
[],
);
const handleTunToggle = async (value: boolean) => {
if (!isTunModeAvailable) {
const msg = "TUN requires Service Mode or Admin Mode";
showErrorNotice(msg);
throw new Error(t(msg));
const msgKey = "settings.sections.proxyControl.tooltips.tunUnavailable";
showErrorNotice(msgKey);
throw new Error(t(msgKey));
}
mutateVerge({ ...verge, enable_tun_mode: value }, false);
await patchVerge({ enable_tun_mode: value });
@@ -145,7 +145,7 @@ const ProxyControlSwitches = ({
await installServiceAndRestartCore();
await mutateSystemState();
} catch (err) {
showNotice("error", (err as Error).message || String(err));
showNotice.error(err);
}
});
@@ -157,20 +157,21 @@ const ProxyControlSwitches = ({
await uninstallServiceAndRestartCore();
await mutateSystemState();
} catch (err) {
showNotice("error", (err as Error).message || String(err));
showNotice.error(err);
}
});
const isSystemProxyMode = label === t("System Proxy") || !label;
const isTunMode = label === t("Tun Mode");
const isSystemProxyMode =
label === t("settings.sections.system.toggles.systemProxy") || !label;
const isTunMode = label === t("settings.sections.system.toggles.tunMode");
return (
<Box sx={{ width: "100%", pr: noRightPadding ? 1 : 2 }}>
{isSystemProxyMode && (
<SwitchRow
label={t("System Proxy")}
label={t("settings.sections.proxyControl.fields.systemProxy")}
active={systemProxyActualState}
infoTitle={t("System Proxy Info")}
infoTitle={t("settings.sections.proxyControl.tooltips.systemProxy")}
onInfoClick={() => sysproxyRef.current?.open()}
onToggle={(value) => toggleSystemProxy(value)}
onError={onError}
@@ -180,9 +181,9 @@ const ProxyControlSwitches = ({
{isTunMode && (
<SwitchRow
label={t("Tun Mode")}
label={t("settings.sections.proxyControl.fields.tunMode")}
active={enable_tun_mode || false}
infoTitle={t("Tun Mode Info")}
infoTitle={t("settings.sections.proxyControl.tooltips.tunMode")}
onInfoClick={() => tunRef.current?.open()}
onToggle={handleTunToggle}
onError={onError}
@@ -193,12 +194,16 @@ const ProxyControlSwitches = ({
{!isTunModeAvailable && (
<>
<TooltipIcon
title={t("TUN requires Service Mode or Admin Mode")}
title={t(
"settings.sections.proxyControl.tooltips.tunUnavailable",
)}
icon={WarningRounded}
sx={{ color: "warning.main", ml: 1 }}
/>
<TooltipIcon
title={t("Install Service")}
title={t(
"settings.sections.proxyControl.actions.installService",
)}
icon={BuildRounded}
color="primary"
onClick={onInstallService}
@@ -208,7 +213,9 @@ const ProxyControlSwitches = ({
)}
{isServiceOk && (
<TooltipIcon
title={t("Uninstall Service")}
title={t(
"settings.sections.proxyControl.actions.uninstallService",
)}
icon={DeleteForeverRounded}
color="secondary"
onClick={onUninstallService}
+2 -2
View File
@@ -82,7 +82,7 @@ export const TestItem = ({
try {
removeTest(uid);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
}
});
@@ -192,7 +192,7 @@ export const TestItem = ({
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
})}
>
{t("Test")}
{t("tests.components.item.actions.test")}
</Widget>
)}
+11 -7
View File
@@ -101,7 +101,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
setLoading(false);
setTimeout(() => formIns.reset(), 500);
} catch (err: any) {
showNotice("error", err.message || err.toString());
showNotice.error(err);
setLoading(false);
}
}),
@@ -124,10 +124,14 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
return (
<BaseDialog
open={open}
title={openType === "new" ? t("Create Test") : t("Edit Test")}
title={
openType === "new"
? t("tests.modals.test.title.create")
: t("tests.modals.test.title.edit")
}
contentSx={{ width: 375, pb: 0, maxHeight: "80%" }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
okBtn={t("shared.actions.save")}
cancelBtn={t("shared.actions.cancel")}
onClose={handleClose}
onCancel={handleClose}
onOk={handleOk}
@@ -137,7 +141,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
name="name"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t("Name")} />
<TextField {...text} {...field} label={t("shared.labels.name")} />
)}
/>
<Controller
@@ -149,7 +153,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
{...field}
multiline
maxRows={5}
label={t("Icon")}
label={t("shared.labels.icon")}
/>
)}
/>
@@ -162,7 +166,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
{...field}
multiline
maxRows={3}
label={t("Test URL")}
label={t("tests.modals.test.fields.url")}
/>
)}
/>
+6 -7
View File
@@ -1,5 +1,4 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import useSWR from "swr";
import { getRunningMode, isAdmin, isServiceAvailable } from "@/services/cmds";
@@ -26,7 +25,6 @@ let disablingTunMode = false;
* 包括运行模式、管理员状态、系统服务是否可用
*/
export function useSystemState() {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
const {
@@ -67,14 +65,15 @@ export function useSystemState() {
disablingTunMode = true;
patchVerge({ enable_tun_mode: false })
.then(() => {
showNotice(
"info",
t("TUN Mode automatically disabled due to service unavailable"),
showNotice.info(
"settings.sections.system.notifications.tunMode.autoDisabled",
);
})
.catch((err) => {
console.error("[useVerge] 自动关闭TUN模式失败:", err);
showNotice("error", t("Failed to disable TUN Mode automatically"));
showNotice.error(
"settings.sections.system.notifications.tunMode.autoDisableFailed",
);
})
.finally(() => {
const tid = setTimeout(() => {
@@ -84,7 +83,7 @@ export function useSystemState() {
}, 1000);
});
}
}, [enable_tun_mode, isTunModeAvailable, patchVerge, isLoading, t]);
}, [enable_tun_mode, isTunModeAvailable, patchVerge, isLoading]);
return {
runningMode: systemState.runningMode,
+10 -12
View File
@@ -1,4 +1,3 @@
import { t } from "i18next";
import { useCallback } from "react";
import { installService, restartCore } from "@/services/cmds";
@@ -8,18 +7,17 @@ import { useSystemState } from "./use-system-state";
const executeWithErrorHandling = async (
operation: () => Promise<void>,
loadingMessage: string,
successMessage?: string,
loadingKey: string,
successKey?: string,
) => {
try {
showNotice("info", t(loadingMessage));
showNotice.info(loadingKey);
await operation();
if (successMessage) {
showNotice("success", t(successMessage));
if (successKey) {
showNotice.success(successKey);
}
} catch (err) {
const msg = (err as Error)?.message || String(err);
showNotice("error", msg);
showNotice.error(err);
throw err;
}
};
@@ -30,14 +28,14 @@ export const useServiceInstaller = () => {
const installServiceAndRestartCore = useCallback(async () => {
await executeWithErrorHandling(
() => installService(),
"Installing Service...",
"Service Installed Successfully",
"settings.statuses.clashService.installing",
"settings.feedback.notifications.clashService.installSuccess",
);
await executeWithErrorHandling(
() => restartCore(),
"Restarting Core...",
"Clash Core Restarted",
"settings.statuses.clash.restarting",
"settings.feedback.notifications.clash.restartSuccess",
);
await mutateSystemState();
+14 -13
View File
@@ -1,4 +1,3 @@
import { t } from "i18next";
import { useCallback } from "react";
import { restartCore, stopCore, uninstallService } from "@/services/cmds";
@@ -8,18 +7,17 @@ import { useSystemState } from "./use-system-state";
const executeWithErrorHandling = async (
operation: () => Promise<void>,
loadingMessage: string,
successMessage?: string,
loadingKey: string,
successKey?: string,
) => {
try {
showNotice("info", t(loadingMessage));
showNotice.info(loadingKey);
await operation();
if (successMessage) {
showNotice("success", t(successMessage));
if (successKey) {
showNotice.success(successKey);
}
} catch (err) {
const msg = (err as Error)?.message || String(err);
showNotice("error", msg);
showNotice.error(err);
throw err;
}
};
@@ -29,18 +27,21 @@ export const useServiceUninstaller = () => {
const uninstallServiceAndRestartCore = useCallback(async () => {
try {
await executeWithErrorHandling(() => stopCore(), "Stopping Core...");
await executeWithErrorHandling(
() => stopCore(),
"settings.statuses.clash.stopping",
);
await executeWithErrorHandling(
() => uninstallService(),
"Uninstalling Service...",
"Service Uninstalled Successfully",
"settings.statuses.clashService.uninstalling",
"settings.feedback.notifications.clashService.uninstallSuccess",
);
} catch (ignore) {
} finally {
await executeWithErrorHandling(
() => restartCore(),
"Restarting Core...",
"Clash Core Restarted",
"settings.statuses.clash.restarting",
"settings.feedback.notifications.clash.restartSuccess",
);
await mutateSystemState();
}
-463
View File
@@ -1,463 +0,0 @@
{
"millis": "ميلي ثانية",
"seconds": "ثواني",
"mins": "دقائق",
"Back": "رجوع",
"Close": "إغلاق",
"Cancel": "إلغاء",
"Confirm": "تأكيد",
"Maximize": "تكبير",
"Minimize": "تصغير",
"Format document": "تنسيق المستند",
"Empty": "فارغ",
"New": "جديد",
"Edit": "تعديل",
"Save": "حفظ",
"Delete": "حذف",
"Enable": "تمكين",
"Disable": "تعطيل",
"Label-Proxies": "الوكلاء",
"Label-Profiles": "الملفات الشخصية",
"Label-Connections": "الاتصالات",
"Label-Rules": "القواعد",
"Label-Logs": "السجلات",
"Label-Settings": "الإعدادات",
"Proxies": "الوكلاء",
"Proxy Groups": "مجموعات الوكلاء",
"Proxy Provider": "مزود الوكيل",
"Update All": "تحديث الكل",
"Update At": "التحديث عند",
"rule": "قاعدة",
"global": "عالمي",
"direct": "مباشر",
"Chain Proxy": "🔗 بروكسي السلسلة",
"script": "سكريبت",
"locate": "الموقع",
"Delay check": "فحص التأخير",
"Sort by default": "الترتيب الافتراضي",
"Sort by delay": "الترتيب حسب التأخير",
"Sort by name": "الترتيب حسب الاسم",
"Delay check URL": "رابط فحص التأخير",
"Delay check to cancel fixed": "فحص التأخير لإلغاء الثابت",
"Proxy basic": "إعدادات الوكيل الأساسية",
"Proxy detail": "تفاصيل الوكيل",
"Profiles": "الملفات الشخصية",
"Update All Profiles": "تحديث جميع الملفات الشخصية",
"View Runtime Config": "عرض تكوين وقت التشغيل",
"Reactivate Profiles": "إعادة تنشيط الملفات الشخصية",
"Paste": "لصق",
"Profile URL": "رابط الملف الشخصي",
"Import": "استيراد",
"From": "من",
"Update Time": "وقت التحديث",
"Used / Total": "المستخدم / الإجمالي",
"Expire Time": "وقت الانتهاء",
"Create Profile": "إنشاء ملف شخصي",
"Edit Profile": "تعديل الملف الشخصي",
"Edit Proxies": "تعديل الوكلاء",
"Use newlines for multiple uri": "استخدم أسطرًا جديدة لعدّة عناوين URI (يدعم التشفير Base64)",
"Edit Rules": "تعديل القواعد",
"Rule Type": "نوع القاعدة",
"Rule Content": "محتوى القاعدة",
"Proxy Policy": "سياسة الوكيل",
"No Resolve": "لا يوجد حل",
"Prepend Rule": "إضافة قاعدة في البداية",
"Append Rule": "إضافة قاعدة في النهاية",
"Prepend Group": "إضافة مجموعة في البداية",
"Append Group": "إضافة مجموعة في النهاية",
"Prepend Proxy": "إضافة وكيل في البداية",
"Append Proxy": "إضافة وكيل في النهاية",
"Rule Condition Required": "شرط القاعدة مطلوب",
"Invalid Rule": "قاعدة غير صالحة",
"Advanced": "متقدم",
"Visualization": "تصور",
"DOMAIN": "مطابقة اسم المجال الكامل",
"DOMAIN-SUFFIX": "مطابقة لاحقة المجال",
"DOMAIN-KEYWORD": "مطابقة كلمة مفتاحية في المجال",
"DOMAIN-REGEX": "مطابقة المجال باستخدام التعبيرات العادية",
"GEOSITE": "مطابقة المجالات ضمن Geosite",
"GEOIP": "مطابقة رمز البلد لعنوان IP",
"SRC-GEOIP": "مطابقة رمز البلد لعنوان IP المصدر",
"IP-ASN": "مطابقة ASN لعنوان IP",
"SRC-IP-ASN": "مطابقة ASN لعنوان IP المصدر",
"IP-CIDR": "مطابقة نطاق عنوان IP",
"IP-CIDR6": "مطابقة نطاق عناوين IPv6",
"SRC-IP-CIDR": "مطابقة نطاق عنوان IP المصدر",
"IP-SUFFIX": "مطابقة لاحقة عنوان IP",
"SRC-IP-SUFFIX": "مطابقة لاحقة عنوان IP المصدر",
"SRC-PORT": "مطابقة نطاق المنفذ المصدر",
"DST-PORT": "مطابقة نطاق المنفذ الوجهة",
"IN-PORT": "مطابقة المنفذ الوارد",
"DSCP": "علامة DSCP (لـ tproxy على UDP فقط)",
"PROCESS-NAME": "مطابقة اسم العملية (اسم حزمة Android)",
"PROCESS-PATH": "مطابقة المسار الكامل للعملية",
"PROCESS-NAME-REGEX": "مطابقة اسم العملية باستخدام التعبيرات العادية (اسم حزمة Android)",
"PROCESS-PATH-REGEX": "مطابقة المسار الكامل للعملية باستخدام التعبيرات العادية",
"NETWORK": "مطابقة بروتوكول النقل (TCP/UDP)",
"UID": "مطابقة معرف المستخدم في Linux",
"IN-TYPE": "مطابقة نوع الإدخال",
"IN-USER": "مطابقة اسم المستخدم للإدخال",
"IN-NAME": "مطابقة اسم الإدخال",
"SUB-RULE": "قاعدة فرعية",
"RULE-SET": "مطابقة مجموعة القواعد",
"AND": "منطقي AND",
"OR": "منطقي OR",
"NOT": "منطقي NOT",
"MATCH": "مطابقة جميع الطلبات",
"DIRECT": "البيانات تخرج مباشرة",
"REJECT": "رفض الطلبات",
"REJECT-DROP": "تجاهل الطلبات",
"PASS": "تخطي هذه القاعدة عند المطابقة",
"Edit Groups": "تعديل مجموعات الوكلاء",
"Group Type": "نوع المجموعة",
"select": "اختيار الوكيل يدويًا",
"url-test": "اختيار الوكيل بناءً على تأخير اختبار الرابط",
"fallback": "التبديل إلى وكيل آخر عند حدوث خطأ",
"load-balance": "توزيع التحميل بين الوكلاء",
"relay": "التمرير عبر سلسلة الوكلاء المحددة",
"Group Name": "اسم المجموعة",
"Use Proxies": "استخدام الوكلاء",
"Use Provider": "استخدام المزود",
"Health Check Url": "رابط فحص الصحة",
"Expected Status": "الحالة المتوقعة",
"Interval": "الفاصل الزمني",
"Lazy": "كسول",
"Timeout": "مهلة",
"Max Failed Times": "الحد الأقصى لمحاولات الفشل",
"Interface Name": "اسم الواجهة",
"Routing Mark": "علامة التوجيه",
"Include All": "تضمين جميع الوكلاء والمزودين",
"Include All Providers": "تضمين جميع المزودين",
"Include All Proxies": "تضمين جميع الوكلاء",
"Exclude Filter": "استبعاد المرشح",
"Exclude Type": "استبعاد النوع",
"Disable UDP": "تعطيل UDP",
"Hidden": "مخفي",
"Group Name Required": "اسم المجموعة مطلوب",
"Group Name Already Exists": "اسم المجموعة موجود بالفعل",
"Extend Config": "توسيع الإعدادات",
"Extend Script": "توسيع السكربت",
"Global Merge": "دمج عالمي للإعدادات",
"Global Script": "سكريبت عالمي",
"Type": "النوع",
"Name": "الاسم",
"Descriptions": "الوصف",
"Subscription URL": "رابط الاشتراك",
"Update Interval": "فاصل التحديث",
"Choose File": "اختر ملف",
"Use System Proxy": "استخدام وكيل النظام",
"Use Clash Proxy": "استخدام وكيل Clash",
"Accept Invalid Certs (Danger)": "قبول الشهادات غير الصالحة (خطر)",
"Refresh": "تحديث",
"Home": "الصفحة الرئيسية",
"Select": "اختيار",
"Edit Info": "تعديل المعلومات",
"Edit File": "تعديل الملف",
"Open File": "فتح الملف",
"Update": "تحديث",
"Update(Proxy)": "تحديث (الوكيل)",
"Confirm deletion": "تأكيد الحذف",
"This operation is not reversible": "لا يمكن التراجع عن هذه العملية",
"Script Console": "وحدة التحكم للسكريبت",
"To Top": "إلى الأعلى",
"To End": "إلى النهاية",
"Connections": "الاتصالات",
"Table View": "عرض الجدول",
"List View": "عرض القائمة",
"Close All": "إغلاق الكل",
"Download Speed": "سرعة التنزيل",
"Upload Speed": "سرعة الرفع",
"Host": "المضيف",
"Downloaded": "تم التنزيل",
"Uploaded": "تم الرفع",
"DL Speed": "سرعة التنزيل",
"UL Speed": "سرعة الرفع",
"Chains": "السلاسل",
"Rule": "قاعدة",
"Process": "عملية",
"Time": "الوقت",
"Source": "المصدر",
"Destination": "عنوان IP الوجهة",
"DestinationPort": "ميناء الوجهة",
"Close Connection": "إغلاق الاتصال",
"Rules": "القواعد",
"Rule Provider": "مزود القواعد",
"Logs": "السجلات",
"Pause": "إيقاف مؤقت",
"Resume": "استأنف",
"Clear": "مسح",
"Test": "اختبار",
"Test All": "اختبار الكل",
"Create Test": "إنشاء اختبار",
"Edit Test": "تعديل الاختبار",
"Icon": "أيقونة",
"Test URL": "رابط الاختبار",
"Settings": "الإعدادات",
"System Setting": "إعدادات النظام",
"Tun Mode": "وضع TUN",
"TUN requires Service Mode": "يتطلب وضع TUN خدمة",
"Install Service": "تثبيت الخدمة ",
"Reset to Default": "إعادة تعيين إلى الافتراضي",
"Tun Mode Info": "وضع TUN (بطاقة شبكة افتراضية): يلتقط كل حركة المرور في النظام. عند تمكينه، لا حاجة لتفعيل وكيل النظام.",
"Stack": "مكدس TUN",
"System and Mixed Can Only be Used in Service Mode": "لا يمكن استخدام النظام والمختلط إلا في وضع الخدمة",
"Device": "اسم الجهاز",
"Auto Route": "توجيه تلقائي",
"Strict Route": "توجيه صارم",
"Auto Detect Interface": "الكشف التلقائي عن الواجهة",
"DNS Hijack": "اختطاف DNS",
"MTU": "وحدة الإرسال القصوى",
"Service Mode": "وضع الخدمة",
"Service Mode Info": "يرجى تثبيت وضع الخدمة قبل تمكين وضع TUN. يمكن للعمليات الأساسية التي يبدأها وضع الخدمة الحصول على إذن لتثبيت بطاقة الشبكة الافتراضية (TUN).",
"Current State": "الحالة الحالية",
"pending": "معلق",
"installed": "مثبت",
"uninstall": "إلغاء التثبيت",
"active": "نشط",
"unknown": "غير معروف",
"Information: Please make sure that the Clash Verge Service is installed and enabled": "معلومة: يرجى التأكد من تثبيت وتشغيل خدمة Clash Verge",
"Install": "تثبيت",
"Uninstall": "إلغاء التثبيت",
"Disable Service Mode": "تعطيل وضع الخدمة",
"System Proxy": "وكيل النظام",
"System Proxy Info": "عند التمكين، سيتم تعديل إعدادات الوكيل في نظام التشغيل. إذا فشل التمكين، فقم بتعديل إعدادات الوكيل في النظام يدويًا.",
"System Proxy Setting": "إعداد وكيل النظام",
"Current System Proxy": "الوكيل الحالي للنظام",
"Enable status": "حالة التمكين:",
"Enabled": "ممكّن",
"Disabled": "معطّل",
"Server Addr": "عنوان الخادم:",
"Proxy Host": "مضيف الوكيل",
"Invalid Proxy Host Format": "تنسيق مضيف الوكيل غير صالح",
"Not available": "غير متوفر",
"Proxy Guard": "حماية الوكيل",
"Proxy Guard Info": "عند التمكين، يمنع برامج أخرى من تعديل إعدادات وكيل النظام",
"Guard Duration": "مدة الحماية",
"Always use Default Bypass": "استخدام التخطي الافتراضي دائمًا",
"Proxy Bypass": "إعدادات تخطي الوكيل:",
"Bypass": "تخطي:",
"Use PAC Mode": "استخدام وضع PAC",
"PAC Script Content": "محتوى سكريبت PAC",
"PAC URL": "رابط PAC:",
"Auto Launch": "إطلاق تلقائي",
"Silent Start": "بدء صامت",
"Silent Start Info": "بدء البرنامج في الخلفية دون عرض الواجهة",
"TG Channel": "قناة تيليجرام",
"Manual": "دليل",
"Github Repo": "مستودع Github",
"Clash Setting": "إعدادات Clash",
"Allow Lan": "السماح بالشبكة المحلية",
"Network Interface": "واجهة الشبكة",
"Ip Address": "عنوان IP",
"Mac Address": "عنوان MAC",
"IPv6": "IPv6",
"Unified Delay": "تأخير موحد",
"Unified Delay Info": "عند تفعيل التأخير الموحد، سيتم إجراء اختبارين للتأخير لتقليل الفروقات الناتجة عن مفاوضات الاتصال",
"Log Level": "مستوى السجلات",
"Port Config": "تكوين المنافذ",
"Random Port": "منفذ عشوائي",
"Mixed Port": "منفذ مختلط",
"Socks Port": "منفذ SOCKS",
"Http Port": "منفذ HTTP(S)",
"Redir Port": "منفذ إعادة التوجيه",
"Tproxy Port": "منفذ Tproxy",
"External": "خارجي",
"External Controller": "وحدة التحكم الخارجية",
"Core Secret": "المفتاح السري للنواة",
"Recommended": "موصى به",
"Open URL": "فتح الرابط",
"Replace host, port, secret with %host, %port, %secret": "استبدل المضيف والمنفذ والمفتاح بـ %host و%port و%secret",
"Support %host, %port, %secret": "يدعم %host و%port و%secret",
"Clash Core": "نواة Clash",
"Upgrade": "ترقية",
"Restart": "إعادة التشغيل",
"Release Version": "إصدار مستقر",
"Alpha Version": "إصدار ألفا",
"Please Enable Service Mode": "يرجى تثبيت وتفعيل وضع الخدمة أولاً",
"Please enter your root password": "يرجى إدخال كلمة مرور الرووت",
"Grant": "منح الصلاحيات",
"Open UWP tool": "فتح أداة UWP",
"Open UWP tool Info": "منذ نظام ويندوز 8، يتم تقييد تطبيقات UWP من الوصول المباشر إلى المضيف المحلي. هذه الأداة تتيح تجاوز هذا التقييد",
"Update GeoData": "تحديث البيانات الجغرافية",
"Verge Basic Setting": "الإعدادات الأساسية Verge",
"Verge Advanced Setting": "الإعدادات الأساسية Verge",
"Language": "اللغة",
"Theme Mode": "وضع السمة",
"theme.light": "سمة فاتحة",
"theme.dark": "سمة داكنة",
"theme.system": "سمة النظام",
"Tray Click Event": "حدث النقر على الأيقونة في شريط المهام",
"Show Main Window": "إظهار النافذة الرئيسية",
"Copy Env Type": "نسخ نوع البيئة",
"Copy Success": "تم النسخ بنجاح",
"Start Page": "صفحة البدء",
"Startup Script": "سكريبت بدء التشغيل",
"Browse": "استعراض",
"Theme Setting": "إعدادات السمة",
"Primary Color": "اللون الأساسي",
"Secondary Color": "اللون الثانوي",
"Primary Text": "النص الأساسي",
"Secondary Text": "النص الثانوي",
"Info Color": "لون المعلومات",
"Warning Color": "لون التحذير",
"Error Color": "لون الخطأ",
"Success Color": "لون النجاح",
"Font Family": "عائلة الخط",
"CSS Injection": "حقن CSS",
"Layout Setting": "إعدادات التخطيط",
"Traffic Graph": "مخطط حركة المرور",
"Memory Usage": "استهلاك الذاكرة",
"Memory Cleanup": "انقر لتنظيف الذاكرة",
"Proxy Group Icon": "أيقونة مجموعة الوكلاء",
"Nav Icon": "أيقونة التنقل",
"Monochrome": "أحادي اللون",
"Colorful": "ملون",
"Tray Icon": "أيقونة شريط المهام",
"Common Tray Icon": "أيقونة شريط مهام عامة",
"System Proxy Tray Icon": "أيقونة شريط المهام لوكيل النظام",
"Tun Tray Icon": "أيقونة شريط المهام لـ TUN",
"Miscellaneous": "متفرقات",
"App Log Level": "مستوى سجلات التطبيق",
"Auto Close Connections": "إغلاق الاتصالات تلقائيًا",
"Auto Close Connections Info": "إنهاء الاتصالات القائمة عند تغيير اختيار مجموعة الوكيل أو وضع الوكيل",
"Auto Check Update": "فحص التحديث تلقائيًا",
"Enable Builtin Enhanced": "تفعيل التحسين المدمج",
"Enable Builtin Enhanced Info": "معالجة توافق ملف التكوين",
"Proxy Layout Columns": "أعمدة عرض الوكيل",
"Auto Columns": "أعمدة تلقائية",
"Auto Log Clean": "تنظيف السجلات تلقائيًا",
"Never Clean": "عدم التنظيف أبدًا",
"Retain _n Days": "الاحتفاظ لمدة {{n}} يومًا",
"Auto Delay Detection": "اكتشاف التأخير التلقائي",
"Auto Delay Detection Info": "يختبر زمن استجابة العقدة الحالية على نحو دوري في الخلفية",
"Default Latency Test": "اختبار التأخير الافتراضي",
"Default Latency Test Info": "يُستخدم فقط لاختبار طلب HTTP العميل. لن يؤثر على ملف التكوين",
"Default Latency Timeout": "مهلة التأخير الافتراضية",
"Hotkey Setting": "إعدادات الاختصارات",
"Enable Global Hotkey": "تمكين مفتاح التشغيل السريع العالمي",
"open_or_close_dashboard": "فتح/إغلاق لوحة التحكم",
"clash_mode_rule": "وضع القواعد",
"clash_mode_global": "الوضع العالمي",
"clash_mode_direct": "الوضع المباشر",
"toggle_system_proxy": "تفعيل/تعطيل وكيل النظام",
"toggle_tun_mode": "تفعيل/تعطيل وضع TUN",
"Backup Setting": "إعداد النسخ الاحتياطي",
"Runtime Config": "تكوين وقت التشغيل",
"Open Conf Dir": "فتح مجلد التكوين",
"Open Conf Dir Info": "إذا عمل البرنامج بشكل غير طبيعي، قم بالنسخ الاحتياطي ثم حذف جميع الملفات في هذا المجلد ثم أعد تشغيل البرنامج",
"Open Core Dir": "فتح مجلد النواة",
"Open Logs Dir": "فتح مجلد السجلات",
"Check for Updates": "التحقق من وجود تحديثات",
"Go to Release Page": "الانتقال إلى صفحة الإصدارات",
"Portable Updater Error": "الإصدار المحمول لا يدعم التحديث داخل التطبيق. يرجى التنزيل والاستبدال يدويًا",
"Break Change Update Error": "هذا الإصدار هو تحديث رئيسي ولا يدعم التحديث داخل التطبيق. يرجى إلغاء التثبيت وتنزيل الإصدار الجديد وتثبيته يدويًا",
"Open Dev Tools": "أدوات المطور",
"Exit": "خروج",
"Verge Version": "إصدار Verge",
"ReadOnly": "للقراءة فقط",
"ReadOnlyMessage": "لا يمكن التعديل في محرر القراءة فقط",
"Filter": "تصفية",
"Filter conditions": "شروط التصفية",
"Match Case": "مطابقة الحالة",
"Match Whole Word": "مطابقة الكلمة بأكملها",
"Use Regular Expression": "استخدام التعبيرات العادية",
"Profile Imported Successfully": "تم استيراد الملف الشخصي بنجاح",
"Profile Switched": "تم التبديل إلى الملف الشخصي",
"Profile Reactivated": "تم إعادة تنشيط الملف الشخصي",
"Only YAML Files Supported": "لا يتم دعم سوى ملفات YAML",
"Settings Applied": "تم تطبيق الإعدادات",
"Installing Service...": "جاري تثبيت الخدمة...",
"Service Installed Successfully": "تم تثبيت الخدمة بنجاح",
"Service Uninstalled Successfully": "تم إلغاء تثبيت الخدمة بنجاح",
"Proxy Daemon Duration Cannot be Less than 1 Second": "لا يمكن أن تقل مدة خادم الوكيل عن ثانية واحدة",
"Invalid Bypass Format": "تنسيق التخطي غير صالح",
"Clash Port Modified": "تم تعديل منفذ Clash",
"Port Conflict": "تعارض في المنفذ",
"Restart Application to Apply Modifications": "أعد تشغيل التطبيق لتطبيق التعديلات",
"External Controller Address Modified": "تم تعديل عنوان وحدة التحكم الخارجية",
"Permissions Granted Successfully for _clash Core": "تم منح الأذونات بنجاح لـ {{core}} Core",
"Core Version Updated": "تم تحديث إصدار النواة",
"Clash Core Restarted": "تم إعادة تشغيل نواة Clash",
"GeoData Updated": "تم تحديث البيانات الجغرافية",
"Currently on the Latest Version": "أنت على أحدث إصدار حاليًا",
"Import Subscription Successful": "تم استيراد الاشتراك بنجاح",
"WebDAV Server URL": "عنوان خادم WebDAV",
"Username": "اسم المستخدم",
"Password": "كلمة المرور",
"Backup": "نسخ احتياطي",
"Filename": "اسم الملف",
"Actions": "الإجراءات",
"Restore": "استعادة",
"No Backups": "لا توجد نسخ احتياطية متاحة",
"WebDAV URL Required": "لا يمكن ترك رابط WebDAV فارغًا",
"Invalid WebDAV URL": "تنسيق رابط WebDAV غير صالح",
"Username Required": "لا يمكن ترك اسم المستخدم فارغًا",
"Password Required": "لا يمكن ترك كلمة المرور فارغة",
"Failed to Fetch Backups": "فشل في جلب ملفات النسخ الاحتياطي",
"WebDAV Config Saved": "تم حفظ إعدادات WebDAV بنجاح",
"WebDAV Config Save Failed": "فشل حفظ إعدادات WebDAV: {{error}}",
"Backup Created": "تم إنشاء النسخة الاحتياطية بنجاح",
"Backup Failed": "فشل في النسخ الاحتياطي: {{error}}",
"Delete Backup": "حذف النسخة الاحتياطية",
"Restore Backup": "استعادة النسخة الاحتياطية",
"Backup Time": "وقت النسخ الاحتياطي",
"Confirm to delete this backup file?": "هل تريد بالتأكيد حذف ملف النسخة الاحتياطية هذا؟",
"Confirm to restore this backup file?": "هل تريد بالتأكيد استعادة ملف النسخة الاحتياطية هذا؟",
"Restore Success, App will restart in 1s": "تمت الاستعادة بنجاح، سيعاد تشغيل التطبيق خلال ثانية واحدة",
"Failed to fetch backup files": "فشل في جلب ملفات النسخ الاحتياطي",
"Profile": "الملف الشخصي",
"Help": "مساعدة",
"About": "حول",
"Theme": "السمة",
"Main Window": "النافذة الرئيسية",
"Group Icon": "أيقونة المجموعة",
"Menu Icon": "أيقونة القائمة",
"PAC File": "ملف PAC",
"Web UI": "واجهة الويب",
"Hotkeys": "اختصارات لوحة المفاتيح",
"Verge Mixed Port": "منفذ Verge المختلط",
"Verge Socks Port": "منفذ Verge SOCKS",
"Verge Redir Port": "منفذ إعادة التوجيه لـ Verge",
"Verge Tproxy Port": "منفذ Tproxy لـ Verge",
"Verge Port": "منفذ Verge",
"Verge HTTP Enabled": "تمكين Verge HTTP",
"WebDAV URL": "رابط WebDAV",
"WebDAV Username": "اسم المستخدم لـ WebDAV",
"WebDAV Password": "كلمة مرور WebDAV",
"Dashboard": "لوحة التحكم",
"Restart App": "إعادة تشغيل التطبيق",
"Restart Clash Core": "إعادة تشغيل نواة Clash",
"TUN Mode": "وضع TUN",
"Copy Env": "نسخ البيئة",
"Conf Dir": "مجلد الإعدادات",
"Core Dir": "مجلد النواة",
"Logs Dir": "مجلد السجلات",
"Open Dir": "فتح المجلد",
"More": "المزيد",
"Rule Mode": "وضع القواعد",
"Global Mode": "الوضع العالمي",
"Direct Mode": "الوضع المباشر",
"Enable Tray Speed": "تفعيل سرعة التراي",
"LightWeight Mode": "وضع الأداء الخفيف",
"LightWeight Mode Info": "إيقاف الواجهة الرسومية والإبقاء على تشغيل النواة",
"Config Validation Failed": "فشل التحقق من تكوين الاشتراك، يرجى فحص ملف التكوين، تم التراجع عن التغييرات، تفاصيل الخطأ:",
"Boot Config Validation Failed": "فشل التحقق من التكوين عند الإقلاع، تم استخدام التكوين الافتراضي، يرجى فحص ملف التكوين، تفاصيل الخطأ:",
"Core Change Config Validation Failed": "فشل التحقق من التكوين عند تغيير النواة، تم استخدام التكوين الافتراضي، يرجى فحص ملف التكوين، تفاصيل الخطأ:",
"Config Validation Process Terminated": "تم إنهاء عملية التحقق",
"Script Syntax Error": "خطأ في بناء جملة السكريبت، تم التراجع عن التغييرات",
"Script Missing Main": "خطأ في السكريبت، تم التراجع عن التغييرات",
"File Not Found": "الملف غير موجود، تم التراجع عن التغييرات",
"Script File Error": "خطأ في ملف السكريبت، تم التراجع عن التغييرات",
"Core Changed Successfully": "تم تغيير النواة بنجاح",
"Failed to Change Core": "فشل تغيير النواة",
"Service Administrator Prompt": "يتطلب Clash Verge امتيازات المسؤول لإعادة تثبيت خدمة النظام",
"Auto Close Connection": "إغلاق الاتصال تلقائيًا",
"Default": "افتراضي",
"Enable Built-in Enhanced": "تفعيل التحسين المدمج",
"Enable Random Port": "تفعيل المنفذ العشوائي",
"Label-Test": "اختبار",
"Proxy Layout Column": "عمود عرض الوكيل",
"System Proxy Bypass": "تخطي وكيل النظام",
"Test List": "قائمة الاختبارات",
"Verge Setting": "إعدادات Verge"
}
+28
View File
@@ -0,0 +1,28 @@
{
"page": {
"title": "الاتصالات"
},
"components": {
"fields": {
"host": "المضيف",
"dlSpeed": "سرعة التنزيل",
"ulSpeed": "سرعة الرفع",
"chains": "السلاسل",
"rule": "قاعدة",
"process": "عملية",
"time": "الوقت",
"source": "المصدر",
"destination": "عنوان IP الوجهة",
"destinationPort": "ميناء الوجهة",
"type": "النوع"
},
"order": {
"default": "Default",
"uploadSpeed": "سرعة الرفع",
"downloadSpeed": "سرعة التنزيل"
},
"actions": {
"closeConnection": "إغلاق الاتصال"
}
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"page": {
"tooltips": {
"lightweightMode": "وضع الأداء الخفيف",
"manual": "دليل",
"settings": "Home Settings"
},
"cards": {
"trafficStats": "Traffic Stats",
"networkSettings": "Network Settings",
"proxyMode": "Proxy Mode"
},
"settings": {
"cards": {
"profile": "Profile Card",
"currentProxy": "Current Proxy Card",
"network": "Network Settings Card",
"proxyMode": "Proxy Mode Card",
"traffic": "Traffic Stats Card",
"tests": "Website Tests Card",
"ip": "IP Information Card",
"clashInfo": "Clash Info Cards",
"systemInfo": "System Info Cards"
},
"title": "Home Settings"
},
"title": "Home"
},
"components": {
"proxyTun": {
"status": {
"systemProxyEnabled": "System proxy is enabled, your applications will access the network through the proxy",
"systemProxyDisabled": "System proxy is disabled, it is recommended for most users to turn on this option",
"tunModeServiceRequired": "TUN mode requires service mode, please install the service first",
"tunModeEnabled": "TUN mode is enabled, applications will access the network through the virtual network card",
"tunModeDisabled": "TUN mode is disabled, suitable for special applications"
},
"tooltips": {
"systemProxy": "عند التمكين، سيتم تعديل إعدادات الوكيل في نظام التشغيل. إذا فشل التمكين، فقم بتعديل إعدادات الوكيل في النظام يدويًا.",
"tunMode": "TUN mode can take over all application traffic, suitable for special applications that do not follow the system proxy settings"
}
},
"clashInfo": {
"title": "Clash Info",
"fields": {
"coreVersion": "Core Version",
"systemProxyAddress": "System Proxy Address",
"mixedPort": "Mixed Port",
"uptime": "Uptime",
"rulesCount": "Rules Count"
}
},
"systemInfo": {
"title": "System Info",
"fields": {
"osInfo": "OS Info",
"autoLaunch": "إطلاق تلقائي",
"runningMode": "Running Mode",
"lastCheckUpdate": "Last Check Update",
"vergeVersion": "إصدار Verge"
},
"actions": {
"settings": "الإعدادات"
},
"tooltips": {
"autoLaunchAdmin": "Administrator mode may not support auto launch"
},
"badges": {
"adminMode": "Administrator Mode",
"serviceMode": "وضع الخدمة",
"sidecarMode": "User Mode",
"adminServiceMode": "Admin + Service Mode"
}
},
"ipInfo": {
"title": "IP Information",
"labels": {
"ip": "IP",
"asn": "ASN",
"isp": "ISP",
"org": "ORG",
"location": "Location",
"timezone": "Timezone",
"autoRefresh": "Auto refresh",
"unknown": "Unknown"
},
"errors": {
"load": "فشل الحصول على معلومات الـ IP"
}
},
"currentProxy": {
"title": "Current Node",
"actions": {
"refreshDelay": "فحص التأخير"
},
"labels": {
"globalMode": "الوضع العالمي",
"directMode": "الوضع المباشر",
"group": "Group",
"proxy": "Proxy",
"noActiveNode": "No active proxy node"
}
},
"tests": {
"title": "Website Tests"
},
"traffic": {
"metrics": {
"uploadSpeed": "سرعة الرفع",
"downloadSpeed": "سرعة التنزيل",
"activeConnections": "Active Connections",
"memoryUsage": "استهلاك الذاكرة"
},
"legends": {
"upload": "Upload",
"download": "Download"
},
"patterns": {
"minutes": "{{time}} Minutes"
}
},
"clashMode": {
"errors": {
"communication": "Core communication error"
},
"labels": {
"rule": "وضع القواعد",
"global": "الوضع العالمي",
"direct": "الوضع المباشر"
},
"descriptions": {
"rule": "Automatically choose proxies according to the rule set.",
"global": "Forward all network requests through the selected proxy.",
"direct": "Bypass the proxy and connect to the internet directly."
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import connections from "./connections.json";
import home from "./home.json";
import layout from "./layout.json";
import logs from "./logs.json";
import profiles from "./profiles.json";
import proxies from "./proxies.json";
import rules from "./rules.json";
import settings from "./settings.json";
import shared from "./shared.json";
import tests from "./tests.json";
const resources = {
connections: connections,
home: home,
layout: layout,
logs: logs,
profiles: profiles,
proxies: proxies,
rules: rules,
settings: settings,
shared: shared,
tests: tests,
};
export default resources;
+21
View File
@@ -0,0 +1,21 @@
{
"components": {
"navigation": {
"tabs": {
"home": "Home",
"proxies": "الوكلاء",
"profiles": "الملفات الشخصية",
"connections": "الاتصالات",
"rules": "القواعد",
"logs": "السجلات",
"unlock": "Test",
"settings": "الإعدادات"
},
"menu": {
"reorderMode": "Menu reorder mode",
"unlock": "Unlock menu order",
"lock": "Lock menu order"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"page": {
"title": "السجلات"
}
}
+182
View File
@@ -0,0 +1,182 @@
{
"page": {
"actions": {
"updateAll": "تحديث جميع الملفات الشخصية",
"viewRuntimeConfig": "عرض تكوين وقت التشغيل",
"reactivate": "إعادة تنشيط الملفات الشخصية",
"import": "استيراد"
},
"batch": {
"actions": {
"delete": "Delete Selected Profiles",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"done": "Done"
},
"summary": {
"selected": "Selected",
"items": "items"
},
"title": "Batch Operations"
},
"importForm": {
"placeholder": "رابط الملف الشخصي",
"actions": {
"paste": "لصق"
}
},
"feedback": {
"errors": {
"invalidUrl": "Invalid profile URL. Please enter a URL starting with http:// or https://",
"onlyYaml": "لا يتم دعم سوى ملفات YAML"
},
"notifications": {
"importRetry": "Import failed, retrying with Clash proxy...",
"importFail": "Import failed even with Clash proxy",
"importNeedsRefresh": "Profile imported but may need manual refresh",
"importSuccess": "Profile imported successfully, please restart if not visible",
"profileSwitched": "تم التبديل إلى الملف الشخصي",
"profileReactivated": "تم إعادة تنشيط الملف الشخصي",
"switchInterrupted": "Profile switch interrupted by new selection",
"batchDeleted": "Selected profiles deleted successfully"
},
"notices": {
"forceRefreshCompleted": "Force refresh completed",
"emergencyRefreshFailed": "Emergency refresh failed: {{message}}"
}
},
"title": "الملفات الشخصية"
},
"components": {
"card": {
"labels": {
"clickToImport": "Click to import subscription"
}
},
"fileInput": {
"chooseFile": "اختر ملف"
},
"menu": {
"home": "Home",
"select": "اختيار",
"editInfo": "تعديل المعلومات",
"editFile": "تعديل الملف",
"editRules": "تعديل القواعد",
"editProxies": "تعديل الوكلاء",
"editGroups": "تعديل مجموعات الوكلاء",
"extendConfig": "توسيع الإعدادات",
"extendScript": "توسيع السكربت",
"openFile": "فتح الملف",
"update": "تحديث",
"updateViaProxy": "Update via proxy"
},
"more": {
"global": {
"merge": "Global Merge",
"script": "Global Script"
},
"chips": {
"merge": "Merge",
"script": "Script"
}
},
"profileItem": {
"tooltips": {
"showLast": "Click to show last update time",
"showNext": "Click to show next update"
},
"status": {
"lastUpdateFailed": "Last Update failed",
"nextUp": "Next Up",
"noSchedule": "No schedule",
"unknown": "Unknown",
"autoUpdateDisabled": "Auto update disabled"
}
}
},
"modals": {
"profileForm": {
"title": {
"create": "إنشاء ملف شخصي",
"edit": "تعديل الملف الشخصي"
},
"fields": {
"type": "النوع",
"description": "الوصف",
"subscriptionUrl": "رابط الاشتراك",
"httpTimeout": "HTTP Request Timeout",
"updateInterval": "فاصل التحديث",
"useSystemProxy": "استخدام وكيل النظام",
"useClashProxy": "استخدام وكيل Clash",
"acceptInvalidCerts": "قبول الشهادات غير الصالحة (خطر)",
"allowAutoUpdate": "Allow Auto Update"
},
"feedback": {
"notifications": {
"creationRetry": "Profile creation failed, retrying with Clash proxy...",
"creationSuccess": "Profile creation succeeded with Clash proxy"
}
}
},
"proxiesEditor": {
"title": "تعديل الوكلاء",
"placeholders": {
"multiUri": "استخدم أسطرًا جديدة لعدّة عناوين URI (يدعم التشفير Base64)"
},
"actions": {
"prepend": "إضافة وكيل في البداية",
"append": "إضافة وكيل في النهاية"
}
},
"groupsEditor": {
"title": "تعديل مجموعات الوكلاء",
"errors": {
"nameRequired": "اسم المجموعة مطلوب",
"nameExists": "اسم المجموعة موجود بالفعل"
},
"fields": {
"type": "نوع المجموعة",
"name": "اسم المجموعة",
"icon": "أيقونة مجموعة الوكلاء",
"proxies": "استخدام الوكلاء",
"provider": "استخدام المزود",
"healthCheckUrl": "رابط فحص الصحة",
"expectedStatus": "الحالة المتوقعة",
"interval": "الفاصل الزمني",
"maxFailedTimes": "الحد الأقصى لمحاولات الفشل",
"interfaceName": "اسم الواجهة",
"routingMark": "علامة التوجيه",
"filter": "تصفية",
"excludeFilter": "استبعاد المرشح",
"excludeType": "استبعاد النوع",
"includeAll": "تضمين جميع الوكلاء والمزودين",
"includeAllProxies": "تضمين جميع الوكلاء",
"includeAllProviders": "تضمين جميع المزودين"
},
"toggles": {
"lazy": "كسول",
"disableUdp": "تعطيل UDP",
"hidden": "مخفي"
},
"actions": {
"prepend": "إضافة مجموعة في البداية",
"append": "إضافة مجموعة في النهاية"
}
},
"editor": {
"actions": {
"format": "تنسيق المستند"
},
"messages": {
"readOnly": "لا يمكن التعديل في محرر القراءة فقط"
}
},
"confirmDelete": {
"title": "تأكيد الحذف",
"message": "لا يمكن التراجع عن هذه العملية"
},
"logViewer": {
"title": "وحدة التحكم للسكريبت"
}
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"page": {
"modes": {
"rule": "Rule",
"global": "Global",
"direct": "Direct"
},
"actions": {
"toggleChain": "🔗 بروكسي السلسلة",
"connect": "Connect",
"disconnect": "Disconnect",
"connecting": "Connecting...",
"clearChainConfig": "Delete Chain Config"
},
"provider": {
"title": "مزود الوكيل",
"actions": {
"updateAll": "تحديث الكل",
"update": "تحديث"
}
},
"rules": {
"title": "Proxy Rules",
"select": "Select Rules"
},
"labels": {
"proxyCount": "Proxy Count",
"delayCheckReset": "فحص التأخير لإلغاء الثابت"
},
"tooltips": {
"locate": "الموقع",
"delayCheck": "فحص التأخير",
"sortDefault": "الترتيب الافتراضي",
"sortDelay": "الترتيب حسب التأخير",
"sortName": "الترتيب حسب الاسم",
"delayCheckUrl": "رابط فحص التأخير",
"showBasic": "إعدادات الوكيل الأساسية",
"showDetail": "تفاصيل الوكيل",
"filter": "تصفية"
},
"placeholders": {
"delayCheckUrl": "رابط فحص التأخير"
},
"chain": {
"header": "Chain Proxy Config",
"empty": "No proxy chain configured",
"instruction": "Click nodes in order to add to proxy chain",
"minimumNodes": "Chain proxy requires at least 2 nodes",
"minimumNodesHint": "Chain proxy requires at least 2 nodes. Please add one more node.",
"connectFailed": "Failed to connect to proxy chain",
"disconnectFailed": "Failed to disconnect from proxy chain",
"duplicateNode": "Proxy node already exists in chain"
},
"messages": {
"directMode": "الوضع المباشر"
},
"title": {
"default": "مجموعات الوكلاء",
"chainMode": "Proxy Chain Mode"
}
},
"feedback": {
"notifications": {
"provider": {
"updateSuccess": "{{name}} updated successfully",
"updateFailed": "Failed to update {{name}}: {{message}}",
"genericError": "Update failed: {{message}}",
"none": "No providers available to update",
"allUpdated": "All providers updated successfully"
}
}
},
"components": {
"enums": {
"strategies": {
"select": "اختيار الوكيل يدويًا",
"url-test": "اختيار الوكيل بناءً على تأخير اختبار الرابط",
"fallback": "التبديل إلى وكيل آخر عند حدوث خطأ",
"load-balance": "توزيع التحميل بين الوكلاء",
"relay": "التمرير عبر سلسلة الوكلاء المحددة"
},
"policies": {
"DIRECT": "البيانات تخرج مباشرة",
"REJECT": "رفض الطلبات",
"REJECT-DROP": "تجاهل الطلبات",
"PASS": "تخطي هذه القاعدة عند المطابقة"
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
{
"page": {
"provider": {
"trigger": "مزود القواعد",
"dialogTitle": "مزود القواعد",
"actions": {
"updateAll": "تحديث الكل",
"update": "تحديث"
}
},
"title": "القواعد"
},
"feedback": {
"notifications": {
"provider": {
"updateSuccess": "{{name}} updated successfully",
"updateFailed": "Failed to update {{name}}: {{message}}",
"genericError": "Update failed: {{message}}",
"none": "No providers available to update",
"allUpdated": "All providers updated successfully"
}
}
},
"modals": {
"editor": {
"form": {
"labels": {
"type": "نوع القاعدة",
"content": "محتوى القاعدة",
"proxyPolicy": "سياسة الوكيل"
},
"toggles": {
"noResolve": "لا يوجد حل"
},
"actions": {
"prependRule": "إضافة قاعدة في البداية",
"appendRule": "إضافة قاعدة في النهاية"
},
"validation": {
"conditionRequired": "شرط القاعدة مطلوب",
"invalidRule": "قاعدة غير صالحة"
}
},
"ruleTypes": {
"DOMAIN": "مطابقة اسم المجال الكامل",
"DOMAIN-SUFFIX": "مطابقة لاحقة المجال",
"DOMAIN-KEYWORD": "مطابقة كلمة مفتاحية في المجال",
"DOMAIN-REGEX": "مطابقة المجال باستخدام التعبيرات العادية",
"GEOSITE": "مطابقة المجالات ضمن Geosite",
"GEOIP": "مطابقة رمز البلد لعنوان IP",
"SRC-GEOIP": "مطابقة رمز البلد لعنوان IP المصدر",
"IP-ASN": "مطابقة ASN لعنوان IP",
"SRC-IP-ASN": "مطابقة ASN لعنوان IP المصدر",
"IP-CIDR": "مطابقة نطاق عنوان IP",
"IP-CIDR6": "مطابقة نطاق عناوين IPv6",
"SRC-IP-CIDR": "مطابقة نطاق عنوان IP المصدر",
"IP-SUFFIX": "مطابقة لاحقة عنوان IP",
"SRC-IP-SUFFIX": "مطابقة لاحقة عنوان IP المصدر",
"SRC-PORT": "مطابقة نطاق المنفذ المصدر",
"DST-PORT": "مطابقة نطاق المنفذ الوجهة",
"IN-PORT": "مطابقة المنفذ الوارد",
"DSCP": "علامة DSCP (لـ tproxy على UDP فقط)",
"PROCESS-NAME": "مطابقة اسم العملية (اسم حزمة Android)",
"PROCESS-PATH": "مطابقة المسار الكامل للعملية",
"PROCESS-NAME-REGEX": "مطابقة اسم العملية باستخدام التعبيرات العادية (اسم حزمة Android)",
"PROCESS-PATH-REGEX": "مطابقة المسار الكامل للعملية باستخدام التعبيرات العادية",
"NETWORK": "مطابقة بروتوكول النقل (TCP/UDP)",
"UID": "مطابقة معرف المستخدم في Linux",
"IN-TYPE": "مطابقة نوع الإدخال",
"IN-USER": "مطابقة اسم المستخدم للإدخال",
"IN-NAME": "مطابقة اسم الإدخال",
"SUB-RULE": "قاعدة فرعية",
"RULE-SET": "مطابقة مجموعة القواعد",
"AND": "منطقي AND",
"OR": "منطقي OR",
"NOT": "منطقي NOT",
"MATCH": "مطابقة جميع الطلبات"
},
"title": "تعديل القواعد"
}
}
}
+573
View File
@@ -0,0 +1,573 @@
{
"page": {
"actions": {
"manual": "دليل",
"telegram": "قناة تيليجرام",
"github": "مستودع Github"
},
"title": "الإعدادات"
},
"sections": {
"system": {
"title": "إعدادات النظام",
"toggles": {
"tunMode": "وضع TUN",
"systemProxy": "وكيل النظام"
},
"tooltips": {
"autoLaunchAdmin": "Administrator mode may not support auto launch",
"silentStart": "بدء البرنامج في الخلفية دون عرض الواجهة"
},
"fields": {
"autoLaunch": "تشغيل تلقائي",
"silentStart": "تشغيل صامت"
},
"notifications": {
"tunMode": {
"autoDisabled": "TUN Mode automatically disabled due to service unavailable",
"autoDisableFailed": "Failed to disable TUN Mode automatically"
}
}
},
"proxyControl": {
"tooltips": {
"systemProxy": "عند التمكين، سيتم تعديل إعدادات الوكيل في نظام التشغيل. إذا فشل التمكين، فقم بتعديل إعدادات الوكيل في النظام يدويًا.",
"tunMode": "وضع TUN (بطاقة شبكة افتراضية): يلتقط كل حركة المرور في النظام. عند تمكينه، لا حاجة لتفعيل وكيل النظام.",
"tunUnavailable": "TUN requires Service Mode or Admin Mode"
},
"actions": {
"installService": "تثبيت الخدمة ",
"uninstallService": "Uninstall Service"
},
"fields": {
"systemProxy": "وكيل النظام",
"tunMode": "وضع TUN"
}
},
"externalController": {
"title": "وحدة التحكم الخارجية",
"fields": {
"enable": "Enable External Controller",
"address": "وحدة التحكم الخارجية",
"secret": "المفتاح السري للنواة"
},
"placeholders": {
"address": "Required",
"secret": "موصى به"
},
"tooltips": {
"copy": "Copy to clipboard"
},
"messages": {
"addressRequired": "Controller address cannot be empty",
"secretRequired": "Secret cannot be empty",
"copyFailed": "Failed to copy",
"controllerCopied": "Controller address copied to clipboard",
"secretCopied": "Secret copied to clipboard"
}
},
"externalCors": {
"title": "External Cors Configuration",
"fields": {
"allowPrivateNetwork": "Allow private network access",
"allowedOrigins": "Allowed Origins"
},
"placeholders": {
"origin": "Please enter a valid url"
},
"actions": {
"add": "Add"
},
"messages": {
"alwaysIncluded": "Always included origins: {{urls}}"
},
"tooltips": {
"open": "External Cors Settings"
}
},
"appearance": {
"light": "Light",
"dark": "Dark",
"system": "System"
},
"clash": {
"title": "إعدادات Clash",
"form": {
"fields": {
"allowLan": "السماح بالشبكة المحلية",
"dnsOverwrite": "DNS Overwrite",
"ipv6": "IPv6",
"unifiedDelay": "تأخير موحد",
"logLevel": "مستوى السجلات",
"portConfig": "تكوين المنافذ",
"external": "خارجي",
"webUI": "واجهة الويب",
"clashCore": "نواة Clash",
"openUwpTool": "فتح أداة UWP",
"updateGeoData": "تحديث البيانات الجغرافية"
},
"tooltips": {
"networkInterface": "واجهة الشبكة",
"unifiedDelay": "عند تفعيل التأخير الموحد، سيتم إجراء اختبارين للتأخير لتقليل الفروقات الناتجة عن مفاوضات الاتصال",
"logLevel": "This parameter is valid only for kernel log files in the log directory Service folder",
"openUwpTool": "منذ نظام ويندوز 8، يتم تقييد تطبيقات UWP من الوصول المباشر إلى المضيف المحلي. هذه الأداة تتيح تجاوز هذا التقييد"
},
"options": {
"logLevel": {
"debug": "Debug",
"info": "Info",
"warning": "Warn",
"error": "Error",
"silent": "Silent"
}
}
}
}
},
"components": {
"verge": {
"basic": {
"title": "الإعدادات الأساسية Verge",
"actions": {
"browse": "استعراض"
},
"trayOptions": {
"showMainWindow": "إظهار النافذة الرئيسية",
"showTrayMenu": "Show Tray Menu",
"disable": "تعطيل"
},
"fields": {
"language": "اللغة",
"themeMode": "وضع السمة",
"trayClickEvent": "حدث النقر على الأيقونة في شريط المهام",
"copyEnvType": "نسخ نوع البيئة",
"startPage": "صفحة البدء",
"startupScript": "سكريبت بدء التشغيل",
"themeSetting": "إعدادات السمة",
"layoutSetting": "إعدادات التخطيط",
"misc": "متفرقات",
"hotkeySetting": "إعدادات الاختصارات"
}
},
"advanced": {
"title": "الإعدادات الأساسية Verge",
"tooltips": {
"backupInfo": "Support local or WebDAV backup of configuration files",
"openConfDir": "إذا عمل البرنامج بشكل غير طبيعي، قم بالنسخ الاحتياطي ثم حذف جميع الملفات في هذا المجلد ثم أعد تشغيل البرنامج",
"liteMode": "إيقاف الواجهة الرسومية والإبقاء على تشغيل النواة"
},
"actions": {
"copyVersion": "Copy Version"
},
"notifications": {
"latestVersion": "أنت على أحدث إصدار حاليًا",
"versionCopied": "Version copied to clipboard"
},
"fields": {
"backupSetting": "إعداد النسخ الاحتياطي",
"runtimeConfig": "تكوين وقت التشغيل",
"openConfDir": "فتح مجلد التكوين",
"openCoreDir": "فتح مجلد النواة",
"openLogsDir": "فتح مجلد السجلات",
"checkUpdates": "التحقق من وجود تحديثات",
"openDevTools": "أدوات المطور",
"liteModeSettings": "LightWeight Mode Settings",
"exit": "خروج",
"exportDiagnostics": "Export Diagnostic Info",
"vergeVersion": "إصدار Verge"
}
},
"theme": {
"title": "إعدادات السمة",
"fields": {
"primaryColor": "اللون الأساسي",
"secondaryColor": "اللون الثانوي",
"primaryText": "النص الأساسي",
"secondaryText": "النص الثانوي",
"infoColor": "لون المعلومات",
"warningColor": "لون التحذير",
"errorColor": "لون الخطأ",
"successColor": "لون النجاح",
"fontFamily": "عائلة الخط",
"cssInjection": "حقن CSS"
},
"actions": {
"editCss": "Edit CSS"
},
"dialogs": {
"editCssTitle": "Edit CSS"
}
},
"layout": {
"title": "إعدادات التخطيط",
"fields": {
"preferSystemTitlebar": "Prefer System Titlebar",
"trafficGraph": "مخطط حركة المرور",
"memoryUsage": "استهلاك الذاكرة",
"proxyGroupIcon": "أيقونة مجموعة الوكلاء",
"hoverNavigator": "Hover Jump Navigator",
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
"navIcon": "أيقونة التنقل",
"trayIcon": "أيقونة شريط المهام",
"showProxyGroupsInline": "Show Proxy Groups Inline",
"commonTrayIcon": "أيقونة شريط مهام عامة",
"systemProxyTrayIcon": "أيقونة شريط المهام لوكيل النظام",
"tunTrayIcon": "أيقونة شريط المهام لـ TUN",
"enableTrayIcon": "Enable Tray Icon",
"enableTraySpeed": "تفعيل سرعة التراي"
},
"tooltips": {
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
},
"options": {
"icon": {
"monochrome": "أحادي اللون",
"colorful": "ملون",
"disable": "تعطيل"
}
}
}
}
},
"modals": {
"clashPort": {
"title": "تكوين المنافذ",
"fields": {
"mixed": "منفذ مختلط",
"socks": "منفذ SOCKS",
"http": "منفذ HTTP(S)",
"redir": "منفذ إعادة التوجيه",
"tproxy": "منفذ Tproxy"
},
"actions": {
"random": "منفذ عشوائي"
},
"messages": {
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings"
}
},
"clashCore": {
"variants": {
"release": "الإصدار المستقر",
"alpha": "الإصدار التجريبي"
}
},
"liteMode": {
"title": "LightWeight Mode Settings",
"actions": {
"enterNow": "Enter LightWeight Mode Now"
},
"toggles": {
"autoEnter": "Auto Enter LightWeight Mode"
},
"tooltips": {
"autoEnter": "Enable to automatically activate LightWeight Mode after the window is closed for a period of time"
},
"fields": {
"delay": "Auto Enter LightWeight Mode Delay"
},
"messages": {
"autoEnterHint": "When closing the window, LightWeight Mode will be automatically activated after {{n}} minutes"
}
},
"backup": {
"title": "إعداد النسخ الاحتياطي",
"tabs": {
"local": "Local backup",
"webdav": "WebDAV backup"
},
"actions": {
"selectTarget": "Select backup target",
"backup": "نسخ احتياطي",
"export": "Export",
"exportBackup": "Export Backup",
"deleteBackup": "حذف النسخة الاحتياطية",
"restore": "استعادة",
"restoreBackup": "استعادة النسخة الاحتياطية"
},
"fields": {
"webdavUrl": "عنوان خادم WebDAV",
"username": "اسم المستخدم",
"info": "Backups are stored locally in the application data directory. Use the list below to restore or delete backups."
},
"messages": {
"webdavUrlRequired": "لا يمكن ترك رابط WebDAV فارغًا",
"invalidWebdavUrl": "تنسيق رابط WebDAV غير صالح",
"usernameRequired": "لا يمكن ترك اسم المستخدم فارغًا",
"passwordRequired": "لا يمكن ترك كلمة المرور فارغة",
"webdavConfigSaved": "تم حفظ إعدادات WebDAV بنجاح",
"webdavConfigSaveFailed": "فشل حفظ إعدادات WebDAV: {{error}}",
"backupCreated": "تم إنشاء النسخة الاحتياطية بنجاح",
"backupFailed": "فشل في النسخ الاحتياطي: {{error}}",
"localBackupCreated": "Local backup created successfully",
"localBackupFailed": "Local backup failed",
"restoreSuccess": "تمت الاستعادة بنجاح، سيعاد تشغيل التطبيق خلال ثانية واحدة",
"localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup",
"confirmDelete": "هل تريد بالتأكيد حذف ملف النسخة الاحتياطية هذا؟",
"confirmRestore": "هل تريد بالتأكيد استعادة ملف النسخة الاحتياطية هذا؟"
},
"table": {
"filename": "اسم الملف",
"backupTime": "وقت النسخ الاحتياطي",
"actions": "الإجراءات",
"noBackups": "لا توجد نسخ احتياطية متاحة",
"rowsPerPage": "Rows per page"
}
},
"misc": {
"title": "متفرقات",
"fields": {
"appLogLevel": "مستوى سجلات التطبيق",
"appLogMaxSize": "App Log Max Size",
"appLogMaxCount": "App Log Max Count",
"autoCloseConnections": "إغلاق الاتصالات تلقائيًا",
"autoCheckUpdate": "فحص التحديث تلقائيًا",
"enableBuiltinEnhanced": "تفعيل التحسين المدمج",
"proxyLayoutColumns": "أعمدة عرض الوكيل",
"autoLogClean": "تنظيف السجلات تلقائيًا",
"autoDelayDetection": "اكتشاف التأخير التلقائي",
"defaultLatencyTest": "اختبار التأخير الافتراضي",
"defaultLatencyTimeout": "مهلة التأخير الافتراضية"
},
"tooltips": {
"autoCloseConnections": "إنهاء الاتصالات القائمة عند تغيير اختيار مجموعة الوكيل أو وضع الوكيل",
"enableBuiltinEnhanced": "معالجة توافق ملف التكوين",
"autoDelayDetection": "يختبر زمن استجابة العقدة الحالية على نحو دوري في الخلفية",
"defaultLatencyTest": "يُستخدم فقط لاختبار طلب HTTP العميل. لن يؤثر على ملف التكوين"
},
"options": {
"proxyLayoutColumns": {
"auto": "أعمدة تلقائية"
},
"autoLogClean": {
"never": "عدم التنظيف أبدًا",
"retainDays": "الاحتفاظ لمدة {{n}} يومًا"
}
}
},
"update": {
"title": "New Version v{{version}}",
"actions": {
"goToRelease": "الانتقال إلى صفحة الإصدارات",
"update": "تحديث"
},
"messages": {
"portableError": "الإصدار المحمول لا يدعم التحديث داخل التطبيق. يرجى التنزيل والاستبدال يدويًا",
"breakChangeError": "هذا الإصدار هو تحديث رئيسي ولا يدعم التحديث داخل التطبيق. يرجى إلغاء التثبيت وتنزيل الإصدار الجديد وتثبيته يدويًا"
}
},
"sysproxy": {
"title": "إعداد وكيل النظام",
"fieldsets": {
"currentStatus": "الوكيل الحالي للنظام"
},
"fields": {
"enableStatus": "حالة التمكين:",
"serverAddr": "عنوان الخادم:",
"pacUrl": "رابط PAC:",
"proxyHost": "مضيف الوكيل",
"usePacMode": "استخدام وضع PAC",
"proxyGuard": "حماية الوكيل",
"guardDuration": "مدة الحماية",
"alwaysUseDefaultBypass": "استخدام التخطي الافتراضي دائمًا",
"proxyBypass": "إعدادات تخطي الوكيل:",
"bypass": "تخطي:",
"pacScriptContent": "محتوى سكريبت PAC"
},
"tooltips": {
"proxyGuard": "عند التمكين، يمنع برامج أخرى من تعديل إعدادات وكيل النظام"
},
"messages": {
"durationTooShort": "لا يمكن أن تقل مدة خادم الوكيل عن ثانية واحدة",
"invalidBypass": "تنسيق التخطي غير صالح",
"invalidProxyHost": "تنسيق مضيف الوكيل غير صالح"
},
"actions": {
"editPac": "تعديل PAC"
}
},
"tun": {
"title": "وضع TUN",
"fields": {
"stack": "مكدس TUN",
"device": "Device Name",
"autoRoute": "توجيه تلقائي",
"strictRoute": "توجيه صارم",
"autoDetectInterface": "الكشف التلقائي عن الواجهة",
"dnsHijack": "اختطاف DNS",
"mtu": "وحدة الإرسال القصوى"
},
"tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers"
},
"messages": {
"applied": "تم تطبيق الإعدادات"
}
},
"dns": {
"dialog": {
"title": "DNS Overwrite",
"warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite enabled"
},
"sections": {
"general": "DNS Settings",
"fallbackFilter": "Fallback Filter Settings",
"hosts": "Hosts Settings"
},
"fields": {
"enable": "Enable DNS",
"listen": "DNS Listen",
"enhancedMode": "Enhanced Mode",
"fakeIpRange": "Fake IP Range",
"fakeIpFilterMode": "Fake IP Filter Mode",
"ipv6": {
"label": "IPv6",
"description": "Enable IPv6 DNS resolution"
},
"preferH3": {
"label": "Prefer H3",
"description": "DNS DOH uses HTTP/3"
},
"respectRules": {
"label": "Respect Rules",
"description": "DNS connections follow routing rules"
},
"useHosts": {
"label": "Use Hosts",
"description": "Enable to resolve hosts through hosts file"
},
"useSystemHosts": {
"label": "Use System Hosts",
"description": "Enable to resolve hosts through system hosts file"
},
"directPolicy": {
"label": "Direct Nameserver Follow Policy",
"description": "Whether to follow nameserver policy"
},
"defaultNameserver": {
"label": "Default Nameserver",
"description": "Default DNS servers used to resolve DNS servers"
},
"nameserver": {
"label": "Nameserver",
"description": "List of DNS servers, comma separated"
},
"fallback": {
"label": "Fallback",
"description": "List of fallback DNS servers, comma separated"
},
"proxy": {
"label": "Proxy Server Nameserver",
"description": "DNS servers for proxy node domain resolution"
},
"directNameserver": {
"label": "Direct Nameserver",
"description": "DNS servers for direct exit domain resolution, supports 'system' keyword, comma separated"
},
"fakeIpFilter": {
"label": "Fake IP Filter",
"description": "Domains that skip fake IP resolution, comma separated"
},
"nameserverPolicy": {
"label": "Nameserver Policy",
"description": "Domain-specific DNS server, multiple servers separated by semicolons, format: domain=server1;server2"
},
"geoipFiltering": {
"label": "GeoIP Filtering",
"description": "Enable GeoIP filtering for fallback"
},
"geoipCode": "GeoIP Code",
"fallbackIpCidr": {
"label": "Fallback IP CIDR",
"description": "IP CIDRs not using fallback servers, comma separated"
},
"fallbackDomain": {
"label": "Fallback Domain",
"description": "Domains using fallback servers, comma separated"
},
"hosts": {
"label": "Hosts",
"description": "Custom domain to IP or domain mapping"
}
},
"messages": {
"saved": "DNS settings saved",
"configError": "DNS configuration error:"
},
"errors": {
"invalid": "Invalid configuration",
"invalidYaml": "Invalid YAML format"
}
},
"webUI": {
"actions": {
"openUrl": "فتح الرابط"
},
"title": "واجهة الويب",
"messages": {
"supportedPlaceholders": "يدعم %host و%port و%secret",
"placeholderInstruction": "استبدل المضيف والمنفذ والمفتاح بـ %host و%port و%secret"
}
},
"hotkey": {
"toggles": {
"enableGlobal": "تمكين مفتاح التشغيل السريع العالمي"
},
"title": "إعدادات الاختصارات",
"functions": {
"rule": "وضع القواعد",
"global": "الوضع العالمي",
"openOrCloseDashboard": "فتح/إغلاق لوحة التحكم",
"toggleSystemProxy": "تفعيل/تعطيل وكيل النظام",
"toggleTunMode": "تفعيل/تعطيل وضع TUN",
"entryLightweightMode": "Entry Lightweight Mode",
"direct": "الوضع المباشر"
}
},
"password": {
"prompts": {
"enterRoot": "يرجى إدخال كلمة مرور الرووت"
}
},
"networkInterface": {
"title": "واجهة الشبكة",
"fields": {
"ipAddress": "عنوان IP",
"macAddress": "عنوان MAC"
}
}
},
"feedback": {
"notifications": {
"clash": {
"restartSuccess": "تم إعادة تشغيل نواة Clash",
"versionUpdated": "تم تحديث إصدار النواة",
"alreadyLatestVersion": "أنت تستخدم بالفعل أحدث إصدار من النواة",
"changeSuccess": "تم تغيير النواة بنجاح",
"changeFailed": "فشل تغيير النواة",
"geoDataUpdated": "تم تحديث البيانات الجغرافية"
},
"clashService": {
"installSuccess": "تم تثبيت الخدمة بنجاح",
"uninstallSuccess": "تم إلغاء تثبيت الخدمة بنجاح"
},
"updater": {
"withClashProxySuccess": "Update with Clash proxy successfully",
"withClashProxyFailed": "Update failed even with Clash proxy"
}
}
},
"statuses": {
"clash": {
"stopping": "Stopping Core...",
"restarting": "Restarting Core..."
},
"clashService": {
"installing": "جاري تثبيت الخدمة...",
"uninstalling": "Uninstalling Service..."
}
}
}
+129
View File
@@ -0,0 +1,129 @@
{
"actions": {
"cancel": "إلغاء",
"close": "إغلاق",
"confirm": "تأكيد",
"save": "حفظ",
"delete": "حذف",
"edit": "تعديل",
"new": "جديد",
"enable": "تمكين",
"upgrade": "ترقية",
"restart": "إعادة التشغيل",
"resetToDefault": "إعادة تعيين إلى الافتراضي",
"refresh": "تحديث",
"retry": "Retry",
"refreshPage": "Refresh Page",
"showDetails": "Show Details",
"hideDetails": "Hide Details",
"listView": "عرض القائمة",
"tableView": "عرض الجدول",
"pause": "إيقاف مؤقت",
"resume": "استأنف",
"closeAll": "إغلاق الكل",
"clear": "مسح"
},
"labels": {
"updateAt": "التحديث عند",
"timeout": "Timeout",
"icon": "أيقونة",
"name": "الاسم",
"readOnly": "للقراءة فقط",
"expireTime": "وقت الانتهاء",
"updateTime": "وقت التحديث",
"usedTotal": "المستخدم / الإجمالي",
"from": "من",
"password": "كلمة المرور",
"retryAttempts": "Retry attempts",
"downloaded": "تم التنزيل",
"uploaded": "تم الرفع"
},
"statuses": {
"enabled": "ممكّن",
"disabled": "معطّل",
"saving": "Saving...",
"empty": "فارغ"
},
"units": {
"milliseconds": "ميلي ثانية",
"seconds": "ثواني",
"minutes": "دقائق",
"kilobytes": "KB",
"files": "Files"
},
"placeholders": {
"filter": "شروط التصفية",
"matchCase": "مطابقة الحالة",
"matchWholeWord": "مطابقة الكلمة بأكملها",
"useRegex": "استخدام التعبيرات العادية"
},
"validation": {
"invalidRegex": "Invalid regular expression"
},
"window": {
"maximize": "تكبير",
"minimize": "تصغير"
},
"editorModes": {
"visualization": "تصور",
"advanced": "متقدم"
},
"feedback": {
"errors": {
"trafficStats": "Traffic Statistics Error",
"trafficStatsDescription": "The traffic statistics component encountered an error and has been disabled to prevent crashes."
},
"notices": {
"raw": "{{message}}",
"prefixedRaw": "{{prefix}} {{message}}"
},
"notifications": {
"importSuccess": "تم استيراد الملف الشخصي بنجاح",
"importSubscriptionSuccess": "تم استيراد الاشتراك بنجاح",
"importWithClashProxy": "Profile Imported with Clash proxy",
"updateAvailable": "Update Available",
"saved": "Saved successfully",
"common": {
"copySuccess": "تم النسخ بنجاح",
"saveSuccess": "Configuration saved successfully",
"saveFailed": "Failed to save configuration"
}
},
"validation": {
"config": {
"failed": "فشل التحقق من تكوين الاشتراك، يرجى فحص ملف التكوين، تم التراجع عن التغييرات، تفاصيل الخطأ:",
"bootFailed": "فشل التحقق من التكوين عند الإقلاع، تم استخدام التكوين الافتراضي، يرجى فحص ملف التكوين، تفاصيل الخطأ:",
"coreChangeFailed": "فشل التحقق من التكوين عند تغيير النواة، تم استخدام التكوين الافتراضي، يرجى فحص ملف التكوين، تفاصيل الخطأ:",
"processTerminated": "تم إنهاء عملية التحقق"
},
"script": {
"syntaxError": "خطأ في بناء جملة السكريبت، تم التراجع عن التغييرات",
"missingMain": "خطأ في السكريبت، تم التراجع عن التغييرات",
"fileNotFound": "الملف غير موجود، تم التراجع عن التغييرات",
"fileError": "خطأ في ملف السكريبت، تم التراجع عن التغييرات"
},
"yaml": {
"syntaxError": "YAML syntax error, changes reverted",
"readError": "YAML read error, changes reverted",
"mappingError": "YAML mapping error, changes reverted",
"keyError": "YAML key error, changes reverted",
"generalError": "YAML error, changes reverted"
},
"merge": {
"syntaxError": "Merge file syntax error, changes reverted",
"mappingError": "Merge file mapping error, changes reverted",
"keyError": "Merge file key error, changes reverted",
"generalError": "Merge file error, changes reverted"
}
}
},
"filters": {
"logLevels": {
"all": "ALL",
"debug": "DEBUG",
"info": "INFO",
"warn": "WARN",
"error": "ERROR"
}
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"page": {
"actions": {
"testAll": "اختبار الكل"
},
"title": "اختبار"
},
"components": {
"item": {
"actions": {
"test": "اختبار"
}
}
},
"modals": {
"test": {
"title": {
"create": "إنشاء اختبار",
"edit": "تعديل الاختبار"
},
"fields": {
"url": "رابط الاختبار"
}
}
},
"statuses": {
"test": {
"pending": "Pending",
"yes": "Yes",
"no": "No",
"failed": "Failed",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
"noDisney": "No (IP Banned By Disney+)",
"unsupportedRegion": "Unsupported Country/Region",
"failedNetwork": "Failed (Network Connection)"
}
},
"unlock": {
"page": {
"actions": {
"testing": "Testing..."
},
"empty": "No unlock test items",
"messages": {
"detectionFailedWithName": "فشل الكشف لـ {{name}}",
"detectionTimeout": "Detection timeout or failed"
},
"title": "Unlock Test"
}
}
}
-563
View File
@@ -1,563 +0,0 @@
{
"millis": "Millisekunden",
"seconds": "Sekunden",
"mins": "Minuten",
"Back": "Zurück",
"Close": "Schließen",
"Cancel": "Abbrechen",
"Confirm": "Bestätigen",
"Maximize": "Maximieren",
"Minimize": "Minimieren",
"Format document": "Dokument formatieren",
"Empty": "Leer",
"New": "Neu",
"Edit": "Bearbeiten",
"Save": "Speichern",
"Delete": "Löschen",
"Enable": "Aktivieren",
"Disable": "Deaktivieren",
"Label-Home": "Startseite",
"Label-Proxies": "Proxy",
"Label-Profiles": "Abonnement",
"Label-Connections": "Verbindungen",
"Label-Rules": "Regeln",
"Label-Logs": "Protokolle",
"Label-Unlock": "Testen",
"Label-Settings": "Einstellungen",
"Proxy Groups": "Proxy-Gruppen",
"Proxy Provider": "Proxy-Sammlung",
"Proxy Count": "Anzahl der Knoten",
"Update All": "Alle aktualisieren",
"Update At": "Aktualisiert am",
"rule": "Regel",
"global": "Global",
"direct": "Direktverbindung",
"Chain Proxy": "🔗 Ketten-Proxy",
"script": "Skript",
"locate": "Aktueller Knoten",
"Delay check": "Latenztest",
"Sort by default": "Standard Sortierung",
"Sort by delay": "Nach Latenz sortieren",
"Sort by name": "Nach Name sortieren",
"Delay check URL": "Latenztest-URL",
"Delay check to cancel fixed": "Latenztest durchführen, um Fixierung aufzuheben",
"Proxy basic": "Knotendetails ausblenden",
"Proxy detail": "Knotendetails anzeigen",
"Profiles": "Abonnement",
"Update All Profiles": "Alle Abonnements aktualisieren",
"View Runtime Config": "Laufzeit-Abonnement anzeigen",
"Reactivate Profiles": "Abonnement erneut aktivieren",
"Paste": "Einfügen",
"Profile URL": "Abonnement-Datei-Link",
"Import": "Importieren",
"From": "Von",
"Update Time": "Aktualisierungszeit",
"Used / Total": "Verwendet / Gesamt",
"Expire Time": "Ablaufzeit",
"Create Profile": "Neue Konfiguration erstellen",
"Edit Profile": "Konfiguration bearbeiten",
"Edit Proxies": "Knoten bearbeiten",
"Use newlines for multiple uri": "Für mehrere URI verwenden Sie Zeilenumbrüche (Base64-Codierung wird unterstützt)",
"Edit Rules": "Regeln bearbeiten",
"Rule Type": "Regeltyp",
"Rule Content": "Regelinhalt",
"Proxy Policy": "Proxy-Strategie",
"No Resolve": "DNS-Auflösung überspringen",
"Prepend Rule": "Vorherige Regel hinzufügen",
"Append Rule": "Nachfolgende Regel hinzufügen",
"Prepend Group": "Vorherige Proxy-Gruppe hinzufügen",
"Append Group": "Nachfolgende Proxy-Gruppe hinzufügen",
"Prepend Proxy": "Vorherigen Proxy-Knoten hinzufügen",
"Append Proxy": "Nachfolgenden Proxy-Knoten hinzufügen",
"Rule Condition Required": "Regelbedingung fehlt",
"Invalid Rule": "Ungültige Regel",
"Advanced": "Erweitert",
"Visualization": "Visualisierung",
"DOMAIN": "Vollständigen Domainnamen übereinstimmen",
"DOMAIN-SUFFIX": "Domain-Suffix übereinstimmen",
"DOMAIN-KEYWORD": "Domain-Schlüsselwort übereinstimmen",
"DOMAIN-REGEX": "Domain-Regulärer Ausdruck übereinstimmen",
"GEOSITE": "Domainnamen in Geosite übereinstimmen",
"GEOIP": "IP-Ländercode übereinstimmen",
"SRC-GEOIP": "Quell-IP-Ländercode übereinstimmen",
"IP-ASN": "IP-ASN übereinstimmen",
"SRC-IP-ASN": "Quell-IP-ASN übereinstimmen",
"IP-CIDR": "IP-Adressbereich übereinstimmen",
"IP-CIDR6": "IP-Adressbereich übereinstimmen",
"SRC-IP-CIDR": "Quell-IP-Adressbereich übereinstimmen",
"IP-SUFFIX": "IP-Suffix-Bereich übereinstimmen",
"SRC-IP-SUFFIX": "Quell-IP-Suffix-Bereich übereinstimmen",
"SRC-PORT": "Quellportbereich der Anfrage übereinstimmen",
"DST-PORT": "Zielportbereich der Anfrage übereinstimmen",
"IN-PORT": "Eingangsport übereinstimmen",
"DSCP": "DSCP-Markierung (nur für TPROXY UDP-Eingang)",
"PROCESS-NAME": "Prozessnamen übereinstimmen (Android-Paketname)",
"PROCESS-PATH": "Vollständigen Prozesspfad übereinstimmen",
"PROCESS-NAME-REGEX": "Regulärer Ausdruck für vollständigen Prozessnamen übereinstimmen (Android-Paketname)",
"PROCESS-PATH-REGEX": "Regulärer Ausdruck für vollständigen Prozesspfad übereinstimmen",
"NETWORK": "Übertragungsprotokoll übereinstimmen (TCP/UDP)",
"UID": "Linux-USER-ID übereinstimmen",
"IN-TYPE": "Eingangstyp übereinstimmen",
"IN-USER": "Eingangsbenutzername übereinstimmen",
"IN-NAME": "Eingangsname übereinstimmen",
"SUB-RULE": "Unterregel",
"RULE-SET": "Regelsatz übereinstimmen",
"AND": "Logisches UND",
"OR": "Logisches ODER",
"NOT": "Logisches NICHT",
"MATCH": "Alle Anfragen übereinstimmen",
"DIRECT": "Direktverbindung",
"REJECT": "Anfrage ablehnen",
"REJECT-DROP": "Anfrage verwerfen",
"PASS": "Diese Regel überspringen",
"Edit Groups": "Proxy-Gruppen bearbeiten",
"Group Type": "Proxy-Gruppentyp",
"select": "Proxy manuell auswählen",
"url-test": "Proxy basierend auf URL-Latenztest auswählen",
"fallback": "Bei Nichtverfügbarkeit zu einem anderen Proxy wechseln",
"load-balance": "Proxy basierend auf Lastverteilung zuweisen",
"relay": "Basierend auf definiertem Proxy-Kette weiterleiten",
"Group Name": "Proxy-Gruppenname",
"Use Proxies": "Proxy einführen",
"Use Provider": "Proxy-Sammlung einführen",
"Health Check Url": "URL für Gesundheitstest",
"Expected Status": "Erwarteter Statuscode",
"Interval": "Prüfintervall",
"Lazy": "Lazy-Status",
"Timeout": "Timeout",
"Max Failed Times": "Maximale Anzahl fehlgeschlagener Versuche",
"Interface Name": "Ausgangsschnittstelle",
"Routing Mark": "Routierungsmarkierung",
"Include All": "Alle Ausgangsproxy und Proxy-Sammlungen einführen",
"Include All Providers": "Alle Proxy-Sammlungen einführen",
"Include All Proxies": "Alle Ausgangsproxy einführen",
"Exclude Filter": "Knoten ausschließen",
"Exclude Type": "Typ der auszuschließenden Knoten",
"Disable UDP": "UDP deaktivieren",
"Hidden": "Proxy-Gruppe ausblenden",
"Group Name Required": "Der Proxy-Gruppenname darf nicht leer sein",
"Group Name Already Exists": "Der Proxy-Gruppenname existiert bereits",
"Extend Config": "Erweiterte Überdeckungskonfiguration",
"Extend Script": "Erweitertes Skript",
"Type": "Typ",
"Name": "Name",
"Descriptions": "Beschreibung",
"Subscription URL": "Abonnement-Link",
"Update Interval": "Aktualisierungsintervall",
"Choose File": "Datei auswählen",
"Use System Proxy": "Systemproxy zur Aktualisierung verwenden",
"Use Clash Proxy": "Kernel-Proxy zur Aktualisierung verwenden",
"Refresh": "Aktualisieren",
"Home": "Startseite",
"Select": "Verwenden",
"Edit Info": "Informationen bearbeiten",
"Edit File": "Datei bearbeiten",
"Open File": "Datei öffnen",
"Update": "Aktualisieren",
"Confirm deletion": "Löschung bestätigen",
"This operation is not reversible": "Diese Operation kann nicht rückgängig gemacht werden",
"Script Console": "Skript-Konsole-Ausgabe",
"Connections": "Verbindungen",
"Table View": "Tabellenansicht",
"List View": "Listenansicht",
"Close All": "Alle schließen",
"Upload": "Hochladen",
"Download": "Herunterladen",
"Download Speed": "Download-Geschwindigkeit",
"Upload Speed": "Upload-Geschwindigkeit",
"Host": "Host",
"Downloaded": "Heruntergeladen",
"Uploaded": "Hochgeladen",
"DL Speed": "Download-Geschwindigkeit",
"UL Speed": "Upload-Geschwindigkeit",
"Active Connections": "Aktive Verbindungen",
"Chains": "Ketten",
"Rule": "Regel",
"Process": "Prozess",
"Time": "Verbindungszeit",
"Source": "Quelladresse",
"Destination": "Zieladresse",
"DestinationPort": "Zielport",
"Close Connection": "Verbindung schließen",
"Rules": "Regeln",
"Rule Provider": "Regelsammlung",
"Logs": "Protokolle",
"Pause": "Pausieren",
"Resume": "Fortsetzen",
"Clear": "Löschen",
"Test": "Testen",
"Test All": "Alle testen",
"Testing...": "Wird getestet...",
"Create Test": "Neuen Test erstellen",
"Edit Test": "Test bearbeiten",
"Icon": "Symbol",
"Test URL": "Test-URL",
"Settings": "Einstellungen",
"System Setting": "Systemeinstellungen",
"Tun Mode": "Virtual Network Interface-Modus",
"Install Service": "Service installieren",
"Uninstall Service": "Dienst deinstallieren",
"Reset to Default": "Auf Standardwerte zurücksetzen",
"Tun Mode Info": "Der TUN-Modus (Virtual Network Interface) übernimmt den gesamten Systemverkehr. Wenn dieser Modus aktiviert ist, muss der Systemproxy nicht geöffnet werden.",
"TUN requires Service Mode or Admin Mode": "TUN-Modus erfordert Service-Modus oder Administrator-Modus",
"System Proxy Enabled": "Der Systemproxy ist aktiviert. Ihre Anwendungen werden über den Proxy auf das Netzwerk zugreifen.",
"System Proxy Disabled": "Der Systemproxy ist deaktiviert. Es wird empfohlen, diesen Eintrag für die meisten Benutzer zu aktivieren.",
"TUN Mode Enabled": "Der TUN-Modus ist aktiviert. Die Anwendungen werden über die virtuelle Netzwerkschnittstelle auf das Netzwerk zugreifen.",
"TUN Mode Disabled": "Der TUN-Modus ist deaktiviert. Dies ist für spezielle Anwendungen geeignet.",
"TUN Mode Service Required": "Der TUN-Modus erfordert den Service-Modus. Bitte installieren Sie zuerst den Service.",
"TUN Mode Intercept Info": "Der TUN-Modus kann den gesamten Anwendungsverkehr übernehmen und eignet sich für spezielle Anwendungen, die die Systemproxy-Einstellungen nicht befolgen.",
"Stack": "TUN-Modus-Stack",
"Device": "TUN-Netzwerkkartenname",
"Auto Route": "Globale Routing automatisch einstellen",
"Strict Route": "Strenges Routing",
"Auto Detect Interface": "Netzwerkschnittstelle automatisch auswählen",
"DNS Hijack": "DNS-Hijacking",
"MTU": "Maximale Übertragungseinheit",
"Service Mode": "Service-Modus",
"active": "Aktiviert",
"unknown": "Unbekannt",
"Install": "Installieren",
"Uninstall": "Deinstallieren",
"System Proxy": "Systemproxy",
"System Proxy Info": "Ändern Sie die Proxy-Einstellungen des Betriebssystems. Wenn die Aktivierung fehlschlägt, können Sie die Proxy-Einstellungen des Betriebssystems manuell ändern.",
"System Proxy Setting": "Systemproxy-Einstellungen",
"Current System Proxy": "Aktueller Systemproxy",
"Enable status": "Aktivierungsstatus: ",
"Enabled": "Aktiviert",
"Disabled": "Deaktiviert",
"Server Addr": "Serveradresse: ",
"Proxy Host": "Proxy-Host",
"Invalid Proxy Host Format": "Ungültiges Format für den Proxy-Host",
"Not available": "Nicht verfügbar",
"Proxy Guard": "Systemproxy-Schutz",
"Proxy Guard Info": "Aktivieren Sie diese Option, um zu verhindern, dass andere Software die Proxy-Einstellungen des Betriebssystems ändert.",
"Guard Duration": "Proxy-Schutz-Intervall",
"Always use Default Bypass": "Immer die Standard-Umgehung verwenden",
"Proxy Bypass": "Proxy-Umgehungseinstellungen: ",
"Bypass": "Aktuelle Umgehung: ",
"Use PAC Mode": "PAC-Modus verwenden",
"PAC Script Content": "PAC-Skriptinhalt",
"PAC URL": "PAC-Adresse: ",
"Auto Launch": "Beim Start automatisch starten",
"Administrator mode may not support auto launch": "Der Administrator-Modus unterstützt möglicherweise keine automatische Startfunktion.",
"Silent Start": "Stillstart",
"Silent Start Info": "Die Anwendung wird im Hintergrund gestartet, ohne dass das Programmfenster angezeigt wird.",
"TG Channel": "Telegram-Kanal",
"Manual": "Bedienungsanleitung",
"Github Repo": "GitHub-Projektadresse",
"Clash Setting": "Clash-Einstellungen",
"Allow Lan": "Netzwerkverbindung im lokalen Netzwerk zulassen",
"Network Interface": "Netzwerkschnittstelle",
"Ip Address": "IP-Adresse",
"Mac Address": "MAC-Adresse",
"IPv6": "IPv6",
"Unified Delay": "Einheitliche Latenz",
"Unified Delay Info": "Wenn die einheitliche Latenz aktiviert ist, werden zwei Latenztests durchgeführt, um die Latenzunterschiede zwischen verschiedenen Knotentypen aufgrund von Verbindungsaufbau und anderen Faktoren zu eliminieren.",
"Log Level": "Protokolliergrad",
"Log Level Info": "Dies wirkt sich nur auf die Kernprotokolldateien im Verzeichnis Service im Protokollverzeichnis aus.",
"Port Config": "Port-Konfiguration",
"Random Port": "Zufälliger Port",
"Mixed Port": "Mischter Proxy-Port",
"Socks Port": "SOCKS-Proxy-Port",
"Http Port": "HTTP(S)-Proxy-Port",
"Redir Port": "Redir-Transparenter Proxy-Port",
"Tproxy Port": "TPROXY-Transparenter Proxy-Port",
"External": "Externe Steuerung",
"External Controller": "Adresse des externen Controllers",
"Core Secret": "API-Zugangsschlüssel",
"Recommended": "Empfohlene Einstellung",
"Open URL": "Link öffnen",
"Replace host, port, secret with %host, %port, %secret": "Verwenden Sie %host, %port, %secret für Host, Port und Zugangsschlüssel",
"Support %host, %port, %secret": "Unterstützt %host, %port, %secret",
"Clash Core": "Clash-Kern",
"Upgrade": "Kern aktualisieren",
"Restart": "Kern neustarten",
"Release Version": "Stabile Version",
"Alpha Version": "Vorschauversion",
"Please enter your root password": "Bitte geben Sie Ihr Root-Passwort ein.",
"Open UWP tool": "UWP-Tool öffnen",
"Open UWP tool Info": "Ab Windows 8 wird die direkte Netzwerkverbindung von UWP-Anwendungen (z. B. Microsoft Store) zu lokalen Hosts eingeschränkt. Mit diesem Tool können Sie diese Einschränkung umgehen.",
"Update GeoData": "Geo-Daten aktualisieren",
"Verge Basic Setting": "Verge-Grundeinstellungen",
"Verge Advanced Setting": "Verge-Erweiterte Einstellungen",
"Language": "Spracheinstellungen",
"Theme Mode": "Thema",
"Tray Click Event": "Tray-Klickereignis",
"Show Main Window": "Hauptfenster anzeigen",
"Show Tray Menu": "Tray-Menü anzeigen",
"Copy Env Type": "Umgebungsvariablentyp kopieren",
"Copy Success": "Kopieren erfolgreich",
"Start Page": "Startseite",
"Startup Script": "Startskript",
"Browse": "Durchsuchen",
"Theme Setting": "Thema-Einstellungen",
"Primary Color": "Hauptfarbe",
"Secondary Color": "Sekundärfarbe",
"Primary Text": "Haupttextfarbe",
"Secondary Text": "Sekundärtextfarbe",
"Info Color": "Informationsfarbe",
"Warning Color": "Warnfarbe",
"Error Color": "Fehlerfarbe",
"Success Color": "Erfolgsfarbe",
"Font Family": "Schriftfamilie",
"CSS Injection": "CSS-Einbindung",
"Layout Setting": "Layout-Einstellungen",
"Traffic Graph": "Verkehrsdiagramm",
"Memory Usage": "Kern-Speichernutzung",
"Memory Cleanup": "Klicken Sie hier, um den Speicher zu bereinigen.",
"Proxy Group Icon": "Proxy-Gruppen-Symbol",
"Nav Icon": "Navigationsleiste-Symbol",
"Monochrome": "Monochromes Symbol",
"Colorful": "Farbiges Symbol",
"Tray Icon": "Tray-Symbol",
"Common Tray Icon": "Standard-Tray-Symbol",
"System Proxy Tray Icon": "Systemproxy-Tray-Symbol",
"Tun Tray Icon": "TUN-Modus-Tray-Symbol",
"Miscellaneous": "Sonstige Einstellungen",
"App Log Level": "Anwendungs-Protokolliergrad",
"Auto Close Connections": "Verbindungen automatisch schließen",
"Auto Close Connections Info": "Wenn der ausgewählte Knoten in der Proxy-Gruppe oder der Proxy-Modus geändert wird, werden die bestehenden Verbindungen geschlossen.",
"Auto Check Update": "Automatisch auf Updates prüfen",
"Enable Builtin Enhanced": "Eingebaute Verbesserungen aktivieren",
"Enable Builtin Enhanced Info": "Kompatibilitätsbehandlung der Konfigurationsdatei",
"Proxy Layout Columns": "Anzahl der Spalten im Proxy-Layout",
"Auto Columns": "Automatische Anzahl der Spalten",
"Auto Log Clean": "Protokolle automatisch bereinigen",
"Never Clean": "Nie bereinigen",
"Retain _n Days": "{{n}} Tage behalten",
"Auto Delay Detection": "Automatische Latenzprüfung",
"Auto Delay Detection Info": "Überprüft regelmäßig im Hintergrund die Latenz des aktuellen Knotens",
"Default Latency Test": "Standard-Testlink",
"Default Latency Test Info": "Dies wird nur für HTTP-Client-Anfragentests verwendet und hat keine Auswirkungen auf die Konfigurationsdatei.",
"Default Latency Timeout": "Test-Timeout",
"Hotkey Setting": "Tastenkombinationseinstellungen",
"Enable Global Hotkey": "Globale Tastenkombinationen aktivieren",
"open_or_close_dashboard": "Dashboard öffnen/schließen",
"clash_mode_rule": "Regel-Modus",
"clash_mode_global": "Globaler Modus",
"clash_mode_direct": "Direktverbindungs-Modus",
"toggle_system_proxy": "Systemproxy ein/ausschalten",
"toggle_tun_mode": "TUN-Modus ein/ausschalten",
"entry_lightweight_mode": "Leichtgewichtigen Modus betreten",
"Backup Setting": "Sicherungseinstellungen",
"Backup Setting Info": "Unterstützt die Sicherung von Konfigurationsdateien über WebDAV",
"Runtime Config": "Aktuelle Konfiguration",
"Open Conf Dir": "Konfigurationsverzeichnis",
"Open Conf Dir Info": "Wenn die Software fehlerhaft funktioniert, !sichern Sie! alle Dateien in diesem Verzeichnis, löschen Sie sie und starten Sie die Software neu.",
"Open Core Dir": "Kernverzeichnis",
"Open Logs Dir": "Protokollverzeichnis",
"Check for Updates": "Auf Updates prüfen",
"Go to Release Page": "Zur Veröffentlichungsseite gehen",
"Portable Updater Error": "Die portable Version unterstützt keine In-App-Aktualisierung. Bitte laden Sie die Dateien manuell herunter und ersetzen Sie sie.",
"Break Change Update Error": "Dies ist eine wichtige Aktualisierung. Die In-App-Aktualisierung wird nicht unterstützt. Bitte deinstallieren Sie die Software und laden Sie die neue Version manuell herunter und installieren Sie sie.",
"Open Dev Tools": "Entwicklertools öffnen",
"Export Diagnostic Info": "Diagnoseinformationen exportieren",
"Exit": "Beenden",
"Verge Version": "Verge-Version",
"ReadOnly": "Schreibgeschützt",
"ReadOnlyMessage": "Bearbeitung im schreibgeschützten Modus nicht möglich",
"Filter": "Knoten filtern",
"Filter conditions": "Filterbedingungen",
"Match Case": "Groß-/Kleinschreibung beachten",
"Match Whole Word": "Ganzes Wort übereinstimmen",
"Use Regular Expression": "Regulären Ausdruck verwenden",
"Profile Imported Successfully": "Abonnement erfolgreich importiert",
"Profile Switched": "Abonnement gewechselt",
"Profile Reactivated": "Abonnement erneut aktiviert",
"Only YAML Files Supported": "Nur YAML-Dateien werden unterstützt",
"Settings Applied": "Einstellungen angewendet",
"Stopping Core...": "Kern wird gestoppt...",
"Restarting Core...": "Kern wird neu gestartet...",
"Installing Service...": "Service wird installiert...",
"Uninstalling Service...": "Service wird deinstalliert...",
"Service Installed Successfully": "Service erfolgreich installiert",
"Service Uninstalled Successfully": "Service erfolgreich deinstalliert",
"Proxy Daemon Duration Cannot be Less than 1 Second": "Das Intervall des Proxy-Daemons darf nicht weniger als 1 Sekunde betragen.",
"Invalid Bypass Format": "Ungültiges Format für die Proxy-Umgehung",
"Waiting for service to be ready...": "Auf Service-Bereitschaft gewartet...",
"Service was ready, but core restart might have issues or service became unavailable. Please check.": "Der Dienst war bereit, aber beim Neustart des Kerns könnten Probleme aufgetreten sein oder der Dienst ist möglicherweise nicht verfügbar. Bitte überprüfen Sie dies.",
"Service installation or core restart encountered issues. Service might not be available. Please check system logs.": "Bei der Dienstinstallation oder dem Neustart des Kerns sind Probleme aufgetreten. Der Dienst ist möglicherweise nicht verfügbar. Bitte prüfen Sie die Systemprotokolle.",
"Service is ready and core restarted": "Service ist bereit und Kern wurde neu gestartet",
"Core restarted. Service is now available.": "Kern wurde neu gestartet. Service ist jetzt verfügbar",
"Core Version Updated": "Kernversion wurde aktualisiert",
"Clash Core Restarted": "Clash-Kern wurde neu gestartet",
"GeoData Updated": "Geo-Daten wurden aktualisiert",
"Currently on the Latest Version": "Sie verwenden bereits die neueste Version",
"Import Subscription Successful": "Abonnement erfolgreich importiert",
"WebDAV Server URL": "WebDAV-Serveradresse http(s)://",
"Username": "Benutzername",
"Password": "Passwort",
"Backup": "Sichern",
"Filename": "Dateiname",
"Actions": "Aktionen",
"Restore": "Wiederherstellen",
"No Backups": "Keine Sicherungen vorhanden",
"WebDAV URL Required": "Die WebDAV-Serveradresse darf nicht leer sein",
"Invalid WebDAV URL": "Ungültiges Format für die WebDAV-Serveradresse",
"Username Required": "Der Benutzername darf nicht leer sein",
"Password Required": "Das Passwort darf nicht leer sein",
"WebDAV Config Saved": "WebDAV-Konfiguration erfolgreich gespeichert",
"WebDAV Config Save Failed": "Speichern der WebDAV-Konfiguration fehlgeschlagen: {{error}}",
"Backup Created": "Sicherung erfolgreich erstellt",
"Backup Failed": "Sicherung fehlgeschlagen: {{error}}",
"Delete Backup": "Sicherung löschen",
"Restore Backup": "Sicherung wiederherstellen",
"Backup Time": "Sicherungszeit",
"Restore Success, App will restart in 1s": "Wiederherstellung erfolgreich. Die App wird in 1 Sekunde neu starten.",
"Failed to fetch backup files": "Abrufen der Sicherungsdateien fehlgeschlagen",
"Profile": "Konfiguration",
"Web UI": "Web-Oberfläche",
"Dashboard": "Dashboard",
"Restart App": "App neu starten",
"Restart Clash Core": "Clash-Kern neu starten",
"TUN Mode": "TUN-Modus",
"Copy Env": "Umgebungsvariablen kopieren",
"Conf Dir": "Konfigurationsverzeichnis",
"Core Dir": "Kernverzeichnis",
"Logs Dir": "Protokollverzeichnis",
"Open Dir": "Verzeichnis öffnen",
"More": "Mehr",
"Rule Mode": "Rule Mode",
"Global Mode": "Global Mode",
"Direct Mode": "Direct Mode",
"Enable Tray Speed": "Tray-Geschwindigkeit aktivieren",
"Enable Tray Icon": "Tray-Symbol aktivieren",
"LightWeight Mode": "Leichtgewichtiger Modus",
"LightWeight Mode Info": "GUI-Oberfläche schließen, nur den Kern laufen lassen",
"LightWeight Mode Settings": "Einstellungen für den Leichtgewichtigen Modus",
"Enter LightWeight Mode Now": "Sofort in den Leichtgewichtigen Modus wechseln",
"Auto Enter LightWeight Mode": "Automatisch in den Leichtgewichtigen Modus wechseln",
"Auto Enter LightWeight Mode Info": "Wenn diese Option aktiviert ist, wird der Leichtgewichtige Modus automatisch aktiviert, nachdem das Fenster für eine bestimmte Zeit geschlossen wurde.",
"Auto Enter LightWeight Mode Delay": "Verzögerung beim automatischen Wechsel in den Leichtgewichtigen Modus",
"When closing the window, LightWeight Mode will be automatically activated after _n minutes": "Nach dem Schließen des Fensters wird der Leichtgewichtige Modus automatisch nach {{n}} Minuten aktiviert.",
"Config Validation Failed": "Abonnement-Konfigurationsüberprüfung fehlgeschlagen. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Die Änderungen wurden rückgängig gemacht. Fehlerdetails: ",
"Boot Config Validation Failed": "Start-Abonnement-Konfigurationsüberprüfung fehlgeschlagen. Die Standardkonfiguration wurde verwendet, um die App zu starten. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Fehlerdetails: ",
"Core Change Config Validation Failed": "Konfigurationsüberprüfung beim Wechsel des Kerns fehlgeschlagen. Die Standardkonfiguration wurde verwendet, um die App zu starten. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Fehlerdetails: ",
"Config Validation Process Terminated": "Validierungsprozess abgebrochen",
"Script Syntax Error": "Skript-Syntaxfehler. Die Änderungen wurden rückgängig gemacht.",
"Script Missing Main": "Skriptfehler. Die Änderungen wurden rückgängig gemacht.",
"File Not Found": "Datei nicht gefunden. Die Änderungen wurden rückgängig gemacht.",
"Script File Error": "Skript-Dateifehler. Die Änderungen wurden rückgängig gemacht.",
"Core Changed Successfully": "Kern erfolgreich gewechselt",
"Failed to Change Core": "Kernwechsel fehlgeschlagen",
"YAML Syntax Error": "YAML-Syntaxfehler. Die Änderungen wurden rückgängig gemacht.",
"YAML Read Error": "YAML-Lesefehler. Die Änderungen wurden rückgängig gemacht.",
"YAML Mapping Error": "YAML-Mappingfehler. Die Änderungen wurden rückgängig gemacht.",
"YAML Key Error": "YAML-Schlüsselfehler. Die Änderungen wurden rückgängig gemacht.",
"YAML Error": "YAML-Fehler. Die Änderungen wurden rückgängig gemacht.",
"Merge File Syntax Error": "Syntaxfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"Merge File Mapping Error": "Mappingfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"Merge File Key Error": "Schlüsselfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"Merge File Error": "Fehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"Service Administrator Prompt": "Clash Verge benötigt Administratorrechte, um den Systemdienst zu installieren.",
"DNS Settings": "DNS-Einstellungen",
"DNS settings saved": "DNS-Einstellungen wurden gespeichert",
"DNS Overwrite": "DNS-Überschreibung",
"DNS Settings Warning": "Wenn Sie sich nicht mit diesen Einstellungen auskennen, ändern Sie sie nicht und lassen Sie die DNS-Überschreibung aktiviert.",
"Enable DNS": "DNS aktivieren",
"DNS Listen": "DNS-Lauschangabe",
"Enhanced Mode": "Erweiterter Modus",
"Fake IP Range": "Fake-IP-Bereich",
"Fake IP Filter Mode": "Fake-IP-Filtermodus",
"Enable IPv6 DNS resolution": "IPv6-DNS-Auflösung aktivieren",
"Prefer H3": "HTTP/3 bevorzugen",
"DNS DOH使用HTTP/3": "DNS DOH verwendet HTTP/3-Protokoll",
"Respect Rules": "Routierungsregeln beachten",
"DNS connections follow routing rules": "DNS-Verbindungen folgen den Routierungsregeln",
"Use Hosts": "Hosts verwenden",
"Enable to resolve hosts through hosts file": "Aktivieren Sie die Auflösung von Hosts über die hosts-Datei",
"Use System Hosts": "System-Hosts verwenden",
"Enable to resolve hosts through system hosts file": "Aktivieren Sie die Auflösung von Hosts über die System-hosts-Datei",
"Direct Nameserver Follow Policy": "Direkte Namenserver folgen der Strategie",
"Whether to follow nameserver policy": "Ob die Namenserver-Strategie befolgt werden soll",
"Default Nameserver": "Standard-Namenserver",
"Default DNS servers used to resolve DNS servers": "Standard-DNS-Server, die zum Auflösen von DNS-Servern verwendet werden",
"Nameserver": "Namenserver",
"List of DNS servers": "Liste der DNS-Server, getrennt durch Kommas",
"Fallback": "Rückfallserver",
"List of fallback DNS servers": "Liste der Rückfall-DNS-Server, getrennt durch Kommas",
"Proxy Server Nameserver": "Proxy-Server-Namenserver",
"Proxy Node Nameserver": "Proxy-Knoten-Namenserver, nur für die Auflösung der Domains von Proxy-Knoten verwendet, getrennt durch Kommas",
"Direct Nameserver": "Direkter Namenserver",
"Direct outbound Nameserver": "Direkter Ausgangs-Namenserver, unterstützt das Schlüsselwort system, getrennt durch Kommas",
"Fake IP Filter": "Fake-IP-Filter",
"Domains that skip fake IP resolution": "Domains, die die Fake-IP-Auflösung überspringen, getrennt durch Kommas",
"Nameserver Policy": "Namenserver-Strategie",
"Domain-specific DNS server": "Domain-spezifischer DNS-Server, mehrere Server getrennt durch Semikolons, Format: domain=server1;server2",
"Fallback Filter Settings": "Rückfallfilter-Einstellungen",
"GeoIP Filtering": "GeoIP-Filterung",
"Enable GeoIP filtering for fallback": "GeoIP-Rückfallfilterung aktivieren",
"GeoIP Code": "GeoIP-Ländercode",
"Fallback IP CIDR": "Rückfall-IP-CIDR",
"IP CIDRs not using fallback servers": "IP-CIDRs, die keine Rückfallserver verwenden, getrennt durch Kommas",
"Fallback Domain": "Rückfall-Domäne",
"Domains using fallback servers": "Domains, die Rückfallserver verwenden, getrennt durch Kommas",
"Hosts Settings": "Hosts-Einstellungen",
"Hosts": "Hosts",
"Custom domain to IP or domain mapping": "Benutzerdefinierte Zuordnung von Domains zu IPs oder Domains, getrennt durch Kommas",
"Home Settings": "Startseite-Einstellungen",
"Profile Card": "Abonnement-Karte",
"Current Proxy Card": "Aktueller Proxy-Karte",
"Network Settings Card": "Netzwerkeinstellungen-Karte",
"Proxy Mode Card": "Proxy-Modus-Karte",
"Traffic Stats Card": "Verkehrsstatistik-Karte",
"Clash Info Cards": "Clash-Informationen-Karten",
"System Info Cards": "Systeminformationen-Karten",
"Website Tests Card": "Website-Tests-Karte",
"Traffic Stats": "Verkehrsstatistik",
"Website Tests": "Website-Tests",
"Clash Info": "Clash-Informationen",
"Core Version": "Kernversion",
"System Proxy Address": "Systemproxy-Adresse",
"Uptime": "Laufzeit",
"Rules Count": "Anzahl der Regeln",
"System Info": "Systeminformationen",
"OS Info": "Betriebssysteminformationen",
"Running Mode": "Betriebsmodus",
"Sidecar Mode": "Benutzermodus",
"Administrator Mode": "Administrator-Modus",
"Last Check Update": "Letzte Aktualitätsprüfung",
"Click to import subscription": "Klicken Sie hier, um ein Abonnement zu importieren.",
"Last Update failed": "Letzte Aktualisierung fehlgeschlagen",
"Next Up": "Nächste Aktualisierung",
"No schedule": "Kein Zeitplan",
"Unknown": "Unbekannt",
"Auto update disabled": "Automatische Aktualisierung deaktiviert",
"Update subscription successfully": "Abonnement erfolgreich aktualisiert",
"Update with Clash proxy successfully": "Aktualisierung mit Clash-Proxy erfolgreich",
"Update failed even with Clash proxy": "Aktualisierung auch mit Clash-Proxy fehlgeschlagen",
"Profile creation failed, retrying with Clash proxy...": "Erstellung des Abonnements fehlgeschlagen. Versuche es mit dem Clash-Proxy erneut...",
"Profile creation succeeded with Clash proxy": "Erstellung des Abonnements mit Clash-Proxy erfolgreich",
"Import failed, retrying with Clash proxy...": "Import des Abonnements fehlgeschlagen. Versuche es mit dem Clash-Proxy erneut...",
"Profile Imported with Clash proxy": "Abonnement mit Clash-Proxy importiert",
"Import failed even with Clash proxy": "Import des Abonnements auch mit Clash-Proxy fehlgeschlagen",
"Current Node": "Aktueller Knoten",
"No active proxy node": "Kein aktiver Proxy-Knoten",
"Network Settings": "Netzwerkeinstellungen",
"Proxy Mode": "Proxy-Modus",
"Group": "Proxy-Gruppe",
"Proxy": "Knoten",
"IP Information Card": "IP-Informationen-Karte",
"IP Information": "IP-Informationen",
"Failed to get IP info": "Abrufen der IP-Informationen fehlgeschlagen",
"ISP": "Internetdienstanbieter",
"ASN": "Autonomes Systemnummer",
"ORG": "Organisation",
"Location": "Standort",
"Timezone": "Zeitzone",
"Auto refresh": "Automatische Aktualisierung",
"Unlock Test": "Entsperrungstest",
"Pending": "Wartend auf Prüfung",
"Yes": "Unterstützt",
"No": "Nicht unterstützt",
"Failed": "Test fehlgeschlagen",
"Completed": "Prüfung abgeschlossen",
"Disallowed ISP": "Nicht zugelassener Internetdienstanbieter",
"Originals Only": "Nur Original",
"Unsupported Country/Region": "Nicht unterstütztes Land/Region",
"Configuration saved successfully": "Zufalls-Konfiguration erfolgreich gespeichert",
"Controller address copied to clipboard": "API-Port in die Zwischenablage kopiert",
"Secret copied to clipboard": "API-Schlüssel in die Zwischenablage kopiert",
"Copy to clipboard": "Klicken Sie hier, um zu kopieren",
"Enable one-click random API port and key. Click to randomize the port and key": "Einstellsichere Zufalls-API-Port- und Schlüsselgenerierung aktivieren. Klicken Sie, um Port und Schlüssel zu randomisieren"
}
+28
View File
@@ -0,0 +1,28 @@
{
"page": {
"title": "Verbindungen"
},
"components": {
"fields": {
"host": "Host",
"dlSpeed": "Download-Geschwindigkeit",
"ulSpeed": "Upload-Geschwindigkeit",
"chains": "Ketten",
"rule": "Regel",
"process": "Prozess",
"time": "Verbindungszeit",
"source": "Quelladresse",
"destination": "Zieladresse",
"destinationPort": "Zielport",
"type": "Typ"
},
"order": {
"default": "Default",
"uploadSpeed": "Upload-Geschwindigkeit",
"downloadSpeed": "Download-Geschwindigkeit"
},
"actions": {
"closeConnection": "Verbindung schließen"
}
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"page": {
"tooltips": {
"lightweightMode": "Leichtgewichtiger Modus",
"manual": "Bedienungsanleitung",
"settings": "Startseite-Einstellungen"
},
"cards": {
"trafficStats": "Verkehrsstatistik",
"networkSettings": "Netzwerkeinstellungen",
"proxyMode": "Proxy-Modus"
},
"settings": {
"cards": {
"profile": "Abonnement-Karte",
"currentProxy": "Aktueller Proxy-Karte",
"network": "Netzwerkeinstellungen-Karte",
"proxyMode": "Proxy-Modus-Karte",
"traffic": "Verkehrsstatistik-Karte",
"tests": "Website-Tests-Karte",
"ip": "IP-Informationen-Karte",
"clashInfo": "Clash-Informationen-Karten",
"systemInfo": "Systeminformationen-Karten"
},
"title": "Startseite-Einstellungen"
},
"title": "Startseite"
},
"components": {
"proxyTun": {
"status": {
"systemProxyEnabled": "Der Systemproxy ist aktiviert. Ihre Anwendungen werden über den Proxy auf das Netzwerk zugreifen.",
"systemProxyDisabled": "Der Systemproxy ist deaktiviert. Es wird empfohlen, diesen Eintrag für die meisten Benutzer zu aktivieren.",
"tunModeServiceRequired": "Der TUN-Modus erfordert den Service-Modus. Bitte installieren Sie zuerst den Service.",
"tunModeEnabled": "Der TUN-Modus ist aktiviert. Die Anwendungen werden über die virtuelle Netzwerkschnittstelle auf das Netzwerk zugreifen.",
"tunModeDisabled": "Der TUN-Modus ist deaktiviert. Dies ist für spezielle Anwendungen geeignet."
},
"tooltips": {
"systemProxy": "Ändern Sie die Proxy-Einstellungen des Betriebssystems. Wenn die Aktivierung fehlschlägt, können Sie die Proxy-Einstellungen des Betriebssystems manuell ändern.",
"tunMode": "Der TUN-Modus kann den gesamten Anwendungsverkehr übernehmen und eignet sich für spezielle Anwendungen, die die Systemproxy-Einstellungen nicht befolgen."
}
},
"clashInfo": {
"title": "Clash-Informationen",
"fields": {
"coreVersion": "Kernversion",
"systemProxyAddress": "Systemproxy-Adresse",
"mixedPort": "Mixed Port",
"uptime": "Laufzeit",
"rulesCount": "Anzahl der Regeln"
}
},
"systemInfo": {
"title": "Systeminformationen",
"fields": {
"osInfo": "Betriebssysteminformationen",
"autoLaunch": "Beim Start automatisch starten",
"runningMode": "Betriebsmodus",
"lastCheckUpdate": "Letzte Aktualitätsprüfung",
"vergeVersion": "Verge-Version"
},
"actions": {
"settings": "Einstellungen"
},
"tooltips": {
"autoLaunchAdmin": "Der Administrator-Modus unterstützt möglicherweise keine automatische Startfunktion."
},
"badges": {
"adminMode": "Administrator-Modus",
"serviceMode": "Service-Modus",
"sidecarMode": "Benutzermodus",
"adminServiceMode": "Admin + Service Mode"
}
},
"ipInfo": {
"title": "IP-Informationen",
"labels": {
"ip": "IP",
"asn": "Autonomes Systemnummer",
"isp": "Internetdienstanbieter",
"org": "Organisation",
"location": "Standort",
"timezone": "Zeitzone",
"autoRefresh": "Automatische Aktualisierung",
"unknown": "Unbekannt"
},
"errors": {
"load": "IP-Informationen konnten nicht abgerufen werden"
}
},
"currentProxy": {
"title": "Aktueller Knoten",
"actions": {
"refreshDelay": "Latenztest"
},
"labels": {
"globalMode": "Global Mode",
"directMode": "Direct Mode",
"group": "Proxy-Gruppe",
"proxy": "Knoten",
"noActiveNode": "Kein aktiver Proxy-Knoten"
}
},
"tests": {
"title": "Website-Tests"
},
"traffic": {
"metrics": {
"uploadSpeed": "Upload-Geschwindigkeit",
"downloadSpeed": "Download-Geschwindigkeit",
"activeConnections": "Aktive Verbindungen",
"memoryUsage": "Kern-Speichernutzung"
},
"legends": {
"upload": "Hochladen",
"download": "Herunterladen"
},
"patterns": {
"minutes": "{{time}} Minutes"
}
},
"clashMode": {
"errors": {
"communication": "Core communication error"
},
"labels": {
"rule": "Regel-Modus",
"global": "Globaler Modus",
"direct": "Direktverbindungs-Modus"
},
"descriptions": {
"rule": "Automatically choose proxies according to the rule set.",
"global": "Forward all network requests through the selected proxy.",
"direct": "Bypass the proxy and connect to the internet directly."
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import connections from "./connections.json";
import home from "./home.json";
import layout from "./layout.json";
import logs from "./logs.json";
import profiles from "./profiles.json";
import proxies from "./proxies.json";
import rules from "./rules.json";
import settings from "./settings.json";
import shared from "./shared.json";
import tests from "./tests.json";
const resources = {
connections: connections,
home: home,
layout: layout,
logs: logs,
profiles: profiles,
proxies: proxies,
rules: rules,
settings: settings,
shared: shared,
tests: tests,
};
export default resources;
+21
View File
@@ -0,0 +1,21 @@
{
"components": {
"navigation": {
"tabs": {
"home": "Startseite",
"proxies": "Proxy",
"profiles": "Abonnement",
"connections": "Verbindungen",
"rules": "Regeln",
"logs": "Protokolle",
"unlock": "Testen",
"settings": "Einstellungen"
},
"menu": {
"reorderMode": "Menu reorder mode",
"unlock": "Unlock menu order",
"lock": "Lock menu order"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"page": {
"title": "Protokolle"
}
}
+182
View File
@@ -0,0 +1,182 @@
{
"page": {
"actions": {
"updateAll": "Alle Abonnements aktualisieren",
"viewRuntimeConfig": "Laufzeit-Abonnement anzeigen",
"reactivate": "Abonnement erneut aktivieren",
"import": "Importieren"
},
"batch": {
"actions": {
"delete": "Delete Selected Profiles",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"done": "Done"
},
"summary": {
"selected": "Selected",
"items": "items"
},
"title": "Batch Operations"
},
"importForm": {
"placeholder": "Abonnement-Datei-Link",
"actions": {
"paste": "Einfügen"
}
},
"feedback": {
"errors": {
"invalidUrl": "Invalid profile URL. Please enter a URL starting with http:// or https://",
"onlyYaml": "Nur YAML-Dateien werden unterstützt"
},
"notifications": {
"importRetry": "Import des Abonnements fehlgeschlagen. Versuche es mit dem Clash-Proxy erneut...",
"importFail": "Import des Abonnements auch mit Clash-Proxy fehlgeschlagen",
"importNeedsRefresh": "Profile imported but may need manual refresh",
"importSuccess": "Profile imported successfully, please restart if not visible",
"profileSwitched": "Abonnement gewechselt",
"profileReactivated": "Abonnement erneut aktiviert",
"switchInterrupted": "Profile switch interrupted by new selection",
"batchDeleted": "Selected profiles deleted successfully"
},
"notices": {
"forceRefreshCompleted": "Force refresh completed",
"emergencyRefreshFailed": "Emergency refresh failed: {{message}}"
}
},
"title": "Abonnement"
},
"components": {
"card": {
"labels": {
"clickToImport": "Klicken Sie hier, um ein Abonnement zu importieren."
}
},
"fileInput": {
"chooseFile": "Datei auswählen"
},
"menu": {
"home": "Startseite",
"select": "Verwenden",
"editInfo": "Informationen bearbeiten",
"editFile": "Datei bearbeiten",
"editRules": "Regeln bearbeiten",
"editProxies": "Knoten bearbeiten",
"editGroups": "Proxy-Gruppen bearbeiten",
"extendConfig": "Erweiterte Überdeckungskonfiguration",
"extendScript": "Erweitertes Skript",
"openFile": "Datei öffnen",
"update": "Aktualisieren",
"updateViaProxy": "Update via proxy"
},
"more": {
"global": {
"merge": "Global Merge",
"script": "Global Script"
},
"chips": {
"merge": "Merge",
"script": "Script"
}
},
"profileItem": {
"tooltips": {
"showLast": "Click to show last update time",
"showNext": "Click to show next update"
},
"status": {
"lastUpdateFailed": "Letzte Aktualisierung fehlgeschlagen",
"nextUp": "Nächste Aktualisierung",
"noSchedule": "Kein Zeitplan",
"unknown": "Unbekannt",
"autoUpdateDisabled": "Automatische Aktualisierung deaktiviert"
}
}
},
"modals": {
"profileForm": {
"title": {
"create": "Neue Konfiguration erstellen",
"edit": "Konfiguration bearbeiten"
},
"fields": {
"type": "Typ",
"description": "Beschreibung",
"subscriptionUrl": "Abonnement-Link",
"httpTimeout": "HTTP Request Timeout",
"updateInterval": "Aktualisierungsintervall",
"useSystemProxy": "Systemproxy zur Aktualisierung verwenden",
"useClashProxy": "Kernel-Proxy zur Aktualisierung verwenden",
"acceptInvalidCerts": "Allows Invalid Certificates (Danger)",
"allowAutoUpdate": "Allow Auto Update"
},
"feedback": {
"notifications": {
"creationRetry": "Erstellung des Abonnements fehlgeschlagen. Versuche es mit dem Clash-Proxy erneut...",
"creationSuccess": "Erstellung des Abonnements mit Clash-Proxy erfolgreich"
}
}
},
"proxiesEditor": {
"title": "Knoten bearbeiten",
"placeholders": {
"multiUri": "Für mehrere URI verwenden Sie Zeilenumbrüche (Base64-Codierung wird unterstützt)"
},
"actions": {
"prepend": "Vorherigen Proxy-Knoten hinzufügen",
"append": "Nachfolgenden Proxy-Knoten hinzufügen"
}
},
"groupsEditor": {
"title": "Proxy-Gruppen bearbeiten",
"errors": {
"nameRequired": "Der Proxy-Gruppenname darf nicht leer sein",
"nameExists": "Der Proxy-Gruppenname existiert bereits"
},
"fields": {
"type": "Proxy-Gruppentyp",
"name": "Proxy-Gruppenname",
"icon": "Proxy-Gruppen-Symbol",
"proxies": "Proxy einführen",
"provider": "Proxy-Sammlung einführen",
"healthCheckUrl": "URL für Gesundheitstest",
"expectedStatus": "Erwarteter Statuscode",
"interval": "Prüfintervall",
"maxFailedTimes": "Maximale Anzahl fehlgeschlagener Versuche",
"interfaceName": "Ausgangsschnittstelle",
"routingMark": "Routierungsmarkierung",
"filter": "Knoten filtern",
"excludeFilter": "Knoten ausschließen",
"excludeType": "Typ der auszuschließenden Knoten",
"includeAll": "Alle Ausgangsproxy und Proxy-Sammlungen einführen",
"includeAllProxies": "Alle Ausgangsproxy einführen",
"includeAllProviders": "Alle Proxy-Sammlungen einführen"
},
"toggles": {
"lazy": "Lazy-Status",
"disableUdp": "UDP deaktivieren",
"hidden": "Proxy-Gruppe ausblenden"
},
"actions": {
"prepend": "Vorherige Proxy-Gruppe hinzufügen",
"append": "Nachfolgende Proxy-Gruppe hinzufügen"
}
},
"editor": {
"actions": {
"format": "Dokument formatieren"
},
"messages": {
"readOnly": "Bearbeitung im schreibgeschützten Modus nicht möglich"
}
},
"confirmDelete": {
"title": "Löschung bestätigen",
"message": "Diese Operation kann nicht rückgängig gemacht werden"
},
"logViewer": {
"title": "Skript-Konsole-Ausgabe"
}
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"page": {
"modes": {
"rule": "Rule",
"global": "Global",
"direct": "Direct"
},
"actions": {
"toggleChain": "🔗 Ketten-Proxy",
"connect": "Connect",
"disconnect": "Disconnect",
"connecting": "Connecting...",
"clearChainConfig": "Delete Chain Config"
},
"provider": {
"title": "Proxy-Sammlung",
"actions": {
"updateAll": "Alle aktualisieren",
"update": "Aktualisieren"
}
},
"rules": {
"title": "Proxy Rules",
"select": "Select Rules"
},
"labels": {
"proxyCount": "Anzahl der Knoten",
"delayCheckReset": "Latenztest durchführen, um Fixierung aufzuheben"
},
"tooltips": {
"locate": "Aktueller Knoten",
"delayCheck": "Latenztest",
"sortDefault": "Standard Sortierung",
"sortDelay": "Nach Latenz sortieren",
"sortName": "Nach Name sortieren",
"delayCheckUrl": "Latenztest-URL",
"showBasic": "Knotendetails ausblenden",
"showDetail": "Knotendetails anzeigen",
"filter": "Knoten filtern"
},
"placeholders": {
"delayCheckUrl": "Latenztest-URL"
},
"chain": {
"header": "Chain Proxy Config",
"empty": "No proxy chain configured",
"instruction": "Click nodes in order to add to proxy chain",
"minimumNodes": "Chain proxy requires at least 2 nodes",
"minimumNodesHint": "Chain proxy requires at least 2 nodes. Please add one more node.",
"connectFailed": "Failed to connect to proxy chain",
"disconnectFailed": "Failed to disconnect from proxy chain",
"duplicateNode": "Proxy node already exists in chain"
},
"messages": {
"directMode": "Direktverbindungs-Modus"
},
"title": {
"default": "Proxy-Gruppen",
"chainMode": "Proxy Chain Mode"
}
},
"feedback": {
"notifications": {
"provider": {
"updateSuccess": "{{name}} updated successfully",
"updateFailed": "Failed to update {{name}}: {{message}}",
"genericError": "Update failed: {{message}}",
"none": "No providers available to update",
"allUpdated": "All providers updated successfully"
}
}
},
"components": {
"enums": {
"strategies": {
"select": "Proxy manuell auswählen",
"url-test": "Proxy basierend auf URL-Latenztest auswählen",
"fallback": "Bei Nichtverfügbarkeit zu einem anderen Proxy wechseln",
"load-balance": "Proxy basierend auf Lastverteilung zuweisen",
"relay": "Basierend auf definiertem Proxy-Kette weiterleiten"
},
"policies": {
"DIRECT": "Direktverbindung",
"REJECT": "Anfrage ablehnen",
"REJECT-DROP": "Anfrage verwerfen",
"PASS": "Diese Regel überspringen"
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
{
"page": {
"provider": {
"trigger": "Regelsammlung",
"dialogTitle": "Regelsammlung",
"actions": {
"updateAll": "Alle aktualisieren",
"update": "Aktualisieren"
}
},
"title": "Regeln"
},
"feedback": {
"notifications": {
"provider": {
"updateSuccess": "{{name}} updated successfully",
"updateFailed": "Failed to update {{name}}: {{message}}",
"genericError": "Update failed: {{message}}",
"none": "No providers available to update",
"allUpdated": "All providers updated successfully"
}
}
},
"modals": {
"editor": {
"form": {
"labels": {
"type": "Regeltyp",
"content": "Regelinhalt",
"proxyPolicy": "Proxy-Strategie"
},
"toggles": {
"noResolve": "DNS-Auflösung überspringen"
},
"actions": {
"prependRule": "Vorherige Regel hinzufügen",
"appendRule": "Nachfolgende Regel hinzufügen"
},
"validation": {
"conditionRequired": "Regelbedingung fehlt",
"invalidRule": "Ungültige Regel"
}
},
"ruleTypes": {
"DOMAIN": "Vollständigen Domainnamen übereinstimmen",
"DOMAIN-SUFFIX": "Domain-Suffix übereinstimmen",
"DOMAIN-KEYWORD": "Domain-Schlüsselwort übereinstimmen",
"DOMAIN-REGEX": "Domain-Regulärer Ausdruck übereinstimmen",
"GEOSITE": "Domainnamen in Geosite übereinstimmen",
"GEOIP": "IP-Ländercode übereinstimmen",
"SRC-GEOIP": "Quell-IP-Ländercode übereinstimmen",
"IP-ASN": "IP-ASN übereinstimmen",
"SRC-IP-ASN": "Quell-IP-ASN übereinstimmen",
"IP-CIDR": "IP-Adressbereich übereinstimmen",
"IP-CIDR6": "IP-Adressbereich übereinstimmen",
"SRC-IP-CIDR": "Quell-IP-Adressbereich übereinstimmen",
"IP-SUFFIX": "IP-Suffix-Bereich übereinstimmen",
"SRC-IP-SUFFIX": "Quell-IP-Suffix-Bereich übereinstimmen",
"SRC-PORT": "Quellportbereich der Anfrage übereinstimmen",
"DST-PORT": "Zielportbereich der Anfrage übereinstimmen",
"IN-PORT": "Eingangsport übereinstimmen",
"DSCP": "DSCP-Markierung (nur für TPROXY UDP-Eingang)",
"PROCESS-NAME": "Prozessnamen übereinstimmen (Android-Paketname)",
"PROCESS-PATH": "Vollständigen Prozesspfad übereinstimmen",
"PROCESS-NAME-REGEX": "Regulärer Ausdruck für vollständigen Prozessnamen übereinstimmen (Android-Paketname)",
"PROCESS-PATH-REGEX": "Regulärer Ausdruck für vollständigen Prozesspfad übereinstimmen",
"NETWORK": "Übertragungsprotokoll übereinstimmen (TCP/UDP)",
"UID": "Linux-USER-ID übereinstimmen",
"IN-TYPE": "Eingangstyp übereinstimmen",
"IN-USER": "Eingangsbenutzername übereinstimmen",
"IN-NAME": "Eingangsname übereinstimmen",
"SUB-RULE": "Unterregel",
"RULE-SET": "Regelsatz übereinstimmen",
"AND": "Logisches UND",
"OR": "Logisches ODER",
"NOT": "Logisches NICHT",
"MATCH": "Alle Anfragen übereinstimmen"
},
"title": "Regeln bearbeiten"
}
}
}
+573
View File
@@ -0,0 +1,573 @@
{
"page": {
"actions": {
"manual": "Bedienungsanleitung",
"telegram": "Telegram-Kanal",
"github": "GitHub-Projektadresse"
},
"title": "Einstellungen"
},
"sections": {
"system": {
"title": "Systemeinstellungen",
"toggles": {
"tunMode": "Virtual Network Interface-Modus",
"systemProxy": "Systemproxy"
},
"tooltips": {
"autoLaunchAdmin": "Der Administrator-Modus unterstützt möglicherweise keine automatische Startfunktion.",
"silentStart": "Die Anwendung wird im Hintergrund gestartet, ohne dass das Programmfenster angezeigt wird."
},
"fields": {
"autoLaunch": "Automatischer Start",
"silentStart": "Leiser Start"
},
"notifications": {
"tunMode": {
"autoDisabled": "TUN Mode automatically disabled due to service unavailable",
"autoDisableFailed": "Failed to disable TUN Mode automatically"
}
}
},
"proxyControl": {
"tooltips": {
"systemProxy": "Ändern Sie die Proxy-Einstellungen des Betriebssystems. Wenn die Aktivierung fehlschlägt, können Sie die Proxy-Einstellungen des Betriebssystems manuell ändern.",
"tunMode": "Der TUN-Modus (Virtual Network Interface) übernimmt den gesamten Systemverkehr. Wenn dieser Modus aktiviert ist, muss der Systemproxy nicht geöffnet werden.",
"tunUnavailable": "TUN-Modus erfordert Service-Modus oder Administrator-Modus"
},
"actions": {
"installService": "Service installieren",
"uninstallService": "Dienst deinstallieren"
},
"fields": {
"systemProxy": "Systemproxy",
"tunMode": "Virtual Network Interface-Modus"
}
},
"externalController": {
"title": "Adresse des externen Controllers",
"fields": {
"enable": "Enable External Controller",
"address": "Adresse des externen Controllers",
"secret": "API-Zugangsschlüssel"
},
"placeholders": {
"address": "Erforderlich",
"secret": "Empfohlene Einstellung"
},
"tooltips": {
"copy": "In die Zwischenablage kopieren"
},
"messages": {
"addressRequired": "Controller address cannot be empty",
"secretRequired": "Secret cannot be empty",
"copyFailed": "Kopieren fehlgeschlagen",
"controllerCopied": "API-Port in die Zwischenablage kopiert",
"secretCopied": "API-Schlüssel in die Zwischenablage kopiert"
}
},
"externalCors": {
"title": "Externe CORS-Konfiguration",
"fields": {
"allowPrivateNetwork": "Zugriff auf privates Netzwerk erlauben",
"allowedOrigins": "Erlaubte Ursprünge"
},
"placeholders": {
"origin": "Bitte eine gültige URL eingeben"
},
"actions": {
"add": "Hinzufügen"
},
"messages": {
"alwaysIncluded": "Immer enthaltene Ursprünge: {{urls}}"
},
"tooltips": {
"open": "Einstellungen für externe CORS"
}
},
"appearance": {
"light": "Light",
"dark": "Dark",
"system": "System"
},
"clash": {
"title": "Clash-Einstellungen",
"form": {
"fields": {
"allowLan": "Netzwerkverbindung im lokalen Netzwerk zulassen",
"dnsOverwrite": "DNS-Überschreibung",
"ipv6": "IPv6",
"unifiedDelay": "Einheitliche Latenz",
"logLevel": "Protokolliergrad",
"portConfig": "Port-Konfiguration",
"external": "Externe Steuerung",
"webUI": "Web-Oberfläche",
"clashCore": "Clash-Kern",
"openUwpTool": "UWP-Tool öffnen",
"updateGeoData": "Geo-Daten aktualisieren"
},
"tooltips": {
"networkInterface": "Netzwerkschnittstelle",
"unifiedDelay": "Wenn die einheitliche Latenz aktiviert ist, werden zwei Latenztests durchgeführt, um die Latenzunterschiede zwischen verschiedenen Knotentypen aufgrund von Verbindungsaufbau und anderen Faktoren zu eliminieren.",
"logLevel": "Dies wirkt sich nur auf die Kernprotokolldateien im Verzeichnis Service im Protokollverzeichnis aus.",
"openUwpTool": "Ab Windows 8 wird die direkte Netzwerkverbindung von UWP-Anwendungen (z. B. Microsoft Store) zu lokalen Hosts eingeschränkt. Mit diesem Tool können Sie diese Einschränkung umgehen."
},
"options": {
"logLevel": {
"debug": "Debug",
"info": "Info",
"warning": "Warn",
"error": "Error",
"silent": "Silent"
}
}
}
}
},
"components": {
"verge": {
"basic": {
"title": "Verge-Grundeinstellungen",
"actions": {
"browse": "Durchsuchen"
},
"trayOptions": {
"showMainWindow": "Hauptfenster anzeigen",
"showTrayMenu": "Tray-Menü anzeigen",
"disable": "Deaktivieren"
},
"fields": {
"language": "Spracheinstellungen",
"themeMode": "Thema",
"trayClickEvent": "Tray-Klickereignis",
"copyEnvType": "Umgebungsvariablentyp kopieren",
"startPage": "Startseite",
"startupScript": "Startskript",
"themeSetting": "Thema-Einstellungen",
"layoutSetting": "Layout-Einstellungen",
"misc": "Sonstige Einstellungen",
"hotkeySetting": "Tastenkombinationseinstellungen"
}
},
"advanced": {
"title": "Verge-Erweiterte Einstellungen",
"tooltips": {
"backupInfo": "Unterstützt die Sicherung von Konfigurationsdateien über WebDAV",
"openConfDir": "Wenn die Software fehlerhaft funktioniert, !sichern Sie! alle Dateien in diesem Verzeichnis, löschen Sie sie und starten Sie die Software neu.",
"liteMode": "GUI-Oberfläche schließen, nur den Kern laufen lassen"
},
"actions": {
"copyVersion": "Copy Version"
},
"notifications": {
"latestVersion": "Sie verwenden bereits die neueste Version",
"versionCopied": "Version copied to clipboard"
},
"fields": {
"backupSetting": "Sicherungseinstellungen",
"runtimeConfig": "Aktuelle Konfiguration",
"openConfDir": "Konfigurationsverzeichnis",
"openCoreDir": "Kernverzeichnis",
"openLogsDir": "Protokollverzeichnis",
"checkUpdates": "Auf Updates prüfen",
"openDevTools": "Entwicklertools öffnen",
"liteModeSettings": "Einstellungen für den Leichtgewichtigen Modus",
"exit": "Beenden",
"exportDiagnostics": "Diagnoseinformationen exportieren",
"vergeVersion": "Verge-Version"
}
},
"theme": {
"title": "Thema-Einstellungen",
"fields": {
"primaryColor": "Hauptfarbe",
"secondaryColor": "Sekundärfarbe",
"primaryText": "Haupttextfarbe",
"secondaryText": "Sekundärtextfarbe",
"infoColor": "Informationsfarbe",
"warningColor": "Warnfarbe",
"errorColor": "Fehlerfarbe",
"successColor": "Erfolgsfarbe",
"fontFamily": "Schriftfamilie",
"cssInjection": "CSS-Einbindung"
},
"actions": {
"editCss": "Edit CSS"
},
"dialogs": {
"editCssTitle": "Edit CSS"
}
},
"layout": {
"title": "Layout-Einstellungen",
"fields": {
"preferSystemTitlebar": "Prefer System Titlebar",
"trafficGraph": "Verkehrsdiagramm",
"memoryUsage": "Kern-Speichernutzung",
"proxyGroupIcon": "Proxy-Gruppen-Symbol",
"hoverNavigator": "Hover Jump Navigator",
"hoverNavigatorDelay": "Hover Jump Navigator Delay",
"navIcon": "Navigationsleiste-Symbol",
"trayIcon": "Tray-Symbol",
"showProxyGroupsInline": "Show Proxy Groups Inline",
"commonTrayIcon": "Standard-Tray-Symbol",
"systemProxyTrayIcon": "Systemproxy-Tray-Symbol",
"tunTrayIcon": "TUN-Modus-Tray-Symbol",
"enableTrayIcon": "Tray-Symbol aktivieren",
"enableTraySpeed": "Tray-Geschwindigkeit aktivieren"
},
"tooltips": {
"hoverNavigator": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
"hoverNavigatorDelay": "Delay before auto scrolling when hovering, in milliseconds"
},
"options": {
"icon": {
"monochrome": "Monochromes Symbol",
"colorful": "Farbiges Symbol",
"disable": "Deaktivieren"
}
}
}
}
},
"modals": {
"clashPort": {
"title": "Port-Konfiguration",
"fields": {
"mixed": "Mischter Proxy-Port",
"socks": "SOCKS-Proxy-Port",
"http": "HTTP(S)-Proxy-Port",
"redir": "Redir-Transparenter Proxy-Port",
"tproxy": "TPROXY-Transparenter Proxy-Port"
},
"actions": {
"random": "Zufälliger Port"
},
"messages": {
"saved": "Port settings saved",
"saveFailed": "Failed to save port settings"
}
},
"clashCore": {
"variants": {
"release": "Stabile Version",
"alpha": "Alpha-Version"
}
},
"liteMode": {
"title": "Einstellungen für den Leichtgewichtigen Modus",
"actions": {
"enterNow": "Sofort in den Leichtgewichtigen Modus wechseln"
},
"toggles": {
"autoEnter": "Automatisch in den Leichtgewichtigen Modus wechseln"
},
"tooltips": {
"autoEnter": "Wenn diese Option aktiviert ist, wird der Leichtgewichtige Modus automatisch aktiviert, nachdem das Fenster für eine bestimmte Zeit geschlossen wurde."
},
"fields": {
"delay": "Verzögerung beim automatischen Wechsel in den Leichtgewichtigen Modus"
},
"messages": {
"autoEnterHint": "Nach dem Schließen des Fensters wird der Leichtgewichtige Modus automatisch nach {{n}} Minuten aktiviert."
}
},
"backup": {
"title": "Sicherungseinstellungen",
"tabs": {
"local": "Local backup",
"webdav": "WebDAV backup"
},
"actions": {
"selectTarget": "Select backup target",
"backup": "Sichern",
"export": "Export",
"exportBackup": "Export Backup",
"deleteBackup": "Sicherung löschen",
"restore": "Wiederherstellen",
"restoreBackup": "Sicherung wiederherstellen"
},
"fields": {
"webdavUrl": "WebDAV-Serveradresse http(s)://",
"username": "Benutzername",
"info": "Backups are stored locally in the application data directory. Use the list below to restore or delete backups."
},
"messages": {
"webdavUrlRequired": "Die WebDAV-Serveradresse darf nicht leer sein",
"invalidWebdavUrl": "Ungültiges Format für die WebDAV-Serveradresse",
"usernameRequired": "Der Benutzername darf nicht leer sein",
"passwordRequired": "Das Passwort darf nicht leer sein",
"webdavConfigSaved": "WebDAV-Konfiguration erfolgreich gespeichert",
"webdavConfigSaveFailed": "Speichern der WebDAV-Konfiguration fehlgeschlagen: {{error}}",
"backupCreated": "Sicherung erfolgreich erstellt",
"backupFailed": "Sicherung fehlgeschlagen: {{error}}",
"localBackupCreated": "Local backup created successfully",
"localBackupFailed": "Local backup failed",
"restoreSuccess": "Wiederherstellung erfolgreich. Die App wird in 1 Sekunde neu starten.",
"localBackupExported": "Local backup exported successfully",
"localBackupExportFailed": "Failed to export local backup",
"confirmDelete": "Confirm to delete this backup file?",
"confirmRestore": "Confirm to restore this backup file?"
},
"table": {
"filename": "Dateiname",
"backupTime": "Sicherungszeit",
"actions": "Aktionen",
"noBackups": "Keine Sicherungen vorhanden",
"rowsPerPage": "Rows per page"
}
},
"misc": {
"title": "Sonstige Einstellungen",
"fields": {
"appLogLevel": "Anwendungs-Protokolliergrad",
"appLogMaxSize": "App Log Max Size",
"appLogMaxCount": "App Log Max Count",
"autoCloseConnections": "Verbindungen automatisch schließen",
"autoCheckUpdate": "Automatisch auf Updates prüfen",
"enableBuiltinEnhanced": "Eingebaute Verbesserungen aktivieren",
"proxyLayoutColumns": "Anzahl der Spalten im Proxy-Layout",
"autoLogClean": "Protokolle automatisch bereinigen",
"autoDelayDetection": "Automatische Latenzprüfung",
"defaultLatencyTest": "Standard-Testlink",
"defaultLatencyTimeout": "Test-Timeout"
},
"tooltips": {
"autoCloseConnections": "Wenn der ausgewählte Knoten in der Proxy-Gruppe oder der Proxy-Modus geändert wird, werden die bestehenden Verbindungen geschlossen.",
"enableBuiltinEnhanced": "Kompatibilitätsbehandlung der Konfigurationsdatei",
"autoDelayDetection": "Überprüft regelmäßig im Hintergrund die Latenz des aktuellen Knotens",
"defaultLatencyTest": "Dies wird nur für HTTP-Client-Anfragentests verwendet und hat keine Auswirkungen auf die Konfigurationsdatei."
},
"options": {
"proxyLayoutColumns": {
"auto": "Automatische Anzahl der Spalten"
},
"autoLogClean": {
"never": "Nie bereinigen",
"retainDays": "{{n}} Tage behalten"
}
}
},
"update": {
"title": "New Version v{{version}}",
"actions": {
"goToRelease": "Zur Veröffentlichungsseite gehen",
"update": "Aktualisieren"
},
"messages": {
"portableError": "Die portable Version unterstützt keine In-App-Aktualisierung. Bitte laden Sie die Dateien manuell herunter und ersetzen Sie sie.",
"breakChangeError": "Dies ist eine wichtige Aktualisierung. Die In-App-Aktualisierung wird nicht unterstützt. Bitte deinstallieren Sie die Software und laden Sie die neue Version manuell herunter und installieren Sie sie."
}
},
"sysproxy": {
"title": "Systemproxy-Einstellungen",
"fieldsets": {
"currentStatus": "Aktueller Systemproxy"
},
"fields": {
"enableStatus": "Aktivierungsstatus: ",
"serverAddr": "Serveradresse: ",
"pacUrl": "PAC-Adresse: ",
"proxyHost": "Proxy-Host",
"usePacMode": "PAC-Modus verwenden",
"proxyGuard": "Systemproxy-Schutz",
"guardDuration": "Proxy-Schutz-Intervall",
"alwaysUseDefaultBypass": "Immer die Standard-Umgehung verwenden",
"proxyBypass": "Proxy-Umgehungseinstellungen: ",
"bypass": "Aktuelle Umgehung: ",
"pacScriptContent": "PAC-Skriptinhalt"
},
"tooltips": {
"proxyGuard": "Aktivieren Sie diese Option, um zu verhindern, dass andere Software die Proxy-Einstellungen des Betriebssystems ändert."
},
"messages": {
"durationTooShort": "Das Intervall des Proxy-Daemons darf nicht weniger als 1 Sekunde betragen.",
"invalidBypass": "Ungültiges Format für die Proxy-Umgehung",
"invalidProxyHost": "Ungültiges Format für den Proxy-Host"
},
"actions": {
"editPac": "Bearbeiten PAC"
}
},
"tun": {
"title": "Virtual Network Interface-Modus",
"fields": {
"stack": "TUN-Modus-Stack",
"device": "Device Name",
"autoRoute": "Globale Routing automatisch einstellen",
"strictRoute": "Strenges Routing",
"autoDetectInterface": "Netzwerkschnittstelle automatisch auswählen",
"dnsHijack": "DNS-Hijacking",
"mtu": "Maximale Übertragungseinheit"
},
"tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers"
},
"messages": {
"applied": "Einstellungen angewendet"
}
},
"dns": {
"dialog": {
"title": "DNS-Überschreibung",
"warning": "Wenn Sie sich nicht mit diesen Einstellungen auskennen, ändern Sie sie nicht und lassen Sie die DNS-Überschreibung aktiviert."
},
"sections": {
"general": "DNS-Einstellungen",
"fallbackFilter": "Rückfallfilter-Einstellungen",
"hosts": "Hosts-Einstellungen"
},
"fields": {
"enable": "DNS aktivieren",
"listen": "DNS-Lauschangabe",
"enhancedMode": "Erweiterter Modus",
"fakeIpRange": "Fake-IP-Bereich",
"fakeIpFilterMode": "Fake-IP-Filtermodus",
"ipv6": {
"label": "IPv6",
"description": "IPv6-DNS-Auflösung aktivieren"
},
"preferH3": {
"label": "HTTP/3 bevorzugen",
"description": "DNS DOH verwendet HTTP/3-Protokoll"
},
"respectRules": {
"label": "Routierungsregeln beachten",
"description": "DNS-Verbindungen folgen den Routierungsregeln"
},
"useHosts": {
"label": "Hosts verwenden",
"description": "Aktivieren Sie die Auflösung von Hosts über die hosts-Datei"
},
"useSystemHosts": {
"label": "System-Hosts verwenden",
"description": "Aktivieren Sie die Auflösung von Hosts über die System-hosts-Datei"
},
"directPolicy": {
"label": "Direkte Namenserver folgen der Strategie",
"description": "Ob die Namenserver-Strategie befolgt werden soll"
},
"defaultNameserver": {
"label": "Standard-Namenserver",
"description": "Standard-DNS-Server, die zum Auflösen von DNS-Servern verwendet werden"
},
"nameserver": {
"label": "Namenserver",
"description": "Liste der DNS-Server, getrennt durch Kommas"
},
"fallback": {
"label": "Rückfallserver",
"description": "Liste der Rückfall-DNS-Server, getrennt durch Kommas"
},
"proxy": {
"label": "Proxy-Server-Namenserver",
"description": "Proxy-Knoten-Namenserver, nur für die Auflösung der Domains von Proxy-Knoten verwendet, getrennt durch Kommas"
},
"directNameserver": {
"label": "Direkter Namenserver",
"description": "Direkter Ausgangs-Namenserver, unterstützt das Schlüsselwort system, getrennt durch Kommas"
},
"fakeIpFilter": {
"label": "Fake-IP-Filter",
"description": "Domains, die die Fake-IP-Auflösung überspringen, getrennt durch Kommas"
},
"nameserverPolicy": {
"label": "Namenserver-Strategie",
"description": "Domain-spezifischer DNS-Server, mehrere Server getrennt durch Semikolons, Format: domain=server1;server2"
},
"geoipFiltering": {
"label": "GeoIP-Filterung",
"description": "GeoIP-Rückfallfilterung aktivieren"
},
"geoipCode": "GeoIP-Ländercode",
"fallbackIpCidr": {
"label": "Rückfall-IP-CIDR",
"description": "IP-CIDRs, die keine Rückfallserver verwenden, getrennt durch Kommas"
},
"fallbackDomain": {
"label": "Rückfall-Domäne",
"description": "Domains, die Rückfallserver verwenden, getrennt durch Kommas"
},
"hosts": {
"label": "Hosts",
"description": "Benutzerdefinierte Zuordnung von Domains zu IPs oder Domains, getrennt durch Kommas"
}
},
"messages": {
"saved": "DNS-Einstellungen wurden gespeichert",
"configError": "DNS configuration error:"
},
"errors": {
"invalid": "Invalid configuration",
"invalidYaml": "Invalid YAML format"
}
},
"webUI": {
"actions": {
"openUrl": "Link öffnen"
},
"title": "Web-Oberfläche",
"messages": {
"supportedPlaceholders": "Unterstützt %host, %port, %secret",
"placeholderInstruction": "Verwenden Sie %host, %port, %secret für Host, Port und Zugangsschlüssel"
}
},
"hotkey": {
"toggles": {
"enableGlobal": "Globale Tastenkombinationen aktivieren"
},
"title": "Tastenkombinationseinstellungen",
"functions": {
"rule": "Regel-Modus",
"global": "Globaler Modus",
"openOrCloseDashboard": "Dashboard öffnen/schließen",
"toggleSystemProxy": "Systemproxy ein/ausschalten",
"toggleTunMode": "TUN-Modus ein/ausschalten",
"entryLightweightMode": "Leichtgewichtigen Modus betreten",
"direct": "Direktverbindungs-Modus"
}
},
"password": {
"prompts": {
"enterRoot": "Bitte geben Sie Ihr Root-Passwort ein."
}
},
"networkInterface": {
"title": "Netzwerkschnittstelle",
"fields": {
"ipAddress": "IP-Adresse",
"macAddress": "MAC-Adresse"
}
}
},
"feedback": {
"notifications": {
"clash": {
"restartSuccess": "Clash-Kern wurde neu gestartet",
"versionUpdated": "Kernversion wurde aktualisiert",
"alreadyLatestVersion": "Bereits die neueste Kernversion in Verwendung",
"changeSuccess": "Kern erfolgreich gewechselt",
"changeFailed": "Kernwechsel fehlgeschlagen",
"geoDataUpdated": "Geo-Daten wurden aktualisiert"
},
"clashService": {
"installSuccess": "Service erfolgreich installiert",
"uninstallSuccess": "Service erfolgreich deinstalliert"
},
"updater": {
"withClashProxySuccess": "Aktualisierung mit Clash-Proxy erfolgreich",
"withClashProxyFailed": "Aktualisierung auch mit Clash-Proxy fehlgeschlagen"
}
}
},
"statuses": {
"clash": {
"stopping": "Kern wird gestoppt...",
"restarting": "Kern wird neu gestartet..."
},
"clashService": {
"installing": "Service wird installiert...",
"uninstalling": "Service wird deinstalliert..."
}
}
}
+129
View File
@@ -0,0 +1,129 @@
{
"actions": {
"cancel": "Abbrechen",
"close": "Schließen",
"confirm": "Bestätigen",
"save": "Speichern",
"delete": "Löschen",
"edit": "Bearbeiten",
"new": "Neu",
"enable": "Aktivieren",
"upgrade": "Kern aktualisieren",
"restart": "Kern neustarten",
"resetToDefault": "Auf Standardwerte zurücksetzen",
"refresh": "Aktualisieren",
"retry": "Retry",
"refreshPage": "Refresh Page",
"showDetails": "Show Details",
"hideDetails": "Hide Details",
"listView": "Listenansicht",
"tableView": "Tabellenansicht",
"pause": "Pausieren",
"resume": "Fortsetzen",
"closeAll": "Alle schließen",
"clear": "Löschen"
},
"labels": {
"updateAt": "Aktualisiert am",
"timeout": "Timeout",
"icon": "Symbol",
"name": "Name",
"readOnly": "Schreibgeschützt",
"expireTime": "Ablaufzeit",
"updateTime": "Aktualisierungszeit",
"usedTotal": "Verwendet / Gesamt",
"from": "Von",
"password": "Passwort",
"retryAttempts": "Retry attempts",
"downloaded": "Heruntergeladen",
"uploaded": "Hochgeladen"
},
"statuses": {
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"saving": "Saving...",
"empty": "Leer"
},
"units": {
"milliseconds": "Millisekunden",
"seconds": "Sekunden",
"minutes": "Minuten",
"kilobytes": "KB",
"files": "Files"
},
"placeholders": {
"filter": "Filterbedingungen",
"matchCase": "Groß-/Kleinschreibung beachten",
"matchWholeWord": "Ganzes Wort übereinstimmen",
"useRegex": "Regulären Ausdruck verwenden"
},
"validation": {
"invalidRegex": "Invalid regular expression"
},
"window": {
"maximize": "Maximieren",
"minimize": "Minimieren"
},
"editorModes": {
"visualization": "Visualisierung",
"advanced": "Erweitert"
},
"feedback": {
"errors": {
"trafficStats": "Traffic Statistics Error",
"trafficStatsDescription": "The traffic statistics component encountered an error and has been disabled to prevent crashes."
},
"notices": {
"raw": "{{message}}",
"prefixedRaw": "{{prefix}} {{message}}"
},
"notifications": {
"importSuccess": "Abonnement erfolgreich importiert",
"importSubscriptionSuccess": "Abonnement erfolgreich importiert",
"importWithClashProxy": "Abonnement mit Clash-Proxy importiert",
"updateAvailable": "Update Available",
"saved": "Saved successfully",
"common": {
"copySuccess": "Kopieren erfolgreich",
"saveSuccess": "Zufalls-Konfiguration erfolgreich gespeichert",
"saveFailed": "Failed to save configuration"
}
},
"validation": {
"config": {
"failed": "Abonnement-Konfigurationsüberprüfung fehlgeschlagen. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Die Änderungen wurden rückgängig gemacht. Fehlerdetails: ",
"bootFailed": "Start-Abonnement-Konfigurationsüberprüfung fehlgeschlagen. Die Standardkonfiguration wurde verwendet, um die App zu starten. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Fehlerdetails: ",
"coreChangeFailed": "Konfigurationsüberprüfung beim Wechsel des Kerns fehlgeschlagen. Die Standardkonfiguration wurde verwendet, um die App zu starten. Bitte überprüfen Sie die Abonnement-Konfigurationsdatei. Fehlerdetails: ",
"processTerminated": "Validierungsprozess abgebrochen"
},
"script": {
"syntaxError": "Skript-Syntaxfehler. Die Änderungen wurden rückgängig gemacht.",
"missingMain": "Skriptfehler. Die Änderungen wurden rückgängig gemacht.",
"fileNotFound": "Datei nicht gefunden. Die Änderungen wurden rückgängig gemacht.",
"fileError": "Skript-Dateifehler. Die Änderungen wurden rückgängig gemacht."
},
"yaml": {
"syntaxError": "YAML-Syntaxfehler. Die Änderungen wurden rückgängig gemacht.",
"readError": "YAML-Lesefehler. Die Änderungen wurden rückgängig gemacht.",
"mappingError": "YAML-Mappingfehler. Die Änderungen wurden rückgängig gemacht.",
"keyError": "YAML-Schlüsselfehler. Die Änderungen wurden rückgängig gemacht.",
"generalError": "YAML-Fehler. Die Änderungen wurden rückgängig gemacht."
},
"merge": {
"syntaxError": "Syntaxfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"mappingError": "Mappingfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"keyError": "Schlüsselfehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht.",
"generalError": "Fehler in der Überdeckungsdatei. Die Änderungen wurden rückgängig gemacht."
}
}
},
"filters": {
"logLevels": {
"all": "ALL",
"debug": "DEBUG",
"info": "INFO",
"warn": "WARN",
"error": "ERROR"
}
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"page": {
"actions": {
"testAll": "Alle testen"
},
"title": "Testen"
},
"components": {
"item": {
"actions": {
"test": "Testen"
}
}
},
"modals": {
"test": {
"title": {
"create": "Neuen Test erstellen",
"edit": "Test bearbeiten"
},
"fields": {
"url": "Test-URL"
}
}
},
"statuses": {
"test": {
"pending": "Wartend auf Prüfung",
"yes": "Unterstützt",
"no": "Nicht unterstützt",
"failed": "Test fehlgeschlagen",
"completed": "Prüfung abgeschlossen",
"disallowedIsp": "Nicht zugelassener Internetdienstanbieter",
"originalsOnly": "Nur Original",
"noDisney": "No (IP Banned By Disney+)",
"unsupportedRegion": "Nicht unterstütztes Land/Region",
"failedNetwork": "Failed (Network Connection)"
}
},
"unlock": {
"page": {
"actions": {
"testing": "Wird getestet..."
},
"empty": "No unlock test items",
"messages": {
"detectionFailedWithName": "Erkennung für {{name}} fehlgeschlagen",
"detectionTimeout": "Detection timeout or failed"
},
"title": "Entsperrungstest"
}
}
}
-718
View File
@@ -1,718 +0,0 @@
{
"millis": "ms",
"seconds": "seconds",
"mins": "mins",
"Back": "Back",
"Close": "Close",
"Cancel": "Cancel",
"Confirm": "Confirm",
"Maximize": "Maximize",
"Minimize": "Minimize",
"Format document": "Format document",
"Empty": "Empty",
"New": "New",
"Edit": "Edit",
"Save": "Save",
"Delete": "Delete",
"Enable": "Enable",
"Disable": "Disable",
"Label-Home": "Home",
"Label-Proxies": "Proxies",
"Label-Profiles": "Profiles",
"Label-Connections": "Connections",
"Label-Rules": "Rules",
"Label-Logs": "Logs",
"Label-Unlock": "Test",
"Label-Settings": "Settings",
"Proxies": "Proxies",
"Proxy Groups": "Proxy Groups",
"Proxy Chain Mode": "Proxy Chain Mode",
"Connect": "Connect",
"Connecting...": "Connecting...",
"Disconnect": "Disconnect",
"Failed to connect to proxy chain": "Failed to connect to proxy chain",
"Proxy Provider": "Proxy Provider",
"Proxy Count": "Proxy Count",
"Update All": "Update All",
"Update At": "Update At",
"rule": "rule",
"global": "global",
"direct": "direct",
"Chain Proxy": "🔗 Chain Proxy",
"Chain Proxy Config": "Chain Proxy Config",
"Proxy Rules": "Proxy Rules",
"Select Rules": "Select Rules",
"Click nodes in order to add to proxy chain": "Click nodes in order to add to proxy chain",
"No proxy chain configured": "No proxy chain configured",
"Proxy Order": "Proxy Order",
"timeout": "Timeout",
"Clear All": "Clear All",
"script": "script",
"locate": "locate",
"Delay check": "Delay check",
"Sort by default": "Sort by default",
"Sort by delay": "Sort by delay",
"Sort by name": "Sort by name",
"Delay check URL": "Delay check URL",
"Delay check to cancel fixed": "Delay check to cancel fixed",
"Proxy basic": "Proxy basic",
"Proxy detail": "Proxy detail",
"Profiles": "Profiles",
"Update All Profiles": "Update All Profiles",
"View Runtime Config": "View Runtime Config",
"Reactivate Profiles": "Reactivate Profiles",
"Paste": "Paste",
"Profile URL": "Profile URL",
"Import": "Import",
"From": "From",
"Update Time": "Update Time",
"Used / Total": "Used / Total",
"Expire Time": "Expire Time",
"Create Profile": "Create Profile",
"Edit Profile": "Edit Profile",
"Edit Proxies": "Edit Proxies",
"Use newlines for multiple uri": "Use newlines for multiple uri(Base64 encoding supported)",
"Edit Rules": "Edit Rules",
"Rule Type": "Rule Type",
"Rule Content": "Rule Content",
"Proxy Policy": "Proxy Policy",
"No Resolve": "No Resolve",
"Prepend Rule": "Prepend Rule",
"Append Rule": "Append Rule",
"Prepend Group": "Prepend Group",
"Append Group": "Append Group",
"Prepend Proxy": "Prepend Proxy",
"Append Proxy": "Append Proxy",
"Rule Condition Required": "Rule Condition Required",
"Invalid Rule": "Invalid Rule",
"Advanced": "Advanced",
"Visualization": "Visualization",
"DOMAIN": "Matches the full domain name",
"DOMAIN-SUFFIX": "Matches the domain suffix",
"DOMAIN-KEYWORD": "Matches the domain keyword",
"DOMAIN-REGEX": "Matches the domain using regular expressions",
"GEOSITE": "Matches domains within the Geosite",
"GEOIP": "Matches the country code of the IP address",
"SRC-GEOIP": "Matches the country code of the source IP address",
"IP-ASN": "Matches the IP address's ASN",
"SRC-IP-ASN": "Matches the source IP address's ASN",
"IP-CIDR": "Matches the IP address range",
"IP-CIDR6": "Matches the IPv6 address range",
"SRC-IP-CIDR": "Matches the source IP address range",
"IP-SUFFIX": "Matches the IP address suffix range",
"SRC-IP-SUFFIX": "Matches the source IP address suffix range",
"SRC-PORT": "Matches the source port range",
"DST-PORT": "Matches the destination port range",
"IN-PORT": "Matches the inbound port",
"DSCP": "DSCP marking (only for tproxy UDP inbound)",
"PROCESS-NAME": "Matches the process name (Android package name)",
"PROCESS-PATH": "Matches the full process path",
"PROCESS-NAME-REGEX": "Matches the full process name using regular expressions (Android package name)",
"PROCESS-PATH-REGEX": "Matches the full process path using regular expressions",
"NETWORK": "Matches the transport protocol (tcp/udp)",
"UID": "Matches the Linux USER ID",
"IN-TYPE": "Matches the inbound type",
"IN-USER": "Matches the inbound username",
"IN-NAME": "Matches the inbound name",
"SUB-RULE": "Sub-rule",
"RULE-SET": "Matches the rule set",
"AND": "Logical AND",
"OR": "Logical OR",
"NOT": "Logical NOT",
"MATCH": "Matches all requests",
"DIRECT": "Data goes directly outbound",
"REJECT": "Intercepts requests",
"REJECT-DROP": "Discards requests",
"PASS": "Skips this rule when matched",
"Edit Groups": "Edit Proxy Groups",
"Group Type": "Group Type",
"select": "Select proxy manually",
"url-test": "Select proxy based on URL test delay",
"fallback": "Switch to another proxy on error",
"load-balance": "Distribute proxy based on load balancing",
"relay": "Pass through the defined proxy chain",
"Group Name": "Group Name",
"Use Proxies": "Use Proxies",
"Use Provider": "Use Provider",
"Health Check Url": "Health Check Url",
"Expected Status": "Expected Status",
"Interval": "Interval",
"Lazy": "Lazy",
"Timeout": "Timeout",
"Max Failed Times": "Max Failed Times",
"Interface Name": "Interface Name",
"Routing Mark": "Routing Mark",
"Include All": "Include All Proxies and Providers",
"Include All Providers": "Include All Providers",
"Include All Proxies": "Include All Proxies",
"Exclude Filter": "Exclude Filter",
"Exclude Type": "Exclude Type",
"Disable UDP": "Disable UDP",
"Hidden": "Hidden",
"Group Name Required": "Group Name Required",
"Group Name Already Exists": "Group Name Already Exists",
"Extend Config": "Extend Config",
"Extend Script": "Extend Script",
"Global Merge": "Global Extend Config",
"Global Script": "Global Extend Script",
"Type": "Type",
"Name": "Name",
"Descriptions": "Descriptions",
"Subscription URL": "Subscription URL",
"Update Interval": "Update Interval",
"Choose File": "Choose File",
"Use System Proxy": "Use System Proxy",
"Use Clash Proxy": "Use Clash Proxy",
"Accept Invalid Certs (Danger)": "Allows Invalid Certificates (Danger)",
"Refresh": "Refresh",
"Home": "Home",
"Select": "Select",
"Edit Info": "Edit Info",
"Edit File": "Edit File",
"Open File": "Open File",
"Update": "Update",
"Update via proxy": "Update via proxy",
"Update(Proxy)": "Update(Proxy)",
"Confirm deletion": "Confirm deletion",
"This operation is not reversible": "This operation is not reversible",
"Script Console": "Script Console",
"To Top": "To Top",
"To End": "To End",
"Connections": "Connections",
"Table View": "Table View",
"List View": "List View",
"Close All": "Close All",
"Close All Connections": "Close All Connections",
"Upload": "Upload",
"Download": "Download",
"Download Speed": "Download Speed",
"Upload Speed": "Upload Speed",
"Host": "Host",
"Downloaded": "Downloaded",
"Uploaded": "Uploaded",
"DL Speed": "DL Speed",
"UL Speed": "UL Speed",
"Active Connections": "Active Connections",
"Chains": "Chains",
"Rule": "Rule",
"Process": "Process",
"Time": "Time",
"Source": "Source",
"Destination": "Destination",
"DestinationPort": "Destination Port",
"Close Connection": "Close Connection",
"Rules": "Rules",
"Rule Provider": "Rule Provider",
"Logs": "Logs",
"Pause": "Pause",
"Resume": "Resume",
"Clear": "Clear",
"Test": "Test",
"Test All": "Test All",
"Testing...": "Testing...",
"Create Test": "Create Test",
"Edit Test": "Edit Test",
"Icon": "Icon",
"Test URL": "Test URL",
"Settings": "Settings",
"System Setting": "System Setting",
"Tun Mode": "Tun Mode",
"TUN requires Service Mode": "TUN mode requires install service",
"Install Service": "Install Service",
"Install Service failed": "Install Service failed",
"Uninstall Service": "Uninstall Service",
"Restart Core failed": "Restart Core failed",
"Reset to Default": "Reset to Default",
"Tun Mode Info": "Tun (Virtual NIC) mode: Captures all system traffic, when enabled, there is no need to enable system proxy.",
"TUN requires Service Mode or Admin Mode": "TUN requires Service Mode or Admin Mode",
"TUN Mode automatically disabled due to service unavailable": "TUN Mode automatically disabled due to service unavailable",
"Failed to disable TUN Mode automatically": "Failed to disable TUN Mode automatically",
"System Proxy Enabled": "System proxy is enabled, your applications will access the network through the proxy",
"System Proxy Disabled": "System proxy is disabled, it is recommended for most users to turn on this option",
"TUN Mode Enabled": "TUN mode is enabled, applications will access the network through the virtual network card",
"TUN Mode Disabled": "TUN mode is disabled, suitable for special applications",
"TUN Mode Service Required": "TUN mode requires service mode, please install the service first",
"TUN Mode Intercept Info": "TUN mode can take over all application traffic, suitable for special applications that do not follow the system proxy settings",
"Core communication error": "Core communication error",
"Rule Mode Description": "Routes traffic according to preset rules, provides flexible proxy strategies",
"Global Mode Description": "All traffic goes through proxy servers, suitable for scenarios requiring global internet access",
"Direct Mode Description": "All traffic doesn't go through proxy nodes, but is forwarded by Clash kernel to target servers, suitable for specific scenarios requiring kernel traffic distribution",
"Stack": "Tun Stack",
"System and Mixed Can Only be Used in Service Mode": "System and Mixed Can Only be Used in Service Mode",
"Device": "Device Name",
"Auto Route": "Auto Route",
"Strict Route": "Strict Route",
"Auto Detect Interface": "Auto Detect Interface",
"DNS Hijack": "DNS Hijack",
"MTU": "Max Transmission Unit",
"Service Mode": "Service Mode",
"Service Mode Info": "Please install the service mode before enabling TUN mode. The kernel process started by the service can obtain the permission to install the virtual network card (TUN mode)",
"Current State": "Current State",
"pending": "pending",
"installed": "installed",
"uninstall": "uninstalled",
"active": "active",
"unknown": "unknown",
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Information: Please make sure that the Clash Verge Service is installed and enabled",
"Install": "Install",
"Uninstall": "Uninstall",
"Disable Service Mode": "Disable Service Mode",
"System Proxy": "System Proxy",
"System Proxy Info": "Enable to modify the operating system's proxy settings. If enabling fails, modify the operating system's proxy settings manually",
"System Proxy Setting": "System Proxy Setting",
"Current System Proxy": "Current System Proxy",
"Enable status": "Enable Status:",
"Enabled": "Enabled",
"Disabled": "Disabled",
"Server Addr": "Server Addr: ",
"Proxy Host": "Proxy Host",
"Invalid Proxy Host Format": "Invalid Proxy Host Format",
"Not available": "Not available",
"Proxy Guard": "Proxy Guard",
"Proxy Guard Info": "Enable to prevent other software from modifying the operating system's proxy settings",
"Guard Duration": "Guard Duration",
"Always use Default Bypass": "Always use Default Bypass",
"Use Bypass Check": "Use Bypass Check",
"Proxy Bypass": "Proxy Bypass Settings: ",
"Bypass": "Bypass: ",
"Use PAC Mode": "Use PAC Mode",
"PAC Script Content": "PAC Script Content",
"PAC URL": "PAC URL: ",
"Auto Launch": "Auto Launch",
"Administrator mode may not support auto launch": "Administrator mode may not support auto launch",
"Silent Start": "Silent Start",
"Silent Start Info": "Start the program in background mode without displaying the panel",
"Hover Jump Navigator": "Hover Jump Navigator",
"Hover Jump Navigator Info": "Automatically scroll to the corresponding proxy group when hovering over alphabet letters",
"Hover Jump Navigator Delay": "Hover Jump Navigator Delay",
"Hover Jump Navigator Delay Info": "Delay before auto scrolling when hovering, in milliseconds",
"TG Channel": "Telegram Channel",
"Manual": "Manual",
"Github Repo": "Github Repo",
"Clash Setting": "Clash Setting",
"Allow Lan": "Allow LAN",
"Network Interface": "Network Interface",
"Ip Address": "IP Address",
"Mac Address": "MAC Address",
"IPv6": "IPv6",
"Unified Delay": "Unified Delay",
"Unified Delay Info": "When unified delay is turned on, two delay tests will be performed to eliminate the delay differences between different types of nodes caused by connection handshakes, etc",
"Log Level": "Log Level",
"Log Level Info": "This parameter is valid only for kernel log files in the log directory Service folder",
"Port Config": "Port Config",
"Random Port": "Random Port",
"Mixed Port": "Mixed Port",
"Socks Port": "Socks Port",
"Http Port": "Http(s) Port",
"Redir Port": "Redir Port",
"Tproxy Port": "Tproxy Port",
"Port settings saved": "Port settings saved",
"Failed to save port settings": "Failed to save port settings",
"External": "External",
"Enable External Controller": "Enable External Controller",
"External Controller": "External Controller",
"Core Secret": "Core Secret",
"Recommended": "Recommended",
"Open URL": "Open URL",
"Replace host, port, secret with %host, %port, %secret": "Replace host, port, secret with %host, %port, %secret",
"Support %host, %port, %secret": "Support %host, %port, %secret",
"Clash Core": "Clash Core",
"Upgrade": "Upgrade",
"Restart": "Restart",
"Release Version": "Release Version",
"Alpha Version": "Alpha Version",
"Please Enable Service Mode": "Please Install and Enable Service Mode First",
"Please enter your root password": "Please enter your root password",
"Grant": "Grant",
"Open UWP tool": "Open UWP tool",
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
"Update GeoData": "Update GeoData",
"Verge Basic Setting": "Verge Basic Setting",
"Verge Advanced Setting": "Verge Advanced Setting",
"Language": "Language",
"Theme Mode": "Theme Mode",
"theme.light": "Light",
"theme.dark": "Dark",
"theme.system": "System",
"Tray Click Event": "Tray Click Event",
"Show Main Window": "Show Main Window",
"Show Tray Menu": "Show Tray Menu",
"Copy Env Type": "Copy Env Type",
"Copy Success": "Copy Success",
"Start Page": "Start Page",
"Startup Script": "Startup Script",
"Browse": "Browse",
"Theme Setting": "Theme Setting",
"Primary Color": "Primary Color",
"Secondary Color": "Secondary Color",
"Primary Text": "Primary Text",
"Secondary Text": "Secondary Text",
"Info Color": "Info Color",
"Warning Color": "Warning Color",
"Error Color": "Error Color",
"Success Color": "Success Color",
"Font Family": "Font Family",
"CSS Injection": "CSS Injection",
"Layout Setting": "Layout Setting",
"Traffic Graph": "Traffic Graph",
"Memory Usage": "Core Usage",
"Memory Cleanup": "Tap to clean up memory",
"Proxy Group Icon": "Proxy Group Icon",
"Nav Icon": "Nav Icon",
"Monochrome": "Monochrome",
"Colorful": "Colorful",
"Tray Icon": "Tray Icon",
"Common Tray Icon": "Common Tray Icon",
"System Proxy Tray Icon": "System Proxy Tray Icon",
"Tun Tray Icon": "Tun Tray Icon",
"Miscellaneous": "Miscellaneous",
"App Log Level": "App Log Level",
"Auto Close Connections": "Auto Close Connections",
"Auto Close Connections Info": "Terminate established connections when the proxy group selection or proxy mode changes",
"Auto Check Update": "Auto Check Update",
"Enable Builtin Enhanced": "Enable Builtin Enhanced",
"Enable Builtin Enhanced Info": "Compatibility handling for the configuration file",
"Proxy Layout Columns": "Proxy Layout Columns",
"Auto Columns": "Auto Columns",
"Auto Log Clean": "Auto Log Clean",
"Never Clean": "Never Clean",
"Retain _n Days": "Retain {{n}} Days",
"Auto Delay Detection": "Auto Delay Detection",
"Auto Delay Detection Info": "Periodically test the current node latency in the background",
"Default Latency Test": "Default Latency Test",
"Default Latency Test Info": "Used for HTTP client request testing only and won't make a difference to the configuration file",
"Default Latency Timeout": "Default Latency Timeout",
"Hotkey Setting": "Hotkey Setting",
"Enable Global Hotkey": "Enable Global Hotkey",
"open_or_close_dashboard": "Open/Close Dashboard",
"clash_mode_rule": "Rule Mode",
"clash_mode_global": "Global Mode",
"clash_mode_direct": "Direct Mode",
"toggle_system_proxy": "Enable/Disable System Proxy",
"toggle_tun_mode": "Enable/Disable Tun Mode",
"entry_lightweight_mode": "Entry Lightweight Mode",
"Backup Setting": "Backup Setting",
"Backup Setting Info": "Support local or WebDAV backup of configuration files",
"Runtime Config": "Runtime Config",
"Open Conf Dir": "Open Conf Dir",
"Open Conf Dir Info": "If the software runs abnormally, BACKUP and delete all files in this folder then restart the software",
"Open Core Dir": "Open Core Dir",
"Open Logs Dir": "Open Logs Dir",
"Check for Updates": "Check for Updates",
"Go to Release Page": "Go to Release Page",
"Portable Updater Error": "The portable version does not support in-app updates. Please manually download and replace it",
"Break Change Update Error": "This version is a major update and does not support in-app updates. Please uninstall it and manually download and install the new version",
"Open Dev Tools": "Dev Tools",
"Export Diagnostic Info": "Export Diagnostic Info",
"Export Diagnostic Info For Issue Reporting": "Export Diagnostic Info For Issue Reporting",
"Exit": "Exit",
"Verge Version": "Verge Version",
"ReadOnly": "ReadOnly",
"ReadOnlyMessage": "Cannot edit in read-only editor",
"Filter": "Filter",
"Filter conditions": "Filter conditions",
"Match Case": "Match Case",
"Match Whole Word": "Match Whole Word",
"Use Regular Expression": "Use Regular Expression",
"Profile Imported Successfully": "Profile Imported Successfully",
"Profile Switched": "Profile Switched",
"Profile Reactivated": "Profile Reactivated",
"Profile switch interrupted by new selection": "Profile switch interrupted by new selection",
"Only YAML Files Supported": "Only YAML Files Supported",
"Settings Applied": "Settings Applied",
"Stopping Core...": "Stopping Core...",
"Restarting Core...": "Restarting Core...",
"Installing Service...": "Installing Service...",
"Uninstalling Service...": "Uninstalling Service...",
"Service Installed Successfully": "Service Installed Successfully",
"Service Uninstalled Successfully": "Service Uninstalled Successfully",
"Proxy Daemon Duration Cannot be Less than 1 Second": "Proxy Daemon Duration Cannot be Less than 1 Second",
"Invalid Bypass Format": "Invalid Bypass Format",
"Waiting for service to be ready...": "Waiting for service to be ready...",
"Service not ready, retrying attempt {count}/{total}...": "Service not ready, retrying attempt {{count}}/{{total}}...",
"Failed to check service status, retrying attempt {count}/{total}...": "Failed to check service status, retrying attempt {{count}}/{{total}}...",
"Service did not become ready after attempts. Proceeding with core restart.": "Service did not become ready after attempts. Proceeding with core restart.",
"Service was ready, but core restart might have issues or service became unavailable. Please check.": "Service was ready, but core restart might have issues or service became unavailable. Please check.",
"Service installation or core restart encountered issues. Service might not be available. Please check system logs.": "Service installation or core restart encountered issues. Service might not be available. Please check system logs.",
"Attempting to restart core as a fallback...": "Attempting to restart core as a fallback...",
"Fallback core restart also failed: {message}": "Fallback core restart also failed: {{message}}",
"Service is ready and core restarted": "Service is ready and core restarted",
"Core restarted. Service is now available.": "Core restarted. Service is now available.",
"Clash Port Modified": "Clash Port Modified",
"Port Conflict": "Port Conflict",
"Restart Application to Apply Modifications": "Restart Application to Apply Modifications",
"External Controller Address Modified": "External Controller Address Modified",
"Permissions Granted Successfully for _clash Core": "Permissions Granted Successfully for {{core}} Core",
"Core Version Updated": "Core Version Updated",
"Clash Core Restarted": "Clash Core Restarted",
"GeoData Updated": "GeoData Updated",
"Currently on the Latest Version": "Currently on the Latest Version",
"Already Using Latest Core": "Already Using Latest Core",
"Import Subscription Successful": "Import subscription successful",
"WebDAV Server URL": "WebDAV Server URL",
"Username": "Username",
"Password": "Password",
"Backup": "Backup",
"Local Backup": "Local backup",
"WebDAV Backup": "WebDAV backup",
"Select Backup Target": "Select backup target",
"Filename": "Filename",
"Actions": "Actions",
"Restore": "Restore",
"Export": "Export",
"No Backups": "No backups available",
"WebDAV URL Required": "WebDAV URL cannot be empty",
"Invalid WebDAV URL": "Invalid WebDAV URL format",
"Username Required": "Username cannot be empty",
"Password Required": "Password cannot be empty",
"Failed to Fetch Backups": "Failed to fetch backup files",
"WebDAV Config Saved": "WebDAV configuration saved successfully",
"WebDAV Config Save Failed": "Failed to save WebDAV configuration: {{error}}",
"Backup Created": "Backup created successfully",
"Backup Failed": "Backup failed: {{error}}",
"Local Backup Created": "Local backup created successfully",
"Local Backup Failed": "Local backup failed",
"Local Backup Exported": "Local backup exported successfully",
"Local Backup Export Failed": "Failed to export local backup",
"Local Backup Info": "Backups are stored locally in the application data directory. Use the list below to restore or delete backups.",
"Delete Backup": "Delete Backup",
"Export Backup": "Export Backup",
"Restore Backup": "Restore Backup",
"Backup Time": "Backup Time",
"Confirm to delete this backup file?": "Confirm to delete this backup file?",
"Confirm to restore this backup file?": "Confirm to restore this backup file?",
"Restore Success, App will restart in 1s": "Restore Success, App will restart in 1s",
"Failed to fetch backup files": "Failed to fetch backup files",
"Profile": "Profile",
"Help": "Help",
"About": "About",
"Theme": "Theme",
"Main Window": "Main Window",
"Group Icon": "Group Icon",
"Menu Icon": "Menu Icon",
"PAC File": "PAC File",
"Web UI": "Web UI",
"Hotkeys": "Hotkeys",
"Verge Mixed Port": "Verge Mixed Port",
"Verge Socks Port": "Verge Socks Port",
"Verge Redir Port": "Verge Redir Port",
"Verge Tproxy Port": "Verge Tproxy Port",
"Verge Port": "Verge Port",
"Verge HTTP Enabled": "Verge HTTP Enabled",
"WebDAV URL": "WebDAV URL",
"WebDAV Username": "WebDAV Username",
"WebDAV Password": "WebDAV Password",
"Dashboard": "Dashboard",
"Restart App": "Restart App",
"Restart Clash Core": "Restart Clash Core",
"TUN Mode": "TUN Mode",
"Copy Env": "Copy Env",
"Conf Dir": "Conf Dir",
"Core Dir": "Core Dir",
"Logs Dir": "Logs Dir",
"Open Dir": "Open Dir",
"More": "More",
"Rule Mode": "Rule Mode",
"Global Mode": "Global Mode",
"Direct Mode": "Direct Mode",
"Enable Tray Speed": "Enable Tray Speed",
"Enable Tray Icon": "Enable Tray Icon",
"Show Proxy Groups Inline": "Show Proxy Groups Inline",
"LightWeight Mode": "Lightweight Mode",
"LightWeight Mode Info": "Close the GUI and keep only the kernel running",
"LightWeight Mode Settings": "LightWeight Mode Settings",
"Enter LightWeight Mode Now": "Enter LightWeight Mode Now",
"Auto Enter LightWeight Mode": "Auto Enter LightWeight Mode",
"Auto Enter LightWeight Mode Info": "Enable to automatically activate LightWeight Mode after the window is closed for a period of time",
"Auto Enter LightWeight Mode Delay": "Auto Enter LightWeight Mode Delay",
"When closing the window, LightWeight Mode will be automatically activated after _n minutes": "When closing the window, LightWeight Mode will be automatically activated after {{n}} minutes",
"Config Validation Failed": "Subscription configuration validation failed. Please check the subscription configuration file; modifications have been rolled back.",
"Boot Config Validation Failed": "Boot subscription configuration validation failed. Started with the default configuration; please check the subscription configuration file.",
"Core Change Config Validation Failed": "Configuration validation failed when switching the kernel. Started with the default configuration; please check the subscription configuration file.",
"Config Validation Process Terminated": "The validation process has been terminated.",
"Script Syntax Error": "Script syntax error, changes reverted",
"Script Missing Main": "Script error, changes reverted",
"File Not Found": "File missing, changes reverted",
"Script File Error": "Script file error, changes reverted",
"Core Changed Successfully": "Core changed successfully",
"Failed to Change Core": "Failed to change core",
"YAML Syntax Error": "YAML syntax error, changes reverted",
"YAML Read Error": "YAML read error, changes reverted",
"YAML Mapping Error": "YAML mapping error, changes reverted",
"YAML Key Error": "YAML key error, changes reverted",
"YAML Error": "YAML error, changes reverted",
"Merge File Syntax Error": "Merge file syntax error, changes reverted",
"Merge File Mapping Error": "Merge file mapping error, changes reverted",
"Merge File Key Error": "Merge file key error, changes reverted",
"Merge File Error": "Merge file error, changes reverted",
"Validate YAML File": "Validate YAML File",
"Validate Merge File": "Validate Merge File",
"Validation Success": "Validation Success",
"Validation Failed": "Validation Failed",
"Service Administrator Prompt": "Clash Verge requires administrator privileges to reinstall the system service",
"DNS Settings": "DNS Settings",
"DNS settings saved": "DNS settings saved",
"DNS Overwrite": "DNS Overwrite",
"DNS Settings Warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite enabled",
"Enable DNS": "Enable DNS",
"DNS Listen": "DNS Listen",
"Enhanced Mode": "Enhanced Mode",
"Fake IP Range": "Fake IP Range",
"Fake IP Filter Mode": "Fake IP Filter Mode",
"Enable IPv6 DNS resolution": "Enable IPv6 DNS resolution",
"Prefer H3": "Prefer H3",
"DNS DOH使用HTTP/3": "DNS DOH uses HTTP/3",
"Respect Rules": "Respect Rules",
"DNS connections follow routing rules": "DNS connections follow routing rules",
"Use Hosts": "Use Hosts",
"Enable to resolve hosts through hosts file": "Enable to resolve hosts through hosts file",
"Use System Hosts": "Use System Hosts",
"Enable to resolve hosts through system hosts file": "Enable to resolve hosts through system hosts file",
"Direct Nameserver Follow Policy": "Direct Nameserver Follow Policy",
"Whether to follow nameserver policy": "Whether to follow nameserver policy",
"Default Nameserver": "Default Nameserver",
"Default DNS servers used to resolve DNS servers": "Default DNS servers used to resolve DNS servers",
"Nameserver": "Nameserver",
"List of DNS servers": "List of DNS servers, comma separated",
"Fallback": "Fallback",
"List of fallback DNS servers": "List of fallback DNS servers, comma separated",
"Proxy Server Nameserver": "Proxy Server Nameserver",
"Proxy Node Nameserver": "DNS servers for proxy node domain resolution",
"Direct Nameserver": "Direct Nameserver",
"Direct outbound Nameserver": "DNS servers for direct exit domain resolution, supports 'system' keyword, comma separated",
"Fake IP Filter": "Fake IP Filter",
"Domains that skip fake IP resolution": "Domains that skip fake IP resolution, comma separated",
"Nameserver Policy": "Nameserver Policy",
"Domain-specific DNS server": "Domain-specific DNS server, multiple servers separated by semicolons, format: domain=server1;server2",
"Fallback Filter Settings": "Fallback Filter Settings",
"GeoIP Filtering": "GeoIP Filtering",
"Enable GeoIP filtering for fallback": "Enable GeoIP filtering for fallback",
"GeoIP Code": "GeoIP Code",
"Fallback IP CIDR": "Fallback IP CIDR",
"IP CIDRs not using fallback servers": "IP CIDRs not using fallback servers, comma separated",
"Fallback Domain": "Fallback Domain",
"Domains using fallback servers": "Domains using fallback servers, comma separated",
"Hosts Settings": "Hosts Settings",
"Hosts": "Hosts",
"Custom domain to IP or domain mapping": "Custom domain to IP or domain mapping",
"Enable Alpha Channel": "Enable Alpha Channel",
"Alpha versions may contain experimental features and bugs": "Alpha versions may contain experimental features and bugs",
"Home Settings": "Home Settings",
"Profile Card": "Profile Card",
"Current Proxy Card": "Current Proxy Card",
"Network Settings Card": "Network Settings Card",
"Proxy Mode Card": "Proxy Mode Card",
"Clash Mode Card": "Clash Mode Card",
"Traffic Stats Card": "Traffic Stats Card",
"Clash Info Cards": "Clash Info Cards",
"System Info Cards": "System Info Cards",
"Website Tests Card": "Website Tests Card",
"Traffic Stats": "Traffic Stats",
"Website Tests": "Website Tests",
"Clash Info": "Clash Info",
"Core Version": "Core Version",
"System Proxy Address": "System Proxy Address",
"Uptime": "Uptime",
"Rules Count": "Rules Count",
"System Info": "System Info",
"OS Info": "OS Info",
"Running Mode": "Running Mode",
"Sidecar Mode": "User Mode",
"Administrator Mode": "Administrator Mode",
"Administrator + Service Mode": "Admin + Service Mode",
"Last Check Update": "Last Check Update",
"Click to import subscription": "Click to import subscription",
"Last Update failed": "Last Update failed",
"Next Up": "Next Up",
"No schedule": "No schedule",
"Unknown": "Unknown",
"Auto update disabled": "Auto update disabled",
"Update subscription successfully": "Update subscription successfully",
"Update with Clash proxy successfully": "Update with Clash proxy successfully",
"Update failed even with Clash proxy": "Update failed even with Clash proxy",
"Profile creation failed, retrying with Clash proxy...": "Profile creation failed, retrying with Clash proxy...",
"Profile creation succeeded with Clash proxy": "Profile creation succeeded with Clash proxy",
"Import failed, retrying with Clash proxy...": "Import failed, retrying with Clash proxy...",
"Profile Imported with Clash proxy": "Profile Imported with Clash proxy",
"Import failed even with Clash proxy": "Import failed even with Clash proxy",
"Current Node": "Current Node",
"No active proxy node": "No active proxy node",
"Network Settings": "Network Settings",
"Proxy Mode": "Proxy Mode",
"Group": "Group",
"Proxy": "Proxy",
"IP Information Card": "IP Information Card",
"IP Information": "IP Information",
"Failed to get IP info": "Failed to get IP info",
"ISP": "ISP",
"ASN": "ASN",
"ORG": "ORG",
"Location": "Location",
"Timezone": "Timezone",
"Auto refresh": "Auto refresh",
"Unlock Test": "Unlock Test",
"Pending": "Pending",
"Yes": "Yes",
"No": "No",
"Failed": "Failed",
"Completed": "Completed",
"Disallowed ISP": "Disallowed ISP",
"Originals Only": "Originals Only",
"No (IP Banned By Disney+)": "No (IP Banned By Disney+)",
"Unsupported Country/Region": "Unsupported Country/Region",
"Failed (Network Connection)": "Failed (Network Connection)",
"DashboardToggledTitle": "Dashboard Toggled",
"DashboardToggledBody": "Dashboard visibility toggled by hotkey",
"ClashModeChangedTitle": "Clash Mode Changed",
"ClashModeChangedBody": "Switched to {mode} mode",
"SystemProxyToggledTitle": "System Proxy Toggled",
"SystemProxyToggledBody": "System proxy state toggled by hotkey",
"TunModeToggledTitle": "TUN Mode Toggled",
"TunModeToggledBody": "TUN mode toggled by hotkey",
"LightweightModeEnteredTitle": "Lightweight Mode",
"LightweightModeEnteredBody": "Entered lightweight mode by hotkey",
"AppQuitTitle": "APP Quit",
"AppQuitBody": "APP quit by hotkey",
"AppHiddenTitle": "APP Hidden",
"AppHiddenBody": "APP window hidden by hotkey",
"Invalid Profile URL": "Invalid profile URL. Please enter a URL starting with http:// or https://",
"Saved Successfully": "Saved successfully",
"External Cors": "External Cors",
"Enable one-click CORS for external API. Click to toggle CORS": "Enable one-click CORS for external API. Click to toggle CORS",
"External Cors Settings": "External Cors Settings",
"External Cors Configuration": "External Cors Configuration",
"Allow private network access": "Allow private network access",
"Allowed Origins": "Allowed Origins",
"Please enter a valid url": "Please enter a valid url",
"Add": "Add",
"Always included origins: {{urls}}": "Always included origins: {{urls}}",
"Invalid regular expression": "Invalid regular expression",
"Copy Version": "Copy Version",
"Version copied to clipboard": "Version copied to clipboard",
"Controller address cannot be empty": "Controller address cannot be empty",
"Secret cannot be empty": "Secret cannot be empty",
"Configuration saved successfully": "Configuration saved successfully",
"Failed to save configuration": "Failed to save configuration",
"Controller address copied to clipboard": "Controller address copied to clipboard",
"Secret copied to clipboard": "Secret copied to clipboard",
"Saving...": "Saving...",
"Proxy node already exists in chain": "Proxy node already exists in chain",
"Detection timeout or failed": "Detection timeout or failed",
"Batch Operations": "Batch Operations",
"Delete Selected Profiles": "Delete Selected Profiles",
"Deselect All": "Deselect All",
"Done": "Done",
"items": "items",
"Select All": "Select All",
"Selected": "Selected",
"Selected profiles deleted successfully": "Selected profiles deleted successfully",
"Prefer System Titlebar": "Prefer System Titlebar",
"App Log Max Size": "App Log Max Size",
"App Log Max Count": "App Log Max Count",
"Allow Auto Update": "Allow Auto Update",
"Menu reorder mode": "Menu reorder mode",
"Unlock menu order": "Unlock menu order",
"Lock menu order": "Lock menu order",
"Open App Log": "Open App Log",
"Open Core Log": "Open Core Log"
}
+28
View File
@@ -0,0 +1,28 @@
{
"page": {
"title": "Connections"
},
"components": {
"fields": {
"host": "Host",
"dlSpeed": "DL Speed",
"ulSpeed": "UL Speed",
"chains": "Chains",
"rule": "Rule",
"process": "Process",
"time": "Time",
"source": "Source",
"destination": "Destination",
"destinationPort": "Destination Port",
"type": "Type"
},
"order": {
"default": "Default",
"uploadSpeed": "Upload Speed",
"downloadSpeed": "Download Speed"
},
"actions": {
"closeConnection": "Close Connection"
}
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"page": {
"tooltips": {
"lightweightMode": "Lightweight Mode",
"manual": "Manual",
"settings": "Home Settings"
},
"cards": {
"trafficStats": "Traffic Stats",
"networkSettings": "Network Settings",
"proxyMode": "Proxy Mode"
},
"settings": {
"cards": {
"profile": "Profile Card",
"currentProxy": "Current Proxy Card",
"network": "Network Settings Card",
"proxyMode": "Proxy Mode Card",
"traffic": "Traffic Stats Card",
"tests": "Website Tests Card",
"ip": "IP Information Card",
"clashInfo": "Clash Info Cards",
"systemInfo": "System Info Cards"
},
"title": "Home Settings"
},
"title": "Home"
},
"components": {
"proxyTun": {
"status": {
"systemProxyEnabled": "System Proxy Enabled",
"systemProxyDisabled": "System Proxy Disabled",
"tunModeServiceRequired": "TUN Mode Service Required",
"tunModeEnabled": "TUN Mode Enabled",
"tunModeDisabled": "TUN Mode Disabled"
},
"tooltips": {
"systemProxy": "System Proxy Info",
"tunMode": "TUN Mode Intercept Info"
}
},
"clashInfo": {
"title": "Clash Info",
"fields": {
"coreVersion": "Core Version",
"systemProxyAddress": "System Proxy Address",
"mixedPort": "Mixed Port",
"uptime": "Uptime",
"rulesCount": "Rules Count"
}
},
"systemInfo": {
"title": "System Info",
"fields": {
"osInfo": "OS Info",
"autoLaunch": "Auto Launch",
"runningMode": "Running Mode",
"lastCheckUpdate": "Last Check Update",
"vergeVersion": "Verge Version"
},
"actions": {
"settings": "Settings"
},
"tooltips": {
"autoLaunchAdmin": "Administrator mode may not support auto launch"
},
"badges": {
"adminMode": "Administrator Mode",
"serviceMode": "Service Mode",
"sidecarMode": "User Mode",
"adminServiceMode": "Admin + Service Mode"
}
},
"ipInfo": {
"title": "IP Information",
"labels": {
"ip": "IP",
"asn": "ASN",
"isp": "ISP",
"org": "ORG",
"location": "Location",
"timezone": "Timezone",
"autoRefresh": "Auto refresh",
"unknown": "Unknown"
},
"errors": {
"load": "Failed to get IP info"
}
},
"currentProxy": {
"title": "Current Node",
"actions": {
"refreshDelay": "Delay check"
},
"labels": {
"globalMode": "Global Mode",
"directMode": "Direct Mode",
"group": "Group",
"proxy": "Proxy",
"noActiveNode": "No active proxy node"
}
},
"tests": {
"title": "Website Tests"
},
"traffic": {
"metrics": {
"uploadSpeed": "Upload Speed",
"downloadSpeed": "Download Speed",
"activeConnections": "Active Connections",
"memoryUsage": "Core Usage"
},
"legends": {
"upload": "Upload",
"download": "Download"
},
"patterns": {
"minutes": "{{time}} Minutes"
}
},
"clashMode": {
"errors": {
"communication": "Core communication error"
},
"labels": {
"rule": "Rule",
"global": "Global",
"direct": "Direct"
},
"descriptions": {
"rule": "Automatically choose proxies according to the rule set.",
"global": "Forward all network requests through the selected proxy.",
"direct": "Bypass the proxy and connect to the internet directly."
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import connections from "./connections.json";
import home from "./home.json";
import layout from "./layout.json";
import logs from "./logs.json";
import profiles from "./profiles.json";
import proxies from "./proxies.json";
import rules from "./rules.json";
import settings from "./settings.json";
import shared from "./shared.json";
import tests from "./tests.json";
const resources = {
connections: connections,
home: home,
layout: layout,
logs: logs,
profiles: profiles,
proxies: proxies,
rules: rules,
settings: settings,
shared: shared,
tests: tests,
};
export default resources;
+21
View File
@@ -0,0 +1,21 @@
{
"components": {
"navigation": {
"tabs": {
"home": "Home",
"proxies": "Proxies",
"profiles": "Profiles",
"connections": "Connections",
"rules": "Rules",
"logs": "Logs",
"unlock": "Test",
"settings": "Settings"
},
"menu": {
"reorderMode": "Menu reorder mode",
"unlock": "Unlock menu order",
"lock": "Lock menu order"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"page": {
"title": "Logs"
}
}

Some files were not shown because too many files have changed in this diff Show More