diff --git a/backend/global/global.go b/backend/global/global.go index 24b660cad..6801170d5 100644 --- a/backend/global/global.go +++ b/backend/global/global.go @@ -28,5 +28,6 @@ var ( MonitorCronID cron.EntryID OneDriveCronID cron.EntryID - I18n *i18n.Localizer + I18n *i18n.Localizer + I18nForCmd *i18n.Localizer ) diff --git a/backend/i18n/i18n.go b/backend/i18n/i18n.go index b1cc34468..cad6f7d92 100644 --- a/backend/i18n/i18n.go +++ b/backend/i18n/i18n.go @@ -2,9 +2,10 @@ package i18n import ( "embed" - "github.com/1Panel-dev/1Panel/backend/global" "strings" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/gin-gonic/gin" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" @@ -89,9 +90,54 @@ func UseI18n() gin.HandlerFunc { } func Init() { + if bundle != nil { + return + } bundle = i18n.NewBundle(language.Chinese) bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) _, _ = bundle.LoadMessageFileFS(fs, "lang/zh.yaml") _, _ = bundle.LoadMessageFileFS(fs, "lang/en.yaml") _, _ = bundle.LoadMessageFileFS(fs, "lang/zh-Hant.yaml") + _, _ = bundle.LoadMessageFileFS(fs, "lang/fa.yaml") +} + +func UseI18nForCmd(lang string) { + if lang == "" { + lang = "zh" + } + if bundle == nil { + Init() + } + global.I18nForCmd = i18n.NewLocalizer(bundle, lang) +} +func GetMsgByKeyForCmd(key string) string { + if global.I18nForCmd == nil { + UseI18nForCmd("") + } + content, _ := global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + return content +} +func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string { + if global.I18nForCmd == nil { + UseI18nForCmd("") + } + var content string + if maps == nil { + content, _ = global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + content = strings.ReplaceAll(content, ": ", "") + if content == "" { + return key + } else { + return content + } } diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 9e123a8a7..f3e3774dc 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -215,4 +215,67 @@ ErrAlert: "Alert information format error, please check and try again!" ErrAlertPush: "Alert push error, please check and try again!" ErrAlertSave: "Alert save error, please check and try again!" ErrAlertSync: "Alert sync error, please check and try again!" -ErrAlertRemote: "Remote alert error, please check and try again!" \ No newline at end of file +ErrAlertRemote: "Remote alert error, please check and try again!" + +#cmd +AppVersion : "App version" +AppCommands : "App related commands" +AppInit : "Initialize app" +AppKeyVal: "App key (only supports English)" +AppCreateFileErr : "File {{ .name }} creation failed {{ .err }}" +AppCreateDirErr : "Folder {{ .name }} creation failed {{ .err }}" +AppMissKey : "App key missing, use -k to specify" +AppMissVersion : "App version missing, use -v to specify" +AppVersionExist : "Version already exists!" +AppCreateSuccessful : "Creation successful!" +AppWriteErr : "File {{ .name }} write failed {{ .err }}" +SudoHelper : "Please use {{ .cmd }} or switch to root user" +ListenIPCommands : "Switch listening ip" +ListenIPv4 : "Listen on IPv4" +ListenIPv6 : "Listen on IPv6" +ListenChangeSuccessful : "Switch successful! Now listening on {{ .value }}" +ResetCommands : "Reset system info" +ResetMFA : "Cancel 1Panel two-factor authentication" +ResetHttps : "Cancel 1Panel https login" +ResetEntrance : "Cancel 1Panel secure entrance" +ResetIPs : "Cancel 1Panel authorized ip restrictions" +ResetDomain : "Cancel 1Panel domain binding" +RestoreCommands : "Rollback 1Panel service and data" +RestoreNoSuchFile : "No files available for rollback" +RestoreStep1 : "(0/4) Starting rollback of 1Panel service and data from {{ .name }} directory..." +RestoreStep2 : "1/4 1panel binary rollback successful" +RestoreStep3 : "2/4 1panel script rollback successful" +RestoreStep4 : "3/4 1panel service rollback successful" +RestoreStep5 : "4/4 1panel data rollback successful" +RestoreSuccessful : "Rollback successful! Restarting service, please wait..." +UpdateCommands : "Update panel info" +UpdateUser : "Update panel user" +UpdatePassword : "Update panel password" +UpdatePort : "Update panel port" +UpdateUserNull : "Error: panel user is empty!" +UpdateUserBlank : "Error: panel user contains spaces!" +UpdateUserFormat : "Error: Invalid panel user format! Only supports English, Chinese, numbers, and _, length 3-30" +UpdateUserErr : "Error: Failed to update panel user, {{ .err }}" +UpdateSuccessful : "Update successful!" +UpdateUserResult : "Panel user: {{ .name }}" +UpdatePasswordRead : "Error: Failed to read panel password information, {{ .err }}" +UpdatePasswordNull : "Error: Panel password is empty!" +UpdateUPasswordBlank : "Error: Panel password contains spaces!" +UpdatePasswordFormat : "Error: Panel password only supports letters, numbers, special characters !@#$%*_,.?, length 8-30!" +UpdatePasswordLen : "Error: Please enter a password longer than 6 characters!" +UpdatePasswordRe : "Confirm password:" +UpdatePasswordErr : "Error: Failed to update panel password, {{ .err }}" +UpdatePasswordSame : "Error: The two passwords do not match, please check and try again!" +UpdatePasswordResult : "Panel password: {{ .name }}" +UpdatePortFormat : "Error: The input port number must be between 1 and 65535!" +UpdatePortUsed : "Error: The port number is already in use, please check and try again!" +UpdatePortErr : "Error: Failed to update panel port, {{ .err }}" +UpdatePortResult : "Panel Port: {{ .name }}" +UpdatePortFirewallAdd : "Failed to add firewall port rule, {{ .err }}, please manually add the {{ .name }} port to the firewall rules." +UpdatePortFirewallDel : "Error: Failed to delete firewall port, {{ .err }}" +UpdatePortFirewallReload : "Failed to reload the firewall, {{ .err }}, please manually reload the firewall." +UserInfo : "Get panel information" +UserInfoAddr : "Panel address: " +UserInfoPassHelp : "Tip: To change the password, you can execute the command: " +SystemVersion : "Get system version information" +DBConnErr : "Error: Failed to initialize database connection, {{ .err }}" \ No newline at end of file diff --git a/backend/i18n/lang/fa.yaml b/backend/i18n/lang/fa.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 6b42011e0..d0166d376 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -216,3 +216,66 @@ ErrAlertPush: "告警資訊推送錯誤,請檢查後重試!" ErrAlertSave: "告警資訊保存錯誤,請檢查後重試!" ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!" ErrAlertRemote: "告警資訊遠端錯誤,請檢查後重試!" + +#cmd +AppVersion : "應用版本" +AppCommands : "應用相關命令" +AppInit : "初始化應用" +AppKeyVal: "應用的key(僅支持英文)" +AppCreateFileErr : "文件 {{ .name }} 創建失敗 {{ .err }}" +AppCreateDirErr : "資料夾 {{ .name }} 創建失敗 {{ .err }}" +AppMissKey : "應用的 key 缺失,使用 -k 指定" +AppMissVersion : "應用版本缺失,使用 -v 指定" +AppVersionExist : "版本已存在!" +AppCreateSuccessful : "創建成功!" +AppWriteErr : "文件 {{ .name }} 寫入失敗 {{ .err }}" +SudoHelper : "請使用 {{ .cmd }} 或者切換到 root 用戶" +ListenIPCommands : "切換監聽 IP" +ListenIPv4 : "監聽 IPv4" +ListenIPv6 : "監聽 IPv6" +ListenChangeSuccessful : "切換成功!已切換至監聽 {{ .value }}" +ResetCommands : "重置系統信息" +ResetMFA : "取消 1Panel 兩步驗證" +ResetHttps : "取消 1Panel https 方式登錄" +ResetEntrance : "取消 1Panel 安全入口" +ResetIPs : "取消 1Panel 授權 IP 限制" +ResetDomain : "取消 1Panel 訪問域名綁定" +RestoreCommands : "回滾 1Panel 服務及數據" +RestoreNoSuchFile : "暫無可回滾文件" +RestoreStep1 : "(0/4) 開始從 {{ .name }} 目錄回滾 1Panel 服務及數據... " +RestoreStep2 : "1/4 1panel 二進制回滾成功" +RestoreStep3 : "2/4 1panel 腳本回滾成功" +RestoreStep4 : "3/4 1panel 服務回滾成功" +RestoreStep5 : "4/4 1panel 數據回滾成功" +RestoreSuccessful : "回滾成功!正在重啟服務,請稍候..." +UpdateCommands : "修改面板信息" +UpdateUser : "修改面板用戶" +UpdatePassword : "修改面板密碼" +UpdatePort : "修改面板端口" +UpdateUserNull : "錯誤:輸入面板用戶為空!" +UpdateUserBlank : "錯誤:輸入面板用戶中包含空格字符!" +UpdateUserFormat : "錯誤:輸入面板用戶錯誤!僅支持英文、中文、數字和_,長度3-30" +UpdateUserErr : "錯誤:面板用戶修改失敗,{{ .err }}" +UpdateSuccessful : "修改成功!" +UpdateUserResult : "面板用戶:{{ .name }}" +UpdatePasswordRead : "錯誤:面板密碼信息讀取錯誤,{{ .err }}" +UpdatePasswordNull : "錯誤:輸入面板密碼為空!" +UpdateUPasswordBlank : "錯誤:輸入面板密碼中包含空格字符!" +UpdatePasswordFormat : "錯誤:面板密碼僅支持字母、數字、特殊字符(!@#$%*_,.?),長度 8-30 位!" +UpdatePasswordLen : "錯誤:請輸入 6 位以上密碼!" +UpdatePasswordRe : "確認密碼:" +UpdatePasswordErr : "錯誤:面板密碼修改失敗,{{ .err }}" +UpdatePasswordSame : "錯誤:兩次密碼不匹配,請檢查後重試!" +UpdatePasswordResult : "面板密碼:{{ .name }}" +UpdatePortFormat : "錯誤:輸入的端口號必須在 1 到 65535 之間!" +UpdatePortUsed : "錯誤:該端口號正被佔用,請檢查後重試!" +UpdatePortErr : "錯誤:面板端口修改失敗,{{ .err }}" +UpdatePortResult : "面板端口:{{ .name }}" +UpdatePortFirewallAdd : "添加防火牆端口規則失敗,{{ .err }},請您手動將 {{ .name }} 端口添加至防火牆規則中。" +UpdatePortFirewallDel : "錯誤:防火牆端口刪除失敗,{{ .err }}" +UpdatePortFirewallReload : "防火牆重載失敗,{{ .err }},請您手動重載防火牆。" +UserInfo : "獲取面板信息" +UserInfoAddr : "面板地址:" +UserInfoPassHelp : "提示:修改密碼可執行命令:" +SystemVersion : "獲取系統版本信息" +DBConnErr : "錯誤:初始化資料庫連接失敗, {{ .err }}" \ No newline at end of file diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 145c7e587..61d0c39b1 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -219,3 +219,65 @@ ErrAlertSave: "告警信息保存错误,请检查后重试!" ErrAlertSync: "告警信息同步错误,请检查后重试!" ErrAlertRemote: "告警信息远端错误,请检查后重试!" +#cmd +AppVersion: "应用版本" +AppCommands: "应用相关命令" +AppInit: "初始化应用" +AppKeyVal: "应用的key(仅支持英文)" +AppCreateFileErr: "文件 {{ .name }} 创建失败 {{ .err }}" +AppCreateDirErr: "文件夹 {{ .name }} 创建失败 {{ .err }}" +AppMissKey: "应用的 key 缺失,使用 -k 指定" +AppMissVersion: "应用版本缺失,使用 -v 指定" +AppVersionExist: "版本已存在!" +AppCreateSuccessful: "创建成功!" +AppWriteErr: "文件 {{ .name }} 写入失败 {{ .err }}" +SudoHelper: "请使用 {{ .cmd }} 或者切换到 root 用户" +ListenIPCommands: "切换监听 IP" +ListenIPv4: "监听 IPv4" +ListenIPv6: "监听 IPv6" +ListenChangeSuccessful: "切换成功!已切换至监听 {{ .value }}" +ResetCommands: "重置系统信息" +ResetMFA: "取消 1Panel 两步验证" +ResetHttps: "取消 1Panel https 方式登录" +ResetEntrance: "取消 1Panel 安全入口" +ResetIPs: "取消 1Panel 授权 IP 限制" +ResetDomain: "取消 1Panel 访问域名绑定" +RestoreCommands: "回滚 1Panel 服务及数据" +RestoreNoSuchFile: "暂无可回滚文件" +RestoreStep1: "(0/4) 开始从 {{ .name }} 目录回滚 1Panel 服务及数据... " +RestoreStep2: "(1/4) 1panel 二进制回滚成功" +RestoreStep3: "(2/4) 1panel 脚本回滚成功" +RestoreStep4: "(3/4) 1panel 服务回滚成功" +RestoreStep5: "(4/4) 1panel 数据回滚成功" +RestoreSuccessful: "回滚成功!正在重启服务,请稍候..." +UpdateCommands: "修改面板信息" +UpdateUser: "修改面板用户" +UpdatePassword: "修改面板密码" +UpdatePort: "修改面板端口" +UpdateUserNull: "错误:输入面板用户为空!" +UpdateUserBlank: "错误:输入面板用户中包含空格字符!" +UpdateUserFormat: "错误:输入面板用户错误!仅支持英文、中文、数字和_,长度3-30" +UpdateUserErr: "错误:面板用户修改失败,{{ .err }}" +UpdateSuccessful: "修改成功!" +UpdateUserResult: "面板用户:{{ .name }}" +UpdatePasswordRead: "错误:面板密码信息读取错误,{{ .err }}" +UpdatePasswordNull: "错误:输入面板密码为空!" +UpdateUPasswordBlank: "错误:输入面板密码中包含空格字符!" +UpdatePasswordFormat: "错误:面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!" +UpdatePasswordLen: "错误:请输入 6 位以上密码!" +UpdatePasswordRe: "确认密码:" +UpdatePasswordErr: "错误:面板密码修改失败,{{ .err }}" +UpdatePasswordSame: "错误:两次密码不匹配,请检查后重试!" +UpdatePasswordResult: 面板密码:{{ .name }}" +UpdatePortFormat: "错误:输入的端口号必须在 1 到 65535 之间!" +UpdatePortUsed: "错误:该端口号正被占用,请检查后重试!" +UpdatePortErr: "错误:面板端口修改失败,{{ .err }}" +UpdatePortResult: "面板端口:{{ .name }}" +UpdatePortFirewallAdd: "添加防火墙端口规则失败,{{ .err }},请您手动将 {{ .name }} 端口添加至防火墙规则中。" +UpdatePortFirewallDel: "错误:防火墙端口删除失败,{{ .err }}" +UpdatePortFirewallReload: "防火墙重载失败,{{ .err }},请您手动重载防火墙。" +UserInfo: "获取面板信息" +UserInfoAddr: "面板地址:" +UserInfoPassHelp: "提示:修改密码可执行命令:" +SystemVersion: "获取系统版本信息" +DBConnErr: "错误:初始化数据库连接失败, {{ .err }}" \ No newline at end of file diff --git a/cmd/server/cmd/app.go b/cmd/server/cmd/app.go index d9a7c780e..042324c44 100644 --- a/cmd/server/cmd/app.go +++ b/cmd/server/cmd/app.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/cmd/server/app" "github.com/pkg/errors" @@ -17,23 +18,31 @@ var ( ) func init() { - initCmd.Flags().StringVarP(&appKey, "key", "k", "", "应用的key(仅支持英文)") - initCmd.Flags().StringVarP(&appVersion, "version", "v", "", "应用版本") + appCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadAppHelper() + }) + initCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadAppInitHelper() + }) + + initCmd.Flags().StringVarP(&appKey, "key", "k", "", "") + initCmd.Flags().StringVarP(&appVersion, "version", "v", "", "") appCmd.AddCommand(initCmd) RootCmd.AddCommand(appCmd) } var appCmd = &cobra.Command{ - Use: "app", - Short: "应用相关命令", + Use: "app", } var initCmd = &cobra.Command{ - Use: "init", - Short: "初始化应用", + Use: "init", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl app init 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl app init"})) return nil } if len(args) > 0 { @@ -43,11 +52,11 @@ var initCmd = &cobra.Command{ } } if appKey == "" { - fmt.Println("应用的 key 缺失,使用 -k 指定") + fmt.Println(i18n.GetMsgByKeyForCmd("AppMissKey")) return nil } if appVersion == "" { - fmt.Println("应用版本缺失,使用 -v 指定") + fmt.Println(i18n.GetMsgByKeyForCmd("AppMissVersion")) return nil } fileOp := files.NewFileOp() @@ -75,7 +84,7 @@ var initCmd = &cobra.Command{ } versionPath := fmt.Sprintf("%s/%s", appKeyPath, appVersion) if fileOp.Stat(versionPath) { - return errors.New("版本已存在!") + return errors.New(i18n.GetMsgByKeyForCmd("AppVersionExist")) } if err := createFolder(fileOp, versionPath); err != nil { return err @@ -91,7 +100,7 @@ var initCmd = &cobra.Command{ if err := createFile(fileOp, dockerComposeYamlPath); err != nil { return err } - fmt.Println("创建成功!") + fmt.Println(i18n.GetMsgByKeyForCmd("AppCreateSuccessful")) return nil }, } @@ -101,7 +110,7 @@ func createFile(fileOp files.FileOp, filePath string) error { return nil } if err := fileOp.CreateFile(filePath); err != nil { - fmt.Printf("文件 %s 创建失败 %v", filePath, err) + fmt.Println(i18n.GetMsgWithMapForCmd("AppCreateFileErr", map[string]interface{}{"name": filePath, "err": err.Error()})) return err } return nil @@ -112,7 +121,7 @@ func createFolder(fileOp files.FileOp, dirPath string) error { return nil } if err := fileOp.CreateDir(dirPath, 0755); err != nil { - fmt.Printf("文件夹 %s 创建失败 %v", dirPath, err) + fmt.Println(i18n.GetMsgWithMapForCmd("AppCreateDirErr", map[string]interface{}{"name": dirPath, "err": err.Error()})) return err } return nil @@ -120,8 +129,26 @@ func createFolder(fileOp files.FileOp, dirPath string) error { func writeFile(fileOp files.FileOp, filePath string, in io.Reader) error { if err := fileOp.WriteFile(filePath, in, 0755); err != nil { - fmt.Printf("文件 %s 写入失败 %v", filePath, err) + fmt.Println(i18n.GetMsgWithMapForCmd("AppWriteErr", map[string]interface{}{"name": filePath, "err": err.Error()})) return err } return nil } + +func loadAppHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("AppCommands")) + fmt.Println("\nUsage:\n 1panel app [command]\n\nAvailable Commands:") + fmt.Println("\n init " + i18n.GetMsgByKeyForCmd("AppInit")) + fmt.Println("\nFlags:\n -h, --help help for app") + fmt.Println(" -k, --key string " + i18n.GetMsgByKeyForCmd("AppKeyVal")) + fmt.Println(" -v, --version string " + i18n.GetMsgByKeyForCmd("AppVersion")) + fmt.Println("\nUse \"1panel app [command] --help\" for more information about a command.") +} + +func loadAppInitHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("AppInit")) + fmt.Println("\nUsage:\n 1panel app init [flags]") + fmt.Println("\nFlags:\n -h, --help help for app") + fmt.Println(" -k, --key string " + i18n.GetMsgByKeyForCmd("AppKeyVal")) + fmt.Println(" -v, --version string " + i18n.GetMsgByKeyForCmd("AppVersion")) +} diff --git a/cmd/server/cmd/listen-ip.go b/cmd/server/cmd/listen-ip.go index c0168878f..314e6107b 100644 --- a/cmd/server/cmd/listen-ip.go +++ b/cmd/server/cmd/listen-ip.go @@ -3,38 +3,48 @@ package cmd import ( "fmt" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/spf13/cobra" ) func init() { + listenCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadListenIPHelper() + }) + RootCmd.AddCommand(listenCmd) listenCmd.AddCommand(listenIpv4Cmd) listenCmd.AddCommand(listenIpv6Cmd) } var listenCmd = &cobra.Command{ - Use: "listen-ip", - Short: "切换监听 IP", + Use: "listen-ip", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadListenIPHelper() + return nil + }, } var listenIpv4Cmd = &cobra.Command{ - Use: "ipv4", - Short: "监听 IPv4", + Use: "ipv4", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) return updateBindInfo("ipv4") }, } var listenIpv6Cmd = &cobra.Command{ - Use: "ipv6", - Short: "监听 IPv6", + Use: "ipv6", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) return updateBindInfo("ipv6") }, } func updateBindInfo(protocol string) error { if !isRoot() { - fmt.Println("请使用 sudo 1pctl listen-ip ipv6 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl listen-ip ipv6"})) return nil } db, err := loadDBConn() @@ -55,6 +65,15 @@ func updateBindInfo(protocol string) error { if err := setSettingByKey(db, "BindAddress", address); err != nil { return err } - fmt.Printf("切换成功!已切换至监听 %s [%s]", tcp, address) + fmt.Println(i18n.GetMsgWithMapForCmd("ListenChangeSuccessful", map[string]interface{}{"value": fmt.Sprintf(" %s [%s]", tcp, address)})) return nil } + +func loadListenIPHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateCommands")) + fmt.Println("\nUsage:\n 1panel listen-ip [command]\n\nAvailable Commands:") + fmt.Println("\n ipv4 " + i18n.GetMsgByKeyForCmd("ListenIPv4")) + fmt.Println(" ipv6 " + i18n.GetMsgByKeyForCmd("ListenIPv6")) + fmt.Println("\nFlags:\n -h, --help help for listen-ip") + fmt.Println("\nUse \"1panel listen-ip [command] --help\" for more information about a command.") +} diff --git a/cmd/server/cmd/reset.go b/cmd/server/cmd/reset.go index d398b967c..07f660fc0 100644 --- a/cmd/server/cmd/reset.go +++ b/cmd/server/cmd/reset.go @@ -3,10 +3,16 @@ package cmd import ( "fmt" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/spf13/cobra" ) func init() { + resetCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadResetHelper() + }) + RootCmd.AddCommand(resetCmd) resetCmd.AddCommand(resetMFACmd) resetCmd.AddCommand(resetSSLCmd) @@ -16,16 +22,20 @@ func init() { } var resetCmd = &cobra.Command{ - Use: "reset", - Short: "重置系统信息", + Use: "reset", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadResetHelper() + return nil + }, } var resetMFACmd = &cobra.Command{ - Use: "mfa", - Short: "取消 1Panel 两步验证", + Use: "mfa", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl reset mfa 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset mfa"})) return nil } db, err := loadDBConn() @@ -37,11 +47,11 @@ var resetMFACmd = &cobra.Command{ }, } var resetSSLCmd = &cobra.Command{ - Use: "https", - Short: "取消 1Panel https 方式登录", + Use: "https", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl reset https 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset https"})) return nil } db, err := loadDBConn() @@ -53,11 +63,11 @@ var resetSSLCmd = &cobra.Command{ }, } var resetEntranceCmd = &cobra.Command{ - Use: "entrance", - Short: "取消 1Panel 安全入口", + Use: "entrance", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl reset entrance 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset entrance"})) return nil } db, err := loadDBConn() @@ -69,11 +79,11 @@ var resetEntranceCmd = &cobra.Command{ }, } var resetBindIpsCmd = &cobra.Command{ - Use: "ips", - Short: "取消 1Panel 授权 IP 限制", + Use: "ips", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl reset ips 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset ips"})) return nil } db, err := loadDBConn() @@ -85,11 +95,11 @@ var resetBindIpsCmd = &cobra.Command{ }, } var resetDomainCmd = &cobra.Command{ - Use: "domain", - Short: "取消 1Panel 访问域名绑定", + Use: "domain", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl reset domain 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset domain"})) return nil } db, err := loadDBConn() @@ -100,3 +110,15 @@ var resetDomainCmd = &cobra.Command{ return setSettingByKey(db, "BindDomain", "") }, } + +func loadResetHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("ResetCommands")) + fmt.Println("\nUsage:\n 1panel reset [command]\n\nAvailable Commands:") + fmt.Println("\n domain " + i18n.GetMsgByKeyForCmd("ResetDomain")) + fmt.Println(" entrance " + i18n.GetMsgByKeyForCmd("ResetEntrance")) + fmt.Println(" https " + i18n.GetMsgByKeyForCmd("ResetHttps")) + fmt.Println(" ips " + i18n.GetMsgByKeyForCmd("ResetIPs")) + fmt.Println(" mfa " + i18n.GetMsgByKeyForCmd("ResetMFA")) + fmt.Println("\nFlags:\n -h, --help help for reset") + fmt.Println("\nUse \"1panel reset [command] --help\" for more information about a command.") +} diff --git a/cmd/server/cmd/restore.go b/cmd/server/cmd/restore.go index ba04c0fae..7b9da5351 100644 --- a/cmd/server/cmd/restore.go +++ b/cmd/server/cmd/restore.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/1Panel-dev/1Panel/backend/i18n" cmdUtils "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/pkg/errors" @@ -20,11 +21,11 @@ func init() { } var restoreCmd = &cobra.Command{ - Use: "restore", - Short: "回滚 1Panel 服务及数据", + Use: "restore", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl restore 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl restore"})) return nil } stdout, err := cmdUtils.Exec("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2") @@ -38,25 +39,25 @@ var restoreCmd = &cobra.Command{ if err != nil { return err } - if tmpPath == "暂无可回滚文件" { - fmt.Println("暂无可回滚文件") + if tmpPath == "no such file" { + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreNoSuchFile")) return nil } tmpPath = path.Join(upgradeDir, tmpPath, "original") - fmt.Printf("(0/4) 开始从 %s 目录回滚 1Panel 服务及数据... \n", tmpPath) + fmt.Println(i18n.GetMsgWithMapForCmd("RestoreStep1", map[string]interface{}{"name": tmpPath})) if err := common.CopyFile(path.Join(tmpPath, "1panel"), "/usr/local/bin"); err != nil { return err } - fmt.Println("(1/4) 1panel 二进制回滚成功") + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep2")) if err := common.CopyFile(path.Join(tmpPath, "1pctl"), "/usr/local/bin"); err != nil { return err } - fmt.Println("(2/4) 1panel 脚本回滚成功") + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3")) if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil { return err } - fmt.Println("(3/4) 1panel 服务回滚成功") + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4")) checkPointOfWal() if _, err := os.Stat(path.Join(tmpPath, "1Panel.db")); err == nil { if err := common.CopyFile(path.Join(tmpPath, "1Panel.db"), path.Join(baseDir, "1panel/db")); err != nil { @@ -68,9 +69,8 @@ var restoreCmd = &cobra.Command{ return err } } - fmt.Printf("(4/4) 1panel 数据回滚成功 \n\n") - - fmt.Println("回滚成功!正在重启服务,请稍候...") + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep5")) + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreSuccessful")) return nil }, } @@ -85,7 +85,7 @@ func checkPointOfWal() { func loadRestorePath(upgradeDir string) (string, error) { if _, err := os.Stat(upgradeDir); err != nil && os.IsNotExist(err) { - return "暂无可回滚文件", nil + return "no such file", nil } files, err := os.ReadDir(upgradeDir) if err != nil { @@ -98,7 +98,7 @@ func loadRestorePath(upgradeDir string) (string, error) { } } if len(folders) == 0 { - return "暂无可回滚文件", nil + return "no such file", nil } sort.Slice(folders, func(i, j int) bool { return folders[i] > folders[j] diff --git a/cmd/server/cmd/root.go b/cmd/server/cmd/root.go index 78023bd3b..4fe640874 100644 --- a/cmd/server/cmd/root.go +++ b/cmd/server/cmd/root.go @@ -13,11 +13,14 @@ import ( "gorm.io/gorm" ) -func init() {} +var language string + +func init() { + RootCmd.PersistentFlags().StringVarP(&language, "language", "l", "zh", "Set the language (default is 'zh')") +} var RootCmd = &cobra.Command{ - Use: "1panel", - Short: "1Panel ,一款现代化的 Linux 面板", + Use: "1panel", RunE: func(cmd *cobra.Command, args []string) error { server.Start() return nil diff --git a/cmd/server/cmd/update.go b/cmd/server/cmd/update.go index 7ac8e2b7e..2b3fb9030 100644 --- a/cmd/server/cmd/update.go +++ b/cmd/server/cmd/update.go @@ -10,6 +10,7 @@ import ( "unicode" "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/encrypt" @@ -20,6 +21,11 @@ import ( ) func init() { + updateCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadUpdateHelper() + }) + RootCmd.AddCommand(updateCmd) updateCmd.AddCommand(updateUserName) updateCmd.AddCommand(updatePassword) @@ -27,16 +33,21 @@ func init() { } var updateCmd = &cobra.Command{ - Use: "update", - Short: "修改面板信息", + Use: "update", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadUpdateHelper() + return nil + }, } var updateUserName = &cobra.Command{ Use: "username", - Short: "修改面板用户", + Short: i18n.GetMsgByKeyForCmd("UpdateUser"), RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl update username 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update username"})) return nil } username() @@ -45,10 +56,11 @@ var updateUserName = &cobra.Command{ } var updatePassword = &cobra.Command{ Use: "password", - Short: "修改面板密码", + Short: i18n.GetMsgByKeyForCmd("UpdatePassword"), RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl update password 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update password"})) return nil } password() @@ -57,10 +69,11 @@ var updatePassword = &cobra.Command{ } var updatePort = &cobra.Command{ Use: "port", - Short: "修改面板端口", + Short: i18n.GetMsgByKeyForCmd("UpdatePort"), RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl update port 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update port"})) return nil } port() @@ -70,83 +83,83 @@ var updatePort = &cobra.Command{ func username() { reader := bufio.NewReader(os.Stdin) - fmt.Print("修改面板用户: ") + fmt.Print(i18n.GetMsgByKeyForCmd("UpdateUser") + ": ") newUsername, _ := reader.ReadString('\n') newUsername = strings.Trim(newUsername, "\n") if len(newUsername) == 0 { - fmt.Println("错误:输入面板用户为空!") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserNull")) return } if strings.Contains(newUsername, " ") { - fmt.Println("错误:输入面板用户中包含空格字符!") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserBlank")) return } result, err := regexp.MatchString("^[a-zA-Z0-9_\u4e00-\u9fa5]{3,30}$", newUsername) if !result || err != nil { - fmt.Println("错误:输入面板用户错误!仅支持英文、中文、数字和_,长度3-30") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserFormat")) return } db, err := loadDBConn() if err != nil { - fmt.Printf("错误:初始化数据库连接失败,%v\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) return } if err := setSettingByKey(db, "UserName", newUsername); err != nil { - fmt.Printf("错误:面板用户修改失败,%v\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()})) return } - fmt.Printf("修改成功!\n\n") - fmt.Printf("面板用户:%s\n", newUsername) + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": newUsername})) } func password() { - fmt.Print("修改面板密码:") + fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePassword") + ": ") bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { - fmt.Printf("\n错误:面板密码信息读取错误,%v\n", err) + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()})) return } newPassword := string(bytePassword) newPassword = strings.Trim(newPassword, "\n") if len(newPassword) == 0 { - fmt.Println("\n错误:输入面板密码为空!") + fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordNull")) return } if strings.Contains(newPassword, " ") { - fmt.Println("\n错误:输入面板密码中包含空格字符!") + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateUPasswordBlank")) return } db, err := loadDBConn() if err != nil { - fmt.Printf("\n错误:初始化数据库连接失败,%v\n", err) + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) return } complexSetting := getSettingByKey(db, "ComplexityVerification") if complexSetting == "enable" { if isValidPassword("newPassword") { - fmt.Println("\n错误:面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!") + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordFormat")) return } } if len(newPassword) < 6 { - fmt.Println("错误:请输入 6 位以上密码!") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePasswordLen")) return } - fmt.Print("\n确认密码:") + fmt.Print("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordRe")) byteConfirmPassword, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { - fmt.Printf("\n错误:面板密码信息读取错误,%v\n", err) + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()})) return } confirmPassword := string(byteConfirmPassword) confirmPassword = strings.Trim(confirmPassword, "\n") if newPassword != confirmPassword { - fmt.Printf("\n错误:两次密码不匹配,请检查后重试!,%v\n", err) + fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordSame")) return } @@ -159,54 +172,54 @@ func password() { p = newPassword } if err := setSettingByKey(db, "Password", p); err != nil { - fmt.Printf("\n错误:面板密码修改失败,%v\n", err) + fmt.Println("\n", i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()})) return } username := getSettingByKey(db, "UserName") - fmt.Printf("\n修改成功!\n\n") - fmt.Printf("面板用户:%s\n", username) - fmt.Printf("面板密码:%s\n", string(newPassword)) + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": username})) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePasswordResult", map[string]interface{}{"name": string(newPassword)})) } func port() { reader := bufio.NewReader(os.Stdin) - fmt.Print("修改面板端口:") + fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePort") + ": ") newPortStr, _ := reader.ReadString('\n') newPortStr = strings.Trim(newPortStr, "\n") newPort, err := strconv.Atoi(strings.TrimSpace(newPortStr)) if err != nil || newPort < 1 || newPort > 65535 { - fmt.Println("错误:输入的端口号必须在 1 到 65535 之间!") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortFormat")) return } if common.ScanPort(newPort) { - fmt.Println("错误:该端口号正被占用,请检查后重试!") + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortUsed")) return } db, err := loadDBConn() if err != nil { - fmt.Printf("错误:初始化数据库连接失败,%v\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) return } oldPortStr := getSettingByKey(db, "ServerPort") if err := setSettingByKey(db, "ServerPort", newPortStr); err != nil { - fmt.Printf("错误:面板端口修改失败,%v\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()})) return } - fmt.Printf("修改成功!\n\n") - fmt.Printf("面板端口:%s\n", newPortStr) + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortResult", map[string]interface{}{"name": newPortStr})) if client, err := firewall.NewFirewallClient(); err == nil { if err := client.Port(fireClient.FireInfo{Port: newPortStr, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { - fmt.Printf("添加防火墙端口规则失败,%v,请您手动将 %s 端口添加至防火墙规则中。\n", newPortStr, err) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortFirewallAdd", map[string]interface{}{"name": newPortStr, "err": err.Error()})) } if err := client.Port(fireClient.FireInfo{Port: oldPortStr, Protocol: "tcp", Strategy: "accept"}, "remove"); err != nil { - fmt.Printf("错误:防火墙端口删除失败,%v\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortFirewallDel", map[string]interface{}{"err": err.Error()})) } if err := client.Reload(); err != nil { - fmt.Printf("防火墙重载失败,%v,请您手动重载防火墙。\n", err) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortFirewallReload", map[string]interface{}{"err": err.Error()})) } } @@ -253,3 +266,13 @@ func contains(specialChars string, char rune) bool { } return false } + +func loadUpdateHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateCommands")) + fmt.Println("\nUsage:\n 1panel update [command]\n\nAvailable Commands:") + fmt.Println("\n password " + i18n.GetMsgByKeyForCmd("UpdatePassword")) + fmt.Println(" port " + i18n.GetMsgByKeyForCmd("UpdatePort")) + fmt.Println(" username " + i18n.GetMsgByKeyForCmd("UpdateUser")) + fmt.Println("\nFlags:\n -h, --help help for update") + fmt.Println("\nUse \"1panel update [command] --help\" for more information about a command.") +} diff --git a/cmd/server/cmd/user-info.go b/cmd/server/cmd/user-info.go index c13a8efa5..5a12b7144 100644 --- a/cmd/server/cmd/user-info.go +++ b/cmd/server/cmd/user-info.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/encrypt" "github.com/spf13/cobra" ) @@ -13,11 +14,11 @@ func init() { } var userinfoCmd = &cobra.Command{ - Use: "user-info", - Short: "获取面板信息", + Use: "user-info", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl user-info 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl user-info"})) return nil } db, err := loadDBConn() @@ -47,10 +48,10 @@ var userinfoCmd = &cobra.Command{ address = "$LOCAL_IP" } - fmt.Printf("面板地址: %s://%s:%s/%s \n", protocol, address, port, entrance) - fmt.Println("面板用户: ", user) - fmt.Println("面板密码: ", pass) - fmt.Println("提示:修改密码可执行命令:1pctl update password") + fmt.Println(i18n.GetMsgByKeyForCmd("UserInfoAddr") + fmt.Sprintf("%s://%s:%s/%s ", protocol, address, port, entrance)) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": user})) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePasswordResult", map[string]interface{}{"name": pass})) + fmt.Println(i18n.GetMsgByKeyForCmd("UserInfoPassHelp") + "1pctl update password") return nil }, } diff --git a/cmd/server/cmd/version.go b/cmd/server/cmd/version.go index e40faff3e..daa89993e 100644 --- a/cmd/server/cmd/version.go +++ b/cmd/server/cmd/version.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/1Panel-dev/1Panel/backend/configs" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/cmd/server/conf" "gopkg.in/yaml.v3" @@ -15,11 +16,11 @@ func init() { } var versionCmd = &cobra.Command{ - Use: "version", - Short: "获取系统版本信息", + Use: "version", RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) if !isRoot() { - fmt.Println("请使用 sudo 1pctl version 或者切换到 root 用户") + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl version"})) return nil } db, err := loadDBConn()