소스 검색

feat: more subscription information fields (#3701)

* feat: more subscription information fields

* fix: incorrect translation

* feat: implement field for Happ custom routing rules
Danil S. 4 시간 전
부모
커밋
fd5f591737

+ 27 - 1
sub/sub.go

@@ -153,6 +153,31 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 		SubTitle = ""
 	}
 
+	SubSupportUrl, err := s.settingService.GetSubSupportUrl()
+	if err != nil {
+		SubSupportUrl = ""
+	}
+
+	SubProfileUrl, err := s.settingService.GetSubProfileUrl()
+	if err != nil {
+		SubProfileUrl = ""
+	}
+
+	SubAnnounce, err := s.settingService.GetSubAnnounce()
+	if err != nil {
+		SubAnnounce = ""
+	}
+
+	SubEnableRouting, err := s.settingService.GetSubEnableRouting()
+	if err != nil {
+		return nil, err
+	}
+
+	SubRoutingRules, err := s.settingService.GetSubRoutingRules()
+	if err != nil {
+		SubRoutingRules = ""
+	}
+
 	// set per-request localizer from headers/cookies
 	engine.Use(locale.LocalizerMiddleware())
 
@@ -231,7 +256,8 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 
 	s.sub = NewSUBController(
 		g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
-		SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
+		SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle, SubSupportUrl,
+		SubProfileUrl, SubAnnounce, SubEnableRouting, SubRoutingRules)
 
 	return engine, nil
 }

+ 49 - 19
sub/subController.go

@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"fmt"
 	"strings"
+	"strconv"
 
 	"github.com/mhsanaei/3x-ui/v2/config"
 
