diff --git a/README.md b/README.md index a400865..fc5a6c3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -# redis-desktop +# Redis Desktop a redis-cli with gui. ![demo](screenshoot/tabpage.png) - -# features +# Features * [x] save sessions * [x] multi tabpages * [x] no any dependencies +* [x] i18n * [ ] batch exec commands +* [ ] other charming features..... -# thanks +# Thanks the project is powered by [walk](https://github.com/lxn/walk) and [redigo](https://github.com/gomodule/redigo) -# license +# License the porject is under [MIT license](LICENSE) which can be found in LICENSE file. diff --git a/i18n/en_us.go b/i18n/en_us.go new file mode 100644 index 0000000..c24c7da --- /dev/null +++ b/i18n/en_us.go @@ -0,0 +1,34 @@ +package i18n + +func init() { + AddLang(en_us) +} + +var en_us = Lang{ + name: "en_us", + Translation: &Translation{ + sections: make([]string, 0), + words: map[string]string{ + "mainwindow.title": "Redis Cli Desktop", + "mainwindow.menu.file": "File", + "mainwindow.menu.file.export": "export", + "mainwindow.menu.file.import": "import", + "mainwindow.menu.edit": "Edit", + "mainwindow.menu.edit.clear": "clear", + "mainwindow.menu.setting": "Setting", + "mainwindow.menu.setting.theme": "theme", + "mainwindow.menu.logpath": "Log Path", + "mainwindow.menu.run": "Run", + "mainwindow.menu.run.batch": "run batch command", + "mainwindow.menu.help": "Help", + "mainwindow.menu.help.source": "view source code", + "mainwindow.menu.help.bug": "new issue", + "mainwindow.menu.help.donate": "donate", + "mainwindow.LBsessions.menu.deletesession": "delete session", + "mainwindow.labelhost": "Host", + "mainwindow.labelport": "Port", + "mainwindow.labelpassword": "Password", + "mainwindow.PBconnect": "Connect", + }, + }, +} diff --git a/i18n/english.go b/i18n/english.go deleted file mode 100644 index 8affc0a..0000000 --- a/i18n/english.go +++ /dev/null @@ -1 +0,0 @@ -package i18n diff --git a/i18n/i18n.go b/i18n/i18n.go new file mode 100644 index 0000000..0574220 --- /dev/null +++ b/i18n/i18n.go @@ -0,0 +1,114 @@ +package i18n + +import ( + "fmt" + "reflect" + "strings" +) + +type Lang struct { + name string + *Translation +} + +type langStore struct { + defaultLang Lang + langs map[string]Lang +} + +var defaultStore = langStore{ + langs: make(map[string]Lang), +} + +func SetDefaultLang(lang Lang) { + defaultStore.defaultLang = lang +} + +func AddLang(lang Lang) error { + if _, ok := defaultStore.langs[strings.ToLower(lang.name)]; ok { + return fmt.Errorf("the lang `%s` already exist", lang.name) + } + defaultStore.langs[strings.ToLower(lang.name)] = lang + if defaultStore.defaultLang.name == "" { + defaultStore.defaultLang = lang + } + return nil +} + +func GetLang(name string) (Lang, bool) { + if lang, ok := defaultStore.langs[strings.ToLower(name)]; ok { + return lang, true + } + return Lang{}, false +} + +func (l Lang) Tr(format string, args ...interface{}) string { + return Tr(l.name, format, args...) +} + +type Translation struct { + sections []string + words map[string]string +} + +func (t *Translation) clone() *Translation { + secs := make([]string, len(t.sections)) + copy(secs, t.sections) + return &Translation{ + sections: secs, + words: t.words, + } +} + +func (t *Translation) Section(sec string) *Translation { + t0 := t.clone() + t0.sections = append(t0.sections, sec) + return t0 +} + +func (t *Translation) Get(name string) (string, bool) { + sections := t.sections + sections = append(sections, name) + key := strings.Join(sections, ".") + v, ok := t.words[key] + return v, ok +} + +// Tr translates content to target language. +func Tr(lang, format string, args ...interface{}) string { + language, ok := defaultStore.langs[lang] + if !ok { + language = defaultStore.defaultLang + } + + value, ok := language.Get(format) + if ok { + format = value + } else { + // try default language + value, ok = defaultStore.defaultLang.Get(format) + if ok { + format = value + } + } + + if len(args) > 0 { + params := make([]interface{}, 0, len(args)) + for _, arg := range args { + if arg == nil { + continue + } + + val := reflect.ValueOf(arg) + if val.Kind() == reflect.Slice { + for i := 0; i < val.Len(); i++ { + params = append(params, val.Index(i).Interface()) + } + } else { + params = append(params, arg) + } + } + return fmt.Sprintf(format, params...) + } + return format +} diff --git a/i18n/zh_cn.go b/i18n/zh_cn.go index 8affc0a..af4f86f 100644 --- a/i18n/zh_cn.go +++ b/i18n/zh_cn.go @@ -1 +1,34 @@ package i18n + +func init() { + AddLang(zh_ch) +} + +var zh_ch = Lang{ + name: "zh_cn", + Translation: &Translation{ + sections: make([]string, 0), + words: map[string]string{ + "mainwindow.title": "redis命令行工具", + "mainwindow.menu.file": "文件", + "mainwindow.menu.file.export": "导出会话", + "mainwindow.menu.file.import": "导入会话", + "mainwindow.menu.edit": "编辑", + "mainwindow.menu.edit.clear": "清屏", + "mainwindow.menu.setting": "设置", + "mainwindow.menu.setting.theme": "主题", + "mainwindow.menu.logpath": "日志路径", + "mainwindow.menu.run": "运行", + "mainwindow.menu.run.batch": "批量运行命令", + "mainwindow.menu.help": "帮助", + "mainwindow.menu.help.source": "查看源码", + "mainwindow.menu.help.bug": "提bug", + "mainwindow.menu.help.donate": "捐赠", + "mainwindow.LBsessions.menu.deletesession": "删除会话", + "mainwindow.labelhost": "主机", + "mainwindow.labelport": "端口", + "mainwindow.labelpassword": "密码", + "mainwindow.PBconnect": "连接", + }, + }, +} diff --git a/main.go b/main.go index 031558b..696ad8f 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/chenqinghe/redis-desktop/i18n" "github.com/sirupsen/logrus" "io/ioutil" "os" @@ -32,14 +33,18 @@ func main() { // coredump(err.Error()) //} - mw := createMainWindow() - mw.sessionFile = filepath.Join(rootPath, "RedisDesktop", "data") - sessions, err := loadSession(mw.sessionFile) - if err != nil { + // TODO: how to config languages? + lang, ok := i18n.GetLang("zh_cn") + if !ok { + return + } + mw := createMainWindow(lang) + + mw.SetSessionFile(filepath.Join(rootPath, "RedisDesktop", "data")) + if err := mw.LoadSession(); err != nil { walk.MsgBox(nil, "ERROR", "加载会话文件失败:"+err.Error(), walk.MsgBoxIconError) return } - mw.LB_sessions.AddSessions(sessions) mw.Run() } diff --git a/mainwindow.go b/mainwindow.go index 9c92c08..fc3a479 100644 --- a/mainwindow.go +++ b/mainwindow.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "github.com/chenqinghe/redis-desktop/i18n" "github.com/lxn/walk" . "github.com/lxn/walk/declarative" "github.com/sirupsen/logrus" @@ -19,6 +20,8 @@ type MainWindowEX struct { logFile string + lang i18n.Lang + LE_host *walk.LineEdit LE_port *walk.LineEdit LE_password *walk.LineEdit @@ -51,23 +54,30 @@ RETRY: return nil } -func loadSession(file string) ([]session, error) { - data, err := ioutil.ReadFile(file) +func (mw *MainWindowEX) SetSessionFile(file string) { + mw.sessionFile = file +} + +func (mw *MainWindowEX) LoadSession() error { + data, err := ioutil.ReadFile(mw.sessionFile) if err != nil { if os.IsNotExist(err) { - return nil, nil + return nil } - return nil, err + return err } sessions := make([]session, 0) if err := json.Unmarshal(data, &sessions); err != nil { - return nil, err + return err } - return sessions, nil + + mw.LB_sessions.AddSessions(sessions) + return nil } -func createMainWindow() *MainWindowEX { +func createMainWindow(lang i18n.Lang) *MainWindowEX { mw := &MainWindowEX{ + lang: lang, PB_connect: new(PushButtonEx), LB_sessions: new(ListBoxEX), TW_screenGroup: new(TabWidgetEx), @@ -76,71 +86,71 @@ func createMainWindow() *MainWindowEX { mw.LB_sessions.root = mw mw.TW_screenGroup.root = mw err := MainWindow{ - Title: "redis命令行工具", + Title: mw.lang.Tr("mainwindow.title"), MinSize: Size{600, 400}, AssignTo: &mw.MainWindow, Layout: VBox{MarginsZero: true}, MenuItems: []MenuItem{ Menu{ - Text: "文件", + Text: mw.lang.Tr("mainwindow.menu.file"), Items: []MenuItem{ Action{ - Text: "导出会话...", + Text: mw.lang.Tr("mainwindow.menu.file.export"), OnTriggered: nil, }, Action{ - Text: "导入会话...", + Text: mw.lang.Tr("mainwindow.menu.file.import"), OnTriggered: nil, }, }, }, Menu{ - Text: "编辑", + Text: mw.lang.Tr("mainwindow.menu.edit"), Items: []MenuItem{ Action{ - Text: "清屏", + Text: mw.lang.Tr("mainwindow.menu.edit.clear"), OnTriggered: nil, }, }, }, Menu{ - Text: "设置", + Text: mw.lang.Tr("mainwindow.menu.setting"), Items: []MenuItem{ Action{ - Text: "主题", + Text: mw.lang.Tr("mainwindow.menu.setting.theme"), OnTriggered: nil, }, Action{ - Text: "日志路径", + Text: mw.lang.Tr("mainwindow.menu.logpath"), OnTriggered: nil, }, }, }, Menu{ - Text: "运行", + Text: mw.lang.Tr("mainwindow.menu.run"), Items: []MenuItem{ Action{ - Text: "批量运行命令", + Text: mw.lang.Tr("mainwindow.menu.run.batch"), OnTriggered: nil, }, }, }, Menu{ - Text: "帮助", + Text: mw.lang.Tr("mainwindow.menu.help"), Items: []MenuItem{ Action{ - Text: "查看源码", + Text: mw.lang.Tr("mainwindow.menu.help.source"), OnTriggered: func() { startPage("https://github.com/chenqinghe/redis-desktop") }, }, Action{ - Text: "报bug", + Text: mw.lang.Tr("mainwindow.menu.help.bug"), OnTriggered: startIssuePage, }, Separator{}, Action{ - Text: "捐赠", + Text: mw.lang.Tr("mainwindow.menu.help.donate"), OnTriggered: func() { showDonate(mw) }, @@ -159,14 +169,14 @@ func createMainWindow() *MainWindowEX { MaxSize: Size{0, 50}, Layout: HBox{}, Children: []Widget{ - Label{Text: "host"}, + Label{Text: mw.lang.Tr("mainwindow.labelhost")}, LineEdit{AssignTo: &mw.LE_host}, - Label{Text: "port"}, + Label{Text: mw.lang.Tr("mainwindow.labelport")}, LineEdit{AssignTo: &mw.LE_port}, - Label{Text: "password"}, + Label{Text: mw.lang.Tr("mainwindow.labelpassword")}, LineEdit{AssignTo: &mw.LE_password, PasswordMode: true}, PushButton{ - Text: "连接", + Text: mw.lang.Tr("mainwindow.PBconnect"), AssignTo: &mw.PB_connect.PushButton, OnClicked: mw.PB_connect.OnClick, }, @@ -189,7 +199,7 @@ func createMainWindow() *MainWindowEX { MultiSelection: false, ContextMenuItems: []MenuItem{ Action{ - Text: "删除会话", + Text: mw.lang.Tr("mainwindow.LBsessions.menu.deletesession"), OnTriggered: mw.LB_sessions.RemoveSelectedSession, }, },