Browse Source

feat(sub): add Incy app-management parameters (#6650)

* feat(sub): add Incy app-management parameters

The panel already pushes a set of Happ headers, but INCY documents its own
lowercase header names and its own value domains, so a Happ-shaped payload gets
ignored by the client (per-app mode is bypass|proxy, not on|bypass, and
per-app-proxy-enable has no Happ counterpart at all). Add a sibling Incy path
that emits exactly the documented headers.

Covered, per https://docs.incy.cc/en/app-management/:
- profile-description, sort-order, support-email, announce-url, premium-url
- banner text/button/URL and the two hex colours
- hide-url, hide-check, no-limit-enabled
- per-app split tunnelling (enable/mode/list)
- TCP fragmentation (enable/length/interval/packets)
- UDP noise packets (enable/type/packet/delay)
- DoH pre-resolution (enable/domain/IP)

Each string setting is tri-state: an empty value omits the header, so an
untouched panel never overrides the subscriber's own choice in the app. Values
are validated against the documented domains and dropped when they do not
match, and non-ASCII text is base64-wrapped the way the docs require for
Cyrillic. INCY identifies itself as INCY/<version>/<platform>, which gates the
headers behind the same auto-detect switch the Happ path uses.

Headers the panel already emits for every client (Profile-Title, Support-Url,
Profile-Web-Page-Url, Announce, Profile-Update-Interval, Subscription-Userinfo)
and Incy's routing line are left as they are.

The Premium API (theme, defaultPingProtocol, fallbackHosts, ...) is a separate
encrypted endpoint and stays out of scope here.

* fix(sub): keep Incy per-app list entries separate on the wire

The Incy settings textarea takes one package per line, as Incy documents for
per-app-proxy-list, but the header path ran the value through
sanitizeHeaderValue, which deletes CR/LF. "com.google.chrome\norg.telegram.messenger"
reached the client as the single bogus package
"com.google.chromeorg.telegram.messenger", so per-app split tunnelling silently
matched no app. Join comma- or line-separated entries as CSV instead.

Also drop three tests that could not fail: TestIncyExcludesHappOnlyHeaders
(ApplyIncyHeaders has no path that emits Happ headers, and the non-Happ UA
gate is already pinned by TestApplyHappHeaders_Gating) and two UI tests that
only asserted updateSetting received the key the JSX passes it.

---------

Co-authored-by: DIMFLIX <[email protected]>
Co-authored-by: MHSanaei <[email protected]>
DIMFLIX 5 hours ago
parent
commit
71e38367c1

+ 226 - 0
docs/public/openapi.json

@@ -342,12 +342,97 @@
           "subHideSettings": {
             "type": "boolean"
           },
+          "subIncyAnnounceUrl": {
+            "type": "string"
+          },
+          "subIncyAppAutoDetect": {
+            "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+            "type": "boolean"
+          },
+          "subIncyBannerBgColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonText": {
+            "type": "string"
+          },
+          "subIncyBannerButtonUrl": {
+            "type": "string"
+          },
+          "subIncyBannerText": {
+            "type": "string"
+          },
           "subIncyEnableRouting": {
             "type": "boolean"
           },
+          "subIncyFragmentInterval": {
+            "type": "string"
+          },
+          "subIncyFragmentLength": {
+            "type": "string"
+          },
+          "subIncyFragmentPackets": {
+            "type": "string"
+          },
+          "subIncyFragmentationEnable": {
+            "type": "string"
+          },
+          "subIncyHideCheck": {
+            "type": "string"
+          },
+          "subIncyHideUrl": {
+            "type": "string"
+          },
+          "subIncyNoLimitEnabled": {
+            "type": "string"
+          },
+          "subIncyNoisesDelay": {
+            "type": "string"
+          },
+          "subIncyNoisesEnable": {
+            "type": "string"
+          },
+          "subIncyNoisesPacket": {
+            "type": "string"
+          },
+          "subIncyNoisesType": {
+            "type": "string"
+          },
+          "subIncyPerAppEnable": {
+            "type": "string"
+          },
+          "subIncyPerAppList": {
+            "type": "string"
+          },
+          "subIncyPerAppMode": {
+            "type": "string"
+          },
+          "subIncyPremiumUrl": {
+            "type": "string"
+          },
+          "subIncyProfileDescription": {
+            "type": "string"
+          },
+          "subIncyResolveDnsDomain": {
+            "type": "string"
+          },
+          "subIncyResolveDnsIp": {
+            "type": "string"
+          },
+          "subIncyResolveEnable": {
+            "type": "string"
+          },
           "subIncyRoutingRules": {
             "type": "string"
           },
+          "subIncySortOrder": {
+            "type": "string"
+          },
+          "subIncySupportEmail": {
+            "type": "string"
+          },
           "subInfoNodeEnable": {
             "type": "boolean"
           },
@@ -610,8 +695,36 @@
           "subHappTunMode",
           "subHappTunType",
           "subHideSettings",
+          "subIncyAnnounceUrl",
+          "subIncyAppAutoDetect",
+          "subIncyBannerBgColor",
+          "subIncyBannerButtonColor",
+          "subIncyBannerButtonText",
+          "subIncyBannerButtonUrl",
+          "subIncyBannerText",
           "subIncyEnableRouting",
+          "subIncyFragmentInterval",
+          "subIncyFragmentLength",
+          "subIncyFragmentPackets",
+          "subIncyFragmentationEnable",
+          "subIncyHideCheck",
+          "subIncyHideUrl",
+          "subIncyNoLimitEnabled",
+          "subIncyNoisesDelay",
+          "subIncyNoisesEnable",
+          "subIncyNoisesPacket",
+          "subIncyNoisesType",
+          "subIncyPerAppEnable",
+          "subIncyPerAppList",
+          "subIncyPerAppMode",
+          "subIncyPremiumUrl",
+          "subIncyProfileDescription",
+          "subIncyResolveDnsDomain",
+          "subIncyResolveDnsIp",
+          "subIncyResolveEnable",
           "subIncyRoutingRules",
+          "subIncySortOrder",
+          "subIncySupportEmail",
           "subInfoNodeEnable",
           "subJsonAlwaysArray",
           "subJsonAutoDetect",
@@ -1005,12 +1118,97 @@
           "subHideSettings": {
             "type": "boolean"
           },
+          "subIncyAnnounceUrl": {
+            "type": "string"
+          },
+          "subIncyAppAutoDetect": {
+            "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+            "type": "boolean"
+          },
+          "subIncyBannerBgColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonText": {
+            "type": "string"
+          },
+          "subIncyBannerButtonUrl": {
+            "type": "string"
+          },
+          "subIncyBannerText": {
+            "type": "string"
+          },
           "subIncyEnableRouting": {
             "type": "boolean"
           },
+          "subIncyFragmentInterval": {
+            "type": "string"
+          },
+          "subIncyFragmentLength": {
+            "type": "string"
+          },
+          "subIncyFragmentPackets": {
+            "type": "string"
+          },
+          "subIncyFragmentationEnable": {
+            "type": "string"
+          },
+          "subIncyHideCheck": {
+            "type": "string"
+          },
+          "subIncyHideUrl": {
+            "type": "string"
+          },
+          "subIncyNoLimitEnabled": {
+            "type": "string"
+          },
+          "subIncyNoisesDelay": {
+            "type": "string"
+          },
+          "subIncyNoisesEnable": {
+            "type": "string"
+          },
+          "subIncyNoisesPacket": {
+            "type": "string"
+          },
+          "subIncyNoisesType": {
+            "type": "string"
+          },
+          "subIncyPerAppEnable": {
+            "type": "string"
+          },
+          "subIncyPerAppList": {
+            "type": "string"
+          },
+          "subIncyPerAppMode": {
+            "type": "string"
+          },
+          "subIncyPremiumUrl": {
+            "type": "string"
+          },
+          "subIncyProfileDescription": {
+            "type": "string"
+          },
+          "subIncyResolveDnsDomain": {
+            "type": "string"
+          },
+          "subIncyResolveDnsIp": {
+            "type": "string"
+          },
+          "subIncyResolveEnable": {
+            "type": "string"
+          },
           "subIncyRoutingRules": {
             "type": "string"
           },
+          "subIncySortOrder": {
+            "type": "string"
+          },
+          "subIncySupportEmail": {
+            "type": "string"
+          },
           "subInfoNodeEnable": {
             "type": "boolean"
           },
@@ -1281,8 +1479,36 @@
           "subHappTunMode",
           "subHappTunType",
           "subHideSettings",
+          "subIncyAnnounceUrl",
+          "subIncyAppAutoDetect",
+          "subIncyBannerBgColor",
+          "subIncyBannerButtonColor",
+          "subIncyBannerButtonText",
+          "subIncyBannerButtonUrl",
+          "subIncyBannerText",
           "subIncyEnableRouting",
+          "subIncyFragmentInterval",
+          "subIncyFragmentLength",
+          "subIncyFragmentPackets",
+          "subIncyFragmentationEnable",
+          "subIncyHideCheck",
+          "subIncyHideUrl",
+          "subIncyNoLimitEnabled",
+          "subIncyNoisesDelay",
+          "subIncyNoisesEnable",
+          "subIncyNoisesPacket",
+          "subIncyNoisesType",
+          "subIncyPerAppEnable",
+          "subIncyPerAppList",
+          "subIncyPerAppMode",
+          "subIncyPremiumUrl",
+          "subIncyProfileDescription",
+          "subIncyResolveDnsDomain",
+          "subIncyResolveDnsIp",
+          "subIncyResolveEnable",
           "subIncyRoutingRules",
+          "subIncySortOrder",
+          "subIncySupportEmail",
           "subInfoNodeEnable",
           "subJsonAlwaysArray",
           "subJsonAutoDetect",

+ 226 - 0
frontend/public/openapi.json

@@ -342,12 +342,97 @@
           "subHideSettings": {
             "type": "boolean"
           },
+          "subIncyAnnounceUrl": {
+            "type": "string"
+          },
+          "subIncyAppAutoDetect": {
+            "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+            "type": "boolean"
+          },
+          "subIncyBannerBgColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonText": {
+            "type": "string"
+          },
+          "subIncyBannerButtonUrl": {
+            "type": "string"
+          },
+          "subIncyBannerText": {
+            "type": "string"
+          },
           "subIncyEnableRouting": {
             "type": "boolean"
           },
+          "subIncyFragmentInterval": {
+            "type": "string"
+          },
+          "subIncyFragmentLength": {
+            "type": "string"
+          },
+          "subIncyFragmentPackets": {
+            "type": "string"
+          },
+          "subIncyFragmentationEnable": {
+            "type": "string"
+          },
+          "subIncyHideCheck": {
+            "type": "string"
+          },
+          "subIncyHideUrl": {
+            "type": "string"
+          },
+          "subIncyNoLimitEnabled": {
+            "type": "string"
+          },
+          "subIncyNoisesDelay": {
+            "type": "string"
+          },
+          "subIncyNoisesEnable": {
+            "type": "string"
+          },
+          "subIncyNoisesPacket": {
+            "type": "string"
+          },
+          "subIncyNoisesType": {
+            "type": "string"
+          },
+          "subIncyPerAppEnable": {
+            "type": "string"
+          },
+          "subIncyPerAppList": {
+            "type": "string"
+          },
+          "subIncyPerAppMode": {
+            "type": "string"
+          },
+          "subIncyPremiumUrl": {
+            "type": "string"
+          },
+          "subIncyProfileDescription": {
+            "type": "string"
+          },
+          "subIncyResolveDnsDomain": {
+            "type": "string"
+          },
+          "subIncyResolveDnsIp": {
+            "type": "string"
+          },
+          "subIncyResolveEnable": {
+            "type": "string"
+          },
           "subIncyRoutingRules": {
             "type": "string"
           },
+          "subIncySortOrder": {
+            "type": "string"
+          },
+          "subIncySupportEmail": {
+            "type": "string"
+          },
           "subInfoNodeEnable": {
             "type": "boolean"
           },
@@ -610,8 +695,36 @@
           "subHappTunMode",
           "subHappTunType",
           "subHideSettings",
+          "subIncyAnnounceUrl",
+          "subIncyAppAutoDetect",
+          "subIncyBannerBgColor",
+          "subIncyBannerButtonColor",
+          "subIncyBannerButtonText",
+          "subIncyBannerButtonUrl",
+          "subIncyBannerText",
           "subIncyEnableRouting",
+          "subIncyFragmentInterval",
+          "subIncyFragmentLength",
+          "subIncyFragmentPackets",
+          "subIncyFragmentationEnable",
+          "subIncyHideCheck",
+          "subIncyHideUrl",
+          "subIncyNoLimitEnabled",
+          "subIncyNoisesDelay",
+          "subIncyNoisesEnable",
+          "subIncyNoisesPacket",
+          "subIncyNoisesType",
+          "subIncyPerAppEnable",
+          "subIncyPerAppList",
+          "subIncyPerAppMode",
+          "subIncyPremiumUrl",
+          "subIncyProfileDescription",
+          "subIncyResolveDnsDomain",
+          "subIncyResolveDnsIp",
+          "subIncyResolveEnable",
           "subIncyRoutingRules",
+          "subIncySortOrder",
+          "subIncySupportEmail",
           "subInfoNodeEnable",
           "subJsonAlwaysArray",
           "subJsonAutoDetect",
@@ -1005,12 +1118,97 @@
           "subHideSettings": {
             "type": "boolean"
           },
+          "subIncyAnnounceUrl": {
+            "type": "string"
+          },
+          "subIncyAppAutoDetect": {
+            "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+            "type": "boolean"
+          },
+          "subIncyBannerBgColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonColor": {
+            "type": "string"
+          },
+          "subIncyBannerButtonText": {
+            "type": "string"
+          },
+          "subIncyBannerButtonUrl": {
+            "type": "string"
+          },
+          "subIncyBannerText": {
+            "type": "string"
+          },
           "subIncyEnableRouting": {
             "type": "boolean"
           },
+          "subIncyFragmentInterval": {
+            "type": "string"
+          },
+          "subIncyFragmentLength": {
+            "type": "string"
+          },
+          "subIncyFragmentPackets": {
+            "type": "string"
+          },
+          "subIncyFragmentationEnable": {
+            "type": "string"
+          },
+          "subIncyHideCheck": {
+            "type": "string"
+          },
+          "subIncyHideUrl": {
+            "type": "string"
+          },
+          "subIncyNoLimitEnabled": {
+            "type": "string"
+          },
+          "subIncyNoisesDelay": {
+            "type": "string"
+          },
+          "subIncyNoisesEnable": {
+            "type": "string"
+          },
+          "subIncyNoisesPacket": {
+            "type": "string"
+          },
+          "subIncyNoisesType": {
+            "type": "string"
+          },
+          "subIncyPerAppEnable": {
+            "type": "string"
+          },
+          "subIncyPerAppList": {
+            "type": "string"
+          },
+          "subIncyPerAppMode": {
+            "type": "string"
+          },
+          "subIncyPremiumUrl": {
+            "type": "string"
+          },
+          "subIncyProfileDescription": {
+            "type": "string"
+          },
+          "subIncyResolveDnsDomain": {
+            "type": "string"
+          },
+          "subIncyResolveDnsIp": {
+            "type": "string"
+          },
+          "subIncyResolveEnable": {
+            "type": "string"
+          },
           "subIncyRoutingRules": {
             "type": "string"
           },
+          "subIncySortOrder": {
+            "type": "string"
+          },
+          "subIncySupportEmail": {
+            "type": "string"
+          },
           "subInfoNodeEnable": {
             "type": "boolean"
           },
@@ -1281,8 +1479,36 @@
           "subHappTunMode",
           "subHappTunType",
           "subHideSettings",
+          "subIncyAnnounceUrl",
+          "subIncyAppAutoDetect",
+          "subIncyBannerBgColor",
+          "subIncyBannerButtonColor",
+          "subIncyBannerButtonText",
+          "subIncyBannerButtonUrl",
+          "subIncyBannerText",
           "subIncyEnableRouting",
+          "subIncyFragmentInterval",
+          "subIncyFragmentLength",
+          "subIncyFragmentPackets",
+          "subIncyFragmentationEnable",
+          "subIncyHideCheck",
+          "subIncyHideUrl",
+          "subIncyNoLimitEnabled",
+          "subIncyNoisesDelay",
+          "subIncyNoisesEnable",
+          "subIncyNoisesPacket",
+          "subIncyNoisesType",
+          "subIncyPerAppEnable",
+          "subIncyPerAppList",
+          "subIncyPerAppMode",
+          "subIncyPremiumUrl",
+          "subIncyProfileDescription",
+          "subIncyResolveDnsDomain",
+          "subIncyResolveDnsIp",
+          "subIncyResolveEnable",
           "subIncyRoutingRules",
+          "subIncySortOrder",
+          "subIncySupportEmail",
           "subInfoNodeEnable",
           "subJsonAlwaysArray",
           "subJsonAutoDetect",

+ 56 - 0
frontend/src/generated/examples.ts

@@ -98,8 +98,36 @@ export const EXAMPLES: Record<string, unknown> = {
     "subHappTunMode": "",
     "subHappTunType": "",
     "subHideSettings": false,
+    "subIncyAnnounceUrl": "",
+    "subIncyAppAutoDetect": false,
+    "subIncyBannerBgColor": "",
+    "subIncyBannerButtonColor": "",
+    "subIncyBannerButtonText": "",
+    "subIncyBannerButtonUrl": "",
+    "subIncyBannerText": "",
     "subIncyEnableRouting": false,
+    "subIncyFragmentInterval": "",
+    "subIncyFragmentLength": "",
+    "subIncyFragmentPackets": "",
+    "subIncyFragmentationEnable": "",
+    "subIncyHideCheck": "",
+    "subIncyHideUrl": "",
+    "subIncyNoLimitEnabled": "",
+    "subIncyNoisesDelay": "",
+    "subIncyNoisesEnable": "",
+    "subIncyNoisesPacket": "",
+    "subIncyNoisesType": "",
+    "subIncyPerAppEnable": "",
+    "subIncyPerAppList": "",
+    "subIncyPerAppMode": "",
+    "subIncyPremiumUrl": "",
+    "subIncyProfileDescription": "",
+    "subIncyResolveDnsDomain": "",
+    "subIncyResolveDnsIp": "",
+    "subIncyResolveEnable": "",
     "subIncyRoutingRules": "",
+    "subIncySortOrder": "",
+    "subIncySupportEmail": "",
     "subInfoNodeEnable": false,
     "subJsonAlwaysArray": false,
     "subJsonAutoDetect": false,
@@ -257,8 +285,36 @@ export const EXAMPLES: Record<string, unknown> = {
     "subHappTunMode": "",
     "subHappTunType": "",
     "subHideSettings": false,
+    "subIncyAnnounceUrl": "",
+    "subIncyAppAutoDetect": false,
+    "subIncyBannerBgColor": "",
+    "subIncyBannerButtonColor": "",
+    "subIncyBannerButtonText": "",
+    "subIncyBannerButtonUrl": "",
+    "subIncyBannerText": "",
     "subIncyEnableRouting": false,
+    "subIncyFragmentInterval": "",
+    "subIncyFragmentLength": "",
+    "subIncyFragmentPackets": "",
+    "subIncyFragmentationEnable": "",
+    "subIncyHideCheck": "",
+    "subIncyHideUrl": "",
+    "subIncyNoLimitEnabled": "",
+    "subIncyNoisesDelay": "",
+    "subIncyNoisesEnable": "",
+    "subIncyNoisesPacket": "",
+    "subIncyNoisesType": "",
+    "subIncyPerAppEnable": "",
+    "subIncyPerAppList": "",
+    "subIncyPerAppMode": "",
+    "subIncyPremiumUrl": "",
+    "subIncyProfileDescription": "",
+    "subIncyResolveDnsDomain": "",
+    "subIncyResolveDnsIp": "",
+    "subIncyResolveEnable": "",
     "subIncyRoutingRules": "",
+    "subIncySortOrder": "",
+    "subIncySupportEmail": "",
     "subInfoNodeEnable": false,
     "subJsonAlwaysArray": false,
     "subJsonAutoDetect": false,

+ 226 - 0
frontend/src/generated/schemas.ts

@@ -316,12 +316,97 @@ export const SCHEMAS: Record<string, unknown> = {
       "subHideSettings": {
         "type": "boolean"
       },
+      "subIncyAnnounceUrl": {
+        "type": "string"
+      },
+      "subIncyAppAutoDetect": {
+        "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+        "type": "boolean"
+      },
+      "subIncyBannerBgColor": {
+        "type": "string"
+      },
+      "subIncyBannerButtonColor": {
+        "type": "string"
+      },
+      "subIncyBannerButtonText": {
+        "type": "string"
+      },
+      "subIncyBannerButtonUrl": {
+        "type": "string"
+      },
+      "subIncyBannerText": {
+        "type": "string"
+      },
       "subIncyEnableRouting": {
         "type": "boolean"
       },
+      "subIncyFragmentInterval": {
+        "type": "string"
+      },
+      "subIncyFragmentLength": {
+        "type": "string"
+      },
+      "subIncyFragmentPackets": {
+        "type": "string"
+      },
+      "subIncyFragmentationEnable": {
+        "type": "string"
+      },
+      "subIncyHideCheck": {
+        "type": "string"
+      },
+      "subIncyHideUrl": {
+        "type": "string"
+      },
+      "subIncyNoLimitEnabled": {
+        "type": "string"
+      },
+      "subIncyNoisesDelay": {
+        "type": "string"
+      },
+      "subIncyNoisesEnable": {
+        "type": "string"
+      },
+      "subIncyNoisesPacket": {
+        "type": "string"
+      },
+      "subIncyNoisesType": {
+        "type": "string"
+      },
+      "subIncyPerAppEnable": {
+        "type": "string"
+      },
+      "subIncyPerAppList": {
+        "type": "string"
+      },
+      "subIncyPerAppMode": {
+        "type": "string"
+      },
+      "subIncyPremiumUrl": {
+        "type": "string"
+      },
+      "subIncyProfileDescription": {
+        "type": "string"
+      },
+      "subIncyResolveDnsDomain": {
+        "type": "string"
+      },
+      "subIncyResolveDnsIp": {
+        "type": "string"
+      },
+      "subIncyResolveEnable": {
+        "type": "string"
+      },
       "subIncyRoutingRules": {
         "type": "string"
       },
+      "subIncySortOrder": {
+        "type": "string"
+      },
+      "subIncySupportEmail": {
+        "type": "string"
+      },
       "subInfoNodeEnable": {
         "type": "boolean"
       },
@@ -584,8 +669,36 @@ export const SCHEMAS: Record<string, unknown> = {
       "subHappTunMode",
       "subHappTunType",
       "subHideSettings",
+      "subIncyAnnounceUrl",
+      "subIncyAppAutoDetect",
+      "subIncyBannerBgColor",
+      "subIncyBannerButtonColor",
+      "subIncyBannerButtonText",
+      "subIncyBannerButtonUrl",
+      "subIncyBannerText",
       "subIncyEnableRouting",
+      "subIncyFragmentInterval",
+      "subIncyFragmentLength",
+      "subIncyFragmentPackets",
+      "subIncyFragmentationEnable",
+      "subIncyHideCheck",
+      "subIncyHideUrl",
+      "subIncyNoLimitEnabled",
+      "subIncyNoisesDelay",
+      "subIncyNoisesEnable",
+      "subIncyNoisesPacket",
+      "subIncyNoisesType",
+      "subIncyPerAppEnable",
+      "subIncyPerAppList",
+      "subIncyPerAppMode",
+      "subIncyPremiumUrl",
+      "subIncyProfileDescription",
+      "subIncyResolveDnsDomain",
+      "subIncyResolveDnsIp",
+      "subIncyResolveEnable",
       "subIncyRoutingRules",
+      "subIncySortOrder",
+      "subIncySupportEmail",
       "subInfoNodeEnable",
       "subJsonAlwaysArray",
       "subJsonAutoDetect",
@@ -979,12 +1092,97 @@ export const SCHEMAS: Record<string, unknown> = {
       "subHideSettings": {
         "type": "boolean"
       },
+      "subIncyAnnounceUrl": {
+        "type": "string"
+      },
+      "subIncyAppAutoDetect": {
+        "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.",
+        "type": "boolean"
+      },
+      "subIncyBannerBgColor": {
+        "type": "string"
+      },
+      "subIncyBannerButtonColor": {
+        "type": "string"
+      },
+      "subIncyBannerButtonText": {
+        "type": "string"
+      },
+      "subIncyBannerButtonUrl": {
+        "type": "string"
+      },
+      "subIncyBannerText": {
+        "type": "string"
+      },
       "subIncyEnableRouting": {
         "type": "boolean"
       },
+      "subIncyFragmentInterval": {
+        "type": "string"
+      },
+      "subIncyFragmentLength": {
+        "type": "string"
+      },
+      "subIncyFragmentPackets": {
+        "type": "string"
+      },
+      "subIncyFragmentationEnable": {
+        "type": "string"
+      },
+      "subIncyHideCheck": {
+        "type": "string"
+      },
+      "subIncyHideUrl": {
+        "type": "string"
+      },
+      "subIncyNoLimitEnabled": {
+        "type": "string"
+      },
+      "subIncyNoisesDelay": {
+        "type": "string"
+      },
+      "subIncyNoisesEnable": {
+        "type": "string"
+      },
+      "subIncyNoisesPacket": {
+        "type": "string"
+      },
+      "subIncyNoisesType": {
+        "type": "string"
+      },
+      "subIncyPerAppEnable": {
+        "type": "string"
+      },
+      "subIncyPerAppList": {
+        "type": "string"
+      },
+      "subIncyPerAppMode": {
+        "type": "string"
+      },
+      "subIncyPremiumUrl": {
+        "type": "string"
+      },
+      "subIncyProfileDescription": {
+        "type": "string"
+      },
+      "subIncyResolveDnsDomain": {
+        "type": "string"
+      },
+      "subIncyResolveDnsIp": {
+        "type": "string"
+      },
+      "subIncyResolveEnable": {
+        "type": "string"
+      },
       "subIncyRoutingRules": {
         "type": "string"
       },
+      "subIncySortOrder": {
+        "type": "string"
+      },
+      "subIncySupportEmail": {
+        "type": "string"
+      },
       "subInfoNodeEnable": {
         "type": "boolean"
       },
@@ -1255,8 +1453,36 @@ export const SCHEMAS: Record<string, unknown> = {
       "subHappTunMode",
       "subHappTunType",
       "subHideSettings",
+      "subIncyAnnounceUrl",
+      "subIncyAppAutoDetect",
+      "subIncyBannerBgColor",
+      "subIncyBannerButtonColor",
+      "subIncyBannerButtonText",
+      "subIncyBannerButtonUrl",
+      "subIncyBannerText",
       "subIncyEnableRouting",
+      "subIncyFragmentInterval",
+      "subIncyFragmentLength",
+      "subIncyFragmentPackets",
+      "subIncyFragmentationEnable",
+      "subIncyHideCheck",
+      "subIncyHideUrl",
+      "subIncyNoLimitEnabled",
+      "subIncyNoisesDelay",
+      "subIncyNoisesEnable",
+      "subIncyNoisesPacket",
+      "subIncyNoisesType",
+      "subIncyPerAppEnable",
+      "subIncyPerAppList",
+      "subIncyPerAppMode",
+      "subIncyPremiumUrl",
+      "subIncyProfileDescription",
+      "subIncyResolveDnsDomain",
+      "subIncyResolveDnsIp",
+      "subIncyResolveEnable",
       "subIncyRoutingRules",
+      "subIncySortOrder",
+      "subIncySupportEmail",
       "subInfoNodeEnable",
       "subJsonAlwaysArray",
       "subJsonAutoDetect",

+ 56 - 0
frontend/src/generated/types.ts

@@ -106,8 +106,36 @@ export interface AllSetting {
   subHappTunMode: string;
   subHappTunType: string;
   subHideSettings: boolean;
+  subIncyAnnounceUrl: string;
+  subIncyAppAutoDetect: boolean;
+  subIncyBannerBgColor: string;
+  subIncyBannerButtonColor: string;
+  subIncyBannerButtonText: string;
+  subIncyBannerButtonUrl: string;
+  subIncyBannerText: string;
   subIncyEnableRouting: boolean;
+  subIncyFragmentInterval: string;
+  subIncyFragmentLength: string;
+  subIncyFragmentPackets: string;
+  subIncyFragmentationEnable: string;
+  subIncyHideCheck: string;
+  subIncyHideUrl: string;
+  subIncyNoLimitEnabled: string;
+  subIncyNoisesDelay: string;
+  subIncyNoisesEnable: string;
+  subIncyNoisesPacket: string;
+  subIncyNoisesType: string;
+  subIncyPerAppEnable: string;
+  subIncyPerAppList: string;
+  subIncyPerAppMode: string;
+  subIncyPremiumUrl: string;
+  subIncyProfileDescription: string;
+  subIncyResolveDnsDomain: string;
+  subIncyResolveDnsIp: string;
+  subIncyResolveEnable: string;
   subIncyRoutingRules: string;
+  subIncySortOrder: string;
+  subIncySupportEmail: string;
   subInfoNodeEnable: boolean;
   subJsonAlwaysArray: boolean;
   subJsonAutoDetect: boolean;
@@ -266,8 +294,36 @@ export interface AllSettingView {
   subHappTunMode: string;
   subHappTunType: string;
   subHideSettings: boolean;
+  subIncyAnnounceUrl: string;
+  subIncyAppAutoDetect: boolean;
+  subIncyBannerBgColor: string;
+  subIncyBannerButtonColor: string;
+  subIncyBannerButtonText: string;
+  subIncyBannerButtonUrl: string;
+  subIncyBannerText: string;
   subIncyEnableRouting: boolean;
+  subIncyFragmentInterval: string;
+  subIncyFragmentLength: string;
+  subIncyFragmentPackets: string;
+  subIncyFragmentationEnable: string;
+  subIncyHideCheck: string;
+  subIncyHideUrl: string;
+  subIncyNoLimitEnabled: string;
+  subIncyNoisesDelay: string;
+  subIncyNoisesEnable: string;
+  subIncyNoisesPacket: string;
+  subIncyNoisesType: string;
+  subIncyPerAppEnable: string;
+  subIncyPerAppList: string;
+  subIncyPerAppMode: string;
+  subIncyPremiumUrl: string;
+  subIncyProfileDescription: string;
+  subIncyResolveDnsDomain: string;
+  subIncyResolveDnsIp: string;
+  subIncyResolveEnable: string;
   subIncyRoutingRules: string;
+  subIncySortOrder: string;
+  subIncySupportEmail: string;
   subInfoNodeEnable: boolean;
   subJsonAlwaysArray: boolean;
   subJsonAutoDetect: boolean;

+ 56 - 0
frontend/src/generated/zod.ts

@@ -122,8 +122,36 @@ export const AllSettingSchema = z.object({
   subHappTunMode: z.string(),
   subHappTunType: z.string(),
   subHideSettings: z.boolean(),
+  subIncyAnnounceUrl: z.string(),
+  subIncyAppAutoDetect: z.boolean(),
+  subIncyBannerBgColor: z.string(),
+  subIncyBannerButtonColor: z.string(),
+  subIncyBannerButtonText: z.string(),
+  subIncyBannerButtonUrl: z.string(),
+  subIncyBannerText: z.string(),
   subIncyEnableRouting: z.boolean(),
+  subIncyFragmentInterval: z.string(),
+  subIncyFragmentLength: z.string(),
+  subIncyFragmentPackets: z.string(),
+  subIncyFragmentationEnable: z.string(),
+  subIncyHideCheck: z.string(),
+  subIncyHideUrl: z.string(),
+  subIncyNoLimitEnabled: z.string(),
+  subIncyNoisesDelay: z.string(),
+  subIncyNoisesEnable: z.string(),
+  subIncyNoisesPacket: z.string(),
+  subIncyNoisesType: z.string(),
+  subIncyPerAppEnable: z.string(),
+  subIncyPerAppList: z.string(),
+  subIncyPerAppMode: z.string(),
+  subIncyPremiumUrl: z.string(),
+  subIncyProfileDescription: z.string(),
+  subIncyResolveDnsDomain: z.string(),
+  subIncyResolveDnsIp: z.string(),
+  subIncyResolveEnable: z.string(),
   subIncyRoutingRules: z.string(),
+  subIncySortOrder: z.string(),
+  subIncySupportEmail: z.string(),
   subInfoNodeEnable: z.boolean(),
   subJsonAlwaysArray: z.boolean(),
   subJsonAutoDetect: z.boolean(),
@@ -283,8 +311,36 @@ export const AllSettingViewSchema = z.object({
   subHappTunMode: z.string(),
   subHappTunType: z.string(),
   subHideSettings: z.boolean(),
+  subIncyAnnounceUrl: z.string(),
+  subIncyAppAutoDetect: z.boolean(),
+  subIncyBannerBgColor: z.string(),
+  subIncyBannerButtonColor: z.string(),
+  subIncyBannerButtonText: z.string(),
+  subIncyBannerButtonUrl: z.string(),
+  subIncyBannerText: z.string(),
   subIncyEnableRouting: z.boolean(),
+  subIncyFragmentInterval: z.string(),
+  subIncyFragmentLength: z.string(),
+  subIncyFragmentPackets: z.string(),
+  subIncyFragmentationEnable: z.string(),
+  subIncyHideCheck: z.string(),
+  subIncyHideUrl: z.string(),
+  subIncyNoLimitEnabled: z.string(),
+  subIncyNoisesDelay: z.string(),
+  subIncyNoisesEnable: z.string(),
+  subIncyNoisesPacket: z.string(),
+  subIncyNoisesType: z.string(),
+  subIncyPerAppEnable: z.string(),
+  subIncyPerAppList: z.string(),
+  subIncyPerAppMode: z.string(),
+  subIncyPremiumUrl: z.string(),
+  subIncyProfileDescription: z.string(),
+  subIncyResolveDnsDomain: z.string(),
+  subIncyResolveDnsIp: z.string(),
+  subIncyResolveEnable: z.string(),
   subIncyRoutingRules: z.string(),
+  subIncySortOrder: z.string(),
+  subIncySupportEmail: z.string(),
   subInfoNodeEnable: z.boolean(),
   subJsonAlwaysArray: z.boolean(),
   subJsonAutoDetect: z.boolean(),

+ 29 - 0
frontend/src/models/setting.ts

@@ -107,6 +107,35 @@ export class AllSetting {
   subHappPerAppList = '';
   subHappLocalProxyAuth = 'auto';
 
+  subIncyAppAutoDetect = false;
+  subIncyProfileDescription = '';
+  subIncySortOrder = '';
+  subIncySupportEmail = '';
+  subIncyAnnounceUrl = '';
+  subIncyPremiumUrl = '';
+  subIncyBannerText = '';
+  subIncyBannerButtonText = '';
+  subIncyBannerButtonUrl = '';
+  subIncyBannerBgColor = '';
+  subIncyBannerButtonColor = '';
+  subIncyHideUrl = '';
+  subIncyHideCheck = '';
+  subIncyNoLimitEnabled = '';
+  subIncyPerAppEnable = '';
+  subIncyPerAppMode = '';
+  subIncyPerAppList = '';
+  subIncyFragmentationEnable = '';
+  subIncyFragmentLength = '';
+  subIncyFragmentInterval = '';
+  subIncyFragmentPackets = '';
+  subIncyNoisesEnable = '';
+  subIncyNoisesType = '';
+  subIncyNoisesPacket = '';
+  subIncyNoisesDelay = '';
+  subIncyResolveEnable = '';
+  subIncyResolveDnsDomain = '';
+  subIncyResolveDnsIp = '';
+
   timeLocation = 'Local';
 
   ldapEnable = false;

+ 488 - 0
frontend/src/pages/settings/IncySettingsContent.tsx

@@ -0,0 +1,488 @@
+import { useTranslation } from 'react-i18next';
+import { Input, Select, Switch, Tabs } from 'antd';
+import {
+  AppstoreOutlined,
+  BranchesOutlined,
+  NotificationOutlined,
+  SafetyOutlined,
+  ThunderboltOutlined,
+} from '@ant-design/icons';
+import type { AllSetting } from '@/models/setting';
+import { SettingListItem } from '@/components/ui';
+import { catTabLabel } from './catTabLabel';
+
+interface IncySettingsContentProps {
+  allSetting: AllSetting;
+  updateSetting: (patch: Partial<AllSetting>) => void;
+  isMobile: boolean;
+  remoteSourceBadge: (val: string) => React.ReactNode;
+}
+
+// Incy documents every switch as `1`/`0`; an empty value omits the header so
+// the subscriber's own app choice wins.
+const onOff = (t: (key: string) => string) => [
+  { value: '', label: t('pages.settings.subIncyNotSet') },
+  { value: '1', label: t('pages.settings.subIncyOn') },
+  { value: '0', label: t('pages.settings.subIncyOff') },
+];
+
+export default function IncySettingsContent({
+  allSetting,
+  updateSetting,
+  isMobile,
+  remoteSourceBadge,
+}: IncySettingsContentProps) {
+  const { t } = useTranslation();
+
+  return (
+    <>
+      <SettingListItem
+        paddings="small"
+        title={t('pages.settings.subIncyAppAutoDetect')}
+        description={t('pages.settings.subIncyAppAutoDetectDesc')}
+      >
+        <Switch
+          checked={allSetting.subIncyAppAutoDetect}
+          onChange={(v) => updateSetting({ subIncyAppAutoDetect: v })}
+        />
+      </SettingListItem>
+
+      <Tabs
+        type="card"
+        size="small"
+        items={[
+          {
+            key: 'app',
+            label: catTabLabel(<AppstoreOutlined />, t('pages.settings.subIncyGroupApp'), isMobile),
+            children: (
+              <>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyProfileDescription')}
+                  description={t('pages.settings.subIncyProfileDescriptionDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyProfileDescription}
+                    maxLength={200}
+                    onChange={(e) => updateSetting({ subIncyProfileDescription: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncySortOrder')}
+                  description={t('pages.settings.subIncySortOrderDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncySortOrder}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncySortOrder: v })}
+                    options={[
+                      { value: '', label: t('pages.settings.subIncyNotSet') },
+                      { value: 'none', label: t('pages.settings.subIncySortNone') },
+                      { value: 'ping', label: t('pages.settings.subIncySortPing') },
+                      { value: 'name', label: t('pages.settings.subIncySortName') },
+                    ]}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncySupportEmail')}
+                  description={t('pages.settings.subIncySupportEmailDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncySupportEmail}
+                    placeholder="[email protected]"
+                    onChange={(e) => updateSetting({ subIncySupportEmail: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyAnnounceUrl')}
+                  description={t('pages.settings.subIncyAnnounceUrlDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyAnnounceUrl}
+                    placeholder="https://t.me/your_channel"
+                    onChange={(e) => updateSetting({ subIncyAnnounceUrl: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyPremiumUrl')}
+                  description={t('pages.settings.subIncyPremiumUrlDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyPremiumUrl}
+                    placeholder="https://example.com/buy"
+                    onChange={(e) => updateSetting({ subIncyPremiumUrl: e.target.value })}
+                  />
+                </SettingListItem>
+              </>
+            ),
+          },
+          {
+            key: 'banners',
+            label: catTabLabel(
+              <NotificationOutlined />,
+              t('pages.settings.subIncyGroupBanners'),
+              isMobile,
+            ),
+            children: (
+              <>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyBannerText')}
+                  description={t('pages.settings.subIncyBannerTextDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyBannerText}
+                    onChange={(e) => updateSetting({ subIncyBannerText: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyBannerButtonText')}
+                  description={t('pages.settings.subIncyBannerButtonTextDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyBannerButtonText}
+                    onChange={(e) => updateSetting({ subIncyBannerButtonText: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyBannerButtonUrl')}
+                  description={t('pages.settings.subIncyBannerButtonUrlDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyBannerButtonUrl}
+                    placeholder="https://example.com/sale"
+                    onChange={(e) => updateSetting({ subIncyBannerButtonUrl: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyBannerBgColor')}
+                  description={t('pages.settings.subIncyBannerBgColorDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyBannerBgColor}
+                    placeholder="#E53E3E"
+                    onChange={(e) => updateSetting({ subIncyBannerBgColor: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyBannerButtonColor')}
+                  description={t('pages.settings.subIncyBannerButtonColorDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyBannerButtonColor}
+                    placeholder="#38A169"
+                    onChange={(e) => updateSetting({ subIncyBannerButtonColor: e.target.value })}
+                  />
+                </SettingListItem>
+              </>
+            ),
+          },
+          {
+            key: 'privacy',
+            label: catTabLabel(
+              <SafetyOutlined />,
+              t('pages.settings.subIncyGroupPrivacy'),
+              isMobile,
+            ),
+            children: (
+              <>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyHideUrl')}
+                  description={t('pages.settings.subIncyHideUrlDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyHideUrl}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyHideUrl: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyHideCheck')}
+                  description={t('pages.settings.subIncyHideCheckDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyHideCheck}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyHideCheck: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyNoLimit')}
+                  description={t('pages.settings.subIncyNoLimitDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyNoLimitEnabled}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyNoLimitEnabled: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyPerAppEnable')}
+                  description={t('pages.settings.subIncyPerAppEnableDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyPerAppEnable}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyPerAppEnable: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyPerAppMode')}
+                  description={t('pages.settings.subIncyPerAppModeDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyPerAppMode}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyPerAppMode: v })}
+                    options={[
+                      { value: '', label: t('pages.settings.subIncyNotSet') },
+                      { value: 'proxy', label: t('pages.settings.subIncyPerAppModeProxy') },
+                      { value: 'bypass', label: t('pages.settings.subIncyPerAppModeBypass') },
+                    ]}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyPerAppList')}
+                  description={t('pages.settings.subIncyPerAppListDesc')}
+                >
+                  <Input.TextArea
+                    value={allSetting.subIncyPerAppList}
+                    rows={4}
+                    placeholder={
+                      'com.google.chrome\norg.telegram.messenger\n\nor https://.../apps.txt'
+                    }
+                    onChange={(e) => updateSetting({ subIncyPerAppList: e.target.value })}
+                  />
+                </SettingListItem>
+              </>
+            ),
+          },
+          {
+            key: 'network',
+            label: catTabLabel(
+              <ThunderboltOutlined />,
+              t('pages.settings.subIncyGroupNetwork'),
+              isMobile,
+            ),
+            children: (
+              <>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyFragmentationEnable')}
+                  description={t('pages.settings.subIncyFragmentationEnableDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyFragmentationEnable}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyFragmentationEnable: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyFragmentLength')}
+                  description={t('pages.settings.subIncyFragmentLengthDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyFragmentLength}
+                    placeholder="10-30"
+                    onChange={(e) => updateSetting({ subIncyFragmentLength: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyFragmentInterval')}
+                  description={t('pages.settings.subIncyFragmentIntervalDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyFragmentInterval}
+                    placeholder="20-40"
+                    onChange={(e) => updateSetting({ subIncyFragmentInterval: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyFragmentPackets')}
+                  description={t('pages.settings.subIncyFragmentPacketsDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyFragmentPackets}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyFragmentPackets: v })}
+                    options={[
+                      { value: '', label: t('pages.settings.subIncyNotSet') },
+                      { value: 'tlshello', label: 'tlshello' },
+                      { value: '1-3', label: '1-3' },
+                      { value: '1', label: '1' },
+                      { value: 'all', label: 'all' },
+                    ]}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyNoisesEnable')}
+                  description={t('pages.settings.subIncyNoisesEnableDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyNoisesEnable}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyNoisesEnable: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyNoisesType')}
+                  description={t('pages.settings.subIncyNoisesTypeDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyNoisesType}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyNoisesType: v })}
+                    options={[
+                      { value: '', label: t('pages.settings.subIncyNotSet') },
+                      { value: 'rand', label: 'rand' },
+                      { value: 'str', label: 'str' },
+                      { value: 'hex', label: 'hex' },
+                    ]}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyNoisesPacket')}
+                  description={t('pages.settings.subIncyNoisesPacketDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyNoisesPacket}
+                    placeholder="10-20"
+                    onChange={(e) => updateSetting({ subIncyNoisesPacket: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyNoisesDelay')}
+                  description={t('pages.settings.subIncyNoisesDelayDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyNoisesDelay}
+                    placeholder="10-50"
+                    onChange={(e) => updateSetting({ subIncyNoisesDelay: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyResolveEnable')}
+                  description={t('pages.settings.subIncyResolveEnableDesc')}
+                >
+                  <Select
+                    value={allSetting.subIncyResolveEnable}
+                    style={{ width: '100%' }}
+                    onChange={(v) => updateSetting({ subIncyResolveEnable: v })}
+                    options={onOff(t)}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyResolveDnsDomain')}
+                  description={t('pages.settings.subIncyResolveDnsDomainDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyResolveDnsDomain}
+                    placeholder="https://common.dot.dns.yandex.net/dns-query"
+                    onChange={(e) => updateSetting({ subIncyResolveDnsDomain: e.target.value })}
+                  />
+                </SettingListItem>
+
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyResolveDnsIp')}
+                  description={t('pages.settings.subIncyResolveDnsIpDesc')}
+                >
+                  <Input
+                    value={allSetting.subIncyResolveDnsIp}
+                    placeholder="77.88.8.8"
+                    onChange={(e) => updateSetting({ subIncyResolveDnsIp: e.target.value })}
+                  />
+                </SettingListItem>
+              </>
+            ),
+          },
+          {
+            key: 'routing',
+            label: catTabLabel(
+              <BranchesOutlined />,
+              t('pages.settings.subIncyGroupRouting'),
+              isMobile,
+            ),
+            children: (
+              <>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyEnableRouting')}
+                  description={t('pages.settings.subIncyEnableRoutingDesc')}
+                >
+                  <Switch
+                    checked={allSetting.subIncyEnableRouting}
+                    onChange={(v) => updateSetting({ subIncyEnableRouting: v })}
+                  />
+                </SettingListItem>
+                <SettingListItem
+                  paddings="small"
+                  title={t('pages.settings.subIncyRoutingRules')}
+                  badge={remoteSourceBadge(allSetting.subIncyRoutingRules)}
+                  description={t('pages.settings.subIncyRoutingRulesDesc')}
+                >
+                  <Input.TextArea
+                    value={allSetting.subIncyRoutingRules}
+                    placeholder="incy://routing/onadd/... or https://.../DEFAULT.JSON"
+                    onChange={(e) => updateSetting({ subIncyRoutingRules: e.target.value })}
+                  />
+                </SettingListItem>
+              </>
+            ),
+          },
+        ]}
+      />
+    </>
+  );
+}

+ 7 - 24
frontend/src/pages/settings/SubscriptionGeneralTab.tsx

@@ -19,6 +19,7 @@ import { useMediaQuery } from '@/hooks/useMediaQuery';
 import { catTabLabel } from './catTabLabel';
 import { sanitizePath, normalizePath } from './uriPath';
 import HappSettingsContent from './HappSettingsContent';
+import IncySettingsContent from './IncySettingsContent';
 import { remoteSourceBadge } from './subscriptionShared';
 
 interface SubscriptionGeneralTabProps {
@@ -455,30 +456,12 @@ export default function SubscriptionGeneralTab({
           key: '7',
           label: catTabLabel(<CompassOutlined />, 'Incy', isMobile),
           children: (
-            <>
-              <SettingListItem
-                paddings="small"
-                title={t('pages.settings.subIncyEnableRouting')}
-                description={t('pages.settings.subIncyEnableRoutingDesc')}
-              >
-                <Switch
-                  checked={allSetting.subIncyEnableRouting}
-                  onChange={(v) => updateSetting({ subIncyEnableRouting: v })}
-                />
-              </SettingListItem>
-              <SettingListItem
-                paddings="small"
-                title={t('pages.settings.subIncyRoutingRules')}
-                badge={remoteSourceBadge(allSetting.subIncyRoutingRules)}
-                description={t('pages.settings.subIncyRoutingRulesDesc')}
-              >
-                <Input.TextArea
-                  value={allSetting.subIncyRoutingRules}
-                  placeholder="incy://routing/onadd/... or https://.../DEFAULT.JSON"
-                  onChange={(e) => updateSetting({ subIncyRoutingRules: e.target.value })}
-                />
-              </SettingListItem>
-            </>
+            <IncySettingsContent
+              allSetting={allSetting}
+              updateSetting={updateSetting}
+              isMobile={isMobile}
+              remoteSourceBadge={remoteSourceBadge}
+            />
           ),
         },
       ]}

+ 28 - 0
frontend/src/schemas/setting.ts

@@ -111,6 +111,34 @@ export const AllSettingSchema = z
     subHappPerAppMode: z.string().optional(),
     subHappPerAppList: z.string().optional(),
     subHappLocalProxyAuth: z.string().optional(),
+    subIncyAppAutoDetect: z.boolean().optional(),
+    subIncyProfileDescription: z.string().optional(),
+    subIncySortOrder: z.string().optional(),
+    subIncySupportEmail: z.string().optional(),
+    subIncyAnnounceUrl: z.string().optional(),
+    subIncyPremiumUrl: z.string().optional(),
+    subIncyBannerText: z.string().optional(),
+    subIncyBannerButtonText: z.string().optional(),
+    subIncyBannerButtonUrl: z.string().optional(),
+    subIncyBannerBgColor: z.string().optional(),
+    subIncyBannerButtonColor: z.string().optional(),
+    subIncyHideUrl: z.string().optional(),
+    subIncyHideCheck: z.string().optional(),
+    subIncyNoLimitEnabled: z.string().optional(),
+    subIncyPerAppEnable: z.string().optional(),
+    subIncyPerAppMode: z.string().optional(),
+    subIncyPerAppList: z.string().optional(),
+    subIncyFragmentationEnable: z.string().optional(),
+    subIncyFragmentLength: z.string().optional(),
+    subIncyFragmentInterval: z.string().optional(),
+    subIncyFragmentPackets: z.string().optional(),
+    subIncyNoisesEnable: z.string().optional(),
+    subIncyNoisesType: z.string().optional(),
+    subIncyNoisesPacket: z.string().optional(),
+    subIncyNoisesDelay: z.string().optional(),
+    subIncyResolveEnable: z.string().optional(),
+    subIncyResolveDnsDomain: z.string().optional(),
+    subIncyResolveDnsIp: z.string().optional(),
     timeLocation: z.string().optional(),
     ldapEnable: z.boolean().optional(),
     ldapHost: z.string().optional(),

+ 84 - 0
frontend/src/test/incy-settings-content.test.tsx

@@ -0,0 +1,84 @@
+import { describe, expect, it, vi } from 'vitest';
+import { fireEvent } from '@testing-library/react';
+
+import IncySettingsContent from '@/pages/settings/IncySettingsContent';
+import { AllSetting } from '@/models/setting';
+
+import { renderWithProviders } from './test-utils';
+
+function openTab(name: string) {
+  const tab = Array.from(document.querySelectorAll('.ant-tabs-tab')).find((t) =>
+    (t.textContent ?? '').includes(name),
+  );
+  if (!tab) throw new Error(`tab '${name}' not found`);
+  fireEvent.click(tab);
+}
+
+function settingRow(title: string): HTMLElement {
+  const row = Array.from(document.querySelectorAll('li,div[class*="setting"]')).find((r) =>
+    (r.textContent ?? '').includes(title),
+  );
+  if (!row) throw new Error(`setting row '${title}' not found`);
+  return row as HTMLElement;
+}
+
+function selectFor(title: string): HTMLElement {
+  const select = settingRow(title).querySelector('.ant-select');
+  if (!select) throw new Error(`select for '${title}' not found`);
+  return select as HTMLElement;
+}
+
+function selectOption(title: string, option: string) {
+  const select = selectFor(title);
+  fireEvent.mouseDown(select.querySelector('.ant-select-selector') ?? select);
+  const match = Array.from(document.querySelectorAll('.ant-select-item-option')).find(
+    (o) => (o.textContent ?? '').trim() === option,
+  );
+  if (!match) throw new Error(`option '${option}' for '${title}' not found`);
+  fireEvent.click(match);
+}
+
+function render(subIncy: Partial<AllSetting> = {}) {
+  const updateSetting = vi.fn();
+  const allSetting = Object.assign(new AllSetting(), subIncy);
+  renderWithProviders(
+    <IncySettingsContent
+      allSetting={allSetting}
+      updateSetting={updateSetting}
+      isMobile={false}
+      remoteSourceBadge={() => null}
+    />,
+  );
+  return updateSetting;
+}
+
+describe('Incy app-management settings', () => {
+  // Every Incy switch and enum is tri-state: "Not set" must store the empty
+  // value so the panel sends no header and the subscriber's app choice wins.
+  it('stores the empty value for "Not set" so no header is emitted', () => {
+    const updateSetting = render({ subIncySortOrder: 'ping' });
+
+    openTab('App');
+    selectOption('Server sort order', 'Not set');
+
+    expect(updateSetting).toHaveBeenCalledWith({ subIncySortOrder: '' });
+  });
+
+  it('sends the documented per-app literal, not the Happ on/include spelling', () => {
+    const updateSetting = render();
+
+    openTab('Privacy');
+    selectOption('Per-app mode (Android)', 'On');
+
+    expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppEnable: '1' });
+  });
+
+  it('offers bypass and proxy for the per-app mode', () => {
+    const updateSetting = render();
+
+    openTab('Privacy');
+    selectOption('Per-app mode type', 'Listed apps bypass');
+
+    expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppMode: 'bypass' });
+  });
+});

+ 8 - 0
internal/sub/controller.go

@@ -62,6 +62,7 @@ type SUBController struct {
 	subJsonRoutingRules string
 	subHideSettings     bool
 	happConfig          HappConfig
+	incyConfig          IncyConfig
 
 	subIncyEnableRouting bool
 	subIncyRoutingRules  string
@@ -124,6 +125,7 @@ type subControllerConfig struct {
 	subRoutingRules  string
 	subHideSettings  bool
 	happConfig       HappConfig
+	incyConfig       IncyConfig
 
 	subIncyEnableRouting bool
 	subIncyRoutingRules  string
@@ -259,6 +261,10 @@ func WithSUBHappConfig(value HappConfig) SUBControllerOption {
 	return func(config *subControllerConfig) { config.happConfig = value }
 }
 
+func WithSUBIncyConfig(value IncyConfig) SUBControllerOption {
+	return func(config *subControllerConfig) { config.incyConfig = value }
+}
+
 func defaultSUBControllerConfig() subControllerConfig {
 	return subControllerConfig{
 		subPath:        "/sub/",
@@ -293,6 +299,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo
 		subJsonRoutingRules: config.subJsonRoutingRules,
 		subHideSettings:     config.subHideSettings,
 		happConfig:          config.happConfig,
+		incyConfig:          config.incyConfig,
 
 		subIncyEnableRouting: config.subIncyEnableRouting,
 		subIncyRoutingRules:  config.subIncyRoutingRules,
@@ -974,4 +981,5 @@ func (a *SUBController) ApplyCommonHeaders(
 	}
 
 	ApplyHappHeaders(c, a.happConfig, happManaged)
+	ApplyIncyHeaders(c, a.incyConfig, a.incyConfig.AutoDetect && c.Request != nil && IsIncyClient(c.GetHeader("User-Agent")))
 }

+ 180 - 0
internal/sub/incy.go

@@ -0,0 +1,180 @@
+package sub
+
+import (
+	"encoding/base64"
+	"regexp"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+)
+
+// INCY clients identify themselves as INCY/<version>/<platform>.
+var incyUserAgentRegex = regexp.MustCompile(`(?i)\bincy\b`)
+
+var (
+	incyRangeRegex = regexp.MustCompile(`^\d+(-\d+)?$`)
+	incyHexRegex   = regexp.MustCompile(`^#[0-9a-fA-F]{3,8}$`)
+	// Incy documents fragmentation-packets as tlshello | 1-3 | 1 | all.
+	incyPacketsRangeRegex = regexp.MustCompile(`^\d+-\d+$`)
+)
+
+// IncyConfig holds the Incy app-management headers the panel can push.
+// A "" field omits its header, so an untouched panel never overrides the app.
+type IncyConfig struct {
+	AutoDetect bool
+
+	ProfileDescription string
+	SortOrder          string
+	SupportEmail       string
+	AnnounceUrl        string
+	PremiumUrl         string
+
+	BannerText        string
+	BannerButtonText  string
+	BannerButtonUrl   string
+	BannerBgColor     string
+	BannerButtonColor string
+
+	HideUrl        string
+	HideCheck      string
+	NoLimitEnabled string
+
+	PerAppProxyEnable string
+	PerAppProxyMode   string
+	PerAppProxyList   string
+
+	FragmentationEnable   string
+	FragmentationLength   string
+	FragmentationInterval string
+	FragmentationPackets  string
+
+	NoisesEnable string
+	NoisesType   string
+	NoisesPacket string
+	NoisesDelay  string
+
+	ServerAddressResolveEnable    string
+	ServerAddressResolveDnsDomain string
+	ServerAddressResolveDnsIp     string
+}
+
+// IsIncyClient checks if the client user-agent identifies as INCY.
+func IsIncyClient(userAgent string) bool {
+	return incyUserAgentRegex.MatchString(userAgent)
+}
+
+// incyOnOff maps a switch setting to the documented `1`/`0` literal. An
+// unrecognised or unset value stays empty so the header is omitted.
+func incyOnOff(v string) string {
+	switch strings.ToLower(strings.TrimSpace(v)) {
+	case "1", "true", "yes", "on":
+		return "1"
+	case "0", "false", "no", "off":
+		return "0"
+	}
+	return ""
+}
+
+// incyEnum passes through only a documented literal from a fixed value set.
+func incyEnum(v string, allowed ...string) string {
+	value := strings.ToLower(strings.TrimSpace(v))
+	for _, a := range allowed {
+		if value == a {
+			return value
+		}
+	}
+	return ""
+}
+
+// incyMatch returns the trimmed value only when it matches a documented shape.
+func incyMatch(v string, re *regexp.Regexp) string {
+	value := strings.TrimSpace(v)
+	if re.MatchString(value) {
+		return value
+	}
+	return ""
+}
+
+func incyASCII(v string) bool {
+	for i := 0; i < len(v); i++ {
+		if v[i] > 0x7e || v[i] < 0x20 {
+			return false
+		}
+	}
+	return true
+}
+
+// incyPackageList joins a comma- or line-separated app list as CSV, since a
+// header cannot carry the newlines the settings textarea accepts.
+func incyPackageList(v string) string {
+	entries := strings.FieldsFunc(v, func(r rune) bool { return r == ',' || r == '\n' || r == '\r' })
+	kept := entries[:0]
+	for _, entry := range entries {
+		if entry = strings.TrimSpace(entry); entry != "" {
+			kept = append(kept, entry)
+		}
+	}
+	return strings.Join(kept, ",")
+}
+
+// incyHeaderText base64-wraps non-ASCII text because the docs require
+// `base64:<...>` for anything outside the ASCII range (Cyrillic, CJK, emoji).
+func incyHeaderText(v string) string {
+	text := sanitizeHeaderValue(v)
+	if text == "" || incyASCII(text) {
+		return text
+	}
+	return "base64:" + base64.StdEncoding.EncodeToString([]byte(text))
+}
+
+// ApplyIncyHeaders sets the Incy app-management headers from
+// https://docs.incy.cc/en/app-management/ (matched case-insensitively).
+func ApplyIncyHeaders(c *gin.Context, cfg IncyConfig, isIncy bool) {
+	if c == nil || c.Writer == nil || !cfg.AutoDetect || !isIncy {
+		return
+	}
+	h := c.Writer.Header()
+	set := func(name, value string) {
+		if value != "" {
+			h.Set(name, value)
+		}
+	}
+
+	set("Profile-Description", incyHeaderText(cfg.ProfileDescription))
+	set("Sort-Order", incyEnum(cfg.SortOrder, "none", "ping", "name"))
+	set("Support-Email", sanitizeHeaderValue(cfg.SupportEmail))
+	set("Announce-Url", sanitizeHeaderValue(cfg.AnnounceUrl))
+	set("Premium-Url", sanitizeHeaderValue(cfg.PremiumUrl))
+
+	set("Banner-Text", incyHeaderText(cfg.BannerText))
+	set("Banner-Button-Text", incyHeaderText(cfg.BannerButtonText))
+	set("Banner-Button-Url", sanitizeHeaderValue(cfg.BannerButtonUrl))
+	set("Banner-Bg-Color", incyMatch(cfg.BannerBgColor, incyHexRegex))
+	set("Banner-Button-Color", incyMatch(cfg.BannerButtonColor, incyHexRegex))
+
+	set("Hide-Url", incyOnOff(cfg.HideUrl))
+	set("Hide-Check", incyOnOff(cfg.HideCheck))
+	set("No-Limit-Enabled", incyOnOff(cfg.NoLimitEnabled))
+
+	set("Per-App-Proxy-Enable", incyOnOff(cfg.PerAppProxyEnable))
+	set("Per-App-Proxy-Mode", incyEnum(cfg.PerAppProxyMode, "bypass", "proxy"))
+	set("Per-App-Proxy-List", incyPackageList(cfg.PerAppProxyList))
+
+	set("Fragmentation-Enable", incyOnOff(cfg.FragmentationEnable))
+	set("Fragmentation-Length", incyMatch(cfg.FragmentationLength, incyRangeRegex))
+	set("Fragmentation-Interval", incyMatch(cfg.FragmentationInterval, incyRangeRegex))
+	if packets := strings.ToLower(strings.TrimSpace(cfg.FragmentationPackets)); packets != "" {
+		if packets == "tlshello" || packets == "all" || packets == "1" || incyPacketsRangeRegex.MatchString(packets) {
+			set("Fragmentation-Packets", packets)
+		}
+	}
+
+	set("Noises-Enable", incyOnOff(cfg.NoisesEnable))
+	set("Noises-Type", incyEnum(cfg.NoisesType, "rand", "str", "hex"))
+	set("Noises-Packet", sanitizeHeaderValue(cfg.NoisesPacket))
+	set("Noises-Delay", incyMatch(cfg.NoisesDelay, incyRangeRegex))
+
+	set("Server-Address-Resolve-Enable", incyOnOff(cfg.ServerAddressResolveEnable))
+	set("Server-Address-Resolve-Dns-Domain", sanitizeHeaderValue(cfg.ServerAddressResolveDnsDomain))
+	set("Server-Address-Resolve-Dns-Ip", sanitizeHeaderValue(cfg.ServerAddressResolveDnsIp))
+}

+ 208 - 0
internal/sub/incy_test.go

@@ -0,0 +1,208 @@
+package sub
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/gin-gonic/gin"
+)
+
+func fullIncyConfig() IncyConfig {
+	return IncyConfig{
+		AutoDetect:                    true,
+		ProfileDescription:            "Fast and stable network",
+		SortOrder:                     "ping",
+		SupportEmail:                  "[email protected]",
+		AnnounceUrl:                   "https://t.me/incy_news",
+		PremiumUrl:                    "https://example.com/buy",
+		BannerText:                    "Summer sale",
+		BannerButtonText:              "Buy now",
+		BannerButtonUrl:               "https://example.com/sale",
+		BannerBgColor:                 "#E53E3E",
+		BannerButtonColor:             "#38A169",
+		HideUrl:                       "1",
+		HideCheck:                     "true",
+		NoLimitEnabled:                "0",
+		PerAppProxyEnable:             "1",
+		PerAppProxyMode:               "bypass",
+		PerAppProxyList:               "com.google.chrome,org.telegram.messenger",
+		FragmentationEnable:           "1",
+		FragmentationLength:           "10-30",
+		FragmentationInterval:         "20-40",
+		FragmentationPackets:          "tlshello",
+		NoisesEnable:                  "1",
+		NoisesType:                    "rand",
+		NoisesPacket:                  "10-20",
+		NoisesDelay:                   "10-50",
+		ServerAddressResolveEnable:    "1",
+		ServerAddressResolveDnsDomain: "https://common.dot.dns.yandex.net/dns-query",
+		ServerAddressResolveDnsIp:     "77.88.8.8",
+	}
+}
+
+func applyIncyToHeaders(t *testing.T, cfg IncyConfig, userAgent string) http.Header {
+	t.Helper()
+	gin.SetMode(gin.TestMode)
+	recorder := httptest.NewRecorder()
+	ctx, _ := gin.CreateTestContext(recorder)
+	ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
+	if userAgent != "" {
+		ctx.Request.Header.Set("User-Agent", userAgent)
+	}
+	ApplyIncyHeaders(ctx, cfg, cfg.AutoDetect && IsIncyClient(ctx.GetHeader("User-Agent")))
+	return recorder.Header()
+}
+
+func TestIsIncyClient(t *testing.T) {
+	for _, tc := range []struct {
+		userAgent string
+		want      bool
+	}{
+		{"INCY/1.0.0/ios", true},
+		{"incy/2.4/android", true},
+		{"Mozilla/5.0 (Linux; Android 14) INCY/1.2.3/android", true},
+		{"Happ/1.2.0 (iPhone; iOS 17.5)", false},
+		{"v2rayNG/1.8.5", false},
+		{"", false},
+	} {
+		if got := IsIncyClient(tc.userAgent); got != tc.want {
+			t.Errorf("IsIncyClient(%q) = %v, want %v", tc.userAgent, got, tc.want)
+		}
+	}
+}
+
+func TestApplyIncyHeaders_AllDocumentedHeaders(t *testing.T) {
+	cfg := fullIncyConfig()
+	h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
+
+	want := map[string]string{
+		"Profile-Description":               "Fast and stable network",
+		"Sort-Order":                        "ping",
+		"Support-Email":                     "[email protected]",
+		"Announce-Url":                      "https://t.me/incy_news",
+		"Premium-Url":                       "https://example.com/buy",
+		"Banner-Text":                       "Summer sale",
+		"Banner-Button-Text":                "Buy now",
+		"Banner-Button-Url":                 "https://example.com/sale",
+		"Banner-Bg-Color":                   "#E53E3E",
+		"Banner-Button-Color":               "#38A169",
+		"Hide-Url":                          "1",
+		"Hide-Check":                        "1",
+		"No-Limit-Enabled":                  "0",
+		"Per-App-Proxy-Enable":              "1",
+		"Per-App-Proxy-Mode":                "bypass",
+		"Per-App-Proxy-List":                "com.google.chrome,org.telegram.messenger",
+		"Fragmentation-Enable":              "1",
+		"Fragmentation-Length":              "10-30",
+		"Fragmentation-Interval":            "20-40",
+		"Fragmentation-Packets":             "tlshello",
+		"Noises-Enable":                     "1",
+		"Noises-Type":                       "rand",
+		"Noises-Packet":                     "10-20",
+		"Noises-Delay":                      "10-50",
+		"Server-Address-Resolve-Enable":     "1",
+		"Server-Address-Resolve-Dns-Domain": "https://common.dot.dns.yandex.net/dns-query",
+		"Server-Address-Resolve-Dns-Ip":     "77.88.8.8",
+	}
+	for name, value := range want {
+		if got := h.Get(name); got != value {
+			t.Errorf("header %s = %q, want %q", name, got, value)
+		}
+	}
+}
+
+func TestApplyIncyHeaders_SkipsUnsetValues(t *testing.T) {
+	cfg := IncyConfig{AutoDetect: true}
+	cfg.HideUrl = ""
+	cfg.SortOrder = ""
+	h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/ios")
+
+	for _, name := range []string{"Hide-Url", "Sort-Order", "Fragmentation-Enable", "Banner-Text"} {
+		if got := h.Get(name); got != "" {
+			t.Errorf("unset header %s = %q, want omitted", name, got)
+		}
+	}
+}
+
+func TestApplyIncyHeaders_NotAppliedWithoutIncyClient(t *testing.T) {
+	cfg := fullIncyConfig()
+
+	for _, userAgent := range []string{"Happ/1.2.0 (iPhone)", "v2rayNG/1.8.5", ""} {
+		h := applyIncyToHeaders(t, cfg, userAgent)
+		if got := h.Get("Hide-Url"); got != "" {
+			t.Errorf("user-agent %q: Hide-Url = %q, want omitted", userAgent, got)
+		}
+	}
+}
+
+func TestApplyIncyHeaders_NotAppliedWhenAutoDetectOff(t *testing.T) {
+	cfg := fullIncyConfig()
+	cfg.AutoDetect = false
+
+	h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
+	if got := h.Get("Hide-Url"); got != "" {
+		t.Errorf("AutoDetect off: Hide-Url = %q, want omitted", got)
+	}
+}
+
+func TestIncyHeaderText_Base64ForNonASCII(t *testing.T) {
+	ascii := incyHeaderText("Plain banner")
+	if ascii != "Plain banner" {
+		t.Errorf("ASCII text = %q, want unchanged", ascii)
+	}
+
+	cyrillic := incyHeaderText("Здравствуйте")
+	if !strings.HasPrefix(cyrillic, "base64:") {
+		t.Fatalf("Cyrillic text = %q, want base64: prefix", cyrillic)
+	}
+	// The docs require base64 for anything outside the ASCII range, so the
+	// raw value must not survive on the wire.
+	if strings.Contains(cyrillic, "Здравствуйте") {
+		t.Errorf("Cyrillic text = %q, want the raw value base64-encoded", cyrillic)
+	}
+}
+
+func TestApplyIncyHeaders_RejectsUndocumentedValues(t *testing.T) {
+	cfg := IncyConfig{
+		AutoDetect:           true,
+		SortOrder:            "alphabetical", // not none|ping|name
+		PerAppProxyMode:      "include",      // Happ spelling, not bypass|proxy
+		NoisesType:           "uuid",         // not rand|str|hex
+		FragmentationLength:  "10..30",       // not min-max
+		FragmentationPackets: "3",            // documented only as tlshello|1-3|1|all
+		BannerBgColor:        "red",          // not #RRGGBB
+		HideUrl:              "maybe",        // not 1|0
+	}
+	h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
+
+	for _, name := range []string{
+		"Sort-Order", "Per-App-Proxy-Mode", "Noises-Type",
+		"Fragmentation-Length", "Fragmentation-Packets", "Banner-Bg-Color", "Hide-Url",
+	} {
+		if got := h.Get(name); got != "" {
+			t.Errorf("undocumented value accepted for %s: %q", name, got)
+		}
+	}
+}
+
+func TestApplyIncyHeaders_PerAppListKeepsSeparators(t *testing.T) {
+	// The settings textarea takes one package per line, and a header cannot
+	// carry a newline, so each line must stay a separate list entry.
+	for _, tc := range []struct {
+		name, list, want string
+	}{
+		{"newline", "com.google.chrome\norg.telegram.messenger", "com.google.chrome,org.telegram.messenger"},
+		{"crlf and blank lines", "com.google.chrome\r\n\r\norg.telegram.messenger\r\n", "com.google.chrome,org.telegram.messenger"},
+		{"url", " https://example.com/apps.txt ", "https://example.com/apps.txt"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			cfg := IncyConfig{AutoDetect: true, PerAppProxyList: tc.list}
+			h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
+			if got := h.Get("Per-App-Proxy-List"); got != tc.want {
+				t.Errorf("Per-App-Proxy-List = %q, want %q", got, tc.want)
+			}
+		})
+	}
+}

+ 31 - 0
internal/sub/sub.go

@@ -255,6 +255,36 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 	happCfg.PerAppList, _ = s.settingService.GetSubHappPerAppList()
 	happCfg.LocalProxyAuth, _ = s.settingService.GetSubHappLocalProxyAuth()
 
+	incyCfg := IncyConfig{}
+	incyCfg.AutoDetect, _ = s.settingService.GetSubIncyAppAutoDetect()
+	incyCfg.ProfileDescription, _ = s.settingService.GetSubIncyProfileDescription()
+	incyCfg.SortOrder, _ = s.settingService.GetSubIncySortOrder()
+	incyCfg.SupportEmail, _ = s.settingService.GetSubIncySupportEmail()
+	incyCfg.AnnounceUrl, _ = s.settingService.GetSubIncyAnnounceUrl()
+	incyCfg.PremiumUrl, _ = s.settingService.GetSubIncyPremiumUrl()
+	incyCfg.BannerText, _ = s.settingService.GetSubIncyBannerText()
+	incyCfg.BannerButtonText, _ = s.settingService.GetSubIncyBannerButtonText()
+	incyCfg.BannerButtonUrl, _ = s.settingService.GetSubIncyBannerButtonUrl()
+	incyCfg.BannerBgColor, _ = s.settingService.GetSubIncyBannerBgColor()
+	incyCfg.BannerButtonColor, _ = s.settingService.GetSubIncyBannerButtonColor()
+	incyCfg.HideUrl, _ = s.settingService.GetSubIncyHideUrl()
+	incyCfg.HideCheck, _ = s.settingService.GetSubIncyHideCheck()
+	incyCfg.NoLimitEnabled, _ = s.settingService.GetSubIncyNoLimitEnabled()
+	incyCfg.PerAppProxyEnable, _ = s.settingService.GetSubIncyPerAppEnable()
+	incyCfg.PerAppProxyMode, _ = s.settingService.GetSubIncyPerAppMode()
+	incyCfg.PerAppProxyList, _ = s.settingService.GetSubIncyPerAppList()
+	incyCfg.FragmentationEnable, _ = s.settingService.GetSubIncyFragmentationEnable()
+	incyCfg.FragmentationLength, _ = s.settingService.GetSubIncyFragmentLength()
+	incyCfg.FragmentationInterval, _ = s.settingService.GetSubIncyFragmentInterval()
+	incyCfg.FragmentationPackets, _ = s.settingService.GetSubIncyFragmentPackets()
+	incyCfg.NoisesEnable, _ = s.settingService.GetSubIncyNoisesEnable()
+	incyCfg.NoisesType, _ = s.settingService.GetSubIncyNoisesType()
+	incyCfg.NoisesPacket, _ = s.settingService.GetSubIncyNoisesPacket()
+	incyCfg.NoisesDelay, _ = s.settingService.GetSubIncyNoisesDelay()
+	incyCfg.ServerAddressResolveEnable, _ = s.settingService.GetSubIncyResolveEnable()
+	incyCfg.ServerAddressResolveDnsDomain, _ = s.settingService.GetSubIncyResolveDnsDomain()
+	incyCfg.ServerAddressResolveDnsIp, _ = s.settingService.GetSubIncyResolveDnsIp()
+
 	// set per-request localizer from headers/cookies
 	engine.Use(locale.LocalizerMiddleware())
 
@@ -340,6 +370,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 		WithSUBRoutingRules(SubRoutingRules),
 		WithSUBHideSettings(SubHideSettings),
 		WithSUBHappConfig(happCfg),
+		WithSUBIncyConfig(incyCfg),
 		WithSUBIncyEnableRouting(SubIncyEnableRouting),
 		WithSUBIncyRoutingRules(SubIncyRoutingRules),
 	)

+ 31 - 0
internal/web/entity/entity.go

@@ -156,6 +156,37 @@ type AllSetting struct {
 	SubHappPerAppList          string `json:"subHappPerAppList" form:"subHappPerAppList"`
 	SubHappLocalProxyAuth      string `json:"subHappLocalProxyAuth" form:"subHappLocalProxyAuth"`
 
+	// Incy client customization settings (app-management). A "" value omits
+	// the header so the subscriber's own app setting is left alone.
+	SubIncyAppAutoDetect       bool   `json:"subIncyAppAutoDetect" form:"subIncyAppAutoDetect"`
+	SubIncyProfileDescription  string `json:"subIncyProfileDescription" form:"subIncyProfileDescription"`
+	SubIncySortOrder           string `json:"subIncySortOrder" form:"subIncySortOrder"`
+	SubIncySupportEmail        string `json:"subIncySupportEmail" form:"subIncySupportEmail"`
+	SubIncyAnnounceUrl         string `json:"subIncyAnnounceUrl" form:"subIncyAnnounceUrl"`
+	SubIncyPremiumUrl          string `json:"subIncyPremiumUrl" form:"subIncyPremiumUrl"`
+	SubIncyBannerText          string `json:"subIncyBannerText" form:"subIncyBannerText"`
+	SubIncyBannerButtonText    string `json:"subIncyBannerButtonText" form:"subIncyBannerButtonText"`
+	SubIncyBannerButtonUrl     string `json:"subIncyBannerButtonUrl" form:"subIncyBannerButtonUrl"`
+	SubIncyBannerBgColor       string `json:"subIncyBannerBgColor" form:"subIncyBannerBgColor"`
+	SubIncyBannerButtonColor   string `json:"subIncyBannerButtonColor" form:"subIncyBannerButtonColor"`
+	SubIncyHideUrl             string `json:"subIncyHideUrl" form:"subIncyHideUrl"`
+	SubIncyHideCheck           string `json:"subIncyHideCheck" form:"subIncyHideCheck"`
+	SubIncyNoLimitEnabled      string `json:"subIncyNoLimitEnabled" form:"subIncyNoLimitEnabled"`
+	SubIncyPerAppEnable        string `json:"subIncyPerAppEnable" form:"subIncyPerAppEnable"`
+	SubIncyPerAppMode          string `json:"subIncyPerAppMode" form:"subIncyPerAppMode"`
+	SubIncyPerAppList          string `json:"subIncyPerAppList" form:"subIncyPerAppList"`
+	SubIncyFragmentationEnable string `json:"subIncyFragmentationEnable" form:"subIncyFragmentationEnable"`
+	SubIncyFragmentLength      string `json:"subIncyFragmentLength" form:"subIncyFragmentLength"`
+	SubIncyFragmentInterval    string `json:"subIncyFragmentInterval" form:"subIncyFragmentInterval"`
+	SubIncyFragmentPackets     string `json:"subIncyFragmentPackets" form:"subIncyFragmentPackets"`
+	SubIncyNoisesEnable        string `json:"subIncyNoisesEnable" form:"subIncyNoisesEnable"`
+	SubIncyNoisesType          string `json:"subIncyNoisesType" form:"subIncyNoisesType"`
+	SubIncyNoisesPacket        string `json:"subIncyNoisesPacket" form:"subIncyNoisesPacket"`
+	SubIncyNoisesDelay         string `json:"subIncyNoisesDelay" form:"subIncyNoisesDelay"`
+	SubIncyResolveEnable       string `json:"subIncyResolveEnable" form:"subIncyResolveEnable"`
+	SubIncyResolveDnsDomain    string `json:"subIncyResolveDnsDomain" form:"subIncyResolveDnsDomain"`
+	SubIncyResolveDnsIp        string `json:"subIncyResolveDnsIp" form:"subIncyResolveDnsIp"`
+
 	LdapEnable             bool   `json:"ldapEnable" form:"ldapEnable"`
 	LdapHost               string `json:"ldapHost" form:"ldapHost"`
 	LdapPort               int    `json:"ldapPort" form:"ldapPort" validate:"gte=0,lte=65535"`

+ 144 - 0
internal/web/service/setting.go

@@ -141,6 +141,34 @@ var defaultValueMap = map[string]string{
 	"subHappLocalProxyAuth":       "auto",
 	"subIncyEnableRouting":        "false",
 	"subIncyRoutingRules":         "",
+	"subIncyAppAutoDetect":        "false",
+	"subIncyProfileDescription":   "",
+	"subIncySortOrder":            "",
+	"subIncySupportEmail":         "",
+	"subIncyAnnounceUrl":          "",
+	"subIncyPremiumUrl":           "",
+	"subIncyBannerText":           "",
+	"subIncyBannerButtonText":     "",
+	"subIncyBannerButtonUrl":      "",
+	"subIncyBannerBgColor":        "",
+	"subIncyBannerButtonColor":    "",
+	"subIncyHideUrl":              "",
+	"subIncyHideCheck":            "",
+	"subIncyNoLimitEnabled":       "",
+	"subIncyPerAppEnable":         "",
+	"subIncyPerAppMode":           "",
+	"subIncyPerAppList":           "",
+	"subIncyFragmentationEnable":  "",
+	"subIncyFragmentLength":       "",
+	"subIncyFragmentInterval":     "",
+	"subIncyFragmentPackets":      "",
+	"subIncyNoisesEnable":         "",
+	"subIncyNoisesType":           "",
+	"subIncyNoisesPacket":         "",
+	"subIncyNoisesDelay":          "",
+	"subIncyResolveEnable":        "",
+	"subIncyResolveDnsDomain":     "",
+	"subIncyResolveDnsIp":         "",
 	"subListen":                   "",
 	"subPort":                     "2096",
 	"subPath":                     "/sub/",
@@ -1014,6 +1042,118 @@ func (s *SettingService) GetSubIncyRoutingRules() (string, error) {
 	return s.getString("subIncyRoutingRules")
 }
 
+func (s *SettingService) GetSubIncyAppAutoDetect() (bool, error) {
+	return s.getBool("subIncyAppAutoDetect")
+}
+
+func (s *SettingService) GetSubIncyProfileDescription() (string, error) {
+	return s.getString("subIncyProfileDescription")
+}
+
+func (s *SettingService) GetSubIncySortOrder() (string, error) {
+	return s.getString("subIncySortOrder")
+}
+
+func (s *SettingService) GetSubIncySupportEmail() (string, error) {
+	return s.getString("subIncySupportEmail")
+}
+
+func (s *SettingService) GetSubIncyAnnounceUrl() (string, error) {
+	return s.getString("subIncyAnnounceUrl")
+}
+
+func (s *SettingService) GetSubIncyPremiumUrl() (string, error) {
+	return s.getString("subIncyPremiumUrl")
+}
+
+func (s *SettingService) GetSubIncyBannerText() (string, error) {
+	return s.getString("subIncyBannerText")
+}
+
+func (s *SettingService) GetSubIncyBannerButtonText() (string, error) {
+	return s.getString("subIncyBannerButtonText")
+}
+
+func (s *SettingService) GetSubIncyBannerButtonUrl() (string, error) {
+	return s.getString("subIncyBannerButtonUrl")
+}
+
+func (s *SettingService) GetSubIncyBannerBgColor() (string, error) {
+	return s.getString("subIncyBannerBgColor")
+}
+
+func (s *SettingService) GetSubIncyBannerButtonColor() (string, error) {
+	return s.getString("subIncyBannerButtonColor")
+}
+
+func (s *SettingService) GetSubIncyHideUrl() (string, error) {
+	return s.getString("subIncyHideUrl")
+}
+
+func (s *SettingService) GetSubIncyHideCheck() (string, error) {
+	return s.getString("subIncyHideCheck")
+}
+
+func (s *SettingService) GetSubIncyNoLimitEnabled() (string, error) {
+	return s.getString("subIncyNoLimitEnabled")
+}
+
+func (s *SettingService) GetSubIncyPerAppEnable() (string, error) {
+	return s.getString("subIncyPerAppEnable")
+}
+
+func (s *SettingService) GetSubIncyPerAppMode() (string, error) {
+	return s.getString("subIncyPerAppMode")
+}
+
+func (s *SettingService) GetSubIncyPerAppList() (string, error) {
+	return s.getString("subIncyPerAppList")
+}
+
+func (s *SettingService) GetSubIncyFragmentationEnable() (string, error) {
+	return s.getString("subIncyFragmentationEnable")
+}
+
+func (s *SettingService) GetSubIncyFragmentLength() (string, error) {
+	return s.getString("subIncyFragmentLength")
+}
+
+func (s *SettingService) GetSubIncyFragmentInterval() (string, error) {
+	return s.getString("subIncyFragmentInterval")
+}
+
+func (s *SettingService) GetSubIncyFragmentPackets() (string, error) {
+	return s.getString("subIncyFragmentPackets")
+}
+
+func (s *SettingService) GetSubIncyNoisesEnable() (string, error) {
+	return s.getString("subIncyNoisesEnable")
+}
+
+func (s *SettingService) GetSubIncyNoisesType() (string, error) {
+	return s.getString("subIncyNoisesType")
+}
+
+func (s *SettingService) GetSubIncyNoisesPacket() (string, error) {
+	return s.getString("subIncyNoisesPacket")
+}
+
+func (s *SettingService) GetSubIncyNoisesDelay() (string, error) {
+	return s.getString("subIncyNoisesDelay")
+}
+
+func (s *SettingService) GetSubIncyResolveEnable() (string, error) {
+	return s.getString("subIncyResolveEnable")
+}
+
+func (s *SettingService) GetSubIncyResolveDnsDomain() (string, error) {
+	return s.getString("subIncyResolveDnsDomain")
+}
+
+func (s *SettingService) GetSubIncyResolveDnsIp() (string, error) {
+	return s.getString("subIncyResolveDnsIp")
+}
+
 func (s *SettingService) GetSubListen() (string, error) {
 	return s.getString("subListen")
 }
@@ -1672,6 +1812,10 @@ func validateSettingsURLs(allSetting *entity.AllSetting) error {
 		&allSetting.SubHappFallbackUrl,
 		&allSetting.SubHappSubInfoButtonLink,
 		&allSetting.SubHappSubExpireButtonLink,
+		&allSetting.SubIncyAnnounceUrl,
+		&allSetting.SubIncyPremiumUrl,
+		&allSetting.SubIncyBannerButtonUrl,
+		&allSetting.SubIncyResolveDnsDomain,
 	} {
 		if strings.TrimSpace(*ptr) != "" {
 			*ptr = common.EnsureURLScheme(strings.TrimSpace(*ptr))

+ 70 - 1
internal/web/translation/ar-EG.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "عدد مرات إرسال بوت ديسكورد للتقارير الدورية. اختر فترة جاهزة، أو اختر «مخصص» لإدخال تعبير crontab.",
       "discordNotifyBackup": "نسخة احتياطية لقاعدة البيانات",
       "discordNotifyBackupDesc": "ابعت ملف النسخة الاحتياطية لقاعدة البيانات مع التقرير.",
-      "discordEventBusNotifyDesc": "اختر الأحداث التي تُطلق إشعارات Discord"
+      "discordEventBusNotifyDesc": "اختر الأحداث التي تُطلق إشعارات Discord",
+      "subIncyAppAutoDetect": "اكتشاف ترويسات Incy تلقائيًا",
+      "subIncyAppAutoDetectDesc": "إرسال ترويسات Incy عندما يكون User-Agent الخاص بالعميل INCY.",
+      "subIncyNotSet": "غير محدد",
+      "subIncyOn": "تشغيل",
+      "subIncyOff": "إيقاف",
+      "subIncyGroupApp": "التطبيق",
+      "subIncyProfileDescription": "وصف الملف",
+      "subIncyProfileDescriptionDesc": "وصف منفصل للاشتراك. النص غير ASCII يُرسل بصيغة base64.",
+      "subIncySortOrder": "ترتيب السيرفرات",
+      "subIncySortOrderDesc": "لهذا الاشتراك على iOS/Android، وعالمي على الكمبيوتر.",
+      "subIncySortNone": "ترتيب الاشتراك",
+      "subIncySortPing": "حسب البينج",
+      "subIncySortName": "حسب الاسم",
+      "subIncySupportEmail": "بريد الدعم",
+      "subIncySupportEmailDesc": "يضيف زر Email إلى بطاقة الاشتراك.",
+      "subIncyAnnounceUrl": "رابط الإعلان",
+      "subIncyAnnounceUrlDesc": "وجهة الإعلان في الشاشة الرئيسية.",
+      "subIncyPremiumUrl": "رابط Premium",
+      "subIncyPremiumUrlDesc": "زر Premium في البطاقة، وله الأولوية في زر البانر.",
+      "subIncyGroupBanners": "البانرات",
+      "subIncyBannerText": "نص البانر",
+      "subIncyBannerTextDesc": "يستبدل نص البانر. لمزودي premium فقط.",
+      "subIncyBannerButtonText": "نص زر البانر",
+      "subIncyBannerButtonTextDesc": "عنوان زر البانر. الفراغ يخفي الزر.",
+      "subIncyBannerButtonUrl": "رابط زر البانر",
+      "subIncyBannerButtonUrlDesc": "عنوان URL لزر البانر.",
+      "subIncyBannerBgColor": "خلفية البانر",
+      "subIncyBannerBgColorDesc": "لون hex، مثل #E53E3E.",
+      "subIncyBannerButtonColor": "لون زر البانر",
+      "subIncyBannerButtonColorDesc": "لون hex، مثل #38A169.",
+      "subIncyGroupPrivacy": "الخصوصية",
+      "subIncyHideUrl": "إخفاء رابط الاشتراك",
+      "subIncyHideUrlDesc": "يخفي الرابط من المشاركة والنسخ وQR والنسخ الاحتياطي.",
+      "subIncyHideCheck": "إخفاء زر الفحص",
+      "subIncyHideCheckDesc": "يزيل زر فحص الاتصال من الشاشة الرئيسية.",
+      "subIncyNoLimit": "وضع No-Limit (iOS)",
+      "subIncyNoLimitDesc": "وضع موفّر للذاكرة ليبقى النفق داخل حد 50 ميجابايت في iOS.",
+      "subIncyPerAppEnable": "وضع per-app (أندرويد)",
+      "subIncyPerAppEnableDesc": "يفعّل التقسيم حسب التطبيقات. الإيقاف يتجاوز اختيار المستخدم.",
+      "subIncyPerAppMode": "نوع وضع per-app",
+      "subIncyPerAppModeDesc": "proxy: التطبيقات المذكورة فقط تمر عبر VPN، وbypass العكس.",
+      "subIncyPerAppModeProxy": "التطبيقات المذكورة فقط",
+      "subIncyPerAppModeBypass": "التطبيقات المذكورة تتجاوز",
+      "subIncyPerAppList": "قائمة التطبيقات",
+      "subIncyPerAppListDesc": "أسماء الحزم بفواصل أو أسطر جديدة، أو رابط ملف نصي.",
+      "subIncyGroupNetwork": "الشبكة",
+      "subIncyFragmentationEnable": "التجزئة",
+      "subIncyFragmentationEnableDesc": "يفعّل تجزئة TCP ويتجاوز الإعداد العام.",
+      "subIncyFragmentLength": "طول الجزء",
+      "subIncyFragmentLengthDesc": "نطاق بالبايت، مثل 10-30.",
+      "subIncyFragmentInterval": "الفاصل بين الأجزاء",
+      "subIncyFragmentIntervalDesc": "نطاق بالمللي ثانية، مثل 20-40.",
+      "subIncyFragmentPackets": "حزم التجزئة",
+      "subIncyFragmentPacketsDesc": "أي حزم TCP تُجزّأ: tlshello أو 1-3 أو 1 أو all.",
+      "subIncyNoisesEnable": "حزم الضوضاء",
+      "subIncyNoisesEnableDesc": "يرسل ضوضاء UDP عشوائية قبل مصافحة VPN.",
+      "subIncyNoisesType": "نوع الضوضاء",
+      "subIncyNoisesTypeDesc": "rand أو str أو hex.",
+      "subIncyNoisesPacket": "محتوى حزمة الضوضاء",
+      "subIncyNoisesPacketDesc": "المحتوى؛ مع rand نطاق طول مثل 10-20.",
+      "subIncyNoisesDelay": "تأخير الضوضاء",
+      "subIncyNoisesDelayDesc": "نطاق التأخير بالمللي ثانية، مثل 10-50.",
+      "subIncyResolveEnable": "حل عنوان السيرفر (DoH)",
+      "subIncyResolveEnableDesc": "يحل عنوان السيرفر عبر DoH قبل الاتصال.",
+      "subIncyResolveDnsDomain": "نقطة DoH",
+      "subIncyResolveDnsDomainDesc": "رابط سيرفر DoH، غالبًا ينتهي بـ /dns-query.",
+      "subIncyResolveDnsIp": "IP سيرفر DoH",
+      "subIncyResolveDnsIpDesc": "عنوان IP لسيرفر DoH للتهيئة.",
+      "subIncyGroupRouting": "التوجيه"
     },
     "xray": {
       "save": "احفظ",

+ 70 - 1
internal/web/translation/en-US.json

@@ -1788,7 +1788,76 @@
       "discordNotifyTimeDesc": "How often the Discord bot sends periodic reports. Pick a preset interval, or choose Custom to enter a raw crontab expression.",
       "discordNotifyBackup": "Database Backup",
       "discordNotifyBackupDesc": "Send a database backup file with a report.",
-      "discordEventBusNotifyDesc": "Select which events trigger Discord notifications"
+      "discordEventBusNotifyDesc": "Select which events trigger Discord notifications",
+      "subIncyAppAutoDetect": "Incy header auto-detection",
+      "subIncyAppAutoDetectDesc": "Send the Incy app-management headers when the client User-Agent is INCY.",
+      "subIncyNotSet": "Not set",
+      "subIncyOn": "On",
+      "subIncyOff": "Off",
+      "subIncyGroupApp": "App",
+      "subIncyProfileDescription": "Profile description",
+      "subIncyProfileDescriptionDesc": "Separate subscription description. Non-ASCII text is sent as base64.",
+      "subIncySortOrder": "Server sort order",
+      "subIncySortOrderDesc": "Applies to this subscription on iOS/Android, globally on Desktop.",
+      "subIncySortNone": "Subscription order",
+      "subIncySortPing": "By ping",
+      "subIncySortName": "By name",
+      "subIncySupportEmail": "Support email",
+      "subIncySupportEmailDesc": "Adds an Email button to the subscription card.",
+      "subIncyAnnounceUrl": "Announcement link",
+      "subIncyAnnounceUrlDesc": "Link target for the home screen announcement.",
+      "subIncyPremiumUrl": "Premium link",
+      "subIncyPremiumUrlDesc": "Premium button in the card; also wins for the banner button.",
+      "subIncyGroupBanners": "Banners",
+      "subIncyBannerText": "Banner text",
+      "subIncyBannerTextDesc": "Overrides the banner text. Premium providers only.",
+      "subIncyBannerButtonText": "Banner button text",
+      "subIncyBannerButtonTextDesc": "Banner button label. Empty hides the button.",
+      "subIncyBannerButtonUrl": "Banner button link",
+      "subIncyBannerButtonUrlDesc": "Banner button URL.",
+      "subIncyBannerBgColor": "Banner background",
+      "subIncyBannerBgColorDesc": "Hex colour, e.g. #E53E3E.",
+      "subIncyBannerButtonColor": "Banner button colour",
+      "subIncyBannerButtonColorDesc": "Hex colour, e.g. #38A169.",
+      "subIncyGroupPrivacy": "Privacy",
+      "subIncyHideUrl": "Hide subscription URL",
+      "subIncyHideUrlDesc": "Hides the URL from Share, Copy, QR and backups.",
+      "subIncyHideCheck": "Hide the Check button",
+      "subIncyHideCheckDesc": "Removes the connectivity check button from the home screen.",
+      "subIncyNoLimit": "No-Limit mode (iOS)",
+      "subIncyNoLimitDesc": "Memory-tight mode that keeps the tunnel alive under the iOS 50 MB cap.",
+      "subIncyPerAppEnable": "Per-app mode (Android)",
+      "subIncyPerAppEnableDesc": "Enables per-app split tunnelling. Off overrides the user's choice.",
+      "subIncyPerAppMode": "Per-app mode type",
+      "subIncyPerAppModeDesc": "Proxy: only the listed apps use the VPN. Bypass: they skip it.",
+      "subIncyPerAppModeProxy": "Only listed apps",
+      "subIncyPerAppModeBypass": "Listed apps bypass",
+      "subIncyPerAppList": "App list",
+      "subIncyPerAppListDesc": "Package names separated by commas or newlines, or a URL to a text file.",
+      "subIncyGroupNetwork": "Network",
+      "subIncyFragmentationEnable": "Fragmentation",
+      "subIncyFragmentationEnableDesc": "Enables TCP fragmentation, overriding the global setting.",
+      "subIncyFragmentLength": "Fragment length",
+      "subIncyFragmentLengthDesc": "Range in bytes, e.g. 10-30.",
+      "subIncyFragmentInterval": "Fragment interval",
+      "subIncyFragmentIntervalDesc": "Range in milliseconds, e.g. 20-40.",
+      "subIncyFragmentPackets": "Fragment packets",
+      "subIncyFragmentPacketsDesc": "Which TCP packets to fragment: tlshello, 1-3, 1 or all.",
+      "subIncyNoisesEnable": "Noise packets",
+      "subIncyNoisesEnableDesc": "Sends random UDP noise before the VPN handshake.",
+      "subIncyNoisesType": "Noise payload type",
+      "subIncyNoisesTypeDesc": "rand, str or hex.",
+      "subIncyNoisesPacket": "Noise packet content",
+      "subIncyNoisesPacketDesc": "Payload; with rand, a length range like 10-20.",
+      "subIncyNoisesDelay": "Noise delay",
+      "subIncyNoisesDelayDesc": "Delay range in milliseconds, e.g. 10-50.",
+      "subIncyResolveEnable": "Resolve server address (DoH)",
+      "subIncyResolveEnableDesc": "Pre-resolves the server address over DoH before connecting.",
+      "subIncyResolveDnsDomain": "DoH endpoint",
+      "subIncyResolveDnsDomainDesc": "DoH server URL, usually ending in /dns-query.",
+      "subIncyResolveDnsIp": "DoH server IP",
+      "subIncyResolveDnsIpDesc": "IP of the DoH server, used for bootstrap.",
+      "subIncyGroupRouting": "Routing"
     },
     "xray": {
       "save": "Save",

+ 70 - 1
internal/web/translation/es-ES.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Con qué frecuencia el bot de Discord envía informes periódicos. Elige un intervalo predefinido o selecciona Personalizado para introducir una expresión crontab.",
       "discordNotifyBackup": "Respaldo de Base de Datos",
       "discordNotifyBackupDesc": "Incluir archivo de respaldo de base de datos con notificación de informe.",
-      "discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord"
+      "discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord",
+      "subIncyAppAutoDetect": "Detección automática de cabeceras de Incy",
+      "subIncyAppAutoDetectDesc": "Envía las cabeceras de Incy cuando el User-Agent del cliente es INCY.",
+      "subIncyNotSet": "Sin definir",
+      "subIncyOn": "Activado",
+      "subIncyOff": "Desactivado",
+      "subIncyGroupApp": "Aplicación",
+      "subIncyProfileDescription": "Descripción del perfil",
+      "subIncyProfileDescriptionDesc": "Descripción aparte de la suscripción. El texto no ASCII se envía en base64.",
+      "subIncySortOrder": "Orden de servidores",
+      "subIncySortOrderDesc": "Para esta suscripción en iOS/Android, global en escritorio.",
+      "subIncySortNone": "Orden de la suscripción",
+      "subIncySortPing": "Por ping",
+      "subIncySortName": "Por nombre",
+      "subIncySupportEmail": "Email de soporte",
+      "subIncySupportEmailDesc": "Añade un botón Email a la tarjeta de suscripción.",
+      "subIncyAnnounceUrl": "Enlace del anuncio",
+      "subIncyAnnounceUrlDesc": "Destino del anuncio de la pantalla principal.",
+      "subIncyPremiumUrl": "Enlace Premium",
+      "subIncyPremiumUrlDesc": "Botón Premium en la tarjeta; también manda en el botón del banner.",
+      "subIncyGroupBanners": "Banners",
+      "subIncyBannerText": "Texto del banner",
+      "subIncyBannerTextDesc": "Sustituye el texto del banner. Solo para proveedores premium.",
+      "subIncyBannerButtonText": "Texto del botón del banner",
+      "subIncyBannerButtonTextDesc": "Etiqueta del botón del banner. Vacío lo oculta.",
+      "subIncyBannerButtonUrl": "Enlace del botón del banner",
+      "subIncyBannerButtonUrlDesc": "URL del botón del banner.",
+      "subIncyBannerBgColor": "Fondo del banner",
+      "subIncyBannerBgColorDesc": "Color hex, p. ej. #E53E3E.",
+      "subIncyBannerButtonColor": "Color del botón del banner",
+      "subIncyBannerButtonColorDesc": "Color hex, p. ej. #38A169.",
+      "subIncyGroupPrivacy": "Privacidad",
+      "subIncyHideUrl": "Ocultar URL de la suscripción",
+      "subIncyHideUrlDesc": "Oculta la URL al compartir, copiar, en QR y en copias de seguridad.",
+      "subIncyHideCheck": "Ocultar el botón de comprobación",
+      "subIncyHideCheckDesc": "Quita el botón de comprobación de conexión de la pantalla principal.",
+      "subIncyNoLimit": "Modo No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Modo de bajo consumo de memoria para no pasar del límite de 50 MB en iOS.",
+      "subIncyPerAppEnable": "Modo per-app (Android)",
+      "subIncyPerAppEnableDesc": "Activa el túnel por aplicación. Desactivado anula la elección del usuario.",
+      "subIncyPerAppMode": "Tipo de modo per-app",
+      "subIncyPerAppModeDesc": "proxy: solo las apps listadas usan la VPN; bypass: al contrario.",
+      "subIncyPerAppModeProxy": "Solo las apps listadas",
+      "subIncyPerAppModeBypass": "Las apps listadas se saltan",
+      "subIncyPerAppList": "Lista de apps",
+      "subIncyPerAppListDesc": "Nombres de paquete separados por comas o saltos de línea, o una URL a un archivo.",
+      "subIncyGroupNetwork": "Red",
+      "subIncyFragmentationEnable": "Fragmentación",
+      "subIncyFragmentationEnableDesc": "Activa la fragmentación TCP y anula la configuración global.",
+      "subIncyFragmentLength": "Longitud del fragmento",
+      "subIncyFragmentLengthDesc": "Rango en bytes, p. ej. 10-30.",
+      "subIncyFragmentInterval": "Intervalo de fragmentación",
+      "subIncyFragmentIntervalDesc": "Rango en milisegundos, p. ej. 20-40.",
+      "subIncyFragmentPackets": "Paquetes a fragmentar",
+      "subIncyFragmentPacketsDesc": "Qué paquetes TCP fragmentar: tlshello, 1-3, 1 o all.",
+      "subIncyNoisesEnable": "Paquetes de ruido",
+      "subIncyNoisesEnableDesc": "Envía ruido UDP aleatorio antes del handshake de la VPN.",
+      "subIncyNoisesType": "Tipo de ruido",
+      "subIncyNoisesTypeDesc": "rand, str o hex.",
+      "subIncyNoisesPacket": "Contenido del paquete de ruido",
+      "subIncyNoisesPacketDesc": "Carga útil; con rand, un rango de longitud como 10-20.",
+      "subIncyNoisesDelay": "Retardo del ruido",
+      "subIncyNoisesDelayDesc": "Rango de retardo en milisegundos, p. ej. 10-50.",
+      "subIncyResolveEnable": "Resolver dirección del servidor (DoH)",
+      "subIncyResolveEnableDesc": "Resuelve la dirección del servidor por DoH antes de conectar.",
+      "subIncyResolveDnsDomain": "Endpoint DoH",
+      "subIncyResolveDnsDomainDesc": "URL del servidor DoH, normalmente acaba en /dns-query.",
+      "subIncyResolveDnsIp": "IP del servidor DoH",
+      "subIncyResolveDnsIpDesc": "IP del servidor DoH para el arranque.",
+      "subIncyGroupRouting": "Enrutado"
     },
     "xray": {
       "save": "Guardar configuración",

+ 70 - 1
internal/web/translation/fa-IR.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "هر چند وقت یک‌بار ربات دیسکورد گزارش دوره‌ای بفرستد. یک بازهٔ آماده انتخاب کنید یا گزینهٔ سفارشی را بزنید تا عبارت crontab وارد کنید.",
       "discordNotifyBackup": "پشتیبان‌گیری از دیتابیس",
       "discordNotifyBackupDesc": "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند",
-      "discordEventBusNotifyDesc": "انتخاب کنید کدام رویدادها اعلان دیسکورد را فعال می‌کنند"
+      "discordEventBusNotifyDesc": "انتخاب کنید کدام رویدادها اعلان دیسکورد را فعال می‌کنند",
+      "subIncyAppAutoDetect": "تشخیص خودکار هدرهای Incy",
+      "subIncyAppAutoDetectDesc": "ارسال هدرهای Incy وقتی User-Agent کلاینت INCY باشد.",
+      "subIncyNotSet": "تنظیم نشده",
+      "subIncyOn": "روشن",
+      "subIncyOff": "خاموش",
+      "subIncyGroupApp": "برنامه",
+      "subIncyProfileDescription": "توضیح پروفایل",
+      "subIncyProfileDescriptionDesc": "توضیح جداگانه اشتراک. متن غیر-ASCII بهصورت base64 ارسال میشود.",
+      "subIncySortOrder": "ترتیب سرورها",
+      "subIncySortOrderDesc": "برای همین اشتراک در iOS/Android و بهصورت کلی در دسکتاپ.",
+      "subIncySortNone": "بر اساس ترتیب اشتراک",
+      "subIncySortPing": "بر اساس پینگ",
+      "subIncySortName": "بر اساس نام",
+      "subIncySupportEmail": "ایمیل پشتیبانی",
+      "subIncySupportEmailDesc": "دکمه Email را به کارت اشتراک اضافه میکند.",
+      "subIncyAnnounceUrl": "لینک اعلان",
+      "subIncyAnnounceUrlDesc": "مقصد لینک اعلان در صفحه اصلی.",
+      "subIncyPremiumUrl": "لینک Premium",
+      "subIncyPremiumUrlDesc": "دکمه Premium در کارت؛ برای دکمه بنر هم اولویت دارد.",
+      "subIncyGroupBanners": "بنرها",
+      "subIncyBannerText": "متن بنر",
+      "subIncyBannerTextDesc": "متن بنر را بازنویسی میکند. فقط برای ارائهدهندگان premium.",
+      "subIncyBannerButtonText": "متن دکمه بنر",
+      "subIncyBannerButtonTextDesc": "برچسب دکمه بنر. خالی بودن یعنی دکمه نمایش داده نشود.",
+      "subIncyBannerButtonUrl": "لینک دکمه بنر",
+      "subIncyBannerButtonUrlDesc": "آدرس دکمه بنر.",
+      "subIncyBannerBgColor": "پسزمینه بنر",
+      "subIncyBannerBgColorDesc": "رنگ hex، مثلاً #E53E3E.",
+      "subIncyBannerButtonColor": "رنگ دکمه بنر",
+      "subIncyBannerButtonColorDesc": "رنگ hex، مثلاً #38A169.",
+      "subIncyGroupPrivacy": "حریم خصوصی",
+      "subIncyHideUrl": "پنهان کردن آدرس اشتراک",
+      "subIncyHideUrlDesc": "آدرس را از اشتراکگذاری، کپی، QR و پشتیبانگیری پنهان میکند.",
+      "subIncyHideCheck": "پنهان کردن دکمه بررسی",
+      "subIncyHideCheckDesc": "دکمه بررسی اتصال را از صفحه اصلی حذف میکند.",
+      "subIncyNoLimit": "حالت No-Limit (iOS)",
+      "subIncyNoLimitDesc": "حالت کممصرف تا تونل زیر سقف ۵۰ مگابایتی iOS بماند.",
+      "subIncyPerAppEnable": "حالت per-app (اندروید)",
+      "subIncyPerAppEnableDesc": "تونل تفکیکی بر اساس برنامه را فعال میکند. خاموش، انتخاب کاربر را بازنویسی میکند.",
+      "subIncyPerAppMode": "نوع حالت per-app",
+      "subIncyPerAppModeDesc": "proxy یعنی فقط برنامههای فهرستشده از VPN بروند، bypass برعکس.",
+      "subIncyPerAppModeProxy": "فقط برنامههای فهرستشده",
+      "subIncyPerAppModeBypass": "برنامههای فهرستشده دور میزنند",
+      "subIncyPerAppList": "فهرست برنامهها",
+      "subIncyPerAppListDesc": "نام بستهها با کاما یا خط جدید، یا آدرس یک فایل متنی.",
+      "subIncyGroupNetwork": "شبکه",
+      "subIncyFragmentationEnable": "تکهتکهسازی",
+      "subIncyFragmentationEnableDesc": "تکهتکهسازی TCP را فعال میکند و تنظیم کلی را بازنویسی میکند.",
+      "subIncyFragmentLength": "طول قطعه",
+      "subIncyFragmentLengthDesc": "بازه به بایت، مثلاً 10-30.",
+      "subIncyFragmentInterval": "فاصله قطعهها",
+      "subIncyFragmentIntervalDesc": "بازه به میلیثانیه، مثلاً 20-40.",
+      "subIncyFragmentPackets": "بستههای قطعه",
+      "subIncyFragmentPacketsDesc": "کدام بستههای TCP تکه شوند: tlshello، 1-3، 1 یا all.",
+      "subIncyNoisesEnable": "بستههای نویز",
+      "subIncyNoisesEnableDesc": "پیش از هندشیک VPN نویز تصادفی UDP میفرستد.",
+      "subIncyNoisesType": "نوع نویز",
+      "subIncyNoisesTypeDesc": "rand، str یا hex.",
+      "subIncyNoisesPacket": "محتوای بسته نویز",
+      "subIncyNoisesPacketDesc": "محتوا؛ در حالت rand یک بازه طول، مثلاً 10-20.",
+      "subIncyNoisesDelay": "تأخیر نویز",
+      "subIncyNoisesDelayDesc": "بازه تأخیر به میلیثانیه، مثلاً 10-50.",
+      "subIncyResolveEnable": "حل آدرس سرور (DoH)",
+      "subIncyResolveEnableDesc": "آدرس سرور را پیش از اتصال با DoH حل میکند.",
+      "subIncyResolveDnsDomain": "نقطه پایانی DoH",
+      "subIncyResolveDnsDomainDesc": "آدرس سرور DoH، معمولاً به /dns-query ختم میشود.",
+      "subIncyResolveDnsIp": "آیپی سرور DoH",
+      "subIncyResolveDnsIpDesc": "آیپی سرور DoH برای بوتاسترپ.",
+      "subIncyGroupRouting": "مسیریابی"
     },
     "xray": {
       "save": "ذخیره",

+ 70 - 1
internal/web/translation/id-ID.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Seberapa sering bot Discord mengirim laporan berkala. Pilih interval siap pakai, atau pilih Kustom untuk memasukkan ekspresi crontab.",
       "discordNotifyBackup": "Cadangan Database",
       "discordNotifyBackupDesc": "Kirim berkas cadangan database dengan laporan.",
-      "discordEventBusNotifyDesc": "Pilih peristiwa yang memicu notifikasi Discord"
+      "discordEventBusNotifyDesc": "Pilih peristiwa yang memicu notifikasi Discord",
+      "subIncyAppAutoDetect": "Deteksi otomatis header Incy",
+      "subIncyAppAutoDetectDesc": "Kirim header Incy saat User-Agent klien adalah INCY.",
+      "subIncyNotSet": "Tidak diatur",
+      "subIncyOn": "Aktif",
+      "subIncyOff": "Nonaktif",
+      "subIncyGroupApp": "Aplikasi",
+      "subIncyProfileDescription": "Deskripsi profil",
+      "subIncyProfileDescriptionDesc": "Deskripsi langganan terpisah. Teks non-ASCII dikirim sebagai base64.",
+      "subIncySortOrder": "Urutan server",
+      "subIncySortOrderDesc": "Untuk langganan ini di iOS/Android, global di desktop.",
+      "subIncySortNone": "Urutan langganan",
+      "subIncySortPing": "Menurut ping",
+      "subIncySortName": "Menurut nama",
+      "subIncySupportEmail": "Email dukungan",
+      "subIncySupportEmailDesc": "Menambahkan tombol Email di kartu langganan.",
+      "subIncyAnnounceUrl": "Tautan pengumuman",
+      "subIncyAnnounceUrlDesc": "Tujuan tautan pengumuman di layar utama.",
+      "subIncyPremiumUrl": "Tautan Premium",
+      "subIncyPremiumUrlDesc": "Tombol Premium di kartu; juga diprioritaskan untuk tombol banner.",
+      "subIncyGroupBanners": "Banner",
+      "subIncyBannerText": "Teks banner",
+      "subIncyBannerTextDesc": "Menimpa teks banner. Hanya untuk penyedia premium.",
+      "subIncyBannerButtonText": "Teks tombol banner",
+      "subIncyBannerButtonTextDesc": "Label tombol banner. Kosong menyembunyikan tombol.",
+      "subIncyBannerButtonUrl": "Tautan tombol banner",
+      "subIncyBannerButtonUrlDesc": "URL tombol banner.",
+      "subIncyBannerBgColor": "Latar banner",
+      "subIncyBannerBgColorDesc": "Warna hex, mis. #E53E3E.",
+      "subIncyBannerButtonColor": "Warna tombol banner",
+      "subIncyBannerButtonColorDesc": "Warna hex, mis. #38A169.",
+      "subIncyGroupPrivacy": "Privasi",
+      "subIncyHideUrl": "Sembunyikan URL langganan",
+      "subIncyHideUrlDesc": "Menyembunyikan URL dari Bagikan, Salin, QR, dan cadangan.",
+      "subIncyHideCheck": "Sembunyikan tombol Check",
+      "subIncyHideCheckDesc": "Menghapus tombol pemeriksaan koneksi dari layar utama.",
+      "subIncyNoLimit": "Mode No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Mode hemat memori agar tunnel tetap hidup di bawah batas 50 MB iOS.",
+      "subIncyPerAppEnable": "Mode per-app (Android)",
+      "subIncyPerAppEnableDesc": "Mengaktifkan split tunnel per aplikasi. Nonaktif menimpa pilihan pengguna.",
+      "subIncyPerAppMode": "Jenis mode per-app",
+      "subIncyPerAppModeDesc": "proxy: hanya aplikasi terdaftar lewat VPN; bypass: sebaliknya.",
+      "subIncyPerAppModeProxy": "Hanya aplikasi terdaftar",
+      "subIncyPerAppModeBypass": "Aplikasi terdaftar dilewati",
+      "subIncyPerAppList": "Daftar aplikasi",
+      "subIncyPerAppListDesc": "Nama paket dipisah koma atau baris baru, atau URL ke berkas teks.",
+      "subIncyGroupNetwork": "Jaringan",
+      "subIncyFragmentationEnable": "Fragmentasi",
+      "subIncyFragmentationEnableDesc": "Mengaktifkan fragmentasi TCP dan menimpa pengaturan global.",
+      "subIncyFragmentLength": "Panjang fragmen",
+      "subIncyFragmentLengthDesc": "Rentang dalam byte, mis. 10-30.",
+      "subIncyFragmentInterval": "Interval fragmen",
+      "subIncyFragmentIntervalDesc": "Rentang dalam milidetik, mis. 20-40.",
+      "subIncyFragmentPackets": "Paket fragmen",
+      "subIncyFragmentPacketsDesc": "Paket TCP mana yang difragmentasi: tlshello, 1-3, 1, atau all.",
+      "subIncyNoisesEnable": "Paket noise",
+      "subIncyNoisesEnableDesc": "Mengirim noise UDP acak sebelum handshake VPN.",
+      "subIncyNoisesType": "Jenis noise",
+      "subIncyNoisesTypeDesc": "rand, str, atau hex.",
+      "subIncyNoisesPacket": "Isi paket noise",
+      "subIncyNoisesPacketDesc": "Muatan; untuk rand berupa rentang panjang, mis. 10-20.",
+      "subIncyNoisesDelay": "Jeda noise",
+      "subIncyNoisesDelayDesc": "Rentang jeda dalam milidetik, mis. 10-50.",
+      "subIncyResolveEnable": "Resolusi alamat server (DoH)",
+      "subIncyResolveEnableDesc": "Meresolusi alamat server via DoH sebelum terhubung.",
+      "subIncyResolveDnsDomain": "Endpoint DoH",
+      "subIncyResolveDnsDomainDesc": "URL server DoH, biasanya berakhiran /dns-query.",
+      "subIncyResolveDnsIp": "IP server DoH",
+      "subIncyResolveDnsIpDesc": "IP server DoH untuk bootstrap.",
+      "subIncyGroupRouting": "Perutean"
     },
     "xray": {
       "save": "Simpan",

+ 70 - 1
internal/web/translation/ja-JP.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Discord ボットが定期レポートを送信する頻度です。プリセットの間隔を選ぶか、「カスタム」を選んで crontab 式を入力します。",
       "discordNotifyBackup": "データベースバックアップ",
       "discordNotifyBackupDesc": "レポート付きのデータベースバックアップファイルを送信",
-      "discordEventBusNotifyDesc": "Discord通知をトリガーするイベントを選択してください"
+      "discordEventBusNotifyDesc": "Discord通知をトリガーするイベントを選択してください",
+      "subIncyAppAutoDetect": "Incy ヘッダー自動判別",
+      "subIncyAppAutoDetectDesc": "クライアントの User-Agent が INCY のとき Incy ヘッダーを送信します。",
+      "subIncyNotSet": "未設定",
+      "subIncyOn": "オン",
+      "subIncyOff": "オフ",
+      "subIncyGroupApp": "アプリ",
+      "subIncyProfileDescription": "プロファイルの説明",
+      "subIncyProfileDescriptionDesc": "購読とは別の説明文。非 ASCII は base64 で送信されます。",
+      "subIncySortOrder": "サーバーの並び順",
+      "subIncySortOrderDesc": "iOS/Android ではこの購読のみ、デスクトップでは全体設定です。",
+      "subIncySortNone": "購読順",
+      "subIncySortPing": "ping 順",
+      "subIncySortName": "名前順",
+      "subIncySupportEmail": "サポートメール",
+      "subIncySupportEmailDesc": "購読カードに Email ボタンを追加します。",
+      "subIncyAnnounceUrl": "お知らせリンク",
+      "subIncyAnnounceUrlDesc": "ホーム画面のお知らせの遷移先です。",
+      "subIncyPremiumUrl": "Premium リンク",
+      "subIncyPremiumUrlDesc": "カードの Premium ボタン。バナーのボタンでも優先されます。",
+      "subIncyGroupBanners": "バナー",
+      "subIncyBannerText": "バナーテキスト",
+      "subIncyBannerTextDesc": "バナーテキストを上書きします。Premium プロバイダーのみ。",
+      "subIncyBannerButtonText": "バナーボタンのテキスト",
+      "subIncyBannerButtonTextDesc": "バナーボタンのラベル。空にすると非表示になります。",
+      "subIncyBannerButtonUrl": "バナーボタンのリンク",
+      "subIncyBannerButtonUrlDesc": "バナーボタンの URL。",
+      "subIncyBannerBgColor": "バナーの背景色",
+      "subIncyBannerBgColorDesc": "16 進カラー、例 #E53E3E。",
+      "subIncyBannerButtonColor": "バナーボタンの色",
+      "subIncyBannerButtonColorDesc": "16 進カラー、例 #38A169。",
+      "subIncyGroupPrivacy": "プライバシー",
+      "subIncyHideUrl": "購読 URL を隠す",
+      "subIncyHideUrlDesc": "共有・コピー・QR・バックアップから URL を隠します。",
+      "subIncyHideCheck": "チェックボタンを隠す",
+      "subIncyHideCheckDesc": "ホーム画面から接続チェックボタンを削除します。",
+      "subIncyNoLimit": "No-Limit モード (iOS)",
+      "subIncyNoLimitDesc": "iOS の 50 MB 制限内でトンネルを維持する省メモリモードです。",
+      "subIncyPerAppEnable": "アプリ別モード (Android)",
+      "subIncyPerAppEnableDesc": "アプリ別スプリットトンネルを有効にします。オフはユーザー設定を上書きします。",
+      "subIncyPerAppMode": "アプリ別モードの種類",
+      "subIncyPerAppModeDesc": "proxy は指定アプリのみ VPN、bypass はその逆です。",
+      "subIncyPerAppModeProxy": "指定アプリのみ",
+      "subIncyPerAppModeBypass": "指定アプリを除外",
+      "subIncyPerAppList": "アプリ一覧",
+      "subIncyPerAppListDesc": "パッケージ名をカンマか改行で区切るか、テキストファイルの URL。",
+      "subIncyGroupNetwork": "ネットワーク",
+      "subIncyFragmentationEnable": "フラグメンテーション",
+      "subIncyFragmentationEnableDesc": "TCP フラグメンテーションを有効にし、全体設定を上書きします。",
+      "subIncyFragmentLength": "フラグメント長",
+      "subIncyFragmentLengthDesc": "バイト範囲、例 10-30。",
+      "subIncyFragmentInterval": "フラグメント間隔",
+      "subIncyFragmentIntervalDesc": "ミリ秒範囲、例 20-40。",
+      "subIncyFragmentPackets": "フラグメント対象パケット",
+      "subIncyFragmentPacketsDesc": "分割する TCP パケット: tlshello、1-3、1、all。",
+      "subIncyNoisesEnable": "ノイズパケット",
+      "subIncyNoisesEnableDesc": "VPN ハンドシェイク前にランダムな UDP ノイズを送信します。",
+      "subIncyNoisesType": "ノイズの種類",
+      "subIncyNoisesTypeDesc": "rand、str、hex。",
+      "subIncyNoisesPacket": "ノイズの内容",
+      "subIncyNoisesPacketDesc": "ペイロード。rand の場合は長さ範囲、例 10-20。",
+      "subIncyNoisesDelay": "ノイズ遅延",
+      "subIncyNoisesDelayDesc": "遅延範囲 (ミリ秒)、例 10-50。",
+      "subIncyResolveEnable": "サーバーアドレスの解決 (DoH)",
+      "subIncyResolveEnableDesc": "接続前に DoH でサーバーアドレスを解決します。",
+      "subIncyResolveDnsDomain": "DoH エンドポイント",
+      "subIncyResolveDnsDomainDesc": "DoH サーバー URL。通常は /dns-query で終わります。",
+      "subIncyResolveDnsIp": "DoH サーバー IP",
+      "subIncyResolveDnsIpDesc": "ブートストラップ用の DoH サーバー IP。",
+      "subIncyGroupRouting": "ルーティング"
     },
     "xray": {
       "importRules": "ルールをインポート",

+ 70 - 1
internal/web/translation/pt-BR.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Com que frequência o bot do Discord envia relatórios periódicos. Escolha um intervalo predefinido ou selecione Personalizado para inserir uma expressão crontab.",
       "discordNotifyBackup": "Backup do Banco de Dados",
       "discordNotifyBackupDesc": "Enviar arquivo de backup do banco de dados junto com o relatório.",
-      "discordEventBusNotifyDesc": "Selecione quais eventos disparam notificações no Discord"
+      "discordEventBusNotifyDesc": "Selecione quais eventos disparam notificações no Discord",
+      "subIncyAppAutoDetect": "Detecção automática de cabeçalhos do Incy",
+      "subIncyAppAutoDetectDesc": "Envia os cabeçalhos do Incy quando o User-Agent do cliente é INCY.",
+      "subIncyNotSet": "Não definido",
+      "subIncyOn": "Ativado",
+      "subIncyOff": "Desativado",
+      "subIncyGroupApp": "Aplicativo",
+      "subIncyProfileDescription": "Descrição do perfil",
+      "subIncyProfileDescriptionDesc": "Descrição separada da assinatura. Texto não ASCII vai em base64.",
+      "subIncySortOrder": "Ordem dos servidores",
+      "subIncySortOrderDesc": "Para esta assinatura no iOS/Android, global no desktop.",
+      "subIncySortNone": "Ordem da assinatura",
+      "subIncySortPing": "Por ping",
+      "subIncySortName": "Por nome",
+      "subIncySupportEmail": "Email de suporte",
+      "subIncySupportEmailDesc": "Adiciona um botão Email ao cartão da assinatura.",
+      "subIncyAnnounceUrl": "Link do anúncio",
+      "subIncyAnnounceUrlDesc": "Destino do anúncio na tela inicial.",
+      "subIncyPremiumUrl": "Link Premium",
+      "subIncyPremiumUrlDesc": "Botão Premium no cartão; também tem prioridade no botão do banner.",
+      "subIncyGroupBanners": "Banners",
+      "subIncyBannerText": "Texto do banner",
+      "subIncyBannerTextDesc": "Substitui o texto do banner. Apenas para provedores premium.",
+      "subIncyBannerButtonText": "Texto do botão do banner",
+      "subIncyBannerButtonTextDesc": "Rótulo do botão do banner. Vazio oculta o botão.",
+      "subIncyBannerButtonUrl": "Link do botão do banner",
+      "subIncyBannerButtonUrlDesc": "URL do botão do banner.",
+      "subIncyBannerBgColor": "Fundo do banner",
+      "subIncyBannerBgColorDesc": "Cor hex, ex. #E53E3E.",
+      "subIncyBannerButtonColor": "Cor do botão do banner",
+      "subIncyBannerButtonColorDesc": "Cor hex, ex. #38A169.",
+      "subIncyGroupPrivacy": "Privacidade",
+      "subIncyHideUrl": "Ocultar URL da assinatura",
+      "subIncyHideUrlDesc": "Oculta a URL em Compartilhar, Copiar, QR e backups.",
+      "subIncyHideCheck": "Ocultar o botão Check",
+      "subIncyHideCheckDesc": "Remove o botão de verificação de conexão da tela inicial.",
+      "subIncyNoLimit": "Modo No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Modo econômico para o túnel sobreviver ao limite de 50 MB do iOS.",
+      "subIncyPerAppEnable": "Modo per-app (Android)",
+      "subIncyPerAppEnableDesc": "Ativa o split tunnel por aplicativo. Desativado sobrepõe a escolha do usuário.",
+      "subIncyPerAppMode": "Tipo do modo per-app",
+      "subIncyPerAppModeDesc": "proxy: só os apps listados usam a VPN; bypass: o contrário.",
+      "subIncyPerAppModeProxy": "Somente apps listados",
+      "subIncyPerAppModeBypass": "Apps listados ignoram",
+      "subIncyPerAppList": "Lista de apps",
+      "subIncyPerAppListDesc": "Nomes de pacote separados por vírgula ou linha, ou uma URL de arquivo texto.",
+      "subIncyGroupNetwork": "Rede",
+      "subIncyFragmentationEnable": "Fragmentação",
+      "subIncyFragmentationEnableDesc": "Ativa a fragmentação TCP e sobrepõe a configuração global.",
+      "subIncyFragmentLength": "Tamanho do fragmento",
+      "subIncyFragmentLengthDesc": "Intervalo em bytes, ex. 10-30.",
+      "subIncyFragmentInterval": "Intervalo de fragmentação",
+      "subIncyFragmentIntervalDesc": "Intervalo em milissegundos, ex. 20-40.",
+      "subIncyFragmentPackets": "Pacotes do fragmento",
+      "subIncyFragmentPacketsDesc": "Quais pacotes TCP fragmentar: tlshello, 1-3, 1 ou all.",
+      "subIncyNoisesEnable": "Pacotes de ruído",
+      "subIncyNoisesEnableDesc": "Envia ruído UDP aleatório antes do handshake da VPN.",
+      "subIncyNoisesType": "Tipo de ruído",
+      "subIncyNoisesTypeDesc": "rand, str ou hex.",
+      "subIncyNoisesPacket": "Conteúdo do pacote de ruído",
+      "subIncyNoisesPacketDesc": "Payload; com rand, um intervalo de tamanho como 10-20.",
+      "subIncyNoisesDelay": "Atraso do ruído",
+      "subIncyNoisesDelayDesc": "Intervalo de atraso em milissegundos, ex. 10-50.",
+      "subIncyResolveEnable": "Resolver endereço do servidor (DoH)",
+      "subIncyResolveEnableDesc": "Resolve o endereço do servidor via DoH antes de conectar.",
+      "subIncyResolveDnsDomain": "Endpoint DoH",
+      "subIncyResolveDnsDomainDesc": "URL do servidor DoH, geralmente terminando em /dns-query.",
+      "subIncyResolveDnsIp": "IP do servidor DoH",
+      "subIncyResolveDnsIpDesc": "IP do servidor DoH para bootstrap.",
+      "subIncyGroupRouting": "Roteamento"
     },
     "xray": {
       "importRules": "Importar regras",

+ 70 - 1
internal/web/translation/ru-RU.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Как часто бот Discord отправляет периодические отчёты. Выберите готовый интервал или «Произвольный», чтобы ввести выражение crontab.",
       "discordNotifyBackup": "Резервное копирование базы данных",
       "discordNotifyBackupDesc": "Отправлять уведомление с файлом резервной копии базы данных",
-      "discordEventBusNotifyDesc": "Выберите события для Discord уведомлений"
+      "discordEventBusNotifyDesc": "Выберите события для Discord уведомлений",
+      "subIncyAppAutoDetect": "Автоопределение заголовков Incy",
+      "subIncyAppAutoDetectDesc": "Отправлять заголовки Incy, когда User-Agent клиента — INCY.",
+      "subIncyNotSet": "Не задано",
+      "subIncyOn": "Вкл",
+      "subIncyOff": "Выкл",
+      "subIncyGroupApp": "Приложение",
+      "subIncyProfileDescription": "Описание профиля",
+      "subIncyProfileDescriptionDesc": "Отдельное описание подписки. Не-ASCII текст уходит в base64.",
+      "subIncySortOrder": "Порядок серверов",
+      "subIncySortOrderDesc": "Для этой подписки на iOS/Android, глобально на десктопе.",
+      "subIncySortNone": "Как в подписке",
+      "subIncySortPing": "По пингу",
+      "subIncySortName": "По имени",
+      "subIncySupportEmail": "Email поддержки",
+      "subIncySupportEmailDesc": "Добавляет кнопку «Email» в карточку подписки.",
+      "subIncyAnnounceUrl": "Ссылка объявления",
+      "subIncyAnnounceUrlDesc": "Куда ведёт объявление на главном экране.",
+      "subIncyPremiumUrl": "Ссылка Premium",
+      "subIncyPremiumUrlDesc": "Кнопка Premium в карточке; приоритетна и для кнопки баннера.",
+      "subIncyGroupBanners": "Баннеры",
+      "subIncyBannerText": "Текст баннера",
+      "subIncyBannerTextDesc": "Переопределяет текст баннера. Только для premium-провайдеров.",
+      "subIncyBannerButtonText": "Текст кнопки баннера",
+      "subIncyBannerButtonTextDesc": "Подпись кнопки баннера. Пустое значение скрывает кнопку.",
+      "subIncyBannerButtonUrl": "Ссылка кнопки баннера",
+      "subIncyBannerButtonUrlDesc": "URL кнопки баннера.",
+      "subIncyBannerBgColor": "Фон баннера",
+      "subIncyBannerBgColorDesc": "HEX-цвет, например #E53E3E.",
+      "subIncyBannerButtonColor": "Цвет кнопки баннера",
+      "subIncyBannerButtonColorDesc": "HEX-цвет, например #38A169.",
+      "subIncyGroupPrivacy": "Приватность",
+      "subIncyHideUrl": "Скрыть URL подписки",
+      "subIncyHideUrlDesc": "Скрывает URL из «Поделиться», копирования, QR и бэкапов.",
+      "subIncyHideCheck": "Скрыть кнопку проверки",
+      "subIncyHideCheckDesc": "Убирает кнопку проверки соединения с главного экрана.",
+      "subIncyNoLimit": "Режим No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Экономный по памяти режим, чтобы туннель жил в пределах 50 МБ на iOS.",
+      "subIncyPerAppEnable": "Режим per-app (Android)",
+      "subIncyPerAppEnableDesc": "Включает split-туннель по приложениям. «Выкл» перебивает выбор юзера.",
+      "subIncyPerAppMode": "Логика per-app",
+      "subIncyPerAppModeDesc": "proxy — через VPN только выбранные приложения, bypass — наоборот.",
+      "subIncyPerAppModeProxy": "Только выбранные",
+      "subIncyPerAppModeBypass": "Выбранные в обход",
+      "subIncyPerAppList": "Список приложений",
+      "subIncyPerAppListDesc": "Пакеты через запятую или с новой строки, либо URL на текстовый файл.",
+      "subIncyGroupNetwork": "Сеть",
+      "subIncyFragmentationEnable": "Фрагментация",
+      "subIncyFragmentationEnableDesc": "Включает фрагментацию TCP, перебивая глобальную настройку.",
+      "subIncyFragmentLength": "Длина фрагмента",
+      "subIncyFragmentLengthDesc": "Диапазон в байтах, например 10-30.",
+      "subIncyFragmentInterval": "Интервал фрагментации",
+      "subIncyFragmentIntervalDesc": "Диапазон в миллисекундах, например 20-40.",
+      "subIncyFragmentPackets": "Пакеты фрагментации",
+      "subIncyFragmentPacketsDesc": "Какие TCP-пакеты фрагментировать: tlshello, 1-3, 1 или all.",
+      "subIncyNoisesEnable": "Шумовые пакеты",
+      "subIncyNoisesEnableDesc": "Шлёт случайный UDP-шум перед хендшейком VPN.",
+      "subIncyNoisesType": "Тип шума",
+      "subIncyNoisesTypeDesc": "rand, str или hex.",
+      "subIncyNoisesPacket": "Содержимое шумового пакета",
+      "subIncyNoisesPacketDesc": "Полезная нагрузка; для rand — диапазон длины, например 10-20.",
+      "subIncyNoisesDelay": "Задержка шума",
+      "subIncyNoisesDelayDesc": "Диапазон задержки в миллисекундах, например 10-50.",
+      "subIncyResolveEnable": "Резолв адреса сервера (DoH)",
+      "subIncyResolveEnableDesc": "Резолвит адрес сервера через DoH до подключения.",
+      "subIncyResolveDnsDomain": "DoH-эндпоинт",
+      "subIncyResolveDnsDomainDesc": "URL DoH-сервера, обычно заканчивается на /dns-query.",
+      "subIncyResolveDnsIp": "IP DoH-сервера",
+      "subIncyResolveDnsIpDesc": "IP DoH-сервера, нужен для bootstrap.",
+      "subIncyGroupRouting": "Маршрутизация"
     },
     "xray": {
       "importRules": "Импорт правил",

+ 70 - 1
internal/web/translation/tr-TR.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Discord botunun periyodik raporları gönderme sıklığı. Hazır bir aralık seçin veya bir crontab ifadesi girmek için Özel'i seçin.",
       "discordNotifyBackup": "Veritabanı Yedeği",
       "discordNotifyBackupDesc": "Bir rapor ile birlikte veritabanı yedek dosyasını gönderir.",
-      "discordEventBusNotifyDesc": "Hangi olayların Discord bildirimi tetikleyeceğini seçin"
+      "discordEventBusNotifyDesc": "Hangi olayların Discord bildirimi tetikleyeceğini seçin",
+      "subIncyAppAutoDetect": "Incy başlık otomatik algılama",
+      "subIncyAppAutoDetectDesc": "İstemci User-Agent değeri INCY ise Incy başlıklarını gönderir.",
+      "subIncyNotSet": "Ayarlanmadı",
+      "subIncyOn": "Açık",
+      "subIncyOff": "Kapalı",
+      "subIncyGroupApp": "Uygulama",
+      "subIncyProfileDescription": "Profil açıklaması",
+      "subIncyProfileDescriptionDesc": "Abonelik için ayrı açıklama. ASCII dışı metin base64 olarak gönderilir.",
+      "subIncySortOrder": "Sunucu sıralaması",
+      "subIncySortOrderDesc": "iOS/Android'de bu abonelik için, masaüstünde genel.",
+      "subIncySortNone": "Abonelik sırası",
+      "subIncySortPing": "Ping'e göre",
+      "subIncySortName": "İsme göre",
+      "subIncySupportEmail": "Destek e-postası",
+      "subIncySupportEmailDesc": "Abonelik kartına Email düğmesi ekler.",
+      "subIncyAnnounceUrl": "Duyuru bağlantısı",
+      "subIncyAnnounceUrlDesc": "Ana ekrandaki duyurunun hedef bağlantısı.",
+      "subIncyPremiumUrl": "Premium bağlantısı",
+      "subIncyPremiumUrlDesc": "Karttaki Premium düğmesi; banner düğmesinde de önceliklidir.",
+      "subIncyGroupBanners": "Bannerlar",
+      "subIncyBannerText": "Banner metni",
+      "subIncyBannerTextDesc": "Banner metnini geçersiz kılar. Yalnızca premium sağlayıcılar.",
+      "subIncyBannerButtonText": "Banner düğme metni",
+      "subIncyBannerButtonTextDesc": "Banner düğme etiketi. Boş bırakılırsa düğme gizlenir.",
+      "subIncyBannerButtonUrl": "Banner düğme bağlantısı",
+      "subIncyBannerButtonUrlDesc": "Banner düğmesinin URL'si.",
+      "subIncyBannerBgColor": "Banner arka planı",
+      "subIncyBannerBgColorDesc": "Hex renk, örn. #E53E3E.",
+      "subIncyBannerButtonColor": "Banner düğme rengi",
+      "subIncyBannerButtonColorDesc": "Hex renk, örn. #38A169.",
+      "subIncyGroupPrivacy": "Gizlilik",
+      "subIncyHideUrl": "Abonelik URL'sini gizle",
+      "subIncyHideUrlDesc": "URL'yi paylaşma, kopyalama, QR ve yedeklerden gizler.",
+      "subIncyHideCheck": "Check düğmesini gizle",
+      "subIncyHideCheckDesc": "Ana ekrandan bağlantı kontrolü düğmesini kaldırır.",
+      "subIncyNoLimit": "No-Limit modu (iOS)",
+      "subIncyNoLimitDesc": "Tünelin iOS 50 MB sınırında kalması için az bellek kullanan mod.",
+      "subIncyPerAppEnable": "Per-app modu (Android)",
+      "subIncyPerAppEnableDesc": "Uygulama bazlı split tüneli açar. Kapalı, kullanıcı seçimini geçersiz kılar.",
+      "subIncyPerAppMode": "Per-app mod tipi",
+      "subIncyPerAppModeDesc": "proxy: yalnızca listelenen uygulamalar VPN kullanır; bypass tersi.",
+      "subIncyPerAppModeProxy": "Yalnızca listelenenler",
+      "subIncyPerAppModeBypass": "Listelenenler atlar",
+      "subIncyPerAppList": "Uygulama listesi",
+      "subIncyPerAppListDesc": "Paket adları virgül veya satırla ayrılır, ya da bir metin dosyası URL'si.",
+      "subIncyGroupNetwork": "Ağ",
+      "subIncyFragmentationEnable": "Parçalama",
+      "subIncyFragmentationEnableDesc": "TCP parçalamayı açar ve genel ayarı geçersiz kılar.",
+      "subIncyFragmentLength": "Parça uzunluğu",
+      "subIncyFragmentLengthDesc": "Bayt aralığı, örn. 10-30.",
+      "subIncyFragmentInterval": "Parça aralığı",
+      "subIncyFragmentIntervalDesc": "Milisaniye aralığı, örn. 20-40.",
+      "subIncyFragmentPackets": "Parça paketleri",
+      "subIncyFragmentPacketsDesc": "Hangi TCP paketleri parçalanacak: tlshello, 1-3, 1 veya all.",
+      "subIncyNoisesEnable": "Gürültü paketleri",
+      "subIncyNoisesEnableDesc": "VPN el sıkışmasından önce rastgele UDP gürültüsü gönderir.",
+      "subIncyNoisesType": "Gürültü tipi",
+      "subIncyNoisesTypeDesc": "rand, str veya hex.",
+      "subIncyNoisesPacket": "Gürültü paket içeriği",
+      "subIncyNoisesPacketDesc": "İçerik; rand için uzunluk aralığı, örn. 10-20.",
+      "subIncyNoisesDelay": "Gürültü gecikmesi",
+      "subIncyNoisesDelayDesc": "Milisaniye cinsinden gecikme aralığı, örn. 10-50.",
+      "subIncyResolveEnable": "Sunucu adresini çöz (DoH)",
+      "subIncyResolveEnableDesc": "Bağlanmadan önce sunucu adresini DoH ile çözer.",
+      "subIncyResolveDnsDomain": "DoH uç noktası",
+      "subIncyResolveDnsDomainDesc": "DoH sunucu URL'si, genelde /dns-query ile biter.",
+      "subIncyResolveDnsIp": "DoH sunucu IP'si",
+      "subIncyResolveDnsIpDesc": "Bootstrap için DoH sunucu IP'si.",
+      "subIncyGroupRouting": "Yönlendirme"
     },
     "xray": {
       "save": "Kaydet",

+ 70 - 1
internal/web/translation/uk-UA.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Як часто бот Discord надсилає періодичні звіти. Виберіть готовий інтервал або «Власний», щоб ввести вираз crontab.",
       "discordNotifyBackup": "Резервне копіювання бази даних",
       "discordNotifyBackupDesc": "Надіслати файл резервної копії бази даних зі звітом.",
-      "discordEventBusNotifyDesc": "Виберіть, які події спричиняють сповіщення в Discord"
+      "discordEventBusNotifyDesc": "Виберіть, які події спричиняють сповіщення в Discord",
+      "subIncyAppAutoDetect": "Автовизначення заголовків Incy",
+      "subIncyAppAutoDetectDesc": "Надсилати заголовки Incy, коли User-Agent клієнта — INCY.",
+      "subIncyNotSet": "Не задано",
+      "subIncyOn": "Увімк.",
+      "subIncyOff": "Вимк.",
+      "subIncyGroupApp": "Застосунок",
+      "subIncyProfileDescription": "Опис профілю",
+      "subIncyProfileDescriptionDesc": "Окремий опис підписки. Не-ASCII текст іде в base64.",
+      "subIncySortOrder": "Порядок серверів",
+      "subIncySortOrderDesc": "Для цієї підписки на iOS/Android, глобально на десктопі.",
+      "subIncySortNone": "Як у підписці",
+      "subIncySortPing": "За пінгом",
+      "subIncySortName": "За назвою",
+      "subIncySupportEmail": "Email підтримки",
+      "subIncySupportEmailDesc": "Додає кнопку «Email» у картку підписки.",
+      "subIncyAnnounceUrl": "Посилання оголошення",
+      "subIncyAnnounceUrlDesc": "Куди веде оголошення на головному екрані.",
+      "subIncyPremiumUrl": "Посилання Premium",
+      "subIncyPremiumUrlDesc": "Кнопка Premium у картці; пріоритетна і для кнопки банера.",
+      "subIncyGroupBanners": "Банери",
+      "subIncyBannerText": "Текст банера",
+      "subIncyBannerTextDesc": "Перевизначає текст банера. Лише для premium-провайдерів.",
+      "subIncyBannerButtonText": "Текст кнопки банера",
+      "subIncyBannerButtonTextDesc": "Підпис кнопки банера. Порожнє значення ховає кнопку.",
+      "subIncyBannerButtonUrl": "Посилання кнопки банера",
+      "subIncyBannerButtonUrlDesc": "URL кнопки банера.",
+      "subIncyBannerBgColor": "Фон банера",
+      "subIncyBannerBgColorDesc": "HEX-колір, наприклад #E53E3E.",
+      "subIncyBannerButtonColor": "Колір кнопки банера",
+      "subIncyBannerButtonColorDesc": "HEX-колір, наприклад #38A169.",
+      "subIncyGroupPrivacy": "Приватність",
+      "subIncyHideUrl": "Сховати URL підписки",
+      "subIncyHideUrlDesc": "Ховає URL з «Поділитися», копіювання, QR і бекапів.",
+      "subIncyHideCheck": "Сховати кнопку перевірки",
+      "subIncyHideCheckDesc": "Прибирає кнопку перевірки з'єднання з головного екрана.",
+      "subIncyNoLimit": "Режим No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Ощадний до пам'яті режим, щоб тунель жив у межах 50 МБ на iOS.",
+      "subIncyPerAppEnable": "Режим per-app (Android)",
+      "subIncyPerAppEnableDesc": "Вмикає split-тунель за застосунками. «Вимк.» перебиває вибір користувача.",
+      "subIncyPerAppMode": "Логіка per-app",
+      "subIncyPerAppModeDesc": "proxy — через VPN лише вибрані застосунки, bypass — навпаки.",
+      "subIncyPerAppModeProxy": "Лише вибрані",
+      "subIncyPerAppModeBypass": "Вибрані в обхід",
+      "subIncyPerAppList": "Список застосунків",
+      "subIncyPerAppListDesc": "Пакети через кому або з нового рядка, або URL на текстовий файл.",
+      "subIncyGroupNetwork": "Мережа",
+      "subIncyFragmentationEnable": "Фрагментація",
+      "subIncyFragmentationEnableDesc": "Вмикає фрагментацію TCP, перебиваючи глобальне налаштування.",
+      "subIncyFragmentLength": "Довжина фрагмента",
+      "subIncyFragmentLengthDesc": "Діапазон у байтах, наприклад 10-30.",
+      "subIncyFragmentInterval": "Інтервал фрагментації",
+      "subIncyFragmentIntervalDesc": "Діапазон у мілісекундах, наприклад 20-40.",
+      "subIncyFragmentPackets": "Пакети фрагментації",
+      "subIncyFragmentPacketsDesc": "Які TCP-пакети фрагментувати: tlshello, 1-3, 1 або all.",
+      "subIncyNoisesEnable": "Шумові пакети",
+      "subIncyNoisesEnableDesc": "Надсилає випадковий UDP-шум перед хендшейком VPN.",
+      "subIncyNoisesType": "Тип шуму",
+      "subIncyNoisesTypeDesc": "rand, str або hex.",
+      "subIncyNoisesPacket": "Вміст шумового пакета",
+      "subIncyNoisesPacketDesc": "Корисне навантаження; для rand — діапазон довжини, наприклад 10-20.",
+      "subIncyNoisesDelay": "Затримка шуму",
+      "subIncyNoisesDelayDesc": "Діапазон затримки в мілісекундах, наприклад 10-50.",
+      "subIncyResolveEnable": "Резолв адреси сервера (DoH)",
+      "subIncyResolveEnableDesc": "Резолвить адресу сервера через DoH до підключення.",
+      "subIncyResolveDnsDomain": "DoH-ендпоінт",
+      "subIncyResolveDnsDomainDesc": "URL DoH-сервера, зазвичай закінчується на /dns-query.",
+      "subIncyResolveDnsIp": "IP DoH-сервера",
+      "subIncyResolveDnsIpDesc": "IP DoH-сервера для bootstrap.",
+      "subIncyGroupRouting": "Маршрутизація"
     },
     "xray": {
       "save": "Зберегти",

+ 70 - 1
internal/web/translation/vi-VN.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Tần suất bot Discord gửi báo cáo định kỳ. Chọn một khoảng thời gian có sẵn, hoặc chọn Tùy chỉnh để nhập biểu thức crontab.",
       "discordNotifyBackup": "Sao lưu Cơ sở dữ liệu",
       "discordNotifyBackupDesc": "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo.",
-      "discordEventBusNotifyDesc": "Chọn những sự kiện nào sẽ kích hoạt thông báo qua Discord"
+      "discordEventBusNotifyDesc": "Chọn những sự kiện nào sẽ kích hoạt thông báo qua Discord",
+      "subIncyAppAutoDetect": "Tự nhận diện header Incy",
+      "subIncyAppAutoDetectDesc": "Gửi header Incy khi User-Agent của client là INCY.",
+      "subIncyNotSet": "Chưa đặt",
+      "subIncyOn": "Bật",
+      "subIncyOff": "Tắt",
+      "subIncyGroupApp": "Ứng dụng",
+      "subIncyProfileDescription": "Mô tả hồ sơ",
+      "subIncyProfileDescriptionDesc": "Mô tả riêng cho gói đăng ký. Văn bản không phải ASCII gửi dạng base64.",
+      "subIncySortOrder": "Thứ tự máy chủ",
+      "subIncySortOrderDesc": "Cho gói này trên iOS/Android, toàn cục trên desktop.",
+      "subIncySortNone": "Theo thứ tự gói",
+      "subIncySortPing": "Theo ping",
+      "subIncySortName": "Theo tên",
+      "subIncySupportEmail": "Email hỗ trợ",
+      "subIncySupportEmailDesc": "Thêm nút Email vào thẻ đăng ký.",
+      "subIncyAnnounceUrl": "Link thông báo",
+      "subIncyAnnounceUrlDesc": "Đích của thông báo trên màn hình chính.",
+      "subIncyPremiumUrl": "Link Premium",
+      "subIncyPremiumUrlDesc": "Nút Premium trong thẻ; cũng ưu tiên cho nút banner.",
+      "subIncyGroupBanners": "Banner",
+      "subIncyBannerText": "Chữ trên banner",
+      "subIncyBannerTextDesc": "Ghi đè chữ banner. Chỉ nhà cung cấp premium.",
+      "subIncyBannerButtonText": "Chữ nút banner",
+      "subIncyBannerButtonTextDesc": "Nhãn nút banner. Để trống sẽ ẩn nút.",
+      "subIncyBannerButtonUrl": "Link nút banner",
+      "subIncyBannerButtonUrlDesc": "URL của nút banner.",
+      "subIncyBannerBgColor": "Nền banner",
+      "subIncyBannerBgColorDesc": "Màu hex, ví dụ #E53E3E.",
+      "subIncyBannerButtonColor": "Màu nút banner",
+      "subIncyBannerButtonColorDesc": "Màu hex, ví dụ #38A169.",
+      "subIncyGroupPrivacy": "Riêng tư",
+      "subIncyHideUrl": "Ẩn URL đăng ký",
+      "subIncyHideUrlDesc": "Ẩn URL khỏi chia sẻ, sao chép, QR và bản sao lưu.",
+      "subIncyHideCheck": "Ẩn nút kiểm tra",
+      "subIncyHideCheckDesc": "Bỏ nút kiểm tra kết nối khỏi màn hình chính.",
+      "subIncyNoLimit": "Chế độ No-Limit (iOS)",
+      "subIncyNoLimitDesc": "Chế độ tiết kiệm bộ nhớ để tunnel sống trong giới hạn 50 MB của iOS.",
+      "subIncyPerAppEnable": "Chế độ per-app (Android)",
+      "subIncyPerAppEnableDesc": "Bật split tunnel theo ứng dụng. Tắt sẽ ghi đè lựa chọn của người dùng.",
+      "subIncyPerAppMode": "Kiểu chế độ per-app",
+      "subIncyPerAppModeDesc": "proxy: chỉ ứng dụng trong danh sách đi qua VPN; bypass thì ngược lại.",
+      "subIncyPerAppModeProxy": "Chỉ ứng dụng trong danh sách",
+      "subIncyPerAppModeBypass": "Ứng dụng trong danh sách đi thẳng",
+      "subIncyPerAppList": "Danh sách ứng dụng",
+      "subIncyPerAppListDesc": "Tên gói cách nhau bằng dấu phẩy hoặc xuống dòng, hoặc URL tới file văn bản.",
+      "subIncyGroupNetwork": "Mạng",
+      "subIncyFragmentationEnable": "Phân mảnh",
+      "subIncyFragmentationEnableDesc": "Bật phân mảnh TCP và ghi đè cấu hình chung.",
+      "subIncyFragmentLength": "Độ dài mảnh",
+      "subIncyFragmentLengthDesc": "Khoảng theo byte, ví dụ 10-30.",
+      "subIncyFragmentInterval": "Khoảng cách mảnh",
+      "subIncyFragmentIntervalDesc": "Khoảng theo mili giây, ví dụ 20-40.",
+      "subIncyFragmentPackets": "Gói phân mảnh",
+      "subIncyFragmentPacketsDesc": "Phân mảnh gói TCP nào: tlshello, 1-3, 1 hoặc all.",
+      "subIncyNoisesEnable": "Gói nhiễu",
+      "subIncyNoisesEnableDesc": "Gửi nhiễu UDP ngẫu nhiên trước khi bắt tay VPN.",
+      "subIncyNoisesType": "Kiểu nhiễu",
+      "subIncyNoisesTypeDesc": "rand, str hoặc hex.",
+      "subIncyNoisesPacket": "Nội dung gói nhiễu",
+      "subIncyNoisesPacketDesc": "Nội dung; với rand là khoảng độ dài, ví dụ 10-20.",
+      "subIncyNoisesDelay": "Độ trễ nhiễu",
+      "subIncyNoisesDelayDesc": "Khoảng trễ theo mili giây, ví dụ 10-50.",
+      "subIncyResolveEnable": "Phân giải địa chỉ máy chủ (DoH)",
+      "subIncyResolveEnableDesc": "Phân giải địa chỉ máy chủ qua DoH trước khi kết nối.",
+      "subIncyResolveDnsDomain": "Endpoint DoH",
+      "subIncyResolveDnsDomainDesc": "URL máy chủ DoH, thường kết thúc bằng /dns-query.",
+      "subIncyResolveDnsIp": "IP máy chủ DoH",
+      "subIncyResolveDnsIpDesc": "IP máy chủ DoH để khởi tạo.",
+      "subIncyGroupRouting": "Định tuyến"
     },
     "xray": {
       "importRules": "Nhập quy tắc",

+ 70 - 1
internal/web/translation/zh-CN.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Discord 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。",
       "discordNotifyBackup": "数据库备份",
       "discordNotifyBackupDesc": "发送带有报告的数据库备份文件",
-      "discordEventBusNotifyDesc": "选择触发 Discord 通知的事件"
+      "discordEventBusNotifyDesc": "选择触发 Discord 通知的事件",
+      "subIncyAppAutoDetect": "Incy 请求头自动识别",
+      "subIncyAppAutoDetectDesc": "当客户端 User-Agent 为 INCY 时发送 Incy 请求头。",
+      "subIncyNotSet": "未设置",
+      "subIncyOn": "开",
+      "subIncyOff": "关",
+      "subIncyGroupApp": "应用",
+      "subIncyProfileDescription": "订阅描述",
+      "subIncyProfileDescriptionDesc": "单独的订阅描述。非 ASCII 文本以 base64 发送。",
+      "subIncySortOrder": "服务器排序",
+      "subIncySortOrderDesc": "iOS/Android 只影响本订阅,桌面端为全局设置。",
+      "subIncySortNone": "按订阅顺序",
+      "subIncySortPing": "按延迟",
+      "subIncySortName": "按名称",
+      "subIncySupportEmail": "客服邮箱",
+      "subIncySupportEmailDesc": "在订阅卡片中显示“Email”按钮。",
+      "subIncyAnnounceUrl": "公告链接",
+      "subIncyAnnounceUrlDesc": "首页公告的跳转链接。",
+      "subIncyPremiumUrl": "Premium 链接",
+      "subIncyPremiumUrlDesc": "订阅卡片中的 Premium 按钮,横幅按钮也优先使用它。",
+      "subIncyGroupBanners": "横幅",
+      "subIncyBannerText": "横幅文字",
+      "subIncyBannerTextDesc": "覆盖横幅文字。仅 Premium 服务商可用。",
+      "subIncyBannerButtonText": "横幅按钮文字",
+      "subIncyBannerButtonTextDesc": "横幅按钮标题。留空则不显示按钮。",
+      "subIncyBannerButtonUrl": "横幅按钮链接",
+      "subIncyBannerButtonUrlDesc": "横幅按钮的 URL。",
+      "subIncyBannerBgColor": "横幅背景色",
+      "subIncyBannerBgColorDesc": "十六进制颜色,例如 #E53E3E。",
+      "subIncyBannerButtonColor": "横幅按钮颜色",
+      "subIncyBannerButtonColorDesc": "十六进制颜色,例如 #38A169。",
+      "subIncyGroupPrivacy": "隐私",
+      "subIncyHideUrl": "隐藏订阅链接",
+      "subIncyHideUrlDesc": "在分享、复制、二维码和备份中隐藏链接。",
+      "subIncyHideCheck": "隐藏检查按钮",
+      "subIncyHideCheckDesc": "从首页移除连接检查按钮。",
+      "subIncyNoLimit": "No-Limit 模式 (iOS)",
+      "subIncyNoLimitDesc": "省内存模式,让隧道在 iOS 50 MB 限制内保持存活。",
+      "subIncyPerAppEnable": "分应用模式 (Android)",
+      "subIncyPerAppEnableDesc": "启用分应用分流。关闭会覆盖用户自己的选择。",
+      "subIncyPerAppMode": "分应用模式类型",
+      "subIncyPerAppModeDesc": "proxy 表示仅列出的应用走 VPN,bypass 则相反。",
+      "subIncyPerAppModeProxy": "仅列出的应用",
+      "subIncyPerAppModeBypass": "列出的应用绕过",
+      "subIncyPerAppList": "应用列表",
+      "subIncyPerAppListDesc": "包名用逗号或换行分隔,也可以填文本文件的 URL。",
+      "subIncyGroupNetwork": "网络",
+      "subIncyFragmentationEnable": "分片",
+      "subIncyFragmentationEnableDesc": "启用 TCP 分片,覆盖全局设置。",
+      "subIncyFragmentLength": "分片长度",
+      "subIncyFragmentLengthDesc": "字节范围,例如 10-30。",
+      "subIncyFragmentInterval": "分片间隔",
+      "subIncyFragmentIntervalDesc": "毫秒范围,例如 20-40。",
+      "subIncyFragmentPackets": "分片数据包",
+      "subIncyFragmentPacketsDesc": "对哪些 TCP 包分片:tlshello、1-3、1 或 all。",
+      "subIncyNoisesEnable": "噪声数据包",
+      "subIncyNoisesEnableDesc": "握手前发送随机 UDP 噪声包。",
+      "subIncyNoisesType": "噪声类型",
+      "subIncyNoisesTypeDesc": "rand、str 或 hex。",
+      "subIncyNoisesPacket": "噪声内容",
+      "subIncyNoisesPacketDesc": "载荷内容;rand 时填长度范围,例如 10-20。",
+      "subIncyNoisesDelay": "噪声延迟",
+      "subIncyNoisesDelayDesc": "毫秒延迟范围,例如 10-50。",
+      "subIncyResolveEnable": "预解析服务器地址 (DoH)",
+      "subIncyResolveEnableDesc": "连接前通过 DoH 预解析服务器地址。",
+      "subIncyResolveDnsDomain": "DoH 端点",
+      "subIncyResolveDnsDomainDesc": "DoH 服务器 URL,通常以 /dns-query 结尾。",
+      "subIncyResolveDnsIp": "DoH 服务器 IP",
+      "subIncyResolveDnsIpDesc": "用于引导的 DoH 服务器 IP。",
+      "subIncyGroupRouting": "路由"
     },
     "xray": {
       "importRules": "导入规则",

+ 70 - 1
internal/web/translation/zh-TW.json

@@ -1670,7 +1670,76 @@
       "discordNotifyTimeDesc": "Discord 機器人傳送週期性報告的頻率。選擇預設間隔,或選擇「自訂」以輸入 crontab 運算式。",
       "discordNotifyBackup": "資料庫備份",
       "discordNotifyBackupDesc": "傳送帶有報告的資料庫備份檔案",
-      "discordEventBusNotifyDesc": "選擇觸發 Discord 通知的事件"
+      "discordEventBusNotifyDesc": "選擇觸發 Discord 通知的事件",
+      "subIncyAppAutoDetect": "Incy 標頭自動辨識",
+      "subIncyAppAutoDetectDesc": "用戶端 User-Agent 為 INCY 時送出 Incy 標頭。",
+      "subIncyNotSet": "未設定",
+      "subIncyOn": "開",
+      "subIncyOff": "關",
+      "subIncyGroupApp": "應用程式",
+      "subIncyProfileDescription": "訂閱描述",
+      "subIncyProfileDescriptionDesc": "獨立的訂閱描述。非 ASCII 文字以 base64 傳送。",
+      "subIncySortOrder": "伺服器排序",
+      "subIncySortOrderDesc": "iOS/Android 只影響本訂閱,桌面版為全域設定。",
+      "subIncySortNone": "依訂閱順序",
+      "subIncySortPing": "依延遲",
+      "subIncySortName": "依名稱",
+      "subIncySupportEmail": "客服信箱",
+      "subIncySupportEmailDesc": "在訂閱卡片顯示「Email」按鈕。",
+      "subIncyAnnounceUrl": "公告連結",
+      "subIncyAnnounceUrlDesc": "首頁公告的導向連結。",
+      "subIncyPremiumUrl": "Premium 連結",
+      "subIncyPremiumUrlDesc": "訂閱卡片中的 Premium 按鈕,橫幅按鈕也優先使用。",
+      "subIncyGroupBanners": "橫幅",
+      "subIncyBannerText": "橫幅文字",
+      "subIncyBannerTextDesc": "覆寫橫幅文字。僅限 Premium 供應商。",
+      "subIncyBannerButtonText": "橫幅按鈕文字",
+      "subIncyBannerButtonTextDesc": "橫幅按鈕標題。留空則不顯示按鈕。",
+      "subIncyBannerButtonUrl": "橫幅按鈕連結",
+      "subIncyBannerButtonUrlDesc": "橫幅按鈕的 URL。",
+      "subIncyBannerBgColor": "橫幅背景色",
+      "subIncyBannerBgColorDesc": "十六進位色碼,例如 #E53E3E。",
+      "subIncyBannerButtonColor": "橫幅按鈕顏色",
+      "subIncyBannerButtonColorDesc": "十六進位色碼,例如 #38A169。",
+      "subIncyGroupPrivacy": "隱私",
+      "subIncyHideUrl": "隱藏訂閱網址",
+      "subIncyHideUrlDesc": "在分享、複製、QR 與備份中隱藏網址。",
+      "subIncyHideCheck": "隱藏檢查按鈕",
+      "subIncyHideCheckDesc": "從首頁移除連線檢查按鈕。",
+      "subIncyNoLimit": "No-Limit 模式 (iOS)",
+      "subIncyNoLimitDesc": "省記憶體模式,讓通道在 iOS 50 MB 限制內存活。",
+      "subIncyPerAppEnable": "分應用程式模式 (Android)",
+      "subIncyPerAppEnableDesc": "啟用分應用程式分流。關閉會覆寫使用者自己的選擇。",
+      "subIncyPerAppMode": "分應用程式模式類型",
+      "subIncyPerAppModeDesc": "proxy 表示只有列出的應用走 VPN,bypass 則相反。",
+      "subIncyPerAppModeProxy": "僅列出的應用",
+      "subIncyPerAppModeBypass": "列出的應用繞過",
+      "subIncyPerAppList": "應用程式清單",
+      "subIncyPerAppListDesc": "套件名稱以逗號或換行分隔,也可填文字檔的 URL。",
+      "subIncyGroupNetwork": "網路",
+      "subIncyFragmentationEnable": "分段",
+      "subIncyFragmentationEnableDesc": "啟用 TCP 分段,覆寫全域設定。",
+      "subIncyFragmentLength": "分段長度",
+      "subIncyFragmentLengthDesc": "位元組範圍,例如 10-30。",
+      "subIncyFragmentInterval": "分段間隔",
+      "subIncyFragmentIntervalDesc": "毫秒範圍,例如 20-40。",
+      "subIncyFragmentPackets": "分段封包",
+      "subIncyFragmentPacketsDesc": "要分段哪些 TCP 封包:tlshello、1-3、1 或 all。",
+      "subIncyNoisesEnable": "雜訊封包",
+      "subIncyNoisesEnableDesc": "握手前送出隨機 UDP 雜訊。",
+      "subIncyNoisesType": "雜訊類型",
+      "subIncyNoisesTypeDesc": "rand、str 或 hex。",
+      "subIncyNoisesPacket": "雜訊內容",
+      "subIncyNoisesPacketDesc": "酬載內容;rand 時填長度範圍,例如 10-20。",
+      "subIncyNoisesDelay": "雜訊延遲",
+      "subIncyNoisesDelayDesc": "毫秒延遲範圍,例如 10-50。",
+      "subIncyResolveEnable": "預先解析伺服器位址 (DoH)",
+      "subIncyResolveEnableDesc": "連線前透過 DoH 預先解析伺服器位址。",
+      "subIncyResolveDnsDomain": "DoH 端點",
+      "subIncyResolveDnsDomainDesc": "DoH 伺服器 URL,通常以 /dns-query 結尾。",
+      "subIncyResolveDnsIp": "DoH 伺服器 IP",
+      "subIncyResolveDnsIpDesc": "用於引導的 DoH 伺服器 IP。",
+      "subIncyGroupRouting": "路由"
     },
     "xray": {
       "save": "儲存",