@@ -12,12 +13,17 @@ import (
 
 // SUBController handles HTTP requests for subscription links and JSON configurations.
 type SUBController struct {
-	subTitle       string
-	subPath        string
-	subJsonPath    string
-	jsonEnabled    bool
-	subEncrypt     bool
-	updateInterval string
+	subTitle         string
+	subSupportUrl    string
+	subProfileUrl    string
+	subAnnounce      string
+	subEnableRouting bool
+	subRoutingRules  string
+	subPath          string
+	subJsonPath      string
+	jsonEnabled      bool
+	subEncrypt       bool
+	updateInterval   string
 
 	subService     *SubService
 	subJsonService *SubJsonService
@@ -38,18 +44,28 @@ func NewSUBController(
 	jsonMux string,
 	jsonRules string,
 	subTitle string,
+	subSupportUrl string,
+	subProfileUrl string,
+	subAnnounce string,
+	subEnableRouting bool,
+	subRoutingRules string,
 ) *SUBController {
 	sub := NewSubService(showInfo, rModel)
 	a := &SUBController{
-		subTitle:       subTitle,
-		subPath:        subPath,
-		subJsonPath:    jsonPath,
-		jsonEnabled:    jsonEnabled,
-		subEncrypt:     encrypt,
-		updateInterval: update,
-
-		subService:     sub,
-		subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
+		subTitle:         subTitle,
+		subSupportUrl:    subSupportUrl,
+		subProfileUrl:    subProfileUrl,
+		subAnnounce:      subAnnounce,
+		subEnableRouting: subEnableRouting,
+		subRoutingRules:  subRoutingRules,
+		subPath:          subPath,
+		subJsonPath:      jsonPath,
+		jsonEnabled:      jsonEnabled,
+		subEncrypt:       encrypt,
+		updateInterval:   update,
+
+		subService:       sub,
+		subJsonService:   NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
 	}
 	a.initRouter(g)
 	return a
@@ -127,7 +143,7 @@ func (a *SUBController) subs(c *gin.Context) {
 
 		// Add headers
 		header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
-		a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
+		a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
 
 		if a.subEncrypt {
 			c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
@@ -145,17 +161,31 @@ func (a *SUBController) subJsons(c *gin.Context) {
 	if err != nil || len(jsonSub) == 0 {
 		c.String(400, "Error!")
 	} else {
-
 		// Add headers
-		a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
+		a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
 
 		c.String(200, jsonSub)
 	}
 }
 
 // ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
-func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) {
+func (a *SUBController) ApplyCommonHeaders(
+	c *gin.Context, 
+	header, 
+	updateInterval, 
+	profileTitle string, 
+	profileSupportUrl string,
+	profileUrl string, 
+	profileAnnounce string, 
+	profileEnableRouting bool,
+	profileRoutingRules string,
+) {
 	c.Writer.Header().Set("Subscription-Userinfo", header)
 	c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
 	c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
+	c.Writer.Header().Set("Support-Url", profileSupportUrl)
+	c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl)
+	c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
+	c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting))
+	c.Writer.Header().Set("Routing", profileRoutingRules)
 }

+ 5 - 0
web/assets/js/model/setting.js

@@ -29,6 +29,11 @@ class AllSetting {
         this.subEnable = true;
         this.subJsonEnable = false;
         this.subTitle = "";
+        this.subSupportUrl = "";
+        this.subProfileUrl = "";
+        this.subAnnounce = "";
+        this.subEnableRouting = true;
+        this.subRoutingRules = "";
         this.subListen = "";
         this.subPort = 2096;
         this.subPath = "/sub/";

+ 5 - 0
web/entity/entity.go

@@ -57,6 +57,11 @@ type AllSetting struct {
 	SubEnable                   bool   `json:"subEnable" form:"subEnable"`                                     // Enable subscription server
 	SubJsonEnable               bool   `json:"subJsonEnable" form:"subJsonEnable"`                             // Enable JSON subscription endpoint
 	SubTitle                    string `json:"subTitle" form:"subTitle"`                                       // Subscription title
+	SubSupportUrl               string `json:"subSupportUrl" form:"subSupportUrl"`                             // Subscription support URL
+	SubProfileUrl               string `json:"subProfileUrl" form:"subProfileUrl"`                             // Subscription profile URL
+	SubAnnounce                 string `json:"subAnnounce" form:"subAnnounce"`                                 // Subscription announce
+	SubEnableRouting            bool   `json:"subEnableRouting" form:"subEnableRouting"`                       // Enable routing for subscription
+	SubRoutingRules             string `json:"subRoutingRules" form:"subRoutingRules"`                         // Subscription global routing rules (Only for Happ)
 	SubListen                   string `json:"subListen" form:"subListen"`                                     // Subscription server listen IP
 	SubPort                     int    `json:"subPort" form:"subPort"`                                         // Subscription server port
 	SubPath                     string `json:"subPath" form:"subPath"`                                         // Base path for subscription URLs

+ 44 - 7
web/html/settings/panel/subscription/general.html

@@ -15,13 +15,6 @@
                 <a-switch v-model="allSetting.subJsonEnable"></a-switch>
             </template>
         </a-setting-list-item>
-        <a-setting-list-item paddings="small">
-            <template #title>{{ i18n "pages.settings.subTitle"}}</template>
-            <template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
-            <template #control>
-                <a-input type="text" v-model="allSetting.subTitle"></a-input>
-            </template>
-        </a-setting-list-item>
         <a-setting-list-item paddings="small">
             <template #title>{{ i18n "pages.settings.subListen"}}</template>
             <template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
@@ -78,6 +71,50 @@
                 <a-switch v-model="allSetting.subShowInfo"></a-switch>
             </template>
         </a-setting-list-item>
+        <a-divider>{{ i18n "pages.xray.basicTemplate"}}</a-divider>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subTitle"}}</template>
+            <template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
+            <template #control>
+                <a-input type="text" v-model="allSetting.subTitle"></a-input>
+            </template>
+        </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subSupportUrl"}}</template>
+            <template #description>{{ i18n "pages.settings.subSupportUrlDesc"}}</template>
+            <template #control>
+                <a-input type="text" v-model="allSetting.subSupportUrl" placeholder="https://example.com"></a-input>
+            </template>
+        </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subProfileUrl"}}</template>
+            <template #description>{{ i18n "pages.settings.subProfileUrlDesc"}}</template>
+            <template #control>
+                <a-input type="text" v-model="allSetting.subProfileUrl" placeholder="https://example.com"></a-input>
+            </template>
+        </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subAnnounce"}}</template>
+            <template #description>{{ i18n "pages.settings.subAnnounceDesc"}}</template>
+            <template #control>
+                <a-textarea v-model="allSetting.subAnnounce"></a-textarea>
+            </template>
+        </a-setting-list-item>
+        <a-divider>{{ i18n "pages.xray.advancedTemplate"}} (Happ)</a-divider>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subEnableRouting"}}</template>
+            <template #description>{{ i18n "pages.settings.subEnableRoutingDesc"}}</template>
+            <template #control>
+                <a-switch v-model="allSetting.subEnableRouting"></a-switch>
+            </template>
+        </a-setting-list-item>
+        <a-setting-list-item paddings="small">
+            <template #title>{{ i18n "pages.settings.subRoutingRules"}}</template>
+            <template #description>{{ i18n "pages.settings.subRoutingRulesDesc"}}</template>
+            <template #control>
+                <a-textarea v-model="allSetting.subRoutingRules" placeholder="happ://routing/add/..."></a-textarea>
+            </template>
+        </a-setting-list-item>
     </a-collapse-panel>
     <a-collapse-panel key="3" header='{{ i18n "pages.settings.certs" }}'>
         <a-setting-list-item paddings="small">

+ 25 - 0
web/service/setting.go

@@ -53,6 +53,11 @@ var defaultValueMap = map[string]string{
 	"subEnable":                   "true",
 	"subJsonEnable":               "false",
 	"subTitle":                    "",
+	"subSupportUrl":               "",
+	"subProfileUrl":               "",
+	"subAnnounce":                 "",
+	"subEnableRouting":            "true",
+	"subRoutingRules":             "",
 	"subListen":                   "",
 	"subPort":                     "2096",
 	"subPath":                     "/sub/",
@@ -459,6 +464,26 @@ func (s *SettingService) GetSubTitle() (string, error) {
 	return s.getString("subTitle")
 }
 
+func (s *SettingService) GetSubSupportUrl() (string, error) {
+	return s.getString("subSupportUrl")
+}
+
+func (s *SettingService) GetSubProfileUrl() (string, error) {
+	return s.getString("subProfileUrl")
+}
+
+func (s *SettingService) GetSubAnnounce() (string, error) {
+	return s.getString("subAnnounce")
+}
+
+func (s *SettingService) GetSubEnableRouting() (bool, error) {
+	return s.getBool("subEnableRouting")
+}
+
+func (s *SettingService) GetSubRoutingRules() (string, error) {
+	return s.getString("subRoutingRules")
+}
+
 func (s *SettingService) GetSubListen() (string, error) {
 	return s.getString("subListen")
 }

+ 10 - 0
web/translation/translate.ar_EG.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل."
 "subTitle" = "عنوان الاشتراك"
 "subTitleDesc" = "العنوان اللي هيظهر في عميل VPN"
+"subSupportUrl" = "رابط الدعم"
+"subSupportUrlDesc" = "رابط الدعم الفني المعروض في عميل VPN"
+"subProfileUrl" = "رابط الملف الشخصي"
+"subProfileUrlDesc" = "رابط لموقعك الإلكتروني يظهر في عميل VPN"
+"subAnnounce" = "إعلان"
+"subAnnounceDesc" = "نص الإعلان المعروض في عميل VPN"
+"subEnableRouting" = "تفعيل التوجيه"
+"subEnableRoutingDesc" = "إعداد عام لتمكين التوجيه (Routing) في عميل VPN. (فقط لـ Happ)"
+"subRoutingRules" = "قواعد التوجيه"
+"subRoutingRulesDesc" = "قواعد التوجيه العامة لعميل VPN. (فقط لـ Happ)"
 "subListen" = "IP الاستماع"
 "subListenDesc" = "عنوان IP لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الـ IPs)"
 "subPort" = "بورت الاستماع"

+ 10 - 0
web/translation/translate.en_US.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Enable/Disable the JSON subscription endpoint independently."
 "subTitle" = "Subscription Title"
 "subTitleDesc" = "Title shown in VPN client"
+"subSupportUrl" = "Support URL"
+"subSupportUrlDesc" = "Technical support link shown in the VPN client"
+"subProfileUrl" = "Profile URL"
+"subProfileUrlDesc" = "A link to your website displayed in the VPN client"
+"subAnnounce" = "Announce"
+"subAnnounceDesc" = "The text of the announce displayed in the VPN client"
+"subEnableRouting" = "Enable routing"
+"subEnableRoutingDesc" = "Global setting to enable routing in the VPN client. (Only for Happ)"
+"subRoutingRules" = "Routing rules"
+"subRoutingRulesDesc" = "Global routing rules for the VPN client. (Only for Happ)"
 "subListen" = "Listen IP"
 "subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
 "subPort" = "Listen Port"

+ 10 - 0
web/translation/translate.es_ES.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente."
 "subTitle" = "Título de la Suscripción"
 "subTitleDesc" = "Título mostrado en el cliente de VPN"
+"subSupportUrl" = "URL de soporte"
+"subSupportUrlDesc" = "Enlace de soporte técnico mostrado en el cliente VPN"
+"subProfileUrl" = "URL del perfil"
+"subProfileUrlDesc" = "Un enlace a tu sitio web mostrado en el cliente VPN"
+"subAnnounce" = "Anuncio"
+"subAnnounceDesc" = "El texto del anuncio mostrado en el cliente VPN"
+"subEnableRouting" = "Habilitar enrutamiento"
+"subEnableRoutingDesc" = "Configuración global para habilitar el enrutamiento en el cliente VPN. (Solo para Happ)"
+"subRoutingRules" = "Reglas de enrutamiento"
+"subRoutingRulesDesc" = "Reglas de enrutamiento globales para el cliente VPN. (Solo para Happ)"
 "subListen" = "Listening IP"
 "subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
 "subPort" = "Puerto de Suscripción"

+ 10 - 0
web/translation/translate.fa_IR.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "فعال/غیرفعال‌سازی مستقل نقطه دسترسی سابسکریپشن JSON."
 "subTitle" = "عنوان اشتراک"
 "subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
+"subSupportUrl" = "آدرس پشتیبانی"
+"subSupportUrlDesc" = "لینک پشتیبانی فنی که در کلاینت VPN نمایش داده می‌شود"
+"subProfileUrl" = "آدرس پروفایل"
+"subProfileUrlDesc" = "لینک وب‌سایت شما که در کلاینت VPN نمایش داده می‌شود"
+"subAnnounce" = "اعلان"
+"subAnnounceDesc" = "متن اعلانی که در کلاینت VPN نمایش داده می‌شود"
+"subEnableRouting" = "فعال‌سازی مسیریابی"
+"subEnableRoutingDesc" = "تنظیمات سراسری برای فعال‌سازی مسیریابی در کلاینت VPN. (فقط برای Happ)"
+"subRoutingRules" = "قوانین مسیریابی"
+"subRoutingRulesDesc" = "قوانین مسیریابی سراسری برای کلاینت VPN. (فقط برای Happ)"
 "subListen" = "آدرس آی‌پی"
 "subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید"
 "subPort" = "پورت"

+ 10 - 0
web/translation/translate.id_ID.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri."
 "subTitle" = "Judul Langganan"
 "subTitleDesc" = "Judul yang ditampilkan di klien VPN"
+"subSupportUrl" = "URL Dukungan"
+"subSupportUrlDesc" = "Tautan dukungan teknis yang ditampilkan di klien VPN"
+"subProfileUrl" = "URL Profil"
+"subProfileUrlDesc" = "Tautan ke situs web Anda yang ditampilkan di klien VPN"
+"subAnnounce" = "Pengumuman"
+"subAnnounceDesc" = "Teks pengumuman yang ditampilkan di klien VPN"
+"subEnableRouting" = "Aktifkan perutean"
+"subEnableRoutingDesc" = "Pengaturan global untuk mengaktifkan perutean (routing) di klien VPN. (Hanya untuk Happ)"
+"subRoutingRules" = "Aturan routing"
+"subRoutingRulesDesc" = "Aturan routing global untuk klien VPN. (Hanya untuk Happ)"
 "subListen" = "IP Pendengar"
 "subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
 "subPort" = "Port Pendengar"

+ 10 - 0
web/translation/translate.ja_JP.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。"
 "subTitle" = "サブスクリプションタイトル"
 "subTitleDesc" = "VPNクライアントに表示されるタイトル"
+"subSupportUrl" = "サポートURL"
+"subSupportUrlDesc" = "VPNクライアントに表示されるテクニカルサポートへのリンク"
+"subProfileUrl" = "プロフィールURL"
+"subProfileUrlDesc" = "VPNクライアントに表示されるWebサイトへのリンク"
+"subAnnounce" = "お知らせ"
+"subAnnounceDesc" = "VPNクライアントに表示されるお知らせのテキスト"
+"subEnableRouting" = "ルーティングを有効化"
+"subEnableRoutingDesc" = "VPNクライアントでルーティングを有効にするためのグローバル設定。(Happのみ)"
+"subRoutingRules" = "ルーティングルール"
+"subRoutingRulesDesc" = "VPNクライアントのグローバルルーティングルール。(Happのみ)"
 "subListen" = "監視IP"
 "subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)"
 "subPort" = "監視ポート"

+ 10 - 0
web/translation/translate.pt_BR.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Ativar/Desativar o endpoint de assinatura JSON de forma independente."
 "subTitle" = "Título da Assinatura"
 "subTitleDesc" = "Título exibido no cliente VPN"
+"subSupportUrl" = "URL de Suporte"
+"subSupportUrlDesc" = "Link de suporte técnico exibido no cliente VPN"
+"subProfileUrl" = "URL de Perfil"
+"subProfileUrlDesc" = "Um link para o seu site exibido no cliente VPN"
+"subAnnounce" = "Anúncio"
+"subAnnounceDesc" = "O texto do anúncio exibido no cliente VPN"
+"subEnableRouting" = "Ativar roteamento"
+"subEnableRoutingDesc" = "Configuração global para habilitar o roteamento no cliente VPN. (Apenas para Happ)"
+"subRoutingRules" = "Regras de roteamento"
+"subRoutingRulesDesc" = "Regras de roteamento globais para o cliente VPN. (Apenas para Happ)"
 "subListen" = "IP de Escuta"
 "subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
 "subPort" = "Porta de Escuta"

+ 11 - 1
web/translation/translate.ru_RU.toml

@@ -373,7 +373,17 @@
 "subEnableDesc" = "Функция подписки с отдельной конфигурацией"
 "subJsonEnable" = "Включить/отключить JSON-эндпоинт подписки независимо."
 "subTitle" = "Заголовок подписки"
-"subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
+"subTitleDesc" = "Название подписки, которое видит клиент в VPN-клиенте"
+"subSupportUrl" = "URL поддержки"
+"subSupportUrlDesc" = "Ссылка на техническую поддержку, отображаемая в VPN-клиенте"
+"subProfileUrl" = "URL профиля"
+"subProfileUrlDesc" = "Ссылка на ваш сайт, отображаемая в VPN-клиенте"
+"subAnnounce" = "Объявление"
+"subAnnounceDesc" = "Текст объявления, отображаемый в VPN-клиенте"
+"subEnableRouting" = "Включить маршрутизацию"
+"subEnableRoutingDesc" = "Глобальная настройка для включения маршрутизации в VPN-клиенте. (Только для Happ)"
+"subRoutingRules" = "Правила маршрутизации"
+"subRoutingRulesDesc" = "Глобальные правила маршрутизации для VPN-клиента. (Только для Happ)"
 "subListen" = "Прослушивание IP"
 "subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
 "subPort" = "Порт подписки"

+ 10 - 0
web/translation/translate.tr_TR.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "JSON abonelik uç noktasını bağımsız olarak Etkinleştir/Devre Dışı bırak."
 "subTitle" = "Abonelik Başlığı"
 "subTitleDesc" = "VPN istemcisinde gösterilen başlık"
+"subSupportUrl" = "Destek URL'si"
+"subSupportUrlDesc" = "VPN istemcisinde gösterilen teknik destek bağlantısı"
+"subProfileUrl" = "Profil URL'si"
+"subProfileUrlDesc" = "VPN istemcisinde görüntülenen web sitenize giden bağlantı"
+"subAnnounce" = "Duyuru"
+"subAnnounceDesc" = "VPN istemcisinde görüntülenen duyuru metni"
+"subEnableRouting" = "Yönlendirmeyi etkinleştir"
+"subEnableRoutingDesc" = "VPN istemcisinde yönlendirmeyi etkinleştirmek için genel ayar. (Yalnızca Happ için)"
+"subRoutingRules" = "Yönlendirme kuralları"
+"subRoutingRulesDesc" = "VPN istemcisi için genel yönlendirme kuralları. (Yalnızca Happ için)"
 "subListen" = "Dinleme IP"
 "subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
 "subPort" = "Dinleme Portu"

+ 10 - 0
web/translation/translate.uk_UA.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Увімкнути/вимкнути JSON-кінець підписки незалежно."
 "subTitle" = "Назва Підписки"
 "subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
+"subSupportUrl" = "URL підтримки"
+"subSupportUrlDesc" = "Посилання на технічну підтримку, що відображається у VPN-клієнті"
+"subProfileUrl" = "URL профілю"
+"subProfileUrlDesc" = "Посилання на ваш вебсайт, що відображається у VPN-клієнті"
+"subAnnounce" = "Оголошення"
+"subAnnounceDesc" = "Текст оголошення, що відображається у VPN-клієнті"
+"subEnableRouting" = "Увімкнути маршрутизацію"
+"subEnableRoutingDesc" = "Глобальне налаштування для увімкнення маршрутизації у VPN-клієнті. (Тільки для Happ)"
+"subRoutingRules" = "Правила маршрутизації"
+"subRoutingRulesDesc" = "Глобальні правила маршрутизації для VPN-клієнта. (Тільки для Happ)"
 "subListen" = "Слухати IP"
 "subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
 "subPort" = "Слухати порт"

+ 10 - 0
web/translation/translate.vi_VN.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "Bật/Tắt điểm cuối đăng ký JSON độc lập."
 "subTitle" = "Tiêu đề Đăng ký"
 "subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN"
+"subSupportUrl" = "URL Hỗ trợ"
+"subSupportUrlDesc" = "Liên kết hỗ trợ kỹ thuật hiển thị trong ứng dụng VPN"
+"subProfileUrl" = "URL Hồ sơ"
+"subProfileUrlDesc" = "Liên kết đến trang web của bạn hiển thị trong ứng dụng VPN"
+"subAnnounce" = "Thông báo"
+"subAnnounceDesc" = "Văn bản thông báo hiển thị trong ứng dụng VPN"
+"subEnableRouting" = "Bật định tuyến"
+"subEnableRoutingDesc" = "Cài đặt toàn cục để bật định tuyến trong ứng dụng khách VPN. (Chỉ dành cho Happ)"
+"subRoutingRules" = "Quy tắc định tuyến"
+"subRoutingRulesDesc" = "Quy tắc định tuyến toàn cầu cho client VPN. (Chỉ dành cho Happ)"
 "subListen" = "Listening IP"
 "subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
 "subPort" = "Cổng gói đăng ký"

+ 10 - 0
web/translation/translate.zh_CN.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "单独启用/禁用 JSON 订阅端点。"
 "subTitle" = "订阅标题"
 "subTitleDesc" = "在VPN客户端中显示的标题"
+"subSupportUrl" = "支持链接"
+"subSupportUrlDesc" = "VPN 客户端中显示的技术支持链接"
+"subProfileUrl" = "个人资料链接"
+"subProfileUrlDesc" = "VPN 客户端中显示的网站链接"
+"subAnnounce" = "公告"
+"subAnnounceDesc" = "VPN 客户端中显示的公告文本"
+"subEnableRouting" = "启用路由"
+"subEnableRoutingDesc" = "在 VPN 客户端中启用路由的全局设置。(僅限 Happ)"
+"subRoutingRules" = "路由規則"
+"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)"
 "subListen" = "监听 IP"
 "subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
 "subPort" = "监听端口"

+ 10 - 0
web/translation/translate.zh_TW.toml

@@ -374,6 +374,16 @@
 "subJsonEnable" = "獨立啟用/停用 JSON 訂閱端點。"
 "subTitle" = "訂閱標題"
 "subTitleDesc" = "在VPN客戶端中顯示的標題"
+"subSupportUrl" = "支援連結"
+"subSupportUrlDesc" = "VPN 用戶端中顯示的技術支援連結"
+"subProfileUrl" = "個人資料連結"
+"subProfileUrlDesc" = "VPN 用戶端中顯示的網站連結"
+"subAnnounce" = "公告"
+"subAnnounceDesc" = "VPN 用戶端中顯示的公告文字"
+"subEnableRouting" = "啟用路由"
+"subEnableRoutingDesc" = "在 VPN 用戶端中啟用路由的全域設定。(僅限 Happ)"
+"subRoutingRules" = "路由規則"
+"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)"
 "subListen" = "監聽 IP"
 "subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)"
 "subPort" = "監聽埠"