chore: rule types locale

This commit is contained in:
dongchengjie 2024-07-02 17:04:22 +08:00
parent f9f4653e33
commit 7124d326fc
5 changed files with 436 additions and 167 deletions

View File

@ -17,6 +17,7 @@ import {
} from "@dnd-kit/sortable"; } from "@dnd-kit/sortable";
import { import {
Autocomplete, Autocomplete,
Box,
Button, Button,
Dialog, Dialog,
DialogActions, DialogActions,
@ -43,100 +44,197 @@ interface Props {
onChange?: (prev?: string, curr?: string) => void; onChange?: (prev?: string, curr?: string) => void;
} }
const RuleTypeList = [ const portValidator = (value: string): boolean => {
"DOMAIN", return new RegExp(
"DOMAIN-SUFFIX", "^(?:[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$"
"DOMAIN-KEYWORD", ).test(value);
"DOMAIN-REGEX", };
"GEOSITE", const ipv4CIDRValidator = (value: string): boolean => {
"IP-CIDR", return new RegExp(
"IP-SUFFIX", "^(?:(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))\\.){3}(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))(?:\\/(?:[12]?[0-9]|3[0-2]))$"
"IP-ASN", ).test(value);
"GEOIP", };
"SRC-GEOIP", const ipv6CIDRValidator = (value: string): boolean => {
"SRC-IP-ASN", return new RegExp(
"SRC-IP-CIDR", "^([0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){7}|::|:(?::[0-9a-fA-F]{1,4}){1,6}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,5}|(?:[0-9a-fA-F]{1,4}:){2}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){3}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){4}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){5}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,6}:)\\/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])$"
"SRC-IP-SUFFIX", ).test(value);
"DST-PORT",
"SRC-PORT",
"IN-PORT",
"IN-TYPE",
"IN-USER",
"IN-NAME",
"PROCESS-PATH",
"PROCESS-PATH-REGEX",
"PROCESS-NAME",
"PROCESS-NAME-REGEX",
"UID",
"NETWORK",
"DSCP",
"RULE-SET",
"SUB-RULE",
"AND",
"OR",
"NOT",
"MATCH",
] as const;
const NoResolveList = [
"GEOIP",
"IP-ASN",
"IP-CIDR",
"IP-CIDR6",
"IP-SUFFIX",
"RULE-SET",
];
const ExampleMap = {
DOMAIN: "example.com",
"DOMAIN-SUFFIX": "example.com",
"DOMAIN-KEYWORD": "example",
"DOMAIN-REGEX": "example.*",
GEOSITE: "youtube",
"IP-CIDR": "127.0.0.0/8",
"IP-SUFFIX": "8.8.8.8/24",
"IP-ASN": "13335",
GEOIP: "CN",
"SRC-GEOIP": "cn",
"SRC-IP-ASN": "9808",
"SRC-IP-CIDR": "192.168.1.201/32",
"SRC-IP-SUFFIX": "192.168.1.201/8",
"DST-PORT": "80",
"SRC-PORT": "7777",
"IN-PORT": "7890",
"IN-TYPE": "SOCKS/HTTP",
"IN-USER": "mihomo",
"IN-NAME": "ss",
"PROCESS-PATH":
getSystem() === "windows"
? "C:Program FilesGoogleChromeApplicationchrome.exe"
: "/usr/bin/wget",
"PROCESS-PATH-REGEX":
getSystem() === "windows" ? "(?i).*Application\\chrome.*" : ".*bin/wget",
"PROCESS-NAME": getSystem() === "windows" ? "chrome.exe" : "curl",
"PROCESS-NAME-REGEX": ".*telegram.*",
UID: "1001",
NETWORK: "udp",
DSCP: "4",
"RULE-SET": "providername",
"SUB-RULE": "",
AND: "((DOMAIN,baidu.com),(NETWORK,UDP))",
OR: "((NETWORK,UDP),(DOMAIN,baidu.com))",
NOT: "((DOMAIN,baidu.com))",
MATCH: "",
}; };
const BuiltinProxyPolicyList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"]; const rules: {
name: string;
required?: boolean;
example?: string;
noResolve?: boolean;
validator?: (value: string) => boolean;
}[] = [
{
name: "DOMAIN",
example: "example.com",
},
{
name: "DOMAIN-SUFFIX",
example: "example.com",
},
{
name: "DOMAIN-KEYWORD",
example: "example",
},
{
name: "DOMAIN-REGEX",
example: "example.*",
},
{
name: "GEOSITE",
example: "youtube",
},
{
name: "GEOIP",
example: "CN",
noResolve: true,
},
{
name: "SRC-GEOIP",
example: "CN",
},
{
name: "IP-ASN",
example: "13335",
noResolve: true,
validator: (value) => (+value ? true : false),
},
{
name: "SRC-IP-ASN",
example: "9808",
validator: (value) => (+value ? true : false),
},
{
name: "IP-CIDR",
example: "127.0.0.0/8",
noResolve: true,
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
},
{
name: "IP-CIDR6",
example: "2620:0:2d0:200::7/32",
noResolve: true,
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
},
{
name: "SRC-IP-CIDR",
example: "192.168.1.201/32",
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
},
{
name: "IP-SUFFIX",
example: "8.8.8.8/24",
noResolve: true,
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
},
{
name: "SRC-IP-SUFFIX",
example: "192.168.1.201/8",
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
},
{
name: "SRC-PORT",
example: "7777",
validator: (value) => portValidator(value),
},
{
name: "DST-PORT",
example: "80",
validator: (value) => portValidator(value),
},
{
name: "IN-PORT",
example: "7890",
validator: (value) => portValidator(value),
},
{
name: "DSCP",
example: "4",
},
{
name: "PROCESS-NAME",
example: getSystem() === "windows" ? "chrome.exe" : "curl",
},
{
name: "PROCESS-PATH",
example:
getSystem() === "windows"
? "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
: "/usr/bin/wget",
},
{
name: "PROCESS-NAME-REGEX",
example: ".*telegram.*",
},
{
name: "PROCESS-PATH-REGEX",
example:
getSystem() === "windows" ? "(?i).*Application\\chrome.*" : ".*bin/wget",
},
{
name: "NETWORK",
example: "udp",
validator: (value) => ["tcp", "udp"].includes(value),
},
{
name: "UID",
example: "1001",
validator: (value) => (+value ? true : false),
},
{
name: "IN-TYPE",
example: "SOCKS/HTTP",
},
{
name: "IN-USER",
example: "mihomo",
},
{
name: "IN-NAME",
example: "ss",
},
{
name: "SUB-RULE",
example: "(NETWORK,tcp)",
},
{
name: "RULE-SET",
example: "providername",
noResolve: true,
},
{
name: "AND",
example: "((DOMAIN,baidu.com),(NETWORK,UDP))",
},
{
name: "OR",
example: "((NETWORK,UDP),(DOMAIN,baidu.com))",
},
{
name: "NOT",
example: "((DOMAIN,baidu.com))",
},
{
name: "MATCH",
required: false,
},
];
const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
export const RulesEditorViewer = (props: Props) => { export const RulesEditorViewer = (props: Props) => {
const { title, profileUid, property, open, onClose, onChange } = props; const { title, profileUid, property, open, onClose, onChange } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const [prevData, setPrevData] = useState(""); const [prevData, setPrevData] = useState("");
const [ruleType, setRuleType] =
useState<(typeof RuleTypeList)[number]>("DOMAIN"); const [ruleType, setRuleType] = useState<(typeof rules)[number]>(rules[0]);
const [ruleContent, setRuleContent] = useState(""); const [ruleContent, setRuleContent] = useState("");
const [noResolve, setNoResolve] = useState(false); const [noResolve, setNoResolve] = useState(false);
const [proxyPolicy, setProxyPolicy] = useState("DIRECT"); const [proxyPolicy, setProxyPolicy] = useState(builtinProxyPolicies[0]);
const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]); const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
const [ruleList, setRuleList] = useState<string[]>([]); const [ruleList, setRuleList] = useState<string[]>([]);
const [ruleSetList, setRuleSetList] = useState<string[]>([]); const [ruleSetList, setRuleSetList] = useState<string[]>([]);
@ -195,7 +293,7 @@ export const RulesEditorViewer = (props: Props) => {
let ruleSetObj = yaml.load(data) as { "rule-providers": [] }; let ruleSetObj = yaml.load(data) as { "rule-providers": [] };
let subRuleObj = yaml.load(data) as { "sub-rules": [] }; let subRuleObj = yaml.load(data) as { "sub-rules": [] };
setProxyPolicyList( setProxyPolicyList(
BuiltinProxyPolicyList.concat( builtinProxyPolicies.concat(
groupsObj["proxy-groups"] groupsObj["proxy-groups"]
? groupsObj["proxy-groups"].map((item: any) => item.name) ? groupsObj["proxy-groups"].map((item: any) => item.name)
: [] : []
@ -217,29 +315,17 @@ export const RulesEditorViewer = (props: Props) => {
fetchProfile(); fetchProfile();
}, [open]); }, [open]);
const spliceRule = () => { const validateRule = () => {
if (ruleContent === "") return ""; if ((ruleType.required ?? true) && !ruleContent) {
// Check valid by regex throw new Error(t("Rule Condition Required"));
switch (ruleType) {
case "IP-CIDR": {
let v4regex = new RegExp(
"^((?:(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))\\.){3}(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5])))?:(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-5]{2}[0-3][0-5])$"
);
let v6regex = new RegExp(
"^([0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){7}|::|:(?::[0-9a-fA-F]{1,4}){1,6}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,5}|(?:[0-9a-fA-F]{1,4}:){2}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){3}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){4}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){5}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,6}:)\\/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])$"
);
if (!v4regex.test(ruleContent) && !v6regex.test(ruleContent)) return "";
break;
}
default:
break;
} }
return `${ruleType}${ if (ruleType.validator && !ruleType.validator(ruleContent)) {
ruleType === "MATCH" ? "" : "," + ruleContent throw new Error(t("Invalid Rule"));
},${proxyPolicy}${ }
NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
}`; return `${ruleType.name}${
ruleContent ? "," + ruleContent : ""
},${proxyPolicy}${ruleType.noResolve && noResolve ? ",no-resolve" : ""}`;
}; };
const onSave = useLockFn(async () => { const onSave = useLockFn(async () => {
@ -274,49 +360,50 @@ export const RulesEditorViewer = (props: Props) => {
<Autocomplete <Autocomplete
size="small" size="small"
sx={{ minWidth: "240px" }} sx={{ minWidth: "240px" }}
value={ruleType}
options={RuleTypeList}
onChange={(_, v) => {
if (v) setRuleType(v);
}}
renderInput={(params) => <TextField {...params} />} renderInput={(params) => <TextField {...params} />}
options={rules}
value={ruleType}
getOptionLabel={(option) => option.name}
renderOption={(props, option) => (
<li {...props} title={t(option.name)}>
{option.name}
</li>
)}
onChange={(_, value) => value && setRuleType(value)}
/> />
</Item> </Item>
<Item> <Item sx={{ display: !(ruleType.required ?? true) ? "none" : "" }}>
<ListItemText primary={t("Rule Content")} /> <ListItemText primary={t("Rule Content")} />
{ruleType === "RULE-SET" && (
{ruleType.name === "RULE-SET" && (
<Autocomplete <Autocomplete
size="small" size="small"
sx={{ minWidth: "240px" }} sx={{ minWidth: "240px" }}
value={ruleContent} renderInput={(params) => <TextField {...params} />}
options={ruleSetList} options={ruleSetList}
onChange={(_, v) => { value={ruleContent}
if (v) setRuleContent(v); onChange={(_, value) => value && setRuleContent(value)}
}}
renderInput={(params) => <TextField {...params} />}
/> />
)} )}
{ruleType === "SUB-RULE" && ( {ruleType.name === "SUB-RULE" && (
<Autocomplete <Autocomplete
size="small" size="small"
sx={{ minWidth: "240px" }} sx={{ minWidth: "240px" }}
value={ruleContent}
options={subRuleList}
onChange={(_, v) => {
if (v) setRuleContent(v);
}}
renderInput={(params) => <TextField {...params} />} renderInput={(params) => <TextField {...params} />}
options={subRuleList}
value={ruleContent}
onChange={(_, value) => value && setRuleContent(value)}
/> />
)} )}
{ruleType !== "RULE-SET" && ruleType !== "SUB-RULE" && ( {ruleType.name !== "RULE-SET" && ruleType.name !== "SUB-RULE" && (
<TextField <TextField
size="small" size="small"
sx={{ minWidth: "240px" }} sx={{ minWidth: "240px" }}
value={ruleContent} value={ruleContent}
placeholder={ExampleMap[ruleType]} required={ruleType.required ?? true}
onChange={(e) => { error={(ruleType.required ?? true) && !ruleContent}
setRuleContent(e.target.value); placeholder={ruleType.example}
}} onChange={(e) => setRuleContent(e.target.value)}
/> />
)} )}
</Item> </Item>
@ -325,22 +412,23 @@ export const RulesEditorViewer = (props: Props) => {
<Autocomplete <Autocomplete
size="small" size="small"
sx={{ minWidth: "240px" }} sx={{ minWidth: "240px" }}
value={proxyPolicy}
options={proxyPolicyList}
onChange={(_, v) => {
if (v) setProxyPolicy(v);
}}
renderInput={(params) => <TextField {...params} />} renderInput={(params) => <TextField {...params} />}
options={proxyPolicyList}
value={proxyPolicy}
renderOption={(props, option) => (
<li {...props} title={t(option)}>
{option}
</li>
)}
onChange={(_, value) => value && setProxyPolicy(value)}
/> />
</Item> </Item>
{NoResolveList.includes(ruleType) && ( {ruleType.noResolve && (
<Item> <Item>
<ListItemText primary={t("No Resolve")} /> <ListItemText primary={t("No Resolve")} />
<Switch <Switch
checked={noResolve} checked={noResolve}
onChange={() => { onChange={() => setNoResolve(!noResolve)}
setNoResolve(!noResolve);
}}
/> />
</Item> </Item>
)} )}
@ -350,16 +438,18 @@ export const RulesEditorViewer = (props: Props) => {
fullWidth fullWidth
variant="contained" variant="contained"
onClick={() => { onClick={() => {
let raw = spliceRule(); try {
if (raw === "") { let raw = validateRule();
Notice.error(t("Invalid Rule")); console.log(raw);
return;
if (prependSeq.includes(raw)) return;
setPrependSeq([...prependSeq, raw]);
} catch (err: any) {
Notice.error(err.message || err.toString());
} }
if (prependSeq.includes(raw)) return;
setPrependSeq([...prependSeq, raw]);
}} }}
> >
{t("Add Prepend Rule")} {t("Prepend Rule")}
</Button> </Button>
</Item> </Item>
<Item> <Item>
@ -367,16 +457,16 @@ export const RulesEditorViewer = (props: Props) => {
fullWidth fullWidth
variant="contained" variant="contained"
onClick={() => { onClick={() => {
let raw = spliceRule(); try {
if (raw === "") { let raw = validateRule();
Notice.error(t("Invalid Rule")); if (appendSeq.includes(raw)) return;
return; setPrependSeq([...appendSeq, raw]);
} catch (err: any) {
Notice.error(err.message || err.toString());
} }
if (appendSeq.includes(raw)) return;
setAppendSeq([...appendSeq, raw]);
}} }}
> >
{t("Add Append Rule")} {t("Append Rule")}
</Button> </Button>
</Item> </Item>
</div> </div>

View File

@ -54,12 +54,50 @@
"Edit Rules": "Edit Rules", "Edit Rules": "Edit Rules",
"Rule Type": "Rule Type", "Rule Type": "Rule Type",
"Rule Content": "Rule Content", "Rule Content": "Rule Content",
"Proxy Policy": "roxy Policy", "Proxy Policy": "Proxy Policy",
"No Resolve": "No Resolve", "No Resolve": "No Resolve",
"Add Prepend Rule": "Add Prepend Rule", "Prepend Rule": "Prepend Rule",
"Add Append Rule": "Add Append Rule", "Append Rule": "Append Rule",
"Invalid Rule": "Invalid Rule",
"Delete Rule": "Delete Rule", "Delete Rule": "Delete Rule",
"Rule Condition Required": "Rule Condition Required",
"Invalid Rule": "Invalid Rule",
"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", "Edit Groups": "Edit Proxy Groups",
"Extend Config": "Extend Config", "Extend Config": "Extend Config",
"Extend Script": "Extend Script", "Extend Script": "Extend Script",
@ -163,12 +201,20 @@
"Auto Launch": "Auto Launch", "Auto Launch": "Auto Launch",
"Silent Start": "Silent Start", "Silent Start": "Silent Start",
"Silent Start Info": "Start the program in background mode without displaying the panel", "Silent Start Info": "Start the program in background mode without displaying the panel",
"TG Channel": "Telegram Channel",
"Manual": "Manual",
"Github Repo": "Github Repo",
"Clash Setting": "Clash Setting", "Clash Setting": "Clash Setting",
"Allow Lan": "Allow Lan", "Allow Lan": "Allow LAN",
"IPv6": "IPv6", "IPv6": "IPv6",
"Log Level": "Log Level", "Log Level": "Log Level",
"Port Config": "Port Config", "Port Config": "Port Config",
"Random Port": "Random Port", "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",
"External": "External", "External": "External",
"External Controller": "External Controller", "External Controller": "External Controller",
"Core Secret": "Core Secret", "Core Secret": "Core Secret",
@ -186,9 +232,6 @@
"Open UWP tool": "Open UWP tool", "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", "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", "Update GeoData": "Update GeoData",
"TG Channel": "Telegram Channel",
"Manual": "Manual",
"Github Repo": "Github Repo",
"Verge Setting": "Verge Setting", "Verge Setting": "Verge Setting",
"Language": "Language", "Language": "Language",
"Theme Mode": "Theme Mode", "Theme Mode": "Theme Mode",

View File

@ -50,6 +50,55 @@
"Expire Time": "زمان انقضا", "Expire Time": "زمان انقضا",
"Create Profile": "ایجاد پروفایل", "Create Profile": "ایجاد پروفایل",
"Edit Profile": "ویرایش پروفایل", "Edit Profile": "ویرایش پروفایل",
"Edit Proxies": "ویرایش پروکسی‌ها",
"Edit Rules": "ویرایش قوانین",
"Rule Type": "نوع قانون",
"Rule Content": "محتوای قانون",
"Proxy Policy": "سیاست پروکسی",
"No Resolve": "بدون حل",
"Prepend Rule": "اضافه کردن قانون به ابتدا",
"Append Rule": "اضافه کردن قانون به انتها",
"Delete Rule": "حذف قانون",
"Rule Condition Required": "شرط قانون الزامی است",
"Invalid Rule": "قانون نامعتبر",
"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": "ویرایش گروه‌های پروکسی",
"Extend Config": "توسعه پیکربندی", "Extend Config": "توسعه پیکربندی",
"Extend Script": "ادغام اسکریپت", "Extend Script": "ادغام اسکریپت",
"Global Merge": "تنظیمات گسترده‌ی سراسری", "Global Merge": "تنظیمات گسترده‌ی سراسری",

View File

@ -50,6 +50,55 @@
"Expire Time": "Время окончания", "Expire Time": "Время окончания",
"Create Profile": "Создать профиль", "Create Profile": "Создать профиль",
"Edit Profile": "Изменить профиль", "Edit Profile": "Изменить профиль",
"Edit Proxies": "Редактировать прокси",
"Edit Rules": "Редактировать правила",
"Rule Type": "Тип правила",
"Rule Content": "Содержимое правила",
"Proxy Policy": "Политика прокси",
"No Resolve": "Без разрешения",
"Prepend Rule": "Добавить правило в начало",
"Append Rule": "Добавить правило в конец",
"Delete Rule": "Удалить правило",
"Rule Condition Required": "Требуется условие правила",
"Invalid Rule": "Недействительное правило",
"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": "Соответствует USER ID в Linux",
"IN-TYPE": "Соответствует типу входящего соединения",
"IN-USER": "Соответствует имени пользователя входящего соединения",
"IN-NAME": "Соответствует имени входящего соединения",
"SUB-RULE": "Подправило",
"RULE-SET": "Соответствует набору правил",
"AND": "Логическое И",
"OR": "Логическое ИЛИ",
"NOT": "Логическое НЕ",
"MATCH": "Соответствует всем запросам",
"DIRECT": "Данные направляются напрямую наружу",
"REJECT": "Перехватывает запросы",
"REJECT-DROP": "Отклоняет запросы",
"PASS": "Пропускает это правило при совпадении",
"Edit Groups": "Редактировать группы прокси",
"Extend Config": "Изменить Merge.", "Extend Config": "Изменить Merge.",
"Extend Script": "Изменить Script", "Extend Script": "Изменить Script",
"Global Merge": "Глобальный расширенный Настройки", "Global Merge": "Глобальный расширенный Настройки",
@ -152,6 +201,9 @@
"Auto Launch": "Автозапуск", "Auto Launch": "Автозапуск",
"Silent Start": "Тихий запуск", "Silent Start": "Тихий запуск",
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели", "Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
"TG Channel": "Канал Telegram",
"Manual": "Документация",
"Github Repo": "GitHub репозиторий",
"Clash Setting": "Настройки Clash", "Clash Setting": "Настройки Clash",
"Allow Lan": "Разрешить локальную сеть", "Allow Lan": "Разрешить локальную сеть",
"IPv6": "IPv6", "IPv6": "IPv6",
@ -180,9 +232,6 @@
"Open UWP tool": "Открыть UWP инструмент", "Open UWP tool": "Открыть UWP инструмент",
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение", "Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
"Update GeoData": "Обновление GeoData", "Update GeoData": "Обновление GeoData",
"TG Channel": "Канал Telegram",
"Manual": "Документация",
"Github Repo": "GitHub репозиторий",
"Verge Setting": "Настройки Verge", "Verge Setting": "Настройки Verge",
"Language": "Язык", "Language": "Язык",
"Theme Mode": "Режим темы", "Theme Mode": "Режим темы",

View File

@ -56,10 +56,48 @@
"Rule Content": "规则内容", "Rule Content": "规则内容",
"Proxy Policy": "代理策略", "Proxy Policy": "代理策略",
"No Resolve": "跳过DNS解析", "No Resolve": "跳过DNS解析",
"Add Prepend Rule": "添加前置规则", "Prepend Rule": "添加前置规则",
"Add Append Rule": "添加后置规则", "Append Rule": "添加后置规则",
"Invalid Rule": "无效规则",
"Delete Rule": "删除规则", "Delete Rule": "删除规则",
"Rule Condition Required": "规则条件缺失",
"Invalid Rule": "无效规则",
"DOMAIN": "匹配完整域名",
"DOMAIN-SUFFIX": "匹配域名后缀",
"DOMAIN-KEYWORD": "匹配域名关键字",
"DOMAIN-REGEX": "匹配域名正则表达式",
"GEOSITE": "匹配Geosite内的域名",
"GEOIP": "匹配IP所属国家代码",
"SRC-GEOIP": "匹配来源IP所属国家代码",
"IP-ASN": "匹配IP所属ASN",
"SRC-IP-ASN": "匹配来源IP所属ASN",
"IP-CIDR": "匹配IP地址范围",
"IP-CIDR6": "匹配IP地址范围",
"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 USER ID",
"IN-TYPE": "匹配入站类型",
"IN-USER": "匹配入站用户名",
"IN-NAME": "匹配入站名称",
"SUB-RULE": "子规则",
"RULE-SET": "匹配规则集",
"AND": "逻辑和",
"OR": "逻辑或",
"NOT": "逻辑非",
"MATCH": "匹配所有请求",
"DIRECT": "直连",
"REJECT": "拦截请求",
"REJECT-DROP": "抛弃请求",
"PASS": "跳过此规则",
"Edit Groups": "编辑代理组", "Edit Groups": "编辑代理组",
"Extend Config": "扩展配置", "Extend Config": "扩展配置",
"Extend Script": "扩展脚本", "Extend Script": "扩展脚本",