mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-11 04:37:16 +00:00
feat(sub): add Happ client integration, routing presets, and app management (#6434)
* feat(sub): add Happ client integration, routing presets, and app management Implement comprehensive Happ proxy client integration according to official developer specifications. - Fix header emission on disabled routing and hidden settings to send explicit '0' headers rather than omitting, allowing Happ clients to reset cached settings. - Add support for 'happ://routing/off' deeplink in routing validation. - Preserve '?serverDescription=' query parameters in link fragments without escaping to support Happ server subtitles across VMess, VLESS, Trojan and SS. - Add Happ application management headers: ProviderID, New-Url, Fallback-Url, Sub-Info banners, Sub-Expire notifications, No-Limit mode, hardware ID enforcement, TUN modes/types, route exclusions, APNS exclusions, and per-app proxy settings. - Add curated routing presets (Iran Bypass, China Direct, AdBlock, Global) and interactive visual rule generator in frontend settings. - Synchronize all 13 translation locales with native Persian, Russian, and Chinese translations. * fix(sub): keep Happ header overrides behind the auto-detect opt-in The Routing-Enable/Hide-Settings off values were emitted on the User-Agent alone, so every panel that upgraded would push "Routing-Enable: 0" — documented by happ.su as disabling routing globally — to every Happ client without the operator enabling anything. They now ride subHappAutoDetect like every other Happ header. Two further mismatches against the vendor spec: - serverDescription was written as a key of the VMess base64 JSON object. happ.su documents it as a "#Title?serverDescription=<base64>" link parameter or a JSON "meta" entry, so the caption never reached Happ while every other VMess consumer received an unknown key. Dropped rather than moved: emitting the documented form is unsafe here because our own parser base64-decodes the whole VMess body (internal/util/link/outbound.go). - The TUN Mode dropdown stored the literal "default", forwarded as "Tun-Mode: default", where happ.su documents system|gvisor only. It now stores the unset value so no header is sent. TUN Type "default" is a documented value and is unchanged. Each fix carries a test that fails without it.
This commit is contained in:
@@ -220,6 +220,76 @@
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -443,6 +513,29 @@
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
@@ -711,6 +804,76 @@
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -941,6 +1104,29 @@
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
|
||||
@@ -220,6 +220,76 @@
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -443,6 +513,29 @@
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
@@ -711,6 +804,76 @@
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -941,6 +1104,29 @@
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
|
||||
@@ -59,6 +59,29 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subEnableRouting": false,
|
||||
"subEncrypt": false,
|
||||
"subExpiredTemplate": "",
|
||||
"subHappAlwaysHwid": false,
|
||||
"subHappAutoConnect": false,
|
||||
"subHappAutoConnectType": "",
|
||||
"subHappAutoDetect": false,
|
||||
"subHappColorProfile": "",
|
||||
"subHappExcludeApns": false,
|
||||
"subHappExcludeRoutes": "",
|
||||
"subHappFallbackUrl": "",
|
||||
"subHappNewUrl": "",
|
||||
"subHappNoLimit": false,
|
||||
"subHappNotificationExpire": false,
|
||||
"subHappPerAppList": "",
|
||||
"subHappPerAppMode": "",
|
||||
"subHappPingType": "",
|
||||
"subHappProviderId": "",
|
||||
"subHappSubExpire": false,
|
||||
"subHappSubExpireButtonLink": "",
|
||||
"subHappSubInfoButtonLink": "",
|
||||
"subHappSubInfoButtonText": "",
|
||||
"subHappSubInfoColor": "",
|
||||
"subHappSubInfoText": "",
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
@@ -176,6 +199,29 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"subEnableRouting": false,
|
||||
"subEncrypt": false,
|
||||
"subExpiredTemplate": "",
|
||||
"subHappAlwaysHwid": false,
|
||||
"subHappAutoConnect": false,
|
||||
"subHappAutoConnectType": "",
|
||||
"subHappAutoDetect": false,
|
||||
"subHappColorProfile": "",
|
||||
"subHappExcludeApns": false,
|
||||
"subHappExcludeRoutes": "",
|
||||
"subHappFallbackUrl": "",
|
||||
"subHappNewUrl": "",
|
||||
"subHappNoLimit": false,
|
||||
"subHappNotificationExpire": false,
|
||||
"subHappPerAppList": "",
|
||||
"subHappPerAppMode": "",
|
||||
"subHappPingType": "",
|
||||
"subHappProviderId": "",
|
||||
"subHappSubExpire": false,
|
||||
"subHappSubExpireButtonLink": "",
|
||||
"subHappSubInfoButtonLink": "",
|
||||
"subHappSubInfoButtonText": "",
|
||||
"subHappSubInfoColor": "",
|
||||
"subHappSubInfoText": "",
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHideSettings": false,
|
||||
"subIncyEnableRouting": false,
|
||||
"subIncyRoutingRules": "",
|
||||
|
||||
@@ -194,6 +194,76 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -417,6 +487,29 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
@@ -685,6 +778,76 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subExpiredTemplate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAlwaysHwid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnect": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappAutoConnectType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappAutoDetect": {
|
||||
"description": "Happ client customization settings (app-management / routing / UX).",
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappColorProfile": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappExcludeApns": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappExcludeRoutes": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappFallbackUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNewUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappNoLimit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappNotificationExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappPerAppList": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPerAppMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappPingType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappProviderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubExpire": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"subHappSubExpireButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonLink": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoButtonText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappSubInfoText": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHappTunType": {
|
||||
"type": "string"
|
||||
},
|
||||
"subHideSettings": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -915,6 +1078,29 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"subEnableRouting",
|
||||
"subEncrypt",
|
||||
"subExpiredTemplate",
|
||||
"subHappAlwaysHwid",
|
||||
"subHappAutoConnect",
|
||||
"subHappAutoConnectType",
|
||||
"subHappAutoDetect",
|
||||
"subHappColorProfile",
|
||||
"subHappExcludeApns",
|
||||
"subHappExcludeRoutes",
|
||||
"subHappFallbackUrl",
|
||||
"subHappNewUrl",
|
||||
"subHappNoLimit",
|
||||
"subHappNotificationExpire",
|
||||
"subHappPerAppList",
|
||||
"subHappPerAppMode",
|
||||
"subHappPingType",
|
||||
"subHappProviderId",
|
||||
"subHappSubExpire",
|
||||
"subHappSubExpireButtonLink",
|
||||
"subHappSubInfoButtonLink",
|
||||
"subHappSubInfoButtonText",
|
||||
"subHappSubInfoColor",
|
||||
"subHappSubInfoText",
|
||||
"subHappTunMode",
|
||||
"subHappTunType",
|
||||
"subHideSettings",
|
||||
"subIncyEnableRouting",
|
||||
"subIncyRoutingRules",
|
||||
|
||||
@@ -67,6 +67,29 @@ export interface AllSetting {
|
||||
subEnableRouting: boolean;
|
||||
subEncrypt: boolean;
|
||||
subExpiredTemplate: string;
|
||||
subHappAlwaysHwid: boolean;
|
||||
subHappAutoConnect: boolean;
|
||||
subHappAutoConnectType: string;
|
||||
subHappAutoDetect: boolean;
|
||||
subHappColorProfile: string;
|
||||
subHappExcludeApns: boolean;
|
||||
subHappExcludeRoutes: string;
|
||||
subHappFallbackUrl: string;
|
||||
subHappNewUrl: string;
|
||||
subHappNoLimit: boolean;
|
||||
subHappNotificationExpire: boolean;
|
||||
subHappPerAppList: string;
|
||||
subHappPerAppMode: string;
|
||||
subHappPingType: string;
|
||||
subHappProviderId: string;
|
||||
subHappSubExpire: boolean;
|
||||
subHappSubExpireButtonLink: string;
|
||||
subHappSubInfoButtonLink: string;
|
||||
subHappSubInfoButtonText: string;
|
||||
subHappSubInfoColor: string;
|
||||
subHappSubInfoText: string;
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
@@ -185,6 +208,29 @@ export interface AllSettingView {
|
||||
subEnableRouting: boolean;
|
||||
subEncrypt: boolean;
|
||||
subExpiredTemplate: string;
|
||||
subHappAlwaysHwid: boolean;
|
||||
subHappAutoConnect: boolean;
|
||||
subHappAutoConnectType: string;
|
||||
subHappAutoDetect: boolean;
|
||||
subHappColorProfile: string;
|
||||
subHappExcludeApns: boolean;
|
||||
subHappExcludeRoutes: string;
|
||||
subHappFallbackUrl: string;
|
||||
subHappNewUrl: string;
|
||||
subHappNoLimit: boolean;
|
||||
subHappNotificationExpire: boolean;
|
||||
subHappPerAppList: string;
|
||||
subHappPerAppMode: string;
|
||||
subHappPingType: string;
|
||||
subHappProviderId: string;
|
||||
subHappSubExpire: boolean;
|
||||
subHappSubExpireButtonLink: string;
|
||||
subHappSubInfoButtonLink: string;
|
||||
subHappSubInfoButtonText: string;
|
||||
subHappSubInfoColor: string;
|
||||
subHappSubInfoText: string;
|
||||
subHappTunMode: string;
|
||||
subHappTunType: string;
|
||||
subHideSettings: boolean;
|
||||
subIncyEnableRouting: boolean;
|
||||
subIncyRoutingRules: string;
|
||||
|
||||
@@ -83,6 +83,29 @@ export const AllSettingSchema = z.object({
|
||||
subEnableRouting: z.boolean(),
|
||||
subEncrypt: z.boolean(),
|
||||
subExpiredTemplate: z.string(),
|
||||
subHappAlwaysHwid: z.boolean(),
|
||||
subHappAutoConnect: z.boolean(),
|
||||
subHappAutoConnectType: z.string(),
|
||||
subHappAutoDetect: z.boolean(),
|
||||
subHappColorProfile: z.string(),
|
||||
subHappExcludeApns: z.boolean(),
|
||||
subHappExcludeRoutes: z.string(),
|
||||
subHappFallbackUrl: z.string(),
|
||||
subHappNewUrl: z.string(),
|
||||
subHappNoLimit: z.boolean(),
|
||||
subHappNotificationExpire: z.boolean(),
|
||||
subHappPerAppList: z.string(),
|
||||
subHappPerAppMode: z.string(),
|
||||
subHappPingType: z.string(),
|
||||
subHappProviderId: z.string(),
|
||||
subHappSubExpire: z.boolean(),
|
||||
subHappSubExpireButtonLink: z.string(),
|
||||
subHappSubInfoButtonLink: z.string(),
|
||||
subHappSubInfoButtonText: z.string(),
|
||||
subHappSubInfoColor: z.string(),
|
||||
subHappSubInfoText: z.string(),
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
@@ -202,6 +225,29 @@ export const AllSettingViewSchema = z.object({
|
||||
subEnableRouting: z.boolean(),
|
||||
subEncrypt: z.boolean(),
|
||||
subExpiredTemplate: z.string(),
|
||||
subHappAlwaysHwid: z.boolean(),
|
||||
subHappAutoConnect: z.boolean(),
|
||||
subHappAutoConnectType: z.string(),
|
||||
subHappAutoDetect: z.boolean(),
|
||||
subHappColorProfile: z.string(),
|
||||
subHappExcludeApns: z.boolean(),
|
||||
subHappExcludeRoutes: z.string(),
|
||||
subHappFallbackUrl: z.string(),
|
||||
subHappNewUrl: z.string(),
|
||||
subHappNoLimit: z.boolean(),
|
||||
subHappNotificationExpire: z.boolean(),
|
||||
subHappPerAppList: z.string(),
|
||||
subHappPerAppMode: z.string(),
|
||||
subHappPingType: z.string(),
|
||||
subHappProviderId: z.string(),
|
||||
subHappSubExpire: z.boolean(),
|
||||
subHappSubExpireButtonLink: z.string(),
|
||||
subHappSubInfoButtonLink: z.string(),
|
||||
subHappSubInfoButtonText: z.string(),
|
||||
subHappSubInfoColor: z.string(),
|
||||
subHappSubInfoText: z.string(),
|
||||
subHappTunMode: z.string(),
|
||||
subHappTunType: z.string(),
|
||||
subHideSettings: z.boolean(),
|
||||
subIncyEnableRouting: z.boolean(),
|
||||
subIncyRoutingRules: z.string(),
|
||||
|
||||
@@ -73,6 +73,29 @@ export class AllSetting {
|
||||
subJsonObservatory = '';
|
||||
subThemeDir = '';
|
||||
subHideSettings = false;
|
||||
subHappAutoDetect = false;
|
||||
subHappProviderId = '';
|
||||
subHappNewUrl = '';
|
||||
subHappFallbackUrl = '';
|
||||
subHappSubInfoColor = 'blue';
|
||||
subHappSubInfoText = '';
|
||||
subHappSubInfoButtonText = '';
|
||||
subHappSubInfoButtonLink = '';
|
||||
subHappSubExpire = false;
|
||||
subHappSubExpireButtonLink = '';
|
||||
subHappNotificationExpire = false;
|
||||
subHappNoLimit = false;
|
||||
subHappAlwaysHwid = false;
|
||||
subHappTunMode = '';
|
||||
subHappTunType = '';
|
||||
subHappExcludeRoutes = '';
|
||||
subHappExcludeApns = false;
|
||||
subHappColorProfile = '';
|
||||
subHappPingType = '';
|
||||
subHappAutoConnect = false;
|
||||
subHappAutoConnectType = 'lowestdelay';
|
||||
subHappPerAppMode = 'off';
|
||||
subHappPerAppList = '';
|
||||
|
||||
timeLocation = 'Local';
|
||||
|
||||
|
||||
@@ -0,0 +1,637 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Modal, Select, Space, Switch, Tabs, message } from 'antd';
|
||||
import {
|
||||
BranchesOutlined,
|
||||
BuildOutlined,
|
||||
CloudSyncOutlined,
|
||||
DesktopOutlined,
|
||||
MobileOutlined,
|
||||
NotificationOutlined,
|
||||
ThunderboltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
import { SettingListItem } from '@/components/ui';
|
||||
import { buildHappPresetDeeplink, parseList, toBase64Utf8 } from './happPresets';
|
||||
|
||||
interface HappSettingsContentProps {
|
||||
allSetting: AllSetting;
|
||||
updateSetting: (patch: Partial<AllSetting>) => void;
|
||||
isMobile: boolean;
|
||||
remoteSourceBadge: (val: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
export default function HappSettingsContent({
|
||||
allSetting,
|
||||
updateSetting,
|
||||
isMobile,
|
||||
remoteSourceBadge,
|
||||
}: HappSettingsContentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPreset, setSelectedPreset] = useState<string>('iran-bypass');
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const [directDomains, setDirectDomains] = useState('');
|
||||
const [proxyDomains, setProxyDomains] = useState('');
|
||||
const [blockDomains, setBlockDomains] = useState('');
|
||||
const [directIPs, setDirectIPs] = useState('');
|
||||
const [proxyIPs, setProxyIPs] = useState('');
|
||||
const [blockIPs, setBlockIPs] = useState('');
|
||||
|
||||
const applyPreset = () => {
|
||||
const payload = buildHappPresetDeeplink(selectedPreset);
|
||||
if (payload) {
|
||||
updateSetting({ subRoutingRules: payload });
|
||||
message.success(t('pages.settings.subHappPresetApplied'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBuildDeeplink = () => {
|
||||
interface FieldRule {
|
||||
type: string;
|
||||
outboundTag: string;
|
||||
domain?: string[];
|
||||
ip?: string[];
|
||||
network?: string;
|
||||
}
|
||||
const rules: FieldRule[] = [];
|
||||
|
||||
const bDom = parseList(blockDomains);
|
||||
const bIp = parseList(blockIPs);
|
||||
if (bDom.length > 0 || bIp.length > 0) {
|
||||
rules.push({
|
||||
type: 'field',
|
||||
outboundTag: 'block',
|
||||
...(bDom.length > 0 ? { domain: bDom } : {}),
|
||||
...(bIp.length > 0 ? { ip: bIp } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
const dDom = parseList(directDomains);
|
||||
const dIp = parseList(directIPs);
|
||||
if (dDom.length > 0 || dIp.length > 0) {
|
||||
rules.push({
|
||||
type: 'field',
|
||||
outboundTag: 'direct',
|
||||
...(dDom.length > 0 ? { domain: dDom } : {}),
|
||||
...(dIp.length > 0 ? { ip: dIp } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
const pDom = parseList(proxyDomains);
|
||||
const pIp = parseList(proxyIPs);
|
||||
if (pDom.length > 0 || pIp.length > 0) {
|
||||
rules.push({
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
...(pDom.length > 0 ? { domain: pDom } : {}),
|
||||
...(pIp.length > 0 ? { ip: pIp } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
rules.push({
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
network: 'tcp,udp',
|
||||
});
|
||||
|
||||
const deeplink = 'happ://routing/onadd/' + toBase64Utf8(JSON.stringify({ rules }));
|
||||
updateSetting({ subRoutingRules: deeplink });
|
||||
setIsModalOpen(false);
|
||||
message.success(t('pages.settings.subHappDeeplinkGenerated'));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
type="card"
|
||||
size="small"
|
||||
items={[
|
||||
{
|
||||
key: 'routing',
|
||||
label: (
|
||||
<span>
|
||||
<BranchesOutlined /> {!isMobile && t('pages.settings.subHappGroupRouting')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subEnableRouting')}
|
||||
description={t('pages.settings.subEnableRoutingDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subEnableRouting}
|
||||
onChange={(v) => updateSetting({ subEnableRouting: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappPresets')}
|
||||
description={t('pages.settings.subHappPresetsDesc')}
|
||||
>
|
||||
<Space orientation="horizontal" style={{ width: '100%' }}>
|
||||
<Select
|
||||
value={selectedPreset}
|
||||
style={{ minWidth: 170 }}
|
||||
onChange={setSelectedPreset}
|
||||
options={[
|
||||
{ value: 'iran-bypass', label: t('pages.settings.subHappPresetIran') },
|
||||
{ value: 'china-direct', label: t('pages.settings.subHappPresetChina') },
|
||||
{ value: 'adblock', label: t('pages.settings.subHappPresetAdblock') },
|
||||
{ value: 'global', label: t('pages.settings.subHappPresetGlobal') },
|
||||
{ value: 'off', label: t('pages.settings.subHappPresetOff') },
|
||||
]}
|
||||
/>
|
||||
<Button type="primary" onClick={applyPreset}>
|
||||
{t('pages.settings.subHappPresets')}
|
||||
</Button>
|
||||
</Space>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappVisualBuilder')}
|
||||
description={t('pages.settings.subHappVisualBuilderDesc')}
|
||||
>
|
||||
<Button icon={<BuildOutlined />} onClick={() => setIsModalOpen(true)}>
|
||||
{t('pages.settings.subHappVisualBuilder')}
|
||||
</Button>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subRoutingRules')}
|
||||
badge={remoteSourceBadge(allSetting.subRoutingRules)}
|
||||
description={t('pages.settings.subRoutingRulesDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subRoutingRules}
|
||||
rows={4}
|
||||
placeholder="happ://routing/onadd/... or https://.../DEFAULT.DEEPLINK"
|
||||
onChange={(e) => updateSetting({ subRoutingRules: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappNoLimit')}
|
||||
description={t('pages.settings.subHappNoLimitDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappNoLimit}
|
||||
onChange={(v) => updateSetting({ subHappNoLimit: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHideSettings')}
|
||||
description={t('pages.settings.subHideSettingsDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHideSettings}
|
||||
onChange={(v) => updateSetting({ subHideSettings: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'banners',
|
||||
label: (
|
||||
<span>
|
||||
<NotificationOutlined /> {!isMobile && t('pages.settings.subHappGroupBanners')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubInfoText')}
|
||||
description={t('pages.settings.subHappSubInfoTextDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappSubInfoText}
|
||||
maxLength={200}
|
||||
placeholder="Welcome to our high-speed network!"
|
||||
onChange={(e) => updateSetting({ subHappSubInfoText: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubInfoColor')}
|
||||
description={t('pages.settings.subHappSubInfoColorDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappSubInfoColor || 'blue'}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappSubInfoColor: v })}
|
||||
options={[
|
||||
{ value: 'blue', label: t('pages.settings.subHappColorBlue') },
|
||||
{ value: 'green', label: t('pages.settings.subHappColorGreen') },
|
||||
{ value: 'red', label: t('pages.settings.subHappColorRed') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubInfoButtonText')}
|
||||
description={t('pages.settings.subHappSubInfoButtonTextDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappSubInfoButtonText}
|
||||
maxLength={25}
|
||||
placeholder="Support Channel"
|
||||
onChange={(e) => updateSetting({ subHappSubInfoButtonText: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubInfoButtonLink')}
|
||||
description={t('pages.settings.subHappSubInfoButtonLinkDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappSubInfoButtonLink}
|
||||
placeholder="https://t.me/your_channel"
|
||||
onChange={(e) => updateSetting({ subHappSubInfoButtonLink: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubExpire')}
|
||||
description={t('pages.settings.subHappSubExpireDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappSubExpire}
|
||||
onChange={(v) => updateSetting({ subHappSubExpire: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappSubExpireButtonLink')}
|
||||
description={t('pages.settings.subHappSubExpireButtonLinkDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappSubExpireButtonLink}
|
||||
placeholder="https://example.com/renew"
|
||||
onChange={(e) => updateSetting({ subHappSubExpireButtonLink: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappNotificationExpire')}
|
||||
description={t('pages.settings.subHappNotificationExpireDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappNotificationExpire}
|
||||
onChange={(v) => updateSetting({ subHappNotificationExpire: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'network',
|
||||
label: (
|
||||
<span>
|
||||
<ThunderboltOutlined /> {!isMobile && t('pages.settings.subHappGroupNetwork')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappTunMode')}
|
||||
description={t('pages.settings.subHappTunModeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappTunMode}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappTunMode: v })}
|
||||
options={[
|
||||
// happ.su documents tun-mode as system|gvisor only, so
|
||||
// Default is the unset state rather than a third value.
|
||||
{ value: '', label: t('pages.settings.subHappTunModeDefault') },
|
||||
{ value: 'system', label: t('pages.settings.subHappTunModeSystem') },
|
||||
{ value: 'gvisor', label: t('pages.settings.subHappTunModeGvisor') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappTunType')}
|
||||
description={t('pages.settings.subHappTunTypeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappTunType || 'default'}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappTunType: v })}
|
||||
options={[
|
||||
{ value: 'singbox', label: t('pages.settings.subHappTunTypeSingbox') },
|
||||
{ value: 'tun2proxy', label: t('pages.settings.subHappTunTypeTun2proxy') },
|
||||
{ value: 'default', label: t('pages.settings.subHappTunTypeDefault') },
|
||||
{ value: 'xray', label: t('pages.settings.subHappTunTypeXray') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappExcludeRoutes')}
|
||||
description={t('pages.settings.subHappExcludeRoutesDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappExcludeRoutes}
|
||||
placeholder="192.168.0.0/16, 10.0.0.0/8"
|
||||
onChange={(e) => updateSetting({ subHappExcludeRoutes: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappExcludeApns')}
|
||||
description={t('pages.settings.subHappExcludeApnsDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappExcludeApns}
|
||||
onChange={(v) => updateSetting({ subHappExcludeApns: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappPingType')}
|
||||
description={t('pages.settings.subHappPingTypeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappPingType || 'proxy'}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappPingType: v })}
|
||||
options={[
|
||||
{ value: 'proxy', label: t('pages.settings.subHappPingProxy') },
|
||||
{ value: 'proxy-head', label: t('pages.settings.subHappPingProxyHead') },
|
||||
{ value: 'tcp', label: t('pages.settings.subHappPingTcp') },
|
||||
{ value: 'icmp', label: t('pages.settings.subHappPingIcmp') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappAutoConnect')}
|
||||
description={t('pages.settings.subHappAutoConnectDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappAutoConnect}
|
||||
onChange={(v) => updateSetting({ subHappAutoConnect: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappAutoConnectType')}
|
||||
description={t('pages.settings.subHappAutoConnectTypeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappAutoConnectType || 'lowestdelay'}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappAutoConnectType: v })}
|
||||
options={[
|
||||
{
|
||||
value: 'lowestdelay',
|
||||
label: t('pages.settings.subHappAutoConnectLowestDelay'),
|
||||
},
|
||||
{ value: 'lastused', label: t('pages.settings.subHappAutoConnectLastUsed') },
|
||||
{ value: 'random', label: t('pages.settings.subHappAutoConnectRandom') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'themes',
|
||||
label: (
|
||||
<span>
|
||||
<DesktopOutlined /> {!isMobile && t('pages.settings.subHappGroupThemes')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappColorProfile')}
|
||||
description={t('pages.settings.subHappColorProfileDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappColorProfile}
|
||||
placeholder="default, violet, turquoise, cyberpunk, or custom JSON"
|
||||
onChange={(e) => updateSetting({ subHappColorProfile: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'failover',
|
||||
label: (
|
||||
<span>
|
||||
<CloudSyncOutlined /> {!isMobile && t('pages.settings.subHappGroupFailover')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappAutoDetect')}
|
||||
description={t('pages.settings.subHappAutoDetectDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappAutoDetect}
|
||||
onChange={(v) => updateSetting({ subHappAutoDetect: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappProviderId')}
|
||||
description={t('pages.settings.subHappProviderIdDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappProviderId}
|
||||
placeholder="my-happ-provider"
|
||||
onChange={(e) => updateSetting({ subHappProviderId: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappNewUrl')}
|
||||
description={t('pages.settings.subHappNewUrlDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappNewUrl}
|
||||
placeholder="https://new-domain.com/sub/..."
|
||||
onChange={(e) => updateSetting({ subHappNewUrl: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappFallbackUrl')}
|
||||
description={t('pages.settings.subHappFallbackUrlDesc')}
|
||||
>
|
||||
<Input
|
||||
value={allSetting.subHappFallbackUrl}
|
||||
placeholder="https://backup-domain.com/sub/..."
|
||||
onChange={(e) => updateSetting({ subHappFallbackUrl: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappAlwaysHwid')}
|
||||
description={t('pages.settings.subHappAlwaysHwidDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHappAlwaysHwid}
|
||||
onChange={(v) => updateSetting({ subHappAlwaysHwid: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'android',
|
||||
label: (
|
||||
<span>
|
||||
<MobileOutlined /> {!isMobile && t('pages.settings.subHappGroupAndroid')}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappPerAppMode')}
|
||||
description={t('pages.settings.subHappPerAppModeDesc')}
|
||||
>
|
||||
<Select
|
||||
value={allSetting.subHappPerAppMode || 'off'}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(v) => updateSetting({ subHappPerAppMode: v })}
|
||||
options={[
|
||||
{ value: 'off', label: t('pages.settings.subHappPerAppOff') },
|
||||
{ value: 'on', label: t('pages.settings.subHappPerAppOn') },
|
||||
{ value: 'bypass', label: t('pages.settings.subHappPerAppBypass') },
|
||||
]}
|
||||
/>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHappPerAppList')}
|
||||
description={t('pages.settings.subHappPerAppListDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subHappPerAppList}
|
||||
rows={4}
|
||||
placeholder="org.telegram.messenger, com.google.android.youtube"
|
||||
onChange={(e) => updateSetting({ subHappPerAppList: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={t('pages.settings.subHappModalTitle')}
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
onOk={handleBuildDeeplink}
|
||||
okText={t('pages.settings.subHappBuildDeeplink')}
|
||||
width={650}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%', marginTop: 12 }} size="middle">
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappDirectDomains')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={directDomains}
|
||||
placeholder="domain:ir, domain:cn, example.local"
|
||||
onChange={(e) => setDirectDomains(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappProxyDomains')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={proxyDomains}
|
||||
placeholder="geosite:google, youtube.com"
|
||||
onChange={(e) => setProxyDomains(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappBlockDomains')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={blockDomains}
|
||||
placeholder="geosite:category-ads-all, analytics.google.com"
|
||||
onChange={(e) => setBlockDomains(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappDirectIPs')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={directIPs}
|
||||
placeholder="geoip:ir, 192.168.0.0/16, 10.0.0.0/8"
|
||||
onChange={(e) => setDirectIPs(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappProxyIPs')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={proxyIPs}
|
||||
placeholder="1.1.1.1/32, 8.8.8.8/32"
|
||||
onChange={(e) => setProxyIPs(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{t('pages.settings.subHappBlockIPs')}
|
||||
</div>
|
||||
<Input.TextArea
|
||||
rows={2}
|
||||
value={blockIPs}
|
||||
placeholder="geoip:phishing, 0.0.0.0/8"
|
||||
onChange={(e) => setBlockIPs(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { RemarkTemplateField } from '@/components/form';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { catTabLabel } from './catTabLabel';
|
||||
import { sanitizePath, normalizePath } from './uriPath';
|
||||
import HappSettingsContent from './HappSettingsContent';
|
||||
|
||||
interface SubscriptionGeneralTabProps {
|
||||
allSetting: AllSetting;
|
||||
@@ -344,40 +345,12 @@ export default function SubscriptionGeneralTab({
|
||||
key: '5',
|
||||
label: catTabLabel(<BranchesOutlined />, 'Happ', isMobile),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subEnableRouting')}
|
||||
description={t('pages.settings.subEnableRoutingDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subEnableRouting}
|
||||
onChange={(v) => updateSetting({ subEnableRouting: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subRoutingRules')}
|
||||
badge={remoteSourceBadge(allSetting.subRoutingRules)}
|
||||
description={t('pages.settings.subRoutingRulesDesc')}
|
||||
>
|
||||
<Input.TextArea
|
||||
value={allSetting.subRoutingRules}
|
||||
placeholder="happ://routing/onadd/... or https://.../DEFAULT.DEEPLINK"
|
||||
onChange={(e) => updateSetting({ subRoutingRules: e.target.value })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.subHideSettings')}
|
||||
description={t('pages.settings.subHideSettingsDesc')}
|
||||
>
|
||||
<Switch
|
||||
checked={allSetting.subHideSettings}
|
||||
onChange={(v) => updateSetting({ subHideSettings: v })}
|
||||
/>
|
||||
</SettingListItem>
|
||||
</>
|
||||
<HappSettingsContent
|
||||
allSetting={allSetting}
|
||||
updateSetting={updateSetting}
|
||||
isMobile={isMobile}
|
||||
remoteSourceBadge={remoteSourceBadge}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Encode UTF-8 string to standard Base64 safe for browser environments.
|
||||
export function toBase64Utf8(str: string): string {
|
||||
return btoa(
|
||||
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
|
||||
String.fromCharCode(Number(`0x${p1}`)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Splits multiline or comma-separated string into clean unique token arrays.
|
||||
export function parseList(input: string): string[] {
|
||||
return input
|
||||
.split(/[\n,]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// Build standard Happ routing deeplink or special state for curated presets.
|
||||
export function buildHappPresetDeeplink(preset: string): string {
|
||||
switch (preset) {
|
||||
case 'off':
|
||||
return 'happ://routing/off';
|
||||
case 'iran-bypass':
|
||||
return (
|
||||
'happ://routing/onadd/' +
|
||||
toBase64Utf8(
|
||||
JSON.stringify({
|
||||
rules: [
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'direct',
|
||||
domain: ['domain:ir', 'regexp:.*\\.ir$'],
|
||||
ip: ['geoip:ir', 'geoip:private'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'block',
|
||||
domain: ['geosite:category-ads-all'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
network: 'tcp,udp',
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
);
|
||||
case 'china-direct':
|
||||
return (
|
||||
'happ://routing/onadd/' +
|
||||
toBase64Utf8(
|
||||
JSON.stringify({
|
||||
rules: [
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'direct',
|
||||
domain: ['domain:cn', 'geosite:cn'],
|
||||
ip: ['geoip:cn', 'geoip:private'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'block',
|
||||
domain: ['geosite:category-ads-all'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
network: 'tcp,udp',
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
);
|
||||
case 'adblock':
|
||||
return (
|
||||
'happ://routing/onadd/' +
|
||||
toBase64Utf8(
|
||||
JSON.stringify({
|
||||
rules: [
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'block',
|
||||
domain: ['geosite:category-ads-all'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'direct',
|
||||
ip: ['geoip:private'],
|
||||
},
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
network: 'tcp,udp',
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
);
|
||||
case 'global':
|
||||
return (
|
||||
'happ://routing/onadd/' +
|
||||
toBase64Utf8(
|
||||
JSON.stringify({
|
||||
rules: [
|
||||
{
|
||||
type: 'field',
|
||||
outboundTag: 'proxy',
|
||||
network: 'tcp,udp',
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,29 @@ export const AllSettingSchema = z
|
||||
subJsonFinalMask: z.string().optional(),
|
||||
subJsonObservatory: z.string().optional(),
|
||||
subHideSettings: z.boolean().optional(),
|
||||
subHappAutoDetect: z.boolean().optional(),
|
||||
subHappProviderId: z.string().optional(),
|
||||
subHappNewUrl: z.string().optional(),
|
||||
subHappFallbackUrl: z.string().optional(),
|
||||
subHappSubInfoColor: z.string().optional(),
|
||||
subHappSubInfoText: z.string().optional(),
|
||||
subHappSubInfoButtonText: z.string().optional(),
|
||||
subHappSubInfoButtonLink: z.string().optional(),
|
||||
subHappSubExpire: z.boolean().optional(),
|
||||
subHappSubExpireButtonLink: z.string().optional(),
|
||||
subHappNotificationExpire: z.boolean().optional(),
|
||||
subHappNoLimit: z.boolean().optional(),
|
||||
subHappAlwaysHwid: z.boolean().optional(),
|
||||
subHappTunMode: z.string().optional(),
|
||||
subHappTunType: z.string().optional(),
|
||||
subHappExcludeRoutes: z.string().optional(),
|
||||
subHappExcludeApns: z.boolean().optional(),
|
||||
subHappColorProfile: z.string().optional(),
|
||||
subHappPingType: z.string().optional(),
|
||||
subHappAutoConnect: z.boolean().optional(),
|
||||
subHappAutoConnectType: z.string().optional(),
|
||||
subHappPerAppMode: z.string().optional(),
|
||||
subHappPerAppList: z.string().optional(),
|
||||
timeLocation: z.string().optional(),
|
||||
ldapEnable: z.boolean().optional(),
|
||||
ldapHost: z.string().optional(),
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildHappPresetDeeplink, parseList, toBase64Utf8 } from '@/pages/settings/happPresets';
|
||||
|
||||
describe('Happ presets and helpers', () => {
|
||||
it('correctly parses comma and newline separated lists', () => {
|
||||
const raw = 'domain:ir\nregexp:.*\\.ir$\n, example.com, , google.com';
|
||||
const parsed = parseList(raw);
|
||||
expect(parsed).toEqual(['domain:ir', 'regexp:.*\\.ir$', 'example.com', 'google.com']);
|
||||
});
|
||||
|
||||
it('generates valid happ://routing/off for off preset', () => {
|
||||
const link = buildHappPresetDeeplink('off');
|
||||
expect(link).toBe('happ://routing/off');
|
||||
});
|
||||
|
||||
it('generates valid base64 payload for iran-bypass preset', () => {
|
||||
const link = buildHappPresetDeeplink('iran-bypass');
|
||||
expect(link.startsWith('happ://routing/onadd/')).toBe(true);
|
||||
|
||||
const b64 = link.replace('happ://routing/onadd/', '');
|
||||
const jsonStr = atob(b64);
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
expect(parsed).toHaveProperty('rules');
|
||||
expect(Array.isArray(parsed.rules)).toBe(true);
|
||||
|
||||
const directRule = parsed.rules.find(
|
||||
(r: { outboundTag: string }) => r.outboundTag === 'direct',
|
||||
);
|
||||
expect(directRule).toBeDefined();
|
||||
expect(directRule.domain).toContain('domain:ir');
|
||||
expect(directRule.ip).toContain('geoip:ir');
|
||||
|
||||
const blockRule = parsed.rules.find((r: { outboundTag: string }) => r.outboundTag === 'block');
|
||||
expect(blockRule).toBeDefined();
|
||||
expect(blockRule.domain).toContain('geosite:category-ads-all');
|
||||
});
|
||||
|
||||
it('generates valid base64 payload for china-direct preset', () => {
|
||||
const link = buildHappPresetDeeplink('china-direct');
|
||||
expect(link.startsWith('happ://routing/onadd/')).toBe(true);
|
||||
|
||||
const b64 = link.replace('happ://routing/onadd/', '');
|
||||
const jsonStr = atob(b64);
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
const directRule = parsed.rules.find(
|
||||
(r: { outboundTag: string }) => r.outboundTag === 'direct',
|
||||
);
|
||||
expect(directRule.domain).toContain('domain:cn');
|
||||
expect(directRule.ip).toContain('geoip:cn');
|
||||
});
|
||||
|
||||
it('generates valid base64 payload for adblock preset', () => {
|
||||
const link = buildHappPresetDeeplink('adblock');
|
||||
const b64 = link.replace('happ://routing/onadd/', '');
|
||||
const jsonStr = atob(b64);
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
const blockRule = parsed.rules.find((r: { outboundTag: string }) => r.outboundTag === 'block');
|
||||
expect(blockRule.domain).toContain('geosite:category-ads-all');
|
||||
});
|
||||
|
||||
it('generates valid base64 payload for global preset', () => {
|
||||
const link = buildHappPresetDeeplink('global');
|
||||
const b64 = link.replace('happ://routing/onadd/', '');
|
||||
const jsonStr = atob(b64);
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
expect(parsed.rules[0].outboundTag).toBe('proxy');
|
||||
expect(parsed.rules[0].network).toBe('tcp,udp');
|
||||
});
|
||||
|
||||
it('encodes unicode properly via toBase64Utf8', () => {
|
||||
const text = 'فیلترشکن و روتینگ';
|
||||
const b64 = toBase64Utf8(text);
|
||||
const decoded = decodeURIComponent(
|
||||
Array.prototype.map
|
||||
.call(atob(b64), (c: string) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
||||
.join(''),
|
||||
);
|
||||
expect(decoded).toBe(text);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
import HappSettingsContent from '@/pages/settings/HappSettingsContent';
|
||||
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 selectFor(title: string): HTMLElement {
|
||||
const row = Array.from(document.querySelectorAll('.ant-select')).find((s) =>
|
||||
(s.closest('li,div[class*="setting"]')?.textContent ?? '').includes(title),
|
||||
);
|
||||
if (!row) throw new Error(`select for '${title}' not found`);
|
||||
return row as HTMLElement;
|
||||
}
|
||||
|
||||
function clickOption(text: string) {
|
||||
const option = Array.from(document.querySelectorAll('.ant-select-item-option')).find(
|
||||
(o) => (o.textContent ?? '').trim() === text,
|
||||
);
|
||||
if (!option) throw new Error(`option '${text}' not found`);
|
||||
fireEvent.click(option);
|
||||
}
|
||||
|
||||
describe('Happ TUN Mode select', () => {
|
||||
// happ.su documents tun-mode as system|gvisor only, so the Default entry has
|
||||
// to mean "send no header", the same state a fresh panel ships with.
|
||||
it('stores the empty value for Default so no Tun-Mode header is emitted', () => {
|
||||
const updateSetting = vi.fn();
|
||||
const allSetting = new AllSetting();
|
||||
allSetting.subHappTunMode = 'gvisor';
|
||||
|
||||
renderWithProviders(
|
||||
<HappSettingsContent
|
||||
allSetting={allSetting}
|
||||
updateSetting={updateSetting}
|
||||
isMobile={false}
|
||||
remoteSourceBadge={() => null}
|
||||
/>,
|
||||
);
|
||||
|
||||
openTab('Network');
|
||||
const select = selectFor('TUN Mode');
|
||||
fireEvent.mouseDown(select.querySelector('.ant-select-selector') ?? select);
|
||||
clickOption('Default');
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith({ subHappTunMode: '' });
|
||||
});
|
||||
|
||||
it('labels the unset state Default rather than leaving the control blank', () => {
|
||||
renderWithProviders(
|
||||
<HappSettingsContent
|
||||
allSetting={new AllSetting()}
|
||||
updateSetting={vi.fn()}
|
||||
isMobile={false}
|
||||
remoteSourceBadge={() => null}
|
||||
/>,
|
||||
);
|
||||
|
||||
openTab('Network');
|
||||
const select = selectFor('TUN Mode');
|
||||
expect(select.querySelector('.ant-select-content')?.textContent).toBe('Default');
|
||||
});
|
||||
});
|
||||
@@ -52,6 +52,7 @@ type SUBController struct {
|
||||
subEnableRouting bool
|
||||
subRoutingRules string
|
||||
subHideSettings bool
|
||||
happConfig HappConfig
|
||||
|
||||
subIncyEnableRouting bool
|
||||
subIncyRoutingRules string
|
||||
@@ -110,6 +111,7 @@ type subControllerConfig struct {
|
||||
subEnableRouting bool
|
||||
subRoutingRules string
|
||||
subHideSettings bool
|
||||
happConfig HappConfig
|
||||
|
||||
subIncyEnableRouting bool
|
||||
subIncyRoutingRules string
|
||||
@@ -229,6 +231,10 @@ func WithSUBIncyRoutingRules(value string) SUBControllerOption {
|
||||
return func(config *subControllerConfig) { config.subIncyRoutingRules = value }
|
||||
}
|
||||
|
||||
func WithSUBHappConfig(value HappConfig) SUBControllerOption {
|
||||
return func(config *subControllerConfig) { config.happConfig = value }
|
||||
}
|
||||
|
||||
func defaultSUBControllerConfig() subControllerConfig {
|
||||
return subControllerConfig{
|
||||
subPath: "/sub/",
|
||||
@@ -258,6 +264,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo
|
||||
subEnableRouting: config.subEnableRouting,
|
||||
subRoutingRules: config.subRoutingRules,
|
||||
subHideSettings: config.subHideSettings,
|
||||
happConfig: config.happConfig,
|
||||
|
||||
subIncyEnableRouting: config.subIncyEnableRouting,
|
||||
subIncyRoutingRules: config.subIncyRoutingRules,
|
||||
@@ -845,16 +852,23 @@ func (a *SUBController) ApplyCommonHeaders(
|
||||
c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
|
||||
}
|
||||
|
||||
// Advanced (Happ). Routing stays independent of the enable flag; remote
|
||||
// values come only from the validated cache and never delay this response.
|
||||
rules, remote, routingErr := resolveRoutingSource(remoteRoutingHapp, profileRoutingRules)
|
||||
// The off values undo a previously pushed setting, so they ride the same
|
||||
// opt-in as every other Happ header rather than reaching every Happ client.
|
||||
happManaged := a.happConfig.AutoDetect && c.Request != nil && IsHappClient(c.GetHeader("User-Agent"))
|
||||
if profileEnableRouting {
|
||||
c.Writer.Header().Set("Routing-Enable", "true")
|
||||
} else if happManaged {
|
||||
c.Writer.Header().Set("Routing-Enable", "0")
|
||||
}
|
||||
if (routingErr == nil || !remote) && strings.TrimSpace(rules) != "" {
|
||||
c.Writer.Header().Set("Routing", rules)
|
||||
}
|
||||
if profileHideSettings {
|
||||
c.Writer.Header().Set("Hide-Settings", "1")
|
||||
} else if happManaged {
|
||||
c.Writer.Header().Set("Hide-Settings", "0")
|
||||
}
|
||||
|
||||
ApplyHappHeaders(c, a.happConfig, happManaged)
|
||||
}
|
||||
|
||||
@@ -116,6 +116,32 @@ func TestBuildEndpointVmessLinks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// happ.su documents serverDescription as a "#title?serverDescription=<base64>"
|
||||
// link parameter, never a key of the VMess object, so nothing may leak into it.
|
||||
func TestBuildEndpointVmessLinks_HostServerDescription(t *testing.T) {
|
||||
s := &SubService{}
|
||||
in := &model.Inbound{Remark: "ib"}
|
||||
baseObj := map[string]any{"v": "2", "add": "base.example.com", "port": 443, "type": "none", "id": "uid", "scy": "auto", "net": "tcp", "tls": "none"}
|
||||
host := &model.Host{Address: "a.example.com", Port: 8443, ServerDescription: "Berlin premium"}
|
||||
eps := []ShareEndpoint{externalProxyToEndpoint(hostToExternalProxyMap(host, "a.example.com", 8443))}
|
||||
|
||||
got := s.buildEndpointVmessLinks(eps, baseObj, in, "user", "tcp")
|
||||
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(got, "vmess://"))
|
||||
if err != nil {
|
||||
t.Fatalf("decode vmess link: %v", err)
|
||||
}
|
||||
var obj map[string]any
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
t.Fatalf("unmarshal vmess object: %v", err)
|
||||
}
|
||||
if obj["add"] != "a.example.com" {
|
||||
t.Fatalf("host endpoint not applied: add = %v", obj["add"])
|
||||
}
|
||||
if value, ok := obj["serverDescription"]; ok {
|
||||
t.Fatalf("VMess object carries serverDescription = %v; it is not a VMess object key", value)
|
||||
}
|
||||
}
|
||||
|
||||
// N5 — a host's Final Mask is appended to the inbound's own fm param (#5831).
|
||||
func TestBuildEndpointLinks_HostFinalMaskMerge(t *testing.T) {
|
||||
s := &SubService{}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var happUserAgentRegex = regexp.MustCompile(`(?i)\bhapp\b`)
|
||||
|
||||
// HappConfig holds all Happ client customization parameters.
|
||||
type HappConfig struct {
|
||||
AutoDetect bool
|
||||
ProviderId string
|
||||
NewUrl string
|
||||
FallbackUrl string
|
||||
SubInfoColor string
|
||||
SubInfoText string
|
||||
SubInfoButtonText string
|
||||
SubInfoButtonLink string
|
||||
SubExpire bool
|
||||
SubExpireButtonLink string
|
||||
NotificationExpire bool
|
||||
NoLimit bool
|
||||
AlwaysHwid bool
|
||||
TunMode string
|
||||
TunType string
|
||||
ExcludeRoutes string
|
||||
ExcludeApns bool
|
||||
ColorProfile string
|
||||
PingType string
|
||||
AutoConnect bool
|
||||
AutoConnectType string
|
||||
PerAppMode string
|
||||
PerAppList string
|
||||
}
|
||||
|
||||
// IsHappClient checks if the client user-agent identifies as Happ.
|
||||
func IsHappClient(userAgent string) bool {
|
||||
return happUserAgentRegex.MatchString(userAgent)
|
||||
}
|
||||
|
||||
// ApplyHappHeaders sets standard and advanced Happ subscription headers.
|
||||
func ApplyHappHeaders(c *gin.Context, cfg HappConfig, isHapp bool) {
|
||||
if c == nil || c.Writer == nil || !cfg.AutoDetect || !isHapp {
|
||||
return
|
||||
}
|
||||
if cfg.ProviderId != "" {
|
||||
c.Writer.Header().Set("ProviderID", cfg.ProviderId)
|
||||
}
|
||||
if cfg.NewUrl != "" {
|
||||
c.Writer.Header().Set("New-Url", cfg.NewUrl)
|
||||
}
|
||||
if cfg.FallbackUrl != "" {
|
||||
c.Writer.Header().Set("Fallback-Url", cfg.FallbackUrl)
|
||||
}
|
||||
if text := strings.TrimSpace(cfg.SubInfoText); text != "" {
|
||||
color := strings.TrimSpace(cfg.SubInfoColor)
|
||||
switch strings.ToLower(color) {
|
||||
case "primary", "info":
|
||||
color = "blue"
|
||||
case "success":
|
||||
color = "green"
|
||||
case "warning", "danger":
|
||||
color = "red"
|
||||
case "":
|
||||
color = "blue"
|
||||
}
|
||||
c.Writer.Header().Set("Sub-Info-Color", color)
|
||||
c.Writer.Header().Set("Sub-Info-Text", text)
|
||||
if btnText := strings.TrimSpace(cfg.SubInfoButtonText); btnText != "" {
|
||||
c.Writer.Header().Set("Sub-Info-Button-Text", btnText)
|
||||
}
|
||||
if btnLink := strings.TrimSpace(cfg.SubInfoButtonLink); btnLink != "" {
|
||||
c.Writer.Header().Set("Sub-Info-Button-Link", btnLink)
|
||||
}
|
||||
}
|
||||
if cfg.SubExpire {
|
||||
c.Writer.Header().Set("Sub-Expire", "1")
|
||||
if link := strings.TrimSpace(cfg.SubExpireButtonLink); link != "" {
|
||||
c.Writer.Header().Set("Sub-Expire-Button-Link", link)
|
||||
}
|
||||
}
|
||||
if cfg.NotificationExpire {
|
||||
c.Writer.Header().Set("Notification-Subs-Expire", "1")
|
||||
}
|
||||
if cfg.NoLimit {
|
||||
c.Writer.Header().Set("No-Limit-Enabled", "1")
|
||||
}
|
||||
if cfg.AlwaysHwid {
|
||||
c.Writer.Header().Set("Subscription-Always-Hwid-Enable", "1")
|
||||
}
|
||||
if cfg.TunMode != "" {
|
||||
c.Writer.Header().Set("Tun-Mode", cfg.TunMode)
|
||||
}
|
||||
if cfg.TunType != "" {
|
||||
c.Writer.Header().Set("Tun-Type", cfg.TunType)
|
||||
}
|
||||
if routes := strings.TrimSpace(cfg.ExcludeRoutes); routes != "" {
|
||||
c.Writer.Header().Set("Exclude-Routes", routes)
|
||||
}
|
||||
if cfg.ExcludeApns {
|
||||
c.Writer.Header().Set("Exclude-Apns-Enable", "true")
|
||||
}
|
||||
if profile := strings.TrimSpace(cfg.ColorProfile); profile != "" {
|
||||
profile = strings.ReplaceAll(strings.ReplaceAll(profile, "\r", ""), "\n", "")
|
||||
c.Writer.Header().Set("Color-Profile", profile)
|
||||
}
|
||||
if ping := strings.TrimSpace(cfg.PingType); ping != "" {
|
||||
if strings.EqualFold(ping, "http") {
|
||||
ping = "proxy"
|
||||
}
|
||||
c.Writer.Header().Set("Ping-Type", ping)
|
||||
}
|
||||
if cfg.AutoConnect {
|
||||
c.Writer.Header().Set("Subscription-Autoconnect", "1")
|
||||
autoType := strings.TrimSpace(cfg.AutoConnectType)
|
||||
switch strings.ToLower(autoType) {
|
||||
case "fastest":
|
||||
autoType = "lowestdelay"
|
||||
case "last":
|
||||
autoType = "lastused"
|
||||
}
|
||||
if autoType != "" {
|
||||
c.Writer.Header().Set("Subscription-Autoconnect-Type", autoType)
|
||||
}
|
||||
}
|
||||
if mode := strings.TrimSpace(cfg.PerAppMode); mode != "" && mode != "off" {
|
||||
switch strings.ToLower(mode) {
|
||||
case "include":
|
||||
mode = "on"
|
||||
case "exclude":
|
||||
mode = "bypass"
|
||||
}
|
||||
c.Writer.Header().Set("Per-App-Proxy-Mode", mode)
|
||||
if list := strings.TrimSpace(cfg.PerAppList); list != "" {
|
||||
c.Writer.Header().Set("Per-App-Proxy-List", list)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestNormalizeHappRouting_Off(t *testing.T) {
|
||||
got, err := normalizeHappRouting([]byte("happ://routing/off"))
|
||||
if err != nil {
|
||||
t.Fatalf("normalizeHappRouting(off) error: %v", err)
|
||||
}
|
||||
if got != "happ://routing/off" {
|
||||
t.Fatalf("normalizeHappRouting(off) = %q, want happ://routing/off", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCommonHeaders_HappClientHeaders(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
cfg := HappConfig{
|
||||
AutoDetect: true,
|
||||
ProviderId: "pid-test-123",
|
||||
NewUrl: "https://new.example.com/sub",
|
||||
FallbackUrl: "https://backup.example.com/sub",
|
||||
SubInfoColor: "primary",
|
||||
SubInfoText: "Welcome to VIP Network",
|
||||
SubInfoButtonText: "Telegram",
|
||||
SubInfoButtonLink: "https://t.me/example",
|
||||
SubExpire: true,
|
||||
SubExpireButtonLink: "https://renew.example.com",
|
||||
NotificationExpire: true,
|
||||
NoLimit: true,
|
||||
AlwaysHwid: true,
|
||||
TunMode: "gvisor",
|
||||
TunType: "singbox",
|
||||
ExcludeRoutes: "192.168.1.0/24, 10.0.0.0/8",
|
||||
ExcludeApns: true,
|
||||
ColorProfile: "{\"serverRowBackgroundColor\":\n\"#21003D67\"}",
|
||||
PingType: "http",
|
||||
AutoConnect: true,
|
||||
AutoConnectType: "fastest",
|
||||
PerAppMode: "include",
|
||||
PerAppList: "com.google.chrome,com.meta.instagram",
|
||||
}
|
||||
|
||||
controller := &SUBController{happConfig: cfg}
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
|
||||
ctx.Request.Header.Set("User-Agent", "Happ/1.2.0 (iPhone; iOS 17.5)")
|
||||
|
||||
controller.ApplyCommonHeaders(ctx, "upload=0; download=100; total=1000; expire=1800000000", "12", "MyTitle", "", "", "", false, "", false)
|
||||
|
||||
h := recorder.Header()
|
||||
if h.Get("Routing-Enable") != "0" {
|
||||
t.Fatalf("Routing-Enable = %q, want 0 for Happ with disabled routing", h.Get("Routing-Enable"))
|
||||
}
|
||||
if h.Get("Hide-Settings") != "0" {
|
||||
t.Fatalf("Hide-Settings = %q, want 0 for Happ with disabled hideSettings", h.Get("Hide-Settings"))
|
||||
}
|
||||
if h.Get("ProviderID") != "pid-test-123" {
|
||||
t.Fatalf("ProviderID = %q, want pid-test-123", h.Get("ProviderID"))
|
||||
}
|
||||
if h.Get("New-Url") != "https://new.example.com/sub" {
|
||||
t.Fatalf("New-Url = %q", h.Get("New-Url"))
|
||||
}
|
||||
if h.Get("Fallback-Url") != "https://backup.example.com/sub" {
|
||||
t.Fatalf("Fallback-Url = %q", h.Get("Fallback-Url"))
|
||||
}
|
||||
if h.Get("Sub-Info-Color") != "blue" || h.Get("Sub-Info-Text") != "Welcome to VIP Network" {
|
||||
t.Fatalf("Sub-Info = %s / %s, want blue / Welcome to VIP Network", h.Get("Sub-Info-Color"), h.Get("Sub-Info-Text"))
|
||||
}
|
||||
if h.Get("Sub-Info-Button-Text") != "Telegram" || h.Get("Sub-Info-Button-Link") != "https://t.me/example" {
|
||||
t.Fatalf("Sub-Info button = %s / %s", h.Get("Sub-Info-Button-Text"), h.Get("Sub-Info-Button-Link"))
|
||||
}
|
||||
if h.Get("Sub-Expire") != "1" || h.Get("Sub-Expire-Button-Link") != "https://renew.example.com" {
|
||||
t.Fatalf("Sub-Expire = %s / %s", h.Get("Sub-Expire"), h.Get("Sub-Expire-Button-Link"))
|
||||
}
|
||||
if h.Get("Notification-Subs-Expire") != "1" {
|
||||
t.Fatalf("Notification-Subs-Expire = %q", h.Get("Notification-Subs-Expire"))
|
||||
}
|
||||
if h.Get("No-Limit-Enabled") != "1" {
|
||||
t.Fatalf("No-Limit-Enabled = %q", h.Get("No-Limit-Enabled"))
|
||||
}
|
||||
if h.Get("Subscription-Always-Hwid-Enable") != "1" {
|
||||
t.Fatalf("Subscription-Always-Hwid-Enable = %q", h.Get("Subscription-Always-Hwid-Enable"))
|
||||
}
|
||||
if h.Get("Tun-Mode") != "gvisor" || h.Get("Tun-Type") != "singbox" {
|
||||
t.Fatalf("Tun mode/type = %s / %s", h.Get("Tun-Mode"), h.Get("Tun-Type"))
|
||||
}
|
||||
if h.Get("Exclude-Routes") != "192.168.1.0/24, 10.0.0.0/8" || h.Get("Exclude-Apns-Enable") != "true" {
|
||||
t.Fatalf("Exclude routes/apns = %s / %s", h.Get("Exclude-Routes"), h.Get("Exclude-Apns-Enable"))
|
||||
}
|
||||
if wantProfile := "{\"serverRowBackgroundColor\":\"#21003D67\"}"; h.Get("Color-Profile") != wantProfile {
|
||||
t.Fatalf("Color-Profile = %q, want %q", h.Get("Color-Profile"), wantProfile)
|
||||
}
|
||||
if h.Get("Ping-Type") != "proxy" {
|
||||
t.Fatalf("Ping-Type = %q, want proxy for http alias", h.Get("Ping-Type"))
|
||||
}
|
||||
if h.Get("Subscription-Autoconnect") != "1" || h.Get("Subscription-Autoconnect-Type") != "lowestdelay" {
|
||||
t.Fatalf("Autoconnect = %s / %s, want 1 / lowestdelay for fastest alias", h.Get("Subscription-Autoconnect"), h.Get("Subscription-Autoconnect-Type"))
|
||||
}
|
||||
if h.Get("Per-App-Proxy-Mode") != "on" || h.Get("Per-App-Proxy-List") != "com.google.chrome,com.meta.instagram" {
|
||||
t.Fatalf("Per-App = %s / %s, want on / com.google.chrome,com.meta.instagram", h.Get("Per-App-Proxy-Mode"), h.Get("Per-App-Proxy-List"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHappHeaders_Gating(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
cfg := HappConfig{
|
||||
AutoDetect: true,
|
||||
ProviderId: "pid-secret",
|
||||
SubInfoText: "Banner",
|
||||
TunMode: "system",
|
||||
}
|
||||
|
||||
t.Run("non-Happ User-Agent receives no headers", func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
|
||||
ctx.Request.Header.Set("User-Agent", "v2rayNG/1.8.5")
|
||||
|
||||
controller := &SUBController{happConfig: cfg}
|
||||
controller.ApplyCommonHeaders(ctx, "", "", "Title", "", "", "", false, "", false)
|
||||
|
||||
if got := recorder.Header().Get("ProviderID"); got != "" {
|
||||
t.Fatalf("ProviderID emitted to non-Happ client: %q", got)
|
||||
}
|
||||
if got := recorder.Header().Get("Sub-Info-Text"); got != "" {
|
||||
t.Fatalf("Sub-Info-Text emitted to non-Happ client: %q", got)
|
||||
}
|
||||
if got := recorder.Header().Get("Tun-Mode"); got != "" {
|
||||
t.Fatalf("Tun-Mode emitted to non-Happ client: %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AutoDetect disabled suppresses Happ headers", func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
|
||||
ctx.Request.Header.Set("User-Agent", "Happ/1.2.0 (Android)")
|
||||
|
||||
disabledCfg := cfg
|
||||
disabledCfg.AutoDetect = false
|
||||
|
||||
controller := &SUBController{happConfig: disabledCfg}
|
||||
controller.ApplyCommonHeaders(ctx, "", "", "Title", "", "", "", false, "", false)
|
||||
|
||||
if got := recorder.Header().Get("ProviderID"); got != "" {
|
||||
t.Fatalf("ProviderID emitted when AutoDetect is false: %q", got)
|
||||
}
|
||||
if got := recorder.Header().Get("Sub-Info-Text"); got != "" {
|
||||
t.Fatalf("Sub-Info-Text emitted when AutoDetect is false: %q", got)
|
||||
}
|
||||
// happ.su documents routing-enable 0/false as "disables routing
|
||||
// globally", so it must stay behind the same opt-in as the rest.
|
||||
if got := recorder.Header().Get("Routing-Enable"); got != "" {
|
||||
t.Fatalf("Routing-Enable emitted when AutoDetect is false: %q", got)
|
||||
}
|
||||
if got := recorder.Header().Get("Hide-Settings"); got != "" {
|
||||
t.Fatalf("Hide-Settings emitted when AutoDetect is false: %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyHappHeaders_Aliases(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg HappConfig
|
||||
wantHeader string
|
||||
wantValue string
|
||||
}{
|
||||
{
|
||||
name: "color warning maps to red",
|
||||
cfg: HappConfig{AutoDetect: true, SubInfoText: "Alert", SubInfoColor: "warning"},
|
||||
wantHeader: "Sub-Info-Color",
|
||||
wantValue: "red",
|
||||
},
|
||||
{
|
||||
name: "color danger maps to red",
|
||||
cfg: HappConfig{AutoDetect: true, SubInfoText: "Alert", SubInfoColor: "danger"},
|
||||
wantHeader: "Sub-Info-Color",
|
||||
wantValue: "red",
|
||||
},
|
||||
{
|
||||
name: "color success maps to green",
|
||||
cfg: HappConfig{AutoDetect: true, SubInfoText: "Ok", SubInfoColor: "success"},
|
||||
wantHeader: "Sub-Info-Color",
|
||||
wantValue: "green",
|
||||
},
|
||||
{
|
||||
name: "autoconnect last maps to lastused",
|
||||
cfg: HappConfig{AutoDetect: true, AutoConnect: true, AutoConnectType: "last"},
|
||||
wantHeader: "Subscription-Autoconnect-Type",
|
||||
wantValue: "lastused",
|
||||
},
|
||||
{
|
||||
name: "per-app exclude maps to bypass",
|
||||
cfg: HappConfig{AutoDetect: true, PerAppMode: "exclude", PerAppList: "app.id"},
|
||||
wantHeader: "Per-App-Proxy-Mode",
|
||||
wantValue: "bypass",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
|
||||
ctx.Request.Header.Set("User-Agent", "Happ/1.2.0 (iOS)")
|
||||
|
||||
controller := &SUBController{happConfig: tc.cfg}
|
||||
controller.ApplyCommonHeaders(ctx, "", "", "Title", "", "", "", false, "", false)
|
||||
|
||||
if got := recorder.Header().Get(tc.wantHeader); got != tc.wantValue {
|
||||
t.Fatalf("%s = %q, want %q", tc.wantHeader, got, tc.wantValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -369,6 +369,9 @@ func normalizeHappRouting(body []byte) (string, error) {
|
||||
}
|
||||
return "happ://routing/onadd/" + base64.StdEncoding.EncodeToString(compact), nil
|
||||
}
|
||||
if text == "happ://routing/off" {
|
||||
return text, nil
|
||||
}
|
||||
if strings.ContainsAny(text, "\r\n") {
|
||||
return "", errors.New("Happ deeplink must be a single line")
|
||||
}
|
||||
|
||||
@@ -2166,8 +2166,7 @@ func appendQueryAndFragment(link string, params map[string]string, fragment, sec
|
||||
|
||||
if fragment != "" {
|
||||
sb.WriteByte('#')
|
||||
// Match the frontend's encodeURIComponent(remark): spaces become
|
||||
// %20 (not + as in query strings).
|
||||
// Match the frontend's encodeURIComponent(remark): spaces become %20.
|
||||
sb.WriteString(strings.ReplaceAll(url.QueryEscape(fragment), "+", "%20"))
|
||||
}
|
||||
return sb.String()
|
||||
|
||||
@@ -215,6 +215,31 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
SubIncyRoutingRules = ""
|
||||
}
|
||||
|
||||
happCfg := HappConfig{}
|
||||
happCfg.AutoDetect, _ = s.settingService.GetSubHappAutoDetect()
|
||||
happCfg.ProviderId, _ = s.settingService.GetSubHappProviderId()
|
||||
happCfg.NewUrl, _ = s.settingService.GetSubHappNewUrl()
|
||||
happCfg.FallbackUrl, _ = s.settingService.GetSubHappFallbackUrl()
|
||||
happCfg.SubInfoColor, _ = s.settingService.GetSubHappSubInfoColor()
|
||||
happCfg.SubInfoText, _ = s.settingService.GetSubHappSubInfoText()
|
||||
happCfg.SubInfoButtonText, _ = s.settingService.GetSubHappSubInfoButtonText()
|
||||
happCfg.SubInfoButtonLink, _ = s.settingService.GetSubHappSubInfoButtonLink()
|
||||
happCfg.SubExpire, _ = s.settingService.GetSubHappSubExpire()
|
||||
happCfg.SubExpireButtonLink, _ = s.settingService.GetSubHappSubExpireButtonLink()
|
||||
happCfg.NotificationExpire, _ = s.settingService.GetSubHappNotificationExpire()
|
||||
happCfg.NoLimit, _ = s.settingService.GetSubHappNoLimit()
|
||||
happCfg.AlwaysHwid, _ = s.settingService.GetSubHappAlwaysHwid()
|
||||
happCfg.TunMode, _ = s.settingService.GetSubHappTunMode()
|
||||
happCfg.TunType, _ = s.settingService.GetSubHappTunType()
|
||||
happCfg.ExcludeRoutes, _ = s.settingService.GetSubHappExcludeRoutes()
|
||||
happCfg.ExcludeApns, _ = s.settingService.GetSubHappExcludeApns()
|
||||
happCfg.ColorProfile, _ = s.settingService.GetSubHappColorProfile()
|
||||
happCfg.PingType, _ = s.settingService.GetSubHappPingType()
|
||||
happCfg.AutoConnect, _ = s.settingService.GetSubHappAutoConnect()
|
||||
happCfg.AutoConnectType, _ = s.settingService.GetSubHappAutoConnectType()
|
||||
happCfg.PerAppMode, _ = s.settingService.GetSubHappPerAppMode()
|
||||
happCfg.PerAppList, _ = s.settingService.GetSubHappPerAppList()
|
||||
|
||||
// set per-request localizer from headers/cookies
|
||||
engine.Use(locale.LocalizerMiddleware())
|
||||
|
||||
@@ -296,6 +321,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
WithSUBEnableRouting(SubEnableRouting),
|
||||
WithSUBRoutingRules(SubRoutingRules),
|
||||
WithSUBHideSettings(SubHideSettings),
|
||||
WithSUBHappConfig(happCfg),
|
||||
WithSUBIncyEnableRouting(SubIncyEnableRouting),
|
||||
WithSUBIncyRoutingRules(SubIncyRoutingRules),
|
||||
)
|
||||
|
||||
@@ -112,6 +112,31 @@ type AllSetting struct {
|
||||
SubThemeDir string `json:"subThemeDir" form:"subThemeDir"`
|
||||
SubHideSettings bool `json:"subHideSettings" form:"subHideSettings"`
|
||||
|
||||
// Happ client customization settings (app-management / routing / UX).
|
||||
SubHappAutoDetect bool `json:"subHappAutoDetect" form:"subHappAutoDetect"`
|
||||
SubHappProviderId string `json:"subHappProviderId" form:"subHappProviderId"`
|
||||
SubHappNewUrl string `json:"subHappNewUrl" form:"subHappNewUrl"`
|
||||
SubHappFallbackUrl string `json:"subHappFallbackUrl" form:"subHappFallbackUrl"`
|
||||
SubHappSubInfoColor string `json:"subHappSubInfoColor" form:"subHappSubInfoColor"`
|
||||
SubHappSubInfoText string `json:"subHappSubInfoText" form:"subHappSubInfoText"`
|
||||
SubHappSubInfoButtonText string `json:"subHappSubInfoButtonText" form:"subHappSubInfoButtonText"`
|
||||
SubHappSubInfoButtonLink string `json:"subHappSubInfoButtonLink" form:"subHappSubInfoButtonLink"`
|
||||
SubHappSubExpire bool `json:"subHappSubExpire" form:"subHappSubExpire"`
|
||||
SubHappSubExpireButtonLink string `json:"subHappSubExpireButtonLink" form:"subHappSubExpireButtonLink"`
|
||||
SubHappNotificationExpire bool `json:"subHappNotificationExpire" form:"subHappNotificationExpire"`
|
||||
SubHappNoLimit bool `json:"subHappNoLimit" form:"subHappNoLimit"`
|
||||
SubHappAlwaysHwid bool `json:"subHappAlwaysHwid" form:"subHappAlwaysHwid"`
|
||||
SubHappTunMode string `json:"subHappTunMode" form:"subHappTunMode"`
|
||||
SubHappTunType string `json:"subHappTunType" form:"subHappTunType"`
|
||||
SubHappExcludeRoutes string `json:"subHappExcludeRoutes" form:"subHappExcludeRoutes"`
|
||||
SubHappExcludeApns bool `json:"subHappExcludeApns" form:"subHappExcludeApns"`
|
||||
SubHappColorProfile string `json:"subHappColorProfile" form:"subHappColorProfile"`
|
||||
SubHappPingType string `json:"subHappPingType" form:"subHappPingType"`
|
||||
SubHappAutoConnect bool `json:"subHappAutoConnect" form:"subHappAutoConnect"`
|
||||
SubHappAutoConnectType string `json:"subHappAutoConnectType" form:"subHappAutoConnectType"`
|
||||
SubHappPerAppMode string `json:"subHappPerAppMode" form:"subHappPerAppMode"`
|
||||
SubHappPerAppList string `json:"subHappPerAppList" form:"subHappPerAppList"`
|
||||
|
||||
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
|
||||
LdapHost string `json:"ldapHost" form:"ldapHost"`
|
||||
LdapPort int `json:"ldapPort" form:"ldapPort" validate:"gte=0,lte=65535"`
|
||||
|
||||
@@ -102,6 +102,29 @@ var defaultValueMap = map[string]string{
|
||||
"subEnableRouting": "false",
|
||||
"subRoutingRules": "",
|
||||
"subHideSettings": "false",
|
||||
"subHappAutoDetect": "false",
|
||||
"subHappProviderId": "",
|
||||
"subHappNewUrl": "",
|
||||
"subHappFallbackUrl": "",
|
||||
"subHappSubInfoColor": "blue",
|
||||
"subHappSubInfoText": "",
|
||||
"subHappSubInfoButtonText": "",
|
||||
"subHappSubInfoButtonLink": "",
|
||||
"subHappSubExpire": "false",
|
||||
"subHappSubExpireButtonLink": "",
|
||||
"subHappNotificationExpire": "false",
|
||||
"subHappNoLimit": "false",
|
||||
"subHappAlwaysHwid": "false",
|
||||
"subHappTunMode": "",
|
||||
"subHappTunType": "",
|
||||
"subHappExcludeRoutes": "",
|
||||
"subHappExcludeApns": "false",
|
||||
"subHappColorProfile": "",
|
||||
"subHappPingType": "",
|
||||
"subHappAutoConnect": "false",
|
||||
"subHappAutoConnectType": "lowestdelay",
|
||||
"subHappPerAppMode": "off",
|
||||
"subHappPerAppList": "",
|
||||
"subIncyEnableRouting": "false",
|
||||
"subIncyRoutingRules": "",
|
||||
"subListen": "",
|
||||
@@ -820,6 +843,98 @@ func (s *SettingService) GetSubHideSettings() (bool, error) {
|
||||
return s.getBool("subHideSettings")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappAutoDetect() (bool, error) {
|
||||
return s.getBool("subHappAutoDetect")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappProviderId() (string, error) {
|
||||
return s.getString("subHappProviderId")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappNewUrl() (string, error) {
|
||||
return s.getString("subHappNewUrl")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappFallbackUrl() (string, error) {
|
||||
return s.getString("subHappFallbackUrl")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubInfoColor() (string, error) {
|
||||
return s.getString("subHappSubInfoColor")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubInfoText() (string, error) {
|
||||
return s.getString("subHappSubInfoText")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubInfoButtonText() (string, error) {
|
||||
return s.getString("subHappSubInfoButtonText")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubInfoButtonLink() (string, error) {
|
||||
return s.getString("subHappSubInfoButtonLink")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubExpire() (bool, error) {
|
||||
return s.getBool("subHappSubExpire")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappSubExpireButtonLink() (string, error) {
|
||||
return s.getString("subHappSubExpireButtonLink")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappNotificationExpire() (bool, error) {
|
||||
return s.getBool("subHappNotificationExpire")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappNoLimit() (bool, error) {
|
||||
return s.getBool("subHappNoLimit")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappAlwaysHwid() (bool, error) {
|
||||
return s.getBool("subHappAlwaysHwid")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappTunMode() (string, error) {
|
||||
return s.getString("subHappTunMode")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappTunType() (string, error) {
|
||||
return s.getString("subHappTunType")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappExcludeRoutes() (string, error) {
|
||||
return s.getString("subHappExcludeRoutes")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappExcludeApns() (bool, error) {
|
||||
return s.getBool("subHappExcludeApns")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappColorProfile() (string, error) {
|
||||
return s.getString("subHappColorProfile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappPingType() (string, error) {
|
||||
return s.getString("subHappPingType")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappAutoConnect() (bool, error) {
|
||||
return s.getBool("subHappAutoConnect")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappAutoConnectType() (string, error) {
|
||||
return s.getString("subHappAutoConnectType")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappPerAppMode() (string, error) {
|
||||
return s.getString("subHappPerAppMode")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubHappPerAppList() (string, error) {
|
||||
return s.getString("subHappPerAppList")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubIncyEnableRouting() (bool, error) {
|
||||
return s.getBool("subIncyEnableRouting")
|
||||
}
|
||||
@@ -1363,6 +1478,16 @@ func validateSettingsURLs(allSetting *entity.AllSetting) error {
|
||||
// the scheme instead of forcing SanitizeHTTPURL's http(s)-only rule.
|
||||
allSetting.SubSupportUrl = common.EnsureURLScheme(allSetting.SubSupportUrl)
|
||||
allSetting.SubProfileUrl = common.EnsureURLScheme(allSetting.SubProfileUrl)
|
||||
for _, ptr := range []*string{
|
||||
&allSetting.SubHappNewUrl,
|
||||
&allSetting.SubHappFallbackUrl,
|
||||
&allSetting.SubHappSubInfoButtonLink,
|
||||
&allSetting.SubHappSubExpireButtonLink,
|
||||
} {
|
||||
if strings.TrimSpace(*ptr) != "" {
|
||||
*ptr = common.EnsureURLScheme(strings.TrimSpace(*ptr))
|
||||
}
|
||||
}
|
||||
for name, value := range map[string]*string{
|
||||
"Happ routing source": &allSetting.SubRoutingRules,
|
||||
"Clash/Mihomo routing source": &allSetting.SubClashRules,
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "قالب انتهاء الصلاحية",
|
||||
"subExpiredTemplateDesc": "قالب التكوين الوهمي عند انتهاء صلاحية اشتراك المستخدم.",
|
||||
"subTrafficDepletedTemplate": "قالب نفاد البيانات",
|
||||
"subTrafficDepletedTemplateDesc": "قالب التكوين الوهمي عند استهلاك حصة بيانات المستخدم بالكامل."
|
||||
"subTrafficDepletedTemplateDesc": "قالب التكوين الوهمي عند استهلاك حصة بيانات المستخدم بالكامل.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "تعطيل التوجيه (happ://routing/off)",
|
||||
"subHappColorBlue": "أزرق (قياسي / افتراضي)",
|
||||
"subHappColorGreen": "أخضر (نجاح)",
|
||||
"subHappColorRed": "أحمر (تحذير / خطر)",
|
||||
"subHappTunModeDefault": "افتراضي",
|
||||
"subHappTunModeSystem": "النظام (حزمة نظام التشغيل القياسية)",
|
||||
"subHappTunModeGvisor": "gVisor (حزمة مساحة المستخدم)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "افتراضي (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "عبر البروكسي (زمن استجابة GET)",
|
||||
"subHappPingProxyHead": "عبر البروكسي (زمن استجابة HEAD)",
|
||||
"subHappPingTcp": "بينج مصافحة TCP",
|
||||
"subHappPingIcmp": "بينج ICMP",
|
||||
"subHappAutoConnectLowestDelay": "أقل تأخير (العقدة الأسرع)",
|
||||
"subHappAutoConnectLastUsed": "آخر عقدة تم استخدامها",
|
||||
"subHappAutoConnectRandom": "عقدة عشوائية",
|
||||
"subHappPerAppOff": "إيقاف",
|
||||
"subHappPerAppOn": "تشغيل (بروكسي للتطبيقات المحددة فقط)",
|
||||
"subHappPerAppBypass": "تجاوز (استثناء التطبيقات المحددة)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "احفظ",
|
||||
|
||||
@@ -1584,7 +1584,99 @@
|
||||
"subExpiredTemplate": "Expired Template",
|
||||
"subExpiredTemplateDesc": "Template for the dummy config when the subscriber account has expired.",
|
||||
"subTrafficDepletedTemplate": "Traffic Depleted Template",
|
||||
"subTrafficDepletedTemplateDesc": "Template for the dummy config when subscriber traffic quota is exhausted."
|
||||
"subTrafficDepletedTemplateDesc": "Template for the dummy config when subscriber traffic quota is exhausted.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Disable Routing (happ://routing/off)",
|
||||
"subHappColorBlue": "Blue (Standard / Default)",
|
||||
"subHappColorGreen": "Green (Success)",
|
||||
"subHappColorRed": "Red (Warning / Danger)",
|
||||
"subHappTunModeDefault": "Default",
|
||||
"subHappTunModeSystem": "System (Standard OS Stack)",
|
||||
"subHappTunModeGvisor": "gVisor (Userspace Stack)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Default (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "via Proxy (GET Latency)",
|
||||
"subHappPingProxyHead": "via Proxy (HEAD Latency)",
|
||||
"subHappPingTcp": "TCP Handshake Ping",
|
||||
"subHappPingIcmp": "ICMP Ping",
|
||||
"subHappAutoConnectLowestDelay": "Lowest Delay (Fastest Node)",
|
||||
"subHappAutoConnectLastUsed": "Last Used Node",
|
||||
"subHappAutoConnectRandom": "Random Node",
|
||||
"subHappPerAppOff": "Off",
|
||||
"subHappPerAppOn": "On (Proxy Only Listed Apps)",
|
||||
"subHappPerAppBypass": "Bypass (Exclude Listed Apps)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "Save",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Plantilla de expirado",
|
||||
"subExpiredTemplateDesc": "Plantilla para el nodo ficticio cuando la suscripción ha expirado.",
|
||||
"subTrafficDepletedTemplate": "Plantilla de tráfico agotado",
|
||||
"subTrafficDepletedTemplateDesc": "Plantilla para el nodo ficticio cuando el límite de tráfico se ha agotado."
|
||||
"subTrafficDepletedTemplateDesc": "Plantilla para el nodo ficticio cuando el límite de tráfico se ha agotado.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Desactivar enrutamiento (happ://routing/off)",
|
||||
"subHappColorBlue": "Azul (estándar / predeterminado)",
|
||||
"subHappColorGreen": "Verde (éxito)",
|
||||
"subHappColorRed": "Rojo (advertencia / peligro)",
|
||||
"subHappTunModeDefault": "Predeterminado",
|
||||
"subHappTunModeSystem": "Sistema (pila estándar del SO)",
|
||||
"subHappTunModeGvisor": "gVisor (pila de espacio de usuario)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Predeterminado (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Vía proxy (latencia GET)",
|
||||
"subHappPingProxyHead": "Vía proxy (latencia HEAD)",
|
||||
"subHappPingTcp": "Ping de saludo TCP",
|
||||
"subHappPingIcmp": "Ping ICMP",
|
||||
"subHappAutoConnectLowestDelay": "Menor latencia (nodo más rápido)",
|
||||
"subHappAutoConnectLastUsed": "Último nodo utilizado",
|
||||
"subHappAutoConnectRandom": "Nodo aleatorio",
|
||||
"subHappPerAppOff": "Desactivado",
|
||||
"subHappPerAppOn": "Activado (solo apps de la lista)",
|
||||
"subHappPerAppBypass": "Omitir (excluir apps de la lista)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "Guardar configuración",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "قالب پیام انقضا",
|
||||
"subExpiredTemplateDesc": "قالب کانفیگ نمایشی زمانی که اشتراک کاربر منقضی شده است.",
|
||||
"subTrafficDepletedTemplate": "قالب پیام اتمام حجم",
|
||||
"subTrafficDepletedTemplateDesc": "قالب کانفیگ نمایشی زمانی که حجم اشتراک کاربر به پایان رسیده است."
|
||||
"subTrafficDepletedTemplateDesc": "قالب کانفیگ نمایشی زمانی که حجم اشتراک کاربر به پایان رسیده است.",
|
||||
"subHappAutoDetect": "تشخیص خودکار کلاینت Happ",
|
||||
"subHappAutoDetectDesc": "تزریق خودکار هدرها و روتینگ Happ هنگامی که User-Agent کلاینت مربوط به برنامه Happ باشد.",
|
||||
"subHappProviderId": "شناسه ارائهدهنده (Provider ID)",
|
||||
"subHappProviderIdDesc": "شناسه یکتا جهت مدیریت کلاینت Happ، اتصال به کانفیگ ریموت و مهاجرت کاربران.",
|
||||
"subHappNewUrl": "لینک اشتراک جدید (مهاجرت)",
|
||||
"subHappNewUrlDesc": "آدرس اشتراک جدید جهت انتقال خودکار؛ برنامه Happ پس از دریافت، آدرس اشتراک را به این لینک تغییر میدهد.",
|
||||
"subHappFallbackUrl": "لینک اشتراک پشتیبان (Fallback)",
|
||||
"subHappFallbackUrlDesc": "آدرس اشتراک جایگزین در صورت در دسترس نبودن سرور اصلی اشتراک.",
|
||||
"subHappSubInfoText": "متن بنر اعلانات",
|
||||
"subHappSubInfoTextDesc": "پیام اعلان سفارشی در بالای صفحه برنامه Happ (حداکثر ۲۰۰ نویسه).",
|
||||
"subHappSubInfoColor": "رنگ بنر اعلانات",
|
||||
"subHappSubInfoColorDesc": "تم رنگی بنر اعلان (پیشفرض، اطلاعرسانی، موفقیت، هشدار یا اخطار).",
|
||||
"subHappSubInfoButtonText": "متن دکمه بنر",
|
||||
"subHappSubInfoButtonTextDesc": "عنوان دکمه اقدام در بنر اعلان (حداکثر ۲۵ نویسه).",
|
||||
"subHappSubInfoButtonLink": "لینک دکمه بنر",
|
||||
"subHappSubInfoButtonLinkDesc": "آدرسی که با کلیک روی دکمه بنر باز میشود.",
|
||||
"subHappSubExpire": "بنر اشتراک منقضیشده",
|
||||
"subHappSubExpireDesc": "نمایش بنر تمدید در برنامه Happ در صورت اتمام ترافیک یا زمان اشتراک کاربر.",
|
||||
"subHappSubExpireButtonLink": "لینک تمدید اشتراک",
|
||||
"subHappSubExpireButtonLinkDesc": "آدرس صفحه خرید یا تمدید اشتراک منقضیشده.",
|
||||
"subHappNotificationExpire": "اعلان انقضای اشتراک",
|
||||
"subHappNotificationExpireDesc": "نمایش هشدار انقضای اشتراک به کاربر پیش از پایان اعتبار در برنامه Happ.",
|
||||
"subHappNoLimit": "حذف محدودیت تعداد قوانین",
|
||||
"subHappNoLimitDesc": "اجازه اعمال تعداد نامحدود قوانین روتینگ بدون برش خوردن روی سیستمهای تلفن همراه.",
|
||||
"subHappAlwaysHwid": "الزام شناسه سختافزاری (HWID)",
|
||||
"subHappAlwaysHwidDesc": "قفل کردن درخواستهای اشتراک به شناسه سختافزاری دستگاه جهت جلوگیری از اشتراکگذاری اکانت.",
|
||||
"subHappTunMode": "حالت تونل (TUN Mode)",
|
||||
"subHappTunModeDesc": "حالت رابط شبکه مجازی TUN در برنامه Happ (پیشفرض، سیستمی یا سختگیرانه).",
|
||||
"subHappTunType": "موتور شبکه TUN",
|
||||
"subHappTunTypeDesc": "پشته شبکه مورد استفاده برای TUN (سیستمی، gVisor یا ترکیبی).",
|
||||
"subHappExcludeRoutes": "مستثنی کردن مسیرهای CIDR",
|
||||
"subHappExcludeRoutesDesc": "رنجهای IP جدا شده با کاما جهت دور زدن تونل VPN (مانند 192.168.0.0/16, 10.0.0.0/8).",
|
||||
"subHappExcludeApns": "مستثنی کردن سرویسهای اعلان اپل (APNs)",
|
||||
"subHappExcludeApnsDesc": "دور زدن سرویسهای اعلان اپل برای اطمینان از دریافت پایدار ناتیفیکیشنها در iOS.",
|
||||
"subHappColorProfile": "پروفایل رنگ و پوسته",
|
||||
"subHappColorProfileDesc": "پوسته ظاهری برنامه Happ (بنفش، فیروزهای، سایبرپانک یا JSON سفارشی).",
|
||||
"subHappPingType": "روش تست پینگ",
|
||||
"subHappPingTypeDesc": "پروتکل اندازهگیری تأخیر گرهها در برنامه Happ (icmp، tcp یا http).",
|
||||
"subHappAutoConnect": "اتصال خودکار هنگام اجرا",
|
||||
"subHappAutoConnectDesc": "اتصال خودکار به ویپیان با باز شدن برنامه Happ.",
|
||||
"subHappAutoConnectType": "راهبرد اتصال خودکار",
|
||||
"subHappAutoConnectTypeDesc": "هدف اتصال خودکار: سریعترین سرور یا آخرین سرور استفادهشده.",
|
||||
"subHappPerAppMode": "پراکسی انتخابی برنامهها در اندروید",
|
||||
"subHappPerAppModeDesc": "مدیریت عبور ترافیک برنامههای اندروید: خاموش، عبور فقط برنامههای منتخب یا مستثنی کردن آنها.",
|
||||
"subHappPerAppList": "نام بستههای برنامههای اندروید",
|
||||
"subHappPerAppListDesc": "نام بستههای اپلیکیشنهای اندروید جدا شده با کاما (مانند com.telegram.messenger).",
|
||||
"subHappPresetIran": "دور زدن سایتهای ایران (Iran Bypass)",
|
||||
"subHappPresetChina": "دور زدن چین (China Direct)",
|
||||
"subHappPresetAdblock": "مسدودسازی تبلیغات (AdBlock)",
|
||||
"subHappPresetGlobal": "پراکسی کل ترافیک (Global)",
|
||||
"subHappPresetOff": "غیرفعالسازی روتینگ (happ://routing/off)",
|
||||
"subHappColorBlue": "آبی (استاندارد / پیشفرض)",
|
||||
"subHappColorGreen": "سبز (موفقیت)",
|
||||
"subHappColorRed": "قرمز (هشدار / خطر)",
|
||||
"subHappTunModeDefault": "پیشفرض",
|
||||
"subHappTunModeSystem": "سیستم (استک استاندارد سیستمعامل)",
|
||||
"subHappTunModeGvisor": "gVisor (استک فضای کاربری)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "پیشفرض (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "از طریق پروکسی (تأخیر GET)",
|
||||
"subHappPingProxyHead": "از طریق پروکسی (تأخیر HEAD)",
|
||||
"subHappPingTcp": "پینگ دستدادن TCP",
|
||||
"subHappPingIcmp": "پینگ ICMP",
|
||||
"subHappAutoConnectLowestDelay": "کمترین تأخیر (سریعترین نود)",
|
||||
"subHappAutoConnectLastUsed": "آخرین نود استفادهشده",
|
||||
"subHappAutoConnectRandom": "نود تصادفی",
|
||||
"subHappPerAppOff": "خاموش",
|
||||
"subHappPerAppOn": "روشن (فقط برنامههای فهرستشده)",
|
||||
"subHappPerAppBypass": "بایپس (مستثنیکردن برنامههای فهرستشده)",
|
||||
"subHappPresetApplied": "الگوی روتینگ Happ اعمال شد",
|
||||
"subHappPresets": "الگوهای آماده روتینگ",
|
||||
"subHappPresetsDesc": "الگوهای آماده و بهینهسازیشده برای روتینگ در برنامه Happ.",
|
||||
"subHappVisualBuilder": "سازنده بصری قوانین",
|
||||
"subHappVisualBuilderDesc": "ایجاد آسان دیپلینک روتینگ سفارشی بر اساس دامنهها و آیپیها.",
|
||||
"subHappBuildDeeplink": "تولید دیپلینک",
|
||||
"subHappModalTitle": "سازنده بصری قوانین روتینگ Happ",
|
||||
"subHappDirectDomains": "دامنههای مستقیم (Direct)",
|
||||
"subHappProxyDomains": "دامنههای پراکسی (Proxy)",
|
||||
"subHappBlockDomains": "دامنههای مسدود (Block)",
|
||||
"subHappDirectIPs": "آیپیهای مستقیم (Direct CIDRs)",
|
||||
"subHappProxyIPs": "آیپیهای پراکسی (Proxy CIDRs)",
|
||||
"subHappBlockIPs": "آیپیهای مسدود (Block CIDRs)",
|
||||
"subHappDeeplinkGenerated": "دیپلینک تولید و در قوانین روتینگ اعمال شد",
|
||||
"subHappGroupRouting": "قوانین و روتینگ",
|
||||
"subHappGroupBanners": "اعلانات و بنرهای هوشمند",
|
||||
"subHappGroupNetwork": "تنظیمات شبکه و TUN",
|
||||
"subHappGroupThemes": "ظاهر و پوسته برنامه",
|
||||
"subHappGroupFailover": "مهاجرت و مدیریت کلاینت",
|
||||
"subHappGroupAndroid": "پراکسی برنامههای اندروید"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "ذخیره",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Templat Kedaluwarsa",
|
||||
"subExpiredTemplateDesc": "Templat untuk konfigurasi dummy saat akun langganan telah kedaluwarsa.",
|
||||
"subTrafficDepletedTemplate": "Templat Kuota Habis",
|
||||
"subTrafficDepletedTemplateDesc": "Templat untuk konfigurasi dummy saat kuota data langganan telah habis."
|
||||
"subTrafficDepletedTemplateDesc": "Templat untuk konfigurasi dummy saat kuota data langganan telah habis.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Nonaktifkan Perutean (happ://routing/off)",
|
||||
"subHappColorBlue": "Biru (Standar / Default)",
|
||||
"subHappColorGreen": "Hijau (Sukses)",
|
||||
"subHappColorRed": "Merah (Peringatan / Bahaya)",
|
||||
"subHappTunModeDefault": "Default",
|
||||
"subHappTunModeSystem": "Sistem (Stack OS Standar)",
|
||||
"subHappTunModeGvisor": "gVisor (Stack Userspace)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Default (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Melalui Proxy (Latensi GET)",
|
||||
"subHappPingProxyHead": "Melalui Proxy (Latensi HEAD)",
|
||||
"subHappPingTcp": "Ping Handshake TCP",
|
||||
"subHappPingIcmp": "Ping ICMP",
|
||||
"subHappAutoConnectLowestDelay": "Latensi Terendah (Node Tercepat)",
|
||||
"subHappAutoConnectLastUsed": "Node Terakhir Digunakan",
|
||||
"subHappAutoConnectRandom": "Node Acak",
|
||||
"subHappPerAppOff": "Mati",
|
||||
"subHappPerAppOn": "Nyala (Hanya Proksikan Aplikasi Terdaftar)",
|
||||
"subHappPerAppBypass": "Bypass (Kecualikan Aplikasi Terdaftar)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "Simpan",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "期限切れテンプレート",
|
||||
"subExpiredTemplateDesc": "サブスクリプションの有効期限が切れた際のダミー構成用テンプレート。",
|
||||
"subTrafficDepletedTemplate": "通信量超過テンプレート",
|
||||
"subTrafficDepletedTemplateDesc": "サブスクリプションの通信量が上限に達した際のダミー構成用テンプレート。"
|
||||
"subTrafficDepletedTemplateDesc": "サブスクリプションの通信量が上限に達した際のダミー構成用テンプレート。",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "ルーティングを無効化 (happ://routing/off)",
|
||||
"subHappColorBlue": "ブルー (標準 / デフォルト)",
|
||||
"subHappColorGreen": "グリーン (成功)",
|
||||
"subHappColorRed": "レッド (警告 / 危険)",
|
||||
"subHappTunModeDefault": "デフォルト",
|
||||
"subHappTunModeSystem": "システム (標準OSスタック)",
|
||||
"subHappTunModeGvisor": "gVisor (ユーザー空間スタック)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "デフォルト (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "プロキシ経由 (GET遅延)",
|
||||
"subHappPingProxyHead": "プロキシ経由 (HEAD遅延)",
|
||||
"subHappPingTcp": "TCPハンドシェイク Ping",
|
||||
"subHappPingIcmp": "ICMP Ping",
|
||||
"subHappAutoConnectLowestDelay": "最小遅延 (最速ノード)",
|
||||
"subHappAutoConnectLastUsed": "最後に使用したノード",
|
||||
"subHappAutoConnectRandom": "ランダムノード",
|
||||
"subHappPerAppOff": "オフ",
|
||||
"subHappPerAppOn": "オン (リストされたアプリのみプロキシ)",
|
||||
"subHappPerAppBypass": "バイパス (リストされたアプリを除外)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"importRules": "ルールをインポート",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Modelo expirado",
|
||||
"subExpiredTemplateDesc": "Modelo para a configuração fictícia quando a assinatura expirou.",
|
||||
"subTrafficDepletedTemplate": "Modelo de tráfego esgotado",
|
||||
"subTrafficDepletedTemplateDesc": "Modelo para a configuração fictícia quando a cota de tráfego foi esgotada."
|
||||
"subTrafficDepletedTemplateDesc": "Modelo para a configuração fictícia quando a cota de tráfego foi esgotada.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Desativar roteamento (happ://routing/off)",
|
||||
"subHappColorBlue": "Azul (padrão)",
|
||||
"subHappColorGreen": "Verde (sucesso)",
|
||||
"subHappColorRed": "Vermelho (aviso / perigo)",
|
||||
"subHappTunModeDefault": "Padrão",
|
||||
"subHappTunModeSystem": "Sistema (pilha padrão do SO)",
|
||||
"subHappTunModeGvisor": "gVisor (pilha de espaço do usuário)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Padrão (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Via proxy (latência GET)",
|
||||
"subHappPingProxyHead": "Via proxy (latência HEAD)",
|
||||
"subHappPingTcp": "Ping de handshake TCP",
|
||||
"subHappPingIcmp": "Ping ICMP",
|
||||
"subHappAutoConnectLowestDelay": "Menor latência (nó mais rápido)",
|
||||
"subHappAutoConnectLastUsed": "Último nó usado",
|
||||
"subHappAutoConnectRandom": "Nó aleatório",
|
||||
"subHappPerAppOff": "Desativado",
|
||||
"subHappPerAppOn": "Ativado (apenas apps listados)",
|
||||
"subHappPerAppBypass": "Desviar (excluir apps listados)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"importRules": "Importar regras",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Шаблон истекшей подписки",
|
||||
"subExpiredTemplateDesc": "Шаблон для фиктивного узла, когда срок действия подписки истек.",
|
||||
"subTrafficDepletedTemplate": "Шаблон исчерпания трафика",
|
||||
"subTrafficDepletedTemplateDesc": "Шаблон для фиктивного узла, когда лимит трафика исчерпан."
|
||||
"subTrafficDepletedTemplateDesc": "Шаблон для фиктивного узла, когда лимит трафика исчерпан.",
|
||||
"subHappAutoDetect": "Автоопределение заголовков Happ",
|
||||
"subHappAutoDetectDesc": "Автоматически добавлять заголовки и маршрутизацию для клиентов с User-Agent Happ.",
|
||||
"subHappProviderId": "Идентификатор провайдера (Provider ID)",
|
||||
"subHappProviderIdDesc": "Уникальный идентификатор для управления приложением, удаленной конфигурации и миграции.",
|
||||
"subHappNewUrl": "Новый URL подписки (Миграция)",
|
||||
"subHappNewUrlDesc": "Новый адрес подписки. При получении клиент Happ автоматически переключится на данный URL.",
|
||||
"subHappFallbackUrl": "Резервный URL подписки (Fallback)",
|
||||
"subHappFallbackUrlDesc": "Резервный адрес подписки на случай недоступности основного сервера.",
|
||||
"subHappSubInfoText": "Текст информационного баннера",
|
||||
"subHappSubInfoTextDesc": "Пользовательский баннер в верхней части приложения Happ (до 200 символов).",
|
||||
"subHappSubInfoColor": "Цвет баннера",
|
||||
"subHappSubInfoColorDesc": "Цветовая тема информационного блока: blue (синий), green (зеленый), red (красный).",
|
||||
"subHappSubInfoButtonText": "Текст кнопки баннера",
|
||||
"subHappSubInfoButtonTextDesc": "Название кнопки действия в информационном блоке (до 25 символов).",
|
||||
"subHappSubInfoButtonLink": "Ссылка кнопки баннера",
|
||||
"subHappSubInfoButtonLinkDesc": "URL-адрес, открывающийся при нажатии на кнопку баннера.",
|
||||
"subHappSubExpire": "Баннер об окончании подписки",
|
||||
"subHappSubExpireDesc": "Отображать баннер о скором окончании или истечении срока действия подписки.",
|
||||
"subHappSubExpireButtonLink": "Ссылка для продления подписки",
|
||||
"subHappSubExpireButtonLinkDesc": "URL для кнопки «Продлить» при истечении срока подписки.",
|
||||
"subHappNotificationExpire": "Уведомление об окончании подписки",
|
||||
"subHappNotificationExpireDesc": "Отправлять напоминания пользователю за 3 дня до окончания подписки.",
|
||||
"subHappNoLimit": "Режим без ограничений (No Limit)",
|
||||
"subHappNoLimitDesc": "Увеличивает лимит оперативной памяти и снимает ограничения на количество правил.",
|
||||
"subHappAlwaysHwid": "Обязательный HWID",
|
||||
"subHappAlwaysHwidDesc": "Запрещает пользователю отключать передачу идентификатора устройства (HWID).",
|
||||
"subHappTunMode": "Режим TUN",
|
||||
"subHappTunModeDesc": "Сетевой стек для TUN: system (системный) или gvisor (пользовательский стек).",
|
||||
"subHappTunType": "Ядро туنнеля (TUN Type)",
|
||||
"subHappTunTypeDesc": "Выбор ядра туннеля: singbox, tun2proxy, default (Happ TUN) или xray.",
|
||||
"subHappExcludeRoutes": "Исключения маршрутов (CIDR)",
|
||||
"subHappExcludeRoutesDesc": "Список подсетей и IP-адресов через запятую, трафик которых идет мимо туннеля.",
|
||||
"subHappExcludeApns": "Исключить push-уведомления Apple (APNS)",
|
||||
"subHappExcludeApnsDesc": "Трафик уведомлений Apple направляется напрямую для надежной доставки на iOS.",
|
||||
"subHappColorProfile": "Цветовая тема клиента",
|
||||
"subHappColorProfileDesc": "Тема оформления интерфейса Happ: violet, turquoise, cyberpunk или свой JSON.",
|
||||
"subHappPingType": "Метод проверки пинга",
|
||||
"subHappPingTypeDesc": "Тип проверки задержки: via Proxy (GET), via Proxy (HEAD), TCP или ICMP.",
|
||||
"subHappAutoConnect": "Автоподключение при запуске",
|
||||
"subHappAutoConnectDesc": "Автоматически подключаться к серверу при запуске приложения.",
|
||||
"subHappAutoConnectType": "Критерий автоподключения",
|
||||
"subHappAutoConnectTypeDesc": "Сервер для автоподключения: lowestdelay (наименьший пинг), lastused (последний) или random.",
|
||||
"subHappPerAppMode": "Прокси для приложений (Android)",
|
||||
"subHappPerAppModeDesc": "Режим раздельного туннелирования: off (выкл), on (только выбранные) или bypass (все кроме выбранных).",
|
||||
"subHappPerAppList": "Пакеты приложений Android",
|
||||
"subHappPerAppListDesc": "Список идентификаторов пакетов через запятую (например, org.telegram.messenger).",
|
||||
"subHappPresetIran": "Обход сайтов Ирана (Iran Bypass)",
|
||||
"subHappPresetChina": "Обход сайтов Китая (China Direct)",
|
||||
"subHappPresetAdblock": "Блокировка рекламы (AdBlock)",
|
||||
"subHappPresetGlobal": "Полный прокси (Global)",
|
||||
"subHappPresetOff": "Отключить маршрутизацию (happ://routing/off)",
|
||||
"subHappColorBlue": "Синий (стандартный / по умолчанию)",
|
||||
"subHappColorGreen": "Зеленый (успех)",
|
||||
"subHappColorRed": "Красный (предупреждение / опасность)",
|
||||
"subHappTunModeDefault": "По умолчанию",
|
||||
"subHappTunModeSystem": "Системный (стандартный стек ОС)",
|
||||
"subHappTunModeGvisor": "gVisor (пользовательский стек)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "По умолчанию (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Через прокси (задержка GET)",
|
||||
"subHappPingProxyHead": "Через прокси (задержка HEAD)",
|
||||
"subHappPingTcp": "TCP-рукопожатие (пинг)",
|
||||
"subHappPingIcmp": "ICMP-пинг",
|
||||
"subHappAutoConnectLowestDelay": "Минимальная задержка (быстрый узел)",
|
||||
"subHappAutoConnectLastUsed": "Последний использованный узел",
|
||||
"subHappAutoConnectRandom": "Случайный узел",
|
||||
"subHappPerAppOff": "Выкл",
|
||||
"subHappPerAppOn": "Вкл (только выбранные приложения)",
|
||||
"subHappPerAppBypass": "Обход (исключить выбранные приложения)",
|
||||
"subHappPresetApplied": "Пресет маршрутизации Happ успешно применен",
|
||||
"subHappPresets": "Пресеты маршрутизации",
|
||||
"subHappPresetsDesc": "Готовые конфигурации маршрутизации для клиентов Happ.",
|
||||
"subHappVisualBuilder": "Визуальный конструктор правил",
|
||||
"subHappVisualBuilderDesc": "Генератор диплинков маршрутизации на основе списков доменов и IP.",
|
||||
"subHappBuildDeeplink": "Сгенерировать диплинк",
|
||||
"subHappModalTitle": "Визуальный конструктор правил маршрутизации Happ",
|
||||
"subHappDirectDomains": "Прямые домены (Direct)",
|
||||
"subHappProxyDomains": "Проксируемые домены (Proxy)",
|
||||
"subHappBlockDomains": "Заблокированные домены (Block)",
|
||||
"subHappDirectIPs": "Прямые IP / CIDR",
|
||||
"subHappProxyIPs": "Проксируемые IP / CIDR",
|
||||
"subHappBlockIPs": "Заблокированные IP / CIDR",
|
||||
"subHappDeeplinkGenerated": "Диплинк сгенерирован и применен к правилам маршрутизации",
|
||||
"subHappGroupRouting": "Маршрутизация и правила",
|
||||
"subHappGroupBanners": "Баннеры и уведомления",
|
||||
"subHappGroupNetwork": "Сетевые настройки и TUN",
|
||||
"subHappGroupThemes": "Внешний вид и темы",
|
||||
"subHappGroupFailover": "Миграция и управление",
|
||||
"subHappGroupAndroid": "Прокси приложений Android"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"importRules": "Импорт правил",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Süresi Dolmuş Şablonu",
|
||||
"subExpiredTemplateDesc": "Abonelik süresi dolduğunda sahte yapılandırma için kullanılacak şablon.",
|
||||
"subTrafficDepletedTemplate": "Trafik Tükendi Şablonu",
|
||||
"subTrafficDepletedTemplateDesc": "Abonelik trafik kotası bittiğinde sahte yapılandırma için kullanılacak şablon."
|
||||
"subTrafficDepletedTemplateDesc": "Abonelik trafik kotası bittiğinde sahte yapılandırma için kullanılacak şablon.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Yönlendirmeyi Devre Dışı Bırak (happ://routing/off)",
|
||||
"subHappColorBlue": "Mavi (Standart / Varsayılan)",
|
||||
"subHappColorGreen": "Yeşil (Başarılı)",
|
||||
"subHappColorRed": "Kırmızı (Uyarı / Tehlike)",
|
||||
"subHappTunModeDefault": "Varsayılan",
|
||||
"subHappTunModeSystem": "Sistem (Standart İşletim Sistemi Yığını)",
|
||||
"subHappTunModeGvisor": "gVisor (Kullanıcı Alanı Yığını)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Varsayılan (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Proxy Üzerinden (GET Gecikmesi)",
|
||||
"subHappPingProxyHead": "Proxy Üzerinden (HEAD Gecikmesi)",
|
||||
"subHappPingTcp": "TCP El Sıkışma Pingi",
|
||||
"subHappPingIcmp": "ICMP Ping",
|
||||
"subHappAutoConnectLowestDelay": "En Düşük Gecikme (En Hızlı Düğüm)",
|
||||
"subHappAutoConnectLastUsed": "Son Kullanılan Düğüm",
|
||||
"subHappAutoConnectRandom": "Rastgele Düğüm",
|
||||
"subHappPerAppOff": "Kapalı",
|
||||
"subHappPerAppOn": "Açık (Yalnızca Listelenen Uygulamalar)",
|
||||
"subHappPerAppBypass": "Atla (Listelenen Uygulamaları Hariç Tut)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "Kaydet",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Шаблон закінчення терміну",
|
||||
"subExpiredTemplateDesc": "Шаблон для фіктивного вузла, коли термін дії підписки закінчився.",
|
||||
"subTrafficDepletedTemplate": "Шаблон вичерпання трафіку",
|
||||
"subTrafficDepletedTemplateDesc": "Шаблон для фіктивного вузла, коли ліміт трафіку вичерпано."
|
||||
"subTrafficDepletedTemplateDesc": "Шаблон для фіктивного вузла, коли ліміт трафіку вичерпано.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Вимкнути маршрутизацію (happ://routing/off)",
|
||||
"subHappColorBlue": "Синій (стандартний / за замовчуванням)",
|
||||
"subHappColorGreen": "Зелений (успіх)",
|
||||
"subHappColorRed": "Червоний (попередження / небезпека)",
|
||||
"subHappTunModeDefault": "За замовчуванням",
|
||||
"subHappTunModeSystem": "Системний (стандартний стек ОС)",
|
||||
"subHappTunModeGvisor": "gVisor (користувацький стек)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "За замовчуванням (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Через проксі (затримка GET)",
|
||||
"subHappPingProxyHead": "Через проксі (затримка HEAD)",
|
||||
"subHappPingTcp": "TCP-рукостискання (пінг)",
|
||||
"subHappPingIcmp": "ICMP-пінг",
|
||||
"subHappAutoConnectLowestDelay": "Найменша затримка (найшвидший вузол)",
|
||||
"subHappAutoConnectLastUsed": "Останній використаний вузол",
|
||||
"subHappAutoConnectRandom": "Випадковий вузол",
|
||||
"subHappPerAppOff": "Вимк",
|
||||
"subHappPerAppOn": "Увімк (тільки обрані програми)",
|
||||
"subHappPerAppBypass": "Обхід (виключити обрані програми)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "Зберегти",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "Mẫu hết hạn",
|
||||
"subExpiredTemplateDesc": "Mẫu cho cấu hình ảo khi tài khoản đăng ký đã hết hạn.",
|
||||
"subTrafficDepletedTemplate": "Mẫu hết dung lượng",
|
||||
"subTrafficDepletedTemplateDesc": "Mẫu cho cấu hình ảo khi tài khoản đăng ký đã dùng hết dung lượng."
|
||||
"subTrafficDepletedTemplateDesc": "Mẫu cho cấu hình ảo khi tài khoản đăng ký đã dùng hết dung lượng.",
|
||||
"subHappAutoDetect": "Happ Header Auto-Detection",
|
||||
"subHappAutoDetectDesc": "Automatically inject Happ routing and headers when client User-Agent indicates Happ.",
|
||||
"subHappProviderId": "Provider ID",
|
||||
"subHappProviderIdDesc": "Unique provider identifier for Happ client management, remote configuration binding, and migration.",
|
||||
"subHappNewUrl": "New Subscription URL",
|
||||
"subHappNewUrlDesc": "Target URL for automatic client migration. When set, Happ clients will migrate to this subscription link.",
|
||||
"subHappFallbackUrl": "Fallback Subscription URL",
|
||||
"subHappFallbackUrlDesc": "Backup subscription address used by Happ if the primary subscription URL becomes unreachable.",
|
||||
"subHappSubInfoText": "Banner Announcement Text",
|
||||
"subHappSubInfoTextDesc": "Custom announcement banner displayed at the top of the Happ client (max 200 characters).",
|
||||
"subHappSubInfoColor": "Banner Accent Color",
|
||||
"subHappSubInfoColorDesc": "Color theme style for the announcement banner.",
|
||||
"subHappSubInfoButtonText": "Banner Button Text",
|
||||
"subHappSubInfoButtonTextDesc": "Button label displayed inside the announcement banner (max 25 characters).",
|
||||
"subHappSubInfoButtonLink": "Banner Button Link",
|
||||
"subHappSubInfoButtonLinkDesc": "Target URL opened when the user clicks the banner action button.",
|
||||
"subHappSubExpire": "Expired Subscription Banner",
|
||||
"subHappSubExpireDesc": "Show an expired subscription banner in Happ when the user's traffic or validity has ended.",
|
||||
"subHappSubExpireButtonLink": "Renewal Link",
|
||||
"subHappSubExpireButtonLinkDesc": "Target URL opened when the user clicks the renewal button on an expired subscription.",
|
||||
"subHappNotificationExpire": "Expiration Notifications",
|
||||
"subHappNotificationExpireDesc": "Instruct Happ to alert the user in advance before their subscription expires.",
|
||||
"subHappNoLimit": "Bypass Rule Limit",
|
||||
"subHappNoLimitDesc": "Allow routing rule count to exceed default mobile platform limits without truncation.",
|
||||
"subHappAlwaysHwid": "Enforce Hardware ID (HWID)",
|
||||
"subHappAlwaysHwidDesc": "Bind subscription sessions to client device hardware identifier for enhanced security.",
|
||||
"subHappTunMode": "TUN Mode",
|
||||
"subHappTunModeDesc": "TUN virtual network interface routing mode: default, system, or strict.",
|
||||
"subHappTunType": "TUN Engine",
|
||||
"subHappTunTypeDesc": "Underlying network stack implementation for TUN: system, gVisor, or mixed.",
|
||||
"subHappExcludeRoutes": "Exclude CIDR Routes",
|
||||
"subHappExcludeRoutesDesc": "Comma-separated IP CIDRs (e.g. 192.168.0.0/16, 10.0.0.0/8) to bypass the VPN tunnel.",
|
||||
"subHappExcludeApns": "Exclude Apple APNs",
|
||||
"subHappExcludeApnsDesc": "Bypass Apple Push Notification services to maintain reliable background notifications on iOS.",
|
||||
"subHappColorProfile": "Client Color Theme",
|
||||
"subHappColorProfileDesc": "Client color theme profile: default, violet, turquoise, cyberpunk, or custom JSON theme.",
|
||||
"subHappPingType": "Latency Ping Method",
|
||||
"subHappPingTypeDesc": "Measurement protocol used by Happ for node latency testing: icmp, tcp, or http.",
|
||||
"subHappAutoConnect": "Auto-Connect on Launch",
|
||||
"subHappAutoConnectDesc": "Instruct Happ to automatically connect to VPN when the application starts.",
|
||||
"subHappAutoConnectType": "Auto-Connect Target",
|
||||
"subHappAutoConnectTypeDesc": "Connection target strategy for auto-connect: fastest node or last used node.",
|
||||
"subHappPerAppMode": "Android Per-App Proxy Mode",
|
||||
"subHappPerAppModeDesc": "Control Android application routing: off, include (proxy only listed apps), or exclude (bypass listed apps).",
|
||||
"subHappPerAppList": "Android Package Names",
|
||||
"subHappPerAppListDesc": "Comma-separated package names of Android applications to include or exclude (e.g. com.telegram.messenger).",
|
||||
"subHappPresetIran": "Iran Bypass",
|
||||
"subHappPresetChina": "China Direct",
|
||||
"subHappPresetAdblock": "AdBlock",
|
||||
"subHappPresetGlobal": "Full Proxy",
|
||||
"subHappPresetOff": "Tắt định tuyến (happ://routing/off)",
|
||||
"subHappColorBlue": "Xanh dương (Tiêu chuẩn / Mặc định)",
|
||||
"subHappColorGreen": "Xanh lá (Thành công)",
|
||||
"subHappColorRed": "Đỏ (Cảnh báo / Nguy hiểm)",
|
||||
"subHappTunModeDefault": "Mặc định",
|
||||
"subHappTunModeSystem": "Hệ thống (Ngăn xếp hệ điều hành tiêu chuẩn)",
|
||||
"subHappTunModeGvisor": "gVisor (Ngăn xếp không gian người dùng)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "Mặc định (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "Qua Proxy (Độ trễ GET)",
|
||||
"subHappPingProxyHead": "Qua Proxy (Độ trễ HEAD)",
|
||||
"subHappPingTcp": "Ping bắt tay TCP",
|
||||
"subHappPingIcmp": "Ping ICMP",
|
||||
"subHappAutoConnectLowestDelay": "Độ trễ thấp nhất (Nút nhanh nhất)",
|
||||
"subHappAutoConnectLastUsed": "Nút sử dụng gần nhất",
|
||||
"subHappAutoConnectRandom": "Nút ngẫu nhiên",
|
||||
"subHappPerAppOff": "Tắt",
|
||||
"subHappPerAppOn": "Bật (Chỉ proxy ứng dụng trong danh sách)",
|
||||
"subHappPerAppBypass": "Bỏ qua (Loại trừ ứng dụng trong danh sách)",
|
||||
"subHappPresetApplied": "Happ preset applied to routing rules",
|
||||
"subHappPresets": "Routing Presets",
|
||||
"subHappPresetsDesc": "Pre-configured routing rule presets tailored for Happ clients.",
|
||||
"subHappVisualBuilder": "Visual Rule Generator",
|
||||
"subHappVisualBuilderDesc": "Create custom routing deeplink from domain and IP lists.",
|
||||
"subHappBuildDeeplink": "Generate Deeplink",
|
||||
"subHappModalTitle": "Happ Visual Routing Rule Generator",
|
||||
"subHappDirectDomains": "Direct Domains (Bypass)",
|
||||
"subHappProxyDomains": "Proxy Domains (Tunnel)",
|
||||
"subHappBlockDomains": "Blocked Domains (Ad/Malware)",
|
||||
"subHappDirectIPs": "Direct IPs / CIDRs",
|
||||
"subHappProxyIPs": "Proxy IPs / CIDRs",
|
||||
"subHappBlockIPs": "Blocked IPs / CIDRs",
|
||||
"subHappDeeplinkGenerated": "Deeplink generated and applied to routing rules",
|
||||
"subHappGroupRouting": "Routing & Rules",
|
||||
"subHappGroupBanners": "Banners & Announcements",
|
||||
"subHappGroupNetwork": "Network & TUN Engine",
|
||||
"subHappGroupThemes": "Appearance & Theme",
|
||||
"subHappGroupFailover": "Migration & App Management",
|
||||
"subHappGroupAndroid": "Android Per-App Proxy"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"importRules": "Nhập quy tắc",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "过期提示模板",
|
||||
"subExpiredTemplateDesc": "用户订阅过期时提示配置的备注模板。",
|
||||
"subTrafficDepletedTemplate": "流量耗尽模板",
|
||||
"subTrafficDepletedTemplateDesc": "用户订阅流量用尽时提示配置的备注模板。"
|
||||
"subTrafficDepletedTemplateDesc": "用户订阅流量用尽时提示配置的备注模板。",
|
||||
"subHappAutoDetect": "Happ 客户端请求头自动识别",
|
||||
"subHappAutoDetectDesc": "当客户端 User-Agent 包含 Happ 时自动注入专属路由规则与响应头。",
|
||||
"subHappProviderId": "服务提供商标识 (Provider ID)",
|
||||
"subHappProviderIdDesc": "Happ 客户端管理、远程配置绑定与订阅迁移所需的唯一标识符。",
|
||||
"subHappNewUrl": "新订阅地址 (迁移)",
|
||||
"subHappNewUrlDesc": "自动迁移的新订阅 URL。客户端收到后会自动将订阅地址切换为此链接。",
|
||||
"subHappFallbackUrl": "备用订阅地址 (Fallback)",
|
||||
"subHappFallbackUrlDesc": "主订阅服务器不可用时 Happ 自动切换的备份订阅链接。",
|
||||
"subHappSubInfoText": "横幅公告文本",
|
||||
"subHappSubInfoTextDesc": "显示在 Happ 客户端顶部的自定义通知横幅(最多 200 字符)。",
|
||||
"subHappSubInfoColor": "横幅配色主题",
|
||||
"subHappSubInfoColorDesc": "横幅主题颜色:blue (默认蓝色)、green (绿色)、red (红色警告)。",
|
||||
"subHappSubInfoButtonText": "横幅按钮文字",
|
||||
"subHappSubInfoButtonTextDesc": "通知横幅内的操作按钮标题(最多 25 字符)。",
|
||||
"subHappSubInfoButtonLink": "横幅按钮链接",
|
||||
"subHappSubInfoButtonLinkDesc": "点击横幅按钮时打开的目标网址或 DeepLink。",
|
||||
"subHappSubExpire": "订阅到期提醒横幅",
|
||||
"subHappSubExpireDesc": "当用户订阅即将到期或已过期时在 Happ 中显示续费提示横幅。",
|
||||
"subHappSubExpireButtonLink": "续费链接",
|
||||
"subHappSubExpireButtonLinkDesc": "到期横幅中点击「续费」按钮时跳转的支付或购买页面。",
|
||||
"subHappNotificationExpire": "订阅到期推送提醒",
|
||||
"subHappNotificationExpireDesc": "在订阅到期前 3 天向用户发送每日一次的客户端到期提醒。",
|
||||
"subHappNoLimit": "解除规则数量限制 (No Limit)",
|
||||
"subHappNoLimitDesc": "提升内核内存上限,允许应用超出移动端默认数量的复杂路由规则。",
|
||||
"subHappAlwaysHwid": "强制绑定硬件标识 (HWID)",
|
||||
"subHappAlwaysHwidDesc": "禁止客户端关闭硬件标识符上报,强化多设备防盗刷安全。",
|
||||
"subHappTunMode": "TUN 运行模式",
|
||||
"subHappTunModeDesc": "TUN 网络接口栈:system (系统网络栈) 或 gvisor (用户态协议栈)。",
|
||||
"subHappTunType": "TUN 隧道内核 (TUN Type)",
|
||||
"subHappTunTypeDesc": "隧道实现引擎:singbox、tun2proxy、default (Happ 原生) 或 xray。",
|
||||
"subHappExcludeRoutes": "排除直连网段 (CIDR)",
|
||||
"subHappExcludeRoutesDesc": "逗号分隔的 IP/CIDR 地址段,此网段流量不走 VPN 隧道直连。",
|
||||
"subHappExcludeApns": "排除苹果推送服务 (APNs)",
|
||||
"subHappExcludeApnsDesc": "让 Apple APNs 流量直连,保障 iOS 设备在后台稳定接收推送通知。",
|
||||
"subHappColorProfile": "客户端外观主题",
|
||||
"subHappColorProfileDesc": "Happ 界面配色主题:violet、turquoise、cyberpunk 或自定义 JSON 调色板。",
|
||||
"subHappPingType": "延迟测速协议",
|
||||
"subHappPingTypeDesc": "节点测速协议:via Proxy (GET)、via Proxy (HEAD)、TCP 握手或 ICMP。",
|
||||
"subHappAutoConnect": "启动时自动连接",
|
||||
"subHappAutoConnectDesc": "Happ 客户端打开时自动连接代理节点。",
|
||||
"subHappAutoConnectType": "自动连接策略",
|
||||
"subHappAutoConnectTypeDesc": "连接目标选择:lowestdelay (最低延迟)、lastused (上次使用) 或 random。",
|
||||
"subHappPerAppMode": "Android 分应用代理",
|
||||
"subHappPerAppModeDesc": "分应用代理模式:off (关闭)、on (仅代理选定应用) 或 bypass (绕过选定应用)。",
|
||||
"subHappPerAppList": "Android 应用包名列表",
|
||||
"subHappPerAppListDesc": "以逗号分隔的应用包名(例如 org.telegram.messenger, com.google.android.youtube)。",
|
||||
"subHappPresetIran": "伊朗直连规则 (Iran Bypass)",
|
||||
"subHappPresetChina": "大陆直连规则 (China Direct)",
|
||||
"subHappPresetAdblock": "广告拦截规则 (AdBlock)",
|
||||
"subHappPresetGlobal": "全局代理规则 (Global)",
|
||||
"subHappPresetOff": "禁用路由 (happ://routing/off)",
|
||||
"subHappColorBlue": "蓝色 (标准 / 默认)",
|
||||
"subHappColorGreen": "绿色 (成功)",
|
||||
"subHappColorRed": "红色 (警告 / 危险)",
|
||||
"subHappTunModeDefault": "默认",
|
||||
"subHappTunModeSystem": "系统 (标准操作系统协议栈)",
|
||||
"subHappTunModeGvisor": "gVisor (用户空间协议栈)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "默认 (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "经由代理 (GET 延迟)",
|
||||
"subHappPingProxyHead": "经由代理 (HEAD 延迟)",
|
||||
"subHappPingTcp": "TCP 握手 Ping",
|
||||
"subHappPingIcmp": "ICMP Ping",
|
||||
"subHappAutoConnectLowestDelay": "最低延迟 (最快节点)",
|
||||
"subHappAutoConnectLastUsed": "最后使用的节点",
|
||||
"subHappAutoConnectRandom": "随机节点",
|
||||
"subHappPerAppOff": "关闭",
|
||||
"subHappPerAppOn": "开启 (仅代理列表中的应用)",
|
||||
"subHappPerAppBypass": "绕过 (排除列表中的应用)",
|
||||
"subHappPresetApplied": "Happ 预设规则已应用",
|
||||
"subHappPresets": "预设路由规则",
|
||||
"subHappPresetsDesc": "专为 Happ 客户端预设调优的常用分流规则。",
|
||||
"subHappVisualBuilder": "可视化规则生成器",
|
||||
"subHappVisualBuilderDesc": "通过域名与 IP 列表快速生成自定义 Happ 路由 DeepLink。",
|
||||
"subHappBuildDeeplink": "生成 DeepLink",
|
||||
"subHappModalTitle": "Happ 路由规则可视化生成器",
|
||||
"subHappDirectDomains": "直连域名 (Direct)",
|
||||
"subHappProxyDomains": "代理域名 (Proxy)",
|
||||
"subHappBlockDomains": "阻止域名 (Block)",
|
||||
"subHappDirectIPs": "直连 IP / CIDR",
|
||||
"subHappProxyIPs": "代理 IP / CIDR",
|
||||
"subHappBlockIPs": "阻止 IP / CIDR",
|
||||
"subHappDeeplinkGenerated": "DeepLink 已生成并填入路由规则",
|
||||
"subHappGroupRouting": "路由分流与规则",
|
||||
"subHappGroupBanners": "横幅公告与通知",
|
||||
"subHappGroupNetwork": "网络与 TUN 引擎",
|
||||
"subHappGroupThemes": "界面与主题外观",
|
||||
"subHappGroupFailover": "迁移与客户端管理",
|
||||
"subHappGroupAndroid": "Android 分应用代理"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"importRules": "导入规则",
|
||||
|
||||
@@ -1466,7 +1466,99 @@
|
||||
"subExpiredTemplate": "過期提示範本",
|
||||
"subExpiredTemplateDesc": "使用者訂閱過期時提示配置的備註範本。",
|
||||
"subTrafficDepletedTemplate": "流量耗盡範本",
|
||||
"subTrafficDepletedTemplateDesc": "使用者訂閱流量用盡時提示配置的備註範本。"
|
||||
"subTrafficDepletedTemplateDesc": "使用者訂閱流量用盡時提示配置的備註範本。",
|
||||
"subHappAutoDetect": "Happ 客户端请求头自动识别",
|
||||
"subHappAutoDetectDesc": "当客户端 User-Agent 包含 Happ 时自动注入专属路由规则与响应头。",
|
||||
"subHappProviderId": "服务提供商标识 (Provider ID)",
|
||||
"subHappProviderIdDesc": "Happ 客户端管理、远程配置绑定与订阅迁移所需的唯一标识符。",
|
||||
"subHappNewUrl": "新订阅地址 (迁移)",
|
||||
"subHappNewUrlDesc": "自动迁移的新订阅 URL。客户端收到后会自动将订阅地址切换为此链接。",
|
||||
"subHappFallbackUrl": "备用订阅地址 (Fallback)",
|
||||
"subHappFallbackUrlDesc": "主订阅服务器不可用时 Happ 自动切换的备份订阅链接。",
|
||||
"subHappSubInfoText": "横幅公告文本",
|
||||
"subHappSubInfoTextDesc": "显示在 Happ 客户端顶部的自定义通知横幅(最多 200 字符)。",
|
||||
"subHappSubInfoColor": "横幅配色主题",
|
||||
"subHappSubInfoColorDesc": "横幅主题颜色:blue (默认蓝色)、green (绿色)、red (红色警告)。",
|
||||
"subHappSubInfoButtonText": "横幅按钮文字",
|
||||
"subHappSubInfoButtonTextDesc": "通知横幅内的操作按钮标题(最多 25 字符)。",
|
||||
"subHappSubInfoButtonLink": "横幅按钮链接",
|
||||
"subHappSubInfoButtonLinkDesc": "点击横幅按钮时打开的目标网址或 DeepLink。",
|
||||
"subHappSubExpire": "订阅到期提醒横幅",
|
||||
"subHappSubExpireDesc": "当用户订阅即将到期或已过期时在 Happ 中显示续费提示横幅。",
|
||||
"subHappSubExpireButtonLink": "续费链接",
|
||||
"subHappSubExpireButtonLinkDesc": "到期横幅中点击「续费」按钮时跳转的支付或购买页面。",
|
||||
"subHappNotificationExpire": "订阅到期推送提醒",
|
||||
"subHappNotificationExpireDesc": "在订阅到期前 3 天向用户发送每日一次的客户端到期提醒。",
|
||||
"subHappNoLimit": "解除规则数量限制 (No Limit)",
|
||||
"subHappNoLimitDesc": "提升内核内存上限,允许应用超出移动端默认数量的复杂路由规则。",
|
||||
"subHappAlwaysHwid": "强制绑定硬件标识 (HWID)",
|
||||
"subHappAlwaysHwidDesc": "禁止客户端关闭硬件标识符上报,强化多设备防盗刷安全。",
|
||||
"subHappTunMode": "TUN 运行模式",
|
||||
"subHappTunModeDesc": "TUN 网络接口栈:system (系统网络栈) 或 gvisor (用户态协议栈)。",
|
||||
"subHappTunType": "TUN 隧道内核 (TUN Type)",
|
||||
"subHappTunTypeDesc": "隧道实现引擎:singbox、tun2proxy、default (Happ 原生) 或 xray。",
|
||||
"subHappExcludeRoutes": "排除直连网段 (CIDR)",
|
||||
"subHappExcludeRoutesDesc": "逗号分隔的 IP/CIDR 地址段,此网段流量不走 VPN 隧道直连。",
|
||||
"subHappExcludeApns": "排除苹果推送服务 (APNs)",
|
||||
"subHappExcludeApnsDesc": "让 Apple APNs 流量直连,保障 iOS 设备在后台稳定接收推送通知。",
|
||||
"subHappColorProfile": "客户端外观主题",
|
||||
"subHappColorProfileDesc": "Happ 界面配色主题:violet、turquoise、cyberpunk 或自定义 JSON 调色板。",
|
||||
"subHappPingType": "延迟测速协议",
|
||||
"subHappPingTypeDesc": "节点测速协议:via Proxy (GET)、via Proxy (HEAD)、TCP 握手或 ICMP。",
|
||||
"subHappAutoConnect": "启动时自动连接",
|
||||
"subHappAutoConnectDesc": "Happ 客户端打开时自动连接代理节点。",
|
||||
"subHappAutoConnectType": "自动连接策略",
|
||||
"subHappAutoConnectTypeDesc": "连接目标选择:lowestdelay (最低延迟)、lastused (上次使用) 或 random。",
|
||||
"subHappPerAppMode": "Android 分应用代理",
|
||||
"subHappPerAppModeDesc": "分应用代理模式:off (关闭)、on (仅代理选定应用) 或 bypass (绕过选定应用)。",
|
||||
"subHappPerAppList": "Android 应用包名列表",
|
||||
"subHappPerAppListDesc": "以逗号分隔的应用包名(例如 org.telegram.messenger, com.google.android.youtube)。",
|
||||
"subHappPresetIran": "伊朗直连规则 (Iran Bypass)",
|
||||
"subHappPresetChina": "大陆直连规则 (China Direct)",
|
||||
"subHappPresetAdblock": "广告拦截规则 (AdBlock)",
|
||||
"subHappPresetGlobal": "全局代理规则 (Global)",
|
||||
"subHappPresetOff": "停用路由 (happ://routing/off)",
|
||||
"subHappColorBlue": "藍色 (標準 / 預設)",
|
||||
"subHappColorGreen": "綠色 (成功)",
|
||||
"subHappColorRed": "紅色 (警告 / 危險)",
|
||||
"subHappTunModeDefault": "預設",
|
||||
"subHappTunModeSystem": "系統 (標準作業系統協議棧)",
|
||||
"subHappTunModeGvisor": "gVisor (使用者空間協議棧)",
|
||||
"subHappTunTypeSingbox": "sing-box",
|
||||
"subHappTunTypeTun2proxy": "tun2proxy",
|
||||
"subHappTunTypeDefault": "預設 (Happ TUN)",
|
||||
"subHappTunTypeXray": "Xray TUN",
|
||||
"subHappPingProxy": "經由代理 (GET 延遲)",
|
||||
"subHappPingProxyHead": "經由代理 (HEAD 延遲)",
|
||||
"subHappPingTcp": "TCP 握手 Ping",
|
||||
"subHappPingIcmp": "ICMP Ping",
|
||||
"subHappAutoConnectLowestDelay": "最低延遲 (最快節點)",
|
||||
"subHappAutoConnectLastUsed": "最後使用的節點",
|
||||
"subHappAutoConnectRandom": "隨機節點",
|
||||
"subHappPerAppOff": "關閉",
|
||||
"subHappPerAppOn": "開啟 (僅代理列表中的應用)",
|
||||
"subHappPerAppBypass": "繞過 (排除列表中的應用)",
|
||||
"subHappPresetApplied": "Happ 预设规则已应用",
|
||||
"subHappPresets": "预设路由规则",
|
||||
"subHappPresetsDesc": "专为 Happ 客户端预设调优的常用分流规则。",
|
||||
"subHappVisualBuilder": "可视化规则生成器",
|
||||
"subHappVisualBuilderDesc": "通过域名与 IP 列表快速生成自定义 Happ 路由 DeepLink。",
|
||||
"subHappBuildDeeplink": "生成 DeepLink",
|
||||
"subHappModalTitle": "Happ 路由规则可视化生成器",
|
||||
"subHappDirectDomains": "直连域名 (Direct)",
|
||||
"subHappProxyDomains": "代理域名 (Proxy)",
|
||||
"subHappBlockDomains": "阻止域名 (Block)",
|
||||
"subHappDirectIPs": "直连 IP / CIDR",
|
||||
"subHappProxyIPs": "代理 IP / CIDR",
|
||||
"subHappBlockIPs": "阻止 IP / CIDR",
|
||||
"subHappDeeplinkGenerated": "DeepLink 已生成并填入路由规则",
|
||||
"subHappGroupRouting": "路由分流与规则",
|
||||
"subHappGroupBanners": "横幅公告与通知",
|
||||
"subHappGroupNetwork": "网络与 TUN 引擎",
|
||||
"subHappGroupThemes": "界面与主题外观",
|
||||
"subHappGroupFailover": "迁移与客户端管理",
|
||||
"subHappGroupAndroid": "Android 分应用代理"
|
||||
|
||||
},
|
||||
"xray": {
|
||||
"save": "儲存",
|
||||
|
||||
Reference in New Issue
Block a user