Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authormhsanaei <ho3ein.sanaei@gmail.com>2025-09-18 14:56:04 +0300
committermhsanaei <ho3ein.sanaei@gmail.com>2025-09-18 14:56:04 +0300
commit59ea2645db827335a0689d2fb7aeeef4e52af52b (patch)
treea52caa80571fef4919c3df59a4bceacd60ba6aa6 /web
parent8c8d280f147ce4e8f604080d1dbf066332e55efc (diff)
new: subJsonEnable
after this subEnable by default is true and subJsonEnable is false
Diffstat (limited to 'web')
-rw-r--r--web/assets/js/model/setting.js3
-rw-r--r--web/assets/js/subscription.js5
-rw-r--r--web/entity/entity.go1
-rw-r--r--web/html/inbounds.html6
-rw-r--r--web/html/modals/inbound_info_modal.html4
-rw-r--r--web/html/modals/qrcode_modal.html6
-rw-r--r--web/html/settings.html4
-rw-r--r--web/html/settings/panel/subscription/general.html7
-rw-r--r--web/html/settings/panel/subscription/subpage.html4
-rw-r--r--web/service/setting.go21
-rw-r--r--web/service/tgbot.go30
-rw-r--r--web/translation/translate.ar_EG.toml1
-rw-r--r--web/translation/translate.en_US.toml5
-rw-r--r--web/translation/translate.es_ES.toml1
-rw-r--r--web/translation/translate.fa_IR.toml1
-rw-r--r--web/translation/translate.id_ID.toml1
-rw-r--r--web/translation/translate.ja_JP.toml1
-rw-r--r--web/translation/translate.pt_BR.toml1
-rw-r--r--web/translation/translate.ru_RU.toml1
-rw-r--r--web/translation/translate.tr_TR.toml1
-rw-r--r--web/translation/translate.uk_UA.toml1
-rw-r--r--web/translation/translate.vi_VN.toml1
-rw-r--r--web/translation/translate.zh_CN.toml1
-rw-r--r--web/translation/translate.zh_TW.toml1
24 files changed, 80 insertions, 28 deletions
diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js
index 55fbf635..d3f7f3e2 100644
--- a/web/assets/js/model/setting.js
+++ b/web/assets/js/model/setting.js
@@ -26,7 +26,8 @@ class AllSetting {
this.twoFactorEnable = false;
this.twoFactorToken = "";
this.xrayTemplateConfig = "";
- this.subEnable = false;
+ this.subEnable = true;
+ this.subJsonEnable = false;
this.subTitle = "";
this.subListen = "";
this.subPort = 2096;
diff --git a/web/assets/js/subscription.js b/web/assets/js/subscription.js
index 2c731be3..0af95890 100644
--- a/web/assets/js/subscription.js
+++ b/web/assets/js/subscription.js
@@ -101,7 +101,10 @@
if (sj) this.app.subJsonUrl = sj;
drawQR(this.app.subUrl);
try {
- new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 });
+ const elJson = document.getElementById('qrcode-subjson');
+ if (elJson && this.app.subJsonUrl) {
+ new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 });
+ }
} catch (e) { /* ignore */ }
this._onResize = () => { this.viewportWidth = window.innerWidth; };
window.addEventListener('resize', this._onResize);
diff --git a/web/entity/entity.go b/web/entity/entity.go
index 844a7ce0..dd6885f6 100644
--- a/web/entity/entity.go
+++ b/web/entity/entity.go
@@ -42,6 +42,7 @@ type AllSetting struct {
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
SubEnable bool `json:"subEnable" form:"subEnable"`
+ SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"`
SubTitle string `json:"subTitle" form:"subTitle"`
SubListen string `json:"subListen" form:"subListen"`
SubPort int `json:"subPort" form:"subPort"`
diff --git a/web/html/inbounds.html b/web/html/inbounds.html
index 1c48480d..5f17c98b 100644
--- a/web/html/inbounds.html
+++ b/web/html/inbounds.html
@@ -736,10 +736,11 @@
refreshing: false,
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
subSettings: {
- enable: false,
+ enable: true,
subTitle: '',
subURI: '',
subJsonURI: '',
+ subJsonEnable: false,
},
remarkModel: '-ieo',
datepicker: 'gregorian',
@@ -795,7 +796,8 @@
enable: subEnable,
subTitle: subTitle,
subURI: subURI,
- subJsonURI: subJsonURI
+ subJsonURI: subJsonURI,
+ subJsonEnable: subJsonEnable,
};
this.pageSize = pageSize;
this.remarkModel = remarkModel;
diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html
index 5112ec1c..55d9919c 100644
--- a/web/html/modals/inbound_info_modal.html
+++ b/web/html/modals/inbound_info_modal.html
@@ -308,7 +308,7 @@
</tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
</tr-info-row>
- <tr-info-row class="tr-info-row">
+ <tr-info-row class="tr-info-row" v-if="app.subSettings.subJsonEnable">
<tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'>
@@ -548,7 +548,7 @@
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
- this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
+ this.subJsonLink = app.subSettings.subJsonEnable ? this.genSubJsonLink(this.clientSettings.subId) : '';
}
}
this.visible = true;
diff --git a/web/html/modals/qrcode_modal.html b/web/html/modals/qrcode_modal.html
index ef438b74..cdbb585b 100644
--- a/web/html/modals/qrcode_modal.html
+++ b/web/html/modals/qrcode_modal.html
@@ -30,7 +30,7 @@
</tr-qr-bg-inner>
</tr-qr-bg>
</tr-qr-box>
- <tr-qr-box class="qr-box">
+ <tr-qr-box class="qr-box" v-if="app.subSettings.subJsonEnable">
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
<tr-qr-bg class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner">
@@ -262,7 +262,9 @@
if (qrModal.client && qrModal.client.subId) {
qrModal.subId = qrModal.client.subId;
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
- this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
+ if (app.subSettings.subJsonEnable) {
+ this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
+ }
}
qrModal.qrcodes.forEach((element, index) => {
this.setQrCode("qrCode-" + index, element.link);
diff --git a/web/html/settings.html b/web/html/settings.html
index 0c88f518..22ad3907 100644
--- a/web/html/settings.html
+++ b/web/html/settings.html
@@ -79,7 +79,7 @@
</template>
{{ template "settings/panel/subscription/general" . }}
</a-tab-pane>
- <a-tab-pane key="5" v-if="allSetting.subEnable" :style="{ paddingTop: '20px' }">
+ <a-tab-pane key="5" v-if="allSetting.subJsonEnable" :style="{ paddingTop: '20px' }">
<template #tab>
<a-icon type="code"></a-icon>
<span>{{ i18n "pages.settings.subSettings" }} (JSON)</span>
@@ -523,6 +523,8 @@
if (this.allSetting.subEnable) {
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
+ }
+ if (this.allSetting.subJsonEnable) {
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
}
diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html
index b6e616dd..e3481df7 100644
--- a/web/html/settings/panel/subscription/general.html
+++ b/web/html/settings/panel/subscription/general.html
@@ -9,6 +9,13 @@
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small">
+ <template #title>JSON Subscription</template>
+ <template #description>{{ i18n "pages.settings.subJsonEnable"}}</template>
+ <template #control>
+ <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>
diff --git a/web/html/settings/panel/subscription/subpage.html b/web/html/settings/panel/subscription/subpage.html
index c9cf241f..6d56496b 100644
--- a/web/html/settings/panel/subscription/subpage.html
+++ b/web/html/settings/panel/subscription/subpage.html
@@ -56,7 +56,7 @@
<a-space direction="vertical" align="center">
<a-row type="flex" :gutter="[8,8]"
justify="center" style="width:100%">
- <a-col :xs="24" :sm="12"
+ <a-col :xs="24" :sm="app.subJsonUrl ? 12 : 24"
style="text-align:center;">
<tr-qr-box class="qr-box">
<a-tag color="purple"
@@ -75,7 +75,7 @@
</tr-qr-bg>
</tr-qr-box>
</a-col>
- <a-col :xs="24" :sm="12"
+ <a-col v-if="app.subJsonUrl" :xs="24" :sm="12"
style="text-align:center;">
<tr-qr-box class="qr-box">
<a-tag color="purple"
diff --git a/web/service/setting.go b/web/service/setting.go
index a54eaea7..39961ad5 100644
--- a/web/service/setting.go
+++ b/web/service/setting.go
@@ -50,7 +50,8 @@ var defaultValueMap = map[string]string{
"tgLang": "en-US",
"twoFactorEnable": "false",
"twoFactorToken": "",
- "subEnable": "false",
+ "subEnable": "true",
+ "subJsonEnable": "false",
"subTitle": "",
"subListen": "",
"subPort": "2096",
@@ -427,6 +428,10 @@ func (s *SettingService) GetSubEnable() (bool, error) {
return s.getBool("subEnable")
}
+func (s *SettingService) GetSubJsonEnable() (bool, error) {
+ return s.getBool("subJsonEnable")
+}
+
func (s *SettingService) GetSubTitle() (string, error) {
return s.getString("subTitle")
}
@@ -575,6 +580,7 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
"defaultKey": func() (any, error) { return s.GetKeyFile() },
"tgBotEnable": func() (any, error) { return s.GetTgbotEnabled() },
"subEnable": func() (any, error) { return s.GetSubEnable() },
+ "subJsonEnable": func() (any, error) { return s.GetSubJsonEnable() },
"subTitle": func() (any, error) { return s.GetSubTitle() },
"subURI": func() (any, error) { return s.GetSubURI() },
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
@@ -593,7 +599,14 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
result[key] = value
}
- if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
+ subEnable := result["subEnable"].(bool)
+ subJsonEnable := false
+ if v, ok := result["subJsonEnable"]; ok {
+ if b, ok2 := v.(bool); ok2 {
+ subJsonEnable = b
+ }
+ }
+ if (subEnable && result["subURI"].(string) == "") || (subJsonEnable && result["subJsonURI"].(string) == "") {
subURI := ""
subTitle, _ := s.GetSubTitle()
subPort, _ := s.GetSubPort()
@@ -619,13 +632,13 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
} else {
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
}
- if result["subURI"].(string) == "" {
+ if subEnable && result["subURI"].(string) == "" {
result["subURI"] = subURI + subPath
}
if result["subTitle"].(string) == "" {
result["subTitle"] = subTitle
}
- if result["subJsonURI"].(string) == "" {
+ if subJsonEnable && result["subJsonURI"].(string) == "" {
result["subJsonURI"] = subURI + subJsonPath
}
}
diff --git a/web/service/tgbot.go b/web/service/tgbot.go
index f21af6cc..dd6ac196 100644
--- a/web/service/tgbot.go
+++ b/web/service/tgbot.go
@@ -2093,6 +2093,7 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
subPort, _ := t.settingService.GetSubPort()
subPath, _ := t.settingService.GetSubPath()
subJsonPath, _ := t.settingService.GetSubJsonPath()
+ subJsonEnable, _ := t.settingService.GetSubJsonEnable()
subKeyFile, _ := t.settingService.GetSubKeyFile()
subCertFile, _ := t.settingService.GetSubCertFile()
@@ -2137,6 +2138,9 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
subURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subPath, client.SubID)
subJsonURL := fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
+ if !subJsonEnable {
+ subJsonURL = ""
+ }
return subURL, subJsonURL, nil
}
@@ -2146,8 +2150,10 @@ func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
return
}
- msg := "Subscription URL:\r\n<code>" + subURL + "</code>\r\n\r\n" +
- "JSON URL:\r\n<code>" + subJsonURL + "</code>"
+ msg := "Subscription URL:\r\n<code>" + subURL + "</code>"
+ if subJsonURL != "" {
+ msg += "\r\n\r\nJSON URL:\r\n<code>" + subJsonURL + "</code>"
+ }
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links "+email)),
@@ -2271,15 +2277,17 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
}
- // Send JSON URL QR (filename: subjson.png)
- if png, err := createQR(subJsonURL, 320); err == nil {
- document := tu.Document(
- tu.ID(chatId),
- tu.FileFromBytes(png, "subjson.png"),
- )
- _, _ = bot.SendDocument(context.Background(), document)
- } else {
- t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
+ // Send JSON URL QR (filename: subjson.png) when available
+ if subJsonURL != "" {
+ if png, err := createQR(subJsonURL, 320); err == nil {
+ document := tu.Document(
+ tu.ID(chatId),
+ tu.FileFromBytes(png, "subjson.png"),
+ )
+ _, _ = bot.SendDocument(context.Background(), document)
+ } else {
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
+ }
}
// Also generate a few individual links' QRs (first up to 5)
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index 4ddf9fd8..2287b52b 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -371,6 +371,7 @@
"subSettings" = "الاشتراك"
"subEnable" = "تفعيل خدمة الاشتراك"
"subEnableDesc" = "يفعل خدمة الاشتراك."
+"subJsonEnable" = "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل."
"subTitle" = "عنوان الاشتراك"
"subTitleDesc" = "العنوان اللي هيظهر في عميل VPN"
"subListen" = "IP الاستماع"
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 5a3735dd..e56f5f89 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -369,8 +369,9 @@
"timeZone" = "Time Zone"
"timeZoneDesc" = "Scheduled tasks will run based on this time zone."
"subSettings" = "Subscription"
-"subEnable" = "Enable Subscription Service"
-"subEnableDesc" = "Enables the subscription service."
+"subEnable" = "Subscription Service"
+"subEnableDesc" = "Enable/Disable the subscription service."
+"subJsonEnable" = "Enable/Disable the JSON subscription endpoint independently."
"subTitle" = "Subscription Title"
"subTitleDesc" = "Title shown in VPN client"
"subListen" = "Listen IP"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 83a6abbc..ea70494c 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -371,6 +371,7 @@
"subSettings" = "Suscripción"
"subEnable" = "Habilitar Servicio"
"subEnableDesc" = "Función de suscripción con configuración separada."
+"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"
"subListen" = "Listening IP"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index 37efbc65..70603d08 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -371,6 +371,7 @@
"subSettings" = "سابسکریپشن"
"subEnable" = "فعال‌سازی سرویس سابسکریپشن"
"subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند"
+"subJsonEnable" = "فعال/غیرفعال‌سازی مستقل نقطه دسترسی سابسکریپشن JSON."
"subTitle" = "عنوان اشتراک"
"subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
"subListen" = "آدرس آی‌پی"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index e0ceaab5..5068aecb 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -371,6 +371,7 @@
"subSettings" = "Langganan"
"subEnable" = "Aktifkan Layanan Langganan"
"subEnableDesc" = "Mengaktifkan layanan langganan."
+"subJsonEnable" = "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri."
"subTitle" = "Judul Langganan"
"subTitleDesc" = "Judul yang ditampilkan di klien VPN"
"subListen" = "IP Pendengar"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index 7f74f1d4..ebec829a 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -371,6 +371,7 @@
"subSettings" = "サブスクリプション設定"
"subEnable" = "サブスクリプションサービスを有効にする"
"subEnableDesc" = "サブスクリプションサービス機能を有効にする"
+"subJsonEnable" = "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。"
"subTitle" = "サブスクリプションタイトル"
"subTitleDesc" = "VPNクライアントに表示されるタイトル"
"subListen" = "監視IP"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index ab21489d..f6f3b975 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -371,6 +371,7 @@
"subSettings" = "Assinatura"
"subEnable" = "Ativar Serviço de Assinatura"
"subEnableDesc" = "Ativa o serviço de assinatura."
+"subJsonEnable" = "Ativar/Desativar o endpoint de assinatura JSON de forma independente."
"subTitle" = "Título da Assinatura"
"subTitleDesc" = "Título exibido no cliente VPN"
"subListen" = "IP de Escuta"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 4e46a9f3..49ea872c 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -371,6 +371,7 @@
"subSettings" = "Подписка"
"subEnable" = "Включить подписку"
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
+"subJsonEnable" = "Включить/отключить JSON-эндпоинт подписки независимо."
"subTitle" = "Заголовок подписки"
"subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
"subListen" = "Прослушивание IP"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index 90394154..d34f6fca 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -371,6 +371,7 @@
"subSettings" = "Abonelik"
"subEnable" = "Abonelik Hizmetini Etkinleştir"
"subEnableDesc" = "Abonelik hizmetini etkinleştirir."
+"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"
"subListen" = "Dinleme IP"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index c59e789d..e8312fb4 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -371,6 +371,7 @@
"subSettings" = "Підписка"
"subEnable" = "Увімкнути службу підписки"
"subEnableDesc" = "Вмикає службу підписки."
+"subJsonEnable" = "Увімкнути/вимкнути JSON-кінець підписки незалежно."
"subTitle" = "Назва Підписки"
"subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
"subListen" = "Слухати IP"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index c653a0cb..787451ae 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -371,6 +371,7 @@
"subSettings" = "Gói đăng ký"
"subEnable" = "Bật dịch vụ"
"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
+"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"
"subListen" = "Listening IP"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index 333a61c7..b11f9073 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -371,6 +371,7 @@
"subSettings" = "订阅设置"
"subEnable" = "启用订阅服务"
"subEnableDesc" = "启用订阅服务功能"
+"subJsonEnable" = "单独启用/禁用 JSON 订阅端点。"
"subTitle" = "订阅标题"
"subTitleDesc" = "在VPN客户端中显示的标题"
"subListen" = "监听 IP"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index f646b45c..ad1bdab9 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -371,6 +371,7 @@
"subSettings" = "訂閱設定"
"subEnable" = "啟用訂閱服務"
"subEnableDesc" = "啟用訂閱服務功能"
+"subJsonEnable" = "獨立啟用/停用 JSON 訂閱端點。"
"subTitle" = "訂閱標題"
"subTitleDesc" = "在VPN客戶端中顯示的標題"
"subListen" = "監聽 IP"