diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 0f75922a9..635445f76 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -342,12 +342,97 @@ "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -610,8 +695,36 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", @@ -1005,12 +1118,97 @@ "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -1281,8 +1479,36 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 0f75922a9..635445f76 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -342,12 +342,97 @@ "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -610,8 +695,36 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", @@ -1005,12 +1118,97 @@ "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -1281,8 +1479,36 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index b8c4ba516..662a5ceac 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -98,8 +98,36 @@ export const EXAMPLES: Record = { "subHappTunMode": "", "subHappTunType": "", "subHideSettings": false, + "subIncyAnnounceUrl": "", + "subIncyAppAutoDetect": false, + "subIncyBannerBgColor": "", + "subIncyBannerButtonColor": "", + "subIncyBannerButtonText": "", + "subIncyBannerButtonUrl": "", + "subIncyBannerText": "", "subIncyEnableRouting": false, + "subIncyFragmentInterval": "", + "subIncyFragmentLength": "", + "subIncyFragmentPackets": "", + "subIncyFragmentationEnable": "", + "subIncyHideCheck": "", + "subIncyHideUrl": "", + "subIncyNoLimitEnabled": "", + "subIncyNoisesDelay": "", + "subIncyNoisesEnable": "", + "subIncyNoisesPacket": "", + "subIncyNoisesType": "", + "subIncyPerAppEnable": "", + "subIncyPerAppList": "", + "subIncyPerAppMode": "", + "subIncyPremiumUrl": "", + "subIncyProfileDescription": "", + "subIncyResolveDnsDomain": "", + "subIncyResolveDnsIp": "", + "subIncyResolveEnable": "", "subIncyRoutingRules": "", + "subIncySortOrder": "", + "subIncySupportEmail": "", "subInfoNodeEnable": false, "subJsonAlwaysArray": false, "subJsonAutoDetect": false, @@ -257,8 +285,36 @@ export const EXAMPLES: Record = { "subHappTunMode": "", "subHappTunType": "", "subHideSettings": false, + "subIncyAnnounceUrl": "", + "subIncyAppAutoDetect": false, + "subIncyBannerBgColor": "", + "subIncyBannerButtonColor": "", + "subIncyBannerButtonText": "", + "subIncyBannerButtonUrl": "", + "subIncyBannerText": "", "subIncyEnableRouting": false, + "subIncyFragmentInterval": "", + "subIncyFragmentLength": "", + "subIncyFragmentPackets": "", + "subIncyFragmentationEnable": "", + "subIncyHideCheck": "", + "subIncyHideUrl": "", + "subIncyNoLimitEnabled": "", + "subIncyNoisesDelay": "", + "subIncyNoisesEnable": "", + "subIncyNoisesPacket": "", + "subIncyNoisesType": "", + "subIncyPerAppEnable": "", + "subIncyPerAppList": "", + "subIncyPerAppMode": "", + "subIncyPremiumUrl": "", + "subIncyProfileDescription": "", + "subIncyResolveDnsDomain": "", + "subIncyResolveDnsIp": "", + "subIncyResolveEnable": "", "subIncyRoutingRules": "", + "subIncySortOrder": "", + "subIncySupportEmail": "", "subInfoNodeEnable": false, "subJsonAlwaysArray": false, "subJsonAutoDetect": false, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index e0e32f275..783f5db44 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -316,12 +316,97 @@ export const SCHEMAS: Record = { "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -584,8 +669,36 @@ export const SCHEMAS: Record = { "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", @@ -979,12 +1092,97 @@ export const SCHEMAS: Record = { "subHideSettings": { "type": "boolean" }, + "subIncyAnnounceUrl": { + "type": "string" + }, + "subIncyAppAutoDetect": { + "description": "Incy client customization settings (app-management). A \"\" value omits\nthe header so the subscriber's own app setting is left alone.", + "type": "boolean" + }, + "subIncyBannerBgColor": { + "type": "string" + }, + "subIncyBannerButtonColor": { + "type": "string" + }, + "subIncyBannerButtonText": { + "type": "string" + }, + "subIncyBannerButtonUrl": { + "type": "string" + }, + "subIncyBannerText": { + "type": "string" + }, "subIncyEnableRouting": { "type": "boolean" }, + "subIncyFragmentInterval": { + "type": "string" + }, + "subIncyFragmentLength": { + "type": "string" + }, + "subIncyFragmentPackets": { + "type": "string" + }, + "subIncyFragmentationEnable": { + "type": "string" + }, + "subIncyHideCheck": { + "type": "string" + }, + "subIncyHideUrl": { + "type": "string" + }, + "subIncyNoLimitEnabled": { + "type": "string" + }, + "subIncyNoisesDelay": { + "type": "string" + }, + "subIncyNoisesEnable": { + "type": "string" + }, + "subIncyNoisesPacket": { + "type": "string" + }, + "subIncyNoisesType": { + "type": "string" + }, + "subIncyPerAppEnable": { + "type": "string" + }, + "subIncyPerAppList": { + "type": "string" + }, + "subIncyPerAppMode": { + "type": "string" + }, + "subIncyPremiumUrl": { + "type": "string" + }, + "subIncyProfileDescription": { + "type": "string" + }, + "subIncyResolveDnsDomain": { + "type": "string" + }, + "subIncyResolveDnsIp": { + "type": "string" + }, + "subIncyResolveEnable": { + "type": "string" + }, "subIncyRoutingRules": { "type": "string" }, + "subIncySortOrder": { + "type": "string" + }, + "subIncySupportEmail": { + "type": "string" + }, "subInfoNodeEnable": { "type": "boolean" }, @@ -1255,8 +1453,36 @@ export const SCHEMAS: Record = { "subHappTunMode", "subHappTunType", "subHideSettings", + "subIncyAnnounceUrl", + "subIncyAppAutoDetect", + "subIncyBannerBgColor", + "subIncyBannerButtonColor", + "subIncyBannerButtonText", + "subIncyBannerButtonUrl", + "subIncyBannerText", "subIncyEnableRouting", + "subIncyFragmentInterval", + "subIncyFragmentLength", + "subIncyFragmentPackets", + "subIncyFragmentationEnable", + "subIncyHideCheck", + "subIncyHideUrl", + "subIncyNoLimitEnabled", + "subIncyNoisesDelay", + "subIncyNoisesEnable", + "subIncyNoisesPacket", + "subIncyNoisesType", + "subIncyPerAppEnable", + "subIncyPerAppList", + "subIncyPerAppMode", + "subIncyPremiumUrl", + "subIncyProfileDescription", + "subIncyResolveDnsDomain", + "subIncyResolveDnsIp", + "subIncyResolveEnable", "subIncyRoutingRules", + "subIncySortOrder", + "subIncySupportEmail", "subInfoNodeEnable", "subJsonAlwaysArray", "subJsonAutoDetect", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 690da30a4..f80b57b7d 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -106,8 +106,36 @@ export interface AllSetting { subHappTunMode: string; subHappTunType: string; subHideSettings: boolean; + subIncyAnnounceUrl: string; + subIncyAppAutoDetect: boolean; + subIncyBannerBgColor: string; + subIncyBannerButtonColor: string; + subIncyBannerButtonText: string; + subIncyBannerButtonUrl: string; + subIncyBannerText: string; subIncyEnableRouting: boolean; + subIncyFragmentInterval: string; + subIncyFragmentLength: string; + subIncyFragmentPackets: string; + subIncyFragmentationEnable: string; + subIncyHideCheck: string; + subIncyHideUrl: string; + subIncyNoLimitEnabled: string; + subIncyNoisesDelay: string; + subIncyNoisesEnable: string; + subIncyNoisesPacket: string; + subIncyNoisesType: string; + subIncyPerAppEnable: string; + subIncyPerAppList: string; + subIncyPerAppMode: string; + subIncyPremiumUrl: string; + subIncyProfileDescription: string; + subIncyResolveDnsDomain: string; + subIncyResolveDnsIp: string; + subIncyResolveEnable: string; subIncyRoutingRules: string; + subIncySortOrder: string; + subIncySupportEmail: string; subInfoNodeEnable: boolean; subJsonAlwaysArray: boolean; subJsonAutoDetect: boolean; @@ -266,8 +294,36 @@ export interface AllSettingView { subHappTunMode: string; subHappTunType: string; subHideSettings: boolean; + subIncyAnnounceUrl: string; + subIncyAppAutoDetect: boolean; + subIncyBannerBgColor: string; + subIncyBannerButtonColor: string; + subIncyBannerButtonText: string; + subIncyBannerButtonUrl: string; + subIncyBannerText: string; subIncyEnableRouting: boolean; + subIncyFragmentInterval: string; + subIncyFragmentLength: string; + subIncyFragmentPackets: string; + subIncyFragmentationEnable: string; + subIncyHideCheck: string; + subIncyHideUrl: string; + subIncyNoLimitEnabled: string; + subIncyNoisesDelay: string; + subIncyNoisesEnable: string; + subIncyNoisesPacket: string; + subIncyNoisesType: string; + subIncyPerAppEnable: string; + subIncyPerAppList: string; + subIncyPerAppMode: string; + subIncyPremiumUrl: string; + subIncyProfileDescription: string; + subIncyResolveDnsDomain: string; + subIncyResolveDnsIp: string; + subIncyResolveEnable: string; subIncyRoutingRules: string; + subIncySortOrder: string; + subIncySupportEmail: string; subInfoNodeEnable: boolean; subJsonAlwaysArray: boolean; subJsonAutoDetect: boolean; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index a52f6ce96..00b6cc330 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -122,8 +122,36 @@ export const AllSettingSchema = z.object({ subHappTunMode: z.string(), subHappTunType: z.string(), subHideSettings: z.boolean(), + subIncyAnnounceUrl: z.string(), + subIncyAppAutoDetect: z.boolean(), + subIncyBannerBgColor: z.string(), + subIncyBannerButtonColor: z.string(), + subIncyBannerButtonText: z.string(), + subIncyBannerButtonUrl: z.string(), + subIncyBannerText: z.string(), subIncyEnableRouting: z.boolean(), + subIncyFragmentInterval: z.string(), + subIncyFragmentLength: z.string(), + subIncyFragmentPackets: z.string(), + subIncyFragmentationEnable: z.string(), + subIncyHideCheck: z.string(), + subIncyHideUrl: z.string(), + subIncyNoLimitEnabled: z.string(), + subIncyNoisesDelay: z.string(), + subIncyNoisesEnable: z.string(), + subIncyNoisesPacket: z.string(), + subIncyNoisesType: z.string(), + subIncyPerAppEnable: z.string(), + subIncyPerAppList: z.string(), + subIncyPerAppMode: z.string(), + subIncyPremiumUrl: z.string(), + subIncyProfileDescription: z.string(), + subIncyResolveDnsDomain: z.string(), + subIncyResolveDnsIp: z.string(), + subIncyResolveEnable: z.string(), subIncyRoutingRules: z.string(), + subIncySortOrder: z.string(), + subIncySupportEmail: z.string(), subInfoNodeEnable: z.boolean(), subJsonAlwaysArray: z.boolean(), subJsonAutoDetect: z.boolean(), @@ -283,8 +311,36 @@ export const AllSettingViewSchema = z.object({ subHappTunMode: z.string(), subHappTunType: z.string(), subHideSettings: z.boolean(), + subIncyAnnounceUrl: z.string(), + subIncyAppAutoDetect: z.boolean(), + subIncyBannerBgColor: z.string(), + subIncyBannerButtonColor: z.string(), + subIncyBannerButtonText: z.string(), + subIncyBannerButtonUrl: z.string(), + subIncyBannerText: z.string(), subIncyEnableRouting: z.boolean(), + subIncyFragmentInterval: z.string(), + subIncyFragmentLength: z.string(), + subIncyFragmentPackets: z.string(), + subIncyFragmentationEnable: z.string(), + subIncyHideCheck: z.string(), + subIncyHideUrl: z.string(), + subIncyNoLimitEnabled: z.string(), + subIncyNoisesDelay: z.string(), + subIncyNoisesEnable: z.string(), + subIncyNoisesPacket: z.string(), + subIncyNoisesType: z.string(), + subIncyPerAppEnable: z.string(), + subIncyPerAppList: z.string(), + subIncyPerAppMode: z.string(), + subIncyPremiumUrl: z.string(), + subIncyProfileDescription: z.string(), + subIncyResolveDnsDomain: z.string(), + subIncyResolveDnsIp: z.string(), + subIncyResolveEnable: z.string(), subIncyRoutingRules: z.string(), + subIncySortOrder: z.string(), + subIncySupportEmail: z.string(), subInfoNodeEnable: z.boolean(), subJsonAlwaysArray: z.boolean(), subJsonAutoDetect: z.boolean(), diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index 73311d1aa..6d7222901 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -107,6 +107,35 @@ export class AllSetting { subHappPerAppList = ''; subHappLocalProxyAuth = 'auto'; + subIncyAppAutoDetect = false; + subIncyProfileDescription = ''; + subIncySortOrder = ''; + subIncySupportEmail = ''; + subIncyAnnounceUrl = ''; + subIncyPremiumUrl = ''; + subIncyBannerText = ''; + subIncyBannerButtonText = ''; + subIncyBannerButtonUrl = ''; + subIncyBannerBgColor = ''; + subIncyBannerButtonColor = ''; + subIncyHideUrl = ''; + subIncyHideCheck = ''; + subIncyNoLimitEnabled = ''; + subIncyPerAppEnable = ''; + subIncyPerAppMode = ''; + subIncyPerAppList = ''; + subIncyFragmentationEnable = ''; + subIncyFragmentLength = ''; + subIncyFragmentInterval = ''; + subIncyFragmentPackets = ''; + subIncyNoisesEnable = ''; + subIncyNoisesType = ''; + subIncyNoisesPacket = ''; + subIncyNoisesDelay = ''; + subIncyResolveEnable = ''; + subIncyResolveDnsDomain = ''; + subIncyResolveDnsIp = ''; + timeLocation = 'Local'; ldapEnable = false; diff --git a/frontend/src/pages/settings/IncySettingsContent.tsx b/frontend/src/pages/settings/IncySettingsContent.tsx new file mode 100644 index 000000000..cbccdc1a8 --- /dev/null +++ b/frontend/src/pages/settings/IncySettingsContent.tsx @@ -0,0 +1,488 @@ +import { useTranslation } from 'react-i18next'; +import { Input, Select, Switch, Tabs } from 'antd'; +import { + AppstoreOutlined, + BranchesOutlined, + NotificationOutlined, + SafetyOutlined, + ThunderboltOutlined, +} from '@ant-design/icons'; +import type { AllSetting } from '@/models/setting'; +import { SettingListItem } from '@/components/ui'; +import { catTabLabel } from './catTabLabel'; + +interface IncySettingsContentProps { + allSetting: AllSetting; + updateSetting: (patch: Partial) => void; + isMobile: boolean; + remoteSourceBadge: (val: string) => React.ReactNode; +} + +// Incy documents every switch as `1`/`0`; an empty value omits the header so +// the subscriber's own app choice wins. +const onOff = (t: (key: string) => string) => [ + { value: '', label: t('pages.settings.subIncyNotSet') }, + { value: '1', label: t('pages.settings.subIncyOn') }, + { value: '0', label: t('pages.settings.subIncyOff') }, +]; + +export default function IncySettingsContent({ + allSetting, + updateSetting, + isMobile, + remoteSourceBadge, +}: IncySettingsContentProps) { + const { t } = useTranslation(); + + return ( + <> + + updateSetting({ subIncyAppAutoDetect: v })} + /> + + + , t('pages.settings.subIncyGroupApp'), isMobile), + children: ( + <> + + updateSetting({ subIncyProfileDescription: e.target.value })} + /> + + + + updateSetting({ subIncySupportEmail: e.target.value })} + /> + + + + updateSetting({ subIncyAnnounceUrl: e.target.value })} + /> + + + + updateSetting({ subIncyPremiumUrl: e.target.value })} + /> + + + ), + }, + { + key: 'banners', + label: catTabLabel( + , + t('pages.settings.subIncyGroupBanners'), + isMobile, + ), + children: ( + <> + + updateSetting({ subIncyBannerText: e.target.value })} + /> + + + + updateSetting({ subIncyBannerButtonText: e.target.value })} + /> + + + + updateSetting({ subIncyBannerButtonUrl: e.target.value })} + /> + + + + updateSetting({ subIncyBannerBgColor: e.target.value })} + /> + + + + updateSetting({ subIncyBannerButtonColor: e.target.value })} + /> + + + ), + }, + { + key: 'privacy', + label: catTabLabel( + , + t('pages.settings.subIncyGroupPrivacy'), + isMobile, + ), + children: ( + <> + + updateSetting({ subIncyHideCheck: v })} + options={onOff(t)} + /> + + + + updateSetting({ subIncyPerAppEnable: v })} + options={onOff(t)} + /> + + + + updateSetting({ subIncyFragmentationEnable: v })} + options={onOff(t)} + /> + + + + updateSetting({ subIncyFragmentLength: e.target.value })} + /> + + + + updateSetting({ subIncyFragmentInterval: e.target.value })} + /> + + + + updateSetting({ subIncyNoisesEnable: v })} + options={onOff(t)} + /> + + + + updateSetting({ subIncyNoisesPacket: e.target.value })} + /> + + + + updateSetting({ subIncyNoisesDelay: e.target.value })} + /> + + + + updateSetting({ subIncyResolveDnsDomain: e.target.value })} + /> + + + + updateSetting({ subIncyResolveDnsIp: e.target.value })} + /> + + + ), + }, + { + key: 'routing', + label: catTabLabel( + , + t('pages.settings.subIncyGroupRouting'), + isMobile, + ), + children: ( + <> + + updateSetting({ subIncyEnableRouting: v })} + /> + + + updateSetting({ subIncyRoutingRules: e.target.value })} + /> + + + ), + }, + ]} + /> + + ); +} diff --git a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx index e73133f7f..970e49ce9 100644 --- a/frontend/src/pages/settings/SubscriptionGeneralTab.tsx +++ b/frontend/src/pages/settings/SubscriptionGeneralTab.tsx @@ -19,6 +19,7 @@ import { useMediaQuery } from '@/hooks/useMediaQuery'; import { catTabLabel } from './catTabLabel'; import { sanitizePath, normalizePath } from './uriPath'; import HappSettingsContent from './HappSettingsContent'; +import IncySettingsContent from './IncySettingsContent'; import { remoteSourceBadge } from './subscriptionShared'; interface SubscriptionGeneralTabProps { @@ -455,30 +456,12 @@ export default function SubscriptionGeneralTab({ key: '7', label: catTabLabel(, 'Incy', isMobile), children: ( - <> - - updateSetting({ subIncyEnableRouting: v })} - /> - - - updateSetting({ subIncyRoutingRules: e.target.value })} - /> - - + ), }, ]} diff --git a/frontend/src/schemas/setting.ts b/frontend/src/schemas/setting.ts index fd25644fb..7c42d4f72 100644 --- a/frontend/src/schemas/setting.ts +++ b/frontend/src/schemas/setting.ts @@ -111,6 +111,34 @@ export const AllSettingSchema = z subHappPerAppMode: z.string().optional(), subHappPerAppList: z.string().optional(), subHappLocalProxyAuth: z.string().optional(), + subIncyAppAutoDetect: z.boolean().optional(), + subIncyProfileDescription: z.string().optional(), + subIncySortOrder: z.string().optional(), + subIncySupportEmail: z.string().optional(), + subIncyAnnounceUrl: z.string().optional(), + subIncyPremiumUrl: z.string().optional(), + subIncyBannerText: z.string().optional(), + subIncyBannerButtonText: z.string().optional(), + subIncyBannerButtonUrl: z.string().optional(), + subIncyBannerBgColor: z.string().optional(), + subIncyBannerButtonColor: z.string().optional(), + subIncyHideUrl: z.string().optional(), + subIncyHideCheck: z.string().optional(), + subIncyNoLimitEnabled: z.string().optional(), + subIncyPerAppEnable: z.string().optional(), + subIncyPerAppMode: z.string().optional(), + subIncyPerAppList: z.string().optional(), + subIncyFragmentationEnable: z.string().optional(), + subIncyFragmentLength: z.string().optional(), + subIncyFragmentInterval: z.string().optional(), + subIncyFragmentPackets: z.string().optional(), + subIncyNoisesEnable: z.string().optional(), + subIncyNoisesType: z.string().optional(), + subIncyNoisesPacket: z.string().optional(), + subIncyNoisesDelay: z.string().optional(), + subIncyResolveEnable: z.string().optional(), + subIncyResolveDnsDomain: z.string().optional(), + subIncyResolveDnsIp: z.string().optional(), timeLocation: z.string().optional(), ldapEnable: z.boolean().optional(), ldapHost: z.string().optional(), diff --git a/frontend/src/test/incy-settings-content.test.tsx b/frontend/src/test/incy-settings-content.test.tsx new file mode 100644 index 000000000..eda3140c0 --- /dev/null +++ b/frontend/src/test/incy-settings-content.test.tsx @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from 'vitest'; +import { fireEvent } from '@testing-library/react'; + +import IncySettingsContent from '@/pages/settings/IncySettingsContent'; +import { AllSetting } from '@/models/setting'; + +import { renderWithProviders } from './test-utils'; + +function openTab(name: string) { + const tab = Array.from(document.querySelectorAll('.ant-tabs-tab')).find((t) => + (t.textContent ?? '').includes(name), + ); + if (!tab) throw new Error(`tab '${name}' not found`); + fireEvent.click(tab); +} + +function settingRow(title: string): HTMLElement { + const row = Array.from(document.querySelectorAll('li,div[class*="setting"]')).find((r) => + (r.textContent ?? '').includes(title), + ); + if (!row) throw new Error(`setting row '${title}' not found`); + return row as HTMLElement; +} + +function selectFor(title: string): HTMLElement { + const select = settingRow(title).querySelector('.ant-select'); + if (!select) throw new Error(`select for '${title}' not found`); + return select as HTMLElement; +} + +function selectOption(title: string, option: string) { + const select = selectFor(title); + fireEvent.mouseDown(select.querySelector('.ant-select-selector') ?? select); + const match = Array.from(document.querySelectorAll('.ant-select-item-option')).find( + (o) => (o.textContent ?? '').trim() === option, + ); + if (!match) throw new Error(`option '${option}' for '${title}' not found`); + fireEvent.click(match); +} + +function render(subIncy: Partial = {}) { + const updateSetting = vi.fn(); + const allSetting = Object.assign(new AllSetting(), subIncy); + renderWithProviders( + null} + />, + ); + return updateSetting; +} + +describe('Incy app-management settings', () => { + // Every Incy switch and enum is tri-state: "Not set" must store the empty + // value so the panel sends no header and the subscriber's app choice wins. + it('stores the empty value for "Not set" so no header is emitted', () => { + const updateSetting = render({ subIncySortOrder: 'ping' }); + + openTab('App'); + selectOption('Server sort order', 'Not set'); + + expect(updateSetting).toHaveBeenCalledWith({ subIncySortOrder: '' }); + }); + + it('sends the documented per-app literal, not the Happ on/include spelling', () => { + const updateSetting = render(); + + openTab('Privacy'); + selectOption('Per-app mode (Android)', 'On'); + + expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppEnable: '1' }); + }); + + it('offers bypass and proxy for the per-app mode', () => { + const updateSetting = render(); + + openTab('Privacy'); + selectOption('Per-app mode type', 'Listed apps bypass'); + + expect(updateSetting).toHaveBeenCalledWith({ subIncyPerAppMode: 'bypass' }); + }); +}); diff --git a/internal/sub/controller.go b/internal/sub/controller.go index 5f5b2ae36..3bc5dff5d 100644 --- a/internal/sub/controller.go +++ b/internal/sub/controller.go @@ -62,6 +62,7 @@ type SUBController struct { subJsonRoutingRules string subHideSettings bool happConfig HappConfig + incyConfig IncyConfig subIncyEnableRouting bool subIncyRoutingRules string @@ -124,6 +125,7 @@ type subControllerConfig struct { subRoutingRules string subHideSettings bool happConfig HappConfig + incyConfig IncyConfig subIncyEnableRouting bool subIncyRoutingRules string @@ -259,6 +261,10 @@ func WithSUBHappConfig(value HappConfig) SUBControllerOption { return func(config *subControllerConfig) { config.happConfig = value } } +func WithSUBIncyConfig(value IncyConfig) SUBControllerOption { + return func(config *subControllerConfig) { config.incyConfig = value } +} + func defaultSUBControllerConfig() subControllerConfig { return subControllerConfig{ subPath: "/sub/", @@ -293,6 +299,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo subJsonRoutingRules: config.subJsonRoutingRules, subHideSettings: config.subHideSettings, happConfig: config.happConfig, + incyConfig: config.incyConfig, subIncyEnableRouting: config.subIncyEnableRouting, subIncyRoutingRules: config.subIncyRoutingRules, @@ -974,4 +981,5 @@ func (a *SUBController) ApplyCommonHeaders( } ApplyHappHeaders(c, a.happConfig, happManaged) + ApplyIncyHeaders(c, a.incyConfig, a.incyConfig.AutoDetect && c.Request != nil && IsIncyClient(c.GetHeader("User-Agent"))) } diff --git a/internal/sub/incy.go b/internal/sub/incy.go new file mode 100644 index 000000000..e3c82d93c --- /dev/null +++ b/internal/sub/incy.go @@ -0,0 +1,180 @@ +package sub + +import ( + "encoding/base64" + "regexp" + "strings" + + "github.com/gin-gonic/gin" +) + +// INCY clients identify themselves as INCY//. +var incyUserAgentRegex = regexp.MustCompile(`(?i)\bincy\b`) + +var ( + incyRangeRegex = regexp.MustCompile(`^\d+(-\d+)?$`) + incyHexRegex = regexp.MustCompile(`^#[0-9a-fA-F]{3,8}$`) + // Incy documents fragmentation-packets as tlshello | 1-3 | 1 | all. + incyPacketsRangeRegex = regexp.MustCompile(`^\d+-\d+$`) +) + +// IncyConfig holds the Incy app-management headers the panel can push. +// A "" field omits its header, so an untouched panel never overrides the app. +type IncyConfig struct { + AutoDetect bool + + ProfileDescription string + SortOrder string + SupportEmail string + AnnounceUrl string + PremiumUrl string + + BannerText string + BannerButtonText string + BannerButtonUrl string + BannerBgColor string + BannerButtonColor string + + HideUrl string + HideCheck string + NoLimitEnabled string + + PerAppProxyEnable string + PerAppProxyMode string + PerAppProxyList string + + FragmentationEnable string + FragmentationLength string + FragmentationInterval string + FragmentationPackets string + + NoisesEnable string + NoisesType string + NoisesPacket string + NoisesDelay string + + ServerAddressResolveEnable string + ServerAddressResolveDnsDomain string + ServerAddressResolveDnsIp string +} + +// IsIncyClient checks if the client user-agent identifies as INCY. +func IsIncyClient(userAgent string) bool { + return incyUserAgentRegex.MatchString(userAgent) +} + +// incyOnOff maps a switch setting to the documented `1`/`0` literal. An +// unrecognised or unset value stays empty so the header is omitted. +func incyOnOff(v string) string { + switch strings.ToLower(strings.TrimSpace(v)) { + case "1", "true", "yes", "on": + return "1" + case "0", "false", "no", "off": + return "0" + } + return "" +} + +// incyEnum passes through only a documented literal from a fixed value set. +func incyEnum(v string, allowed ...string) string { + value := strings.ToLower(strings.TrimSpace(v)) + for _, a := range allowed { + if value == a { + return value + } + } + return "" +} + +// incyMatch returns the trimmed value only when it matches a documented shape. +func incyMatch(v string, re *regexp.Regexp) string { + value := strings.TrimSpace(v) + if re.MatchString(value) { + return value + } + return "" +} + +func incyASCII(v string) bool { + for i := 0; i < len(v); i++ { + if v[i] > 0x7e || v[i] < 0x20 { + return false + } + } + return true +} + +// incyPackageList joins a comma- or line-separated app list as CSV, since a +// header cannot carry the newlines the settings textarea accepts. +func incyPackageList(v string) string { + entries := strings.FieldsFunc(v, func(r rune) bool { return r == ',' || r == '\n' || r == '\r' }) + kept := entries[:0] + for _, entry := range entries { + if entry = strings.TrimSpace(entry); entry != "" { + kept = append(kept, entry) + } + } + return strings.Join(kept, ",") +} + +// incyHeaderText base64-wraps non-ASCII text because the docs require +// `base64:<...>` for anything outside the ASCII range (Cyrillic, CJK, emoji). +func incyHeaderText(v string) string { + text := sanitizeHeaderValue(v) + if text == "" || incyASCII(text) { + return text + } + return "base64:" + base64.StdEncoding.EncodeToString([]byte(text)) +} + +// ApplyIncyHeaders sets the Incy app-management headers from +// https://docs.incy.cc/en/app-management/ (matched case-insensitively). +func ApplyIncyHeaders(c *gin.Context, cfg IncyConfig, isIncy bool) { + if c == nil || c.Writer == nil || !cfg.AutoDetect || !isIncy { + return + } + h := c.Writer.Header() + set := func(name, value string) { + if value != "" { + h.Set(name, value) + } + } + + set("Profile-Description", incyHeaderText(cfg.ProfileDescription)) + set("Sort-Order", incyEnum(cfg.SortOrder, "none", "ping", "name")) + set("Support-Email", sanitizeHeaderValue(cfg.SupportEmail)) + set("Announce-Url", sanitizeHeaderValue(cfg.AnnounceUrl)) + set("Premium-Url", sanitizeHeaderValue(cfg.PremiumUrl)) + + set("Banner-Text", incyHeaderText(cfg.BannerText)) + set("Banner-Button-Text", incyHeaderText(cfg.BannerButtonText)) + set("Banner-Button-Url", sanitizeHeaderValue(cfg.BannerButtonUrl)) + set("Banner-Bg-Color", incyMatch(cfg.BannerBgColor, incyHexRegex)) + set("Banner-Button-Color", incyMatch(cfg.BannerButtonColor, incyHexRegex)) + + set("Hide-Url", incyOnOff(cfg.HideUrl)) + set("Hide-Check", incyOnOff(cfg.HideCheck)) + set("No-Limit-Enabled", incyOnOff(cfg.NoLimitEnabled)) + + set("Per-App-Proxy-Enable", incyOnOff(cfg.PerAppProxyEnable)) + set("Per-App-Proxy-Mode", incyEnum(cfg.PerAppProxyMode, "bypass", "proxy")) + set("Per-App-Proxy-List", incyPackageList(cfg.PerAppProxyList)) + + set("Fragmentation-Enable", incyOnOff(cfg.FragmentationEnable)) + set("Fragmentation-Length", incyMatch(cfg.FragmentationLength, incyRangeRegex)) + set("Fragmentation-Interval", incyMatch(cfg.FragmentationInterval, incyRangeRegex)) + if packets := strings.ToLower(strings.TrimSpace(cfg.FragmentationPackets)); packets != "" { + if packets == "tlshello" || packets == "all" || packets == "1" || incyPacketsRangeRegex.MatchString(packets) { + set("Fragmentation-Packets", packets) + } + } + + set("Noises-Enable", incyOnOff(cfg.NoisesEnable)) + set("Noises-Type", incyEnum(cfg.NoisesType, "rand", "str", "hex")) + set("Noises-Packet", sanitizeHeaderValue(cfg.NoisesPacket)) + set("Noises-Delay", incyMatch(cfg.NoisesDelay, incyRangeRegex)) + + set("Server-Address-Resolve-Enable", incyOnOff(cfg.ServerAddressResolveEnable)) + set("Server-Address-Resolve-Dns-Domain", sanitizeHeaderValue(cfg.ServerAddressResolveDnsDomain)) + set("Server-Address-Resolve-Dns-Ip", sanitizeHeaderValue(cfg.ServerAddressResolveDnsIp)) +} diff --git a/internal/sub/incy_test.go b/internal/sub/incy_test.go new file mode 100644 index 000000000..5d0919773 --- /dev/null +++ b/internal/sub/incy_test.go @@ -0,0 +1,208 @@ +package sub + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" +) + +func fullIncyConfig() IncyConfig { + return IncyConfig{ + AutoDetect: true, + ProfileDescription: "Fast and stable network", + SortOrder: "ping", + SupportEmail: "support@example.com", + AnnounceUrl: "https://t.me/incy_news", + PremiumUrl: "https://example.com/buy", + BannerText: "Summer sale", + BannerButtonText: "Buy now", + BannerButtonUrl: "https://example.com/sale", + BannerBgColor: "#E53E3E", + BannerButtonColor: "#38A169", + HideUrl: "1", + HideCheck: "true", + NoLimitEnabled: "0", + PerAppProxyEnable: "1", + PerAppProxyMode: "bypass", + PerAppProxyList: "com.google.chrome,org.telegram.messenger", + FragmentationEnable: "1", + FragmentationLength: "10-30", + FragmentationInterval: "20-40", + FragmentationPackets: "tlshello", + NoisesEnable: "1", + NoisesType: "rand", + NoisesPacket: "10-20", + NoisesDelay: "10-50", + ServerAddressResolveEnable: "1", + ServerAddressResolveDnsDomain: "https://common.dot.dns.yandex.net/dns-query", + ServerAddressResolveDnsIp: "77.88.8.8", + } +} + +func applyIncyToHeaders(t *testing.T, cfg IncyConfig, userAgent string) http.Header { + t.Helper() + gin.SetMode(gin.TestMode) + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil) + if userAgent != "" { + ctx.Request.Header.Set("User-Agent", userAgent) + } + ApplyIncyHeaders(ctx, cfg, cfg.AutoDetect && IsIncyClient(ctx.GetHeader("User-Agent"))) + return recorder.Header() +} + +func TestIsIncyClient(t *testing.T) { + for _, tc := range []struct { + userAgent string + want bool + }{ + {"INCY/1.0.0/ios", true}, + {"incy/2.4/android", true}, + {"Mozilla/5.0 (Linux; Android 14) INCY/1.2.3/android", true}, + {"Happ/1.2.0 (iPhone; iOS 17.5)", false}, + {"v2rayNG/1.8.5", false}, + {"", false}, + } { + if got := IsIncyClient(tc.userAgent); got != tc.want { + t.Errorf("IsIncyClient(%q) = %v, want %v", tc.userAgent, got, tc.want) + } + } +} + +func TestApplyIncyHeaders_AllDocumentedHeaders(t *testing.T) { + cfg := fullIncyConfig() + h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android") + + want := map[string]string{ + "Profile-Description": "Fast and stable network", + "Sort-Order": "ping", + "Support-Email": "support@example.com", + "Announce-Url": "https://t.me/incy_news", + "Premium-Url": "https://example.com/buy", + "Banner-Text": "Summer sale", + "Banner-Button-Text": "Buy now", + "Banner-Button-Url": "https://example.com/sale", + "Banner-Bg-Color": "#E53E3E", + "Banner-Button-Color": "#38A169", + "Hide-Url": "1", + "Hide-Check": "1", + "No-Limit-Enabled": "0", + "Per-App-Proxy-Enable": "1", + "Per-App-Proxy-Mode": "bypass", + "Per-App-Proxy-List": "com.google.chrome,org.telegram.messenger", + "Fragmentation-Enable": "1", + "Fragmentation-Length": "10-30", + "Fragmentation-Interval": "20-40", + "Fragmentation-Packets": "tlshello", + "Noises-Enable": "1", + "Noises-Type": "rand", + "Noises-Packet": "10-20", + "Noises-Delay": "10-50", + "Server-Address-Resolve-Enable": "1", + "Server-Address-Resolve-Dns-Domain": "https://common.dot.dns.yandex.net/dns-query", + "Server-Address-Resolve-Dns-Ip": "77.88.8.8", + } + for name, value := range want { + if got := h.Get(name); got != value { + t.Errorf("header %s = %q, want %q", name, got, value) + } + } +} + +func TestApplyIncyHeaders_SkipsUnsetValues(t *testing.T) { + cfg := IncyConfig{AutoDetect: true} + cfg.HideUrl = "" + cfg.SortOrder = "" + h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/ios") + + for _, name := range []string{"Hide-Url", "Sort-Order", "Fragmentation-Enable", "Banner-Text"} { + if got := h.Get(name); got != "" { + t.Errorf("unset header %s = %q, want omitted", name, got) + } + } +} + +func TestApplyIncyHeaders_NotAppliedWithoutIncyClient(t *testing.T) { + cfg := fullIncyConfig() + + for _, userAgent := range []string{"Happ/1.2.0 (iPhone)", "v2rayNG/1.8.5", ""} { + h := applyIncyToHeaders(t, cfg, userAgent) + if got := h.Get("Hide-Url"); got != "" { + t.Errorf("user-agent %q: Hide-Url = %q, want omitted", userAgent, got) + } + } +} + +func TestApplyIncyHeaders_NotAppliedWhenAutoDetectOff(t *testing.T) { + cfg := fullIncyConfig() + cfg.AutoDetect = false + + h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android") + if got := h.Get("Hide-Url"); got != "" { + t.Errorf("AutoDetect off: Hide-Url = %q, want omitted", got) + } +} + +func TestIncyHeaderText_Base64ForNonASCII(t *testing.T) { + ascii := incyHeaderText("Plain banner") + if ascii != "Plain banner" { + t.Errorf("ASCII text = %q, want unchanged", ascii) + } + + cyrillic := incyHeaderText("Здравствуйте") + if !strings.HasPrefix(cyrillic, "base64:") { + t.Fatalf("Cyrillic text = %q, want base64: prefix", cyrillic) + } + // The docs require base64 for anything outside the ASCII range, so the + // raw value must not survive on the wire. + if strings.Contains(cyrillic, "Здравствуйте") { + t.Errorf("Cyrillic text = %q, want the raw value base64-encoded", cyrillic) + } +} + +func TestApplyIncyHeaders_RejectsUndocumentedValues(t *testing.T) { + cfg := IncyConfig{ + AutoDetect: true, + SortOrder: "alphabetical", // not none|ping|name + PerAppProxyMode: "include", // Happ spelling, not bypass|proxy + NoisesType: "uuid", // not rand|str|hex + FragmentationLength: "10..30", // not min-max + FragmentationPackets: "3", // documented only as tlshello|1-3|1|all + BannerBgColor: "red", // not #RRGGBB + HideUrl: "maybe", // not 1|0 + } + h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android") + + for _, name := range []string{ + "Sort-Order", "Per-App-Proxy-Mode", "Noises-Type", + "Fragmentation-Length", "Fragmentation-Packets", "Banner-Bg-Color", "Hide-Url", + } { + if got := h.Get(name); got != "" { + t.Errorf("undocumented value accepted for %s: %q", name, got) + } + } +} + +func TestApplyIncyHeaders_PerAppListKeepsSeparators(t *testing.T) { + // The settings textarea takes one package per line, and a header cannot + // carry a newline, so each line must stay a separate list entry. + for _, tc := range []struct { + name, list, want string + }{ + {"newline", "com.google.chrome\norg.telegram.messenger", "com.google.chrome,org.telegram.messenger"}, + {"crlf and blank lines", "com.google.chrome\r\n\r\norg.telegram.messenger\r\n", "com.google.chrome,org.telegram.messenger"}, + {"url", " https://example.com/apps.txt ", "https://example.com/apps.txt"}, + } { + t.Run(tc.name, func(t *testing.T) { + cfg := IncyConfig{AutoDetect: true, PerAppProxyList: tc.list} + h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android") + if got := h.Get("Per-App-Proxy-List"); got != tc.want { + t.Errorf("Per-App-Proxy-List = %q, want %q", got, tc.want) + } + }) + } +} diff --git a/internal/sub/sub.go b/internal/sub/sub.go index 578c3ae87..6bcb55005 100644 --- a/internal/sub/sub.go +++ b/internal/sub/sub.go @@ -255,6 +255,36 @@ func (s *Server) initRouter() (*gin.Engine, error) { happCfg.PerAppList, _ = s.settingService.GetSubHappPerAppList() happCfg.LocalProxyAuth, _ = s.settingService.GetSubHappLocalProxyAuth() + incyCfg := IncyConfig{} + incyCfg.AutoDetect, _ = s.settingService.GetSubIncyAppAutoDetect() + incyCfg.ProfileDescription, _ = s.settingService.GetSubIncyProfileDescription() + incyCfg.SortOrder, _ = s.settingService.GetSubIncySortOrder() + incyCfg.SupportEmail, _ = s.settingService.GetSubIncySupportEmail() + incyCfg.AnnounceUrl, _ = s.settingService.GetSubIncyAnnounceUrl() + incyCfg.PremiumUrl, _ = s.settingService.GetSubIncyPremiumUrl() + incyCfg.BannerText, _ = s.settingService.GetSubIncyBannerText() + incyCfg.BannerButtonText, _ = s.settingService.GetSubIncyBannerButtonText() + incyCfg.BannerButtonUrl, _ = s.settingService.GetSubIncyBannerButtonUrl() + incyCfg.BannerBgColor, _ = s.settingService.GetSubIncyBannerBgColor() + incyCfg.BannerButtonColor, _ = s.settingService.GetSubIncyBannerButtonColor() + incyCfg.HideUrl, _ = s.settingService.GetSubIncyHideUrl() + incyCfg.HideCheck, _ = s.settingService.GetSubIncyHideCheck() + incyCfg.NoLimitEnabled, _ = s.settingService.GetSubIncyNoLimitEnabled() + incyCfg.PerAppProxyEnable, _ = s.settingService.GetSubIncyPerAppEnable() + incyCfg.PerAppProxyMode, _ = s.settingService.GetSubIncyPerAppMode() + incyCfg.PerAppProxyList, _ = s.settingService.GetSubIncyPerAppList() + incyCfg.FragmentationEnable, _ = s.settingService.GetSubIncyFragmentationEnable() + incyCfg.FragmentationLength, _ = s.settingService.GetSubIncyFragmentLength() + incyCfg.FragmentationInterval, _ = s.settingService.GetSubIncyFragmentInterval() + incyCfg.FragmentationPackets, _ = s.settingService.GetSubIncyFragmentPackets() + incyCfg.NoisesEnable, _ = s.settingService.GetSubIncyNoisesEnable() + incyCfg.NoisesType, _ = s.settingService.GetSubIncyNoisesType() + incyCfg.NoisesPacket, _ = s.settingService.GetSubIncyNoisesPacket() + incyCfg.NoisesDelay, _ = s.settingService.GetSubIncyNoisesDelay() + incyCfg.ServerAddressResolveEnable, _ = s.settingService.GetSubIncyResolveEnable() + incyCfg.ServerAddressResolveDnsDomain, _ = s.settingService.GetSubIncyResolveDnsDomain() + incyCfg.ServerAddressResolveDnsIp, _ = s.settingService.GetSubIncyResolveDnsIp() + // set per-request localizer from headers/cookies engine.Use(locale.LocalizerMiddleware()) @@ -340,6 +370,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { WithSUBRoutingRules(SubRoutingRules), WithSUBHideSettings(SubHideSettings), WithSUBHappConfig(happCfg), + WithSUBIncyConfig(incyCfg), WithSUBIncyEnableRouting(SubIncyEnableRouting), WithSUBIncyRoutingRules(SubIncyRoutingRules), ) diff --git a/internal/web/entity/entity.go b/internal/web/entity/entity.go index 441b2791e..c3ba6aab7 100644 --- a/internal/web/entity/entity.go +++ b/internal/web/entity/entity.go @@ -156,6 +156,37 @@ type AllSetting struct { SubHappPerAppList string `json:"subHappPerAppList" form:"subHappPerAppList"` SubHappLocalProxyAuth string `json:"subHappLocalProxyAuth" form:"subHappLocalProxyAuth"` + // Incy client customization settings (app-management). A "" value omits + // the header so the subscriber's own app setting is left alone. + SubIncyAppAutoDetect bool `json:"subIncyAppAutoDetect" form:"subIncyAppAutoDetect"` + SubIncyProfileDescription string `json:"subIncyProfileDescription" form:"subIncyProfileDescription"` + SubIncySortOrder string `json:"subIncySortOrder" form:"subIncySortOrder"` + SubIncySupportEmail string `json:"subIncySupportEmail" form:"subIncySupportEmail"` + SubIncyAnnounceUrl string `json:"subIncyAnnounceUrl" form:"subIncyAnnounceUrl"` + SubIncyPremiumUrl string `json:"subIncyPremiumUrl" form:"subIncyPremiumUrl"` + SubIncyBannerText string `json:"subIncyBannerText" form:"subIncyBannerText"` + SubIncyBannerButtonText string `json:"subIncyBannerButtonText" form:"subIncyBannerButtonText"` + SubIncyBannerButtonUrl string `json:"subIncyBannerButtonUrl" form:"subIncyBannerButtonUrl"` + SubIncyBannerBgColor string `json:"subIncyBannerBgColor" form:"subIncyBannerBgColor"` + SubIncyBannerButtonColor string `json:"subIncyBannerButtonColor" form:"subIncyBannerButtonColor"` + SubIncyHideUrl string `json:"subIncyHideUrl" form:"subIncyHideUrl"` + SubIncyHideCheck string `json:"subIncyHideCheck" form:"subIncyHideCheck"` + SubIncyNoLimitEnabled string `json:"subIncyNoLimitEnabled" form:"subIncyNoLimitEnabled"` + SubIncyPerAppEnable string `json:"subIncyPerAppEnable" form:"subIncyPerAppEnable"` + SubIncyPerAppMode string `json:"subIncyPerAppMode" form:"subIncyPerAppMode"` + SubIncyPerAppList string `json:"subIncyPerAppList" form:"subIncyPerAppList"` + SubIncyFragmentationEnable string `json:"subIncyFragmentationEnable" form:"subIncyFragmentationEnable"` + SubIncyFragmentLength string `json:"subIncyFragmentLength" form:"subIncyFragmentLength"` + SubIncyFragmentInterval string `json:"subIncyFragmentInterval" form:"subIncyFragmentInterval"` + SubIncyFragmentPackets string `json:"subIncyFragmentPackets" form:"subIncyFragmentPackets"` + SubIncyNoisesEnable string `json:"subIncyNoisesEnable" form:"subIncyNoisesEnable"` + SubIncyNoisesType string `json:"subIncyNoisesType" form:"subIncyNoisesType"` + SubIncyNoisesPacket string `json:"subIncyNoisesPacket" form:"subIncyNoisesPacket"` + SubIncyNoisesDelay string `json:"subIncyNoisesDelay" form:"subIncyNoisesDelay"` + SubIncyResolveEnable string `json:"subIncyResolveEnable" form:"subIncyResolveEnable"` + SubIncyResolveDnsDomain string `json:"subIncyResolveDnsDomain" form:"subIncyResolveDnsDomain"` + SubIncyResolveDnsIp string `json:"subIncyResolveDnsIp" form:"subIncyResolveDnsIp"` + LdapEnable bool `json:"ldapEnable" form:"ldapEnable"` LdapHost string `json:"ldapHost" form:"ldapHost"` LdapPort int `json:"ldapPort" form:"ldapPort" validate:"gte=0,lte=65535"` diff --git a/internal/web/service/setting.go b/internal/web/service/setting.go index 07027babe..6a3c16955 100644 --- a/internal/web/service/setting.go +++ b/internal/web/service/setting.go @@ -141,6 +141,34 @@ var defaultValueMap = map[string]string{ "subHappLocalProxyAuth": "auto", "subIncyEnableRouting": "false", "subIncyRoutingRules": "", + "subIncyAppAutoDetect": "false", + "subIncyProfileDescription": "", + "subIncySortOrder": "", + "subIncySupportEmail": "", + "subIncyAnnounceUrl": "", + "subIncyPremiumUrl": "", + "subIncyBannerText": "", + "subIncyBannerButtonText": "", + "subIncyBannerButtonUrl": "", + "subIncyBannerBgColor": "", + "subIncyBannerButtonColor": "", + "subIncyHideUrl": "", + "subIncyHideCheck": "", + "subIncyNoLimitEnabled": "", + "subIncyPerAppEnable": "", + "subIncyPerAppMode": "", + "subIncyPerAppList": "", + "subIncyFragmentationEnable": "", + "subIncyFragmentLength": "", + "subIncyFragmentInterval": "", + "subIncyFragmentPackets": "", + "subIncyNoisesEnable": "", + "subIncyNoisesType": "", + "subIncyNoisesPacket": "", + "subIncyNoisesDelay": "", + "subIncyResolveEnable": "", + "subIncyResolveDnsDomain": "", + "subIncyResolveDnsIp": "", "subListen": "", "subPort": "2096", "subPath": "/sub/", @@ -1014,6 +1042,118 @@ func (s *SettingService) GetSubIncyRoutingRules() (string, error) { return s.getString("subIncyRoutingRules") } +func (s *SettingService) GetSubIncyAppAutoDetect() (bool, error) { + return s.getBool("subIncyAppAutoDetect") +} + +func (s *SettingService) GetSubIncyProfileDescription() (string, error) { + return s.getString("subIncyProfileDescription") +} + +func (s *SettingService) GetSubIncySortOrder() (string, error) { + return s.getString("subIncySortOrder") +} + +func (s *SettingService) GetSubIncySupportEmail() (string, error) { + return s.getString("subIncySupportEmail") +} + +func (s *SettingService) GetSubIncyAnnounceUrl() (string, error) { + return s.getString("subIncyAnnounceUrl") +} + +func (s *SettingService) GetSubIncyPremiumUrl() (string, error) { + return s.getString("subIncyPremiumUrl") +} + +func (s *SettingService) GetSubIncyBannerText() (string, error) { + return s.getString("subIncyBannerText") +} + +func (s *SettingService) GetSubIncyBannerButtonText() (string, error) { + return s.getString("subIncyBannerButtonText") +} + +func (s *SettingService) GetSubIncyBannerButtonUrl() (string, error) { + return s.getString("subIncyBannerButtonUrl") +} + +func (s *SettingService) GetSubIncyBannerBgColor() (string, error) { + return s.getString("subIncyBannerBgColor") +} + +func (s *SettingService) GetSubIncyBannerButtonColor() (string, error) { + return s.getString("subIncyBannerButtonColor") +} + +func (s *SettingService) GetSubIncyHideUrl() (string, error) { + return s.getString("subIncyHideUrl") +} + +func (s *SettingService) GetSubIncyHideCheck() (string, error) { + return s.getString("subIncyHideCheck") +} + +func (s *SettingService) GetSubIncyNoLimitEnabled() (string, error) { + return s.getString("subIncyNoLimitEnabled") +} + +func (s *SettingService) GetSubIncyPerAppEnable() (string, error) { + return s.getString("subIncyPerAppEnable") +} + +func (s *SettingService) GetSubIncyPerAppMode() (string, error) { + return s.getString("subIncyPerAppMode") +} + +func (s *SettingService) GetSubIncyPerAppList() (string, error) { + return s.getString("subIncyPerAppList") +} + +func (s *SettingService) GetSubIncyFragmentationEnable() (string, error) { + return s.getString("subIncyFragmentationEnable") +} + +func (s *SettingService) GetSubIncyFragmentLength() (string, error) { + return s.getString("subIncyFragmentLength") +} + +func (s *SettingService) GetSubIncyFragmentInterval() (string, error) { + return s.getString("subIncyFragmentInterval") +} + +func (s *SettingService) GetSubIncyFragmentPackets() (string, error) { + return s.getString("subIncyFragmentPackets") +} + +func (s *SettingService) GetSubIncyNoisesEnable() (string, error) { + return s.getString("subIncyNoisesEnable") +} + +func (s *SettingService) GetSubIncyNoisesType() (string, error) { + return s.getString("subIncyNoisesType") +} + +func (s *SettingService) GetSubIncyNoisesPacket() (string, error) { + return s.getString("subIncyNoisesPacket") +} + +func (s *SettingService) GetSubIncyNoisesDelay() (string, error) { + return s.getString("subIncyNoisesDelay") +} + +func (s *SettingService) GetSubIncyResolveEnable() (string, error) { + return s.getString("subIncyResolveEnable") +} + +func (s *SettingService) GetSubIncyResolveDnsDomain() (string, error) { + return s.getString("subIncyResolveDnsDomain") +} + +func (s *SettingService) GetSubIncyResolveDnsIp() (string, error) { + return s.getString("subIncyResolveDnsIp") +} + func (s *SettingService) GetSubListen() (string, error) { return s.getString("subListen") } @@ -1672,6 +1812,10 @@ func validateSettingsURLs(allSetting *entity.AllSetting) error { &allSetting.SubHappFallbackUrl, &allSetting.SubHappSubInfoButtonLink, &allSetting.SubHappSubExpireButtonLink, + &allSetting.SubIncyAnnounceUrl, + &allSetting.SubIncyPremiumUrl, + &allSetting.SubIncyBannerButtonUrl, + &allSetting.SubIncyResolveDnsDomain, } { if strings.TrimSpace(*ptr) != "" { *ptr = common.EnsureURLScheme(strings.TrimSpace(*ptr)) diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index d097db920..cf9348353 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "عدد مرات إرسال بوت ديسكورد للتقارير الدورية. اختر فترة جاهزة، أو اختر «مخصص» لإدخال تعبير crontab.", "discordNotifyBackup": "نسخة احتياطية لقاعدة البيانات", "discordNotifyBackupDesc": "ابعت ملف النسخة الاحتياطية لقاعدة البيانات مع التقرير.", - "discordEventBusNotifyDesc": "اختر الأحداث التي تُطلق إشعارات Discord" + "discordEventBusNotifyDesc": "اختر الأحداث التي تُطلق إشعارات Discord", + "subIncyAppAutoDetect": "اكتشاف ترويسات Incy تلقائيًا", + "subIncyAppAutoDetectDesc": "إرسال ترويسات Incy عندما يكون User-Agent الخاص بالعميل INCY.", + "subIncyNotSet": "غير محدد", + "subIncyOn": "تشغيل", + "subIncyOff": "إيقاف", + "subIncyGroupApp": "التطبيق", + "subIncyProfileDescription": "وصف الملف", + "subIncyProfileDescriptionDesc": "وصف منفصل للاشتراك. النص غير ASCII يُرسل بصيغة base64.", + "subIncySortOrder": "ترتيب السيرفرات", + "subIncySortOrderDesc": "لهذا الاشتراك على iOS/Android، وعالمي على الكمبيوتر.", + "subIncySortNone": "ترتيب الاشتراك", + "subIncySortPing": "حسب البينج", + "subIncySortName": "حسب الاسم", + "subIncySupportEmail": "بريد الدعم", + "subIncySupportEmailDesc": "يضيف زر Email إلى بطاقة الاشتراك.", + "subIncyAnnounceUrl": "رابط الإعلان", + "subIncyAnnounceUrlDesc": "وجهة الإعلان في الشاشة الرئيسية.", + "subIncyPremiumUrl": "رابط Premium", + "subIncyPremiumUrlDesc": "زر Premium في البطاقة، وله الأولوية في زر البانر.", + "subIncyGroupBanners": "البانرات", + "subIncyBannerText": "نص البانر", + "subIncyBannerTextDesc": "يستبدل نص البانر. لمزودي premium فقط.", + "subIncyBannerButtonText": "نص زر البانر", + "subIncyBannerButtonTextDesc": "عنوان زر البانر. الفراغ يخفي الزر.", + "subIncyBannerButtonUrl": "رابط زر البانر", + "subIncyBannerButtonUrlDesc": "عنوان URL لزر البانر.", + "subIncyBannerBgColor": "خلفية البانر", + "subIncyBannerBgColorDesc": "لون hex، مثل #E53E3E.", + "subIncyBannerButtonColor": "لون زر البانر", + "subIncyBannerButtonColorDesc": "لون hex، مثل #38A169.", + "subIncyGroupPrivacy": "الخصوصية", + "subIncyHideUrl": "إخفاء رابط الاشتراك", + "subIncyHideUrlDesc": "يخفي الرابط من المشاركة والنسخ وQR والنسخ الاحتياطي.", + "subIncyHideCheck": "إخفاء زر الفحص", + "subIncyHideCheckDesc": "يزيل زر فحص الاتصال من الشاشة الرئيسية.", + "subIncyNoLimit": "وضع No-Limit (iOS)", + "subIncyNoLimitDesc": "وضع موفّر للذاكرة ليبقى النفق داخل حد 50 ميجابايت في iOS.", + "subIncyPerAppEnable": "وضع per-app (أندرويد)", + "subIncyPerAppEnableDesc": "يفعّل التقسيم حسب التطبيقات. الإيقاف يتجاوز اختيار المستخدم.", + "subIncyPerAppMode": "نوع وضع per-app", + "subIncyPerAppModeDesc": "proxy: التطبيقات المذكورة فقط تمر عبر VPN، وbypass العكس.", + "subIncyPerAppModeProxy": "التطبيقات المذكورة فقط", + "subIncyPerAppModeBypass": "التطبيقات المذكورة تتجاوز", + "subIncyPerAppList": "قائمة التطبيقات", + "subIncyPerAppListDesc": "أسماء الحزم بفواصل أو أسطر جديدة، أو رابط ملف نصي.", + "subIncyGroupNetwork": "الشبكة", + "subIncyFragmentationEnable": "التجزئة", + "subIncyFragmentationEnableDesc": "يفعّل تجزئة TCP ويتجاوز الإعداد العام.", + "subIncyFragmentLength": "طول الجزء", + "subIncyFragmentLengthDesc": "نطاق بالبايت، مثل 10-30.", + "subIncyFragmentInterval": "الفاصل بين الأجزاء", + "subIncyFragmentIntervalDesc": "نطاق بالمللي ثانية، مثل 20-40.", + "subIncyFragmentPackets": "حزم التجزئة", + "subIncyFragmentPacketsDesc": "أي حزم TCP تُجزّأ: tlshello أو 1-3 أو 1 أو all.", + "subIncyNoisesEnable": "حزم الضوضاء", + "subIncyNoisesEnableDesc": "يرسل ضوضاء UDP عشوائية قبل مصافحة VPN.", + "subIncyNoisesType": "نوع الضوضاء", + "subIncyNoisesTypeDesc": "rand أو str أو hex.", + "subIncyNoisesPacket": "محتوى حزمة الضوضاء", + "subIncyNoisesPacketDesc": "المحتوى؛ مع rand نطاق طول مثل 10-20.", + "subIncyNoisesDelay": "تأخير الضوضاء", + "subIncyNoisesDelayDesc": "نطاق التأخير بالمللي ثانية، مثل 10-50.", + "subIncyResolveEnable": "حل عنوان السيرفر (DoH)", + "subIncyResolveEnableDesc": "يحل عنوان السيرفر عبر DoH قبل الاتصال.", + "subIncyResolveDnsDomain": "نقطة DoH", + "subIncyResolveDnsDomainDesc": "رابط سيرفر DoH، غالبًا ينتهي بـ /dns-query.", + "subIncyResolveDnsIp": "IP سيرفر DoH", + "subIncyResolveDnsIpDesc": "عنوان IP لسيرفر DoH للتهيئة.", + "subIncyGroupRouting": "التوجيه" }, "xray": { "save": "احفظ", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index cbb086056..94638318b 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1788,7 +1788,76 @@ "discordNotifyTimeDesc": "How often the Discord bot sends periodic reports. Pick a preset interval, or choose Custom to enter a raw crontab expression.", "discordNotifyBackup": "Database Backup", "discordNotifyBackupDesc": "Send a database backup file with a report.", - "discordEventBusNotifyDesc": "Select which events trigger Discord notifications" + "discordEventBusNotifyDesc": "Select which events trigger Discord notifications", + "subIncyAppAutoDetect": "Incy header auto-detection", + "subIncyAppAutoDetectDesc": "Send the Incy app-management headers when the client User-Agent is INCY.", + "subIncyNotSet": "Not set", + "subIncyOn": "On", + "subIncyOff": "Off", + "subIncyGroupApp": "App", + "subIncyProfileDescription": "Profile description", + "subIncyProfileDescriptionDesc": "Separate subscription description. Non-ASCII text is sent as base64.", + "subIncySortOrder": "Server sort order", + "subIncySortOrderDesc": "Applies to this subscription on iOS/Android, globally on Desktop.", + "subIncySortNone": "Subscription order", + "subIncySortPing": "By ping", + "subIncySortName": "By name", + "subIncySupportEmail": "Support email", + "subIncySupportEmailDesc": "Adds an Email button to the subscription card.", + "subIncyAnnounceUrl": "Announcement link", + "subIncyAnnounceUrlDesc": "Link target for the home screen announcement.", + "subIncyPremiumUrl": "Premium link", + "subIncyPremiumUrlDesc": "Premium button in the card; also wins for the banner button.", + "subIncyGroupBanners": "Banners", + "subIncyBannerText": "Banner text", + "subIncyBannerTextDesc": "Overrides the banner text. Premium providers only.", + "subIncyBannerButtonText": "Banner button text", + "subIncyBannerButtonTextDesc": "Banner button label. Empty hides the button.", + "subIncyBannerButtonUrl": "Banner button link", + "subIncyBannerButtonUrlDesc": "Banner button URL.", + "subIncyBannerBgColor": "Banner background", + "subIncyBannerBgColorDesc": "Hex colour, e.g. #E53E3E.", + "subIncyBannerButtonColor": "Banner button colour", + "subIncyBannerButtonColorDesc": "Hex colour, e.g. #38A169.", + "subIncyGroupPrivacy": "Privacy", + "subIncyHideUrl": "Hide subscription URL", + "subIncyHideUrlDesc": "Hides the URL from Share, Copy, QR and backups.", + "subIncyHideCheck": "Hide the Check button", + "subIncyHideCheckDesc": "Removes the connectivity check button from the home screen.", + "subIncyNoLimit": "No-Limit mode (iOS)", + "subIncyNoLimitDesc": "Memory-tight mode that keeps the tunnel alive under the iOS 50 MB cap.", + "subIncyPerAppEnable": "Per-app mode (Android)", + "subIncyPerAppEnableDesc": "Enables per-app split tunnelling. Off overrides the user's choice.", + "subIncyPerAppMode": "Per-app mode type", + "subIncyPerAppModeDesc": "Proxy: only the listed apps use the VPN. Bypass: they skip it.", + "subIncyPerAppModeProxy": "Only listed apps", + "subIncyPerAppModeBypass": "Listed apps bypass", + "subIncyPerAppList": "App list", + "subIncyPerAppListDesc": "Package names separated by commas or newlines, or a URL to a text file.", + "subIncyGroupNetwork": "Network", + "subIncyFragmentationEnable": "Fragmentation", + "subIncyFragmentationEnableDesc": "Enables TCP fragmentation, overriding the global setting.", + "subIncyFragmentLength": "Fragment length", + "subIncyFragmentLengthDesc": "Range in bytes, e.g. 10-30.", + "subIncyFragmentInterval": "Fragment interval", + "subIncyFragmentIntervalDesc": "Range in milliseconds, e.g. 20-40.", + "subIncyFragmentPackets": "Fragment packets", + "subIncyFragmentPacketsDesc": "Which TCP packets to fragment: tlshello, 1-3, 1 or all.", + "subIncyNoisesEnable": "Noise packets", + "subIncyNoisesEnableDesc": "Sends random UDP noise before the VPN handshake.", + "subIncyNoisesType": "Noise payload type", + "subIncyNoisesTypeDesc": "rand, str or hex.", + "subIncyNoisesPacket": "Noise packet content", + "subIncyNoisesPacketDesc": "Payload; with rand, a length range like 10-20.", + "subIncyNoisesDelay": "Noise delay", + "subIncyNoisesDelayDesc": "Delay range in milliseconds, e.g. 10-50.", + "subIncyResolveEnable": "Resolve server address (DoH)", + "subIncyResolveEnableDesc": "Pre-resolves the server address over DoH before connecting.", + "subIncyResolveDnsDomain": "DoH endpoint", + "subIncyResolveDnsDomainDesc": "DoH server URL, usually ending in /dns-query.", + "subIncyResolveDnsIp": "DoH server IP", + "subIncyResolveDnsIpDesc": "IP of the DoH server, used for bootstrap.", + "subIncyGroupRouting": "Routing" }, "xray": { "save": "Save", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index f3d6aac69..b9721e53a 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Con qué frecuencia el bot de Discord envía informes periódicos. Elige un intervalo predefinido o selecciona Personalizado para introducir una expresión crontab.", "discordNotifyBackup": "Respaldo de Base de Datos", "discordNotifyBackupDesc": "Incluir archivo de respaldo de base de datos con notificación de informe.", - "discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord" + "discordEventBusNotifyDesc": "Seleccione qué eventos generan notificaciones de Discord", + "subIncyAppAutoDetect": "Detección automática de cabeceras de Incy", + "subIncyAppAutoDetectDesc": "Envía las cabeceras de Incy cuando el User-Agent del cliente es INCY.", + "subIncyNotSet": "Sin definir", + "subIncyOn": "Activado", + "subIncyOff": "Desactivado", + "subIncyGroupApp": "Aplicación", + "subIncyProfileDescription": "Descripción del perfil", + "subIncyProfileDescriptionDesc": "Descripción aparte de la suscripción. El texto no ASCII se envía en base64.", + "subIncySortOrder": "Orden de servidores", + "subIncySortOrderDesc": "Para esta suscripción en iOS/Android, global en escritorio.", + "subIncySortNone": "Orden de la suscripción", + "subIncySortPing": "Por ping", + "subIncySortName": "Por nombre", + "subIncySupportEmail": "Email de soporte", + "subIncySupportEmailDesc": "Añade un botón Email a la tarjeta de suscripción.", + "subIncyAnnounceUrl": "Enlace del anuncio", + "subIncyAnnounceUrlDesc": "Destino del anuncio de la pantalla principal.", + "subIncyPremiumUrl": "Enlace Premium", + "subIncyPremiumUrlDesc": "Botón Premium en la tarjeta; también manda en el botón del banner.", + "subIncyGroupBanners": "Banners", + "subIncyBannerText": "Texto del banner", + "subIncyBannerTextDesc": "Sustituye el texto del banner. Solo para proveedores premium.", + "subIncyBannerButtonText": "Texto del botón del banner", + "subIncyBannerButtonTextDesc": "Etiqueta del botón del banner. Vacío lo oculta.", + "subIncyBannerButtonUrl": "Enlace del botón del banner", + "subIncyBannerButtonUrlDesc": "URL del botón del banner.", + "subIncyBannerBgColor": "Fondo del banner", + "subIncyBannerBgColorDesc": "Color hex, p. ej. #E53E3E.", + "subIncyBannerButtonColor": "Color del botón del banner", + "subIncyBannerButtonColorDesc": "Color hex, p. ej. #38A169.", + "subIncyGroupPrivacy": "Privacidad", + "subIncyHideUrl": "Ocultar URL de la suscripción", + "subIncyHideUrlDesc": "Oculta la URL al compartir, copiar, en QR y en copias de seguridad.", + "subIncyHideCheck": "Ocultar el botón de comprobación", + "subIncyHideCheckDesc": "Quita el botón de comprobación de conexión de la pantalla principal.", + "subIncyNoLimit": "Modo No-Limit (iOS)", + "subIncyNoLimitDesc": "Modo de bajo consumo de memoria para no pasar del límite de 50 MB en iOS.", + "subIncyPerAppEnable": "Modo per-app (Android)", + "subIncyPerAppEnableDesc": "Activa el túnel por aplicación. Desactivado anula la elección del usuario.", + "subIncyPerAppMode": "Tipo de modo per-app", + "subIncyPerAppModeDesc": "proxy: solo las apps listadas usan la VPN; bypass: al contrario.", + "subIncyPerAppModeProxy": "Solo las apps listadas", + "subIncyPerAppModeBypass": "Las apps listadas se saltan", + "subIncyPerAppList": "Lista de apps", + "subIncyPerAppListDesc": "Nombres de paquete separados por comas o saltos de línea, o una URL a un archivo.", + "subIncyGroupNetwork": "Red", + "subIncyFragmentationEnable": "Fragmentación", + "subIncyFragmentationEnableDesc": "Activa la fragmentación TCP y anula la configuración global.", + "subIncyFragmentLength": "Longitud del fragmento", + "subIncyFragmentLengthDesc": "Rango en bytes, p. ej. 10-30.", + "subIncyFragmentInterval": "Intervalo de fragmentación", + "subIncyFragmentIntervalDesc": "Rango en milisegundos, p. ej. 20-40.", + "subIncyFragmentPackets": "Paquetes a fragmentar", + "subIncyFragmentPacketsDesc": "Qué paquetes TCP fragmentar: tlshello, 1-3, 1 o all.", + "subIncyNoisesEnable": "Paquetes de ruido", + "subIncyNoisesEnableDesc": "Envía ruido UDP aleatorio antes del handshake de la VPN.", + "subIncyNoisesType": "Tipo de ruido", + "subIncyNoisesTypeDesc": "rand, str o hex.", + "subIncyNoisesPacket": "Contenido del paquete de ruido", + "subIncyNoisesPacketDesc": "Carga útil; con rand, un rango de longitud como 10-20.", + "subIncyNoisesDelay": "Retardo del ruido", + "subIncyNoisesDelayDesc": "Rango de retardo en milisegundos, p. ej. 10-50.", + "subIncyResolveEnable": "Resolver dirección del servidor (DoH)", + "subIncyResolveEnableDesc": "Resuelve la dirección del servidor por DoH antes de conectar.", + "subIncyResolveDnsDomain": "Endpoint DoH", + "subIncyResolveDnsDomainDesc": "URL del servidor DoH, normalmente acaba en /dns-query.", + "subIncyResolveDnsIp": "IP del servidor DoH", + "subIncyResolveDnsIpDesc": "IP del servidor DoH para el arranque.", + "subIncyGroupRouting": "Enrutado" }, "xray": { "save": "Guardar configuración", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index 8880dc3b7..cca66e175 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "هر چند وقت یک‌بار ربات دیسکورد گزارش دوره‌ای بفرستد. یک بازهٔ آماده انتخاب کنید یا گزینهٔ سفارشی را بزنید تا عبارت crontab وارد کنید.", "discordNotifyBackup": "پشتیبان‌گیری از دیتابیس", "discordNotifyBackupDesc": "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند", - "discordEventBusNotifyDesc": "انتخاب کنید کدام رویدادها اعلان دیسکورد را فعال می‌کنند" + "discordEventBusNotifyDesc": "انتخاب کنید کدام رویدادها اعلان دیسکورد را فعال می‌کنند", + "subIncyAppAutoDetect": "تشخیص خودکار هدرهای Incy", + "subIncyAppAutoDetectDesc": "ارسال هدرهای Incy وقتی User-Agent کلاینت INCY باشد.", + "subIncyNotSet": "تنظیم نشده", + "subIncyOn": "روشن", + "subIncyOff": "خاموش", + "subIncyGroupApp": "برنامه", + "subIncyProfileDescription": "توضیح پروفایل", + "subIncyProfileDescriptionDesc": "توضیح جداگانه اشتراک. متن غیر-ASCII بهصورت base64 ارسال میشود.", + "subIncySortOrder": "ترتیب سرورها", + "subIncySortOrderDesc": "برای همین اشتراک در iOS/Android و بهصورت کلی در دسکتاپ.", + "subIncySortNone": "بر اساس ترتیب اشتراک", + "subIncySortPing": "بر اساس پینگ", + "subIncySortName": "بر اساس نام", + "subIncySupportEmail": "ایمیل پشتیبانی", + "subIncySupportEmailDesc": "دکمه Email را به کارت اشتراک اضافه میکند.", + "subIncyAnnounceUrl": "لینک اعلان", + "subIncyAnnounceUrlDesc": "مقصد لینک اعلان در صفحه اصلی.", + "subIncyPremiumUrl": "لینک Premium", + "subIncyPremiumUrlDesc": "دکمه Premium در کارت؛ برای دکمه بنر هم اولویت دارد.", + "subIncyGroupBanners": "بنرها", + "subIncyBannerText": "متن بنر", + "subIncyBannerTextDesc": "متن بنر را بازنویسی میکند. فقط برای ارائهدهندگان premium.", + "subIncyBannerButtonText": "متن دکمه بنر", + "subIncyBannerButtonTextDesc": "برچسب دکمه بنر. خالی بودن یعنی دکمه نمایش داده نشود.", + "subIncyBannerButtonUrl": "لینک دکمه بنر", + "subIncyBannerButtonUrlDesc": "آدرس دکمه بنر.", + "subIncyBannerBgColor": "پسزمینه بنر", + "subIncyBannerBgColorDesc": "رنگ hex، مثلاً #E53E3E.", + "subIncyBannerButtonColor": "رنگ دکمه بنر", + "subIncyBannerButtonColorDesc": "رنگ hex، مثلاً #38A169.", + "subIncyGroupPrivacy": "حریم خصوصی", + "subIncyHideUrl": "پنهان کردن آدرس اشتراک", + "subIncyHideUrlDesc": "آدرس را از اشتراکگذاری، کپی، QR و پشتیبانگیری پنهان میکند.", + "subIncyHideCheck": "پنهان کردن دکمه بررسی", + "subIncyHideCheckDesc": "دکمه بررسی اتصال را از صفحه اصلی حذف میکند.", + "subIncyNoLimit": "حالت No-Limit (iOS)", + "subIncyNoLimitDesc": "حالت کممصرف تا تونل زیر سقف ۵۰ مگابایتی iOS بماند.", + "subIncyPerAppEnable": "حالت per-app (اندروید)", + "subIncyPerAppEnableDesc": "تونل تفکیکی بر اساس برنامه را فعال میکند. خاموش، انتخاب کاربر را بازنویسی میکند.", + "subIncyPerAppMode": "نوع حالت per-app", + "subIncyPerAppModeDesc": "proxy یعنی فقط برنامههای فهرستشده از VPN بروند، bypass برعکس.", + "subIncyPerAppModeProxy": "فقط برنامههای فهرستشده", + "subIncyPerAppModeBypass": "برنامههای فهرستشده دور میزنند", + "subIncyPerAppList": "فهرست برنامهها", + "subIncyPerAppListDesc": "نام بستهها با کاما یا خط جدید، یا آدرس یک فایل متنی.", + "subIncyGroupNetwork": "شبکه", + "subIncyFragmentationEnable": "تکهتکهسازی", + "subIncyFragmentationEnableDesc": "تکهتکهسازی TCP را فعال میکند و تنظیم کلی را بازنویسی میکند.", + "subIncyFragmentLength": "طول قطعه", + "subIncyFragmentLengthDesc": "بازه به بایت، مثلاً 10-30.", + "subIncyFragmentInterval": "فاصله قطعهها", + "subIncyFragmentIntervalDesc": "بازه به میلیثانیه، مثلاً 20-40.", + "subIncyFragmentPackets": "بستههای قطعه", + "subIncyFragmentPacketsDesc": "کدام بستههای TCP تکه شوند: tlshello، 1-3، 1 یا all.", + "subIncyNoisesEnable": "بستههای نویز", + "subIncyNoisesEnableDesc": "پیش از هندشیک VPN نویز تصادفی UDP میفرستد.", + "subIncyNoisesType": "نوع نویز", + "subIncyNoisesTypeDesc": "rand، str یا hex.", + "subIncyNoisesPacket": "محتوای بسته نویز", + "subIncyNoisesPacketDesc": "محتوا؛ در حالت rand یک بازه طول، مثلاً 10-20.", + "subIncyNoisesDelay": "تأخیر نویز", + "subIncyNoisesDelayDesc": "بازه تأخیر به میلیثانیه، مثلاً 10-50.", + "subIncyResolveEnable": "حل آدرس سرور (DoH)", + "subIncyResolveEnableDesc": "آدرس سرور را پیش از اتصال با DoH حل میکند.", + "subIncyResolveDnsDomain": "نقطه پایانی DoH", + "subIncyResolveDnsDomainDesc": "آدرس سرور DoH، معمولاً به /dns-query ختم میشود.", + "subIncyResolveDnsIp": "آیپی سرور DoH", + "subIncyResolveDnsIpDesc": "آیپی سرور DoH برای بوتاسترپ.", + "subIncyGroupRouting": "مسیریابی" }, "xray": { "save": "ذخیره", diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 77734beea..3b48d1b5d 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Seberapa sering bot Discord mengirim laporan berkala. Pilih interval siap pakai, atau pilih Kustom untuk memasukkan ekspresi crontab.", "discordNotifyBackup": "Cadangan Database", "discordNotifyBackupDesc": "Kirim berkas cadangan database dengan laporan.", - "discordEventBusNotifyDesc": "Pilih peristiwa yang memicu notifikasi Discord" + "discordEventBusNotifyDesc": "Pilih peristiwa yang memicu notifikasi Discord", + "subIncyAppAutoDetect": "Deteksi otomatis header Incy", + "subIncyAppAutoDetectDesc": "Kirim header Incy saat User-Agent klien adalah INCY.", + "subIncyNotSet": "Tidak diatur", + "subIncyOn": "Aktif", + "subIncyOff": "Nonaktif", + "subIncyGroupApp": "Aplikasi", + "subIncyProfileDescription": "Deskripsi profil", + "subIncyProfileDescriptionDesc": "Deskripsi langganan terpisah. Teks non-ASCII dikirim sebagai base64.", + "subIncySortOrder": "Urutan server", + "subIncySortOrderDesc": "Untuk langganan ini di iOS/Android, global di desktop.", + "subIncySortNone": "Urutan langganan", + "subIncySortPing": "Menurut ping", + "subIncySortName": "Menurut nama", + "subIncySupportEmail": "Email dukungan", + "subIncySupportEmailDesc": "Menambahkan tombol Email di kartu langganan.", + "subIncyAnnounceUrl": "Tautan pengumuman", + "subIncyAnnounceUrlDesc": "Tujuan tautan pengumuman di layar utama.", + "subIncyPremiumUrl": "Tautan Premium", + "subIncyPremiumUrlDesc": "Tombol Premium di kartu; juga diprioritaskan untuk tombol banner.", + "subIncyGroupBanners": "Banner", + "subIncyBannerText": "Teks banner", + "subIncyBannerTextDesc": "Menimpa teks banner. Hanya untuk penyedia premium.", + "subIncyBannerButtonText": "Teks tombol banner", + "subIncyBannerButtonTextDesc": "Label tombol banner. Kosong menyembunyikan tombol.", + "subIncyBannerButtonUrl": "Tautan tombol banner", + "subIncyBannerButtonUrlDesc": "URL tombol banner.", + "subIncyBannerBgColor": "Latar banner", + "subIncyBannerBgColorDesc": "Warna hex, mis. #E53E3E.", + "subIncyBannerButtonColor": "Warna tombol banner", + "subIncyBannerButtonColorDesc": "Warna hex, mis. #38A169.", + "subIncyGroupPrivacy": "Privasi", + "subIncyHideUrl": "Sembunyikan URL langganan", + "subIncyHideUrlDesc": "Menyembunyikan URL dari Bagikan, Salin, QR, dan cadangan.", + "subIncyHideCheck": "Sembunyikan tombol Check", + "subIncyHideCheckDesc": "Menghapus tombol pemeriksaan koneksi dari layar utama.", + "subIncyNoLimit": "Mode No-Limit (iOS)", + "subIncyNoLimitDesc": "Mode hemat memori agar tunnel tetap hidup di bawah batas 50 MB iOS.", + "subIncyPerAppEnable": "Mode per-app (Android)", + "subIncyPerAppEnableDesc": "Mengaktifkan split tunnel per aplikasi. Nonaktif menimpa pilihan pengguna.", + "subIncyPerAppMode": "Jenis mode per-app", + "subIncyPerAppModeDesc": "proxy: hanya aplikasi terdaftar lewat VPN; bypass: sebaliknya.", + "subIncyPerAppModeProxy": "Hanya aplikasi terdaftar", + "subIncyPerAppModeBypass": "Aplikasi terdaftar dilewati", + "subIncyPerAppList": "Daftar aplikasi", + "subIncyPerAppListDesc": "Nama paket dipisah koma atau baris baru, atau URL ke berkas teks.", + "subIncyGroupNetwork": "Jaringan", + "subIncyFragmentationEnable": "Fragmentasi", + "subIncyFragmentationEnableDesc": "Mengaktifkan fragmentasi TCP dan menimpa pengaturan global.", + "subIncyFragmentLength": "Panjang fragmen", + "subIncyFragmentLengthDesc": "Rentang dalam byte, mis. 10-30.", + "subIncyFragmentInterval": "Interval fragmen", + "subIncyFragmentIntervalDesc": "Rentang dalam milidetik, mis. 20-40.", + "subIncyFragmentPackets": "Paket fragmen", + "subIncyFragmentPacketsDesc": "Paket TCP mana yang difragmentasi: tlshello, 1-3, 1, atau all.", + "subIncyNoisesEnable": "Paket noise", + "subIncyNoisesEnableDesc": "Mengirim noise UDP acak sebelum handshake VPN.", + "subIncyNoisesType": "Jenis noise", + "subIncyNoisesTypeDesc": "rand, str, atau hex.", + "subIncyNoisesPacket": "Isi paket noise", + "subIncyNoisesPacketDesc": "Muatan; untuk rand berupa rentang panjang, mis. 10-20.", + "subIncyNoisesDelay": "Jeda noise", + "subIncyNoisesDelayDesc": "Rentang jeda dalam milidetik, mis. 10-50.", + "subIncyResolveEnable": "Resolusi alamat server (DoH)", + "subIncyResolveEnableDesc": "Meresolusi alamat server via DoH sebelum terhubung.", + "subIncyResolveDnsDomain": "Endpoint DoH", + "subIncyResolveDnsDomainDesc": "URL server DoH, biasanya berakhiran /dns-query.", + "subIncyResolveDnsIp": "IP server DoH", + "subIncyResolveDnsIpDesc": "IP server DoH untuk bootstrap.", + "subIncyGroupRouting": "Perutean" }, "xray": { "save": "Simpan", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 696b03694..3746ff2de 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Discord ボットが定期レポートを送信する頻度です。プリセットの間隔を選ぶか、「カスタム」を選んで crontab 式を入力します。", "discordNotifyBackup": "データベースバックアップ", "discordNotifyBackupDesc": "レポート付きのデータベースバックアップファイルを送信", - "discordEventBusNotifyDesc": "Discord通知をトリガーするイベントを選択してください" + "discordEventBusNotifyDesc": "Discord通知をトリガーするイベントを選択してください", + "subIncyAppAutoDetect": "Incy ヘッダー自動判別", + "subIncyAppAutoDetectDesc": "クライアントの User-Agent が INCY のとき Incy ヘッダーを送信します。", + "subIncyNotSet": "未設定", + "subIncyOn": "オン", + "subIncyOff": "オフ", + "subIncyGroupApp": "アプリ", + "subIncyProfileDescription": "プロファイルの説明", + "subIncyProfileDescriptionDesc": "購読とは別の説明文。非 ASCII は base64 で送信されます。", + "subIncySortOrder": "サーバーの並び順", + "subIncySortOrderDesc": "iOS/Android ではこの購読のみ、デスクトップでは全体設定です。", + "subIncySortNone": "購読順", + "subIncySortPing": "ping 順", + "subIncySortName": "名前順", + "subIncySupportEmail": "サポートメール", + "subIncySupportEmailDesc": "購読カードに Email ボタンを追加します。", + "subIncyAnnounceUrl": "お知らせリンク", + "subIncyAnnounceUrlDesc": "ホーム画面のお知らせの遷移先です。", + "subIncyPremiumUrl": "Premium リンク", + "subIncyPremiumUrlDesc": "カードの Premium ボタン。バナーのボタンでも優先されます。", + "subIncyGroupBanners": "バナー", + "subIncyBannerText": "バナーテキスト", + "subIncyBannerTextDesc": "バナーテキストを上書きします。Premium プロバイダーのみ。", + "subIncyBannerButtonText": "バナーボタンのテキスト", + "subIncyBannerButtonTextDesc": "バナーボタンのラベル。空にすると非表示になります。", + "subIncyBannerButtonUrl": "バナーボタンのリンク", + "subIncyBannerButtonUrlDesc": "バナーボタンの URL。", + "subIncyBannerBgColor": "バナーの背景色", + "subIncyBannerBgColorDesc": "16 進カラー、例 #E53E3E。", + "subIncyBannerButtonColor": "バナーボタンの色", + "subIncyBannerButtonColorDesc": "16 進カラー、例 #38A169。", + "subIncyGroupPrivacy": "プライバシー", + "subIncyHideUrl": "購読 URL を隠す", + "subIncyHideUrlDesc": "共有・コピー・QR・バックアップから URL を隠します。", + "subIncyHideCheck": "チェックボタンを隠す", + "subIncyHideCheckDesc": "ホーム画面から接続チェックボタンを削除します。", + "subIncyNoLimit": "No-Limit モード (iOS)", + "subIncyNoLimitDesc": "iOS の 50 MB 制限内でトンネルを維持する省メモリモードです。", + "subIncyPerAppEnable": "アプリ別モード (Android)", + "subIncyPerAppEnableDesc": "アプリ別スプリットトンネルを有効にします。オフはユーザー設定を上書きします。", + "subIncyPerAppMode": "アプリ別モードの種類", + "subIncyPerAppModeDesc": "proxy は指定アプリのみ VPN、bypass はその逆です。", + "subIncyPerAppModeProxy": "指定アプリのみ", + "subIncyPerAppModeBypass": "指定アプリを除外", + "subIncyPerAppList": "アプリ一覧", + "subIncyPerAppListDesc": "パッケージ名をカンマか改行で区切るか、テキストファイルの URL。", + "subIncyGroupNetwork": "ネットワーク", + "subIncyFragmentationEnable": "フラグメンテーション", + "subIncyFragmentationEnableDesc": "TCP フラグメンテーションを有効にし、全体設定を上書きします。", + "subIncyFragmentLength": "フラグメント長", + "subIncyFragmentLengthDesc": "バイト範囲、例 10-30。", + "subIncyFragmentInterval": "フラグメント間隔", + "subIncyFragmentIntervalDesc": "ミリ秒範囲、例 20-40。", + "subIncyFragmentPackets": "フラグメント対象パケット", + "subIncyFragmentPacketsDesc": "分割する TCP パケット: tlshello、1-3、1、all。", + "subIncyNoisesEnable": "ノイズパケット", + "subIncyNoisesEnableDesc": "VPN ハンドシェイク前にランダムな UDP ノイズを送信します。", + "subIncyNoisesType": "ノイズの種類", + "subIncyNoisesTypeDesc": "rand、str、hex。", + "subIncyNoisesPacket": "ノイズの内容", + "subIncyNoisesPacketDesc": "ペイロード。rand の場合は長さ範囲、例 10-20。", + "subIncyNoisesDelay": "ノイズ遅延", + "subIncyNoisesDelayDesc": "遅延範囲 (ミリ秒)、例 10-50。", + "subIncyResolveEnable": "サーバーアドレスの解決 (DoH)", + "subIncyResolveEnableDesc": "接続前に DoH でサーバーアドレスを解決します。", + "subIncyResolveDnsDomain": "DoH エンドポイント", + "subIncyResolveDnsDomainDesc": "DoH サーバー URL。通常は /dns-query で終わります。", + "subIncyResolveDnsIp": "DoH サーバー IP", + "subIncyResolveDnsIpDesc": "ブートストラップ用の DoH サーバー IP。", + "subIncyGroupRouting": "ルーティング" }, "xray": { "importRules": "ルールをインポート", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index 9d49960f4..9dd49eb04 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Com que frequência o bot do Discord envia relatórios periódicos. Escolha um intervalo predefinido ou selecione Personalizado para inserir uma expressão crontab.", "discordNotifyBackup": "Backup do Banco de Dados", "discordNotifyBackupDesc": "Enviar arquivo de backup do banco de dados junto com o relatório.", - "discordEventBusNotifyDesc": "Selecione quais eventos disparam notificações no Discord" + "discordEventBusNotifyDesc": "Selecione quais eventos disparam notificações no Discord", + "subIncyAppAutoDetect": "Detecção automática de cabeçalhos do Incy", + "subIncyAppAutoDetectDesc": "Envia os cabeçalhos do Incy quando o User-Agent do cliente é INCY.", + "subIncyNotSet": "Não definido", + "subIncyOn": "Ativado", + "subIncyOff": "Desativado", + "subIncyGroupApp": "Aplicativo", + "subIncyProfileDescription": "Descrição do perfil", + "subIncyProfileDescriptionDesc": "Descrição separada da assinatura. Texto não ASCII vai em base64.", + "subIncySortOrder": "Ordem dos servidores", + "subIncySortOrderDesc": "Para esta assinatura no iOS/Android, global no desktop.", + "subIncySortNone": "Ordem da assinatura", + "subIncySortPing": "Por ping", + "subIncySortName": "Por nome", + "subIncySupportEmail": "Email de suporte", + "subIncySupportEmailDesc": "Adiciona um botão Email ao cartão da assinatura.", + "subIncyAnnounceUrl": "Link do anúncio", + "subIncyAnnounceUrlDesc": "Destino do anúncio na tela inicial.", + "subIncyPremiumUrl": "Link Premium", + "subIncyPremiumUrlDesc": "Botão Premium no cartão; também tem prioridade no botão do banner.", + "subIncyGroupBanners": "Banners", + "subIncyBannerText": "Texto do banner", + "subIncyBannerTextDesc": "Substitui o texto do banner. Apenas para provedores premium.", + "subIncyBannerButtonText": "Texto do botão do banner", + "subIncyBannerButtonTextDesc": "Rótulo do botão do banner. Vazio oculta o botão.", + "subIncyBannerButtonUrl": "Link do botão do banner", + "subIncyBannerButtonUrlDesc": "URL do botão do banner.", + "subIncyBannerBgColor": "Fundo do banner", + "subIncyBannerBgColorDesc": "Cor hex, ex. #E53E3E.", + "subIncyBannerButtonColor": "Cor do botão do banner", + "subIncyBannerButtonColorDesc": "Cor hex, ex. #38A169.", + "subIncyGroupPrivacy": "Privacidade", + "subIncyHideUrl": "Ocultar URL da assinatura", + "subIncyHideUrlDesc": "Oculta a URL em Compartilhar, Copiar, QR e backups.", + "subIncyHideCheck": "Ocultar o botão Check", + "subIncyHideCheckDesc": "Remove o botão de verificação de conexão da tela inicial.", + "subIncyNoLimit": "Modo No-Limit (iOS)", + "subIncyNoLimitDesc": "Modo econômico para o túnel sobreviver ao limite de 50 MB do iOS.", + "subIncyPerAppEnable": "Modo per-app (Android)", + "subIncyPerAppEnableDesc": "Ativa o split tunnel por aplicativo. Desativado sobrepõe a escolha do usuário.", + "subIncyPerAppMode": "Tipo do modo per-app", + "subIncyPerAppModeDesc": "proxy: só os apps listados usam a VPN; bypass: o contrário.", + "subIncyPerAppModeProxy": "Somente apps listados", + "subIncyPerAppModeBypass": "Apps listados ignoram", + "subIncyPerAppList": "Lista de apps", + "subIncyPerAppListDesc": "Nomes de pacote separados por vírgula ou linha, ou uma URL de arquivo texto.", + "subIncyGroupNetwork": "Rede", + "subIncyFragmentationEnable": "Fragmentação", + "subIncyFragmentationEnableDesc": "Ativa a fragmentação TCP e sobrepõe a configuração global.", + "subIncyFragmentLength": "Tamanho do fragmento", + "subIncyFragmentLengthDesc": "Intervalo em bytes, ex. 10-30.", + "subIncyFragmentInterval": "Intervalo de fragmentação", + "subIncyFragmentIntervalDesc": "Intervalo em milissegundos, ex. 20-40.", + "subIncyFragmentPackets": "Pacotes do fragmento", + "subIncyFragmentPacketsDesc": "Quais pacotes TCP fragmentar: tlshello, 1-3, 1 ou all.", + "subIncyNoisesEnable": "Pacotes de ruído", + "subIncyNoisesEnableDesc": "Envia ruído UDP aleatório antes do handshake da VPN.", + "subIncyNoisesType": "Tipo de ruído", + "subIncyNoisesTypeDesc": "rand, str ou hex.", + "subIncyNoisesPacket": "Conteúdo do pacote de ruído", + "subIncyNoisesPacketDesc": "Payload; com rand, um intervalo de tamanho como 10-20.", + "subIncyNoisesDelay": "Atraso do ruído", + "subIncyNoisesDelayDesc": "Intervalo de atraso em milissegundos, ex. 10-50.", + "subIncyResolveEnable": "Resolver endereço do servidor (DoH)", + "subIncyResolveEnableDesc": "Resolve o endereço do servidor via DoH antes de conectar.", + "subIncyResolveDnsDomain": "Endpoint DoH", + "subIncyResolveDnsDomainDesc": "URL do servidor DoH, geralmente terminando em /dns-query.", + "subIncyResolveDnsIp": "IP do servidor DoH", + "subIncyResolveDnsIpDesc": "IP do servidor DoH para bootstrap.", + "subIncyGroupRouting": "Roteamento" }, "xray": { "importRules": "Importar regras", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 89e493f8e..aa9cc281e 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Как часто бот Discord отправляет периодические отчёты. Выберите готовый интервал или «Произвольный», чтобы ввести выражение crontab.", "discordNotifyBackup": "Резервное копирование базы данных", "discordNotifyBackupDesc": "Отправлять уведомление с файлом резервной копии базы данных", - "discordEventBusNotifyDesc": "Выберите события для Discord уведомлений" + "discordEventBusNotifyDesc": "Выберите события для Discord уведомлений", + "subIncyAppAutoDetect": "Автоопределение заголовков Incy", + "subIncyAppAutoDetectDesc": "Отправлять заголовки Incy, когда User-Agent клиента — INCY.", + "subIncyNotSet": "Не задано", + "subIncyOn": "Вкл", + "subIncyOff": "Выкл", + "subIncyGroupApp": "Приложение", + "subIncyProfileDescription": "Описание профиля", + "subIncyProfileDescriptionDesc": "Отдельное описание подписки. Не-ASCII текст уходит в base64.", + "subIncySortOrder": "Порядок серверов", + "subIncySortOrderDesc": "Для этой подписки на iOS/Android, глобально на десктопе.", + "subIncySortNone": "Как в подписке", + "subIncySortPing": "По пингу", + "subIncySortName": "По имени", + "subIncySupportEmail": "Email поддержки", + "subIncySupportEmailDesc": "Добавляет кнопку «Email» в карточку подписки.", + "subIncyAnnounceUrl": "Ссылка объявления", + "subIncyAnnounceUrlDesc": "Куда ведёт объявление на главном экране.", + "subIncyPremiumUrl": "Ссылка Premium", + "subIncyPremiumUrlDesc": "Кнопка Premium в карточке; приоритетна и для кнопки баннера.", + "subIncyGroupBanners": "Баннеры", + "subIncyBannerText": "Текст баннера", + "subIncyBannerTextDesc": "Переопределяет текст баннера. Только для premium-провайдеров.", + "subIncyBannerButtonText": "Текст кнопки баннера", + "subIncyBannerButtonTextDesc": "Подпись кнопки баннера. Пустое значение скрывает кнопку.", + "subIncyBannerButtonUrl": "Ссылка кнопки баннера", + "subIncyBannerButtonUrlDesc": "URL кнопки баннера.", + "subIncyBannerBgColor": "Фон баннера", + "subIncyBannerBgColorDesc": "HEX-цвет, например #E53E3E.", + "subIncyBannerButtonColor": "Цвет кнопки баннера", + "subIncyBannerButtonColorDesc": "HEX-цвет, например #38A169.", + "subIncyGroupPrivacy": "Приватность", + "subIncyHideUrl": "Скрыть URL подписки", + "subIncyHideUrlDesc": "Скрывает URL из «Поделиться», копирования, QR и бэкапов.", + "subIncyHideCheck": "Скрыть кнопку проверки", + "subIncyHideCheckDesc": "Убирает кнопку проверки соединения с главного экрана.", + "subIncyNoLimit": "Режим No-Limit (iOS)", + "subIncyNoLimitDesc": "Экономный по памяти режим, чтобы туннель жил в пределах 50 МБ на iOS.", + "subIncyPerAppEnable": "Режим per-app (Android)", + "subIncyPerAppEnableDesc": "Включает split-туннель по приложениям. «Выкл» перебивает выбор юзера.", + "subIncyPerAppMode": "Логика per-app", + "subIncyPerAppModeDesc": "proxy — через VPN только выбранные приложения, bypass — наоборот.", + "subIncyPerAppModeProxy": "Только выбранные", + "subIncyPerAppModeBypass": "Выбранные в обход", + "subIncyPerAppList": "Список приложений", + "subIncyPerAppListDesc": "Пакеты через запятую или с новой строки, либо URL на текстовый файл.", + "subIncyGroupNetwork": "Сеть", + "subIncyFragmentationEnable": "Фрагментация", + "subIncyFragmentationEnableDesc": "Включает фрагментацию TCP, перебивая глобальную настройку.", + "subIncyFragmentLength": "Длина фрагмента", + "subIncyFragmentLengthDesc": "Диапазон в байтах, например 10-30.", + "subIncyFragmentInterval": "Интервал фрагментации", + "subIncyFragmentIntervalDesc": "Диапазон в миллисекундах, например 20-40.", + "subIncyFragmentPackets": "Пакеты фрагментации", + "subIncyFragmentPacketsDesc": "Какие TCP-пакеты фрагментировать: tlshello, 1-3, 1 или all.", + "subIncyNoisesEnable": "Шумовые пакеты", + "subIncyNoisesEnableDesc": "Шлёт случайный UDP-шум перед хендшейком VPN.", + "subIncyNoisesType": "Тип шума", + "subIncyNoisesTypeDesc": "rand, str или hex.", + "subIncyNoisesPacket": "Содержимое шумового пакета", + "subIncyNoisesPacketDesc": "Полезная нагрузка; для rand — диапазон длины, например 10-20.", + "subIncyNoisesDelay": "Задержка шума", + "subIncyNoisesDelayDesc": "Диапазон задержки в миллисекундах, например 10-50.", + "subIncyResolveEnable": "Резолв адреса сервера (DoH)", + "subIncyResolveEnableDesc": "Резолвит адрес сервера через DoH до подключения.", + "subIncyResolveDnsDomain": "DoH-эндпоинт", + "subIncyResolveDnsDomainDesc": "URL DoH-сервера, обычно заканчивается на /dns-query.", + "subIncyResolveDnsIp": "IP DoH-сервера", + "subIncyResolveDnsIpDesc": "IP DoH-сервера, нужен для bootstrap.", + "subIncyGroupRouting": "Маршрутизация" }, "xray": { "importRules": "Импорт правил", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 6e30365c3..f8c900216 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Discord botunun periyodik raporları gönderme sıklığı. Hazır bir aralık seçin veya bir crontab ifadesi girmek için Özel'i seçin.", "discordNotifyBackup": "Veritabanı Yedeği", "discordNotifyBackupDesc": "Bir rapor ile birlikte veritabanı yedek dosyasını gönderir.", - "discordEventBusNotifyDesc": "Hangi olayların Discord bildirimi tetikleyeceğini seçin" + "discordEventBusNotifyDesc": "Hangi olayların Discord bildirimi tetikleyeceğini seçin", + "subIncyAppAutoDetect": "Incy başlık otomatik algılama", + "subIncyAppAutoDetectDesc": "İstemci User-Agent değeri INCY ise Incy başlıklarını gönderir.", + "subIncyNotSet": "Ayarlanmadı", + "subIncyOn": "Açık", + "subIncyOff": "Kapalı", + "subIncyGroupApp": "Uygulama", + "subIncyProfileDescription": "Profil açıklaması", + "subIncyProfileDescriptionDesc": "Abonelik için ayrı açıklama. ASCII dışı metin base64 olarak gönderilir.", + "subIncySortOrder": "Sunucu sıralaması", + "subIncySortOrderDesc": "iOS/Android'de bu abonelik için, masaüstünde genel.", + "subIncySortNone": "Abonelik sırası", + "subIncySortPing": "Ping'e göre", + "subIncySortName": "İsme göre", + "subIncySupportEmail": "Destek e-postası", + "subIncySupportEmailDesc": "Abonelik kartına Email düğmesi ekler.", + "subIncyAnnounceUrl": "Duyuru bağlantısı", + "subIncyAnnounceUrlDesc": "Ana ekrandaki duyurunun hedef bağlantısı.", + "subIncyPremiumUrl": "Premium bağlantısı", + "subIncyPremiumUrlDesc": "Karttaki Premium düğmesi; banner düğmesinde de önceliklidir.", + "subIncyGroupBanners": "Bannerlar", + "subIncyBannerText": "Banner metni", + "subIncyBannerTextDesc": "Banner metnini geçersiz kılar. Yalnızca premium sağlayıcılar.", + "subIncyBannerButtonText": "Banner düğme metni", + "subIncyBannerButtonTextDesc": "Banner düğme etiketi. Boş bırakılırsa düğme gizlenir.", + "subIncyBannerButtonUrl": "Banner düğme bağlantısı", + "subIncyBannerButtonUrlDesc": "Banner düğmesinin URL'si.", + "subIncyBannerBgColor": "Banner arka planı", + "subIncyBannerBgColorDesc": "Hex renk, örn. #E53E3E.", + "subIncyBannerButtonColor": "Banner düğme rengi", + "subIncyBannerButtonColorDesc": "Hex renk, örn. #38A169.", + "subIncyGroupPrivacy": "Gizlilik", + "subIncyHideUrl": "Abonelik URL'sini gizle", + "subIncyHideUrlDesc": "URL'yi paylaşma, kopyalama, QR ve yedeklerden gizler.", + "subIncyHideCheck": "Check düğmesini gizle", + "subIncyHideCheckDesc": "Ana ekrandan bağlantı kontrolü düğmesini kaldırır.", + "subIncyNoLimit": "No-Limit modu (iOS)", + "subIncyNoLimitDesc": "Tünelin iOS 50 MB sınırında kalması için az bellek kullanan mod.", + "subIncyPerAppEnable": "Per-app modu (Android)", + "subIncyPerAppEnableDesc": "Uygulama bazlı split tüneli açar. Kapalı, kullanıcı seçimini geçersiz kılar.", + "subIncyPerAppMode": "Per-app mod tipi", + "subIncyPerAppModeDesc": "proxy: yalnızca listelenen uygulamalar VPN kullanır; bypass tersi.", + "subIncyPerAppModeProxy": "Yalnızca listelenenler", + "subIncyPerAppModeBypass": "Listelenenler atlar", + "subIncyPerAppList": "Uygulama listesi", + "subIncyPerAppListDesc": "Paket adları virgül veya satırla ayrılır, ya da bir metin dosyası URL'si.", + "subIncyGroupNetwork": "Ağ", + "subIncyFragmentationEnable": "Parçalama", + "subIncyFragmentationEnableDesc": "TCP parçalamayı açar ve genel ayarı geçersiz kılar.", + "subIncyFragmentLength": "Parça uzunluğu", + "subIncyFragmentLengthDesc": "Bayt aralığı, örn. 10-30.", + "subIncyFragmentInterval": "Parça aralığı", + "subIncyFragmentIntervalDesc": "Milisaniye aralığı, örn. 20-40.", + "subIncyFragmentPackets": "Parça paketleri", + "subIncyFragmentPacketsDesc": "Hangi TCP paketleri parçalanacak: tlshello, 1-3, 1 veya all.", + "subIncyNoisesEnable": "Gürültü paketleri", + "subIncyNoisesEnableDesc": "VPN el sıkışmasından önce rastgele UDP gürültüsü gönderir.", + "subIncyNoisesType": "Gürültü tipi", + "subIncyNoisesTypeDesc": "rand, str veya hex.", + "subIncyNoisesPacket": "Gürültü paket içeriği", + "subIncyNoisesPacketDesc": "İçerik; rand için uzunluk aralığı, örn. 10-20.", + "subIncyNoisesDelay": "Gürültü gecikmesi", + "subIncyNoisesDelayDesc": "Milisaniye cinsinden gecikme aralığı, örn. 10-50.", + "subIncyResolveEnable": "Sunucu adresini çöz (DoH)", + "subIncyResolveEnableDesc": "Bağlanmadan önce sunucu adresini DoH ile çözer.", + "subIncyResolveDnsDomain": "DoH uç noktası", + "subIncyResolveDnsDomainDesc": "DoH sunucu URL'si, genelde /dns-query ile biter.", + "subIncyResolveDnsIp": "DoH sunucu IP'si", + "subIncyResolveDnsIpDesc": "Bootstrap için DoH sunucu IP'si.", + "subIncyGroupRouting": "Yönlendirme" }, "xray": { "save": "Kaydet", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index 733b6621f..0768baa1a 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Як часто бот Discord надсилає періодичні звіти. Виберіть готовий інтервал або «Власний», щоб ввести вираз crontab.", "discordNotifyBackup": "Резервне копіювання бази даних", "discordNotifyBackupDesc": "Надіслати файл резервної копії бази даних зі звітом.", - "discordEventBusNotifyDesc": "Виберіть, які події спричиняють сповіщення в Discord" + "discordEventBusNotifyDesc": "Виберіть, які події спричиняють сповіщення в Discord", + "subIncyAppAutoDetect": "Автовизначення заголовків Incy", + "subIncyAppAutoDetectDesc": "Надсилати заголовки Incy, коли User-Agent клієнта — INCY.", + "subIncyNotSet": "Не задано", + "subIncyOn": "Увімк.", + "subIncyOff": "Вимк.", + "subIncyGroupApp": "Застосунок", + "subIncyProfileDescription": "Опис профілю", + "subIncyProfileDescriptionDesc": "Окремий опис підписки. Не-ASCII текст іде в base64.", + "subIncySortOrder": "Порядок серверів", + "subIncySortOrderDesc": "Для цієї підписки на iOS/Android, глобально на десктопі.", + "subIncySortNone": "Як у підписці", + "subIncySortPing": "За пінгом", + "subIncySortName": "За назвою", + "subIncySupportEmail": "Email підтримки", + "subIncySupportEmailDesc": "Додає кнопку «Email» у картку підписки.", + "subIncyAnnounceUrl": "Посилання оголошення", + "subIncyAnnounceUrlDesc": "Куди веде оголошення на головному екрані.", + "subIncyPremiumUrl": "Посилання Premium", + "subIncyPremiumUrlDesc": "Кнопка Premium у картці; пріоритетна і для кнопки банера.", + "subIncyGroupBanners": "Банери", + "subIncyBannerText": "Текст банера", + "subIncyBannerTextDesc": "Перевизначає текст банера. Лише для premium-провайдерів.", + "subIncyBannerButtonText": "Текст кнопки банера", + "subIncyBannerButtonTextDesc": "Підпис кнопки банера. Порожнє значення ховає кнопку.", + "subIncyBannerButtonUrl": "Посилання кнопки банера", + "subIncyBannerButtonUrlDesc": "URL кнопки банера.", + "subIncyBannerBgColor": "Фон банера", + "subIncyBannerBgColorDesc": "HEX-колір, наприклад #E53E3E.", + "subIncyBannerButtonColor": "Колір кнопки банера", + "subIncyBannerButtonColorDesc": "HEX-колір, наприклад #38A169.", + "subIncyGroupPrivacy": "Приватність", + "subIncyHideUrl": "Сховати URL підписки", + "subIncyHideUrlDesc": "Ховає URL з «Поділитися», копіювання, QR і бекапів.", + "subIncyHideCheck": "Сховати кнопку перевірки", + "subIncyHideCheckDesc": "Прибирає кнопку перевірки з'єднання з головного екрана.", + "subIncyNoLimit": "Режим No-Limit (iOS)", + "subIncyNoLimitDesc": "Ощадний до пам'яті режим, щоб тунель жив у межах 50 МБ на iOS.", + "subIncyPerAppEnable": "Режим per-app (Android)", + "subIncyPerAppEnableDesc": "Вмикає split-тунель за застосунками. «Вимк.» перебиває вибір користувача.", + "subIncyPerAppMode": "Логіка per-app", + "subIncyPerAppModeDesc": "proxy — через VPN лише вибрані застосунки, bypass — навпаки.", + "subIncyPerAppModeProxy": "Лише вибрані", + "subIncyPerAppModeBypass": "Вибрані в обхід", + "subIncyPerAppList": "Список застосунків", + "subIncyPerAppListDesc": "Пакети через кому або з нового рядка, або URL на текстовий файл.", + "subIncyGroupNetwork": "Мережа", + "subIncyFragmentationEnable": "Фрагментація", + "subIncyFragmentationEnableDesc": "Вмикає фрагментацію TCP, перебиваючи глобальне налаштування.", + "subIncyFragmentLength": "Довжина фрагмента", + "subIncyFragmentLengthDesc": "Діапазон у байтах, наприклад 10-30.", + "subIncyFragmentInterval": "Інтервал фрагментації", + "subIncyFragmentIntervalDesc": "Діапазон у мілісекундах, наприклад 20-40.", + "subIncyFragmentPackets": "Пакети фрагментації", + "subIncyFragmentPacketsDesc": "Які TCP-пакети фрагментувати: tlshello, 1-3, 1 або all.", + "subIncyNoisesEnable": "Шумові пакети", + "subIncyNoisesEnableDesc": "Надсилає випадковий UDP-шум перед хендшейком VPN.", + "subIncyNoisesType": "Тип шуму", + "subIncyNoisesTypeDesc": "rand, str або hex.", + "subIncyNoisesPacket": "Вміст шумового пакета", + "subIncyNoisesPacketDesc": "Корисне навантаження; для rand — діапазон довжини, наприклад 10-20.", + "subIncyNoisesDelay": "Затримка шуму", + "subIncyNoisesDelayDesc": "Діапазон затримки в мілісекундах, наприклад 10-50.", + "subIncyResolveEnable": "Резолв адреси сервера (DoH)", + "subIncyResolveEnableDesc": "Резолвить адресу сервера через DoH до підключення.", + "subIncyResolveDnsDomain": "DoH-ендпоінт", + "subIncyResolveDnsDomainDesc": "URL DoH-сервера, зазвичай закінчується на /dns-query.", + "subIncyResolveDnsIp": "IP DoH-сервера", + "subIncyResolveDnsIpDesc": "IP DoH-сервера для bootstrap.", + "subIncyGroupRouting": "Маршрутизація" }, "xray": { "save": "Зберегти", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index c4530ca4b..e7af268cd 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Tần suất bot Discord gửi báo cáo định kỳ. Chọn một khoảng thời gian có sẵn, hoặc chọn Tùy chỉnh để nhập biểu thức crontab.", "discordNotifyBackup": "Sao lưu Cơ sở dữ liệu", "discordNotifyBackupDesc": "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo.", - "discordEventBusNotifyDesc": "Chọn những sự kiện nào sẽ kích hoạt thông báo qua Discord" + "discordEventBusNotifyDesc": "Chọn những sự kiện nào sẽ kích hoạt thông báo qua Discord", + "subIncyAppAutoDetect": "Tự nhận diện header Incy", + "subIncyAppAutoDetectDesc": "Gửi header Incy khi User-Agent của client là INCY.", + "subIncyNotSet": "Chưa đặt", + "subIncyOn": "Bật", + "subIncyOff": "Tắt", + "subIncyGroupApp": "Ứng dụng", + "subIncyProfileDescription": "Mô tả hồ sơ", + "subIncyProfileDescriptionDesc": "Mô tả riêng cho gói đăng ký. Văn bản không phải ASCII gửi dạng base64.", + "subIncySortOrder": "Thứ tự máy chủ", + "subIncySortOrderDesc": "Cho gói này trên iOS/Android, toàn cục trên desktop.", + "subIncySortNone": "Theo thứ tự gói", + "subIncySortPing": "Theo ping", + "subIncySortName": "Theo tên", + "subIncySupportEmail": "Email hỗ trợ", + "subIncySupportEmailDesc": "Thêm nút Email vào thẻ đăng ký.", + "subIncyAnnounceUrl": "Link thông báo", + "subIncyAnnounceUrlDesc": "Đích của thông báo trên màn hình chính.", + "subIncyPremiumUrl": "Link Premium", + "subIncyPremiumUrlDesc": "Nút Premium trong thẻ; cũng ưu tiên cho nút banner.", + "subIncyGroupBanners": "Banner", + "subIncyBannerText": "Chữ trên banner", + "subIncyBannerTextDesc": "Ghi đè chữ banner. Chỉ nhà cung cấp premium.", + "subIncyBannerButtonText": "Chữ nút banner", + "subIncyBannerButtonTextDesc": "Nhãn nút banner. Để trống sẽ ẩn nút.", + "subIncyBannerButtonUrl": "Link nút banner", + "subIncyBannerButtonUrlDesc": "URL của nút banner.", + "subIncyBannerBgColor": "Nền banner", + "subIncyBannerBgColorDesc": "Màu hex, ví dụ #E53E3E.", + "subIncyBannerButtonColor": "Màu nút banner", + "subIncyBannerButtonColorDesc": "Màu hex, ví dụ #38A169.", + "subIncyGroupPrivacy": "Riêng tư", + "subIncyHideUrl": "Ẩn URL đăng ký", + "subIncyHideUrlDesc": "Ẩn URL khỏi chia sẻ, sao chép, QR và bản sao lưu.", + "subIncyHideCheck": "Ẩn nút kiểm tra", + "subIncyHideCheckDesc": "Bỏ nút kiểm tra kết nối khỏi màn hình chính.", + "subIncyNoLimit": "Chế độ No-Limit (iOS)", + "subIncyNoLimitDesc": "Chế độ tiết kiệm bộ nhớ để tunnel sống trong giới hạn 50 MB của iOS.", + "subIncyPerAppEnable": "Chế độ per-app (Android)", + "subIncyPerAppEnableDesc": "Bật split tunnel theo ứng dụng. Tắt sẽ ghi đè lựa chọn của người dùng.", + "subIncyPerAppMode": "Kiểu chế độ per-app", + "subIncyPerAppModeDesc": "proxy: chỉ ứng dụng trong danh sách đi qua VPN; bypass thì ngược lại.", + "subIncyPerAppModeProxy": "Chỉ ứng dụng trong danh sách", + "subIncyPerAppModeBypass": "Ứng dụng trong danh sách đi thẳng", + "subIncyPerAppList": "Danh sách ứng dụng", + "subIncyPerAppListDesc": "Tên gói cách nhau bằng dấu phẩy hoặc xuống dòng, hoặc URL tới file văn bản.", + "subIncyGroupNetwork": "Mạng", + "subIncyFragmentationEnable": "Phân mảnh", + "subIncyFragmentationEnableDesc": "Bật phân mảnh TCP và ghi đè cấu hình chung.", + "subIncyFragmentLength": "Độ dài mảnh", + "subIncyFragmentLengthDesc": "Khoảng theo byte, ví dụ 10-30.", + "subIncyFragmentInterval": "Khoảng cách mảnh", + "subIncyFragmentIntervalDesc": "Khoảng theo mili giây, ví dụ 20-40.", + "subIncyFragmentPackets": "Gói phân mảnh", + "subIncyFragmentPacketsDesc": "Phân mảnh gói TCP nào: tlshello, 1-3, 1 hoặc all.", + "subIncyNoisesEnable": "Gói nhiễu", + "subIncyNoisesEnableDesc": "Gửi nhiễu UDP ngẫu nhiên trước khi bắt tay VPN.", + "subIncyNoisesType": "Kiểu nhiễu", + "subIncyNoisesTypeDesc": "rand, str hoặc hex.", + "subIncyNoisesPacket": "Nội dung gói nhiễu", + "subIncyNoisesPacketDesc": "Nội dung; với rand là khoảng độ dài, ví dụ 10-20.", + "subIncyNoisesDelay": "Độ trễ nhiễu", + "subIncyNoisesDelayDesc": "Khoảng trễ theo mili giây, ví dụ 10-50.", + "subIncyResolveEnable": "Phân giải địa chỉ máy chủ (DoH)", + "subIncyResolveEnableDesc": "Phân giải địa chỉ máy chủ qua DoH trước khi kết nối.", + "subIncyResolveDnsDomain": "Endpoint DoH", + "subIncyResolveDnsDomainDesc": "URL máy chủ DoH, thường kết thúc bằng /dns-query.", + "subIncyResolveDnsIp": "IP máy chủ DoH", + "subIncyResolveDnsIpDesc": "IP máy chủ DoH để khởi tạo.", + "subIncyGroupRouting": "Định tuyến" }, "xray": { "importRules": "Nhập quy tắc", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index 1960277a8..aee16fbd7 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Discord 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。", "discordNotifyBackup": "数据库备份", "discordNotifyBackupDesc": "发送带有报告的数据库备份文件", - "discordEventBusNotifyDesc": "选择触发 Discord 通知的事件" + "discordEventBusNotifyDesc": "选择触发 Discord 通知的事件", + "subIncyAppAutoDetect": "Incy 请求头自动识别", + "subIncyAppAutoDetectDesc": "当客户端 User-Agent 为 INCY 时发送 Incy 请求头。", + "subIncyNotSet": "未设置", + "subIncyOn": "开", + "subIncyOff": "关", + "subIncyGroupApp": "应用", + "subIncyProfileDescription": "订阅描述", + "subIncyProfileDescriptionDesc": "单独的订阅描述。非 ASCII 文本以 base64 发送。", + "subIncySortOrder": "服务器排序", + "subIncySortOrderDesc": "iOS/Android 只影响本订阅,桌面端为全局设置。", + "subIncySortNone": "按订阅顺序", + "subIncySortPing": "按延迟", + "subIncySortName": "按名称", + "subIncySupportEmail": "客服邮箱", + "subIncySupportEmailDesc": "在订阅卡片中显示“Email”按钮。", + "subIncyAnnounceUrl": "公告链接", + "subIncyAnnounceUrlDesc": "首页公告的跳转链接。", + "subIncyPremiumUrl": "Premium 链接", + "subIncyPremiumUrlDesc": "订阅卡片中的 Premium 按钮,横幅按钮也优先使用它。", + "subIncyGroupBanners": "横幅", + "subIncyBannerText": "横幅文字", + "subIncyBannerTextDesc": "覆盖横幅文字。仅 Premium 服务商可用。", + "subIncyBannerButtonText": "横幅按钮文字", + "subIncyBannerButtonTextDesc": "横幅按钮标题。留空则不显示按钮。", + "subIncyBannerButtonUrl": "横幅按钮链接", + "subIncyBannerButtonUrlDesc": "横幅按钮的 URL。", + "subIncyBannerBgColor": "横幅背景色", + "subIncyBannerBgColorDesc": "十六进制颜色,例如 #E53E3E。", + "subIncyBannerButtonColor": "横幅按钮颜色", + "subIncyBannerButtonColorDesc": "十六进制颜色,例如 #38A169。", + "subIncyGroupPrivacy": "隐私", + "subIncyHideUrl": "隐藏订阅链接", + "subIncyHideUrlDesc": "在分享、复制、二维码和备份中隐藏链接。", + "subIncyHideCheck": "隐藏检查按钮", + "subIncyHideCheckDesc": "从首页移除连接检查按钮。", + "subIncyNoLimit": "No-Limit 模式 (iOS)", + "subIncyNoLimitDesc": "省内存模式,让隧道在 iOS 50 MB 限制内保持存活。", + "subIncyPerAppEnable": "分应用模式 (Android)", + "subIncyPerAppEnableDesc": "启用分应用分流。关闭会覆盖用户自己的选择。", + "subIncyPerAppMode": "分应用模式类型", + "subIncyPerAppModeDesc": "proxy 表示仅列出的应用走 VPN,bypass 则相反。", + "subIncyPerAppModeProxy": "仅列出的应用", + "subIncyPerAppModeBypass": "列出的应用绕过", + "subIncyPerAppList": "应用列表", + "subIncyPerAppListDesc": "包名用逗号或换行分隔,也可以填文本文件的 URL。", + "subIncyGroupNetwork": "网络", + "subIncyFragmentationEnable": "分片", + "subIncyFragmentationEnableDesc": "启用 TCP 分片,覆盖全局设置。", + "subIncyFragmentLength": "分片长度", + "subIncyFragmentLengthDesc": "字节范围,例如 10-30。", + "subIncyFragmentInterval": "分片间隔", + "subIncyFragmentIntervalDesc": "毫秒范围,例如 20-40。", + "subIncyFragmentPackets": "分片数据包", + "subIncyFragmentPacketsDesc": "对哪些 TCP 包分片:tlshello、1-3、1 或 all。", + "subIncyNoisesEnable": "噪声数据包", + "subIncyNoisesEnableDesc": "握手前发送随机 UDP 噪声包。", + "subIncyNoisesType": "噪声类型", + "subIncyNoisesTypeDesc": "rand、str 或 hex。", + "subIncyNoisesPacket": "噪声内容", + "subIncyNoisesPacketDesc": "载荷内容;rand 时填长度范围,例如 10-20。", + "subIncyNoisesDelay": "噪声延迟", + "subIncyNoisesDelayDesc": "毫秒延迟范围,例如 10-50。", + "subIncyResolveEnable": "预解析服务器地址 (DoH)", + "subIncyResolveEnableDesc": "连接前通过 DoH 预解析服务器地址。", + "subIncyResolveDnsDomain": "DoH 端点", + "subIncyResolveDnsDomainDesc": "DoH 服务器 URL,通常以 /dns-query 结尾。", + "subIncyResolveDnsIp": "DoH 服务器 IP", + "subIncyResolveDnsIpDesc": "用于引导的 DoH 服务器 IP。", + "subIncyGroupRouting": "路由" }, "xray": { "importRules": "导入规则", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index aaaf593e8..bf664317b 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -1670,7 +1670,76 @@ "discordNotifyTimeDesc": "Discord 機器人傳送週期性報告的頻率。選擇預設間隔,或選擇「自訂」以輸入 crontab 運算式。", "discordNotifyBackup": "資料庫備份", "discordNotifyBackupDesc": "傳送帶有報告的資料庫備份檔案", - "discordEventBusNotifyDesc": "選擇觸發 Discord 通知的事件" + "discordEventBusNotifyDesc": "選擇觸發 Discord 通知的事件", + "subIncyAppAutoDetect": "Incy 標頭自動辨識", + "subIncyAppAutoDetectDesc": "用戶端 User-Agent 為 INCY 時送出 Incy 標頭。", + "subIncyNotSet": "未設定", + "subIncyOn": "開", + "subIncyOff": "關", + "subIncyGroupApp": "應用程式", + "subIncyProfileDescription": "訂閱描述", + "subIncyProfileDescriptionDesc": "獨立的訂閱描述。非 ASCII 文字以 base64 傳送。", + "subIncySortOrder": "伺服器排序", + "subIncySortOrderDesc": "iOS/Android 只影響本訂閱,桌面版為全域設定。", + "subIncySortNone": "依訂閱順序", + "subIncySortPing": "依延遲", + "subIncySortName": "依名稱", + "subIncySupportEmail": "客服信箱", + "subIncySupportEmailDesc": "在訂閱卡片顯示「Email」按鈕。", + "subIncyAnnounceUrl": "公告連結", + "subIncyAnnounceUrlDesc": "首頁公告的導向連結。", + "subIncyPremiumUrl": "Premium 連結", + "subIncyPremiumUrlDesc": "訂閱卡片中的 Premium 按鈕,橫幅按鈕也優先使用。", + "subIncyGroupBanners": "橫幅", + "subIncyBannerText": "橫幅文字", + "subIncyBannerTextDesc": "覆寫橫幅文字。僅限 Premium 供應商。", + "subIncyBannerButtonText": "橫幅按鈕文字", + "subIncyBannerButtonTextDesc": "橫幅按鈕標題。留空則不顯示按鈕。", + "subIncyBannerButtonUrl": "橫幅按鈕連結", + "subIncyBannerButtonUrlDesc": "橫幅按鈕的 URL。", + "subIncyBannerBgColor": "橫幅背景色", + "subIncyBannerBgColorDesc": "十六進位色碼,例如 #E53E3E。", + "subIncyBannerButtonColor": "橫幅按鈕顏色", + "subIncyBannerButtonColorDesc": "十六進位色碼,例如 #38A169。", + "subIncyGroupPrivacy": "隱私", + "subIncyHideUrl": "隱藏訂閱網址", + "subIncyHideUrlDesc": "在分享、複製、QR 與備份中隱藏網址。", + "subIncyHideCheck": "隱藏檢查按鈕", + "subIncyHideCheckDesc": "從首頁移除連線檢查按鈕。", + "subIncyNoLimit": "No-Limit 模式 (iOS)", + "subIncyNoLimitDesc": "省記憶體模式,讓通道在 iOS 50 MB 限制內存活。", + "subIncyPerAppEnable": "分應用程式模式 (Android)", + "subIncyPerAppEnableDesc": "啟用分應用程式分流。關閉會覆寫使用者自己的選擇。", + "subIncyPerAppMode": "分應用程式模式類型", + "subIncyPerAppModeDesc": "proxy 表示只有列出的應用走 VPN,bypass 則相反。", + "subIncyPerAppModeProxy": "僅列出的應用", + "subIncyPerAppModeBypass": "列出的應用繞過", + "subIncyPerAppList": "應用程式清單", + "subIncyPerAppListDesc": "套件名稱以逗號或換行分隔,也可填文字檔的 URL。", + "subIncyGroupNetwork": "網路", + "subIncyFragmentationEnable": "分段", + "subIncyFragmentationEnableDesc": "啟用 TCP 分段,覆寫全域設定。", + "subIncyFragmentLength": "分段長度", + "subIncyFragmentLengthDesc": "位元組範圍,例如 10-30。", + "subIncyFragmentInterval": "分段間隔", + "subIncyFragmentIntervalDesc": "毫秒範圍,例如 20-40。", + "subIncyFragmentPackets": "分段封包", + "subIncyFragmentPacketsDesc": "要分段哪些 TCP 封包:tlshello、1-3、1 或 all。", + "subIncyNoisesEnable": "雜訊封包", + "subIncyNoisesEnableDesc": "握手前送出隨機 UDP 雜訊。", + "subIncyNoisesType": "雜訊類型", + "subIncyNoisesTypeDesc": "rand、str 或 hex。", + "subIncyNoisesPacket": "雜訊內容", + "subIncyNoisesPacketDesc": "酬載內容;rand 時填長度範圍,例如 10-20。", + "subIncyNoisesDelay": "雜訊延遲", + "subIncyNoisesDelayDesc": "毫秒延遲範圍,例如 10-50。", + "subIncyResolveEnable": "預先解析伺服器位址 (DoH)", + "subIncyResolveEnableDesc": "連線前透過 DoH 預先解析伺服器位址。", + "subIncyResolveDnsDomain": "DoH 端點", + "subIncyResolveDnsDomainDesc": "DoH 伺服器 URL,通常以 /dns-query 結尾。", + "subIncyResolveDnsIp": "DoH 伺服器 IP", + "subIncyResolveDnsIpDesc": "用於引導的 DoH 伺服器 IP。", + "subIncyGroupRouting": "路由" }, "xray": { "save": "儲存",