mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-17 10:06:08 +00:00
5e1cb7693b
* fix(email): resolve a name-addr smtpFrom into bare envelope address and display name The save-time validator accepts any RFC 5322 address form, so a value like '3x-ui Panel <panel(at)example.com>' passes validation, but Send and TestConnection fed that raw string to MAIL FROM, which strict servers reject with 501, and buildMessage mangled it into a quoted local part. Parse the configured sender at the point of use: the envelope gets the bare address and, when no explicit sender name is set, the display name embedded in the setting is used for the From header. * fix(email): report a missing sender address from the SMTP connection test TestConnection skipped the empty-from guard that Send enforces, so with no sender and no username configured the test issued the null reverse-path and could report success against a lenient relay while every real notification send kept failing with the missing-sender error. Guard the test path the same way and surface a dedicated translated message. * fix(sub): fall back to the raw subscription when an auto-detected format has no content With format auto-detection enabled, a client whose User-Agent matched the Clash or JSON regex was routed straight to that format handler. For a subscription whose entries convert to neither format (an MTProto-only subscription, for example) the handler returns an empty document and the request ended as 404, breaking a URL that served the raw list before the toggle. The auto-detect branches now serve the detected format only when it produces content and otherwise continue to the raw response; the explicit format endpoints keep answering 404 for empty documents. * fix(node): match prefixed central tags when filtering a selected-mode node snapshot FilterNodeSnapshot compared a node snapshot's inbound tags against the raw selected-tag list with an exact match, while its two siblings (SnapshotHasUnadoptedInbounds and the reconcile tagToCentral map) expand each selected tag to both its bare node-side form and its n<id>- prefixed central form. A panel-created node inbound is recorded in the selected list under the central prefixed tag but reported by the node under the bare tag, so the exact match dropped it from every snapshot and the orphan sweep then deleted its central row one tick after creation. Expand the allowed set with the same prefix flip the siblings use. * fix(client): refuse a bulk quota reduction that would fall to or below zero BulkAdjust clamped a client's new traffic limit with max(total+addBytes, 0). Because 0 is the unlimited sentinel, reducing a client's quota by more than it had left silently granted that client unlimited traffic. The sibling expiry branch already refuses an over-reduction; mirror it for quota so the adjustment is skipped with a clear reason instead of crossing the sentinel. * fix(client): persist a bulk adjustment's applied field even when the sibling field is skipped In a mixed BulkAdjust (both a days delta and a bytes delta), a per-field planning skip such as "unlimited expiry" or "unlimited traffic" was recorded in the same map that gated the client_traffics write. The applied field was already written to the inbound JSON and the clients table, but the enforcement row was left untouched, so the depletion job cut the client on the old limit while the panel showed the new one. Gate the traffic-row write on an actual inbound-processing failure rather than on any planning-phase skip note. * fix(inbound): always create in AddInbound instead of overwriting a row whose id was posted The add controller binds the inbound model's id form field and never clears it, and AddInbound persisted with GORM Save, which updates in place when the primary key is non-zero. A client that reused an existing id (for instance by duplicating an inbound fetched from /get and changing the port) silently overwrote that stored row instead of creating a new inbound. Zero the id at the top of AddInbound, matching how it already zeroes the client-stat ids. * fix(inbound): accept WireGuard clients when creating an inbound AddInbound's per-client validation switch had cases for every protocol except WireGuard, so a WireGuard client fell through to the default branch that requires a non-empty id. WireGuard clients are keyed by their public key and carry no id, so importing a WireGuard inbound or re-adding one to a reconciling node was rejected with "empty client ID". Add a wireguard case that validates the client key, mirroring addInboundClient. * fix(client): stop holding the inbound-lock registry mutex while waiting on one inbound lockInbound acquired the global registry mutex and then blocked on the per-inbound mutex without releasing the registry first. A slow client operation holding one inbound's mutex (for example a bulk delete pushing to an unreachable node) made the next waiter park on that inbound while still holding the registry mutex, which in turn blocked lockInbound for every other inbound — freezing client mutations panel-wide. Release the registry mutex before taking the per-inbound lock. * fix(client): honor keepTraffic when deleting a client that is attached to inbounds Delete, DeleteByEmail and BulkDelete all pass keepTraffic to their final cleanup transaction, but each called the per-inbound delete helper with a hardcoded false. That helper purges the client's traffic, IP and stat rows before the gated cleanup runs, so keepTraffic=true still destroyed all traffic history for any client actually attached to an inbound (the pinned test only covered a record with no inbound mappings). Thread the caller's keepTraffic through to the per-inbound helper at all three call sites. * fix(inbound): defer a local MTProto inbound edit's sidecar push until after commit UpdateInbound applied a local MTProto inbound change by calling the runtime UpdateInbound (which stops/starts the mtg sidecar or talks to it) from inside runSerializedTx. That runs process and network I/O on the single traffic-writer goroutine while a DB transaction is open, so a slow sidecar stalls traffic accounting and every concurrent client mutation, and a later step failing the transaction leaves the sidecar ahead of the rolled-back row. Move the push into the post-commit hook, matching the xray branch. Adds a SetLocalRuntimeOverride test seam mirroring the existing node override so the deferral is regression tested. * fix(client): delete external-link rows when bulk-deleting clients The single-client Delete path removes a client's client_external_links rows, but BulkDelete (and the DelDepleted reaper that routes through it) deleted the record, mappings and traffic while leaving the external-link rows keyed by the now-dead client id, so they accumulated as orphans. Delete them in the same cleanup transaction, keyed by client id like the single path. * fix(inbound): request an xray restart when toggling a routed MTProto inbound AddInbound, DelInbound and UpdateInbound all flag needRestart when an inbound routes MTProto through xray, so the egress SOCKS bridge is regenerated. Only SetInboundEnable's local path omitted it, so toggling a routed MTProto inbound off then on left the bridge out of the running config while the sidecar dialed its loopback port, blackholing that inbound until an unrelated restart. Flag the restart on the local enable path too. * fix(client): apply enable-by-email to every inbound a client is attached to ToggleClientEnableByEmail (Telegram bot) and SetClientEnableByEmail (LDAP sync) resolved a single inbound via the legacy client_traffics pointer and flipped enable only there. A client attached to several inbounds kept connecting through the siblings' running Xray after being disabled, and the next edit could re-enable it everywhere from a stale sibling. Route both through the applyClientFieldByEmail fan-out (the #5039 fix path) so the whole multi-inbound identity is toggled at once, dropping the circular Set/Toggle dependency. * fix(traffic): commit a traffic tick even when a best-effort maintenance helper fails addTrafficLocked stages the inbound and client deltas, then runs three helpers (auto-renew, disable depleted clients, disable depleted inbounds) that are meant to log and continue. All three reused the function-scope err that the deferred commit/rollback inspects, so the last helper's error decided the whole tick: a failure in disableInvalidInbounds rolled back the already-staged traffic while AddTraffic reported success, and because xray had already advanced its counter baseline that traffic was lost for good. Give each best-effort helper its own error variable so only a genuine staging failure rolls the tick back. * fix(traffic): re-enable clients and serialize the write in Reset All Client Traffic ClientService.ResetAllTraffics zeroed up/down but, unlike every sibling reset path, never restored enable=true, so clients that had been auto-disabled for exceeding their quota stayed cut with zero usage after a reset. It also wrote client_traffics directly on the shared DB handle instead of through the serial traffic writer, reintroducing the cross-transaction lock-order deadlock the writer exists to prevent. Restore enable and run the reset inside submitTrafficWrite within one transaction. * fix(traffic): keep node reset propagation out of the serial traffic writer ResetAllTraffics and ResetInboundTraffic performed their remote-node reset HTTP calls inside submitTrafficWrite. Each call can block up to the remote timeout, and Reset All Traffics loops every node serially, so the single traffic-writer goroutine was held for seconds — long enough that the concurrent 5s traffic poll timed out submitting its own write and dropped the deltas it had already drained from xray. Do the DB reset inside the writer, then propagate to the nodes after it returns, matching how the mtproto quota reset is already sequenced. * fix(sub): stop the subscription from 500ing on valid-but-unusual stream settings The raw share-link generators used unchecked type assertions and unguarded array indexing: an empty Reality shortIds/serverNames array (random.Num(0) panics), a tcp-http header with no request block or an empty request.path, a grpc block missing its keys, empty stream settings, and a non-string Host header all panicked mid-generation. Because getSubs loops every client's link with no recover, one such client 500s the entire subscription for everyone. The sibling JSON, Clash and frontend generators already guard these; make the raw generators match with comma-ok assertions and length checks. * fix(sub): tolerate a hysteria inbound without hysteriaSettings in the JSON subscription genHy asserted stream["hysteriaSettings"].(map[string]any) without the comma-ok form, so a hysteria inbound whose StreamSettings omit the hysteriaSettings key (a valid, representable shape the raw generator renders fine) panicked and 500ed the entire JSON subscription. Use comma-ok; the downstream reads already guard each key, so a nil map degrades gracefully. * fix(sub): emit the pinned peer cert sha256 in Clash subscriptions The Clash stream builder computed tlsSettings["pin-sha256"] from the inbound's pinnedPeerCertSha256, but applySecurity's tls case never copied it onto the proxy, so it was written with no reader and silently dropped. Clash subscribers lost certificate pinning while JSON subscribers kept it. Surface pin-sha256 on the proxy in the tls case, matching the JSON emitter. * fix(link): parse the snake_case and extra-blob xhttp fields when importing a share link The panel's share-link emitters (Go and TS) carry advanced xhttp knobs as a snake_case x_padding_bytes plus an extra=<json> payload, but the Go parser's xhttp branch read only top-level camelCase params, so importing an xhttp link via the outbound-subscription feature dropped xPaddingBytes, scMaxEachPostBytes and the rest, silently reverting them to the stream defaults and producing a non-working outbound. Mirror the TS parser: read the snake_case alias, merge the extra JSON blob, then let explicit camelCase params win. * fix(frontend): decode URL-safe base64 when parsing an imported share link Base64.decode called window.atob directly, which rejects the base64url alphabet (- and _) and unpadded input. But the panel's own share-link emitter uses Base64.encode(x, true) (URL-safe, unpadded), and real SIP002 links do too, so importing a Shadowsocks link whose method:password encodes with a - or _ threw, fell back to the raw undecoded string, and produced a wrong method and garbage password (the vmess parser shared the same limitation). Normalize base64url and re-pad before atob so decode round-trips every emitted link. * fix(link): honor the vmess ws path and hysteria2 vcn params on import Two Go/TS parser parity gaps in the outbound share-link import path: parseVmess only applied a ws link's path when the inner JSON also carried a host key, so a generator that omits host dropped the path back to the default; and parseHysteria2 hardcoded verifyPeerCertByName to empty, ignoring the vcn param the panel emits, so a hysteria2 outbound with a decoy SNI and a distinct cert name failed TLS verification after import. The TS parser handles both; make the Go parser match. * fix(ui): stop the sniffing form island from clobbering unrendered fields antd's Form.useWatch only reports registered fields, so while the sniffing toggle was off the island emitted { enabled: false } upward and replaced the full Sniffing object in form state. Saving a VLESS reverse outbound then crashed in sniffingToWire on the missing ipsExcluded array; the loopback outbound and the inbound sniffing tab shared the same hole. Watch the store with preserve: true so unrendered fields keep their values, and seed a missing value from the schema defaults instead of an empty cast. * fix(sub): drop empty remark segments instead of leaving a stray separator expandSegment dropped a "|" segment only when its tokens rendered the unlimited mark, so a segment whose only token resolved to the empty string (a client with no comment, an unlimited client's expiry date) was kept as bare decoration, leaving a trailing "|" or a dangling emoji on every share link's remark. Drop a token-bearing segment whenever none of its tokens produce a real value, while still keeping pure-literal segments. * fix(xray): keep source- and domains-scoped routing rules when an inbound is deleted removeInboundTagFromRules drops a routing rule whose inboundTag list becomes empty only if the rule has no other matcher, but routingMatcherKeys omitted xray-core's canonical source and domains keys. A rule scoped by source or domains (common in hand-authored or imported configs) therefore lost its whole body — including a security-relevant block — when its single listed inbound was deleted, instead of just having the tag trimmed. Recognize source and domains as live matchers. * fix(xray): guard RemoveUser against an uninitialized handler client Every XrayAPI handler method returns an error when HandlerServiceClient is nil, except RemoveUser, which dereferenced it directly. A depletion sweep runs Init with the port ignored and, during a restart window where the fresh process's api port is still 0, Init fails and leaves the client nil — so RemoveUser panicked (recovered by the traffic writer, but re-thrown every poll) instead of returning an error. Add the same nil guard the siblings have. * fix(xray): do not revive a manually stopped Xray on a background restart RestartXray cleared isManuallyStopped unconditionally at its top, so the @30s pending-config cron (and warp/ldap/outbound reconcile jobs) that call RestartXray(false) resurrected an Xray the admin had deliberately stopped — unlike the crash-detector, which honors the manual-stop flag. Skip a non-forced restart while the stop flag is set; only an explicit forced restart clears it. * fix(xray): retry a failed pending-restart instead of dropping the config change The 30s cron consumed the need-restart flag with IsNeedRestartAndSetFalse before calling RestartXray and only logged a failure. If RestartXray failed early (a transient GetXrayConfig DB error) the old process kept running the old config, the crash detector saw a running process and never retried, and the flag stayed cleared — so an admin's saved change silently never reached the core. Move the consume/restart/retry into ApplyPendingRestart, which re-arms the flag on failure so the next tick retries. * fix(xray): synchronize the process version and apiPort fields Start writes p.version and p.apiPort (via refreshVersion/refreshAPIPort) after flipping the process to running, while GetXrayVersion and GetAPIPort read them lock-free from the status and traffic poll goroutines. The struct mutex deliberately excluded these fields, so a restart racing a poll was a real data race — a torn read of the version string header can crash. Extend the mutex to cover version and apiPort, doing the blocking version probe before taking the lock. * fix(settings): detect a wildcard listen collision between the web and sub ports The web/sub same-port check compared the two listen addresses as raw strings, so binding both on all interfaces with different spellings (webListen 0.0.0.0 vs an empty subListen) slipped past validation and only failed at startup with an opaque bind error. Treat any wildcard listen ('', 0.0.0.0, ::) as overlapping so the clash is reported up front, while still allowing two distinct specific addresses to share a port. * fix(db): mark the IP-limit cleanup seeder done on a fresh install ResetIpLimitNoFail2ban is a one-time migration that, on a host without fail2ban, zeroes every existing client's limitIp because the limit can't be enforced. It was missing from the fresh-install fast-path seeder list, so on a brand-new DB it did not run on the first boot but fired on the second — wiping any IP limits the admin had set in between. Add it to the fast-path so a truly fresh install marks it done up front (there is nothing to clean), leaving later admin-set limits intact. * fix(security): dial outbound subscriptions through the SSRF guard The outbound-subscription fetch validated the URL host once (resolving DNS and rejecting private targets) but then fetched with a plain HTTP client that re-resolves the host at dial time, so a subscription domain the attacker controls could pass validation as a public IP and rebind to 127.0.0.1 / a cloud metadata endpoint / an internal host for the actual dial — a blind SSRF into the panel's network. Route the direct fetch (and its redirects) through netsafe.SSRFGuardedDialContext, which resolves, checks and dials the same IP atomically, carrying the subscription's AllowPrivate flag on the request context; a configured egress proxy still dials its loopback bridge unguarded. * fix(security): bound the login-limiter attempts map The login rate limiter keys its records on the caller-supplied username and only evicted a record when that exact key was revisited or the login succeeded. An unauthenticated attacker replaying one CSRF token while rotating a fresh username per request seeded a record that was never revisited, growing the map without bound until the panel OOMs. Cap the map: before inserting a new record, reclaim records whose block has lapsed and whose failures aged out, and if the map is still at the ceiling under a broad flood, drop one so memory can never grow past the cap. * fix(tgbot): require admin for privileged callbacks, not just the first switch answerCallback wraps only its first callback switch in an isAdmin guard; the second switch (server usage, inbound/online enumeration, database backup export, ban logs, mass traffic reset, client creation) ran for every caller. Telegram delivers a callback with the tapping user's id, so a non-admin who can see an admin's inline keyboard — as when the bot runs in a group — could tap Backup and receive the full database and config, or reset all traffic. Default-deny before the second switch: a non-admin may only run the per-user client_* callbacks that resolve their own data from their Telegram id. * fix(eventbus): dispatch each subscriber in its own goroutine The fan-out loop called every subscriber's handler sequentially on the single dispatch goroutine. The email and Telegram notifiers block on network I/O for tens of seconds (or minutes when the remote is slow), so one slow subscriber stalled the whole loop: the 256-slot channel then filled and Publish silently dropped later events — including high-value xray.crash and node.down notifications unrelated to the slow handler. Hand each delivered event to every handler in its own goroutine so a blocking subscriber can no longer stall delivery to the others. safeCall already recovers panics, so a detached handler cannot take down the bus. * fix(integration): cap WARP API response body size doWarpRequest read the response with an unbounded io.ReadAll, unlike the sibling NordVPN client which already caps every read at maxResponseSize. A hostile panel egress proxy or a MITM on the Cloudflare WARP endpoint could stream an arbitrarily large body and force the panel into an unbounded allocation. Wrap the body in an io.LimitReader(maxResponseSize) to match the NordVPN client. * fix(email): bound every SMTP step with a connection deadline The "starttls"/"none" transport delivered through net/smtp.SendMail, which dials with an untimed net.Dial and never sets a socket deadline. When an SMTP server accepted the TCP connection but then stalled (or was a blackhole), the caller was released by Send's 30s select, but the sender goroutine and its socket stayed blocked until the OS TCP timeout — minutes per notification, leaking a goroutine and a connection each time. sendWithTLS dialed with a timeout but likewise armed no deadline on the protocol phase, and TestConnection (called synchronously from the settings handler, with no select guard) could hang the request indefinitely. Replace SendMail with sendPlain, which dials with smtpConnectTimeout and arms conn.SetDeadline(smtpDeadline) before the greeting read, preserving SendMail's opportunistic STARTTLS upgrade. Arm the same deadline in sendWithTLS and TestConnection so every SMTP step is bounded. * fix(server): guard access-log parser against malformed lines GetXrayLogs split each Xray access-log line on whitespace and then read fixed offsets — parts[1] for the timestamp and parts[i+1] after the "from", "accepted" and "email:" markers — without checking the line had that many fields. A truncated or malformed line (the logged destination is attacker-influenced) indexed past the slice and panicked; the panel handler returned a 500 via Gin's recovery. Extract the per-line field parsing into parseAccessLogFields and length guard every positional lookup so a short line yields a partial entry instead of panicking. * fix(server): guard xray key-generator output parsing GetNewX25519Cert, GetNewmldsa65 and GetNewmlkem768 parsed xray's stdout by reading lines[0], lines[1] and each line's second colon-separated field without any length check — unlike GetNewEchCert, which already guards its line count. If the xray binary printed fewer than two lines or reformatted its labels (a version change, or a silent failure that emitted nothing), the fixed slice index panicked and the handler 500'd. Extract the shared parsing into parseXrayKeyPairOutput, which length guards the line count and each label split and returns an error instead of panicking, then route all three generators through it. * fix(tgbot): stop auto-deleted messages from resetting wizard state SendMsgToTgbotDeleteAfter spawns a goroutine that, after the display delay, deleted the transient message and then unconditionally cleared the chat's conversation state. Every caller that ends a wizard step already clears the state synchronously, so that call was redundant — and harmful: if within the delay the user advanced to the next step (a callback sets a fresh awaiting_* state), the late goroutine wiped it, and the user's next message fell through unrecognized, silently dropping their input. Move the delayed deletion into deleteMessageAfterDelay, which only removes the message and no longer touches the conversation state. Guard deleteMessageTgBot against a nil bot so the deletion path is unit-testable. * fix(frontend): refetch a fresh CSRF token on 403 instead of reusing the stale meta tag On a 403 to an unsafe method the client cleared its cached CSRF token and called ensureCsrfToken to retry. But ensureCsrfToken prefers the <meta name="csrf-token"> tag baked into the page, which the production panel always injects, so the "refresh" re-read the same stale token and the /csrf-token refetch was never reached — the retry re-sent the token that had just been rejected and the save failed with an error toast. The token lives in the session and rotates when the session is regenerated (for example re-login in another tab), leaving the tab's baked-in meta token stale. Fetch the current token straight from /csrf-token in the 403 branch so the retry uses the authoritative server value. The existing tests only passed because they strip the meta tag; the new test keeps a stale tag present. * fix(frontend): surface backend error text from failed requests HttpUtil.get/post read the thrown HttpError body as response.data.message, but the backend error envelope (entity.Msg) serializes its text as msg. On any non-2xx JSON response the real reason was therefore dropped and the operator saw only the generic "Request failed with status N" toast. Read response.data.msg first (keeping message and the native error text as fallbacks). The sibling test had pinned the wrong body shape ({ message }); correct it to the real backend shape ({ success:false, msg }) so it exercises the actual envelope. * fix(frontend): share one WebSocket connection across bridge and hooks websocketBridge.ts and useWebSocket.ts each declared their own module-scoped sharedClient plus an identical getSharedClient, so the "shared" client was not shared between them: whenever a page using useWebSocket (Clients/Inbounds) mounted alongside the always-mounted bridge, the panel opened two sockets to /ws. The server then pushed every traffic/stats/nodes/inbounds snapshot to both, doubling WebSocket bandwidth and running two independent reconnect loops, and the hook's socket was never disconnected on unmount. Hoist a single getSharedWebSocketClient into api/websocket.ts and route both the bridge and the hook through it, so exactly one connection is opened. * fix(frontend): guard the outbounds WebSocket handler against non-array payloads onOutbounds wrote the raw WebSocket payload straight into the outboundsTraffic cache, unlike the sibling onNodes/onInbounds handlers which first check Array.isArray. A malformed non-array push (for example an object) would land in the cache with staleTime Infinity; consumers that call .find()/.map() on the outbounds list would then throw and crash the Outbounds tab. Add the same Array.isArray guard so a bad push is ignored. * fix(frontend): key the node table by the computed row key, not id The desktop node table used rowKey="id", but transitive sub-nodes (the read-only rows surfaced from downstream nodes) all carry id 0, so a topology with two or more transitive rows gave React duplicate keys. antd's rowKey prop overrides the row object's own computed `key` (`t-${guid}` for transitive rows, the numeric id otherwise), so the unique key the code already builds was ignored — causing row-state/DOM mis-association on any re-render (heartbeat refetch, address-eye toggle). The mobile card path already keyed by record.key. Key the table by "key" so transitive rows get their distinct t-${guid} identity; direct nodes keep key === id, so row selection (filtered to numeric keys) is unchanged. * fix(frontend): map routing row actions through the rule's real index The routing table hides balancer-loopback rules (`_bl_*`) but keeps each visible row's original index in `key`, then handed antd's positional row index straight to edit/delete/toggle/move/drag — all of which mutate the full, unfiltered routing.rules array. Once a hidden loopback rule precedes a visible one (e.g. a balancer whose fallback is another balancer, plus any rule added afterwards), the positional index no longer matches the array index, so deleting or editing a rule silently hit the wrong one — including destroying the loopback rule that keeps the balancer alive. Add originalRuleIndex to translate a positional row index back through the row's `key`, and route every mutating handler (openEdit, confirmDelete, toggleRule, moveUp/moveDown, drag) through it. When no loopback rows are hidden the mapping is the identity, so ordinary configs are unaffected. * fix(frontend): map outbound row actions through the outbound's real index The outbounds table hides balancer-loopback outbounds (`_bl_*`) but keeps each visible row's original index in `key`, then passed antd's positional row index to edit/delete/move and to the per-row probe (onTest) and its result lookup — all of which address the full, unfiltered outbounds array. Once a hidden loopback outbound precedes a visible one, the positional index diverges from the array index, so deleting or editing an outbound hit the wrong one (its deletion-impact plan and removal targeting the wrong entry), and the test button probed / showed results against the wrong outbound. Add originalOutboundIndex and route the mutating handlers through it; key the probe trigger and test-result columns by record.key. With no loopback rows hidden the mapping is the identity, so ordinary configs are unaffected. * fix(frontend): tolerate a malformed happyEyeballs value in the Xray Basics tab BasicsTab derived directHappyEyeballs by calling HappyEyeballsSchema.parse during render, guarding only against null/non-object. A wrong-typed field (e.g. happyEyeballs.tryDelayMs as a string) or any other shape mismatch — reachable via the Complete Template JSON editor or an imported config — threw straight out of render, white-screening the default Xray landing tab. Use safeParse and fall back to null so a bad value degrades to "no override" instead of crashing the page. * fix(frontend): preserve routing-rule fields the form does not surface The rule form rebuilt the rule from a fixed literal of only the fields it edits, and RoutingTab replaces the rule wholesale on confirm. Fields the form never exposes — localPort, localIP, process, ruleTag, webhook — are in the rule schema and can arrive via the advanced JSON editor or Import Rules; opening such a rule in the form and saving silently dropped them. Carry over every key of the original rule the form does not manage before applying the form-derived fields, so an edit only touches what it surfaces. * fix(frontend): re-sync the sniffing island when its value changes externally The sniffing config editor froze its seed value at mount and only watched its own inner AntD form, never reflecting a later change to the shared RHF `sniffing` path. Because the inbound form mounts every tab with forceRender, the friendly Sniffing tab and the Advanced JSON editor are live at once: editing sniffing in the JSON editor updated the RHF value but not the frozen island, so the next interaction with the friendly tab emitted the stale value and silently discarded the JSON edit. Add an effect that pushes an external value change into the inner form, guarded by the same lastEmitted marker the emit path uses so the island never re-seeds from its own echo and no update loop forms. * fix(frontend): don't drift a client's byte quota on a no-op save The quota field shows the total in GB rounded to two decimals; editing a client and saving converted that display value straight back to bytes. A byte total not aligned to 0.01 GB — one set via the API or an import — was therefore rewritten to the rounded value on any save that never touched the field, losing a few MB each time. Add resolveTotalBytes: keep the original byte total when the displayed GB still matches it, and only re-derive from GB when the user actually changed the field. * fix(eventbus): deliver events on a bounded per-subscriber worker The previous fix dispatched each event to every subscriber with a bare `go safeCall`. That unblocked the dispatch loop, but removed the bus's backpressure: under a login-attempt flood (which both notifier subscribers process without rate-limiting) with email/Telegram enabled, every attempt spawned handler goroutines that each block on network I/O for up to ~30s, with no bound — a goroutine and outbound-connection storm. It also let a subscriber's handler run concurrently with itself, racing the Telegram notifier's lazily-cached hostname. Give each subscriber its own bounded queue drained by a single worker goroutine. Dispatch does a non-blocking send per subscriber (dropping only that subscriber's event when its queue is full), so a slow subscriber still can't stall the others, concurrency is bounded to one in-flight handler per subscriber, per-subscriber event order is preserved, and Stop again waits for in-flight handlers to finish. * fix(frontend): map outbound mobile-card actions through the real index too The desktop outbounds table was keyed by the outbound's real index, but the mobile card list was left keying the probe trigger and every test-state lookup by the positional row index. With a hidden balancer-loopback outbound present, tapping Check on a mobile card probed the wrong outbound and the Test-All results landed on the wrong card. Key onTest and the testResult/isTesting reads by record.key, matching the desktop columns. * fix(frontend): meet WCAG AA contrast on the config-block link text The Storybook accessibility test flagged the share-link <code> block: with no explicit color it inherited a muted grey that renders as #888888 on the #f8f8f8 tertiary-fill background in CI's Chromium — a 3.33:1 contrast, below the 4.5:1 AA threshold. Set the text to the theme's primary text token so the colour is explicit and high-contrast in both light and dark themes instead of depending on an inherited value that varies by browser. * style(sub): simplify a negated conjunction to satisfy staticcheck QF1001 golangci-lint (staticcheck QF1001) flagged the `!(a && b)` guard in expandSegment. Rewrite it via De Morgan's law to the equivalent `!a || !b` form so the linter passes; behavior is unchanged. * fix: close panics and races the audit's own fixes left nearby Second-pass review of the 54-commit self-correcting audit. Each item below was confirmed by reading the surrounding source (and, where practical, the pre-fix code) before being changed; regression tests are included for every behavioral fix. Concurrency: - eventbus: Bus.Subscribe called wg.Add with no synchronization against a concurrent Bus.Stop's wg.Wait, a real "WaitGroup misuse" panic risk (e.g. a Telegram-bot settings save racing panel shutdown/restart). Stop now flips a mu-guarded `stopped` flag before waiting, and Subscribe checks it under the same lock, so Add and Wait can no longer race. Security: - login_limiter: evictForRoom's fallback eviction picked an arbitrary map key, including ones still under an active cooldown - an attacker flooding /login with fresh usernames could evict their own (or anyone's) blocked record and reset the lockout. The fallback now skips actively-blocked records, only falling back to an unconditional evict if the map is somehow entirely full of active blocks (preserves the hard memory cap). Subscription-endpoint panics (reachable by any client hitting /sub): - internal/sub/service.go: applyPathAndHostParams/Obj (ws/httpupgrade/xhttp with no path settings object) and the TLS alpn readers in three places used unchecked type assertions - exactly the bug classabab7cd0patched elsewhere in the same switch statements, just not these call sites. - internal/sub/json_service.go, clash_service.go: the externalProxy loops in the JSON and Clash generators used unchecked assertions on a legacy/admin-supplied field (missing "port", non-object entry, etc.). - internal/sub/json_service.go: realityData's shortId/serverName selection could assert a non-string array element. Other correctness: - client_traffic.go: ResetAllTraffics (touched by3eb214d0) still skipped clearing NodeClientTraffic node-sync baselines, unlike its sibling reset paths in the same file - a node's next sync would re-add pre-reset delta on top of the freshly-zeroed counter. - inbound_traffic.go: the traffic-tick tx's Commit/Rollback errors were silently discarded; now logged so a backend-level commit failure (e.g. an aborted Postgres tx from a best-effort helper) doesn't masquerade as a successful tick. - outbound_subscription.go: the new subscriptionFetchClient doc comment was wedged between fetchAndStore's existing comment and fetchAndStore itself, leaving fetchAndStore undocumented and the comment describing the wrong function. Convention cleanup: - Removed narrative // comments added by the audit that violate this repo's no-inline-comment rule (mostly narrating the specific bug/fix rather than a lasting contract, and mostly on new Test functions, which this repo's existing tests never comment) - calibrated against this exact codebase's own pre-existing comment style so legitimate godoc-style doc comments were left alone. --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2241 lines
120 KiB
JSON
2241 lines
120 KiB
JSON
{
|
||
"username": "Username",
|
||
"password": "Password",
|
||
"login": "Log In",
|
||
"confirm": "Confirm",
|
||
"cancel": "Cancel",
|
||
"close": "Close",
|
||
"save": "Save",
|
||
"logout": "Log Out",
|
||
"create": "Create",
|
||
"add": "Add",
|
||
"remove": "Remove",
|
||
"update": "Update",
|
||
"copy": "Copy",
|
||
"copied": "Copied",
|
||
"more": "more",
|
||
"download": "Download",
|
||
"regenerate": "Regenerate",
|
||
"jsonEditor": "JSON editor",
|
||
"downloadImage": "Download Image",
|
||
"sort": "Sort",
|
||
"remark": "Remark",
|
||
"enable": "Enabled",
|
||
"protocol": "Protocol",
|
||
"search": "Search",
|
||
"filter": "Filter",
|
||
"all": "All",
|
||
"from": "From",
|
||
"to": "To",
|
||
"done": "Done",
|
||
"loading": "Loading...",
|
||
"refresh": "Refresh",
|
||
"clear": "Clear",
|
||
"second": "Second",
|
||
"minute": "Minute",
|
||
"hour": "Hour",
|
||
"day": "Day",
|
||
"check": "Check",
|
||
"indefinite": "Indefinite",
|
||
"unlimited": "Unlimited",
|
||
"none": "None",
|
||
"qrCode": "QR Code",
|
||
"info": "More Information",
|
||
"edit": "Edit",
|
||
"delete": "Delete",
|
||
"reset": "Reset",
|
||
"noData": "Nothing here yet",
|
||
"copySuccess": "Copied successfully",
|
||
"sure": "Sure",
|
||
"encryption": "Encryption",
|
||
"useIPv4ForHost": "Use IPv4 for host",
|
||
"transmission": "Transmission",
|
||
"host": "Host",
|
||
"path": "Path",
|
||
"camouflage": "Obfuscation",
|
||
"status": "Status",
|
||
"enabled": "Enabled",
|
||
"disabled": "Disabled",
|
||
"depleted": "Ended",
|
||
"depletingSoon": "Depleting",
|
||
"offline": "Offline",
|
||
"online": "Online",
|
||
"domainName": "Domain Name",
|
||
"monitor": "Listen IP",
|
||
"certificate": "Digital Certificate",
|
||
"fail": "Failed",
|
||
"comment": "Comment",
|
||
"success": "Success",
|
||
"lastOnline": "Last Online",
|
||
"getVersion": "Get Version",
|
||
"install": "Install",
|
||
"clients": "Clients",
|
||
"usage": "Usage",
|
||
"twoFactorCode": "Code",
|
||
"remained": "Remaining",
|
||
"security": "Security",
|
||
"secAlertTitle": "Security Alert",
|
||
"secAlertSsl": "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection.",
|
||
"secAlertConf": "Certain settings are vulnerable to attacks. It is recommended to reinforce security protocols to prevent potential breaches.",
|
||
"secAlertSSL": "Panel lacks secure connection. Please install TLS certificate for data protection.",
|
||
"secAlertPanelPort": "Panel default port is vulnerable. Please configure a random or specific port.",
|
||
"secAlertPanelURI": "Panel default URI path is insecure. Please configure a complex URI path.",
|
||
"secAlertSubURI": "Subscription default URI path is insecure. Please configure a complex URI path.",
|
||
"secAlertSubJsonURI": "Subscription JSON default URI path is insecure. Please configure a complex URI path.",
|
||
"emptyDnsDesc": "No added DNS servers.",
|
||
"emptyFakeDnsDesc": "No added Fake DNS servers.",
|
||
"emptyBalancersDesc": "No added balancers.",
|
||
"emptyReverseDesc": "No added reverse proxies.",
|
||
"somethingWentWrong": "Something went wrong",
|
||
"subscription": {
|
||
"title": "Subscription info",
|
||
"subId": "Subscription ID",
|
||
"email": "Email",
|
||
"status": "Status",
|
||
"downloaded": "Downloaded",
|
||
"uploaded": "Uploaded",
|
||
"expiry": "Expiry",
|
||
"totalQuota": "Total quota",
|
||
"individualLinks": "Individual links",
|
||
"active": "Active",
|
||
"inactive": "Inactive",
|
||
"unlimited": "Unlimited",
|
||
"noExpiry": "No expiry",
|
||
"copyAllConfigs": "Copy All Configs",
|
||
"copyAllConfigsCopied": "All configs copied"
|
||
},
|
||
"menu": {
|
||
"theme": "Theme",
|
||
"dark": "Dark",
|
||
"ultraDark": "Ultra Dark",
|
||
"dashboard": "Overview",
|
||
"inbounds": "Inbounds",
|
||
"clients": "Clients",
|
||
"groups": "Groups",
|
||
"nodes": "Nodes",
|
||
"hosts": "Hosts",
|
||
"settings": "Panel Settings",
|
||
"xray": "Xray Configs",
|
||
"routing": "Routing",
|
||
"outbounds": "Outbounds",
|
||
"apiDocs": "API Docs",
|
||
"logout": "Log Out",
|
||
"link": "Manage",
|
||
"donate": "Donate",
|
||
"docs": "Documentation",
|
||
"openMenu": "Open menu"
|
||
},
|
||
"pages": {
|
||
"login": {
|
||
"hello": "Hello",
|
||
"title": "Welcome",
|
||
"loginAgain": "Your session has expired, please log in again",
|
||
"toasts": {
|
||
"invalidFormData": "The input data format is invalid.",
|
||
"emptyUsername": "Username is required",
|
||
"emptyPassword": "Password is required",
|
||
"wrongUsernameOrPassword": "Invalid username or password or two-factor code.",
|
||
"successLogin": "You have successfully logged into your account."
|
||
}
|
||
},
|
||
"index": {
|
||
"title": "Overview",
|
||
"cpu": "CPU",
|
||
"logicalProcessors": "Logical Processors",
|
||
"frequency": "Frequency",
|
||
"swap": "Swap",
|
||
"storage": "Storage",
|
||
"memory": "RAM",
|
||
"threads": "Threads",
|
||
"xrayStatus": "Xray",
|
||
"stopXray": "Stop",
|
||
"restartXray": "Restart",
|
||
"xraySwitch": "Version",
|
||
"xrayUpdates": "Xray Updates",
|
||
"xraySwitchClick": "Choose the version you want to switch to.",
|
||
"xraySwitchClickDesk": "Choose carefully, as older versions may not be compatible with current configurations.",
|
||
"updatePanel": "Update Panel",
|
||
"panelUpdateDesc": "This will update 3X-UI itself to the latest release and restart the panel service.",
|
||
"currentPanelVersion": "Current panel version",
|
||
"latestPanelVersion": "Latest panel version",
|
||
"panelUpToDate": "Panel is up to date",
|
||
"devChannel": "Dev channel",
|
||
"devChannelWarning": "Dev builds track every commit on main and aren't stable releases — there is no automatic downgrade.",
|
||
"currentCommit": "Current commit",
|
||
"latestCommit": "Latest commit",
|
||
"updateChannelChanged": "Update channel changed",
|
||
"upToDate": "Up to date",
|
||
"xrayStatusUnknown": "Unknown",
|
||
"xrayStatusRunning": "Running",
|
||
"xrayStatusStop": "Stopped",
|
||
"xrayStatusError": "Error",
|
||
"xrayErrorPopoverTitle": "An error occurred while running Xray",
|
||
"operationHours": "Uptime",
|
||
"systemHistoryTitle": "System History",
|
||
"historyTitleCpu": "CPU Usage",
|
||
"historyTitleMem": "Memory Usage",
|
||
"historyTitleNetwork": "Network Bandwidth",
|
||
"historyTitlePackets": "Network Packets",
|
||
"historyTitleDisk": "Disk I/O",
|
||
"historyTitleOnline": "Online Clients",
|
||
"historyTitleLoad": "System Load Average (1m / 5m / 15m)",
|
||
"historyTitleConnections": "Active Connections (TCP / UDP)",
|
||
"historyTitleDiskUsage": "Disk Space Usage",
|
||
"historyTabBandwidth": "Bandwidth",
|
||
"historyTabPackets": "Packets",
|
||
"historyTabDisk": "Disk I/O",
|
||
"historyTabOnline": "Online",
|
||
"historyTabLoad": "Load",
|
||
"historyTabConnections": "Connections",
|
||
"historyTabDiskUsage": "Disk Usage",
|
||
"charts": "Charts",
|
||
"xrayMetricsTitle": "Xray Metrics",
|
||
"xrayTitleHeap": "Allocated Heap Memory",
|
||
"xrayTitleSys": "Memory Reserved from OS",
|
||
"xrayTitleObjects": "Live Heap Objects",
|
||
"xrayTitleGcCount": "Completed GC Cycles",
|
||
"xrayTitleGcPause": "GC Pause Duration",
|
||
"xrayTitleObservatory": "Outbound Connection Health",
|
||
"xrayTabHeap": "Heap",
|
||
"xrayTabSys": "Sys",
|
||
"xrayTabObjects": "Objects",
|
||
"xrayTabGcCount": "GC Count",
|
||
"xrayTabGcPause": "GC Pause",
|
||
"xrayTabObservatory": "Observatory",
|
||
"xrayMetricsDisabled": "Xray metrics endpoint not configured",
|
||
"xrayMetricsHint": "Add a top-level metrics block to the xray config with tag metrics_out and listen 127.0.0.1:11111, then restart xray.",
|
||
"xrayObservatoryEmpty": "No observatory data yet",
|
||
"xrayObservatoryHint": "Add an observatory block to the xray config listing the outbound tags to probe, then restart xray.",
|
||
"xrayObservatoryTagPlaceholder": "Select outbound",
|
||
"xrayObservatoryAlive": "Alive",
|
||
"xrayObservatoryDead": "Down",
|
||
"xrayObservatoryLastSeen": "Last seen",
|
||
"xrayObservatoryLastTry": "Last try",
|
||
"trendLast2Min": "Last 2 minutes",
|
||
"systemLoad": "System Load",
|
||
"systemLoadDesc": "System load average for the past 1, 5, and 15 minutes",
|
||
"connectionCount": "Connection Stats",
|
||
"ipAddresses": "IP Addresses",
|
||
"toggleIpVisibility": "Toggle visibility of the IP",
|
||
"overallSpeed": "Overall Speed",
|
||
"upload": "Upload",
|
||
"download": "Download",
|
||
"totalData": "Total Data",
|
||
"sent": "Sent",
|
||
"received": "Received",
|
||
"documentation": "Documentation",
|
||
"xraySwitchVersionDialog": "Do you really want to change the Xray version?",
|
||
"xraySwitchVersionDialogDesc": "This will change the Xray version to #version#.",
|
||
"xraySwitchVersionPopover": "Xray updated successfully",
|
||
"panelUpdateDialog": "Do you really want to update the panel?",
|
||
"panelUpdateDialogDesc": "This will update 3X-UI to #version# and restart the panel service.",
|
||
"panelUpdateCheckPopover": "Panel update check failed",
|
||
"panelUpdateStartedPopover": "Panel update started",
|
||
"panelUpdateFailedTitle": "Panel update failed",
|
||
"panelUpdateFailedDesc": "The update did not finish successfully. Check the server logs, or run 'x-ui update' from the command line.",
|
||
"panelUpdateUnknownTitle": "Couldn't confirm the update finished",
|
||
"panelUpdateUnknownDesc": "The panel didn't report a result in time. Reload to check the current version, or check the server logs.",
|
||
"geofileUpdateDialog": "Do you really want to update the geofile?",
|
||
"geofileUpdateDialogDesc": "This will update the #filename# file.",
|
||
"geofilesUpdateDialogDesc": "This will update all geofiles.",
|
||
"geofilesUpdateAll": "Update all",
|
||
"geofileUpdatePopover": "Geofile updated successfully",
|
||
"geodataTitle": "Geodata Auto-Update",
|
||
"geodataHint": "Xray downloads these files on schedule and hot-reloads them without a restart. URLs must be HTTPS. Each file must already exist in the bin folder once before Xray can update it.",
|
||
"geodataCron": "Schedule (cron)",
|
||
"geodataOutbound": "Download through outbound (optional)",
|
||
"geodataFile": "File name",
|
||
"geodataAddFile": "Add file",
|
||
"geodataSaveRestart": "Save & Restart Xray",
|
||
"geodataConfirmTitle": "Save geodata settings?",
|
||
"geodataConfirmContent": "This updates the Xray config template and restarts Xray.",
|
||
"geodataInvalidUrl": "Each file needs an HTTPS URL.",
|
||
"geodataInvalidFile": "File names must be plain names like geosite_custom.dat (no paths).",
|
||
"geodataInvalidCron": "Cron must have 5 fields, e.g. 0 4 * * *",
|
||
"geodataEmpty": "No files configured. Reference files in routing rules as ext:geosite_custom.dat:category.",
|
||
"dontRefresh": "Installation is in progress, please do not refresh this page",
|
||
"logs": "Logs",
|
||
"accessLogs": "Access Logs",
|
||
"autoUpdate": "Auto Update",
|
||
"config": "Config",
|
||
"backup": "Backup",
|
||
"backupTitle": "Backup & Restore",
|
||
"exportDatabase": "Back Up",
|
||
"exportDatabaseDesc": "Click to download a .db file containing a backup of your current database to your device. The same file can also be restored into a panel running on PostgreSQL.",
|
||
"importDatabase": "Restore",
|
||
"importDatabaseDesc": "Click to select and upload a .db backup or a migration dump (.dump) from your device to restore your database.",
|
||
"importDatabaseSuccess": "The database has been successfully imported.",
|
||
"importDatabaseError": "An error occurred while importing the database.",
|
||
"readDatabaseError": "An error occurred while reading the database.",
|
||
"getDatabaseError": "An error occurred while retrieving the database.",
|
||
"getConfigError": "An error occurred while retrieving the config file.",
|
||
"backupPostgresNote": "This panel runs on PostgreSQL. Back Up downloads a pg_dump archive (.dump) and Restore loads it back with pg_restore. Restore also accepts a SQLite database (.db) or a SQLite migration dump and imports its data into PostgreSQL. The server needs the PostgreSQL client tools (pg_dump and pg_restore) installed.",
|
||
"exportDatabasePgDesc": "Click to download a PostgreSQL dump (.dump) of your current database to your device.",
|
||
"importDatabasePgDesc": "Click to select and upload a PostgreSQL backup (.dump), a SQLite database (.db), or a SQLite migration dump to restore your database. This replaces all current data.",
|
||
"migrationDownload": "Download Migration",
|
||
"migrationDownloadPgDesc": "Click to download a .db SQLite database built from your PostgreSQL data, ready to run this panel on SQLite."
|
||
},
|
||
"inbounds": {
|
||
"title": "Inbounds",
|
||
"totalDownUp": "Total Sent/Received",
|
||
"totalUsage": "Total Usage",
|
||
"inboundCount": "Total Inbounds",
|
||
"operate": "Menu",
|
||
"enable": "Enabled",
|
||
"remark": "Remark",
|
||
"node": "Node",
|
||
"deployTo": "Deploy to",
|
||
"localPanel": "Local panel",
|
||
"fallbacks": {
|
||
"title": "Fallbacks",
|
||
"help": "When a connection on this inbound does not match any client, route it elsewhere. Pick a child inbound below to auto-fill the routing fields (SNI / ALPN / path / xver) from its transport, or leave the picker empty and set Dest directly (e.g. 8080 or 127.0.0.1:8080) to route to an external server such as Nginx. Each child inbound should listen on 127.0.0.1 with security=none.",
|
||
"empty": "No fallbacks yet",
|
||
"add": "Add fallback",
|
||
"pickInbound": "Pick an inbound",
|
||
"matchAny": "any",
|
||
"destPlaceholder": "auto (child listen:port)",
|
||
"rederive": "Re-fill from child",
|
||
"rederived": "Re-filled from child",
|
||
"editAdvanced": "Edit routing fields",
|
||
"hideAdvanced": "Hide advanced",
|
||
"quickAddAll": "Quick add all eligible",
|
||
"quickAdded": "Added {n} fallback(s)",
|
||
"quickAddedNone": "No new eligible inbounds to add",
|
||
"routesWhen": "Routes when",
|
||
"defaultCatchAll": "Default — catches anything else",
|
||
"needsTls": "Fallbacks become available once Security is set to TLS or Reality on the Security tab (VLESS/Trojan over RAW only)."
|
||
},
|
||
"protocol": "Protocol",
|
||
"port": "Port",
|
||
"portMap": "Port Mapping",
|
||
"traffic": "Traffic",
|
||
"speed": "Speed",
|
||
"details": "Details",
|
||
"transportConfig": "Transport",
|
||
"expireDate": "Duration",
|
||
"createdAt": "Created",
|
||
"updatedAt": "Updated",
|
||
"resetTraffic": "Reset Traffic",
|
||
"addInbound": "Add Inbound",
|
||
"generalActions": "General Actions",
|
||
"modifyInbound": "Modify Inbound",
|
||
"deleteInbound": "Delete Inbound",
|
||
"deleteInboundContent": "Are you sure you want to delete this inbound?",
|
||
"deleteConfirmTitle": "Delete inbound \"{remark}\"?",
|
||
"deleteConfirmContent": "This removes the inbound and all its clients. This cannot be undone.",
|
||
"resetConfirmTitle": "Reset traffic for \"{remark}\"?",
|
||
"resetConfirmContent": "Resets up/down counters to 0 for this inbound.",
|
||
"selectedCount": "{count} selected",
|
||
"selectAll": "Select all",
|
||
"bulkDeleteConfirmTitle": "Delete {count} inbounds?",
|
||
"bulkDeleteConfirmContent": "This removes the selected inbounds and all their clients. This cannot be undone.",
|
||
"cloneConfirmTitle": "Clone inbound \"{remark}\"?",
|
||
"cloneConfirmContent": "Creates a copy with a new port and an empty client list.",
|
||
"delAllClients": "Delete All Clients",
|
||
"delAllClientsConfirmTitle": "Delete all {count} clients from \"{remark}\"?",
|
||
"delAllClientsConfirmContent": "This removes every client from this inbound and drops their traffic records. The inbound itself is kept. This cannot be undone.",
|
||
"attachClients": "Attach Clients To…",
|
||
"addClientsToGroup": "Add Clients To Group…",
|
||
"attachClientsTitle": "Attach clients from \"{remark}\"",
|
||
"attachClientsDesc": "Attaches the same {count} clients (same UUID/password and shared traffic) to the selected inbound(s). They stay on this inbound too.",
|
||
"attachClientsTargets": "Target inbounds",
|
||
"attachClientsNoTargets": "No other compatible inbounds available to attach to.",
|
||
"attachClientsResult": "Attached {attached}, skipped {skipped}.",
|
||
"attachClientsResultMixed": "Attached {attached}, skipped {skipped}, errors {errors}.",
|
||
"attachClientsSelectLabel": "Clients to attach",
|
||
"attachClientsSearchPlaceholder": "Search email or comment",
|
||
"attachClientsStatusDisabled": "Disabled",
|
||
"attachClientsSelectedCount": "{selected} of {total} selected",
|
||
"attachExistingClients": "Attach Existing Clients…",
|
||
"attachExistingTitle": "Attach existing clients to \"{remark}\"",
|
||
"attachExistingDesc": "Attaches existing clients ({count} available) to this inbound — same UUID/password and shared traffic. Clients already on it are skipped.",
|
||
"attachExistingNoClients": "No clients exist yet. Create clients first, then attach them here.",
|
||
"attachExistingStatusAttached": "Already attached",
|
||
"detachClients": "Detach Clients",
|
||
"detachClientsTitle": "Detach clients of \"{remark}\"",
|
||
"detachClientsDesc": "Removes the selected client(s) from this inbound only. Client records themselves are kept (use Delete to remove fully). Source has {count} clients in total.",
|
||
"detachClientsResult": "Detached {detached}, skipped {skipped}.",
|
||
"detachClientsResultMixed": "Detached {detached}, skipped {skipped}, errors {errors}.",
|
||
"detachClientsSelectLabel": "Clients to detach",
|
||
"exportLinksTitle": "Export inbound links",
|
||
"exportSubsTitle": "Export subscription links",
|
||
"exportAllLinksTitle": "Export all inbound links",
|
||
"exportAllSubsTitle": "Export all subscription links",
|
||
"exportAllLinksFileName": "All-Inbounds",
|
||
"exportAllSubsFileName": "All-Inbounds-Subs",
|
||
"inboundJsonTitle": "Inbound JSON",
|
||
"deleteClient": "Delete Client",
|
||
"deleteClientContent": "Are you sure you want to delete this client?",
|
||
"resetTrafficContent": "Are you sure you want to reset traffic?",
|
||
"copyLink": "Copy URL",
|
||
"address": "Address",
|
||
"network": "Network",
|
||
"destinationPort": "Destination Port",
|
||
"targetAddress": "Target Address",
|
||
"monitorDesc": "Leave blank to listen on all IPs",
|
||
"meansNoLimit": "= Unlimited. (unit: GB)",
|
||
"totalFlow": "Total Flow",
|
||
"leaveBlankToNeverExpire": "Leave blank to never expire",
|
||
"noRecommendKeepDefault": "It is recommended to keep the default",
|
||
"certificatePath": "File Path",
|
||
"certificateContent": "File Content",
|
||
"publicKey": "Public Key",
|
||
"privatekey": "Private Key",
|
||
"clickOnQRcode": "Click on QR Code to Copy",
|
||
"client": "Client",
|
||
"export": "Export All URLs",
|
||
"clone": "Clone",
|
||
"cloneInbound": "Clone",
|
||
"cloneInboundContent": "All settings of this inbound, except Port, Listening IP, and Clients, will be applied to the clone.",
|
||
"cloneInboundOk": "Clone",
|
||
"resetAllTraffic": "Reset Traffic for All Inbounds",
|
||
"resetAllTrafficTitle": "Reset Traffic for All Inbounds",
|
||
"resetAllTrafficContent": "Are you sure you want to reset the traffic of all inbounds?",
|
||
"resetInboundClientTraffics": "Reset Clients' Traffic",
|
||
"resetInboundClientTrafficTitle": "Reset Clients' Traffic",
|
||
"resetInboundClientTrafficContent": "Are you sure you want to reset the traffic of this inbound's clients?",
|
||
"resetAllClientTraffics": "Reset All Clients' Traffic",
|
||
"resetAllClientTrafficTitle": "Reset All Clients' Traffic",
|
||
"resetAllClientTrafficContent": "Are you sure you want to reset the traffic of all clients?",
|
||
"delDepletedClients": "Delete Depleted Clients",
|
||
"delDepletedClientsTitle": "Delete Depleted Clients",
|
||
"delDepletedClientsContent": "Are you sure you want to delete all the depleted clients?",
|
||
"email": "Email",
|
||
"emailDesc": "Please provide a unique email address.",
|
||
"IPLimit": "IP Limit",
|
||
"IPLimitDesc": "Disables inbound if the count exceeds the set value. (0 = disable)",
|
||
"IPLimitlog": "IP Log",
|
||
"IPLimitlogDesc": "The IP history log. (to re-enable the inbound after disabling, clear the log)",
|
||
"IPLimitlogclear": "Clear the Log",
|
||
"setDefaultCert": "Set Cert from Panel",
|
||
"setDefaultCertEmpty": "No certificate is configured for the panel. Set one under Settings first.",
|
||
"streamTab": "Stream",
|
||
"securityTab": "Security",
|
||
"sniffingTab": "Sniffing",
|
||
"sniffingMetadataOnly": "Metadata only",
|
||
"sniffingRouteOnly": "Route only",
|
||
"sniffingIpsExcluded": "IPs excluded",
|
||
"sniffingDomainsExcluded": "Domains excluded",
|
||
"decryption": "Decryption",
|
||
"encryption": "Encryption",
|
||
"vlessAuthX25519": "X25519 (native)",
|
||
"vlessAuthMlkem768": "ML-KEM-768 (native)",
|
||
"vlessAuthX25519Xorpub": "X25519 (xorpub)",
|
||
"vlessAuthX25519Random": "X25519 (random)",
|
||
"vlessAuthMlkem768Xorpub": "ML-KEM-768 (xorpub)",
|
||
"vlessAuthMlkem768Random": "ML-KEM-768 (random)",
|
||
"vlessAuthCustom": "Custom",
|
||
"vlessAuthSelected": "Selected: {auth}",
|
||
"vlessAuthGenerate": "Generate keys",
|
||
"vlessAuthGenerateButton": "Generate",
|
||
"advanced": {
|
||
"title": "Inbound JSON sections",
|
||
"subtitle": "Full inbound JSON and focused editors for settings, sniffing, and streamSettings.",
|
||
"all": "All",
|
||
"allHelp": "Full inbound object with all fields in one editor.",
|
||
"settings": "Settings",
|
||
"settingsHelp": "Xray settings block wrapper:",
|
||
"sniffing": "Sniffing",
|
||
"sniffingHelp": "Xray sniffing block wrapper:",
|
||
"stream": "Stream",
|
||
"streamHelp": "Xray stream block wrapper:",
|
||
"jsonErrorPrefix": "Advanced JSON"
|
||
},
|
||
"telegramDesc": "Please provide Telegram Chat ID. (use '/id' command in the bot) or ({'@'}userinfobot)",
|
||
"subscriptionDesc": "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients.",
|
||
"subSortIndex": "Sub order",
|
||
"same": "Same",
|
||
"inboundInfo": "Inbound Information",
|
||
"exportInbound": "Export Inbound",
|
||
"import": "Import",
|
||
"importInbound": "Import an Inbound",
|
||
"periodicTrafficResetTitle": "Traffic Reset",
|
||
"periodicTrafficResetDesc": "Automatically reset traffic counter at specified intervals",
|
||
"lastReset": "Last Reset",
|
||
"periodicTrafficReset": {
|
||
"never": "Never",
|
||
"daily": "Daily",
|
||
"weekly": "Weekly",
|
||
"monthly": "Monthly",
|
||
"hourly": "Hourly"
|
||
},
|
||
"toasts": {
|
||
"obtain": "Obtain",
|
||
"updateSuccess": "The update was successful.",
|
||
"logCleanSuccess": "The log has been cleared.",
|
||
"inboundsUpdateSuccess": "Inbounds have been successfully updated.",
|
||
"inboundUpdateSuccess": "Inbound has been successfully updated.",
|
||
"inboundCreateSuccess": "Inbound has been successfully created.",
|
||
"bulkDeleted": "{count} inbounds deleted",
|
||
"bulkDeletedMixed": "{ok} deleted, {failed} failed",
|
||
"inboundDeleteSuccess": "Inbound has been successfully deleted.",
|
||
"inboundClientAddSuccess": "Inbound client(s) have been added.",
|
||
"inboundClientDeleteSuccess": "Inbound client has been deleted.",
|
||
"inboundClientUpdateSuccess": "Inbound client has been updated.",
|
||
"savedNodeOfflineWillSync": "Saved locally. A backing node is offline or disabled — the change will sync once it reconnects.",
|
||
"delDepletedClientsSuccess": "All depleted clients have been deleted.",
|
||
"resetAllClientTrafficSuccess": "Traffic for all clients has been reset.",
|
||
"resetAllTrafficSuccess": "All traffic has been reset.",
|
||
"resetInboundClientTrafficSuccess": "Traffic has been reset.",
|
||
"resetInboundTrafficSuccess": "Inbound traffic has been reset.",
|
||
"trafficGetError": "Error getting traffic.",
|
||
"getNewX25519CertError": "Error while obtaining the X25519 certificate.",
|
||
"getNewmldsa65Error": "Error while obtaining mldsa65.",
|
||
"getNewVlessEncError": "Error while obtaining VlessEnc.",
|
||
"scanRealityTargetError": "Failed to scan REALITY target.",
|
||
"scanRealityTargetFeasible": "Target is feasible — filled target and SNI.",
|
||
"scanRealityTargetNotFeasible": "Target is reachable but not feasible for REALITY.",
|
||
"invalidClientField": "Client {client}: {field} — {reason}",
|
||
"invalidField": "{field} — {reason}",
|
||
"moreIssues": "{message} (+{count} more)"
|
||
},
|
||
"form": {
|
||
"moveUp": "Move up",
|
||
"moveDown": "Move down",
|
||
"addAll": "Add all",
|
||
"addAllFallbackTooltip": "Add a fallback row for every eligible inbound not yet wired up",
|
||
"peers": "Peers",
|
||
"addPeer": "Add peer",
|
||
"keepAlive": "Keep-alive",
|
||
"autoSystemRoutesTooltip": "Windows-only. CIDRs added to the system routing table automatically so matching traffic goes through TUN.",
|
||
"autoOutboundsInterface": "Auto outbounds interface",
|
||
"autoOutboundsInterfaceTooltip": "Physical interface for outbound traffic. Use 'auto' to detect; auto-enabled when Auto system routes is set.",
|
||
"rewriteAddress": "Rewrite address",
|
||
"rewritePort": "Rewrite port",
|
||
"allowedNetwork": "Allowed network",
|
||
"followRedirect": "Follow redirect",
|
||
"accounts": "Accounts",
|
||
"allowTransparent": "Allow transparent",
|
||
"encryptionMethod": "Encryption method",
|
||
"fakeTlsDomain": "FakeTLS domain (SNI)",
|
||
"mtprotoSecret": "Secret",
|
||
"mtgDomainFrontingIp": "Domain fronting IP",
|
||
"mtgDomainFrontingPort": "Domain fronting port",
|
||
"mtgDomainFrontingProxyProtocol": "Domain fronting PROXY protocol",
|
||
"mtgDomainFrontingHint": "Where mtg sends non-Telegram traffic — e.g. your NGINX fake site. Leave the IP empty to use the FakeTLS domain via DNS; default port is 443.",
|
||
"mtgProxyProtocolListener": "Accept PROXY protocol (listener)",
|
||
"mtgPreferIp": "IP preference",
|
||
"mtgDebug": "Debug logging",
|
||
"mtgRouteThroughXray": "Route through Xray",
|
||
"mtgRouteThroughXrayHint": "Send this proxy's Telegram traffic through Xray so it follows your routing rules. The mtg sidecar dials out via a loopback SOCKS bridge tagged with this inbound's tag; reference that tag in the Routing tab for advanced rules.",
|
||
"mtgRouteOutbound": "Outbound",
|
||
"mtgRouteOutboundHint": "Optional. Force Telegram traffic out through this outbound (or balancer). Leave empty to let your routing rules decide.",
|
||
"mtgRouteOutboundPlaceholder": "Use routing rules",
|
||
"mtprotoFakeTlsDomainHint": "Default FakeTLS domain used to generate a new client's secret. Each client can front its own domain.",
|
||
"mtgThrottleMaxConnections": "Max connections",
|
||
"mtgThrottleMaxConnectionsHint": "Cap concurrent connections across all users with a fair-share limit. 0 disables throttling.",
|
||
"mtgAdTagInvalid": "Ad-tag must be exactly 32 hexadecimal characters.",
|
||
"mtgPublicIpv4": "Public IPv4",
|
||
"mtgPublicIpv6": "Public IPv6",
|
||
"mtgPublicIpHint": "This server's reachable public address, used by the ad-tag middle proxy. Leave blank to let mtg auto-detect it.",
|
||
"visionTestseed": "Vision testseed",
|
||
"version": "Version",
|
||
"udpIdleTimeout": "UDP idle timeout (s)",
|
||
"masquerade": "Masquerade",
|
||
"type": "Type",
|
||
"upstreamUrl": "Upstream URL",
|
||
"rewriteHost": "Rewrite Host",
|
||
"skipTlsVerify": "Skip TLS verify",
|
||
"directory": "Directory",
|
||
"statusCode": "Status code",
|
||
"body": "Body",
|
||
"headers": "Headers",
|
||
"proxyProtocol": "Proxy Protocol",
|
||
"requestVersion": "Request version",
|
||
"requestMethod": "Request method",
|
||
"requestPath": "Request path",
|
||
"requestHeaders": "Request headers",
|
||
"responseVersion": "Response version",
|
||
"responseStatus": "Response status",
|
||
"responseReason": "Response reason",
|
||
"responseHeaders": "Response headers",
|
||
"heartbeatPeriod": "Heartbeat Period",
|
||
"serviceName": "Service Name",
|
||
"authority": "Authority",
|
||
"multiMode": "Multi Mode",
|
||
"maxBufferedUpload": "Max Buffered Upload",
|
||
"maxUploadSize": "Max Upload Size (Byte)",
|
||
"streamUpServer": "Stream-Up Server",
|
||
"serverMaxHeaderBytes": "Server Max Header Bytes",
|
||
"paddingBytes": "Padding Bytes",
|
||
"uplinkHttpMethod": "Uplink HTTP Method",
|
||
"paddingObfsMode": "Padding Obfs Mode",
|
||
"paddingKey": "Padding Key",
|
||
"paddingHeader": "Padding Header",
|
||
"paddingPlacement": "Padding Placement",
|
||
"paddingMethod": "Padding Method",
|
||
"sessionPlacement": "Session Placement",
|
||
"sessionKey": "Session Key",
|
||
"sessionIDTable": "Session ID Table",
|
||
"sessionIDTableHint": "Charset for generated session IDs: a predefined name (ALPHABET, Base62, hex, number, …) or a literal ASCII string. Leave empty for xray-core's default.",
|
||
"sessionIDLength": "Session ID Length",
|
||
"sessionIDLengthHint": "Length or range (e.g. 8-16) of generated session IDs. Only used when a Session ID Table is set; minimum must be greater than 0.",
|
||
"sequencePlacement": "Sequence Placement",
|
||
"sequenceKey": "Sequence Key",
|
||
"uplinkDataPlacement": "Uplink Data Placement",
|
||
"uplinkDataKey": "Uplink Data Key",
|
||
"noSseHeader": "No SSE Header",
|
||
"ttiMs": "TTI (ms)",
|
||
"uplinkMbps": "Uplink (MB/s)",
|
||
"downlinkMbps": "Downlink (MB/s)",
|
||
"cwndMultiplier": "CWND Multiplier",
|
||
"maxSendingWindow": "Max Sending Window",
|
||
"externalProxy": "External Proxy",
|
||
"forceTls": "Force TLS",
|
||
"fingerprint": "Fingerprint",
|
||
"defaultOption": "Default",
|
||
"routeMark": "Route Mark",
|
||
"tcpKeepAliveInterval": "TCP Keep Alive Interval",
|
||
"tcpKeepAliveIdle": "TCP Keep Alive Idle",
|
||
"tcpMaxSeg": "TCP Max Seg",
|
||
"tcpUserTimeout": "TCP User Timeout",
|
||
"tcpWindowClamp": "TCP Window Clamp",
|
||
"tcpWindowClampHint": "Leave 0 to use the OS default. Non-zero values cap the advertised TCP receive window; values like 600 (from the Xray docs example) can collapse throughput on high-latency links.",
|
||
"tcpFastOpen": "TCP Fast Open",
|
||
"multipathTcp": "Multipath TCP",
|
||
"penetrate": "Penetrate",
|
||
"v6Only": "V6 Only",
|
||
"tcpCongestion": "TCP Congestion",
|
||
"dialerProxy": "Dialer Proxy",
|
||
"trustedXForwardedFor": "Trusted X-Forwarded-For",
|
||
"trustedXForwardedForHint": "Trust this request header for the real client IP (e.g. CF-Connecting-IP behind Cloudflare's CDN). Only honored on WebSocket, HTTPUpgrade, XHTTP and gRPC transports. Leave empty to ignore forwarded headers.",
|
||
"proxyProtocolHint": "Accept the PROXY-protocol header to learn the real client IP from an upstream L4 tunnel or relay (HAProxy, gost, nginx-stream, Xray dokodemo-door) or Cloudflare Spectrum. The upstream MUST emit PROXY protocol. Works on TCP, WebSocket, HTTPUpgrade and gRPC; not on mKCP.",
|
||
"realClientIp": "Real client IP",
|
||
"realClientIpHint": "Capture the visitor's real IP when traffic reaches this inbound through a CDN or relay, instead of recording the intermediary's address. Pick a preset to fill the matching sockopt fields below. These fields are never sent to clients in subscriptions.",
|
||
"realClientIpPresetOff": "Off / direct",
|
||
"realClientIpPresetCloudflare": "Cloudflare CDN",
|
||
"realClientIpPresetProxyProtocol": "L4 relay / Spectrum (PROXY)",
|
||
"realClientIpTrustedHeaderTransportWarn": "Trusted X-Forwarded-For is only honored on WebSocket, HTTPUpgrade and XHTTP. On the current transport this header is ignored.",
|
||
"realClientIpProxyProtocolTransportWarn": "PROXY protocol is not supported on this transport (mKCP). Use TCP/RAW, WebSocket, HTTPUpgrade, gRPC or XHTTP.",
|
||
"addressPortStrategy": "Address+port strategy",
|
||
"tryDelayMs": "Try delay (ms)",
|
||
"prioritizeIPv6": "Prioritize IPv6",
|
||
"interleave": "Interleave",
|
||
"maxConcurrentTry": "Max concurrent try",
|
||
"customSockopt": "Custom sockopt",
|
||
"addCustomOption": "Add custom option",
|
||
"serverNameIndication": "Server Name Indication",
|
||
"cipherSuites": "Cipher Suites",
|
||
"autoOption": "Auto",
|
||
"minMaxVersion": "Min/Max Version",
|
||
"rejectUnknownSni": "Reject Unknown SNI",
|
||
"disableSystemRoot": "Disable System Root",
|
||
"sessionResumption": "Session Resumption",
|
||
"oneTimeLoading": "One Time Loading",
|
||
"usageOption": "Usage Option",
|
||
"buildChain": "Build Chain",
|
||
"echKey": "ECH key",
|
||
"echConfig": "ECH config",
|
||
"echSockopt": "ECH Sockopt",
|
||
"echSockoptTip": "Socket options for the connection Xray uses to fetch the ECH config list (e.g. route the lookup through a dialerProxy outbound). Leave disabled to use defaults.",
|
||
"curvePreferences": "Curve Preferences",
|
||
"curvePreferencesTip": "Restrict the TLS key-exchange curves the server offers, in preference order (e.g. X25519MLKEM768, X25519). Leave empty to use Xray-core defaults.",
|
||
"masterKeyLog": "Master Key Log",
|
||
"masterKeyLogTip": "Path to write TLS master keys (SSLKEYLOGFILE format) for debugging with Wireshark. Leave empty in production — it lets anyone with the file decrypt traffic.",
|
||
"verifyPeerCertByName": "Verify Peer Cert By Name",
|
||
"verifyPeerCertByNameTip": "Tell clients to verify the server certificate against this name instead of the SNI. Comma-separated names. Panel-only — included in share links (vcn). The modern replacement for allowInsecure, which Xray removed after 2026-06-01.",
|
||
"pinnedPeerCertSha256": "Pinned Peer Cert SHA-256",
|
||
"pinnedPeerCertSha256Tip": "SHA-256 hash(es) of the peer certificate as a hex string (e.g. e8e2d3…), comma-separated. Panel-only — not written to the server's xray config, but included in share links so clients can pin the certificate.",
|
||
"pinnedPeerCertSha256Placeholder": "hex hash(es), comma-separated",
|
||
"pinFromCert": "Fill from this inbound's certificate",
|
||
"pinFromRemote": "Fetch the hash by pinging the SNI (xray tls ping)",
|
||
"pinFromRemoteNoSni": "Set the SNI (serverName) first to ping the remote certificate.",
|
||
"pinFromRemoteFailed": "Could not fetch the remote certificate hash.",
|
||
"getNewEchCert": "Get New ECH Cert",
|
||
"show": "Show",
|
||
"xver": "Xver",
|
||
"target": "Target",
|
||
"maxTimeDiff": "Max Time Diff (ms)",
|
||
"minClientVer": "Min Client Ver",
|
||
"maxClientVer": "Max Client Ver",
|
||
"shortIds": "Short IDs",
|
||
"realityTargetHint": "Required. Must include a port (e.g. example.com:443). Without a port Xray-core refuses to start.",
|
||
"realityTargetRequired": "REALITY target is required",
|
||
"realityTargetNeedsPort": "REALITY target must include a port (e.g. example.com:443)",
|
||
"realityTargetInvalidPort": "REALITY target has an invalid port",
|
||
"scan": "Scan",
|
||
"findTargets": "Find Targets",
|
||
"scanModalTitle": "REALITY Target Scanner",
|
||
"scanModalDesc": "Validate a domain, or scan an IP / CIDR range to discover new REALITY targets from their certificates. Leave the box empty to probe common candidates.",
|
||
"scanDiscoverPlaceholder": "IP, CIDR, or domain — leave empty for common candidates",
|
||
"scanStatus": "Status",
|
||
"scanFeasible": "Feasible",
|
||
"scanNotFeasible": "Not feasible",
|
||
"scanCurve": "Key Exchange",
|
||
"scanCert": "Certificate",
|
||
"scanCertInvalid": "Not trusted",
|
||
"scanLatency": "Latency",
|
||
"scanUse": "Use",
|
||
"scanRescan": "Rescan",
|
||
"spiderX": "SpiderX",
|
||
"spiderXHint": "Per-client seed — the panel derives a unique spx path for each client from it; regenerate to rotate everyone's paths",
|
||
"getNewCert": "Get New Cert",
|
||
"mldsa65Seed": "mldsa65 Seed",
|
||
"mldsa65Verify": "mldsa65 Verify",
|
||
"getNewSeed": "Get New Seed",
|
||
"limitFallback": "Limit Fallback",
|
||
"limitFallbackUpload": "Limit Fallback Upload",
|
||
"limitFallbackDownload": "Limit Fallback Download",
|
||
"afterBytes": "After Bytes",
|
||
"afterBytesTip": "Let the fallback run at full speed for this many bytes, then start throttling. 0 = throttle from the first byte.",
|
||
"bytesPerSec": "Bytes Per Sec",
|
||
"bytesPerSecTip": "Speed cap (bytes/sec) applied to fallback traffic after the threshold, so probes can't use your server as free bandwidth to the target. 0 = no limit (disables this direction).",
|
||
"burstBytesPerSec": "Burst Bytes Per Sec",
|
||
"burstBytesPerSecTip": "Allowance for short bursts above the steady rate (token-bucket size). If lower than Bytes Per Sec it is raised to match.",
|
||
"listenHelp": "You can also enter a Unix socket path (e.g. /run/xray/in.sock), or an abstract socket name prefixed with @ (e.g. @xray/in.sock), to listen on a socket instead of a TCP port — set Port to 0 in that case.",
|
||
"shareAddrStrategy": "Share address strategy",
|
||
"shareAddrStrategyHelp": "Controls which address is written into exported share links, QR codes, and subscription output.",
|
||
"shareAddr": "Custom share address",
|
||
"shareAddrHelp": "Used only when the share address strategy is Custom. Enter a host or IP without a scheme or port.",
|
||
"subSortIndex": "Subscription sort order",
|
||
"subSortIndexHelp": "Position of this inbound's links in subscription output (sub page and client apps). Lower values come first; equal values keep creation order. Does not affect the panel inbound list.",
|
||
"shareAddrStrategyOptions": {
|
||
"node": "Node address",
|
||
"listen": "Inbound listen",
|
||
"custom": "Custom"
|
||
}
|
||
},
|
||
"info": {
|
||
"mode": "Mode",
|
||
"grpcServiceName": "grpc serviceName",
|
||
"grpcMultiMode": "grpc multiMode",
|
||
"interfaceName": "Interface name",
|
||
"mtu": "MTU",
|
||
"gateway": "Gateway",
|
||
"dns": "DNS",
|
||
"outboundsInterface": "Outbounds interface",
|
||
"autoSystemRoutes": "Auto system routes",
|
||
"followRedirect": "FollowRedirect",
|
||
"auth": "Auth",
|
||
"noKernelTun": "No-kernel TUN",
|
||
"keepAlive": "Keep alive",
|
||
"peerNumber": "Peer {n}",
|
||
"peerNumberConfig": "Peer {n} config"
|
||
},
|
||
"stream": {
|
||
"general": {
|
||
"request": "Request",
|
||
"response": "Response",
|
||
"name": "Name",
|
||
"value": "Value"
|
||
},
|
||
"tcp": {
|
||
"version": "Version",
|
||
"method": "Method",
|
||
"path": "Path",
|
||
"status": "Status",
|
||
"statusDescription": "Status Desc",
|
||
"requestHeader": "Request Header",
|
||
"responseHeader": "Response Header"
|
||
}
|
||
},
|
||
"sniffingDestOverride": "Destination override"
|
||
},
|
||
"clients": {
|
||
"tabBasics": "Basics",
|
||
"tabCredentials": "Credentials",
|
||
"tabLinks": "Links",
|
||
"wireguardConfig": "WireGuard config",
|
||
"config": "Config",
|
||
"linksHint": "Add third-party share links and remote subscription URLs to include in this client's subscription.",
|
||
"addExternalLink": "Add External Link",
|
||
"addExternalSubscription": "Add External Subscription",
|
||
"noExternalLinks": "No external links yet.",
|
||
"noExternalSubscriptions": "No external subscriptions yet.",
|
||
"add": "Add Client",
|
||
"edit": "Edit Client",
|
||
"submitAdd": "Add Client",
|
||
"submitEdit": "Save Changes",
|
||
"clientCount": "Number of Clients",
|
||
"bulk": "Add Bulk",
|
||
"copyFromInbound": "Copy Clients from Inbound",
|
||
"copyToInbound": "Copy clients to",
|
||
"copySelected": "Copy Selected",
|
||
"copySource": "Source",
|
||
"copyEmailPreview": "Resulting email preview",
|
||
"copySelectSourceFirst": "Please select a source inbound first.",
|
||
"copyResult": "Copy result",
|
||
"copyResultSuccess": "Copied successfully",
|
||
"copyResultNone": "Nothing to copy: no clients selected or source is empty",
|
||
"copyResultErrors": "Copy errors",
|
||
"copyFlowLabel": "Flow for new clients (VLESS)",
|
||
"copyFlowHint": "Applied to all copied clients. Leave empty to skip.",
|
||
"selectAll": "Select all",
|
||
"clearAll": "Clear all",
|
||
"method": "Method",
|
||
"first": "First",
|
||
"last": "Last",
|
||
"ipLog": "IP Log",
|
||
"prefix": "Prefix",
|
||
"postfix": "Postfix",
|
||
"delayedStart": "Start After First Use",
|
||
"expireDays": "Duration (days)",
|
||
"days": "Day(s)",
|
||
"renew": "Auto Renew",
|
||
"renewDesc": "Auto-renewal after expiration. (0 = disable)(unit: day)",
|
||
"renewDays": "Auto Renew (days)",
|
||
"searchPlaceholder": "Search email, comment, sub ID, UUID, password, auth, Telegram ID…",
|
||
"filterTitle": "Filter clients",
|
||
"clearAllFilters": "Clear all",
|
||
"filters": {
|
||
"nodes": "Nodes",
|
||
"localPanel": "Local (this panel)"
|
||
},
|
||
"showingCount": "Showing {shown} of {total}",
|
||
"sortOldest": "Oldest first",
|
||
"sortNewest": "Newest first",
|
||
"sortRecentlyUpdated": "Recently updated",
|
||
"sortRecentlyOnline": "Recently online",
|
||
"sortEmailAZ": "Email A→Z",
|
||
"sortEmailZA": "Email Z→A",
|
||
"sortMostTraffic": "Most traffic",
|
||
"sortHighestRemaining": "Highest remaining",
|
||
"sortExpiringSoonest": "Expiring soonest",
|
||
"has": "Has",
|
||
"hasNot": "Doesn't have",
|
||
"title": "Clients",
|
||
"actions": "Actions",
|
||
"totalGB": "Traffic Limit (GB)",
|
||
"totalGBDesc": "Data quota for this client. 0 = unlimited.",
|
||
"expiryTime": "Expiry",
|
||
"addClients": "Add Clients",
|
||
"limitIp": "IP Limit",
|
||
"limitIpDesc": "Maximum simultaneous IPs. 0 = unlimited.",
|
||
"limitIpFail2banMissing": "Fail2ban is not installed, so the IP limit cannot be enforced. Install Fail2ban from the x-ui bash menu to enable this option.",
|
||
"limitIpFail2banWindows": "Fail2ban is not available on Windows, so the IP limit cannot be enforced.",
|
||
"limitIpDisabled": "The IP limit feature is disabled on this server.",
|
||
"password": "Password",
|
||
"passwordDesc": "Only used by Trojan and Shadowsocks clients; ignored for VLESS, VMess, Hysteria, and WireGuard.",
|
||
"subId": "Subscription ID",
|
||
"online": "Online",
|
||
"email": "Email",
|
||
"emailInvalidChars": "Email cannot contain spaces, '/', '\\', or control characters",
|
||
"subIdInvalidChars": "Subscription ID cannot contain spaces, '/', '\\', or control characters",
|
||
"group": "Group",
|
||
"groupDesc": "Logical label used to bucket related clients (e.g. team, customer, region). Filterable from the toolbar.",
|
||
"groupPlaceholder": "e.g. customer-a",
|
||
"comment": "Comment",
|
||
"traffic": "Traffic",
|
||
"speed": "Speed",
|
||
"offline": "Offline",
|
||
"addClient": "Add Client",
|
||
"qrCode": "QR Code",
|
||
"clientInfo": "Client Information",
|
||
"delete": "Delete",
|
||
"reset": "Reset Traffic",
|
||
"editClient": "Edit Client",
|
||
"client": "Client",
|
||
"enabled": "Enabled",
|
||
"remaining": "Remaining",
|
||
"duration": "Duration",
|
||
"attachedInbounds": "Attached inbounds",
|
||
"selectInbound": "Select one or more inbounds",
|
||
"selectAllInbounds": "Select all",
|
||
"clearAllInbounds": "Clear all",
|
||
"noSubId": "This client has no subId, no shareable link.",
|
||
"noLinks": "No shareable links — attach this client to a protocol-capable inbound first.",
|
||
"link": "Link",
|
||
"resetNotPossible": "Attach this client to an inbound first.",
|
||
"general": "General",
|
||
"resetAllTraffics": "Reset all client traffic",
|
||
"resetAllTrafficsTitle": "Reset all client traffic?",
|
||
"resetAllTrafficsContent": "Every client's up/down counter drops to zero. Quotas and expiry are not affected. This cannot be undone.",
|
||
"deleteConfirmTitle": "Delete client {email}?",
|
||
"deleteConfirmContent": "This removes the client from every attached inbound and drops its traffic record. This cannot be undone.",
|
||
"deleteSelected": "Delete ({count})",
|
||
"adjustSelected": "Adjust ({count})",
|
||
"subLinksSelected": "Sub links ({count})",
|
||
"addToGroupTitle": "Add {count} client(s) to a group",
|
||
"addToGroupTooltip": "Pick an existing group or type a new name. Use the Ungroup action to remove clients from their current group.",
|
||
"groupName": "Group name",
|
||
"addToGroupSuccessToast": "Added {count} client(s) to {group}",
|
||
"ungroupSuccessToast": "Cleared group from {count} client(s)",
|
||
"ungroup": "Ungroup",
|
||
"ungroupConfirmTitle": "Remove {count} client(s) from their group?",
|
||
"ungroupConfirmContent": "Clears the group label on each selected client. Clients themselves are kept (use Delete to remove them entirely).",
|
||
"addToGroup": "Add to group",
|
||
"attach": "Attach",
|
||
"adjust": "Adjust",
|
||
"subLinks": "Sub links",
|
||
"enable": "Enable",
|
||
"disable": "Disable",
|
||
"bulkEnableConfirmTitle": "Enable {count} clients?",
|
||
"bulkEnableConfirmContent": "Enables each selected client on every attached inbound. Clients whose quota is exhausted or whose expiry has passed will be disabled again automatically.",
|
||
"bulkDisableConfirmTitle": "Disable {count} clients?",
|
||
"bulkDisableConfirmContent": "Disables each selected client on every attached inbound. They lose access immediately but their records and traffic are kept.",
|
||
"selectedCount": "{count} selected",
|
||
"attachSelected": "Attach ({count})",
|
||
"attachToInboundsTitle": "Attach {count} client(s) to inbound(s)",
|
||
"attachToInboundsDesc": "Attaches the selected {count} client(s) (same UUID/password and shared traffic) to the chosen inbound(s). They keep their existing attachments too.",
|
||
"attachToInboundsTargets": "Target inbounds",
|
||
"attachToInboundsNoTargets": "No multi-user inbounds available to attach to.",
|
||
"detachSelected": "Detach ({count})",
|
||
"detach": "Detach",
|
||
"detachFromInboundsTitle": "Detach {count} client(s) from inbound(s)",
|
||
"detachFromInboundsDesc": "Removes the selected {count} client(s) from the chosen inbound(s). Pairs where the client wasn't attached are silently skipped. Client records are kept (use Delete to remove fully).",
|
||
"detachFromInboundsTargets": "Inbounds to detach from",
|
||
"detachFromInboundsNoTargets": "No multi-user inbounds available.",
|
||
"detachFromInboundsResult": "Detached {detached}, skipped {skipped}.",
|
||
"detachFromInboundsResultMixed": "Detached {detached}, skipped {skipped}, errors {errors}.",
|
||
"subLinksTitle": "Sub links ({count})",
|
||
"subLinkColumn": "Subscription URL",
|
||
"subJsonLinkColumn": "Subscription JSON URL",
|
||
"subLinksCopyAll": "Copy all",
|
||
"subLinksCopiedAll": "Copied {count} link(s)",
|
||
"subLinksEmpty": "None of the selected clients have a subscription ID.",
|
||
"subLinksDisabled": "Subscription service is disabled.",
|
||
"subLinksDisabledHint": "Enable subscription in Panel Settings → Subscription to generate links.",
|
||
"bulkDeleteConfirmTitle": "Delete {count} clients?",
|
||
"bulkDeleteConfirmContent": "Each selected client is removed from every attached inbound and its traffic record is dropped. This cannot be undone.",
|
||
"bulkAdjustTitle": "Adjust {count} clients",
|
||
"bulkAdjustHint": "Positive values extend, negative values reduce. Clients with unlimited expiry or traffic are skipped for that field.",
|
||
"bulkAdjustNothing": "Set days, traffic, or flow before applying.",
|
||
"addDays": "Add days",
|
||
"addTrafficGB": "Add traffic (GB)",
|
||
"bulkFlow": "Set flow",
|
||
"bulkFlowNoChange": "No change",
|
||
"bulkFlowDisable": "Disable (clear flow)",
|
||
"delDepleted": "Delete depleted",
|
||
"delDepletedConfirmTitle": "Delete depleted clients?",
|
||
"delDepletedConfirmContent": "Removes every client whose traffic quota is exhausted or whose expiry has passed. This cannot be undone.",
|
||
"exportClients": "Export clients",
|
||
"importClients": "Import clients",
|
||
"import": "Import",
|
||
"delOrphans": "Delete unattached clients",
|
||
"delOrphansConfirmTitle": "Delete clients without an inbound?",
|
||
"delOrphansConfirmContent": "Removes every client that is not attached to any inbound, along with its traffic record. This cannot be undone.",
|
||
"auth": "Auth",
|
||
"hysteriaAuth": "Hysteria Auth",
|
||
"hysteriaAuthDesc": "Credential used only by Hysteria clients. Trojan and Shadowsocks use the Password field instead.",
|
||
"uuid": "UUID",
|
||
"flow": "Flow",
|
||
"vmessSecurity": "VMess Security",
|
||
"wireguardPrivateKey": "WireGuard Private Key",
|
||
"wireguardPublicKey": "WireGuard Public Key",
|
||
"wireguardPreSharedKey": "WireGuard Pre-Shared Key",
|
||
"wireguardAllowedIPs": "WireGuard Allowed IPs",
|
||
"wireguardAllowedIPsHint": "Leave empty to auto-assign; separate entries with commas",
|
||
"mtprotoSecret": "MTProto secret",
|
||
"mtprotoSecretHint": "The client's FakeTLS secret. Regenerate to rotate it.",
|
||
"mtprotoAdTag": "Ad-tag (sponsored channel)",
|
||
"mtprotoAdTagHint": "Optional 32-character hex tag from Telegram's proxy registration. When set, this client is routed through Telegram middle proxies and a sponsored channel appears at the top of their chat list.",
|
||
"reverseTag": "Reverse tag",
|
||
"reverseTagPlaceholder": "Optional reverse tag",
|
||
"telegramId": "Telegram user ID",
|
||
"telegramIdPlaceholder": "Numeric Telegram user ID (0 = none)",
|
||
"created": "Created",
|
||
"updated": "Updated",
|
||
"ipLimit": "IP limit",
|
||
"toasts": {
|
||
"deleted": "Client deleted",
|
||
"trafficReset": "Traffic reset",
|
||
"allTrafficsReset": "All client traffic reset",
|
||
"bulkDeleted": "{count} clients deleted",
|
||
"bulkDeletedMixed": "{ok} deleted, {failed} failed",
|
||
"bulkEnabled": "{count} clients enabled",
|
||
"bulkEnabledMixed": "{ok} enabled, {failed} failed",
|
||
"bulkDisabled": "{count} clients disabled",
|
||
"bulkDisabledMixed": "{ok} disabled, {failed} failed",
|
||
"bulkCreated": "{count} clients created",
|
||
"bulkCreatedMixed": "{ok} created, {failed} failed",
|
||
"bulkAdjusted": "{count} clients adjusted",
|
||
"bulkAdjustedMixed": "{ok} adjusted, {skipped} skipped",
|
||
"delDepleted": "{count} depleted clients deleted",
|
||
"delOrphans": "{count} unattached clients deleted",
|
||
"imported": "{count} clients imported",
|
||
"importedMixed": "{ok} imported, {failed} skipped"
|
||
}
|
||
},
|
||
"groups": {
|
||
"title": "Groups",
|
||
"name": "Name",
|
||
"clientCount": "Clients",
|
||
"totalGroups": "Total groups",
|
||
"totalGroupedClients": "Clients with a group",
|
||
"trafficUsed": "Traffic used",
|
||
"upload": "Upload",
|
||
"download": "Download",
|
||
"totalTraffic": "Total traffic",
|
||
"totalUpDown": "Total upload / download",
|
||
"addGroup": "Add Group",
|
||
"createSuccess": "Group \"{name}\" created.",
|
||
"rename": "Rename",
|
||
"renameTitle": "Rename {name}",
|
||
"renameCollision": "A group named \"{name}\" already exists.",
|
||
"renameSuccess": "Renamed group on {count} client(s).",
|
||
"deleteConfirmTitle": "Delete group {name}?",
|
||
"deleteConfirmContent": "This removes the group and clears its label from {count} client(s). The clients themselves are not deleted.",
|
||
"deleteSuccess": "Cleared group from {count} client(s).",
|
||
"resetTraffic": "Reset traffic",
|
||
"resetConfirmTitle": "Reset traffic for group {name}?",
|
||
"resetConfirmContent": "This resets only the group's traffic counter. Individual client counters are not affected.",
|
||
"resetSuccess": "Group {name} traffic reset.",
|
||
"adjustSuccess": "Adjusted {count} client(s) in {name}.",
|
||
"emptyForAction": "This group has no clients yet.",
|
||
"deleteGroupOnly": "Delete group (keep clients)",
|
||
"deleteClients": "Delete clients in group",
|
||
"deleteClientsConfirmTitle": "Delete all clients in {name}?",
|
||
"deleteClientsConfirmContent": "This permanently removes {count} client(s) along with their traffic records. The group label is cleared too. This cannot be undone.",
|
||
"deleteClientsSuccess": "Deleted {count} client(s).",
|
||
"deleteClientsMixed": "{ok} deleted, {failed} skipped",
|
||
"addToGroup": "Add clients…",
|
||
"addToGroupTitle": "Add clients to group \"{name}\"",
|
||
"addToGroupDesc": "Select clients to add to this group. They keep their existing inbound attachments; only the group label changes. Clients already in this group are not listed.",
|
||
"addToGroupEmpty": "No other clients available to add.",
|
||
"addToGroupResult": "Added {count} client(s) to {name}.",
|
||
"removeFromGroup": "Remove clients…",
|
||
"removeFromGroupTitle": "Remove clients from group \"{name}\"",
|
||
"removeFromGroupDesc": "Select members to remove from this group. Clients themselves are kept (use \"Delete clients in group\" to remove them entirely).",
|
||
"removeFromGroupResult": "Removed {count} client(s) from {name}."
|
||
},
|
||
"hosts": {
|
||
"addHost": "Add Host",
|
||
"editHost": "Edit Host",
|
||
"selectInbound": "Select an inbound",
|
||
"selectedCount": "{count} selected",
|
||
"summary": {
|
||
"total": "Total",
|
||
"enabled": "Enabled",
|
||
"disabled": "Disabled"
|
||
},
|
||
"moveUp": "Move up",
|
||
"moveDown": "Move down",
|
||
"bulkEnable": "Enable",
|
||
"bulkDisable": "Disable",
|
||
"bulkDelete": "Delete",
|
||
"bulkDeleteConfirm": "Delete {count} selected host(s)?",
|
||
"deleteConfirmTitle": "Delete host \"{name}\"?",
|
||
"sections": {
|
||
"basic": "Basic",
|
||
"security": "Security",
|
||
"advanced": "Advanced",
|
||
"general": "General",
|
||
"clash": "Clash (mihomo)"
|
||
},
|
||
"fields": {
|
||
"remark": "Remark",
|
||
"serverDescription": "Description",
|
||
"inbound": "Inbounds",
|
||
"address": "Address",
|
||
"port": "Port",
|
||
"endpoint": "Endpoint",
|
||
"enable": "Enable",
|
||
"actions": "Actions",
|
||
"security": "Security",
|
||
"sni": "SNI",
|
||
"overrideSniFromAddress": "Use address as SNI",
|
||
"keepSniBlank": "Keep SNI blank",
|
||
"hostHeader": "Host header",
|
||
"path": "Path",
|
||
"alpn": "ALPN",
|
||
"fingerprint": "Fingerprint",
|
||
"pins": "Pinned cert SHA-256",
|
||
"verifyPeerCertByName": "Verify peer cert by name",
|
||
"allowInsecure": "Allow insecure",
|
||
"echConfigList": "ECH config list",
|
||
"muxParams": "Mux",
|
||
"sockoptParams": "Sockopt",
|
||
"finalMask": "Final Mask",
|
||
"vlessRoute": "VLESS route",
|
||
"mihomoIpVersion": "IP version",
|
||
"mihomoX25519": "Mihomo X25519",
|
||
"shuffleHost": "Shuffle host",
|
||
"tags": "Tags",
|
||
"nodeGuids": "Nodes",
|
||
"excludeFromSubTypes": "Exclude from formats",
|
||
"inheritAddress": "Inherits"
|
||
},
|
||
"hints": {
|
||
"address": "Leave blank to inherit the inbound's own address.",
|
||
"port": "0 inherits the inbound's port.",
|
||
"tags": "Not visible to end users; sent with RAW subscription only. Uppercase letters, digits, _ and : only.",
|
||
"nodeGuids": "Pick nodes which resolved from this host. Only visual assignment.",
|
||
"serverDescription": "Optional note shown under the remark.",
|
||
"allowInsecure": "Skip TLS certificate verification (allowInsecure / skip-cert-verify).",
|
||
"vlessRoute": "Single VLESS route value (0-65535) baked into the UUID, e.g. 443. Leave blank for none.",
|
||
"remark": "A plain label for this host. Shown as the config name only when the inbound has no remark of its own."
|
||
},
|
||
"remarkVars": {
|
||
"title": "Template Variables",
|
||
"intro": "Click a variable to add it. It is replaced per client when the subscription is generated.",
|
||
"preview": "Preview",
|
||
"groups": {
|
||
"client": "Client",
|
||
"traffic": "Traffic",
|
||
"time": "Time & status",
|
||
"connection": "Connection"
|
||
},
|
||
"descEMAIL": "Client email",
|
||
"descINBOUND": "Inbound's own remark (the config name)",
|
||
"descHOST": "Host remark",
|
||
"descID": "Client UUID",
|
||
"descSHORT_ID": "First 8 characters of the UUID",
|
||
"descTELEGRAM_ID": "Client's Telegram ID (empty if unset)",
|
||
"descSUB_ID": "Subscription ID",
|
||
"descCOMMENT": "Client comment",
|
||
"descTRAFFIC_USED": "Used traffic (human readable)",
|
||
"descTRAFFIC_LEFT": "Remaining traffic (hidden if unlimited)",
|
||
"descTRAFFIC_TOTAL": "Total traffic (hidden if unlimited)",
|
||
"descTRAFFIC_USED_BYTES": "Used traffic in bytes",
|
||
"descTRAFFIC_LEFT_BYTES": "Remaining traffic in bytes",
|
||
"descTRAFFIC_TOTAL_BYTES": "Total traffic in bytes",
|
||
"descUP": "Upload traffic",
|
||
"descDOWN": "Download traffic",
|
||
"descSTATUS": "active / expired / disabled / depleted",
|
||
"descSTATUS_EMOJI": "Status as an emoji (✅ ⏳ 🚫)",
|
||
"descDAYS_LEFT": "Days until expiry (hidden if unlimited)",
|
||
"descTIME_LEFT": "Remaining time (e.g. 12d 4h 30m)",
|
||
"descUSAGE_PERCENTAGE": "Used traffic as a percentage (hidden if unlimited)",
|
||
"descEXPIRE_DATE": "Expiry date (YYYY-MM-DD)",
|
||
"descJALALI_EXPIRE_DATE": "Expiry date in the Jalali calendar (YYYY/MM/DD)",
|
||
"descEXPIRE_UNIX": "Expiry as a Unix timestamp (seconds)",
|
||
"descCREATED_UNIX": "Creation time as a Unix timestamp (seconds)",
|
||
"descRESET_DAYS": "Traffic reset period in days",
|
||
"descPROTOCOL": "Inbound protocol (VLESS, VMess, Trojan, …)",
|
||
"descTRANSPORT": "Transport network (tcp, ws, grpc, …)",
|
||
"descSECURITY": "Transport security (TLS, REALITY, NONE)"
|
||
},
|
||
"toasts": {
|
||
"list": "Failed to load hosts",
|
||
"obtain": "Failed to load host",
|
||
"add": "Host added successfully",
|
||
"update": "Host updated successfully",
|
||
"delete": "Host deleted successfully",
|
||
"badTag": "Invalid tag",
|
||
"badVlessRoute": "Enter a single number between 0 and 65535"
|
||
}
|
||
},
|
||
"nodes": {
|
||
"title": "Nodes",
|
||
"addNode": "Add Node",
|
||
"editNode": "Edit Node",
|
||
"totalNodes": "Total Nodes",
|
||
"onlineNodes": "Online",
|
||
"offlineNodes": "Offline",
|
||
"avgLatency": "Avg Latency",
|
||
"name": "Name",
|
||
"namePlaceholder": "e.g. de-frankfurt-1",
|
||
"addressPlaceholder": "panel.example.com or 1.2.3.4",
|
||
"remark": "Remark",
|
||
"scheme": "Scheme",
|
||
"address": "Address",
|
||
"port": "Port",
|
||
"basePath": "Base Path",
|
||
"apiToken": "API Token",
|
||
"apiTokenPlaceholder": "Token from the remote panel's Settings page",
|
||
"apiTokenHint": "The remote panel exposes its API token under Authentication → API Token.",
|
||
"regenerate": "Regenerate Token",
|
||
"regenerateConfirm": "Regenerating invalidates the current token. Any central panel using it will lose access until updated. Continue?",
|
||
"allowPrivateAddress": "Allow private address",
|
||
"allowPrivateAddressHint": "Enable only for nodes on a private network or VPN.",
|
||
"outboundTag": "Connection outbound",
|
||
"outboundTagHint": "Route this node's panel API traffic through the selected Xray outbound. A loopback bridge inbound is added to the running config automatically and applied live. Leave empty for a direct connection.",
|
||
"outboundTagPlaceholder": "Direct connection",
|
||
"inboundSyncMode": "Inbound import",
|
||
"inboundSyncModeHint": "Choose which inbounds are imported from this node. Existing nodes default to all inbounds.",
|
||
"allInbounds": "All inbounds",
|
||
"selectedInbounds": "Selected inbounds",
|
||
"inboundTags": "Inbounds",
|
||
"inboundTagsHint": "Selection is matched by the inbound tag. An empty selection imports none.",
|
||
"inboundTagsPlaceholder": "Load and select inbounds",
|
||
"loadInbounds": "Load inbounds from node",
|
||
"inboundsLoaded": "Loaded {{count}} inbounds",
|
||
"inboundsLoadFailed": "Failed to load inbounds",
|
||
"enable": "Enabled",
|
||
"status": "Status",
|
||
"cpu": "CPU",
|
||
"mem": "Memory",
|
||
"netUp": "Net Up (KB/s)",
|
||
"netDown": "Net Down (KB/s)",
|
||
"uptime": "Uptime",
|
||
"latency": "Latency",
|
||
"lastHeartbeat": "Last Heartbeat",
|
||
"xrayVersion": "Xray Version",
|
||
"panelVersion": "Panel Version",
|
||
"actions": "Actions",
|
||
"probe": "Probe Now",
|
||
"updatePanel": "Update Panel",
|
||
"updateSelected": "Update Selected ({count})",
|
||
"updateAvailable": "Update available",
|
||
"upToDate": "Up to date",
|
||
"updateConfirmTitle": "Update {count} node(s) to the latest version?",
|
||
"updateConfirmContent": "Each selected node downloads the latest release and restarts onto it. Only enabled, online nodes are updated.",
|
||
"updateDevChannel": "Update to Dev channel (latest commit)",
|
||
"testConnection": "Test Connection",
|
||
"connectionOk": "Connection OK ({ms} ms)",
|
||
"connectionFailed": "Connection failed",
|
||
"never": "never",
|
||
"justNow": "just now",
|
||
"subNode": "Sub-node",
|
||
"subNodeTip": "Read-only: a downstream node reached through {parent}. Manage it from {parent}'s own panel.",
|
||
"deleteConfirmTitle": "Delete node \"{name}\"?",
|
||
"deleteConfirmContent": "This stops monitoring the node. The remote panel itself is unaffected.",
|
||
"statusValues": {
|
||
"online": "Online",
|
||
"offline": "Offline",
|
||
"unknown": "Unknown",
|
||
"xrayError": "Xray Error",
|
||
"xrayStopped": "Stopped"
|
||
},
|
||
"toasts": {
|
||
"list": "Failed to load nodes",
|
||
"obtain": "Failed to load node",
|
||
"add": "Add node",
|
||
"update": "Update node",
|
||
"delete": "Delete node",
|
||
"deleted": "Node deleted",
|
||
"test": "Test connection",
|
||
"fillRequired": "Name, address, port and API token are required",
|
||
"probeFailed": "Probe failed",
|
||
"updateStarted": "Panel update started",
|
||
"updateResult": "Update triggered on {ok} node(s), {failed} failed",
|
||
"updateNoneEligible": "Select at least one online, enabled node",
|
||
"saveMtls": "Save node mTLS"
|
||
},
|
||
"tlsVerifyMode": "TLS verification",
|
||
"tlsVerifyModeHint": "How the panel validates the node's HTTPS certificate. Pin or Skip are for self-signed certs (https nodes only).",
|
||
"tlsVerify": "Verify (default CA)",
|
||
"tlsPin": "Pin certificate (SHA-256)",
|
||
"tlsSkip": "Skip verification",
|
||
"tlsMtls": "Mutual TLS (client certificate)",
|
||
"mtlsFormHint": "This node authenticates the panel with a client certificate. Copy this panel's CA from the Node mTLS section onto the node, set its Trusted parent CA, then restart it.",
|
||
"mtls": {
|
||
"title": "Node mTLS",
|
||
"intro": "Mutual TLS adds a client-certificate factor on top of the API token for node-to-node calls. It is opt-in: leave it empty to keep token-only auth.",
|
||
"copyCa": "Copy this panel's CA",
|
||
"copyCaHint": "Hand this CA to the nodes this panel manages, then set their TLS verification to Mutual TLS.",
|
||
"caCopied": "CA certificate copied to clipboard",
|
||
"caFailed": "Failed to obtain the CA certificate",
|
||
"trustLabel": "Trusted parent CA",
|
||
"trustHint": "When this panel is itself a node, paste the managing panel's CA here to require its client certificate. Restart the panel to apply.",
|
||
"trustPlaceholder": "-----BEGIN CERTIFICATE-----",
|
||
"save": "Save trust CA",
|
||
"saved": "Trust CA saved — restart the panel to apply"
|
||
},
|
||
"tlsSkipWarning": "Skipping verification removes protection against man-in-the-middle attacks — the API token could be intercepted. Prefer pinning the certificate.",
|
||
"pinnedCert": "Pinned certificate SHA-256",
|
||
"pinnedCertHint": "Base64 or hex SHA-256 of the node's certificate. Use Fetch to read it from the node now.",
|
||
"pinnedCertPlaceholder": "base64 or hex SHA-256",
|
||
"fetchPin": "Fetch",
|
||
"pinFetched": "Fetched the node's current certificate",
|
||
"pinFetchFailed": "Could not fetch the certificate"
|
||
},
|
||
"settings": {
|
||
"title": "Panel Settings",
|
||
"save": "Save",
|
||
"infoDesc": "Every change made here needs to be saved. Please restart the panel to apply changes.",
|
||
"restartPanel": "Restart Panel",
|
||
"restartPanelDesc": "Are you sure you want to restart the panel? If you cannot access the panel after restarting, please view the panel log info on the server.",
|
||
"restartPanelSuccess": "The panel was successfully restarted.",
|
||
"actions": "Actions",
|
||
"resetDefaultConfig": "Reset to Default",
|
||
"panelSettings": "General",
|
||
"securitySettings": "Authentication",
|
||
"securityWarnings": "Security warnings",
|
||
"panelExposed": "Your panel may be exposed:",
|
||
"warnHttp": "Panel is served over plain HTTP — set up TLS for production.",
|
||
"warnDefaultPort": "Default port 2053 is well-known — change it to a random port.",
|
||
"warnDefaultBasePath": "Default base path \"/\" is well-known — change it to a random path.",
|
||
"warnDefaultSubPath": "Default subscription path \"/sub/\" is well-known — change it.",
|
||
"warnDefaultJsonPath": "Default JSON subscription path \"/json/\" is well-known — change it.",
|
||
"TGBotSettings": "Telegram Bot",
|
||
"panelListeningIP": "Listen IP",
|
||
"panelListeningIPDesc": "The IP address for the web panel. (leave blank to listen on all IPs)",
|
||
"panelListeningDomain": "Listen Domain",
|
||
"panelListeningDomainDesc": "The domain name for the web panel. (leave blank to listen on all domains and IPs)",
|
||
"panelPort": "Listen Port",
|
||
"panelPortDesc": "The port number for the web panel. (must be an unused port)",
|
||
"publicKeyPath": "Public Key Path",
|
||
"publicKeyPathDesc": "The public key file path for the web panel. (begins with ‘/‘)",
|
||
"privateKeyPath": "Private Key Path",
|
||
"privateKeyPathDesc": "The private key file path for the web panel. (begins with ‘/‘)",
|
||
"panelUrlPath": "URI Path",
|
||
"panelUrlPathDesc": "The URI path for the web panel. (begins with ‘/‘ and concludes with ‘/‘)",
|
||
"pageSize": "Pagination Size",
|
||
"pageSizeDesc": "Define page size for inbounds table. (0 = disable)",
|
||
"panelOutbound": "Panel Traffic Outbound",
|
||
"panelOutboundDesc": "Routes the panel's own requests — panel/Xray version checks and downloads, Telegram, and the normal geo-file update — through this Xray outbound to bypass server-side filtering of GitHub/Telegram. A loopback bridge inbound is added to the running config automatically and applied live. The Xray-native Geodata Auto-Update is not affected; it has its own download outbound. Leave empty for a direct connection.",
|
||
"panelOutboundPh": "Direct connection",
|
||
"remarkTemplate": "Remark Template",
|
||
"remarkTemplateDesc": "When set, this replaces the remark model for every subscription link — write your own format with the variable tokens (use the button to insert them). Leave empty to use the model above.",
|
||
"datepicker": "Calendar Type",
|
||
"datepickerPlaceholder": "Select date",
|
||
"datepickerDescription": "Scheduled tasks will run based on this calendar.",
|
||
"oldUsername": "Current Username",
|
||
"currentPassword": "Current Password",
|
||
"newUsername": "New Username",
|
||
"newPassword": "New Password",
|
||
"telegramBotEnable": "Enable Telegram Bot",
|
||
"telegramBotEnableDesc": "Enables the Telegram bot.",
|
||
"telegramToken": "Telegram Token",
|
||
"telegramTokenDesc": "The Telegram bot token obtained from '{'@'}BotFather'.",
|
||
"telegramProxy": "SOCKS Proxy",
|
||
"telegramProxyDesc": "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)",
|
||
"telegramAPIServer": "Telegram API Server",
|
||
"telegramAPIServerDesc": "The Telegram API server to use. Leave blank to use the default server.",
|
||
"telegramChatId": "Admin Chat ID",
|
||
"telegramChatIdDesc": "The Telegram Admin Chat ID(s). (comma-separated)(get it here {'@'}userinfobot) or (use '/id' command in the bot)",
|
||
"telegramNotifyTime": "Notification Time",
|
||
"telegramNotifyTimeDesc": "How often the Telegram bot sends periodic reports. Pick a preset interval, or choose Custom to enter a raw crontab expression.",
|
||
"notifyTime": {
|
||
"every": "@every — repeat at an interval",
|
||
"hourly": "@hourly — every hour",
|
||
"daily": "@daily — every day at 00:00",
|
||
"weekly": "@weekly — every week",
|
||
"monthly": "@monthly — every month",
|
||
"custom": "Custom (crontab)",
|
||
"seconds": "Seconds",
|
||
"minutes": "Minutes",
|
||
"hours": "Hours",
|
||
"interval": "Interval",
|
||
"unit": "Unit"
|
||
},
|
||
"tgNotifyBackup": "Database Backup",
|
||
"tgNotifyBackupDesc": "Send a database backup file with a report.",
|
||
"tgNotifyLogin": "Login Notification",
|
||
"tgNotifyLoginDesc": "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel.",
|
||
"sessionMaxAge": "Session Duration",
|
||
"sessionMaxAgeDesc": "The duration for which you can stay logged in. (unit: minute)",
|
||
"expireTimeDiff": "Expiration Date Notification",
|
||
"expireTimeDiffDesc": "Get notified about expiration date when reaching this threshold. (unit: day)",
|
||
"trafficDiff": "Traffic Cap Notification",
|
||
"trafficDiffDesc": "Get notified about traffic cap when reaching this threshold. (unit: GB)",
|
||
"tgNotifyCpu": "CPU Load Notification",
|
||
"tgNotifyCpuDesc": "Get notified if CPU load exceeds this threshold. (unit: %)",
|
||
"timeZone": "Time Zone",
|
||
"timeZoneDesc": "Scheduled tasks will run based on this time zone.",
|
||
"subSettings": "Subscription",
|
||
"subEnable": "Subscription Service",
|
||
"subEnableDesc": "Enable/Disable the subscription service.",
|
||
"subJsonEnable": "Enable/Disable the JSON subscription endpoint independently.",
|
||
"subJsonEnableTitle": "JSON subscription",
|
||
"subClashEnableTitle": "Clash / Mihomo subscription",
|
||
"subFormatsTipTitle": "Format-specific subscription settings",
|
||
"subFormatsTipDesc": "Configure JSON and Clash / Mihomo URL paths, reverse URLs, and client auto-detection separately.",
|
||
"subFormatsTipAction": "Open Sub Formats",
|
||
"subJsonAutoDetect": "Auto-detect Xray JSON clients",
|
||
"subJsonAutoDetectDesc": "When enabled, recognized compatible clients requesting the standard subscription URL receive an Xray JSON configuration array automatically. Other clients keep the raw/base64 response. Requires JSON subscription to be enabled and a panel restart to apply.",
|
||
"subJsonAlwaysArray": "Always return a JSON array",
|
||
"subJsonAlwaysArrayDesc": "Return the explicit JSON subscription endpoint as an array even when it contains one profile, as required by the XTLS subscription standard. Auto-detected JSON responses always use arrays. Leave disabled to preserve the legacy single-object response.",
|
||
"subJsonUserAgentRegex": "Xray JSON User-Agent regex",
|
||
"subJsonUserAgentRegexDesc": "Go RE2 regular expression matched against the client's User-Agent to auto-select the Xray JSON format on the standard subscription URL. Empty by default, so auto-detection stays off until you set a pattern for the clients you want to serve. Other clients keep the raw/base64 response. Restart the panel after changes.",
|
||
"subClashAutoDetect": "Auto-detect Clash/Mihomo clients",
|
||
"subClashAutoDetectDesc": "When enabled, recognized Clash/Mihomo clients requesting the standard subscription URL receive Clash YAML automatically. Browsers still show the subscription page, other clients keep the raw/base64 response, and the explicit JSON and Clash URLs remain available. Requires Clash/Mihomo subscription to be enabled and a panel restart to apply.",
|
||
"subClashUserAgentRegex": "Clash/Mihomo User-Agent regex",
|
||
"subClashUserAgentRegexDesc": "Go RE2 regular expression matched against the client's User-Agent to recognize Clash/Mihomo clients on the standard subscription URL. Leave empty to use the default pattern. Restart the panel after changes.",
|
||
"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 announcement text displayed in the VPN client",
|
||
"subThemeDir": "Sub Theme Directory",
|
||
"subThemeDirDesc": "Absolute path to a folder containing a custom index.html/sub.html subscription page template (e.g. /etc/3x-ui/sub_templates/my-theme/). Leave empty to use the default page.",
|
||
"subThemeDirDocs": "Template guide ↗",
|
||
"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)",
|
||
"subHideSettings": "Hide server settings",
|
||
"subHideSettingsDesc": "Hide the ability to view and edit server configurations in the VPN client. (Only for Happ)",
|
||
"subIncyEnableRouting": "Enable routing",
|
||
"subIncyEnableRoutingDesc": "Inject a routing profile into the subscription body for the Incy client. (Only for Incy)",
|
||
"subIncyRoutingRules": "Routing rules",
|
||
"subIncyRoutingRulesDesc": "Incy routing deep-link added to the subscription body, e.g. incy://routing/onadd/<base64>. (Only for Incy)",
|
||
"subClashEnableRouting": "Enable routing",
|
||
"subClashEnableRoutingDesc": "Include global Clash/Mihomo routing rules in generated YAML subscriptions.",
|
||
"subClashRoutingRules": "Global routing rules",
|
||
"subClashRoutingRulesDesc": "Default Clash/Mihomo rules prepended to every generated YAML subscription before MATCH,PROXY.",
|
||
"subListen": "Listen IP",
|
||
"subListenDesc": "The IP address for the subscription service. (leave blank to listen on all IPs)",
|
||
"subPort": "Listen Port",
|
||
"subPortDesc": "The port number for the subscription service. (must be an unused port). Also used to build the subscription link/QR shown in the panel when \"Reverse Proxy URI\" below is empty — if the subscription is reached through a reverse proxy on a different port, set \"Reverse Proxy URI\" instead.",
|
||
"subCertPath": "Public Key Path",
|
||
"subCertPathDesc": "The public key file path for the subscription service. (begins with ‘/‘)",
|
||
"subKeyPath": "Private Key Path",
|
||
"subKeyPathDesc": "The private key file path for the subscription service. (begins with ‘/‘)",
|
||
"subPath": "URI Path",
|
||
"subPathDesc": "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)",
|
||
"subDomain": "Listen Domain",
|
||
"subDomainDesc": "The domain name for the subscription service. (leave blank to listen on all domains and IPs). Also used as the fallback domain for the displayed subscription link when \"Reverse Proxy URI\" is empty — set \"Reverse Proxy URI\" if the panel and the subscription are reached through different domains (e.g. behind a reverse proxy).",
|
||
"subUpdates": "Update Intervals",
|
||
"subUpdatesDesc": "The update intervals of the subscription URL in the client apps. (unit: hour)",
|
||
"subEncrypt": "Encode",
|
||
"subEncryptDesc": "The returned content of subscription service will be Base64 encoded.",
|
||
"subURI": "Reverse Proxy URI",
|
||
"subURIDesc": "The full base URL (scheme://domain[:port]/path/) for the subscription link and QR code, used instead of Listen Domain/Listen Port. Set this whenever the subscription is reached through a reverse proxy or a domain/port different from the ones above.",
|
||
"externalTrafficInformEnable": "External Traffic Inform",
|
||
"externalTrafficInformEnableDesc": "Inform external API on every traffic update.",
|
||
"externalTrafficInformURI": "External Traffic Inform URI",
|
||
"externalTrafficInformURIDesc": "Traffic updates are sent to this URI.",
|
||
"restartXrayOnClientDisable": "Restart Xray After Auto Disable",
|
||
"restartXrayOnClientDisableDesc": "When a client is automatically disabled due to expiration or traffic limit, restart Xray.",
|
||
"fragment": "Fragmentation",
|
||
"fragmentDesc": "Enable fragmentation for TLS hello packet.",
|
||
"fragmentSett": "Fragmentation Settings",
|
||
"noisesDesc": "Enable Noises.",
|
||
"noisesSett": "Noises Settings",
|
||
"trustedProxyCidrs": "Trusted proxy CIDRs",
|
||
"trustedProxyCidrsDesc": "Comma-separated IPs/CIDRs allowed to set forwarded host, proto, and client IP headers.",
|
||
"ldap": {
|
||
"enable": "Enable LDAP sync",
|
||
"host": "LDAP host",
|
||
"port": "LDAP port",
|
||
"useTls": "Use TLS (LDAPS)",
|
||
"skipTlsVerify": "Skip TLS certificate verification",
|
||
"skipTlsVerifyDesc": "Insecure — disables server certificate validation. Use only with internal/untrusted CAs.",
|
||
"bindDn": "Bind DN",
|
||
"passwordConfigured": "Configured; leave blank to keep current password.",
|
||
"passwordUnconfigured": "Not configured.",
|
||
"passwordPlaceholder": "Configured - enter a new value to replace",
|
||
"baseDn": "Base DN",
|
||
"userFilter": "User filter",
|
||
"userAttr": "User attribute (username/email)",
|
||
"vlessField": "VLESS flag attribute",
|
||
"flagField": "Generic flag attribute (optional)",
|
||
"flagFieldDesc": "If set, overrides VLESS flag — e.g. shadowInactive.",
|
||
"truthyValues": "Truthy values",
|
||
"truthyValuesDesc": "Comma-separated; default: true,1,yes,on",
|
||
"invertFlag": "Invert flag",
|
||
"invertFlagDesc": "Enable when the attribute means disabled (e.g. shadowInactive).",
|
||
"syncSchedule": "Sync schedule",
|
||
"syncScheduleDesc": "Cron-like string, e.g. @every 1m",
|
||
"inboundTags": "Inbound tags",
|
||
"inboundTagsDesc": "Inbounds that LDAP sync may auto-create or auto-delete clients on.",
|
||
"noInbounds": "No inbounds found. Create one in Inbounds first.",
|
||
"autoCreate": "Auto create clients",
|
||
"autoDelete": "Auto delete clients",
|
||
"defaultTotalGb": "Default total (GB)",
|
||
"defaultExpiryDays": "Default expiry (days)",
|
||
"defaultIpLimit": "Default IP limit"
|
||
},
|
||
"subFormats": {
|
||
"finalMask": "Final Mask",
|
||
"finalMaskDesc": "Inject Xray finalmask TCP/UDP masks and QUIC parameters into every generated Xray JSON profile. Requires a client app that supports Xray JSON subscriptions and a recent Xray core.",
|
||
"packets": "Packets",
|
||
"length": "Length",
|
||
"interval": "Interval",
|
||
"maxSplit": "Max split",
|
||
"noises": "Noises",
|
||
"noiseItem": "Noise №{n}",
|
||
"type": "Type",
|
||
"packet": "Packet",
|
||
"delayMs": "Delay (ms)",
|
||
"applyTo": "Apply to",
|
||
"addNoise": "+ Noise",
|
||
"concurrency": "Concurrency",
|
||
"xudpConcurrency": "xudp concurrency",
|
||
"xudpUdp443": "xudp UDP 443"
|
||
},
|
||
"mux": "Mux",
|
||
"muxDesc": "Transmit multiple independent data streams within an established data stream.",
|
||
"muxSett": "Mux Settings",
|
||
"direct": "Direct Connection",
|
||
"directDesc": "Directly establishes connections with domains or IP ranges of a specific country.",
|
||
"notifications": "Notifications",
|
||
"certs": "Certificates",
|
||
"externalTraffic": "External Traffic",
|
||
"dateAndTime": "Date and Time",
|
||
"proxyAndServer": "Proxy and Server",
|
||
"intervals": "Intervals",
|
||
"information": "Information",
|
||
"profile": "Profile",
|
||
"language": "Language",
|
||
"telegramBotLanguage": "Telegram Bot Language",
|
||
"security": {
|
||
"admin": "Admin credentials",
|
||
"twoFactor": "Two-factor authentication",
|
||
"twoFactorEnable": "Enable 2FA",
|
||
"twoFactorEnableDesc": "Adds an additional layer of authentication to provide more security.",
|
||
"twoFactorModalSetTitle": "Enable two-factor authentication",
|
||
"twoFactorModalDeleteTitle": "Disable two-factor authentication",
|
||
"twoFactorModalSteps": "To set up two-factor authentication, perform a few steps:",
|
||
"twoFactorModalFirstStep": "1. Scan this QR code in the app for authentication or copy the token near the QR code and paste it into the app",
|
||
"twoFactorModalSecondStep": "2. Enter the code from the app",
|
||
"twoFactorModalRemoveStep": "Enter the code from the application to remove two-factor authentication.",
|
||
"twoFactorModalChangeCredentialsTitle": "Change credentials",
|
||
"twoFactorModalChangeCredentialsStep": "Enter the code from the application to change administrator credentials.",
|
||
"twoFactorModalSetSuccess": "Two-factor authentication has been successfully established",
|
||
"twoFactorModalDeleteSuccess": "Two-factor authentication has been successfully deleted",
|
||
"twoFactorModalError": "Wrong code",
|
||
"show": "Show",
|
||
"hide": "Hide",
|
||
"apiTokenNew": "New token",
|
||
"apiTokenName": "Name",
|
||
"apiTokenNamePlaceholder": "e.g. central-panel-a",
|
||
"apiTokenNameRequired": "Name is required",
|
||
"apiTokenEmpty": "No tokens yet — create one to authenticate bots or remote panels.",
|
||
"apiTokenDeleteWarning": "Any caller using this token will stop authenticating immediately.",
|
||
"apiTokenCreatedTitle": "Token created",
|
||
"apiTokenCreatedNotice": "Copy this token now. For security it is not stored in readable form and will not be shown again."
|
||
},
|
||
"toasts": {
|
||
"modifySettings": "The parameters have been changed.",
|
||
"getSettings": "An error occurred while retrieving parameters.",
|
||
"modifyUserError": "An error occurred while changing administrator credentials.",
|
||
"modifyUser": "You have successfully changed the credentials of the administrator.",
|
||
"originalUserPassIncorrect": "The current username or password is invalid",
|
||
"userPassMustBeNotEmpty": "The new username and password are empty",
|
||
"getOutboundTrafficError": "Error getting traffic",
|
||
"resetOutboundTrafficError": "Error resetting outbound traffic"
|
||
},
|
||
"smtpSettings": "SMTP Settings",
|
||
"smtpEnable": "Enable Email Notifications",
|
||
"smtpEnableDesc": "Enable email notifications via SMTP",
|
||
"smtpHost": "SMTP Host",
|
||
"smtpHostDesc": "SMTP server hostname (e.g. smtp.gmail.com)",
|
||
"smtpPort": "SMTP Port",
|
||
"smtpPortDesc": "SMTP server port (default: 587)",
|
||
"smtpUsername": "SMTP Username",
|
||
"smtpUsernameDesc": "SMTP authentication username",
|
||
"smtpFrom": "SMTP From Address",
|
||
"smtpFromDesc": "Sender address used in the email From header. Leave empty to use the username.",
|
||
"smtpFromName": "SMTP Sender Name",
|
||
"smtpFromNameDesc": "Optional display name shown before the sender address in the From header.",
|
||
"smtpPassword": "SMTP Password",
|
||
"smtpPasswordDesc": "SMTP authentication password",
|
||
"smtpTo": "Recipients",
|
||
"smtpToDesc": "Comma-separated recipient email addresses",
|
||
"emailSettings": "Email",
|
||
"emailNotifications": "Notifications",
|
||
"smtpEventBusNotify": "Email Event Notifications",
|
||
"smtpEventBusNotifyDesc": "Select which events trigger email notifications",
|
||
"tgEventBusNotify": "Telegram Event Notifications",
|
||
"tgEventBusNotifyDesc": "Select which events trigger Telegram notifications",
|
||
"testSmtp": "Send Test Email",
|
||
"testTgBot": "Send Test Message",
|
||
"eventGroupOutbound": "Outbound",
|
||
"eventGroupXray": "Xray Core",
|
||
"eventGroupSystem": "System",
|
||
"eventGroupSecurity": "Security",
|
||
"eventGroupNode": "Nodes",
|
||
"eventOutboundDown": "Down",
|
||
"eventOutboundUp": "Up",
|
||
"eventXrayCrash": "Crash",
|
||
"eventNodeDown": "Down",
|
||
"eventNodeUp": "Up",
|
||
"eventCPUHigh": "CPU high (%)",
|
||
"requestFailed": "Request failed",
|
||
"smtpEncryption": "Encryption",
|
||
"smtpEncryptionDesc": "SMTP connection encryption method",
|
||
"smtpEncryptionNone": "None (plain text)",
|
||
"smtpEncryptionStartTLS": "STARTTLS",
|
||
"smtpEncryptionTLS": "TLS (implicit)",
|
||
"smtpStageConnect": "Connection",
|
||
"smtpStageAuth": "Authentication",
|
||
"smtpStageSend": "Send",
|
||
"smtpTestSuccess": "Test email sent successfully",
|
||
"smtpHostNotConfigured": "SMTP host not configured",
|
||
"smtpNoRecipients": "No recipients configured",
|
||
"smtpFromNotConfigured": "SMTP sender address not configured",
|
||
"eventLoginAttempt": "Login attempt",
|
||
"telegramTokenConfigured": "Configured; leave blank to keep current token.",
|
||
"telegramTokenPlaceholder": "Configured - enter a new token to replace",
|
||
"smtpPasswordConfigured": "Configured; leave blank to keep current password.",
|
||
"smtpPasswordPlaceholder": "Configured - enter a new password to replace",
|
||
"smtpNotInitialized": "SMTP not initialized",
|
||
"tgBotNotEnabled": "Telegram bot is not enabled",
|
||
"tgTestFailed": "Telegram test failed",
|
||
"tgTestSuccess": "Test message sent to Telegram",
|
||
"tgBotNotRunning": "Telegram bot not running",
|
||
"smtpErrorAuth": "Authentication failed — check username and password",
|
||
"smtpErrorStarttls": "Server requires STARTTLS — change encryption type",
|
||
"smtpErrorTls": "Server requires TLS — change encryption type",
|
||
"smtpErrorRefused": "Connection refused — check host and port",
|
||
"smtpErrorTimeout": "Connection timeout — host unreachable",
|
||
"smtpErrorRelay": "Server rejects sending from this address",
|
||
"smtpErrorEof": "Connection closed by server",
|
||
"smtpErrorUnknown": "SMTP error: {{ .Error }}",
|
||
"eventMemoryHigh": "Memory high (%)",
|
||
"validation": {
|
||
"pathLeadingSlash": "Path must start with /"
|
||
},
|
||
"secretClear": "Clear",
|
||
"secretClearUndo": "Undo clear"
|
||
},
|
||
"xray": {
|
||
"title": "Xray Configs",
|
||
"save": "Save",
|
||
"restart": "Restart Xray",
|
||
"restartSuccess": "Xray has been successfully relaunched.",
|
||
"restartOutputTitle": "Xray restart output",
|
||
"restartConfirmTitle": "Restart xray?",
|
||
"restartConfirmContent": "Reloads the xray service with the saved configuration.",
|
||
"stopSuccess": "Xray has been successfully stopped.",
|
||
"restartError": "There was an error when rebooting the Xray.",
|
||
"stopError": "There was an error when stopping the Xray.",
|
||
"basicTemplate": "Basics",
|
||
"advancedTemplate": "Advanced",
|
||
"generalConfigs": "General",
|
||
"generalConfigsDesc": "These options will determine general adjustments.",
|
||
"logConfigs": "Log",
|
||
"logConfigsDesc": "Logs may affect your server's efficiency. It is recommended to enable them wisely only when needed.",
|
||
"blockConfigsDesc": "These options will block traffic based on specific requested protocols and websites.",
|
||
"basicRouting": "Basic Routing",
|
||
"blockConnectionsConfigsDesc": "These options will block traffic based on the specific requested country.",
|
||
"directConnectionsConfigsDesc": "A direct connection ensures that specific traffic is not routed through another server.",
|
||
"blockips": "Block IPs",
|
||
"blockdomains": "Block Domains",
|
||
"directips": "Direct IPs",
|
||
"directdomains": "Direct Domains",
|
||
"ipv4Routing": "IPv4 Routing",
|
||
"ipv4RoutingDesc": "These options will route traffic based on a specific destination via IPv4.",
|
||
"warpRouting": "WARP Routing",
|
||
"warpRoutingDesc": "These options will route traffic based on a specific destination via WARP.",
|
||
"nordRouting": "NordVPN Routing",
|
||
"nordRoutingDesc": "These options will route traffic based on a specific destination via NordVPN.",
|
||
"Template": "Advanced Xray Configuration Template",
|
||
"TemplateDesc": "The final Xray config file will be generated based on this template.",
|
||
"FreedomStrategy": "Freedom Protocol Strategy",
|
||
"FreedomStrategyDesc": "Set the output strategy for the network in the Freedom Protocol.",
|
||
"FreedomHappyEyeballs": "Freedom Happy Eyeballs (IPv4/IPv6)",
|
||
"FreedomHappyEyeballsDesc": "Dual-stack dialing for the direct (freedom) outbound — useful on exit servers with both IPv4 and IPv6.",
|
||
"FreedomHappyEyeballsTryDelayDesc": "Milliseconds before trying the alternate address family. 150–250 ms is a good starting point.",
|
||
"RoutingStrategy": "Overall Routing Strategy",
|
||
"RoutingStrategyDesc": "Set the overall traffic routing strategy for resolving all requests.",
|
||
"outboundTestUrl": "Outbound Test URL",
|
||
"outboundTestUrlDesc": "URL used when testing outbound connectivity.",
|
||
"Torrent": "Block BitTorrent Protocol",
|
||
"Inbounds": "Inbounds",
|
||
"InboundsDesc": "Accepting the specific clients.",
|
||
"Outbounds": "Outbounds",
|
||
"OutboundSubscriptions": "Outbound Subscriptions",
|
||
"OutboundSubscriptionsDesc": "Import outbounds from remote subscription URLs (vmess/vless/trojan/ss/...). Tags are kept stable for use in balancers and routing rules. Updates are automatic.",
|
||
"Balancers": "Balancers",
|
||
"balancerTagRequired": "Tag is required",
|
||
"balancerSelectorRequired": "Pick at least one outbound",
|
||
"balancerLive": "Live Target",
|
||
"balancerOverride": "Override",
|
||
"balancerOverridePh": "Auto (strategy)",
|
||
"balancerLiveRefresh": "Refresh live balancer state",
|
||
"balancerNotRunning": "This balancer is not active in the running Xray — save your changes or start Xray first",
|
||
"routeTester": "Route Tester",
|
||
"routeTesterDesc": "Ask the running Xray which outbound would handle a connection. No traffic is sent — the decision comes straight from the live routing engine.",
|
||
"routeTesterDest": "Domain or IP",
|
||
"routeTesterPort": "Port",
|
||
"routeTesterInbound": "Inbound",
|
||
"routeTesterProtocol": "Sniffed protocol",
|
||
"routeTesterTest": "Test Route",
|
||
"routeTesterMatchedOutbound": "Matched outbound",
|
||
"routeTesterViaBalancer": "via balancer",
|
||
"routeTesterDefaultOutbound": "No routing rule matched — traffic goes to the default (first) outbound.",
|
||
"OutboundsDesc": "Set the outgoing traffic pathway.",
|
||
"Routings": "Routing Rules",
|
||
"RoutingsDesc": "The priority of each rule is important!",
|
||
"importRules": "Import Rules",
|
||
"exportRules": "Export Rules",
|
||
"importOutbounds": "Import Outbounds",
|
||
"exportOutbounds": "Export Outbounds",
|
||
"importInvalidJson": "Invalid JSON — expected an array or an object with a matching key.",
|
||
"completeTemplate": "All",
|
||
"logLevel": "Log Level",
|
||
"logLevelDesc": "The log level for error logs, indicating the information that needs to be recorded.",
|
||
"accessLog": "Access Log",
|
||
"accessLogDesc": "The file path for the access log. The special value 'none' disables access logs",
|
||
"errorLog": "Error Log",
|
||
"errorLogDesc": "The file path for the error log. The special value 'none' disables error logs",
|
||
"dnsLog": "DNS Log",
|
||
"dnsLogDesc": "Whether to enable DNS query logs",
|
||
"maskAddress": "Mask Address",
|
||
"maskAddressDesc": "IP address mask, when enabled, will automatically replace the IP address that appears in the log.",
|
||
"statistics": "Statistics",
|
||
"statsInboundUplink": "Inbound Upload Statistics",
|
||
"statsInboundUplinkDesc": "Enables the statistics collection for upstream traffic of all inbound proxies.",
|
||
"statsInboundDownlink": "Inbound Download Statistics",
|
||
"statsInboundDownlinkDesc": "Enables the statistics collection for downstream traffic of all inbound proxies.",
|
||
"statsOutboundUplink": "Outbound Upload Statistics",
|
||
"statsOutboundUplinkDesc": "Enables the statistics collection for upstream traffic of all outbound proxies.",
|
||
"statsOutboundDownlink": "Outbound Download Statistics",
|
||
"statsOutboundDownlinkDesc": "Enables the statistics collection for downstream traffic of all outbound proxies.",
|
||
"metricsListen": "Metrics Endpoint",
|
||
"metricsListenDesc": "Expose Xray's Prometheus-style metrics on this address:port (e.g. 127.0.0.1:11111). Leave empty to disable. Bind to localhost and reverse-proxy it — it is unauthenticated.",
|
||
"metricsTag": "Metrics Tag",
|
||
"connectionLimits": "Connection Limits",
|
||
"connectionLimitsDesc": "Connection-level policies for user level 0. Leave a field empty to use Xray's default.",
|
||
"connIdle": "Idle Timeout",
|
||
"connIdleDesc": "Closes a connection after it stays idle for this many seconds. Lowering it frees memory and file descriptors faster on busy servers (Xray default: 300).",
|
||
"bufferSize": "Buffer Size",
|
||
"bufferSizeDesc": "Per-connection internal buffer size in KB. Set to 0 to minimize memory usage on low-RAM servers (Xray default depends on the platform).",
|
||
"bufferSizePlaceholder": "auto",
|
||
"seconds": "seconds",
|
||
"rules": {
|
||
"first": "First",
|
||
"last": "Last",
|
||
"up": "Up",
|
||
"down": "Down",
|
||
"source": "Source",
|
||
"dest": "Destination",
|
||
"inbound": "Inbound",
|
||
"outbound": "Outbound",
|
||
"balancer": "Balancer",
|
||
"info": "Info",
|
||
"add": "Add Rule",
|
||
"edit": "Edit Rule",
|
||
"useComma": "Comma-separated list"
|
||
},
|
||
"routing": {
|
||
"dragToReorder": "Drag to reorder"
|
||
},
|
||
"ruleForm": {
|
||
"sourceIps": "Source IPs",
|
||
"sourcePort": "Source port",
|
||
"vlessRoute": "VLESS route",
|
||
"attributes": "Attributes",
|
||
"value": "Value",
|
||
"user": "User",
|
||
"inboundTags": "Inbound tags",
|
||
"outboundTag": "Outbound tag",
|
||
"balancerTag": "Balancer tag",
|
||
"balancerTagTooltip": "Routes traffic through one of the configured load balancers"
|
||
},
|
||
"outboundForm": {
|
||
"tagDuplicate": "Tag already used by another outbound",
|
||
"tagRequired": "Tag is required",
|
||
"tagPlaceholder": "unique-tag",
|
||
"localIpPlaceholder": "local IP",
|
||
"dialerProxyPlaceholder": "Select an outbound to chain through",
|
||
"dialerProxyHint": "Dial this outbound through another outbound (by tag) to build a proxy chain. Leave empty to connect directly.",
|
||
"targetStrategyHint": "How the destination domain is resolved before connecting: AsIs (default) sends it unresolved, UseIP… resolves with fallback, ForceIP… requires successful resolution.",
|
||
"addressRequired": "Address is required",
|
||
"portRequired": "Port is required",
|
||
"optional": "optional",
|
||
"udpOverTcp": "UDP over TCP",
|
||
"uotVersion": "UoT version",
|
||
"inboundTag": "Inbound tag",
|
||
"inboundTagPlaceholder": "inbound tag used in routing rules",
|
||
"responseType": "Response type",
|
||
"rewriteNetwork": "Rewrite network",
|
||
"unchanged": "(unchanged)",
|
||
"unchangedAddress": "(unchanged) e.g. 1.1.1.1",
|
||
"rules": "Rules",
|
||
"ruleN": "Rule {n}",
|
||
"action": "Action",
|
||
"redirect": "Redirect",
|
||
"fragment": "Fragment",
|
||
"finalRules": "Final Rules",
|
||
"overrideXrayPrivateIp": "Override Xray's default private-IP block",
|
||
"blockDelay": "Block delay (ms)",
|
||
"reverseSniffing": "Reverse Sniffing",
|
||
"reserved": "Reserved",
|
||
"minUploadInterval": "Min upload interval (ms)",
|
||
"maxUploadSizeBytes": "Max upload size (bytes)",
|
||
"uplinkChunkSize": "Uplink chunk size",
|
||
"noGrpcHeader": "No gRPC header",
|
||
"maxConcurrency": "Max concurrency",
|
||
"maxConnections": "Max connections",
|
||
"maxReuseTimes": "Max reuse times",
|
||
"maxRequestTimes": "Max request times",
|
||
"maxReusableSecs": "Max reusable secs",
|
||
"keepAlivePeriod": "Keep alive period",
|
||
"authPassword": "Auth password",
|
||
"visionTestpre": "Vision testpre",
|
||
"serverNamePlaceholder": "server name",
|
||
"verifyPeerName": "Verify peer name",
|
||
"pinnedSha256": "Pinned SHA256",
|
||
"shortId": "Short ID",
|
||
"sockopts": "Sockopts",
|
||
"keepAliveInterval": "Keep alive interval",
|
||
"markFwmark": "Mark (fwmark)",
|
||
"interface": "Interface",
|
||
"ipv6Only": "IPv6 only",
|
||
"acceptProxyProtocol": "Accept proxy protocol",
|
||
"proxyProtocol": "Proxy protocol",
|
||
"tcpUserTimeoutMs": "TCP user timeout (ms)",
|
||
"tcpKeepAliveIdleS": "TCP keep-alive idle (s)"
|
||
},
|
||
"outbound": {
|
||
"addOutbound": "Add Outbound",
|
||
"addReverse": "Add Reverse",
|
||
"editOutbound": "Edit Outbound",
|
||
"editReverse": "Edit Reverse",
|
||
"reverseTag": "Reverse Tag",
|
||
"reverseTagDesc": "VLESS simple reverse proxy tag. Leave empty to disable.",
|
||
"reverseTagPlaceholder": "reverse tag (leave empty to disable)",
|
||
"tag": "Tag",
|
||
"tagDesc": "Unique Tag",
|
||
"address": "Address",
|
||
"egress": "Egress",
|
||
"egressHint": "Run an HTTP test to show egress IP and country.",
|
||
"reverse": "Reverse",
|
||
"domain": "Domain",
|
||
"type": "Type",
|
||
"bridge": "Bridge",
|
||
"portal": "Portal",
|
||
"link": "Link",
|
||
"intercon": "Interconnection",
|
||
"settings": "Settings",
|
||
"accountInfo": "Account Information",
|
||
"outboundStatus": "Outbound Status",
|
||
"sendThrough": "Send Through",
|
||
"targetStrategy": "Target Strategy",
|
||
"test": "Test",
|
||
"testResult": "Test Result",
|
||
"testing": "Testing connection...",
|
||
"testSuccess": "Test successful",
|
||
"testFailed": "Test failed",
|
||
"testError": "Failed to test outbound",
|
||
"modeRealDelay": "Real delay",
|
||
"testModeTooltip": "TCP: fast dial-only probe. HTTP: full request through xray. Real delay: total time including connection setup.",
|
||
"testAll": "Test all",
|
||
"httpStatus": "HTTP status",
|
||
"breakdownConnect": "Proxy connect",
|
||
"breakdownTls": "TLS via outbound",
|
||
"breakdownTtfb": "First byte",
|
||
"nordvpn": "NordVPN",
|
||
"accessToken": "Access Token",
|
||
"country": "Country",
|
||
"server": "Server",
|
||
"city": "City",
|
||
"allCities": "All Cities",
|
||
"privateKey": "Private Key",
|
||
"load": "Load",
|
||
"moveToTop": "Move to top"
|
||
},
|
||
"outboundSub": {
|
||
"manage": "Subscriptions",
|
||
"title": "Outbound Subscriptions",
|
||
"remark": "Remark (optional)",
|
||
"remarkPlaceholder": "e.g. HK nodes",
|
||
"url": "Subscription URL",
|
||
"urlPlaceholder": "https://... (base64 list of links)",
|
||
"tagPrefix": "Tag prefix",
|
||
"tagPrefixPlaceholder": "hk-",
|
||
"interval": "Update interval",
|
||
"hours": "h",
|
||
"minutes": "min",
|
||
"intervalHint": "Default 10 minutes. The background job checks frequently; each subscription only re-fetches when its own interval has passed.",
|
||
"enabled": "Enabled",
|
||
"allowPrivate": "Allow private address",
|
||
"allowPrivateHint": "Permit localhost / LAN / private IPs for this subscription's URL. Off by default for security — enable only for a trusted local source.",
|
||
"prepend": "Before manual outbounds",
|
||
"prependHint": "Place this subscription's outbounds before your manual ones, so one can become the default.",
|
||
"preview": "Preview",
|
||
"previewEmpty": "No outbounds found at this URL.",
|
||
"refreshAll": "Refresh all",
|
||
"statusOk": "OK",
|
||
"toastUpdated": "Subscription updated",
|
||
"addButton": "Add",
|
||
"active": "Active subscriptions",
|
||
"empty": "No subscriptions yet. Add one above.",
|
||
"colRemark": "Remark",
|
||
"colPrefix": "Prefix",
|
||
"colInterval": "Interval",
|
||
"colLastFetch": "Last fetch",
|
||
"colEnabled": "Enabled",
|
||
"auto": "auto",
|
||
"never": "never",
|
||
"yes": "Yes",
|
||
"no": "No",
|
||
"refreshNow": "Refresh now",
|
||
"lastError": "Last error",
|
||
"deleteConfirm": "Delete this subscription?",
|
||
"restartHint": "After adding or refreshing, restart Xray (or wait for the next auto-reload) to make the outbounds active.",
|
||
"fromSubsTitle": "From outbound subscriptions (read-only)",
|
||
"fromSubsDesc": "Imported from your active subscriptions. Manage them in the Subscriptions panel above.",
|
||
"toastLoadFailed": "Failed to load subscriptions",
|
||
"toastUrlRequired": "Subscription URL is required",
|
||
"toastAdded": "Subscription added",
|
||
"toastAddFailed": "Failed to add subscription",
|
||
"toastRefreshed": "Refreshed",
|
||
"toastRefreshFailed": "Refresh failed",
|
||
"toastDeleted": "Deleted",
|
||
"toastDeleteFailed": "Delete failed"
|
||
},
|
||
"tabBalancerSettings": "Balancer Settings",
|
||
"tabObservatory": "Observatory",
|
||
"observatory": {
|
||
"title": "Observatory",
|
||
"burstTitle": "Burst Observatory",
|
||
"autoManaged": "Observers are managed automatically from your balancers. Tune how they probe below — the watched outbounds follow your balancer selectors.",
|
||
"emptyHint": "No connection observer is active. One is added automatically when you create a Least Ping or Least Load balancer — or a Random / Round-robin balancer with a fallback — so observer-backed balancers can check outbound health before choosing a target.",
|
||
"mixedLegacy": "This config contains both Observatory and Burst Observatory. Xray uses one global observer, so this mixed legacy state is not supported; saving balancers will normalize it to one observer.",
|
||
"subjectSelector": "Watched Outbounds",
|
||
"subjectSelectorDesc": "Outbound tags this observer probes. Managed automatically from your balancers.",
|
||
"probeURL": "Probe URL",
|
||
"probeURLDesc": "URL fetched to measure each outbound. Should return HTTP 204.",
|
||
"probeInterval": "Probe Interval",
|
||
"probeIntervalDesc": "How often to probe each outbound, e.g. 30s, 1m, 2h45m.",
|
||
"enableConcurrency": "Concurrent Probing",
|
||
"enableConcurrencyDesc": "Probe all watched outbounds at once instead of one-by-one. Faster, but more visible on the network.",
|
||
"destination": "Probe Destination",
|
||
"destinationDesc": "URL fetched to measure each outbound. Should return HTTP 204.",
|
||
"connectivity": "Connectivity Check",
|
||
"connectivityDesc": "Optional local-network check URL, tried only after the destination fails. Leave empty to skip.",
|
||
"interval": "Probe Interval",
|
||
"intervalDesc": "Average time between probes per outbound, e.g. 1m. Minimum 10s.",
|
||
"timeout": "Probe Timeout",
|
||
"timeoutDesc": "How long to wait for a probe before it counts as failed, e.g. 5s.",
|
||
"sampling": "Sampling Count",
|
||
"samplingDesc": "Number of recent probe results kept to score each outbound.",
|
||
"httpMethod": "HTTP Method",
|
||
"httpMethodDesc": "HTTP method used for probes.",
|
||
"deleteAlsoObservatory": "This is the last balancer using the Observatory, so it will be removed too.",
|
||
"deleteAlsoBurst": "This is the last balancer using the Burst Observatory, so it will be removed too."
|
||
},
|
||
"refCleanup": {
|
||
"header": "Deleting this also updates your routing:",
|
||
"ruleRemoved": "Rule {label} — removed (no destination left)",
|
||
"ruleModified": "Rule {label} — kept (now uses {keeps})",
|
||
"balancerRemoved": "Balancer {tag} — removed (no targets left)"
|
||
},
|
||
"balancer": {
|
||
"addBalancer": "Add Balancer",
|
||
"editBalancer": "Edit Balancer",
|
||
"balancerStrategy": "Strategy",
|
||
"balancerSelectors": "Selectors",
|
||
"tag": "Tag",
|
||
"tagDesc": "Unique Tag",
|
||
"tagDuplicate": "Tag already used by another balancer",
|
||
"tagPlaceholder": "unique balancer tag",
|
||
"selector": "Selector",
|
||
"fallback": "Fallback",
|
||
"fallbackBalancerHint": "Select another balancer as fallback",
|
||
"balancerFallbackInfo": "Traffic will be routed through: Balancer → Loopback → Server → Target Balancer → Outbound. This adds an extra hop through the server, which may introduce slight delays.",
|
||
"balancerFallbackCycle": "Cannot set this balancer as fallback — it would create a circular dependency.",
|
||
"balancerDeleteInUse": "Cannot delete this balancer — it is used as fallback by: {names}",
|
||
"reservedPrefix": "_bl_ prefix is reserved for internal balancer loopback objects",
|
||
"cycleTooltip": "Cycle: {path} → (back to {start})",
|
||
"expected": "Expected",
|
||
"expectedPlaceholder": "optimal node count",
|
||
"maxRtt": "Max RTT",
|
||
"tolerance": "Tolerance",
|
||
"baselines": "Baselines",
|
||
"costs": "Costs",
|
||
"balancerDesc": "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work.",
|
||
"costMatch": "Tag pattern",
|
||
"costValue": "Weight",
|
||
"costRegexp": "Regular expression match"
|
||
},
|
||
"wireguard": {
|
||
"secretKey": "Secret Key",
|
||
"publicKey": "Public Key",
|
||
"allowedIPs": "Allowed IPs",
|
||
"endpoint": "Endpoint",
|
||
"psk": "PreShared Key",
|
||
"domainStrategy": "Domain Strategy"
|
||
},
|
||
"tun": {
|
||
"nameDesc": "The name of the TUN interface. Default is 'xray0'",
|
||
"mtuDesc": "Maximum Transmission Unit. The maximum size of data packets. Default is 1500",
|
||
"userLevel": "User Level",
|
||
"userLevelDesc": "All connections made through this inbound will use this user level. Default is 0"
|
||
},
|
||
"nord": {
|
||
"accessToken": "Access token",
|
||
"privateKey": "Private key",
|
||
"noServers": "No servers found for the selected country",
|
||
"noPublicKey": "Selected server does not advertise a NordLynx public key.",
|
||
"outboundAdded": "NordVPN outbound added",
|
||
"outboundUpdated": "NordVPN outbound updated"
|
||
},
|
||
"warp": {
|
||
"changeIp": "Change IP",
|
||
"changeIpSuccess": "WARP IP changed successfully!",
|
||
"autoUpdateIp": "Auto Update IP Address",
|
||
"intervalDays": "Interval (Days)",
|
||
"intervalDesc": "0 to disable. Changes IP address automatically.",
|
||
"licenseError": "Failed to set WARP license.",
|
||
"fetchFirst": "Fetch the WARP config first.",
|
||
"createAccount": "Create WARP account",
|
||
"accessToken": "Access token",
|
||
"deviceId": "Device ID",
|
||
"licenseKey": "License key",
|
||
"privateKey": "Private key",
|
||
"deleteAccount": "Delete account",
|
||
"settings": "Settings",
|
||
"licenseKeyLabel": "WARP / WARP+ license key",
|
||
"key": "Key",
|
||
"keyPlaceholder": "26-char WARP+ key",
|
||
"accountInfo": "Account info",
|
||
"deviceName": "Device name",
|
||
"deviceModel": "Device model",
|
||
"deviceEnabled": "Device enabled",
|
||
"accountType": "Account type",
|
||
"role": "Role",
|
||
"warpPlusData": "WARP+ data",
|
||
"quota": "Quota",
|
||
"usage": "Usage",
|
||
"addOutbound": "Add outbound"
|
||
},
|
||
"dns": {
|
||
"enable": "Enable DNS",
|
||
"enableDesc": "Enable built-in DNS server",
|
||
"tag": "DNS Inbound Tag",
|
||
"tagDesc": "This tag will be available as an Inbound tag in routing rules.",
|
||
"clientIp": "Client IP",
|
||
"clientIpDesc": "Used to notify the server of the specified IP location during DNS queries",
|
||
"disableCache": "Disable cache",
|
||
"disableCacheDesc": "Disables DNS caching",
|
||
"disableFallback": "Disable Fallback",
|
||
"disableFallbackDesc": "Disables fallback DNS queries",
|
||
"disableFallbackIfMatch": "Disable Fallback If Match",
|
||
"disableFallbackIfMatchDesc": "Disables fallback DNS queries when the matching domain list of the DNS server is hit",
|
||
"enableParallelQuery": "Enable Parallel Query",
|
||
"enableParallelQueryDesc": "Enable parallel DNS queries to multiple servers for faster resolution",
|
||
"strategy": "Query Strategy",
|
||
"strategyDesc": "Overall strategy to resolve domain names",
|
||
"add": "Add Server",
|
||
"edit": "Edit Server",
|
||
"domains": "Domains",
|
||
"expectIPs": "Expect IPs",
|
||
"unexpectIPs": "Unexpected IPs",
|
||
"useSystemHosts": "Use System Hosts",
|
||
"useSystemHostsDesc": "Use the operating system's hosts file",
|
||
"serveStale": "Serve Stale",
|
||
"serveStaleDesc": "Return expired cached results while refreshing in the background",
|
||
"serveExpiredTTL": "Serve Expired TTL",
|
||
"serveExpiredTTLDesc": "Validity (seconds) of stale cache entries; 0 = never expire",
|
||
"timeoutMs": "Timeout (ms)",
|
||
"skipFallback": "Skip Fallback",
|
||
"finalQuery": "Final Query",
|
||
"hosts": "Hosts",
|
||
"hostsAdd": "Add Host",
|
||
"hostsEmpty": "No host overrides defined",
|
||
"hostsDomain": "Domain (e.g. domain:example.com)",
|
||
"hostsValues": "IP or domain — type and press Enter",
|
||
"usePreset": "Use Preset",
|
||
"dnsPresetTitle": "DNS Presets",
|
||
"dnsPresetFamily": "Family",
|
||
"clearAll": "Delete All",
|
||
"clearAllTitle": "Delete all DNS servers?",
|
||
"clearAllConfirm": "This removes every DNS server from the list. This cannot be undone.",
|
||
"dnsLeakWarning": "DNS can leak through localhost, plain UDP/TCP, local-mode DoH/DoQ, fallback queries, or EDNS client IP. Use routed DoH, hosts pins, and disable fallback when privacy matters."
|
||
},
|
||
"fakedns": {
|
||
"add": "Add Fake DNS",
|
||
"edit": "Edit Fake DNS",
|
||
"ipPool": "IP Pool Subnet",
|
||
"poolSize": "Pool Size"
|
||
},
|
||
"defaultOutbound": "Default Outbound",
|
||
"defaultOutboundDesc": "Traffic that does not match any routing rule uses this outbound (Xray uses the first outbound in the list)."
|
||
}
|
||
},
|
||
"tgbot": {
|
||
"keyboardClosed": "❌ Custom keyboard closed!",
|
||
"noResult": "❗ No result!",
|
||
"noQuery": "❌ Query not found! Please use the command again!",
|
||
"wentWrong": "❌ Something went wrong!",
|
||
"noIpRecord": "❗ No IP Record!",
|
||
"noInbounds": "❗ No inbound found!",
|
||
"unlimited": "♾ Unlimited(Reset)",
|
||
"add": "Add",
|
||
"month": "Month",
|
||
"months": "Months",
|
||
"day": "Day",
|
||
"days": "Days",
|
||
"hours": "Hours",
|
||
"minutes": "Minutes",
|
||
"unknown": "Unknown",
|
||
"inbounds": "Inbounds",
|
||
"clients": "Clients",
|
||
"offline": "🔴 Offline",
|
||
"online": "🟢 Online",
|
||
"commands": {
|
||
"unknown": "❗ Unknown command.",
|
||
"pleaseChoose": "👇 Please choose:\r\n",
|
||
"help": "🤖 Welcome to this bot! It's designed to offer specific data from the web panel and allows you to make modifications as needed.\r\n\r\n",
|
||
"start": "👋 Hello <i>{{ .Firstname }}</i>.\r\n",
|
||
"welcome": "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n",
|
||
"status": "✅ Bot is OK!",
|
||
"usage": "❗ Please provide a text to search!",
|
||
"getID": "🆔 Your ID: <code>{{ .ID }}</code>",
|
||
"helpAdminCommands": "To restart Xray Core:\r\n<code>/restart</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>",
|
||
"helpClientCommands": "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>",
|
||
"restartUsage": "\r\n\r\n<code>/restart</code>",
|
||
"restartSuccess": "✅ Operation successful!",
|
||
"restartFailed": "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>.",
|
||
"xrayNotRunning": "❗ Xray Core is not running.",
|
||
"startDesc": "Show the main menu",
|
||
"helpDesc": "Bot help",
|
||
"statusDesc": "Check bot status",
|
||
"idDesc": "Show your Telegram ID",
|
||
"usageDesc": "Show client usage: /usage email",
|
||
"inboundDesc": "Search inbounds: /inbound remark (admin)",
|
||
"restartDesc": "Restart Xray core (admin)",
|
||
"clearallDesc": "Reset all clients' traffic (admin)"
|
||
},
|
||
"messages": {
|
||
"cpuThreshold": "CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%",
|
||
"selectUserFailed": "❌ Error in user selection!",
|
||
"userSaved": "✅ Telegram User saved.",
|
||
"loginSuccess": "✅ Logged in to the panel successfully.\r\n",
|
||
"loginFailed": "❗️Login attempt to the panel failed.\r\n",
|
||
"2faFailed": "2FA Failed",
|
||
"report": "🕰 Scheduled Reports: {{ .RunTime }}\r\n",
|
||
"datetime": "⏰ Date&Time: {{ .DateTime }}\r\n",
|
||
"hostname": "💻 Host: {{ .Hostname }}\r\n",
|
||
"version": "🚀 3X-UI Version: {{ .Version }}\r\n",
|
||
"xrayVersion": "📡 Xray Version: {{ .XrayVersion }}\r\n",
|
||
"ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n",
|
||
"ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n",
|
||
"ip": "🌐 IP: {{ .IP }}\r\n",
|
||
"ips": "🔢 IPs:\r\n{{ .IPs }}\r\n",
|
||
"serverUpTime": "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n",
|
||
"serverLoad": "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n",
|
||
"serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n",
|
||
"tcpCount": "🔹 TCP: {{ .Count }}\r\n",
|
||
"udpCount": "🔸 UDP: {{ .Count }}\r\n",
|
||
"traffic": "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n",
|
||
"xrayStatus": "ℹ️ Status: {{ .State }}\r\n",
|
||
"username": "👤 Username: {{ .Username }}\r\n",
|
||
"reason": "❗️ Reason: {{ .Reason }}\r\n",
|
||
"time": "⏰ Time: {{ .Time }}\r\n",
|
||
"inbound": "📍 Inbound: {{ .Remark }}\r\n",
|
||
"port": "🔌 Port: {{ .Port }}\r\n",
|
||
"expire": "📅 Expire Date: {{ .Time }}\r\n",
|
||
"expireIn": "📅 Expire In: {{ .Time }}\r\n",
|
||
"active": "💡 Active: {{ .Enable }}\r\n",
|
||
"enabled": "🚨 Enabled: {{ .Enable }}\r\n",
|
||
"online": "🌐 Connection status: {{ .Status }}\r\n",
|
||
"lastOnline": "🔙 Last online: {{ .Time }}\r\n",
|
||
"email": "📧 Email: {{ .Email }}\r\n",
|
||
"upload": "🔼 Upload: ↑{{ .Upload }}\r\n",
|
||
"download": "🔽 Download: ↓{{ .Download }}\r\n",
|
||
"total": "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n",
|
||
"TGUser": "👤 Telegram User: {{ .TelegramID }}\r\n",
|
||
"exhaustedMsg": "🚨 Exhausted {{ .Type }}:\r\n",
|
||
"exhaustedCount": "🚨 Exhausted {{ .Type }} count:\r\n",
|
||
"onlinesCount": "🌐 Online Clients: {{ .Count }}\r\n",
|
||
"disabled": "🛑 Disabled: {{ .Disabled }}\r\n",
|
||
"depleteSoon": "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n",
|
||
"backupTime": "🗄 Backup Time: {{ .Time }}\r\n",
|
||
"refreshedOn": "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n",
|
||
"yes": "✅ Yes",
|
||
"no": "❌ No",
|
||
"received_id": "🔑📥 ID updated.",
|
||
"received_password": "🔑📥 Password updated.",
|
||
"received_email": "📧📥 Email updated.",
|
||
"received_comment": "💬📥 Comment updated.",
|
||
"id_prompt": "🔑 Default ID: {{ .ClientId }}\n\nEnter your ID.",
|
||
"pass_prompt": "🔑 Default Password: {{ .ClientPassword }}\n\nEnter your password.",
|
||
"email_prompt": "📧 Default Email: {{ .ClientEmail }}\n\nEnter your email.",
|
||
"comment_prompt": "💬 Default Comment: {{ .ClientComment }}\n\nEnter your comment.",
|
||
"inbound_client_data_id": "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!",
|
||
"inbound_client_data_pass": "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!",
|
||
"cancel": "❌ Process Canceled! \n\nYou can /start again anytime. 🔄",
|
||
"error_add_client": "⚠️ Error:\n\n {{ .error }}",
|
||
"using_default_value": "Okay, I'll stick with the default value. 😊",
|
||
"incorrect_input": "Your input is not valid.\nThe phrases should be continuous without spaces.\nCorrect example: aaaaaa\nIncorrect example: aaa aaa 🚫",
|
||
"AreYouSure": "Are you sure? 🤔",
|
||
"SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Result: ✅ Success",
|
||
"FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]",
|
||
"FinishProcess": "🔚 Traffic reset process finished for all clients.",
|
||
"eventOutboundDown": "Outbound {{ .Tag }} is DOWN",
|
||
"eventOutboundUp": "Outbound {{ .Tag }} is UP",
|
||
"eventErrorDetail": "Error: {{ .Error }}",
|
||
"eventDelayDetail": "Delay: {{ .Delay }}ms",
|
||
"eventXrayCrash": "Xray CRASHED",
|
||
"eventXrayCrashError": "Error: {{ .Error }}",
|
||
"eventNodeDown": "Node {{ .Name }} is DOWN",
|
||
"eventNodeUp": "Node {{ .Name }} is UP",
|
||
"eventCPUHigh": "CPU high",
|
||
"eventCPUHighDetail": "CPU: {{ .Detail }}",
|
||
"eventLoginFallback": "Login failed from {{ .Source }}",
|
||
"memoryThreshold": "Memory Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%"
|
||
},
|
||
"buttons": {
|
||
"closeKeyboard": "❌ Close Keyboard",
|
||
"cancel": "❌ Cancel",
|
||
"cancelReset": "❌ Cancel Reset",
|
||
"cancelIpLimit": "❌ Cancel IP Limit",
|
||
"confirmResetTraffic": "✅ Confirm Reset Traffic?",
|
||
"confirmClearIps": "✅ Confirm Clear IPs?",
|
||
"confirmRemoveTGUser": "✅ Confirm Remove Telegram User?",
|
||
"confirmToggle": "✅ Confirm Enable/Disable User?",
|
||
"dbBackup": "Get DB Backup",
|
||
"serverUsage": "Server Usage",
|
||
"getInbounds": "Get Inbounds",
|
||
"depleteSoon": "Deplete Soon",
|
||
"clientUsage": "Get Usage",
|
||
"onlines": "Online Clients",
|
||
"commands": "Commands",
|
||
"refresh": "🔄 Refresh",
|
||
"clearIPs": "❌ Clear IPs",
|
||
"removeTGUser": "❌ Remove Telegram User",
|
||
"selectTGUser": "👤 Select Telegram User",
|
||
"selectOneTGUser": "👤 Select a Telegram User:",
|
||
"resetTraffic": "📈 Reset Traffic",
|
||
"resetExpire": "📅 Change Expiry Date",
|
||
"ipLog": "🔢 IP Log",
|
||
"ipLimit": "🔢 IP Limit",
|
||
"setTGUser": "👤 Set Telegram User",
|
||
"toggle": "🔘 Enable / Disable",
|
||
"custom": "🔢 Custom",
|
||
"confirmNumber": "✅ Confirm: {{ .Num }}",
|
||
"confirmNumberAdd": "✅ Confirm adding: {{ .Num }}",
|
||
"limitTraffic": "🚧 Traffic Limit",
|
||
"getBanLogs": "Get Ban Logs",
|
||
"allClients": "All Clients",
|
||
"addClient": "Add Client",
|
||
"submitDisable": "Submit As Disable ☑️",
|
||
"submitEnable": "Submit As Enable ✅",
|
||
"use_default": "🏷️ Use default",
|
||
"change_id": "⚙️🔑 ID",
|
||
"change_password": "⚙️🔑 Password",
|
||
"change_email": "⚙️📧 Email",
|
||
"change_comment": "⚙️💬 Comment",
|
||
"change_flow": "⚙️🚦 Flow",
|
||
"ResetAllTraffics": "Reset All Traffic",
|
||
"SortedTrafficUsageReport": "Sorted Traffic Usage Report"
|
||
},
|
||
"answers": {
|
||
"successfulOperation": "✅ Operation successful!",
|
||
"errorOperation": "❗ Error in operation.",
|
||
"getInboundsFailed": "❌ Failed to get inbounds.",
|
||
"getClientsFailed": "❌ Failed to get clients.",
|
||
"canceled": "❌ {{ .Email }}: Operation canceled.",
|
||
"clientRefreshSuccess": "✅ {{ .Email }}: Client refreshed successfully.",
|
||
"IpRefreshSuccess": "✅ {{ .Email }}: IPs refreshed successfully.",
|
||
"TGIdRefreshSuccess": "✅ {{ .Email }}: Client's Telegram User refreshed successfully.",
|
||
"resetTrafficSuccess": "✅ {{ .Email }}: Traffic reset successfully.",
|
||
"setTrafficLimitSuccess": "✅ {{ .Email }}: Traffic limit saved successfully.",
|
||
"expireResetSuccess": "✅ {{ .Email }}: Expire days reset successfully.",
|
||
"resetIpSuccess": "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully.",
|
||
"clearIpSuccess": "✅ {{ .Email }}: IPs cleared successfully.",
|
||
"getIpLog": "✅ {{ .Email }}: Get IP Log.",
|
||
"getUserInfo": "✅ {{ .Email }}: Get Telegram User Info.",
|
||
"removedTGUserSuccess": "✅ {{ .Email }}: Telegram User removed successfully.",
|
||
"enableSuccess": "✅ {{ .Email }}: Enabled successfully.",
|
||
"disableSuccess": "✅ {{ .Email }}: Disabled successfully.",
|
||
"askToAddUserId": "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ChatID in your configuration(s).\r\n\r\nYour ChatID: <code>{{ .TgUserID }}</code>",
|
||
"chooseClient": "Choose a Client for Inbound {{ .Inbound }}",
|
||
"chooseInbound": "Choose an Inbound"
|
||
}
|
||
},
|
||
"email": {
|
||
"subjectOutboundDown": "Outbound {{ .Tag }} is DOWN",
|
||
"subjectOutboundUp": "Outbound {{ .Tag }} is UP",
|
||
"subjectXrayCrash": "Xray CRASHED",
|
||
"subjectCPUHigh": "CPU high",
|
||
"subjectLoginSuccess": "Login successful",
|
||
"subjectLoginFailed": "Login failed",
|
||
"titleOutboundDown": "Outbound DOWN",
|
||
"titleOutboundUp": "Outbound UP",
|
||
"titleXrayCrash": "Xray CRASHED",
|
||
"titleCPUHigh": "CPU high",
|
||
"titleLoginSuccess": "Login successful",
|
||
"titleLoginFailed": "Login failed",
|
||
"labelStatus": "Status",
|
||
"labelOutbound": "Outbound",
|
||
"labelNode": "Node",
|
||
"labelError": "Error",
|
||
"labelDelay": "Delay",
|
||
"labelDetail": "Detail",
|
||
"labelUsername": "Username",
|
||
"labelIP": "IP",
|
||
"labelReason": "Reason",
|
||
"labelSource": "Source",
|
||
"labelTime": "Time",
|
||
"statusCrashed": "CRASHED",
|
||
"statusRunning": "Running",
|
||
"statusHigh": "HIGH",
|
||
"statusSuccess": "SUCCESS",
|
||
"statusFailed": "FAILED",
|
||
"statusDown": "DOWN",
|
||
"statusUp": "UP"
|
||
}
|
||
}
|