diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 6fd0a400..52faa90e 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -2655,13 +2655,13 @@
"close": "Close"
},
"configVersions": {
- "title": "Release versions",
+ "title": "Config versions",
"description": "Browse historical snapshots, preview pending diffs, and reactivate an older version when needed.",
"refresh": "Refresh",
- "cleanupOld": "Clean",
- "viewDiff": "Diff",
+ "cleanupOld": "Clean old versions",
+ "viewDiff": "View diff",
"forcePublish": "Force publish",
- "previewPublish": "Publish",
+ "previewPublish": "Preview and publish",
"emptyTitle": "No history",
"emptyDesc": "There are no published versions yet. Publish a config first.",
"colVersion": "Version",
diff --git a/frontend/messages/fragments/admin.en.json b/frontend/messages/fragments/admin.en.json
new file mode 100644
index 00000000..9f2453d9
--- /dev/null
+++ b/frontend/messages/fragments/admin.en.json
@@ -0,0 +1,141 @@
+{
+ "auth": {
+ "login": {
+ "needBind": "Your third-party account is not linked to a local account, and registration is closed. Sign in with an existing local account to bind it.",
+ "bindSuccess": "Account bound",
+ "checkingSession": "Checking sign-in status",
+ "checkingSessionDesc": "Please wait while we confirm your session...",
+ "redirecting": "Redirecting to the console...",
+ "verifyingCredentials": "Verifying credentials",
+ "establishingSession": "Please wait while we establish a secure session..."
+ }
+ },
+ "settings": {
+ "other": {
+ "groupBusiness": "Business menu",
+ "groupAdmin": "Admin menu",
+ "groupDocs": "Docs menu",
+ "descDashboard": "View multi-dimensional charts for edge requests, visitors, and traffic",
+ "descNodes": "Register and manage reverse-proxy edge node status",
+ "descProxyRoutes": "Configure and publish proxy routes, load balancing, and origin bindings",
+ "descWebsites": "Configure root domains and view linked website details",
+ "descCertificates": "Manage and distribute website SSL/TLS certificates",
+ "descDnsAccounts": "Configure Cloudflare/Alidns and other DNS provider API credentials",
+ "descOrigins": "Centrally maintain backend origin server groups",
+ "descWaf": "Configure WAF custom rules and rate limits",
+ "descIpGroups": "Manage allow/deny IP sets used by WAF rules",
+ "descPages": "Host and publish static site projects with automatic domain binding",
+ "descConfigVersions": "View rule snapshots and perform hot updates or rollbacks",
+ "descAccessLogs": "Browse real-time edge request access details",
+ "descPerformance": "Configure cache, image compression, and other acceleration policies",
+ "descUsers": "View and manage system users and their status",
+ "descTasks": "View and schedule asynchronous and cron tasks",
+ "descStorage": "Manage file storage and clean unused files",
+ "descDatabase": "Monitor the physical database, browse tables, and run interactive SQL",
+ "descPush": "Configure and send system notifications",
+ "descLogs": "View async task execution logs and system runtime status",
+ "descSystem": "Manage core system key-value configuration",
+ "descAdminSettings": "Configure captcha, email, and menu visibility",
+ "descUsageDocs": "Deployment and operations guide for developers"
+ },
+ "operation": {
+ "typeAvatar": "Avatar (avatar)",
+ "typeAttachment": "Attachment (attachment)",
+ "typeDoc": "Document (doc)",
+ "typeGeneric": "Generic (generic)",
+ "logRetentionTitle": "Log retention",
+ "logRetentionDesc": "Configure retention days for each log database. Expired logs are cleaned automatically after switching the log store.",
+ "days": "days",
+ "save": "Save",
+ "logRetentionUpdated": "Log retention updated",
+ "logRetentionUpdateFailed": "Failed to update log retention",
+ "retentionInvalid": "{label} must be an integer greater than or equal to 1",
+ "retentionPostgresDesc": "Retention days for access logs and observability metrics",
+ "retentionSqliteDesc": "SQLite log retention days",
+ "retentionClickhouseDesc": "ClickHouse log retention days"
+ }
+ },
+ "admin": {
+ "settings": {
+ "tabs": {
+ "openflare": "OpenFlare"
+ }
+ },
+ "files": {
+ "stats": {
+ "catImages": "Images",
+ "catVideos": "Videos",
+ "catAudio": "Audio",
+ "catDocuments": "Documents",
+ "catArchives": "Archives",
+ "catOthers": "Others",
+ "none": "None"
+ },
+ "list": {
+ "extensionFile": "{ext} file"
+ }
+ },
+ "tasks": {
+ "selectDate": "Select date",
+ "logPrimaryDb": "Log primary database",
+ "retentionDays": "Retention days",
+ "migrationStatus": "Migration status",
+ "migrating": "Migrating",
+ "idle": "Idle",
+ "selectLogDb": "Select log database",
+ "dbPostgresPrimary": "PostgreSQL (primary)",
+ "dbSqlitePrimary": "SQLite (primary)",
+ "dbClickhouse": "ClickHouse",
+ "taskTypeFilter": "Task type",
+ "executionRecord": "Execution record"
+ },
+ "push": {
+ "events": {
+ "taskCompleteTitle": "Task completed: {name}",
+ "taskCompleteContent": "Async task {{task_name}} completed. Status: {{task_status}}, duration: {{task_duration}} ms."
+ },
+ "settings": {
+ "fieldRequired": "{label} is required",
+ "configHelp": "{name} configuration help"
+ }
+ },
+ "database": {
+ "unknownError": "Unknown error",
+ "exportException": "Export failed",
+ "cache": {
+ "unknownError": "Unknown error",
+ "maxSizePlaceholder": "e.g. 100",
+ "ttlPlaceholder": "e.g. 60, 0 means never expire"
+ },
+ "sql": {
+ "unknownExecError": "Unknown execution error",
+ "clearEditor": "Clear editor",
+ "dragResize": "Drag to resize",
+ "execErrorTitle": "SQL execution error:"
+ },
+ "tableBrowser": {
+ "fetchTableFailed": "Failed to fetch data for table {table}",
+ "unknownError": "Unknown error"
+ }
+ },
+ "logs": {
+ "accessLogs": {
+ "copiedToClipboard": "{subject} copied to clipboard",
+ "noHeaderData": "No header data",
+ "accessId": "Access ID: {id}",
+ "copyUserAgent": "Copy User-Agent",
+ "copyHeaders": "Copy Headers"
+ },
+ "analytics": {
+ "fetchFailed": "Failed to fetch analytics",
+ "requestVolume": "Requests",
+ "requestCount": "Request count",
+ "countTimes": "{count} ({percent}%)"
+ },
+ "appLogs": {
+ "fetchFailed": "Failed to fetch logs",
+ "loadOlderFailed": "Failed to load older logs"
+ }
+ }
+ }
+}
diff --git a/frontend/messages/fragments/admin.zh-CN.json b/frontend/messages/fragments/admin.zh-CN.json
new file mode 100644
index 00000000..c10d1747
--- /dev/null
+++ b/frontend/messages/fragments/admin.zh-CN.json
@@ -0,0 +1,141 @@
+{
+ "auth": {
+ "login": {
+ "needBind": "您的第三方账号未绑定本地账号,系统已关闭注册。请登录已有本地账号进行绑定。",
+ "bindSuccess": "绑定成功",
+ "checkingSession": "正在检查登录状态",
+ "checkingSessionDesc": "请稍候,我们正在确认当前会话...",
+ "redirecting": "正在跳转至控制台...",
+ "verifyingCredentials": "正在验证凭据",
+ "establishingSession": "请稍候,我们正在为您建立安全会话..."
+ }
+ },
+ "settings": {
+ "other": {
+ "groupBusiness": "业务菜单",
+ "groupAdmin": "管理菜单",
+ "groupDocs": "文档菜单",
+ "descDashboard": "查看边缘节点请求、访客与流量等多维度图表看板",
+ "descNodes": "注册与管理反向代理边缘节点服务状态",
+ "descProxyRoutes": "配置和发布反代路由规则、负载均衡与源站绑定",
+ "descWebsites": "配置站点根域名并查看关联网站详情",
+ "descCertificates": "管理和下发网站 SSL/TLS 证书",
+ "descDnsAccounts": "配置 Cloudflare/Alidns 等 DNS 服务商 API 凭证",
+ "descOrigins": "集中维护和管理后端业务源站服务器组",
+ "descWaf": "配置 Web 应用防火墙自定义规则与频率限制",
+ "descIpGroups": "管理黑白名单 IP 集合,用于 WAF 规则过滤",
+ "descPages": "托管和发布静态网页项目,支持自动关联域名",
+ "descConfigVersions": "查看规则快照版本并进行配置热更新分发与回滚",
+ "descAccessLogs": "多维度实时浏览边缘节点请求访问明细",
+ "descPerformance": "配置缓存、图片压缩等页面性能加速策略",
+ "descUsers": "查看和管理系统用户列表及其状态",
+ "descTasks": "查看和调度系统异步及定时任务",
+ "descStorage": "管理系统文件存储和清理无用文件",
+ "descDatabase": "监控物理数据库状态、分页浏览表数据及交互式 SQL 查询",
+ "descPush": "配置和发送系统通知及推送消息",
+ "descLogs": "查看异步任务执行日志和系统运行情况",
+ "descSystem": "管理和维护系统基础键值对配置",
+ "descAdminSettings": "配置安全验证、邮箱服务及目录显示",
+ "descUsageDocs": "面向开发与运营的部署使用指南"
+ },
+ "operation": {
+ "typeAvatar": "头像 (avatar)",
+ "typeAttachment": "附件 (attachment)",
+ "typeDoc": "文档 (doc)",
+ "typeGeneric": "通用 (generic)",
+ "logRetentionTitle": "日志保留时间",
+ "logRetentionDesc": "配置各日志数据库的日志保留天数,切换日志数据库后自动按对应配置清理过期日志。",
+ "days": "天",
+ "save": "保存",
+ "logRetentionUpdated": "日志保留时间已更新",
+ "logRetentionUpdateFailed": "更新日志保留时间失败",
+ "retentionInvalid": "{label}必须为大于等于 1 的整数",
+ "retentionPostgresDesc": "访问日志与可观测指标统一保留天数",
+ "retentionSqliteDesc": "SQLite 日志保留天数",
+ "retentionClickhouseDesc": "ClickHouse 日志保留天数"
+ }
+ },
+ "admin": {
+ "settings": {
+ "tabs": {
+ "openflare": "OpenFlare"
+ }
+ },
+ "files": {
+ "stats": {
+ "catImages": "图片",
+ "catVideos": "视频",
+ "catAudio": "音频",
+ "catDocuments": "文档",
+ "catArchives": "压缩包",
+ "catOthers": "其他",
+ "none": "无"
+ },
+ "list": {
+ "extensionFile": "{ext} 文件"
+ }
+ },
+ "tasks": {
+ "selectDate": "选择日期",
+ "logPrimaryDb": "日志主库",
+ "retentionDays": "保留天数",
+ "migrationStatus": "迁移状态",
+ "migrating": "迁移中",
+ "idle": "空闲",
+ "selectLogDb": "选择日志数据库",
+ "dbPostgresPrimary": "PostgreSQL(主库)",
+ "dbSqlitePrimary": "SQLite(主库)",
+ "dbClickhouse": "ClickHouse",
+ "taskTypeFilter": "任务类型",
+ "executionRecord": "执行记录"
+ },
+ "push": {
+ "events": {
+ "taskCompleteTitle": "任务完成: {name}",
+ "taskCompleteContent": "异步任务 {{task_name}} 已完成。状态: {{task_status}},耗时: {{task_duration}} ms。"
+ },
+ "settings": {
+ "fieldRequired": "{label}不能为空",
+ "configHelp": "{name}配置说明"
+ }
+ },
+ "database": {
+ "unknownError": "未知错误",
+ "exportException": "导出异常",
+ "cache": {
+ "unknownError": "未知错误",
+ "maxSizePlaceholder": "例如 100",
+ "ttlPlaceholder": "例如 60,0 表示永不过期"
+ },
+ "sql": {
+ "unknownExecError": "未知执行错误",
+ "clearEditor": "清空编辑器",
+ "dragResize": "拖动调整大小",
+ "execErrorTitle": "SQL 执行报错 (Error):"
+ },
+ "tableBrowser": {
+ "fetchTableFailed": "获取数据表 {table} 数据失败",
+ "unknownError": "未知错误"
+ }
+ },
+ "logs": {
+ "accessLogs": {
+ "copiedToClipboard": "{subject}已复制到剪贴板",
+ "noHeaderData": "暂无头部数据",
+ "accessId": "访问 ID: {id}",
+ "copyUserAgent": "复制 User-Agent",
+ "copyHeaders": "复制 Headers"
+ },
+ "analytics": {
+ "fetchFailed": "获取数据统计失败",
+ "requestVolume": "请求量",
+ "requestCount": "请求次数",
+ "countTimes": "{count} 次 ({percent}%)"
+ },
+ "appLogs": {
+ "fetchFailed": "获取日志失败",
+ "loadOlderFailed": "加载更早日志失败"
+ }
+ }
+ }
+}
diff --git a/frontend/messages/fragments/layout.en.json b/frontend/messages/fragments/layout.en.json
new file mode 100644
index 00000000..661cf306
--- /dev/null
+++ b/frontend/messages/fragments/layout.en.json
@@ -0,0 +1,44 @@
+{
+ "layout": {
+ "user": {
+ "unknownUser": "Unknown user"
+ },
+ "header": {
+ "guest": "Guest"
+ },
+ "search": {
+ "items": {
+ "globalSettings": "Settings",
+ "appearance": "Appearance",
+ "homeDesc": "Return to the console dashboard",
+ "usageDocsDesc": "View getting-started guides and integration examples",
+ "nodesDesc": "Manage edge nodes, relays, and intranet tunnels",
+ "proxyRoutesDesc": "Configure reverse proxies, match rules, WAF policies, and cache settings",
+ "websitesDesc": "Manage hosted domains, certificate bindings, and listeners",
+ "certificatesDesc": "Issue and manage SSL/TLS certificates with auto-renewal",
+ "dnsAccountsDesc": "Configure DNS provider credentials for certificate issuance and records",
+ "originsDesc": "Manage reverse-proxy backends and load-balancing groups",
+ "responsesDesc": "Configure origin error pages and offline fallback contact pages",
+ "wafDesc": "Configure Web Application Firewall rules to block malicious requests",
+ "ipGroupsDesc": "Define IP lists for allow/deny control in WAF or routes",
+ "rateLimitsDesc": "Configure default concurrency and bandwidth limits for edge sites",
+ "pagesDesc": "Upload or deploy static sites with global CDN hosting",
+ "configVersionsDesc": "View, compare, publish, and roll back configuration versions",
+ "accessLogsDesc": "Search full-site access logs and network analytics",
+ "applyLogsDesc": "View history of node config delivery, sync, and activation",
+ "performanceDesc": "Tune connections, proxy timeouts, and core performance settings",
+ "settingsDesc": "Configure personal application preferences",
+ "profileDesc": "Edit nickname, avatar, and profile attributes",
+ "appearanceDesc": "Configure the display theme (light/dark)",
+ "adminSettingsDesc": "Manage login, registration, and auth sources (admin only)",
+ "systemDesc": "Change core platform runtime configuration (admin only)",
+ "usersDesc": "Manage registered users and their status (admin only)",
+ "tasksDesc": "Dispatch and inspect background scheduled tasks (admin only)",
+ "storageDesc": "Browse, search, and clean files in object storage (admin only)",
+ "databaseDesc": "Monitor table size, browse physical tables, and run interactive SQL (admin only)",
+ "pushDesc": "Configure email, Lark, and Telegram notification channels (admin only)",
+ "logsDesc": "View system logs and background task execution logs (admin only)"
+ }
+ }
+ }
+}
diff --git a/frontend/messages/fragments/layout.zh-CN.json b/frontend/messages/fragments/layout.zh-CN.json
new file mode 100644
index 00000000..ffab5f98
--- /dev/null
+++ b/frontend/messages/fragments/layout.zh-CN.json
@@ -0,0 +1,44 @@
+{
+ "layout": {
+ "user": {
+ "unknownUser": "未知用户"
+ },
+ "header": {
+ "guest": "访客"
+ },
+ "search": {
+ "items": {
+ "globalSettings": "全局设置",
+ "appearance": "外观设置",
+ "homeDesc": "返回控制台总览",
+ "usageDocsDesc": "查看新手教程和集成示例",
+ "nodesDesc": "管理边缘节点、中继节点与内网穿透通道",
+ "proxyRoutesDesc": "配置反向代理、路由匹配规则、WAF 策略与缓存设置",
+ "websitesDesc": "管理托管域名及证书绑定与监听配置",
+ "certificatesDesc": "申请与管理 SSL/TLS 证书,支持自动续期",
+ "dnsAccountsDesc": "配置 DNS 服务商 API 凭证以自动申请证书及管理解析",
+ "originsDesc": "管理反向代理的目标后端服务器与负载均衡组",
+ "responsesDesc": "配置源站错误页与离线兜底联系页",
+ "wafDesc": "配置 Web 应用防火墙规则,阻断恶意请求",
+ "ipGroupsDesc": "定义 IP 地址列表以在 WAF 或路由中实现黑白名单控制",
+ "rateLimitsDesc": "配置边缘站点默认并发与带宽限流策略",
+ "pagesDesc": "上传或部署静态网页,提供全球 CDN 加速托管",
+ "configVersionsDesc": "查看、对比、发布与回滚系统配置版本",
+ "accessLogsDesc": "查看并检索全量网站访问请求日志与网络分析数据",
+ "applyLogsDesc": "查看节点配置下发、同步与生效的历史记录",
+ "performanceDesc": "调优网络连接、代理超时与核心系统性能参数",
+ "settingsDesc": "配置应用个人偏好选项",
+ "profileDesc": "编辑昵称、头像和个人属性",
+ "appearanceDesc": "配置系统显示主题(亮色/暗色)",
+ "adminSettingsDesc": "管理系统登录注册与认证源配置 (管理员专属)",
+ "systemDesc": "动态修改平台核心运行时配置 (管理员专属)",
+ "usersDesc": "管理平台注册用户的活跃状态 (管理员专属)",
+ "tasksDesc": "下发与排查后台异步定时任务 (管理员专属)",
+ "storageDesc": "查看、检索与清理上传到对象存储中的文件 (管理员专属)",
+ "databaseDesc": "监控数据库表大小、分页浏览物理表内容并支持交互式 SQL (管理员专属)",
+ "pushDesc": "配置与下发邮件、Lark 和 Telegram 渠道通知推送 (管理员专属)",
+ "logsDesc": "查看系统日志与后台异步任务执行日志 (管理员专属)"
+ }
+ }
+ }
+}
diff --git a/frontend/messages/fragments/ops.en.json b/frontend/messages/fragments/ops.en.json
new file mode 100644
index 00000000..fed7b1d8
--- /dev/null
+++ b/frontend/messages/fragments/ops.en.json
@@ -0,0 +1,673 @@
+{
+ "dashboard": {
+ "title": "Overview",
+ "generatedAt": "Generated at {time}",
+ "refresh": "Refresh",
+ "loadingTitle": "Loading overview",
+ "loadingDesc": "Aggregating node health, traffic, and capacity metrics...",
+ "loadFailed": "Failed to load overview: {error}",
+ "emptyTitle": "No overview data",
+ "emptyDesc": "The system is running, but there is no aggregated overview to show yet.",
+ "requestFailed": "Request failed. Please try again later.",
+ "stats": {
+ "requests24h": "24h requests",
+ "requestsMeta": "24h unique visitors {visitors} · errors {errors} · estimated QPS {qps}",
+ "clusterCapacity": "Cluster capacity",
+ "avgMemoryHighLoad": "Avg memory {memory} · high-load nodes CPU {cpu} / memory {memoryNodes} / storage {storage}",
+ "nodeOverview": "Node overview",
+ "onlineOf": "/ {total} online",
+ "pending": "Pending {count}",
+ "unhealthyOffline": "Unhealthy {unhealthy} · offline {offline}"
+ },
+ "world": {
+ "title": "Global stage",
+ "description": "Summarizes node distribution, source-country heat, and recent runtime status.",
+ "legendSource": "Source",
+ "legendHealthy": "Healthy",
+ "legendPressure": "Under load",
+ "legendUnhealthy": "Unhealthy",
+ "modeVisitorSource": "Visitor sources",
+ "modeNodeCoords": "Node coordinates",
+ "modeCoverage": "Coverage signal",
+ "onlineCoverage": "Online coverage",
+ "onlineHint": "{online}/{total} online",
+ "runHealth": "Runtime health",
+ "unhealthyCount": "{count} unhealthy",
+ "requests24h": "24h requests",
+ "qpsReported": "QPS {qps} · {count} nodes reporting",
+ "nodeCoords": "Node coordinates",
+ "sourceCountries": "{count} source countries",
+ "fallbackPins": "Preset pins are used when coordinates are missing",
+ "avgCpu": "Avg CPU",
+ "highCount": "{count} high",
+ "avgMemory": "Avg memory",
+ "highStorage": "High storage",
+ "offlinePending": "{offline} offline · {pending} pending",
+ "mapPreparing": "Preparing map",
+ "mapPreparingDesc": "Loads when it enters the viewport",
+ "noNodesOverlay": "No nodes yet. Geographic distribution will appear after nodes join."
+ },
+ "map": {
+ "sourceRequests": "Source requests in the last 24 hours {count}",
+ "presetPin": "Preset pin",
+ "requestsErrors": "Requests {requests} · errors {errors}",
+ "eventsStatus": "Active events {events} · node status {status}",
+ "openrestyStatus": "OpenResty status {status}",
+ "loadFailed": "Failed to load world map",
+ "loading": "Loading world map",
+ "loadFailedDesc": "The ECharts world map failed to register. Refresh and try again.",
+ "loadingDesc": "Initializing the world map after the first screen is ready.",
+ "measuring": "Measuring map container size..."
+ },
+ "trafficTrend": {
+ "title": "24-hour request trend",
+ "description": "Hourly request totals and 2xx/4xx/5xx counts to spot abnormal status spikes.",
+ "summaryHint": "Last 24 hours",
+ "requests": "Requests",
+ "status2xx": "2xx requests",
+ "status4xx": "4xx requests",
+ "status5xx": "5xx requests"
+ },
+ "capacityTrend": {
+ "title": "24-hour capacity trend",
+ "description": "Hourly CPU and memory usage to see whether cluster capacity stays tight.",
+ "avgCpu": "Avg CPU",
+ "avgMemory": "Avg memory"
+ },
+ "trafficCapacity": {
+ "title": "24-hour traffic and capacity trend",
+ "networkHint": "Last 24 hours · from access logs",
+ "received": "Data received",
+ "provided": "Data served",
+ "capacityHint": "Last 24 hours · host capacity · average"
+ },
+ "distributions": {
+ "sourceTitle": "Source distribution",
+ "sourceDesc": "Top source countries over the last 24 hours.",
+ "sourceEmpty": "No source distribution data",
+ "statusTitle": "Status code distribution",
+ "statusDesc": "Check whether successful responses still dominate and whether errors are rising.",
+ "statusEmpty": "No status code distribution",
+ "httpLabel": "HTTP {code}",
+ "domainTitle": "Top Domain",
+ "domainDesc": "See which domains concentrate most of the traffic.",
+ "domainEmpty": "No domain distribution"
+ },
+ "nodeHealth": {
+ "title": "Node health",
+ "description": "Sorted by active incidents and resource pressure.",
+ "openNodes": "Open nodes",
+ "emptyTitle": "No nodes",
+ "emptyDesc": "After nodes join, this list will show health and capacity summaries.",
+ "colNode": "Node",
+ "colStatus": "Status",
+ "colCpuMem": "CPU / Memory",
+ "colReqErr": "Requests / Errors",
+ "colEvents": "Active incidents",
+ "colHeartbeat": "Last heartbeat",
+ "colActions": "Actions",
+ "noGeo": "Map point not configured",
+ "wsConnected": "WS connected",
+ "na": "N/A",
+ "detail": "Details"
+ },
+ "nodeRank": {
+ "title": "Top nodes",
+ "traffic": "Highest traffic",
+ "trafficEmpty": "No traffic ranking",
+ "pressure": "Highest capacity pressure",
+ "pressureEmpty": "No capacity pressure data"
+ }
+ },
+ "nodes": {
+ "title": "Nodes",
+ "applyLogs": "Apply logs",
+ "create": "Add node",
+ "listTitle": "Node list",
+ "refreshNow": "Refresh now",
+ "loadingList": "Loading nodes...",
+ "emptyAll": "No nodes yet. Create one to get started.",
+ "emptyFilter": "No nodes match the current filter",
+ "updated": "Node updated",
+ "created": "Node created",
+ "deleted": "Node deleted",
+ "deleteTitle": "Delete node",
+ "deleteDesc": "Delete node “{name}”? You will need to recreate and rejoin it.",
+ "deleting": "Deleting...",
+ "confirmDelete": "Delete",
+ "edit": "Edit",
+ "delete": "Delete",
+ "refresh": "Refresh",
+ "detail": "Details",
+ "na": "N/A",
+ "wsConnected": "WS connected",
+ "wsEstablished": "WebSocket connection established",
+ "requestFailed": "Request failed. Please try again later.",
+ "unknown": "Unknown",
+ "running": "Running",
+ "liveConfig": "Live config",
+ "notApplied": "Not applied",
+ "enabled": "Enabled",
+ "manual": "Manual",
+ "lockedSuffix": " (locked)",
+ "noGeo": "Map point not configured",
+ "notConfigured": "Not configured",
+ "ipLine": "IP: {ip}",
+ "locationLine": "Location: {location}",
+ "backToList": "Back to nodes",
+ "missingId": "Missing node ID. Open details from the node list.",
+ "loadingDetail": "Loading node details...",
+ "notFound": "Node not found. It may have been deleted or the ID is invalid.",
+ "syncing": "Syncing...",
+ "forceSync": "Force sync",
+ "dispatching": "Dispatching...",
+ "viewUpgrade": "View upgrade",
+ "tabOverview": "Overview",
+ "tabManage": "Status & deploy",
+ "kpiNodeId": "Node ID",
+ "kpiLastSeen": "Last heartbeat",
+ "kpiCurrentConfig": "Current config",
+ "filter": {
+ "all": "All nodes",
+ "descAll": "Showing all nodes.",
+ "descRelay": "Showing Relay nodes only.",
+ "descTunnel": "Showing Tunnel nodes only.",
+ "descEdge": "Showing Edge nodes only."
+ },
+ "status": {
+ "online": "Online",
+ "pending": "Pending",
+ "offline": "Offline",
+ "running": "Running",
+ "wsConnected": "WS connected"
+ },
+ "apply": {
+ "success": "Success",
+ "warning": "Warning",
+ "failed": "Failed",
+ "none": "N/A"
+ },
+ "health": {
+ "healthy": "Healthy",
+ "unhealthy": "Unhealthy",
+ "unknown": "Unknown"
+ },
+ "relative": {
+ "justNow": "Just now",
+ "minutesAgo": "{count} minutes ago",
+ "hoursAgo": "{count} hours ago",
+ "daysAgo": "{count} days ago",
+ "monthsAgo": "{count} months ago"
+ },
+ "uptime": {
+ "daysHours": "{days}d {hours}h",
+ "hoursMinutes": "{hours}h {minutes}m",
+ "minutes": "{minutes}m"
+ },
+ "channel": {
+ "preview": "preview",
+ "stable": "stable"
+ },
+ "table": {
+ "node": "Node",
+ "status": "Status",
+ "version": "Version",
+ "runHealth": "Runtime health",
+ "currentVersion": "Current version",
+ "latestApply": "Latest apply",
+ "lastSeen": "Last heartbeat",
+ "actions": "Actions"
+ },
+ "editor": {
+ "editTitle": "Edit node",
+ "createTitle": "Add node",
+ "description": "After creating a node, open its details for a dedicated token and deploy command.",
+ "nodeType": "Node type",
+ "typeEdge": "Edge node",
+ "typeRelay": "Relay node",
+ "typeTunnel": "Tunnel node",
+ "name": "Node name",
+ "namePlaceholder": "e.g. edge-hk-01",
+ "ip": "Node IP",
+ "ipPlaceholder": "Optional; reported automatically after join",
+ "lockIp": "Lock IP",
+ "lockIpDesc": "When enabled, the control plane will not overwrite the IP",
+ "autoUpdate": "Auto-update Agent",
+ "autoUpdateDesc": "When enabled, the node pulls stable updates automatically",
+ "bindPort": "Bind port",
+ "vhostPort": "VHost port",
+ "manualGeo": "Manual map point",
+ "manualGeoDesc": "Used on the overview map",
+ "geoName": "Location name",
+ "latitude": "Latitude",
+ "longitude": "Longitude",
+ "saving": "Saving...",
+ "saveEdit": "Save changes",
+ "createAction": "Add node",
+ "errName": "Enter a node name",
+ "errLockIp": "A node IP is required when IP is locked",
+ "errBindPort": "Enter a valid bind port",
+ "errVhostPort": "Enter a valid VHost port"
+ },
+ "install": {
+ "edgeTitle": "Edge node deploy",
+ "edgeDesc": "Use the Agent Token to join an edge node to the control plane.",
+ "relayTitle": "Relay deploy command",
+ "relayDesc": "Use the Discovery Token to join an frps relay node to the control plane.",
+ "tunnelTitle": "Tunnel deploy command",
+ "tunnelDesc": "Use the Tunnel Token to join an openflared client to the control plane.",
+ "agentToken": "Agent Token",
+ "discoveryToken": "Discovery Token",
+ "tunnelToken": "Tunnel Token",
+ "scriptLabel": "One-line script (Linux / macOS)",
+ "dockerLabel": "Docker container deploy",
+ "copyFailed": "Copy failed. Select the command text manually.",
+ "copiedScript": "Deploy script copied",
+ "copiedDocker": "Docker deploy command copied",
+ "copyScript": "Copy script",
+ "copyDocker": "Copy Docker",
+ "nodeId": "Node ID",
+ "serverUrl": "Server URL (control plane)",
+ "serverUrlHint": "The script and container will use this address to reach the OpenFlare control plane.",
+ "noToken": "This node has no access token yet. Refresh later.",
+ "needServerUrl": "Enter a valid Server URL to generate the deploy command."
+ },
+ "update": {
+ "title": "Agent upgrade",
+ "description": "Check GitHub releases and dispatch an upgrade. The node runs it after the next heartbeat.",
+ "currentVersion": "Current version",
+ "channel": "Release channel",
+ "updateStatus": "Update status",
+ "waitingPreview": "Waiting for preview update",
+ "waitingUpdate": "Waiting for update",
+ "notRequested": "Not dispatched",
+ "checking": "Checking releases...",
+ "hasUpdate": "Update available",
+ "upToDate": "Already up to date",
+ "previewRelease": "Preview release",
+ "stableRelease": "Stable release",
+ "targetVersion": "Target version",
+ "notFound": "Not found",
+ "publishedAt": "Published",
+ "noNotes": "No release notes",
+ "viewRelease": "View release",
+ "checkStable": "Check stable",
+ "checkPreview": "Check preview",
+ "upgradePreview": "Upgrade to preview",
+ "upgradeStable": "Upgrade to stable"
+ },
+ "edge": {
+ "upgrade": "Upgrade Agent",
+ "restartOpenresty": "Restart OpenResty",
+ "runStatus": "Runtime status",
+ "runStatusDesc": "Node online state and OpenResty health",
+ "openrestyHealth": "OpenResty health",
+ "lastSeen": "Last heartbeat",
+ "ip": "IP address",
+ "geo": "Map point",
+ "syncTitle": "Config sync",
+ "syncDesc": "Version catch-up and latest apply result",
+ "currentVersion": "Current config version",
+ "latestApply": "Latest apply",
+ "latestApplyAt": "Latest apply time",
+ "agentVersion": "Agent version",
+ "nginxVersion": "Nginx version",
+ "autoUpdate": "Auto update",
+ "runtimeMessages": "Runtime messages",
+ "runtimeMessagesDesc": "Status notes reported by OpenResty and Agent",
+ "openrestyMessage": "OpenResty status message",
+ "noExtraError": "No extra error reported.",
+ "restartPending": "Waiting for the node to restart OpenResty after the next heartbeat.",
+ "kpiAgent": "Agent version",
+ "connectionHint": "Current OpenResty connections",
+ "toastUpdated": "Node updated",
+ "toastForceSync": "Force-sync dispatched to node {name}",
+ "toastRestart": "OpenResty restart dispatched to node {name}",
+ "toastUpgrade": "{channel} upgrade dispatched to node {name}",
+ "toastDeleted": "Node deleted"
+ },
+ "relay": {
+ "upgrade": "Upgrade Relay",
+ "runStatus": "Relay runtime",
+ "runStatusDesc": "frps online state and load summary",
+ "health": "Relay health",
+ "connections": "Active connections / proxies",
+ "portsTitle": "Network ports",
+ "portsDesc": "frps control plane and HTTP vhost settings",
+ "bindPort": "Control bind port",
+ "vhostPort": "VHost HTTP port",
+ "agentAddr": "Agent access address",
+ "relayVersion": "Relay version",
+ "frpsVersion": "frps core version",
+ "webuiTitle": "FRPS WebUI",
+ "webuiDesc": "Enable the built-in frps web console and set its listen port",
+ "enableWebui": "Enable web console",
+ "enableWebuiHint": "When on, the node starts the built-in frps admin service.",
+ "webuiPort": "Web console port",
+ "webuiPortPlaceholder": "e.g. 17500",
+ "actualPort": "Currently listening on {port} (takes effect on the next heartbeat; default is bind port + 500).",
+ "openWebui": "Open FRPS WebUI",
+ "copiedLink": "Link copied",
+ "copyLink": "Copy link",
+ "webuiDisabled": "WebUI is disabled or the bind port is not set.",
+ "metadata": "Node metadata",
+ "createdAt": "Created",
+ "updatedAt": "Updated",
+ "kpiFrps": "frps core",
+ "connectionHint": "Relay active connections",
+ "invalidPort": "Port must be between 1 and 65535",
+ "portSaveFailed": "Failed to update port",
+ "toastPortSaved": "FRPS WebUI port updated",
+ "toastUpdated": "Relay node updated",
+ "toastWebui": "FRPS WebUI setting updated",
+ "toastForceSync": "Force-sync dispatched to relay {name}",
+ "toastUpgrade": "{channel} upgrade dispatched to relay {name}",
+ "toastDeleted": "Relay node deleted",
+ "deleteTitle": "Delete relay node",
+ "deleteDesc": "Delete relay node “{name}”? You will need to recreate and rejoin it."
+ },
+ "tunnel": {
+ "upgrade": "Upgrade openflared",
+ "runStatus": "Tunnel runtime",
+ "runStatusDesc": "openflared connection and online state",
+ "flaredStatus": "flared status",
+ "autoUpdate": "Auto update",
+ "syncTitle": "Config sync",
+ "syncDesc": "Tunnel client config catch-up",
+ "currentVersion": "Current config version",
+ "latestApply": "Latest apply",
+ "latestApplyAt": "Latest apply time",
+ "fileCount": "Synced files",
+ "fileCountValue": "{count}",
+ "upgradeStatus": "Upgrade status",
+ "credentials": "Join credentials",
+ "credentialsDesc": "Token required by the tunnel client",
+ "tunnelToken": "Tunnel Token",
+ "createdAt": "Created",
+ "updatedAt": "Updated",
+ "kpiOpenflared": "openflared",
+ "connectionHint": "Tunnel concurrent connections",
+ "toastUpdated": "Tunnel node updated",
+ "toastForceSync": "Force-sync dispatched to tunnel {name}",
+ "toastUpgrade": "{channel} upgrade dispatched to tunnel {name}",
+ "toastDeleted": "Tunnel node deleted",
+ "deleteTitle": "Delete tunnel node",
+ "deleteDesc": "Delete tunnel node “{name}”? You will need to recreate and rejoin it."
+ },
+ "obs": {
+ "hostname": "Hostname",
+ "os": "Operating system",
+ "kernelArch": "Kernel / arch",
+ "uptime": "Uptime",
+ "cpu": "CPU",
+ "cpuCores": "{count} cores",
+ "totalMemory": "Total memory",
+ "totalStorage": "Total storage",
+ "reportedAt": "Reported at",
+ "eventsTitle": "Health event timeline",
+ "eventsDesc": "Keeps active and recovered events to help judge runtime health.",
+ "cleanupLogs": "Clean logs",
+ "filterAll": "All events",
+ "filterActive": "Active",
+ "filterResolved": "Recovered",
+ "filterEmpty": "No health events for this filter.",
+ "noMessage": "No detailed message",
+ "firstTriggered": "First triggered:",
+ "lastTriggered": "Last triggered:",
+ "resolvedAt": "Recovered:",
+ "noEvents": "This node has not reported any health events yet.",
+ "cleaned": "Cleaned {count} health event logs",
+ "nothingToClean": "No health event logs to clean",
+ "loading": "Loading observability data...",
+ "diag": "Runtime diagnosis",
+ "activeIssues": "{count} active incidents",
+ "stable": "Stable",
+ "noActiveEvents": "No active health events",
+ "systemCore": "System core",
+ "uptimeFromProfile": "From the node system profile",
+ "profileTitle": "System profile",
+ "profileDesc": "Host and hardware info reported by the node",
+ "noProfile": "The node has joined but has not reported a full system profile yet.",
+ "noProfileLong": "The node has joined but has not reported a full system profile yet.",
+ "snapshotTitle": "Live resource snapshot",
+ "snapshotDesc": "Latest metrics report",
+ "realtimeResources": "Live resources",
+ "memory": "Memory",
+ "storage": "Storage",
+ "connections": "Connections",
+ "snapshotAgo": "Snapshot {time}",
+ "noSnapshot": "The node has joined but has not reported a resource snapshot yet.",
+ "noSnapshotLong": "The node has joined but has not reported a resource snapshot yet.",
+ "cleanupTitle": "Clean health event logs",
+ "cleanupDesc": "This deletes all health diagnosis history recorded for this node on the control plane. New events will still be reported.",
+ "cleaning": "Cleaning...",
+ "confirmCleanup": "Clean",
+ "windowRequests": "Window requests",
+ "windowVisitorsHint": "Window unique visitors {visitors} · error rate {rate}%",
+ "noWindowSummary": "No request window summary",
+ "capacityPressure": "Capacity pressure",
+ "needsAttention": "Needs attention",
+ "normalRange": "Normal",
+ "cpuStorage": "CPU {cpu} · storage {storage}",
+ "noResourceSnapshot": "No resource snapshot",
+ "sourceSignal": "Source signal",
+ "requestTimes": "{count} requests",
+ "noSourceDist": "No source distribution data",
+ "systemInfo": "System info",
+ "networkTraffic": "Network traffic",
+ "noActiveIssue": "No active incidents",
+ "openrestyConn": "OpenResty connections",
+ "currentConn": "Current connections:",
+ "throughputHint": "See access-log trends for business throughput (served / received)",
+ "windowVisitors": "Window unique visitors",
+ "reqQps": "Requests {requests} · estimated QPS {qps}",
+ "noTrafficSummary": "No window traffic summary",
+ "windowErrors": "Window errors",
+ "errorRate": "Error rate {rate}%",
+ "noErrorSummary": "No error-rate summary",
+ "noNetworkSnapshot": "The node has joined but has not reported a network traffic snapshot yet.",
+ "trafficTrendDesc": "Hourly request and error counts for this node.",
+ "capacityTrendDesc": "CPU and memory usage for this node over 24 hours.",
+ "structureTitle": "Request structure",
+ "structureDesc": "Last 24 hours: which status codes and domains concentrate traffic and errors.",
+ "mainStatus": "Top status code",
+ "times": "{count} times",
+ "noStatusDist": "No status code distribution",
+ "topDomain": "Top Domain",
+ "noDomainDist": "No domain distribution",
+ "resolvedEvents": "Recovered events",
+ "resolvedHint": "Health events recovered in the last 24 hours",
+ "statusDist": "Status code distribution",
+ "distEmpty": "No distribution data",
+ "unknownLabel": "Unknown"
+ },
+ "networkTrend": {
+ "title": "24-hour network trend",
+ "description": "Hourly data served / received from access logs; summary is the last-24h total.",
+ "summaryHint": "Last 24 hours",
+ "received": "Data received",
+ "provided": "Data served"
+ },
+ "diskIo": {
+ "title": "24-hour disk IO trend",
+ "description": "Hourly disk read/write rate (B/s) to spot log amplification, cache jitter, or disk pressure.",
+ "summaryHint": "Last 24 hours · average rate",
+ "read": "Disk read",
+ "write": "Disk write"
+ }
+ },
+ "performance": {
+ "title": "Performance",
+ "description": "Manage global OpenResty performance settings. Saving enters the unified config publish flow.",
+ "preview": "View config preview",
+ "saved": "Performance settings saved",
+ "saveFailed": "Save failed",
+ "loadingAuth": "Loading permissions...",
+ "forbiddenTitle": "Insufficient permissions",
+ "forbiddenDesc": "Only administrators can access performance settings.",
+ "loading": "Loading performance settings...",
+ "runtimeTitle": "Connections and events",
+ "runtimeDesc": "Workers, timeouts, and request body size",
+ "proxyTitle": "Proxy buffers and timeouts",
+ "proxyDesc": "Upstream connections, buffers, and WebSocket/HTTP3 switches",
+ "gzipTitle": "Compression",
+ "cacheTitle": "Cache",
+ "cacheDesc": "Single-node reverse-proxy cache",
+ "errors": {
+ "workerProcesses": "worker_processes must be auto or an integer greater than 0",
+ "timeouts": "Timeout and connection values must be integers greater than 0",
+ "statusCode": "Default server return status must be between 100 and 999",
+ "bodySize": "Invalid client_max_body_size format",
+ "headerBuffers": "large_client_header_buffers must look like \"4 16k\"",
+ "proxyTimeouts": "Proxy timeouts must be integers greater than 0 seconds",
+ "proxyBuffers": "proxy_buffers must look like \"16 16k\"",
+ "bufferSize": "Buffer sizes must be integers or values with k/m/g units",
+ "gzipMinLength": "gzip_min_length must be an integer greater than 0",
+ "gzipLevel": "gzip_comp_level must be between 1 and 9",
+ "cachePath": "proxy_cache_path is required when cache is enabled",
+ "cacheFormat": "Invalid cache levels, inactive, max_size, or lock_timeout format",
+ "cacheKey": "A cache key template is required when cache is enabled"
+ }
+ },
+ "applyLogs": {
+ "title": "Apply logs",
+ "description": "Inspect successful, warning, and failed config applies. Filter by node_id.",
+ "refresh": "Refresh",
+ "clear": "Clear logs",
+ "backToNodes": "Back to nodes",
+ "totalRecords": "Total records",
+ "currentPage": "Current page",
+ "totalPages": "Total pages",
+ "pageNodes": "Nodes on this page",
+ "nodeId": "Node ID",
+ "nodePlaceholder": "Filter apply logs by node_id",
+ "pageSize": "Page size",
+ "pageSizeOption": "{count}",
+ "filter": "Filter",
+ "reset": "Reset filters",
+ "emptyTitle": "No apply logs",
+ "emptyDesc": "No apply records match the current filters.",
+ "colNodeId": "Node ID",
+ "colVersion": "Version",
+ "colResult": "Result",
+ "colChecksum": "Checksum",
+ "colTime": "Time",
+ "colMessage": "Message",
+ "colActions": "Actions",
+ "detail": "Details",
+ "pageInfo": "Page {current} / {total}, {count} records.",
+ "prev": "Previous",
+ "next": "Next",
+ "loadFailed": "Load failed",
+ "cleared": "Apply logs cleared",
+ "clearedDesc": "Deleted {count} records",
+ "clearFailed": "Failed to clear apply logs",
+ "unknownError": "Unknown error",
+ "clearTitle": "Clear all apply logs?",
+ "clearDesc": "This deletes all {count} apply log records and cannot be undone. Nodes will continue to report new apply results.",
+ "clearing": "Clearing...",
+ "confirmClear": "Clear",
+ "success": "Success",
+ "warning": "Warning",
+ "failed": "Failed",
+ "sheetTitle": "Apply log details",
+ "sheetDesc": "Full result and checksum details for this apply log.",
+ "versionBadge": "Version: {version}",
+ "createdAt": "Created",
+ "targetChecksum": "Target Checksum",
+ "mainChecksum": "Main config digest",
+ "routeChecksum": "Route config digest",
+ "supportFiles": "Support files",
+ "message": "Message",
+ "close": "Close"
+ },
+ "configVersions": {
+ "title": "Config versions",
+ "description": "Browse historical snapshots, preview pending diffs, and reactivate an older version when needed.",
+ "refresh": "Refresh",
+ "cleanupOld": "Clean",
+ "viewDiff": "Diff",
+ "forcePublish": "Force publish",
+ "previewPublish": "Publish",
+ "emptyTitle": "No history",
+ "emptyDesc": "There are no published versions yet. Publish a config first.",
+ "colVersion": "Version",
+ "colStatus": "Status",
+ "colAuthor": "Created by",
+ "colChecksum": "Checksum",
+ "colCreatedAt": "Created",
+ "colActions": "Actions",
+ "active": "Active",
+ "history": "History",
+ "system": "System",
+ "snapshot": "Snapshot",
+ "activate": "Activate",
+ "loadFailed": "Load failed",
+ "previewLoadFailed": "Failed to load preview",
+ "diffLoadFailed": "Failed to load diff",
+ "publishSuccess": "Published",
+ "publishSuccessDesc": "Version {version}",
+ "publishFailed": "Publish failed",
+ "activateSuccess": "Activated",
+ "activateSuccessDesc": "Version {version}",
+ "activateFailed": "Activate failed",
+ "cleanupDone": "Cleanup complete",
+ "cleanupDoneDesc": "Deleted {count} historical snapshots",
+ "cleanupFailed": "Cleanup failed",
+ "unknownError": "Unknown error",
+ "confirmPublishTitle": "Publish config",
+ "confirmPublishDesc": "This creates a new version from the pending config and marks it active. Nodes will pull the update next.",
+ "confirmPublish": "Publish",
+ "confirmForceTitle": "Force publish",
+ "confirmForceDesc": "This ignores the change check and immediately creates a new version. Confirm you really need to republish.",
+ "confirmActivateTitle": "Activate version",
+ "confirmActivateDesc": "Set version {version} as the active version?",
+ "confirmActivate": "Activate",
+ "previewTitle": "Publish preview",
+ "previewDesc": "Rendered pending config and support files.",
+ "loadingPreview": "Loading preview...",
+ "routeCount": "{count} routes",
+ "websiteCount": "{count} websites",
+ "mainConfig": "Main config",
+ "routeConfig": "Route config",
+ "supportFiles": "Support files ({count})",
+ "noSupportFiles": "This publish has no extra support files.",
+ "noChanges": "Pending routes match the active version, so it cannot be published again.",
+ "close": "Close",
+ "publishing": "Publishing...",
+ "diffTitle": "Config diff",
+ "diffDesc": "Differences between the pending config and the active version.",
+ "loadingDiff": "Loading diff...",
+ "activeVersion": "Active version",
+ "none": "None",
+ "addedDomains": "Added domains",
+ "removedDomains": "Removed domains",
+ "modifiedDomains": "Modified domains",
+ "itemCount": "{count}",
+ "noChange": "No changes",
+ "emptyValue": "empty",
+ "mainChanged": "Main config changed",
+ "mainUnchanged": "Main config unchanged",
+ "wafChanged": "WAF changed",
+ "wafUnchanged": "WAF unchanged",
+ "websiteDelta": "Websites {from} → {to}",
+ "optionKey": "Option",
+ "activeValue": "Active value",
+ "pendingValue": "Pending value",
+ "noOptionChanges": "No OpenResty option changes.",
+ "snapshotTitle": "Version snapshot",
+ "snapshotVersion": "Version {version}",
+ "snapshotDesc": "Full snapshot of this historical config version.",
+ "loadingSnapshot": "Loading snapshot...",
+ "createdBy": "Created by",
+ "createdAt": "Created",
+ "snapshotLoadFailed": "Failed to load version snapshot",
+ "cleanupTitle": "Clean historical snapshots",
+ "cleanupDesc": "Delete older snapshots. The currently active version is always protected.",
+ "keepCount": "Keep latest snapshots",
+ "keepHint": "Default 10, minimum 3.",
+ "keepMin": "Keep at least 3 historical snapshots",
+ "cleaning": "Cleaning...",
+ "confirmCleanup": "Clean"
+ }
+}
diff --git a/frontend/messages/fragments/ops.zh-CN.json b/frontend/messages/fragments/ops.zh-CN.json
new file mode 100644
index 00000000..3f7570f4
--- /dev/null
+++ b/frontend/messages/fragments/ops.zh-CN.json
@@ -0,0 +1,673 @@
+{
+ "dashboard": {
+ "title": "总览",
+ "generatedAt": "数据生成于 {time}",
+ "refresh": "刷新",
+ "loadingTitle": "加载总览数据",
+ "loadingDesc": "正在聚合节点健康、流量与容量指标...",
+ "loadFailed": "总览看板加载失败:{error}",
+ "emptyTitle": "暂无总览数据",
+ "emptyDesc": "系统已经启动,但还没有可展示的总览聚合结果。",
+ "requestFailed": "请求失败,请稍后重试。",
+ "stats": {
+ "requests24h": "24h 请求量",
+ "requestsMeta": "24h 独立访客 {visitors} · 错误 {errors} · 估算 QPS {qps}",
+ "clusterCapacity": "集群容量",
+ "avgMemoryHighLoad": "平均内存 {memory} · 高负载节点 CPU {cpu} / 内存 {memoryNodes} / 存储 {storage}",
+ "nodeOverview": "节点概览",
+ "onlineOf": "/ {total} 在线",
+ "pending": "待接入 {count}",
+ "unhealthyOffline": "异常 {unhealthy} · 离线 {offline}"
+ },
+ "world": {
+ "title": "全球态势板",
+ "description": "汇总节点分布、来源国家热度与最近窗口运行状态。",
+ "legendSource": "来源",
+ "legendHealthy": "正常",
+ "legendPressure": "承压",
+ "legendUnhealthy": "异常",
+ "modeVisitorSource": "访客来源",
+ "modeNodeCoords": "节点坐标",
+ "modeCoverage": "覆盖信号",
+ "onlineCoverage": "在线覆盖",
+ "onlineHint": "{online}/{total} 在线",
+ "runHealth": "运行健康",
+ "unhealthyCount": "{count} 个异常",
+ "requests24h": "24小时请求",
+ "qpsReported": "QPS {qps} · {count} 个节点上报",
+ "nodeCoords": "节点坐标",
+ "sourceCountries": "{count} 个来源国家",
+ "fallbackPins": "未配置时使用预设落点",
+ "avgCpu": "平均 CPU",
+ "highCount": "{count} 个偏高",
+ "avgMemory": "平均内存",
+ "highStorage": "高存储",
+ "offlinePending": "{offline} 离线 · {pending} 待接入",
+ "mapPreparing": "地图准备中",
+ "mapPreparingDesc": "进入可视区域后加载",
+ "noNodesOverlay": "暂无节点,接入后将展示地理分布"
+ },
+ "map": {
+ "sourceRequests": "最近 24 小时来源请求 {count}",
+ "presetPin": "预设落点",
+ "requestsErrors": "请求量 {requests} · 错误数 {errors}",
+ "eventsStatus": "活动事件 {events} · 节点状态 {status}",
+ "openrestyStatus": "OpenResty 状态 {status}",
+ "loadFailed": "全球地图加载失败",
+ "loading": "全球地图加载中",
+ "loadFailedDesc": "ECharts 世界地图资源未能成功注册,请稍后刷新重试。",
+ "loadingDesc": "正在按需初始化全球地图,这一步会延后到首屏内容稳定后执行。",
+ "measuring": "正在测量地图容器尺寸..."
+ },
+ "trafficTrend": {
+ "title": "24 小时请求趋势",
+ "description": "按小时拆分请求总量与 2xx/4xx/5xx 状态码请求量,判断各状态是否异常抬升。",
+ "summaryHint": "近 24 小时",
+ "requests": "请求量",
+ "status2xx": "2xx 请求",
+ "status4xx": "4xx 请求",
+ "status5xx": "5xx 请求"
+ },
+ "capacityTrend": {
+ "title": "24 小时容量趋势",
+ "description": "按小时聚合 CPU 与内存使用率,判断整体容量是否持续紧张。",
+ "avgCpu": "平均 CPU",
+ "avgMemory": "平均内存"
+ },
+ "trafficCapacity": {
+ "title": "24 小时业务流量与容量趋势",
+ "networkHint": "近 24 小时 · 来自访问日志",
+ "received": "接收数据",
+ "provided": "已提供数据",
+ "capacityHint": "近 24 小时 · 宿主机容量 · 平均值"
+ },
+ "distributions": {
+ "sourceTitle": "来源分布",
+ "sourceDesc": "聚合最近 24 小时主要来源国家。",
+ "sourceEmpty": "暂无来源分布数据",
+ "statusTitle": "状态码分布",
+ "statusDesc": "快速判断成功响应是否仍是主流,以及错误码是否有抬升。",
+ "statusEmpty": "暂无状态码分布",
+ "httpLabel": "HTTP {code}",
+ "domainTitle": "Top Domain",
+ "domainDesc": "观察主要流量集中在哪些域名。",
+ "domainEmpty": "暂无域名分布"
+ },
+ "nodeHealth": {
+ "title": "节点健康列表",
+ "description": "按异常数量和资源压力排序。",
+ "openNodes": "进入节点页",
+ "emptyTitle": "暂无节点",
+ "emptyDesc": "节点接入后,这里会展示系统健康与容量摘要。",
+ "colNode": "节点",
+ "colStatus": "状态",
+ "colCpuMem": "CPU / 内存",
+ "colReqErr": "请求 / 错误",
+ "colEvents": "活动异常",
+ "colHeartbeat": "最近心跳",
+ "colActions": "操作",
+ "noGeo": "未配置地图点位",
+ "wsConnected": "WS 已连接",
+ "na": "暂无",
+ "detail": "详情"
+ },
+ "nodeRank": {
+ "title": "Top 节点榜单",
+ "traffic": "流量最高节点",
+ "trafficEmpty": "暂无流量榜单",
+ "pressure": "容量压力节点",
+ "pressureEmpty": "暂无容量压力数据"
+ }
+ },
+ "nodes": {
+ "title": "节点管理",
+ "applyLogs": "应用记录",
+ "create": "新增节点",
+ "listTitle": "节点列表",
+ "refreshNow": "立即刷新",
+ "loadingList": "加载节点列表中...",
+ "emptyAll": "暂无节点,请先创建一个节点。",
+ "emptyFilter": "当前筛选无结果",
+ "updated": "节点已更新",
+ "created": "节点已创建",
+ "deleted": "节点已删除",
+ "deleteTitle": "确认删除节点",
+ "deleteDesc": "确认删除节点「{name}」吗?删除后该节点需要重新创建并重新接入。",
+ "deleting": "删除中...",
+ "confirmDelete": "确认删除",
+ "edit": "编辑",
+ "delete": "删除",
+ "refresh": "刷新",
+ "detail": "详情",
+ "na": "暂无",
+ "wsConnected": "WS 已连接",
+ "wsEstablished": "WebSocket 长连接已建立",
+ "requestFailed": "请求失败,请稍后重试。",
+ "unknown": "未知",
+ "running": "运行中",
+ "liveConfig": "实时配置",
+ "notApplied": "未应用",
+ "enabled": "已启用",
+ "manual": "手动",
+ "lockedSuffix": "(已锁定)",
+ "noGeo": "未配置地图点位",
+ "notConfigured": "未配置",
+ "ipLine": "IP:{ip}",
+ "locationLine": "位置:{location}",
+ "backToList": "返回节点列表",
+ "missingId": "缺少节点 ID,请从节点列表进入详情页。",
+ "loadingDetail": "加载节点详情中...",
+ "notFound": "节点不存在,可能已被删除或 ID 无效。",
+ "syncing": "同步中...",
+ "forceSync": "强制同步",
+ "dispatching": "下发中...",
+ "viewUpgrade": "查看升级",
+ "tabOverview": "概览",
+ "tabManage": "状态与部署",
+ "kpiNodeId": "节点 ID",
+ "kpiLastSeen": "最近心跳",
+ "kpiCurrentConfig": "当前配置",
+ "filter": {
+ "all": "全部节点",
+ "descAll": "当前展示全部节点。",
+ "descRelay": "当前仅展示 Relay 节点。",
+ "descTunnel": "当前仅展示 Tunnel 节点。",
+ "descEdge": "当前仅展示 Edge 节点。"
+ },
+ "status": {
+ "online": "在线",
+ "pending": "待接入",
+ "offline": "离线",
+ "running": "运行中",
+ "wsConnected": "WS 已连接"
+ },
+ "apply": {
+ "success": "成功",
+ "warning": "警告",
+ "failed": "失败",
+ "none": "暂无"
+ },
+ "health": {
+ "healthy": "健康",
+ "unhealthy": "异常",
+ "unknown": "未知"
+ },
+ "relative": {
+ "justNow": "刚刚",
+ "minutesAgo": "{count} 分钟前",
+ "hoursAgo": "{count} 小时前",
+ "daysAgo": "{count} 天前",
+ "monthsAgo": "{count} 个月前"
+ },
+ "uptime": {
+ "daysHours": "{days} 天 {hours} 小时",
+ "hoursMinutes": "{hours} 小时 {minutes} 分钟",
+ "minutes": "{minutes} 分钟"
+ },
+ "channel": {
+ "preview": "预览版",
+ "stable": "正式版"
+ },
+ "table": {
+ "node": "节点",
+ "status": "状态",
+ "version": "Version",
+ "runHealth": "运行健康",
+ "currentVersion": "当前版本",
+ "latestApply": "最近应用",
+ "lastSeen": "最近心跳",
+ "actions": "操作"
+ },
+ "editor": {
+ "editTitle": "编辑节点",
+ "createTitle": "新增节点",
+ "description": "预创建节点后可在详情页查看专属 Token 与部署命令。",
+ "nodeType": "节点类型",
+ "typeEdge": "Edge 节点",
+ "typeRelay": "Relay 节点",
+ "typeTunnel": "Tunnel 节点",
+ "name": "节点名称",
+ "namePlaceholder": "例如 edge-hk-01",
+ "ip": "节点 IP",
+ "ipPlaceholder": "可选,接入后自动上报",
+ "lockIp": "锁定 IP",
+ "lockIpDesc": "启用后管理端不再自动覆盖 IP",
+ "autoUpdate": "自动更新 Agent",
+ "autoUpdateDesc": "启用后节点将自动拉取正式版更新",
+ "bindPort": "绑定端口",
+ "vhostPort": "VHost 端口",
+ "manualGeo": "手动地图点位",
+ "manualGeoDesc": "用于总览地图展示",
+ "geoName": "位置名称",
+ "latitude": "纬度",
+ "longitude": "经度",
+ "saving": "保存中...",
+ "saveEdit": "保存修改",
+ "createAction": "新增节点",
+ "errName": "请输入节点名称",
+ "errLockIp": "锁定 IP 时必须填写节点 IP",
+ "errBindPort": "请输入有效的绑定端口",
+ "errVhostPort": "请输入有效的 VHost 端口"
+ },
+ "install": {
+ "edgeTitle": "边缘节点部署",
+ "edgeDesc": "使用 Agent Token 将边缘节点接入控制端。",
+ "relayTitle": "中继部署命令",
+ "relayDesc": "使用 Discovery Token 将 frps 中继节点接入控制端。",
+ "tunnelTitle": "隧道部署命令",
+ "tunnelDesc": "使用 Tunnel Token 将 openflared 客户端接入控制端。",
+ "agentToken": "Agent Token",
+ "discoveryToken": "Discovery Token",
+ "tunnelToken": "Tunnel Token",
+ "scriptLabel": "一键脚本部署 (Linux / macOS)",
+ "dockerLabel": "Docker 容器部署",
+ "copyFailed": "复制失败,请手动选择命令文本。",
+ "copiedScript": "部署脚本命令已复制",
+ "copiedDocker": "Docker 部署命令已复制",
+ "copyScript": "复制脚本",
+ "copyDocker": "复制 Docker",
+ "nodeId": "Node ID",
+ "serverUrl": "Server URL(控制端地址)",
+ "serverUrlHint": "部署脚本和容器将使用此地址连接 OpenFlare 控制端。",
+ "noToken": "节点尚未生成接入 Token,请稍后刷新。",
+ "needServerUrl": "请填写有效的 Server URL 以生成部署命令。"
+ },
+ "update": {
+ "title": "Agent 升级",
+ "description": "检查 GitHub 发布并向节点下发升级指令,节点将在下一次心跳后执行。",
+ "currentVersion": "当前版本",
+ "channel": "检查通道",
+ "updateStatus": "更新状态",
+ "waitingPreview": "等待预览更新",
+ "waitingUpdate": "等待更新",
+ "notRequested": "未下发",
+ "checking": "检查发布版本中...",
+ "hasUpdate": "发现可升级版本",
+ "upToDate": "当前已是最新版本",
+ "previewRelease": "Preview 发布",
+ "stableRelease": "正式发布",
+ "targetVersion": "目标版本",
+ "notFound": "未找到",
+ "publishedAt": "发布时间",
+ "noNotes": "暂无更新说明",
+ "viewRelease": "查看发布详情",
+ "checkStable": "检查正式版",
+ "checkPreview": "检查预览版",
+ "upgradePreview": "升级到预览版",
+ "upgradeStable": "升级到正式版"
+ },
+ "edge": {
+ "upgrade": "升级 Agent",
+ "restartOpenresty": "重启 OpenResty",
+ "runStatus": "运行状态",
+ "runStatusDesc": "节点在线情况与 OpenResty 健康",
+ "openrestyHealth": "OpenResty 健康",
+ "lastSeen": "最近心跳",
+ "ip": "IP 地址",
+ "geo": "地图点位",
+ "syncTitle": "配置同步",
+ "syncDesc": "版本追平与应用结果摘要",
+ "currentVersion": "当前配置版本",
+ "latestApply": "最近应用",
+ "latestApplyAt": "最近应用时间",
+ "agentVersion": "Agent 版本",
+ "nginxVersion": "Nginx 版本",
+ "autoUpdate": "自动更新",
+ "runtimeMessages": "运行消息",
+ "runtimeMessagesDesc": "OpenResty 与 Agent 上报的状态说明",
+ "openrestyMessage": "OpenResty 状态消息",
+ "noExtraError": "当前未上报额外错误。",
+ "restartPending": "已等待节点在下一次心跳后执行 OpenResty 重启。",
+ "kpiAgent": "Agent 版本",
+ "connectionHint": "OpenResty 当前连接",
+ "toastUpdated": "节点已更新",
+ "toastForceSync": "已向节点 {name} 下发强制同步指令",
+ "toastRestart": "已向节点 {name} 下发 OpenResty 重启指令",
+ "toastUpgrade": "已向节点 {name} 下发{channel}升级指令",
+ "toastDeleted": "节点已删除"
+ },
+ "relay": {
+ "upgrade": "升级 Relay",
+ "runStatus": "中继运行状态",
+ "runStatusDesc": "frps 在线情况与负载摘要",
+ "health": "中继健康",
+ "connections": "活动连接 / 代理数",
+ "portsTitle": "网络端口",
+ "portsDesc": "frps 控制面与 HTTP 虚拟主机配置",
+ "bindPort": "绑定控制端口",
+ "vhostPort": "VHost HTTP 端口",
+ "agentAddr": "Agent 接入地址",
+ "relayVersion": "Relay 版本",
+ "frpsVersion": "frps 核心版本",
+ "webuiTitle": "FRPS WebUI",
+ "webuiDesc": "控制 frps 内置 Web 管理界面是否启用及其监听端口",
+ "enableWebui": "启用 Web 管理界面",
+ "enableWebuiHint": "开启后,节点将启动 frps 的内置管理服务。",
+ "webuiPort": "Web 管理界面端口",
+ "webuiPortPlaceholder": "例如: 17500",
+ "actualPort": "当前实际监听端口:{port} (修改后在下次节点心跳同步时生效,默认为绑定端口 + 500)。",
+ "openWebui": "打开 FRPS WebUI",
+ "copiedLink": "链接已复制到剪贴板",
+ "copyLink": "复制链接",
+ "webuiDisabled": "WebUI 已禁用或未配置绑定端口。",
+ "metadata": "节点元数据",
+ "createdAt": "创建时间",
+ "updatedAt": "更新时间",
+ "kpiFrps": "frps 核心",
+ "connectionHint": "中继承载活动连接数",
+ "invalidPort": "端口号必须在 1 ~ 65535 范围内",
+ "portSaveFailed": "更新端口失败",
+ "toastPortSaved": "FRPS WebUI 端口配置已更新",
+ "toastUpdated": "中继节点已更新",
+ "toastWebui": "FRPS WebUI 设置已更新",
+ "toastForceSync": "已向中继节点 {name} 下发强制同步指令",
+ "toastUpgrade": "已向中继节点 {name} 下发{channel}升级指令",
+ "toastDeleted": "中继节点已删除",
+ "deleteTitle": "确认删除中继节点",
+ "deleteDesc": "确认删除中继节点「{name}」吗?删除后该节点需要重新创建并重新接入。"
+ },
+ "tunnel": {
+ "upgrade": "升级 openflared",
+ "runStatus": "隧道运行状态",
+ "runStatusDesc": "openflared 连接与在线情况",
+ "flaredStatus": "flared 状态",
+ "autoUpdate": "自动更新",
+ "syncTitle": "配置同步",
+ "syncDesc": "隧道客户端配置追平状态",
+ "currentVersion": "当前配置版本",
+ "latestApply": "最近应用",
+ "latestApplyAt": "最近应用时间",
+ "fileCount": "同步文件数",
+ "fileCountValue": "{count} 个",
+ "upgradeStatus": "升级状态",
+ "credentials": "接入凭证",
+ "credentialsDesc": "隧道客户端接入所需 Token",
+ "tunnelToken": "Tunnel Token",
+ "createdAt": "创建时间",
+ "updatedAt": "更新时间",
+ "kpiOpenflared": "openflared",
+ "connectionHint": "隧道并发连接数",
+ "toastUpdated": "隧道节点已更新",
+ "toastForceSync": "已向隧道节点 {name} 下发强制同步指令",
+ "toastUpgrade": "已向隧道节点 {name} 下发{channel}升级指令",
+ "toastDeleted": "隧道节点已删除",
+ "deleteTitle": "确认删除隧道节点",
+ "deleteDesc": "确认删除隧道节点「{name}」吗?删除后该节点需要重新创建并重新接入。"
+ },
+ "obs": {
+ "hostname": "主机名",
+ "os": "操作系统",
+ "kernelArch": "内核 / 架构",
+ "uptime": "在线时长",
+ "cpu": "CPU",
+ "cpuCores": "{count} 核",
+ "totalMemory": "总内存",
+ "totalStorage": "总存储",
+ "reportedAt": "上报时间",
+ "eventsTitle": "健康事件时间线",
+ "eventsDesc": "保留活动与已恢复事件,帮助判断运行状态。",
+ "cleanupLogs": "清理日志",
+ "filterAll": "全部事件",
+ "filterActive": "活动中",
+ "filterResolved": "已恢复",
+ "filterEmpty": "当前筛选下没有健康事件。",
+ "noMessage": "暂无详细消息",
+ "firstTriggered": "首次触发:",
+ "lastTriggered": "最近触发:",
+ "resolvedAt": "恢复时间:",
+ "noEvents": "节点当前还没有上报健康事件记录。",
+ "cleaned": "已清理 {count} 条健康事件日志",
+ "nothingToClean": "当前没有可清理的健康事件日志",
+ "loading": "加载运行观测数据中...",
+ "diag": "运行诊断",
+ "activeIssues": "{count} 个活动异常",
+ "stable": "运行稳定",
+ "noActiveEvents": "当前没有活动中的健康事件",
+ "systemCore": "系统核心",
+ "uptimeFromProfile": "来自节点系统画像上报",
+ "profileTitle": "系统画像",
+ "profileDesc": "节点上报的主机与硬件信息",
+ "noProfile": "节点已接入,但尚未上报完整系统画像。",
+ "noProfileLong": "节点已经接入,但还没有上报完整系统画像。",
+ "snapshotTitle": "实时资源快照",
+ "snapshotDesc": "最近一次 metrics 上报",
+ "realtimeResources": "实时资源",
+ "memory": "内存",
+ "storage": "存储",
+ "connections": "连接数",
+ "snapshotAgo": "快照 {time}",
+ "noSnapshot": "节点已接入,但尚未上报资源快照。",
+ "noSnapshotLong": "节点已经接入,但还没有上报资源快照。",
+ "cleanupTitle": "清理健康事件日志",
+ "cleanupDesc": "该操作会删除此节点在控制端记录的所有健康诊断事件历史,不会影响后续新事件上报。",
+ "cleaning": "清理中...",
+ "confirmCleanup": "确认清理",
+ "windowRequests": "查询窗口请求",
+ "windowVisitorsHint": "查询窗口独立访客 {visitors} · 错误率 {rate}%",
+ "noWindowSummary": "当前没有可展示的请求窗口摘要",
+ "capacityPressure": "容量压力",
+ "needsAttention": "需要关注",
+ "normalRange": "正常范围",
+ "cpuStorage": "CPU {cpu} · 存储 {storage}",
+ "noResourceSnapshot": "当前没有资源快照",
+ "sourceSignal": "来源信号",
+ "requestTimes": "{count} 次请求",
+ "noSourceDist": "当前没有来源分布数据",
+ "systemInfo": "系统信息",
+ "networkTraffic": "网络流量",
+ "noActiveIssue": "无活动异常",
+ "openrestyConn": "OpenResty 连接",
+ "currentConn": "当前连接:",
+ "throughputHint": "业务吞吐请看访问日志趋势(已提供/接收数据)",
+ "windowVisitors": "查询窗口独立访客",
+ "reqQps": "请求 {requests} · 估算 QPS {qps}",
+ "noTrafficSummary": "暂无窗口流量摘要",
+ "windowErrors": "查询窗口错误",
+ "errorRate": "错误率 {rate}%",
+ "noErrorSummary": "暂无错误率摘要",
+ "noNetworkSnapshot": "节点已经接入,但还没有上报网络流量相关快照。",
+ "trafficTrendDesc": "按小时聚合该节点的请求量和错误量。",
+ "capacityTrendDesc": "观察该节点 CPU 与内存使用率在 24 小时内的变化。",
+ "structureTitle": "请求结构分布",
+ "structureDesc": "聚合最近 24 小时窗口上报,帮助判断错误集中在哪些状态码、流量集中在哪些域名。",
+ "mainStatus": "主状态码",
+ "times": "{count} 次",
+ "noStatusDist": "暂无状态码分布",
+ "topDomain": "Top Domain",
+ "noDomainDist": "暂无域名分布",
+ "resolvedEvents": "已恢复事件",
+ "resolvedHint": "最近 24 小时已恢复健康事件",
+ "statusDist": "状态码分布",
+ "distEmpty": "暂无分布数据",
+ "unknownLabel": "未知"
+ },
+ "networkTrend": {
+ "title": "24 小时网络趋势",
+ "description": "按小时展示已提供/接收数据(访问日志);摘要为近 24 小时总量。",
+ "summaryHint": "近 24 小时",
+ "received": "接收数据",
+ "provided": "已提供数据"
+ },
+ "diskIo": {
+ "title": "24 小时磁盘 IO 趋势",
+ "description": "按小时展示磁盘读写速率(B/s),辅助判断日志放大、缓存抖动或磁盘压力。",
+ "summaryHint": "近 24 小时 · 平均速率",
+ "read": "磁盘读",
+ "write": "磁盘写"
+ }
+ },
+ "performance": {
+ "title": "性能",
+ "description": "集中管理 OpenResty 全局性能参数,保存后进入统一配置发布链路。",
+ "preview": "查看配置预览",
+ "saved": "性能参数已保存",
+ "saveFailed": "保存失败",
+ "loadingAuth": "加载权限信息...",
+ "forbiddenTitle": "权限不足",
+ "forbiddenDesc": "只有管理员可以访问性能设置。",
+ "loading": "加载性能参数...",
+ "runtimeTitle": "连接与事件",
+ "runtimeDesc": "worker、超时、请求体大小等运行参数",
+ "proxyTitle": "反代缓冲与超时",
+ "proxyDesc": "upstream 连接、缓冲与 WebSocket/HTTP3 开关",
+ "gzipTitle": "压缩",
+ "cacheTitle": "缓存",
+ "cacheDesc": "单节点反代缓存优化场景",
+ "errors": {
+ "workerProcesses": "worker_processes 必须为 auto 或大于 0 的整数",
+ "timeouts": "超时与连接参数必须为大于 0 的整数",
+ "statusCode": "空白页面返回状态码必须在 100 到 999 之间",
+ "bodySize": "client_max_body_size 格式不合法",
+ "headerBuffers": "large_client_header_buffers 格式必须类似 \"4 16k\"",
+ "proxyTimeouts": "代理超时参数必须为大于 0 的整数秒",
+ "proxyBuffers": "proxy_buffers 格式必须类似 \"16 16k\"",
+ "bufferSize": "缓冲大小必须为整数或带 k/m/g 单位的值",
+ "gzipMinLength": "gzip_min_length 必须为大于 0 的整数",
+ "gzipLevel": "gzip_comp_level 必须在 1 到 9 之间",
+ "cachePath": "启用缓存时必须填写 proxy_cache_path 目录",
+ "cacheFormat": "缓存 levels、inactive、max_size 或 lock_timeout 格式不合法",
+ "cacheKey": "启用缓存时必须填写缓存 Key 模板"
+ }
+ },
+ "applyLogs": {
+ "title": "应用日志",
+ "description": "查看节点应用配置的成功、警告和失败记录,支持按 node_id 过滤。",
+ "refresh": "刷新",
+ "clear": "清空日志",
+ "backToNodes": "返回节点",
+ "totalRecords": "总记录数",
+ "currentPage": "当前页",
+ "totalPages": "总页数",
+ "pageNodes": "当前页节点数",
+ "nodeId": "Node ID",
+ "nodePlaceholder": "输入 node_id 过滤应用日志",
+ "pageSize": "每页条数",
+ "pageSizeOption": "{count} 条",
+ "filter": "筛选",
+ "reset": "重置筛选",
+ "emptyTitle": "暂无应用日志",
+ "emptyDesc": "当前筛选条件下没有可展示的应用记录。",
+ "colNodeId": "Node ID",
+ "colVersion": "版本",
+ "colResult": "结果",
+ "colChecksum": "Checksum",
+ "colTime": "时间",
+ "colMessage": "消息",
+ "colActions": "操作",
+ "detail": "详情",
+ "pageInfo": "第 {current} / {total} 页,共 {count} 条记录。",
+ "prev": "上一页",
+ "next": "下一页",
+ "loadFailed": "加载失败",
+ "cleared": "应用日志已清空",
+ "clearedDesc": "共删除 {count} 条记录",
+ "clearFailed": "清空应用日志失败",
+ "unknownError": "未知错误",
+ "clearTitle": "确认清空应用日志?",
+ "clearDesc": "该操作将删除全部 {count} 条应用日志记录,且不可撤销。清空后节点仍会照常上报新的应用结果。",
+ "clearing": "清空中...",
+ "confirmClear": "确认清空",
+ "success": "成功",
+ "warning": "警告",
+ "failed": "失败",
+ "sheetTitle": "应用日志详情",
+ "sheetDesc": "查看单条应用日志的完整结果与校验信息。",
+ "versionBadge": "版本: {version}",
+ "createdAt": "创建时间",
+ "targetChecksum": "目标 Checksum",
+ "mainChecksum": "主配置摘要",
+ "routeChecksum": "路由配置摘要",
+ "supportFiles": "支持文件数",
+ "message": "消息",
+ "close": "关闭"
+ },
+ "configVersions": {
+ "title": "配置版本",
+ "description": "查看历史快照、预览待发布配置差异,并在需要时重新激活旧版本。",
+ "refresh": "刷新",
+ "cleanupOld": "清理旧版本",
+ "viewDiff": "查看差异",
+ "forcePublish": "强制发布",
+ "previewPublish": "发布",
+ "emptyTitle": "暂无历史版本",
+ "emptyDesc": "当前还没有可查看的发布记录,请先触发一次配置发布。",
+ "colVersion": "版本号",
+ "colStatus": "状态",
+ "colAuthor": "创建人",
+ "colChecksum": "Checksum",
+ "colCreatedAt": "创建时间",
+ "colActions": "操作",
+ "active": "当前激活",
+ "history": "历史版本",
+ "system": "系统",
+ "snapshot": "快照",
+ "activate": "激活",
+ "loadFailed": "加载失败",
+ "previewLoadFailed": "加载预览失败",
+ "diffLoadFailed": "加载差异失败",
+ "publishSuccess": "发布成功",
+ "publishSuccessDesc": "版本 {version}",
+ "publishFailed": "发布失败",
+ "activateSuccess": "激活成功",
+ "activateSuccessDesc": "版本 {version}",
+ "activateFailed": "激活失败",
+ "cleanupDone": "清理完成",
+ "cleanupDoneDesc": "已删除 {count} 个历史快照",
+ "cleanupFailed": "清理失败",
+ "unknownError": "未知错误",
+ "confirmPublishTitle": "确认发布配置",
+ "confirmPublishDesc": "将把当前待发布配置生成新版本并设为激活版本,节点将随后拉取更新。",
+ "confirmPublish": "确认发布",
+ "confirmForceTitle": "确认强制发布",
+ "confirmForceDesc": "将忽略配置变化检查并立即生成一个新版本,请确认你确实需要重新发布。",
+ "confirmActivateTitle": "确认激活版本",
+ "confirmActivateDesc": "确认将版本 {version} 设为当前激活版本吗?",
+ "confirmActivate": "确认激活",
+ "previewTitle": "发布预览",
+ "previewDesc": "查看待发布配置的渲染结果与支持文件。",
+ "loadingPreview": "正在加载预览...",
+ "routeCount": "规则 {count} 条",
+ "websiteCount": "网站 {count} 个",
+ "mainConfig": "主配置",
+ "routeConfig": "路由配置",
+ "supportFiles": "支持文件 ({count})",
+ "noSupportFiles": "当前发布不需要额外支持文件。",
+ "noChanges": "当前规则与已激活版本一致,无法重复发布。",
+ "close": "关闭",
+ "publishing": "发布中...",
+ "diffTitle": "配置差异",
+ "diffDesc": "对比当前待发布配置与已激活版本之间的差异。",
+ "loadingDiff": "正在加载差异数据...",
+ "activeVersion": "激活版本",
+ "none": "无",
+ "addedDomains": "新增域名",
+ "removedDomains": "删除域名",
+ "modifiedDomains": "修改域名",
+ "itemCount": "{count} 项",
+ "noChange": "无变更",
+ "emptyValue": "空",
+ "mainChanged": "主配置 已变化",
+ "mainUnchanged": "主配置 无变化",
+ "wafChanged": "WAF 已变化",
+ "wafUnchanged": "WAF 无变化",
+ "websiteDelta": "网站数 {from} → {to}",
+ "optionKey": "参数",
+ "activeValue": "激活值",
+ "pendingValue": "待发布值",
+ "noOptionChanges": "当前无 OpenResty 参数变化。",
+ "snapshotTitle": "版本快照",
+ "snapshotVersion": "版本 {version}",
+ "snapshotDesc": "查看历史配置版本的完整快照内容。",
+ "loadingSnapshot": "正在加载快照...",
+ "createdBy": "创建人",
+ "createdAt": "创建时间",
+ "snapshotLoadFailed": "加载版本快照失败",
+ "cleanupTitle": "清理历史快照",
+ "cleanupDesc": "删除旧的历史快照配置,系统将始终保护当前已激活的版本不被删除。",
+ "keepCount": "保留最近快照个数",
+ "keepHint": "默认为 10 个,最少需保留 3 个。",
+ "keepMin": "最少保留 3 个历史快照",
+ "cleaning": "清理中...",
+ "confirmCleanup": "确认清理"
+ }
+}
diff --git a/frontend/messages/fragments/security.en.json b/frontend/messages/fragments/security.en.json
new file mode 100644
index 00000000..7854d61d
--- /dev/null
+++ b/frontend/messages/fragments/security.en.json
@@ -0,0 +1,1080 @@
+{
+ "waf": {
+ "title": "WAF",
+ "createRule": "New rule",
+ "ruleGroups": "Rule groups",
+ "ruleGroupsDesc": "Build WAF protection rules with a visual flow. Global rules always apply to every site.",
+ "refresh": "Refresh",
+ "loading": "Loading WAF rule groups...",
+ "empty": "No rule groups yet. The system usually creates a global group automatically.",
+ "created": "Rule created",
+ "groupDeleted": "Rule group deleted",
+ "deleteTitle": "Delete rule group",
+ "deleteDesc": "Delete rule group “{name}”? This cannot be undone.",
+ "deleting": "Deleting...",
+ "confirmDelete": "Delete",
+ "operationFailed": "Operation failed",
+ "autoConfigMustBeObject": "Automatic configuration must be a JSON object.",
+ "autoConfigInvalid": "Invalid automatic configuration format",
+ "saveFailed": "Save failed",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "enable": "Enabled",
+ "disable": "Disabled",
+ "global": "Global",
+ "custom": "Custom",
+ "allSites": "All sites",
+ "siteCount": "{count} sites",
+ "columns": {
+ "name": "Name",
+ "type": "Type",
+ "status": "Status",
+ "nodeCount": "Nodes",
+ "scope": "Scope",
+ "updatedAt": "Updated",
+ "actions": "Actions"
+ },
+ "actions": {
+ "compose": "Edit",
+ "delete": "Delete"
+ },
+ "createDialog": {
+ "title": "New WAF rule",
+ "description": "After creation you will open the editor and configure the processing flow.",
+ "name": "Rule name",
+ "namePlaceholder": "e.g. Edge protection",
+ "submit": "Create and edit"
+ },
+ "editor": {
+ "missingId": "A valid rule ID is required.",
+ "loadFailed": "Failed to load the rule. Please try again.",
+ "reload": "Reload",
+ "back": "Back",
+ "graphValid": "Graph is valid",
+ "issueCount": "{count} issues",
+ "unsaved": "Unsaved",
+ "enableRule": "Enable rule",
+ "format": "Format",
+ "formatTitle": "Auto-arrange nodes and edges",
+ "saving": "Saving...",
+ "save": "Save",
+ "saved": "Rule graph saved",
+ "conflict": "This rule was updated elsewhere. Please reload.",
+ "saveCheckNodes": "Save failed. Check the highlighted nodes and edges.",
+ "ruleEnabled": "Rule enabled",
+ "ruleDisabled": "Rule disabled",
+ "leaveTitle": "Unsaved changes",
+ "leaveDesc": "You have unsaved changes. Leave anyway?",
+ "leaveConfirm": "Leave",
+ "deleteNode": "Delete node",
+ "systemNodeLocked": "System nodes cannot be deleted",
+ "deleteEdge": "Delete edge",
+ "propsTitle": "Node properties",
+ "propsDesc": "Configure this processing unit",
+ "systemNodeNoConfig": "System nodes do not need configuration.",
+ "displayName": "Display name",
+ "ips": "IP addresses",
+ "cidrs": "CIDR ranges",
+ "ipGroups": "IP groups",
+ "countries": "Country codes",
+ "countriesDesc": "{count} countries/regions, showing names and ISO codes",
+ "regions": "Region codes",
+ "regionsDesc": "{count} first-level regions. Search by name or code.",
+ "uaCheck": "UA check",
+ "enableUaCheck": "Enable UA check",
+ "enableUaCheckTip": "When enabled, requests without a User-Agent return False",
+ "block": "Block",
+ "blockTip": "A match returns false and takes priority over match rules",
+ "blockBots": "Block common crawler UAs",
+ "blockBotsTip": "Browser or OS classified as Bot (bot / spider / crawler / slurp, e.g. Googlebot)",
+ "blockAbnormal": "Block abnormal UAs",
+ "blockAbnormalTip": "Browser classified as Other or Unknown (excludes search-engine bots; use the switch above for crawlers)",
+ "blockCustom": "Block custom UAs",
+ "blockCustomTip": "Returns false if the raw User-Agent matches any regex (Lua pattern syntax)",
+ "customUaPatterns": "Custom UA regex",
+ "customUaPatternsTip": "One Lua-pattern regex per line. Any match returns false. Max 32.",
+ "uaMatch": "UA match",
+ "matchMode": "Match mode",
+ "matchModeTip": "Used when both browser and OS selections are set",
+ "matchOr": "OR",
+ "matchAnd": "AND",
+ "browsers": "Browsers",
+ "os": "Operating systems",
+ "security": "Security checks",
+ "securityTip": "Any enabled rule match returns False",
+ "basicProtection": "Basic protection",
+ "help": "Help",
+ "oneValuePerLine": "One value per line",
+ "selectedCount": "{count} selected",
+ "pleaseSelect": "Select",
+ "createLabel": "New {label}",
+ "searchNameOrCode": "Search name or code",
+ "addCode": "Add code",
+ "searchOptions": "Search {count} options by name or code",
+ "noMatchingOptions": "No matching options",
+ "algorithm": "Algorithm",
+ "algorithmFast": "Fast",
+ "algorithmSlow": "Robust",
+ "difficulty": "Difficulty",
+ "sessionTtl": "Session TTL (seconds)",
+ "challengeTtl": "Challenge TTL (seconds)",
+ "statusCode": "HTTP status code",
+ "responseBody": "HTML response body",
+ "bytes": "bytes",
+ "securityRules": {
+ "path_traversal": {
+ "label": "Path traversal",
+ "tip": "Detect ../ and encoded variants in Path / Query / Body"
+ },
+ "file_inclusion": {
+ "label": "File inclusion (LFI/RFI)",
+ "tip": "Detect php://, file://, /etc/passwd, and similar in Path / Query / Body"
+ },
+ "sql_injection": {
+ "label": "SQL injection",
+ "tip": "Detect SQL injection patterns in Query / Body / Header / Cookie"
+ },
+ "command_injection": {
+ "label": "Command injection",
+ "tip": "Detect OS command injection patterns in Query / Body / Header"
+ },
+ "xss": {
+ "label": "XSS",
+ "tip": "Detect reflected XSS patterns in Query / Body / Header"
+ },
+ "ssrf": {
+ "label": "SSRF",
+ "tip": "Detect private addresses and dangerous protocols in Query / Body"
+ },
+ "malicious_upload": {
+ "label": "Malicious upload",
+ "tip": "Detect dangerous multipart filenames and extensions"
+ },
+ "xxe": {
+ "label": "XXE",
+ "tip": "Detect external-entity patterns in XML bodies"
+ },
+ "crlf_injection": {
+ "label": "CRLF injection",
+ "tip": "Detect newline injection in Header / Query / Body"
+ }
+ }
+ }
+ },
+ "ipGroups": {
+ "title": "IP groups",
+ "create": "New IP group",
+ "listTitle": "IP groups",
+ "listDesc": "Maintain manual, automatic, and subscription IP sets that WAF allow/deny lists can reference.",
+ "refresh": "Refresh",
+ "loading": "Loading IP groups...",
+ "empty": "No IP groups yet. Create one first.",
+ "updated": "IP group updated",
+ "created": "IP group created",
+ "deleted": "IP group deleted",
+ "synced": "Sync completed",
+ "ipRemoved": "IP removed",
+ "testHit": "Rule test finished. {count} IPs matched.",
+ "testMiss": "Rule test finished. No IPs matched.",
+ "deleteTitle": "Delete IP group",
+ "deleteDesc": "Delete IP group “{name}”? This cannot be undone.",
+ "deleting": "Deleting...",
+ "confirmDelete": "Delete",
+ "operationFailed": "Operation failed",
+ "autoConfigMustBeObject": "Automatic configuration must be a JSON object.",
+ "types": {
+ "manual": "Manual",
+ "automatic": "Automatic",
+ "subscription": "Subscription"
+ },
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "noSyncRecord": "No sync history",
+ "columns": {
+ "name": "Name",
+ "type": "Type",
+ "status": "Status",
+ "ipCount": "IPs",
+ "refCount": "References",
+ "syncStatus": "Sync status",
+ "updatedAt": "Updated",
+ "actions": "Actions"
+ },
+ "actions": {
+ "view": "View",
+ "edit": "Edit",
+ "test": "Test rule",
+ "runNow": "Run now",
+ "syncNow": "Sync now",
+ "delete": "Delete"
+ },
+ "ban": {
+ "permanent": "Permanent",
+ "expired": "Expired",
+ "minutesLater": "in {count} min",
+ "hoursLater": "in {count} hr"
+ },
+ "presets": {
+ "scan404": "High-frequency 404 scan from one IP",
+ "directAccess": "Abnormal direct access from one IP"
+ },
+ "dialog": {
+ "editTitle": "Edit {name}",
+ "createTitle": "New IP group",
+ "description": "Maintain manual, automatic, and subscription IP sets that WAF allow/deny lists can reference.",
+ "name": "IP group name",
+ "nameRequired": "Enter an IP group name",
+ "type": "Type",
+ "enable": "Enable IP group",
+ "enableDesc": "When disabled, the configuration is kept but not expanded into the WAF runtime list on publish.",
+ "subscriptionUrl": "Subscription URL",
+ "subscriptionUrlRequired": "Subscription type requires a URL",
+ "subscriptionFormat": "Subscription format",
+ "formatText": "Text list",
+ "syncInterval": "Sync interval (minutes)",
+ "syncIntervalDesc": "Minimum 1 minute. Default 1440 minutes.",
+ "mappingRule": "JSON mapping rule",
+ "mappingPlaceholder": "Leave empty for the root array",
+ "autoIntervalDesc": "How often to mine malicious IPs from request logs. Minimum 1 minute, default 1440 minutes.",
+ "presetRules": "Preset rules",
+ "autoConfigJson": "Automatic configuration JSON",
+ "ipList": "IPs / CIDRs",
+ "ipListSubscriptionHint": "The next subscription sync overwrites this list. You can still save the current contents first.",
+ "ipListManualHint": "One IP or CIDR per line.",
+ "saving": "Saving...",
+ "save": "Save IP group"
+ },
+ "testDialog": {
+ "title": "Automatic rule test result",
+ "description": "Replay request logs against the current automatic configuration JSON.",
+ "testing": "Testing...",
+ "summary": "Lookback {lookback} · {rules} rules · {matched} IPs matched",
+ "testedAt": "Tested at: {time}",
+ "noMatch": "No IPs matched.",
+ "empty": "No test result yet.",
+ "close": "Close"
+ },
+ "viewDialog": {
+ "titleNamed": "View {name}",
+ "title": "View IP group",
+ "summary": "{type} · {count} IPs",
+ "fallbackDesc": "View IPs in this group and remove entries you no longer need.",
+ "loading": "Loading IP list...",
+ "noGroup": "No IP group selected.",
+ "emptyAuto": "This automatic rule has not captured any IPs yet. Click “Run now” to trigger a capture.",
+ "empty": "This IP group has no IPs.",
+ "subscriptionHint": "Subscription groups may restore deleted IPs on the next sync.",
+ "ipAddress": "IP address",
+ "capturedAt": "Captured at",
+ "banRemaining": "Ban remaining",
+ "actions": "Actions",
+ "deleteIp": "Delete {ip}",
+ "close": "Close",
+ "deleteTitle": "Delete IP",
+ "deleteDesc": "Remove {ip} from IP group “{name}”?",
+ "deleting": "Deleting...",
+ "confirmDelete": "Delete"
+ }
+ },
+ "rateLimits": {
+ "title": "Rate limits",
+ "subtitle": "Inspect edge request pressure and configure default concurrency and bandwidth limits.",
+ "loadingAuth": "Loading permissions...",
+ "forbiddenTitle": "Insufficient permissions",
+ "forbiddenDesc": "Only administrators can access rate-limit settings.",
+ "tabAnalysis": "Analysis",
+ "tabConfig": "Configuration",
+ "loadingPage": "Loading rate limits...",
+ "loadingPressure": "Loading request pressure...",
+ "loadFailed": "Load failed",
+ "emptyTitle": "No data",
+ "emptyDesc": "There are no access logs in the current time range.",
+ "topHost": "Highest average RPS host",
+ "topIp": "Highest average RPS IP",
+ "avgRate": "Average request rate for {range}",
+ "range": {
+ "h24": "24 hours",
+ "h72": "3 days"
+ },
+ "chart": {
+ "title": "Request pressure",
+ "empty": "No request-pressure data",
+ "requestRate": "Request rate",
+ "uniqueVisitors": "Unique visitors",
+ "visitorsPerBucket": "Visitors / bucket"
+ },
+ "config": {
+ "saved": "Rate-limit settings saved",
+ "saveFailed": "Save failed",
+ "loading": "Loading rate-limit settings...",
+ "preview": "View config preview",
+ "title": "Global default rate limits",
+ "description": "0 or empty means disabled by default. Sites without their own settings inherit these values. Publish a release to apply changes.",
+ "save": "Save",
+ "connPerServer": "Default concurrency (per site)",
+ "connPerServerHint": "Default maximum concurrent connections per site",
+ "connPerIp": "Default concurrency per IP",
+ "connPerIpHint": "Default maximum concurrent connections per IP",
+ "limitRate": "Default bandwidth limit",
+ "limitRateHint": "Default per-request bandwidth, e.g. 512k or 1m",
+ "reqPerIp": "Default request-rate limit per IP",
+ "reqPerIpHint": "Default request rate per IP, e.g. 10r/s or 100r/m. Leave empty to disable.",
+ "invalidConn": "Enter a non-negative integer for concurrency, or leave empty to disable",
+ "invalidRate": "Invalid rate format. Use 512k, 1m, a number, or leave empty to disable",
+ "invalidReq": "Invalid request-rate format. Use values like 10r/s or 100r/m, or leave empty to disable"
+ }
+ },
+ "pages": {
+ "title": "Pages",
+ "subtitle": "Host static sites at the edge. Upload a static package and select Pages as the upstream in proxy rules.",
+ "refresh": "Refresh",
+ "create": "New project",
+ "loadFailed": "Load failed",
+ "emptyTitle": "No Pages projects yet",
+ "emptyDesc": "Create a project, then upload a static deployment package.",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "spaFallback": "SPA fallback",
+ "strict404": "Strict 404",
+ "currentActive": "Currently active",
+ "none": "None",
+ "activatedAt": "Activated: ",
+ "notActivated": "Not activated",
+ "viewDetail": "View details",
+ "saveFailed": "Save failed",
+ "updated": "Project updated",
+ "created": "Project created",
+ "deleted": "Project deleted",
+ "deleteFailed": "Delete failed",
+ "editTitle": "Edit Pages project",
+ "createTitle": "New Pages project",
+ "editorDesc": "Configure static hosting settings. After uploading a package, select Pages as the upstream in proxy rules.",
+ "settingsDesc": "Configure static hosting settings. Saving syncs the project details and proxy references.",
+ "reset": "Reset",
+ "saving": "Saving...",
+ "saveChanges": "Save changes",
+ "createProject": "Create project",
+ "form": {
+ "name": "Project name",
+ "nameRequired": "Enter a project name",
+ "slug": "Project slug",
+ "slugPlaceholder": "Leave empty to auto-generate",
+ "entryFile": "Entry file",
+ "entryRequired": "Enter an entry file",
+ "rootDir": "Root directory",
+ "optional": "Optional",
+ "spaFallback": "SPA fallback",
+ "spaFallbackDesc": "Fall back to the specified path when no static file matches",
+ "fallbackPath": "Fallback path",
+ "fallbackPathPrefix": "Fallback path must start with /",
+ "apiProxy": "API reverse proxy",
+ "apiProxyDesc": "Attach API proxy rules to the static site",
+ "matchPath": "Match path",
+ "matchPathPrefix": "Match path must start with /",
+ "backend": "Backend URL",
+ "backendPrefix": "Backend URL must start with http:// or https://",
+ "rewrite": "Rewrite rule"
+ },
+ "detail": {
+ "invalidId": "A valid Pages project ID is required.",
+ "loading": "Loading project details...",
+ "loadFailed": "Failed to load project details",
+ "back": "Back to list",
+ "notFound": "This Pages project does not exist or has been deleted.",
+ "deploymentCount": "{count} deployments",
+ "tabDeployments": "Deployments",
+ "tabSettings": "Settings"
+ },
+ "danger": {
+ "title": "Danger zone",
+ "deleteProject": "Delete project",
+ "deleteHint": "Permanently delete {name} ({slug}) and all of its deployments.",
+ "confirmTitle": "Delete Pages project",
+ "confirmDesc": "Delete project {name}? This cannot be undone.",
+ "confirm": "Delete"
+ },
+ "upload": {
+ "selectFile": "Select a deployment package",
+ "success": "Deployment package uploaded",
+ "failed": "Upload failed",
+ "unsupported": "Only zip, tar.gz, tar.xz, tar.bz2, tar, and 7z packages are supported",
+ "title": "Upload deployment package",
+ "description": "Upload an immutable static archive. You can activate it from deployment history afterward.",
+ "localPackage": "Local package",
+ "dropHint": "Drop a package here, or click to choose a file",
+ "selected": "Selected {name} ({size})",
+ "chooseArchive": "Choose a supported archive.",
+ "entry": "Deployment entry",
+ "entryHint": "The entry comes from project settings. Uploading a package does not overwrite it.",
+ "processing": "Processing on server",
+ "progress": "Upload progress",
+ "pleaseWait": "Please wait",
+ "uploading": "Uploading...",
+ "submit": "Upload and create deployment"
+ },
+ "files": {
+ "loadFailed": "Failed to load file list",
+ "empty": "No file records",
+ "path": "Path",
+ "size": "Size"
+ },
+ "history": {
+ "source": {
+ "manual_upload": "Local upload",
+ "manual_url": "URL import"
+ },
+ "trigger": {
+ "manual_upload": "Manual upload",
+ "manual_url": "Manual import",
+ "manual_sync": "Manual sync",
+ "scheduled_auto_update": "Scheduled update"
+ },
+ "fileMeta": "{checksum} · {count} files · ",
+ "createdAt": "Created {time}",
+ "activatedAt": " · Activated {time}",
+ "collapseFiles": "Collapse file list",
+ "expandFiles": "Expand file list",
+ "deploymentNumber": "Deployment #{number}",
+ "activate": "Activate",
+ "delete": "Delete",
+ "activated": "Historical deployment activated. Auto-update (if enabled) has been turned off.",
+ "activateFailed": "Activation failed",
+ "deleted": "Deployment deleted",
+ "deleteFailed": "Delete failed",
+ "activateTitle": "Activate historical deployment",
+ "deleteTitle": "Delete deployment",
+ "activateDesc": "Activating another historical deployment stops the current source task. If auto-update is enabled, it will be turned off.",
+ "deleteDesc": "Delete deployment #{number}? This cannot be undone.",
+ "confirm": "Confirm",
+ "loading": "Loading deployment history...",
+ "loadFailed": "Failed to load deployment history",
+ "productionHint": "This is the live production deployment. Changing the active deployment takes effect immediately.",
+ "manualUpload": "Manual upload",
+ "emptyProductionTitle": "No production deployment",
+ "emptyProductionDesc": "Upload a local package, or configure a Remote URL / GitHub Release source and sync.",
+ "historyHint": "Deployments are immutable. Source info is a snapshot from creation time. You can activate or delete historical deployments.",
+ "emptyTitle": "No deployments",
+ "emptyDesc": "Upload a local package, or configure a Remote URL / GitHub Release source and sync."
+ },
+ "source": {
+ "status": {
+ "idle": "Idle",
+ "checking": "Checking",
+ "update_available": "Update available",
+ "syncing": "Syncing",
+ "failed": "Last sync failed",
+ "attention": "Needs confirmation"
+ },
+ "check": "Check",
+ "syncPublish": "Sync and publish",
+ "missingExecutionId": "Missing task execution ID",
+ "actionDone": "Source {action} completed",
+ "actionFailed": "Source {action} failed",
+ "checkQueued": "Check task queued",
+ "checkQueueFailed": "Failed to queue check task",
+ "syncQueued": "Sync task queued",
+ "syncQueueFailed": "Failed to queue sync task",
+ "title": "Deployment source",
+ "loadingDesc": "Loading source configuration...",
+ "independentDesc": "Source configuration is independent of deployment history.",
+ "loadFailed": "Failed to load deployment source",
+ "configDesc": "Configure a remote source and sync to publish. Results appear in deployment history.",
+ "manualBadge": "Manual deploy",
+ "localPackage": "Local package",
+ "noRemote": "No persistent remote source. Upload a package, then activate it from deployment history.",
+ "submitFailed": "Failed to queue source task",
+ "statusFailed": "Failed to read task status",
+ "waitStopped": "Automatic waiting stopped. The task may still be running in the background.",
+ "refreshStatus": "Refresh task status",
+ "configure": "Configure",
+ "checkUpdate": "Check for updates",
+ "refresh": "Refresh",
+ "confirmTitle": "Publish the current GitHub revision",
+ "confirmLead": "This publishes the exact revision currently shown on the card:",
+ "revisionUnavailable": "The current revision is no longer available. Refresh and try again.",
+ "confirmPublish": "Confirm and publish",
+ "updated": "Deployment source updated",
+ "updateFailed": "Failed to update deployment source",
+ "switchedManual": "Switched back to manual deploy",
+ "switchFailed": "Switch failed",
+ "urlRequired": "Enter a Remote URL",
+ "urlInvalid": "Enter a valid HTTP(S) URL",
+ "githubUrlInvalid": "Enter a public repository URL in https://github.com/'{owner}'/'{repo}' format",
+ "tagInvalid": "Release tag must be a valid Git ref (1–255 bytes; / # & = are allowed)",
+ "assetInvalid": "Asset name must be 1–255 bytes and cannot be a path or contain control, newline, or bidi characters",
+ "intervalInvalid": "Check interval must be an integer between 5 and 1440 minutes",
+ "useManual": "Use manual deploy",
+ "saveRemote": "Save Remote source",
+ "saveGithub": "Save GitHub source",
+ "settingsTitle": "Deployment source",
+ "settingsDesc": "Choose a deployment source.",
+ "mode": "Source type",
+ "manual": "Manual deploy",
+ "manualHint": "Keep existing deployments and the current production version. Create new deployments with “Upload package”.",
+ "urlHint": "Enter an HTTP(S) URL that downloads a deployment package directly.",
+ "allowInsecure": "Allow insecure connections",
+ "allowInsecureDesc": "Public and private addresses are allowed by default. Enabling this skips TLS certificate verification for self-signed or private CAs.",
+ "switchManualTitle": "Switch back to manual deploy",
+ "switchManualDesc": "The current source configuration will be deleted. Existing deployments and the current production version are kept.",
+ "githubUrl": "GitHub repository URL",
+ "githubUrlHint": "Only public github.com repositories are supported.",
+ "releaseSelect": "Release selection",
+ "latestRelease": "Latest release",
+ "pinnedTag": "Pinned tag",
+ "pinnedTagHint": "Check and sync this exact tag.",
+ "autoUpdate": "Auto-update",
+ "autoUpdateHint": "Automatically sync and publish when a new release is found.",
+ "checkInterval": "Check interval (minutes)",
+ "checkIntervalHint": "Can be set to 5–1440 minutes.",
+ "assetName": "Release asset filename",
+ "noRecord": "No record yet",
+ "address": "URL",
+ "verifyCert": "Verify certificate",
+ "lastSync": "Last sync",
+ "neverSynced": "Not synced yet",
+ "applied": "Applied",
+ "latestReleaseLabel": "Latest release",
+ "pinnedTagLabel": "Pinned tag · {tag}",
+ "tagMissing": "Not provided",
+ "assetChangedTitle": "Release asset changed and needs confirmation",
+ "assetChangedDesc": "The remote revision does not match the published content. Verify the version and assets, then confirm publishing this exact revision.",
+ "autoUpdateOn": "On",
+ "autoUpdateOff": "Off",
+ "intervalMinutes": "{count} minutes",
+ "nextCheck": "Next check",
+ "waitingSchedule": "Waiting for schedule",
+ "lastCheck": "Last check",
+ "neverChecked": "Not checked yet",
+ "remote": "Remote"
+ }
+ },
+ "accessLogs": {
+ "title": "Access logs",
+ "subtitle": "View traffic overview, IP details, and request logs.",
+ "refresh": "Refresh",
+ "cleanup": "Clean up logs",
+ "tabOverview": "Overview",
+ "tabIps": "IPs",
+ "tabList": "Logs",
+ "cleaned": "Cleaned up {count} logs",
+ "cleanupFailed": "Cleanup failed",
+ "loadingTitle": "Loading access logs",
+ "loadingDesc": "Preparing the page...",
+ "loadFailed": "Load failed",
+ "range": {
+ "h24": "24 hours",
+ "h168": "7 days",
+ "h360": "15 days",
+ "h720": "30 days"
+ },
+ "rangeHint": {
+ "hours24": "Last 24 hours",
+ "days": "Last {count} days",
+ "hours": "Last {count} hours"
+ },
+ "sort": {
+ "logged_at:desc": "Newest first",
+ "logged_at:asc": "Oldest first",
+ "status_code:desc": "Status high to low",
+ "status_code:asc": "Status low to high",
+ "remote_addr:asc": "IP A–Z",
+ "remote_addr:desc": "IP Z–A",
+ "total_requests:desc": "Requests high to low",
+ "total_requests:asc": "Requests low to high",
+ "request_length:desc": "Inbound high to low",
+ "request_length:asc": "Inbound low to high",
+ "bytes_sent:desc": "Outbound high to low",
+ "bytes_sent:asc": "Outbound low to high",
+ "success_ratio:desc": "2xx ratio high to low",
+ "success_ratio:asc": "2xx ratio low to high",
+ "last_seen_at:desc": "Last seen newest first",
+ "last_seen_at:asc": "Last seen oldest first"
+ },
+ "cache": {
+ "hit": "Hit",
+ "origin": "Origin",
+ "uncached": "Uncached"
+ },
+ "filters": {
+ "pickTime": "Pick a time",
+ "remoteAddr": "Source IP",
+ "remoteAddrPlaceholder": "Search by IP",
+ "host": "Host",
+ "hostPlaceholder": "Search by host",
+ "statusCode": "Status code",
+ "statusCodePlaceholder": "Enter a status code, e.g. 404",
+ "more": "More filters",
+ "nodeId": "Node ID",
+ "nodeIdPlaceholder": "Search by node_id",
+ "path": "Path",
+ "pathPlaceholder": "Search by path",
+ "timeRange": "Time range",
+ "pageSize": "Per page",
+ "pageSizeItem": "{count} items",
+ "apply": "Filter",
+ "clear": "Clear"
+ },
+ "cleanupDialog": {
+ "invalidDays": "Retention days must be greater than 0",
+ "title": "Clean up access logs",
+ "description": "Delete access logs older than the retention period. This cannot be undone.",
+ "policy": "Retention policy",
+ "keepDays": "Keep last {days} days",
+ "custom": "Custom days",
+ "customDays": "Custom retention days",
+ "cleaning": "Cleaning...",
+ "confirm": "Clean up"
+ },
+ "detail": {
+ "title": "Access log details",
+ "description": "All fields for this request. Use the IPs tab for IP analysis.",
+ "logId": "Log ID",
+ "requestTime": "Request time",
+ "storedAt": "Stored at",
+ "node": "Node",
+ "nodeId": "Node ID",
+ "clientIp": "Client IP",
+ "region": "Region",
+ "host": "Host",
+ "status": "Status",
+ "cache": "Cache",
+ "bytesSent": "Outbound",
+ "requestLength": "Inbound",
+ "duration": "Duration",
+ "path": "Path",
+ "page": "Page {page}",
+ "prev": "Previous",
+ "next": "Next",
+ "listTitle": "Logs",
+ "empty": "No access logs",
+ "time": "Time",
+ "view": "View details"
+ },
+ "ip": {
+ "needRange": "Enter a start and end time",
+ "invalidTime": "Invalid time format",
+ "endAfterStart": "End time must be after start time",
+ "maxRange": "Time range cannot exceed 30 days",
+ "page": "Page {page}",
+ "total": " · {count} IPs",
+ "prev": "Previous",
+ "next": "Next",
+ "customRange": "Custom range",
+ "customPending": "Custom (not applied)",
+ "timeRange": "Time range",
+ "current": "Current: {range}",
+ "custom": "Custom",
+ "start": "Start",
+ "end": "End",
+ "apply": "Apply",
+ "applyHint": "Click Apply after changing the time. The detail window matches the list range.",
+ "sort": "Sort",
+ "pageSize": "Per page",
+ "title": "IPs",
+ "empty": "No IP data",
+ "region": "Region",
+ "requests": "Requests",
+ "successRatio": "2xx ratio",
+ "inbound": "Inbound",
+ "outbound": "Outbound",
+ "lastSeen": "Last seen",
+ "view": "View IP details",
+ "dialogTitle": "IP details",
+ "dialogDesc": ". View this IP’s traffic trend, distribution, and WAF IP-group actions."
+ },
+ "analysis": {
+ "emptySeries": "No {title} data",
+ "selectGroup": "Select an IP group to join",
+ "added": "Added {ip} to IP group “{name}”",
+ "addFailed": "Failed to add to IP group",
+ "removed": "Removed {ip} from IP group “{name}”",
+ "removeFailed": "Remove failed",
+ "joinTitle": "Add IP to IP group",
+ "targetIp": "Target IP:",
+ "loadingGroups": "Loading IP groups",
+ "loadGroupsFailed": "Failed to load IP groups",
+ "alreadyIn": "This IP is already in these IP groups",
+ "alreadyInHint": "You can remove it, or add it to another IP group.",
+ "entryCount": "{type} · {count} entries",
+ "delete": "Delete",
+ "cannotDelete": "Cannot delete manually",
+ "notInAny": "This IP is not in any IP group yet.",
+ "addToOther": "Add to another IP group",
+ "noWritable": "No writable manual IP groups (or they already contain this IP).",
+ "selectManual": "Select a manual IP group",
+ "close": "Close",
+ "processing": "Working...",
+ "joinSelected": "Add to selected IP group",
+ "recentHours": "Last {hours} hours",
+ "invalidIp": "No valid IP.",
+ "addToGroup": "Add IP to IP group",
+ "loading": "Loading IP analysis",
+ "loadFailed": "Failed to load IP analysis",
+ "totalRequests": "Total requests",
+ "errors": "Errors",
+ "bandwidth": "Bandwidth served",
+ "received": "Data received",
+ "uniqueHosts": "Unique hosts",
+ "uniquePaths": "Unique paths",
+ "trend": "IP request trend",
+ "trendMeta": "{ip} · {range} · {minutes}-minute buckets",
+ "refresh": "Refresh",
+ "trendFailed": "Failed to load trend",
+ "trendEmpty": "This IP has no requests in {range}."
+ },
+ "overview": {
+ "loadingTitle": "Loading traffic overview",
+ "loadingDesc": "Aggregating requests, visits, and bandwidth trends...",
+ "loadFailed": "Failed to load traffic overview",
+ "emptyTitle": "No overview data",
+ "emptyDesc": "There are no traffic stats in the current time range.",
+ "uniqueVisitors": "{hint} · unique visitors",
+ "served": "{hint} · data served",
+ "requestsHint": "Watch for unusual spikes or drops in request volume.",
+ "requests": "Requests",
+ "deviceDesc": "Request share by device type.",
+ "deviceEmpty": "No device-type data",
+ "statusDesc": "Request share by HTTP status code.",
+ "statusEmpty": "No status-code data",
+ "topPathsDesc": "Most requested paths.",
+ "topHostsDesc": "Hosts with the most traffic.",
+ "topIpsDesc": "Source IPs with the most requests.",
+ "browsersDesc": "Requests aggregated by browser.",
+ "osDesc": "Requests aggregated by operating system.",
+ "uaDesc": "Requests ranked by raw User-Agent."
+ },
+ "toolbar": {
+ "hostsFiltered": "{count} hosts filtered",
+ "filterByHost": "Filter by host",
+ "filterHosts": "Filter hosts",
+ "clear": "Clear",
+ "searchPlaceholder": "Search zone or host",
+ "loadingHosts": "Loading hosts…",
+ "loadHostsFailed": "Failed to load hosts",
+ "retry": "Retry",
+ "noRegistered": "No registered hosts",
+ "noMatch": "No matching hosts",
+ "selectZone": "Select zone {name}",
+ "selectHost": "Select host {name}",
+ "selectedCount": "{count} hosts selected"
+ }
+ },
+ "cloudflare": {
+ "title": "Cloudflare",
+ "connectionSettings": "Connection settings",
+ "addGroup": "New group",
+ "loadingOverview": "Loading Cloudflare overview...",
+ "notReadyTitle": "Cloudflare connection is not ready",
+ "notReadyDesc": "Import an existing Cloudflare DNS account, or enter a standalone API token and test the connection.",
+ "configure": "Configure connection",
+ "groupsTitle": "Pointing groups",
+ "groupsDesc": "Manage the nodes and domain members behind Cloudflare A records.",
+ "loadingGroups": "Loading pointing groups...",
+ "emptyTitle": "No pointing groups",
+ "emptyDesc": "After creating a group, you can sync domain members to selected edge nodes.",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "memberSummary": "{count} members · new members default orange-cloud {proxy}",
+ "proxyOn": "on",
+ "proxyOff": "off",
+ "manage": "Manage",
+ "sync": "Sync",
+ "delete": "Delete",
+ "created": "Pointing group created",
+ "syncQueued": "Group sync queued",
+ "deleted": "Pointing group and remote records deleted",
+ "deleteTitle": "Delete pointing group",
+ "deleteDesc": "This deletes all members of {name} and the remote A records managed by this module. This cannot be undone.",
+ "confirmDelete": "Delete",
+ "loadingDetail": "Loading group details...",
+ "groupUpdated": "Group settings updated",
+ "memberAddedOne": "Domain added and queued for sync",
+ "memberAddedMany": "Added {count} domains and queued them for sync",
+ "memberAddFailed": "Failed to add {count} domains",
+ "memberPartial": "Partial success: {succeeded} added, {failed} failed",
+ "proxyUpdated": "Orange-cloud setting updated and queued for sync",
+ "memberSyncQueued": "Member sync queued",
+ "memberDeleted": "Member and remote A record deleted",
+ "back": "Back to list",
+ "refresh": "Refresh",
+ "edit": "Edit",
+ "addDomain": "Add domain",
+ "currentPoint": "Current target",
+ "activeNode": "Active node {name} · {ip}",
+ "syncEnabled": "Sync enabled",
+ "syncDisabled": "Sync disabled",
+ "primaryNode": "Primary {name}",
+ "backupNode": "Backup {name}",
+ "backupUnset": "Not set",
+ "members": "Domain members",
+ "membersDesc": "Per-member orange-cloud is the only source of truth during sync.",
+ "noMembers": "No domain members yet.",
+ "columns": {
+ "domain": "Domain",
+ "desiredIp": "Desired IP",
+ "status": "Status",
+ "proxied": "Orange cloud",
+ "actions": "Actions"
+ },
+ "pendingSync": "Pending sync",
+ "remove": "Remove",
+ "groupDialog": {
+ "editTitle": "Edit pointing group",
+ "createTitle": "New pointing group",
+ "description": "Phase 1 uses the primary node as the active node. Backup nodes are saved only and do not fail over automatically.",
+ "name": "Group name",
+ "primary": "Primary node",
+ "primaryPlaceholder": "Select an edge node with IPv4",
+ "noIp": "No IP configured",
+ "backup": "Backup node",
+ "none": "None",
+ "defaultProxied": "Enable orange cloud for new members by default",
+ "enableSync": "Enable sync",
+ "save": "Save"
+ },
+ "memberDialog": {
+ "title": "Add domain members",
+ "description": "Search and select in bulk. Joining creates or takes over the unique same-name A record. Multiple same-name A records are rejected.",
+ "zoneDomain": "Zone domain",
+ "searchPlaceholder": "Search domains or zones…",
+ "selected": "{count} selected",
+ "visible": "{count} visible",
+ "unselectAll": "Clear visible",
+ "selectVisible": "Select visible",
+ "clear": "Clear",
+ "noAvailable": "No available domains (already in a group, or not added in website management).",
+ "noMatch": "No matching domains",
+ "groupByZone": "Group by zone",
+ "selectZone": "Select zone {name}",
+ "zone": "Zone",
+ "proxied": "Enable orange-cloud proxy",
+ "submitCount": "Add and sync ({count})",
+ "submit": "Add and sync"
+ },
+ "tasks": {
+ "pending": "Pending",
+ "running": "Running",
+ "succeeded": "Succeeded",
+ "failed": "Failed",
+ "system": "System",
+ "manual": "Manual",
+ "retry": "Retry",
+ "schedule": "Scheduled",
+ "retried": "Sync task re-queued",
+ "newTaskId": "New task ID: {id}",
+ "retryFailed": "Failed to retry task",
+ "unknownError": "Unknown error",
+ "title": "Sync tasks",
+ "description": "Shows Cloudflare domain sync (sync_member) and group sync (sync_group) executions only.",
+ "statusPlaceholder": "Status",
+ "allStatus": "All statuses",
+ "refresh": "Refresh",
+ "loadFailed": "Failed to load sync tasks",
+ "loading": "Loading sync tasks...",
+ "emptyTitle": "No sync tasks",
+ "emptyDesc": "After adding domains or syncing a group, Cloudflare domain/group sync executions appear here.",
+ "columns": {
+ "task": "Task",
+ "status": "Status",
+ "trigger": "Trigger",
+ "duration": "Duration",
+ "result": "Result / error",
+ "createdAt": "Created"
+ },
+ "pageSummary": "{total} total, page {page}/{pages}",
+ "prev": "Previous",
+ "next": "Next",
+ "detailTitle": "Sync task details",
+ "defaultName": "Cloudflare sync",
+ "loadingDetail": "Loading task details...",
+ "status": "Status",
+ "trigger": "Trigger",
+ "retries": "Retries",
+ "duration": "Duration",
+ "taskId": "Task ID",
+ "createdAt": "Created",
+ "finishedAt": "Finished",
+ "result": "Result",
+ "error": "Error",
+ "logs": "Logs",
+ "close": "Close"
+ },
+ "settings": {
+ "saved": "Cloudflare connection saved",
+ "verified": "Cloudflare connection verified",
+ "cleared": "Cloudflare connection cleared",
+ "back": "Back to list",
+ "title": "Cloudflare connection",
+ "sourceTitle": "Credential source",
+ "sourceDesc": "The token should have Zone:Read and DNS:Edit. Tokens are never echoed in the API or UI.",
+ "source": "Connection source",
+ "importDns": "Import existing DNS account",
+ "standalone": "Standalone API token",
+ "dnsAccount": "Cloudflare DNS account",
+ "selectAccount": "Select a DNS account",
+ "dnsHint": "Only cloudflare-type DNS accounts are shown. Add one in",
+ "dnsLink": "DNS accounts",
+ "dnsHintAfter": ".",
+ "tokenKeep": "Leave empty to keep the existing token; saving replaces it",
+ "tokenEnter": "Enter a Cloudflare API token",
+ "save": "Save settings",
+ "test": "Test connection",
+ "clear": "Clear connection",
+ "statusTitle": "Current status",
+ "ready": "Token verified. Cloudflare pointing sync can run.",
+ "notReady": "After saving, you still need to test the connection. Sync is rejected until it is verified."
+ }
+ },
+ "openflareOps": {
+ "saved": "OpenFlare operations settings saved",
+ "saveFailed": "Save failed",
+ "tokenRotated": "Discovery token regenerated",
+ "tokenRotateFailed": "Failed to rotate token",
+ "kumaSynced": "Uptime Kuma sync ran",
+ "syncFailed": "Sync failed",
+ "invalidParams": "Validation failed",
+ "loading": "Loading OpenFlare operations settings...",
+ "loadFailed": "Load failed",
+ "save": "Save",
+ "agent": {
+ "title": "Agent runtime",
+ "description": "Heartbeat interval and offline threshold sync to nodes on the next heartbeat.",
+ "heartbeat": "Heartbeat interval ({duration})",
+ "offline": "Offline threshold ({duration})",
+ "wsUpgrade": "Enable WebSocket upgrade",
+ "wsUpgradeDesc": "After a successful HTTP heartbeat, try upgrading to WebSocket so config publishes can notify immediately.",
+ "updateRepo": "Agent update repository"
+ },
+ "geo": {
+ "title": "IP geolocation",
+ "description": "Controls the GeoIP source for node maps and similar views. Visitor access-log geolocation always uses MaxMind mmdb.",
+ "mode": "Lookup method",
+ "disabled": "Off",
+ "testIp": "Test IP",
+ "querying": "Looking up...",
+ "query": "Look up",
+ "country": "Country / region"
+ },
+ "pages": {
+ "title": "Pages static hosting",
+ "description": "Configure the upload size limit and how many historical deployments each project keeps.",
+ "maxSize": "Package size limit (MiB)",
+ "history": "Historical deployments to keep (0 = unlimited)",
+ "historyHint": "Each project keeps at most N deployments: the active one is always kept, then the rest fill newest-first. Extra inactive deployments are cleaned up after a successful upload. zip, tar.gz, tar.xz, tar.bz2, tar, and 7z are supported."
+ },
+ "discovery": {
+ "title": "Discovery token and deploy",
+ "description": "New nodes use the discovery token on first join. Rotate it from node management or the action below.",
+ "manageNodes": "Node management",
+ "rotate": "Rotate token",
+ "tokenReadonly": "Discovery token (read-only)",
+ "loading": "Loading...",
+ "notGenerated": "Not generated",
+ "command": "One-line deploy command",
+ "commandHint": "Enter a reachable Server URL and fetch a token first.",
+ "copied": "Command copied",
+ "copy": "Copy command"
+ },
+ "kuma": {
+ "title": "Uptime Kuma integration",
+ "description": "Diff-sync reverse-proxy sites to an Uptime Kuma instance.",
+ "syncNow": "Sync now",
+ "enable": "Enable Uptime Kuma",
+ "url": "Uptime Kuma URL",
+ "username": "Username",
+ "password": "Password",
+ "passwordKeep": "Leave empty to keep the current password",
+ "syncInterval": "Sync interval (minutes)",
+ "scope": "Monitor scope",
+ "allSites": "All sites",
+ "selectedSites": "Selected sites",
+ "selectedLabel": "Selected sites",
+ "pickSites": "Choose monitored sites",
+ "noneSelected": "No sites selected",
+ "interval": "Check interval (seconds)",
+ "retry": "Retries",
+ "retryInterval": "Retry interval (seconds)",
+ "timeout": "Request timeout (seconds)"
+ },
+ "duration": {
+ "minutes": "{count} min",
+ "seconds": "{count} sec"
+ },
+ "errors": {
+ "heartbeatMin": "Heartbeat interval cannot be less than 1000 milliseconds.",
+ "offlineMin": "Offline threshold cannot be less than 10000 milliseconds.",
+ "offlineRatio": "Offline threshold should be at least 3× the heartbeat interval.",
+ "kumaUrl": "Enter an Uptime Kuma URL.",
+ "kumaUser": "Enter an Uptime Kuma username.",
+ "syncInterval": "Sync interval must be a positive integer.",
+ "interval": "Heartbeat interval must be a positive integer.",
+ "retry": "Retry count must be a non-negative integer.",
+ "retryInterval": "Heartbeat retry interval must be a positive integer.",
+ "timeout": "Request timeout must be a positive integer.",
+ "pagesSize": "Pages package size limit must be between 1 and 2048 MiB.",
+ "pagesHistory": "Pages history count must be an integer ≥ 0 (0 means unlimited)."
+ },
+ "status": {
+ "title": "System status",
+ "loadFailed": "Failed to load system status",
+ "unknownError": "An unknown error occurred",
+ "autoRefresh": "Auto-refresh",
+ "refresh": "Refresh",
+ "overview": "Service overview",
+ "overviewDesc": "Basic service health metrics",
+ "uptime": "Uptime",
+ "goroutines": "Goroutines",
+ "heapObjects": "Heap objects",
+ "memory": "Memory",
+ "memoryDesc": "Primary memory usage",
+ "alloc": "Current allocation",
+ "totalAlloc": "Total allocated",
+ "sys": "System memory",
+ "nextGc": "Next GC target",
+ "usageRatio": "Current usage (Alloc / Sys)",
+ "heapStack": "Heap / stack",
+ "heapStackDesc": "Runtime heap and stack details",
+ "heapAlloc": "Heap allocated",
+ "heapSys": "Heap system",
+ "heapIdle": "Heap idle",
+ "heapInuse": "Heap in use",
+ "heapReleased": "Heap released",
+ "stackInuse": "Stack in use",
+ "stackSys": "Stack system",
+ "structs": "Internal structures",
+ "structsDesc": "Runtime management structure overhead",
+ "mspanInuse": "MSpan in use",
+ "mspanSys": "MSpan system",
+ "mcacheInuse": "MCache in use",
+ "mcacheSys": "MCache system",
+ "buckHashSys": "Profiling hash table",
+ "gcSys": "GC metadata",
+ "otherSys": "Other system memory",
+ "gc": "GC and allocations",
+ "gcDesc": "GC history and allocation counts",
+ "numGc": "GC runs",
+ "lastGc": "Time since last GC",
+ "pauseTotal": "Total GC pause",
+ "lastPause": "Last GC pause",
+ "mallocs": "Allocations",
+ "frees": "Frees",
+ "lookups": "Pointer lookups"
+ },
+ "siteModal": {
+ "title": "Choose monitored sites",
+ "description": "Select reverse-proxy sites to sync to Uptime Kuma. Search by site name or domain.",
+ "search": "Search sites",
+ "searchPlaceholder": "Search by name or domain...",
+ "selectFiltered": "Select filtered",
+ "clearFiltered": "Clear filtered",
+ "loading": "Loading sites...",
+ "loadFailed": "Failed to load sites",
+ "noMatch": "No matching sites",
+ "select": "Select",
+ "siteName": "Site name",
+ "primaryDomain": "Primary domain",
+ "selectedCount": "{count} monitored sites selected",
+ "save": "Save selection"
+ }
+ }
+}
diff --git a/frontend/messages/fragments/security.zh-CN.json b/frontend/messages/fragments/security.zh-CN.json
new file mode 100644
index 00000000..12ac4acc
--- /dev/null
+++ b/frontend/messages/fragments/security.zh-CN.json
@@ -0,0 +1,1080 @@
+{
+ "waf": {
+ "title": "WAF",
+ "createRule": "新建规则",
+ "ruleGroups": "规则组",
+ "ruleGroupsDesc": "使用可视化流程编排 WAF 防护规则;全局规则始终应用到所有网站。",
+ "refresh": "刷新",
+ "loading": "加载 WAF 规则组中...",
+ "empty": "暂无规则组,系统通常会自动创建全局规则组。",
+ "created": "规则已创建",
+ "groupDeleted": "规则组已删除",
+ "deleteTitle": "确认删除规则组",
+ "deleteDesc": "确认删除规则组「{name}」吗?删除后无法恢复。",
+ "deleting": "删除中...",
+ "confirmDelete": "确认删除",
+ "operationFailed": "操作失败",
+ "autoConfigMustBeObject": "自动配置必须是 JSON 对象。",
+ "autoConfigInvalid": "自动配置格式错误",
+ "saveFailed": "保存失败",
+ "enabled": "已启用",
+ "disabled": "已停用",
+ "enable": "启用",
+ "disable": "停用",
+ "global": "全局",
+ "custom": "自定义",
+ "allSites": "全部网站",
+ "siteCount": "{count} 个网站",
+ "columns": {
+ "name": "名称",
+ "type": "类型",
+ "status": "状态",
+ "nodeCount": "节点数",
+ "scope": "应用范围",
+ "updatedAt": "更新时间",
+ "actions": "操作"
+ },
+ "actions": {
+ "compose": "编排",
+ "delete": "删除"
+ },
+ "createDialog": {
+ "title": "新建 WAF 规则",
+ "description": "创建后将进入编排页面,通过处理单元配置规则执行流程。",
+ "name": "规则名称",
+ "namePlaceholder": "例如:入口防护",
+ "submit": "创建并编排"
+ },
+ "editor": {
+ "missingId": "缺少有效的规则 ID。",
+ "loadFailed": "规则加载失败,请重试。",
+ "reload": "重新加载",
+ "back": "返回",
+ "graphValid": "图校验通过",
+ "issueCount": "{count} 个问题",
+ "unsaved": "未保存",
+ "enableRule": "启用规则",
+ "format": "格式化",
+ "formatTitle": "自动整理节点与连线布局",
+ "saving": "保存中...",
+ "save": "保存",
+ "saved": "规则图已保存",
+ "conflict": "规则已在其他页面更新,请重新加载",
+ "saveCheckNodes": "保存失败,请检查标记的节点和连线",
+ "ruleEnabled": "规则已启用",
+ "ruleDisabled": "规则已停用",
+ "leaveTitle": "未保存的更改",
+ "leaveDesc": "存在未保存的更改,确定离开吗?",
+ "leaveConfirm": "确定离开",
+ "deleteNode": "删除节点",
+ "systemNodeLocked": "系统节点不可删除",
+ "deleteEdge": "删除连线",
+ "propsTitle": "节点属性",
+ "propsDesc": "配置当前处理单元",
+ "systemNodeNoConfig": "系统节点无需配置。",
+ "displayName": "显示名称",
+ "ips": "IP 地址",
+ "cidrs": "CIDR 网段",
+ "ipGroups": "IP 组",
+ "countries": "国家代码",
+ "countriesDesc": "共 {count} 个国家/地区,显示国家名与 ISO 代码",
+ "regions": "地区代码",
+ "regionsDesc": "共 {count} 个一级行政区,输入名称或代码搜索",
+ "uaCheck": "UA 检查",
+ "enableUaCheck": "开启 UA 检查",
+ "enableUaCheckTip": "开启后如果请求头不携带 UA 将返回 False",
+ "block": "屏蔽",
+ "blockTip": "命中返回 false,优先级高于匹配",
+ "blockBots": "屏蔽常见爬虫 UA",
+ "blockBotsTip": "浏览器或操作系统分类为 Bot(含 bot / spider / crawler / slurp 等特征,如 Googlebot)",
+ "blockAbnormal": "屏蔽非正常 UA",
+ "blockAbnormalTip": "浏览器分类为 Other 或 Unknown(不含搜索引擎等爬虫 Bot,爬虫请用上方开关)",
+ "blockCustom": "屏蔽自定义 UA",
+ "blockCustomTip": "原始 User-Agent 命中任一条正则时返回 false(Lua 模式语法)",
+ "customUaPatterns": "自定义 UA 正则",
+ "customUaPatternsTip": "每行一条 Lua 模式正则,命中任一条即 false;最多 32 条",
+ "uaMatch": "UA 匹配",
+ "matchMode": "匹配模式",
+ "matchModeTip": "浏览器与操作系统两侧都有选择时生效",
+ "matchOr": "或(OR)",
+ "matchAnd": "且(AND)",
+ "browsers": "浏览器",
+ "os": "操作系统",
+ "security": "安全防护",
+ "securityTip": "命中任意已启用规则返回 False",
+ "basicProtection": "基础防护",
+ "help": "说明",
+ "oneValuePerLine": "每行一个值",
+ "selectedCount": "已选择 {count} 项",
+ "pleaseSelect": "请选择",
+ "createLabel": "新建{label}",
+ "searchNameOrCode": "搜索名称或代码",
+ "addCode": "添加代码",
+ "searchOptions": "输入名称或代码搜索 {count} 个选项",
+ "noMatchingOptions": "没有匹配的选项",
+ "algorithm": "算法",
+ "algorithmFast": "快速",
+ "algorithmSlow": "稳健",
+ "difficulty": "难度",
+ "sessionTtl": "会话 TTL(秒)",
+ "challengeTtl": "挑战 TTL(秒)",
+ "statusCode": "HTTP 状态码",
+ "responseBody": "HTML 响应体",
+ "bytes": "字节",
+ "securityRules": {
+ "path_traversal": {
+ "label": "路径穿越防护",
+ "tip": "检测 Path / Query / Body 中的 ../ 与编码变种"
+ },
+ "file_inclusion": {
+ "label": "文件包含(LFI/RFI)",
+ "tip": "检测 Path / Query / Body 中的 php://、file://、/etc/passwd 等"
+ },
+ "sql_injection": {
+ "label": "SQL 注入",
+ "tip": "检测 Query / Body / Header / Cookie 中的 SQL 注入特征"
+ },
+ "command_injection": {
+ "label": "命令注入",
+ "tip": "检测 Query / Body / Header 中的 OS 命令注入特征"
+ },
+ "xss": {
+ "label": "XSS",
+ "tip": "检测 Query / Body / Header 中的反射型 XSS 特征"
+ },
+ "ssrf": {
+ "label": "SSRF",
+ "tip": "检测 Query / Body 中的内网地址与危险协议"
+ },
+ "malicious_upload": {
+ "label": "恶意文件上传",
+ "tip": "检测 Multipart 文件名与危险扩展名"
+ },
+ "xxe": {
+ "label": "XXE",
+ "tip": "检测 XML Body 中的外部实体特征"
+ },
+ "crlf_injection": {
+ "label": "CRLF 注入",
+ "tip": "检测 Header / Query / Body 中的换行注入"
+ }
+ }
+ }
+ },
+ "ipGroups": {
+ "title": "IP 组",
+ "create": "新建 IP 组",
+ "listTitle": "IP 组列表",
+ "listDesc": "维护可被 WAF IP 黑白名单引用的手动、自动与订阅 IP 集合。",
+ "refresh": "刷新",
+ "loading": "加载 IP 组中...",
+ "empty": "暂无 IP 组,请先创建一个。",
+ "updated": "IP 组已更新",
+ "created": "IP 组已创建",
+ "deleted": "IP 组已删除",
+ "synced": "同步完成",
+ "ipRemoved": "IP 已移除",
+ "testHit": "规则测试完成,命中 {count} 个 IP",
+ "testMiss": "规则测试完成,当前未命中任何 IP",
+ "deleteTitle": "确认删除 IP 组",
+ "deleteDesc": "确认删除 IP 组「{name}」吗?删除后无法恢复。",
+ "deleting": "删除中...",
+ "confirmDelete": "确认删除",
+ "operationFailed": "操作失败",
+ "autoConfigMustBeObject": "自动配置必须是 JSON 对象。",
+ "types": {
+ "manual": "手动",
+ "automatic": "自动",
+ "subscription": "订阅"
+ },
+ "enabled": "启用",
+ "disabled": "停用",
+ "noSyncRecord": "尚无同步记录",
+ "columns": {
+ "name": "名称",
+ "type": "类型",
+ "status": "状态",
+ "ipCount": "IP 数",
+ "refCount": "引用次数",
+ "syncStatus": "同步状态",
+ "updatedAt": "更新时间",
+ "actions": "操作"
+ },
+ "actions": {
+ "view": "查看",
+ "edit": "编辑",
+ "test": "测试规则",
+ "runNow": "立即执行",
+ "syncNow": "立即同步",
+ "delete": "删除"
+ },
+ "ban": {
+ "permanent": "永久",
+ "expired": "已过期",
+ "minutesLater": "{count} 分钟后",
+ "hoursLater": "{count} 小时后"
+ },
+ "presets": {
+ "scan404": "单 IP 404 高频扫描",
+ "directAccess": "单 IP 直连访问异常"
+ },
+ "dialog": {
+ "editTitle": "编辑 {name}",
+ "createTitle": "新建 IP 组",
+ "description": "维护可被 WAF IP 黑白名单引用的手动、自动与订阅 IP 集合。",
+ "name": "IP 组名称",
+ "nameRequired": "请输入 IP 组名称",
+ "type": "类型",
+ "enable": "启用 IP 组",
+ "enableDesc": "关闭后保留配置,但发布时不会展开到 WAF 运行时名单。",
+ "subscriptionUrl": "订阅 URL",
+ "subscriptionUrlRequired": "订阅类型需要填写订阅 URL",
+ "subscriptionFormat": "订阅格式",
+ "formatText": "文本列表",
+ "syncInterval": "同步间隔(分钟)",
+ "syncIntervalDesc": "最小 1 分钟,默认 1440 分钟。",
+ "mappingRule": "JSON 映射规则",
+ "mappingPlaceholder": "留空表示根数组",
+ "autoIntervalDesc": "定时从请求日志挖掘恶意 IP 的周期。最小 1 分钟,默认 1440 分钟。",
+ "presetRules": "预设规则",
+ "autoConfigJson": "自动配置 JSON",
+ "ipList": "IP / IP 段",
+ "ipListSubscriptionHint": "订阅同步会覆盖此列表;也可以先手动保存当前内容。",
+ "ipListManualHint": "支持单个 IP 或 CIDR,每行一个。",
+ "saving": "保存中...",
+ "save": "保存 IP 组"
+ },
+ "testDialog": {
+ "title": "自动规则测试结果",
+ "description": "基于当前自动配置 JSON 对请求日志进行回看测试。",
+ "testing": "测试中...",
+ "summary": "回看 {lookback} · 规则 {rules} 条 · 命中 {matched} 个 IP",
+ "testedAt": "测试时间:{time}",
+ "noMatch": "当前没有匹配到任何 IP。",
+ "empty": "暂无测试结果。",
+ "close": "关闭"
+ },
+ "viewDialog": {
+ "titleNamed": "查看 {name}",
+ "title": "查看 IP 组",
+ "summary": "{type} · 共 {count} 条 IP",
+ "fallbackDesc": "查看当前 IP 组中的 IP 列表,并可移除不需要的条目。",
+ "loading": "加载 IP 列表...",
+ "noGroup": "未选择 IP 组。",
+ "emptyAuto": "该自动规则暂未抓取任何 IP,可点击「立即执行」手动触发抓取。",
+ "empty": "当前 IP 组暂无 IP 条目。",
+ "subscriptionHint": "订阅类型在下次同步时可能重新拉取已删除的 IP。",
+ "ipAddress": "IP 地址",
+ "capturedAt": "抓取时间",
+ "banRemaining": "封禁剩余",
+ "actions": "操作",
+ "deleteIp": "删除 {ip}",
+ "close": "关闭",
+ "deleteTitle": "确认删除 IP",
+ "deleteDesc": "确认从 IP 组「{name}」中移除 {ip} 吗?",
+ "deleting": "删除中...",
+ "confirmDelete": "确认删除"
+ }
+ },
+ "rateLimits": {
+ "title": "限流",
+ "subtitle": "查看边缘请求压力,并配置站点默认并发与带宽限流。",
+ "loadingAuth": "加载权限信息...",
+ "forbiddenTitle": "权限不足",
+ "forbiddenDesc": "只有管理员可以访问限流设置。",
+ "tabAnalysis": "分析",
+ "tabConfig": "配置",
+ "loadingPage": "加载限流页面...",
+ "loadingPressure": "加载请求压力...",
+ "loadFailed": "加载失败",
+ "emptyTitle": "暂无数据",
+ "emptyDesc": "当前时间范围内没有可用的访问日志。",
+ "topHost": "平均 RPS 最高域名",
+ "topIp": "平均 RPS 最高 IP",
+ "avgRate": "{range}平均请求速率",
+ "range": {
+ "h24": "24 小时",
+ "h72": "3 天"
+ },
+ "chart": {
+ "title": "请求压力",
+ "empty": "暂无请求压力数据",
+ "requestRate": "请求速率",
+ "uniqueVisitors": "独立访客",
+ "visitorsPerBucket": "访客 / 桶"
+ },
+ "config": {
+ "saved": "限流参数已保存",
+ "saveFailed": "保存失败",
+ "loading": "加载限流参数...",
+ "preview": "查看配置预览",
+ "title": "全局默认限流",
+ "description": "0 或空表示默认关闭;站点未单独配置时继承此处设置。修改后需在版本发布中生效。",
+ "save": "保存",
+ "connPerServer": "默认并发限制(每站点)",
+ "connPerServerHint": "站点最大并发连接数默认值",
+ "connPerIp": "默认单 IP 并发",
+ "connPerIpHint": "单个 IP 最大并发连接数默认值",
+ "limitRate": "默认限速",
+ "limitRateHint": "单请求带宽默认值,例如 512k 或 1m",
+ "reqPerIp": "默认单 IP 请求频率限制",
+ "reqPerIpHint": "单个 IP 请求频率默认值,例如 10r/s 或 100r/m,留空关闭",
+ "invalidConn": "并发限制请输入非负整数,或留空表示关闭",
+ "invalidRate": "限速格式不合法,请使用 512k、1m、纯数字,或留空关闭",
+ "invalidReq": "请求频率限制格式不合法,请使用类似 10r/s、100r/m,或留空关闭"
+ }
+ },
+ "pages": {
+ "title": "Pages",
+ "subtitle": "边缘静态站点托管,上传静态资源部署包并在代理规则中选择 Pages 上游。",
+ "refresh": "刷新",
+ "create": "新建项目",
+ "loadFailed": "加载失败",
+ "emptyTitle": "还没有 Pages 项目",
+ "emptyDesc": "先创建一个项目,再上传静态资源部署包。",
+ "enabled": "已启用",
+ "disabled": "已停用",
+ "spaFallback": "SPA fallback",
+ "strict404": "严格 404",
+ "currentActive": "当前激活",
+ "none": "暂无",
+ "activatedAt": "激活时间:",
+ "notActivated": "未激活",
+ "viewDetail": "查看详情",
+ "saveFailed": "保存失败",
+ "updated": "项目已更新",
+ "created": "项目已创建",
+ "deleted": "项目已删除",
+ "deleteFailed": "删除失败",
+ "editTitle": "编辑 Pages 项目",
+ "createTitle": "新建 Pages 项目",
+ "editorDesc": "配置静态站点托管参数,上传部署包后在代理规则中选择 Pages 上游。",
+ "settingsDesc": "配置静态站点托管参数,保存后会同步到项目详情与代理引用。",
+ "reset": "重置",
+ "saving": "保存中...",
+ "saveChanges": "保存修改",
+ "createProject": "创建项目",
+ "form": {
+ "name": "项目名称",
+ "nameRequired": "请输入项目名称",
+ "slug": "项目标识",
+ "slugPlaceholder": "留空自动生成",
+ "entryFile": "入口文件",
+ "entryRequired": "请输入入口文件",
+ "rootDir": "根目录",
+ "optional": "可选",
+ "spaFallback": "SPA fallback",
+ "spaFallbackDesc": "未命中静态文件时回退到指定路径",
+ "fallbackPath": "回退路径",
+ "fallbackPathPrefix": "回退路径必须以 / 开头",
+ "apiProxy": "API 反向代理",
+ "apiProxyDesc": "为静态站点附加 API 反代规则",
+ "matchPath": "匹配路径",
+ "matchPathPrefix": "匹配路径必须以 / 开头",
+ "backend": "后端地址",
+ "backendPrefix": "后端地址必须以 http:// 或 https:// 开头",
+ "rewrite": "重写规则"
+ },
+ "detail": {
+ "invalidId": "缺少有效的 Pages 项目 ID。",
+ "loading": "加载项目详情...",
+ "loadFailed": "项目详情加载失败",
+ "back": "返回列表",
+ "notFound": "Pages 项目不存在或已被删除。",
+ "deploymentCount": "{count} 个部署",
+ "tabDeployments": "部署",
+ "tabSettings": "设置"
+ },
+ "danger": {
+ "title": "危险区域",
+ "deleteProject": "删除项目",
+ "deleteHint": "将永久删除 {name}({slug})及其全部部署。",
+ "confirmTitle": "删除 Pages 项目",
+ "confirmDesc": "确认删除项目 {name} 吗?此操作不可恢复。",
+ "confirm": "确认删除"
+ },
+ "upload": {
+ "selectFile": "请选择部署包",
+ "success": "部署包上传成功",
+ "failed": "上传失败",
+ "unsupported": "仅支持 zip、tar.gz、tar.xz、tar.bz2、tar、7z 格式的部署包",
+ "title": "上传部署包",
+ "description": "上传不可变的静态资源压缩包,完成后可在部署历史中激活。",
+ "localPackage": "本地部署包",
+ "dropHint": "拖拽部署包到此处,或点击选择文件",
+ "selected": "已选择 {name}({size})",
+ "chooseArchive": "请选择一个受支持的压缩包。",
+ "entry": "部署入口",
+ "entryHint": "入口来自项目设置;部署包上传不会覆盖该配置。",
+ "processing": "服务端处理中",
+ "progress": "上传进度",
+ "pleaseWait": "请稍候",
+ "uploading": "上传中...",
+ "submit": "上传并创建部署"
+ },
+ "files": {
+ "loadFailed": "文件清单加载失败",
+ "empty": "暂无文件记录",
+ "path": "路径",
+ "size": "大小"
+ },
+ "history": {
+ "source": {
+ "manual_upload": "本地上传",
+ "manual_url": "URL 导入"
+ },
+ "trigger": {
+ "manual_upload": "手动上传",
+ "manual_url": "手动导入",
+ "manual_sync": "手动同步",
+ "scheduled_auto_update": "定时更新"
+ },
+ "fileMeta": "{checksum} · {count} 个文件 · ",
+ "createdAt": "创建于 {time}",
+ "activatedAt": " · 激活于 {time}",
+ "collapseFiles": "收起文件清单",
+ "expandFiles": "展开文件清单",
+ "deploymentNumber": "部署 #{number}",
+ "activate": "激活",
+ "delete": "删除",
+ "activated": "历史部署已激活;自动更新(如已开启)已关闭",
+ "activateFailed": "激活失败",
+ "deleted": "部署已删除",
+ "deleteFailed": "删除失败",
+ "activateTitle": "激活历史部署",
+ "deleteTitle": "删除部署",
+ "activateDesc": "激活其它历史部署会终止当前来源任务;若已开启自动更新,将同时关闭自动更新。",
+ "deleteDesc": "确认删除部署 #{number} 吗?此操作不可恢复。",
+ "confirm": "确认",
+ "loading": "加载部署历史...",
+ "loadFailed": "部署历史加载失败",
+ "productionHint": "当前对外生效的生产部署;切换激活状态后会立即生效。",
+ "manualUpload": "手动上传",
+ "emptyProductionTitle": "暂无 Production 部署",
+ "emptyProductionDesc": "上传本地部署包,或配置 Remote URL / GitHub Release 来源后同步发布。",
+ "historyHint": "部署记录不可变,来源信息是创建部署时的安全快照。可从历史部署激活或删除。",
+ "emptyTitle": "暂无部署",
+ "emptyDesc": "上传本地部署包,或配置 Remote URL / GitHub Release 来源后同步发布。"
+ },
+ "source": {
+ "status": {
+ "idle": "空闲",
+ "checking": "检查中",
+ "update_available": "有可用更新",
+ "syncing": "同步中",
+ "failed": "最近同步失败",
+ "attention": "需要确认"
+ },
+ "check": "检查",
+ "syncPublish": "同步并发布",
+ "missingExecutionId": "缺少任务执行 ID",
+ "actionDone": "部署源{action}完成",
+ "actionFailed": "部署源{action}失败",
+ "checkQueued": "检查任务已提交",
+ "checkQueueFailed": "检查任务提交失败",
+ "syncQueued": "同步任务已提交",
+ "syncQueueFailed": "同步任务提交失败",
+ "title": "部署源",
+ "loadingDesc": "加载来源配置...",
+ "independentDesc": "来源配置与部署历史相互独立。",
+ "loadFailed": "部署源加载失败",
+ "configDesc": "配置远端来源并同步发布;发布结果见部署历史。",
+ "manualBadge": "手动部署",
+ "localPackage": "本地部署包",
+ "noRemote": "当前没有持久化远端来源。上传部署包后,再从部署历史显式激活。",
+ "submitFailed": "来源任务提交失败",
+ "statusFailed": "任务状态读取失败",
+ "waitStopped": "自动等待已停止,任务可能仍在后台运行。",
+ "refreshStatus": "刷新任务状态",
+ "configure": "配置",
+ "checkUpdate": "检查更新",
+ "refresh": "刷新",
+ "confirmTitle": "确认发布当前 GitHub revision",
+ "confirmLead": "这将发布卡片当前显示的精确 revision:",
+ "revisionUnavailable": "当前 revision 已不可用,请刷新后重试",
+ "confirmPublish": "确认并发布",
+ "updated": "部署源已更新",
+ "updateFailed": "部署源更新失败",
+ "switchedManual": "已切换回手动部署",
+ "switchFailed": "切换失败",
+ "urlRequired": "请输入 Remote URL",
+ "urlInvalid": "请输入有效的 HTTP(S) URL",
+ "githubUrlInvalid": "请输入 https://github.com/'{owner}'/'{repo}' 格式的公开仓库地址",
+ "tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)",
+ "assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符",
+ "intervalInvalid": "检查间隔须为 5–1440 分钟的整数",
+ "useManual": "使用手动部署",
+ "saveRemote": "保存 Remote 来源",
+ "saveGithub": "保存 GitHub 来源",
+ "settingsTitle": "部署源设置",
+ "settingsDesc": "选择部署来源。",
+ "mode": "来源类型",
+ "manual": "手动部署",
+ "manualHint": "保留现有部署与当前生产版本,后续通过“上传部署包”创建新部署。",
+ "urlHint": "填写可直接下载的部署包 HTTP(S) 地址。",
+ "allowInsecure": "允许不安全的连接",
+ "allowInsecureDesc": "默认允许公网与内网地址;开启后跳过 TLS 证书校验,适用于自签名或私有 CA。",
+ "switchManualTitle": "切换回手动部署",
+ "switchManualDesc": "当前来源配置将被删除,但已有部署与当前生产版本会保留。",
+ "githubUrl": "GitHub 仓库 URL",
+ "githubUrlHint": "仅支持公开 github.com 仓库。",
+ "releaseSelect": "Release 选择",
+ "latestRelease": "最新 Release",
+ "pinnedTag": "固定 Tag",
+ "pinnedTagHint": "精确检查并同步指定 tag。",
+ "autoUpdate": "自动更新",
+ "autoUpdateHint": "检查到新的 Release 后自动同步并发布。",
+ "checkInterval": "检查间隔(分钟)",
+ "checkIntervalHint": "可设置为 5–1440 分钟。",
+ "assetName": "Release Asset 文件名",
+ "noRecord": "尚无记录",
+ "address": "地址",
+ "verifyCert": "校验证书",
+ "lastSync": "最近同步",
+ "neverSynced": "尚未同步",
+ "applied": "已应用",
+ "latestReleaseLabel": "最新 Release",
+ "pinnedTagLabel": "固定 Tag · {tag}",
+ "tagMissing": "未提供",
+ "assetChangedTitle": "Release Asset 发生变化,需要显式确认",
+ "assetChangedDesc": "当前远端 revision 与已发布内容不一致。请核对版本和资源后,再确认发布这一精确 revision。",
+ "autoUpdateOn": "已开启",
+ "autoUpdateOff": "已关闭",
+ "intervalMinutes": "{count} 分钟",
+ "nextCheck": "下次检查",
+ "waitingSchedule": "等待调度",
+ "lastCheck": "最近检查",
+ "neverChecked": "尚未检查",
+ "remote": "远端"
+ }
+ },
+ "accessLogs": {
+ "title": "访问日志",
+ "subtitle": "查看访问概览、IP 明细与请求日志。",
+ "refresh": "刷新",
+ "cleanup": "清理日志",
+ "tabOverview": "概览",
+ "tabIps": "IP 明细",
+ "tabList": "日志明细",
+ "cleaned": "已清理 {count} 条日志",
+ "cleanupFailed": "清理失败",
+ "loadingTitle": "加载访问日志",
+ "loadingDesc": "正在准备页面...",
+ "loadFailed": "加载失败",
+ "range": {
+ "h24": "24 小时",
+ "h168": "7 天",
+ "h360": "15 天",
+ "h720": "30 天"
+ },
+ "rangeHint": {
+ "hours24": "近 24 小时",
+ "days": "近 {count} 天",
+ "hours": "近 {count} 小时"
+ },
+ "sort": {
+ "logged_at:desc": "时间从新到旧",
+ "logged_at:asc": "时间从旧到新",
+ "status_code:desc": "状态码从高到低",
+ "status_code:asc": "状态码从低到高",
+ "remote_addr:asc": "IP 正序",
+ "remote_addr:desc": "IP 倒序",
+ "total_requests:desc": "请求数从高到低",
+ "total_requests:asc": "请求数从低到高",
+ "request_length:desc": "入站从高到低",
+ "request_length:asc": "入站从低到高",
+ "bytes_sent:desc": "出站从高到低",
+ "bytes_sent:asc": "出站从低到高",
+ "success_ratio:desc": "2xx 比例从高到低",
+ "success_ratio:asc": "2xx 比例从低到高",
+ "last_seen_at:desc": "最后访问从新到旧",
+ "last_seen_at:asc": "最后访问从旧到新"
+ },
+ "cache": {
+ "hit": "命中",
+ "origin": "回源",
+ "uncached": "未缓存"
+ },
+ "filters": {
+ "pickTime": "选择时间",
+ "remoteAddr": "来源 IP",
+ "remoteAddrPlaceholder": "按 IP 搜索",
+ "host": "访问域名",
+ "hostPlaceholder": "按域名搜索",
+ "statusCode": "状态码",
+ "statusCodePlaceholder": "输入状态码,如 404",
+ "more": "更多筛选",
+ "nodeId": "节点 ID",
+ "nodeIdPlaceholder": "按 node_id 搜索",
+ "path": "请求路径",
+ "pathPlaceholder": "按路径搜索",
+ "timeRange": "时间范围",
+ "pageSize": "每页条数",
+ "pageSizeItem": "{count} 条",
+ "apply": "筛选",
+ "clear": "清空"
+ },
+ "cleanupDialog": {
+ "invalidDays": "保留天数必须大于 0",
+ "title": "清理访问日志",
+ "description": "删除早于指定保留天数的访问日志记录,操作不可恢复。",
+ "policy": "保留策略",
+ "keepDays": "保留最近 {days} 天",
+ "custom": "自定义天数",
+ "customDays": "自定义保留天数",
+ "cleaning": "清理中...",
+ "confirm": "确认清理"
+ },
+ "detail": {
+ "title": "访问日志详情",
+ "description": "本条请求的全部业务字段。IP 访问分析请前往「IP 明细」。",
+ "logId": "日志 ID",
+ "requestTime": "请求时间",
+ "storedAt": "入库时间",
+ "node": "节点",
+ "nodeId": "节点 ID",
+ "clientIp": "客户端 IP",
+ "region": "地区",
+ "host": "域名",
+ "status": "状态码",
+ "cache": "缓存",
+ "bytesSent": "出站流量",
+ "requestLength": "入站流量",
+ "duration": "请求耗时",
+ "path": "路径",
+ "page": "当前第 {page} 页",
+ "prev": "上一页",
+ "next": "下一页",
+ "listTitle": "日志明细",
+ "empty": "暂无访问日志",
+ "time": "时间",
+ "view": "查看详情"
+ },
+ "ip": {
+ "needRange": "请填写开始与结束时间",
+ "invalidTime": "时间格式无效",
+ "endAfterStart": "结束时间必须晚于开始时间",
+ "maxRange": "时间范围不能超过 30 天",
+ "page": "当前第 {page} 页",
+ "total": " · 共 {count} 个 IP",
+ "prev": "上一页",
+ "next": "下一页",
+ "customRange": "自定义区间",
+ "customPending": "自定义(未应用)",
+ "timeRange": "时间范围",
+ "current": "当前:{range}",
+ "custom": "自定义",
+ "start": "开始",
+ "end": "结束",
+ "apply": "应用",
+ "applyHint": "修改时间后点击「应用」再查询;详情分析窗口与列表时长对齐。",
+ "sort": "排序",
+ "pageSize": "每页",
+ "title": "IP 明细",
+ "empty": "暂无 IP 数据",
+ "region": "地区",
+ "requests": "请求数",
+ "successRatio": "2xx 比例",
+ "inbound": "入站",
+ "outbound": "出站",
+ "lastSeen": "最后访问",
+ "view": "查看 IP 详情",
+ "dialogTitle": "IP 详情",
+ "dialogDesc": "。查看该 IP 的访问趋势、分布与 WAF IP 组操作。"
+ },
+ "analysis": {
+ "emptySeries": "暂无 {title} 数据",
+ "selectGroup": "请选择要加入的 IP 组",
+ "added": "已将 {ip} 加入 IP 组「{name}」",
+ "addFailed": "加入 IP 组失败",
+ "removed": "已从 IP 组「{name}」移除 {ip}",
+ "removeFailed": "移除失败",
+ "joinTitle": "将 IP 加入 IP 组",
+ "targetIp": "目标 IP:",
+ "loadingGroups": "加载 IP 组",
+ "loadGroupsFailed": "加载 IP 组失败",
+ "alreadyIn": "该 IP 已存在于以下 IP 组",
+ "alreadyInHint": "可选择删除,或继续添加到其他 IP 组。",
+ "entryCount": "{type} · {count} 条",
+ "delete": "删除",
+ "cannotDelete": "不可手动删除",
+ "notInAny": "该 IP 尚未加入任何 IP 组。",
+ "addToOther": "添加到其他 IP 组",
+ "noWritable": "没有可写入的手动 IP 组(或已全部包含该 IP)。",
+ "selectManual": "选择手动 IP 组",
+ "close": "关闭",
+ "processing": "处理中...",
+ "joinSelected": "加入所选 IP 组",
+ "recentHours": "近 {hours} 小时",
+ "invalidIp": "没有有效 IP。",
+ "addToGroup": "将 IP 加入到 IP 组",
+ "loading": "加载 IP 分析",
+ "loadFailed": "加载 IP 分析失败",
+ "totalRequests": "总请求",
+ "errors": "错误数",
+ "bandwidth": "已提供带宽",
+ "received": "接收数据",
+ "uniqueHosts": "独立域名",
+ "uniquePaths": "独立路径",
+ "trend": "IP 请求趋势",
+ "trendMeta": "{ip} · {range} · {minutes} 分钟桶",
+ "refresh": "刷新",
+ "trendFailed": "加载趋势失败",
+ "trendEmpty": "该 IP 在{range}内没有访问记录。"
+ },
+ "overview": {
+ "loadingTitle": "加载访问概览",
+ "loadingDesc": "正在聚合请求量、访问量与带宽趋势...",
+ "loadFailed": "加载访问概览失败",
+ "emptyTitle": "暂无概览数据",
+ "emptyDesc": "当前时间范围内没有可展示的访问统计。",
+ "uniqueVisitors": "{hint} · 独立访客",
+ "served": "{hint} · 已提供数据",
+ "requestsHint": "观察请求量是否出现异常抬升或回落。",
+ "requests": "请求量",
+ "deviceDesc": "按设备类型统计请求占比。",
+ "deviceEmpty": "暂无设备类型数据",
+ "statusDesc": "按 HTTP 状态码统计请求占比。",
+ "statusEmpty": "暂无状态码分布数据",
+ "topPathsDesc": "访问量最高的请求路径。",
+ "topHostsDesc": "流量集中的访问域名。",
+ "topIpsDesc": "请求次数最多的来源 IP。",
+ "browsersDesc": "按浏览器聚合的请求排行。",
+ "osDesc": "按操作系统聚合的请求排行。",
+ "uaDesc": "原始 User-Agent 请求排行。"
+ },
+ "toolbar": {
+ "hostsFiltered": "已筛选 {count} 个域名",
+ "filterByHost": "按域名筛选",
+ "filterHosts": "筛选域名",
+ "clear": "清除",
+ "searchPlaceholder": "搜索 Zone 或域名",
+ "loadingHosts": "加载域名…",
+ "loadHostsFailed": "加载域名失败",
+ "retry": "重试",
+ "noRegistered": "暂无已登记域名",
+ "noMatch": "没有匹配的域名",
+ "selectZone": "选择 Zone {name}",
+ "selectHost": "选择域名 {name}",
+ "selectedCount": "已选 {count} 个域名"
+ }
+ },
+ "cloudflare": {
+ "title": "Cloudflare",
+ "connectionSettings": "连接设置",
+ "addGroup": "新增分组",
+ "loadingOverview": "加载 Cloudflare 总览中...",
+ "notReadyTitle": "Cloudflare 连接尚未就绪",
+ "notReadyDesc": "请先导入现有 Cloudflare DNS 账号,或录入独立 API Token 并完成连接测试。",
+ "configure": "配置连接",
+ "groupsTitle": "指向分组",
+ "groupsDesc": "管理 Cloudflare A 记录对应的节点与域名成员。",
+ "loadingGroups": "加载指向分组中...",
+ "emptyTitle": "暂无指向分组",
+ "emptyDesc": "创建分组后,可将域名成员同步到指定的边缘节点。",
+ "enabled": "已启用",
+ "disabled": "已停用",
+ "memberSummary": "成员 {count} 个 · 新成员默认{proxy}橙云",
+ "proxyOn": "开启",
+ "proxyOff": "关闭",
+ "manage": "管理",
+ "sync": "同步",
+ "delete": "删除",
+ "created": "指向分组已创建",
+ "syncQueued": "分组同步任务已入队",
+ "deleted": "指向分组及远端记录已删除",
+ "deleteTitle": "删除指向分组",
+ "deleteDesc": "将删除 {name} 的全部成员及本模块管理的远端 A 记录。此操作不可撤销。",
+ "confirmDelete": "确认删除",
+ "loadingDetail": "加载分组详情中...",
+ "groupUpdated": "分组配置已更新",
+ "memberAddedOne": "域名已加入并排队同步",
+ "memberAddedMany": "已添加 {count} 个域名并排队同步",
+ "memberAddFailed": "添加失败:{count} 个域名未能加入",
+ "memberPartial": "部分成功:{succeeded} 个已加入,{failed} 个失败",
+ "proxyUpdated": "橙云设置已更新并排队同步",
+ "memberSyncQueued": "成员同步任务已入队",
+ "memberDeleted": "成员及远端 A 记录已删除",
+ "back": "返回列表",
+ "refresh": "刷新",
+ "edit": "编辑",
+ "addDomain": "添加域名",
+ "currentPoint": "当前指向",
+ "activeNode": "生效节点 {name} · {ip}",
+ "syncEnabled": "同步已启用",
+ "syncDisabled": "同步已停用",
+ "primaryNode": "主节点 {name}",
+ "backupNode": "备用节点 {name}",
+ "backupUnset": "未设置",
+ "members": "域名成员",
+ "membersDesc": "成员级橙云是同步时的唯一依据。",
+ "noMembers": "暂无域名成员。",
+ "columns": {
+ "domain": "域名",
+ "desiredIp": "期望 IP",
+ "status": "状态",
+ "proxied": "橙云",
+ "actions": "操作"
+ },
+ "pendingSync": "待同步",
+ "remove": "移出",
+ "groupDialog": {
+ "editTitle": "编辑指向分组",
+ "createTitle": "新增指向分组",
+ "description": "一期使用主节点作为生效节点;备用节点仅保存,不会自动切换。",
+ "name": "分组名称",
+ "primary": "主节点",
+ "primaryPlaceholder": "选择带 IPv4 的边缘节点",
+ "noIp": "未配置 IP",
+ "backup": "备用节点",
+ "none": "不设置",
+ "defaultProxied": "新成员默认开启橙云",
+ "enableSync": "启用同步",
+ "save": "保存"
+ },
+ "memberDialog": {
+ "title": "添加域名成员",
+ "description": "支持搜索筛选与批量勾选;加入后会创建或接管唯一同名 A 记录,多条同名 A 会拒绝同步。",
+ "zoneDomain": "Zone 域名",
+ "searchPlaceholder": "搜索域名或顶级域…",
+ "selected": "已选 {count}",
+ "visible": "可见 {count}",
+ "unselectAll": "取消全选",
+ "selectVisible": "全选可见",
+ "clear": "清空",
+ "noAvailable": "暂无可用域名(均已加入分组,或尚未在网站管理中添加)。",
+ "noMatch": "没有匹配的域名",
+ "groupByZone": "按顶级域分组",
+ "selectZone": "选择顶级域 {name}",
+ "zone": "顶级域",
+ "proxied": "开启橙云代理",
+ "submitCount": "添加并同步({count})",
+ "submit": "添加并同步"
+ },
+ "tasks": {
+ "pending": "等待中",
+ "running": "执行中",
+ "succeeded": "成功",
+ "failed": "失败",
+ "system": "系统",
+ "manual": "手动",
+ "retry": "重试",
+ "schedule": "定时",
+ "retried": "同步任务已重新下发",
+ "newTaskId": "新任务 ID:{id}",
+ "retryFailed": "任务重试失败",
+ "unknownError": "未知错误",
+ "title": "同步任务",
+ "description": "仅展示 Cloudflare 域名同步(sync_member)与分组同步(sync_group)的执行记录。",
+ "statusPlaceholder": "状态",
+ "allStatus": "全部状态",
+ "refresh": "刷新",
+ "loadFailed": "加载同步任务失败",
+ "loading": "加载同步任务中...",
+ "emptyTitle": "暂无同步任务",
+ "emptyDesc": "添加域名或同步分组后,这里会显示 Cloudflare 域名 / 分组同步执行记录。",
+ "columns": {
+ "task": "任务",
+ "status": "状态",
+ "trigger": "触发",
+ "duration": "耗时",
+ "result": "结果/错误",
+ "createdAt": "创建时间"
+ },
+ "pageSummary": "共 {total} 条,第 {page}/{pages} 页",
+ "prev": "上一页",
+ "next": "下一页",
+ "detailTitle": "同步任务详情",
+ "defaultName": "Cloudflare 同步",
+ "loadingDetail": "加载任务详情中...",
+ "status": "状态",
+ "trigger": "触发",
+ "retries": "重试",
+ "duration": "耗时",
+ "taskId": "任务 ID",
+ "createdAt": "创建时间",
+ "finishedAt": "结束时间",
+ "result": "执行结果",
+ "error": "错误信息",
+ "logs": "执行日志",
+ "close": "关闭"
+ },
+ "settings": {
+ "saved": "Cloudflare 连接配置已保存",
+ "verified": "Cloudflare 连接验证成功",
+ "cleared": "Cloudflare 连接已清除",
+ "back": "返回列表",
+ "title": "Cloudflare 连接设置",
+ "sourceTitle": "凭据来源",
+ "sourceDesc": "Token 建议授予 Zone:Read 与 DNS:Edit 权限;Token 不会在 API 或页面中回显。",
+ "source": "连接来源",
+ "importDns": "导入现有 DNS 账号",
+ "standalone": "独立 API Token",
+ "dnsAccount": "Cloudflare DNS 账号",
+ "selectAccount": "选择 DNS 账号",
+ "dnsHint": "仅显示类型为 cloudflare 的 DNS 账号。可前往",
+ "dnsLink": "DNS 账号",
+ "dnsHintAfter": "新增。",
+ "tokenKeep": "留空不会回显现有 Token;保存将替换",
+ "tokenEnter": "输入 Cloudflare API Token",
+ "save": "保存配置",
+ "test": "测试连接",
+ "clear": "清除连接",
+ "statusTitle": "当前状态",
+ "ready": "Token 已验证,Cloudflare 指向同步可以执行。",
+ "notReady": "保存配置后仍需测试连接;未验证状态下同步任务会被拒绝。"
+ }
+ },
+ "openflareOps": {
+ "saved": "OpenFlare 运维设置已保存",
+ "saveFailed": "保存失败",
+ "tokenRotated": "Discovery Token 已重新生成",
+ "tokenRotateFailed": "Token 轮换失败",
+ "kumaSynced": "Uptime Kuma 同步任务已执行",
+ "syncFailed": "同步失败",
+ "invalidParams": "参数校验失败",
+ "loading": "加载 OpenFlare 运维设置...",
+ "loadFailed": "加载失败",
+ "save": "保存",
+ "agent": {
+ "title": "Agent 运行参数",
+ "description": "心跳间隔与离线阈值会在下个心跳周期同步到节点。",
+ "heartbeat": "心跳间隔 ({duration})",
+ "offline": "离线阈值 ({duration})",
+ "wsUpgrade": "开启 WS 连接升级",
+ "wsUpgradeDesc": "HTTP 心跳成功后尝试升级为 WebSocket,配置发布可即时通知。",
+ "updateRepo": "Agent 更新仓库"
+ },
+ "geo": {
+ "title": "IP 归属解析",
+ "description": "控制节点地图等场景的 GeoIP 来源;访客访问记录归属地固定使用 MaxMind mmdb。",
+ "mode": "归属方式",
+ "disabled": "关闭",
+ "testIp": "测试 IP",
+ "querying": "查询中...",
+ "query": "查询归属",
+ "country": "国家/地区"
+ },
+ "pages": {
+ "title": "Pages 静态托管",
+ "description": "配置部署包上传体积上限与每个项目的历史部署保留数量。",
+ "maxSize": "部署包大小上限 (MiB)",
+ "history": "历史部署保留数(0 不限制)",
+ "historyHint": "每个项目最多保留 N 条部署:激活部署始终保留,其余按从新到旧填充;超出的非激活部署会在上传成功后自动清理。支持 zip、tar.gz、tar.xz、tar.bz2、tar、7z 格式。"
+ },
+ "discovery": {
+ "title": "Discovery Token 与部署",
+ "description": "新节点首次接入使用 Discovery Token;轮换请前往节点管理或使用下方操作。",
+ "manageNodes": "节点管理",
+ "rotate": "轮换 Token",
+ "tokenReadonly": "Discovery Token(只读)",
+ "loading": "加载中...",
+ "notGenerated": "未生成",
+ "command": "一键部署命令",
+ "commandHint": "请先填写可访问的 Server URL 并获取 Token。",
+ "copied": "命令已复制",
+ "copy": "复制命令"
+ },
+ "kuma": {
+ "title": "Uptime Kuma 集成",
+ "description": "将反代站点差分同步至 Uptime Kuma 监控实例。",
+ "syncNow": "立即同步",
+ "enable": "开启 Uptime Kuma",
+ "url": "Uptime Kuma 地址",
+ "username": "用户名",
+ "password": "密码",
+ "passwordKeep": "留空表示不更新",
+ "syncInterval": "同步间隔 (分钟)",
+ "scope": "监控范围",
+ "allSites": "全部站点",
+ "selectedSites": "选择站点",
+ "selectedLabel": "已选站点",
+ "pickSites": "选择监控站点",
+ "noneSelected": "未选择任何站点",
+ "interval": "检测频率 (秒)",
+ "retry": "重试次数",
+ "retryInterval": "重试间隔 (秒)",
+ "timeout": "请求超时 (秒)"
+ },
+ "duration": {
+ "minutes": "{count} 分钟",
+ "seconds": "{count} 秒"
+ },
+ "errors": {
+ "heartbeatMin": "心跳间隔不能小于 1000 毫秒。",
+ "offlineMin": "离线阈值不能小于 10000 毫秒。",
+ "offlineRatio": "离线阈值建议至少为心跳间隔的 3 倍。",
+ "kumaUrl": "请输入 Uptime Kuma 地址。",
+ "kumaUser": "请输入 Uptime Kuma 用户名。",
+ "syncInterval": "同步间隔必须为正整数。",
+ "interval": "心跳间隔必须为正整数。",
+ "retry": "重试次数必须为非负整数。",
+ "retryInterval": "心跳重试间隔必须为正整数。",
+ "timeout": "请求超时必须为正整数。",
+ "pagesSize": "Pages 部署包大小上限必须为 1~2048 MiB。",
+ "pagesHistory": "Pages 历史保留数必须为大于等于 0 的整数(0 表示不限制)。"
+ },
+ "status": {
+ "title": "系统状态",
+ "loadFailed": "获取系统状态失败",
+ "unknownError": "请求时发生未知错误",
+ "autoRefresh": "自动刷新",
+ "refresh": "刷新",
+ "overview": "服务概览",
+ "overviewDesc": "服务的基础生命指标",
+ "uptime": "服务运行时间",
+ "goroutines": "当前 Goroutines 数量",
+ "heapObjects": "Heap 对象数量",
+ "memory": "内存统计",
+ "memoryDesc": "主内存消耗概览",
+ "alloc": "当前内存使用量",
+ "totalAlloc": "所有已分配的内存",
+ "sys": "内存占用量",
+ "nextGc": "下次 GC 内存回收量",
+ "usageRatio": "当前使用率 (Alloc / Sys)",
+ "heapStack": "堆/栈详情",
+ "heapStackDesc": "运行时堆栈内存空间细节",
+ "heapAlloc": "当前 Heap 内存使用量",
+ "heapSys": "Heap 内存占用量",
+ "heapIdle": "Heap 内存空闲量",
+ "heapInuse": "正在使用的 Heap 内存",
+ "heapReleased": "已释放的 Heap 内存",
+ "stackInuse": "启动 Stack 使用量",
+ "stackSys": "已分配的 Stack 内存",
+ "structs": "底层及结构体内存",
+ "structsDesc": "运行时底层管理结构体开销",
+ "mspanInuse": "MSpan 结构内存使用量",
+ "mspanSys": "已分配的 MSpan 结构内存",
+ "mcacheInuse": "MCache 结构内存使用量",
+ "mcacheSys": "已分配的 MCache 结构内存",
+ "buckHashSys": "已分配的剖析哈希表内存",
+ "gcSys": "已分配的 GC 元数据内存",
+ "otherSys": "其它已分配的系统内存",
+ "gc": "垃圾回收与分配计数",
+ "gcDesc": "GC 历史数据与分配频次",
+ "numGc": "GC 执行次数",
+ "lastGc": "距离上次 GC 时间",
+ "pauseTotal": "GC 暂停时间总量",
+ "lastPause": "上次 GC 暂停时间",
+ "mallocs": "内存分配次数",
+ "frees": "内存释放次数",
+ "lookups": "指针查找次数"
+ },
+ "siteModal": {
+ "title": "选择监控站点",
+ "description": "选择要同步到 Uptime Kuma 的反代站点,支持按站点名称和域名搜索。",
+ "search": "搜索站点",
+ "searchPlaceholder": "按名称或域名搜索...",
+ "selectFiltered": "全选过滤项",
+ "clearFiltered": "清空过滤项",
+ "loading": "加载站点列表...",
+ "loadFailed": "加载站点失败",
+ "noMatch": "无匹配的站点",
+ "select": "选择",
+ "siteName": "站点名称",
+ "primaryDomain": "主域名",
+ "selectedCount": "已选择 {count} 个监控站点",
+ "save": "保存选择"
+ }
+ }
+}
diff --git a/frontend/messages/fragments/websites.en.json b/frontend/messages/fragments/websites.en.json
new file mode 100644
index 00000000..16432c97
--- /dev/null
+++ b/frontend/messages/fragments/websites.en.json
@@ -0,0 +1,582 @@
+{
+ "websites": {
+ "title": "Websites",
+ "createZone": "Add Zone",
+ "searchRoot": "Search Zone root domain",
+ "loadingList": "Loading Zone list...",
+ "requestFailed": "Request failed. Please try again later.",
+ "emptySearch": "No matching Zone found.",
+ "emptyList": "No Zones yet. Click “Add Zone” in the top right to get started.",
+ "rootDomain": "Root domain",
+ "domainCount": "Domains",
+ "createdAt": "Created",
+ "updatedAt": "Updated",
+ "actions": "Actions",
+ "manage": "Manage",
+ "manageZone": "Manage Zone",
+ "loadingDetail": "Loading Zone details...",
+ "invalidId": "Invalid website ID. Open the details page from the website list.",
+ "notFound": "Website not found. It may have been deleted or the ID is invalid.",
+ "back": "Back",
+ "detailSubtitle": "Zone website management",
+ "tabOverview": "Overview",
+ "tabDomains": "Domains ({count})",
+ "tabCertificates": "Certificates ({count})",
+ "basicInfo": "Basic information",
+ "basicInfoDesc": "Maintain the Zone’s registrable root domain. After changing the root, confirm that its domains still belong to it.",
+ "currentRoot": "Current root domain",
+ "editRoot": "Edit root domain",
+ "dangerZone": "Dangerous actions",
+ "dangerZoneDesc": "Clear all domains under the Zone before deleting it. Deletion cannot be undone.",
+ "deleteZone": "Delete Zone",
+ "deleteZoneTitle": "Delete Zone",
+ "deleteZoneDesc": "Delete {domain}? Domains under it must be unbound from routes first. This cannot be undone.",
+ "deleting": "Deleting…",
+ "confirmDelete": "Delete",
+ "zoneDeleted": "Zone deleted",
+ "editZone": "Edit Zone",
+ "editorDesc": "A Zone accepts only a registrable root domain, such as example.com.",
+ "rootRequired": "Enter a Zone root domain",
+ "rootInvalid": "Enter a root domain without a protocol or wildcard",
+ "zoneUpdated": "Zone updated",
+ "zoneCreated": "Zone created",
+ "saveFailed": "Save failed",
+ "saveChanges": "Save changes",
+ "range24h": "24 hours",
+ "range7d": "7 days",
+ "range30d": "30 days",
+ "chartUv": "Bucket UV",
+ "chartBytes": "Data served",
+ "statsFailed": "Failed to load traffic stats",
+ "analyticsUnavailable": "Analytics storage is unavailable, so charts may be empty. Confirm that ClickHouse is enabled.",
+ "uniqueVisitors": "Unique visitors in query window",
+ "uniqueVisitorsDesc": "The top total is de-duplicated across the whole window; the curve is UV per time bucket and buckets cannot be added together",
+ "totalRequests": "Total requests",
+ "totalBytes": "Total data served",
+ "zoneInfo": "Zone information",
+ "addDomain": "Add domain",
+ "emptyDomains": "No domains added yet",
+ "certificate": "Certificate",
+ "upstream": "Upstream",
+ "certNumber": "Certificate #{id}",
+ "unbound": "Unbound",
+ "unlinkedRoute": "No linked route",
+ "loadingEllipsis": "Loading…",
+ "upstreamUnset": "No upstream configured",
+ "viewRoute": "View route details",
+ "deleteDomain": "Delete domain",
+ "domainDeleted": "Domain deleted",
+ "deleteFailed": "Delete failed",
+ "deleteDomainTitle": "Delete domain",
+ "deleteDomainDesc": "Delete {domain}? This cannot be undone.",
+ "zoneCertsTitle": "Certificates used by this Zone",
+ "zoneCertsDesc": "Certificates are managed in the global certificate library. This list only shows certificates bound to domains in this Zone.",
+ "certLibrary": "Certificate library",
+ "unboundCertHint": "{count} domains still have no certificate. Choose one in the Domains tab.",
+ "emptyBoundCerts": "No bound certificates. Choose a certificate on a domain row, or import/apply one in the certificate library.",
+ "status": "Status",
+ "coverage": "Covered domains",
+ "expiresAt": "Expires",
+ "selectZone": "Select a Zone first",
+ "enterDomain": "Enter a domain",
+ "noWildcard": "Zone domains do not support wildcards",
+ "invalidFormat": "Invalid domain format",
+ "invalidSubdomain": "Invalid subdomain name format",
+ "mustBelongToZone": "The domain must belong to Zone {root}",
+ "domainAdded": "Domain added",
+ "addFailed": "Add failed",
+ "quickCreateTitle": "Quick add domain",
+ "quickCreateDesc": "After selecting a Zone, enter a subdomain name (such as api), @ (the root), or a full FQDN.",
+ "selectRegisteredRoot": "Select registered root domain",
+ "zoneLabel": "Zone:",
+ "domain": "Domain",
+ "domainPlaceholderWithZone": "api or @ or api.{domain}",
+ "domainPlaceholder": "api / @ / full domain",
+ "willCreate": "Will create:",
+ "domainExample": "Example: enter api → api.zone.com; @ → the root itself",
+ "certOptional": "Certificate (optional)",
+ "noCert": "No certificate"
+ },
+ "certificates": {
+ "title": "Certificates",
+ "refresh": "Refresh certificates",
+ "importCert": "Import certificate",
+ "applyTitle": "Apply for certificate",
+ "listTitle": "Certificate list",
+ "listDesc": "Shows certificate validity, remarks, and status. You can view details, edit contents, or delete certificates.",
+ "loadingList": "Loading certificate list...",
+ "requestFailed": "Request failed. Please try again later.",
+ "emptyList": "No certificates yet. Click “Import certificate” or “Apply for certificate” in the top right to get started.",
+ "notBeforeLabel": "Valid from: {value}",
+ "notAfterLabel": "Expires: {value}",
+ "sourceLabel": "Source: {value}",
+ "sourceAcme": "ACME application",
+ "sourceUpload": "Manual upload",
+ "statusLabel": "Status: {value}",
+ "converting": "Conversion in progress...",
+ "applying": "Applying...",
+ "statusError": "Status: {value} ({message})",
+ "convertFailed": "Conversion failed",
+ "applyFailed": "Application failed",
+ "remarkLabel": "Remark: {value}",
+ "noRemark": "No remark",
+ "view": "View",
+ "edit": "Edit",
+ "renew": "Renew",
+ "deleted": "Certificate deleted",
+ "renewSubmitted": "Renewal task submitted for certificate {name}",
+ "imported": "Certificate {name} imported",
+ "applySubmitted": "Application task submitted for certificate {name}",
+ "convertSubmitted": "Conversion application submitted for certificate {name}",
+ "reapplySubmitted": "Certificate {name} updated and is being re-applied...",
+ "updated": "Certificate {name} updated",
+ "deleteTitle": "Delete certificate",
+ "deleteDesc": "Delete certificate {name}?",
+ "applyEditTitle": "Edit and re-apply certificate",
+ "applyConvertTitle": "Convert to applied certificate",
+ "applyEditDesc": "Change the ACME certificate settings. After saving, the certificate will be re-applied with the new settings.",
+ "applyConvertDesc": "Fill in the ACME application details. After a successful application, the current manual certificate is converted in place to an auto-renewable applied certificate.",
+ "applyDesc": "Use ACME (Let's Encrypt and others) to automatically apply for and renew certificates, including wildcard domains.",
+ "name": "Certificate name",
+ "namePlaceholder": "For example: main site certificate",
+ "primaryDomain": "Primary domain",
+ "primaryDomainPlaceholder": "example.com or *.example.com",
+ "otherDomains": "Other domains",
+ "otherDomainsHint": "One domain per line. For a wildcard certificate, also enter the corresponding root domain so it can be issued together.",
+ "dnsAccount": "DNS provider account",
+ "selectDnsAccount": "Select a DNS account",
+ "keyAlgorithm": "Key algorithm",
+ "remark": "Remark",
+ "remarkPlaceholder": "Optional. Record what this certificate is used for.",
+ "autoRenew": "Enable auto-renewal",
+ "autoRenewDesc": "When enabled, the certificate is renewed automatically 7 days before it expires.",
+ "advanced": "Advanced options",
+ "dnsServer1": "DNS validation server 1",
+ "dnsServer2": "DNS validation server 2",
+ "dnsServerPlaceholder": "Leave empty to use the default authoritative DNS",
+ "skipCname": "Skip CNAME check",
+ "skipCnameDesc": "Do not follow CNAME records during DNS-01 validation.",
+ "skipDns": "Skip DNS pre-check",
+ "skipDnsDesc": "Ask Let's Encrypt to validate directly without a local check.",
+ "submitting": "Submitting...",
+ "saveAndApply": "Save and apply",
+ "startConvert": "Start conversion",
+ "startApply": "Start application",
+ "editTitle": "Edit certificate",
+ "editDesc": "You can change the certificate name and remark, and re-upload the PEM certificate and private key.",
+ "loadingContent": "Loading certificate contents...",
+ "notFound": "Certificate not found. It may have been deleted.",
+ "certPem": "Certificate PEM",
+ "keyPem": "Private key PEM",
+ "convertSource": "Convert source",
+ "saving": "Saving...",
+ "saveCert": "Save certificate",
+ "importTitle": "Add certificate",
+ "importDesc": "Paste PEM manually or upload certificate files. After import, you can select it immediately in website forms.",
+ "manualImport": "Manual import",
+ "fileImport": "File import",
+ "remarkProdPlaceholder": "For example: production certificate for the main site",
+ "importing": "Importing...",
+ "remarkWildcardPlaceholder": "For example: production wildcard certificate",
+ "certFile": "Certificate file",
+ "keyFile": "Private key file",
+ "fileSelected": "Selected: {name}",
+ "selectCertFile": "Select a PEM/CRT file",
+ "selectKeyFile": "Select a KEY/PEM file",
+ "uploading": "Uploading...",
+ "uploadFiles": "Upload files",
+ "clearFiles": "Clear files",
+ "selectFiles": "Select a certificate file and a private key file.",
+ "detailTitle": "Certificate details",
+ "detailDesc": "View certificate metadata, remarks, and the currently saved PEM contents.",
+ "loadingDetail": "Loading certificate details...",
+ "status": "Status",
+ "notBefore": "Valid from",
+ "notAfter": "Expires",
+ "certPemCopied": "Certificate PEM copied",
+ "keyPemCopied": "Private key PEM copied",
+ "copy": "Copy",
+ "close": "Close",
+ "editCert": "Edit certificate",
+ "deleting": "Deleting...",
+ "deleteCert": "Delete certificate",
+ "certStatus": {
+ "unknownExpiry": "Expiry unknown",
+ "expired": "Expired",
+ "expiresInDays": "Expires within {days} days",
+ "valid": "Valid"
+ },
+ "validation": {
+ "nameRequired": "Enter a certificate name",
+ "nameTooLong": "Certificate name cannot exceed 255 characters",
+ "certPemRequired": "Enter the certificate PEM",
+ "keyPemRequired": "Enter the private key PEM",
+ "remarkTooLong": "Remark cannot exceed 255 characters",
+ "primaryDomainRequired": "Enter a primary domain",
+ "dnsAccountRequired": "Select a DNS account"
+ }
+ },
+ "dnsAccounts": {
+ "title": "DNS accounts",
+ "addAccount": "Add account",
+ "listTitle": "DNS account list",
+ "listDesc": "Centrally manage DNS provider accounts used for ACME certificate DNS validation.",
+ "loadingList": "Loading DNS accounts...",
+ "requestFailed": "Request failed. Please try again later.",
+ "emptyList": "No DNS accounts yet. Click “Add account” in the top right to get started.",
+ "createdAt": "Created: {date}",
+ "created": "DNS account added",
+ "deleted": "DNS account deleted",
+ "deleteTitle": "Delete DNS account",
+ "deleteDesc": "Delete DNS account {name}?",
+ "createTitle": "Add DNS account",
+ "createDesc": "Centrally manage DNS provider accounts used for ACME certificate DNS validation.",
+ "accountName": "Account name",
+ "accountNamePlaceholder": "Cloudflare email account",
+ "provider": "DNS provider",
+ "tokenPlaceholder": "Do not use a Global API Key",
+ "nameRequired": "Enter a name",
+ "tokenRequired": "Enter a token",
+ "submitting": "Submitting...",
+ "submit": "Submit"
+ },
+ "origins": {
+ "title": "Origins",
+ "subtitle": "Centrally maintain origin addresses reused by routes, so bulk address changes need less repeated work.",
+ "refresh": "Refresh",
+ "create": "Add origin",
+ "loadFailed": "Load failed",
+ "emptyTitle": "No origins",
+ "emptyDesc": "Click Add origin in the top right. Later routes can reuse these addresses directly.",
+ "routeCount": "{count} routes",
+ "noRemark": "No remark",
+ "lastUpdated": "Last updated: {date}",
+ "detail": "Details",
+ "edit": "Edit",
+ "deleted": "Origin deleted",
+ "deleteFailed": "Delete failed",
+ "deleteTitle": "Delete origin",
+ "deleteDesc": "Delete origin {name}?",
+ "confirmDelete": "Delete",
+ "missingId": "A valid origin ID is missing.",
+ "loadingDetail": "Loading origin details...",
+ "backToList": "Back to list",
+ "notFound": "Origin not found or already deleted.",
+ "editOrigin": "Edit origin",
+ "deleteOrigin": "Delete origin",
+ "boundRoutes": "Bound routes",
+ "createdAt": "Created",
+ "updatedAt": "Updated",
+ "remark": "Remark",
+ "relatedRoutes": "Related routes",
+ "relatedRoutesDesc": "Shows routes that bind this origin as the primary origin.",
+ "noRelatedRoutes": "No related routes",
+ "noRelatedRoutesDesc": "This origin is not referenced by any route yet.",
+ "domain": "Domain",
+ "address": "Origin address",
+ "status": "Status",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "addressRequired": "Enter an origin address",
+ "addressInvalid": "Invalid origin address format",
+ "updated": "Origin updated",
+ "created": "Origin created",
+ "saveFailed": "Save failed",
+ "editorDesc": "Origins are a reusable address directory for routes. Protocol and port are still decided by the route.",
+ "name": "Origin name",
+ "namePlaceholder": "Main origin",
+ "saving": "Saving...",
+ "saveChanges": "Save changes"
+ },
+ "proxyRoutes": {
+ "title": "Route configuration",
+ "refresh": "Refresh",
+ "createRule": "New route",
+ "listTitle": "Route list",
+ "searchPlaceholder": "Search sites, domains, or upstreams...",
+ "emptyTitle": "No websites",
+ "emptyDesc": "Create a website first, then open the configuration page to finish HTTPS, cache, and rate limits.",
+ "noMatches": "No matching results",
+ "noMatchesDesc": "Try a different search term, or clear the filter.",
+ "siteName": "Site name",
+ "domain": "Domain",
+ "status": "Status",
+ "upstream": "Upstream",
+ "actions": "Actions",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "configure": "Configure",
+ "siteCreated": "Website created",
+ "siteDeleted": "Website deleted",
+ "deleteFailed": "Delete failed",
+ "loadListFailed": "Failed to load route list",
+ "unknownError": "Unknown error",
+ "deleteSiteTitle": "Delete website",
+ "deleteSiteDesc": "Delete website {name}? This deletes all domains and configuration under the site.",
+ "deleting": "Deleting...",
+ "missingId": "A valid route ID is missing.",
+ "notFound": "Route not found",
+ "notFoundDesc": "Return to the list and try again.",
+ "saving": "Saving...",
+ "saveFailed": "Save failed",
+ "requestFailed": "Request failed. Please try again later.",
+ "back": "Back",
+ "publish": "Publish configuration",
+ "diffFailed": "Failed to load configuration diff",
+ "noPublishNeeded": "The current configuration matches the active version. Nothing to publish.",
+ "publishSuccess": "Published successfully",
+ "versionLabel": "Version {version}",
+ "publishFailed": "Publish failed",
+ "publishTitle": "Publish configuration",
+ "publishDesc": "This creates a new version from the pending configuration and sets it as active. Nodes will then pull the update.",
+ "confirmPublish": "Publish",
+ "createDesc": "Choose bindings from registered Zone domains. After creating the route, you can continue configuring cache, rate limits, and other advanced options.",
+ "siteId": "Site identifier",
+ "siteIdOptionalDesc": "Optional. If left empty, the first domain is used automatically.",
+ "siteIdStableDesc": "Use a stable, readable business identifier. It does not have to match the domain exactly.",
+ "bindDomains": "Bound domains",
+ "bindDomainsHint": "Select registered domains to bind. Use “Quick add domain” to create an FQDN under a Zone and select it automatically.",
+ "upstreamType": "Origin mode",
+ "upstreamDirect": "Direct upstream",
+ "upstreamTunnel": "Tunnel",
+ "upstreamPages": "Pages static site",
+ "upstreamAddresses": "Upstream addresses",
+ "upstreamUrlsHint": "One full URL per line. The first line is the primary origin. In multi-upstream mode, keep the same protocol and do not include a path or query.",
+ "selectTunnel": "Select a tunnel",
+ "pleaseSelect": "Please select...",
+ "online": "Online",
+ "offline": "Offline",
+ "tunnelForwardHint": "Forward requests to the client node connected by this tunnel.",
+ "tunnelProtocol": "Internal service protocol",
+ "tunnelAddress": "Internal service address",
+ "tunnelAddressHint": "For example: 127.0.0.1:8080 or 192.168.1.10:80",
+ "selectPagesProject": "Select a Pages project",
+ "pagesProjectHint": "Only enabled Pages projects with an active deployment are shown.",
+ "enableSite": "Enable site",
+ "enableSiteDesc": "When disabled, the configuration is kept but not included in publish.",
+ "createFailed": "Create failed. Please try again later.",
+ "creating": "Creating…",
+ "create": "Create",
+ "searchDomainOrZone": "Search domain or Zone…",
+ "quickAddDomain": "Quick add domain",
+ "noZoneDomains": "No Zone domains available. Add an FQDN in Website management first, or use “Quick add domain”.",
+ "noBindableDomains": "No bindable domains (the rest are already bound to other routes). Use “Quick add domain” to create one.",
+ "noMatchingDomains": "No matching domains",
+ "certNumber": "Certificate #{id}",
+ "noCert": "No certificate",
+ "zoneName": "Zone {name}",
+ "zoneId": "Zone #{id}",
+ "unboundDomain": "No domain bound",
+ "pagesProjectId": "Pages project #{id}",
+ "pagesUnbound": "Pages project not bound",
+ "tunnelTargetUnset": "Target not configured",
+ "upstreamUnset": "No upstream configured",
+ "domainSettings": "Domain settings",
+ "domainSettingsDesc": "Bind FQDNs already registered in a Zone. Maintain certificates in Zone domain management.",
+ "redirectNeedsCert": "Bind a certificate to the selected domains (in the Zone) before enabling HTTP redirect",
+ "domainSettingsSaved": "Domain settings saved",
+ "redirectHttp": "Automatically redirect HTTP to HTTPS",
+ "redirectHttpEnabledHint": "When enabled, extra port-80 redirect rules are generated.",
+ "redirectHttpNeedsCert": "At least one selected domain must have a bound certificate before this can be enabled.",
+ "reverseProxy": "Reverse proxy",
+ "reverseProxyDesc": "Configure the origin strategy and upstream addresses.",
+ "proxySaved": "Reverse proxy settings saved",
+ "originHostHint": "If left empty, the request hostname $host is passed through by default.",
+ "customHeaders": "Custom request headers",
+ "customHeadersHint": "One per line, in Key: Value format.",
+ "limits": "Traffic limits",
+ "limitsDesc": "Site rate limits. Empty or 0 inherits the global default; -1 turns it off explicitly; greater than 0 is custom.",
+ "limitsSaved": "Traffic limits saved",
+ "connLimit": "Concurrency limit",
+ "connLimitHint": "Empty or 0 inherits the global default; -1 turns it off; greater than 0 is a custom concurrency cap.",
+ "ipLimit": "Per-IP limit",
+ "ipLimitHint": "Empty or 0 inherits the global default; -1 turns it off; greater than 0 is a custom per-IP cap.",
+ "rateLimit": "Rate limit",
+ "rateLimitPlaceholder": "512k/1m or -1",
+ "rateLimitHint": "Empty or 0 inherits the global default; -1 turns it off; for example 512k or 1m is a custom bandwidth.",
+ "reqLimit": "Per-IP request rate",
+ "reqLimitPlaceholder": "10r/s / 100r/m or -1",
+ "reqLimitHint": "Empty or 0 inherits the global default; -1 turns it off; for example 10r/s or 100r/m is a custom rate.",
+ "cache": "Cache",
+ "cacheDesc": "Configure the site edge cache policy.",
+ "cacheSaved": "Cache settings saved",
+ "enableCache": "Enable site cache",
+ "enableCacheDesc": "New sites should use “Standard static assets”.",
+ "cachePolicy": "Cache policy",
+ "policyStatic": "Standard static assets (recommended)",
+ "policyAll": "All cacheable GET (advanced)",
+ "policySuffix": "Custom suffix",
+ "policyPrefix": "Path prefix",
+ "policyExact": "Exact path",
+ "advancedRiskTitle": "Advanced policy risk",
+ "advancedRiskDesc": "Personalized HTML may be edge-cached and served to other users if the origin does not declare private / no-store. See the help next to the title for details.",
+ "cacheRules": "Cache rules",
+ "cacheHelpTitle": "Cache usage notes",
+ "cacheHelpIntro": "The extension/policy decides whether a response can be cached. Origin headers and Set-Cookie decide whether it is stored. Global OpenResty cache in performance settings must also be enabled.",
+ "cacheHelpRecommendLabel": "Recommended policy:",
+ "cacheHelpRecommend": "New sites should use “Standard static assets” (includes css/js/map/images/fonts/media, excludes HTML/JSON).",
+ "cacheHelpGeneralLabel": "General rules:",
+ "cacheHelpGeneral": "Non-GET is not cached; login cookies do not skip cache on their own; origin private/no-store or a Set-Cookie response is not written to the edge.",
+ "cacheHelpAllGetLabel": "All cacheable GET:",
+ "cacheHelpAllGet": "An advanced option, similar to Cache Everything. Personalized pages must be declared private/no-store by the origin, or HTML may be edge-cached and served to other users.",
+ "cacheHelpCustomLabel": "Custom rules:",
+ "cacheHelpCustom": "Use suffixes such as jpg/css/js; path prefixes such as /assets; exact paths such as /robots.txt. After saving, republish a configuration version before it takes effect on nodes.",
+ "cacheHintSuffix": "One suffix per line, for example jpg, css, js.",
+ "cacheHintPrefix": "One path prefix per line, for example /assets, /static.",
+ "cacheHintExact": "One exact path per line, for example /robots.txt.",
+ "cacheHintStatic": "Standard static assets use the built-in extension list. No extra rules are needed.",
+ "cacheHintNone": "The current policy does not need extra path rules.",
+ "cachePlaceholderNone": "No extra rules for the current policy",
+ "auth": "Authentication",
+ "authDesc": "Configure Basic Auth to block unauthorized access. Configure PoW protection in a WAF rule group.",
+ "authSaved": "Authentication settings saved",
+ "authFormInvalid": "Check the authentication form",
+ "authMode": "Authentication mode",
+ "authNone": "No authentication",
+ "powHint": "PoW protection can only be enabled in a WAF rule group and bound to a site.",
+ "username": "Username",
+ "password": "Password",
+ "wafDesc": "Global rule groups always apply. Here you can add custom rule groups for the current website.",
+ "wafUpdated": "WAF rule groups updated",
+ "loadingWaf": "Loading WAF rule groups...",
+ "alwaysOn": "Always on",
+ "selectCustomRules": "Select custom rules",
+ "selectCustomRulesDesc": "Selected rules run in the execution order below.",
+ "selectRule": "Select {name}",
+ "ruleEnabled": "Enabled",
+ "ruleDisabled": "Disabled",
+ "nodeCount": "{count} nodes",
+ "executionOrder": "Execution order",
+ "executionOrderDesc": "Custom rules run in this order. Drag them or use the up/down buttons to change the order.",
+ "noCustomWaf": "No custom WAF rule groups",
+ "dragRule": "Drag {name}",
+ "moveUpRule": "Move {name} up",
+ "moveDownRule": "Move {name} down",
+ "sections": {
+ "domains": "Domain settings",
+ "domainsDesc": "Maintain the site identifier and choose bound domains from a Zone.",
+ "limits": "Traffic limits",
+ "limitsDesc": "Set connection limits and rate limits (can inherit global defaults).",
+ "proxy": "Reverse proxy",
+ "proxyDesc": "Configure the primary origin and upstream addresses.",
+ "cache": "Cache",
+ "cacheDesc": "Configure the site cache policy.",
+ "waf": "WAF",
+ "wafDesc": "Bind WAF rule groups and view the policies that apply to this site.",
+ "auth": "Authentication",
+ "authDesc": "Configure basic authentication so visitors must enter a username and password."
+ },
+ "validation": {
+ "enterDomain": "Enter a domain",
+ "invalidDomain": "Invalid domain format",
+ "enterAtLeastOneDomain": "Enter at least one domain",
+ "invalidDomainWithValue": "Invalid domain format: {domain}",
+ "duplicateDomain": "Duplicate domain: {domain}",
+ "enterAtLeastOneUpstream": "Enter at least one upstream address",
+ "invalidUpstream": "Invalid upstream address format: {url}",
+ "upstreamMustBeHttp": "Upstream address must start with http:// or https://: {url}",
+ "upstreamMissingHost": "Upstream address is missing a hostname: {url}",
+ "multiUpstreamNoPath": "Multi-upstream mode does not support addresses with a path or query",
+ "multiUpstreamSameProtocol": "Multiple upstreams for the same site must use the same protocol",
+ "invalidOriginHost": "Invalid origin Host format",
+ "invalidHeader": "Invalid custom request header format: {line}",
+ "invalidHeaderName": "Invalid custom request header name: {key}",
+ "invalidLimitRate": "Invalid rate-limit format. Use 512k, 1m, a number, or -1 to turn it off",
+ "invalidLimitReq": "Invalid request-rate format. Use 10r/s, 100r/m, or -1 to turn it off",
+ "cacheRuleRequired": "The current cache policy needs at least one rule",
+ "invalidCacheSuffix": "Invalid cache suffix format: {rule}",
+ "invalidCachePath": "Invalid cache path rule format: {rule}",
+ "siteNameTooLong": "Site identifier cannot exceed 255 characters",
+ "selectAtLeastOneDomain": "Select at least one domain",
+ "selectTunnel": "Select a tunnel",
+ "enterTunnelTarget": "Enter an internal service address (for example 127.0.0.1:8080)",
+ "selectPagesProject": "Select a Pages project",
+ "enterSiteId": "Enter a site identifier",
+ "enterIntegerOrMinusOne": "Enter -1, 0, or a positive integer",
+ "enterUsername": "Enter a username",
+ "enterPassword": "Enter a password"
+ }
+ },
+ "responses": {
+ "title": "Response pages",
+ "subtitle": "Configure origin error pages and offline pages.",
+ "loadingPermission": "Loading permission information...",
+ "loadingConfig": "Loading response page settings...",
+ "loadingPage": "Loading response pages...",
+ "loadingErrorConfig": "Loading error page settings...",
+ "loadingOfflineConfig": "Loading offline page settings...",
+ "loadFailed": "Load failed",
+ "forbidden": "Insufficient permission",
+ "forbiddenSettings": "Only administrators can access response page settings.",
+ "forbiddenPreviewError": "Only administrators can preview the error page.",
+ "forbiddenPreviewOffline": "Only administrators can preview the offline page.",
+ "forbiddenEditError": "Only administrators can edit the error page HTML.",
+ "forbiddenEditOffline": "Only administrators can edit the offline page HTML.",
+ "errorPage": "Error page",
+ "offlinePage": "Offline page",
+ "saveFailed": "Save failed",
+ "policySaved": "Trigger policy saved. Publish a version for the configuration to take effect.",
+ "triggerPolicy": "Trigger policy",
+ "triggerPolicyDesc": "Configure when the origin error page is enabled and which status codes trigger it.",
+ "enableErrorPage": "Enable origin error page",
+ "enableErrorPageDesc": "When disabled, the origin or Nginx default error response is passed through.",
+ "getOnly": "GET requests only",
+ "getOnlyDesc": "When enabled, the custom error page is returned only for matching error status codes on GET requests.",
+ "statusCodes": "Trigger status codes",
+ "statusCodesDesc": "Supports a single code (such as 502) or a closed range (such as 500-599).",
+ "statusCodesPlaceholder": "For example 502 or 500-599, then press Enter",
+ "pagePreview": "Page preview",
+ "preview": "Preview",
+ "edit": "Edit",
+ "errorPagePreview": "Origin error page preview",
+ "errorPreview": "Error page preview",
+ "offlinePreview": "Offline page preview",
+ "offlinePageDesc": "When enabled, a Service Worker is issued to HTTPS websites. If the domain is unreachable, the browser shows this offline page from cache.",
+ "offlineSaved": "Offline page saved. Publish a version for the configuration to take effect.",
+ "enableOffline": "Enable Service Worker offline page",
+ "enableOfflineDesc": "Only takes effect on HTTPS websites.",
+ "enableOfflineAria": "Enable offline page",
+ "scope": "Scope",
+ "scopeDesc": "Applies only to the selected HTTPS domains. If empty, nothing is injected.",
+ "addDomain": "Add domain",
+ "noScopeSelected": "No scope domains selected. After saving, nothing is injected into any site.",
+ "enableThenSelectScope": "Enable the offline fallback before choosing scope domains.",
+ "removeDomain": "Remove {domain}",
+ "selectScopeTitle": "Select scope domains",
+ "selectScopeDesc": "Inject the offline fallback only for selected HTTPS domains. Search, filter, and bulk-select.",
+ "domain": "Domain",
+ "searchDomainOrZone": "Search domain or apex…",
+ "selectedCount": "Selected {count}",
+ "visibleCount": "Visible {count}",
+ "unselectVisible": "Clear visible",
+ "selectVisible": "Select visible",
+ "clear": "Clear",
+ "noAvailableDomains": "No domains available. Register domains in Zone management first.",
+ "noMatchingDomains": "No matching domains",
+ "selectZone": "Select apex {domain}",
+ "apex": "Apex",
+ "confirm": "OK",
+ "backToError": "Back to error page",
+ "backToOffline": "Back to offline page",
+ "closePreview": "Close preview",
+ "fullscreen": "Full-screen view",
+ "htmlTooLarge": "HTML exceeds the size limit (max 256KB)",
+ "errorHtmlSaved": "Origin error page HTML saved. Publish a version for the configuration to take effect.",
+ "offlineHtmlSaved": "Offline page HTML saved. Publish a version for the configuration to take effect.",
+ "templateNotFound": "Selected template not found",
+ "templateLoaded": "Loaded template “{name}” into the editor",
+ "errorRestored": "Cleared to use the server built-in default template (save to apply)",
+ "offlineRestored": "Cleared to use the built-in default template (save to apply)",
+ "editErrorHtml": "Edit error page HTML",
+ "editOfflineHtml": "Edit offline page HTML",
+ "offlineLivePreview": "Offline page live preview",
+ "builtinTemplates": "Built-in templates",
+ "selectTemplate": "Select a template",
+ "loadTemplate": "Load template",
+ "restoreDefault": "Restore default",
+ "restoreTitle": "Restore default HTML?",
+ "restoreErrorDesc": "This clears the editor. After saving, the server built-in default template is used. This action does not save automatically.",
+ "restoreOfflineDesc": "This clears the editor. After saving, the built-in default template is used. This action does not save automatically.",
+ "confirmRestore": "Restore"
+ }
+}
diff --git a/frontend/messages/fragments/websites.zh-CN.json b/frontend/messages/fragments/websites.zh-CN.json
new file mode 100644
index 00000000..660af003
--- /dev/null
+++ b/frontend/messages/fragments/websites.zh-CN.json
@@ -0,0 +1,582 @@
+{
+ "websites": {
+ "title": "网站",
+ "createZone": "新增 Zone",
+ "searchRoot": "搜索 Zone 根域",
+ "loadingList": "加载 Zone 列表中...",
+ "requestFailed": "请求失败,请稍后重试。",
+ "emptySearch": "未找到匹配的 Zone。",
+ "emptyList": "暂无 Zone,点击右上角「新增 Zone」开始录入。",
+ "rootDomain": "根域",
+ "domainCount": "域名数",
+ "createdAt": "创建时间",
+ "updatedAt": "更新时间",
+ "actions": "操作",
+ "manage": "管理",
+ "manageZone": "管理 Zone",
+ "loadingDetail": "加载 Zone 详情中...",
+ "invalidId": "无效的网站 ID,请从网站列表进入详情页。",
+ "notFound": "网站不存在,可能已被删除或 ID 无效。",
+ "back": "返回",
+ "detailSubtitle": "Zone 网站管理",
+ "tabOverview": "概览",
+ "tabDomains": "域名 ({count})",
+ "tabCertificates": "证书 ({count})",
+ "basicInfo": "基本信息",
+ "basicInfoDesc": "维护 Zone 的可注册根域。根域变更后,请确认其下域名仍归属该根域。",
+ "currentRoot": "当前根域",
+ "editRoot": "编辑根域",
+ "dangerZone": "危险操作",
+ "dangerZoneDesc": "删除 Zone 前须清空其下全部域名;删除后不可恢复。",
+ "deleteZone": "删除 Zone",
+ "deleteZoneTitle": "确认删除 Zone",
+ "deleteZoneDesc": "确认删除 {domain} 吗?其下域名须先解绑路由后才能删除,此操作不可撤销。",
+ "deleting": "删除中…",
+ "confirmDelete": "确认删除",
+ "zoneDeleted": "Zone 已删除",
+ "editZone": "编辑 Zone",
+ "editorDesc": "Zone 仅接受可注册根域,例如 example.com。",
+ "rootRequired": "请输入 Zone 根域",
+ "rootInvalid": "请输入不含协议或通配符的根域",
+ "zoneUpdated": "Zone 已更新",
+ "zoneCreated": "Zone 已创建",
+ "saveFailed": "保存失败",
+ "saveChanges": "保存修改",
+ "range24h": "24 小时",
+ "range7d": "7 天",
+ "range30d": "30 天",
+ "chartUv": "分桶 UV",
+ "chartBytes": "已提供数据",
+ "statsFailed": "加载流量统计失败",
+ "analyticsUnavailable": "分析存储暂不可用,图表可能为空。请确认 ClickHouse 已启用。",
+ "uniqueVisitors": "查询窗口独立访客",
+ "uniqueVisitorsDesc": "顶部合计为整窗去重;曲线为各时间桶内 UV,桶间不可相加",
+ "totalRequests": "请求总数",
+ "totalBytes": "已提供的数据总计",
+ "zoneInfo": "Zone 信息",
+ "addDomain": "添加域名",
+ "emptyDomains": "暂无已添加域名",
+ "certificate": "证书",
+ "upstream": "上游",
+ "certNumber": "证书 #{id}",
+ "unbound": "未绑定",
+ "unlinkedRoute": "未关联路由",
+ "loadingEllipsis": "加载中…",
+ "upstreamUnset": "未配置上游",
+ "viewRoute": "查看路由详情",
+ "deleteDomain": "删除域名",
+ "domainDeleted": "域名已删除",
+ "deleteFailed": "删除失败",
+ "deleteDomainTitle": "确认删除域名",
+ "deleteDomainDesc": "确认删除 {domain} 吗?此操作不可撤销。",
+ "zoneCertsTitle": "本 Zone 使用的证书",
+ "zoneCertsDesc": "证书在全局证书库管理;此处仅展示已绑定到本 Zone 域名的证书。",
+ "certLibrary": "证书库",
+ "unboundCertHint": "还有 {count} 个域名未绑定证书,可在「域名」Tab 中选择证书。",
+ "emptyBoundCerts": "暂无绑定证书。请先在域名行选择证书,或前往证书库导入/申请。",
+ "status": "状态",
+ "coverage": "覆盖域名",
+ "expiresAt": "到期时间",
+ "selectZone": "请先选择 Zone",
+ "enterDomain": "请输入域名",
+ "noWildcard": "Zone 域名不支持通配符",
+ "invalidFormat": "域名格式不合法",
+ "invalidSubdomain": "子域名称格式不合法",
+ "mustBelongToZone": "域名必须属于 Zone {root}",
+ "domainAdded": "域名已添加",
+ "addFailed": "添加失败",
+ "quickCreateTitle": "快捷新增域名",
+ "quickCreateDesc": "选择 Zone 后输入:子域名称(如 api)、@(根域)或完整 FQDN。",
+ "selectRegisteredRoot": "选择注册根域",
+ "zoneLabel": "Zone:",
+ "domain": "域名",
+ "domainPlaceholderWithZone": "api 或 @ 或 api.{domain}",
+ "domainPlaceholder": "api / @ / 完整域名",
+ "willCreate": "将创建:",
+ "domainExample": "示例:输入 api → api.zone.com;@ → 根域本身",
+ "certOptional": "证书(可选)",
+ "noCert": "不绑定证书"
+ },
+ "certificates": {
+ "title": "证书",
+ "refresh": "刷新证书",
+ "importCert": "导入证书",
+ "applyTitle": "申请证书",
+ "listTitle": "证书列表",
+ "listDesc": "展示证书有效期、备注和状态,支持查看详情、编辑内容或删除证书。",
+ "loadingList": "加载证书列表中...",
+ "requestFailed": "请求失败,请稍后重试。",
+ "emptyList": "暂无证书,点击右上角「导入证书」或「申请证书」开始录入。",
+ "notBeforeLabel": "生效:{value}",
+ "notAfterLabel": "到期:{value}",
+ "sourceLabel": "来源:{value}",
+ "sourceAcme": "ACME 申请",
+ "sourceUpload": "手动上传",
+ "statusLabel": "状态:{value}",
+ "converting": "转换申请中...",
+ "applying": "申请中...",
+ "statusError": "状态:{value}({message})",
+ "convertFailed": "转换失败",
+ "applyFailed": "申请失败",
+ "remarkLabel": "备注:{value}",
+ "noRemark": "暂无备注",
+ "view": "查看",
+ "edit": "编辑",
+ "renew": "续期",
+ "deleted": "证书已删除",
+ "renewSubmitted": "证书 {name} 续期任务已提交",
+ "imported": "证书 {name} 已导入",
+ "applySubmitted": "证书 {name} 申请任务已提交",
+ "convertSubmitted": "证书 {name} 转换申请已提交",
+ "reapplySubmitted": "证书 {name} 配置已更新,重新申请中...",
+ "updated": "证书 {name} 已更新",
+ "deleteTitle": "确认删除证书",
+ "deleteDesc": "确认删除证书 {name} 吗?",
+ "applyEditTitle": "编辑并重新申请证书",
+ "applyConvertTitle": "转换为申请证书",
+ "applyEditDesc": "修改 ACME 证书配置。保存后将使用新配置重新申请证书。",
+ "applyConvertDesc": "填写 ACME 申请资料。申请成功后,当前手动证书会原地转换为可自动续签的申请证书。",
+ "applyDesc": "使用 ACME (Let's Encrypt 等) 自动申请和续期证书,支持通配符域名。",
+ "name": "证书名称",
+ "namePlaceholder": "例如:主站证书",
+ "primaryDomain": "主域名",
+ "primaryDomainPlaceholder": "example.com 或 *.example.com",
+ "otherDomains": "其他域名",
+ "otherDomainsHint": "每行一个域名。如申请通配符证书,请填写对应的根域名以便一并签发。",
+ "dnsAccount": "DNS 服务商账号",
+ "selectDnsAccount": "请选择 DNS 账号",
+ "keyAlgorithm": "密钥算法",
+ "remark": "备注",
+ "remarkPlaceholder": "可选,用于记录证书用途。",
+ "autoRenew": "开启自动续签",
+ "autoRenewDesc": "开启后,将在证书过期前 7 天自动续期。",
+ "advanced": "高级选项",
+ "dnsServer1": "DNS 验证服务器 1",
+ "dnsServer2": "DNS 验证服务器 2",
+ "dnsServerPlaceholder": "为空则使用默认权威 DNS",
+ "skipCname": "跳过 CNAME 检查",
+ "skipCnameDesc": "在执行 DNS-01 验证时不追踪 CNAME 记录。",
+ "skipDns": "跳过 DNS 前置检查",
+ "skipDnsDesc": "直接请求 Let's Encrypt 验证而不做本地校验。",
+ "submitting": "提交中...",
+ "saveAndApply": "保存并申请",
+ "startConvert": "开始转换",
+ "startApply": "开始申请",
+ "editTitle": "编辑证书",
+ "editDesc": "可以修改证书名称、备注,以及重新上传 PEM 证书和私钥内容。",
+ "loadingContent": "加载证书内容中...",
+ "notFound": "证书不存在,可能已被删除。",
+ "certPem": "证书 PEM",
+ "keyPem": "私钥 PEM",
+ "convertSource": "转换来源",
+ "saving": "保存中...",
+ "saveCert": "保存证书",
+ "importTitle": "添加证书",
+ "importDesc": "支持手动粘贴 PEM 或上传证书文件。导入成功后可立即在网站表单里选择。",
+ "manualImport": "手动导入",
+ "fileImport": "文件导入",
+ "remarkProdPlaceholder": "例如:主站生产证书",
+ "importing": "导入中...",
+ "remarkWildcardPlaceholder": "例如:泛域名生产证书",
+ "certFile": "证书文件",
+ "keyFile": "私钥文件",
+ "fileSelected": "已选择:{name}",
+ "selectCertFile": "请选择 PEM/CRT 文件",
+ "selectKeyFile": "请选择 KEY/PEM 文件",
+ "uploading": "上传中...",
+ "uploadFiles": "上传文件",
+ "clearFiles": "清空文件",
+ "selectFiles": "请选择证书文件和私钥文件。",
+ "detailTitle": "证书详情",
+ "detailDesc": "查看证书元信息、备注以及当前保存的 PEM 内容。",
+ "loadingDetail": "加载证书详情中...",
+ "status": "状态",
+ "notBefore": "生效时间",
+ "notAfter": "到期时间",
+ "certPemCopied": "证书 PEM 已复制",
+ "keyPemCopied": "私钥 PEM 已复制",
+ "copy": "复制",
+ "close": "关闭",
+ "editCert": "编辑证书",
+ "deleting": "删除中...",
+ "deleteCert": "删除证书",
+ "certStatus": {
+ "unknownExpiry": "有效期未知",
+ "expired": "已过期",
+ "expiresInDays": "{days} 天内到期",
+ "valid": "有效"
+ },
+ "validation": {
+ "nameRequired": "请输入证书名称",
+ "nameTooLong": "证书名称不能超过 255 个字符",
+ "certPemRequired": "请输入证书 PEM 内容",
+ "keyPemRequired": "请输入私钥 PEM 内容",
+ "remarkTooLong": "备注不能超过 255 个字符",
+ "primaryDomainRequired": "请输入主域名",
+ "dnsAccountRequired": "请选择 DNS 账号"
+ }
+ },
+ "dnsAccounts": {
+ "title": "DNS 账号",
+ "addAccount": "添加账号",
+ "listTitle": "DNS 账号列表",
+ "listDesc": "统一管理 DNS 服务商账号,用于 ACME 证书的 DNS 验证申请。",
+ "loadingList": "加载 DNS 账号中...",
+ "requestFailed": "请求失败,请稍后重试。",
+ "emptyList": "暂无 DNS 账号,点击右上角「添加账号」开始录入。",
+ "createdAt": "创建于:{date}",
+ "created": "DNS 账号已添加",
+ "deleted": "DNS 账号已删除",
+ "deleteTitle": "确认删除 DNS 账号",
+ "deleteDesc": "确认删除 DNS 账号 {name} 吗?",
+ "createTitle": "添加 DNS 账号",
+ "createDesc": "统一管理 DNS 服务商账号,用于 ACME 证书的 DNS 验证申请。",
+ "accountName": "账号名称",
+ "accountNamePlaceholder": "Cloudflare 邮箱账号",
+ "provider": "DNS 服务商",
+ "tokenPlaceholder": "请勿使用 Global API Key",
+ "nameRequired": "请输入名称",
+ "tokenRequired": "请输入 Token",
+ "submitting": "提交中...",
+ "submit": "提交"
+ },
+ "origins": {
+ "title": "源站",
+ "subtitle": "集中维护规则复用的源站地址,减少批量改地址时的重复操作。",
+ "refresh": "刷新",
+ "create": "新增源站",
+ "loadFailed": "加载失败",
+ "emptyTitle": "暂无源站",
+ "emptyDesc": "点击右上角新增源站,后续规则可直接复用这些地址。",
+ "routeCount": "{count} 条规则",
+ "noRemark": "暂无备注",
+ "lastUpdated": "最后更新:{date}",
+ "detail": "详情",
+ "edit": "编辑",
+ "deleted": "源站已删除",
+ "deleteFailed": "删除失败",
+ "deleteTitle": "删除源站",
+ "deleteDesc": "确认删除源站 {name} 吗?",
+ "confirmDelete": "确认删除",
+ "missingId": "缺少有效的源站 ID。",
+ "loadingDetail": "加载源站详情...",
+ "backToList": "返回列表",
+ "notFound": "源站不存在或已被删除。",
+ "editOrigin": "编辑源站",
+ "deleteOrigin": "删除源站",
+ "boundRoutes": "绑定规则",
+ "createdAt": "创建时间",
+ "updatedAt": "更新时间",
+ "remark": "备注",
+ "relatedRoutes": "关联规则",
+ "relatedRoutesDesc": "展示当前源站作为主源站绑定的规则。",
+ "noRelatedRoutes": "暂无关联规则",
+ "noRelatedRoutesDesc": "当前源站还没有被任何规则引用。",
+ "domain": "域名",
+ "address": "源站地址",
+ "status": "状态",
+ "enabled": "启用",
+ "disabled": "停用",
+ "addressRequired": "请输入源站地址",
+ "addressInvalid": "源站地址格式不合法",
+ "updated": "源站已更新",
+ "created": "源站已创建",
+ "saveFailed": "保存失败",
+ "editorDesc": "源站作为规则里的可复用地址目录,协议和端口仍由规则决定。",
+ "name": "源站名",
+ "namePlaceholder": "主站源站",
+ "saving": "保存中...",
+ "saveChanges": "保存修改"
+ },
+ "proxyRoutes": {
+ "title": "规则配置",
+ "refresh": "刷新",
+ "createRule": "新建规则",
+ "listTitle": "规则列表",
+ "searchPlaceholder": "搜索站点、域名或上游...",
+ "emptyTitle": "暂无网站",
+ "emptyDesc": "先创建一个网站,再进入配置子页面继续补齐 HTTPS、缓存和限流。",
+ "noMatches": "没有匹配结果",
+ "noMatchesDesc": "试试调整搜索词,或者清空筛选条件。",
+ "siteName": "站点名称",
+ "domain": "域名",
+ "status": "状态",
+ "upstream": "上游",
+ "actions": "操作",
+ "enabled": "已启用",
+ "disabled": "已停用",
+ "configure": "配置",
+ "siteCreated": "网站已创建",
+ "siteDeleted": "网站已删除",
+ "deleteFailed": "删除失败",
+ "loadListFailed": "规则列表加载失败",
+ "unknownError": "未知错误",
+ "deleteSiteTitle": "确认删除网站",
+ "deleteSiteDesc": "确认删除网站 {name} 吗?这会删除该站点下的全部域名与配置。",
+ "deleting": "删除中...",
+ "missingId": "缺少有效的规则 ID。",
+ "notFound": "未找到对应规则",
+ "notFoundDesc": "请返回列表重试。",
+ "saving": "保存中...",
+ "saveFailed": "保存失败",
+ "requestFailed": "请求失败,请稍后重试。",
+ "back": "返回",
+ "publish": "发布配置",
+ "diffFailed": "获取配置差异失败",
+ "noPublishNeeded": "当前配置与激活版本一致,无需发布",
+ "publishSuccess": "发布成功",
+ "versionLabel": "版本 {version}",
+ "publishFailed": "发布失败",
+ "publishTitle": "确认发布配置",
+ "publishDesc": "将把当前待发布配置生成新版本并设为激活版本,节点将随后拉取更新。",
+ "confirmPublish": "确认发布",
+ "createDesc": "从已注册的 Zone 域名中选择绑定关系,创建后可继续配置缓存和限流等高级选项。",
+ "siteId": "站点标识",
+ "siteIdOptionalDesc": "可选,留空时会自动使用首个域名。",
+ "siteIdStableDesc": "建议使用稳定、可读的业务标识,不必与域名完全一致。",
+ "bindDomains": "绑定域名",
+ "bindDomainsHint": "从已登记域名中勾选绑定;可用「快捷新增域名」在 Zone 下创建 FQDN 并自动勾选。",
+ "upstreamType": "回源方式",
+ "upstreamDirect": "直连上游",
+ "upstreamTunnel": "内网穿透 (Tunnel)",
+ "upstreamPages": "Pages 静态站点",
+ "upstreamAddresses": "上游地址",
+ "upstreamUrlsHint": "每行一个完整 URL。第一行作为主回源,多上游模式请保持相同协议且不要包含 path 或 query。",
+ "selectTunnel": "选择内网穿透隧道",
+ "pleaseSelect": "请选择...",
+ "online": "在线",
+ "offline": "离线",
+ "tunnelForwardHint": "将请求转发到该隧道连接的客户端节点。",
+ "tunnelProtocol": "内网服务协议",
+ "tunnelAddress": "内网服务地址",
+ "tunnelAddressHint": "例如: 127.0.0.1:8080 或 192.168.1.10:80",
+ "selectPagesProject": "选择 Pages 项目",
+ "pagesProjectHint": "仅显示已启用且已有激活部署的 Pages 项目。",
+ "enableSite": "启用站点",
+ "enableSiteDesc": "关闭后会保留配置,但不会参与发布。",
+ "createFailed": "创建失败,请稍后重试",
+ "creating": "创建中…",
+ "create": "创建",
+ "searchDomainOrZone": "搜索域名或 Zone…",
+ "quickAddDomain": "快捷新增域名",
+ "noZoneDomains": "暂无可用 Zone 域名。请先在 网站管理 中添加 FQDN,或使用「快捷新增域名」。",
+ "noBindableDomains": "没有可绑定的域名(其余域名已绑定其他路由)。可用「快捷新增域名」创建。",
+ "noMatchingDomains": "没有匹配的域名",
+ "certNumber": "证书 #{id}",
+ "noCert": "无证书",
+ "zoneName": "Zone {name}",
+ "zoneId": "Zone #{id}",
+ "unboundDomain": "未绑定域名",
+ "pagesProjectId": "Pages 项目 #{id}",
+ "pagesUnbound": "Pages 项目未绑定",
+ "tunnelTargetUnset": "未配置目标",
+ "upstreamUnset": "未配置上游",
+ "domainSettings": "域名设置",
+ "domainSettingsDesc": "绑定已在 Zone 中注册的 FQDN。证书请在 Zone 域名管理中维护。",
+ "redirectNeedsCert": "启用 HTTP 跳转前,请先为所选域名绑定证书(在 Zone 中配置)",
+ "domainSettingsSaved": "域名设置已保存",
+ "redirectHttp": "HTTP 自动跳转到 HTTPS",
+ "redirectHttpEnabledHint": "开启后会额外生成 80 端口重定向规则。",
+ "redirectHttpNeedsCert": "所选域名至少绑定一张证书后才能启用。",
+ "reverseProxy": "反向代理",
+ "reverseProxyDesc": "配置请求回源上游的策略与地址。",
+ "proxySaved": "反向代理设置已保存",
+ "originHostHint": "留空时默认透传访问域名 $host。",
+ "customHeaders": "自定义请求头",
+ "customHeadersHint": "每行一条,格式为 Key: Value。",
+ "limits": "流量限制",
+ "limitsDesc": "站点限流。空或 0 继承全局默认;-1 显式关闭;大于 0 为自定义。",
+ "limitsSaved": "流量限制已保存",
+ "connLimit": "并发限制",
+ "connLimitHint": "空或 0 继承全局默认;-1 关闭;大于 0 为自定义并发上限。",
+ "ipLimit": "单 IP 限制",
+ "ipLimitHint": "空或 0 继承全局默认;-1 关闭;大于 0 为单 IP 自定义上限。",
+ "rateLimit": "限速",
+ "rateLimitPlaceholder": "512k/1m 或 -1",
+ "rateLimitHint": "空或 0 继承全局默认;-1 关闭;例如 512k、1m 为自定义带宽。",
+ "reqLimit": "单 IP 请求频率",
+ "reqLimitPlaceholder": "10r/s / 100r/m 或 -1",
+ "reqLimitHint": "空或 0 继承全局默认;-1 关闭;例如 10r/s、100r/m 为自定义频率。",
+ "cache": "缓存",
+ "cacheDesc": "配置站点边缘缓存策略。",
+ "cacheSaved": "缓存设置已保存",
+ "enableCache": "启用站点缓存",
+ "enableCacheDesc": "新建推荐「标准静态资源」。",
+ "cachePolicy": "缓存策略",
+ "policyStatic": "标准静态资源(推荐)",
+ "policyAll": "所有可缓存 GET(高级)",
+ "policySuffix": "自定义后缀",
+ "policyPrefix": "路径前缀",
+ "policyExact": "精确路径",
+ "advancedRiskTitle": "高级策略风险",
+ "advancedRiskDesc": "个性化 HTML 在源站未声明 private / no-store 时可能被边缘缓存并串给其他用户。详情见标题旁帮助。",
+ "cacheRules": "缓存规则",
+ "cacheHelpTitle": "缓存使用说明",
+ "cacheHelpIntro": "扩展名/策略决定是否可缓存,源站头与Set-Cookie 决定是否入库。须同时开启性能设置中的全局 OpenResty 缓存。",
+ "cacheHelpRecommendLabel": "推荐策略:",
+ "cacheHelpRecommend": "新建站点建议使用「标准静态资源」(含 css/js/map/图片/字体/媒体等,不含 HTML/JSON)。",
+ "cacheHelpGeneralLabel": "通用规则:",
+ "cacheHelpGeneral": "非 GET 不缓存;登录 Cookie 不会单独跳过缓存;源站 private/no-store 或响应 Set-Cookie 不会写入边缘。",
+ "cacheHelpAllGetLabel": "所有可缓存 GET:",
+ "cacheHelpAllGet": "高级选项,类似 Cache Everything。个性化页面须由源站声明 private/no-store,否则 HTML 可能被边缘缓存并串给其他用户。",
+ "cacheHelpCustomLabel": "自定义规则:",
+ "cacheHelpCustom": "后缀填 jpg/css/js;路径前缀填 /assets;精确路径填 /robots.txt。保存后须重新发布配置版本才会在节点生效。",
+ "cacheHintSuffix": "每行一个后缀,例如 jpg、css、js。",
+ "cacheHintPrefix": "每行一个路径前缀,例如 /assets、/static。",
+ "cacheHintExact": "每行一个精确路径,例如 /robots.txt。",
+ "cacheHintStatic": "标准静态资源使用内置扩展名列表,无需填写规则。",
+ "cacheHintNone": "当前策略无需额外路径规则。",
+ "cachePlaceholderNone": "当前策略无需额外规则",
+ "auth": "认证配置",
+ "authDesc": "配置 Basic Auth 限制未授权访问。PoW 防护请在 WAF 规则组中配置。",
+ "authSaved": "认证配置已保存",
+ "authFormInvalid": "请检查认证配置表单",
+ "authMode": "认证模式",
+ "authNone": "无认证",
+ "powHint": "PoW 防护仅支持在 WAF 规则组中启用并绑定站点。",
+ "username": "账号",
+ "password": "密码",
+ "wafDesc": "全局规则组始终生效;这里可以为当前网站叠加自定义规则组。",
+ "wafUpdated": "WAF 规则组已更新",
+ "loadingWaf": "加载 WAF 规则组...",
+ "alwaysOn": "始终生效",
+ "selectCustomRules": "选择自定义规则",
+ "selectCustomRulesDesc": "选中的规则会按下方执行顺序依次运行。",
+ "selectRule": "选择{name}",
+ "ruleEnabled": "启用中",
+ "ruleDisabled": "已停用",
+ "nodeCount": "{count} 个节点",
+ "executionOrder": "执行顺序",
+ "executionOrderDesc": "自定义规则按此顺序执行,可拖动或使用上下移动按钮调整。",
+ "noCustomWaf": "暂无自定义 WAF 规则组",
+ "dragRule": "拖动{name}",
+ "moveUpRule": "上移{name}",
+ "moveDownRule": "下移{name}",
+ "sections": {
+ "domains": "域名设置",
+ "domainsDesc": "维护站点标识,并从 Zone 中选择绑定域名。",
+ "limits": "流量限制",
+ "limitsDesc": "设置连接数和限速(可继承全局默认)。",
+ "proxy": "反向代理",
+ "proxyDesc": "配置主回源和上游地址。",
+ "cache": "缓存",
+ "cacheDesc": "配置站点缓存策略。",
+ "waf": "WAF",
+ "wafDesc": "绑定 WAF 规则组,并查看当前站点生效策略。",
+ "auth": "认证配置",
+ "authDesc": "配置基础鉴权访问,需要输入账号密码才能访问网站。"
+ },
+ "validation": {
+ "enterDomain": "请输入域名",
+ "invalidDomain": "域名格式不合法",
+ "enterAtLeastOneDomain": "请至少填写一个域名",
+ "invalidDomainWithValue": "域名格式不合法:{domain}",
+ "duplicateDomain": "域名重复:{domain}",
+ "enterAtLeastOneUpstream": "请至少填写一个上游地址",
+ "invalidUpstream": "上游地址格式不合法:{url}",
+ "upstreamMustBeHttp": "上游地址必须以 http:// 或 https:// 开头:{url}",
+ "upstreamMissingHost": "上游地址缺少主机名:{url}",
+ "multiUpstreamNoPath": "多上游模式暂不支持带路径或查询参数的地址",
+ "multiUpstreamSameProtocol": "同一站点的多个上游必须使用相同协议",
+ "invalidOriginHost": "回源 Host 格式不合法",
+ "invalidHeader": "自定义请求头格式不合法:{line}",
+ "invalidHeaderName": "自定义请求头名称不合法:{key}",
+ "invalidLimitRate": "限速格式不合法,请使用 512k、1m、纯数字,或 -1 关闭",
+ "invalidLimitReq": "请求频率格式不合法,请使用 10r/s、100r/m,或 -1 关闭",
+ "cacheRuleRequired": "当前缓存策略至少需要一条规则",
+ "invalidCacheSuffix": "缓存后缀格式不合法:{rule}",
+ "invalidCachePath": "缓存路径规则格式不合法:{rule}",
+ "siteNameTooLong": "站点标识不能超过 255 个字符",
+ "selectAtLeastOneDomain": "请至少选择一个域名",
+ "selectTunnel": "请选择内网穿透隧道",
+ "enterTunnelTarget": "请填写内网服务地址 (如 127.0.0.1:8080)",
+ "selectPagesProject": "请选择 Pages 项目",
+ "enterSiteId": "请输入站点标识",
+ "enterIntegerOrMinusOne": "请输入 -1、0 或正整数",
+ "enterUsername": "请输入账号",
+ "enterPassword": "请输入密码"
+ }
+ },
+ "responses": {
+ "title": "响应页面",
+ "subtitle": "配置源站错误页与离线页。",
+ "loadingPermission": "加载权限信息...",
+ "loadingConfig": "加载响应页面配置...",
+ "loadingPage": "加载响应页面...",
+ "loadingErrorConfig": "加载错误页配置...",
+ "loadingOfflineConfig": "加载离线页配置...",
+ "loadFailed": "加载失败",
+ "forbidden": "权限不足",
+ "forbiddenSettings": "只有管理员可以访问响应页面设置。",
+ "forbiddenPreviewError": "只有管理员可以预览错误页。",
+ "forbiddenPreviewOffline": "只有管理员可以预览离线页。",
+ "forbiddenEditError": "只有管理员可以编辑错误页 HTML。",
+ "forbiddenEditOffline": "只有管理员可以编辑离线页 HTML。",
+ "errorPage": "错误页",
+ "offlinePage": "离线页",
+ "saveFailed": "保存失败",
+ "policySaved": "触发策略已保存,请前往版本发布使配置生效",
+ "triggerPolicy": "触发策略",
+ "triggerPolicyDesc": "配置源站错误页的启用条件与触发状态码。",
+ "enableErrorPage": "启用源站错误页",
+ "enableErrorPageDesc": "关闭后会透传源站或 Nginx 默认错误响应。",
+ "getOnly": "仅针对 GET 请求",
+ "getOnlyDesc": "开启后仅对 GET 请求的匹配错误状态码返回自定义错误页。",
+ "statusCodes": "触发状态码",
+ "statusCodesDesc": "支持单码(如 502)或闭区间(如 500-599)。",
+ "statusCodesPlaceholder": "例如 502 或 500-599,回车添加",
+ "pagePreview": "页面预览",
+ "preview": "预览",
+ "edit": "编辑",
+ "errorPagePreview": "源站错误页预览",
+ "errorPreview": "错误页预览",
+ "offlinePreview": "离线页预览",
+ "offlinePageDesc": "启用后给启用 HTTPS 的网站下发 Service Worker,域名无法访问时浏览器从缓存展示此离线页。",
+ "offlineSaved": "离线页已保存,请前往版本发布使配置生效",
+ "enableOffline": "启用 Service Worker 离线页",
+ "enableOfflineDesc": "仅对 HTTPS 网站生效。",
+ "enableOfflineAria": "启用离线页",
+ "scope": "生效范围",
+ "scopeDesc": "仅对选中的 HTTPS 域名生效;留空则不注入。",
+ "addDomain": "添加域名",
+ "noScopeSelected": "尚未选择生效域名,保存后不注入任何站点。",
+ "enableThenSelectScope": "启用离线兜底后可选择生效域名。",
+ "removeDomain": "移除 {domain}",
+ "selectScopeTitle": "选择生效域名",
+ "selectScopeDesc": "仅对选中的 HTTPS 域名注入离线兜底;搜索筛选与批量勾选。",
+ "domain": "域名",
+ "searchDomainOrZone": "搜索域名或顶级域…",
+ "selectedCount": "已选 {count}",
+ "visibleCount": "可见 {count}",
+ "unselectVisible": "取消全选",
+ "selectVisible": "全选可见",
+ "clear": "清空",
+ "noAvailableDomains": "暂无可用域名,请先在 Zone 管理中注册域名。",
+ "noMatchingDomains": "没有匹配的域名",
+ "selectZone": "选择顶级域 {domain}",
+ "apex": "顶级域",
+ "confirm": "确定",
+ "backToError": "返回错误页",
+ "backToOffline": "返回离线页",
+ "closePreview": "关闭预览",
+ "fullscreen": "全屏展示",
+ "htmlTooLarge": "HTML 大小超出限制(最大 256KB)",
+ "errorHtmlSaved": "源站错误页 HTML 已保存,请前往版本发布使配置生效",
+ "offlineHtmlSaved": "离线页 HTML 已保存,请前往版本发布使配置生效",
+ "templateNotFound": "未找到所选模板",
+ "templateLoaded": "已加载模板「{name}」到编辑器",
+ "errorRestored": "已清空为使用服务端内置默认模板(需保存后生效)",
+ "offlineRestored": "已清空为使用内置默认模板(需保存后生效)",
+ "editErrorHtml": "编辑错误页 HTML",
+ "editOfflineHtml": "编辑离线页 HTML",
+ "offlineLivePreview": "离线页实时预览",
+ "builtinTemplates": "内置模板",
+ "selectTemplate": "选择模板",
+ "loadTemplate": "加载模板",
+ "restoreDefault": "恢复默认",
+ "restoreTitle": "恢复默认 HTML?",
+ "restoreErrorDesc": "将清空编辑器内容,保存后使用服务端内置默认模板。此操作不会自动保存。",
+ "restoreOfflineDesc": "将清空编辑器内容,保存后使用内置默认模板。此操作不会自动保存。",
+ "confirmRestore": "确认恢复"
+ }
+}