diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index 72ba653a..b363f632 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml @@ -5,7 +5,7 @@ on: - main pull_request: jobs: - mockery: + xgotext: runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/cmd/cli/wire_gen.go b/cmd/cli/wire_gen.go index 88e3ec41..82abe325 100644 --- a/cmd/cli/wire_gen.go +++ b/cmd/cli/wire_gen.go @@ -76,30 +76,30 @@ func initCli() (*app.Cli, error) { cli := route.NewCli(cliService) command := bootstrap.NewCli(locale, cli) gormigrate := bootstrap.NewMigrate(db) - benchmarkApp := benchmark.NewApp() + benchmarkApp := benchmark.NewApp(locale) dockerApp := docker.NewApp() - fail2banApp := fail2ban.NewApp(websiteRepo) + fail2banApp := fail2ban.NewApp(locale, websiteRepo) frpApp := frp.NewApp() giteaApp := gitea.NewApp() - memcachedApp := memcached.NewApp() + memcachedApp := memcached.NewApp(locale) minioApp := minio.NewApp() - mysqlApp := mysql.NewApp(settingRepo) - nginxApp := nginx.NewApp() - php74App := php74.NewApp(taskRepo) - php80App := php80.NewApp(taskRepo) - php81App := php81.NewApp(taskRepo) - php82App := php82.NewApp(taskRepo) - php83App := php83.NewApp(taskRepo) - php84App := php84.NewApp(taskRepo) - phpmyadminApp := phpmyadmin.NewApp() + mysqlApp := mysql.NewApp(locale, settingRepo) + nginxApp := nginx.NewApp(locale) + php74App := php74.NewApp(locale, taskRepo) + php80App := php80.NewApp(locale, taskRepo) + php81App := php81.NewApp(locale, taskRepo) + php82App := php82.NewApp(locale, taskRepo) + php83App := php83.NewApp(locale, taskRepo) + php84App := php84.NewApp(locale, taskRepo) + phpmyadminApp := phpmyadmin.NewApp(locale) podmanApp := podman.NewApp() - postgresqlApp := postgresql.NewApp() - pureftpdApp := pureftpd.NewApp() - redisApp := redis.NewApp() - rsyncApp := rsync.NewApp() - s3fsApp := s3fs.NewApp(settingRepo) - supervisorApp := supervisor.NewApp() - toolboxApp := toolbox.NewApp() + postgresqlApp := postgresql.NewApp(locale) + pureftpdApp := pureftpd.NewApp(locale) + redisApp := redis.NewApp(locale) + rsyncApp := rsync.NewApp(locale) + s3fsApp := s3fs.NewApp(locale, settingRepo) + supervisorApp := supervisor.NewApp(locale) + toolboxApp := toolbox.NewApp(locale) loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp) appCli := app.NewCli(command, gormigrate, loader) return appCli, nil diff --git a/cmd/web/wire_gen.go b/cmd/web/wire_gen.go index edab39af..76124c2c 100644 --- a/cmd/web/wire_gen.go +++ b/cmd/web/wire_gen.go @@ -111,30 +111,34 @@ func initWeb() (*app.Web, error) { monitorService := service.NewMonitorService(settingRepo, monitorRepo) settingService := service.NewSettingService(settingRepo) systemctlService := service.NewSystemctlService() - benchmarkApp := benchmark.NewApp() + locale, err := bootstrap.NewT(koanf) + if err != nil { + return nil, err + } + benchmarkApp := benchmark.NewApp(locale) dockerApp := docker.NewApp() - fail2banApp := fail2ban.NewApp(websiteRepo) + fail2banApp := fail2ban.NewApp(locale, websiteRepo) frpApp := frp.NewApp() giteaApp := gitea.NewApp() - memcachedApp := memcached.NewApp() + memcachedApp := memcached.NewApp(locale) minioApp := minio.NewApp() - mysqlApp := mysql.NewApp(settingRepo) - nginxApp := nginx.NewApp() - php74App := php74.NewApp(taskRepo) - php80App := php80.NewApp(taskRepo) - php81App := php81.NewApp(taskRepo) - php82App := php82.NewApp(taskRepo) - php83App := php83.NewApp(taskRepo) - php84App := php84.NewApp(taskRepo) - phpmyadminApp := phpmyadmin.NewApp() + mysqlApp := mysql.NewApp(locale, settingRepo) + nginxApp := nginx.NewApp(locale) + php74App := php74.NewApp(locale, taskRepo) + php80App := php80.NewApp(locale, taskRepo) + php81App := php81.NewApp(locale, taskRepo) + php82App := php82.NewApp(locale, taskRepo) + php83App := php83.NewApp(locale, taskRepo) + php84App := php84.NewApp(locale, taskRepo) + phpmyadminApp := phpmyadmin.NewApp(locale) podmanApp := podman.NewApp() - postgresqlApp := postgresql.NewApp() - pureftpdApp := pureftpd.NewApp() - redisApp := redis.NewApp() - rsyncApp := rsync.NewApp() - s3fsApp := s3fs.NewApp(settingRepo) - supervisorApp := supervisor.NewApp() - toolboxApp := toolbox.NewApp() + postgresqlApp := postgresql.NewApp(locale) + pureftpdApp := pureftpd.NewApp(locale) + redisApp := redis.NewApp(locale) + rsyncApp := rsync.NewApp(locale) + s3fsApp := s3fs.NewApp(locale, settingRepo) + supervisorApp := supervisor.NewApp(locale) + toolboxApp := toolbox.NewApp(locale) loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp) http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader) wsService := service.NewWsService(koanf, sshRepo) diff --git a/go.sum b/go.sum index 08d0c265..19ccca24 100644 --- a/go.sum +++ b/go.sum @@ -57,7 +57,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= @@ -203,8 +202,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -264,8 +261,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/apps/benchmark/app.go b/internal/apps/benchmark/app.go index 62292129..3fb6f1ce 100644 --- a/internal/apps/benchmark/app.go +++ b/internal/apps/benchmark/app.go @@ -22,15 +22,19 @@ import ( "time" "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/service" ) type App struct { + t *gotext.Locale } -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -74,7 +78,7 @@ func (s *App) Test(w http.ResponseWriter, r *http.Request) { result := s.memoryTestTask() service.Success(w, result) default: - service.Error(w, http.StatusUnprocessableEntity, "未知测试类型") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown test type")) } } diff --git a/internal/apps/fail2ban/app.go b/internal/apps/fail2ban/app.go index 12c1948f..3c80910e 100644 --- a/internal/apps/fail2ban/app.go +++ b/internal/apps/fail2ban/app.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" "github.com/go-rat/utils/str" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -19,11 +20,13 @@ import ( ) type App struct { + t *gotext.Locale websiteRepo biz.WebsiteRepo } -func NewApp(website biz.WebsiteRepo) *App { +func NewApp(t *gotext.Locale, website biz.WebsiteRepo) *App { return &App{ + t: t, websiteRepo: website, } } @@ -47,12 +50,8 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) { } jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1) - if len(jailList) == 0 { - service.Error(w, http.StatusUnprocessableEntity, "Fail2ban 规则为空") - return - } - var jails []Jail + jails := make([]Jail, 0) for i, jail := range jailList { if i == 0 { continue @@ -107,7 +106,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { return } if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") { - service.Error(w, http.StatusUnprocessableEntity, "规则已存在") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule already exists")) return } @@ -115,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { case "website": website, err := s.websiteRepo.GetByName(jailWebsiteName) if err != nil { - service.Error(w, http.StatusUnprocessableEntity, "获取网站配置失败:%v", err) + service.Error(w, http.StatusUnprocessableEntity, "%v", err) return } var ports string @@ -140,7 +139,7 @@ logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log ` raw += rule if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -159,7 +158,7 @@ ignoreregex = ` } if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -178,11 +177,11 @@ ignoreregex = filter = "pure-ftpd" port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root) default: - service.Error(w, http.StatusUnprocessableEntity, "未知服务") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown service")) return } if len(port) == 0 || err != nil { - service.Error(w, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("get service port failed, please check if it is installed")) return } @@ -199,13 +198,13 @@ bantime = ` + jailBanTime + ` ` raw += rule if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } } if _, err = shell.Execf("fail2ban-client reload"); err != nil { - service.Error(w, http.StatusInternalServerError, "重载配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -226,7 +225,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { return } if !strings.Contains(raw, "["+req.Name+"]") { - service.Error(w, http.StatusUnprocessableEntity, "规则不存在") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule not found")) return } @@ -234,12 +233,12 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { raw = strings.ReplaceAll(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "") raw = strings.TrimSpace(raw) if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } if _, err := shell.Execf("fail2ban-client reload"); err != nil { - service.Error(w, http.StatusInternalServerError, "重载配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -256,17 +255,17 @@ func (s *App) BanList(w http.ResponseWriter, r *http.Request) { currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, req.Name) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取封禁列表失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get current banned list")) return } totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, req.Name) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取封禁列表失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get total banned list")) return } bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, req.Name) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取封禁列表失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get banned ip list")) return } bannedIpList := strings.Split(bannedIp, " ") @@ -300,7 +299,7 @@ func (s *App) Unban(w http.ResponseWriter, r *http.Request) { } if _, err = shell.Execf("fail2ban-client set %s unbanip %s", req.Name, req.IP); err != nil { - service.Error(w, http.StatusInternalServerError, "解封失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -325,17 +324,17 @@ func (s *App) SetWhiteList(w http.ResponseWriter, r *http.Request) { if reg.MatchString(raw) { raw = reg.ReplaceAllString(raw, "ignoreip = "+req.IP+"\n") } else { - service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban")) return } if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } if _, err = shell.Execf("fail2ban-client reload"); err != nil { - service.Error(w, http.StatusInternalServerError, "重载配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } service.Success(w, nil) @@ -353,7 +352,7 @@ func (s *App) GetWhiteList(w http.ResponseWriter, r *http.Request) { ignoreIp := reg.FindStringSubmatch(raw)[1] service.Success(w, ignoreIp) } else { - service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban")) return } } diff --git a/internal/apps/memcached/app.go b/internal/apps/memcached/app.go index 8876b84f..c96ccd52 100644 --- a/internal/apps/memcached/app.go +++ b/internal/apps/memcached/app.go @@ -7,6 +7,7 @@ import ( "regexp" "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/service" "github.com/tnb-labs/panel/pkg/io" @@ -14,10 +15,14 @@ import ( "github.com/tnb-labs/panel/pkg/types" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -29,7 +34,7 @@ func (s *App) Route(r chi.Router) { func (s *App) Load(w http.ResponseWriter, r *http.Request) { status, err := systemctl.Status("memcached") if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get Memcached status: %v"), err) return } if !status { @@ -48,7 +53,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { _, err = conn.Write([]byte("stats\nquit\n")) if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write to Memcached: %v"), err) return } @@ -69,7 +74,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { } if err = scanner.Err(); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to read from Memcached: %v"), err) return } diff --git a/internal/apps/mysql/app.go b/internal/apps/mysql/app.go index 447e0f65..b26a8cc5 100644 --- a/internal/apps/mysql/app.go +++ b/internal/apps/mysql/app.go @@ -7,6 +7,7 @@ import ( "regexp" "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -21,11 +22,13 @@ import ( ) type App struct { + t *gotext.Locale settingRepo biz.SettingRepo } -func NewApp(setting biz.SettingRepo) *App { +func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App { return &App{ + t: t, settingRepo: setting, } } @@ -45,7 +48,7 @@ func (s *App) Route(r chi.Router) { func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) { config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -61,12 +64,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) { } if err = io.Write(app.Root+"/server/mysql/conf/my.cnf", req.Config, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入配置失败:%v", err) + service.Error(w, http.StatusInternalServerError, "%v", err) return } if err = systemctl.Restart("mysqld"); err != nil { - service.Error(w, http.StatusInternalServerError, "重启失败:%v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart MySQL: %v"), err) return } @@ -77,12 +80,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) { func (s *App) Load(w http.ResponseWriter, r *http.Request) { rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取root密码失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err)) return } if len(rootPassword) == 0 { - service.Error(w, http.StatusUnprocessableEntity, "root密码为空") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("MySQL root password is empty")) return } @@ -93,12 +96,12 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { } if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil { - service.Error(w, http.StatusInternalServerError, "设置环境变量失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MYSQL_PWD env: %v", err)) return } raw, err := shell.Execf(`mysqladmin -u root extended-status`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取负载失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MySQL status: %v", err)) return } _ = os.Unsetenv("MYSQL_PWD") @@ -108,24 +111,24 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { regex string name string }{ - {`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"}, - {`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"}, - {`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"}, - {`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"}, - {`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"}, - {`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"}, - {`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"}, - {`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"}, - {`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"}, - {`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"}, - {`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"}, - {`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到硬盘"}, - {`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"}, - {`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"}, - {`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"}, - {`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"}, - {`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"}, - {`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"}, + {`Uptime\s+\|\s+(\d+)\s+\|`, s.t.Get("Uptime")}, + {`Queries\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Queries")}, + {`Connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Connections")}, + {`Com_commit\s+\|\s+(\d+)\s+\|`, s.t.Get("Transactions per Second")}, + {`Com_rollback\s+\|\s+(\d+)\s+\|`, s.t.Get("Rollbacks per Second")}, + {`Bytes_sent\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Sent")}, + {`Bytes_received\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Received")}, + {`Threads_connected\s+\|\s+(\d+)\s+\|`, s.t.Get("Active Connections")}, + {`Max_used_connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Peak Connections")}, + {`Key_read_requests\s+\|\s+(\d+)\s+\|`, s.t.Get("Index Hit Rate")}, + {`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, s.t.Get("Innodb Index Hit Rate")}, + {`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Temporary Tables Created on Disk")}, + {`Open_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Open Tables")}, + {`Select_full_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Joins without Index")}, + {`Select_full_range_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Range Joins without Index")}, + {`Select_range_check\s+\|\s+(\d+)\s+\|`, s.t.Get("Subqueries without Index")}, + {`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, s.t.Get("Sort Merge Passes")}, + {`Table_locks_waited\s+\|\s+(\d+)\s+\|`, s.t.Get("Table Locks Waited")}, } for _, expression := range expressions { @@ -133,7 +136,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { matches := re.FindStringSubmatch(raw) if len(matches) > 1 { d := map[string]string{"name": expression.name, "value": matches[1]} - if expression.name == "发送" || expression.name == "接收" { + if expression.name == s.t.Get("Bytes Sent") || expression.name == s.t.Get("Bytes Received") { d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1])) } @@ -182,11 +185,7 @@ func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) { func (s *App) GetRootPassword(w http.ResponseWriter, r *http.Request) { rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取root密码失败") - return - } - if len(rootPassword) == 0 { - service.Error(w, http.StatusUnprocessableEntity, "root密码为空") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err)) return } @@ -219,7 +218,7 @@ func (s *App) SetRootPassword(w http.ResponseWriter, r *http.Request) { } } if err = s.settingRepo.Set(biz.SettingKeyMySQLRootPassword, req.Password); err != nil { - service.Error(w, http.StatusInternalServerError, "设置保存失败:%v", err) + service.Error(w, http.StatusInternalServerError, "%v", err) return } diff --git a/internal/apps/nginx/app.go b/internal/apps/nginx/app.go index 6e84f7f6..1a48f5f0 100644 --- a/internal/apps/nginx/app.go +++ b/internal/apps/nginx/app.go @@ -8,6 +8,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-resty/resty/v2" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -20,11 +21,13 @@ import ( ) type App struct { - // Dependent services + t *gotext.Locale } -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -38,7 +41,7 @@ func (s *App) Route(r chi.Router) { func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) { config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root)) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -53,13 +56,13 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) { } if err = io.Write(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root), req.Config, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "保存配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } if err = systemctl.Reload("nginx"); err != nil { _, err = shell.Execf("nginx -t") - service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v"), err) return } @@ -84,6 +87,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { resp, err := client.R().Get("http://127.0.0.1/nginx_status") if err != nil || !resp.IsSuccess() { service.Success(w, []types.NV{}) + return } raw := resp.String() @@ -91,29 +95,29 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取负载失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v"), err) return } data = append(data, types.NV{ - Name: "工作进程", + Name: s.t.Get("Workers"), Value: workers, }) out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取负载失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v"), err) return } mem := tools.FormatBytes(cast.ToFloat64(out)) data = append(data, types.NV{ - Name: "内存占用", + Name: s.t.Get("Memory"), Value: mem, }) match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 2 { data = append(data, types.NV{ - Name: "活跃连接数", + Name: s.t.Get("Active connections"), Value: match[1], }) } @@ -121,15 +125,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 4 { data = append(data, types.NV{ - Name: "总连接次数", + Name: s.t.Get("Total connections"), Value: match[1], }) data = append(data, types.NV{ - Name: "总握手次数", + Name: s.t.Get("Total handshakes"), Value: match[2], }) data = append(data, types.NV{ - Name: "总请求次数", + Name: s.t.Get("Total requests"), Value: match[3], }) } @@ -137,15 +141,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw) if len(match) == 4 { data = append(data, types.NV{ - Name: "请求数", + Name: s.t.Get("Reading"), Value: match[1], }) data = append(data, types.NV{ - Name: "响应数", + Name: s.t.Get("Writing"), Value: match[2], }) data = append(data, types.NV{ - Name: "驻留进程", + Name: s.t.Get("Waiting"), Value: match[3], }) } diff --git a/internal/apps/php/app.go b/internal/apps/php/app.go index 2a37cadf..6f859a20 100644 --- a/internal/apps/php/app.go +++ b/internal/apps/php/app.go @@ -10,6 +10,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-resty/resty/v2" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -22,11 +23,13 @@ import ( type App struct { version uint + t *gotext.Locale taskRepo biz.TaskRepo } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ + t: t, taskRepo: task, } } @@ -120,8 +123,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { return } - dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"} - rawKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"} + dataKeys := []string{ + s.t.Get("Application Pool"), + s.t.Get("Process Manager"), + s.t.Get("Start Time"), + s.t.Get("Accepted Connections"), + s.t.Get("Listen Queue"), + s.t.Get("Max Listen Queue"), + s.t.Get("Listen Queue Length"), + s.t.Get("Idle Processes"), + s.t.Get("Active Processes"), + s.t.Get("Total Processes"), + s.t.Get("Max Active Processes"), + s.t.Get("Max Children Reached"), + s.t.Get("Slow Requests"), + } + rawKeys := []string{ + "pool", + "process manager", + "start time", + "accepted conn", + "listen queue", + "max listen queue", + "listen queue len", + "idle processes", + "active processes", + "total processes", + "max active processes", + "max children reached", + "slow requests", + } loads := make([]types.NV, 0) for i := range dataKeys { @@ -194,7 +225,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) { } if !s.checkExtension(req.Slug) { - service.Error(w, http.StatusUnprocessableEntity, "扩展不存在") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug)) return } @@ -205,7 +236,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) { } task := new(biz.Task) - task.Name = fmt.Sprintf("安装PHP-%d扩展 %s", s.version, req.Slug) + task.Name = s.t.Get("Install PHP-%d %s extension", s.version, req.Slug) task.Status = biz.TaskStatusWaiting task.Shell = cmd task.Log = "/tmp/" + req.Slug + ".log" @@ -225,7 +256,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) { } if !s.checkExtension(req.Slug) { - service.Error(w, http.StatusUnprocessableEntity, "扩展不存在") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug)) return } @@ -236,7 +267,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) { } task := new(biz.Task) - task.Name = fmt.Sprintf("卸载PHP-%d扩展 %s", s.version, req.Slug) + task.Name = s.t.Get("Uninstall PHP-%d %s extension", s.version, req.Slug) task.Status = biz.TaskStatusWaiting task.Shell = cmd task.Log = "/tmp/" + req.Slug + ".log" @@ -253,198 +284,197 @@ func (s *App) getExtensions() []Extension { { Name: "fileinfo", Slug: "fileinfo", - Description: "Fileinfo 是一个用于识别文件类型的库", + Description: s.t.Get("Fileinfo is a library used to identify file types"), }, { Name: "OPcache", Slug: "Zend OPcache", - Description: "OPcache 将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能", + Description: s.t.Get("OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"), }, { Name: "igbinary", Slug: "igbinary", - Description: "Igbinary 是一个用于序列化和反序列化数据的库", + Description: s.t.Get("Igbinary is a library for serializing and deserializing data"), }, { Name: "Redis", Slug: "redis", - Description: "PhpRedis 连接并操作 Redis 数据库上的数据(需先安装上面 igbinary 拓展)", + Description: s.t.Get("PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)"), }, { Name: "Memcached", Slug: "memcached", - Description: "Memcached 是一个用于连接 Memcached 服务器的驱动程序", + Description: s.t.Get("Memcached is a driver for connecting to Memcached servers"), }, { Name: "ImageMagick", Slug: "imagick", - Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件", + Description: s.t.Get("ImageMagick is free software for creating, editing, and composing images"), }, { Name: "exif", Slug: "exif", - Description: "exif 是一个用于读取和写入图像元数据的库", + Description: s.t.Get("Exif is a library for reading and writing image metadata"), }, { Name: "pgsql", Slug: "pgsql", - Description: "pgsql 是一个用于连接 PostgreSQL 的驱动程序(需先安装 PostgreSQL)", + Description: s.t.Get("pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"), }, { Name: "pdo_pgsql", Slug: "pdo_pgsql", - Description: "pdo_pgsql 是一个用于连接 PostgreSQL 的 PDO 驱动程序(需先安装 PostgreSQL)", + Description: s.t.Get("pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"), }, { Name: "sqlsrv", Slug: "sqlsrv", - Description: "sqlsrv 是一个用于连接 SQL Server 的驱动程序", + Description: s.t.Get("sqlsrv is a driver for connecting to SQL Server"), }, { Name: "pdo_sqlsrv", Slug: "pdo_sqlsrv", - Description: "pdo_sqlsrv 是一个用于连接 SQL Server 的 PDO 驱动程序", + Description: s.t.Get("pdo_sqlsrv is a PDO driver for connecting to SQL Server"), }, { Name: "imap", Slug: "imap", - Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件", + Description: s.t.Get("IMAP extension allows PHP to read, search, delete, download, and manage emails"), }, { Name: "zip", Slug: "zip", - Description: "Zip 是一个用于处理 ZIP 文件的库", + Description: s.t.Get("Zip is a library for handling ZIP files"), }, { Name: "bz2", Slug: "bz2", - Description: "Bzip2 是一个用于压缩和解压缩文件的库", + Description: s.t.Get("Bzip2 is a library for compressing and decompressing files"), }, { Name: "ssh2", Slug: "ssh2", - Description: "SSH2 是一个用于连接 SSH 服务器的库", + Description: s.t.Get("SSH2 is a library for connecting to SSH servers"), }, { Name: "event", Slug: "event", - Description: "Event 是一个用于处理事件的库", + Description: s.t.Get("Event is a library for handling events"), }, { Name: "readline", Slug: "readline", - Description: "Readline 是一个处理文本的库", + Description: s.t.Get("Readline is a library for processing text"), }, { Name: "snmp", Slug: "snmp", - Description: "SNMP 是一种用于网络管理的协议", + Description: s.t.Get("SNMP is a protocol for network management"), }, { Name: "ldap", Slug: "ldap", - Description: "LDAP 是一种用于访问目录服务的协议", + Description: s.t.Get("LDAP is a protocol for accessing directory services"), }, { Name: "enchant", Slug: "enchant", - Description: "Enchant 是一个拼写检查库", + Description: s.t.Get("Enchant is a spell-checking library"), }, { Name: "pspell", Slug: "pspell", - Description: "Pspell 是一个拼写检查库", + Description: s.t.Get("Pspell is a spell-checking library"), }, { Name: "calendar", Slug: "calendar", - Description: "Calendar 是一个用于处理日期的库", + Description: s.t.Get("Calendar is a library for handling dates"), }, { Name: "gmp", Slug: "gmp", - Description: "GMP 是一个用于处理大整数的库", + Description: s.t.Get("GMP is a library for handling large integers"), }, { Name: "xlswriter", Slug: "xlswriter", - Description: "XLSWriter 是一个高性能读写 Excel 文件的库", + Description: s.t.Get("XLSWriter is a high-performance library for reading and writing Excel files"), }, { Name: "xsl", Slug: "xsl", - Description: "XSL 是一个用于处理 XML 文档的库", + Description: s.t.Get("XSL is a library for processing XML documents"), }, { Name: "intl", Slug: "intl", - Description: "Intl 是一个用于处理国际化和本地化的库", + Description: s.t.Get("Intl is a library for handling internationalization and localization"), }, { Name: "gettext", Slug: "gettext", - Description: "Gettext 是一个用于处理多语言的库", + Description: s.t.Get("Gettext is a library for handling multilingual support"), }, { Name: "grpc", Slug: "grpc", - Description: "gRPC 是一个高性能、开源和通用的 RPC 框架", + Description: s.t.Get("gRPC is a high-performance, open-source, and general-purpose RPC framework"), }, { Name: "protobuf", Slug: "protobuf", - Description: "protobuf 是一个用于序列化和反序列化数据的库", + Description: s.t.Get("protobuf is a library for serializing and deserializing data"), }, { Name: "rdkafka", Slug: "rdkafka", - Description: "rdkafka 是一个用于连接 Apache Kafka 的库", + Description: s.t.Get("rdkafka is a library for connecting to Apache Kafka"), }, { Name: "xhprof", Slug: "xhprof", - Description: "xhprof 是一个用于性能分析的库", + Description: s.t.Get("xhprof is a library for performance profiling"), }, { Name: "xdebug", Slug: "xdebug", - Description: "xdebug 是一个用于调试和分析 PHP 代码的库", + Description: s.t.Get("xdebug is a library for debugging and profiling PHP code"), }, { Name: "yaml", Slug: "yaml", - Description: "yaml 是一个用于处理 YAML 的库", + Description: s.t.Get("yaml is a library for handling YAML"), }, { Name: "zstd", Slug: "zstd", - Description: "zstd 是一个用于压缩和解压缩文件的库", + Description: s.t.Get("zstd is a library for compressing and decompressing files"), }, - { Name: "sysvmsg", Slug: "sysvmsg", - Description: "Sysvmsg 是一个用于处理 System V 消息队列的库", + Description: s.t.Get("Sysvmsg is a library for handling System V message queues"), }, { Name: "sysvsem", Slug: "sysvsem", - Description: "Sysvsem 是一个用于处理 System V 信号量的库", + Description: s.t.Get("Sysvsem is a library for handling System V semaphores"), }, { Name: "sysvshm", Slug: "sysvshm", - Description: "Sysvshm 是一个用于处理 System V 共享内存的库", + Description: s.t.Get("Sysvshm is a library for handling System V shared memory"), }, { Name: "ionCube", Slug: "ionCube Loader", - Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)", + Description: s.t.Get("ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"), }, { Name: "Swoole", Slug: "swoole", - Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展", + Description: s.t.Get("Swoole is a PHP extension for building high-performance asynchronous concurrent servers"), }, } @@ -453,7 +483,7 @@ func (s *App) getExtensions() []Extension { extensions = append(extensions, Extension{ Name: "Swow", Slug: "Swow", - Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展", + Description: s.t.Get("Swow is a PHP extension for building high-performance asynchronous concurrent servers"), }) } // PHP 8.4 移除了 pspell 和 imap 并且不再建议使用 diff --git a/internal/apps/php74/app.go b/internal/apps/php74/app.go index abbc463b..bc0698e1 100644 --- a/internal/apps/php74/app.go +++ b/internal/apps/php74/app.go @@ -2,6 +2,7 @@ package php74 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/php80/app.go b/internal/apps/php80/app.go index 159d884c..31259c9a 100644 --- a/internal/apps/php80/app.go +++ b/internal/apps/php80/app.go @@ -2,6 +2,7 @@ package php80 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/php81/app.go b/internal/apps/php81/app.go index ca9f3e13..551e827c 100644 --- a/internal/apps/php81/app.go +++ b/internal/apps/php81/app.go @@ -2,6 +2,7 @@ package php81 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/php82/app.go b/internal/apps/php82/app.go index 0049d565..26c48a25 100644 --- a/internal/apps/php82/app.go +++ b/internal/apps/php82/app.go @@ -2,6 +2,7 @@ package php82 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/php83/app.go b/internal/apps/php83/app.go index 49389969..e5af2819 100644 --- a/internal/apps/php83/app.go +++ b/internal/apps/php83/app.go @@ -2,6 +2,7 @@ package php83 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/php84/app.go b/internal/apps/php84/app.go index 50ab7de3..e9b9b15e 100644 --- a/internal/apps/php84/app.go +++ b/internal/apps/php84/app.go @@ -2,6 +2,7 @@ package php84 import ( "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/apps/php" "github.com/tnb-labs/panel/internal/biz" @@ -11,9 +12,9 @@ type App struct { php *php.App } -func NewApp(task biz.TaskRepo) *App { +func NewApp(t *gotext.Locale, task biz.TaskRepo) *App { return &App{ - php: php.NewApp(task), + php: php.NewApp(t, task), } } diff --git a/internal/apps/phpmyadmin/app.go b/internal/apps/phpmyadmin/app.go index 5982b183..f0137d44 100644 --- a/internal/apps/phpmyadmin/app.go +++ b/internal/apps/phpmyadmin/app.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -19,10 +20,14 @@ import ( "github.com/tnb-labs/panel/pkg/systemctl" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -35,7 +40,7 @@ func (s *App) Route(r chi.Router) { func (s *App) Info(w http.ResponseWriter, r *http.Request) { files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root)) if err != nil { - service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录") + service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found")) return } @@ -46,7 +51,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) { } } if len(phpmyadmin) == 0 { - service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录") + service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found")) return } @@ -57,7 +62,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) { } match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf) if len(match) == 0 { - service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 端口") + service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin port not found")) return } @@ -81,7 +86,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) { } conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(req.Port)+";") if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root), conf, 0644); err != nil { - service.ErrorSystem(w) + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -100,7 +105,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) { if err = systemctl.Reload("nginx"); err != nil { _, err = shell.Execf("nginx -t") - service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err)) return } @@ -131,7 +136,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) { if err = systemctl.Reload("nginx"); err != nil { _, err = shell.Execf("nginx -t") - service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err)) return } diff --git a/internal/apps/postgresql/app.go b/internal/apps/postgresql/app.go index 1fc4cd21..e82415b0 100644 --- a/internal/apps/postgresql/app.go +++ b/internal/apps/postgresql/app.go @@ -6,6 +6,7 @@ import ( "time" "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/app" "github.com/tnb-labs/panel/internal/service" @@ -15,10 +16,14 @@ import ( "github.com/tnb-labs/panel/pkg/types" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -36,7 +41,7 @@ func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) { // 获取配置 config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root)) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -52,12 +57,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) { } if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } if err = systemctl.Reload("postgresql"); err != nil { - service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err)) return } @@ -69,7 +74,7 @@ func (s *App) GetUserConfig(w http.ResponseWriter, r *http.Request) { // 获取配置 config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root)) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } @@ -85,12 +90,12 @@ func (s *App) UpdateUserConfig(w http.ResponseWriter, r *http.Request) { } if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败") + service.Error(w, http.StatusInternalServerError, "%v", err) return } if err = systemctl.Reload("postgresql"); err != nil { - service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err)) return } @@ -107,36 +112,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { start, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL启动时间失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL start time: %v", err)) return } pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程PID失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL backend pid: %v", err)) return } process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程数失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL process: %v", err)) return } connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL连接数失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL connections: %v", err)) return } storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PostgreSQL空间占用失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL database size: %v", err)) return } data := []types.NV{ - {Name: "启动时间", Value: start}, - {Name: "进程 PID", Value: pid}, - {Name: "进程数", Value: process}, - {Name: "总连接数", Value: connections}, - {Name: "空间占用", Value: storage}, + {Name: s.t.Get("Start Time"), Value: start}, + {Name: s.t.Get("Process PID"), Value: pid}, + {Name: s.t.Get("Process Count"), Value: process}, + {Name: s.t.Get("Total Connections"), Value: connections}, + {Name: s.t.Get("Storage Usage"), Value: storage}, } service.Success(w, data) diff --git a/internal/apps/pureftpd/app.go b/internal/apps/pureftpd/app.go index e9baee9c..499d177d 100644 --- a/internal/apps/pureftpd/app.go +++ b/internal/apps/pureftpd/app.go @@ -7,6 +7,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -17,10 +18,14 @@ import ( "github.com/tnb-labs/panel/pkg/systemctl" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -76,16 +81,16 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { req.Path = "/" + req.Path } if !io.Exists(req.Path) { - service.Error(w, http.StatusUnprocessableEntity, "目录不存在") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("directory %s does not exist", req.Path)) return } if err = io.Chmod(req.Path, 0755); err != nil { - service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败") + service.Error(w, http.StatusUnprocessableEntity, "%v", err) return } if err = io.Chown(req.Path, "www", "www"); err != nil { - service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败") + service.Error(w, http.StatusUnprocessableEntity, "%v", err) return } if _, err = shell.Execf(`yes '%s' | pure-pw useradd '%s' -u www -g www -d '%s'`, req.Password, req.Username, req.Path); err != nil { @@ -144,7 +149,7 @@ func (s *App) ChangePassword(w http.ResponseWriter, r *http.Request) { func (s *App) GetPort(w http.ResponseWriter, r *http.Request) { port, err := shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取PureFtpd端口失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get port: %v", err)) return } diff --git a/internal/apps/redis/app.go b/internal/apps/redis/app.go index 575b210e..b6c742c3 100644 --- a/internal/apps/redis/app.go +++ b/internal/apps/redis/app.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/go-chi/chi/v5" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/app" "github.com/tnb-labs/panel/internal/service" @@ -16,10 +17,14 @@ import ( "github.com/tnb-labs/panel/pkg/types" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -31,7 +36,7 @@ func (s *App) Route(r chi.Router) { func (s *App) Load(w http.ResponseWriter, r *http.Request) { status, err := systemctl.Status("redis") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取 Redis 状态失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis status: %v", err)) return } if !status { @@ -54,7 +59,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { raw, err := shell.Execf("redis-cli%s info", withPassword) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取 Redis 负载失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis info: %v", err)) return } @@ -69,19 +74,19 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) { } data := []types.NV{ - {Name: "TCP 端口", Value: dataRaw["tcp_port"]}, - {Name: "已运行天数", Value: dataRaw["uptime_in_days"]}, - {Name: "连接的客户端数", Value: dataRaw["connected_clients"]}, - {Name: "已分配的内存总量", Value: dataRaw["used_memory_human"]}, - {Name: "占用内存总量", Value: dataRaw["used_memory_rss_human"]}, - {Name: "占用内存峰值", Value: dataRaw["used_memory_peak_human"]}, - {Name: "内存碎片比率", Value: dataRaw["mem_fragmentation_ratio"]}, - {Name: "运行以来连接过的客户端的总数", Value: dataRaw["total_connections_received"]}, - {Name: "运行以来执行过的命令的总数", Value: dataRaw["total_commands_processed"]}, - {Name: "每秒执行的命令数", Value: dataRaw["instantaneous_ops_per_sec"]}, - {Name: "查找数据库键成功次数", Value: dataRaw["keyspace_hits"]}, - {Name: "查找数据库键失败次数", Value: dataRaw["keyspace_misses"]}, - {Name: "最近一次 fork() 操作耗费的毫秒数", Value: dataRaw["latest_fork_usec"]}, + {Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]}, + {Name: s.t.Get("Uptime in Days"), Value: dataRaw["uptime_in_days"]}, + {Name: s.t.Get("Connected Clients"), Value: dataRaw["connected_clients"]}, + {Name: s.t.Get("Total Allocated Memory"), Value: dataRaw["used_memory_human"]}, + {Name: s.t.Get("Total Memory Usage"), Value: dataRaw["used_memory_rss_human"]}, + {Name: s.t.Get("Peak Memory Usage"), Value: dataRaw["used_memory_peak_human"]}, + {Name: s.t.Get("Memory Fragmentation Ratio"), Value: dataRaw["mem_fragmentation_ratio"]}, + {Name: s.t.Get("Total Connections Received"), Value: dataRaw["total_connections_received"]}, + {Name: s.t.Get("Total Commands Processed"), Value: dataRaw["total_commands_processed"]}, + {Name: s.t.Get("Commands Per Second"), Value: dataRaw["instantaneous_ops_per_sec"]}, + {Name: s.t.Get("Keyspace Hits"), Value: dataRaw["keyspace_hits"]}, + {Name: s.t.Get("Keyspace Misses"), Value: dataRaw["keyspace_misses"]}, + {Name: s.t.Get("Latest Fork Time (ms)"), Value: dataRaw["latest_fork_usec"]}, } service.Success(w, data) diff --git a/internal/apps/rsync/app.go b/internal/apps/rsync/app.go index a9ef1fa3..a5def2f1 100644 --- a/internal/apps/rsync/app.go +++ b/internal/apps/rsync/app.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" "github.com/go-rat/utils/str" + "github.com/leonelquinteros/gotext" "github.com/tnb-labs/panel/internal/service" "github.com/tnb-labs/panel/pkg/io" @@ -16,10 +17,14 @@ import ( "github.com/tnb-labs/panel/pkg/systemctl" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -74,7 +79,7 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) { currentModule.AuthUser = value currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get the secret key for module %s", currentModule.AuthUser)) return } case "hosts allow": @@ -109,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { return } if strings.Contains(config, "["+req.Name+"]") { - service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name) + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s already exists", req.Name)) return } @@ -154,7 +159,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { return } if !strings.Contains(config, "["+req.Name+"]") { - service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name) + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name)) return } @@ -196,7 +201,7 @@ func (s *App) Update(w http.ResponseWriter, r *http.Request) { return } if !strings.Contains(config, "["+req.Name+"]") { - service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name) + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name)) return } diff --git a/internal/apps/s3fs/app.go b/internal/apps/s3fs/app.go index 1252fc6f..6a37837b 100644 --- a/internal/apps/s3fs/app.go +++ b/internal/apps/s3fs/app.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/biz" @@ -18,11 +19,13 @@ import ( ) type App struct { + t *gotext.Locale settingRepo biz.SettingRepo } -func NewApp(setting biz.SettingRepo) *App { +func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App { return &App{ + t: t, settingRepo: setting, } } @@ -38,12 +41,12 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) { var s3fsList []Mount list, err := s.settingRepo.Get("s3fs", "[]") if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } if err = json.Unmarshal([]byte(list), &s3fsList); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } @@ -65,36 +68,36 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { // 检查下地域节点中是否包含bucket,如果包含了,肯定是错误的 if strings.Contains(req.URL, req.Bucket) { - service.Error(w, http.StatusUnprocessableEntity, "endpoint should not contain bucket") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("endpoint should not contain bucket")) return } // 检查挂载目录是否存在且为空 if !io.Exists(req.Path) { if err = os.MkdirAll(req.Path, 0755); err != nil { - service.Error(w, http.StatusUnprocessableEntity, "failed to create mount path: %v", err) + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("failed to create mount path: %v", err)) return } } if !io.Empty(req.Path) { - service.Error(w, http.StatusUnprocessableEntity, "mount path is not empty") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path is not empty")) return } var s3fsList []Mount list, err := s.settingRepo.Get("s3fs", "[]") if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } if err = json.Unmarshal([]byte(list), &s3fsList); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } - for _, s := range s3fsList { - if s.Path == req.Path { - service.Error(w, http.StatusUnprocessableEntity, "mount path already exists") + for _, item := range s3fsList { + if item.Path == req.Path { + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists")) return } } @@ -102,7 +105,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { id := time.Now().UnixMicro() password := req.Ak + ":" + req.Sk if err = io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to create passwd file: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to create passwd file: %v", err)) return } if _, err = shell.Execf(`echo 's3fs#%s %s fuse _netdev,allow_other,nonempty,url=%s,passwd_file=/etc/passwd-s3fs-%s 0 0' >> /etc/fstab`, req.Bucket, req.Path, req.URL, cast.ToString(id)); err != nil { @@ -111,12 +114,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { } if _, err = shell.Execf("mount -a"); err != nil { _, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path) - service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err) + service.Error(w, http.StatusInternalServerError, "%v", err) return } if _, err = shell.Execf(`df -h | grep '%s'`, req.Path); err != nil { _, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path) - service.Error(w, http.StatusInternalServerError, "mount failed: %v", err) + service.Error(w, http.StatusInternalServerError, s.t.Get("mount failed: %v", err)) return } @@ -128,12 +131,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) { }) encoded, err := json.Marshal(s3fsList) if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err)) return } if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err)) return } @@ -151,11 +154,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { var s3fsList []Mount list, err := s.settingRepo.Get("s3fs", "[]") if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } if err = json.Unmarshal([]byte(list), &s3fsList); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to get s3fs list") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err)) return } @@ -167,7 +170,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { } } if mount.ID == 0 { - service.Error(w, http.StatusUnprocessableEntity, "mount not found") + service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount not found")) return } @@ -184,7 +187,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { return } if _, err = shell.Execf("mount -a"); err != nil { - service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err) + service.Error(w, http.StatusInternalServerError, "%v", err) return } if err = io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil { @@ -200,11 +203,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) { } encoded, err := json.Marshal(newS3fsList) if err != nil { - service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err)) return } if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil { - service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err)) return } diff --git a/internal/apps/supervisor/app.go b/internal/apps/supervisor/app.go index d1335580..11c1b1d4 100644 --- a/internal/apps/supervisor/app.go +++ b/internal/apps/supervisor/app.go @@ -6,6 +6,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/service" @@ -16,10 +17,11 @@ import ( ) type App struct { + t *gotext.Locale name string } -func NewApp() *App { +func NewApp(t *gotext.Locale) *App { var name string if os.IsRHEL() { name = "supervisord" @@ -28,6 +30,7 @@ func NewApp() *App { } return &App{ + t: t, name: name, } } @@ -102,7 +105,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) { } if err = systemctl.Restart(s.name); err != nil { - service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", s.name, err) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart %s: %v", s.name, err)) return } @@ -156,8 +159,8 @@ func (s *App) StartProcess(w http.ResponseWriter, r *http.Request) { return } - if _, err = shell.Execf(`supervisorctl start %s`, req.Process); err != nil { - service.Error(w, http.StatusInternalServerError, "%v", err) + if out, err := shell.Execf(`supervisorctl start %s`, req.Process); err != nil { + service.Error(w, http.StatusInternalServerError, "%v %s", err, out) return } @@ -172,8 +175,8 @@ func (s *App) StopProcess(w http.ResponseWriter, r *http.Request) { return } - if _, err = shell.Execf(`supervisorctl stop %s`, req.Process); err != nil { - service.Error(w, http.StatusInternalServerError, "%v", err) + if out, err := shell.Execf(`supervisorctl stop %s`, req.Process); err != nil { + service.Error(w, http.StatusInternalServerError, "%v %s", err, out) return } @@ -188,8 +191,8 @@ func (s *App) RestartProcess(w http.ResponseWriter, r *http.Request) { return } - if _, err = shell.Execf(`supervisorctl restart %s`, req.Process); err != nil { - service.Error(w, http.StatusInternalServerError, "%v", err) + if out, err := shell.Execf(`supervisorctl restart %s`, req.Process); err != nil { + service.Error(w, http.StatusInternalServerError, "%v %s", err, out) return } @@ -212,7 +215,7 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) { } if err != nil { - service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err)) return } @@ -235,7 +238,7 @@ func (s *App) ClearProcessLog(w http.ResponseWriter, r *http.Request) { } if err != nil { - service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err)) return } @@ -343,8 +346,8 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) { return } - if _, err = shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil { - service.Error(w, http.StatusInternalServerError, "%v", err) + if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil { + service.Error(w, http.StatusInternalServerError, "%v %s", err, out) return } @@ -352,7 +355,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) { if os.IsRHEL() { logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process) if err != nil { - service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err)) return } if err = io.Remove(`/etc/supervisord.d/` + req.Process + `.conf`); err != nil { @@ -362,7 +365,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) { } else { logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process) if err != nil { - service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process) + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err)) return } if err = io.Remove(`/etc/supervisor/conf.d/` + req.Process + `.conf`); err != nil { diff --git a/internal/apps/toolbox/app.go b/internal/apps/toolbox/app.go index 833e2dee..796b228a 100644 --- a/internal/apps/toolbox/app.go +++ b/internal/apps/toolbox/app.go @@ -8,6 +8,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-rat/chix" + "github.com/leonelquinteros/gotext" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/app" @@ -19,10 +20,14 @@ import ( "github.com/tnb-labs/panel/pkg/types" ) -type App struct{} +type App struct { + t *gotext.Locale +} -func NewApp() *App { - return &App{} +func NewApp(t *gotext.Locale) *App { + return &App{ + t: t, + } } func (s *App) Route(r chi.Router) { @@ -48,13 +53,9 @@ func (s *App) GetDNS(w http.ResponseWriter, r *http.Request) { service.Error(w, http.StatusInternalServerError, "%v", err) return } - match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1) - if len(match) == 0 { - service.Error(w, http.StatusInternalServerError, "找不到 DNS 信息") - return - } - var dns []string + match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1) + dns := make([]string, 0) for _, m := range match { dns = append(dns, m[1]) } @@ -75,7 +76,7 @@ func (s *App) UpdateDNS(w http.ResponseWriter, r *http.Request) { dns += "nameserver " + req.DNS2 + "\n" if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入 DNS 信息失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to update DNS: %v", err)) return } @@ -89,7 +90,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) { if io.Exists(filepath.Join(app.Root, "swap")) { file, err := io.FileInfo(filepath.Join(app.Root, "swap")) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err)) return } @@ -102,7 +103,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) { raw, err := shell.Execf("free | grep Swap") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err)) return } @@ -147,11 +148,11 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) { var free string free, err = shell.Execf("df -k %s | awk '{print $4}' | tail -n 1", app.Root) if err != nil { - service.Error(w, http.StatusInternalServerError, "获取硬盘空间失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get disk space: %v", err)) return } if cast.ToInt64(free)*1024 < req.Size*1024*1024 { - service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free))) + service.Error(w, http.StatusInternalServerError, s.t.Get("disk space is insufficient, current free %s", tools.FormatBytes(cast.ToFloat64(free)))) return } @@ -171,7 +172,7 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) { return } if err = io.Chmod(filepath.Join(app.Root, "swap"), 0600); err != nil { - service.Error(w, http.StatusInternalServerError, "设置 SWAP 权限失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set SWAP permission: %v", err)) return } } @@ -192,19 +193,18 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) { func (s *App) GetTimezone(w http.ResponseWriter, r *http.Request) { raw, err := shell.Execf("timedatectl | grep zone") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取时区信息失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get timezone: %v", err)) return } match := regexp.MustCompile(`zone:\s+(\S+)`).FindStringSubmatch(raw) if len(match) == 0 { - service.Error(w, http.StatusInternalServerError, "找不到时区信息") - return + match = append(match, "") } zonesRaw, err := shell.Execf("timedatectl list-timezones") if err != nil { - service.Error(w, http.StatusInternalServerError, "获取时区列表失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get available timezones: %v", err)) return } zones := strings.Split(zonesRaw, "\n") @@ -289,7 +289,7 @@ func (s *App) UpdateHostname(w http.ResponseWriter, r *http.Request) { if _, err = shell.Execf("hostnamectl set-hostname '%s'", req.Hostname); err != nil { // 直接写 /etc/hostname if err = io.Write("/etc/hostname", req.Hostname, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入主机名失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hostname: %v", err)) return } } @@ -312,7 +312,7 @@ func (s *App) UpdateHosts(w http.ResponseWriter, r *http.Request) { } if err = io.Write("/etc/hosts", req.Hosts, 0644); err != nil { - service.Error(w, http.StatusInternalServerError, "写入 hosts 信息失败") + service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hosts: %v", err)) return } @@ -329,7 +329,7 @@ func (s *App) UpdateRootPassword(w http.ResponseWriter, r *http.Request) { req.Password = strings.ReplaceAll(req.Password, `'`, `\'`) if _, err = shell.Execf(`yes '%s' | passwd root`, req.Password); err != nil { - service.Error(w, http.StatusInternalServerError, "%v", err) + service.Error(w, http.StatusInternalServerError, "%v", s.t.Get("failed to set root password: %v", err)) return } diff --git a/internal/bootstrap/t.go b/internal/bootstrap/t.go index f85d379c..7ffe9cdf 100644 --- a/internal/bootstrap/t.go +++ b/internal/bootstrap/t.go @@ -6,6 +6,7 @@ import ( "github.com/knadh/koanf/v2" "github.com/leonelquinteros/gotext" + "github.com/tnb-labs/panel/pkg/embed" ) diff --git a/pkg/embed/locales/cli.pot b/pkg/embed/locales/cli.pot index cc7f7659..58f57f43 100644 --- a/pkg/embed/locales/cli.pot +++ b/pkg/embed/locales/cli.pot @@ -11,6 +11,40 @@ msgstr "" msgid "AUTHOR" msgstr "" +#: internal/apps/php/app.go:130 +msgid "Accepted Connections" +msgstr "" + +#: internal/apps/mysql/app.go:121 +msgid "Active Connections" +msgstr "" + +#: internal/apps/php/app.go:135 +msgid "Active Processes" +msgstr "" + +#: internal/apps/nginx/app.go:120 +msgid "Active connections" +msgstr "" + +#: internal/apps/php/app.go:127 +msgid "Application Pool" +msgstr "" + +#: internal/apps/mysql/app.go:120 +#: internal/apps/mysql/app.go:139 +msgid "Bytes Received" +msgstr "" + +#: internal/apps/mysql/app.go:119 +#: internal/apps/mysql/app.go:139 +msgid "Bytes Sent" +msgstr "" + +#: internal/apps/php/app.go:352 +msgid "Bzip2 is a library for compressing and decompressing files" +msgstr "" + #: internal/bootstrap/cli.go:24 msgid "CATEGORY" msgstr "" @@ -24,19 +58,123 @@ msgstr "" msgid "COPYRIGHT" msgstr "" +#: internal/apps/php/app.go:392 +msgid "Calendar is a library for handling dates" +msgstr "" + #: internal/bootstrap/cli.go:17 #: internal/bootstrap/cli.go:25 msgid "DESCRIPTION" msgstr "" +#: internal/apps/php/app.go:382 +msgid "Enchant is a spell-checking library" +msgstr "" + +#: internal/apps/php/app.go:362 +msgid "Event is a library for handling events" +msgstr "" + +#: internal/apps/php/app.go:317 +msgid "Exif is a library for reading and writing image metadata" +msgstr "" + +#: internal/apps/php/app.go:287 +msgid "Fileinfo is a library used to identify file types" +msgstr "" + #: internal/bootstrap/cli.go:34 msgid "Forum:https://bbs.haozi.net" msgstr "" +#: internal/apps/mysql/app.go:127 +msgid "Full Joins without Index" +msgstr "" + +#: internal/apps/mysql/app.go:128 +msgid "Full Range Joins without Index" +msgstr "" + #: internal/bootstrap/cli.go:20 msgid "GLOBAL OPTIONS" msgstr "" +#: internal/apps/php/app.go:397 +msgid "GMP is a library for handling large integers" +msgstr "" + +#: internal/apps/php/app.go:417 +msgid "Gettext is a library for handling multilingual support" +msgstr "" + +#: internal/apps/php/app.go:342 +msgid "IMAP extension allows PHP to read, search, delete, download, and manage emails" +msgstr "" + +#: internal/apps/php/app.go:134 +msgid "Idle Processes" +msgstr "" + +#: internal/apps/php/app.go:297 +msgid "Igbinary is a library for serializing and deserializing data" +msgstr "" + +#: internal/apps/php/app.go:312 +msgid "ImageMagick is free software for creating, editing, and composing images" +msgstr "" + +#: internal/apps/mysql/app.go:123 +msgid "Index Hit Rate" +msgstr "" + +#: internal/apps/mysql/app.go:124 +msgid "Innodb Index Hit Rate" +msgstr "" + +#: internal/apps/php/app.go:239 +msgid "Install PHP-%d %s extension" +msgstr "" + +#: internal/apps/php/app.go:412 +msgid "Intl is a library for handling internationalization and localization" +msgstr "" + +#: internal/apps/php/app.go:377 +msgid "LDAP is a protocol for accessing directory services" +msgstr "" + +#: internal/apps/php/app.go:131 +msgid "Listen Queue" +msgstr "" + +#: internal/apps/php/app.go:133 +msgid "Listen Queue Length" +msgstr "" + +#: internal/apps/php/app.go:137 +msgid "Max Active Processes" +msgstr "" + +#: internal/apps/php/app.go:138 +msgid "Max Children Reached" +msgstr "" + +#: internal/apps/php/app.go:132 +msgid "Max Listen Queue" +msgstr "" + +#: internal/apps/php/app.go:307 +msgid "Memcached is a driver for connecting to Memcached servers" +msgstr "" + +#: internal/apps/nginx/app.go:113 +msgid "Memory" +msgstr "" + +#: internal/apps/mysql/app.go:88 +msgid "MySQL root password is empty" +msgstr "" + #: internal/bootstrap/cli.go:14 #: internal/bootstrap/cli.go:22 #: internal/bootstrap/cli.go:27 @@ -48,6 +186,30 @@ msgstr "" msgid "OPTIONS" msgstr "" +#: internal/apps/php/app.go:292 +msgid "OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance" +msgstr "" + +#: internal/apps/mysql/app.go:126 +msgid "Open Tables" +msgstr "" + +#: internal/apps/mysql/app.go:122 +msgid "Peak Connections" +msgstr "" + +#: internal/apps/php/app.go:302 +msgid "PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)" +msgstr "" + +#: internal/apps/php/app.go:128 +msgid "Process Manager" +msgstr "" + +#: internal/apps/php/app.go:387 +msgid "Pspell is a spell-checking library" +msgstr "" + #: internal/bootstrap/cli.go:35 msgid "QQ Group:12370907" msgstr "" @@ -56,6 +218,98 @@ msgstr "" msgid "Rat Panel CLI Tool" msgstr "" +#: internal/apps/nginx/app.go:144 +msgid "Reading" +msgstr "" + +#: internal/apps/php/app.go:367 +msgid "Readline is a library for processing text" +msgstr "" + +#: internal/apps/mysql/app.go:118 +msgid "Rollbacks per Second" +msgstr "" + +#: internal/apps/php/app.go:372 +msgid "SNMP is a protocol for network management" +msgstr "" + +#: internal/apps/php/app.go:357 +msgid "SSH2 is a library for connecting to SSH servers" +msgstr "" + +#: internal/apps/php/app.go:139 +msgid "Slow Requests" +msgstr "" + +#: internal/apps/mysql/app.go:130 +msgid "Sort Merge Passes" +msgstr "" + +#: internal/apps/php/app.go:129 +msgid "Start Time" +msgstr "" + +#: internal/apps/mysql/app.go:129 +msgid "Subqueries without Index" +msgstr "" + +#: internal/apps/php/app.go:477 +msgid "Swoole is a PHP extension for building high-performance asynchronous concurrent servers" +msgstr "" + +#: internal/apps/php/app.go:486 +msgid "Swow is a PHP extension for building high-performance asynchronous concurrent servers" +msgstr "" + +#: internal/apps/php/app.go:457 +msgid "Sysvmsg is a library for handling System V message queues" +msgstr "" + +#: internal/apps/php/app.go:462 +msgid "Sysvsem is a library for handling System V semaphores" +msgstr "" + +#: internal/apps/php/app.go:467 +msgid "Sysvshm is a library for handling System V shared memory" +msgstr "" + +#: internal/apps/mysql/app.go:131 +msgid "Table Locks Waited" +msgstr "" + +#: internal/apps/mysql/app.go:125 +msgid "Temporary Tables Created on Disk" +msgstr "" + +#: internal/apps/mysql/app.go:116 +msgid "Total Connections" +msgstr "" + +#: internal/apps/php/app.go:136 +msgid "Total Processes" +msgstr "" + +#: internal/apps/mysql/app.go:115 +msgid "Total Queries" +msgstr "" + +#: internal/apps/nginx/app.go:128 +msgid "Total connections" +msgstr "" + +#: internal/apps/nginx/app.go:132 +msgid "Total handshakes" +msgstr "" + +#: internal/apps/nginx/app.go:136 +msgid "Total requests" +msgstr "" + +#: internal/apps/mysql/app.go:117 +msgid "Transactions per Second" +msgstr "" + #: internal/bootstrap/cli.go:15 #: internal/bootstrap/cli.go:23 #: internal/bootstrap/cli.go:28 @@ -63,10 +317,170 @@ msgstr "" msgid "USAGE" msgstr "" +#: internal/apps/php/app.go:270 +msgid "Uninstall PHP-%d %s extension" +msgstr "" + +#: internal/apps/mysql/app.go:114 +msgid "Uptime" +msgstr "" + #: internal/bootstrap/cli.go:16 msgid "VERSION" msgstr "" +#: internal/apps/nginx/app.go:152 +msgid "Waiting" +msgstr "" + #: internal/bootstrap/cli.go:33 msgid "Website:https://panel.haozi.net" +msgstr "" + +#: internal/apps/nginx/app.go:102 +msgid "Workers" +msgstr "" + +#: internal/apps/nginx/app.go:148 +msgid "Writing" +msgstr "" + +#: internal/apps/php/app.go:402 +msgid "XLSWriter is a high-performance library for reading and writing Excel files" +msgstr "" + +#: internal/apps/php/app.go:407 +msgid "XSL is a library for processing XML documents" +msgstr "" + +#: internal/apps/php/app.go:347 +msgid "Zip is a library for handling ZIP files" +msgstr "" + +#: internal/apps/php/app.go:228 +#: internal/apps/php/app.go:259 +msgid "extension %s does not exist" +msgstr "" + +#: internal/apps/memcached/app.go:37 +msgid "failed to get Memcached status: %v" +msgstr "" + +#: internal/apps/mysql/app.go:104 +msgid "failed to get MySQL status: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:268 +msgid "failed to get banned ip list" +msgstr "" + +#: internal/apps/fail2ban/app.go:258 +msgid "failed to get current banned list" +msgstr "" + +#: internal/apps/nginx/app.go:108 +#: internal/apps/nginx/app.go:98 +msgid "failed to get nginx workers: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:263 +msgid "failed to get total banned list" +msgstr "" + +#: internal/apps/mysql/app.go:188 +#: internal/apps/mysql/app.go:83 +msgid "failed to load MySQL root password: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:327 +#: internal/apps/fail2ban/app.go:355 +msgid "failed to parse the ignoreip of fail2ban" +msgstr "" + +#: internal/apps/memcached/app.go:77 +msgid "failed to read from Memcached: %v" +msgstr "" + +#: internal/apps/nginx/app.go:65 +msgid "failed to reload nginx: %v" +msgstr "" + +#: internal/apps/mysql/app.go:72 +msgid "failed to restart MySQL: %v" +msgstr "" + +#: internal/apps/mysql/app.go:99 +msgid "failed to set MYSQL_PWD env: %v" +msgstr "" + +#: internal/apps/memcached/app.go:56 +msgid "failed to write to Memcached: %v" +msgstr "" + +#: internal/apps/php/app.go:422 +msgid "gRPC is a high-performance, open-source, and general-purpose RPC framework" +msgstr "" + +#: internal/apps/fail2ban/app.go:184 +msgid "get service port failed, please check if it is installed" +msgstr "" + +#: internal/apps/php/app.go:472 +msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)" +msgstr "" + +#: internal/apps/php/app.go:327 +msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)" +msgstr "" + +#: internal/apps/php/app.go:337 +msgid "pdo_sqlsrv is a PDO driver for connecting to SQL Server" +msgstr "" + +#: internal/apps/php/app.go:322 +msgid "pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)" +msgstr "" + +#: internal/apps/php/app.go:427 +msgid "protobuf is a library for serializing and deserializing data" +msgstr "" + +#: internal/apps/php/app.go:432 +msgid "rdkafka is a library for connecting to Apache Kafka" +msgstr "" + +#: internal/apps/fail2ban/app.go:109 +msgid "rule already exists" +msgstr "" + +#: internal/apps/fail2ban/app.go:228 +msgid "rule not found" +msgstr "" + +#: internal/apps/php/app.go:332 +msgid "sqlsrv is a driver for connecting to SQL Server" +msgstr "" + +#: internal/apps/fail2ban/app.go:180 +msgid "unknown service" +msgstr "" + +#: internal/apps/benchmark/app.go:81 +msgid "unknown test type" +msgstr "" + +#: internal/apps/php/app.go:442 +msgid "xdebug is a library for debugging and profiling PHP code" +msgstr "" + +#: internal/apps/php/app.go:437 +msgid "xhprof is a library for performance profiling" +msgstr "" + +#: internal/apps/php/app.go:447 +msgid "yaml is a library for handling YAML" +msgstr "" + +#: internal/apps/php/app.go:452 +msgid "zstd is a library for compressing and decompressing files" msgstr "" \ No newline at end of file diff --git a/pkg/embed/locales/web.pot b/pkg/embed/locales/web.pot index cc7f7659..58f57f43 100644 --- a/pkg/embed/locales/web.pot +++ b/pkg/embed/locales/web.pot @@ -11,6 +11,40 @@ msgstr "" msgid "AUTHOR" msgstr "" +#: internal/apps/php/app.go:130 +msgid "Accepted Connections" +msgstr "" + +#: internal/apps/mysql/app.go:121 +msgid "Active Connections" +msgstr "" + +#: internal/apps/php/app.go:135 +msgid "Active Processes" +msgstr "" + +#: internal/apps/nginx/app.go:120 +msgid "Active connections" +msgstr "" + +#: internal/apps/php/app.go:127 +msgid "Application Pool" +msgstr "" + +#: internal/apps/mysql/app.go:120 +#: internal/apps/mysql/app.go:139 +msgid "Bytes Received" +msgstr "" + +#: internal/apps/mysql/app.go:119 +#: internal/apps/mysql/app.go:139 +msgid "Bytes Sent" +msgstr "" + +#: internal/apps/php/app.go:352 +msgid "Bzip2 is a library for compressing and decompressing files" +msgstr "" + #: internal/bootstrap/cli.go:24 msgid "CATEGORY" msgstr "" @@ -24,19 +58,123 @@ msgstr "" msgid "COPYRIGHT" msgstr "" +#: internal/apps/php/app.go:392 +msgid "Calendar is a library for handling dates" +msgstr "" + #: internal/bootstrap/cli.go:17 #: internal/bootstrap/cli.go:25 msgid "DESCRIPTION" msgstr "" +#: internal/apps/php/app.go:382 +msgid "Enchant is a spell-checking library" +msgstr "" + +#: internal/apps/php/app.go:362 +msgid "Event is a library for handling events" +msgstr "" + +#: internal/apps/php/app.go:317 +msgid "Exif is a library for reading and writing image metadata" +msgstr "" + +#: internal/apps/php/app.go:287 +msgid "Fileinfo is a library used to identify file types" +msgstr "" + #: internal/bootstrap/cli.go:34 msgid "Forum:https://bbs.haozi.net" msgstr "" +#: internal/apps/mysql/app.go:127 +msgid "Full Joins without Index" +msgstr "" + +#: internal/apps/mysql/app.go:128 +msgid "Full Range Joins without Index" +msgstr "" + #: internal/bootstrap/cli.go:20 msgid "GLOBAL OPTIONS" msgstr "" +#: internal/apps/php/app.go:397 +msgid "GMP is a library for handling large integers" +msgstr "" + +#: internal/apps/php/app.go:417 +msgid "Gettext is a library for handling multilingual support" +msgstr "" + +#: internal/apps/php/app.go:342 +msgid "IMAP extension allows PHP to read, search, delete, download, and manage emails" +msgstr "" + +#: internal/apps/php/app.go:134 +msgid "Idle Processes" +msgstr "" + +#: internal/apps/php/app.go:297 +msgid "Igbinary is a library for serializing and deserializing data" +msgstr "" + +#: internal/apps/php/app.go:312 +msgid "ImageMagick is free software for creating, editing, and composing images" +msgstr "" + +#: internal/apps/mysql/app.go:123 +msgid "Index Hit Rate" +msgstr "" + +#: internal/apps/mysql/app.go:124 +msgid "Innodb Index Hit Rate" +msgstr "" + +#: internal/apps/php/app.go:239 +msgid "Install PHP-%d %s extension" +msgstr "" + +#: internal/apps/php/app.go:412 +msgid "Intl is a library for handling internationalization and localization" +msgstr "" + +#: internal/apps/php/app.go:377 +msgid "LDAP is a protocol for accessing directory services" +msgstr "" + +#: internal/apps/php/app.go:131 +msgid "Listen Queue" +msgstr "" + +#: internal/apps/php/app.go:133 +msgid "Listen Queue Length" +msgstr "" + +#: internal/apps/php/app.go:137 +msgid "Max Active Processes" +msgstr "" + +#: internal/apps/php/app.go:138 +msgid "Max Children Reached" +msgstr "" + +#: internal/apps/php/app.go:132 +msgid "Max Listen Queue" +msgstr "" + +#: internal/apps/php/app.go:307 +msgid "Memcached is a driver for connecting to Memcached servers" +msgstr "" + +#: internal/apps/nginx/app.go:113 +msgid "Memory" +msgstr "" + +#: internal/apps/mysql/app.go:88 +msgid "MySQL root password is empty" +msgstr "" + #: internal/bootstrap/cli.go:14 #: internal/bootstrap/cli.go:22 #: internal/bootstrap/cli.go:27 @@ -48,6 +186,30 @@ msgstr "" msgid "OPTIONS" msgstr "" +#: internal/apps/php/app.go:292 +msgid "OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance" +msgstr "" + +#: internal/apps/mysql/app.go:126 +msgid "Open Tables" +msgstr "" + +#: internal/apps/mysql/app.go:122 +msgid "Peak Connections" +msgstr "" + +#: internal/apps/php/app.go:302 +msgid "PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)" +msgstr "" + +#: internal/apps/php/app.go:128 +msgid "Process Manager" +msgstr "" + +#: internal/apps/php/app.go:387 +msgid "Pspell is a spell-checking library" +msgstr "" + #: internal/bootstrap/cli.go:35 msgid "QQ Group:12370907" msgstr "" @@ -56,6 +218,98 @@ msgstr "" msgid "Rat Panel CLI Tool" msgstr "" +#: internal/apps/nginx/app.go:144 +msgid "Reading" +msgstr "" + +#: internal/apps/php/app.go:367 +msgid "Readline is a library for processing text" +msgstr "" + +#: internal/apps/mysql/app.go:118 +msgid "Rollbacks per Second" +msgstr "" + +#: internal/apps/php/app.go:372 +msgid "SNMP is a protocol for network management" +msgstr "" + +#: internal/apps/php/app.go:357 +msgid "SSH2 is a library for connecting to SSH servers" +msgstr "" + +#: internal/apps/php/app.go:139 +msgid "Slow Requests" +msgstr "" + +#: internal/apps/mysql/app.go:130 +msgid "Sort Merge Passes" +msgstr "" + +#: internal/apps/php/app.go:129 +msgid "Start Time" +msgstr "" + +#: internal/apps/mysql/app.go:129 +msgid "Subqueries without Index" +msgstr "" + +#: internal/apps/php/app.go:477 +msgid "Swoole is a PHP extension for building high-performance asynchronous concurrent servers" +msgstr "" + +#: internal/apps/php/app.go:486 +msgid "Swow is a PHP extension for building high-performance asynchronous concurrent servers" +msgstr "" + +#: internal/apps/php/app.go:457 +msgid "Sysvmsg is a library for handling System V message queues" +msgstr "" + +#: internal/apps/php/app.go:462 +msgid "Sysvsem is a library for handling System V semaphores" +msgstr "" + +#: internal/apps/php/app.go:467 +msgid "Sysvshm is a library for handling System V shared memory" +msgstr "" + +#: internal/apps/mysql/app.go:131 +msgid "Table Locks Waited" +msgstr "" + +#: internal/apps/mysql/app.go:125 +msgid "Temporary Tables Created on Disk" +msgstr "" + +#: internal/apps/mysql/app.go:116 +msgid "Total Connections" +msgstr "" + +#: internal/apps/php/app.go:136 +msgid "Total Processes" +msgstr "" + +#: internal/apps/mysql/app.go:115 +msgid "Total Queries" +msgstr "" + +#: internal/apps/nginx/app.go:128 +msgid "Total connections" +msgstr "" + +#: internal/apps/nginx/app.go:132 +msgid "Total handshakes" +msgstr "" + +#: internal/apps/nginx/app.go:136 +msgid "Total requests" +msgstr "" + +#: internal/apps/mysql/app.go:117 +msgid "Transactions per Second" +msgstr "" + #: internal/bootstrap/cli.go:15 #: internal/bootstrap/cli.go:23 #: internal/bootstrap/cli.go:28 @@ -63,10 +317,170 @@ msgstr "" msgid "USAGE" msgstr "" +#: internal/apps/php/app.go:270 +msgid "Uninstall PHP-%d %s extension" +msgstr "" + +#: internal/apps/mysql/app.go:114 +msgid "Uptime" +msgstr "" + #: internal/bootstrap/cli.go:16 msgid "VERSION" msgstr "" +#: internal/apps/nginx/app.go:152 +msgid "Waiting" +msgstr "" + #: internal/bootstrap/cli.go:33 msgid "Website:https://panel.haozi.net" +msgstr "" + +#: internal/apps/nginx/app.go:102 +msgid "Workers" +msgstr "" + +#: internal/apps/nginx/app.go:148 +msgid "Writing" +msgstr "" + +#: internal/apps/php/app.go:402 +msgid "XLSWriter is a high-performance library for reading and writing Excel files" +msgstr "" + +#: internal/apps/php/app.go:407 +msgid "XSL is a library for processing XML documents" +msgstr "" + +#: internal/apps/php/app.go:347 +msgid "Zip is a library for handling ZIP files" +msgstr "" + +#: internal/apps/php/app.go:228 +#: internal/apps/php/app.go:259 +msgid "extension %s does not exist" +msgstr "" + +#: internal/apps/memcached/app.go:37 +msgid "failed to get Memcached status: %v" +msgstr "" + +#: internal/apps/mysql/app.go:104 +msgid "failed to get MySQL status: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:268 +msgid "failed to get banned ip list" +msgstr "" + +#: internal/apps/fail2ban/app.go:258 +msgid "failed to get current banned list" +msgstr "" + +#: internal/apps/nginx/app.go:108 +#: internal/apps/nginx/app.go:98 +msgid "failed to get nginx workers: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:263 +msgid "failed to get total banned list" +msgstr "" + +#: internal/apps/mysql/app.go:188 +#: internal/apps/mysql/app.go:83 +msgid "failed to load MySQL root password: %v" +msgstr "" + +#: internal/apps/fail2ban/app.go:327 +#: internal/apps/fail2ban/app.go:355 +msgid "failed to parse the ignoreip of fail2ban" +msgstr "" + +#: internal/apps/memcached/app.go:77 +msgid "failed to read from Memcached: %v" +msgstr "" + +#: internal/apps/nginx/app.go:65 +msgid "failed to reload nginx: %v" +msgstr "" + +#: internal/apps/mysql/app.go:72 +msgid "failed to restart MySQL: %v" +msgstr "" + +#: internal/apps/mysql/app.go:99 +msgid "failed to set MYSQL_PWD env: %v" +msgstr "" + +#: internal/apps/memcached/app.go:56 +msgid "failed to write to Memcached: %v" +msgstr "" + +#: internal/apps/php/app.go:422 +msgid "gRPC is a high-performance, open-source, and general-purpose RPC framework" +msgstr "" + +#: internal/apps/fail2ban/app.go:184 +msgid "get service port failed, please check if it is installed" +msgstr "" + +#: internal/apps/php/app.go:472 +msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)" +msgstr "" + +#: internal/apps/php/app.go:327 +msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)" +msgstr "" + +#: internal/apps/php/app.go:337 +msgid "pdo_sqlsrv is a PDO driver for connecting to SQL Server" +msgstr "" + +#: internal/apps/php/app.go:322 +msgid "pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)" +msgstr "" + +#: internal/apps/php/app.go:427 +msgid "protobuf is a library for serializing and deserializing data" +msgstr "" + +#: internal/apps/php/app.go:432 +msgid "rdkafka is a library for connecting to Apache Kafka" +msgstr "" + +#: internal/apps/fail2ban/app.go:109 +msgid "rule already exists" +msgstr "" + +#: internal/apps/fail2ban/app.go:228 +msgid "rule not found" +msgstr "" + +#: internal/apps/php/app.go:332 +msgid "sqlsrv is a driver for connecting to SQL Server" +msgstr "" + +#: internal/apps/fail2ban/app.go:180 +msgid "unknown service" +msgstr "" + +#: internal/apps/benchmark/app.go:81 +msgid "unknown test type" +msgstr "" + +#: internal/apps/php/app.go:442 +msgid "xdebug is a library for debugging and profiling PHP code" +msgstr "" + +#: internal/apps/php/app.go:437 +msgid "xhprof is a library for performance profiling" +msgstr "" + +#: internal/apps/php/app.go:447 +msgid "yaml is a library for handling YAML" +msgstr "" + +#: internal/apps/php/app.go:452 +msgid "zstd is a library for compressing and decompressing files" msgstr "" \ No newline at end of file