Browse Source

chore: rule types locale

dongchengjie 11 months ago
parent
commit
b558202441
5 changed files with 435 additions and 166 deletions
  1. 242 152
      src/components/profile/rules-editor-viewer.tsx
  2. 51 8
      src/locales/en.json
  3. 49 0
      src/locales/fa.json
  4. 52 3
      src/locales/ru.json
  5. 41 3
      src/locales/zh.json

+ 242 - 152
src/components/profile/rules-editor-viewer.tsx

@@ -17,6 +17,7 @@ import {
 } from "@dnd-kit/sortable";
 import {
   Autocomplete,
+  Box,
   Button,
   Dialog,
   DialogActions,
@@ -43,100 +44,197 @@ interface Props {
   onChange?: (prev?: string, curr?: string) => void;
 }
 
-const RuleTypeList = [
-  "DOMAIN",
-  "DOMAIN-SUFFIX",
-  "DOMAIN-KEYWORD",
-  "DOMAIN-REGEX",
-  "GEOSITE",
-  "IP-CIDR",
-  "IP-SUFFIX",
-  "IP-ASN",
-  "GEOIP",
-  "SRC-GEOIP",
-  "SRC-IP-ASN",
-  "SRC-IP-CIDR",
-  "SRC-IP-SUFFIX",
-  "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 portValidator = (value: string): boolean => {
+  return new RegExp(
+    "^(?:[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])$"
+  ).test(value);
+};
+const ipv4CIDRValidator = (value: string): boolean => {
+  return 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]))(?:\\/(?:[12]?[0-9]|3[0-2]))$"
+  ).test(value);
+};
+const ipv6CIDRValidator = (value: string): boolean => {
+  return 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])$"
+  ).test(value);
+};
 
