From 0f0545d72ea297d73430e9256e13cf97a1b91de7 Mon Sep 17 00:00:00 2001 From: zhangyangrui <740884666@qq.com> Date: Fri, 24 Jul 2026 19:31:45 +0800 Subject: [PATCH 1/3] feat: localize TUI interface to Simplified Chinese Translate user-facing labels, help text, tables, and status messages so the dashboard is usable in Chinese while keeping internal action keys unchanged. Co-authored-by: Cursor --- README.md | 204 ++++++----------------------- main.go | 4 +- system/logs/logs.go | 2 +- system/performance/cpu.go | 54 ++++---- system/performance/io.go | 18 +-- system/performance/memory.go | 84 ++++++------ system/performance/systemHealth.go | 16 +-- system/performance/view.go | 14 +- system/security/SystemRestart.go | 6 +- system/security/SystemUpdates.go | 10 +- system/security/autoBane.go | 12 +- system/security/firewall.go | 20 +-- system/security/passwordPolicy.go | 10 +- system/security/ports.go | 16 +-- system/security/sshPassword.go | 8 +- system/security/sshRoot.go | 6 +- system/security/ssl.go | 18 +-- utils/test/utils_test.go | 22 ++-- utils/utils.go | 40 +++--- widgets/auth/styles.go | 12 +- widgets/handles-common.go | 14 +- widgets/handles.go | 60 ++++----- widgets/help.go | 176 ++++++++++++------------- widgets/help_test.go | 6 +- widgets/init.go | 102 +++++++-------- widgets/model/performance.go | 6 +- widgets/model/reporting.go | 30 ++--- widgets/model/type.go | 4 +- widgets/model/ui.go | 10 +- widgets/render-body.go | 2 +- widgets/render-charts.go | 22 ++-- widgets/render-common.go | 4 +- widgets/render-container.go | 70 +++++----- widgets/render-diagnostic.go | 118 ++++++++--------- widgets/render-footer.go | 2 +- widgets/render-header.go | 2 +- widgets/render-monitor.go | 14 +- widgets/render-nav.go | 2 +- widgets/render-network.go | 38 +++--- widgets/render-reporting.go | 26 ++-- widgets/utils.go | 8 +- widgets/vars/vars.go | 29 ++-- widgets/view.go | 2 +- widgets/widgets.go | 4 +- 44 files changed, 603 insertions(+), 724 deletions(-) diff --git a/README.md b/README.md index a34576e..99064a9 100644 --- a/README.md +++ b/README.md @@ -1,194 +1,76 @@

server-pulse

-# Server Pulse 🚀 +# Server Pulse(中文界面版)🚀 ![License: GPL-3.0](https://img.shields.io/badge/license-GPL3.0-blue) -![release](https://img.shields.io/github/v/release/Server-Pulse/server-pulse) -A real-time Terminal User Interface (TUI) for system monitoring, Docker container management, network diagnostics, and security auditing on Linux servers. +本仓库为 [Server-Pulse/server-pulse](https://github.com/Server-Pulse/server-pulse) 的 **Fork**,将 TUI 界面文案本地化为**简体中文**。 -

server-pulse

- -## Features - -- **System Monitoring** — Real-time CPU, memory, disk, and network usage with visual graphs -- **Process Management** — List, search, sort, and kill processes -- **Docker Management** — Start, stop, restart, pause, remove containers; view logs (with live streaming); exec into shells; monitor per-container CPU/memory/network stats -- **Security Diagnostics** — SSL certificate checks, SSH root access audit, open port scanning, firewall rules, Fail2Ban status -- **Performance Analysis** — System health scoring, I/O metrics, per-core CPU breakdown, memory analysis -- **Log Viewer** — Filter system logs by time range, service, and log level -- **Network Diagnostics** — Interface stats, ping, traceroute, speed tests, routing table, DNS servers, connection analysis -- **Report Generation** — Generate, save, and load comprehensive system health reports (`~/.server-pulse/reports/`) - -## Requirements - -- **Linux** (uses `/proc` filesystem — no macOS or Windows support) -- **Docker** installed and running -- Current user must be in the `docker` group or run as root -- Terminal size of at least 80x20 - -## Installation - -### Pre-built Binaries (Recommended) +实时终端界面(TUI),用于 Linux 服务器监控、Docker 容器管理、网络诊断与安全审计。 -**curl:** - -```bash -curl -fsSL https://raw.githubusercontent.com/Server-Pulse/server-pulse/main/scripts/install.sh | bash -``` - -**wget:** - -```bash -wget -qO- https://raw.githubusercontent.com/Server-Pulse/server-pulse/main/scripts/install.sh | bash -``` - -The install script auto-detects your platform, downloads the latest release, verifies the SHA256 checksum, and installs the binary to `/usr/local/bin/`. +

server-pulse

-### Manual Download +## 功能 -Download a specific release for Linux amd64: +- **系统监控** — CPU、内存、磁盘、网络实时图表 +- **进程管理** — 列表、搜索、排序、结束进程 +- **Docker 管理** — 启停/重启/暂停/删除、日志、进入 Shell、容器资源 +- **安全诊断** — SSL、SSH Root、开放端口、防火墙、Fail2Ban 等 +- **性能分析** — 健康评分、I/O、每核 CPU、内存分析 +- **日志查看** — 按时间、服务、级别筛选 +- **网络诊断** — 网卡、Ping、路由追踪、测速、路由表、DNS、连接 +- **报告** — 生成 / 保存 / 加载系统健康报告(`~/.server-pulse/reports/`) -```bash -sudo wget https://github.com/Server-Pulse/server-pulse/releases/download/vX.X.X/server-pulse-X.X.X-linux-amd64 -O /usr/local/bin/server-pulse -sudo chmod +x /usr/local/bin/server-pulse -``` +## 环境要求 -### From Source +- **Linux**(依赖 `/proc`) +- 已安装并运行 **Docker** +- 当前用户在 `docker` 组,或以 root 运行 +- 终端至少 80x20 -#### Prerequisites -- Go 1.24+ -- Git +## 从源码安装 ```bash -git clone https://github.com/Server-Pulse/server-pulse.git +git clone https://github.com/zhangyang-crazy-one/server-pulse.git cd server-pulse +# 或本地路径:~/my_programes/server-pulse-zh make build sudo mv server-pulse /usr/local/bin/ ``` -## Usage - -After installation, launch the TUI: +也可直接: ```bash -server-pulse +go build -o server-pulse . +sudo install -m 755 server-pulse /usr/local/bin/server-pulse ``` -The application opens an interactive dashboard. Use the number keys to jump between the four main sections: - -| Key | Section | Description | -|-----|---------|-------------| -| `1` | Monitor | System resources, processes, and containers | -| `2` | Diagnostics | Security checks, performance analysis, and logs | -| `3` | Network | Interfaces, connectivity tests, routing, and protocol analysis | -| `4` | Reporting | Generate and manage system health reports | - -## Keybindings - -### Global - -| Key | Action | -|-----|--------| -| `?` | Toggle help | -| `q` / `Esc` / `Ctrl+C` | Quit | -| `b` | Go back | -| `Tab` / `←` `→` | Navigate tabs | -| `Enter` | Select | - -### Process View - -| Key | Action | -|-----|--------| -| `/` | Search | -| `s` | Sort by CPU | -| `m` | Sort by memory | -| `k` | Kill process | - -### Containers View - -| Key | Action | -|-----|--------| -| `/` | Search | -| `Enter` | Open container actions (details, logs, restart, delete, exec shell, etc.) | - -### Container Logs - -| Key | Action | -|-----|--------| -| `s` | Toggle live streaming | -| `r` | Refresh | -| `Home` / `End` | Jump to start/end | - -### Network View - -| Key | Action | -|-----|--------| -| `p` | Start ping | -| `t` | Start traceroute | -| `c` | Clear results | - -### Diagnostics View - -| Key | Action | -|-----|--------| -| `1` `2` `3` | Switch sub-tabs (Security / Performance / Logs) | -| `/` | Search | -| `r` | Reload | -| `Shift+←` `Shift+→` | Switch filters | - -### Reporting View - -| Key | Action | -|-----|--------| -| `g` | Generate report | -| `s` | Save report | -| `l` | Load saved reports | -| `d` | Delete report | - -## Uninstallation - -**curl:** - -```bash -curl -fsSL https://raw.githubusercontent.com/Server-Pulse/server-pulse/main/scripts/uninstall.sh | bash -``` - -**wget:** +## 使用 ```bash -wget -qO- https://raw.githubusercontent.com/Server-Pulse/server-pulse/main/scripts/uninstall.sh | bash -``` - -## Building - -1. Clone the repository: -```bash -git clone https://github.com/Server-Pulse/server-pulse.git -cd server-pulse +server-pulse ``` -2. Build for your current platform: -```bash -make build -``` +主菜单快捷键: -3. For cross-platform builds: -```bash -make build-all -``` +| 键 | 功能 | +|----|------| +| `1` | 监控 | +| `2` | 诊断 | +| `3` | 网络 | +| `4` | 报告 | +| `?` | 帮助 | +| `q` / `Esc` | 退出 | +| `b` | 返回 | -Build artifacts will be placed in the `_build` directory. +## 与上游关系 -### Supported Build Targets +- Upstream: https://github.com/Server-Pulse/server-pulse +- Fork: https://github.com/zhangyang-crazy-one/server-pulse +- 分支:`feat/chinese-ui`(中文界面改动) -| Architecture | Platform | -|-------------|----------| -| amd64 | Linux | -| arm | Linux | -| arm64 | Linux | -| ppc64le | Linux | +内部操作键(如 Docker Action:`restart` / `delete` / `logs`)保持英文,以免破坏逻辑;仅界面展示文案为中文。 -## License +## 许可证 -This project is licensed under the [GPL-3.0 License](LICENSE). +GPL-3.0(与上游一致) diff --git a/main.go b/main.go index 913f3dc..0aaf3d3 100644 --- a/main.go +++ b/main.go @@ -71,7 +71,7 @@ func runTUI() bool { finalModel, err := p.Run() if err != nil { - fmt.Printf("Erreur: %v", err) + fmt.Printf("错误: %v", err) os.Exit(1) } @@ -85,7 +85,7 @@ func runTUI() bool { err := dockerManager.ExecInteractiveShellAlternative(shellRequest.ContainerID) if err != nil { fmt.Printf("Shell execution failed: %v\n", err) - fmt.Println("Press Enter to continue...") + fmt.Println("按 Enter 继续...") bufio.NewScanner(os.Stdin).Scan() } time.Sleep(300 * time.Millisecond) diff --git a/system/logs/logs.go b/system/logs/logs.go index 6b511ec..29e55cf 100644 --- a/system/logs/logs.go +++ b/system/logs/logs.go @@ -60,7 +60,7 @@ func (lm *LogManager) GetSystemLogs(filters LogFilters) tea.Cmd { Entries: []LogEntry{}, TotalCount: 0, Filters: filters, - ErrorMsg: fmt.Sprintf("Error loading logs: %v", err), + ErrorMsg: fmt.Sprintf("加载日志出错:%v", err), }) } diff --git a/system/performance/cpu.go b/system/performance/cpu.go index 18a6e56..55b4acd 100644 --- a/system/performance/cpu.go +++ b/system/performance/cpu.go @@ -191,7 +191,7 @@ func getProcessCounts() (int, int, error) { } func RenderCPU() string { - return "⏳ Loading CPU Performance Metrics..." + return "⏳ 正在加载 CPU 性能指标..." } // RenderCPUOverview renders the CPU overview section (always visible at top) @@ -199,7 +199,7 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { var b strings.Builder // Overall CPU Usage section - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("⚡ CPU OVERVIEW")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("⚡ CPU 概览")) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -207,15 +207,15 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { // CPU Usage with visual indicator usageColor := lipgloss.Color("46") // Green by default usageIcon := "●" - usageStatus := "Normal" + usageStatus := "正常" if metrics.OverallUsage > 80 { usageColor = lipgloss.Color("196") // Red for high usage usageIcon = "●" - usageStatus = "High" + usageStatus = "高" } else if metrics.OverallUsage > 60 { usageColor = lipgloss.Color("214") // Orange for medium usage usageIcon = "●" - usageStatus = "Medium" + usageStatus = "中" } usageText := lipgloss.NewStyle().Foreground(usageColor).Bold(true).Render( @@ -228,15 +228,15 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { prog.Empty = '░' usageBar := prog.ViewAs(metrics.OverallUsage / 100.0) - b.WriteString(fmt.Sprintf(" Overall CPU Usage: %s\n", usageText)) + b.WriteString(fmt.Sprintf(" CPU 总体使用率: %s\n", usageText)) b.WriteString(fmt.Sprintf(" %s\n\n", usageBar)) // Last update and load averages in columns lastUpdateText := lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render( - fmt.Sprintf("Last Update: %s", metrics.LastUpdate.Format("15:04:05")), + fmt.Sprintf("最后更新:%s", metrics.LastUpdate.Format("15:04:05")), ) loadAvgText := lipgloss.NewStyle().Foreground(lipgloss.Color("117")).Render( - fmt.Sprintf("Load Avg: %.2f, %.2f, %.2f", metrics.LoadAverage[0], metrics.LoadAverage[1], metrics.LoadAverage[2]), + fmt.Sprintf("负载均值:%.2f, %.2f, %.2f", metrics.LoadAverage[0], metrics.LoadAverage[1], metrics.LoadAverage[2]), ) b.WriteString(fmt.Sprintf(" %s %s\n", lastUpdateText, loadAvgText)) b.WriteString("\n") @@ -249,7 +249,7 @@ func RenderCPUStateBreakdown(metrics *model.CPUMetrics) string { var b strings.Builder // CPU State Breakdown - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📊 CPU STATE BREAKDOWN")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📊 CPU 状态分解")) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -260,13 +260,13 @@ func RenderCPUStateBreakdown(metrics *model.CPUMetrics) string { icon string desc string }{ - {"User", metrics.StateBreakdown.User, "👤", "User space processes"}, - {"System", metrics.StateBreakdown.System, "⚙️ ", "Kernel space processes"}, - {"Idle", metrics.StateBreakdown.Idle, "💤", "CPU idle time"}, - {"IOWait", metrics.StateBreakdown.IOWait, "⏳", "Waiting for I/O operations"}, - {"IRQ", metrics.StateBreakdown.IRQ, "⚡", "Hardware interrupts"}, - {"SoftIRQ", metrics.StateBreakdown.SoftIRQ, "📡", "Software interrupts"}, - {"Steal", metrics.StateBreakdown.Steal, "🔒", "Stolen by hypervisor"}, + {"用户态", metrics.StateBreakdown.User, "👤", "User space processes"}, + {"内核态", metrics.StateBreakdown.System, "⚙️ ", "Kernel space processes"}, + {"空闲", metrics.StateBreakdown.Idle, "💤", "CPU idle time"}, + {"IO 等待", metrics.StateBreakdown.IOWait, "⏳", "Waiting for I/O operations"}, + {"硬中断", metrics.StateBreakdown.IRQ, "⚡", "Hardware interrupts"}, + {"软中断", metrics.StateBreakdown.SoftIRQ, "📡", "Software interrupts"}, + {"被偷用", metrics.StateBreakdown.Steal, "🔒", "Stolen by hypervisor"}, {"Nice", metrics.StateBreakdown.Nice, "✨", "Nice priority processes"}, } @@ -306,7 +306,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { // Per-Core Performance if len(metrics.Cores) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("🔥 PER-CORE PERFORMANCE")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("🔥 每核性能")) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -328,7 +328,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { coreProg1.Empty = '░' bar1 := coreProg1.ViewAs(core1.Usage / 100.0) - coreLabel1 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("Core %2d", core1.CoreID)) + coreLabel1 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("核心 %2d", core1.CoreID)) coreValue1 := lipgloss.NewStyle().Width(7).Foreground(coreColor1).Bold(true).Render(fmt.Sprintf("%5.1f%%", core1.Usage)) line := fmt.Sprintf(" %s %s %s", coreLabel1, coreValue1, bar1) @@ -349,7 +349,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { coreProg2.Empty = '░' bar2 := coreProg2.ViewAs(core2.Usage / 100.0) - coreLabel2 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("Core %2d", core2.CoreID)) + coreLabel2 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("核心 %2d", core2.CoreID)) coreValue2 := lipgloss.NewStyle().Width(7).Foreground(coreColor2).Bold(true).Render(fmt.Sprintf("%5.1f%%", core2.Usage)) line += fmt.Sprintf(" %s %s %s", coreLabel2, coreValue2, bar2) @@ -359,7 +359,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { } b.WriteString("\n") } else { - b.WriteString(" No core data available\n\n") + b.WriteString(" 暂无核心数据\n\n") } return b.String() @@ -370,7 +370,7 @@ func RenderCPUSystemActivity(metrics *model.CPUMetrics) string { var b strings.Builder // System Activity Metrics - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📈 SYSTEM ACTIVITY METRICS")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📈 系统活动指标")) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -381,10 +381,10 @@ func RenderCPUSystemActivity(metrics *model.CPUMetrics) string { value string icon string }{ - {"Context Switches", formatNumber(metrics.ContextSwitches), "🔄"}, - {"Interrupts", formatNumber(metrics.Interrupts), "⚡"}, - {"Processes", fmt.Sprintf("%d", metrics.ProcessCount), "📋"}, - {"Threads", fmt.Sprintf("%d", metrics.ThreadCount), "🧵"}, + {"上下文切换", formatNumber(metrics.ContextSwitches), "🔄"}, + {"中断", formatNumber(metrics.Interrupts), "⚡"}, + {"进程数", fmt.Sprintf("%d", metrics.ProcessCount), "📋"}, + {"线程数", fmt.Sprintf("%d", metrics.ThreadCount), "🧵"}, } for _, item := range leftCol { @@ -422,11 +422,11 @@ func RenderCPUContent(metrics *model.CPUMetrics) string { func RenderCPUWithData(metrics *model.CPUMetrics, loading bool) string { if loading { - return vars.CardStyle.Render("⏳ Loading CPU Performance Metrics...") + return vars.CardStyle.Render("⏳ 正在加载 CPU 性能指标...") } if metrics == nil { - return vars.CardStyle.Render("Press 'r' to load CPU metrics") + return vars.CardStyle.Render("按 r 加载 CPU 指标") } return vars.CardStyle.Render(RenderCPUContent(metrics)) diff --git a/system/performance/io.go b/system/performance/io.go index bb64cc0..4448216 100644 --- a/system/performance/io.go +++ b/system/performance/io.go @@ -199,27 +199,27 @@ func getTopIOProcesses() ([]model.ProcessIOInfo, error) { } func RenderInputOutput() string { - return vars.CardStyle.Render("⏳ Loading I/O Performance Metrics...") + return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") } func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { if loading { - return vars.CardStyle.Render("⏳ Loading I/O Performance Metrics...") + return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") } if metrics == nil { - return vars.CardStyle.Render("⏳ Loading I/O Performance Metrics...") + return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") } var b strings.Builder // Title titleStyle := lipgloss.NewStyle().Bold(true).Foreground(vars.AccentColor).MarginBottom(1) - b.WriteString(titleStyle.Render("─ I/O PERFORMANCE ANALYSIS ─")) + b.WriteString(titleStyle.Render("─ I/O 性能分析 ─")) b.WriteString("\n") // Summary section - b.WriteString(lipgloss.NewStyle().Bold(true).Render("📊 I/O SUMMARY")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render("📊 I/O 摘要")) b.WriteString("\n\n") // Average latency with color coding @@ -236,13 +236,13 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { latencyText := lipgloss.NewStyle().Foreground(latencyColor).Bold(true).Render( fmt.Sprintf("%s %.2f ms", latencyIcon, metrics.AverageLatency), ) - b.WriteString(fmt.Sprintf("│ Average Latency: %s ", latencyText)) + b.WriteString(fmt.Sprintf("│ 平均延迟:%s ", latencyText)) // Last update lastUpdateText := lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render( metrics.LastUpdate.Format("15:04:05"), ) - b.WriteString(fmt.Sprintf("│ Last Update: %s\n", lastUpdateText)) + b.WriteString(fmt.Sprintf("│ 最后更新: %s\n", lastUpdateText)) b.WriteString("\n") // Create summary table @@ -287,7 +287,7 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Disk I/O table if len(metrics.Disks) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("💾 DISK I/O PERFORMANCE")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render("💾 磁盘 I/O 性能")) b.WriteString("\n\n") // Create table @@ -341,7 +341,7 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Top I/O Processes if len(metrics.TopProcesses) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("🔥 TOP I/O PROCESSES")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render("🔥 I/O 占用最高进程")) b.WriteString("\n\n") columns := []table.Column{ diff --git a/system/performance/memory.go b/system/performance/memory.go index c147778..e763c83 100644 --- a/system/performance/memory.go +++ b/system/performance/memory.go @@ -15,7 +15,7 @@ import ( // RenderMemoryOverview renders the always-visible memory overview section func RenderMemoryOverview(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("Loading memory data...") + return vars.CardStyle.Render("正在加载内存数据...") } titleStyle := lipgloss.NewStyle(). @@ -37,19 +37,19 @@ func RenderMemoryOverview(metrics *model.MemoryMetrics) string { // Create progress bar for memory usage usageBar := createMemoryProgressBar(metrics.UsedPercent, 50) - overview := titleStyle.Render("MEMORY OVERVIEW") + "\n\n" + overview := titleStyle.Render("内存概览") + "\n\n" - overview += labelStyle.Render("Total Memory:") + " " + + overview += labelStyle.Render("内存总量:") + " " + valueStyle.Render(formatBytes(metrics.Total)) + "\n" - overview += labelStyle.Render("Used:") + " " + + overview += labelStyle.Render("已用:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Used), metrics.UsedPercent)) + "\n" - overview += labelStyle.Render("Available:") + " " + + overview += labelStyle.Render("可用:") + " " + valueStyle.Render(formatBytes(metrics.Available)) + "\n" - overview += labelStyle.Render("Free:") + " " + + overview += labelStyle.Render("空闲:") + " " + valueStyle.Render(formatBytes(metrics.Free)) + "\n\n" overview += progressStyle.Render(usageBar) + "\n" @@ -60,7 +60,7 @@ func RenderMemoryOverview(metrics *model.MemoryMetrics) string { // RenderMemoryUsageBreakdown renders the Usage Breakdown sub-tab func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("Loading memory usage data...") + return vars.CardStyle.Render("正在加载内存使用数据...") } titleStyle := lipgloss.NewStyle(). @@ -80,42 +80,42 @@ func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { Foreground(lipgloss.Color("241")). Italic(true) - content := titleStyle.Render("USAGE BREAKDOWN") + "\n\n" + content := titleStyle.Render("使用分解") + "\n\n" // Application Memory appMemPercent := float64(metrics.ApplicationMem) / float64(metrics.Total) * 100 - content += labelStyle.Render("Application Memory:") + " " + + content += labelStyle.Render("应用内存:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.ApplicationMem), appMemPercent)) + "\n" - content += " " + descStyle.Render("Memory actively used by applications") + "\n\n" + content += " " + descStyle.Render("应用程序实际使用的内存") + "\n\n" // Buffers buffersPercent := float64(metrics.Buffers) / float64(metrics.Total) * 100 - content += labelStyle.Render("Buffers:") + " " + + content += labelStyle.Render("缓冲区:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Buffers), buffersPercent)) + "\n" - content += " " + descStyle.Render("I/O buffers for disk operations") + "\n\n" + content += " " + descStyle.Render("磁盘操作的 I/O 缓冲区") + "\n\n" // Cached cachedPercent := float64(metrics.Cached) / float64(metrics.Total) * 100 - content += labelStyle.Render("Cached:") + " " + + content += labelStyle.Render("缓存:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Cached), cachedPercent)) + "\n" - content += " " + descStyle.Render("File system cache (can be freed if needed)") + "\n\n" + content += " " + descStyle.Render("文件系统缓存(需要时可释放)") + "\n\n" // Available availablePercent := float64(metrics.Available) / float64(metrics.Total) * 100 - content += labelStyle.Render("Available:") + " " + + content += labelStyle.Render("可用:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Available), availablePercent)) + "\n" - content += " " + descStyle.Render("Memory available for applications") + "\n\n" + content += " " + descStyle.Render("应用程序可用内存") + "\n\n" // Shared sharedPercent := float64(metrics.Shared) / float64(metrics.Total) * 100 - content += labelStyle.Render("Shared:") + " " + + content += labelStyle.Render("共享:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Shared), sharedPercent)) + "\n" - content += " " + descStyle.Render("Memory shared between processes") + "\n" + content += " " + descStyle.Render("进程间共享内存") + "\n" return vars.CardStyle.Render(content) } @@ -123,7 +123,7 @@ func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { // RenderMemorySwapAnalysis renders the Swap Analysis sub-tab func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("Loading swap data...") + return vars.CardStyle.Render("正在加载交换分区数据...") } titleStyle := lipgloss.NewStyle(). @@ -142,21 +142,21 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { progressStyle := lipgloss.NewStyle(). Width(50) - content := titleStyle.Render("SWAP ANALYSIS") + "\n\n" + content := titleStyle.Render("交换分区分析") + "\n\n" // Swap Overview if metrics.SwapTotal == 0 { - content += labelStyle.Render("Swap Status:") + " " + - lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("No swap configured") + "\n" + content += labelStyle.Render("交换分区状态:") + " " + + lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("未配置交换分区") + "\n" } else { - content += labelStyle.Render("Swap Total:") + " " + + content += labelStyle.Render("交换总量:") + " " + valueStyle.Render(formatBytes(metrics.SwapTotal)) + "\n" - content += labelStyle.Render("Swap Used:") + " " + + content += labelStyle.Render("交换已用:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.SwapUsed), metrics.SwapUsedPercent)) + "\n" - content += labelStyle.Render("Swap Free:") + " " + + content += labelStyle.Render("交换空闲:") + " " + valueStyle.Render(formatBytes(metrics.SwapFree)) + "\n\n" // Swap progress bar @@ -164,13 +164,13 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { content += progressStyle.Render(swapBar) + "\n\n" // SwapCached - content += labelStyle.Render("Swap Cached:") + " " + + content += labelStyle.Render("交换缓存:") + " " + valueStyle.Render(formatBytes(metrics.SwapCached)) + "\n" content += " " + lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Italic(true). - Render("Swap memory also present in RAM") + "\n\n" + Render("同时存在于内存中的交换数据") + "\n\n" // Health Status - content += titleStyle.Render("STATUS") + "\n\n" + content += titleStyle.Render("状态") + "\n\n" status := getSwapHealthStatus(metrics) content += status } @@ -181,7 +181,7 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { // RenderMemorySystemMemory renders the System Memory sub-tab func RenderMemorySystemMemory(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("Loading system memory data...") + return vars.CardStyle.Render("正在加载系统内存数据...") } titleStyle := lipgloss.NewStyle(). @@ -201,38 +201,38 @@ func RenderMemorySystemMemory(metrics *model.MemoryMetrics) string { Foreground(lipgloss.Color("241")). Italic(true) - content := titleStyle.Render("SYSTEM MEMORY") + "\n\n" + content := titleStyle.Render("系统内存") + "\n\n" // Dirty Pages dirtyPercent := float64(metrics.Dirty) / float64(metrics.Total) * 100 - content += labelStyle.Render("Dirty Pages:") + " " + + content += labelStyle.Render("脏页:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Dirty), dirtyPercent)) + "\n" - content += " " + descStyle.Render("Modified data waiting to be written to disk") + "\n\n" + content += " " + descStyle.Render("等待写入磁盘的已修改数据") + "\n\n" // WriteBack writebackPercent := float64(metrics.WriteBack) / float64(metrics.Total) * 100 - content += labelStyle.Render("WriteBack:") + " " + + content += labelStyle.Render("回写中:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.WriteBack), writebackPercent)) + "\n" - content += " " + descStyle.Render("Data currently being written to disk") + "\n\n" + content += " " + descStyle.Render("正在写入磁盘的数据") + "\n\n" // Slab slabPercent := float64(metrics.Slab) / float64(metrics.Total) * 100 content += labelStyle.Render("Slab:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Slab), slabPercent)) + "\n" - content += " " + descStyle.Render("Kernel data structure cache") + "\n\n" + content += " " + descStyle.Render("内核数据结构缓存") + "\n\n" // PageTables pageTablesPercent := float64(metrics.PageTables) / float64(metrics.Total) * 100 - content += labelStyle.Render("Page Tables:") + " " + + content += labelStyle.Render("页表:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.PageTables), pageTablesPercent)) + "\n" - content += " " + descStyle.Render("Memory used for page table management") + "\n\n" + content += " " + descStyle.Render("页表管理占用的内存") + "\n\n" // Diagnostics - content += titleStyle.Render("DIAGNOSTICS") + "\n\n" + content += titleStyle.Render("诊断") + "\n\n" if metrics.Dirty > 1024*1024*1024 { // > 1GB content += lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render("⚠ ") + @@ -267,26 +267,26 @@ func getSwapHealthStatus(metrics *model.MemoryMetrics) string { // No swap used - good if metrics.SwapUsed == 0 { - return goodStyle.Render("✓ No swap used") + " - System has sufficient RAM\n" + return goodStyle.Render("✓ 未使用交换分区") + " - 系统内存充足\n" } // Swap used but memory available - configuration issue availablePercent := float64(metrics.Available) / float64(metrics.Total) * 100 if metrics.SwapUsed > 0 && availablePercent > 20 { - return warningStyle.Render("⚠ Swap used with available RAM") + + return warningStyle.Render("⚠ 仍有可用内存却使用了交换分区") + " - Check swappiness setting\n" + " Recommendation: Reduce vm.swappiness value\n" } // Swap used and memory low - critical if metrics.SwapUsedPercent > 50 { - return criticalStyle.Render("✗ High swap usage") + + return criticalStyle.Render("✗ 交换分区使用率过高") + " - System under memory pressure\n" + " Recommendation: Add more RAM or reduce memory usage\n" } // Moderate swap usage - return warningStyle.Render("⚠ Moderate swap usage") + + return warningStyle.Render("⚠ 交换分区使用率中等") + " - Monitor for performance issues\n" } diff --git a/system/performance/systemHealth.go b/system/performance/systemHealth.go index cb293cd..4613bd8 100644 --- a/system/performance/systemHealth.go +++ b/system/performance/systemHealth.go @@ -115,14 +115,14 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { var checksPerformed []string // IOWait - checksPerformed = append(checksPerformed, "IOWait") + checksPerformed = append(checksPerformed, "IO 等待") if metrics.IOWait > 20 { score -= 30 - issues = append(issues, fmt.Sprintf("IOWait very high (%.2f%%)", metrics.IOWait)) + issues = append(issues, fmt.Sprintf("IOWait 非常高 (%.2f%%)", metrics.IOWait)) recommendations = append(recommendations, "Investigate disk activity, upgrade to faster storage (SSD/NVMe)") } else if metrics.IOWait > 10 { score -= 15 - issues = append(issues, fmt.Sprintf("IOWait elevated (%.2f%%)", metrics.IOWait)) + issues = append(issues, fmt.Sprintf("IOWait 偏高 (%.2f%%)", metrics.IOWait)) recommendations = append(recommendations, "Check for processes with high disk I/O, consider upgrading storage") } @@ -130,11 +130,11 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { checksPerformed = append(checksPerformed, "CPU Steal Time") if metrics.StealTime > 10 { score -= 20 - issues = append(issues, fmt.Sprintf("CPU Steal Time high (%.2f%%)", metrics.StealTime)) + issues = append(issues, fmt.Sprintf("CPU Steal Time 过高 (%.2f%%)", metrics.StealTime)) recommendations = append(recommendations, "Check host machine load, consider migrating VM or upgrading host") } else if metrics.StealTime > 5 { score -= 10 - issues = append(issues, fmt.Sprintf("CPU Steal Time elevated (%.2f%%)", metrics.StealTime)) + issues = append(issues, fmt.Sprintf("CPU Steal Time 偏高 (%.2f%%)", metrics.StealTime)) recommendations = append(recommendations, "Monitor host machine performance") } @@ -142,15 +142,15 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { checksPerformed = append(checksPerformed, "Major Page Faults") if metrics.MajorFaults > 10000 { score -= 15 - issues = append(issues, fmt.Sprintf("High number of major page faults (%s)", formatNumber(metrics.MajorFaults))) + issues = append(issues, fmt.Sprintf("主缺页次数过高 (%s)", formatNumber(metrics.MajorFaults))) recommendations = append(recommendations, "Investigate memory usage, consider increasing RAM") } // Context Switches - checksPerformed = append(checksPerformed, "Context Switches") + checksPerformed = append(checksPerformed, "上下文切换") if metrics.ContextSwitches > 100000000 { // 100 million score -= 10 - issues = append(issues, fmt.Sprintf("High number of context switches (%s)", formatNumber(metrics.ContextSwitches))) + issues = append(issues, fmt.Sprintf("上下文切换次数过高 (%s)", formatNumber(metrics.ContextSwitches))) recommendations = append(recommendations, "Optimize multi-threaded applications, check for excessive context switching") } diff --git a/system/performance/view.go b/system/performance/view.go index 4f1569b..5bf6be3 100644 --- a/system/performance/view.go +++ b/system/performance/view.go @@ -11,18 +11,18 @@ import ( func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, healthScore *HealthScore) string { if healthLoading { - return vars.CardStyle.Render("⏳ Loading System Health...") + return vars.CardStyle.Render("⏳ 正在加载系统健康...") } if healthMetrics == nil || healthScore == nil { - return vars.CardStyle.Render("Press 'r' to load system health metrics.") + return vars.CardStyle.Render("按 r 加载系统健康指标。") } var b strings.Builder // Title titleStyle := lipgloss.NewStyle().Bold(true).Foreground(vars.AccentColor).MarginBottom(1) - b.WriteString(titleStyle.Render("─ SYSTEM HEALTH ANALYSIS ─")) + b.WriteString(titleStyle.Render("─ 系统健康分析 ─")) b.WriteString("\n") // Health Score @@ -38,11 +38,11 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he } else if healthScore.Score < 80 { healthStatus = " [Fair]" } - b.WriteString(fmt.Sprintf("│ Health Score: %s%s%s \n\n", prog.ViewAs(float64(healthScore.Score)/100.0), scoreStr, healthStatus)) + b.WriteString(fmt.Sprintf("│ 健康评分:%s%s%s \n\n", prog.ViewAs(float64(healthScore.Score)/100.0), scoreStr, healthStatus)) // Detected Issues if len(healthScore.Issues) > 0 { - b.WriteString(lipgloss.NewStyle().Foreground(vars.ErrorColor).Render("⚠️ DETECTED ISSUES")) + b.WriteString(lipgloss.NewStyle().Foreground(vars.ErrorColor).Render("⚠️ 发现问题")) b.WriteString("\n") for _, issue := range healthScore.Issues { b.WriteString(fmt.Sprintf("- %s \n", issue)) @@ -52,7 +52,7 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he // Recommendations if len(healthScore.Recommendations) > 0 { - b.WriteString(lipgloss.NewStyle().Foreground(vars.SuccessColor).Render("💡 RECOMMENDATIONS")) + b.WriteString(lipgloss.NewStyle().Foreground(vars.SuccessColor).Render("💡 建议")) b.WriteString("\n") for _, rec := range healthScore.Recommendations { b.WriteString(fmt.Sprintf("- %s \n", rec)) @@ -62,7 +62,7 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he // Checks Performed if len(healthScore.ChecksPerformed) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("CHECKS PERFORMED")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render("已执行检查")) b.WriteString("\n") for _, check := range healthScore.ChecksPerformed { b.WriteString(fmt.Sprintf("- %s \n", check)) diff --git a/system/security/SystemRestart.go b/system/security/SystemRestart.go index 58168c0..1722e66 100644 --- a/system/security/SystemRestart.go +++ b/system/security/SystemRestart.go @@ -18,7 +18,7 @@ func (sm *SecurityManager) checkSystemRestart() SecurityCheck { for _, check := range checks { if required, reason := check(); required { return SecurityCheck{ - Name: "System Restart", + Name: "系统重启", Status: "Required", Details: reason, } @@ -26,9 +26,9 @@ func (sm *SecurityManager) checkSystemRestart() SecurityCheck { } return SecurityCheck{ - Name: "System Restart", + Name: "系统重启", Status: "Not Required", - Details: "No pending system restart required", + Details: "无需系统重启", } } diff --git a/system/security/SystemUpdates.go b/system/security/SystemUpdates.go index 1224376..f4690ba 100644 --- a/system/security/SystemUpdates.go +++ b/system/security/SystemUpdates.go @@ -20,7 +20,7 @@ func (sm *SecurityManager) checkSystemUpdates() SecurityCheck { } return SecurityCheck{ - Name: "System Updates", + Name: "系统更新", Status: "Unknown", Details: "Could not determine update status - package manager not detected", } @@ -104,13 +104,13 @@ func (sm *SecurityManager) handleYumDnfResult(mgr struct { if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 100 { count, _ := mgr.parseFunc(string(output)) return SecurityCheck{ - Name: "System Updates", + Name: "系统更新", Status: "Updates Available", Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), } } else if err == nil { return SecurityCheck{ - Name: "System Updates", + Name: "系统更新", Status: "Up to date", Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), } @@ -129,13 +129,13 @@ func (sm *SecurityManager) handleAptResult(mgr struct { count, _ := mgr.parseFunc(string(output)) if count > 0 { return SecurityCheck{ - Name: "System Updates", + Name: "系统更新", Status: "Updates Available", Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), } } return SecurityCheck{ - Name: "System Updates", + Name: "系统更新", Status: "Up to date", Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), } diff --git a/system/security/autoBane.go b/system/security/autoBane.go index ab42d72..dc9c97d 100644 --- a/system/security/autoBane.go +++ b/system/security/autoBane.go @@ -42,9 +42,9 @@ func (sm *SecurityManager) checkAutoBan() SecurityCheck { } return SecurityCheck{ - Name: "Auto Ban", + Name: "自动封禁", Status: "Disabled", - Details: "No intrusion prevention service detected (fail2ban, crowdsec, ossec, suricata, snort, denyhosts, sshguard)", + Details: "未检测到入侵防护服务(fail2ban、crowdsec、ossec、suricata、snort、denyhosts、sshguard)", } } @@ -62,7 +62,7 @@ func (sm *SecurityManager) checkSystemdServices() SecurityCheck { if status == "active" { details := service.detailsFunc() return SecurityCheck{ - Name: "Auto Ban", + Name: "自动封禁", Status: "Enabled", Details: details, } @@ -73,7 +73,7 @@ func (sm *SecurityManager) checkSystemdServices() SecurityCheck { cmd := exec.Command("systemctl", "is-active", "ossec-hids") if output, err := cmd.Output(); err == nil && strings.TrimSpace(string(output)) == "active" { return SecurityCheck{ - Name: "Auto Ban", + Name: "自动封禁", Status: "Enabled", Details: "ossec-hids is active with real-time monitoring", } @@ -201,7 +201,7 @@ func (sm *SecurityManager) checkRunningProcesses() SecurityCheck { if len(activeServices) > 0 { return SecurityCheck{ - Name: "Auto Ban", + Name: "自动封禁", Status: "Enabled", Details: fmt.Sprintf("Active intrusion prevention: %s", strings.Join(activeServices, ", ")), } @@ -249,7 +249,7 @@ func (sm *SecurityManager) DisplayAutoBanInfos() tea.Cmd { return AutoBanMsg(AutoBanInfos{ ServiceType: "None", Status: "Disabled", - Details: "No intrusion prevention service detected", + Details: "未检测到入侵防护服务", Jails: []AutoBanJail{}, BannedIPs: []string{}, RawOutput: "", diff --git a/system/security/firewall.go b/system/security/firewall.go index b1f0e64..7cb599c 100644 --- a/system/security/firewall.go +++ b/system/security/firewall.go @@ -96,13 +96,13 @@ func (sm *SecurityManager) analyzeFirewallOutput(firewallName string, output []b func (sm *SecurityManager) analyzeUFWStatus(outputStr string) *SecurityCheck { if strings.Contains(outputStr, "status: active") { return &SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Active", Details: "UFW is active and properly configured", } } else if strings.Contains(outputStr, "status: inactive") { return &SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Inactive", Details: "UFW is installed but inactive", } @@ -113,7 +113,7 @@ func (sm *SecurityManager) analyzeUFWStatus(outputStr string) *SecurityCheck { func (sm *SecurityManager) analyzeFirewalldStatus(outputStr string) *SecurityCheck { if strings.Contains(outputStr, "running") { return &SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Active", Details: "firewalld is active and properly configured", } @@ -124,7 +124,7 @@ func (sm *SecurityManager) analyzeFirewalldStatus(outputStr string) *SecurityChe func (sm *SecurityManager) analyzeIptablesStatus(output []byte) *SecurityCheck { if len(strings.Split(string(output), "\n")) > 10 { return &SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Active", Details: "iptables rules are configured", } @@ -135,7 +135,7 @@ func (sm *SecurityManager) analyzeIptablesStatus(output []byte) *SecurityCheck { func (sm *SecurityManager) analyzeNftablesStatus(output []byte) *SecurityCheck { if len(strings.Split(string(output), "\n")) > 10 { return &SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Active", Details: "nftables rules are configured", } @@ -145,9 +145,9 @@ func (sm *SecurityManager) analyzeNftablesStatus(output []byte) *SecurityCheck { func (sm *SecurityManager) createInactiveFirewallCheck() SecurityCheck { return SecurityCheck{ - Name: "Firewall Status", + Name: "防火墙状态", Status: "Inactive", - Details: "No active firewall detected (UFW, firewalld, or iptables)", + Details: "未检测到活动防火墙(UFW、firewalld 或 iptables)", } } @@ -186,7 +186,7 @@ func (sm *SecurityManager) DisplayFirewallInfos() tea.Cmd { FirewallType: "None", Status: "Inactive", Rules: []FirewallRule{}, - Details: "No active firewall detected", + Details: "未检测到活动防火墙", RawOutput: "", }) } @@ -234,12 +234,12 @@ func (sm *SecurityManager) getRawFirewallOutput(firewallType string) string { cmd = exec.Command("nft", "list", "ruleset") } default: - return "No firewall detected" + return "未检测到防火墙" } output, err := cmd.Output() if err != nil { - return fmt.Sprintf("Error retrieving raw output: %v", err) + return fmt.Sprintf("获取原始输出出错:%v", err) } return string(output) diff --git a/system/security/passwordPolicy.go b/system/security/passwordPolicy.go index d90526a..d1c43a7 100644 --- a/system/security/passwordPolicy.go +++ b/system/security/passwordPolicy.go @@ -29,14 +29,14 @@ func (sm *SecurityManager) checkPasswordPolicy() SecurityCheck { if strings.Contains(contentStr, "minlen") { re := regexp.MustCompile(`minlen=(\d+)`) if matches := re.FindStringSubmatch(contentStr); len(matches) > 1 { - details = append(details, fmt.Sprintf("Minimum length: %s", matches[1])) + details = append(details, fmt.Sprintf("最小长度:%s", matches[1])) } } if strings.Contains(contentStr, "minclass") { re := regexp.MustCompile(`minclass=(\d+)`) if matches := re.FindStringSubmatch(contentStr); len(matches) > 1 { - details = append(details, fmt.Sprintf("Minimum character classes: %s", matches[1])) + details = append(details, fmt.Sprintf("最少字符类别:%s", matches[1])) } } } @@ -45,15 +45,15 @@ func (sm *SecurityManager) checkPasswordPolicy() SecurityCheck { if hasPolicy { return SecurityCheck{ - Name: "Password Policy", + Name: "密码策略", Status: "Enabled", Details: strings.Join(details, ", "), } } return SecurityCheck{ - Name: "Password Policy", + Name: "密码策略", Status: "Disabled", - Details: "No password complexity policy found", + Details: "未找到密码复杂度策略", } } diff --git a/system/security/ports.go b/system/security/ports.go index 1498ff9..1ab8c76 100644 --- a/system/security/ports.go +++ b/system/security/ports.go @@ -89,17 +89,17 @@ func (sm *SecurityManager) checkOpenPorts() SecurityCheck { openPorts, err := o.GetOpenedPorts(sm) if err != nil { return SecurityCheck{ - Name: "Open Ports", + Name: "开放端口", Status: "Error", - Details: fmt.Sprintf("Failed to get open ports: %v", err), + Details: fmt.Sprintf("获取开放端口失败:%v", err), } } if len(openPorts) == 0 { return SecurityCheck{ - Name: "Open Ports", + Name: "开放端口", Status: "Secure", - Details: "No open ports detected", + Details: "未检测到开放端口", } } @@ -151,20 +151,20 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort switch maxRisk { case HighRisk: return SecurityCheck{ - Name: "Open Ports", + Name: "开放端口", Status: "High Risk", Details: fmt.Sprintf("Critical ports detected (%s): %s", strings.Join(riskPorts, ", "), strings.Join(findings, " | ")), } case Warning: return SecurityCheck{ - Name: "Open Ports", + Name: "开放端口", Status: "Warning", Details: strings.Join(findings, " | "), } default: return SecurityCheck{ - Name: "Open Ports", + Name: "开放端口", Status: "Secure", Details: fmt.Sprintf("%d ports open: %s", len(allPorts), ShowOpenedPorts(allPorts)), } @@ -173,7 +173,7 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort func ShowOpenedPorts(ports map[int]bool) string { if len(ports) == 0 { - return "No open ports detected" + return "未检测到开放端口" } var portList []string diff --git a/system/security/sshPassword.go b/system/security/sshPassword.go index 8fdf8ed..62474c5 100644 --- a/system/security/sshPassword.go +++ b/system/security/sshPassword.go @@ -11,9 +11,9 @@ func (sm *SecurityManager) checkSSHPasswordAuthentication() SecurityCheck { output, err := cmd.Output() if err != nil { return SecurityCheck{ - Name: "SSH Password Authentication", + Name: "SSH 密码认证", Status: "Error", - Details: fmt.Sprintf("Failed to check SSH config: %v", err), + Details: fmt.Sprintf("检查 SSH 配置失败:%v", err), } } @@ -36,14 +36,14 @@ func (sm *SecurityManager) checkSSHPasswordAuthentication() SecurityCheck { if passwordAuth == "yes" { return SecurityCheck{ - Name: "SSH Password Authentication", + Name: "SSH 密码认证", Status: "Enabled", Details: "SSH password authentication is enabled - consider disabling for better security", } } return SecurityCheck{ - Name: "SSH Password Authentication", + Name: "SSH 密码认证", Status: "Disabled", Details: "SSH password authentication is disabled", } diff --git a/system/security/sshRoot.go b/system/security/sshRoot.go index 1f32130..7c09cf9 100644 --- a/system/security/sshRoot.go +++ b/system/security/sshRoot.go @@ -72,9 +72,9 @@ func (sm *SecurityManager) checkSSHRootLogin() SecurityCheck { config, err := s.GetActiveConfig(sm) if err != nil { return SecurityCheck{ - Name: "SSH Root Login", + Name: "SSH Root 登录", Status: "Error", - Details: fmt.Sprintf("Failed to get SSH config: %v", err), + Details: fmt.Sprintf("获取 SSH 配置失败:%v", err), } } @@ -101,7 +101,7 @@ func (sm *SecurityManager) checkSSHRootLogin() SecurityCheck { } return SecurityCheck{ - Name: "SSH Root Login", + Name: "SSH Root 登录", Status: status, Details: details, } diff --git a/system/security/ssl.go b/system/security/ssl.go index 862eba8..f878720 100644 --- a/system/security/ssl.go +++ b/system/security/ssl.go @@ -36,7 +36,7 @@ type CertificateDisplayMsg CertificateInfos func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if domaine == "" { return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Error", Details: "Domain name is required", } @@ -49,9 +49,9 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { }) if err != nil { return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Error", - Details: fmt.Sprintf("Failed to connect to %v: %v", domaine, err), + Details: fmt.Sprintf("连接失败 %v: %v", domaine, err), } } defer conn.Close() @@ -61,9 +61,9 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if len(state.PeerCertificates) == 0 { return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Invalid", - Details: "No SSL certificate found", + Details: "未找到 SSL 证书", } } @@ -73,7 +73,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if cert.NotAfter.Before(now) { return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Invalid", Details: "Certificate has expired", } @@ -82,7 +82,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if cert.NotAfter.Before(now.Add(30 * 24 * time.Hour)) { daysUntilExpiration := cert.NotAfter.Sub(now).Hours() / 24 return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Warning", Details: fmt.Sprintf("Certificate expires soon in %.0f days", daysUntilExpiration), } @@ -90,7 +90,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { daysUntilExpiration := time.Until(cert.NotAfter).Hours() / 24 return SecurityCheck{ - Name: "SSL Certificate", + Name: "SSL 证书", Status: "Valid", Details: fmt.Sprintf("Certificate expires in %.0f days", daysUntilExpiration), } @@ -104,7 +104,7 @@ func DisplayCertificateInfo(cert *x509.Certificate, hostname string) Certificate otherNames := cert.DNSNames if len(otherNames) == 0 { - otherNames = []string{"No alternative names"} + otherNames = []string{"无备用名称"} } return CertificateInfos{ Subject: cert.Subject.CommonName, diff --git a/utils/test/utils_test.go b/utils/test/utils_test.go index c311343..80ce8d7 100644 --- a/utils/test/utils_test.go +++ b/utils/test/utils_test.go @@ -146,11 +146,11 @@ func TestFormatUptime(t *testing.T) { input uint64 expected string }{ - {"Minutes only", 300, "5 minutes"}, - {"Hours and minutes", 3660, "1 hours, 1 minutes"}, - {"Days and hours", 90000, "1 days, 1 hours"}, - {"Multiple days", 172800, "2 days, 0 hours"}, - {"Zero seconds", 0, "0 minutes"}, + {"Minutes only", 300, "5 分钟"}, + {"Hours and minutes", 3660, "1 小时 1 分钟"}, + {"Days and hours", 90000, "1 天 1 小时"}, + {"Multiple days", 172800, "2 天 0 小时"}, + {"Zero seconds", 0, "0 分钟"}, } for _, tt := range tests { @@ -264,12 +264,12 @@ func TestFormatOperationMessage(t *testing.T) { err error expected string }{ - {"Success restart", "restart", true, nil, "✅ Container restarted successfully"}, - {"Failure restart", "restart", false, nil, "❌ Container restarted failed"}, - {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ Container started failed: permission denied"}, - {"Unknown operation success", "unknown", true, nil, "✅ Operation 'unknown' successfully"}, - {"Unknown operation failure", "unknown", false, nil, "❌ Operation 'unknown' failed"}, - {"Empty operation", "", true, nil, "✅ Operation '' successfully"}, + {"Success restart", "restart", true, nil, "✅ 容器已重启成功"}, + {"Failure restart", "restart", false, nil, "❌ 容器已重启失败"}, + {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ 容器已启动失败: permission denied"}, + {"Unknown operation success", "unknown", true, nil, "✅ 操作 'unknown'成功"}, + {"Unknown operation failure", "unknown", false, nil, "❌ 操作 'unknown'失败"}, + {"Empty operation", "", true, nil, "✅ 操作 ''成功"}, } for _, tt := range tests { diff --git a/utils/utils.go b/utils/utils.go index ec016a9..a46b2aa 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -81,11 +81,11 @@ func FormatUptime(seconds uint64) string { minutes := (seconds % (60 * 60)) / 60 if days > 0 { - return fmt.Sprintf("%d days, %d hours", days, hours) + return fmt.Sprintf("%d 天 %d 小时", days, hours) } else if hours > 0 { - return fmt.Sprintf("%d hours, %d minutes", hours, minutes) + return fmt.Sprintf("%d 小时 %d 分钟", hours, minutes) } else { - return fmt.Sprintf("%d minutes", minutes) + return fmt.Sprintf("%d 分钟", minutes) } } @@ -146,12 +146,12 @@ func CheckDockerPermissions() (bool, string) { case "linux": // Linux currentUser, err := user.Current() if err != nil { - return false, fmt.Sprintf("Error getting the current user: %v", err) + return false, fmt.Sprintf("获取当前用户失败:%v", err) } groups, err := currentUser.GroupIds() if err != nil { - return false, fmt.Sprintf("Error getting user's groups: %v", err) + return false, fmt.Sprintf("获取用户组失败:%v", err) } for _, groupID := range groups { @@ -167,36 +167,36 @@ func CheckDockerPermissions() (bool, string) { return false, "The user is not in the 'docker' group. To add them, run 'sudo usermod -aG docker ' and then log out and log back in." default: - return false, fmt.Sprintf("Unsupported operating system: %s", runtime.GOOS) + return false, fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS) } } func FormatOperationMessage(operation string, success bool, err error) string { operationLabels := map[string]string{ - "restart": "Container restarted", - "start": "Container started", - "stop": "Container stopped", - "pause": "Container paused", - "unpause": "Container resumed", - "delete": "Container deleted", - "toggle_start": "Container state changed", - "toggle_pause": "Container pause state changed", - "exec": "Shell opened", - "logs": "Logs loaded", + "restart": "容器已重启", + "start": "容器已启动", + "stop": "容器已停止", + "pause": "容器已暂停", + "unpause": "容器已恢复", + "delete": "容器已删除", + "toggle_start": "容器状态已变更", + "toggle_pause": "容器暂停状态已变更", + "exec": "已打开 Shell", + "logs": "日志已加载", } label, exists := operationLabels[operation] if !exists { - label = fmt.Sprintf("Operation '%s'", operation) + label = fmt.Sprintf("操作 '%s'", operation) } if success { - return fmt.Sprintf("✅ %s successfully", label) + return fmt.Sprintf("✅ %s成功", label) } else { if err != nil { - return fmt.Sprintf("❌ %s failed: %v", label, err) + return fmt.Sprintf("❌ %s失败: %v", label, err) } - return fmt.Sprintf("❌ %s failed", label) + return fmt.Sprintf("❌ %s失败", label) } } diff --git a/widgets/auth/styles.go b/widgets/auth/styles.go index e8bd5d1..a0c0394 100644 --- a/widgets/auth/styles.go +++ b/widgets/auth/styles.go @@ -42,21 +42,21 @@ const ( AuthInProgressMessage = "⏳ Authenticating..." - AuthPromptMessage = "Enter password for admin access:" + AuthPromptMessage = "输入管理员密码:" - AuthInstructions = "Press Enter to authenticate, Esc to cancel" + AuthInstructions = "按 Enter 认证,按 Esc 取消" - AuthRetryMessage = "Press 'a' to retry authentication" + AuthRetryMessage = "按 a 重试认证" AdminAccessGranted = "✅ Admin access granted" AdminAccessRequired = "🔒 Some checks require admin privileges. Press 'a' to authenticate" - SudoNotAvailable = "Sudo is not available. Please run as root." + SudoNotAvailable = "Sudo 不可用,请以 root 运行。" - InvalidPasswordMessage = "Invalid password or insufficient privileges" + InvalidPasswordMessage = "密码错误或权限不足" - AuthenticationFailedGeneric = "Authentication failed" + AuthenticationFailedGeneric = "认证失败" LockedCheckIndicator = "🔒" ) diff --git a/widgets/handles-common.go b/widgets/handles-common.go index a092826..0ea0239 100644 --- a/widgets/handles-common.go +++ b/widgets/handles-common.go @@ -107,7 +107,7 @@ func (m *Model) setContainerLogs(logs string) { func (m *Model) updateLogsViewport() { if len(m.Monitor.ContainerLogsPagination.Lines) == 0 { - m.LogsViewport.SetContent("No logs available") + m.LogsViewport.SetContent("暂无日志") return } @@ -178,7 +178,7 @@ func calculateConnectivityContentLines(m Model) int { tempContent := strings.Builder{} if len(m.Network.PingResults) > 0 { - tempContent.WriteString("Ping Results\n") + tempContent.WriteString("Ping 结果\n") for _, result := range m.Network.PingResults { statusIcon := "❌" if result.Success { @@ -186,7 +186,7 @@ func calculateConnectivityContentLines(m Model) int { } tempContent.WriteString(fmt.Sprintf("%s %s - ", statusIcon, result.Target)) if result.Success { - tempContent.WriteString(fmt.Sprintf("Latency: %v, Packet Loss: %.1f%%", result.Latency, result.PacketLoss)) + tempContent.WriteString(fmt.Sprintf("延迟: %v,丢包率: %.1f%%", result.Latency, result.PacketLoss)) } else { tempContent.WriteString(fmt.Sprintf("Error: %s", result.Error)) } @@ -197,7 +197,7 @@ func calculateConnectivityContentLines(m Model) int { for _, tracerouteResult := range m.Network.TracerouteResults { if tracerouteResult.Target != "" { - tempContent.WriteString(fmt.Sprintf("Traceroute to %s\n", tracerouteResult.Target)) + tempContent.WriteString(fmt.Sprintf("路由追踪:%s\n", tracerouteResult.Target)) if tracerouteResult.Error != "" { tempContent.WriteString("Error: " + tracerouteResult.Error + "\n") } else if len(tracerouteResult.Hops) > 0 { @@ -217,7 +217,7 @@ func calculateConnectivityContentLines(m Model) int { tempContent.WriteString(hopLine + "\n") } } else { - tempContent.WriteString("No route found\n") + tempContent.WriteString("未找到路由\n") } tempContent.WriteString("\n") } @@ -225,9 +225,9 @@ func calculateConnectivityContentLines(m Model) int { // Speed test results for _, result := range m.Network.SpeedTestResults { - tempContent.WriteString("Speed Test Results\n") + tempContent.WriteString("测速结果\n") tempContent.WriteString(fmt.Sprintf("Server: %s\n", result.Server)) - tempContent.WriteString(fmt.Sprintf("Test Duration: %v\n\n", result.TestDuration)) + tempContent.WriteString(fmt.Sprintf("测试时长: %v\n\n", result.TestDuration)) if result.PingResult != nil { tempContent.WriteString("Latency (Ping):\n") diff --git a/widgets/handles.go b/widgets/handles.go index 6b14ceb..501c722 100644 --- a/widgets/handles.go +++ b/widgets/handles.go @@ -124,7 +124,7 @@ func (m Model) handleContainerRelatedMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { if container.ID == m.Monitor.SelectedContainer.ID { if strings.ToLower(container.Status) != "up" && m.Monitor.ContainerLogsStreaming { m.cleanupLogsStream() - m.LastOperationMsg = fmt.Sprintf("Container stopped, streaming disabled (status: %s)", container.Status) + m.LastOperationMsg = fmt.Sprintf("容器已停止,已禁用实时流(状态:%s)", container.Status) } break } @@ -139,10 +139,10 @@ func (m Model) handleContainerRelatedMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { m.Monitor.ContainerLogsLoading = false if logsMsg.Error != nil { if strings.Contains(logsMsg.Error.Error(), "streaming unavailable") { - m.Monitor.ContainerLogs = fmt.Sprintf("Streaming not available: %v\nShowing static logs instead...", logsMsg.Error) + m.Monitor.ContainerLogs = fmt.Sprintf("实时流不可用:%v\n改为显示静态日志...", logsMsg.Error) return m, m.Monitor.App.GetContainerLogsCmd(m.Monitor.SelectedContainer.ID) } else { - m.Monitor.ContainerLogs = fmt.Sprintf("Error loading logs: %v", logsMsg.Error) + m.Monitor.ContainerLogs = fmt.Sprintf("加载日志出错:%v", logsMsg.Error) } m.Monitor.ContainerLogsPagination.Lines = []string{m.Monitor.ContainerLogs} m.Monitor.ContainerLogsPagination.TotalPages = 1 @@ -550,19 +550,19 @@ func (m Model) handleNetworkKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "enter": if m.Diagnostic.Password.Value() != "" { m.Network.AuthState = model.AuthInProgress - m.Network.AuthMessage = "Authenticating..." + m.Network.AuthMessage = "正在认证..." // Try to authenticate err := m.setRoot() if err != nil { m.Network.AuthState = model.AuthFailed - m.Network.AuthMessage = fmt.Sprintf("Authentication failed: %v", err) + m.Network.AuthMessage = fmt.Sprintf("认证失败:%v", err) if !m.SudoAvailable { m.Network.AuthMessage += "\nSudo is not available. Please run as root." } m.Diagnostic.Password.Reset() } else { m.Network.AuthState = model.AuthSuccess - m.Network.AuthMessage = "Authentication successful!" + m.Network.AuthMessage = "认证成功!" m.AsRoot = true m.Network.AuthTimer = 2 @@ -889,13 +889,13 @@ func (m Model) handleConnectivityInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // Clear password input and reset echo mode m.Network.TracerouteInput.SetValue("") m.Network.TracerouteInput.EchoMode = textinput.EchoNormal - m.Network.TracerouteInput.Placeholder = "Enter target (IP or domain)..." + m.Network.TracerouteInput.Placeholder = "输入目标(IP 或域名)..." m.Network.ConnectivityMode = model.ConnectivityModeNone m.Network.TracerouteInstallTarget = "" // Start installation m.OperationInProgress = true - m.LastOperationMsg = "Installing traceroute..." + m.LastOperationMsg = "正在安装 traceroute..." m.resetSpinner() return m, tea.Batch( network.InstallTraceroute(target, password), @@ -908,7 +908,7 @@ func (m Model) handleConnectivityInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.Network.PingInput.SetValue("") m.Network.TracerouteInput.SetValue("") m.Network.TracerouteInput.EchoMode = textinput.EchoNormal - m.Network.TracerouteInput.Placeholder = "Enter target (IP or domain)..." + m.Network.TracerouteInput.Placeholder = "输入目标(IP 或域名)..." m.Network.TracerouteInstallTarget = "" m.Network.PingLoading = false m.Network.TracerouteLoading = false @@ -1050,19 +1050,19 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "enter": if m.Diagnostic.Password.Value() != "" { m.Diagnostic.AuthState = model.AuthInProgress - m.Diagnostic.AuthMessage = "Authenticating..." + m.Diagnostic.AuthMessage = "正在认证..." // Try to authenticate err := m.setRoot() if err != nil { m.Diagnostic.AuthState = model.AuthFailed - m.Diagnostic.AuthMessage = fmt.Sprintf("Authentication failed: %v", err) + m.Diagnostic.AuthMessage = fmt.Sprintf("认证失败:%v", err) if !m.SudoAvailable { m.Diagnostic.AuthMessage += "\nSudo is not available. Please run as root." } m.Diagnostic.Password.Reset() } else { m.Diagnostic.AuthState = model.AuthSuccess - m.Diagnostic.AuthMessage = "Authentication successful!" + m.Diagnostic.AuthMessage = "认证成功!" m.AsRoot = true m.Diagnostic.AuthTimer = 2 @@ -1467,7 +1467,7 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "a": if m.Diagnostic.SelectedItem == model.DiagnosticSecurityChecks && !m.AsRoot && !m.CanRunSudo { m.Diagnostic.AuthState = model.AuthRequired - m.Diagnostic.AuthMessage = "Enter password for admin access:" + m.Diagnostic.AuthMessage = "输入管理员密码:" m.Diagnostic.Password.Focus() m.Diagnostic.Password.SetValue("") } @@ -1550,7 +1550,7 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // Check if admin privileges are required for this diagnostic if !m.canAccessDiagnostic(checkName) { m.Diagnostic.AuthState = model.AuthRequired - m.Diagnostic.AuthMessage = fmt.Sprintf("Admin privileges required for: %s\nEnter password:", checkName) + m.Diagnostic.AuthMessage = fmt.Sprintf("需要管理员权限:%s\n请输入密码:", checkName) m.Diagnostic.Password.Focus() m.Diagnostic.Password.SetValue("") return m, nil @@ -1560,11 +1560,11 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch checkName { case "SSL Certificate": return m, m.Diagnostic.SecurityManager.RunCertificateDisplay() - case "SSH Root Login": + case "SSH Root 登录": return m, m.Diagnostic.SecurityManager.DisplaySSHRootInfos() - case "Open Ports": + case "开放端口": return m, m.Diagnostic.SecurityManager.DisplayOpenedPortsInfos() - case "Firewall Status": + case "防火墙状态": return m, m.Diagnostic.SecurityManager.DisplayFirewallInfos() case "Auto Ban": return m, m.Diagnostic.SecurityManager.DisplayAutoBanInfos() @@ -1598,7 +1598,7 @@ func (m Model) handleSecurityCheckMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { } if hasAdminChecks && m.Diagnostic.AuthState == model.AuthNotRequired { - m.Diagnostic.AuthMessage = "Some checks require admin privileges. Press 'a' to authenticate." + m.Diagnostic.AuthMessage = "部分检查需要管理员权限。按 a 进行认证。" } } @@ -1945,21 +1945,21 @@ func (m Model) handleContainerMenuKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "r": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Restart container '%s'?\nThis will stop and start the container.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定重启容器 '%s'?\n将先停止再启动该容器。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "restart" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "d": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Delete container '%s'?\nThis action cannot be undone.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "delete" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "x": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Force remove container '%s'?\nThis action cannot be undone.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定强制删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "remove" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil @@ -1978,7 +1978,7 @@ func (m Model) handleContainerMenuKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, tea.Quit case "c": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden - m.LastOperationMsg = "Commit functionality not yet implemented" + m.LastOperationMsg = "提交功能尚未实现" return m, nil case "esc", "b": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden @@ -2010,21 +2010,21 @@ func (m Model) executeContainerMenuAction() (tea.Model, tea.Cmd) { case "restart": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Restart container '%s'?\nThis will stop and start the container.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定重启容器 '%s'?\n将先停止再启动该容器。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "restart" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "delete": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Delete container '%s'?\nThis action cannot be undone.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "delete" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "remove": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("Force remove container '%s'?\nThis action cannot be undone.", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf("确定强制删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "remove" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil @@ -2043,7 +2043,7 @@ func (m Model) executeContainerMenuAction() (tea.Model, tea.Cmd) { return m, tea.Quit case "commit": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden - m.LastOperationMsg = "Commit functionality not yet implemented" + m.LastOperationMsg = "提交功能尚未实现" return m, nil } return m, nil @@ -2088,7 +2088,7 @@ func (m Model) handleConfirmationKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.Network.TracerouteInstallTarget = target m.Network.ConnectivityMode = model.ConnectivityModeInstallPassword m.Network.TracerouteInput.SetValue("") - m.Network.TracerouteInput.Placeholder = "Enter sudo password..." + m.Network.TracerouteInput.Placeholder = "输入 sudo 密码..." m.Network.TracerouteInput.EchoMode = textinput.EchoPassword m.Network.TracerouteInput.Focus() return m, nil @@ -2343,11 +2343,11 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case network.SpeedTestErrorMsg: m.Network.SpeedTestLoading = false - m.LastOperationMsg = "Speed test failed: " + msg.Error + m.LastOperationMsg = "测速失败: " + msg.Error return m, clearOperationMessage() case network.SpeedTestProgressMsg: // Handle progress updates if needed - m.LastOperationMsg = fmt.Sprintf("Speed test: %s (%.0f%%)", msg.Message, msg.Progress*100) + m.LastOperationMsg = fmt.Sprintf("测速:%s (%.0f%%)", msg.Message, msg.Progress*100) return m, nil case network.TracerouteInstallPromptMsg: // Traceroute is not installed, show confirmation dialog @@ -2375,7 +2375,7 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { m.Network.TracerouteInstallTarget = msg.Target m.Network.ConnectivityMode = model.ConnectivityModeInstallPassword m.Network.TracerouteInput.SetValue("") - m.Network.TracerouteInput.Placeholder = "Enter sudo password..." + m.Network.TracerouteInput.Placeholder = "输入 sudo 密码..." m.Network.TracerouteInput.EchoMode = textinput.EchoPassword m.Network.TracerouteInput.Focus() return m, clearOperationMessage() diff --git a/widgets/help.go b/widgets/help.go index b8459d0..b9d5f72 100644 --- a/widgets/help.go +++ b/widgets/help.go @@ -321,15 +321,15 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "toggle help"), + key.WithHelp("?", "切换帮助"), ), Quit: key.NewBinding( key.WithKeys("q", "esc", "ctrl+c"), - key.WithHelp("q", "quit"), + key.WithHelp("q", "退出"), ), Back: key.NewBinding( key.WithKeys("b"), - key.WithHelp("b", "back"), + key.WithHelp("b", "返回"), ), } @@ -339,27 +339,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "select"), + key.WithHelp("enter", "选择"), ), Navigate: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "navigate"), + key.WithHelp("tab/←→", "移动"), ), Quick1: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "monitor"), + key.WithHelp("1", "监控"), ), Quick2: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "diagnostics"), + key.WithHelp("2", "诊断"), ), Quick3: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "network"), + key.WithHelp("3", "网络"), ), Quick4: key.NewBinding( key.WithKeys("4"), - key.WithHelp("4", "reporting"), + key.WithHelp("4", "报告"), ), } @@ -368,23 +368,23 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "select"), + key.WithHelp("enter", "选择"), ), Navigate: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "navigate"), + key.WithHelp("tab/←→", "移动"), ), Quick1: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "system"), + key.WithHelp("1", "系统"), ), Quick2: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "process"), + key.WithHelp("2", "进程"), ), Quick3: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "containers"), + key.WithHelp("3", "容器"), ), } @@ -393,7 +393,7 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Scroll: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "scroll"), + key.WithHelp("↑↓", "滚动"), ), } @@ -402,23 +402,23 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "search"), + key.WithHelp("/", "搜索"), ), Kill: key.NewBinding( key.WithKeys("k"), - key.WithHelp("k", "kill"), + key.WithHelp("k", "结束进程"), ), SortMem: key.NewBinding( key.WithKeys("m"), - key.WithHelp("m", "sort by mem"), + key.WithHelp("m", "按内存排序"), ), SortCPU: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "sort by cpu"), + key.WithHelp("s", "按CPU排序"), ), } @@ -427,15 +427,15 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "menu"), + key.WithHelp("enter", "菜单"), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "search"), + key.WithHelp("/", "搜索"), ), } @@ -444,7 +444,7 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "switch tabs"), + key.WithHelp("tab/←→", "切换标签"), ), } @@ -453,19 +453,19 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Scroll: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "scroll"), + key.WithHelp("↑↓", "滚动"), ), Refresh: key.NewBinding( key.WithKeys("r"), - key.WithHelp("r", "refresh"), + key.WithHelp("r", "刷新"), ), HomeEnd: key.NewBinding( key.WithKeys("home", "end"), - key.WithHelp("home/end", "navigate"), + key.WithHelp("home/end", "移动"), ), Stream: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "toggle stream"), + key.WithHelp("s", "切换实时流"), ), } @@ -474,27 +474,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "switch tabs"), + key.WithHelp("tab/←→", "切换标签"), ), Ping: key.NewBinding( key.WithKeys("p"), - key.WithHelp("p", "ping"), + key.WithHelp("p", "Ping"), ), Trace: key.NewBinding( key.WithKeys("t"), - key.WithHelp("t", "trace route"), + key.WithHelp("t", "路由追踪"), ), Clear: key.NewBinding( key.WithKeys("c"), - key.WithHelp("c", "clear"), + key.WithHelp("c", "清除"), ), Switch: key.NewBinding( key.WithKeys("space"), - key.WithHelp("space", "switch view"), + key.WithHelp("space", "切换视图"), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate tables/pages"), + key.WithHelp("↑↓", "浏览表格/翻页"), ), } @@ -505,35 +505,35 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "switch tabs"), + key.WithHelp("←→", "切换标签"), ), Reload: key.NewBinding( key.WithKeys("enter", "r"), - key.WithHelp("enter/r", "reload"), + key.WithHelp("enter/r", "刷新"), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "search"), + key.WithHelp("/", "搜索"), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "details"), + key.WithHelp("enter", "详情"), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "security"), + key.WithHelp("1", "安全"), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "performance"), + key.WithHelp("2", "性能"), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "logs"), + key.WithHelp("3", "日志"), ), } case model.DiagnosticTabPerformances: @@ -541,27 +541,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "switch tabs"), + key.WithHelp("←→", "切换标签"), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "details"), + key.WithHelp("enter", "详情"), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "security"), + key.WithHelp("1", "安全"), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "performance"), + key.WithHelp("2", "性能"), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "logs"), + key.WithHelp("3", "日志"), ), } case model.DiagnosticTabLogs: @@ -569,51 +569,51 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, TimeRange: key.NewBinding( key.WithKeys("shift+left", "shift+right"), - key.WithHelp("shift+←→", "time range"), + key.WithHelp("shift+←→", "时间范围"), ), Level: key.NewBinding( key.WithKeys("shift+left", "shift+right"), - key.WithHelp("shift+←→", "level"), + key.WithHelp("shift+←→", "级别"), ), SwitchFilter: key.NewBinding( key.WithKeys("space"), - key.WithHelp("space", "switch filter"), + key.WithHelp("space", "切换筛选"), ), SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "switch tabs"), + key.WithHelp("←→", "切换标签"), ), Reload: key.NewBinding( key.WithKeys("enter", "r"), - key.WithHelp("enter/r", "reload"), + key.WithHelp("enter/r", "刷新"), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "search"), + key.WithHelp("/", "搜索"), ), Service: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "service"), + key.WithHelp("s", "服务"), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Details: key.NewBinding( key.WithKeys("d"), - key.WithHelp("d", "details"), + key.WithHelp("d", "详情"), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "security"), + key.WithHelp("1", "安全"), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "performance"), + key.WithHelp("2", "性能"), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "logs"), + key.WithHelp("3", "日志"), ), } default: @@ -621,27 +621,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "switch tabs"), + key.WithHelp("←→", "切换标签"), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "details"), + key.WithHelp("enter", "详情"), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "security"), + key.WithHelp("1", "安全"), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "performance"), + key.WithHelp("2", "性能"), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "logs"), + key.WithHelp("3", "日志"), ), } } @@ -651,27 +651,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Generate: key.NewBinding( key.WithKeys("g"), - key.WithHelp("g", "generate report"), + key.WithHelp("g", "生成报告"), ), Save: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "save report"), + key.WithHelp("s", "保存报告"), ), Load: key.NewBinding( key.WithKeys("l"), - key.WithHelp("l", "load reports"), + key.WithHelp("l", "加载报告"), ), Navigate: key.NewBinding( key.WithKeys("up", "down", "k", "j"), - key.WithHelp("↑↓", "navigate"), + key.WithHelp("↑↓", "移动"), ), Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "select"), + key.WithHelp("enter", "选择"), ), Delete: key.NewBinding( key.WithKeys("d"), - key.WithHelp("d", "delete"), + key.WithHelp("d", "删除"), ), } } @@ -722,15 +722,15 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "toggle help"), + key.WithHelp("?", "切换帮助"), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "quit"), + key.WithHelp("q", "退出"), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "back"), + key.WithHelp("b/esc", "返回"), ), } @@ -738,7 +738,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "switch CPU sub-tabs"), + key.WithHelp("←→", "切换CPU子标签"), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -759,15 +759,15 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "toggle help"), + key.WithHelp("?", "切换帮助"), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "quit"), + key.WithHelp("q", "退出"), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "back"), + key.WithHelp("b/esc", "返回"), ), } @@ -775,7 +775,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "switch Memory sub-tabs"), + key.WithHelp("←→", "切换内存子标签"), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -797,32 +797,32 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "toggle help"), + key.WithHelp("?", "切换帮助"), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "quit"), + key.WithHelp("q", "退出"), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "back"), + key.WithHelp("b/esc", "返回"), ), } - enterHelp := "activate tab" + enterHelp := "激活标签" // Show special help when on CPU or Memory tab switch m.Diagnostic.Performance.SelectedItem { case model.CPU: - enterHelp = "enter CPU sub-tabs" + enterHelp = "进入CPU子标签" case model.Memory: - enterHelp = "enter Memory sub-tabs" + enterHelp = "进入内存子标签" } return DiagnosticsKeyMap{ BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "switch performance tabs"), + key.WithHelp("←→", "切换性能标签"), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -834,7 +834,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti ), Reload: key.NewBinding( key.WithKeys("r"), - key.WithHelp("r", "reload data"), + key.WithHelp("r", "刷新数据"), ), } } diff --git a/widgets/help_test.go b/widgets/help_test.go index 609b45c..ed34446 100644 --- a/widgets/help_test.go +++ b/widgets/help_test.go @@ -179,15 +179,15 @@ func TestBaseKeyMap(t *testing.T) { baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "toggle help"), + key.WithHelp("?", "切换帮助"), ), Quit: key.NewBinding( key.WithKeys("q", "esc", "ctrl+c"), - key.WithHelp("q", "quit"), + key.WithHelp("q", "退出"), ), Back: key.NewBinding( key.WithKeys("b"), - key.WithHelp("b", "back"), + key.WithHelp("b", "返回"), ), } diff --git a/widgets/init.go b/widgets/init.go index 6ba9145..1706c57 100644 --- a/widgets/init.go +++ b/widgets/init.go @@ -39,16 +39,16 @@ func InitialModelWithManager(apk *app.DockerManager) Model { var err error containers, err = apk.RefreshContainers() if err != nil { - fmt.Printf("Erreur lors du chargement des conteneurs: %v\n", err) + fmt.Printf("加载容器时出错: %v\n", err) } } columns := []table.Column{ {Title: "PID", Width: 8}, - {Title: "User", Width: 12}, + {Title: "用户", Width: 12}, {Title: "CPU%", Width: 8}, - {Title: "Mem%", Width: 8}, - {Title: "Command", Width: 30}, + {Title: "内存%", Width: 8}, + {Title: "命令", Width: 30}, } t := table.New( table.WithColumns(columns), @@ -61,12 +61,12 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // containers table ctColumns := []table.Column{ {Title: "ID", Width: 12}, - {Title: "Image", Width: 12}, - {Title: "Name", Width: 16}, - {Title: "Status", Width: 12}, - {Title: "Health", Width: 12}, - {Title: "Project", Width: 20}, - {Title: "Ports", Width: 20}, + {Title: "镜像", Width: 12}, + {Title: "名称", Width: 16}, + {Title: "状态", Width: 12}, + {Title: "健康状态", Width: 12}, + {Title: "项目", Width: 20}, + {Title: "端口", Width: 20}, } ct := table.New( table.WithColumns(ctColumns), @@ -79,11 +79,11 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Network table networkColumns := []table.Column{ - {Title: "Interface", Width: 12}, - {Title: "Status", Width: 8}, - {Title: "IP Addresses", Width: 25}, - {Title: "RX", Width: 12}, - {Title: "TX", Width: 12}, + {Title: "接口", Width: 12}, + {Title: "状态", Width: 8}, + {Title: "IP 地址", Width: 25}, + {Title: "接收", Width: 12}, + {Title: "发送", Width: 12}, } networkTable := table.New( table.WithColumns(networkColumns), @@ -100,13 +100,13 @@ func InitialModelWithManager(apk *app.DockerManager) Model { networkTable.SetStyles(networkStyle) connectionsColumns := []table.Column{ - {Title: "Proto", Width: 6}, - {Title: "Recv-Q", Width: 8}, - {Title: "Send-Q", Width: 8}, - {Title: "Local Address", Width: 22}, - {Title: "Foreign Address", Width: 20}, - {Title: "State", Width: 12}, - {Title: "PID/Program", Width: 18}, + {Title: "协议", Width: 6}, + {Title: "接收队列", Width: 8}, + {Title: "发送队列", Width: 8}, + {Title: "本地地址", Width: 22}, + {Title: "远程地址", Width: 20}, + {Title: "状态", Width: 12}, + {Title: "PID/程序", Width: 18}, } connectionsTable := table.New( table.WithColumns(connectionsColumns), @@ -116,11 +116,11 @@ func InitialModelWithManager(apk *app.DockerManager) Model { connectionsTable.SetStyles(networkStyle) routesColumns := []table.Column{ - {Title: "Destination", Width: 18}, - {Title: "Gateway", Width: 12}, - {Title: "Genmask", Width: 16}, - {Title: "Flags", Width: 8}, - {Title: "Iface", Width: 16}, + {Title: "目标", Width: 18}, + {Title: "网关", Width: 12}, + {Title: "子网掩码", Width: 16}, + {Title: "标志", Width: 8}, + {Title: "接口", Width: 16}, } routesTable := table.New( table.WithColumns(routesColumns), @@ -140,9 +140,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { dnsTable.SetStyles(networkStyle) diagnosticColumns := []table.Column{ - {Title: "Security Check", Width: 30}, - {Title: "Performances", Width: 12}, - {Title: "Logs", Width: 40}, + {Title: "安全检查", Width: 30}, + {Title: "性能", Width: 12}, + {Title: "日志", Width: 40}, } diagnosticTable := table.New( @@ -152,13 +152,13 @@ func InitialModelWithManager(apk *app.DockerManager) Model { diagnosticTable.SetStyles(networkStyle) searchInput := textinput.New() - searchInput.Placeholder = "Search a process..." + searchInput.Placeholder = "搜索进程..." searchInput.Prompt = "/" searchInput.CharLimit = 50 searchInput.Width = 30 passwordInput := textinput.New() - passwordInput.Placeholder = "Enter root password..." + passwordInput.Placeholder = "输入 root 密码..." passwordInput.EchoMode = textinput.EchoPassword passwordInput.EchoCharacter = '•' passwordInput.CharLimit = 50 @@ -181,9 +181,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { securityManager.SudoPassword = "" // No password initially securityColumns := []table.Column{ - {Title: "Name", Width: 20}, - {Title: "Status", Width: 15}, - {Title: "Details", Width: 40}, + {Title: "名称", Width: 20}, + {Title: "状态", Width: 15}, + {Title: "详情", Width: 40}, } securityTable := table.New( table.WithColumns(securityColumns), @@ -202,9 +202,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { securityTable.SetStyles(tableStyle) portsColumns := []table.Column{ - {Title: "Port", Width: 10}, - {Title: "Service", Width: 20}, - {Title: "Protocol", Width: 10}, + {Title: "端口", Width: 10}, + {Title: "服务", Width: 20}, + {Title: "协议", Width: 10}, {Title: "PID", Width: 10}, } @@ -217,7 +217,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { spinnerModel := getRandomSpinner() firewallColumns := []table.Column{ - {Title: "Firewall Rule", Width: 100}, + {Title: "防火墙规则", Width: 100}, } firewallTable := table.New( @@ -228,7 +228,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { firewallTable.SetStyles(tableStyle) autoBanColumns := []table.Column{ - {Title: "Jail/Service Details", Width: 100}, + {Title: "Jail/服务详情", Width: 100}, } autoBanTable := table.New( @@ -240,10 +240,10 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Logs table logsColumns := []table.Column{ - {Title: "Time", Width: 20}, - {Title: "Level", Width: 8}, - {Title: "Service", Width: 20}, - {Title: "Message", Width: 70}, + {Title: "时间", Width: 20}, + {Title: "级别", Width: 8}, + {Title: "服务", Width: 20}, + {Title: "消息", Width: 70}, } logsTable := table.New( @@ -256,17 +256,17 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Log filter inputs logSearchInput := textinput.New() - logSearchInput.Placeholder = "Search logs..." + logSearchInput.Placeholder = "搜索日志..." logSearchInput.CharLimit = 100 logSearchInput.Width = 30 logServiceInput := textinput.New() - logServiceInput.Placeholder = "Filter by service..." + logServiceInput.Placeholder = "按服务筛选..." logServiceInput.CharLimit = 50 logServiceInput.Width = 25 logTimeRangeInput := textinput.New() - logTimeRangeInput.Placeholder = "e.g., '2 hours ago', '2025-01-08', '3 days ago'" + logTimeRangeInput.Placeholder = "例如:'2 小时前'、'2025-01-08'、'3 天前'" logTimeRangeInput.CharLimit = 50 logTimeRangeInput.Width = 50 @@ -294,14 +294,14 @@ func InitialModelWithManager(apk *app.DockerManager) Model { SelectedItem: model.NetworkTabInterface, PingInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "Enter host or IP to ping (e.g., 8.8.8.8)" + ti.Placeholder = "输入要 Ping 的主机或 IP(如 8.8.8.8)" ti.CharLimit = 100 ti.Width = 40 return ti }(), TracerouteInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "Enter host or IP to trace (e.g., google.com)" + ti.Placeholder = "输入要追踪的主机或 IP(如 google.com)" ti.CharLimit = 100 ti.Width = 40 return ti @@ -319,7 +319,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { SelectedItem: model.DiagnosticSecurityChecks, Performance: model.PerformanceModel{ SelectedItem: model.SystemHealth, - Nav: []string{"System Health", "I/O", "CPU", "Memory"}, + Nav: []string{"系统健康", "I/O", "CPU", "内存"}, SubTabNavigationActive: false, CPUSelectedTab: model.CPUTabStateBreakdown, CPUSubTabActive: false, @@ -343,7 +343,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { CustomTimeInputMode: false, DomainInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "Enter domain name (e.g., google.com)" + ti.Placeholder = "输入域名(如 google.com)" ti.CharLimit = 100 ti.Width = 40 return ti diff --git a/widgets/model/performance.go b/widgets/model/performance.go index 90d8df3..16af746 100644 --- a/widgets/model/performance.go +++ b/widgets/model/performance.go @@ -31,7 +31,7 @@ const ( ) func (pt PerformanceTab) String() string { - return []string{"System Health", "I/O", "CPU", "Memory"}[pt] + return []string{"系统健康", "I/O", "CPU", "内存"}[pt] } type CPUTab int @@ -43,7 +43,7 @@ const ( ) func (ct CPUTab) String() string { - return []string{"CPU State Breakdown", "Per-Core Performance", "System Activity Metrics"}[ct] + return []string{"CPU 状态分解", "每核性能", "系统活动指标"}[ct] } type MemoryTab int @@ -56,7 +56,7 @@ const ( ) func (mt MemoryTab) String() string { - return []string{"Overview", "Usage Breakdown", "Swap Analysis", "System Memory"}[mt] + return []string{"概览", "使用分解", "交换分区分析", "系统内存"}[mt] } type PerformanceModel struct { diff --git a/widgets/model/reporting.go b/widgets/model/reporting.go index 8f6fa57..c372ea4 100644 --- a/widgets/model/reporting.go +++ b/widgets/model/reporting.go @@ -426,19 +426,19 @@ func (rm *ReportModel) getSecurityRecommendations(securityChecks []security.Secu for _, check := range securityChecks { if strings.Contains(check.Status, "Warning") { switch check.Name { - case "SSH Password Authentication": - recommendations = append(recommendations, "SSH password authentication is enabled. Consider using SSH keys only for better security.") - case "System Updates": - recommendations = append(recommendations, "System updates are available. Run 'sudo apt update && sudo apt upgrade' to apply security patches.") - case "System Restart Required": - recommendations = append(recommendations, "System restart required for kernel updates. Schedule a maintenance window to reboot the system.") + case "SSH 密码认证": + recommendations = append(recommendations, "已启用 SSH 密码认证。建议仅使用 SSH 密钥以提高安全性。") + case "系统更新": + recommendations = append(recommendations, "系统有可用更新。请运行 'sudo apt update && sudo apt upgrade' 应用安全补丁。") + case "系统重启": + recommendations = append(recommendations, "内核更新需要重启系统。请安排维护窗口重启。") } } else if strings.Contains(check.Status, "Failed") { switch check.Name { - case "Firewall Status": - recommendations = append(recommendations, "Firewall is not active. Enable it with 'sudo ufw enable'.") - case "Open Ports": - recommendations = append(recommendations, "Risky ports are open. Close unnecessary ports to reduce attack surface.") + case "防火墙状态": + recommendations = append(recommendations, "防火墙未启用。可用 'sudo ufw enable' 开启。") + case "开放端口": + recommendations = append(recommendations, "存在高风险开放端口。请关闭不必要端口以减小攻击面。") } } } @@ -454,7 +454,7 @@ func (rm *ReportModel) getPerformanceRecommendations(m MonitorModel) []string { if len(m.Processes) > 0 { topProcess := m.Processes[0] recommendations = append(recommendations, - fmt.Sprintf("Memory usage very high (%.1f%%). Process '%s' is consuming %.1f%% memory. Consider optimizing or allocating more resources.", + fmt.Sprintf("内存使用率很高 (%.1f%%)。进程 '%s' 占用 %.1f%% 内存。建议优化或增加资源。", m.Memory.Usage, topProcess.Command, topProcess.Mem)) } else { recommendations = append(recommendations, @@ -495,7 +495,7 @@ func (rm *ReportModel) getDockerRecommendations(m MonitorModel) []string { for _, container := range containers { if container.Health == "unhealthy" { recommendations = append(recommendations, - fmt.Sprintf("Container '%s' is unhealthy. Check its logs with 'docker logs %s'.", + fmt.Sprintf("容器 '%s' 状态不健康。请用 'docker logs %s' 查看日志。", container.Name, container.ID[:12])) } } @@ -511,7 +511,7 @@ func (rm *ReportModel) SaveReport() (string, error) { filename := fmt.Sprintf("report_%s_%s.md", time.Now().Format("2006-01-02_150405"), - "system") // In practice, you might want to get the actual hostname + "系统") // In practice, you might want to get the actual hostname filepath := filepath.Join(rm.ReportDirectory, filename) err := os.WriteFile(filepath, []byte(rm.CurrentReport), 0644) @@ -569,9 +569,9 @@ func formatUptime(uptime uint64) string { if days > 0 { return fmt.Sprintf("%d days, %d hours, %d minutes", days, hours, minutes) } else if hours > 0 { - return fmt.Sprintf("%d hours, %d minutes", hours, minutes) + return fmt.Sprintf("%d 小时 %d 分钟", hours, minutes) } else { - return fmt.Sprintf("%d minutes", minutes) + return fmt.Sprintf("%d 分钟", minutes) } } diff --git a/widgets/model/type.go b/widgets/model/type.go index 5f10249..71140e2 100644 --- a/widgets/model/type.go +++ b/widgets/model/type.go @@ -11,7 +11,7 @@ const ( ContainerTabNetwork // ContainerTabDisk ContainerTabEnv - // "Interface", "Connectivity", "Configuration", "Protocol Analysis" + // "网卡", "Connectivity", "Configuration", "Protocol Analysis" // ================================ // NetworkTabInterface ContainerTab = ContainerTabGeneral NetworkTabConnectivity = ContainerTabCPU @@ -19,7 +19,7 @@ const ( NetworkTabProtocol = ContainerTabNetwork // ================================ // - // "Health Checks", "Performances", "Logs" + // "Health Checks", "性能", "Logs" DiagnosticSecurityChecks ContainerTab = ContainerTabGeneral DiagnosticTabPerformances ContainerTab = ContainerTabCPU DiagnosticTabLogs ContainerTab = ContainerTabMemory diff --git a/widgets/model/ui.go b/widgets/model/ui.go index f4d0328..6d5fe52 100644 --- a/widgets/model/ui.go +++ b/widgets/model/ui.go @@ -10,13 +10,13 @@ type AppState string const ( StateHome AppState = "home" - StateMonitor AppState = "monitor" + StateMonitor AppState = "监控" StateSystem AppState = "monitor.system" StateProcess AppState = "monitor.process" StateContainers AppState = "monitor.containers" StateContainer AppState = "monitor.containers.single" StateContainerLogs AppState = "monitor.containers.logs" - StateDiagnostics AppState = "diagnostics" + StateDiagnostics AppState = "诊断" StateCertificateDetails AppState = "diagnostics.certificate" StateSSHRootDetails AppState = "diagnostics.sshroot" StateOpenedPortsDetails AppState = "diagnostics.openedports" @@ -24,16 +24,16 @@ const ( StateAutoBanDetails AppState = "diagnostics.autoban" StateLogDetails AppState = "diagnostics.logs" StateLogEntryDetails AppState = "diagnostics.logs.entry" - StateNetwork AppState = "network" + StateNetwork AppState = "网络" StateInterfaces AppState = "network.interfaces" StateConnectivity AppState = "network.connectivity" StateConfiguration AppState = "network.configuration" StateProtocolAnalysis AppState = "network.protocol.analysis" - StateReporting AppState = "reporting" + StateReporting AppState = "报告" StateGeneratingReport AppState = "reporting.generating" StateViewingReport AppState = "reporting.viewing" StateSavingReport AppState = "reporting.saving" - StatePerformance AppState = "performance" + StatePerformance AppState = "性能" StateSystemHealth AppState = "performance.systemhealth" StateInputOutput AppState = "performance.inputoutput" StateCPU AppState = "performance.cpu" diff --git a/widgets/render-body.go b/widgets/render-body.go index ad8fd75..ee81815 100644 --- a/widgets/render-body.go +++ b/widgets/render-body.go @@ -45,7 +45,7 @@ func (m Model) renderMainContent() string { case model.StateSystemHealth, model.StateInputOutput, model.StateCPU, model.StateMemory, model.StateQuickTests: currentView = m.renderPerformanceAnalysis() default: - currentView = fmt.Sprintf("Unknown state: %v", m.Ui.State) + currentView = fmt.Sprintf("未知状态:%v", m.Ui.State) } m.Ui.Viewport.SetContent(currentView) m.Ui.Viewport.SetYOffset(m.Ui.Viewport.YOffset) diff --git a/widgets/render-charts.go b/widgets/render-charts.go index 35e3f10..1c697b1 100644 --- a/widgets/render-charts.go +++ b/widgets/render-charts.go @@ -82,7 +82,7 @@ func (m Model) renderNetworkRXChart(width, height int) string { // Get container-specific history if available var points []float64 var maxValue float64 - caption := "Network RX" + caption := "网络接收" if m.Monitor.SelectedContainer != nil { if containerHistory, exists := m.Monitor.ContainerHistories[m.Monitor.SelectedContainer.ID]; exists { @@ -98,7 +98,7 @@ func (m Model) renderNetworkRXChart(width, height int) string { } if len(points) < 1 { - return renderEmptyChart("Network RX", height) + return renderEmptyChart("网络接收", height) } // Ensure minimum scale for visibility @@ -123,7 +123,7 @@ func (m Model) renderNetworkTXChart(width, height int) string { // Get container-specific history if available var points []float64 var maxValue float64 - caption := "Network TX" + caption := "网络发送" if m.Monitor.SelectedContainer != nil { if containerHistory, exists := m.Monitor.ContainerHistories[m.Monitor.SelectedContainer.ID]; exists { @@ -139,7 +139,7 @@ func (m Model) renderNetworkTXChart(width, height int) string { } if len(points) < 1 { - return renderEmptyChart("Network TX", height) + return renderEmptyChart("网络发送", height) } // Ensure minimum scale for visibility @@ -175,18 +175,18 @@ func renderEmptyChart(title string, height int) string { builder += "\n" } - builder += "Collecting data..." + builder += "正在采集数据..." return builder } // getOptimalNetworkScale determines the best scale and unit for network data func getOptimalNetworkScale(points []float64, direction string) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "Network " + direction + " (MB/s)" + return points, 10.0, "网络 " + direction + " (MB/s)" } maxValue := getMaxValueFromFloat64(points) - caption := "Network " + direction + caption := "网络 " + direction // Determine the best unit and scale based on the maximum value if maxValue < 0.001 { // Less than 1 KB/s in MB units @@ -234,11 +234,11 @@ func getMaxValueFromFloat64(values []float64) float64 { // getOptimalCPUScale determines the best scale for CPU usage data func getOptimalCPUScale(points []float64) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "CPU Usage (%)" + return points, 10.0, "CPU 使用率 (%)" } maxValue := getMaxValueFromFloat64(points) - caption := "CPU Usage (%)" + caption := "CPU 使用率 (%)" // Dynamic scaling for low CPU usage if maxValue < 1.0 { // Less than 1% @@ -273,11 +273,11 @@ func getOptimalCPUScale(points []float64) ([]float64, float64, string) { // getOptimalMemoryScale determines the best scale for memory usage data func getOptimalMemoryScale(points []float64) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "Memory Usage (%)" + return points, 10.0, "内存使用率 (%)" } maxValue := getMaxValueFromFloat64(points) - caption := "Memory Usage (%)" + caption := "内存使用率 (%)" // Dynamic scaling for low memory usage if maxValue < 1.0 { // Less than 1% diff --git a/widgets/render-common.go b/widgets/render-common.go index 56a4c37..75be755 100644 --- a/widgets/render-common.go +++ b/widgets/render-common.go @@ -25,9 +25,9 @@ func (m Model) renderConfirmationDialog() string { doc.WriteString(v.MetricLabelStyle.Render(m.ConfirmationMessage)) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Are you sure?")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("确认操作?")) doc.WriteString("\n") - doc.WriteString("Press 'y' to confirm or 'n' to cancel") + doc.WriteString("按 y 确认,按 n 取消") confirmationStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). diff --git a/widgets/render-container.go b/widgets/render-container.go index 7ed5229..9c2250b 100644 --- a/widgets/render-container.go +++ b/widgets/render-container.go @@ -21,8 +21,8 @@ func (m Model) renderContainerMenu() string { doc := strings.Builder{} doc.WriteString("CONTAINER MENU\n") - doc.WriteString(fmt.Sprintf("Container: %s\n", m.Monitor.SelectedContainer.Name)) - doc.WriteString(fmt.Sprintf("Status: %s\n", m.Monitor.SelectedContainer.Status)) + doc.WriteString(fmt.Sprintf("容器: %s\n", m.Monitor.SelectedContainer.Name)) + doc.WriteString(fmt.Sprintf("状态: %s\n", m.Monitor.SelectedContainer.Status)) doc.WriteString("\n") for i, item := range m.Monitor.ContainerMenuItems { @@ -42,7 +42,7 @@ func (m Model) renderContainerMenu() string { func (m Model) renderContainerSingleView() string { if m.Monitor.SelectedContainer == nil { - return v.CardStyle.Render("No container selected.") + return v.CardStyle.Render("未选择容器。") } style := lipgloss.NewStyle().Padding(0, 2). Foreground(v.WhiteColor). @@ -67,7 +67,7 @@ func (m Model) renderContainerSingleView() string { case model.ContainerTabEnv: content = m.renderContainerEnv() default: - content = "Not implemented" + content = "尚未实现" } return lipgloss.JoinVertical(lipgloss.Left, tabsHeader, content) @@ -83,11 +83,11 @@ func (m Model) renderContainerGeneral() string { containerName = m.Monitor.SelectedContainer.Name } - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(fmt.Sprintf("Container: %s", containerName))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(fmt.Sprintf("容器: %s", containerName))) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil { - info := fmt.Sprintf("ID: %s\nName: %s\nImage: %s\nStatus: %s\nProject: %s\nCreated: %s\nUptime: %s\nHealth: %s\nIP Address: %s\nGateway: %s", + info := fmt.Sprintf("ID: %s\n名称: %s\n镜像: %s\n状态: %s\n项目: %s\n创建时间: %s\n运行时间: %s\n健康: %s\nIP 地址: %s\n网关: %s", m.Monitor.ContainerDetails.ID, m.Monitor.ContainerDetails.Name, m.Monitor.ContainerDetails.Image, @@ -121,11 +121,11 @@ func (m Model) renderContainerGeneral() string { if len(m.Monitor.ContainerDetails.Environment) > 0 { doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Environment:")) - doc.WriteString(fmt.Sprintf(" %d variables", len(m.Monitor.ContainerDetails.Environment))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("环境变量:")) + doc.WriteString(fmt.Sprintf(" %d 个变量", len(m.Monitor.ContainerDetails.Environment))) } } else { - info := "Loading container details..." + info := "正在加载容器详情..." doc.WriteString(v.MetricLabelStyle.Render(info)) } @@ -136,22 +136,22 @@ func (m Model) renderContainerCPU() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("CPU Usage")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("CPU 使用率")) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { cpuPercent := m.Monitor.ContainerDetails.Stats.CPUPercent - doc.WriteString(fmt.Sprintf("Usage: %.1f%%\n", cpuPercent)) + doc.WriteString(fmt.Sprintf("使用率: %.1f%%\n", cpuPercent)) // Display progress bar with dynamic color doc.WriteString(renderProgressBar(cpuPercent) + "\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Usage History:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("使用历史:")) doc.WriteString("\n") chart := m.renderCPUChart(50, 10) doc.WriteString(chart) } else { - doc.WriteString(v.MetricLabelStyle.Render("Loading CPU metrics...")) + doc.WriteString(v.MetricLabelStyle.Render("正在加载 CPU 指标...")) } return v.CardStyle.Render(doc.String()) @@ -161,7 +161,7 @@ func (m Model) renderContainerMemory() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Memory Usage")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("内存使用")) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { @@ -169,18 +169,18 @@ func (m Model) renderContainerMemory() string { memUsage := m.Monitor.ContainerDetails.Stats.MemoryUsage memLimit := m.Monitor.ContainerDetails.Stats.MemoryLimit - doc.WriteString(fmt.Sprintf("Usage: %.1f%% | Used: %s | Limit: %s | Available: %s\n\n", memPercent, + doc.WriteString(fmt.Sprintf("使用率: %.1f%% | 已用: %s | 限制: %s | 可用: %s\n\n", memPercent, utils.FormatBytes(memUsage), utils.FormatBytes(memLimit), utils.FormatBytes(memLimit-memUsage))) // Display progress bar with dynamic color doc.WriteString(renderProgressBar(memPercent) + "\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Usage History:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("使用历史:")) doc.WriteString("\n") chart := m.renderMemoryChart(50, 10) doc.WriteString(chart) } else { - doc.WriteString(v.MetricLabelStyle.Render("Loading memory metrics...")) + doc.WriteString(v.MetricLabelStyle.Render("正在加载内存指标...")) } return v.CardStyle.Render(doc.String()) @@ -189,15 +189,15 @@ func (m Model) renderContainerMemory() string { func (m Model) renderContainerNetwork() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Network Usage")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("网络使用")) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { rxBytes := m.Monitor.ContainerDetails.Stats.NetworkRx txBytes := m.Monitor.ContainerDetails.Stats.NetworkTx - doc.WriteString(fmt.Sprintf("RX Total: %s\n", utils.FormatBytes(rxBytes))) - doc.WriteString(fmt.Sprintf("TX Total: %s\n\n", utils.FormatBytes(txBytes))) + doc.WriteString(fmt.Sprintf("接收总量: %s\n", utils.FormatBytes(rxBytes))) + doc.WriteString(fmt.Sprintf("发送总量: %s\n\n", utils.FormatBytes(txBytes))) rxChart := m.renderNetworkRXChart(50, 6) doc.WriteString(rxChart) @@ -206,12 +206,12 @@ func (m Model) renderContainerNetwork() string { doc.WriteString(txChart) if m.Monitor.ContainerDetails.IPAddress != "" { - doc.WriteString("\n\n" + lipgloss.NewStyle().Bold(true).Render("Network Interfaces:")) + doc.WriteString("\n\n" + lipgloss.NewStyle().Bold(true).Render("网络接口:")) doc.WriteString("\n") doc.WriteString(v.MetricValueStyle.Render(" " + m.Monitor.ContainerDetails.IPAddress)) } } else { - doc.WriteString(v.MetricLabelStyle.Render("Loading network metrics...")) + doc.WriteString(v.MetricLabelStyle.Render("正在加载网络指标...")) } return v.CardStyle.Render(doc.String()) @@ -221,15 +221,15 @@ func (m Model) renderContainerNetwork() string { func (m Model) renderContainerDisk() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Disk I/O")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("磁盘 I/O")) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil { readBytes := m.Monitor.ContainerDetails.Stats.BlockRead writeBytes := m.Monitor.ContainerDetails.Stats.BlockWrite - doc.WriteString(fmt.Sprintf("Read: %s\n", utils.FormatBytes(readBytes))) - doc.WriteString(fmt.Sprintf("Write: %s\n\n", utils.FormatBytes(writeBytes))) + doc.WriteString(fmt.Sprintf("读取: %s\n", utils.FormatBytes(readBytes))) + doc.WriteString(fmt.Sprintf("写入: %s\n\n", utils.FormatBytes(writeBytes))) totalIO := readBytes + writeBytes if totalIO > 0 { @@ -246,11 +246,11 @@ func (m Model) renderContainerDisk() string { doc.WriteString(fmt.Sprintf("WRITE [%s] %.1f%%\n", writeBar, writePercent)) } - doc.WriteString("\n" + lipgloss.NewStyle().Bold(true).Render("I/O History:")) + doc.WriteString("\n" + lipgloss.NewStyle().Bold(true).Render("I/O 历史:")) doc.WriteString("\n") - doc.WriteString("Disk I/O history charts coming soon...") + doc.WriteString("磁盘 I/O 历史图表即将推出...") } else { - doc.WriteString(v.MetricLabelStyle.Render("Loading disk metrics...")) + doc.WriteString(v.MetricLabelStyle.Render("正在加载磁盘指标...")) } return v.CardStyle.Render(doc.String()) @@ -261,7 +261,7 @@ func (m Model) renderContainerEnv() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Environment Variables")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("环境变量")) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil && len(m.Monitor.ContainerDetails.Environment) > 0 { @@ -279,9 +279,9 @@ func (m Model) renderContainerEnv() string { } } } else if m.Monitor.ContainerDetails != nil { - doc.WriteString(v.MetricLabelStyle.Render("No environment variables found")) + doc.WriteString(v.MetricLabelStyle.Render("未找到环境变量")) } else { - doc.WriteString(v.MetricLabelStyle.Render("Loading environment variables...")) + doc.WriteString(v.MetricLabelStyle.Render("正在加载环境变量...")) } return v.CardStyle.Render(doc.String()) @@ -289,11 +289,11 @@ func (m Model) renderContainerEnv() string { func (m Model) renderContainerLogs() string { if m.Monitor.SelectedContainer == nil { - return v.CardStyle.Render("No container selected") + return v.CardStyle.Render("未选择容器") } doc := strings.Builder{} - status := "Loading..." + status := "加载中..." if m.Monitor.ContainerLogsStreaming { status = "🟢 Live" } else if !m.Monitor.ContainerLogsLoading { @@ -314,7 +314,7 @@ func (m Model) renderContainerLogs() string { m.Monitor.ContainerLogsPagination.TotalPages))) if m.Monitor.ContainerLogsLoading { - doc.WriteString("\n\n" + v.MetricLabelStyle.Render("Loading logs...")) + doc.WriteString("\n\n" + v.MetricLabelStyle.Render("正在加载日志...")) } else if len(m.Monitor.ContainerLogsPagination.Lines) > 0 { logStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("250")). @@ -326,7 +326,7 @@ func (m Model) renderContainerLogs() string { m.LogsViewport.Style = logStyle doc.WriteString("\n" + m.LogsViewport.View()) } else { - doc.WriteString("\n\n" + v.MetricLabelStyle.Render("No logs available")) + doc.WriteString("\n\n" + v.MetricLabelStyle.Render("暂无日志")) } return v.CardStyle.Render(doc.String()) diff --git a/widgets/render-diagnostic.go b/widgets/render-diagnostic.go index b4cc141..7c18b7e 100644 --- a/widgets/render-diagnostic.go +++ b/widgets/render-diagnostic.go @@ -14,23 +14,23 @@ import ( func (m Model) renderLogEntryDetails() string { if m.Diagnostic.SelectedLogEntry == nil { - return vars.CardStyle.Render("No log entry selected.") + return vars.CardStyle.Render("未选择日志条目。") } entry := m.Diagnostic.SelectedLogEntry var builder strings.Builder // Title - title := lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Log Entry Details") + title := lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("日志条目详情") builder.WriteString(title) builder.WriteString("\n\n") // Details - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("Time"), entry.Timestamp)) - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("Level"), entry.Level)) - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("Service"), entry.Service)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("时间"), entry.Timestamp)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("级别"), entry.Level)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("服务"), entry.Service)) builder.WriteString("\n") - builder.WriteString(vars.MetricLabelStyle.Render("Message:")) + builder.WriteString(vars.MetricLabelStyle.Render("消息:")) builder.WriteString("\n\n") // Message with word wrapping in a bordered box @@ -42,7 +42,7 @@ func (m Model) renderLogEntryDetails() string { builder.WriteString(messageStyle.Render(entry.Message)) builder.WriteString("\n\n") - builder.WriteString(lipgloss.NewStyle().Faint(true).Render("Press 'b', 'esc', or 'enter' to go back.")) + builder.WriteString(lipgloss.NewStyle().Faint(true).Render("按 b、esc 或 enter 返回。")) return vars.CardStyle.Render(builder.String()) } @@ -68,7 +68,7 @@ func (m Model) renderDiagnosticSecurity() string { doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Security Checks")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("安全检查")) doc.WriteString("\n\n") // Authentication section @@ -111,28 +111,28 @@ func (m Model) renderDiagnosticSecurity() string { } if len(m.Diagnostic.SecurityChecks) == 0 { - doc.WriteString("Loading security checks...") + doc.WriteString("正在加载安全检查...") doc.WriteString("\n\n") return vars.CardStyle.Render(doc.String()) } // Domain input section if m.Diagnostic.DomainInputMode { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Enter Domain Name for SSL Check")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("输入域名进行 SSL 检查")) doc.WriteString("\n") doc.WriteString(m.Diagnostic.DomainInput.View()) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("Press Enter to check SSL, Esc to cancel")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render("按 Enter 检查 SSL,按 Esc 取消")) doc.WriteString("\n\n") } else { // Show current domain or prompt to enter one currentDomain := m.Diagnostic.DomainInput.Value() if currentDomain == "" { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("Press 'd' to enter domain name for SSL check")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 d 输入域名进行 SSL 检查")) } else { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Current Domain: ") + currentDomain) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("当前域名: ") + currentDomain) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("Press 'd' to change domain, 'r' to refresh checks")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 d 更改域名,按 r 刷新检查")) } doc.WriteString("\n\n") } @@ -177,28 +177,28 @@ func (m Model) renderDiagnosticSecurity() string { func (m Model) renderCertificateDetails() string { if m.Diagnostic.CertificateInfo == nil { - return vars.CardStyle.Render("No certificate information available") + return vars.CardStyle.Render("暂无证书信息") } cert := m.Diagnostic.CertificateInfo doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSL Certificate Details")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSL 证书详情")) doc.WriteString("\n\n") // Certificate information - doc.WriteString(vars.MetricLabelStyle.Render("Subject: ") + cert.Subject + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Issuer: ") + cert.Issuer + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Serial Number: ") + cert.SerialNumber + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Version: ") + fmt.Sprintf("%d", cert.Version) + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("主题: ") + cert.Subject + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("颁发者: ") + cert.Issuer + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("序列号: ") + cert.SerialNumber + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("版本: ") + fmt.Sprintf("%d", cert.Version) + "\n") doc.WriteString("\n") // Validity period - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Validity Period")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("有效期")) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("Valid From: ") + cert.ValidityPeriodFrom + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Valid To: ") + cert.ValidityPeriodTo + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("生效时间: ") + cert.ValidityPeriodFrom + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("到期时间: ") + cert.ValidityPeriodTo + "\n") // Days until expiry with color coding expiryText := "" @@ -209,14 +209,14 @@ func (m Model) renderCertificateDetails() string { } else { expiryText = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("✓ " + fmt.Sprintf("%d", cert.DaysUntilExpiry) + " days") } - doc.WriteString(vars.MetricLabelStyle.Render("Days Until Expiry: ") + expiryText + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("剩余天数: ") + expiryText + "\n") doc.WriteString("\n") // Security information - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Security Information")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("安全信息")) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("Algorithm: ") + cert.Algorithm + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Signature Algorithm: ") + cert.SignatureAlgorithm + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("算法: ") + cert.Algorithm + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("签名算法: ") + cert.SignatureAlgorithm + "\n") hostnameStatus := "" if cert.HostnameVerified { @@ -224,12 +224,12 @@ func (m Model) renderCertificateDetails() string { } else { hostnameStatus = lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("✗ Not Verified") } - doc.WriteString(vars.MetricLabelStyle.Render("Hostname Verified: ") + hostnameStatus + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("主机名校验: ") + hostnameStatus + "\n") doc.WriteString("\n") // Alternative names if len(cert.AlternativeNames) > 0 { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Alternative Names")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("备用名称")) doc.WriteString("\n") for _, name := range cert.AlternativeNames { doc.WriteString("• " + name + "\n") @@ -242,31 +242,31 @@ func (m Model) renderCertificateDetails() string { func (m Model) renderSSHRootDetails() string { if m.Diagnostic.SSHRootInfo == nil { - return vars.CardStyle.Render("No SSH root login information available") + return vars.CardStyle.Render("暂无 SSH Root 登录信息") } sshInfo := m.Diagnostic.SSHRootInfo doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSH Root Login Details")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSH Root 登录详情")) doc.WriteString("\n\n") - doc.WriteString(vars.MetricLabelStyle.Render("Status: ") + sshInfo.Status + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("Details: ") + sshInfo.Details + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("状态: ") + sshInfo.Status + "\n") + doc.WriteString(vars.MetricLabelStyle.Render("详情: ") + sshInfo.Details + "\n") return vars.CardStyle.Render(doc.String()) } func (m Model) renderOpenedPortsDetails() string { if m.Diagnostic.OpenedPortsInfo == nil { - return vars.CardStyle.Render("No opened ports information available") + return vars.CardStyle.Render("暂无开放端口信息") } doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("Opened Ports Details")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("开放端口详情")) doc.WriteString("\n\n") doc.WriteString(m.Diagnostic.PortsTable.View()) @@ -277,7 +277,7 @@ func (m Model) renderOpenedPortsDetails() string { func (m Model) renderFirewallDetails() string { if m.Diagnostic.FirewallInfo == nil { - return vars.CardStyle.Render("No firewall information available") + return vars.CardStyle.Render("暂无防火墙信息") } doc := strings.Builder{} @@ -295,10 +295,10 @@ func (m Model) renderFirewallDetails() string { statusStyle = statusStyle.Foreground(lipgloss.Color("196")) // Red } - doc.WriteString(vars.MetricLabelStyle.Render("Status: ")) + doc.WriteString(vars.MetricLabelStyle.Render("状态: ")) doc.WriteString(statusStyle.Render(m.Diagnostic.FirewallInfo.Status)) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("Details: ")) + doc.WriteString(vars.MetricLabelStyle.Render("详情: ")) doc.WriteString(m.Diagnostic.FirewallInfo.Details) doc.WriteString("\n\n") @@ -309,13 +309,13 @@ func (m Model) renderFirewallDetails() string { doc.WriteString(m.Diagnostic.FirewallTable.View()) doc.WriteString("\n\n") } else { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("No firewall rules configured")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("未配置防火墙规则")) doc.WriteString("\n\n") } // Raw output section (collapsible view) if m.Diagnostic.FirewallInfo.RawOutput != "" { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Complete Firewall Configuration:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("完整防火墙配置:")) doc.WriteString("\n") doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from firewall command)")) doc.WriteString("\n\n") @@ -336,7 +336,7 @@ func (m Model) renderFirewallDetails() string { func (m Model) renderAutoBanDetails() string { if m.Diagnostic.AutoBanInfo == nil { - return vars.CardStyle.Render("No auto-ban information available") + return vars.CardStyle.Render("暂无自动封禁信息") } doc := strings.Builder{} @@ -354,17 +354,17 @@ func (m Model) renderAutoBanDetails() string { statusStyle = statusStyle.Foreground(lipgloss.Color("196")) // Red } - doc.WriteString(vars.MetricLabelStyle.Render("Status: ")) + doc.WriteString(vars.MetricLabelStyle.Render("状态: ")) doc.WriteString(statusStyle.Render(m.Diagnostic.AutoBanInfo.Status)) doc.WriteString("\n") if m.Diagnostic.AutoBanInfo.Version != "" { - doc.WriteString(vars.MetricLabelStyle.Render("Version: ")) + doc.WriteString(vars.MetricLabelStyle.Render("版本: ")) doc.WriteString(m.Diagnostic.AutoBanInfo.Version) doc.WriteString("\n") } - doc.WriteString(vars.MetricLabelStyle.Render("Details: ")) + doc.WriteString(vars.MetricLabelStyle.Render("详情: ")) doc.WriteString(m.Diagnostic.AutoBanInfo.Details) doc.WriteString("\n\n") @@ -375,13 +375,13 @@ func (m Model) renderAutoBanDetails() string { doc.WriteString(m.Diagnostic.AutoBanTable.View()) doc.WriteString("\n\n") } else { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("No jails/services configured")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("未配置 Jail/服务")) doc.WriteString("\n\n") } // Raw output section if m.Diagnostic.AutoBanInfo.RawOutput != "" { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Complete Configuration:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("完整配置:")) doc.WriteString("\n") doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from service)")) doc.WriteString("\n\n") @@ -404,7 +404,7 @@ func (m Model) renderDiagnosticLogs() string { // Custom time input mode - show popup if m.Diagnostic.CustomTimeInputMode { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Enter Custom Time Range")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("输入自定义时间范围")) doc.WriteString("\n") doc.WriteString(m.Diagnostic.LogTimeRangeInput.View()) doc.WriteString("\n\n") @@ -418,9 +418,9 @@ func (m Model) renderDiagnosticLogs() string { doc.WriteString("\n\n") } - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("Examples: '2 hours ago', '2025-01-08 14:30:00', '3 days ago'")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render("示例:'2 hours ago'、'2025-01-08 14:30:00'、'3 days ago'")) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("Press Enter to apply, ESC to cancel")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render("按 Enter 应用,按 ESC 取消")) return vars.CardStyle.Render(doc.String()) } @@ -435,8 +435,8 @@ func (m Model) renderDiagnosticLogs() string { // Filter selection indicator filterIndicatorStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("214")) - timeIndicator := "Time" - levelIndicator := "Level" + timeIndicator := "时间" + levelIndicator := "级别" if m.Diagnostic.LogFilterSelected == 0 { timeIndicator = filterIndicatorStyle.Render("▶ Time") } else { @@ -478,13 +478,13 @@ func (m Model) renderDiagnosticLogs() string { // Log entries table or loading message if m.Diagnostic.LogsInfo == nil { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("Press 'r' to load logs or Enter to apply filters")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 r 加载日志,或按 Enter 应用筛选")) doc.WriteString("\n") } else if m.Diagnostic.LogsInfo.ErrorMsg != "" { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("Error: " + m.Diagnostic.LogsInfo.ErrorMsg)) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("错误: " + m.Diagnostic.LogsInfo.ErrorMsg)) doc.WriteString("\n") } else if len(m.Diagnostic.LogsInfo.Entries) == 0 { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("No log entries found matching filters")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("没有匹配筛选条件的日志")) doc.WriteString("\n") } else { // Display logs table @@ -557,15 +557,15 @@ func (m Model) renderPerformanceAnalysis() string { func (m Model) renderCPUPerformance() string { if m.Diagnostic.Performance.CPULoading { - return vars.CardStyle.Render("⏳ Loading CPU Performance Metrics...") + return vars.CardStyle.Render("⏳ 正在加载 CPU 性能指标...") } if m.Diagnostic.Performance.CPUMetrics == nil { - return vars.CardStyle.Render("Press 'r' to load CPU metrics") + return vars.CardStyle.Render("按 r 加载 CPU 指标") } // CPU sub-tabs navigation - cpuTabs := []string{"CPU State Breakdown", "Per-Core Performance", "System Activity Metrics"} + cpuTabs := []string{"CPU 状态分解", "每核性能", "系统活动指标"} activeTabStyle := lipgloss.NewStyle().Padding(0, 2). Foreground(lipgloss.Color("240")). @@ -609,11 +609,11 @@ func (m Model) renderMemoryPerformance() string { } if m.Diagnostic.Performance.MemoryMetrics == nil { - return vars.CardStyle.Render("Press 'r' to load memory metrics") + return vars.CardStyle.Render("按 r 加载内存指标") } // Memory sub-tabs navigation - memoryTabs := []string{"Overview", "Usage Breakdown", "Swap Analysis", "System Memory"} + memoryTabs := []string{"概览", "使用分解", "交换分区分析", "系统内存"} activeTabStyle := lipgloss.NewStyle().Padding(0, 2). Foreground(lipgloss.Color("240")). diff --git a/widgets/render-footer.go b/widgets/render-footer.go index 3175ade..fec7729 100644 --- a/widgets/render-footer.go +++ b/widgets/render-footer.go @@ -19,7 +19,7 @@ func (m Model) renderFooter() string { statusLine += statusStyle.Render("⏳ Operation in progress...") + "\n" } else if m.LastOperationMsg != "" { var statusStyle lipgloss.Style - if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") { + if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") || strings.Contains(m.LastOperationMsg, "失败") || strings.Contains(m.LastOperationMsg, "错误") { statusStyle = lipgloss.NewStyle(). Foreground(v.WhiteColor). Background(v.ErrorColor). diff --git a/widgets/render-header.go b/widgets/render-header.go index f554422..5408de6 100644 --- a/widgets/render-header.go +++ b/widgets/render-header.go @@ -40,7 +40,7 @@ func (m Model) renderHeader() string { } doc := strings.Builder{} - systemInfo := fmt.Sprintf("Host: %s | OS: %s | Kernel: %s | Uptime: %s", m.Monitor.System.Hostname, m.Monitor.System.OS, m.Monitor.System.Kernel, utils.FormatUptime(m.Monitor.System.Uptime)) + systemInfo := fmt.Sprintf("主机: %s | 系统: %s | 内核: %s | 运行时间: %s", m.Monitor.System.Hostname, m.Monitor.System.OS, m.Monitor.System.Kernel, utils.FormatUptime(m.Monitor.System.Uptime)) doc.WriteString(v.MetricLabelStyle.Render(systemInfo)) header = append(header, v.CardStyle.MarginBottom(0).Render(doc.String())) diff --git a/widgets/render-monitor.go b/widgets/render-monitor.go index 7c72f8d..268ced6 100644 --- a/widgets/render-monitor.go +++ b/widgets/render-monitor.go @@ -22,31 +22,31 @@ func (m Model) renderMonitor() string { } func (m Model) renderContainers() string { - p := "Search a container..." + p := "搜索容器..." return m.renderTable(m.Monitor.Container, p) } func (m Model) renderProcesses() string { - p := "Search a process..." + p := "搜索进程..." return m.renderTable(m.Monitor.ProcessTable, p) } func (m Model) renderSystem() string { doc := strings.Builder{} - cpuInfo := fmt.Sprintf("CPU: %s %.1f%% | Load: %.2f, %.2f, %.2f", m.Monitor.CpuProgress.View(), m.Monitor.Cpu.Usage, m.Monitor.Cpu.LoadAvg1, m.Monitor.Cpu.LoadAvg5, m.Monitor.Cpu.LoadAvg15) + cpuInfo := fmt.Sprintf("CPU: %s %.1f%% | 负载: %.2f, %.2f, %.2f", m.Monitor.CpuProgress.View(), m.Monitor.Cpu.Usage, m.Monitor.Cpu.LoadAvg1, m.Monitor.Cpu.LoadAvg5, m.Monitor.Cpu.LoadAvg15) doc.WriteString(lipgloss.NewStyle().Bold(true).Render("CPU")) doc.WriteString("\n") doc.WriteString(cpuInfo) doc.WriteString("\n\n") - memInfo := fmt.Sprintf("RAM: %s %.1f%% | Total: %s | Used: %s | Free: %s", m.Monitor.MemProgress.View(), m.Monitor.Memory.Usage, utils.FormatBytes(m.Monitor.Memory.Total), utils.FormatBytes(m.Monitor.Memory.Used), utils.FormatBytes(m.Monitor.Memory.Free)) - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Memory")) + memInfo := fmt.Sprintf("内存: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", m.Monitor.MemProgress.View(), m.Monitor.Memory.Usage, utils.FormatBytes(m.Monitor.Memory.Total), utils.FormatBytes(m.Monitor.Memory.Used), utils.FormatBytes(m.Monitor.Memory.Free)) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("内存")) doc.WriteString("\n") doc.WriteString(memInfo) doc.WriteString("\n") - swapInfo := fmt.Sprintf("SWP: %s %.1f%% | Total: %s | Used: %s | Free: %s", m.Monitor.SwapProgress.View(), m.Monitor.Memory.SwapUsage, utils.FormatBytes(m.Monitor.Memory.SwapTotal), utils.FormatBytes(m.Monitor.Memory.SwapUsed), utils.FormatBytes(m.Monitor.Memory.SwapFree)) + swapInfo := fmt.Sprintf("交换: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", m.Monitor.SwapProgress.View(), m.Monitor.Memory.SwapUsage, utils.FormatBytes(m.Monitor.Memory.SwapTotal), utils.FormatBytes(m.Monitor.Memory.SwapUsed), utils.FormatBytes(m.Monitor.Memory.SwapFree)) doc.WriteString(swapInfo) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Disks")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render("磁盘")) doc.WriteString("\n") for _, disk := range m.Monitor.Disks { if disk.Total > 0 { diff --git a/widgets/render-nav.go b/widgets/render-nav.go index df9a46d..70d8602 100644 --- a/widgets/render-nav.go +++ b/widgets/render-nav.go @@ -26,7 +26,7 @@ func renderNav(header []string, state model.ContainerTab, styleColor lipgloss.St } func (m Model) renderCurrentNav() string { - if strings.HasPrefix(string(m.Ui.State), "monitor") { + if strings.HasPrefix(string(m.Ui.State), "监控") { style := lipgloss.NewStyle().Padding(0, 2). Foreground(v.ClearWhite). Background(v.PurpleCollor). diff --git a/widgets/render-network.go b/widgets/render-network.go index ca44aa3..e3258f5 100644 --- a/widgets/render-network.go +++ b/widgets/render-network.go @@ -15,12 +15,12 @@ func (m Model) interfaces() string { content := strings.Builder{} statusIcon := "🔴" - statusText := "Disconnected" + statusText := "已断开" statusColor := v.ErrorColor if m.Network.NetworkResource.Connected { statusIcon = "🟢" - statusText = "Connected" + statusText = "已连接" statusColor = v.SuccessColor } @@ -30,7 +30,7 @@ func (m Model) interfaces() string { content.WriteString(statusLine) if len(m.Network.NetworkResource.Interfaces) > 0 { - tableContent := m.renderTable(m.Network.NetworkTable, "No network interfaces") + tableContent := m.renderTable(m.Network.NetworkTable, "无网络接口") content.WriteString(tableContent) } @@ -68,7 +68,7 @@ func (m Model) renderProtocolAnalysis() string { content.WriteString("\n\n") content.WriteString(m.Network.AuthMessage) if m.Network.AuthState == model.AuthRequired { - content.WriteString(auth.AuthPromptStyle.Render("Enter Password:")) + content.WriteString(auth.AuthPromptStyle.Render("输入密码:")) content.WriteString("\n") content.WriteString(m.Diagnostic.Password.View()) content.WriteString("\n\n") @@ -107,7 +107,7 @@ func (m Model) renderProtocolAnalysis() string { stats := m.getConnectionStats() statsText := lipgloss.NewStyle(). Foreground(lipgloss.Color("229")). - Render(fmt.Sprintf("TCP: %d | UDP: %d | Listening: %d | Established: %d", + Render(fmt.Sprintf("TCP: %d | UDP: %d | 监听: %d | 已建立: %d", stats.tcp, stats.udp, stats.listening, stats.established)) content.WriteString(statsText + "\n") } @@ -122,10 +122,10 @@ func (m Model) renderProtocolAnalysis() string { content.WriteString(accessInfo + "\n") content.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("244")). - Render("Press 'a' to authenticate for detailed connection information")) + Render("按 a 认证以查看详细连接信息")) } - tableContent := m.renderTable(m.Network.ConnectionsTable, "No active connections") + tableContent := m.renderTable(m.Network.ConnectionsTable, "无活动连接") content.WriteString(tableContent) return v.CardNetworkStyle. @@ -141,12 +141,12 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Foreground(lipgloss.Color("62")). MarginBottom(1). - Render("Network Connectivity Tools") + Render("网络连通性工具") content.WriteString(header + "\n") instructions := lipgloss.NewStyle(). Foreground(lipgloss.Color("240")). - Render("Press 'p' to ping, 't' to traceroute, 's' for speed test, 'c' to clear results") + Render("按 p 进行 Ping,按 t 路由追踪,按 s 测速,按 c 清除结果") content.WriteString(instructions + "\n\n") // Input modes - always visible @@ -189,7 +189,7 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Underline(true). Foreground(lipgloss.Color("39")). - Render("Ping Results") + Render("Ping 结果") resultsContent.WriteString(pingTitle + "\n") for _, result := range m.Network.PingResults { @@ -202,7 +202,7 @@ func (m Model) renderConnectivityAnalysis() string { statusLine := fmt.Sprintf("%s %s - ", statusIcon, result.Target) if result.Success { - statusLine += fmt.Sprintf("Latency: %v, Packet Loss: %.1f%%", + statusLine += fmt.Sprintf("延迟: %v,丢包率: %.1f%%", result.Latency, result.PacketLoss) } else { statusLine += fmt.Sprintf("Error: %s", result.Error) @@ -220,7 +220,7 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Underline(true). Foreground(lipgloss.Color("208")). - Render(fmt.Sprintf("Traceroute to %s", tracerouteResult.Target)) + Render(fmt.Sprintf("路由追踪:%s", tracerouteResult.Target)) resultsContent.WriteString(tracerouteTitle + "\n") if tracerouteResult.Error != "" { @@ -245,7 +245,7 @@ func (m Model) renderConnectivityAnalysis() string { resultsContent.WriteString(hopLine + "\n") } } else { - resultsContent.WriteString("No route found\n") + resultsContent.WriteString("未找到路由\n") } resultsContent.WriteString("\n") } @@ -261,8 +261,8 @@ func (m Model) renderConnectivityAnalysis() string { Render("🚀 Speed Test Results") resultsContent.WriteString(speedTestTitle + "\n") - resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("Server: %s", result.Server)) + "\n") - resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("Test Duration: %v", result.TestDuration)) + "\n\n") + resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("服务器: %s", result.Server)) + "\n") + resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("测试时长: %v", result.TestDuration)) + "\n\n") if result.PingResult != nil { pingSection := lipgloss.NewStyle(). @@ -313,7 +313,7 @@ func (m Model) renderConnectivityAnalysis() string { if totalResultsLines > m.Network.ConnectivityPerPage { paginationInstructions := lipgloss.NewStyle(). Foreground(lipgloss.Color("214")). - Render("Use '↑' and '↓' to navigate results pages") + Render("用 ↑ ↓ 翻页查看结果") content.WriteString(paginationInstructions + "\n\n") } @@ -328,7 +328,7 @@ func (m Model) renderConnectivityAnalysis() string { // Add pagination info if needed if totalPages > 1 { - paginationInfo := fmt.Sprintf("Page %d/%d (Results %d-%d of %d)", + paginationInfo := fmt.Sprintf("第 %d/%d 页(结果 %d-%d / 共 %d)", m.Network.ConnectivityPage+1, totalPages, startIdx+1, endIdx, totalResultsLines) paginationStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("244")). @@ -379,7 +379,7 @@ func (m Model) renderNetworkConfiguration() string { if len(m.Network.Routes) > 0 { content.WriteString(m.Network.RoutesTable.View()) } else { - content.WriteString("No routing table entries found") + content.WriteString("未找到路由表项") } } else { dnsTitle := lipgloss.NewStyle(). @@ -393,7 +393,7 @@ func (m Model) renderNetworkConfiguration() string { if len(m.Network.DNS) > 0 { content.WriteString(m.Network.DNSTable.View()) } else { - content.WriteString("No DNS servers configured") + content.WriteString("未配置 DNS 服务器") } } diff --git a/widgets/render-reporting.go b/widgets/render-reporting.go index 348867c..0743c05 100644 --- a/widgets/render-reporting.go +++ b/widgets/render-reporting.go @@ -26,7 +26,7 @@ func (m Model) renderReporting() string { Foreground(lipgloss.Color("241")). Italic(true) - content.WriteString(instructions.Render("Press 'g' to generate a new report, 'l' to load saved reports")) + content.WriteString(instructions.Render("按 g 生成新报告,按 l 加载已保存报告")) content.WriteString("\n\n") // Report status @@ -59,7 +59,7 @@ func (m Model) renderReportMenu() string { Foreground(lipgloss.Color("39")). Italic(true) - content.WriteString(infoStyle.Render(fmt.Sprintf("Last report generated: %s", + content.WriteString(infoStyle.Render(fmt.Sprintf("上次生成报告:%s", m.Reporting.LastGenerated.Format("2006-01-02 15:04:05")))) content.WriteString("\n\n") } @@ -69,15 +69,15 @@ func (m Model) renderReportMenu() string { Foreground(lipgloss.Color("228")). Bold(true) - content.WriteString(actionStyle.Render("Available Actions:")) + content.WriteString(actionStyle.Render("可用操作:")) content.WriteString("\n\n") actions := []struct { key string description string }{ - {"g", "Generate new system health report"}, - {"l", "Load and view saved reports"}, + {"g", "生成新的系统健康报告"}, + {"l", "加载并查看已保存报告"}, } for _, action := range actions { @@ -97,7 +97,7 @@ func (m Model) renderSavedReports() string { Foreground(lipgloss.Color("228")). Bold(true) - content.WriteString(actionStyle.Render("Saved Reports:")) + content.WriteString(actionStyle.Render("已保存报告:")) content.WriteString("\n\n") m.Reporting.RefreshSavedReports() @@ -122,11 +122,11 @@ func (m Model) renderSavedReports() string { } content.WriteString("\n") - content.WriteString(instructionsStyle().Render("Press ENTER to view selected report, 'd' to delete, 'b' to go back")) + content.WriteString(instructionsStyle().Render("按 Enter 查看选中报告,按 d 删除,按 b 返回")) } else { - content.WriteString(instructionsStyle().Render("No saved reports found. Generate a report first.")) + content.WriteString(instructionsStyle().Render("没有已保存报告,请先生成报告。")) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("Press 'b' to go back")) + content.WriteString(instructionsStyle().Render("按 b 返回")) } return content.String() @@ -143,7 +143,7 @@ func (m Model) renderGeneratingReport() string { content.WriteString(spinnerStyle.Render("🔄 Generating System Health Report...")) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("Please wait while we collect system data...")) + content.WriteString(instructionsStyle().Render("正在采集系统数据,请稍候...")) content.WriteString("\n\n") // Show what's being collected @@ -174,7 +174,7 @@ func (m Model) renderSavingReport() string { content.WriteString(savingStyle.Render("💾 Saving Report...")) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("Saving the current report to file...")) + content.WriteString(instructionsStyle().Render("正在将当前报告保存到文件...")) return content.String() } @@ -196,7 +196,7 @@ func (m Model) renderViewingReport() string { Foreground(lipgloss.Color("228")). Italic(true) - content.WriteString(navStyle.Render("Use arrow keys to scroll, 'q' to return to report menu, 's' to save")) + content.WriteString(navStyle.Render("用方向键滚动,按 q 返回报告菜单,按 s 保存")) content.WriteString("\n\n") // Report content in viewport @@ -208,7 +208,7 @@ func (m Model) renderViewingReport() string { } else { content.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). - Render("No report content available. Generate a report first.")) + Render("暂无报告内容,请先生成报告。")) } return content.String() diff --git a/widgets/utils.go b/widgets/utils.go index 6bcafac..191d222 100644 --- a/widgets/utils.go +++ b/widgets/utils.go @@ -60,10 +60,10 @@ func canRunSudo() bool { // getAdminRequiredChecks returns the list of diagnostic checks that require admin privileges func (m Model) getAdminRequiredChecks() []string { return []string{ - "Open Ports", - "SSH Root Login", - "Firewall Status", - "System Updates", + "开放端口", + "SSH Root 登录", + "防火墙状态", + "系统更新", } } diff --git a/widgets/vars/vars.go b/widgets/vars/vars.go index a8b5450..a6270c3 100644 --- a/widgets/vars/vars.go +++ b/widgets/vars/vars.go @@ -22,30 +22,27 @@ const ( ) var ( - dashboard = []string{"Monitor", "Diagnostic", "Network", "Reporting"} - monitor = []string{"System", "Process", "Containers"} + dashboard = []string{"监控", "诊断", "网络", "报告"} + monitor = []string{"系统", "进程", "容器"} Menu = model.Menu{ DashBoard: dashboard, Monitor: monitor, } - NetworkNav = []string{"Interface", "Connectivity", "Configuration", "Protocol Analysis"} - DiagnosticNav = []string{"Security Checks", "Performances", "Logs"} + NetworkNav = []string{"网卡", "连通性", "配置", "协议分析"} + DiagnosticNav = []string{"安全检查", "性能", "日志"} ContainerMenuItems = []model.ContainerMenuItem{ - {Key: "o", Label: "Open detailed view", Description: "View detailed container information", Action: "open_single"}, - {Key: "l", Label: "View logs", Description: "Show container logs", Action: "logs"}, - {Key: "r", Label: "Restart", Description: "Restart container", Action: "restart"}, - {Key: "d", Label: "Delete", Description: "Remove container", Action: "delete"}, - {Key: "x", Label: "Remove", Description: "Force remove container", Action: "remove"}, - {Key: "s", Label: "Stop/Start", Description: "Toggle container state", Action: "toggle_start"}, - {Key: "p", Label: "Pause/Resume", Description: "Toggle pause state", Action: "toggle_pause"}, - {Key: "e", Label: "Exec shell", Description: "Open interactive shell", Action: "exec"}, - // {Key: "t", Label: "Top/Stats", Description: "View real-time metrics", Action: "stats"}, - // {Key: "i", Label: "Inspect", Description: "Show container configuration", Action: "inspect"}, - // {Key: "c", Label: "Commit", Description: "Create image from container", Action: "commit"}, + {Key: "o", Label: "打开详情", Description: "查看容器详细信息", Action: "open_single"}, + {Key: "l", Label: "查看日志", Description: "显示容器日志", Action: "logs"}, + {Key: "r", Label: "重启", Description: "重启容器", Action: "restart"}, + {Key: "d", Label: "删除", Description: "删除容器", Action: "delete"}, + {Key: "x", Label: "强制删除", Description: "强制移除容器", Action: "remove"}, + {Key: "s", Label: "停止/启动", Description: "切换容器运行状态", Action: "toggle_start"}, + {Key: "p", Label: "暂停/恢复", Description: "切换暂停状态", Action: "toggle_pause"}, + {Key: "e", Label: "进入终端", Description: "打开交互式 Shell", Action: "exec"}, } - ContainerTabs = []string{"General", "CPU", "MEM", "NET", "ENV"} // disk remove + ContainerTabs = []string{"概览", "CPU", "内存", "网络", "环境变量"} // disk remove Spinners = []spinner.Spinner{ spinner.Line, diff --git a/widgets/view.go b/widgets/view.go index d8cabc4..1906b3c 100644 --- a/widgets/view.go +++ b/widgets/view.go @@ -17,7 +17,7 @@ func (m Model) View() string { return lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). Bold(true). - Render("Terminal too small!\nMinimum size: (min: 80x20)\nCurrent: " + + Render("终端窗口太小!\n最小尺寸:80x20\n当前:" + fmt.Sprintf("%dx%d", m.Ui.Width, m.Ui.Height)) } diff --git a/widgets/widgets.go b/widgets/widgets.go index 8c7f508..6853566 100644 --- a/widgets/widgets.go +++ b/widgets/widgets.go @@ -175,14 +175,14 @@ func (m *Model) updateNetworkTable() tea.Cmd { var rows []table.Row for _, iface := range m.Network.NetworkResource.Interfaces { - statusText := "DOWN" + statusText := "断开" if iface.Status == "up" { statusText = "UP" } ips := strings.Join(iface.IPs, ", ") if ips == "" { - ips = "No IP" + ips = "无 IP" } rows = append(rows, table.Row{ From 7ef8c9746f1c17ea66a97ed2cce3e876612bfa24 Mon Sep 17 00:00:00 2001 From: zhangyangrui <740884666@qq.com> Date: Fri, 24 Jul 2026 19:37:21 +0800 Subject: [PATCH 2/3] feat: add English/Chinese UI language switching Introduce an i18n layer with runtime toggle (L / Ctrl+L), persist the choice in ~/.server-pulse/config.json, and keep English as the default source language for upstream compatibility. --- README.md | 81 +++--- i18n/i18n.go | 132 +++++++++ i18n/zh.go | 449 +++++++++++++++++++++++++++++ main.go | 7 +- system/logs/logs.go | 3 +- system/performance/cpu.go | 55 ++-- system/performance/io.go | 21 +- system/performance/memory.go | 85 +++--- system/performance/systemHealth.go | 17 +- system/performance/view.go | 15 +- system/security/SystemRestart.go | 7 +- system/security/SystemUpdates.go | 11 +- system/security/autoBane.go | 13 +- system/security/firewall.go | 21 +- system/security/passwordPolicy.go | 11 +- system/security/ports.go | 17 +- system/security/sshPassword.go | 9 +- system/security/sshRoot.go | 7 +- system/security/ssl.go | 19 +- utils/test/utils_test.go | 22 +- utils/utils.go | 41 +-- widgets/auth/styles.go | 54 ++-- widgets/handles-common.go | 17 +- widgets/handles.go | 74 +++-- widgets/help.go | 192 ++++++------ widgets/help_test.go | 6 +- widgets/init.go | 113 ++++---- widgets/locale.go | 88 ++++++ widgets/model/performance.go | 12 +- widgets/model/reporting.go | 31 +- widgets/model/type.go | 4 +- widgets/model/ui.go | 10 +- widgets/render-body.go | 3 +- widgets/render-charts.go | 31 +- widgets/render-common.go | 5 +- widgets/render-container.go | 71 ++--- widgets/render-diagnostic.go | 125 ++++---- widgets/render-footer.go | 2 +- widgets/render-header.go | 3 +- widgets/render-monitor.go | 15 +- widgets/render-nav.go | 3 +- widgets/render-network.go | 45 +-- widgets/render-reporting.go | 27 +- widgets/utils.go | 9 +- widgets/vars/vars.go | 79 +++-- widgets/view.go | 3 +- widgets/widgets.go | 5 +- 47 files changed, 1416 insertions(+), 654 deletions(-) create mode 100644 i18n/i18n.go create mode 100644 i18n/zh.go create mode 100644 widgets/locale.go diff --git a/README.md b/README.md index 99064a9..e76c826 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,83 @@

server-pulse

-# Server Pulse(中文界面版)🚀 +# Server Pulse 🚀 ![License: GPL-3.0](https://img.shields.io/badge/license-GPL3.0-blue) +![release](https://img.shields.io/github/v/release/Server-Pulse/server-pulse) -本仓库为 [Server-Pulse/server-pulse](https://github.com/Server-Pulse/server-pulse) 的 **Fork**,将 TUI 界面文案本地化为**简体中文**。 +A real-time Terminal User Interface (TUI) for system monitoring, Docker container management, network diagnostics, and security auditing on Linux servers. -实时终端界面(TUI),用于 Linux 服务器监控、Docker 容器管理、网络诊断与安全审计。 +**This fork adds bilingual UI (English / 简体中文) with runtime language switching.**

server-pulse

-## 功能 +## Features -- **系统监控** — CPU、内存、磁盘、网络实时图表 -- **进程管理** — 列表、搜索、排序、结束进程 -- **Docker 管理** — 启停/重启/暂停/删除、日志、进入 Shell、容器资源 -- **安全诊断** — SSL、SSH Root、开放端口、防火墙、Fail2Ban 等 -- **性能分析** — 健康评分、I/O、每核 CPU、内存分析 -- **日志查看** — 按时间、服务、级别筛选 -- **网络诊断** — 网卡、Ping、路由追踪、测速、路由表、DNS、连接 -- **报告** — 生成 / 保存 / 加载系统健康报告(`~/.server-pulse/reports/`) +- **System Monitoring** — Real-time CPU, memory, disk, and network usage with visual graphs +- **Process Management** — List, search, sort, and kill processes +- **Docker Management** — Start, stop, restart, pause, remove containers; view logs (with live streaming); exec into shells; monitor per-container CPU/memory/network stats +- **Security Diagnostics** — SSL certificate checks, SSH root access audit, open port scanning, firewall rules, Fail2Ban status +- **Performance Analysis** — System health scoring, I/O metrics, per-core CPU breakdown, memory analysis +- **Log Viewer** — Filter system logs by time range, service, and log level +- **Network Diagnostics** — Interface stats, ping, traceroute, speed tests, routing table, DNS servers, connection analysis +- **Report Generation** — Generate, save, and load comprehensive system health reports (`~/.server-pulse/reports/`) +- **i18n** — Switch between English and Simplified Chinese at runtime (`L` / `Ctrl+L`); preference saved to `~/.server-pulse/config.json` -## 环境要求 +## Language -- **Linux**(依赖 `/proc`) -- 已安装并运行 **Docker** -- 当前用户在 `docker` 组,或以 root 运行 -- 终端至少 80x20 +| Key | Action | +|-----|--------| +| `L` or `Ctrl+L` | Toggle English ↔ 中文 | -## 从源码安装 +Default language is English. After switching, the choice is persisted. + +## Requirements + +- **Linux** (uses `/proc` filesystem — no macOS or Windows support) +- **Docker** installed and running +- Current user must be in the `docker` group or run as root +- Terminal size of at least 80x20 + +## Installation + +### From source (this fork) ```bash git clone https://github.com/zhangyang-crazy-one/server-pulse.git cd server-pulse -# 或本地路径:~/my_programes/server-pulse-zh +git checkout feat/chinese-ui make build sudo mv server-pulse /usr/local/bin/ ``` -也可直接: +Or: ```bash go build -o server-pulse . sudo install -m 755 server-pulse /usr/local/bin/server-pulse ``` -## 使用 +## Usage ```bash server-pulse ``` -主菜单快捷键: +Main shortcuts: -| 键 | 功能 | -|----|------| -| `1` | 监控 | -| `2` | 诊断 | -| `3` | 网络 | -| `4` | 报告 | -| `?` | 帮助 | -| `q` / `Esc` | 退出 | -| `b` | 返回 | +| Key | Action | +|-----|--------| +| `1`–`4` | Monitor / Diagnostics / Network / Reporting | +| `L` / `Ctrl+L` | Switch language | +| `?` | Help | +| `q` / `Esc` | Quit | +| `b` | Back | -## 与上游关系 +## Upstream - Upstream: https://github.com/Server-Pulse/server-pulse - Fork: https://github.com/zhangyang-crazy-one/server-pulse -- 分支:`feat/chinese-ui`(中文界面改动) - -内部操作键(如 Docker Action:`restart` / `delete` / `logs`)保持英文,以免破坏逻辑;仅界面展示文案为中文。 -## 许可证 +## License -GPL-3.0(与上游一致) +GPL-3.0 (same as upstream) diff --git a/i18n/i18n.go b/i18n/i18n.go new file mode 100644 index 0000000..818adc9 --- /dev/null +++ b/i18n/i18n.go @@ -0,0 +1,132 @@ +package i18n + +import ( + "encoding/json" + "os" + "path/filepath" + "sync" +) + +type Lang string + +const ( + En Lang = "en" + Zh Lang = "zh" +) + +var ( + mu sync.RWMutex + current = En +) + +type configFile struct { + Language string `json:"language"` +} + +func Current() Lang { + mu.RLock() + defer mu.RUnlock() + return current +} + +func SetLang(lang Lang) { + if lang != En && lang != Zh { + lang = En + } + mu.Lock() + current = lang + mu.Unlock() + _ = Save() +} + +func Toggle() Lang { + if Current() == Zh { + SetLang(En) + } else { + SetLang(Zh) + } + return Current() +} + +// T translates an English source string when the active language is Chinese. +// Unknown keys fall back to the English source. +func T(s string) string { + if s == "" { + return s + } + mu.RLock() + lang := current + mu.RUnlock() + if lang != Zh { + return s + } + if v, ok := zh[s]; ok { + return v + } + return s +} + +func Ts(ss ...string) []string { + out := make([]string, len(ss)) + for i, s := range ss { + out[i] = T(s) + } + return out +} + +func configPath() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + dir := filepath.Join(home, ".server-pulse") + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", err + } + return filepath.Join(dir, "config.json"), nil +} + +func Load() { + path, err := configPath() + if err != nil { + return + } + data, err := os.ReadFile(path) + if err != nil { + return + } + var cfg configFile + if err := json.Unmarshal(data, &cfg); err != nil { + return + } + switch Lang(cfg.Language) { + case Zh: + mu.Lock() + current = Zh + mu.Unlock() + case En, "": + mu.Lock() + current = En + mu.Unlock() + } +} + +func Save() error { + path, err := configPath() + if err != nil { + return err + } + cfg := configFile{Language: string(Current())} + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o644) +} + +func Label() string { + if Current() == Zh { + return "中文" + } + return "English" +} diff --git a/i18n/zh.go b/i18n/zh.go new file mode 100644 index 0000000..e44a55a --- /dev/null +++ b/i18n/zh.go @@ -0,0 +1,449 @@ +package i18n + +// Code generated from EN/ZH UI map. DO NOT edit by hand unless needed. + +var zh = map[string]string{ + " %d variables": " %d 个变量", + " No core data available\\n\\n": " 暂无核心数据\\n\\n", + " Overall CPU Usage: %s\\n": " CPU 总体使用率: %s\\n", + " - System has sufficient RAM\\n": " - 系统内存充足\\n", + "%d days, %d hours": "%d 天 %d 小时", + "%d hours, %d minutes": "%d 小时 %d 分钟", + "%d minutes": "%d 分钟", + "0 minutes": "0 分钟", + "1 days, 1 hours": "1 天 1 小时", + "1 hours, 1 minutes": "1 小时 1 分钟", + "2 days, 0 hours": "2 天 0 小时", + "5 minutes": "5 分钟", + "Admin privileges required for: %s\\nEnter password:": "需要管理员权限:%s\\n请输入密码:", + "Algorithm: ": "算法: ", + "Alternative Names": "备用名称", + "Application Memory:": "应用内存:", + "Are you sure?": "确认操作?", + "Authenticating...": "正在认证...", + "Authentication failed": "认证失败", + "Authentication failed: %v": "认证失败:%v", + "Authentication successful!": "认证成功!", + "Auto Ban": "自动封禁", + "Available Actions:": "可用操作:", + "Available:": "可用:", + "Buffers:": "缓冲区:", + "CHECKS PERFORMED": "已执行检查", + "CPU State Breakdown": "CPU 状态分解", + "CPU Steal Time elevated (%.2f%%)": "CPU Steal Time 偏高 (%.2f%%)", + "CPU Steal Time high (%.2f%%)": "CPU Steal Time 过高 (%.2f%%)", + "CPU Usage": "CPU 使用率", + "CPU Usage (%)": "CPU 使用率 (%)", + "CPU: %s %.1f%% | Load: %.2f, %.2f, %.2f": "CPU: %s %.1f%% | 负载: %.2f, %.2f, %.2f", + "Cached:": "缓存:", + "Collecting data...": "正在采集数据...", + "Command": "命令", + "Commit functionality not yet implemented": "提交功能尚未实现", + "Complete Configuration:": "完整配置:", + "Complete Firewall Configuration:": "完整防火墙配置:", + "Configuration": "配置", + "Connected": "已连接", + "Connectivity": "连通性", + "Container '%s' is unhealthy. Check its logs with 'docker logs %s'.": "容器 '%s' 状态不健康。请用 'docker logs %s' 查看日志。", + "Container deleted": "容器已删除", + "Container pause state changed": "容器暂停状态已变更", + "Container paused": "容器已暂停", + "Container restarted": "容器已重启", + "Container resumed": "容器已恢复", + "Container started": "容器已启动", + "Container state changed": "容器状态已变更", + "Container stopped": "容器已停止", + "Container stopped, streaming disabled (status: %s)": "容器已停止,已禁用实时流(状态:%s)", + "Container: %s": "容器: %s", + "Container: %s\\n": "容器: %s\\n", + "Containers": "容器", + "Context Switches": "上下文切换", + "Core %2d": "核心 %2d", + "Current Domain: ": "当前域名: ", + "DIAGNOSTICS": "诊断", + "DOWN": "断开", + "Data currently being written to disk": "正在写入磁盘的数据", + "Days Until Expiry: ": "剩余天数: ", + "Delete": "删除", + "Delete container '%s'?\\nThis action cannot be undone.": "确定删除容器 '%s'?\\n此操作不可撤销。", + "Destination": "目标", + "Details": "详情", + "Details: ": "详情: ", + "Diagnostic": "诊断", + "Dirty Pages:": "脏页:", + "Disconnected": "已断开", + "Disk I/O": "磁盘 I/O", + "Disk I/O history charts coming soon...": "磁盘 I/O 历史图表即将推出...", + "Disks": "磁盘", + "ENV": "环境变量", + "Enter Custom Time Range": "输入自定义时间范围", + "Enter Domain Name for SSL Check": "输入域名进行 SSL 检查", + "Enter Password:": "输入密码:", + "Enter domain name (e.g., google.com)": "输入域名(如 google.com)", + "Enter host or IP to ping (e.g., 8.8.8.8)": "输入要 Ping 的主机或 IP(如 8.8.8.8)", + "Enter host or IP to trace (e.g., google.com)": "输入要追踪的主机或 IP(如 google.com)", + "Enter password for admin access:": "输入管理员密码:", + "Enter root password...": "输入 root 密码...", + "Enter sudo password...": "输入 sudo 密码...", + "Enter target (IP or domain)...": "输入目标(IP 或域名)...", + "Environment Variables": "环境变量", + "Environment:": "环境变量:", + "Erreur lors du chargement des conteneurs: %v\\n": "加载容器时出错: %v\\n", + "Erreur: %v": "错误: %v", + "Error getting the current user: %v": "获取当前用户失败:%v", + "Error getting user's groups: %v": "获取用户组失败:%v", + "Error loading logs: %v": "加载日志出错:%v", + "Error retrieving raw output: %v": "获取原始输出出错:%v", + "Error: ": "错误: ", + "Examples: '2 hours ago', '2025-01-08 14:30:00', '3 days ago'": "示例:'2 hours ago'、'2025-01-08 14:30:00'、'3 days ago'", + "Exec shell": "进入终端", + "Failed to check SSH config: %v": "检查 SSH 配置失败:%v", + "Failed to connect to %v: %v": "连接失败 %v: %v", + "Failed to get SSH config: %v": "获取 SSH 配置失败:%v", + "Failed to get open ports: %v": "获取开放端口失败:%v", + "File system cache (can be freed if needed)": "文件系统缓存(需要时可释放)", + "Filter by service...": "按服务筛选...", + "Firewall Rule": "防火墙规则", + "Firewall Status": "防火墙状态", + "Firewall is not active. Enable it with 'sudo ufw enable'.": "防火墙未启用。可用 'sudo ufw enable' 开启。", + "Flags": "标志", + "Force remove container": "强制移除容器", + "Force remove container '%s'?\\nThis action cannot be undone.": "确定强制删除容器 '%s'?\\n此操作不可撤销。", + "Foreign Address": "远程地址", + "Free:": "空闲:", + "Gateway": "网关", + "General": "概览", + "Generate new system health report": "生成新的系统健康报告", + "Genmask": "子网掩码", + "Health": "健康状态", + "High": "高", + "High number of context switches (%s)": "上下文切换次数过高 (%s)", + "High number of major page faults (%s)": "主缺页次数过高 (%s)", + "Host: %s\t|\tOS: %s\t|\tKernel: %s\t|\tUptime: %s": "主机: %s\t|\t系统: %s\t|\t内核: %s\t|\t运行时间: %s", + "Hostname Verified: ": "主机名校验: ", + "I/O History:": "I/O 历史:", + "I/O buffers for disk operations": "磁盘操作的 I/O 缓冲区", + "ID: %s\\nName: %s\\nImage: %s\\nStatus: %s\\nProject: %s\\nCreated: %s\\nUptime: %s\\nHealth: %s\\nIP Address: %s\\nGateway: %s": "ID: %s\\n名称: %s\\n镜像: %s\\n状态: %s\\n项目: %s\\n创建时间: %s\\n运行时间: %s\\n健康: %s\\nIP 地址: %s\\n网关: %s", + "IOWait": "IO 等待", + "IOWait elevated (%.2f%%)": "IOWait 偏高 (%.2f%%)", + "IOWait very high (%.2f%%)": "IOWait 非常高 (%.2f%%)", + "IP Addresses": "IP 地址", + "IRQ": "硬中断", + "Idle": "空闲", + "Iface": "接口", + "Image": "镜像", + "Installing traceroute...": "正在安装 traceroute...", + "Interface": "网卡", + "Interrupts": "中断", + "Invalid password or insufficient privileges": "密码错误或权限不足", + "Issuer: ": "颁发者: ", + "Jail/Service Details": "Jail/服务详情", + "Kernel data structure cache": "内核数据结构缓存", + "Language": "语言", + "Last Update: %s": "最后更新:%s", + "Last report generated: %s": "上次生成报告:%s", + "Latency: %v, Packet Loss: %.1f%%": "延迟: %v,丢包率: %.1f%%", + "Level": "级别", + "Load Avg: %.2f, %.2f, %.2f": "负载均值:%.2f, %.2f, %.2f", + "Load and view saved reports": "加载并查看已保存报告", + "Loading CPU metrics...": "正在加载 CPU 指标...", + "Loading container details...": "正在加载容器详情...", + "Loading disk metrics...": "正在加载磁盘指标...", + "Loading environment variables...": "正在加载环境变量...", + "Loading logs...": "正在加载日志...", + "Loading memory data...": "正在加载内存数据...", + "Loading memory metrics...": "正在加载内存指标...", + "Loading memory usage data...": "正在加载内存使用数据...", + "Loading network metrics...": "正在加载网络指标...", + "Loading security checks...": "正在加载安全检查...", + "Loading swap data...": "正在加载交换分区数据...", + "Loading system memory data...": "正在加载系统内存数据...", + "Loading...": "加载中...", + "Local Address": "本地地址", + "Log Entry Details": "日志条目详情", + "Logs": "日志", + "Logs loaded": "日志已加载", + "MEM": "内存", + "MEMORY OVERVIEW": "内存概览", + "Medium": "中", + "Mem%": "内存%", + "Memory": "内存", + "Memory Usage": "内存使用", + "Memory Usage (%)": "内存使用率 (%)", + "Memory actively used by applications": "应用程序实际使用的内存", + "Memory available for applications": "应用程序可用内存", + "Memory shared between processes": "进程间共享内存", + "Memory usage very high (%.1f%%). Process '%s' is consuming %.1f%% memory. Consider optimizing or allocating more resources.": "内存使用率很高 (%.1f%%)。进程 '%s' 占用 %.1f%% 内存。建议优化或增加资源。", + "Memory used for page table management": "页表管理占用的内存", + "Message": "消息", + "Message:": "消息:", + "Minimum character classes: %s": "最少字符类别:%s", + "Minimum length: %s": "最小长度:%s", + "Modified data waiting to be written to disk": "等待写入磁盘的已修改数据", + "Monitor": "监控", + "NET": "网络", + "Name": "名称", + "Network": "网络", + "Network ": "网络 ", + "Network Connectivity Tools": "网络连通性工具", + "Network Interfaces:": "网络接口:", + "Network RX": "网络接收", + "Network TX": "网络发送", + "Network Usage": "网络使用", + "No DNS servers configured": "未配置 DNS 服务器", + "No IP": "无 IP", + "No SSH root login information available": "暂无 SSH Root 登录信息", + "No SSL certificate found": "未找到 SSL 证书", + "No active connections": "无活动连接", + "No active firewall detected": "未检测到活动防火墙", + "No active firewall detected (UFW, firewalld, or iptables)": "未检测到活动防火墙(UFW、firewalld 或 iptables)", + "No alternative names": "无备用名称", + "No auto-ban information available": "暂无自动封禁信息", + "No certificate information available": "暂无证书信息", + "No container selected": "未选择容器", + "No container selected.": "未选择容器。", + "No environment variables found": "未找到环境变量", + "No firewall detected": "未检测到防火墙", + "No firewall information available": "暂无防火墙信息", + "No firewall rules configured": "未配置防火墙规则", + "No intrusion prevention service detected": "未检测到入侵防护服务", + "No intrusion prevention service detected (fail2ban, crowdsec, ossec, suricata, snort, denyhosts, sshguard)": "未检测到入侵防护服务(fail2ban、crowdsec、ossec、suricata、snort、denyhosts、sshguard)", + "No jails/services configured": "未配置 Jail/服务", + "No log entries found matching filters": "没有匹配筛选条件的日志", + "No log entry selected.": "未选择日志条目。", + "No logs available": "暂无日志", + "No network interfaces": "无网络接口", + "No open ports detected": "未检测到开放端口", + "No opened ports information available": "暂无开放端口信息", + "No password complexity policy found": "未找到密码复杂度策略", + "No pending system restart required": "无需系统重启", + "No report content available. Generate a report first.": "暂无报告内容,请先生成报告。", + "No route found\\n": "未找到路由\\n", + "No routing table entries found": "未找到路由表项", + "No saved reports found. Generate a report first.": "没有已保存报告,请先生成报告。", + "No swap configured": "未配置交换分区", + "Normal": "正常", + "Not implemented": "尚未实现", + "Open Ports": "开放端口", + "Open detailed view": "打开详情", + "Open interactive shell": "打开交互式 Shell", + "Opened Ports Details": "开放端口详情", + "Operation '%s'": "操作 '%s'", + "Overview": "概览", + "Page %d/%d (Results %d-%d of %d)": "第 %d/%d 页(结果 %d-%d / 共 %d)", + "Page Tables:": "页表:", + "Password Policy": "密码策略", + "Per-Core Performance": "每核性能", + "Performances": "性能", + "Ping Results": "Ping 结果", + "Ping Results\\n": "Ping 结果\\n", + "Please wait while we collect system data...": "正在采集系统数据,请稍候...", + "Port": "端口", + "Ports": "端口", + "Press 'a' to authenticate for detailed connection information": "按 a 认证以查看详细连接信息", + "Press 'a' to retry authentication": "按 a 重试认证", + "Press 'b' to go back": "按 b 返回", + "Press 'b', 'esc', or 'enter' to go back.": "按 b、esc 或 enter 返回。", + "Press 'd' to change domain, 'r' to refresh checks": "按 d 更改域名,按 r 刷新检查", + "Press 'd' to enter domain name for SSL check": "按 d 输入域名进行 SSL 检查", + "Press 'g' to generate a new report, 'l' to load saved reports": "按 g 生成新报告,按 l 加载已保存报告", + "Press 'p' to ping, 't' to traceroute, 's' for speed test, 'c' to clear results": "按 p 进行 Ping,按 t 路由追踪,按 s 测速,按 c 清除结果", + "Press 'r' to load CPU metrics": "按 r 加载 CPU 指标", + "Press 'r' to load logs or Enter to apply filters": "按 r 加载日志,或按 Enter 应用筛选", + "Press 'r' to load memory metrics": "按 r 加载内存指标", + "Press 'r' to load system health metrics.": "按 r 加载系统健康指标。", + "Press 'y' to confirm or 'n' to cancel": "按 y 确认,按 n 取消", + "Press ENTER to view selected report, 'd' to delete, 'b' to go back": "按 Enter 查看选中报告,按 d 删除,按 b 返回", + "Press Enter to apply, ESC to cancel": "按 Enter 应用,按 ESC 取消", + "Press Enter to authenticate, Esc to cancel": "按 Enter 认证,按 Esc 取消", + "Press Enter to check SSL, Esc to cancel": "按 Enter 检查 SSL,按 Esc 取消", + "Press Enter to continue...": "按 Enter 继续...", + "Process": "进程", + "Processes": "进程数", + "Project": "项目", + "Proto": "协议", + "Protocol": "协议", + "Protocol Analysis": "协议分析", + "RAM: %s %.1f%% | Total: %s | Used: %s | Free: %s": "内存: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", + "RX": "接收", + "RX Total: %s\\n": "接收总量: %s\\n", + "Read: %s\\n": "读取: %s\\n", + "Recv-Q": "接收队列", + "Remove": "强制删除", + "Remove container": "删除容器", + "Reporting": "报告", + "Restart": "重启", + "Restart container": "重启容器", + "Restart container '%s'?\\nThis will stop and start the container.": "确定重启容器 '%s'?\\n将先停止再启动该容器。", + "Risky ports are open. Close unnecessary ports to reduce attack surface.": "存在高风险开放端口。请关闭不必要端口以减小攻击面。", + "SSH Password Authentication": "SSH 密码认证", + "SSH Root Login": "SSH Root 登录", + "SSH Root Login Details": "SSH Root 登录详情", + "SSH password authentication is enabled. Consider using SSH keys only for better security.": "已启用 SSH 密码认证。建议仅使用 SSH 密钥以提高安全性。", + "SSL Certificate": "SSL 证书", + "SSL Certificate Details": "SSL 证书详情", + "STATUS": "状态", + "SWAP ANALYSIS": "交换分区分析", + "SWP: %s %.1f%% | Total: %s | Used: %s | Free: %s": "交换: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", + "SYSTEM MEMORY": "系统内存", + "Saved Reports:": "已保存报告:", + "Saving the current report to file...": "正在将当前报告保存到文件...", + "Search a container...": "搜索容器...", + "Search a process...": "搜索进程...", + "Search logs...": "搜索日志...", + "Security Check": "安全检查", + "Security Checks": "安全检查", + "Security Information": "安全信息", + "Send-Q": "发送队列", + "Serial Number: ": "序列号: ", + "Server: %s": "服务器: %s", + "Service": "服务", + "Shared:": "共享:", + "Shell opened": "已打开 Shell", + "Show container logs": "显示容器日志", + "Signature Algorithm: ": "签名算法: ", + "SoftIRQ": "软中断", + "Some checks require admin privileges. Press 'a' to authenticate.": "部分检查需要管理员权限。按 a 进行认证。", + "Speed Test Results\\n": "测速结果\\n", + "Speed test failed: ": "测速失败: ", + "Speed test: %s (%.0f%%)": "测速:%s (%.0f%%)", + "State": "状态", + "Status": "状态", + "Status: ": "状态: ", + "Status: %s\\n": "状态: %s\\n", + "Steal": "被偷用", + "Streaming not available: %v\\nShowing static logs instead...": "实时流不可用:%v\\n改为显示静态日志...", + "Subject: ": "主题: ", + "Sudo is not available. Please run as root.": "Sudo 不可用,请以 root 运行。", + "Swap Analysis": "交换分区分析", + "Swap Cached:": "交换缓存:", + "Swap Free:": "交换空闲:", + "Swap Status:": "交换分区状态:", + "Swap Total:": "交换总量:", + "Swap Used:": "交换已用:", + "Swap memory also present in RAM": "同时存在于内存中的交换数据", + "System": "系统", + "System Activity Metrics": "系统活动指标", + "System Health": "系统健康", + "System Memory": "系统内存", + "System Restart": "系统重启", + "System Restart Required": "系统重启", + "System Updates": "系统更新", + "System restart required for kernel updates. Schedule a maintenance window to reboot the system.": "内核更新需要重启系统。请安排维护窗口重启。", + "System updates are available. Run 'sudo apt update && sudo apt upgrade' to apply security patches.": "系统有可用更新。请运行 'sudo apt update && sudo apt upgrade' 应用安全补丁。", + "TCP: %d | UDP: %d | Listening: %d | Established: %d": "TCP: %d | UDP: %d | 监听: %d | 已建立: %d", + "TX": "发送", + "TX Total: %s\\n\\n": "发送总量: %s\\n\\n", + "Terminal too small!\\nMinimum size: (min: 80x20)\\nCurrent: ": "终端窗口太小!\\n最小尺寸:80x20\\n当前:", + "Test Duration: %v": "测试时长: %v", + "Test Duration: %v\\n\\n": "测试时长: %v\\n\\n", + "Threads": "线程数", + "Time": "时间", + "Toggle container state": "切换容器运行状态", + "Toggle pause state": "切换暂停状态", + "Total Memory:": "内存总量:", + "Traceroute to %s": "路由追踪:%s", + "Traceroute to %s\\n": "路由追踪:%s\\n", + "USAGE BREAKDOWN": "使用分解", + "Unknown state: %v": "未知状态:%v", + "Unsupported operating system: %s": "不支持的操作系统: %s", + "Usage Breakdown": "使用分解", + "Usage History:": "使用历史:", + "Usage: %.1f%% | Used: %s | Limit: %s | Available: %s\\n\\n": "使用率: %.1f%% | 已用: %s | 限制: %s | 可用: %s\\n\\n", + "Usage: %.1f%%\\n": "使用率: %.1f%%\\n", + "Use '↑' and '↓' to navigate results pages": "用 ↑ ↓ 翻页查看结果", + "Use arrow keys to scroll, 'q' to return to report menu, 's' to save": "用方向键滚动,按 q 返回报告菜单,按 s 保存", + "Used:": "已用:", + "User": "用户", + "Valid From: ": "生效时间: ", + "Valid To: ": "到期时间: ", + "Validity Period": "有效期", + "Version: ": "版本: ", + "View detailed container information": "查看容器详细信息", + "View logs": "查看日志", + "Write: %s\\n\\n": "写入: %s\\n\\n", + "WriteBack:": "回写中:", + "activate tab": "激活标签", + "back": "返回", + "clear": "清除", + "containers": "容器", + "details": "详情", + "diagnostics": "诊断", + "e.g., '2 hours ago', '2025-01-08', '3 days ago'": "例如:'2 小时前'、'2025-01-08'、'3 天前'", + "enter CPU sub-tabs": "进入CPU子标签", + "enter Memory sub-tabs": "进入内存子标签", + "generate report": "生成报告", + "kill": "结束进程", + "level": "级别", + "load reports": "加载报告", + "menu": "菜单", + "monitor": "监控", + "navigate": "导航", + "navigate tables/pages": "浏览表格/翻页", + "network": "网络", + "performance": "性能", + "ping": "Ping", + "process": "进程", + "quit": "退出", + "refresh": "刷新", + "reload": "刷新", + "reload data": "刷新数据", + "reporting": "报告", + "save report": "保存报告", + "scroll": "滚动", + "search": "搜索", + "security": "安全", + "select": "选择", + "service": "服务", + "sort by cpu": "按CPU排序", + "sort by mem": "按内存排序", + "switch CPU sub-tabs": "切换CPU子标签", + "switch Memory sub-tabs": "切换内存子标签", + "switch filter": "切换筛选", + "switch language": "切换语言", + "switch performance tabs": "切换性能标签", + "switch tabs": "切换标签", + "switch view": "切换视图", + "system": "系统", + "time range": "时间范围", + "toggle help": "切换帮助", + "toggle stream": "切换实时流", + "trace route": "路由追踪", + "⏳ Loading CPU Performance Metrics...": "⏳ 正在加载 CPU 性能指标...", + "⏳ Loading I/O Performance Metrics...": "⏳ 正在加载 I/O 性能指标...", + "⏳ Loading System Health...": "⏳ 正在加载系统健康...", + "─ I/O PERFORMANCE ANALYSIS ─": "─ I/O 性能分析 ─", + "─ SYSTEM HEALTH ANALYSIS ─": "─ 系统健康分析 ─", + "│ Last Update: %s\\n": "│ 最后更新: %s\\n", + "│ Average Latency: %s ": "│ 平均延迟:%s ", + "│ Health Score: %s%s%s \\n\\n": "│ 健康评分:%s%s%s \\n\\n", + "⚠ Moderate swap usage": "⚠ 交换分区使用率中等", + "⚠ Swap used with available RAM": "⚠ 仍有可用内存却使用了交换分区", + "⚠️ DETECTED ISSUES": "⚠️ 发现问题", + "⚡ CPU OVERVIEW": "⚡ CPU 概览", + "✅ %s successfully": "✅ %s成功", + "✅ Container restarted successfully": "✅ 容器已重启成功", + "✅ Operation '' successfully": "✅ 操作 ''成功", + "✅ Operation 'unknown' successfully": "✅ 操作 'unknown'成功", + "✓ No swap used": "✓ 未使用交换分区", + "✗ High swap usage": "✗ 交换分区使用率过高", + "❌ %s failed": "❌ %s失败", + "❌ %s failed: %v": "❌ %s失败: %v", + "❌ Container restarted failed": "❌ 容器已重启失败", + "❌ Container started failed: permission denied": "❌ 容器已启动失败: permission denied", + "❌ Operation 'unknown' failed": "❌ 操作 'unknown'失败", + "💡 RECOMMENDATIONS": "💡 建议", + "💾 DISK I/O PERFORMANCE": "💾 磁盘 I/O 性能", + "📈 SYSTEM ACTIVITY METRICS": "📈 系统活动指标", + "📊 CPU STATE BREAKDOWN": "📊 CPU 状态分解", + "📊 I/O SUMMARY": "📊 I/O 摘要", + "🔥 PER-CORE PERFORMANCE": "🔥 每核性能", + "🔥 TOP I/O PROCESSES": "🔥 I/O 占用最高进程", + "Language: %s": "语言:%s", + "🔐 Admin Authentication Required": "🔐 需要管理员认证", + "✅ Authentication Successful": "✅ 认证成功", + "❌ Authentication Failed": "❌ 认证失败", + "⏳ Authenticating...": "⏳ 正在认证...", + "✅ Admin access granted": "✅ 已获得管理员权限", + "🔒 Some checks require admin privileges. Press 'a' to authenticate": "🔒 部分检查需要管理员权限。按 a 进行认证", +} diff --git a/main.go b/main.go index 0aaf3d3..426381f 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "log" "os" "time" @@ -18,6 +19,8 @@ var dockerManager *app.DockerManager var currentModel tea.Model func main() { + i18n.Load() + if ok, err := utils.CheckDockerPermissions(); !ok { fmt.Println(err) os.Exit(1) @@ -71,7 +74,7 @@ func runTUI() bool { finalModel, err := p.Run() if err != nil { - fmt.Printf("错误: %v", err) + fmt.Printf(i18n.T("Erreur: %v"), err) os.Exit(1) } @@ -85,7 +88,7 @@ func runTUI() bool { err := dockerManager.ExecInteractiveShellAlternative(shellRequest.ContainerID) if err != nil { fmt.Printf("Shell execution failed: %v\n", err) - fmt.Println("按 Enter 继续...") + fmt.Println(i18n.T("Press Enter to continue...")) bufio.NewScanner(os.Stdin).Scan() } time.Sleep(300 * time.Millisecond) diff --git a/system/logs/logs.go b/system/logs/logs.go index 29e55cf..30416f7 100644 --- a/system/logs/logs.go +++ b/system/logs/logs.go @@ -1,6 +1,7 @@ package logs import ( + "github.com/Server-Pulse/server-pulse/i18n" "encoding/json" "fmt" "os/exec" @@ -60,7 +61,7 @@ func (lm *LogManager) GetSystemLogs(filters LogFilters) tea.Cmd { Entries: []LogEntry{}, TotalCount: 0, Filters: filters, - ErrorMsg: fmt.Sprintf("加载日志出错:%v", err), + ErrorMsg: fmt.Sprintf(i18n.T("Error loading logs: %v"), err), }) } diff --git a/system/performance/cpu.go b/system/performance/cpu.go index 55b4acd..995948b 100644 --- a/system/performance/cpu.go +++ b/system/performance/cpu.go @@ -1,6 +1,7 @@ package performance import ( + "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" "os" @@ -191,7 +192,7 @@ func getProcessCounts() (int, int, error) { } func RenderCPU() string { - return "⏳ 正在加载 CPU 性能指标..." + return i18n.T("⏳ Loading CPU Performance Metrics...") } // RenderCPUOverview renders the CPU overview section (always visible at top) @@ -199,7 +200,7 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { var b strings.Builder // Overall CPU Usage section - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("⚡ CPU 概览")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render(i18n.T("⚡ CPU OVERVIEW"))) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -207,15 +208,15 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { // CPU Usage with visual indicator usageColor := lipgloss.Color("46") // Green by default usageIcon := "●" - usageStatus := "正常" + usageStatus := i18n.T("Normal") if metrics.OverallUsage > 80 { usageColor = lipgloss.Color("196") // Red for high usage usageIcon = "●" - usageStatus = "高" + usageStatus = i18n.T("High") } else if metrics.OverallUsage > 60 { usageColor = lipgloss.Color("214") // Orange for medium usage usageIcon = "●" - usageStatus = "中" + usageStatus = i18n.T("Medium") } usageText := lipgloss.NewStyle().Foreground(usageColor).Bold(true).Render( @@ -228,15 +229,15 @@ func RenderCPUOverview(metrics *model.CPUMetrics) string { prog.Empty = '░' usageBar := prog.ViewAs(metrics.OverallUsage / 100.0) - b.WriteString(fmt.Sprintf(" CPU 总体使用率: %s\n", usageText)) + b.WriteString(fmt.Sprintf(i18n.T(" Overall CPU Usage: %s\n"), usageText)) b.WriteString(fmt.Sprintf(" %s\n\n", usageBar)) // Last update and load averages in columns lastUpdateText := lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render( - fmt.Sprintf("最后更新:%s", metrics.LastUpdate.Format("15:04:05")), + fmt.Sprintf(i18n.T("Last Update: %s"), metrics.LastUpdate.Format("15:04:05")), ) loadAvgText := lipgloss.NewStyle().Foreground(lipgloss.Color("117")).Render( - fmt.Sprintf("负载均值:%.2f, %.2f, %.2f", metrics.LoadAverage[0], metrics.LoadAverage[1], metrics.LoadAverage[2]), + fmt.Sprintf(i18n.T("Load Avg: %.2f, %.2f, %.2f"), metrics.LoadAverage[0], metrics.LoadAverage[1], metrics.LoadAverage[2]), ) b.WriteString(fmt.Sprintf(" %s %s\n", lastUpdateText, loadAvgText)) b.WriteString("\n") @@ -249,7 +250,7 @@ func RenderCPUStateBreakdown(metrics *model.CPUMetrics) string { var b strings.Builder // CPU State Breakdown - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📊 CPU 状态分解")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render(i18n.T("📊 CPU STATE BREAKDOWN"))) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -260,13 +261,13 @@ func RenderCPUStateBreakdown(metrics *model.CPUMetrics) string { icon string desc string }{ - {"用户态", metrics.StateBreakdown.User, "👤", "User space processes"}, - {"内核态", metrics.StateBreakdown.System, "⚙️ ", "Kernel space processes"}, - {"空闲", metrics.StateBreakdown.Idle, "💤", "CPU idle time"}, - {"IO 等待", metrics.StateBreakdown.IOWait, "⏳", "Waiting for I/O operations"}, - {"硬中断", metrics.StateBreakdown.IRQ, "⚡", "Hardware interrupts"}, - {"软中断", metrics.StateBreakdown.SoftIRQ, "📡", "Software interrupts"}, - {"被偷用", metrics.StateBreakdown.Steal, "🔒", "Stolen by hypervisor"}, + {i18n.T("User"), metrics.StateBreakdown.User, "👤", "User space processes"}, + {i18n.T("System"), metrics.StateBreakdown.System, "⚙️ ", "Kernel space processes"}, + {i18n.T("Idle"), metrics.StateBreakdown.Idle, "💤", "CPU idle time"}, + {i18n.T("IOWait"), metrics.StateBreakdown.IOWait, "⏳", "Waiting for I/O operations"}, + {i18n.T("IRQ"), metrics.StateBreakdown.IRQ, "⚡", "Hardware interrupts"}, + {i18n.T("SoftIRQ"), metrics.StateBreakdown.SoftIRQ, "📡", "Software interrupts"}, + {i18n.T("Steal"), metrics.StateBreakdown.Steal, "🔒", "Stolen by hypervisor"}, {"Nice", metrics.StateBreakdown.Nice, "✨", "Nice priority processes"}, } @@ -306,7 +307,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { // Per-Core Performance if len(metrics.Cores) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("🔥 每核性能")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render(i18n.T("🔥 PER-CORE PERFORMANCE"))) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -328,7 +329,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { coreProg1.Empty = '░' bar1 := coreProg1.ViewAs(core1.Usage / 100.0) - coreLabel1 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("核心 %2d", core1.CoreID)) + coreLabel1 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf(i18n.T("Core %2d"), core1.CoreID)) coreValue1 := lipgloss.NewStyle().Width(7).Foreground(coreColor1).Bold(true).Render(fmt.Sprintf("%5.1f%%", core1.Usage)) line := fmt.Sprintf(" %s %s %s", coreLabel1, coreValue1, bar1) @@ -349,7 +350,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { coreProg2.Empty = '░' bar2 := coreProg2.ViewAs(core2.Usage / 100.0) - coreLabel2 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf("核心 %2d", core2.CoreID)) + coreLabel2 := lipgloss.NewStyle().Width(8).Render(fmt.Sprintf(i18n.T("Core %2d"), core2.CoreID)) coreValue2 := lipgloss.NewStyle().Width(7).Foreground(coreColor2).Bold(true).Render(fmt.Sprintf("%5.1f%%", core2.Usage)) line += fmt.Sprintf(" %s %s %s", coreLabel2, coreValue2, bar2) @@ -359,7 +360,7 @@ func RenderCPUPerCore(metrics *model.CPUMetrics) string { } b.WriteString("\n") } else { - b.WriteString(" 暂无核心数据\n\n") + b.WriteString(i18n.T(" No core data available\n\n")) } return b.String() @@ -370,7 +371,7 @@ func RenderCPUSystemActivity(metrics *model.CPUMetrics) string { var b strings.Builder // System Activity Metrics - b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render("📈 系统活动指标")) + b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("51")).Render(i18n.T("📈 SYSTEM ACTIVITY METRICS"))) b.WriteString("\n") b.WriteString(strings.Repeat("─", 70)) b.WriteString("\n\n") @@ -381,10 +382,10 @@ func RenderCPUSystemActivity(metrics *model.CPUMetrics) string { value string icon string }{ - {"上下文切换", formatNumber(metrics.ContextSwitches), "🔄"}, - {"中断", formatNumber(metrics.Interrupts), "⚡"}, - {"进程数", fmt.Sprintf("%d", metrics.ProcessCount), "📋"}, - {"线程数", fmt.Sprintf("%d", metrics.ThreadCount), "🧵"}, + {i18n.T("Context Switches"), formatNumber(metrics.ContextSwitches), "🔄"}, + {i18n.T("Interrupts"), formatNumber(metrics.Interrupts), "⚡"}, + {i18n.T("Processes"), fmt.Sprintf("%d", metrics.ProcessCount), "📋"}, + {i18n.T("Threads"), fmt.Sprintf("%d", metrics.ThreadCount), "🧵"}, } for _, item := range leftCol { @@ -422,11 +423,11 @@ func RenderCPUContent(metrics *model.CPUMetrics) string { func RenderCPUWithData(metrics *model.CPUMetrics, loading bool) string { if loading { - return vars.CardStyle.Render("⏳ 正在加载 CPU 性能指标...") + return vars.CardStyle.Render(i18n.T("⏳ Loading CPU Performance Metrics...")) } if metrics == nil { - return vars.CardStyle.Render("按 r 加载 CPU 指标") + return vars.CardStyle.Render(i18n.T("Press 'r' to load CPU metrics")) } return vars.CardStyle.Render(RenderCPUContent(metrics)) diff --git a/system/performance/io.go b/system/performance/io.go index 4448216..3818053 100644 --- a/system/performance/io.go +++ b/system/performance/io.go @@ -1,6 +1,7 @@ package performance import ( + "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" "os" @@ -199,27 +200,27 @@ func getTopIOProcesses() ([]model.ProcessIOInfo, error) { } func RenderInputOutput() string { - return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") + return vars.CardStyle.Render(i18n.T("⏳ Loading I/O Performance Metrics...")) } func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { if loading { - return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") + return vars.CardStyle.Render(i18n.T("⏳ Loading I/O Performance Metrics...")) } if metrics == nil { - return vars.CardStyle.Render("⏳ 正在加载 I/O 性能指标...") + return vars.CardStyle.Render(i18n.T("⏳ Loading I/O Performance Metrics...")) } var b strings.Builder // Title titleStyle := lipgloss.NewStyle().Bold(true).Foreground(vars.AccentColor).MarginBottom(1) - b.WriteString(titleStyle.Render("─ I/O 性能分析 ─")) + b.WriteString(titleStyle.Render(i18n.T("─ I/O PERFORMANCE ANALYSIS ─"))) b.WriteString("\n") // Summary section - b.WriteString(lipgloss.NewStyle().Bold(true).Render("📊 I/O 摘要")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("📊 I/O SUMMARY"))) b.WriteString("\n\n") // Average latency with color coding @@ -236,13 +237,13 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { latencyText := lipgloss.NewStyle().Foreground(latencyColor).Bold(true).Render( fmt.Sprintf("%s %.2f ms", latencyIcon, metrics.AverageLatency), ) - b.WriteString(fmt.Sprintf("│ 平均延迟:%s ", latencyText)) + b.WriteString(fmt.Sprintf(i18n.T("│ Average Latency: %s "), latencyText)) // Last update lastUpdateText := lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render( metrics.LastUpdate.Format("15:04:05"), ) - b.WriteString(fmt.Sprintf("│ 最后更新: %s\n", lastUpdateText)) + b.WriteString(fmt.Sprintf(i18n.T("│ Last Update: %s\n"), lastUpdateText)) b.WriteString("\n") // Create summary table @@ -287,7 +288,7 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Disk I/O table if len(metrics.Disks) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("💾 磁盘 I/O 性能")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("💾 DISK I/O PERFORMANCE"))) b.WriteString("\n\n") // Create table @@ -341,12 +342,12 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Top I/O Processes if len(metrics.TopProcesses) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("🔥 I/O 占用最高进程")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("🔥 TOP I/O PROCESSES"))) b.WriteString("\n\n") columns := []table.Column{ {Title: "PID", Width: 6}, - {Title: "Command", Width: 20}, + {Title: i18n.T("Command"), Width: 20}, {Title: "Read IOPS", Width: 10}, {Title: "Write IOPS", Width: 11}, {Title: "Read MB", Width: 10}, diff --git a/system/performance/memory.go b/system/performance/memory.go index e763c83..2d3bf32 100644 --- a/system/performance/memory.go +++ b/system/performance/memory.go @@ -1,6 +1,7 @@ package performance import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" "time" @@ -15,7 +16,7 @@ import ( // RenderMemoryOverview renders the always-visible memory overview section func RenderMemoryOverview(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("正在加载内存数据...") + return vars.CardStyle.Render(i18n.T("Loading memory data...")) } titleStyle := lipgloss.NewStyle(). @@ -37,19 +38,19 @@ func RenderMemoryOverview(metrics *model.MemoryMetrics) string { // Create progress bar for memory usage usageBar := createMemoryProgressBar(metrics.UsedPercent, 50) - overview := titleStyle.Render("内存概览") + "\n\n" + overview := titleStyle.Render(i18n.T("MEMORY OVERVIEW")) + "\n\n" - overview += labelStyle.Render("内存总量:") + " " + + overview += labelStyle.Render(i18n.T("Total Memory:")) + " " + valueStyle.Render(formatBytes(metrics.Total)) + "\n" - overview += labelStyle.Render("已用:") + " " + + overview += labelStyle.Render(i18n.T("Used:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Used), metrics.UsedPercent)) + "\n" - overview += labelStyle.Render("可用:") + " " + + overview += labelStyle.Render(i18n.T("Available:")) + " " + valueStyle.Render(formatBytes(metrics.Available)) + "\n" - overview += labelStyle.Render("空闲:") + " " + + overview += labelStyle.Render(i18n.T("Free:")) + " " + valueStyle.Render(formatBytes(metrics.Free)) + "\n\n" overview += progressStyle.Render(usageBar) + "\n" @@ -60,7 +61,7 @@ func RenderMemoryOverview(metrics *model.MemoryMetrics) string { // RenderMemoryUsageBreakdown renders the Usage Breakdown sub-tab func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("正在加载内存使用数据...") + return vars.CardStyle.Render(i18n.T("Loading memory usage data...")) } titleStyle := lipgloss.NewStyle(). @@ -80,42 +81,42 @@ func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { Foreground(lipgloss.Color("241")). Italic(true) - content := titleStyle.Render("使用分解") + "\n\n" + content := titleStyle.Render(i18n.T("USAGE BREAKDOWN")) + "\n\n" // Application Memory appMemPercent := float64(metrics.ApplicationMem) / float64(metrics.Total) * 100 - content += labelStyle.Render("应用内存:") + " " + + content += labelStyle.Render(i18n.T("Application Memory:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.ApplicationMem), appMemPercent)) + "\n" - content += " " + descStyle.Render("应用程序实际使用的内存") + "\n\n" + content += " " + descStyle.Render(i18n.T("Memory actively used by applications")) + "\n\n" // Buffers buffersPercent := float64(metrics.Buffers) / float64(metrics.Total) * 100 - content += labelStyle.Render("缓冲区:") + " " + + content += labelStyle.Render(i18n.T("Buffers:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Buffers), buffersPercent)) + "\n" - content += " " + descStyle.Render("磁盘操作的 I/O 缓冲区") + "\n\n" + content += " " + descStyle.Render(i18n.T("I/O buffers for disk operations")) + "\n\n" // Cached cachedPercent := float64(metrics.Cached) / float64(metrics.Total) * 100 - content += labelStyle.Render("缓存:") + " " + + content += labelStyle.Render(i18n.T("Cached:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Cached), cachedPercent)) + "\n" - content += " " + descStyle.Render("文件系统缓存(需要时可释放)") + "\n\n" + content += " " + descStyle.Render(i18n.T("File system cache (can be freed if needed)")) + "\n\n" // Available availablePercent := float64(metrics.Available) / float64(metrics.Total) * 100 - content += labelStyle.Render("可用:") + " " + + content += labelStyle.Render(i18n.T("Available:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Available), availablePercent)) + "\n" - content += " " + descStyle.Render("应用程序可用内存") + "\n\n" + content += " " + descStyle.Render(i18n.T("Memory available for applications")) + "\n\n" // Shared sharedPercent := float64(metrics.Shared) / float64(metrics.Total) * 100 - content += labelStyle.Render("共享:") + " " + + content += labelStyle.Render(i18n.T("Shared:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Shared), sharedPercent)) + "\n" - content += " " + descStyle.Render("进程间共享内存") + "\n" + content += " " + descStyle.Render(i18n.T("Memory shared between processes")) + "\n" return vars.CardStyle.Render(content) } @@ -123,7 +124,7 @@ func RenderMemoryUsageBreakdown(metrics *model.MemoryMetrics) string { // RenderMemorySwapAnalysis renders the Swap Analysis sub-tab func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("正在加载交换分区数据...") + return vars.CardStyle.Render(i18n.T("Loading swap data...")) } titleStyle := lipgloss.NewStyle(). @@ -142,21 +143,21 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { progressStyle := lipgloss.NewStyle(). Width(50) - content := titleStyle.Render("交换分区分析") + "\n\n" + content := titleStyle.Render(i18n.T("SWAP ANALYSIS")) + "\n\n" // Swap Overview if metrics.SwapTotal == 0 { - content += labelStyle.Render("交换分区状态:") + " " + - lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render("未配置交换分区") + "\n" + content += labelStyle.Render(i18n.T("Swap Status:")) + " " + + lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render(i18n.T("No swap configured")) + "\n" } else { - content += labelStyle.Render("交换总量:") + " " + + content += labelStyle.Render(i18n.T("Swap Total:")) + " " + valueStyle.Render(formatBytes(metrics.SwapTotal)) + "\n" - content += labelStyle.Render("交换已用:") + " " + + content += labelStyle.Render(i18n.T("Swap Used:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.SwapUsed), metrics.SwapUsedPercent)) + "\n" - content += labelStyle.Render("交换空闲:") + " " + + content += labelStyle.Render(i18n.T("Swap Free:")) + " " + valueStyle.Render(formatBytes(metrics.SwapFree)) + "\n\n" // Swap progress bar @@ -164,13 +165,13 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { content += progressStyle.Render(swapBar) + "\n\n" // SwapCached - content += labelStyle.Render("交换缓存:") + " " + + content += labelStyle.Render(i18n.T("Swap Cached:")) + " " + valueStyle.Render(formatBytes(metrics.SwapCached)) + "\n" content += " " + lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Italic(true). - Render("同时存在于内存中的交换数据") + "\n\n" + Render(i18n.T("Swap memory also present in RAM")) + "\n\n" // Health Status - content += titleStyle.Render("状态") + "\n\n" + content += titleStyle.Render(i18n.T("STATUS")) + "\n\n" status := getSwapHealthStatus(metrics) content += status } @@ -181,7 +182,7 @@ func RenderMemorySwapAnalysis(metrics *model.MemoryMetrics) string { // RenderMemorySystemMemory renders the System Memory sub-tab func RenderMemorySystemMemory(metrics *model.MemoryMetrics) string { if metrics == nil { - return vars.CardStyle.Render("正在加载系统内存数据...") + return vars.CardStyle.Render(i18n.T("Loading system memory data...")) } titleStyle := lipgloss.NewStyle(). @@ -201,38 +202,38 @@ func RenderMemorySystemMemory(metrics *model.MemoryMetrics) string { Foreground(lipgloss.Color("241")). Italic(true) - content := titleStyle.Render("系统内存") + "\n\n" + content := titleStyle.Render(i18n.T("SYSTEM MEMORY")) + "\n\n" // Dirty Pages dirtyPercent := float64(metrics.Dirty) / float64(metrics.Total) * 100 - content += labelStyle.Render("脏页:") + " " + + content += labelStyle.Render(i18n.T("Dirty Pages:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Dirty), dirtyPercent)) + "\n" - content += " " + descStyle.Render("等待写入磁盘的已修改数据") + "\n\n" + content += " " + descStyle.Render(i18n.T("Modified data waiting to be written to disk")) + "\n\n" // WriteBack writebackPercent := float64(metrics.WriteBack) / float64(metrics.Total) * 100 - content += labelStyle.Render("回写中:") + " " + + content += labelStyle.Render(i18n.T("WriteBack:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.WriteBack), writebackPercent)) + "\n" - content += " " + descStyle.Render("正在写入磁盘的数据") + "\n\n" + content += " " + descStyle.Render(i18n.T("Data currently being written to disk")) + "\n\n" // Slab slabPercent := float64(metrics.Slab) / float64(metrics.Total) * 100 content += labelStyle.Render("Slab:") + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Slab), slabPercent)) + "\n" - content += " " + descStyle.Render("内核数据结构缓存") + "\n\n" + content += " " + descStyle.Render(i18n.T("Kernel data structure cache")) + "\n\n" // PageTables pageTablesPercent := float64(metrics.PageTables) / float64(metrics.Total) * 100 - content += labelStyle.Render("页表:") + " " + + content += labelStyle.Render(i18n.T("Page Tables:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.PageTables), pageTablesPercent)) + "\n" - content += " " + descStyle.Render("页表管理占用的内存") + "\n\n" + content += " " + descStyle.Render(i18n.T("Memory used for page table management")) + "\n\n" // Diagnostics - content += titleStyle.Render("诊断") + "\n\n" + content += titleStyle.Render(i18n.T("DIAGNOSTICS")) + "\n\n" if metrics.Dirty > 1024*1024*1024 { // > 1GB content += lipgloss.NewStyle().Foreground(lipgloss.Color("220")).Render("⚠ ") + @@ -267,26 +268,26 @@ func getSwapHealthStatus(metrics *model.MemoryMetrics) string { // No swap used - good if metrics.SwapUsed == 0 { - return goodStyle.Render("✓ 未使用交换分区") + " - 系统内存充足\n" + return goodStyle.Render(i18n.T("✓ No swap used")) + i18n.T(" - System has sufficient RAM\n") } // Swap used but memory available - configuration issue availablePercent := float64(metrics.Available) / float64(metrics.Total) * 100 if metrics.SwapUsed > 0 && availablePercent > 20 { - return warningStyle.Render("⚠ 仍有可用内存却使用了交换分区") + + return warningStyle.Render(i18n.T("⚠ Swap used with available RAM")) + " - Check swappiness setting\n" + " Recommendation: Reduce vm.swappiness value\n" } // Swap used and memory low - critical if metrics.SwapUsedPercent > 50 { - return criticalStyle.Render("✗ 交换分区使用率过高") + + return criticalStyle.Render(i18n.T("✗ High swap usage")) + " - System under memory pressure\n" + " Recommendation: Add more RAM or reduce memory usage\n" } // Moderate swap usage - return warningStyle.Render("⚠ 交换分区使用率中等") + + return warningStyle.Render(i18n.T("⚠ Moderate swap usage")) + " - Monitor for performance issues\n" } diff --git a/system/performance/systemHealth.go b/system/performance/systemHealth.go index 4613bd8..ac8d379 100644 --- a/system/performance/systemHealth.go +++ b/system/performance/systemHealth.go @@ -1,6 +1,7 @@ package performance import ( + "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" "os" @@ -115,14 +116,14 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { var checksPerformed []string // IOWait - checksPerformed = append(checksPerformed, "IO 等待") + checksPerformed = append(checksPerformed, i18n.T("IOWait")) if metrics.IOWait > 20 { score -= 30 - issues = append(issues, fmt.Sprintf("IOWait 非常高 (%.2f%%)", metrics.IOWait)) + issues = append(issues, fmt.Sprintf(i18n.T("IOWait very high (%.2f%%)"), metrics.IOWait)) recommendations = append(recommendations, "Investigate disk activity, upgrade to faster storage (SSD/NVMe)") } else if metrics.IOWait > 10 { score -= 15 - issues = append(issues, fmt.Sprintf("IOWait 偏高 (%.2f%%)", metrics.IOWait)) + issues = append(issues, fmt.Sprintf(i18n.T("IOWait elevated (%.2f%%)"), metrics.IOWait)) recommendations = append(recommendations, "Check for processes with high disk I/O, consider upgrading storage") } @@ -130,11 +131,11 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { checksPerformed = append(checksPerformed, "CPU Steal Time") if metrics.StealTime > 10 { score -= 20 - issues = append(issues, fmt.Sprintf("CPU Steal Time 过高 (%.2f%%)", metrics.StealTime)) + issues = append(issues, fmt.Sprintf(i18n.T("CPU Steal Time high (%.2f%%)"), metrics.StealTime)) recommendations = append(recommendations, "Check host machine load, consider migrating VM or upgrading host") } else if metrics.StealTime > 5 { score -= 10 - issues = append(issues, fmt.Sprintf("CPU Steal Time 偏高 (%.2f%%)", metrics.StealTime)) + issues = append(issues, fmt.Sprintf(i18n.T("CPU Steal Time elevated (%.2f%%)"), metrics.StealTime)) recommendations = append(recommendations, "Monitor host machine performance") } @@ -142,15 +143,15 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { checksPerformed = append(checksPerformed, "Major Page Faults") if metrics.MajorFaults > 10000 { score -= 15 - issues = append(issues, fmt.Sprintf("主缺页次数过高 (%s)", formatNumber(metrics.MajorFaults))) + issues = append(issues, fmt.Sprintf(i18n.T("High number of major page faults (%s)"), formatNumber(metrics.MajorFaults))) recommendations = append(recommendations, "Investigate memory usage, consider increasing RAM") } // Context Switches - checksPerformed = append(checksPerformed, "上下文切换") + checksPerformed = append(checksPerformed, i18n.T("Context Switches")) if metrics.ContextSwitches > 100000000 { // 100 million score -= 10 - issues = append(issues, fmt.Sprintf("上下文切换次数过高 (%s)", formatNumber(metrics.ContextSwitches))) + issues = append(issues, fmt.Sprintf(i18n.T("High number of context switches (%s)"), formatNumber(metrics.ContextSwitches))) recommendations = append(recommendations, "Optimize multi-threaded applications, check for excessive context switching") } diff --git a/system/performance/view.go b/system/performance/view.go index 5bf6be3..f6bc9b2 100644 --- a/system/performance/view.go +++ b/system/performance/view.go @@ -1,6 +1,7 @@ package performance import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -11,18 +12,18 @@ import ( func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, healthScore *HealthScore) string { if healthLoading { - return vars.CardStyle.Render("⏳ 正在加载系统健康...") + return vars.CardStyle.Render(i18n.T("⏳ Loading System Health...")) } if healthMetrics == nil || healthScore == nil { - return vars.CardStyle.Render("按 r 加载系统健康指标。") + return vars.CardStyle.Render(i18n.T("Press 'r' to load system health metrics.")) } var b strings.Builder // Title titleStyle := lipgloss.NewStyle().Bold(true).Foreground(vars.AccentColor).MarginBottom(1) - b.WriteString(titleStyle.Render("─ 系统健康分析 ─")) + b.WriteString(titleStyle.Render(i18n.T("─ SYSTEM HEALTH ANALYSIS ─"))) b.WriteString("\n") // Health Score @@ -38,11 +39,11 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he } else if healthScore.Score < 80 { healthStatus = " [Fair]" } - b.WriteString(fmt.Sprintf("│ 健康评分:%s%s%s \n\n", prog.ViewAs(float64(healthScore.Score)/100.0), scoreStr, healthStatus)) + b.WriteString(fmt.Sprintf(i18n.T("│ Health Score: %s%s%s \n\n"), prog.ViewAs(float64(healthScore.Score)/100.0), scoreStr, healthStatus)) // Detected Issues if len(healthScore.Issues) > 0 { - b.WriteString(lipgloss.NewStyle().Foreground(vars.ErrorColor).Render("⚠️ 发现问题")) + b.WriteString(lipgloss.NewStyle().Foreground(vars.ErrorColor).Render(i18n.T("⚠️ DETECTED ISSUES"))) b.WriteString("\n") for _, issue := range healthScore.Issues { b.WriteString(fmt.Sprintf("- %s \n", issue)) @@ -52,7 +53,7 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he // Recommendations if len(healthScore.Recommendations) > 0 { - b.WriteString(lipgloss.NewStyle().Foreground(vars.SuccessColor).Render("💡 建议")) + b.WriteString(lipgloss.NewStyle().Foreground(vars.SuccessColor).Render(i18n.T("💡 RECOMMENDATIONS"))) b.WriteString("\n") for _, rec := range healthScore.Recommendations { b.WriteString(fmt.Sprintf("- %s \n", rec)) @@ -62,7 +63,7 @@ func RenderSystemHealthView(healthLoading bool, healthMetrics *HealthMetrics, he // Checks Performed if len(healthScore.ChecksPerformed) > 0 { - b.WriteString(lipgloss.NewStyle().Bold(true).Render("已执行检查")) + b.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("CHECKS PERFORMED"))) b.WriteString("\n") for _, check := range healthScore.ChecksPerformed { b.WriteString(fmt.Sprintf("- %s \n", check)) diff --git a/system/security/SystemRestart.go b/system/security/SystemRestart.go index 1722e66..9bc2c04 100644 --- a/system/security/SystemRestart.go +++ b/system/security/SystemRestart.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os" "os/exec" @@ -18,7 +19,7 @@ func (sm *SecurityManager) checkSystemRestart() SecurityCheck { for _, check := range checks { if required, reason := check(); required { return SecurityCheck{ - Name: "系统重启", + Name: i18n.T("System Restart"), Status: "Required", Details: reason, } @@ -26,9 +27,9 @@ func (sm *SecurityManager) checkSystemRestart() SecurityCheck { } return SecurityCheck{ - Name: "系统重启", + Name: i18n.T("System Restart"), Status: "Not Required", - Details: "无需系统重启", + Details: i18n.T("No pending system restart required"), } } diff --git a/system/security/SystemUpdates.go b/system/security/SystemUpdates.go index f4690ba..48bf4c6 100644 --- a/system/security/SystemUpdates.go +++ b/system/security/SystemUpdates.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strings" @@ -20,7 +21,7 @@ func (sm *SecurityManager) checkSystemUpdates() SecurityCheck { } return SecurityCheck{ - Name: "系统更新", + Name: i18n.T("System Updates"), Status: "Unknown", Details: "Could not determine update status - package manager not detected", } @@ -104,13 +105,13 @@ func (sm *SecurityManager) handleYumDnfResult(mgr struct { if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 100 { count, _ := mgr.parseFunc(string(output)) return SecurityCheck{ - Name: "系统更新", + Name: i18n.T("System Updates"), Status: "Updates Available", Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), } } else if err == nil { return SecurityCheck{ - Name: "系统更新", + Name: i18n.T("System Updates"), Status: "Up to date", Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), } @@ -129,13 +130,13 @@ func (sm *SecurityManager) handleAptResult(mgr struct { count, _ := mgr.parseFunc(string(output)) if count > 0 { return SecurityCheck{ - Name: "系统更新", + Name: i18n.T("System Updates"), Status: "Updates Available", Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), } } return SecurityCheck{ - Name: "系统更新", + Name: i18n.T("System Updates"), Status: "Up to date", Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), } diff --git a/system/security/autoBane.go b/system/security/autoBane.go index dc9c97d..f00ac22 100644 --- a/system/security/autoBane.go +++ b/system/security/autoBane.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strings" @@ -42,9 +43,9 @@ func (sm *SecurityManager) checkAutoBan() SecurityCheck { } return SecurityCheck{ - Name: "自动封禁", + Name: i18n.T("Auto Ban"), Status: "Disabled", - Details: "未检测到入侵防护服务(fail2ban、crowdsec、ossec、suricata、snort、denyhosts、sshguard)", + Details: i18n.T("No intrusion prevention service detected (fail2ban, crowdsec, ossec, suricata, snort, denyhosts, sshguard)"), } } @@ -62,7 +63,7 @@ func (sm *SecurityManager) checkSystemdServices() SecurityCheck { if status == "active" { details := service.detailsFunc() return SecurityCheck{ - Name: "自动封禁", + Name: i18n.T("Auto Ban"), Status: "Enabled", Details: details, } @@ -73,7 +74,7 @@ func (sm *SecurityManager) checkSystemdServices() SecurityCheck { cmd := exec.Command("systemctl", "is-active", "ossec-hids") if output, err := cmd.Output(); err == nil && strings.TrimSpace(string(output)) == "active" { return SecurityCheck{ - Name: "自动封禁", + Name: i18n.T("Auto Ban"), Status: "Enabled", Details: "ossec-hids is active with real-time monitoring", } @@ -201,7 +202,7 @@ func (sm *SecurityManager) checkRunningProcesses() SecurityCheck { if len(activeServices) > 0 { return SecurityCheck{ - Name: "自动封禁", + Name: i18n.T("Auto Ban"), Status: "Enabled", Details: fmt.Sprintf("Active intrusion prevention: %s", strings.Join(activeServices, ", ")), } @@ -249,7 +250,7 @@ func (sm *SecurityManager) DisplayAutoBanInfos() tea.Cmd { return AutoBanMsg(AutoBanInfos{ ServiceType: "None", Status: "Disabled", - Details: "未检测到入侵防护服务", + Details: i18n.T("No intrusion prevention service detected"), Jails: []AutoBanJail{}, BannedIPs: []string{}, RawOutput: "", diff --git a/system/security/firewall.go b/system/security/firewall.go index 7cb599c..68a4428 100644 --- a/system/security/firewall.go +++ b/system/security/firewall.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strings" @@ -96,13 +97,13 @@ func (sm *SecurityManager) analyzeFirewallOutput(firewallName string, output []b func (sm *SecurityManager) analyzeUFWStatus(outputStr string) *SecurityCheck { if strings.Contains(outputStr, "status: active") { return &SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Active", Details: "UFW is active and properly configured", } } else if strings.Contains(outputStr, "status: inactive") { return &SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Inactive", Details: "UFW is installed but inactive", } @@ -113,7 +114,7 @@ func (sm *SecurityManager) analyzeUFWStatus(outputStr string) *SecurityCheck { func (sm *SecurityManager) analyzeFirewalldStatus(outputStr string) *SecurityCheck { if strings.Contains(outputStr, "running") { return &SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Active", Details: "firewalld is active and properly configured", } @@ -124,7 +125,7 @@ func (sm *SecurityManager) analyzeFirewalldStatus(outputStr string) *SecurityChe func (sm *SecurityManager) analyzeIptablesStatus(output []byte) *SecurityCheck { if len(strings.Split(string(output), "\n")) > 10 { return &SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Active", Details: "iptables rules are configured", } @@ -135,7 +136,7 @@ func (sm *SecurityManager) analyzeIptablesStatus(output []byte) *SecurityCheck { func (sm *SecurityManager) analyzeNftablesStatus(output []byte) *SecurityCheck { if len(strings.Split(string(output), "\n")) > 10 { return &SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Active", Details: "nftables rules are configured", } @@ -145,9 +146,9 @@ func (sm *SecurityManager) analyzeNftablesStatus(output []byte) *SecurityCheck { func (sm *SecurityManager) createInactiveFirewallCheck() SecurityCheck { return SecurityCheck{ - Name: "防火墙状态", + Name: i18n.T("Firewall Status"), Status: "Inactive", - Details: "未检测到活动防火墙(UFW、firewalld 或 iptables)", + Details: i18n.T("No active firewall detected (UFW, firewalld, or iptables)"), } } @@ -186,7 +187,7 @@ func (sm *SecurityManager) DisplayFirewallInfos() tea.Cmd { FirewallType: "None", Status: "Inactive", Rules: []FirewallRule{}, - Details: "未检测到活动防火墙", + Details: i18n.T("No active firewall detected"), RawOutput: "", }) } @@ -234,12 +235,12 @@ func (sm *SecurityManager) getRawFirewallOutput(firewallType string) string { cmd = exec.Command("nft", "list", "ruleset") } default: - return "未检测到防火墙" + return i18n.T("No firewall detected") } output, err := cmd.Output() if err != nil { - return fmt.Sprintf("获取原始输出出错:%v", err) + return fmt.Sprintf(i18n.T("Error retrieving raw output: %v"), err) } return string(output) diff --git a/system/security/passwordPolicy.go b/system/security/passwordPolicy.go index d1c43a7..b33fe7b 100644 --- a/system/security/passwordPolicy.go +++ b/system/security/passwordPolicy.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os" "regexp" @@ -29,14 +30,14 @@ func (sm *SecurityManager) checkPasswordPolicy() SecurityCheck { if strings.Contains(contentStr, "minlen") { re := regexp.MustCompile(`minlen=(\d+)`) if matches := re.FindStringSubmatch(contentStr); len(matches) > 1 { - details = append(details, fmt.Sprintf("最小长度:%s", matches[1])) + details = append(details, fmt.Sprintf(i18n.T("Minimum length: %s"), matches[1])) } } if strings.Contains(contentStr, "minclass") { re := regexp.MustCompile(`minclass=(\d+)`) if matches := re.FindStringSubmatch(contentStr); len(matches) > 1 { - details = append(details, fmt.Sprintf("最少字符类别:%s", matches[1])) + details = append(details, fmt.Sprintf(i18n.T("Minimum character classes: %s"), matches[1])) } } } @@ -45,15 +46,15 @@ func (sm *SecurityManager) checkPasswordPolicy() SecurityCheck { if hasPolicy { return SecurityCheck{ - Name: "密码策略", + Name: i18n.T("Password Policy"), Status: "Enabled", Details: strings.Join(details, ", "), } } return SecurityCheck{ - Name: "密码策略", + Name: i18n.T("Password Policy"), Status: "Disabled", - Details: "未找到密码复杂度策略", + Details: i18n.T("No password complexity policy found"), } } diff --git a/system/security/ports.go b/system/security/ports.go index 1ab8c76..b40979d 100644 --- a/system/security/ports.go +++ b/system/security/ports.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strconv" @@ -89,17 +90,17 @@ func (sm *SecurityManager) checkOpenPorts() SecurityCheck { openPorts, err := o.GetOpenedPorts(sm) if err != nil { return SecurityCheck{ - Name: "开放端口", + Name: i18n.T("Open Ports"), Status: "Error", - Details: fmt.Sprintf("获取开放端口失败:%v", err), + Details: fmt.Sprintf(i18n.T("Failed to get open ports: %v"), err), } } if len(openPorts) == 0 { return SecurityCheck{ - Name: "开放端口", + Name: i18n.T("Open Ports"), Status: "Secure", - Details: "未检测到开放端口", + Details: i18n.T("No open ports detected"), } } @@ -151,20 +152,20 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort switch maxRisk { case HighRisk: return SecurityCheck{ - Name: "开放端口", + Name: i18n.T("Open Ports"), Status: "High Risk", Details: fmt.Sprintf("Critical ports detected (%s): %s", strings.Join(riskPorts, ", "), strings.Join(findings, " | ")), } case Warning: return SecurityCheck{ - Name: "开放端口", + Name: i18n.T("Open Ports"), Status: "Warning", Details: strings.Join(findings, " | "), } default: return SecurityCheck{ - Name: "开放端口", + Name: i18n.T("Open Ports"), Status: "Secure", Details: fmt.Sprintf("%d ports open: %s", len(allPorts), ShowOpenedPorts(allPorts)), } @@ -173,7 +174,7 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort func ShowOpenedPorts(ports map[int]bool) string { if len(ports) == 0 { - return "未检测到开放端口" + return i18n.T("No open ports detected") } var portList []string diff --git a/system/security/sshPassword.go b/system/security/sshPassword.go index 62474c5..e28b664 100644 --- a/system/security/sshPassword.go +++ b/system/security/sshPassword.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strings" @@ -11,9 +12,9 @@ func (sm *SecurityManager) checkSSHPasswordAuthentication() SecurityCheck { output, err := cmd.Output() if err != nil { return SecurityCheck{ - Name: "SSH 密码认证", + Name: i18n.T("SSH Password Authentication"), Status: "Error", - Details: fmt.Sprintf("检查 SSH 配置失败:%v", err), + Details: fmt.Sprintf(i18n.T("Failed to check SSH config: %v"), err), } } @@ -36,14 +37,14 @@ func (sm *SecurityManager) checkSSHPasswordAuthentication() SecurityCheck { if passwordAuth == "yes" { return SecurityCheck{ - Name: "SSH 密码认证", + Name: i18n.T("SSH Password Authentication"), Status: "Enabled", Details: "SSH password authentication is enabled - consider disabling for better security", } } return SecurityCheck{ - Name: "SSH 密码认证", + Name: i18n.T("SSH Password Authentication"), Status: "Disabled", Details: "SSH password authentication is disabled", } diff --git a/system/security/sshRoot.go b/system/security/sshRoot.go index 7c09cf9..f591781 100644 --- a/system/security/sshRoot.go +++ b/system/security/sshRoot.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os/exec" "strings" @@ -72,9 +73,9 @@ func (sm *SecurityManager) checkSSHRootLogin() SecurityCheck { config, err := s.GetActiveConfig(sm) if err != nil { return SecurityCheck{ - Name: "SSH Root 登录", + Name: i18n.T("SSH Root Login"), Status: "Error", - Details: fmt.Sprintf("获取 SSH 配置失败:%v", err), + Details: fmt.Sprintf(i18n.T("Failed to get SSH config: %v"), err), } } @@ -101,7 +102,7 @@ func (sm *SecurityManager) checkSSHRootLogin() SecurityCheck { } return SecurityCheck{ - Name: "SSH Root 登录", + Name: i18n.T("SSH Root Login"), Status: status, Details: details, } diff --git a/system/security/ssl.go b/system/security/ssl.go index f878720..dff69bd 100644 --- a/system/security/ssl.go +++ b/system/security/ssl.go @@ -1,6 +1,7 @@ package security import ( + "github.com/Server-Pulse/server-pulse/i18n" "crypto/tls" "crypto/x509" "fmt" @@ -36,7 +37,7 @@ type CertificateDisplayMsg CertificateInfos func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if domaine == "" { return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Error", Details: "Domain name is required", } @@ -49,9 +50,9 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { }) if err != nil { return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Error", - Details: fmt.Sprintf("连接失败 %v: %v", domaine, err), + Details: fmt.Sprintf(i18n.T("Failed to connect to %v: %v"), domaine, err), } } defer conn.Close() @@ -61,9 +62,9 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if len(state.PeerCertificates) == 0 { return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Invalid", - Details: "未找到 SSL 证书", + Details: i18n.T("No SSL certificate found"), } } @@ -73,7 +74,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if cert.NotAfter.Before(now) { return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Invalid", Details: "Certificate has expired", } @@ -82,7 +83,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { if cert.NotAfter.Before(now.Add(30 * 24 * time.Hour)) { daysUntilExpiration := cert.NotAfter.Sub(now).Hours() / 24 return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Warning", Details: fmt.Sprintf("Certificate expires soon in %.0f days", daysUntilExpiration), } @@ -90,7 +91,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { daysUntilExpiration := time.Until(cert.NotAfter).Hours() / 24 return SecurityCheck{ - Name: "SSL 证书", + Name: i18n.T("SSL Certificate"), Status: "Valid", Details: fmt.Sprintf("Certificate expires in %.0f days", daysUntilExpiration), } @@ -104,7 +105,7 @@ func DisplayCertificateInfo(cert *x509.Certificate, hostname string) Certificate otherNames := cert.DNSNames if len(otherNames) == 0 { - otherNames = []string{"无备用名称"} + otherNames = []string{i18n.T("No alternative names")} } return CertificateInfos{ Subject: cert.Subject.CommonName, diff --git a/utils/test/utils_test.go b/utils/test/utils_test.go index 80ce8d7..c311343 100644 --- a/utils/test/utils_test.go +++ b/utils/test/utils_test.go @@ -146,11 +146,11 @@ func TestFormatUptime(t *testing.T) { input uint64 expected string }{ - {"Minutes only", 300, "5 分钟"}, - {"Hours and minutes", 3660, "1 小时 1 分钟"}, - {"Days and hours", 90000, "1 天 1 小时"}, - {"Multiple days", 172800, "2 天 0 小时"}, - {"Zero seconds", 0, "0 分钟"}, + {"Minutes only", 300, "5 minutes"}, + {"Hours and minutes", 3660, "1 hours, 1 minutes"}, + {"Days and hours", 90000, "1 days, 1 hours"}, + {"Multiple days", 172800, "2 days, 0 hours"}, + {"Zero seconds", 0, "0 minutes"}, } for _, tt := range tests { @@ -264,12 +264,12 @@ func TestFormatOperationMessage(t *testing.T) { err error expected string }{ - {"Success restart", "restart", true, nil, "✅ 容器已重启成功"}, - {"Failure restart", "restart", false, nil, "❌ 容器已重启失败"}, - {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ 容器已启动失败: permission denied"}, - {"Unknown operation success", "unknown", true, nil, "✅ 操作 'unknown'成功"}, - {"Unknown operation failure", "unknown", false, nil, "❌ 操作 'unknown'失败"}, - {"Empty operation", "", true, nil, "✅ 操作 ''成功"}, + {"Success restart", "restart", true, nil, "✅ Container restarted successfully"}, + {"Failure restart", "restart", false, nil, "❌ Container restarted failed"}, + {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ Container started failed: permission denied"}, + {"Unknown operation success", "unknown", true, nil, "✅ Operation 'unknown' successfully"}, + {"Unknown operation failure", "unknown", false, nil, "❌ Operation 'unknown' failed"}, + {"Empty operation", "", true, nil, "✅ Operation '' successfully"}, } for _, tt := range tests { diff --git a/utils/utils.go b/utils/utils.go index a46b2aa..48afa22 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "github.com/Server-Pulse/server-pulse/i18n" "bufio" "encoding/binary" "fmt" @@ -81,11 +82,11 @@ func FormatUptime(seconds uint64) string { minutes := (seconds % (60 * 60)) / 60 if days > 0 { - return fmt.Sprintf("%d 天 %d 小时", days, hours) + return fmt.Sprintf(i18n.T("%d days, %d hours"), days, hours) } else if hours > 0 { - return fmt.Sprintf("%d 小时 %d 分钟", hours, minutes) + return fmt.Sprintf(i18n.T("%d hours, %d minutes"), hours, minutes) } else { - return fmt.Sprintf("%d 分钟", minutes) + return fmt.Sprintf(i18n.T("%d minutes"), minutes) } } @@ -146,12 +147,12 @@ func CheckDockerPermissions() (bool, string) { case "linux": // Linux currentUser, err := user.Current() if err != nil { - return false, fmt.Sprintf("获取当前用户失败:%v", err) + return false, fmt.Sprintf(i18n.T("Error getting the current user: %v"), err) } groups, err := currentUser.GroupIds() if err != nil { - return false, fmt.Sprintf("获取用户组失败:%v", err) + return false, fmt.Sprintf(i18n.T("Error getting user's groups: %v"), err) } for _, groupID := range groups { @@ -167,36 +168,36 @@ func CheckDockerPermissions() (bool, string) { return false, "The user is not in the 'docker' group. To add them, run 'sudo usermod -aG docker ' and then log out and log back in." default: - return false, fmt.Sprintf("不支持的操作系统: %s", runtime.GOOS) + return false, fmt.Sprintf(i18n.T("Unsupported operating system: %s"), runtime.GOOS) } } func FormatOperationMessage(operation string, success bool, err error) string { operationLabels := map[string]string{ - "restart": "容器已重启", - "start": "容器已启动", - "stop": "容器已停止", - "pause": "容器已暂停", - "unpause": "容器已恢复", - "delete": "容器已删除", - "toggle_start": "容器状态已变更", - "toggle_pause": "容器暂停状态已变更", - "exec": "已打开 Shell", - "logs": "日志已加载", + "restart": i18n.T("Container restarted"), + "start": i18n.T("Container started"), + "stop": i18n.T("Container stopped"), + "pause": i18n.T("Container paused"), + "unpause": i18n.T("Container resumed"), + "delete": i18n.T("Container deleted"), + "toggle_start": i18n.T("Container state changed"), + "toggle_pause": i18n.T("Container pause state changed"), + "exec": i18n.T("Shell opened"), + "logs": i18n.T("Logs loaded"), } label, exists := operationLabels[operation] if !exists { - label = fmt.Sprintf("操作 '%s'", operation) + label = fmt.Sprintf(i18n.T("Operation '%s'"), operation) } if success { - return fmt.Sprintf("✅ %s成功", label) + return fmt.Sprintf(i18n.T("✅ %s successfully"), label) } else { if err != nil { - return fmt.Sprintf("❌ %s失败: %v", label, err) + return fmt.Sprintf(i18n.T("❌ %s failed: %v"), label, err) } - return fmt.Sprintf("❌ %s失败", label) + return fmt.Sprintf(i18n.T("❌ %s failed"), label) } } diff --git a/widgets/auth/styles.go b/widgets/auth/styles.go index a0c0394..8318675 100644 --- a/widgets/auth/styles.go +++ b/widgets/auth/styles.go @@ -1,6 +1,7 @@ package auth import ( + "github.com/Server-Pulse/server-pulse/i18n" "github.com/charmbracelet/lipgloss" ) @@ -33,33 +34,24 @@ var ( Foreground(lipgloss.Color("214")) // Orange ) -const ( - AuthRequiredMessage = "🔐 Admin Authentication Required" - - AuthSuccessMessage = "✅ Authentication Successful" - - AuthFailedMessage = "❌ Authentication Failed" - - AuthInProgressMessage = "⏳ Authenticating..." - - AuthPromptMessage = "输入管理员密码:" - - AuthInstructions = "按 Enter 认证,按 Esc 取消" - - AuthRetryMessage = "按 a 重试认证" - - AdminAccessGranted = "✅ Admin access granted" - - AdminAccessRequired = "🔒 Some checks require admin privileges. Press 'a' to authenticate" - - SudoNotAvailable = "Sudo 不可用,请以 root 运行。" - - InvalidPasswordMessage = "密码错误或权限不足" - - AuthenticationFailedGeneric = "认证失败" - - LockedCheckIndicator = "🔒" -) +const LockedCheckIndicator = "🔒" + +func AuthRequiredMessage() string { return i18n.T("🔐 Admin Authentication Required") } +func AuthSuccessMessage() string { return i18n.T("✅ Authentication Successful") } +func AuthFailedMessage() string { return i18n.T("❌ Authentication Failed") } +func AuthInProgressMessage() string { return i18n.T("⏳ Authenticating...") } +func AuthPromptMessage() string { return i18n.T("Enter password for admin access:") } +func AuthInstructions() string { return i18n.T("Press Enter to authenticate, Esc to cancel") } +func AuthRetryMessage() string { return i18n.T("Press 'a' to retry authentication") } +func AdminAccessGranted() string { return i18n.T("✅ Admin access granted") } +func AdminAccessRequired() string { + return i18n.T("🔒 Some checks require admin privileges. Press 'a' to authenticate") +} +func SudoNotAvailable() string { return i18n.T("Sudo is not available. Please run as root.") } +func InvalidPasswordMessage() string { + return i18n.T("Invalid password or insufficient privileges") +} +func AuthenticationFailedGeneric() string { return i18n.T("Authentication failed") } func GetAuthMessage(state int, customMessage string) string { switch state { @@ -67,16 +59,16 @@ func GetAuthMessage(state int, customMessage string) string { if customMessage != "" { return customMessage } - return AuthRequiredMessage + return AuthRequiredMessage() case 2: // AuthInProgress - return AuthInProgressMessage + return AuthInProgressMessage() case 3: // AuthSuccess - return AuthSuccessMessage + return AuthSuccessMessage() case 4: // AuthFailed if customMessage != "" { return customMessage } - return AuthFailedMessage + return AuthFailedMessage() default: return "" } diff --git a/widgets/handles-common.go b/widgets/handles-common.go index 0ea0239..03cfed3 100644 --- a/widgets/handles-common.go +++ b/widgets/handles-common.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "math" "strings" @@ -107,7 +108,7 @@ func (m *Model) setContainerLogs(logs string) { func (m *Model) updateLogsViewport() { if len(m.Monitor.ContainerLogsPagination.Lines) == 0 { - m.LogsViewport.SetContent("暂无日志") + m.LogsViewport.SetContent(i18n.T("No logs available")) return } @@ -178,7 +179,7 @@ func calculateConnectivityContentLines(m Model) int { tempContent := strings.Builder{} if len(m.Network.PingResults) > 0 { - tempContent.WriteString("Ping 结果\n") + tempContent.WriteString(i18n.T("Ping Results\n")) for _, result := range m.Network.PingResults { statusIcon := "❌" if result.Success { @@ -186,7 +187,7 @@ func calculateConnectivityContentLines(m Model) int { } tempContent.WriteString(fmt.Sprintf("%s %s - ", statusIcon, result.Target)) if result.Success { - tempContent.WriteString(fmt.Sprintf("延迟: %v,丢包率: %.1f%%", result.Latency, result.PacketLoss)) + tempContent.WriteString(fmt.Sprintf(i18n.T("Latency: %v, Packet Loss: %.1f%%"), result.Latency, result.PacketLoss)) } else { tempContent.WriteString(fmt.Sprintf("Error: %s", result.Error)) } @@ -197,9 +198,9 @@ func calculateConnectivityContentLines(m Model) int { for _, tracerouteResult := range m.Network.TracerouteResults { if tracerouteResult.Target != "" { - tempContent.WriteString(fmt.Sprintf("路由追踪:%s\n", tracerouteResult.Target)) + tempContent.WriteString(fmt.Sprintf(i18n.T("Traceroute to %s\n"), tracerouteResult.Target)) if tracerouteResult.Error != "" { - tempContent.WriteString("Error: " + tracerouteResult.Error + "\n") + tempContent.WriteString(i18n.T("Error: ") + tracerouteResult.Error + "\n") } else if len(tracerouteResult.Hops) > 0 { for _, hop := range tracerouteResult.Hops { hopLine := fmt.Sprintf("%2d. ", hop.HopNumber) @@ -217,7 +218,7 @@ func calculateConnectivityContentLines(m Model) int { tempContent.WriteString(hopLine + "\n") } } else { - tempContent.WriteString("未找到路由\n") + tempContent.WriteString(i18n.T("No route found\n")) } tempContent.WriteString("\n") } @@ -225,9 +226,9 @@ func calculateConnectivityContentLines(m Model) int { // Speed test results for _, result := range m.Network.SpeedTestResults { - tempContent.WriteString("测速结果\n") + tempContent.WriteString(i18n.T("Speed Test Results\n")) tempContent.WriteString(fmt.Sprintf("Server: %s\n", result.Server)) - tempContent.WriteString(fmt.Sprintf("测试时长: %v\n\n", result.TestDuration)) + tempContent.WriteString(fmt.Sprintf(i18n.T("Test Duration: %v\n\n"), result.TestDuration)) if result.PingResult != nil { tempContent.WriteString("Latency (Ping):\n") diff --git a/widgets/handles.go b/widgets/handles.go index 501c722..c9ba587 100644 --- a/widgets/handles.go +++ b/widgets/handles.go @@ -2,6 +2,7 @@ package widgets import ( "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "path/filepath" "slices" @@ -124,7 +125,7 @@ func (m Model) handleContainerRelatedMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { if container.ID == m.Monitor.SelectedContainer.ID { if strings.ToLower(container.Status) != "up" && m.Monitor.ContainerLogsStreaming { m.cleanupLogsStream() - m.LastOperationMsg = fmt.Sprintf("容器已停止,已禁用实时流(状态:%s)", container.Status) + m.LastOperationMsg = fmt.Sprintf(i18n.T("Container stopped, streaming disabled (status: %s)"), container.Status) } break } @@ -139,10 +140,10 @@ func (m Model) handleContainerRelatedMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { m.Monitor.ContainerLogsLoading = false if logsMsg.Error != nil { if strings.Contains(logsMsg.Error.Error(), "streaming unavailable") { - m.Monitor.ContainerLogs = fmt.Sprintf("实时流不可用:%v\n改为显示静态日志...", logsMsg.Error) + m.Monitor.ContainerLogs = fmt.Sprintf(i18n.T("Streaming not available: %v\nShowing static logs instead..."), logsMsg.Error) return m, m.Monitor.App.GetContainerLogsCmd(m.Monitor.SelectedContainer.ID) } else { - m.Monitor.ContainerLogs = fmt.Sprintf("加载日志出错:%v", logsMsg.Error) + m.Monitor.ContainerLogs = fmt.Sprintf(i18n.T("Error loading logs: %v"), logsMsg.Error) } m.Monitor.ContainerLogsPagination.Lines = []string{m.Monitor.ContainerLogs} m.Monitor.ContainerLogsPagination.TotalPages = 1 @@ -230,6 +231,15 @@ func (m Model) handleKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m.handleSearchKeys(msg) } + // Global language toggle (Shift+L / Ctrl+L). Plain "l" remains navigation. + switch msg.String() { + case "L", "ctrl+l": + i18n.Toggle() + m.applyLocale() + m.LastOperationMsg = fmt.Sprintf(i18n.T("Language: %s"), i18n.Label()) + return m, nil + } + switch m.Ui.State { case model.StateHome: return m.handleHomeKeys(msg) @@ -550,19 +560,19 @@ func (m Model) handleNetworkKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "enter": if m.Diagnostic.Password.Value() != "" { m.Network.AuthState = model.AuthInProgress - m.Network.AuthMessage = "正在认证..." + m.Network.AuthMessage = i18n.T("Authenticating...") // Try to authenticate err := m.setRoot() if err != nil { m.Network.AuthState = model.AuthFailed - m.Network.AuthMessage = fmt.Sprintf("认证失败:%v", err) + m.Network.AuthMessage = fmt.Sprintf(i18n.T("Authentication failed: %v"), err) if !m.SudoAvailable { m.Network.AuthMessage += "\nSudo is not available. Please run as root." } m.Diagnostic.Password.Reset() } else { m.Network.AuthState = model.AuthSuccess - m.Network.AuthMessage = "认证成功!" + m.Network.AuthMessage = i18n.T("Authentication successful!") m.AsRoot = true m.Network.AuthTimer = 2 @@ -889,13 +899,13 @@ func (m Model) handleConnectivityInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // Clear password input and reset echo mode m.Network.TracerouteInput.SetValue("") m.Network.TracerouteInput.EchoMode = textinput.EchoNormal - m.Network.TracerouteInput.Placeholder = "输入目标(IP 或域名)..." + m.Network.TracerouteInput.Placeholder = i18n.T("Enter target (IP or domain)...") m.Network.ConnectivityMode = model.ConnectivityModeNone m.Network.TracerouteInstallTarget = "" // Start installation m.OperationInProgress = true - m.LastOperationMsg = "正在安装 traceroute..." + m.LastOperationMsg = i18n.T("Installing traceroute...") m.resetSpinner() return m, tea.Batch( network.InstallTraceroute(target, password), @@ -908,7 +918,7 @@ func (m Model) handleConnectivityInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.Network.PingInput.SetValue("") m.Network.TracerouteInput.SetValue("") m.Network.TracerouteInput.EchoMode = textinput.EchoNormal - m.Network.TracerouteInput.Placeholder = "输入目标(IP 或域名)..." + m.Network.TracerouteInput.Placeholder = i18n.T("Enter target (IP or domain)...") m.Network.TracerouteInstallTarget = "" m.Network.PingLoading = false m.Network.TracerouteLoading = false @@ -1050,19 +1060,19 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "enter": if m.Diagnostic.Password.Value() != "" { m.Diagnostic.AuthState = model.AuthInProgress - m.Diagnostic.AuthMessage = "正在认证..." + m.Diagnostic.AuthMessage = i18n.T("Authenticating...") // Try to authenticate err := m.setRoot() if err != nil { m.Diagnostic.AuthState = model.AuthFailed - m.Diagnostic.AuthMessage = fmt.Sprintf("认证失败:%v", err) + m.Diagnostic.AuthMessage = fmt.Sprintf(i18n.T("Authentication failed: %v"), err) if !m.SudoAvailable { m.Diagnostic.AuthMessage += "\nSudo is not available. Please run as root." } m.Diagnostic.Password.Reset() } else { m.Diagnostic.AuthState = model.AuthSuccess - m.Diagnostic.AuthMessage = "认证成功!" + m.Diagnostic.AuthMessage = i18n.T("Authentication successful!") m.AsRoot = true m.Diagnostic.AuthTimer = 2 @@ -1467,7 +1477,7 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "a": if m.Diagnostic.SelectedItem == model.DiagnosticSecurityChecks && !m.AsRoot && !m.CanRunSudo { m.Diagnostic.AuthState = model.AuthRequired - m.Diagnostic.AuthMessage = "输入管理员密码:" + m.Diagnostic.AuthMessage = i18n.T("Enter password for admin access:") m.Diagnostic.Password.Focus() m.Diagnostic.Password.SetValue("") } @@ -1550,7 +1560,7 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // Check if admin privileges are required for this diagnostic if !m.canAccessDiagnostic(checkName) { m.Diagnostic.AuthState = model.AuthRequired - m.Diagnostic.AuthMessage = fmt.Sprintf("需要管理员权限:%s\n请输入密码:", checkName) + m.Diagnostic.AuthMessage = fmt.Sprintf(i18n.T("Admin privileges required for: %s\nEnter password:"), checkName) m.Diagnostic.Password.Focus() m.Diagnostic.Password.SetValue("") return m, nil @@ -1558,15 +1568,15 @@ func (m Model) handleDiagnosticsKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // Execute the diagnostic check switch checkName { - case "SSL Certificate": + case i18n.T("SSL Certificate"): return m, m.Diagnostic.SecurityManager.RunCertificateDisplay() - case "SSH Root 登录": + case i18n.T("SSH Root Login"): return m, m.Diagnostic.SecurityManager.DisplaySSHRootInfos() - case "开放端口": + case i18n.T("Open Ports"): return m, m.Diagnostic.SecurityManager.DisplayOpenedPortsInfos() - case "防火墙状态": + case i18n.T("Firewall Status"): return m, m.Diagnostic.SecurityManager.DisplayFirewallInfos() - case "Auto Ban": + case i18n.T("Auto Ban"): return m, m.Diagnostic.SecurityManager.DisplayAutoBanInfos() } } @@ -1598,7 +1608,7 @@ func (m Model) handleSecurityCheckMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { } if hasAdminChecks && m.Diagnostic.AuthState == model.AuthNotRequired { - m.Diagnostic.AuthMessage = "部分检查需要管理员权限。按 a 进行认证。" + m.Diagnostic.AuthMessage = i18n.T("Some checks require admin privileges. Press 'a' to authenticate.") } } @@ -1945,21 +1955,21 @@ func (m Model) handleContainerMenuKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "r": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定重启容器 '%s'?\n将先停止再启动该容器。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Restart container '%s'?\nThis will stop and start the container."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "restart" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "d": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Delete container '%s'?\nThis action cannot be undone."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "delete" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "x": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定强制删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Force remove container '%s'?\nThis action cannot be undone."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "remove" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil @@ -1978,7 +1988,7 @@ func (m Model) handleContainerMenuKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, tea.Quit case "c": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden - m.LastOperationMsg = "提交功能尚未实现" + m.LastOperationMsg = i18n.T("Commit functionality not yet implemented") return m, nil case "esc", "b": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden @@ -2010,21 +2020,21 @@ func (m Model) executeContainerMenuAction() (tea.Model, tea.Cmd) { case "restart": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定重启容器 '%s'?\n将先停止再启动该容器。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Restart container '%s'?\nThis will stop and start the container."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "restart" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "delete": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Delete container '%s'?\nThis action cannot be undone."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "delete" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil case "remove": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden m.ConfirmationVisible = true - m.ConfirmationMessage = fmt.Sprintf("确定强制删除容器 '%s'?\n此操作不可撤销。", m.Monitor.SelectedContainer.Name) + m.ConfirmationMessage = fmt.Sprintf(i18n.T("Force remove container '%s'?\nThis action cannot be undone."), m.Monitor.SelectedContainer.Name) m.ConfirmationAction = "remove" m.ConfirmationData = m.Monitor.SelectedContainer.ID return m, nil @@ -2043,7 +2053,7 @@ func (m Model) executeContainerMenuAction() (tea.Model, tea.Cmd) { return m, tea.Quit case "commit": m.Monitor.ContainerMenuState = model.ContainerMenuState(0) // ContainerMenuHidden - m.LastOperationMsg = "提交功能尚未实现" + m.LastOperationMsg = i18n.T("Commit functionality not yet implemented") return m, nil } return m, nil @@ -2088,7 +2098,7 @@ func (m Model) handleConfirmationKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.Network.TracerouteInstallTarget = target m.Network.ConnectivityMode = model.ConnectivityModeInstallPassword m.Network.TracerouteInput.SetValue("") - m.Network.TracerouteInput.Placeholder = "输入 sudo 密码..." + m.Network.TracerouteInput.Placeholder = i18n.T("Enter sudo password...") m.Network.TracerouteInput.EchoMode = textinput.EchoPassword m.Network.TracerouteInput.Focus() return m, nil @@ -2343,11 +2353,11 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case network.SpeedTestErrorMsg: m.Network.SpeedTestLoading = false - m.LastOperationMsg = "测速失败: " + msg.Error + m.LastOperationMsg = i18n.T("Speed test failed: ") + msg.Error return m, clearOperationMessage() case network.SpeedTestProgressMsg: // Handle progress updates if needed - m.LastOperationMsg = fmt.Sprintf("测速:%s (%.0f%%)", msg.Message, msg.Progress*100) + m.LastOperationMsg = fmt.Sprintf(i18n.T("Speed test: %s (%.0f%%)"), msg.Message, msg.Progress*100) return m, nil case network.TracerouteInstallPromptMsg: // Traceroute is not installed, show confirmation dialog @@ -2375,7 +2385,7 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { m.Network.TracerouteInstallTarget = msg.Target m.Network.ConnectivityMode = model.ConnectivityModeInstallPassword m.Network.TracerouteInput.SetValue("") - m.Network.TracerouteInput.Placeholder = "输入 sudo 密码..." + m.Network.TracerouteInput.Placeholder = i18n.T("Enter sudo password...") m.Network.TracerouteInput.EchoMode = textinput.EchoPassword m.Network.TracerouteInput.Focus() return m, clearOperationMessage() diff --git a/widgets/help.go b/widgets/help.go index b9d5f72..91593f3 100644 --- a/widgets/help.go +++ b/widgets/help.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" model "github.com/Server-Pulse/server-pulse/widgets/model" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -15,18 +16,19 @@ type KeyMap interface { // BaseKeyMap contains keys that are available in most states type BaseKeyMap struct { - Help key.Binding - Quit key.Binding - Back key.Binding + Help key.Binding + Quit key.Binding + Back key.Binding + Language key.Binding } func (k BaseKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Quit} + return []key.Binding{k.Help, k.Language, k.Quit} } func (k BaseKeyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ - {k.Help, k.Quit, k.Back}, + {k.Help, k.Language, k.Quit, k.Back}, } } @@ -321,15 +323,19 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "切换帮助"), + key.WithHelp("?", i18n.T("toggle help")), ), Quit: key.NewBinding( key.WithKeys("q", "esc", "ctrl+c"), - key.WithHelp("q", "退出"), + key.WithHelp("q", i18n.T("quit")), ), Back: key.NewBinding( key.WithKeys("b"), - key.WithHelp("b", "返回"), + key.WithHelp("b", i18n.T("back")), + ), + Language: key.NewBinding( + key.WithKeys("L", "ctrl+l"), + key.WithHelp("L", i18n.T("switch language")), ), } @@ -339,27 +345,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "选择"), + key.WithHelp("enter", i18n.T("select")), ), Navigate: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "移动"), + key.WithHelp("tab/←→", i18n.T("navigate")), ), Quick1: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "监控"), + key.WithHelp("1", i18n.T("monitor")), ), Quick2: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "诊断"), + key.WithHelp("2", i18n.T("diagnostics")), ), Quick3: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "网络"), + key.WithHelp("3", i18n.T("network")), ), Quick4: key.NewBinding( key.WithKeys("4"), - key.WithHelp("4", "报告"), + key.WithHelp("4", i18n.T("reporting")), ), } @@ -368,23 +374,23 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "选择"), + key.WithHelp("enter", i18n.T("select")), ), Navigate: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "移动"), + key.WithHelp("tab/←→", i18n.T("navigate")), ), Quick1: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "系统"), + key.WithHelp("1", i18n.T("system")), ), Quick2: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "进程"), + key.WithHelp("2", i18n.T("process")), ), Quick3: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "容器"), + key.WithHelp("3", i18n.T("containers")), ), } @@ -393,7 +399,7 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Scroll: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "滚动"), + key.WithHelp("↑↓", i18n.T("scroll")), ), } @@ -402,23 +408,23 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "搜索"), + key.WithHelp("/", i18n.T("search")), ), Kill: key.NewBinding( key.WithKeys("k"), - key.WithHelp("k", "结束进程"), + key.WithHelp("k", i18n.T("kill")), ), SortMem: key.NewBinding( key.WithKeys("m"), - key.WithHelp("m", "按内存排序"), + key.WithHelp("m", i18n.T("sort by mem")), ), SortCPU: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "按CPU排序"), + key.WithHelp("s", i18n.T("sort by cpu")), ), } @@ -427,15 +433,15 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "菜单"), + key.WithHelp("enter", i18n.T("menu")), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "搜索"), + key.WithHelp("/", i18n.T("search")), ), } @@ -444,7 +450,7 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "切换标签"), + key.WithHelp("tab/←→", i18n.T("switch tabs")), ), } @@ -453,19 +459,19 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Scroll: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "滚动"), + key.WithHelp("↑↓", i18n.T("scroll")), ), Refresh: key.NewBinding( key.WithKeys("r"), - key.WithHelp("r", "刷新"), + key.WithHelp("r", i18n.T("refresh")), ), HomeEnd: key.NewBinding( key.WithKeys("home", "end"), - key.WithHelp("home/end", "移动"), + key.WithHelp("home/end", i18n.T("navigate")), ), Stream: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "切换实时流"), + key.WithHelp("s", i18n.T("toggle stream")), ), } @@ -474,27 +480,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("tab", "left", "right"), - key.WithHelp("tab/←→", "切换标签"), + key.WithHelp("tab/←→", i18n.T("switch tabs")), ), Ping: key.NewBinding( key.WithKeys("p"), - key.WithHelp("p", "Ping"), + key.WithHelp("p", i18n.T("ping")), ), Trace: key.NewBinding( key.WithKeys("t"), - key.WithHelp("t", "路由追踪"), + key.WithHelp("t", i18n.T("trace route")), ), Clear: key.NewBinding( key.WithKeys("c"), - key.WithHelp("c", "清除"), + key.WithHelp("c", i18n.T("clear")), ), Switch: key.NewBinding( key.WithKeys("space"), - key.WithHelp("space", "切换视图"), + key.WithHelp("space", i18n.T("switch view")), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "浏览表格/翻页"), + key.WithHelp("↑↓", i18n.T("navigate tables/pages")), ), } @@ -505,35 +511,35 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "切换标签"), + key.WithHelp("←→", i18n.T("switch tabs")), ), Reload: key.NewBinding( key.WithKeys("enter", "r"), - key.WithHelp("enter/r", "刷新"), + key.WithHelp("enter/r", i18n.T("reload")), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "搜索"), + key.WithHelp("/", i18n.T("search")), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "详情"), + key.WithHelp("enter", i18n.T("details")), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "安全"), + key.WithHelp("1", i18n.T("security")), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "性能"), + key.WithHelp("2", i18n.T("performance")), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "日志"), + key.WithHelp("3", "logs"), ), } case model.DiagnosticTabPerformances: @@ -541,27 +547,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "切换标签"), + key.WithHelp("←→", i18n.T("switch tabs")), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "详情"), + key.WithHelp("enter", i18n.T("details")), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "安全"), + key.WithHelp("1", i18n.T("security")), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "性能"), + key.WithHelp("2", i18n.T("performance")), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "日志"), + key.WithHelp("3", "logs"), ), } case model.DiagnosticTabLogs: @@ -569,51 +575,51 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, TimeRange: key.NewBinding( key.WithKeys("shift+left", "shift+right"), - key.WithHelp("shift+←→", "时间范围"), + key.WithHelp("shift+←→", i18n.T("time range")), ), Level: key.NewBinding( key.WithKeys("shift+left", "shift+right"), - key.WithHelp("shift+←→", "级别"), + key.WithHelp("shift+←→", i18n.T("level")), ), SwitchFilter: key.NewBinding( key.WithKeys("space"), - key.WithHelp("space", "切换筛选"), + key.WithHelp("space", i18n.T("switch filter")), ), SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "切换标签"), + key.WithHelp("←→", i18n.T("switch tabs")), ), Reload: key.NewBinding( key.WithKeys("enter", "r"), - key.WithHelp("enter/r", "刷新"), + key.WithHelp("enter/r", i18n.T("reload")), ), Search: key.NewBinding( key.WithKeys("/"), - key.WithHelp("/", "搜索"), + key.WithHelp("/", i18n.T("search")), ), Service: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "服务"), + key.WithHelp("s", i18n.T("service")), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Details: key.NewBinding( key.WithKeys("d"), - key.WithHelp("d", "详情"), + key.WithHelp("d", i18n.T("details")), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "安全"), + key.WithHelp("1", i18n.T("security")), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "性能"), + key.WithHelp("2", i18n.T("performance")), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "日志"), + key.WithHelp("3", "logs"), ), } default: @@ -621,27 +627,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right"), - key.WithHelp("←→", "切换标签"), + key.WithHelp("←→", i18n.T("switch tabs")), ), Navigate: key.NewBinding( key.WithKeys("up", "down"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Details: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "详情"), + key.WithHelp("enter", i18n.T("details")), ), Security: key.NewBinding( key.WithKeys("1"), - key.WithHelp("1", "安全"), + key.WithHelp("1", i18n.T("security")), ), Performance: key.NewBinding( key.WithKeys("2"), - key.WithHelp("2", "性能"), + key.WithHelp("2", i18n.T("performance")), ), Logs: key.NewBinding( key.WithKeys("3"), - key.WithHelp("3", "日志"), + key.WithHelp("3", "logs"), ), } } @@ -651,27 +657,27 @@ func (hs *HelpSystem) GetKeyMapForState(state model.AppState, diagnosticSelected BaseKeyMap: baseKeys, Generate: key.NewBinding( key.WithKeys("g"), - key.WithHelp("g", "生成报告"), + key.WithHelp("g", i18n.T("generate report")), ), Save: key.NewBinding( key.WithKeys("s"), - key.WithHelp("s", "保存报告"), + key.WithHelp("s", i18n.T("save report")), ), Load: key.NewBinding( key.WithKeys("l"), - key.WithHelp("l", "加载报告"), + key.WithHelp("l", i18n.T("load reports")), ), Navigate: key.NewBinding( key.WithKeys("up", "down", "k", "j"), - key.WithHelp("↑↓", "移动"), + key.WithHelp("↑↓", i18n.T("navigate")), ), Select: key.NewBinding( key.WithKeys("enter"), - key.WithHelp("enter", "选择"), + key.WithHelp("enter", i18n.T("select")), ), Delete: key.NewBinding( key.WithKeys("d"), - key.WithHelp("d", "删除"), + key.WithHelp("d", "delete"), ), } } @@ -722,15 +728,15 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "切换帮助"), + key.WithHelp("?", i18n.T("toggle help")), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "退出"), + key.WithHelp("q", i18n.T("quit")), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "返回"), + key.WithHelp("b/esc", i18n.T("back")), ), } @@ -738,7 +744,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "切换CPU子标签"), + key.WithHelp("←→", i18n.T("switch CPU sub-tabs")), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -759,15 +765,15 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "切换帮助"), + key.WithHelp("?", i18n.T("toggle help")), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "退出"), + key.WithHelp("q", i18n.T("quit")), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "返回"), + key.WithHelp("b/esc", i18n.T("back")), ), } @@ -775,7 +781,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "切换内存子标签"), + key.WithHelp("←→", i18n.T("switch Memory sub-tabs")), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -797,32 +803,32 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "切换帮助"), + key.WithHelp("?", i18n.T("toggle help")), ), Quit: key.NewBinding( key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "退出"), + key.WithHelp("q", i18n.T("quit")), ), Back: key.NewBinding( key.WithKeys("b", "esc"), - key.WithHelp("b/esc", "返回"), + key.WithHelp("b/esc", i18n.T("back")), ), } - enterHelp := "激活标签" + enterHelp := i18n.T("activate tab") // Show special help when on CPU or Memory tab switch m.Diagnostic.Performance.SelectedItem { case model.CPU: - enterHelp = "进入CPU子标签" + enterHelp = i18n.T("enter CPU sub-tabs") case model.Memory: - enterHelp = "进入内存子标签" + enterHelp = i18n.T("enter Memory sub-tabs") } return DiagnosticsKeyMap{ BaseKeyMap: baseKeys, SwitchTab: key.NewBinding( key.WithKeys("left", "right", "h", "l"), - key.WithHelp("←→", "切换性能标签"), + key.WithHelp("←→", i18n.T("switch performance tabs")), ), Navigate: key.NewBinding( key.WithKeys(""), @@ -834,7 +840,7 @@ func (hs *HelpSystem) GetKeyMapForStateWithModel(state model.AppState, diagnosti ), Reload: key.NewBinding( key.WithKeys("r"), - key.WithHelp("r", "刷新数据"), + key.WithHelp("r", i18n.T("reload data")), ), } } diff --git a/widgets/help_test.go b/widgets/help_test.go index ed34446..609b45c 100644 --- a/widgets/help_test.go +++ b/widgets/help_test.go @@ -179,15 +179,15 @@ func TestBaseKeyMap(t *testing.T) { baseKeys := BaseKeyMap{ Help: key.NewBinding( key.WithKeys("?"), - key.WithHelp("?", "切换帮助"), + key.WithHelp("?", "toggle help"), ), Quit: key.NewBinding( key.WithKeys("q", "esc", "ctrl+c"), - key.WithHelp("q", "退出"), + key.WithHelp("q", "quit"), ), Back: key.NewBinding( key.WithKeys("b"), - key.WithHelp("b", "返回"), + key.WithHelp("b", "back"), ), } diff --git a/widgets/init.go b/widgets/init.go index 1706c57..9e12d75 100644 --- a/widgets/init.go +++ b/widgets/init.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "time" @@ -39,16 +40,16 @@ func InitialModelWithManager(apk *app.DockerManager) Model { var err error containers, err = apk.RefreshContainers() if err != nil { - fmt.Printf("加载容器时出错: %v\n", err) + fmt.Printf(i18n.T("Erreur lors du chargement des conteneurs: %v\n"), err) } } columns := []table.Column{ {Title: "PID", Width: 8}, - {Title: "用户", Width: 12}, + {Title: i18n.T("User"), Width: 12}, {Title: "CPU%", Width: 8}, - {Title: "内存%", Width: 8}, - {Title: "命令", Width: 30}, + {Title: i18n.T("Mem%"), Width: 8}, + {Title: i18n.T("Command"), Width: 30}, } t := table.New( table.WithColumns(columns), @@ -61,12 +62,12 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // containers table ctColumns := []table.Column{ {Title: "ID", Width: 12}, - {Title: "镜像", Width: 12}, - {Title: "名称", Width: 16}, - {Title: "状态", Width: 12}, - {Title: "健康状态", Width: 12}, - {Title: "项目", Width: 20}, - {Title: "端口", Width: 20}, + {Title: i18n.T("Image"), Width: 12}, + {Title: i18n.T("Name"), Width: 16}, + {Title: i18n.T("Status"), Width: 12}, + {Title: i18n.T("Health"), Width: 12}, + {Title: i18n.T("Project"), Width: 20}, + {Title: i18n.T("Ports"), Width: 20}, } ct := table.New( table.WithColumns(ctColumns), @@ -79,11 +80,11 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Network table networkColumns := []table.Column{ - {Title: "接口", Width: 12}, - {Title: "状态", Width: 8}, - {Title: "IP 地址", Width: 25}, - {Title: "接收", Width: 12}, - {Title: "发送", Width: 12}, + {Title: i18n.T("Interface"), Width: 12}, + {Title: i18n.T("Status"), Width: 8}, + {Title: i18n.T("IP Addresses"), Width: 25}, + {Title: i18n.T("RX"), Width: 12}, + {Title: i18n.T("TX"), Width: 12}, } networkTable := table.New( table.WithColumns(networkColumns), @@ -100,13 +101,13 @@ func InitialModelWithManager(apk *app.DockerManager) Model { networkTable.SetStyles(networkStyle) connectionsColumns := []table.Column{ - {Title: "协议", Width: 6}, - {Title: "接收队列", Width: 8}, - {Title: "发送队列", Width: 8}, - {Title: "本地地址", Width: 22}, - {Title: "远程地址", Width: 20}, - {Title: "状态", Width: 12}, - {Title: "PID/程序", Width: 18}, + {Title: i18n.T("Proto"), Width: 6}, + {Title: i18n.T("Recv-Q"), Width: 8}, + {Title: i18n.T("Send-Q"), Width: 8}, + {Title: i18n.T("Local Address"), Width: 22}, + {Title: i18n.T("Foreign Address"), Width: 20}, + {Title: i18n.T("State"), Width: 12}, + {Title: i18n.T("PID/Program"), Width: 18}, } connectionsTable := table.New( table.WithColumns(connectionsColumns), @@ -116,11 +117,11 @@ func InitialModelWithManager(apk *app.DockerManager) Model { connectionsTable.SetStyles(networkStyle) routesColumns := []table.Column{ - {Title: "目标", Width: 18}, - {Title: "网关", Width: 12}, - {Title: "子网掩码", Width: 16}, - {Title: "标志", Width: 8}, - {Title: "接口", Width: 16}, + {Title: i18n.T("Destination"), Width: 18}, + {Title: i18n.T("Gateway"), Width: 12}, + {Title: i18n.T("Genmask"), Width: 16}, + {Title: i18n.T("Flags"), Width: 8}, + {Title: i18n.T("Iface"), Width: 16}, } routesTable := table.New( table.WithColumns(routesColumns), @@ -140,9 +141,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { dnsTable.SetStyles(networkStyle) diagnosticColumns := []table.Column{ - {Title: "安全检查", Width: 30}, - {Title: "性能", Width: 12}, - {Title: "日志", Width: 40}, + {Title: i18n.T("Security Check"), Width: 30}, + {Title: i18n.T("Performances"), Width: 12}, + {Title: i18n.T("Logs"), Width: 40}, } diagnosticTable := table.New( @@ -152,13 +153,13 @@ func InitialModelWithManager(apk *app.DockerManager) Model { diagnosticTable.SetStyles(networkStyle) searchInput := textinput.New() - searchInput.Placeholder = "搜索进程..." + searchInput.Placeholder = i18n.T("Search a process...") searchInput.Prompt = "/" searchInput.CharLimit = 50 searchInput.Width = 30 passwordInput := textinput.New() - passwordInput.Placeholder = "输入 root 密码..." + passwordInput.Placeholder = i18n.T("Enter root password...") passwordInput.EchoMode = textinput.EchoPassword passwordInput.EchoCharacter = '•' passwordInput.CharLimit = 50 @@ -181,9 +182,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { securityManager.SudoPassword = "" // No password initially securityColumns := []table.Column{ - {Title: "名称", Width: 20}, - {Title: "状态", Width: 15}, - {Title: "详情", Width: 40}, + {Title: i18n.T("Name"), Width: 20}, + {Title: i18n.T("Status"), Width: 15}, + {Title: i18n.T("Details"), Width: 40}, } securityTable := table.New( table.WithColumns(securityColumns), @@ -202,9 +203,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { securityTable.SetStyles(tableStyle) portsColumns := []table.Column{ - {Title: "端口", Width: 10}, - {Title: "服务", Width: 20}, - {Title: "协议", Width: 10}, + {Title: i18n.T("Port"), Width: 10}, + {Title: i18n.T("Service"), Width: 20}, + {Title: i18n.T("Protocol"), Width: 10}, {Title: "PID", Width: 10}, } @@ -217,7 +218,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { spinnerModel := getRandomSpinner() firewallColumns := []table.Column{ - {Title: "防火墙规则", Width: 100}, + {Title: i18n.T("Firewall Rule"), Width: 100}, } firewallTable := table.New( @@ -228,7 +229,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { firewallTable.SetStyles(tableStyle) autoBanColumns := []table.Column{ - {Title: "Jail/服务详情", Width: 100}, + {Title: i18n.T("Jail/Service Details"), Width: 100}, } autoBanTable := table.New( @@ -240,10 +241,10 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Logs table logsColumns := []table.Column{ - {Title: "时间", Width: 20}, - {Title: "级别", Width: 8}, - {Title: "服务", Width: 20}, - {Title: "消息", Width: 70}, + {Title: i18n.T("Time"), Width: 20}, + {Title: i18n.T("Level"), Width: 8}, + {Title: i18n.T("Service"), Width: 20}, + {Title: i18n.T("Message"), Width: 70}, } logsTable := table.New( @@ -256,17 +257,17 @@ func InitialModelWithManager(apk *app.DockerManager) Model { // Log filter inputs logSearchInput := textinput.New() - logSearchInput.Placeholder = "搜索日志..." + logSearchInput.Placeholder = i18n.T("Search logs...") logSearchInput.CharLimit = 100 logSearchInput.Width = 30 logServiceInput := textinput.New() - logServiceInput.Placeholder = "按服务筛选..." + logServiceInput.Placeholder = i18n.T("Filter by service...") logServiceInput.CharLimit = 50 logServiceInput.Width = 25 logTimeRangeInput := textinput.New() - logTimeRangeInput.Placeholder = "例如:'2 小时前'、'2025-01-08'、'3 天前'" + logTimeRangeInput.Placeholder = i18n.T("e.g., '2 hours ago', '2025-01-08', '3 days ago'") logTimeRangeInput.CharLimit = 50 logTimeRangeInput.Width = 50 @@ -290,18 +291,18 @@ func InitialModelWithManager(apk *app.DockerManager) Model { ConnectionsTable: connectionsTable, RoutesTable: routesTable, DNSTable: dnsTable, - Nav: v.NetworkNav, + Nav: v.LocalizedNetworkNav(), SelectedItem: model.NetworkTabInterface, PingInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "输入要 Ping 的主机或 IP(如 8.8.8.8)" + ti.Placeholder = i18n.T("Enter host or IP to ping (e.g., 8.8.8.8)") ti.CharLimit = 100 ti.Width = 40 return ti }(), TracerouteInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "输入要追踪的主机或 IP(如 google.com)" + ti.Placeholder = i18n.T("Enter host or IP to trace (e.g., google.com)") ti.CharLimit = 100 ti.Width = 40 return ti @@ -315,11 +316,11 @@ func InitialModelWithManager(apk *app.DockerManager) Model { }, Diagnostic: model.DiagnosticModel{ DiagnosticTable: diagnosticTable, - Nav: v.DiagnosticNav, + Nav: v.LocalizedDiagnosticNav(), SelectedItem: model.DiagnosticSecurityChecks, Performance: model.PerformanceModel{ SelectedItem: model.SystemHealth, - Nav: []string{"系统健康", "I/O", "CPU", "内存"}, + Nav: v.LocalizedPerformanceNav(), SubTabNavigationActive: false, CPUSelectedTab: model.CPUTabStateBreakdown, CPUSubTabActive: false, @@ -343,7 +344,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { CustomTimeInputMode: false, DomainInput: func() textinput.Model { ti := textinput.New() - ti.Placeholder = "输入域名(如 google.com)" + ti.Placeholder = i18n.T("Enter domain name (e.g., google.com)") ti.CharLimit = 100 ti.Width = 40 return ti @@ -368,9 +369,9 @@ func InitialModelWithManager(apk *app.DockerManager) Model { App: apk, ContainerMenuState: v.ContainerMenuHidden, SelectedContainer: nil, - ContainerMenuItems: v.ContainerMenuItems, + ContainerMenuItems: v.LocalizedContainerMenuItems(), ContainerViewState: v.ContainerViewNone, - ContainerTabs: v.ContainerTabs, + ContainerTabs: v.LocalizedContainerTabs(), ContainerLogsPagination: model.ContainerLogsPagination{ PageSize: 100, CurrentPage: 1, @@ -405,7 +406,7 @@ func InitialModelWithManager(apk *app.DockerManager) Model { }, Ui: model.UIModel{ State: model.StateHome, - Tabs: v.Menu, + Tabs: v.LocalizedMenu(), SelectedTab: 0, ActiveView: -1, SearchInput: searchInput, diff --git a/widgets/locale.go b/widgets/locale.go new file mode 100644 index 0000000..ff6fbe3 --- /dev/null +++ b/widgets/locale.go @@ -0,0 +1,88 @@ +package widgets + +import ( + "github.com/Server-Pulse/server-pulse/i18n" + v "github.com/Server-Pulse/server-pulse/widgets/vars" + "github.com/charmbracelet/bubbles/table" +) + +// applyLocale refreshes all in-memory UI labels after a language switch. +func (m *Model) applyLocale() { + m.Ui.Tabs = v.LocalizedMenu() + m.Network.Nav = v.LocalizedNetworkNav() + m.Diagnostic.Nav = v.LocalizedDiagnosticNav() + m.Diagnostic.Performance.Nav = v.LocalizedPerformanceNav() + m.Monitor.ContainerTabs = v.LocalizedContainerTabs() + m.Monitor.ContainerMenuItems = v.LocalizedContainerMenuItems() + + m.Monitor.ProcessTable.SetColumns([]table.Column{ + {Title: "PID", Width: 8}, + {Title: i18n.T("User"), Width: 12}, + {Title: "CPU%", Width: 8}, + {Title: i18n.T("Mem%"), Width: 8}, + {Title: i18n.T("Command"), Width: 30}, + }) + m.Monitor.Container.SetColumns([]table.Column{ + {Title: "ID", Width: 12}, + {Title: i18n.T("Image"), Width: 12}, + {Title: i18n.T("Name"), Width: 16}, + {Title: i18n.T("Status"), Width: 12}, + {Title: i18n.T("Health"), Width: 12}, + {Title: i18n.T("Project"), Width: 20}, + {Title: i18n.T("Ports"), Width: 20}, + }) + m.Network.NetworkTable.SetColumns([]table.Column{ + {Title: i18n.T("Interface"), Width: 12}, + {Title: i18n.T("Status"), Width: 8}, + {Title: i18n.T("IP Addresses"), Width: 25}, + {Title: i18n.T("RX"), Width: 12}, + {Title: i18n.T("TX"), Width: 12}, + }) + m.Network.ConnectionsTable.SetColumns([]table.Column{ + {Title: i18n.T("Proto"), Width: 6}, + {Title: i18n.T("Recv-Q"), Width: 8}, + {Title: i18n.T("Send-Q"), Width: 8}, + {Title: i18n.T("Local Address"), Width: 22}, + {Title: i18n.T("Foreign Address"), Width: 20}, + {Title: i18n.T("State"), Width: 12}, + {Title: i18n.T("PID/Program"), Width: 18}, + }) + m.Network.RoutesTable.SetColumns([]table.Column{ + {Title: i18n.T("Destination"), Width: 18}, + {Title: i18n.T("Gateway"), Width: 12}, + {Title: i18n.T("Genmask"), Width: 16}, + {Title: i18n.T("Flags"), Width: 8}, + {Title: i18n.T("Iface"), Width: 16}, + }) + m.Diagnostic.SecurityTable.SetColumns([]table.Column{ + {Title: i18n.T("Name"), Width: 20}, + {Title: i18n.T("Status"), Width: 15}, + {Title: i18n.T("Details"), Width: 40}, + }) + m.Diagnostic.PortsTable.SetColumns([]table.Column{ + {Title: i18n.T("Port"), Width: 10}, + {Title: i18n.T("Service"), Width: 20}, + {Title: i18n.T("Protocol"), Width: 10}, + {Title: "PID", Width: 10}, + }) + m.Diagnostic.FirewallTable.SetColumns([]table.Column{ + {Title: i18n.T("Firewall Rule"), Width: 100}, + }) + m.Diagnostic.AutoBanTable.SetColumns([]table.Column{ + {Title: i18n.T("Jail/Service Details"), Width: 100}, + }) + m.Diagnostic.LogsTable.SetColumns([]table.Column{ + {Title: i18n.T("Time"), Width: 20}, + {Title: i18n.T("Level"), Width: 8}, + {Title: i18n.T("Service"), Width: 20}, + {Title: i18n.T("Message"), Width: 70}, + }) + + m.Ui.SearchInput.Placeholder = i18n.T("Search a process...") + m.Diagnostic.Password.Placeholder = i18n.T("Enter root password...") + m.Diagnostic.LogSearchInput.Placeholder = i18n.T("Search logs...") + m.Diagnostic.LogServiceInput.Placeholder = i18n.T("Filter by service...") + m.Network.PingInput.Placeholder = i18n.T("Enter host or IP to ping (e.g., 8.8.8.8)") + m.Network.TracerouteInput.Placeholder = i18n.T("Enter host or IP to trace (e.g., google.com)") + m.Diagnostic.DomainInput.Placeholder = i18n.T("Enter domain name (e.g., google.com)") +} diff --git a/widgets/model/performance.go b/widgets/model/performance.go index 16af746..7d1ca30 100644 --- a/widgets/model/performance.go +++ b/widgets/model/performance.go @@ -1,7 +1,9 @@ package model -import "time" - +import ( + "time" + "github.com/Server-Pulse/server-pulse/i18n" +) // HealthMetrics holds the metrics for the system health check. type HealthMetrics struct { IOWait float64 @@ -31,7 +33,7 @@ const ( ) func (pt PerformanceTab) String() string { - return []string{"系统健康", "I/O", "CPU", "内存"}[pt] + return []string{i18n.T("System Health"), "I/O", "CPU", i18n.T("Memory")}[pt] } type CPUTab int @@ -43,7 +45,7 @@ const ( ) func (ct CPUTab) String() string { - return []string{"CPU 状态分解", "每核性能", "系统活动指标"}[ct] + return []string{i18n.T("CPU State Breakdown"), i18n.T("Per-Core Performance"), i18n.T("System Activity Metrics")}[ct] } type MemoryTab int @@ -56,7 +58,7 @@ const ( ) func (mt MemoryTab) String() string { - return []string{"概览", "使用分解", "交换分区分析", "系统内存"}[mt] + return []string{i18n.T("Overview"), i18n.T("Usage Breakdown"), i18n.T("Swap Analysis"), i18n.T("System Memory")}[mt] } type PerformanceModel struct { diff --git a/widgets/model/reporting.go b/widgets/model/reporting.go index c372ea4..6476b9f 100644 --- a/widgets/model/reporting.go +++ b/widgets/model/reporting.go @@ -1,6 +1,7 @@ package model import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "os" "path/filepath" @@ -426,19 +427,19 @@ func (rm *ReportModel) getSecurityRecommendations(securityChecks []security.Secu for _, check := range securityChecks { if strings.Contains(check.Status, "Warning") { switch check.Name { - case "SSH 密码认证": - recommendations = append(recommendations, "已启用 SSH 密码认证。建议仅使用 SSH 密钥以提高安全性。") - case "系统更新": - recommendations = append(recommendations, "系统有可用更新。请运行 'sudo apt update && sudo apt upgrade' 应用安全补丁。") - case "系统重启": - recommendations = append(recommendations, "内核更新需要重启系统。请安排维护窗口重启。") + case i18n.T("SSH Password Authentication"): + recommendations = append(recommendations, i18n.T("SSH password authentication is enabled. Consider using SSH keys only for better security.")) + case i18n.T("System Updates"): + recommendations = append(recommendations, i18n.T("System updates are available. Run 'sudo apt update && sudo apt upgrade' to apply security patches.")) + case i18n.T("System Restart"): + recommendations = append(recommendations, i18n.T("System restart required for kernel updates. Schedule a maintenance window to reboot the system.")) } } else if strings.Contains(check.Status, "Failed") { switch check.Name { - case "防火墙状态": - recommendations = append(recommendations, "防火墙未启用。可用 'sudo ufw enable' 开启。") - case "开放端口": - recommendations = append(recommendations, "存在高风险开放端口。请关闭不必要端口以减小攻击面。") + case i18n.T("Firewall Status"): + recommendations = append(recommendations, i18n.T("Firewall is not active. Enable it with 'sudo ufw enable'.")) + case i18n.T("Open Ports"): + recommendations = append(recommendations, i18n.T("Risky ports are open. Close unnecessary ports to reduce attack surface.")) } } } @@ -454,7 +455,7 @@ func (rm *ReportModel) getPerformanceRecommendations(m MonitorModel) []string { if len(m.Processes) > 0 { topProcess := m.Processes[0] recommendations = append(recommendations, - fmt.Sprintf("内存使用率很高 (%.1f%%)。进程 '%s' 占用 %.1f%% 内存。建议优化或增加资源。", + fmt.Sprintf(i18n.T("Memory usage very high (%.1f%%). Process '%s' is consuming %.1f%% memory. Consider optimizing or allocating more resources."), m.Memory.Usage, topProcess.Command, topProcess.Mem)) } else { recommendations = append(recommendations, @@ -495,7 +496,7 @@ func (rm *ReportModel) getDockerRecommendations(m MonitorModel) []string { for _, container := range containers { if container.Health == "unhealthy" { recommendations = append(recommendations, - fmt.Sprintf("容器 '%s' 状态不健康。请用 'docker logs %s' 查看日志。", + fmt.Sprintf(i18n.T("Container '%s' is unhealthy. Check its logs with 'docker logs %s'."), container.Name, container.ID[:12])) } } @@ -511,7 +512,7 @@ func (rm *ReportModel) SaveReport() (string, error) { filename := fmt.Sprintf("report_%s_%s.md", time.Now().Format("2006-01-02_150405"), - "系统") // In practice, you might want to get the actual hostname + i18n.T("system")) // In practice, you might want to get the actual hostname filepath := filepath.Join(rm.ReportDirectory, filename) err := os.WriteFile(filepath, []byte(rm.CurrentReport), 0644) @@ -569,9 +570,9 @@ func formatUptime(uptime uint64) string { if days > 0 { return fmt.Sprintf("%d days, %d hours, %d minutes", days, hours, minutes) } else if hours > 0 { - return fmt.Sprintf("%d 小时 %d 分钟", hours, minutes) + return fmt.Sprintf(i18n.T("%d hours, %d minutes"), hours, minutes) } else { - return fmt.Sprintf("%d 分钟", minutes) + return fmt.Sprintf(i18n.T("%d minutes"), minutes) } } diff --git a/widgets/model/type.go b/widgets/model/type.go index 71140e2..5f10249 100644 --- a/widgets/model/type.go +++ b/widgets/model/type.go @@ -11,7 +11,7 @@ const ( ContainerTabNetwork // ContainerTabDisk ContainerTabEnv - // "网卡", "Connectivity", "Configuration", "Protocol Analysis" + // "Interface", "Connectivity", "Configuration", "Protocol Analysis" // ================================ // NetworkTabInterface ContainerTab = ContainerTabGeneral NetworkTabConnectivity = ContainerTabCPU @@ -19,7 +19,7 @@ const ( NetworkTabProtocol = ContainerTabNetwork // ================================ // - // "Health Checks", "性能", "Logs" + // "Health Checks", "Performances", "Logs" DiagnosticSecurityChecks ContainerTab = ContainerTabGeneral DiagnosticTabPerformances ContainerTab = ContainerTabCPU DiagnosticTabLogs ContainerTab = ContainerTabMemory diff --git a/widgets/model/ui.go b/widgets/model/ui.go index 6d5fe52..f4d0328 100644 --- a/widgets/model/ui.go +++ b/widgets/model/ui.go @@ -10,13 +10,13 @@ type AppState string const ( StateHome AppState = "home" - StateMonitor AppState = "监控" + StateMonitor AppState = "monitor" StateSystem AppState = "monitor.system" StateProcess AppState = "monitor.process" StateContainers AppState = "monitor.containers" StateContainer AppState = "monitor.containers.single" StateContainerLogs AppState = "monitor.containers.logs" - StateDiagnostics AppState = "诊断" + StateDiagnostics AppState = "diagnostics" StateCertificateDetails AppState = "diagnostics.certificate" StateSSHRootDetails AppState = "diagnostics.sshroot" StateOpenedPortsDetails AppState = "diagnostics.openedports" @@ -24,16 +24,16 @@ const ( StateAutoBanDetails AppState = "diagnostics.autoban" StateLogDetails AppState = "diagnostics.logs" StateLogEntryDetails AppState = "diagnostics.logs.entry" - StateNetwork AppState = "网络" + StateNetwork AppState = "network" StateInterfaces AppState = "network.interfaces" StateConnectivity AppState = "network.connectivity" StateConfiguration AppState = "network.configuration" StateProtocolAnalysis AppState = "network.protocol.analysis" - StateReporting AppState = "报告" + StateReporting AppState = "reporting" StateGeneratingReport AppState = "reporting.generating" StateViewingReport AppState = "reporting.viewing" StateSavingReport AppState = "reporting.saving" - StatePerformance AppState = "性能" + StatePerformance AppState = "performance" StateSystemHealth AppState = "performance.systemhealth" StateInputOutput AppState = "performance.inputoutput" StateCPU AppState = "performance.cpu" diff --git a/widgets/render-body.go b/widgets/render-body.go index ee81815..93ebe11 100644 --- a/widgets/render-body.go +++ b/widgets/render-body.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "github.com/Server-Pulse/server-pulse/widgets/model" @@ -45,7 +46,7 @@ func (m Model) renderMainContent() string { case model.StateSystemHealth, model.StateInputOutput, model.StateCPU, model.StateMemory, model.StateQuickTests: currentView = m.renderPerformanceAnalysis() default: - currentView = fmt.Sprintf("未知状态:%v", m.Ui.State) + currentView = fmt.Sprintf(i18n.T("Unknown state: %v"), m.Ui.State) } m.Ui.Viewport.SetContent(currentView) m.Ui.Viewport.SetYOffset(m.Ui.Viewport.YOffset) diff --git a/widgets/render-charts.go b/widgets/render-charts.go index 1c697b1..03759b8 100644 --- a/widgets/render-charts.go +++ b/widgets/render-charts.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "math" @@ -82,23 +83,23 @@ func (m Model) renderNetworkRXChart(width, height int) string { // Get container-specific history if available var points []float64 var maxValue float64 - caption := "网络接收" + caption := i18n.T("Network RX") if m.Monitor.SelectedContainer != nil { if containerHistory, exists := m.Monitor.ContainerHistories[m.Monitor.SelectedContainer.ID]; exists { points = extractValues(containerHistory.NetworkRxHistory.Points) - points, maxValue, caption = getOptimalNetworkScale(points, "RX") + points, maxValue, caption = getOptimalNetworkScale(points, i18n.T("RX")) } } // Fallback to global history if no container-specific data if len(points) < 1 { points = extractValues(m.Monitor.NetworkRxHistory.Points) - points, maxValue, caption = getOptimalNetworkScale(points, "RX") + points, maxValue, caption = getOptimalNetworkScale(points, i18n.T("RX")) } if len(points) < 1 { - return renderEmptyChart("网络接收", height) + return renderEmptyChart(i18n.T("Network RX"), height) } // Ensure minimum scale for visibility @@ -123,23 +124,23 @@ func (m Model) renderNetworkTXChart(width, height int) string { // Get container-specific history if available var points []float64 var maxValue float64 - caption := "网络发送" + caption := i18n.T("Network TX") if m.Monitor.SelectedContainer != nil { if containerHistory, exists := m.Monitor.ContainerHistories[m.Monitor.SelectedContainer.ID]; exists { points = extractValues(containerHistory.NetworkTxHistory.Points) - points, maxValue, caption = getOptimalNetworkScale(points, "TX") + points, maxValue, caption = getOptimalNetworkScale(points, i18n.T("TX")) } } // Fallback to global history if no container-specific data if len(points) < 1 { points = extractValues(m.Monitor.NetworkTxHistory.Points) - points, maxValue, caption = getOptimalNetworkScale(points, "TX") + points, maxValue, caption = getOptimalNetworkScale(points, i18n.T("TX")) } if len(points) < 1 { - return renderEmptyChart("网络发送", height) + return renderEmptyChart(i18n.T("Network TX"), height) } // Ensure minimum scale for visibility @@ -175,18 +176,18 @@ func renderEmptyChart(title string, height int) string { builder += "\n" } - builder += "正在采集数据..." + builder += i18n.T("Collecting data...") return builder } // getOptimalNetworkScale determines the best scale and unit for network data func getOptimalNetworkScale(points []float64, direction string) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "网络 " + direction + " (MB/s)" + return points, 10.0, i18n.T("Network ") + direction + " (MB/s)" } maxValue := getMaxValueFromFloat64(points) - caption := "网络 " + direction + caption := i18n.T("Network ") + direction // Determine the best unit and scale based on the maximum value if maxValue < 0.001 { // Less than 1 KB/s in MB units @@ -234,11 +235,11 @@ func getMaxValueFromFloat64(values []float64) float64 { // getOptimalCPUScale determines the best scale for CPU usage data func getOptimalCPUScale(points []float64) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "CPU 使用率 (%)" + return points, 10.0, i18n.T("CPU Usage (%)") } maxValue := getMaxValueFromFloat64(points) - caption := "CPU 使用率 (%)" + caption := i18n.T("CPU Usage (%)") // Dynamic scaling for low CPU usage if maxValue < 1.0 { // Less than 1% @@ -273,11 +274,11 @@ func getOptimalCPUScale(points []float64) ([]float64, float64, string) { // getOptimalMemoryScale determines the best scale for memory usage data func getOptimalMemoryScale(points []float64) ([]float64, float64, string) { if len(points) == 0 { - return points, 10.0, "内存使用率 (%)" + return points, 10.0, i18n.T("Memory Usage (%)") } maxValue := getMaxValueFromFloat64(points) - caption := "内存使用率 (%)" + caption := i18n.T("Memory Usage (%)") // Dynamic scaling for low memory usage if maxValue < 1.0 { // Less than 1% diff --git a/widgets/render-common.go b/widgets/render-common.go index 75be755..d4cd5a0 100644 --- a/widgets/render-common.go +++ b/widgets/render-common.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" v "github.com/Server-Pulse/server-pulse/widgets/vars" "strings" @@ -25,9 +26,9 @@ func (m Model) renderConfirmationDialog() string { doc.WriteString(v.MetricLabelStyle.Render(m.ConfirmationMessage)) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("确认操作?")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Are you sure?"))) doc.WriteString("\n") - doc.WriteString("按 y 确认,按 n 取消") + doc.WriteString(i18n.T("Press 'y' to confirm or 'n' to cancel")) confirmationStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). diff --git a/widgets/render-container.go b/widgets/render-container.go index 9c2250b..e05c3b5 100644 --- a/widgets/render-container.go +++ b/widgets/render-container.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -21,8 +22,8 @@ func (m Model) renderContainerMenu() string { doc := strings.Builder{} doc.WriteString("CONTAINER MENU\n") - doc.WriteString(fmt.Sprintf("容器: %s\n", m.Monitor.SelectedContainer.Name)) - doc.WriteString(fmt.Sprintf("状态: %s\n", m.Monitor.SelectedContainer.Status)) + doc.WriteString(fmt.Sprintf(i18n.T("Container: %s\n"), m.Monitor.SelectedContainer.Name)) + doc.WriteString(fmt.Sprintf(i18n.T("Status: %s\n"), m.Monitor.SelectedContainer.Status)) doc.WriteString("\n") for i, item := range m.Monitor.ContainerMenuItems { @@ -42,7 +43,7 @@ func (m Model) renderContainerMenu() string { func (m Model) renderContainerSingleView() string { if m.Monitor.SelectedContainer == nil { - return v.CardStyle.Render("未选择容器。") + return v.CardStyle.Render(i18n.T("No container selected.")) } style := lipgloss.NewStyle().Padding(0, 2). Foreground(v.WhiteColor). @@ -67,7 +68,7 @@ func (m Model) renderContainerSingleView() string { case model.ContainerTabEnv: content = m.renderContainerEnv() default: - content = "尚未实现" + content = i18n.T("Not implemented") } return lipgloss.JoinVertical(lipgloss.Left, tabsHeader, content) @@ -83,11 +84,11 @@ func (m Model) renderContainerGeneral() string { containerName = m.Monitor.SelectedContainer.Name } - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(fmt.Sprintf("容器: %s", containerName))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(fmt.Sprintf(i18n.T("Container: %s"), containerName))) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil { - info := fmt.Sprintf("ID: %s\n名称: %s\n镜像: %s\n状态: %s\n项目: %s\n创建时间: %s\n运行时间: %s\n健康: %s\nIP 地址: %s\n网关: %s", + info := fmt.Sprintf(i18n.T("ID: %s\nName: %s\nImage: %s\nStatus: %s\nProject: %s\nCreated: %s\nUptime: %s\nHealth: %s\nIP Address: %s\nGateway: %s"), m.Monitor.ContainerDetails.ID, m.Monitor.ContainerDetails.Name, m.Monitor.ContainerDetails.Image, @@ -121,11 +122,11 @@ func (m Model) renderContainerGeneral() string { if len(m.Monitor.ContainerDetails.Environment) > 0 { doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("环境变量:")) - doc.WriteString(fmt.Sprintf(" %d 个变量", len(m.Monitor.ContainerDetails.Environment))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Environment:"))) + doc.WriteString(fmt.Sprintf(i18n.T(" %d variables"), len(m.Monitor.ContainerDetails.Environment))) } } else { - info := "正在加载容器详情..." + info := i18n.T("Loading container details...") doc.WriteString(v.MetricLabelStyle.Render(info)) } @@ -136,22 +137,22 @@ func (m Model) renderContainerCPU() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("CPU 使用率")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("CPU Usage"))) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { cpuPercent := m.Monitor.ContainerDetails.Stats.CPUPercent - doc.WriteString(fmt.Sprintf("使用率: %.1f%%\n", cpuPercent)) + doc.WriteString(fmt.Sprintf(i18n.T("Usage: %.1f%%\n"), cpuPercent)) // Display progress bar with dynamic color doc.WriteString(renderProgressBar(cpuPercent) + "\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("使用历史:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Usage History:"))) doc.WriteString("\n") chart := m.renderCPUChart(50, 10) doc.WriteString(chart) } else { - doc.WriteString(v.MetricLabelStyle.Render("正在加载 CPU 指标...")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("Loading CPU metrics..."))) } return v.CardStyle.Render(doc.String()) @@ -161,7 +162,7 @@ func (m Model) renderContainerMemory() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("内存使用")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Memory Usage"))) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { @@ -169,18 +170,18 @@ func (m Model) renderContainerMemory() string { memUsage := m.Monitor.ContainerDetails.Stats.MemoryUsage memLimit := m.Monitor.ContainerDetails.Stats.MemoryLimit - doc.WriteString(fmt.Sprintf("使用率: %.1f%% | 已用: %s | 限制: %s | 可用: %s\n\n", memPercent, + doc.WriteString(fmt.Sprintf(i18n.T("Usage: %.1f%% | Used: %s | Limit: %s | Available: %s\n\n"), memPercent, utils.FormatBytes(memUsage), utils.FormatBytes(memLimit), utils.FormatBytes(memLimit-memUsage))) // Display progress bar with dynamic color doc.WriteString(renderProgressBar(memPercent) + "\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("使用历史:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Usage History:"))) doc.WriteString("\n") chart := m.renderMemoryChart(50, 10) doc.WriteString(chart) } else { - doc.WriteString(v.MetricLabelStyle.Render("正在加载内存指标...")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("Loading memory metrics..."))) } return v.CardStyle.Render(doc.String()) @@ -189,15 +190,15 @@ func (m Model) renderContainerMemory() string { func (m Model) renderContainerNetwork() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("网络使用")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Network Usage"))) doc.WriteString("\n") if m.Monitor.ContainerDetails != nil { rxBytes := m.Monitor.ContainerDetails.Stats.NetworkRx txBytes := m.Monitor.ContainerDetails.Stats.NetworkTx - doc.WriteString(fmt.Sprintf("接收总量: %s\n", utils.FormatBytes(rxBytes))) - doc.WriteString(fmt.Sprintf("发送总量: %s\n\n", utils.FormatBytes(txBytes))) + doc.WriteString(fmt.Sprintf(i18n.T("RX Total: %s\n"), utils.FormatBytes(rxBytes))) + doc.WriteString(fmt.Sprintf(i18n.T("TX Total: %s\n\n"), utils.FormatBytes(txBytes))) rxChart := m.renderNetworkRXChart(50, 6) doc.WriteString(rxChart) @@ -206,12 +207,12 @@ func (m Model) renderContainerNetwork() string { doc.WriteString(txChart) if m.Monitor.ContainerDetails.IPAddress != "" { - doc.WriteString("\n\n" + lipgloss.NewStyle().Bold(true).Render("网络接口:")) + doc.WriteString("\n\n" + lipgloss.NewStyle().Bold(true).Render(i18n.T("Network Interfaces:"))) doc.WriteString("\n") doc.WriteString(v.MetricValueStyle.Render(" " + m.Monitor.ContainerDetails.IPAddress)) } } else { - doc.WriteString(v.MetricLabelStyle.Render("正在加载网络指标...")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("Loading network metrics..."))) } return v.CardStyle.Render(doc.String()) @@ -221,15 +222,15 @@ func (m Model) renderContainerNetwork() string { func (m Model) renderContainerDisk() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("磁盘 I/O")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Disk I/O"))) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil { readBytes := m.Monitor.ContainerDetails.Stats.BlockRead writeBytes := m.Monitor.ContainerDetails.Stats.BlockWrite - doc.WriteString(fmt.Sprintf("读取: %s\n", utils.FormatBytes(readBytes))) - doc.WriteString(fmt.Sprintf("写入: %s\n\n", utils.FormatBytes(writeBytes))) + doc.WriteString(fmt.Sprintf(i18n.T("Read: %s\n"), utils.FormatBytes(readBytes))) + doc.WriteString(fmt.Sprintf(i18n.T("Write: %s\n\n"), utils.FormatBytes(writeBytes))) totalIO := readBytes + writeBytes if totalIO > 0 { @@ -246,11 +247,11 @@ func (m Model) renderContainerDisk() string { doc.WriteString(fmt.Sprintf("WRITE [%s] %.1f%%\n", writeBar, writePercent)) } - doc.WriteString("\n" + lipgloss.NewStyle().Bold(true).Render("I/O 历史:")) + doc.WriteString("\n" + lipgloss.NewStyle().Bold(true).Render(i18n.T("I/O History:"))) doc.WriteString("\n") - doc.WriteString("磁盘 I/O 历史图表即将推出...") + doc.WriteString(i18n.T("Disk I/O history charts coming soon...")) } else { - doc.WriteString(v.MetricLabelStyle.Render("正在加载磁盘指标...")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("Loading disk metrics..."))) } return v.CardStyle.Render(doc.String()) @@ -261,7 +262,7 @@ func (m Model) renderContainerEnv() string { doc := strings.Builder{} - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("环境变量")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Environment Variables"))) doc.WriteString("\n\n") if m.Monitor.ContainerDetails != nil && len(m.Monitor.ContainerDetails.Environment) > 0 { @@ -279,9 +280,9 @@ func (m Model) renderContainerEnv() string { } } } else if m.Monitor.ContainerDetails != nil { - doc.WriteString(v.MetricLabelStyle.Render("未找到环境变量")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("No environment variables found"))) } else { - doc.WriteString(v.MetricLabelStyle.Render("正在加载环境变量...")) + doc.WriteString(v.MetricLabelStyle.Render(i18n.T("Loading environment variables..."))) } return v.CardStyle.Render(doc.String()) @@ -289,11 +290,11 @@ func (m Model) renderContainerEnv() string { func (m Model) renderContainerLogs() string { if m.Monitor.SelectedContainer == nil { - return v.CardStyle.Render("未选择容器") + return v.CardStyle.Render(i18n.T("No container selected")) } doc := strings.Builder{} - status := "加载中..." + status := i18n.T("Loading...") if m.Monitor.ContainerLogsStreaming { status = "🟢 Live" } else if !m.Monitor.ContainerLogsLoading { @@ -314,7 +315,7 @@ func (m Model) renderContainerLogs() string { m.Monitor.ContainerLogsPagination.TotalPages))) if m.Monitor.ContainerLogsLoading { - doc.WriteString("\n\n" + v.MetricLabelStyle.Render("正在加载日志...")) + doc.WriteString("\n\n" + v.MetricLabelStyle.Render(i18n.T("Loading logs..."))) } else if len(m.Monitor.ContainerLogsPagination.Lines) > 0 { logStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("250")). @@ -326,7 +327,7 @@ func (m Model) renderContainerLogs() string { m.LogsViewport.Style = logStyle doc.WriteString("\n" + m.LogsViewport.View()) } else { - doc.WriteString("\n\n" + v.MetricLabelStyle.Render("暂无日志")) + doc.WriteString("\n\n" + v.MetricLabelStyle.Render(i18n.T("No logs available"))) } return v.CardStyle.Render(doc.String()) diff --git a/widgets/render-diagnostic.go b/widgets/render-diagnostic.go index 7c18b7e..87b2953 100644 --- a/widgets/render-diagnostic.go +++ b/widgets/render-diagnostic.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -14,23 +15,23 @@ import ( func (m Model) renderLogEntryDetails() string { if m.Diagnostic.SelectedLogEntry == nil { - return vars.CardStyle.Render("未选择日志条目。") + return vars.CardStyle.Render(i18n.T("No log entry selected.")) } entry := m.Diagnostic.SelectedLogEntry var builder strings.Builder // Title - title := lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("日志条目详情") + title := lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Log Entry Details")) builder.WriteString(title) builder.WriteString("\n\n") // Details - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("时间"), entry.Timestamp)) - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("级别"), entry.Level)) - builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render("服务"), entry.Service)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render(i18n.T("Time")), entry.Timestamp)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render(i18n.T("Level")), entry.Level)) + builder.WriteString(fmt.Sprintf("%s: %s\n", vars.MetricLabelStyle.Render(i18n.T("Service")), entry.Service)) builder.WriteString("\n") - builder.WriteString(vars.MetricLabelStyle.Render("消息:")) + builder.WriteString(vars.MetricLabelStyle.Render(i18n.T("Message:"))) builder.WriteString("\n\n") // Message with word wrapping in a bordered box @@ -42,7 +43,7 @@ func (m Model) renderLogEntryDetails() string { builder.WriteString(messageStyle.Render(entry.Message)) builder.WriteString("\n\n") - builder.WriteString(lipgloss.NewStyle().Faint(true).Render("按 b、esc 或 enter 返回。")) + builder.WriteString(lipgloss.NewStyle().Faint(true).Render(i18n.T("Press 'b', 'esc', or 'enter' to go back."))) return vars.CardStyle.Render(builder.String()) } @@ -68,7 +69,7 @@ func (m Model) renderDiagnosticSecurity() string { doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("安全检查")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Security Checks"))) doc.WriteString("\n\n") // Authentication section @@ -82,7 +83,7 @@ func (m Model) renderDiagnosticSecurity() string { doc.WriteString("\n") doc.WriteString(m.Diagnostic.Password.View()) doc.WriteString("\n\n") - doc.WriteString(auth.AuthInfoStyle.Render(auth.AuthInstructions)) + doc.WriteString(auth.AuthInfoStyle.Render(auth.AuthInstructions())) } else { doc.WriteString(auth.AuthInProgressStyle.Render("⏳ " + m.Diagnostic.AuthMessage)) } @@ -98,7 +99,7 @@ func (m Model) renderDiagnosticSecurity() string { doc.WriteString("\n\n") doc.WriteString(m.Diagnostic.AuthMessage) doc.WriteString("\n\n") - doc.WriteString(auth.AuthInfoStyle.Render(auth.AuthRetryMessage)) + doc.WriteString(auth.AuthInfoStyle.Render(auth.AuthRetryMessage())) doc.WriteString("\n\n") } @@ -111,28 +112,28 @@ func (m Model) renderDiagnosticSecurity() string { } if len(m.Diagnostic.SecurityChecks) == 0 { - doc.WriteString("正在加载安全检查...") + doc.WriteString(i18n.T("Loading security checks...")) doc.WriteString("\n\n") return vars.CardStyle.Render(doc.String()) } // Domain input section if m.Diagnostic.DomainInputMode { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("输入域名进行 SSL 检查")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Enter Domain Name for SSL Check"))) doc.WriteString("\n") doc.WriteString(m.Diagnostic.DomainInput.View()) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("按 Enter 检查 SSL,按 Esc 取消")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render(i18n.T("Press Enter to check SSL, Esc to cancel"))) doc.WriteString("\n\n") } else { // Show current domain or prompt to enter one currentDomain := m.Diagnostic.DomainInput.Value() if currentDomain == "" { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 d 输入域名进行 SSL 检查")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("Press 'd' to enter domain name for SSL check"))) } else { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("当前域名: ") + currentDomain) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Current Domain: ")) + currentDomain) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 d 更改域名,按 r 刷新检查")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("Press 'd' to change domain, 'r' to refresh checks"))) } doc.WriteString("\n\n") } @@ -167,7 +168,7 @@ func (m Model) renderDiagnosticSecurity() string { // Footer with authentication info doc.WriteString("\n\n") if !m.AsRoot && !m.CanRunSudo { - doc.WriteString(auth.AccessIndicatorStyle.Render(auth.AdminAccessRequired)) + doc.WriteString(auth.AccessIndicatorStyle.Render(auth.AdminAccessRequired())) } doc.WriteString("\n\n") @@ -177,28 +178,28 @@ func (m Model) renderDiagnosticSecurity() string { func (m Model) renderCertificateDetails() string { if m.Diagnostic.CertificateInfo == nil { - return vars.CardStyle.Render("暂无证书信息") + return vars.CardStyle.Render(i18n.T("No certificate information available")) } cert := m.Diagnostic.CertificateInfo doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSL 证书详情")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("SSL Certificate Details"))) doc.WriteString("\n\n") // Certificate information - doc.WriteString(vars.MetricLabelStyle.Render("主题: ") + cert.Subject + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("颁发者: ") + cert.Issuer + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("序列号: ") + cert.SerialNumber + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("版本: ") + fmt.Sprintf("%d", cert.Version) + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Subject: ")) + cert.Subject + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Issuer: ")) + cert.Issuer + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Serial Number: ")) + cert.SerialNumber + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Version: ")) + fmt.Sprintf("%d", cert.Version) + "\n") doc.WriteString("\n") // Validity period - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("有效期")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Validity Period"))) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("生效时间: ") + cert.ValidityPeriodFrom + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("到期时间: ") + cert.ValidityPeriodTo + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Valid From: ")) + cert.ValidityPeriodFrom + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Valid To: ")) + cert.ValidityPeriodTo + "\n") // Days until expiry with color coding expiryText := "" @@ -209,14 +210,14 @@ func (m Model) renderCertificateDetails() string { } else { expiryText = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Render("✓ " + fmt.Sprintf("%d", cert.DaysUntilExpiry) + " days") } - doc.WriteString(vars.MetricLabelStyle.Render("剩余天数: ") + expiryText + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Days Until Expiry: ")) + expiryText + "\n") doc.WriteString("\n") // Security information - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("安全信息")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Security Information"))) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("算法: ") + cert.Algorithm + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("签名算法: ") + cert.SignatureAlgorithm + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Algorithm: ")) + cert.Algorithm + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Signature Algorithm: ")) + cert.SignatureAlgorithm + "\n") hostnameStatus := "" if cert.HostnameVerified { @@ -224,12 +225,12 @@ func (m Model) renderCertificateDetails() string { } else { hostnameStatus = lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("✗ Not Verified") } - doc.WriteString(vars.MetricLabelStyle.Render("主机名校验: ") + hostnameStatus + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Hostname Verified: ")) + hostnameStatus + "\n") doc.WriteString("\n") // Alternative names if len(cert.AlternativeNames) > 0 { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("备用名称")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Alternative Names"))) doc.WriteString("\n") for _, name := range cert.AlternativeNames { doc.WriteString("• " + name + "\n") @@ -242,31 +243,31 @@ func (m Model) renderCertificateDetails() string { func (m Model) renderSSHRootDetails() string { if m.Diagnostic.SSHRootInfo == nil { - return vars.CardStyle.Render("暂无 SSH Root 登录信息") + return vars.CardStyle.Render(i18n.T("No SSH root login information available")) } sshInfo := m.Diagnostic.SSHRootInfo doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("SSH Root 登录详情")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("SSH Root Login Details"))) doc.WriteString("\n\n") - doc.WriteString(vars.MetricLabelStyle.Render("状态: ") + sshInfo.Status + "\n") - doc.WriteString(vars.MetricLabelStyle.Render("详情: ") + sshInfo.Details + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Status: ")) + sshInfo.Status + "\n") + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Details: ")) + sshInfo.Details + "\n") return vars.CardStyle.Render(doc.String()) } func (m Model) renderOpenedPortsDetails() string { if m.Diagnostic.OpenedPortsInfo == nil { - return vars.CardStyle.Render("暂无开放端口信息") + return vars.CardStyle.Render(i18n.T("No opened ports information available")) } doc := strings.Builder{} // Title - doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render("开放端口详情")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(i18n.T("Opened Ports Details"))) doc.WriteString("\n\n") doc.WriteString(m.Diagnostic.PortsTable.View()) @@ -277,7 +278,7 @@ func (m Model) renderOpenedPortsDetails() string { func (m Model) renderFirewallDetails() string { if m.Diagnostic.FirewallInfo == nil { - return vars.CardStyle.Render("暂无防火墙信息") + return vars.CardStyle.Render(i18n.T("No firewall information available")) } doc := strings.Builder{} @@ -295,10 +296,10 @@ func (m Model) renderFirewallDetails() string { statusStyle = statusStyle.Foreground(lipgloss.Color("196")) // Red } - doc.WriteString(vars.MetricLabelStyle.Render("状态: ")) + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Status: "))) doc.WriteString(statusStyle.Render(m.Diagnostic.FirewallInfo.Status)) doc.WriteString("\n") - doc.WriteString(vars.MetricLabelStyle.Render("详情: ")) + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Details: "))) doc.WriteString(m.Diagnostic.FirewallInfo.Details) doc.WriteString("\n\n") @@ -309,13 +310,13 @@ func (m Model) renderFirewallDetails() string { doc.WriteString(m.Diagnostic.FirewallTable.View()) doc.WriteString("\n\n") } else { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("未配置防火墙规则")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("No firewall rules configured"))) doc.WriteString("\n\n") } // Raw output section (collapsible view) if m.Diagnostic.FirewallInfo.RawOutput != "" { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("完整防火墙配置:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Complete Firewall Configuration:"))) doc.WriteString("\n") doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from firewall command)")) doc.WriteString("\n\n") @@ -336,7 +337,7 @@ func (m Model) renderFirewallDetails() string { func (m Model) renderAutoBanDetails() string { if m.Diagnostic.AutoBanInfo == nil { - return vars.CardStyle.Render("暂无自动封禁信息") + return vars.CardStyle.Render(i18n.T("No auto-ban information available")) } doc := strings.Builder{} @@ -354,17 +355,17 @@ func (m Model) renderAutoBanDetails() string { statusStyle = statusStyle.Foreground(lipgloss.Color("196")) // Red } - doc.WriteString(vars.MetricLabelStyle.Render("状态: ")) + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Status: "))) doc.WriteString(statusStyle.Render(m.Diagnostic.AutoBanInfo.Status)) doc.WriteString("\n") if m.Diagnostic.AutoBanInfo.Version != "" { - doc.WriteString(vars.MetricLabelStyle.Render("版本: ")) + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Version: "))) doc.WriteString(m.Diagnostic.AutoBanInfo.Version) doc.WriteString("\n") } - doc.WriteString(vars.MetricLabelStyle.Render("详情: ")) + doc.WriteString(vars.MetricLabelStyle.Render(i18n.T("Details: "))) doc.WriteString(m.Diagnostic.AutoBanInfo.Details) doc.WriteString("\n\n") @@ -375,13 +376,13 @@ func (m Model) renderAutoBanDetails() string { doc.WriteString(m.Diagnostic.AutoBanTable.View()) doc.WriteString("\n\n") } else { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("未配置 Jail/服务")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("No jails/services configured"))) doc.WriteString("\n\n") } // Raw output section if m.Diagnostic.AutoBanInfo.RawOutput != "" { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("完整配置:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Complete Configuration:"))) doc.WriteString("\n") doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from service)")) doc.WriteString("\n\n") @@ -404,7 +405,7 @@ func (m Model) renderDiagnosticLogs() string { // Custom time input mode - show popup if m.Diagnostic.CustomTimeInputMode { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("输入自定义时间范围")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Enter Custom Time Range"))) doc.WriteString("\n") doc.WriteString(m.Diagnostic.LogTimeRangeInput.View()) doc.WriteString("\n\n") @@ -418,9 +419,9 @@ func (m Model) renderDiagnosticLogs() string { doc.WriteString("\n\n") } - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("示例:'2 hours ago'、'2025-01-08 14:30:00'、'3 days ago'")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render(i18n.T("Examples: '2 hours ago', '2025-01-08 14:30:00', '3 days ago'"))) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Faint(true).Render("按 Enter 应用,按 ESC 取消")) + doc.WriteString(lipgloss.NewStyle().Faint(true).Render(i18n.T("Press Enter to apply, ESC to cancel"))) return vars.CardStyle.Render(doc.String()) } @@ -435,8 +436,8 @@ func (m Model) renderDiagnosticLogs() string { // Filter selection indicator filterIndicatorStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("214")) - timeIndicator := "时间" - levelIndicator := "级别" + timeIndicator := i18n.T("Time") + levelIndicator := i18n.T("Level") if m.Diagnostic.LogFilterSelected == 0 { timeIndicator = filterIndicatorStyle.Render("▶ Time") } else { @@ -478,13 +479,13 @@ func (m Model) renderDiagnosticLogs() string { // Log entries table or loading message if m.Diagnostic.LogsInfo == nil { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("按 r 加载日志,或按 Enter 应用筛选")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("Press 'r' to load logs or Enter to apply filters"))) doc.WriteString("\n") } else if m.Diagnostic.LogsInfo.ErrorMsg != "" { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render("错误: " + m.Diagnostic.LogsInfo.ErrorMsg)) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Render(i18n.T("Error: ") + m.Diagnostic.LogsInfo.ErrorMsg)) doc.WriteString("\n") } else if len(m.Diagnostic.LogsInfo.Entries) == 0 { - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("没有匹配筛选条件的日志")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("No log entries found matching filters"))) doc.WriteString("\n") } else { // Display logs table @@ -557,15 +558,15 @@ func (m Model) renderPerformanceAnalysis() string { func (m Model) renderCPUPerformance() string { if m.Diagnostic.Performance.CPULoading { - return vars.CardStyle.Render("⏳ 正在加载 CPU 性能指标...") + return vars.CardStyle.Render(i18n.T("⏳ Loading CPU Performance Metrics...")) } if m.Diagnostic.Performance.CPUMetrics == nil { - return vars.CardStyle.Render("按 r 加载 CPU 指标") + return vars.CardStyle.Render(i18n.T("Press 'r' to load CPU metrics")) } // CPU sub-tabs navigation - cpuTabs := []string{"CPU 状态分解", "每核性能", "系统活动指标"} + cpuTabs := []string{i18n.T("CPU State Breakdown"), i18n.T("Per-Core Performance"), i18n.T("System Activity Metrics")} activeTabStyle := lipgloss.NewStyle().Padding(0, 2). Foreground(lipgloss.Color("240")). @@ -609,11 +610,11 @@ func (m Model) renderMemoryPerformance() string { } if m.Diagnostic.Performance.MemoryMetrics == nil { - return vars.CardStyle.Render("按 r 加载内存指标") + return vars.CardStyle.Render(i18n.T("Press 'r' to load memory metrics")) } // Memory sub-tabs navigation - memoryTabs := []string{"概览", "使用分解", "交换分区分析", "系统内存"} + memoryTabs := []string{i18n.T("Overview"), i18n.T("Usage Breakdown"), i18n.T("Swap Analysis"), i18n.T("System Memory")} activeTabStyle := lipgloss.NewStyle().Padding(0, 2). Foreground(lipgloss.Color("240")). diff --git a/widgets/render-footer.go b/widgets/render-footer.go index fec7729..3175ade 100644 --- a/widgets/render-footer.go +++ b/widgets/render-footer.go @@ -19,7 +19,7 @@ func (m Model) renderFooter() string { statusLine += statusStyle.Render("⏳ Operation in progress...") + "\n" } else if m.LastOperationMsg != "" { var statusStyle lipgloss.Style - if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") || strings.Contains(m.LastOperationMsg, "失败") || strings.Contains(m.LastOperationMsg, "错误") { + if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") { statusStyle = lipgloss.NewStyle(). Foreground(v.WhiteColor). Background(v.ErrorColor). diff --git a/widgets/render-header.go b/widgets/render-header.go index 5408de6..c62b67b 100644 --- a/widgets/render-header.go +++ b/widgets/render-header.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" v "github.com/Server-Pulse/server-pulse/widgets/vars" "strings" @@ -40,7 +41,7 @@ func (m Model) renderHeader() string { } doc := strings.Builder{} - systemInfo := fmt.Sprintf("主机: %s | 系统: %s | 内核: %s | 运行时间: %s", m.Monitor.System.Hostname, m.Monitor.System.OS, m.Monitor.System.Kernel, utils.FormatUptime(m.Monitor.System.Uptime)) + systemInfo := fmt.Sprintf(i18n.T("Host: %s | OS: %s | Kernel: %s | Uptime: %s"), m.Monitor.System.Hostname, m.Monitor.System.OS, m.Monitor.System.Kernel, utils.FormatUptime(m.Monitor.System.Uptime)) doc.WriteString(v.MetricLabelStyle.Render(systemInfo)) header = append(header, v.CardStyle.MarginBottom(0).Render(doc.String())) diff --git a/widgets/render-monitor.go b/widgets/render-monitor.go index 268ced6..8c8f4e2 100644 --- a/widgets/render-monitor.go +++ b/widgets/render-monitor.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -22,31 +23,31 @@ func (m Model) renderMonitor() string { } func (m Model) renderContainers() string { - p := "搜索容器..." + p := i18n.T("Search a container...") return m.renderTable(m.Monitor.Container, p) } func (m Model) renderProcesses() string { - p := "搜索进程..." + p := i18n.T("Search a process...") return m.renderTable(m.Monitor.ProcessTable, p) } func (m Model) renderSystem() string { doc := strings.Builder{} - cpuInfo := fmt.Sprintf("CPU: %s %.1f%% | 负载: %.2f, %.2f, %.2f", m.Monitor.CpuProgress.View(), m.Monitor.Cpu.Usage, m.Monitor.Cpu.LoadAvg1, m.Monitor.Cpu.LoadAvg5, m.Monitor.Cpu.LoadAvg15) + cpuInfo := fmt.Sprintf(i18n.T("CPU: %s %.1f%% | Load: %.2f, %.2f, %.2f"), m.Monitor.CpuProgress.View(), m.Monitor.Cpu.Usage, m.Monitor.Cpu.LoadAvg1, m.Monitor.Cpu.LoadAvg5, m.Monitor.Cpu.LoadAvg15) doc.WriteString(lipgloss.NewStyle().Bold(true).Render("CPU")) doc.WriteString("\n") doc.WriteString(cpuInfo) doc.WriteString("\n\n") - memInfo := fmt.Sprintf("内存: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", m.Monitor.MemProgress.View(), m.Monitor.Memory.Usage, utils.FormatBytes(m.Monitor.Memory.Total), utils.FormatBytes(m.Monitor.Memory.Used), utils.FormatBytes(m.Monitor.Memory.Free)) - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("内存")) + memInfo := fmt.Sprintf(i18n.T("RAM: %s %.1f%% | Total: %s | Used: %s | Free: %s"), m.Monitor.MemProgress.View(), m.Monitor.Memory.Usage, utils.FormatBytes(m.Monitor.Memory.Total), utils.FormatBytes(m.Monitor.Memory.Used), utils.FormatBytes(m.Monitor.Memory.Free)) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Memory"))) doc.WriteString("\n") doc.WriteString(memInfo) doc.WriteString("\n") - swapInfo := fmt.Sprintf("交换: %s %.1f%% | 总量: %s | 已用: %s | 空闲: %s", m.Monitor.SwapProgress.View(), m.Monitor.Memory.SwapUsage, utils.FormatBytes(m.Monitor.Memory.SwapTotal), utils.FormatBytes(m.Monitor.Memory.SwapUsed), utils.FormatBytes(m.Monitor.Memory.SwapFree)) + swapInfo := fmt.Sprintf(i18n.T("SWP: %s %.1f%% | Total: %s | Used: %s | Free: %s"), m.Monitor.SwapProgress.View(), m.Monitor.Memory.SwapUsage, utils.FormatBytes(m.Monitor.Memory.SwapTotal), utils.FormatBytes(m.Monitor.Memory.SwapUsed), utils.FormatBytes(m.Monitor.Memory.SwapFree)) doc.WriteString(swapInfo) doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("磁盘")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Disks"))) doc.WriteString("\n") for _, disk := range m.Monitor.Disks { if disk.Total > 0 { diff --git a/widgets/render-nav.go b/widgets/render-nav.go index 70d8602..9712735 100644 --- a/widgets/render-nav.go +++ b/widgets/render-nav.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/widgets/model" @@ -26,7 +27,7 @@ func renderNav(header []string, state model.ContainerTab, styleColor lipgloss.St } func (m Model) renderCurrentNav() string { - if strings.HasPrefix(string(m.Ui.State), "监控") { + if strings.HasPrefix(string(m.Ui.State), i18n.T("monitor")) { style := lipgloss.NewStyle().Padding(0, 2). Foreground(v.ClearWhite). Background(v.PurpleCollor). diff --git a/widgets/render-network.go b/widgets/render-network.go index e3258f5..da8320b 100644 --- a/widgets/render-network.go +++ b/widgets/render-network.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -15,12 +16,12 @@ func (m Model) interfaces() string { content := strings.Builder{} statusIcon := "🔴" - statusText := "已断开" + statusText := i18n.T("Disconnected") statusColor := v.ErrorColor if m.Network.NetworkResource.Connected { statusIcon = "🟢" - statusText = "已连接" + statusText = i18n.T("Connected") statusColor = v.SuccessColor } @@ -30,7 +31,7 @@ func (m Model) interfaces() string { content.WriteString(statusLine) if len(m.Network.NetworkResource.Interfaces) > 0 { - tableContent := m.renderTable(m.Network.NetworkTable, "无网络接口") + tableContent := m.renderTable(m.Network.NetworkTable, i18n.T("No network interfaces")) content.WriteString(tableContent) } @@ -68,11 +69,11 @@ func (m Model) renderProtocolAnalysis() string { content.WriteString("\n\n") content.WriteString(m.Network.AuthMessage) if m.Network.AuthState == model.AuthRequired { - content.WriteString(auth.AuthPromptStyle.Render("输入密码:")) + content.WriteString(auth.AuthPromptStyle.Render(i18n.T("Enter Password:"))) content.WriteString("\n") content.WriteString(m.Diagnostic.Password.View()) content.WriteString("\n\n") - content.WriteString(auth.AuthInfoStyle.Render(auth.AuthInstructions)) + content.WriteString(auth.AuthInfoStyle.Render(auth.AuthInstructions())) } else { content.WriteString(auth.AuthInProgressStyle.Render("⏳ " + m.Network.AuthMessage)) } @@ -88,7 +89,7 @@ func (m Model) renderProtocolAnalysis() string { content.WriteString("\n\n") content.WriteString(m.Network.AuthMessage) content.WriteString("\n\n") - content.WriteString(auth.AuthInfoStyle.Render(auth.AuthRetryMessage)) + content.WriteString(auth.AuthInfoStyle.Render(auth.AuthRetryMessage())) } if m.Network.AuthState == model.AuthSuccess && m.Network.AuthTimer > 0 { @@ -107,7 +108,7 @@ func (m Model) renderProtocolAnalysis() string { stats := m.getConnectionStats() statsText := lipgloss.NewStyle(). Foreground(lipgloss.Color("229")). - Render(fmt.Sprintf("TCP: %d | UDP: %d | 监听: %d | 已建立: %d", + Render(fmt.Sprintf(i18n.T("TCP: %d | UDP: %d | Listening: %d | Established: %d"), stats.tcp, stats.udp, stats.listening, stats.established)) content.WriteString(statsText + "\n") } @@ -122,10 +123,10 @@ func (m Model) renderProtocolAnalysis() string { content.WriteString(accessInfo + "\n") content.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("244")). - Render("按 a 认证以查看详细连接信息")) + Render(i18n.T("Press 'a' to authenticate for detailed connection information"))) } - tableContent := m.renderTable(m.Network.ConnectionsTable, "无活动连接") + tableContent := m.renderTable(m.Network.ConnectionsTable, i18n.T("No active connections")) content.WriteString(tableContent) return v.CardNetworkStyle. @@ -141,12 +142,12 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Foreground(lipgloss.Color("62")). MarginBottom(1). - Render("网络连通性工具") + Render(i18n.T("Network Connectivity Tools")) content.WriteString(header + "\n") instructions := lipgloss.NewStyle(). Foreground(lipgloss.Color("240")). - Render("按 p 进行 Ping,按 t 路由追踪,按 s 测速,按 c 清除结果") + Render(i18n.T("Press 'p' to ping, 't' to traceroute, 's' for speed test, 'c' to clear results")) content.WriteString(instructions + "\n\n") // Input modes - always visible @@ -189,7 +190,7 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Underline(true). Foreground(lipgloss.Color("39")). - Render("Ping 结果") + Render(i18n.T("Ping Results")) resultsContent.WriteString(pingTitle + "\n") for _, result := range m.Network.PingResults { @@ -202,7 +203,7 @@ func (m Model) renderConnectivityAnalysis() string { statusLine := fmt.Sprintf("%s %s - ", statusIcon, result.Target) if result.Success { - statusLine += fmt.Sprintf("延迟: %v,丢包率: %.1f%%", + statusLine += fmt.Sprintf(i18n.T("Latency: %v, Packet Loss: %.1f%%"), result.Latency, result.PacketLoss) } else { statusLine += fmt.Sprintf("Error: %s", result.Error) @@ -220,11 +221,11 @@ func (m Model) renderConnectivityAnalysis() string { Bold(true). Underline(true). Foreground(lipgloss.Color("208")). - Render(fmt.Sprintf("路由追踪:%s", tracerouteResult.Target)) + Render(fmt.Sprintf(i18n.T("Traceroute to %s"), tracerouteResult.Target)) resultsContent.WriteString(tracerouteTitle + "\n") if tracerouteResult.Error != "" { - resultsContent.WriteString(lipgloss.NewStyle().Foreground(v.ErrorColor).Render("Error: "+tracerouteResult.Error) + "\n") + resultsContent.WriteString(lipgloss.NewStyle().Foreground(v.ErrorColor).Render(i18n.T("Error: ")+tracerouteResult.Error) + "\n") } else if len(tracerouteResult.Hops) > 0 { for _, hop := range tracerouteResult.Hops { hopLine := fmt.Sprintf("%2d. ", hop.HopNumber) @@ -245,7 +246,7 @@ func (m Model) renderConnectivityAnalysis() string { resultsContent.WriteString(hopLine + "\n") } } else { - resultsContent.WriteString("未找到路由\n") + resultsContent.WriteString(i18n.T("No route found\n")) } resultsContent.WriteString("\n") } @@ -261,8 +262,8 @@ func (m Model) renderConnectivityAnalysis() string { Render("🚀 Speed Test Results") resultsContent.WriteString(speedTestTitle + "\n") - resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("服务器: %s", result.Server)) + "\n") - resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf("测试时长: %v", result.TestDuration)) + "\n\n") + resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf(i18n.T("Server: %s"), result.Server)) + "\n") + resultsContent.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(fmt.Sprintf(i18n.T("Test Duration: %v"), result.TestDuration)) + "\n\n") if result.PingResult != nil { pingSection := lipgloss.NewStyle(). @@ -313,7 +314,7 @@ func (m Model) renderConnectivityAnalysis() string { if totalResultsLines > m.Network.ConnectivityPerPage { paginationInstructions := lipgloss.NewStyle(). Foreground(lipgloss.Color("214")). - Render("用 ↑ ↓ 翻页查看结果") + Render(i18n.T("Use '↑' and '↓' to navigate results pages")) content.WriteString(paginationInstructions + "\n\n") } @@ -328,7 +329,7 @@ func (m Model) renderConnectivityAnalysis() string { // Add pagination info if needed if totalPages > 1 { - paginationInfo := fmt.Sprintf("第 %d/%d 页(结果 %d-%d / 共 %d)", + paginationInfo := fmt.Sprintf(i18n.T("Page %d/%d (Results %d-%d of %d)"), m.Network.ConnectivityPage+1, totalPages, startIdx+1, endIdx, totalResultsLines) paginationStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("244")). @@ -379,7 +380,7 @@ func (m Model) renderNetworkConfiguration() string { if len(m.Network.Routes) > 0 { content.WriteString(m.Network.RoutesTable.View()) } else { - content.WriteString("未找到路由表项") + content.WriteString(i18n.T("No routing table entries found")) } } else { dnsTitle := lipgloss.NewStyle(). @@ -393,7 +394,7 @@ func (m Model) renderNetworkConfiguration() string { if len(m.Network.DNS) > 0 { content.WriteString(m.Network.DNSTable.View()) } else { - content.WriteString("未配置 DNS 服务器") + content.WriteString(i18n.T("No DNS servers configured")) } } diff --git a/widgets/render-reporting.go b/widgets/render-reporting.go index 0743c05..e5fe4e5 100644 --- a/widgets/render-reporting.go +++ b/widgets/render-reporting.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "strings" @@ -26,7 +27,7 @@ func (m Model) renderReporting() string { Foreground(lipgloss.Color("241")). Italic(true) - content.WriteString(instructions.Render("按 g 生成新报告,按 l 加载已保存报告")) + content.WriteString(instructions.Render(i18n.T("Press 'g' to generate a new report, 'l' to load saved reports"))) content.WriteString("\n\n") // Report status @@ -59,7 +60,7 @@ func (m Model) renderReportMenu() string { Foreground(lipgloss.Color("39")). Italic(true) - content.WriteString(infoStyle.Render(fmt.Sprintf("上次生成报告:%s", + content.WriteString(infoStyle.Render(fmt.Sprintf(i18n.T("Last report generated: %s"), m.Reporting.LastGenerated.Format("2006-01-02 15:04:05")))) content.WriteString("\n\n") } @@ -69,15 +70,15 @@ func (m Model) renderReportMenu() string { Foreground(lipgloss.Color("228")). Bold(true) - content.WriteString(actionStyle.Render("可用操作:")) + content.WriteString(actionStyle.Render(i18n.T("Available Actions:"))) content.WriteString("\n\n") actions := []struct { key string description string }{ - {"g", "生成新的系统健康报告"}, - {"l", "加载并查看已保存报告"}, + {"g", i18n.T("Generate new system health report")}, + {"l", i18n.T("Load and view saved reports")}, } for _, action := range actions { @@ -97,7 +98,7 @@ func (m Model) renderSavedReports() string { Foreground(lipgloss.Color("228")). Bold(true) - content.WriteString(actionStyle.Render("已保存报告:")) + content.WriteString(actionStyle.Render(i18n.T("Saved Reports:"))) content.WriteString("\n\n") m.Reporting.RefreshSavedReports() @@ -122,11 +123,11 @@ func (m Model) renderSavedReports() string { } content.WriteString("\n") - content.WriteString(instructionsStyle().Render("按 Enter 查看选中报告,按 d 删除,按 b 返回")) + content.WriteString(instructionsStyle().Render(i18n.T("Press ENTER to view selected report, 'd' to delete, 'b' to go back"))) } else { - content.WriteString(instructionsStyle().Render("没有已保存报告,请先生成报告。")) + content.WriteString(instructionsStyle().Render(i18n.T("No saved reports found. Generate a report first."))) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("按 b 返回")) + content.WriteString(instructionsStyle().Render(i18n.T("Press 'b' to go back"))) } return content.String() @@ -143,7 +144,7 @@ func (m Model) renderGeneratingReport() string { content.WriteString(spinnerStyle.Render("🔄 Generating System Health Report...")) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("正在采集系统数据,请稍候...")) + content.WriteString(instructionsStyle().Render(i18n.T("Please wait while we collect system data..."))) content.WriteString("\n\n") // Show what's being collected @@ -174,7 +175,7 @@ func (m Model) renderSavingReport() string { content.WriteString(savingStyle.Render("💾 Saving Report...")) content.WriteString("\n\n") - content.WriteString(instructionsStyle().Render("正在将当前报告保存到文件...")) + content.WriteString(instructionsStyle().Render(i18n.T("Saving the current report to file..."))) return content.String() } @@ -196,7 +197,7 @@ func (m Model) renderViewingReport() string { Foreground(lipgloss.Color("228")). Italic(true) - content.WriteString(navStyle.Render("用方向键滚动,按 q 返回报告菜单,按 s 保存")) + content.WriteString(navStyle.Render(i18n.T("Use arrow keys to scroll, 'q' to return to report menu, 's' to save"))) content.WriteString("\n\n") // Report content in viewport @@ -208,7 +209,7 @@ func (m Model) renderViewingReport() string { } else { content.WriteString(lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). - Render("暂无报告内容,请先生成报告。")) + Render(i18n.T("No report content available. Generate a report first."))) } return content.String() diff --git a/widgets/utils.go b/widgets/utils.go index 191d222..cbf63b7 100644 --- a/widgets/utils.go +++ b/widgets/utils.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "math/rand" @@ -60,10 +61,10 @@ func canRunSudo() bool { // getAdminRequiredChecks returns the list of diagnostic checks that require admin privileges func (m Model) getAdminRequiredChecks() []string { return []string{ - "开放端口", - "SSH Root 登录", - "防火墙状态", - "系统更新", + i18n.T("Open Ports"), + i18n.T("SSH Root Login"), + i18n.T("Firewall Status"), + i18n.T("System Updates"), } } diff --git a/widgets/vars/vars.go b/widgets/vars/vars.go index a6270c3..ae09187 100644 --- a/widgets/vars/vars.go +++ b/widgets/vars/vars.go @@ -1,6 +1,7 @@ package vars import ( + "github.com/Server-Pulse/server-pulse/i18n" model "github.com/Server-Pulse/server-pulse/widgets/model" "github.com/charmbracelet/bubbles/spinner" ) @@ -21,28 +22,32 @@ const ( ContainerViewConfirmation ) +// English source labels. Always go through Localized* so language switching works. var ( - dashboard = []string{"监控", "诊断", "网络", "报告"} - monitor = []string{"系统", "进程", "容器"} - Menu = model.Menu{ - DashBoard: dashboard, - Monitor: monitor, - } - NetworkNav = []string{"网卡", "连通性", "配置", "协议分析"} - DiagnosticNav = []string{"安全检查", "性能", "日志"} - - ContainerMenuItems = []model.ContainerMenuItem{ - {Key: "o", Label: "打开详情", Description: "查看容器详细信息", Action: "open_single"}, - {Key: "l", Label: "查看日志", Description: "显示容器日志", Action: "logs"}, - {Key: "r", Label: "重启", Description: "重启容器", Action: "restart"}, - {Key: "d", Label: "删除", Description: "删除容器", Action: "delete"}, - {Key: "x", Label: "强制删除", Description: "强制移除容器", Action: "remove"}, - {Key: "s", Label: "停止/启动", Description: "切换容器运行状态", Action: "toggle_start"}, - {Key: "p", Label: "暂停/恢复", Description: "切换暂停状态", Action: "toggle_pause"}, - {Key: "e", Label: "进入终端", Description: "打开交互式 Shell", Action: "exec"}, + dashboardEN = []string{"Monitor", "Diagnostic", "Network", "Reporting"} + monitorEN = []string{"System", "Process", "Containers"} + + NetworkNavEN = []string{"Interface", "Connectivity", "Configuration", "Protocol Analysis"} + DiagnosticNavEN = []string{"Security Checks", "Performances", "Logs"} + ContainerTabsEN = []string{"General", "CPU", "MEM", "NET", "ENV"} + + containerMenuItemsEN = []model.ContainerMenuItem{ + {Key: "o", Label: "Open detailed view", Description: "View detailed container information", Action: "open_single"}, + {Key: "l", Label: "View logs", Description: "Show container logs", Action: "logs"}, + {Key: "r", Label: "Restart", Description: "Restart container", Action: "restart"}, + {Key: "d", Label: "Delete", Description: "Remove container", Action: "delete"}, + {Key: "x", Label: "Remove", Description: "Force remove container", Action: "remove"}, + {Key: "s", Label: "Stop/Start", Description: "Toggle container state", Action: "toggle_start"}, + {Key: "p", Label: "Pause/Resume", Description: "Toggle pause state", Action: "toggle_pause"}, + {Key: "e", Label: "Exec shell", Description: "Open interactive shell", Action: "exec"}, } - ContainerTabs = []string{"概览", "CPU", "内存", "网络", "环境变量"} // disk remove + // Back-compat aliases (English). Prefer Localized* at runtime. + NetworkNav = NetworkNavEN + DiagnosticNav = DiagnosticNavEN + ContainerTabs = ContainerTabsEN + ContainerMenuItems = containerMenuItemsEN + Menu = model.Menu{DashBoard: dashboardEN, Monitor: monitorEN} Spinners = []spinner.Spinner{ spinner.Line, @@ -56,3 +61,39 @@ var ( spinner.Monkey, } ) + +func LocalizedMenu() model.Menu { + return model.Menu{ + DashBoard: i18n.Ts(dashboardEN...), + Monitor: i18n.Ts(monitorEN...), + } +} + +func LocalizedNetworkNav() []string { + return i18n.Ts(NetworkNavEN...) +} + +func LocalizedDiagnosticNav() []string { + return i18n.Ts(DiagnosticNavEN...) +} + +func LocalizedContainerTabs() []string { + return i18n.Ts(ContainerTabsEN...) +} + +func LocalizedContainerMenuItems() []model.ContainerMenuItem { + items := make([]model.ContainerMenuItem, len(containerMenuItemsEN)) + for i, it := range containerMenuItemsEN { + items[i] = model.ContainerMenuItem{ + Key: it.Key, + Label: i18n.T(it.Label), + Description: i18n.T(it.Description), + Action: it.Action, + } + } + return items +} + +func LocalizedPerformanceNav() []string { + return i18n.Ts("System Health", "I/O", "CPU", "Memory") +} diff --git a/widgets/view.go b/widgets/view.go index 1906b3c..516fc39 100644 --- a/widgets/view.go +++ b/widgets/view.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" v "github.com/Server-Pulse/server-pulse/widgets/vars" @@ -17,7 +18,7 @@ func (m Model) View() string { return lipgloss.NewStyle(). Foreground(lipgloss.Color("196")). Bold(true). - Render("终端窗口太小!\n最小尺寸:80x20\n当前:" + + Render(i18n.T("Terminal too small!\nMinimum size: (min: 80x20)\nCurrent: ") + fmt.Sprintf("%dx%d", m.Ui.Width, m.Ui.Height)) } diff --git a/widgets/widgets.go b/widgets/widgets.go index 6853566..4ad9ea2 100644 --- a/widgets/widgets.go +++ b/widgets/widgets.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/Server-Pulse/server-pulse/i18n" "fmt" "sort" "strings" @@ -175,14 +176,14 @@ func (m *Model) updateNetworkTable() tea.Cmd { var rows []table.Row for _, iface := range m.Network.NetworkResource.Interfaces { - statusText := "断开" + statusText := i18n.T("DOWN") if iface.Status == "up" { statusText = "UP" } ips := strings.Join(iface.IPs, ", ") if ips == "" { - ips = "无 IP" + ips = i18n.T("No IP") } rows = append(rows, table.Row{ From 647783524eb15916f60fda318174ef01f970d83a Mon Sep 17 00:00:00 2001 From: zhangyangrui <740884666@qq.com> Date: Fri, 24 Jul 2026 20:52:58 +0800 Subject: [PATCH 3/3] fix: show language toggle shortcut in home help bar Include L / Ctrl+L in the home and other ShortHelp footers so the language switch is discoverable without opening full help. Co-authored-by: Cursor --- README.md | 2 +- i18n/i18n.go | 12 +-- i18n/zh.go | 143 +++++++++++++++++++++-------- main.go | 4 +- system/logs/logs.go | 2 +- system/network/connectivity.go | 19 ++-- system/network/dns.go | 3 +- system/network/speedtest.go | 27 +++--- system/performance/cpu.go | 2 +- system/performance/io.go | 30 +++--- system/performance/memory.go | 4 +- system/performance/systemHealth.go | 18 ++-- system/performance/view.go | 2 +- system/security/SystemRestart.go | 10 +- system/security/SystemUpdates.go | 12 +-- system/security/autoBane.go | 30 +++--- system/security/firewall.go | 12 +-- system/security/passwordPolicy.go | 4 +- system/security/ports.go | 22 ++--- system/security/sshPassword.go | 6 +- system/security/sshRoot.go | 10 +- system/security/ssl.go | 10 +- utils/test/utils_test.go | 22 ++--- utils/utils.go | 8 +- widgets/handles-common.go | 4 +- widgets/handles.go | 6 +- widgets/help.go | 37 +++++--- widgets/init.go | 2 +- widgets/model/performance.go | 3 +- widgets/model/reporting.go | 2 +- widgets/render-body.go | 2 +- widgets/render-charts.go | 2 +- widgets/render-common.go | 2 +- widgets/render-container.go | 6 +- widgets/render-diagnostic.go | 24 ++--- widgets/render-footer.go | 6 +- widgets/render-header.go | 2 +- widgets/render-monitor.go | 2 +- widgets/render-network.go | 10 +- widgets/render-reporting.go | 2 +- widgets/utils.go | 2 +- widgets/view.go | 2 +- widgets/widgets.go | 2 +- 43 files changed, 306 insertions(+), 226 deletions(-) diff --git a/README.md b/README.md index e76c826..a3e8081 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A real-time Terminal User Interface (TUI) for system monitoring, Docker containe |-----|--------| | `L` or `Ctrl+L` | Toggle English ↔ 中文 | -Default language is English. After switching, the choice is persisted. +Default language is Simplified Chinese (中文). After switching, the choice is persisted. ## Requirements diff --git a/i18n/i18n.go b/i18n/i18n.go index 818adc9..0ce0eaa 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -16,7 +16,7 @@ const ( var ( mu sync.RWMutex - current = En + current = Zh ) type configFile struct { @@ -31,7 +31,7 @@ func Current() Lang { func SetLang(lang Lang) { if lang != En && lang != Zh { - lang = En + lang = Zh } mu.Lock() current = lang @@ -100,13 +100,13 @@ func Load() { return } switch Lang(cfg.Language) { - case Zh: + case En: mu.Lock() - current = Zh + current = En mu.Unlock() - case En, "": + case Zh, "": mu.Lock() - current = En + current = Zh mu.Unlock() } } diff --git a/i18n/zh.go b/i18n/zh.go index e44a55a..79fef5a 100644 --- a/i18n/zh.go +++ b/i18n/zh.go @@ -3,6 +3,40 @@ package i18n // Code generated from EN/ZH UI map. DO NOT edit by hand unless needed. var zh = map[string]string{ + "All": "全部", + "\nRestoring terminal after panic...": "\n程序崩溃后正在恢复终端...", + "Shell execution failed: %v\n": "Shell 执行失败:%v\n", + "Custom": "自定义", + "Error": "错误", + "Warn": "警告", + "Info": "信息", + "Debug": "调试", + "Routing Table": "路由表", + "Ports:": "端口:", + "Command:": "命令:", + "\nCurrently viewing: %s | Press SPACE to switch": "\n当前查看:%s | 按空格切换", + "Traceroute is not installed. Would you like to install it?": "尚未安装 Traceroute,是否安装?", + "Traceroute installed successfully. Running traceroute...": "Traceroute 安装成功,正在运行...", + "Installation failed: ": "安装失败:", + "Installation failed: %v\n%s": "安装失败:%v\n%s", + "Invalid target: must be a valid hostname or IP address": "目标无效:必须是有效的主机名或 IP 地址", + "Ping failed: %v": "Ping 失败:%v", + "No response from target": "目标无响应", + "traceroute failed: %v": "路由追踪失败:%v", + "Could not detect package manager. Please install traceroute manually.": "未能识别包管理器,请手动安装 traceroute。", + "Incorrect password. Please try again.": "密码错误,请重试。", + "Server selection failed: %v": "服务器选择失败:%v", + "Ping test failed: %v": "Ping 测试失败:%v", + "Download test failed: %v": "下载测试失败:%v", + "Upload test failed: %v": "上传测试失败:%v", + "Selecting server...": "正在选择服务器...", + "Testing latency...": "正在测试延迟...", + "Testing download speed...": "正在测试下载速度...", + "Testing upload speed...": "正在测试上传速度...", + "Speed test completed": "测速完成", + "The 'docker' command was not found. Please ensure Docker is installed.": "未找到 'docker' 命令,请确保已安装 Docker。", + "The user has permissions to run Docker.": "当前用户拥有运行 Docker 的权限。", + "The user is not in the 'docker' group. To add them, run 'sudo usermod -aG docker ' and then log out and log back in.": "当前用户不在 'docker' 组中。可运行 'sudo usermod -aG docker <你的用户名>' 添加,然后重新登录。", " %d variables": " %d 个变量", " No core data available\\n\\n": " 暂无核心数据\\n\\n", " Overall CPU Usage: %s\\n": " CPU 总体使用率: %s\\n", @@ -11,33 +45,43 @@ var zh = map[string]string{ "%d hours, %d minutes": "%d 小时 %d 分钟", "%d minutes": "%d 分钟", "0 minutes": "0 分钟", + "⏳ Operation in progress...": "⏳ 操作进行中...", "1 days, 1 hours": "1 天 1 小时", "1 hours, 1 minutes": "1 小时 1 分钟", "2 days, 0 hours": "2 天 0 小时", "5 minutes": "5 分钟", "Admin privileges required for: %s\\nEnter password:": "需要管理员权限:%s\\n请输入密码:", - "Algorithm: ": "算法: ", - "Alternative Names": "备用名称", - "Application Memory:": "应用内存:", - "Are you sure?": "确认操作?", - "Authenticating...": "正在认证...", - "Authentication failed": "认证失败", - "Authentication failed: %v": "认证失败:%v", - "Authentication successful!": "认证成功!", - "Auto Ban": "自动封禁", - "Available Actions:": "可用操作:", - "Available:": "可用:", - "Buffers:": "缓冲区:", - "CHECKS PERFORMED": "已执行检查", - "CPU State Breakdown": "CPU 状态分解", - "CPU Steal Time elevated (%.2f%%)": "CPU Steal Time 偏高 (%.2f%%)", - "CPU Steal Time high (%.2f%%)": "CPU Steal Time 过高 (%.2f%%)", - "CPU Usage": "CPU 使用率", - "CPU Usage (%)": "CPU 使用率 (%)", - "CPU: %s %.1f%% | Load: %.2f, %.2f, %.2f": "CPU: %s %.1f%% | 负载: %.2f, %.2f, %.2f", - "Cached:": "缓存:", - "Collecting data...": "正在采集数据...", - "Command": "命令", + "Algorithm: ": "算法: ", + "Alternative Names": "备用名称", + "Application Memory:": "应用内存:", + "Are you sure?": "确认操作?", + "Authenticating...": "正在认证...", + "Authentication failed": "认证失败", + "Authentication failed: %v": "认证失败:%v", + "Authentication successful!": "认证成功!", + "Auto Ban": "自动封禁", + "Auto Ban Details - %s": "自动封禁详情 - %s", + "Available Actions:": "可用操作:", + "Available:": "可用:", + "Buffers:": "缓冲区:", + "CHECKS PERFORMED": "已执行检查", + "CPU State Breakdown": "CPU 状态分解", + "CPU Steal Time": "CPU Steal Time", + "CPU Steal Time elevated (%.2f%%)": "CPU Steal Time 偏高 (%.2f%%)", + "CPU Steal Time high (%.2f%%)": "CPU Steal Time 过高 (%.2f%%)", + "Major Page Faults": "主缺页错误", + "Check for processes with high disk I/O, consider upgrading storage": "检查高磁盘 I/O 的进程,考虑升级存储设备", + "Check host machine load, consider migrating VM or upgrading host": "检查宿主机负载,考虑迁移虚拟机或升级宿主机", + "Investigate disk activity, upgrade to faster storage (SSD/NVMe)": "排查磁盘活动,升级为更快的存储(SSD/NVMe)", + "Investigate memory usage, consider increasing RAM": "排查内存使用情况,考虑增加内存", + "Monitor host machine performance": "监控宿主机性能", + "Optimize multi-threaded applications, check for excessive context switching": "优化多线程应用,检查是否存在过多上下文切换", + "CPU Usage": "CPU 使用率", + "CPU Usage (%)": "CPU 使用率 (%)", + "CPU: %s %.1f%% | Load: %.2f, %.2f, %.2f": "CPU: %s %.1f%% | 负载: %.2f, %.2f, %.2f", + "Cached:": "缓存:", + "Collecting data...": "正在采集数据...", + "Command": "命令", "Commit functionality not yet implemented": "提交功能尚未实现", "Complete Configuration:": "完整配置:", "Complete Firewall Configuration:": "完整防火墙配置:", @@ -67,6 +111,7 @@ var zh = map[string]string{ "Delete": "删除", "Delete container '%s'?\\nThis action cannot be undone.": "确定删除容器 '%s'?\\n此操作不可撤销。", "Destination": "目标", + "DNS Servers": "DNS 服务器", "Details": "详情", "Details: ": "详情: ", "Diagnostic": "诊断", @@ -103,26 +148,30 @@ var zh = map[string]string{ "Failed to get open ports: %v": "获取开放端口失败:%v", "File system cache (can be freed if needed)": "文件系统缓存(需要时可释放)", "Filter by service...": "按服务筛选...", + "Firewall Details - %s": "防火墙详情 - %s", "Firewall Rule": "防火墙规则", + "Firewall Rules (%d total)": "防火墙规则(共 %d 条)", "Firewall Status": "防火墙状态", "Firewall is not active. Enable it with 'sudo ufw enable'.": "防火墙未启用。可用 'sudo ufw enable' 开启。", - "Flags": "标志", - "Force remove container": "强制移除容器", + "(Full raw output from firewall command)": "(防火墙命令的完整原始输出)", + "(Full raw output from service)": "(服务的完整原始输出)", + "Flags": "标志", + "Force remove container": "强制移除容器", "Force remove container '%s'?\\nThis action cannot be undone.": "确定强制删除容器 '%s'?\\n此操作不可撤销。", - "Foreign Address": "远程地址", - "Free:": "空闲:", - "Gateway": "网关", - "General": "概览", - "Generate new system health report": "生成新的系统健康报告", - "Genmask": "子网掩码", - "Health": "健康状态", - "High": "高", - "High number of context switches (%s)": "上下文切换次数过高 (%s)", - "High number of major page faults (%s)": "主缺页次数过高 (%s)", - "Host: %s\t|\tOS: %s\t|\tKernel: %s\t|\tUptime: %s": "主机: %s\t|\t系统: %s\t|\t内核: %s\t|\t运行时间: %s", - "Hostname Verified: ": "主机名校验: ", - "I/O History:": "I/O 历史:", - "I/O buffers for disk operations": "磁盘操作的 I/O 缓冲区", + "Foreign Address": "远程地址", + "Free:": "空闲:", + "Gateway": "网关", + "General": "概览", + "Generate new system health report": "生成新的系统健康报告", + "Genmask": "子网掩码", + "Health": "健康状态", + "High": "高", + "High number of context switches (%s)": "上下文切换次数过高 (%s)", + "High number of major page faults (%s)": "主缺页次数过高 (%s)", + "Host: %s\t|\tOS: %s\t|\tKernel: %s\t|\tUptime: %s": "主机: %s\t|\t系统: %s\t|\t内核: %s\t|\t运行时间: %s", + "Hostname Verified: ": "主机名校验: ", + "I/O History:": "I/O 历史:", + "I/O buffers for disk operations": "磁盘操作的 I/O 缓冲区", "ID: %s\\nName: %s\\nImage: %s\\nStatus: %s\\nProject: %s\\nCreated: %s\\nUptime: %s\\nHealth: %s\\nIP Address: %s\\nGateway: %s": "ID: %s\\n名称: %s\\n镜像: %s\\n状态: %s\\n项目: %s\\n创建时间: %s\\n运行时间: %s\\n健康: %s\\nIP 地址: %s\\n网关: %s", "IOWait": "IO 等待", "IOWait elevated (%.2f%%)": "IOWait 偏高 (%.2f%%)", @@ -138,6 +187,7 @@ var zh = map[string]string{ "Invalid password or insufficient privileges": "密码错误或权限不足", "Issuer: ": "颁发者: ", "Jail/Service Details": "Jail/服务详情", + "Jails/Services (%d total)": "Jail/服务(共 %d 个)", "Kernel data structure cache": "内核数据结构缓存", "Language": "语言", "Last Update: %s": "最后更新:%s", @@ -191,6 +241,7 @@ var zh = map[string]string{ "Network TX": "网络发送", "Network Usage": "网络使用", "No DNS servers configured": "未配置 DNS 服务器", + "No DNS servers found": "未找到 DNS 服务器", "No IP": "无 IP", "No SSH root login information available": "暂无 SSH Root 登录信息", "No SSL certificate found": "未找到 SSL 证书", @@ -291,6 +342,9 @@ var zh = map[string]string{ "Search a container...": "搜索容器...", "Search a process...": "搜索进程...", "Search logs...": "搜索日志...", + "Search: ": "搜索:", + "Service: ": "服务:", + "Showing %d entries": "显示 %d 条记录", "Security Check": "安全检查", "Security Checks": "安全检查", "Security Information": "安全信息", @@ -299,6 +353,7 @@ var zh = map[string]string{ "Server: %s": "服务器: %s", "Service": "服务", "Shared:": "共享:", + "Slab:": "内核缓存:", "Shell opened": "已打开 Shell", "Show container logs": "显示容器日志", "Signature Algorithm: ": "签名算法: ", @@ -342,6 +397,18 @@ var zh = map[string]string{ "Toggle container state": "切换容器运行状态", "Toggle pause state": "切换暂停状态", "Total Memory:": "内存总量:", + "Total Read": "读取总量", + "Total Read IOPS": "读取 IOPS 总量", + "Total Write": "写入总量", + "Total Write IOPS": "写入 IOPS 总量", + "Device": "设备", + "Read IOPS": "读取 IOPS", + "Write IOPS": "写入 IOPS", + "Read MB/s": "读取 MB/s", + "Write MB/s": "写入 MB/s", + "Read MB": "读取 MB", + "Write MB": "写入 MB", + "Queue": "队列", "Traceroute to %s": "路由追踪:%s", "Traceroute to %s\\n": "路由追踪:%s\\n", "USAGE BREAKDOWN": "使用分解", diff --git a/main.go b/main.go index 426381f..1d85a82 100644 --- a/main.go +++ b/main.go @@ -46,7 +46,7 @@ func runTUI() bool { defer func() { if r := recover(); r != nil { log.Printf("Recovered from panic: %v", r) - fmt.Println("\nRestoring terminal after panic...") + fmt.Println(i18n.T("\nRestoring terminal after panic...")) time.Sleep(2 * time.Second) } }() @@ -87,7 +87,7 @@ func runTUI() bool { err := dockerManager.ExecInteractiveShellAlternative(shellRequest.ContainerID) if err != nil { - fmt.Printf("Shell execution failed: %v\n", err) + fmt.Printf(i18n.T("Shell execution failed: %v\n"), err) fmt.Println(i18n.T("Press Enter to continue...")) bufio.NewScanner(os.Stdin).Scan() } diff --git a/system/logs/logs.go b/system/logs/logs.go index 30416f7..3b38619 100644 --- a/system/logs/logs.go +++ b/system/logs/logs.go @@ -1,9 +1,9 @@ package logs import ( - "github.com/Server-Pulse/server-pulse/i18n" "encoding/json" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strconv" "strings" diff --git a/system/network/connectivity.go b/system/network/connectivity.go index ab70f7d..90083e8 100644 --- a/system/network/connectivity.go +++ b/system/network/connectivity.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/Server-Pulse/server-pulse/i18n" tea "github.com/charmbracelet/bubbletea" ) @@ -30,7 +31,7 @@ func Ping(target string, count int) tea.Cmd { return PingMsg(PingResult{ Target: target, Success: false, - Error: "Invalid target: must be a valid hostname or IP address", + Error: i18n.T("Invalid target: must be a valid hostname or IP address"), }) } if count < 1 || count > 100 { @@ -47,7 +48,7 @@ func Ping(target string, count int) tea.Cmd { return PingMsg(PingResult{ Target: target, Success: false, - Error: fmt.Sprintf("Ping failed: %v", err), + Error: fmt.Sprintf(i18n.T("Ping failed: %v"), err), }) } @@ -81,7 +82,7 @@ func parseSystemPingOutput(target, output string) PingMsg { return PingMsg{ Target: target, Success: false, - Error: "No response from target", + Error: i18n.T("No response from target"), } } @@ -90,7 +91,7 @@ func Traceroute(target string) tea.Cmd { if !isValidTarget(target) { return TracerouteMsg(TracerouteResult{ Target: target, - Error: "Invalid target: must be a valid hostname or IP address", + Error: i18n.T("Invalid target: must be a valid hostname or IP address"), }) } checkCmd := exec.Command("which", "traceroute") @@ -105,7 +106,7 @@ func Traceroute(target string) tea.Cmd { if err != nil { return TracerouteMsg(TracerouteResult{ Target: target, - Error: fmt.Sprintf("traceroute failed: %v", err), + Error: fmt.Sprintf(i18n.T("traceroute failed: %v"), err), }) } @@ -194,7 +195,7 @@ func InstallTraceroute(target string, sudoPassword string) tea.Cmd { return TracerouteInstallResultMsg{ Success: false, Target: target, - Error: "Could not detect package manager. Please install traceroute manually.", + Error: i18n.T("Could not detect package manager. Please install traceroute manually."), } } @@ -281,14 +282,14 @@ func InstallTraceroute(target string, sudoPassword string) tea.Cmd { return TracerouteInstallResultMsg{ Success: false, Target: target, - Error: "Incorrect password. Please try again.", + Error: i18n.T("Incorrect password. Please try again."), PasswordInvalid: true, } } return TracerouteInstallResultMsg{ Success: false, Target: target, - Error: fmt.Sprintf("Installation failed: %v\n%s", err, stderrOutput), + Error: fmt.Sprintf(i18n.T("Installation failed: %v\n%s"), err, stderrOutput), } } } else { @@ -297,7 +298,7 @@ func InstallTraceroute(target string, sudoPassword string) tea.Cmd { return TracerouteInstallResultMsg{ Success: false, Target: target, - Error: fmt.Sprintf("Installation failed: %v\n%s", err, string(output)), + Error: fmt.Sprintf(i18n.T("Installation failed: %v\n%s"), err, string(output)), } } } diff --git a/system/network/dns.go b/system/network/dns.go index ed0c786..8fc52be 100644 --- a/system/network/dns.go +++ b/system/network/dns.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/Server-Pulse/server-pulse/i18n" "github.com/Server-Pulse/server-pulse/utils" tea "github.com/charmbracelet/bubbletea" ) @@ -42,7 +43,7 @@ func GetDNS() tea.Cmd { } if len(dnsServers) == 0 { - return DNSMsg([]DNSInfo{{Server: "No DNS servers found"}}) + return DNSMsg([]DNSInfo{{Server: i18n.T("No DNS servers found")}}) } return DNSMsg(dnsServers) diff --git a/system/network/speedtest.go b/system/network/speedtest.go index 3e5722b..f2ef772 100644 --- a/system/network/speedtest.go +++ b/system/network/speedtest.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "time" + "github.com/Server-Pulse/server-pulse/i18n" tea "github.com/charmbracelet/bubbletea" ) @@ -399,25 +400,25 @@ func RunSpeedTest() tea.Cmd { // Server selection if err := selectServer(client, pingURL, 3); err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Server selection failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Server selection failed: %v"), err)} } // Ping test pingResult, err := testPing(client, pingURL, pingCount) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Ping test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Ping test failed: %v"), err)} } // Download test downloadMbps, err := testDownload(client, downloadURL, downloadConcurrency, downloadTestDuration, nil) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Download test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Download test failed: %v"), err)} } // Upload test uploadMbps, err := testUpload(client, uploadURL, uploadConcurrency, uploadChunkSize, uploadTestDuration, nil) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Upload test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Upload test failed: %v"), err)} } testDuration := time.Since(startTime) @@ -445,12 +446,12 @@ func RunSpeedTestWithProgress(progressChan chan<- SpeedTestProgressMsg) tea.Cmd progressChan <- SpeedTestProgressMsg{ Stage: "server", Progress: 0.1, - Message: "Selecting server...", + Message: i18n.T("Selecting server..."), } } if err := selectServer(client, pingURL, 3); err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Server selection failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Server selection failed: %v"), err)} } // Ping test @@ -458,13 +459,13 @@ func RunSpeedTestWithProgress(progressChan chan<- SpeedTestProgressMsg) tea.Cmd progressChan <- SpeedTestProgressMsg{ Stage: "ping", Progress: 0.2, - Message: "Testing latency...", + Message: i18n.T("Testing latency..."), } } pingResult, err := testPing(client, pingURL, pingCount) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Ping test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Ping test failed: %v"), err)} } // Download test with progress @@ -472,13 +473,13 @@ func RunSpeedTestWithProgress(progressChan chan<- SpeedTestProgressMsg) tea.Cmd progressChan <- SpeedTestProgressMsg{ Stage: "download", Progress: 0.4, - Message: "Testing download speed...", + Message: i18n.T("Testing download speed..."), } } downloadMbps, err := testDownload(client, downloadURL, downloadConcurrency, downloadTestDuration, nil) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Download test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Download test failed: %v"), err)} } // Upload test @@ -486,13 +487,13 @@ func RunSpeedTestWithProgress(progressChan chan<- SpeedTestProgressMsg) tea.Cmd progressChan <- SpeedTestProgressMsg{ Stage: "upload", Progress: 0.7, - Message: "Testing upload speed...", + Message: i18n.T("Testing upload speed..."), } } uploadMbps, err := testUpload(client, uploadURL, uploadConcurrency, uploadChunkSize, uploadTestDuration, nil) if err != nil { - return SpeedTestErrorMsg{Error: fmt.Sprintf("Upload test failed: %v", err)} + return SpeedTestErrorMsg{Error: fmt.Sprintf(i18n.T("Upload test failed: %v"), err)} } testDuration := time.Since(startTime) @@ -501,7 +502,7 @@ func RunSpeedTestWithProgress(progressChan chan<- SpeedTestProgressMsg) tea.Cmd progressChan <- SpeedTestProgressMsg{ Stage: "complete", Progress: 1.0, - Message: "Speed test completed", + Message: i18n.T("Speed test completed"), } } diff --git a/system/performance/cpu.go b/system/performance/cpu.go index 995948b..942436d 100644 --- a/system/performance/cpu.go +++ b/system/performance/cpu.go @@ -1,9 +1,9 @@ package performance import ( - "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "strconv" "strings" diff --git a/system/performance/io.go b/system/performance/io.go index 3818053..01fe627 100644 --- a/system/performance/io.go +++ b/system/performance/io.go @@ -1,9 +1,9 @@ package performance import ( - "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "sort" "strconv" @@ -248,10 +248,10 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Create summary table summaryColumns := []table.Column{ - {Title: "Total Read IOPS", Width: 16}, - {Title: "Total Write IOPS", Width: 17}, - {Title: "Total Read", Width: 15}, - {Title: "Total Write", Width: 16}, + {Title: i18n.T("Total Read IOPS"), Width: 16}, + {Title: i18n.T("Total Write IOPS"), Width: 17}, + {Title: i18n.T("Total Read"), Width: 15}, + {Title: i18n.T("Total Write"), Width: 16}, } summaryRows := []table.Row{ @@ -293,13 +293,13 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { // Create table columns := []table.Column{ - {Title: "Device", Width: 8}, - {Title: "Read IOPS", Width: 10}, - {Title: "Write IOPS", Width: 11}, - {Title: "Read MB/s", Width: 10}, - {Title: "Write MB/s", Width: 11}, + {Title: i18n.T("Device"), Width: 8}, + {Title: i18n.T("Read IOPS"), Width: 10}, + {Title: i18n.T("Write IOPS"), Width: 11}, + {Title: i18n.T("Read MB/s"), Width: 10}, + {Title: i18n.T("Write MB/s"), Width: 11}, {Title: "Util%", Width: 6}, - {Title: "Queue", Width: 6}, + {Title: i18n.T("Queue"), Width: 6}, } var rows []table.Row @@ -348,10 +348,10 @@ func RenderInputOutputWithData(metrics *model.IOMetrics, loading bool) string { columns := []table.Column{ {Title: "PID", Width: 6}, {Title: i18n.T("Command"), Width: 20}, - {Title: "Read IOPS", Width: 10}, - {Title: "Write IOPS", Width: 11}, - {Title: "Read MB", Width: 10}, - {Title: "Write MB", Width: 11}, + {Title: i18n.T("Read IOPS"), Width: 10}, + {Title: i18n.T("Write IOPS"), Width: 11}, + {Title: i18n.T("Read MB"), Width: 10}, + {Title: i18n.T("Write MB"), Width: 11}, } var rows []table.Row diff --git a/system/performance/memory.go b/system/performance/memory.go index 2d3bf32..e4f6c62 100644 --- a/system/performance/memory.go +++ b/system/performance/memory.go @@ -1,8 +1,8 @@ package performance import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "time" @@ -220,7 +220,7 @@ func RenderMemorySystemMemory(metrics *model.MemoryMetrics) string { // Slab slabPercent := float64(metrics.Slab) / float64(metrics.Total) * 100 - content += labelStyle.Render("Slab:") + " " + + content += labelStyle.Render(i18n.T("Slab:")) + " " + valueStyle.Render(fmt.Sprintf("%s (%.1f%%)", formatBytes(metrics.Slab), slabPercent)) + "\n" content += " " + descStyle.Render(i18n.T("Kernel data structure cache")) + "\n\n" diff --git a/system/performance/systemHealth.go b/system/performance/systemHealth.go index ac8d379..7906b9d 100644 --- a/system/performance/systemHealth.go +++ b/system/performance/systemHealth.go @@ -1,9 +1,9 @@ package performance import ( - "github.com/Server-Pulse/server-pulse/i18n" "bufio" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "strconv" "strings" @@ -120,31 +120,31 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { if metrics.IOWait > 20 { score -= 30 issues = append(issues, fmt.Sprintf(i18n.T("IOWait very high (%.2f%%)"), metrics.IOWait)) - recommendations = append(recommendations, "Investigate disk activity, upgrade to faster storage (SSD/NVMe)") + recommendations = append(recommendations, i18n.T("Investigate disk activity, upgrade to faster storage (SSD/NVMe)")) } else if metrics.IOWait > 10 { score -= 15 issues = append(issues, fmt.Sprintf(i18n.T("IOWait elevated (%.2f%%)"), metrics.IOWait)) - recommendations = append(recommendations, "Check for processes with high disk I/O, consider upgrading storage") + recommendations = append(recommendations, i18n.T("Check for processes with high disk I/O, consider upgrading storage")) } // CPU Steal Time - checksPerformed = append(checksPerformed, "CPU Steal Time") + checksPerformed = append(checksPerformed, i18n.T("CPU Steal Time")) if metrics.StealTime > 10 { score -= 20 issues = append(issues, fmt.Sprintf(i18n.T("CPU Steal Time high (%.2f%%)"), metrics.StealTime)) - recommendations = append(recommendations, "Check host machine load, consider migrating VM or upgrading host") + recommendations = append(recommendations, i18n.T("Check host machine load, consider migrating VM or upgrading host")) } else if metrics.StealTime > 5 { score -= 10 issues = append(issues, fmt.Sprintf(i18n.T("CPU Steal Time elevated (%.2f%%)"), metrics.StealTime)) - recommendations = append(recommendations, "Monitor host machine performance") + recommendations = append(recommendations, i18n.T("Monitor host machine performance")) } // Major Page Faults - checksPerformed = append(checksPerformed, "Major Page Faults") + checksPerformed = append(checksPerformed, i18n.T("Major Page Faults")) if metrics.MajorFaults > 10000 { score -= 15 issues = append(issues, fmt.Sprintf(i18n.T("High number of major page faults (%s)"), formatNumber(metrics.MajorFaults))) - recommendations = append(recommendations, "Investigate memory usage, consider increasing RAM") + recommendations = append(recommendations, i18n.T("Investigate memory usage, consider increasing RAM")) } // Context Switches @@ -152,7 +152,7 @@ func CalculateHealthScore(metrics *HealthMetrics) *HealthScore { if metrics.ContextSwitches > 100000000 { // 100 million score -= 10 issues = append(issues, fmt.Sprintf(i18n.T("High number of context switches (%s)"), formatNumber(metrics.ContextSwitches))) - recommendations = append(recommendations, "Optimize multi-threaded applications, check for excessive context switching") + recommendations = append(recommendations, i18n.T("Optimize multi-threaded applications, check for excessive context switching")) } if score < 0 { diff --git a/system/performance/view.go b/system/performance/view.go index f6bc9b2..6b7a9bb 100644 --- a/system/performance/view.go +++ b/system/performance/view.go @@ -1,8 +1,8 @@ package performance import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/widgets/vars" diff --git a/system/security/SystemRestart.go b/system/security/SystemRestart.go index 9bc2c04..3be95bf 100644 --- a/system/security/SystemRestart.go +++ b/system/security/SystemRestart.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "os/exec" "regexp" @@ -37,9 +37,9 @@ func checkRebootRequired() (bool, string) { if _, err := os.Stat("/var/run/reboot-required"); err == nil { if content, err := os.ReadFile("/var/run/reboot-required.pkgs"); err == nil { packages := strings.TrimSpace(string(content)) - return true, fmt.Sprintf("Reboot required due to package updates: %s", packages) + return true, fmt.Sprintf(i18n.T("Reboot required due to package updates: %s"), packages) } - return true, "Reboot required flag detected" + return true, i18n.T("Reboot required flag detected") } return false, "" } @@ -66,7 +66,7 @@ func checkKernelUpdate() (bool, string) { for _, match := range matches { if len(match) > 1 && match[1] != currentVersion { - return true, fmt.Sprintf("Newer kernel available: %s (current: %s)", match[1], currentVersion) + return true, fmt.Sprintf(i18n.T("Newer kernel available: %s (current: %s)"), match[1], currentVersion) } } @@ -85,7 +85,7 @@ func checkSystemdRestart() (bool, string) { } } if failedServices > 0 { - return true, fmt.Sprintf("%d failed services detected - restart may be needed", failedServices) + return true, fmt.Sprintf(i18n.T("%d failed services detected - restart may be needed"), failedServices) } } diff --git a/system/security/SystemUpdates.go b/system/security/SystemUpdates.go index 48bf4c6..a695b4f 100644 --- a/system/security/SystemUpdates.go +++ b/system/security/SystemUpdates.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strings" ) @@ -23,7 +23,7 @@ func (sm *SecurityManager) checkSystemUpdates() SecurityCheck { return SecurityCheck{ Name: i18n.T("System Updates"), Status: "Unknown", - Details: "Could not determine update status - package manager not detected", + Details: i18n.T("Could not determine update status - package manager not detected"), } } @@ -107,13 +107,13 @@ func (sm *SecurityManager) handleYumDnfResult(mgr struct { return SecurityCheck{ Name: i18n.T("System Updates"), Status: "Updates Available", - Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), + Details: fmt.Sprintf(i18n.T("%d updates available via %s"), count, mgr.name), } } else if err == nil { return SecurityCheck{ Name: i18n.T("System Updates"), Status: "Up to date", - Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), + Details: fmt.Sprintf(i18n.T("All packages are up to date (%s)"), mgr.name), } } @@ -132,12 +132,12 @@ func (sm *SecurityManager) handleAptResult(mgr struct { return SecurityCheck{ Name: i18n.T("System Updates"), Status: "Updates Available", - Details: fmt.Sprintf("%d updates available via %s", count, mgr.name), + Details: fmt.Sprintf(i18n.T("%d updates available via %s"), count, mgr.name), } } return SecurityCheck{ Name: i18n.T("System Updates"), Status: "Up to date", - Details: fmt.Sprintf("All packages are up to date (%s)", mgr.name), + Details: fmt.Sprintf(i18n.T("All packages are up to date (%s)"), mgr.name), } } diff --git a/system/security/autoBane.go b/system/security/autoBane.go index f00ac22..a96f204 100644 --- a/system/security/autoBane.go +++ b/system/security/autoBane.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strings" @@ -76,7 +76,7 @@ func (sm *SecurityManager) checkSystemdServices() SecurityCheck { return SecurityCheck{ Name: i18n.T("Auto Ban"), Status: "Enabled", - Details: "ossec-hids is active with real-time monitoring", + Details: i18n.T("ossec-hids is active with real-time monitoring"), } } } @@ -204,7 +204,7 @@ func (sm *SecurityManager) checkRunningProcesses() SecurityCheck { return SecurityCheck{ Name: i18n.T("Auto Ban"), Status: "Enabled", - Details: fmt.Sprintf("Active intrusion prevention: %s", strings.Join(activeServices, ", ")), + Details: fmt.Sprintf(i18n.T("Active intrusion prevention: %s"), strings.Join(activeServices, ", ")), } } @@ -277,7 +277,7 @@ func (sm *SecurityManager) getFail2banDetails() AutoBanInfos { return AutoBanInfos{ ServiceType: "fail2ban", Status: "Active", - Details: "fail2ban is running but unable to get details", + Details: i18n.T("fail2ban is running but unable to get details"), Jails: []AutoBanJail{}, BannedIPs: []string{}, } @@ -334,7 +334,7 @@ func (sm *SecurityManager) getFail2banDetails() AutoBanInfos { Version: version, Jails: jails, BannedIPs: bannedIPs, - Details: fmt.Sprintf("fail2ban is active with %d configured jails", len(jails)), + Details: fmt.Sprintf(i18n.T("fail2ban is active with %d configured jails"), len(jails)), RawOutput: rawOutput, } } @@ -378,7 +378,7 @@ func parseFailfbanJail(name string, output string) AutoBanJail { } if jail.Details == "" { - jail.Details = fmt.Sprintf("Currently: %d banned, Total: %d banned", jail.CurrentBans, jail.TotalBans) + jail.Details = fmt.Sprintf(i18n.T("Currently: %d banned, Total: %d banned"), jail.CurrentBans, jail.TotalBans) } return jail @@ -401,7 +401,7 @@ func (sm *SecurityManager) getCrowdsecDetails() AutoBanInfos { ServiceType: "CrowdSec", Status: "Active", Version: version, - Details: "CrowdSec is active with behavioral analysis", + Details: i18n.T("CrowdSec is active with behavioral analysis"), Jails: []AutoBanJail{}, BannedIPs: []string{}, RawOutput: string(output), @@ -413,10 +413,10 @@ func (sm *SecurityManager) getOSSECDetails() AutoBanInfos { return AutoBanInfos{ ServiceType: "OSSEC", Status: "Active", - Details: "OSSEC HIDS is active with real-time monitoring", + Details: i18n.T("OSSEC HIDS is active with real-time monitoring"), Jails: []AutoBanJail{}, BannedIPs: []string{}, - RawOutput: "OSSEC is a host-based intrusion detection system", + RawOutput: i18n.T("OSSEC is a host-based intrusion detection system"), } } @@ -425,10 +425,10 @@ func (sm *SecurityManager) getDenyhostsDetails() AutoBanInfos { return AutoBanInfos{ ServiceType: "DenyHosts", Status: "Active", - Details: "DenyHosts is enabled and active", + Details: i18n.T("DenyHosts is enabled and active"), Jails: []AutoBanJail{}, BannedIPs: []string{}, - RawOutput: "DenyHosts monitors SSH login attempts", + RawOutput: i18n.T("DenyHosts monitors SSH login attempts"), } } @@ -437,10 +437,10 @@ func (sm *SecurityManager) getSSHGuardDetails() AutoBanInfos { return AutoBanInfos{ ServiceType: "SSHGuard", Status: "Active", - Details: "SSHGuard is enabled and active", + Details: i18n.T("SSHGuard is enabled and active"), Jails: []AutoBanJail{}, BannedIPs: []string{}, - RawOutput: "SSHGuard protects SSH and other services", + RawOutput: i18n.T("SSHGuard protects SSH and other services"), } } @@ -462,7 +462,7 @@ func (sm *SecurityManager) getSuricataDetails() AutoBanInfos { ServiceType: "Suricata", Status: "Active", Version: version, - Details: "Suricata IDS/IPS is active with network intrusion detection", + Details: i18n.T("Suricata IDS/IPS is active with network intrusion detection"), Jails: []AutoBanJail{}, BannedIPs: []string{}, RawOutput: string(output), @@ -486,7 +486,7 @@ func (sm *SecurityManager) getSnortDetails() AutoBanInfos { ServiceType: "Snort", Status: "Active", Version: version, - Details: "Snort IDS/IPS is active with network intrusion detection", + Details: i18n.T("Snort IDS/IPS is active with network intrusion detection"), Jails: []AutoBanJail{}, BannedIPs: []string{}, RawOutput: string(output), diff --git a/system/security/firewall.go b/system/security/firewall.go index 68a4428..68c9f36 100644 --- a/system/security/firewall.go +++ b/system/security/firewall.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strings" @@ -99,13 +99,13 @@ func (sm *SecurityManager) analyzeUFWStatus(outputStr string) *SecurityCheck { return &SecurityCheck{ Name: i18n.T("Firewall Status"), Status: "Active", - Details: "UFW is active and properly configured", + Details: i18n.T("UFW is active and properly configured"), } } else if strings.Contains(outputStr, "status: inactive") { return &SecurityCheck{ Name: i18n.T("Firewall Status"), Status: "Inactive", - Details: "UFW is installed but inactive", + Details: i18n.T("UFW is installed but inactive"), } } return nil @@ -116,7 +116,7 @@ func (sm *SecurityManager) analyzeFirewalldStatus(outputStr string) *SecurityChe return &SecurityCheck{ Name: i18n.T("Firewall Status"), Status: "Active", - Details: "firewalld is active and properly configured", + Details: i18n.T("firewalld is active and properly configured"), } } return nil @@ -127,7 +127,7 @@ func (sm *SecurityManager) analyzeIptablesStatus(output []byte) *SecurityCheck { return &SecurityCheck{ Name: i18n.T("Firewall Status"), Status: "Active", - Details: "iptables rules are configured", + Details: i18n.T("iptables rules are configured"), } } return nil @@ -138,7 +138,7 @@ func (sm *SecurityManager) analyzeNftablesStatus(output []byte) *SecurityCheck { return &SecurityCheck{ Name: i18n.T("Firewall Status"), Status: "Active", - Details: "nftables rules are configured", + Details: i18n.T("nftables rules are configured"), } } return nil diff --git a/system/security/passwordPolicy.go b/system/security/passwordPolicy.go index b33fe7b..d187076 100644 --- a/system/security/passwordPolicy.go +++ b/system/security/passwordPolicy.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "regexp" "strings" @@ -25,7 +25,7 @@ func (sm *SecurityManager) checkPasswordPolicy() SecurityCheck { if strings.Contains(contentStr, "pam_pwquality") || strings.Contains(contentStr, "pam_cracklib") { hasPolicy = true - details = append(details, fmt.Sprintf("Password policy found in %s", file)) + details = append(details, fmt.Sprintf(i18n.T("Password policy found in %s"), file)) if strings.Contains(contentStr, "minlen") { re := regexp.MustCompile(`minlen=(\d+)`) diff --git a/system/security/ports.go b/system/security/ports.go index b40979d..82cd504 100644 --- a/system/security/ports.go +++ b/system/security/ports.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strconv" "strings" @@ -128,21 +128,21 @@ func (sm *SecurityManager) checkOpenPorts() SecurityCheck { func analyzePortRisk(port int) (RiskLevel, string) { switch port { case 22: - return Warning, "Port 22 (SSH) is open. Change default ssh port for more security." + return Warning, i18n.T("Port 22 (SSH) is open. Change default ssh port for more security.") case 20, 21: - return Warning, "Port 20/21 (FTP) is open. FTP is insecure, consider using SFTP or FTPS." + return Warning, i18n.T("Port 20/21 (FTP) is open. FTP is insecure, consider using SFTP or FTPS.") case 23: - return HighRisk, "Port 23 (Telnet) is open. Telnet is insecure and should be closed." + return HighRisk, i18n.T("Port 23 (Telnet) is open. Telnet is insecure and should be closed.") case 161, 162: - return Warning, "Port 161/162 (SNMP) is open. SNMP can expose sensitive information." + return Warning, i18n.T("Port 161/162 (SNMP) is open. SNMP can expose sensitive information.") case 137, 138, 139: - return HighRisk, "Port 137/138/139 (NetBIOS), you can be attacked by null sessions." + return HighRisk, i18n.T("Port 137/138/139 (NetBIOS), you can be attacked by null sessions.") case 445: - return HighRisk, "Port 445 (SMB) is open. SMB has had many vulnerabilities." + return HighRisk, i18n.T("Port 445 (SMB) is open. SMB has had many vulnerabilities.") case 3389: - return HighRisk, "Port 3389 (RDP) is open. RDP is often targeted by attackers." + return HighRisk, i18n.T("Port 3389 (RDP) is open. RDP is often targeted by attackers.") case 3306, 5432, 6379, 27017: - return HighRisk, "Database port exposed. Databases should not be accessible from internet." + return HighRisk, i18n.T("Database port exposed. Databases should not be accessible from internet.") default: return Secure, "" } @@ -154,7 +154,7 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort return SecurityCheck{ Name: i18n.T("Open Ports"), Status: "High Risk", - Details: fmt.Sprintf("Critical ports detected (%s): %s", + Details: fmt.Sprintf(i18n.T("Critical ports detected (%s): %s"), strings.Join(riskPorts, ", "), strings.Join(findings, " | ")), } case Warning: @@ -167,7 +167,7 @@ func buildSecurityCheckResult(maxRisk RiskLevel, allPorts map[int]bool, riskPort return SecurityCheck{ Name: i18n.T("Open Ports"), Status: "Secure", - Details: fmt.Sprintf("%d ports open: %s", len(allPorts), ShowOpenedPorts(allPorts)), + Details: fmt.Sprintf(i18n.T("%d ports open: %s"), len(allPorts), ShowOpenedPorts(allPorts)), } } } diff --git a/system/security/sshPassword.go b/system/security/sshPassword.go index e28b664..7303dc2 100644 --- a/system/security/sshPassword.go +++ b/system/security/sshPassword.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strings" ) @@ -39,13 +39,13 @@ func (sm *SecurityManager) checkSSHPasswordAuthentication() SecurityCheck { return SecurityCheck{ Name: i18n.T("SSH Password Authentication"), Status: "Enabled", - Details: "SSH password authentication is enabled - consider disabling for better security", + Details: i18n.T("SSH password authentication is enabled - consider disabling for better security"), } } return SecurityCheck{ Name: i18n.T("SSH Password Authentication"), Status: "Disabled", - Details: "SSH password authentication is disabled", + Details: i18n.T("SSH password authentication is disabled"), } } diff --git a/system/security/sshRoot.go b/system/security/sshRoot.go index f591781..20a3784 100644 --- a/system/security/sshRoot.go +++ b/system/security/sshRoot.go @@ -1,8 +1,8 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os/exec" "strings" @@ -86,18 +86,18 @@ func (sm *SecurityManager) checkSSHRootLogin() SecurityCheck { } status := "Disabled" - details := "SSH root login is disabled." + details := i18n.T("SSH root login is disabled.") switch strings.ToLower(permitRootLogin) { case "yes": status = "Enabled (with password)" - details = "Root login with a password is permitted. This is a high security risk." + details = i18n.T("Root login with a password is permitted. This is a high security risk.") case "without-password", "prohibit-password": status = "Enabled (key-only)" - details = "Root can login, but only with key-based authentication." + details = i18n.T("Root can login, but only with key-based authentication.") case "forced-commands-only": status = "Enabled (commands-only)" - details = "Root can login via public key, but only to run specific commands." + details = i18n.T("Root can login via public key, but only to run specific commands.") case "no": } diff --git a/system/security/ssl.go b/system/security/ssl.go index dff69bd..41cabf3 100644 --- a/system/security/ssl.go +++ b/system/security/ssl.go @@ -1,10 +1,10 @@ package security import ( - "github.com/Server-Pulse/server-pulse/i18n" "crypto/tls" "crypto/x509" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "time" tea "github.com/charmbracelet/bubbletea" @@ -39,7 +39,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { return SecurityCheck{ Name: i18n.T("SSL Certificate"), Status: "Error", - Details: "Domain name is required", + Details: i18n.T("Domain name is required"), } } @@ -76,7 +76,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { return SecurityCheck{ Name: i18n.T("SSL Certificate"), Status: "Invalid", - Details: "Certificate has expired", + Details: i18n.T("Certificate has expired"), } } @@ -85,7 +85,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { return SecurityCheck{ Name: i18n.T("SSL Certificate"), Status: "Warning", - Details: fmt.Sprintf("Certificate expires soon in %.0f days", daysUntilExpiration), + Details: fmt.Sprintf(i18n.T("Certificate expires soon in %.0f days"), daysUntilExpiration), } } @@ -93,7 +93,7 @@ func (sm *SecurityManager) checkSSLCertificate(domaine string) SecurityCheck { return SecurityCheck{ Name: i18n.T("SSL Certificate"), Status: "Valid", - Details: fmt.Sprintf("Certificate expires in %.0f days", daysUntilExpiration), + Details: fmt.Sprintf(i18n.T("Certificate expires in %.0f days"), daysUntilExpiration), } } diff --git a/utils/test/utils_test.go b/utils/test/utils_test.go index c311343..80ce8d7 100644 --- a/utils/test/utils_test.go +++ b/utils/test/utils_test.go @@ -146,11 +146,11 @@ func TestFormatUptime(t *testing.T) { input uint64 expected string }{ - {"Minutes only", 300, "5 minutes"}, - {"Hours and minutes", 3660, "1 hours, 1 minutes"}, - {"Days and hours", 90000, "1 days, 1 hours"}, - {"Multiple days", 172800, "2 days, 0 hours"}, - {"Zero seconds", 0, "0 minutes"}, + {"Minutes only", 300, "5 分钟"}, + {"Hours and minutes", 3660, "1 小时 1 分钟"}, + {"Days and hours", 90000, "1 天 1 小时"}, + {"Multiple days", 172800, "2 天 0 小时"}, + {"Zero seconds", 0, "0 分钟"}, } for _, tt := range tests { @@ -264,12 +264,12 @@ func TestFormatOperationMessage(t *testing.T) { err error expected string }{ - {"Success restart", "restart", true, nil, "✅ Container restarted successfully"}, - {"Failure restart", "restart", false, nil, "❌ Container restarted failed"}, - {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ Container started failed: permission denied"}, - {"Unknown operation success", "unknown", true, nil, "✅ Operation 'unknown' successfully"}, - {"Unknown operation failure", "unknown", false, nil, "❌ Operation 'unknown' failed"}, - {"Empty operation", "", true, nil, "✅ Operation '' successfully"}, + {"Success restart", "restart", true, nil, "✅ 容器已重启成功"}, + {"Failure restart", "restart", false, nil, "❌ 容器已重启失败"}, + {"Failure with error", "start", false, fmt.Errorf("permission denied"), "❌ 容器已启动失败: permission denied"}, + {"Unknown operation success", "unknown", true, nil, "✅ 操作 'unknown'成功"}, + {"Unknown operation failure", "unknown", false, nil, "❌ 操作 'unknown'失败"}, + {"Empty operation", "", true, nil, "✅ 操作 ''成功"}, } for _, tt := range tests { diff --git a/utils/utils.go b/utils/utils.go index 48afa22..9c7372b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,10 +1,10 @@ package utils import ( - "github.com/Server-Pulse/server-pulse/i18n" "bufio" "encoding/binary" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "net" "os" "os/exec" @@ -139,7 +139,7 @@ func CheckDockerPermissions() (bool, string) { // Step 1: Check if the 'docker' command exists in the user's PATH (cross-platform) _, err := exec.LookPath("docker") if err != nil { - return false, "The 'docker' command was not found. Please ensure Docker is installed." + return false, i18n.T("The 'docker' command was not found. Please ensure Docker is installed.") } // Step 2: Platform-specific permission checks @@ -161,11 +161,11 @@ func CheckDockerPermissions() (bool, string) { continue } if group.Name == "docker" || group.Name == "root" { - return true, "The user has permissions to run Docker." + return true, i18n.T("The user has permissions to run Docker.") } } - return false, "The user is not in the 'docker' group. To add them, run 'sudo usermod -aG docker ' and then log out and log back in." + return false, i18n.T("The user is not in the 'docker' group. To add them, run 'sudo usermod -aG docker ' and then log out and log back in.") default: return false, fmt.Sprintf(i18n.T("Unsupported operating system: %s"), runtime.GOOS) diff --git a/widgets/handles-common.go b/widgets/handles-common.go index 03cfed3..8db4235 100644 --- a/widgets/handles-common.go +++ b/widgets/handles-common.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "math" "strings" "time" @@ -189,7 +189,7 @@ func calculateConnectivityContentLines(m Model) int { if result.Success { tempContent.WriteString(fmt.Sprintf(i18n.T("Latency: %v, Packet Loss: %.1f%%"), result.Latency, result.PacketLoss)) } else { - tempContent.WriteString(fmt.Sprintf("Error: %s", result.Error)) + tempContent.WriteString(i18n.T("Error: ") + result.Error) } tempContent.WriteString("\n") } diff --git a/widgets/handles.go b/widgets/handles.go index c9ba587..27bf1d7 100644 --- a/widgets/handles.go +++ b/widgets/handles.go @@ -2363,14 +2363,14 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { // Traceroute is not installed, show confirmation dialog m.Network.TracerouteLoading = false m.ConfirmationVisible = true - m.ConfirmationMessage = "Traceroute is not installed. Would you like to install it?" + m.ConfirmationMessage = i18n.T("Traceroute is not installed. Would you like to install it?") m.ConfirmationAction = "install_traceroute" m.ConfirmationData = msg.Target return m, nil case network.TracerouteInstallResultMsg: m.OperationInProgress = false if msg.Success { - m.LastOperationMsg = "Traceroute installed successfully. Running traceroute..." + m.LastOperationMsg = i18n.T("Traceroute installed successfully. Running traceroute...") m.Network.TracerouteLoading = true m.resetSpinner() // Now run traceroute with the original target @@ -2390,7 +2390,7 @@ func (m Model) handleNetworkMsgs(msg tea.Msg) (tea.Model, tea.Cmd) { m.Network.TracerouteInput.Focus() return m, clearOperationMessage() } - m.LastOperationMsg = "Installation failed: " + msg.Error + m.LastOperationMsg = i18n.T("Installation failed: ") + msg.Error return m, clearOperationMessage() } } diff --git a/widgets/help.go b/widgets/help.go index 91593f3..53a9387 100644 --- a/widgets/help.go +++ b/widgets/help.go @@ -23,13 +23,20 @@ type BaseKeyMap struct { } func (k BaseKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Language, k.Quit} + keys := []key.Binding{k.Help} + if len(k.Language.Keys()) > 0 { + keys = append(keys, k.Language) + } + keys = append(keys, k.Quit) + return keys } func (k BaseKeyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{ - {k.Help, k.Language, k.Quit, k.Back}, + row := []key.Binding{k.Help, k.Quit, k.Back} + if len(k.Language.Keys()) > 0 { + row = []key.Binding{k.Help, k.Language, k.Quit, k.Back} } + return [][]key.Binding{row} } // HomeKeyMap defines keybindings for the home state @@ -44,14 +51,14 @@ type HomeKeyMap struct { } func (k HomeKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Select, k.Navigate, k.Help, k.Quit} + return []key.Binding{k.Select, k.Navigate, k.Help, k.Language, k.Quit} } func (k HomeKeyMap) FullHelp() [][]key.Binding { return [][]key.Binding{ {k.Select, k.Navigate}, {k.Quick1, k.Quick2, k.Quick3, k.Quick4}, - {k.Help, k.Quit}, + {k.Help, k.Language, k.Quit}, } } @@ -66,7 +73,7 @@ type MonitorKeyMap struct { } func (k MonitorKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Select, k.Navigate, k.Help, k.Back, k.Quit} + return []key.Binding{k.Select, k.Navigate, k.Help, k.Language, k.Back, k.Quit} } func (k MonitorKeyMap) FullHelp() [][]key.Binding { @@ -84,7 +91,7 @@ type SystemKeyMap struct { } func (k SystemKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Scroll, k.Help, k.Back, k.Quit} + return []key.Binding{k.Scroll, k.Help, k.Language, k.Back, k.Quit} } func (k SystemKeyMap) FullHelp() [][]key.Binding { @@ -105,7 +112,7 @@ type ProcessKeyMap struct { } func (k ProcessKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Navigate, k.Search, k.Help, k.Back, k.Quit} + return []key.Binding{k.Navigate, k.Search, k.Help, k.Language, k.Back, k.Quit} } func (k ProcessKeyMap) FullHelp() [][]key.Binding { @@ -125,7 +132,7 @@ type ContainersKeyMap struct { } func (k ContainersKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Navigate, k.Select, k.Search, k.Help, k.Back, k.Quit} + return []key.Binding{k.Navigate, k.Select, k.Search, k.Help, k.Language, k.Back, k.Quit} } func (k ContainersKeyMap) FullHelp() [][]key.Binding { @@ -142,7 +149,7 @@ type ContainerKeyMap struct { } func (k ContainerKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.SwitchTab, k.Help, k.Back, k.Quit} + return []key.Binding{k.SwitchTab, k.Help, k.Language, k.Back, k.Quit} } func (k ContainerKeyMap) FullHelp() [][]key.Binding { @@ -162,7 +169,7 @@ type ContainerLogsKeyMap struct { } func (k ContainerLogsKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Scroll, k.Refresh, k.Help, k.Back, k.Quit} + return []key.Binding{k.Scroll, k.Refresh, k.Help, k.Language, k.Back, k.Quit} } func (k ContainerLogsKeyMap) FullHelp() [][]key.Binding { @@ -185,7 +192,7 @@ type NetworkKeyMap struct { } func (k NetworkKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.SwitchTab, k.Navigate, k.Help, k.Back, k.Quit} + return []key.Binding{k.SwitchTab, k.Navigate, k.Help, k.Language, k.Back, k.Quit} } func (k NetworkKeyMap) FullHelp() [][]key.Binding { @@ -214,7 +221,7 @@ type DiagnosticsKeyMap struct { } func (k DiagnosticsKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Navigate, k.Search, k.SwitchTab, k.Help, k.Back, k.Quit} + return []key.Binding{k.Navigate, k.Search, k.SwitchTab, k.Help, k.Language, k.Back, k.Quit} } func (k DiagnosticsKeyMap) FullHelp() [][]key.Binding { @@ -258,7 +265,7 @@ type ContainerMenuKeyMap struct { } func (k ContainerMenuKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Navigate, k.Select, k.Help, k.Back, k.Quit} + return []key.Binding{k.Navigate, k.Select, k.Help, k.Language, k.Back, k.Quit} } func (k ContainerMenuKeyMap) FullHelp() [][]key.Binding { @@ -282,7 +289,7 @@ type ReportingKeyMap struct { } func (k ReportingKeyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Back, k.Quit} + return []key.Binding{k.Help, k.Language, k.Back, k.Quit} } func (k ReportingKeyMap) FullHelp() [][]key.Binding { diff --git a/widgets/init.go b/widgets/init.go index 9e12d75..a6cca42 100644 --- a/widgets/init.go +++ b/widgets/init.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "time" "github.com/Server-Pulse/server-pulse/system/app" diff --git a/widgets/model/performance.go b/widgets/model/performance.go index 7d1ca30..47ebd85 100644 --- a/widgets/model/performance.go +++ b/widgets/model/performance.go @@ -1,9 +1,10 @@ package model import ( - "time" "github.com/Server-Pulse/server-pulse/i18n" + "time" ) + // HealthMetrics holds the metrics for the system health check. type HealthMetrics struct { IOWait float64 diff --git a/widgets/model/reporting.go b/widgets/model/reporting.go index 6476b9f..fdf51e2 100644 --- a/widgets/model/reporting.go +++ b/widgets/model/reporting.go @@ -1,8 +1,8 @@ package model import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "os" "path/filepath" "sort" diff --git a/widgets/render-body.go b/widgets/render-body.go index 93ebe11..8265f00 100644 --- a/widgets/render-body.go +++ b/widgets/render-body.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "github.com/Server-Pulse/server-pulse/widgets/model" ) diff --git a/widgets/render-charts.go b/widgets/render-charts.go index 03759b8..dc08b3e 100644 --- a/widgets/render-charts.go +++ b/widgets/render-charts.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "math" model "github.com/Server-Pulse/server-pulse/widgets/model" diff --git a/widgets/render-common.go b/widgets/render-common.go index d4cd5a0..2c2c9bf 100644 --- a/widgets/render-common.go +++ b/widgets/render-common.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" v "github.com/Server-Pulse/server-pulse/widgets/vars" "strings" diff --git a/widgets/render-container.go b/widgets/render-container.go index e05c3b5..667e0e3 100644 --- a/widgets/render-container.go +++ b/widgets/render-container.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/utils" @@ -104,7 +104,7 @@ func (m Model) renderContainerGeneral() string { if len(m.Monitor.ContainerDetails.Ports) > 0 { doc.WriteString("\n\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Ports:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Ports:"))) doc.WriteString("\n") for _, port := range m.Monitor.ContainerDetails.Ports { portInfo := fmt.Sprintf(" %s:%d → %d/%s", port.HostIP, port.PublicPort, port.PrivatePort, port.Type) @@ -115,7 +115,7 @@ func (m Model) renderContainerGeneral() string { if m.Monitor.ContainerDetails.Command != "" { doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Bold(true).Render("Command:")) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Command:"))) doc.WriteString("\n") doc.WriteString(v.MetricValueStyle.Render(" " + m.Monitor.ContainerDetails.Command)) } diff --git a/widgets/render-diagnostic.go b/widgets/render-diagnostic.go index 87b2953..56dba7e 100644 --- a/widgets/render-diagnostic.go +++ b/widgets/render-diagnostic.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/system/performance" @@ -284,7 +284,7 @@ func (m Model) renderFirewallDetails() string { doc := strings.Builder{} // Title - title := fmt.Sprintf("Firewall Details - %s", m.Diagnostic.FirewallInfo.FirewallType) + title := fmt.Sprintf(i18n.T("Firewall Details - %s"), m.Diagnostic.FirewallInfo.FirewallType) doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(title)) doc.WriteString("\n\n") @@ -305,7 +305,7 @@ func (m Model) renderFirewallDetails() string { // Rules summary if len(m.Diagnostic.FirewallInfo.Rules) > 0 { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render(fmt.Sprintf("Firewall Rules (%d total)", len(m.Diagnostic.FirewallInfo.Rules)))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(fmt.Sprintf(i18n.T("Firewall Rules (%d total)"), len(m.Diagnostic.FirewallInfo.Rules)))) doc.WriteString("\n\n") doc.WriteString(m.Diagnostic.FirewallTable.View()) doc.WriteString("\n\n") @@ -318,7 +318,7 @@ func (m Model) renderFirewallDetails() string { if m.Diagnostic.FirewallInfo.RawOutput != "" { doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Complete Firewall Configuration:"))) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from firewall command)")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("(Full raw output from firewall command)"))) doc.WriteString("\n\n") // Show raw output in a bordered box @@ -343,7 +343,7 @@ func (m Model) renderAutoBanDetails() string { doc := strings.Builder{} // Title - title := fmt.Sprintf("Auto Ban Details - %s", m.Diagnostic.AutoBanInfo.ServiceType) + title := fmt.Sprintf(i18n.T("Auto Ban Details - %s"), m.Diagnostic.AutoBanInfo.ServiceType) doc.WriteString(lipgloss.NewStyle().Bold(true).Underline(true).MarginBottom(1).Render(title)) doc.WriteString("\n\n") @@ -371,7 +371,7 @@ func (m Model) renderAutoBanDetails() string { // Jails/Services table if len(m.Diagnostic.AutoBanInfo.Jails) > 0 { - doc.WriteString(lipgloss.NewStyle().Bold(true).Render(fmt.Sprintf("Jails/Services (%d total)", len(m.Diagnostic.AutoBanInfo.Jails)))) + doc.WriteString(lipgloss.NewStyle().Bold(true).Render(fmt.Sprintf(i18n.T("Jails/Services (%d total)"), len(m.Diagnostic.AutoBanInfo.Jails)))) doc.WriteString("\n\n") doc.WriteString(m.Diagnostic.AutoBanTable.View()) doc.WriteString("\n\n") @@ -384,7 +384,7 @@ func (m Model) renderAutoBanDetails() string { if m.Diagnostic.AutoBanInfo.RawOutput != "" { doc.WriteString(lipgloss.NewStyle().Bold(true).Render(i18n.T("Complete Configuration:"))) doc.WriteString("\n") - doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render("(Full raw output from service)")) + doc.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("244")).Render(i18n.T("(Full raw output from service)"))) doc.WriteString("\n\n") rawOutputStyle := lipgloss.NewStyle(). @@ -445,7 +445,7 @@ func (m Model) renderDiagnosticLogs() string { } // Time range options - timeRanges := []string{"All", "5m", "1h", "24h", "7d", "Custom"} + timeRanges := i18n.Ts("All", "5m", "1h", "24h", "7d", "Custom") timeRangeStr := timeIndicator + ": " for i, tr := range timeRanges { if i == m.Diagnostic.LogTimeSelected { @@ -458,7 +458,7 @@ func (m Model) renderDiagnosticLogs() string { filterDoc.WriteString("\n") // Log level options - levels := []string{"All", "Error", "Warn", "Info", "Debug"} + levels := i18n.Ts("All", "Error", "Warn", "Info", "Debug") levelStr := levelIndicator + ": " for i, level := range levels { if i == m.Diagnostic.LogLevelSelected { @@ -471,8 +471,8 @@ func (m Model) renderDiagnosticLogs() string { filterDoc.WriteString("\n") // Filter inputs - filterDoc.WriteString("Search: " + m.Diagnostic.LogSearchInput.View() + " ") - filterDoc.WriteString("Service: " + m.Diagnostic.LogServiceInput.View()) + filterDoc.WriteString(i18n.T("Search: ") + m.Diagnostic.LogSearchInput.View() + " ") + filterDoc.WriteString(i18n.T("Service: ") + m.Diagnostic.LogServiceInput.View()) doc.WriteString(filterStyle.Render(filterDoc.String())) doc.WriteString("\n\n") @@ -494,7 +494,7 @@ func (m Model) renderDiagnosticLogs() string { // Summary summaryStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("244")) - summary := fmt.Sprintf("Showing %d entries", m.Diagnostic.LogsInfo.TotalCount) + summary := fmt.Sprintf(i18n.T("Showing %d entries"), m.Diagnostic.LogsInfo.TotalCount) if m.Diagnostic.LogsInfo.HasMore { summary += " (more available - increase limit or refine filters)" } diff --git a/widgets/render-footer.go b/widgets/render-footer.go index 3175ade..9379bcd 100644 --- a/widgets/render-footer.go +++ b/widgets/render-footer.go @@ -3,6 +3,7 @@ package widgets import ( "strings" + "github.com/Server-Pulse/server-pulse/i18n" "github.com/Server-Pulse/server-pulse/widgets/model" v "github.com/Server-Pulse/server-pulse/widgets/vars" "github.com/charmbracelet/lipgloss" @@ -16,10 +17,11 @@ func (m Model) renderFooter() string { Background(v.PurpleCollor). Padding(0, 1). Bold(true) - statusLine += statusStyle.Render("⏳ Operation in progress...") + "\n" + statusLine += statusStyle.Render(i18n.T("⏳ Operation in progress...")) + "\n" } else if m.LastOperationMsg != "" { var statusStyle lipgloss.Style - if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") { + if strings.Contains(m.LastOperationMsg, "failed") || strings.Contains(m.LastOperationMsg, "Error") || + strings.Contains(m.LastOperationMsg, "失败") || strings.Contains(m.LastOperationMsg, "错误") { statusStyle = lipgloss.NewStyle(). Foreground(v.WhiteColor). Background(v.ErrorColor). diff --git a/widgets/render-header.go b/widgets/render-header.go index c62b67b..c3a8bab 100644 --- a/widgets/render-header.go +++ b/widgets/render-header.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" v "github.com/Server-Pulse/server-pulse/widgets/vars" "strings" diff --git a/widgets/render-monitor.go b/widgets/render-monitor.go index 8c8f4e2..6c1ab70 100644 --- a/widgets/render-monitor.go +++ b/widgets/render-monitor.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/utils" diff --git a/widgets/render-network.go b/widgets/render-network.go index da8320b..0154c37 100644 --- a/widgets/render-network.go +++ b/widgets/render-network.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" "github.com/Server-Pulse/server-pulse/widgets/auth" @@ -206,7 +206,7 @@ func (m Model) renderConnectivityAnalysis() string { statusLine += fmt.Sprintf(i18n.T("Latency: %v, Packet Loss: %.1f%%"), result.Latency, result.PacketLoss) } else { - statusLine += fmt.Sprintf("Error: %s", result.Error) + statusLine += i18n.T("Error: ") + result.Error } resultsContent.WriteString(lipgloss.NewStyle().Foreground(statusColor).Render(statusLine) + "\n") @@ -400,12 +400,12 @@ func (m Model) renderNetworkConfiguration() string { footer := lipgloss.NewStyle(). Foreground(lipgloss.Color("244")). - Render(fmt.Sprintf("\nCurrently viewing: %s | Press SPACE to switch", + Render(fmt.Sprintf(i18n.T("\nCurrently viewing: %s | Press SPACE to switch"), func() string { if m.Network.RoutesTable.Focused() { - return "Routing Table" + return i18n.T("Routing Table") } - return "DNS Servers" + return i18n.T("DNS Servers") }())) content.WriteString(footer) diff --git a/widgets/render-reporting.go b/widgets/render-reporting.go index e5fe4e5..4ea94c6 100644 --- a/widgets/render-reporting.go +++ b/widgets/render-reporting.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "strings" model "github.com/Server-Pulse/server-pulse/widgets/model" diff --git a/widgets/utils.go b/widgets/utils.go index cbf63b7..b1e275a 100644 --- a/widgets/utils.go +++ b/widgets/utils.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "math/rand" "os/exec" diff --git a/widgets/view.go b/widgets/view.go index 516fc39..b654903 100644 --- a/widgets/view.go +++ b/widgets/view.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" v "github.com/Server-Pulse/server-pulse/widgets/vars" diff --git a/widgets/widgets.go b/widgets/widgets.go index 4ad9ea2..2ffbeb4 100644 --- a/widgets/widgets.go +++ b/widgets/widgets.go @@ -1,8 +1,8 @@ package widgets import ( - "github.com/Server-Pulse/server-pulse/i18n" "fmt" + "github.com/Server-Pulse/server-pulse/i18n" "sort" "strings"