diff options
Diffstat (limited to 'web')
23 files changed, 190 insertions, 35 deletions
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index c91dbd11..1fc8ea19 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -1301,6 +1301,7 @@ class Inbound extends XrayCommonClass { const security = forceTls == 'same' ? this.stream.security : forceTls; const params = new Map(); params.set("type", this.stream.network); + params.set("encryption", this.settings.encryption); switch (type) { case "tcp": const tcp = this.stream.tcp; @@ -1859,13 +1860,16 @@ Inbound.VLESSSettings = class extends Inbound.Settings { constructor( protocol, vlesses = [new Inbound.VLESSSettings.VLESS()], - decryption = 'none', - fallbacks = [] + decryption = "none", + encryption = "", + fallbacks = [], ) { super(protocol); this.vlesses = vlesses; this.decryption = decryption; + this.encryption = encryption; this.fallbacks = fallbacks; + this.selectedAuth = "X25519, not Post-Quantum"; } addFallback() { @@ -1876,22 +1880,43 @@ Inbound.VLESSSettings = class extends Inbound.Settings { this.fallbacks.splice(index, 1); } - // decryption should be set to static value static fromJson(json = {}) { - return new Inbound.VLESSSettings( + const obj = new Inbound.VLESSSettings( Protocols.VLESS, - json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), - json.decryption || 'none', - Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),); + (json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), + json.decryption, + json.encryption, + Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || []) + ); + obj.selectedAuth = json.selectedAuth || "X25519, not Post-Quantum"; + return obj; } + toJson() { - return { + const json = { clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), - decryption: this.decryption, - fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks), }; + + if (this.decryption) { + json.decryption = this.decryption; + } + + if (this.encryption) { + json.encryption = this.encryption; + } + + if (this.fallbacks && this.fallbacks.length > 0) { + json.fallbacks = Inbound.VLESSSettings.toJsonArray(this.fallbacks); + } + if (this.selectedAuth) { + json.selectedAuth = this.selectedAuth; + } + + return json; } + + }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index ee78795f..2d5660fb 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -813,7 +813,7 @@ class Outbound extends CommonClass { var settings; switch (protocol) { case Protocols.VLESS: - settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? ''); + settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none'); break; case Protocols.Trojan: settings = new Outbound.TrojanSettings(address, port, userData); @@ -1046,13 +1046,13 @@ Outbound.VmessSettings = class extends CommonClass { } }; Outbound.VLESSSettings = class extends CommonClass { - constructor(address, port, id, flow, encryption = 'none') { + constructor(address, port, id, flow, encryption) { super(); this.address = address; this.port = port; this.id = id; this.flow = flow; - this.encryption = encryption + this.encryption = encryption; } static fromJson(json = {}) { @@ -1071,7 +1071,7 @@ Outbound.VLESSSettings = class extends CommonClass { vnext: [{ address: this.address, port: this.port, - users: [{ id: this.id, flow: this.flow, encryption: 'none', }], + users: [{ id: this.id, flow: this.flow, encryption: this.encryption }], }], }; } diff --git a/web/controller/server.go b/web/controller/server.go index 2d2f741e..b1174b8f 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -55,6 +55,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/getNewX25519Cert", a.getNewX25519Cert) g.POST("/getNewmldsa65", a.getNewmldsa65) g.POST("/getNewEchCert", a.getNewEchCert) + g.POST("/getNewVlessEnc", a.getNewVlessEnc) } func (a *ServerController) refreshStatus() { @@ -266,3 +267,12 @@ func (a *ServerController) getNewEchCert(c *gin.Context) { } jsonObj(c, cert, nil) } + +func (a *ServerController) getNewVlessEnc(c *gin.Context) { + out, err := a.serverService.GetNewVlessEnc() + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewVlessEncError"), err) + return + } + jsonObj(c, out, nil) +} diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html index c7a786b7..cfaaafd7 100644 --- a/web/html/form/outbound.html +++ b/web/html/form/outbound.html @@ -226,6 +226,11 @@ </template> <!-- vless settings --> + <template v-if="outbound.protocol === Protocols.VLESS"> + <a-form-item label='encryption'> + <a-input v-model.trim="outbound.settings.encryption"></a-input> + </a-form-item> + </template> <template v-if="outbound.canEnableTlsFlow()"> <a-form-item label='Flow'> <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme"> diff --git a/web/html/form/protocol/vless.html b/web/html/form/protocol/vless.html index 3cebda6e..6f421731 100644 --- a/web/html/form/protocol/vless.html +++ b/web/html/form/protocol/vless.html @@ -18,7 +18,29 @@ </table> </a-collapse-panel> </a-collapse> -<template v-if="inbound.isTcp"> +<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality"> + <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> + <a-form-item label="Authentication"> + <a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme"> + <a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option> + <a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="decryption"> + <a-input v-model.trim="inbound.settings.decryption"></a-input> + </a-form-item> + <a-form-item label="encryption"> + <a-input v-model="inbound.settings.encryption" disabled></a-input> + </a-form-item> + <a-form-item label=" "> + <a-space> + <a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button> + <a-button danger @click="clearKeys">Clear</a-button> + </a-space> + </a-form-item> + </a-form> +</template> +<template v-if="inbound.isTcp && !inbound.settings.encryption"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form-item label="Fallbacks"> <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button> diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html index 3a1802a3..82031bd7 100644 --- a/web/html/form/tls_settings.html +++ b/web/html/form/tls_settings.html @@ -5,13 +5,13 @@ <a-form-item label='{{ i18n "security" }}'> <a-radio-group v-model="inbound.stream.security" button-style="solid"> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button> - <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button> - <a-radio-button value="tls">TLS</a-radio-button> + <a-radio-button v-if="inbound.canEnableReality() && !inbound.settings.encryption" value="reality">Reality</a-radio-button> + <a-radio-button v-if="!inbound.settings.encryption" value="tls">TLS</a-radio-button> </a-radio-group> </a-form-item> <!-- tls settings --> - <template v-if="inbound.stream.isTls"> + <template v-if="inbound.stream.isTls && !inbound.settings.encryption"> <a-form-item label="SNI" placeholder="Server Name Indication"> <a-input v-model.trim="inbound.stream.tls.sni"></a-input> </a-form-item> @@ -121,7 +121,7 @@ </template> <!-- reality settings --> - <template v-if="inbound.stream.isReality"> + <template v-if="inbound.stream.isReality && !inbound.settings.encryption"> {{template "form/realitySettings"}} </template> </a-form> diff --git a/web/html/inbounds.html b/web/html/inbounds.html index dfccdd70..81ef1b39 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -706,7 +706,7 @@ }, { title: '{{ i18n "pages.inbounds.enable" }}', align: 'center', - width: 30, + width: 35, scopedSlots: { customRender: 'enable' }, }, { title: '{{ i18n "pages.inbounds.remark" }}', @@ -770,8 +770,8 @@ const innerColumns = [ { title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } }, - { title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } }, - { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, + { title: '{{ i18n "pages.inbounds.enable" }}', width: 35, scopedSlots: { customRender: 'enable' } }, + { title: '{{ i18n "online" }}', width: 32, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } }, diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html index a15172f3..4110c244 100644 --- a/web/html/modals/inbound_info_modal.html +++ b/web/html/modals/inbound_info_modal.html @@ -101,6 +101,12 @@ {{ i18n "security" }} <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag> <br /> + <td>Authentication</td> + <a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ? inbound.settings.selectedAuth : '' ]]</a-tag> + <br /> + {{ i18n "encryption" }} + <a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ? inbound.settings.encryption : '' ]]</a-tag> + <br /> <template v-if="inbound.stream.security != 'none'"> {{ i18n "domainName" }} <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag> diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html index b77e74e2..a7a59c99 100644 --- a/web/html/modals/inbound_modal.html +++ b/web/html/modals/inbound_modal.html @@ -1,9 +1,7 @@ {{define "modals/inboundModal"}} -<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" - :dialog-style="{ top: '20px' }" @ok="inModal.ok" - :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" - :class="themeSwitcher.currentTheme" - :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> +<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" :dialog-style="{ top: '20px' }" + @ok="inModal.ok" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" + :class="themeSwitcher.currentTheme" :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> {{template "form/inbound"}} </a-modal> <script> @@ -20,7 +18,7 @@ ok() { ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound); }, - show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => {}, isEdit = false }) { + show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => { }, isEdit = false }) { this.title = title; this.okText = okText; if (inbound) { @@ -41,7 +39,7 @@ inModal.visible = false; inModal.loading(false); }, - loading(loading=true) { + loading(loading = true) { inModal.confirmLoading = loading; }, }; @@ -105,9 +103,9 @@ }, SSMethodChange() { this.inModal.inbound.settings.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method) - + if (this.inModal.inbound.isSSMultiUser) { - if (this.inModal.inbound.settings.shadowsockses.length ==0){ + if (this.inModal.inbound.settings.shadowsockses.length == 0) { this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()]; } if (!this.inModal.inbound.isSS2022) { @@ -123,7 +121,7 @@ client.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method) }) } else { - if (this.inModal.inbound.settings.shadowsockses.length > 0){ + if (this.inModal.inbound.settings.shadowsockses.length > 0) { this.inModal.inbound.settings.shadowsockses = []; } } @@ -154,7 +152,7 @@ }, async getNewEchCert() { inModal.loading(true); - const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni}); + const msg = await HttpUtil.post('/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni }); inModal.loading(false); if (!msg.success) { return; @@ -162,8 +160,34 @@ inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys; inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList; }, + async getNewVlessEnc() { + inModal.loading(true); + const msg = await HttpUtil.post('/server/getNewVlessEnc'); + inModal.loading(false); + + if (!msg.success) { + return; + } + + const auths = msg.obj.auths || []; + const selected = inModal.inbound.settings.selectedAuth; + const block = auths.find(a => a.label === selected); + + if (!block) { + console.error("No auth block for", selected); + return; + } + + inModal.inbound.settings.decryption = block.decryption; + inModal.inbound.settings.encryption = block.encryption; + }, + clearKeys() { + this.inbound.settings.decryption = 'none'; + this.inbound.settings.encryption = ''; + } + }, }); </script> -{{end}} +{{end}}
\ No newline at end of file diff --git a/web/service/server.go b/web/service/server.go index 2dc83d77..3078e88b 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -871,3 +871,53 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) { "echConfigList": configList, }, nil } + +type AuthBlock struct { + Label string `json:"label"` + Decryption string `json:"decryption"` + Encryption string `json:"encryption"` +} + +func (s *ServerService) GetNewVlessEnc() (any, error) { + cmd := exec.Command(xray.GetBinaryPath(), "vlessenc") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return nil, err + } + + lines := strings.Split(out.String(), "\n") + + var blocks []AuthBlock + var current *AuthBlock + + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "Authentication:") { + if current != nil { + blocks = append(blocks, *current) + } + current = &AuthBlock{Label: strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))} + } else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) { + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 && current != nil { + key := strings.Trim(parts[0], `" `) + val := strings.Trim(parts[1], `" `) + switch key { + case "decryption": + current.Decryption = val + case "encryption": + current.Encryption = val + } + } + } + } + + if current != nil { + blocks = append(blocks, *current) + } + + return map[string]any{ + "auths": blocks, + }, nil +} diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml index dd5618cb..9f11a30c 100644 --- a/web/translation/translate.ar_EG.toml +++ b/web/translation/translate.ar_EG.toml @@ -267,6 +267,7 @@ "trafficGetError" = "خطأ في الحصول على حركات المرور" "getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519." "getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65." +"getNewVlessEncError" = "حدث خطأ أثناء الحصول على VlessEnc." [pages.inbounds.stream.general] "request" = "طلب" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 89f127a8..f802dd6d 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Error getting traffics." "getNewX25519CertError" = "Error while obtaining the X25519 certificate." "getNewmldsa65Error" = "Error while obtaining mldsa65." +"getNewVlessEncError" = "Error while obtaining VlessEnc." [pages.inbounds.stream.general] "request" = "Request" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index 070b6b57..80ddd98a 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Error al obtener los tráficos"
"getNewX25519CertError" = "Error al obtener el certificado X25519."
"getNewmldsa65Error" = "Error al obtener el certificado mldsa65."
+"getNewVlessEncError" = "Error al obtener el certificado VlessEnc."
[pages.inbounds.stream.general]
"request" = "Pedido"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 5c949928..778dd528 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -267,6 +267,7 @@ "trafficGetError" = "خطا در دریافت ترافیکها" "getNewX25519CertError" = "خطا در دریافت گواهی X25519." "getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65." +"getNewVlessEncError" = "خطا در دریافت گواهی VlessEnc." [pages.inbounds.stream.general] "request" = "درخواست" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index 4dc8e378..2a5b568b 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Gagal mendapatkan data lalu lintas" "getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519." "getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65." +"getNewVlessEncError" = "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc." [pages.inbounds.stream.general] "request" = "Permintaan" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index 54479232..7af0a339 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -267,6 +267,7 @@ "trafficGetError" = "トラフィックの取得中にエラーが発生しました" "getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。" "getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。" +"getNewVlessEncError" = "VlessEnc証明書の取得中にエラーが発生しました。" [pages.inbounds.stream.general] "request" = "リクエスト" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index a3aac778..f61f8995 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Erro ao obter tráfegos" "getNewX25519CertError" = "Erro ao obter o certificado X25519." "getNewmldsa65Error" = "Erro ao obter o certificado mldsa65." +"getNewVlessEncError" = "Erro ao obter o certificado VlessEnc." [pages.inbounds.stream.general] "request" = "Requisição" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 718edb51..060b7334 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Ошибка получения данных о трафике" "getNewX25519CertError" = "Ошибка при получении сертификата X25519." "getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65." +"getNewVlessEncError" = "Ошибка при получении сертификата VlessEnc." [pages.inbounds.stream.general] "request" = "Запрос" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index 047d9d57..ac10bf65 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Trafik bilgisi alınırken hata oluştu" "getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu." "getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu." +"getNewVlessEncError" = "VlessEnc sertifikası alınırken hata oluştu." [pages.inbounds.stream.general] "request" = "İstek" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index 3dc5b3e7..4005f14f 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -267,6 +267,7 @@ "trafficGetError" = "Помилка отримання даних про трафік" "getNewX25519CertError" = "Помилка при отриманні сертифіката X25519." "getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65." +"getNewVlessEncError" = "Помилка при отриманні сертифіката VlessEnc." [pages.inbounds.stream.general] "request" = "Запит" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index aa0009eb..66ed38ce 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -266,7 +266,8 @@ "resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng"
"trafficGetError" = "Lỗi khi lấy thông tin lưu lượng"
"getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519."
-"getNewmldsa65Error" = "Lỗi khi lấy chúng tôi mldsa65."
+"getNewmldsa65Error" = "Lỗi khi lấy chứng chỉ mldsa65."
+"getNewVlessEncError" = "Lỗi khi lấy chứng chỉ VlessEnc."
[pages.inbounds.stream.general]
"request" = "Lời yêu cầu"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 01844f13..5875add9 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -267,6 +267,7 @@ "trafficGetError" = "获取流量数据时出错" "getNewX25519CertError" = "获取X25519证书时出错。" "getNewmldsa65Error" = "获取mldsa65证书时出错。" +"getNewVlessEncError" = "获取VlessEnc证书时出错。" [pages.inbounds.stream.general] "request" = "请求" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index f3121c69..6843e9ee 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -267,6 +267,7 @@ "trafficGetError" = "取得流量資料時發生錯誤" "getNewX25519CertError" = "取得X25519憑證時發生錯誤。" "getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。" +"getNewVlessEncError" = "取得VlessEnc憑證時發生錯誤。" [pages.inbounds.stream.general] "request" = "請求" |