-const NoResolveList = [
-  "GEOIP",
-  "IP-ASN",
-  "IP-CIDR",
-  "IP-CIDR6",
-  "IP-SUFFIX",
-  "RULE-SET",
+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 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 builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"];
 
 export const RulesEditorViewer = (props: Props) => {
   const { title, profileUid, property, open, onClose, onChange } = props;
   const { t } = useTranslation();
 
   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 [noResolve, setNoResolve] = useState(false);
-  const [proxyPolicy, setProxyPolicy] = useState("DIRECT");
+  const [proxyPolicy, setProxyPolicy] = useState(builtinProxyPolicies[0]);
   const [proxyPolicyList, setProxyPolicyList] = useState<string[]>([]);
   const [ruleList, setRuleList] = 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 subRuleObj = yaml.load(data) as { "sub-rules": [] };
     setProxyPolicyList(
-      BuiltinProxyPolicyList.concat(
+      builtinProxyPolicies.concat(
         groupsObj["proxy-groups"]
           ? groupsObj["proxy-groups"].map((item: any) => item.name)
           : []
@@ -217,29 +315,17 @@ export const RulesEditorViewer = (props: Props) => {
     fetchProfile();
   }, [open]);
 
-  const spliceRule = () => {
-    if (ruleContent === "") return "";
-    // Check valid by regex
-    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;
+  const validateRule = () => {
+    if ((ruleType.required ?? true) && !ruleContent) {
+      throw new Error(t("Rule Condition Required"));
+    }
+    if (ruleType.validator && !ruleType.validator(ruleContent)) {
+      throw new Error(t("Invalid Rule"));
     }
-    return `${ruleType}${
-      ruleType === "MATCH" ? "" : "," + ruleContent
-    },${proxyPolicy}${
-      NoResolveList.includes(ruleType) && noResolve ? ",no-resolve" : ""
-    }`;
+
+    return `${ruleType.name}${
+      ruleContent ? "," + ruleContent : ""
+    },${proxyPolicy}${ruleType.noResolve && noResolve ? ",no-resolve" : ""}`;
   };
 
   const onSave = useLockFn(async () => {
@@ -274,49 +360,50 @@ export const RulesEditorViewer = (props: Props) => {
               <Autocomplete
                 size="small"
                 sx={{ minWidth: "240px" }}
-                value={ruleType}
-                options={RuleTypeList}
-                onChange={(_, v) => {
-                  if (v) setRuleType(v);
-                }}
                 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 sx={{ display: !(ruleType.required ?? true) ? "none" : "" }}>
               <ListItemText primary={t("Rule Content")} />
-              {ruleType === "RULE-SET" && (
+
+              {ruleType.name === "RULE-SET" && (
                 <Autocomplete
                   size="small"
                   sx={{ minWidth: "240px" }}
-                  value={ruleContent}
-                  options={ruleSetList}
-                  onChange={(_, v) => {
-                    if (v) setRuleContent(v);
-                  }}
                   renderInput={(params) => <TextField {...params} />}
+                  options={ruleSetList}
+                  value={ruleContent}
+                  onChange={(_, value) => value && setRuleContent(value)}
                 />
               )}
-              {ruleType === "SUB-RULE" && (
+              {ruleType.name === "SUB-RULE" && (
                 <Autocomplete
                   size="small"
                   sx={{ minWidth: "240px" }}
-                  value={ruleContent}
-                  options={subRuleList}
-                  onChange={(_, v) => {
-                    if (v) setRuleContent(v);
-                  }}
                   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
                   size="small"
                   sx={{ minWidth: "240px" }}
                   value={ruleContent}
-                  placeholder={ExampleMap[ruleType]}
-                  onChange={(e) => {
-                    setRuleContent(e.target.value);
-                  }}
+                  required={ruleType.required ?? true}
+                  error={(ruleType.required ?? true) && !ruleContent}
+                  placeholder={ruleType.example}
+                  onChange={(e) => setRuleContent(e.target.value)}
                 />
               )}
             </Item>
@@ -325,22 +412,23 @@ export const RulesEditorViewer = (props: Props) => {
               <Autocomplete
                 size="small"
                 sx={{ minWidth: "240px" }}
-                value={proxyPolicy}
-                options={proxyPolicyList}
-                onChange={(_, v) => {
-                  if (v) setProxyPolicy(v);
-                }}
                 renderInput={(params) => <TextField {...params} />}
+                options={proxyPolicyList}
+                value={proxyPolicy}
+                renderOption={(props, option) => (
+                  <li {...props} title={t(option)}>
+                    {option}
+                  </li>
+                )}
+                onChange={(_, value) => value && setProxyPolicy(value)}
               />
             </Item>
-            {NoResolveList.includes(ruleType) && (
+            {ruleType.noResolve && (
               <Item>
                 <ListItemText primary={t("No Resolve")} />
                 <Switch
                   checked={noResolve}
-                  onChange={() => {
-                    setNoResolve(!noResolve);
-                  }}
+                  onChange={() => setNoResolve(!noResolve)}
                 />
               </Item>
             )}
@@ -350,16 +438,18 @@ export const RulesEditorViewer = (props: Props) => {
               fullWidth
               variant="contained"
               onClick={() => {
-                let raw = spliceRule();
-                if (raw === "") {
-                  Notice.error(t("Invalid Rule"));
-                  return;
+                try {
+                  let raw = validateRule();
+                  console.log(raw);
+
+                  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>
           </Item>
           <Item>
@@ -367,16 +457,16 @@ export const RulesEditorViewer = (props: Props) => {
               fullWidth
               variant="contained"
               onClick={() => {
-                let raw = spliceRule();
-                if (raw === "") {
-                  Notice.error(t("Invalid Rule"));
-                  return;
+                try {
+                  let raw = validateRule();
+                  if (appendSeq.includes(raw)) 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>
           </Item>
         </div>

+ 51 - 8
src/locales/en.json

@@ -54,12 +54,50 @@
   "Edit Rules": "Edit Rules",
   "Rule Type": "Rule Type",
   "Rule Content": "Rule Content",
-  "Proxy Policy": "roxy Policy",
+  "Proxy Policy": "Proxy Policy",
   "No Resolve": "No Resolve",
-  "Add Prepend Rule": "Add Prepend Rule",
-  "Add Append Rule": "Add Append Rule",
-  "Invalid Rule": "Invalid Rule",
+  "Prepend Rule": "Prepend Rule",
+  "Append Rule": "Append 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",
   "Extend Config": "Extend Config",
   "Extend Script": "Extend Script",
@@ -163,12 +201,20 @@
   "Auto Launch": "Auto Launch",
   "Silent Start": "Silent Start",
   "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",
-  "Allow Lan": "Allow Lan",
+  "Allow Lan": "Allow LAN",
   "IPv6": "IPv6",
   "Log Level": "Log Level",
   "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",
   "External": "External",
   "External Controller": "External Controller",
   "Core Secret": "Core Secret",
@@ -186,9 +232,6 @@
   "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",
-  "TG Channel": "Telegram Channel",
-  "Manual": "Manual",
-  "Github Repo": "Github Repo",
   "Verge Setting": "Verge Setting",
   "Language": "Language",
   "Theme Mode": "Theme Mode",

+ 49 - 0
src/locales/fa.json

@@ -50,6 +50,55 @@
   "Expire Time": "زمان انقضا",
   "Create 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 Script": "ادغام اسکریپت",
   "Global Merge": "تنظیمات گسترده‌ی سراسری",

+ 52 - 3
src/locales/ru.json

@@ -50,6 +50,55 @@
   "Expire Time": "Время окончания",
   "Create 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 Script": "Изменить Script",
   "Global Merge": "Глобальный расширенный Настройки",
@@ -152,6 +201,9 @@
   "Auto Launch": "Автозапуск",
   "Silent Start": "Тихий запуск",
   "Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
+  "TG Channel": "Канал Telegram",
+  "Manual": "Документация",
+  "Github Repo": "GitHub репозиторий",
   "Clash Setting": "Настройки Clash",
   "Allow Lan": "Разрешить локальную сеть",
   "IPv6": "IPv6",
@@ -180,9 +232,6 @@
   "Open UWP tool": "Открыть UWP инструмент",
   "Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
   "Update GeoData": "Обновление GeoData",
-  "TG Channel": "Канал Telegram",
-  "Manual": "Документация",
-  "Github Repo": "GitHub репозиторий",
   "Verge Setting": "Настройки Verge",
   "Language": "Язык",
   "Theme Mode": "Режим темы",

+ 41 - 3
src/locales/zh.json

@@ -56,10 +56,48 @@
   "Rule Content": "规则内容",
   "Proxy Policy": "代理策略",
   "No Resolve": "跳过DNS解析",
-  "Add Prepend Rule": "添加前置规则",
-  "Add Append Rule": "添加后置规则",
-  "Invalid Rule": "无效规则",
+  "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": "匹配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": "编辑代理组",
   "Extend Config": "扩展配置",
   "Extend Script": "扩展脚本",