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