Resolves merge conflicts

This commit is contained in:
Sean E. Russell 2020-11-23 14:58:32 -06:00
commit daf5916fde
37 changed files with 1558 additions and 318 deletions

44
LICENSE
View File

@ -1,29 +1,25 @@
The MIT License (MIT) The MIT License (Festival variant)
Copyright (c) 2020 Sean E. Russell <ser@ser1.net> 87651D71 Permission is hereby granted, free of charge, to use and distribute this
software and its documentation without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of this work, and to permit persons to whom
this work is furnished to do so, subject to the following conditions:
Redistribution and use in source and binary forms, with or without 1. The codemust retain the above copyright notice, this list of conditions
modification, are permitted provided that the following conditions are met: and the following disclaimer.
2. Any modifications must be clearly marked as such.
3. Original authors' names are not deleted.
4. The authors' names are not used to endorse or promote products derived
from this software without specific prior written permission.
1. Redistributions of source code must retain the above copyright notice, THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS TO THIS WORK DISCLAIM ALL
this list of conditions and the following disclaimer. WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS NOR THE
CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its https://fedoraproject.org/wiki/Licensing:MIT?rd=Licensing/MIT#Festival_variant
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -73,6 +73,10 @@ Move `gotop` to somewhere in your `$PATH`.
If Go is not installed or is the wrong version, and you don't have root access or don't want to upgrade Go, a script is provided to download Go and the gotop sources, compile gotop, and then clean up. See `scripts/install_without_root.sh`. If Go is not installed or is the wrong version, and you don't have root access or don't want to upgrade Go, a script is provided to download Go and the gotop sources, compile gotop, and then clean up. See `scripts/install_without_root.sh`.
#### go generate
Apple SMC tags are embedded in a text file that is compiled into the executable; the same happens with the language translations. When the file `devices/data/sm.tsv` or any translations in `translations/dicts/` change, `go generate` should be re-run.
## Usage ## Usage
Run with `-h` to get an extensive list of command line arguments. Many of these can be configured by creating a configuration file; see the next section for more information. Key bindings can be viewed while gotop is running by pressing the `?` key, or they can be printed out by using the `--list keys` command. Run with `-h` to get an extensive list of command line arguments. Many of these can be configured by creating a configuration file; see the next section for more information. Key bindings can be viewed while gotop is running by pressing the `?` key, or they can be printed out by using the `--list keys` command.
@ -114,6 +118,7 @@ For more information on other topics, see:
- [goreleaser/nfpm](https://github.com/goreleaser/nfpm) - [goreleaser/nfpm](https://github.com/goreleaser/nfpm)
- [distatus/battery](https://github.com/distatus/battery) - [distatus/battery](https://github.com/distatus/battery)
- [VictoriaMetrics/metrics](https://github.com/VictoriaMetrics/metrics) Check this out! The API is clean, elegant, introduces many fewer indirect dependencies than the Prometheus client, and adds 50% less size to binaries. - [VictoriaMetrics/metrics](https://github.com/VictoriaMetrics/metrics) Check this out! The API is clean, elegant, introduces many fewer indirect dependencies than the Prometheus client, and adds 50% less size to binaries.
- [lingo-toml](https://github.com/jdkeke142/lingo-toml) provides the translation support library.
## History ## History

View File

@ -5,6 +5,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -18,7 +19,9 @@ import (
//_ "net/http/pprof" //_ "net/http/pprof"
"github.com/VictoriaMetrics/metrics" "github.com/VictoriaMetrics/metrics"
jj "github.com/cloudfoundry-attic/jibber_jabber"
ui "github.com/gizak/termui/v3" ui "github.com/gizak/termui/v3"
"github.com/jdkeke142/lingo-toml"
"github.com/shibukawa/configdir" "github.com/shibukawa/configdir"
"github.com/xxxserxxx/opflag" "github.com/xxxserxxx/opflag"
@ -27,7 +30,7 @@ import (
"github.com/xxxserxxx/gotop/v4/devices" "github.com/xxxserxxx/gotop/v4/devices"
"github.com/xxxserxxx/gotop/v4/layout" "github.com/xxxserxxx/gotop/v4/layout"
"github.com/xxxserxxx/gotop/v4/logging" "github.com/xxxserxxx/gotop/v4/logging"
"github.com/xxxserxxx/gotop/v4/widgets" "github.com/xxxserxxx/gotop/v4/translations"
w "github.com/xxxserxxx/gotop/v4/widgets" w "github.com/xxxserxxx/gotop/v4/widgets"
) )
@ -50,6 +53,7 @@ var (
bar *w.StatusBar bar *w.StatusBar
statusbar bool statusbar bool
stderrLogger = log.New(os.Stderr, "", 0) stderrLogger = log.New(os.Stderr, "", 0)
tr lingo.Translations
) )
func parseArgs() error { func parseArgs() error {
@ -58,33 +62,27 @@ func parseArgs() error {
for i, p := range cds { for i, p := range cds {
cpaths[i] = p.Path cpaths[i] = p.Path
} }
help := opflag.BoolP("help", "h", false, "Show this screen.") help := opflag.BoolP("help", "h", false, tr.Value("args.help"))
color := opflag.StringP("color", "c", conf.Colorscheme.Name, "Set a colorscheme.") color := opflag.StringP("color", "c", conf.Colorscheme.Name, tr.Value("args.color"))
opflag.IntVarP(&conf.GraphHorizontalScale, "graphscale", "S", conf.GraphHorizontalScale, "Graph scale factor, >0") opflag.IntVarP(&conf.GraphHorizontalScale, "graphscale", "S", conf.GraphHorizontalScale, tr.Value("args.scale"))
version := opflag.BoolP("version", "v", false, "Print version and exit.") version := opflag.BoolP("version", "v", false, tr.Value("args.version"))
versioN := opflag.BoolP("", "V", false, "Print version and exit.") versioN := opflag.BoolP("", "V", false, tr.Value("args.version"))
opflag.BoolVarP(&conf.PercpuLoad, "percpu", "p", conf.PercpuLoad, "Show each CPU in the CPU widget.") opflag.BoolVarP(&conf.PercpuLoad, "percpu", "p", conf.PercpuLoad, tr.Value("args.percpu"))
opflag.BoolVarP(&conf.AverageLoad, "averagecpu", "a", conf.AverageLoad, "Show average CPU in the CPU widget.") opflag.BoolVarP(&conf.AverageLoad, "averagecpu", "a", conf.AverageLoad, tr.Value("args.cpuavg"))
fahrenheit := opflag.BoolP("fahrenheit", "f", conf.TempScale == 'F', "Show temperatures in fahrenheit.Show temperatures in fahrenheit.") fahrenheit := opflag.BoolP("fahrenheit", "f", conf.TempScale == 'F', tr.Value("args.temp"))
opflag.BoolVarP(&conf.Statusbar, "statusbar", "s", conf.Statusbar, "Show a statusbar with the time.") opflag.BoolVarP(&conf.Statusbar, "statusbar", "s", conf.Statusbar, tr.Value("args.statusbar"))
opflag.DurationVarP(&conf.UpdateInterval, "rate", "r", conf.UpdateInterval, "Refresh frequency. Most time units accepted. `1m` = refresh every minute. `100ms` = refresh every 100ms.") opflag.DurationVarP(&conf.UpdateInterval, "rate", "r", conf.UpdateInterval, tr.Value("args.rate"))
opflag.StringVarP(&conf.Layout, "layout", "l", conf.Layout, `Name of layout spec file for the UI. Use "-" to pipe.`) opflag.StringVarP(&conf.Layout, "layout", "l", conf.Layout, tr.Value("args.layout"))
opflag.StringVarP(&conf.NetInterface, "interface", "i", "all", "Select network interface. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using `!`") opflag.StringVarP(&conf.NetInterface, "interface", "i", "all", tr.Value("args.net"))
opflag.StringVarP(&conf.ExportPort, "export", "x", conf.ExportPort, "Enable metrics for export on the specified port.") opflag.StringVarP(&conf.ExportPort, "export", "x", conf.ExportPort, tr.Value("args.export"))
opflag.BoolVarP(&conf.Mbps, "mbps", "", conf.Mbps, "Show network rate as mbps.") opflag.BoolVarP(&conf.Mbps, "mbps", "", conf.Mbps, tr.Value("args.mbps"))
opflag.BoolVar(&conf.Test, "test", conf.Test, "Runs tests and exits with success/failure code.") opflag.BoolVar(&conf.Test, "test", conf.Test, tr.Value("args.test"))
opflag.StringP("", "C", "", "Config file to use instead of default (MUST BE FIRST ARGUMENT)") opflag.StringP("", "C", "", tr.Value("args.conffile"))
list := opflag.String("list", "", `List <devices|layouts|colorschemes|paths|keys> list := opflag.String("list", "", tr.Value("args.list"))
devices: Prints out device names for filterable widgets wc := opflag.Bool("write-config", false, tr.Value("args.write"))
layouts: Lists build-in layouts
colorschemes: Lists built-in colorschemes
paths: List out configuration file search paths
widgets: Widgets that can be used in a layout
keys: Show the keyboard bindings.`)
wc := opflag.Bool("write-config", false, "Write out a default config file.")
opflag.SortFlags = false opflag.SortFlags = false
opflag.Usage = func() { opflag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\nOptions:\n", os.Args[0]) fmt.Fprintf(os.Stderr, tr.Value("usage", os.Args[0]))
opflag.PrintDefaults() opflag.PrintDefaults()
} }
opflag.Parse() opflag.Parse()
@ -109,25 +107,26 @@ func parseArgs() error {
if *list != "" { if *list != "" {
switch *list { switch *list {
case "layouts": case "layouts":
fmt.Println(_layouts) fmt.Println(tr.Value("help.layouts"))
case "colorschemes": case "colorschemes":
fmt.Println(_colorschemes) fmt.Println(tr.Value("help.colorschemes"))
case "paths": case "paths":
fmt.Println("Loadable colorschemes & layouts, and the config file, are searched for, in order:") fmt.Println(tr.Value("help.paths"))
paths := make([]string, 0) paths := make([]string, 0)
for _, d := range conf.ConfigDir.QueryFolders(configdir.All) { for _, d := range conf.ConfigDir.QueryFolders(configdir.All) {
paths = append(paths, d.Path) paths = append(paths, d.Path)
} }
fmt.Println(strings.Join(paths, "\n")) fmt.Println(strings.Join(paths, "\n"))
fmt.Printf("\nThe log file is in %s\n", filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE)) fmt.Println()
fmt.Println(tr.Value("help.log", filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE)))
case "devices": case "devices":
listDevices() listDevices()
case "keys": case "keys":
fmt.Println(widgets.KEYBINDS) fmt.Println(tr.Value("help.help"))
case "widgets": case "widgets":
fmt.Println(_widgets) fmt.Println(tr.Value("help.widgets"))
default: default:
fmt.Printf("Unknown option \"%s\"; try layouts, colorschemes, keys, paths, or devices\n", *list) fmt.Printf(tr.Value("error.unknownopt", *list))
os.Exit(1) os.Exit(1)
} }
os.Exit(0) os.Exit(0)
@ -135,10 +134,10 @@ func parseArgs() error {
if *wc { if *wc {
path, err := conf.Write() path, err := conf.Write()
if err != nil { if err != nil {
fmt.Printf("Failed to write configuration file: %s\n", err) fmt.Println(tr.Value("error.writefail", err.Error()))
os.Exit(1) os.Exit(1)
} }
fmt.Printf("Config written to %s\n", path) fmt.Println(tr.Value("help.written", path))
os.Exit(0) os.Exit(0)
} }
return nil return nil
@ -345,38 +344,55 @@ func main() {
ec := run() ec := run()
if ec > 0 { if ec > 0 {
if ec < 2 { if ec < 2 {
fmt.Printf("errors encountered; check the log file %s\n", filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE)) logpath := filepath.Join(conf.ConfigDir.QueryCacheFolder().Path, logging.LOGFILE)
fmt.Println(tr.Value("error.checklog", logpath))
bs, _ := ioutil.ReadFile(logpath)
fmt.Println(string(bs))
} }
} }
os.Exit(ec) os.Exit(ec)
} }
func run() int { func run() int {
ling, err := lingo.New("en_US", "translations", translations.AssetFile())
if err != nil {
fmt.Printf("failed to load language files: %s\n", err)
return 2
}
lang, err := jj.DetectIETF()
if err != nil {
lang = "en_US"
}
lang = strings.Replace(lang, "-", "_", -1)
// Get the locale from the os
tr = ling.TranslationsForLocale(lang)
colorschemes.SetTr(tr)
conf = gotop.NewConfig() conf = gotop.NewConfig()
conf.Tr = tr
// Find the config file; look in (1) local, (2) user, (3) global // Find the config file; look in (1) local, (2) user, (3) global
// Check the last argument first // Check the last argument first
fs := flag.NewFlagSet("config", flag.ContinueOnError) fs := flag.NewFlagSet("config", flag.ContinueOnError)
cfg := fs.String("C", "", "Config file") cfg := fs.String("C", "", tr.Value("configfile"))
fs.SetOutput(bufio.NewWriter(nil)) fs.SetOutput(bufio.NewWriter(nil))
fs.Parse(os.Args[1:]) fs.Parse(os.Args[1:])
if *cfg != "" { if *cfg != "" {
conf.ConfigFile = *cfg conf.ConfigFile = *cfg
} }
err := conf.Load() err = conf.Load()
if err != nil { if err != nil {
fmt.Printf("failed to parse config file: %s\n", err) fmt.Println(tr.Value("error.configparse", err.Error()))
return 2 return 2
} }
// Override with command line arguments // Override with command line arguments
err = parseArgs() err = parseArgs()
if err != nil { if err != nil {
fmt.Printf("parsing CLI args: %s\n", err) fmt.Println(tr.Value("error.cliparse", err.Error()))
return 2 return 2
} }
logfile, err := logging.New(conf) logfile, err := logging.New(conf)
if err != nil { if err != nil {
fmt.Printf("failed to setup log file: %v\n", err) fmt.Println(tr.Value("logsetup", err.Error()))
return 2 return 2
} }
defer logfile.Close() defer logfile.Close()
@ -407,7 +423,7 @@ func run() int {
defer ui.Close() defer ui.Close()
setDefaultTermuiColors(conf) // done before initializing widgets to allow inheriting colors setDefaultTermuiColors(conf) // done before initializing widgets to allow inheriting colors
help = w.NewHelpMenu() help = w.NewHelpMenu(tr)
if statusbar { if statusbar {
bar = w.NewStatusBar() bar = w.NewStatusBar()
} }
@ -467,7 +483,7 @@ func getLayout(conf gotop.Config) (io.Reader, error) {
for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) { for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) {
paths = append(paths, d.Path) paths = append(paths, d.Path)
} }
return nil, fmt.Errorf("unable find layout file %s in %s", conf.Layout, strings.Join(paths, ", ")) return nil, fmt.Errorf(tr.Value("error.findlayout", conf.Layout, strings.Join(paths, ", ")))
} }
lo, err := folder.ReadFile(conf.Layout) lo, err := folder.ReadFile(conf.Layout)
if err != nil { if err != nil {
@ -492,25 +508,3 @@ func listDevices() {
} }
} }
} }
const _layouts = `Built-in layouts:
default
minimal
battery
kitchensink`
const _colorschemes = `Built-in colorschemes:
default
default-dark (for white background)
solarized
solarized16-dark
solarized16-light
monokai
vice`
const _widgets = `Widgets that can be used in layouts:
cpu - CPU load graph
mem - Physical & swap memory use graph
temp - Sensor temperatures
disk - Physical disk partition use
power - A battery bar
net - Network load
procs - Interactive process list`

View File

@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/jdkeke142/lingo-toml"
"github.com/shibukawa/configdir" "github.com/shibukawa/configdir"
) )
@ -17,6 +18,13 @@ func init() {
} }
} }
var tr lingo.Translations
// Set the translation library
func SetTr(tra lingo.Translations) {
tr = tra
}
// FromName loads a Colorscheme by name; confDir is used to search // FromName loads a Colorscheme by name; confDir is used to search
// directories for a scheme matching the name. The search order // directories for a scheme matching the name. The search order
// is the same as for config files. // is the same as for config files.
@ -46,15 +54,15 @@ func getCustomColorscheme(confDir configdir.ConfigDir, name string) (Colorscheme
for _, d := range confDir.QueryFolders(configdir.Existing) { for _, d := range confDir.QueryFolders(configdir.Existing) {
paths = append(paths, d.Path) paths = append(paths, d.Path)
} }
return cs, fmt.Errorf("failed to find colorscheme file %s in %s", fn, strings.Join(paths, ", ")) return cs, fmt.Errorf(tr.Value("error.colorschemefile", fn, strings.Join(paths, ", ")))
} }
dat, err := folder.ReadFile(fn) dat, err := folder.ReadFile(fn)
if err != nil { if err != nil {
return cs, fmt.Errorf("failed to read colorscheme file %s: %v", filepath.Join(folder.Path, fn), err) return cs, fmt.Errorf(tr.Value("error.colorschemeload", filepath.Join(folder.Path, fn), err.Error()))
} }
err = json.Unmarshal(dat, &cs) err = json.Unmarshal(dat, &cs)
if err != nil { if err != nil {
return cs, fmt.Errorf("failed to parse colorscheme file: %v", err) return cs, fmt.Errorf(tr.Value("error.colorschemeparse", err.Error()))
} }
return cs, nil return cs, nil
} }

View File

@ -1,5 +1,8 @@
package gotop package gotop
//go:generate go-bindata -fs -pkg translations -prefix translations -o translations/dicts.go translations/dicts
//go:generate go-bindata -pkg devices -prefix devices/data -o devices/smc.go devices/data
import ( import (
"bufio" "bufio"
"bytes" "bytes"
@ -13,6 +16,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/jdkeke142/lingo-toml"
"github.com/shibukawa/configdir" "github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v4/colorschemes" "github.com/xxxserxxx/gotop/v4/colorschemes"
"github.com/xxxserxxx/gotop/v4/widgets" "github.com/xxxserxxx/gotop/v4/widgets"
@ -40,6 +44,7 @@ type Config struct {
Test bool Test bool
ExtensionVars map[string]string ExtensionVars map[string]string
ConfigFile string ConfigFile string
Tr lingo.Translations
} }
func NewConfig() Config { func NewConfig() Config {
@ -97,18 +102,15 @@ func load(in io.Reader, conf *Config) error {
} }
kv := strings.Split(l, "=") kv := strings.Split(l, "=")
if len(kv) != 2 { if len(kv) != 2 {
return fmt.Errorf("bad config file syntax; should be KEY=VALUE, was %s", l) return fmt.Errorf(conf.Tr.Value("config.err.configsyntax", l))
} }
key := strings.ToLower(kv[0]) key := strings.ToLower(kv[0])
ln := strconv.Itoa(lineNo)
switch key { switch key {
default: default:
conf.ExtensionVars[key] = kv[1] conf.ExtensionVars[key] = kv[1]
case "configdir": case "configdir", "logdir", "logfile":
log.Printf("configdir is deprecated. Ignored configdir=%s", kv[1]) log.Printf(conf.Tr.Value("config.err.deprecation", ln, key, kv[1]))
case "logdir":
log.Printf("logdir is deprecated. Ignored logdir=%s", kv[1])
case "logfile":
log.Printf("logfile is deprecated. Ignored logfile=%s", kv[1])
case graphhorizontalscale: case graphhorizontalscale:
iv, err := strconv.Atoi(kv[1]) iv, err := strconv.Atoi(kv[1])
if err != nil { if err != nil {
@ -118,31 +120,31 @@ func load(in io.Reader, conf *Config) error {
case helpvisible: case helpvisible:
bv, err := strconv.ParseBool(kv[1]) bv, err := strconv.ParseBool(kv[1])
if err != nil { if err != nil {
return fmt.Errorf("line %d: %v", lineNo, err) return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.HelpVisible = bv conf.HelpVisible = bv
case colorscheme: case colorscheme:
cs, err := colorschemes.FromName(conf.ConfigDir, kv[1]) cs, err := colorschemes.FromName(conf.ConfigDir, kv[1])
if err != nil { if err != nil {
return fmt.Errorf("line %d: %v", lineNo, err) return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.Colorscheme = cs conf.Colorscheme = cs
case updateinterval: case updateinterval:
iv, err := strconv.Atoi(kv[1]) iv, err := strconv.Atoi(kv[1])
if err != nil { if err != nil {
return err return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.UpdateInterval = time.Duration(iv) conf.UpdateInterval = time.Duration(iv)
case averagecpu: case averagecpu:
bv, err := strconv.ParseBool(kv[1]) bv, err := strconv.ParseBool(kv[1])
if err != nil { if err != nil {
return fmt.Errorf("line %d: %v", lineNo, err) return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.AverageLoad = bv conf.AverageLoad = bv
case percpuload: case percpuload:
bv, err := strconv.ParseBool(kv[1]) bv, err := strconv.ParseBool(kv[1])
if err != nil { if err != nil {
return fmt.Errorf("line %d: %v", lineNo, err) return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.PercpuLoad = bv conf.PercpuLoad = bv
case tempscale: case tempscale:
@ -153,12 +155,12 @@ func load(in io.Reader, conf *Config) error {
conf.TempScale = 'F' conf.TempScale = 'F'
default: default:
conf.TempScale = 'C' conf.TempScale = 'C'
return fmt.Errorf("invalid TempScale value %s", kv[1]) return fmt.Errorf(conf.Tr.Value("config.err.tempscale", kv[1]))
} }
case statusbar: case statusbar:
bv, err := strconv.ParseBool(kv[1]) bv, err := strconv.ParseBool(kv[1])
if err != nil { if err != nil {
return fmt.Errorf("line %d: %v", lineNo, err) return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.Statusbar = bv conf.Statusbar = bv
case netinterface: case netinterface:
@ -168,7 +170,7 @@ func load(in io.Reader, conf *Config) error {
case maxlogsize: case maxlogsize:
iv, err := strconv.Atoi(kv[1]) iv, err := strconv.Atoi(kv[1])
if err != nil { if err != nil {
return err return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
} }
conf.MaxLogSize = int64(iv) conf.MaxLogSize = int64(iv)
case export: case export:

View File

@ -2,6 +2,7 @@ package devices
import ( import (
"log" "log"
"github.com/jdkeke142/lingo-toml"
) )
const ( const (
@ -15,6 +16,7 @@ var _shutdownFuncs []func() error
var _devs map[string][]string var _devs map[string][]string
var _defaults map[string][]string var _defaults map[string][]string
var _startup []func(map[string]string) error var _startup []func(map[string]string) error
var tr lingo.Translations
// RegisterShutdown stores a function to be called by gotop on exit, allowing // RegisterShutdown stores a function to be called by gotop on exit, allowing
// extensions to properly release resources. Extensions should register a // extensions to properly release resources. Extensions should register a
@ -86,3 +88,7 @@ func Devices(domain string, all bool) []string {
} }
return _defaults[domain] return _defaults[domain]
} }
func SetTr(tra lingo.Translations) {
tr = tra
}

View File

@ -1,8 +1,7 @@
// Code generated by go-bindata. // Code generated by go-bindata. (@generated) DO NOT EDIT.
// sources:
// data/smc.tsv
// DO NOT EDIT!
//Package devices generated by go-bindata.// sources:
// devices/data/smc.tsv
package devices package devices
import ( import (
@ -20,7 +19,7 @@ import (
func bindataRead(data []byte, name string) ([]byte, error) { func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data)) gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil { if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err) return nil, fmt.Errorf("read %q: %v", name, err)
} }
var buf bytes.Buffer var buf bytes.Buffer
@ -28,7 +27,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
clErr := gz.Close() clErr := gz.Close()
if err != nil { if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err) return nil, fmt.Errorf("read %q: %v", name, err)
} }
if clErr != nil { if clErr != nil {
return nil, err return nil, err
@ -49,21 +48,32 @@ type bindataFileInfo struct {
modTime time.Time modTime time.Time
} }
// Name return file name
func (fi bindataFileInfo) Name() string { func (fi bindataFileInfo) Name() string {
return fi.name return fi.name
} }
// Size return file size
func (fi bindataFileInfo) Size() int64 { func (fi bindataFileInfo) Size() int64 {
return fi.size return fi.size
} }
// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode { func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode return fi.mode
} }
// ModTime return file modify time
func (fi bindataFileInfo) ModTime() time.Time { func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime return fi.modTime
} }
// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool { func (fi bindataFileInfo) IsDir() bool {
return false return fi.mode&os.ModeDir != 0
} }
// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} { func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
@ -83,7 +93,7 @@ func smcTsv() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "smc.tsv", size: 3398, mode: os.FileMode(420), modTime: time.Unix(1591711965, 0)} info := bindataFileInfo{name: "smc.tsv", size: 3398, mode: os.FileMode(420), modTime: time.Unix(1592515411, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -182,6 +192,7 @@ type bintree struct {
Func func() (*asset, error) Func func() (*asset, error)
Children map[string]*bintree Children map[string]*bintree
} }
var _bintree = &bintree{nil, map[string]*bintree{ var _bintree = &bintree{nil, map[string]*bintree{
"smc.tsv": &bintree{smcTsv, map[string]*bintree{}}, "smc.tsv": &bintree{smcTsv, map[string]*bintree{}},
}} }}
@ -232,4 +243,3 @@ func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1) cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
} }

View File

@ -1,7 +1,5 @@
package devices package devices
//go:generate go-bindata -pkg devices -prefix data -o smc.go data
import ( import (
"log" "log"
) )
@ -19,7 +17,7 @@ func UpdateTemps(temps map[string]int) {
errs := f(temps) errs := f(temps)
if errs != nil { if errs != nil {
for k, e := range errs { for k, e := range errs {
log.Printf("error updating temp for %s: %s", k, e) log.Printf(tr.Value("error.recovfetch", "temp", k, e.Error()))
} }
} }
} }

View File

@ -13,7 +13,7 @@ import (
func init() { func init() {
if len(devs()) == 0 { if len(devs()) == 0 {
log.Println("temp: no thermal sensors found") log.Println(tr.Value("error.nodevfound", "thermal sensors"))
return return
} }
RegisterTemp(update) RegisterTemp(update)
@ -58,13 +58,13 @@ func devs() []string {
// Check that thermal sensors are really available; they aren't in VMs // Check that thermal sensors are really available; they aren't in VMs
bs, err := exec.Command("sysctl", "-a").Output() bs, err := exec.Command("sysctl", "-a").Output()
if err != nil { if err != nil {
log.Printf("temp: failure to get system information %s", err.Error()) log.Printf(tr.Value("error.fatalfetch", "temp", err.Error()))
return []string{} return []string{}
} }
for k, _ := range sensorOIDS { for k, _ := range sensorOIDS {
idx := strings.Index(string(bs), k) idx := strings.Index(string(bs), k)
if idx < 0 { if idx < 0 {
log.Printf("temp: no device %s found", k) log.Printf(tr.Value("error.nodevfound", k))
} else { } else {
rv = append(rv, k) rv = append(rv, k)
} }

2
go.mod
View File

@ -4,9 +4,11 @@ require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/VictoriaMetrics/metrics v1.11.2 github.com/VictoriaMetrics/metrics v1.11.2
github.com/VividCortex/ewma v1.1.1 github.com/VividCortex/ewma v1.1.1
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/distatus/battery v0.9.0 github.com/distatus/battery v0.9.0
github.com/gizak/termui/v3 v3.1.0 github.com/gizak/termui/v3 v3.1.0
github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-ole/go-ole v1.2.4 // indirect
github.com/jdkeke142/lingo-toml v0.0.0-20200705162942-3520fe0dec06
github.com/mattn/go-runewidth v0.0.4 github.com/mattn/go-runewidth v0.0.4
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0

24
go.sum
View File

@ -1,3 +1,5 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/metrics v1.11.2 h1:t/ceLP6SvagUqypCKU7cI7+tQn54+TIV/tGoxihHvx8= github.com/VictoriaMetrics/metrics v1.11.2 h1:t/ceLP6SvagUqypCKU7cI7+tQn54+TIV/tGoxihHvx8=
@ -6,21 +8,21 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM= github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd h1:XtfPmj9tQRilnrEmI1HjQhxXWRhEM+m8CACtaMJE/kM=
github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4= github.com/cjbassi/drawille-go v0.0.0-20190126131713-27dc511fe6fd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:Yg2hDs4b13Evkpj42FU2idX2cVXVFqQSheXYKM86Qsk=
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:MgJyK38wkzZbiZSKeIeFankxxSA8gayko/nr5x5bgBA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distatus/battery v0.9.0 h1:8NS5o00/j3Oh2xgocA6pQROTp5guoR+s8CZlWzHC4QM= github.com/distatus/battery v0.9.0 h1:8NS5o00/j3Oh2xgocA6pQROTp5guoR+s8CZlWzHC4QM=
github.com/distatus/battery v0.9.0/go.mod h1:gGO7GxHTi1zlRT+cAj8uGG0/8HFiqAeH0TJvoipnuPs= github.com/distatus/battery v0.9.0/go.mod h1:gGO7GxHTi1zlRT+cAj8uGG0/8HFiqAeH0TJvoipnuPs=
github.com/gizak/termui/v3 v3.0.0 h1:NYTUG6ig/sJK05O5FyhWemwlVPO8ilNpvS/PgRtrKAE=
github.com/gizak/termui/v3 v3.0.0/go.mod h1:uinu2dMdtMI+FTIdEFUJQT5y+KShnhQRshvPblXq3lY=
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc= github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY= github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/jdkeke142/lingo-toml v0.0.0-20200508150411-0ad65ccb786d/go.mod h1:Kji1cjs9t6xDSLWuz6vP6ockOTT1rIdMLnNryfHXVuw=
github.com/jdkeke142/lingo-toml v0.0.0-20200705162942-3520fe0dec06 h1:Mzc7Br3CA20ztYUP68pghZgaVTkumWNzgmEAo8etfbE=
github.com/jdkeke142/lingo-toml v0.0.0-20200705162942-3520fe0dec06/go.mod h1:Kji1cjs9t6xDSLWuz6vP6ockOTT1rIdMLnNryfHXVuw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
@ -30,32 +32,28 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag= github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag=
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y= github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ= github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg= github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg=
github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto= github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto=
github.com/xxxserxxx/lingo v0.0.0-20200619152803-440e0ec753ec h1:72Kid6wRHf4IZq0oJyJZGo4FuIyJeKqN4n9MEh2l744=
github.com/xxxserxxx/lingo v0.0.0-20200619152803-440e0ec753ec/go.mod h1:Kji1cjs9t6xDSLWuz6vP6ockOTT1rIdMLnNryfHXVuw=
github.com/xxxserxxx/opflag v1.0.5 h1:2H4Qtl1qe+dSkEcGt+fBe2mQ8z14MgkWPqcLaoa6k90= github.com/xxxserxxx/opflag v1.0.5 h1:2H4Qtl1qe+dSkEcGt+fBe2mQ8z14MgkWPqcLaoa6k90=
github.com/xxxserxxx/opflag v1.0.5/go.mod h1:GWZtb3/tGGj5W1GE/JTyJAuqgxDxl1+jqDGAGM+P/p4= github.com/xxxserxxx/opflag v1.0.5/go.mod h1:GWZtb3/tGGj5W1GE/JTyJAuqgxDxl1+jqDGAGM+P/p4=
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
golang.org/x/sys v0.0.0-20200316230553-a7d97aace0b0 h1:4Khi5GeNOkZS5DqSBRn4Sy7BE6GuxwOqARPqfurkdNk= golang.org/x/sys v0.0.0-20200316230553-a7d97aace0b0 h1:4Khi5GeNOkZS5DqSBRn4Sy7BE6GuxwOqARPqfurkdNk=
golang.org/x/sys v0.0.0-20200316230553-a7d97aace0b0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200316230553-a7d97aace0b0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 h1:OKbAoGs4fGM5cPLlVQLZGYkFC8OnOfgo6tt0Smf9XhM= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc= howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

View File

@ -3,7 +3,9 @@ package layout
import ( import (
"log" "log"
"sort" "sort"
"strings"
"github.com/jdkeke142/lingo-toml"
"github.com/xxxserxxx/gotop/v4" "github.com/xxxserxxx/gotop/v4"
"github.com/xxxserxxx/gotop/v4/widgets" "github.com/xxxserxxx/gotop/v4/widgets"
@ -28,8 +30,10 @@ type MyGrid struct {
} }
var widgetNames []string = []string{"cpu", "disk", "mem", "temp", "net", "procs", "batt"} var widgetNames []string = []string{"cpu", "disk", "mem", "temp", "net", "procs", "batt"}
var tr lingo.Translations
func Layout(wl layout, c gotop.Config) (*MyGrid, error) { func Layout(wl layout, c gotop.Config) (*MyGrid, error) {
tr = c.Tr
rowDefs := wl.Rows rowDefs := wl.Rows
uiRows := make([][]interface{}, 0) uiRows := make([][]interface{}, 0)
numRows := countNumRows(wl.Rows) numRows := countNumRows(wl.Rows)
@ -197,7 +201,7 @@ func makeWidget(c gotop.Config, widRule widgetRule) interface{} {
b.BarColor = ui.Color(c.Colorscheme.ProcCursor) b.BarColor = ui.Color(c.Colorscheme.ProcCursor)
w = b w = b
default: default:
log.Printf("Invalid widget name %s. Must be one of %v", widRule.Widget, widgetNames) log.Printf(tr.Value("layout.error.widget", widRule.Widget, strings.Join(widgetNames, ",")))
return ui.NewBlock() return ui.NewBlock()
} }
if c.ExportPort != "" { if c.ExportPort != "" {

View File

@ -83,7 +83,8 @@ func ParseLayout(i io.Reader) layout {
if len(rs) > 1 { if len(rs) > 1 {
v, e := strconv.Atoi(rs[0]) v, e := strconv.Atoi(rs[0])
if e != nil { if e != nil {
log.Printf("Layout error on line %d: format must be INT:STRING/INT. Error parsing %s as a int. Word was %s. Using a row height of 1.", lineNo, rs[0], w) ln := strconv.Itoa(lineNo)
log.Printf(tr.Value("layout.error.format", "INT:STRING/INT", ln, rs[0], w))
v = 1 v = 1
} }
if v < 1 { if v < 1 {
@ -99,7 +100,8 @@ func ParseLayout(i io.Reader) layout {
if len(ks) > 1 { if len(ks) > 1 {
weight, e := strconv.Atoi(ks[1]) weight, e := strconv.Atoi(ks[1])
if e != nil { if e != nil {
log.Printf("Layout error on line %d: format must be STRING/INT. Error parsing %s as a int. Word was %s. Using a weight of 1 for widget.", lineNo, ks[1], w) ln := strconv.Itoa(lineNo)
log.Printf(tr.Value("layout.error.format", "STRING/INT", ln, ks[1], w))
weight = 1 weight = 1
} }
if weight < 1 { if weight < 1 {
@ -107,7 +109,8 @@ func ParseLayout(i io.Reader) layout {
} }
wr.Weight = float64(weight) wr.Weight = float64(weight)
if len(ks) > 2 { if len(ks) > 2 {
log.Printf("Layout warning on line %d: too many '/' in word %s; ignoring extra junk.", lineNo, w) ln := strconv.Itoa(lineNo)
log.Printf(tr.Value("layout.error.slashes", ln, w))
} }
weightTotal += weight weightTotal += weight
} else { } else {

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/jdkeke142/lingo-toml"
"github.com/xxxserxxx/gotop/v4" "github.com/xxxserxxx/gotop/v4"
) )
@ -29,6 +30,7 @@ func New(c gotop.Config) (io.WriteCloser, error) {
w := &RotateWriter{ w := &RotateWriter{
filename: filepath.Join(cache.Path, LOGFILE), filename: filepath.Join(cache.Path, LOGFILE),
maxLogSize: c.MaxLogSize, maxLogSize: c.MaxLogSize,
tr: c.Tr,
} }
err = w.rotate() err = w.rotate()
if err != nil { if err != nil {
@ -48,6 +50,7 @@ type RotateWriter struct {
filename string // should be set to the actual filename filename string // should be set to the actual filename
fp *os.File fp *os.File
maxLogSize int64 maxLogSize int64
tr lingo.Translations
} }
func (w *RotateWriter) Close() error { func (w *RotateWriter) Close() error {
@ -103,7 +106,7 @@ func (w *RotateWriter) rotate() (err error) {
// open the log file // open the log file
w.fp, err = os.OpenFile(w.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0660) w.fp, err = os.OpenFile(w.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0660)
if err != nil { if err != nil {
return fmt.Errorf("failed to open log file %s: %v", w.filename, err) return fmt.Errorf(w.tr.Value("error.logopen", w.filename, err.Error()))
} }
return nil return nil

View File

@ -5,8 +5,10 @@ import (
"image" "image"
"log" "log"
"strings" "strings"
"strconv"
. "github.com/gizak/termui/v3" . "github.com/gizak/termui/v3"
"github.com/jdkeke142/lingo-toml"
) )
type Table struct { type Table struct {
@ -30,6 +32,8 @@ type Table struct {
TopRow int // used to indicate where in the table we are scrolled at TopRow int // used to indicate where in the table we are scrolled at
ColResizer func() ColResizer func()
Tr lingo.Translations
} }
// NewTable returns a new Table instance // NewTable returns a new Table instance
@ -79,7 +83,8 @@ func (self *Table) Draw(buf *Buffer) {
} }
if self.TopRow < 0 { if self.TopRow < 0 {
log.Printf("table widget TopRow value less than 0. TopRow: %v", self.TopRow) r := strconv.Itoa(self.TopRow)
log.Printf(self.Tr.Value("error.table", r))
return return
} }

404
translations/dicts.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,180 @@
configfile="Config file"
usage="Usage: {0} [options]\n\nOptions:\n"
total="Total"
[help]
paths="Loadable colorschemes & layouts, and the config file, are searched for, in order:"
log="The log file is in {0}"
written="Config written to {0}"
help="""
Quit: q or <C-c>
Process navigation:
- k and <Up>: up
- j and <Down>: down
- <C-u>: half page up
- <C-d>: half page down
- <C-b>: full page up
- <C-f>: full page down
- gg and <Home>: jump to top
- G and <End>: jump to bottom
Process actions:
- <Tab>: toggle process grouping
- dd: kill selected process or group of processes with SIGTERM (15)
- d3: kill selected process or group of processes with SIGQUIT (3)
- d9: kill selected process or group of processes with SIGKILL (9)
Process sorting:
- c: CPU
- m: Mem
- p: PID
Process filtering:
- /: start editing filter
- (while editing):
- <Enter>: accept filter
- <C-c> and <Escape>: clear filter
CPU and Mem graph scaling:
- h: scale in
- l: scale out
Network:
- b: toggle between mbps and scaled bytes per second
"""
# TRANSLATORS: Please don't translate the layout **names**
layouts = """Built-in layouts:
default
minimal
battery
kitchensink"""
# TRANSLATORS: Please don't translate the colorcheme **names**
colorschemes = """Built-in colorschemes:
default
default-dark (for white background)
solarized
solarized16-dark
solarized16-light
monokai
vice"""
# TRANSLATORS: Please don't translate the widget **names**
widgets = """Widgets that can be used in layouts:
cpu - CPU load graph
mem - Physical & swap memory use graph
temp - Sensor temperatures
disk - Physical disk partition use
power - A battery bar
net - Network load
procs - Interactive process list"""
[args]
help="Hilfetext anzeigen."
color="Ein Farbschema feststellen."
scale="Stellen den Skalierungsfaktor ein, >0"
version="Zeigen die Version aus und beenden."
percpu="Show each CPU in the CPU widget."
cpuavg="Show average CPU in the CPU widget."
temp="Show temperatures in fahrenheit.Show temperatures in fahrenheit."
statusbar="Show a statusbar with the time."
rate="Refresh frequency. Most time units accepted. \"1m\" = refresh every minute. \"100ms\" = refresh every 100ms."
layout="Name of layout spec file for the UI. Use \"-\" to pipe."
net="Select network interface. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using \"!\""
export="Enable metrics for export on the specified port."
mbps="Show network rate as mbps."
test="Runs tests and exits with success/failure code."
conffile="Config file to use instead of default (MUST BE FIRST ARGUMENT)"
list="""
List <devices|layouts|colorschemes|paths|keys>
devices: Prints out device names for filterable widgets
layouts: Lists build-in layouts
colorschemes: Lists built-in colorschemes
paths: List out configuration file search paths
widgets: Widgets that can be used in a layout
keys: Show the keyboard bindings."""
write="Write out a default config file."
[config.err]
configsyntax="0| bad config file syntax; should be KEY=VALUE, was {0}"
deprecation="1| line {0}: '{1}' is deprecated. Ignored {1}={2}"
line="2| line #{0}: {1}"
tempscale="3| invalid TempScale value {0}"
[error]
configparse="4| failed to parse config file: {0}"
cliparse="5| parsing CLI args: {0}"
logsetup="6| failed to setup log file: {0}"
unknownopt="7| Unknown option \"{0}\"; try layouts, colorschemes, keys, paths, or devices\n"
writefail="8| Failed to write configuration file: {0}"
checklog="9| errors encountered; from {0}:"
metricsetup="10| error setting up {0} metrics: {1}"
nometrics="11| no metrics for {0} {1}"
fatalfetch="12| fatal error fetching {0} info: {1}"
recovfetch="13| recoverable error fetching {0} info; skipping {0}: {1}"
nodevfound="14| no usable {0} found"
setuperr="15| error setting up {0}: {1}"
colorschemefile="16| failed to find colorscheme file {0} in {1}"
colorschemeread="17| failed to read colorscheme file {0}: {1}"
colorschemeparse="18| failed to parse colorscheme file: {0}"
findlayout="19| failed to read colorscheme file {0}: {1}"
logopen="20| failed to open log file {0}: {1}"
table="21| table widget TopRow value less than 0. TopRow: {0}"
nohostname="22| could not get hostname: {0}"
[layout.error]
widget="23| Invalid widget name {0}. Must be one of {1}"
format="24| Layout error on line {0}: format must be {1}. Error parsing {2} as a int. Word was {3}. Using a row height of 1."
slashes="25| Layout warning on line {0}: too many '/' in word {1}; ignoring extra junk."
[widget.label]
disk=" Disk Usage "
cpu=" CPU Usage "
gauge=" Power Level "
battery=" Battery Status "
batt=" Battery "
temp=" Temperatures "
net=" Network Usage "
netint=" Network Usage: {0} "
mem=" Memory Usage "
[widget.net.err]
netactivity="26| failed to get network activity from gopsutil: {0}"
negvalrecv="27| error: negative value for recently received network data from gopsutil. recentBytesRecv: {0}"
negvalsent="28| error: negative value for recently sent network data from gopsutil. recentBytesSent: {0}"
[widget.disk]
disk="Disk"
mount="Mount"
used="Used"
free="Free"
rs="R/s"
ws="W/s"
[widget.proc]
filter=" Filter: "
label=" Processes "
[widget.proc.header]
count="Count"
command="Command"
cpu="CPU%"
mem="Mem%"
pid="PID"
[widget.proc.err]
count="29| failed to get CPU count from gopsutil: {0}"
retrieve="30| failed to retrieve processes: {0}"
ps="31| failed to execute 'ps' command: {0}"
gopsutil="32| failed to get processes from gopsutil: {0}"
pidconv="33| failed to convert PID to int: {0}. line: {1}"
cpuconv="34| failed to convert CPU usage to float: {0}. line: {1}"
memconv="35| failed to convert Mem usage to float: {0}. line: {1}"
getcmd="36| failed to get process command from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
cpupercent="37| failed to get process cpu usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
mempercent="38| failed to get process memeory usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
parse="39| failed to parse output: {0}"

View File

@ -0,0 +1,180 @@
configfile="Config file"
usage="Usage: {0} [options]\n\nOptions:\n"
total="Total"
[help]
paths="Loadable colorschemes & layouts, and the config file, are searched for, in order:"
log="The log file is in {0}"
written="Config written to {0}"
help="""
Quit: q or <C-c>
Process navigation:
- k and <Up>: up
- j and <Down>: down
- <C-u>: half page up
- <C-d>: half page down
- <C-b>: full page up
- <C-f>: full page down
- gg and <Home>: jump to top
- G and <End>: jump to bottom
Process actions:
- <Tab>: toggle process grouping
- dd: kill selected process or group of processes with SIGTERM (15)
- d3: kill selected process or group of processes with SIGQUIT (3)
- d9: kill selected process or group of processes with SIGKILL (9)
Process sorting:
- c: CPU
- m: Mem
- p: PID
Process filtering:
- /: start editing filter
- (while editing):
- <Enter>: accept filter
- <C-c> and <Escape>: clear filter
CPU and Mem graph scaling:
- h: scale in
- l: scale out
Network:
- b: toggle between mbps and scaled bytes per second
"""
# TRANSLATORS: Please don't translate the layout **names**
layouts = """Built-in layouts:
default
minimal
battery
kitchensink"""
# TRANSLATORS: Please don't translate the colorcheme **names**
colorschemes = """Built-in colorschemes:
default
default-dark (for white background)
solarized
solarized16-dark
solarized16-light
monokai
vice"""
# TRANSLATORS: Please don't translate the widget **names**
widgets = """Widgets that can be used in layouts:
cpu - CPU load graph
mem - Physical & swap memory use graph
temp - Sensor temperatures
disk - Physical disk partition use
power - A battery bar
net - Network load
procs - Interactive process list"""
[args]
help="Show this screen."
color="Set a colorscheme."
scale="Graph scale factor, >0"
version="Print version and exit."
percpu="Show each CPU in the CPU widget."
cpuavg="Show average CPU in the CPU widget."
temp="Show temperatures in fahrenheit.Show temperatures in fahrenheit."
statusbar="Show a statusbar with the time."
rate="Refresh frequency. Most time units accepted. \"1m\" = refresh every minute. \"100ms\" = refresh every 100ms."
layout="Name of layout spec file for the UI. Use \"-\" to pipe."
net="Select network interface. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using \"!\""
export="Enable metrics for export on the specified port."
mbps="Show network rate as mbps."
test="Runs tests and exits with success/failure code."
conffile="Config file to use instead of default (MUST BE FIRST ARGUMENT)"
list="""
List <devices|layouts|colorschemes|paths|keys>
devices: Prints out device names for filterable widgets
layouts: Lists build-in layouts
colorschemes: Lists built-in colorschemes
paths: List out configuration file search paths
widgets: Widgets that can be used in a layout
keys: Show the keyboard bindings."""
write="Write out a default config file."
[config.err]
configsyntax="0| bad config file syntax; should be KEY=VALUE, was {0}"
deprecation="1| line {0}: '{1}' is deprecated. Ignored {1}={2}"
line="2| line #{0}: {1}"
tempscale="3| invalid TempScale value {0}"
[error]
configparse="4| failed to parse config file: {0}"
cliparse="5| parsing CLI args: {0}"
logsetup="6| failed to setup log file: {0}"
unknownopt="7| Unknown option \"{0}\"; try layouts, colorschemes, keys, paths, or devices\n"
writefail="8| Failed to write configuration file: {0}"
checklog="9| errors encountered; from {0}:"
metricsetup="10| error setting up {0} metrics: {1}"
nometrics="11| no metrics for {0} {1}"
fatalfetch="12| fatal error fetching {0} info: {1}"
recovfetch="13| recoverable error fetching {0} info; skipping {0}: {1}"
nodevfound="14| no usable {0} found"
setuperr="15| error setting up {0}: {1}"
colorschemefile="16| failed to find colorscheme file {0} in {1}"
colorschemeread="17| failed to read colorscheme file {0}: {1}"
colorschemeparse="18| failed to parse colorscheme file: {0}"
findlayout="19| failed to read colorscheme file {0}: {1}"
logopen="20| failed to open log file {0}: {1}"
table="21| table widget TopRow value less than 0. TopRow: {0}"
nohostname="22| could not get hostname: {0}"
[layout.error]
widget="23| Invalid widget name {0}. Must be one of {1}"
format="24| Layout error on line {0}: format must be {1}. Error parsing {2} as a int. Word was {3}. Using a row height of 1."
slashes="25| Layout warning on line {0}: too many '/' in word {1}; ignoring extra junk."
[widget.label]
disk=" Disk Usage "
cpu=" CPU Usage "
gauge=" Power Level "
battery=" Battery Status "
batt=" Battery "
temp=" Temperatures "
net=" Network Usage "
netint=" Network Usage: {0} "
mem=" Memory Usage "
[widget.net.err]
netactivity="26| failed to get network activity from gopsutil: {0}"
negvalrecv="27| error: negative value for recently received network data from gopsutil. recentBytesRecv: {0}"
negvalsent="28| error: negative value for recently sent network data from gopsutil. recentBytesSent: {0}"
[widget.disk]
disk="Disk"
mount="Mount"
used="Used"
free="Free"
rs="R/s"
ws="W/s"
[widget.proc]
filter=" Filter: "
label=" Processes "
[widget.proc.header]
count="Count"
command="Command"
cpu="CPU%"
mem="Mem%"
pid="PID"
[widget.proc.err]
count="29| failed to get CPU count from gopsutil: {0}"
retrieve="30| failed to retrieve processes: {0}"
ps="31| failed to execute 'ps' command: {0}"
gopsutil="32| failed to get processes from gopsutil: {0}"
pidconv="33| failed to convert PID to int: {0}. line: {1}"
cpuconv="34| failed to convert CPU usage to float: {0}. line: {1}"
memconv="35| failed to convert Mem usage to float: {0}. line: {1}"
getcmd="36| failed to get process command from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
cpupercent="37| failed to get process cpu usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
mempercent="38| failed to get process memeory usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
parse="39| failed to parse output: {0}"

182
translations/dicts/eo.toml Normal file
View File

@ -0,0 +1,182 @@
configfile="Argododosiero"
usage="Uzado: {0} [ebloj]\n\nEbloj:\n"
total="Sumo"
[help]
paths="Ŝarĝebla kloraj skemoj & enpaĝigoj, kaj la argododosiero, estas orda serĉatigis:"
log="Logodosiero troviĝas ĉe {0}"
written="Argordo skribiĝis ĉe {0}"
help="""
Eliri: q aŭ <C-c>
Proceza navigadoj:
- k kaj <Supren>: supren
- j kaj <Malsupren>: malsupren
- <C-u>: duona paĝo supren
- <C-d>: duona paĝo malsupren
- <C-b>: plena paĝo supren
- <C-f>: plena paĝo malsupren
- gg kaj <Hejmo>: salti al supron
- G kaj <Fino>: salti al malsupron
Proceza agoj:
- <Langeto>: alterni procezon grupigi
- dd: fini la elektitajn procezojn aŭ procezon grupigon kun SIGTERM (15)
- d3: fini la elektitajn procezojn aŭ procezon grupigon kun SIGQUIT (3)
- d9: fini la elektitajn procezojn aŭ procezon grupigon kun SIGKILL (9)
Proceza ordigoj:
- c: CPU
- m: Memoro
- p: PID
Proceza filtradoj:
- /: komenci redakti filtrilon
- (dum redaktadi):
- <Eniri>: akcepti filtrilon
- <C-c> kaj <Eskapi>: eliri filtrilon
CPU kaj Memora grafilo skali:
- h: zomi
- l: malzomi
Reto:
- b: alterni inter mbps kaj skale bajtoj por dua
"""
# TRANSLATORS: Please don't translate the layout **names**
layouts = """Enkonstruitaj enpaĝigoj:
default
minimal
battery
kitchensink"""
# TRANSLATORS: Please don't translate the colorscheme **names**
colorschemes = """Enkonstruitaj kloraj skemoj:
default
default-dark (por blanka fono)
solarized
solarized16-dark
solarized16-light
monokai
vice"""
# TRANSLATORS: Please don't translate the widget **names**
widgets = """Enpaĝigaj Fenestraĵoj:
cpu - CPU ŝarĝa grafilo
mem - Fizika kay interŝanĝa memora grafilo
temp - Temperatura sensiloj
disk - Fizikaj diskdispartigaj uzadilo
power - Bateria mezurilo
net - Retuzadilo
procs - Interaga proceza listo"""
[args]
help="Ĉi tiun informoj."
color="Agordi kloraj skemoj."
scale="Agordi grafilan skalon, >0"
version="Montri version kaj eliri."
percpu="Montri ĉiun CPU en la CPU-fenestraĵo."
cpuavg="Montri duonan CPU en la CPU-fenestraĵo."
temp="Montri temperaturojn en fahrenheit."
statusbar="Montri statusbarbaron kun la tempo."
rate="Refreŝiga ofteco. Plej multaj unuoj akceptitaj. \"1m\" = refreŝigi ĉiun minuton. \"100ms\" = refreŝigi ĉiun dekonon minuton."
layout="Nomo de aranĝa specifa dosiero por la UI. Uzu \"-\" por pipi."
net="Elekti retinterfacon. Multaj interfacoj povas esti difinitaj per komparaj valoroj. Interfacoj ankaŭ povas esti ignorataj per \"!\""
export="Ebligu metrikojn por eksportado en la specifita haveno."
mbps="Montri reta takson kiel mbps."
test="Ekzekutas testojn kaj forirojn kun sukceso / fiaska kodo."
conffile="Agordi dosiero por uzi anstataŭ defaŭlte (DEVAS ESTI UNUA ARGUMENTO)"
# TRANSLATORS: Please don't translate the list entries
list="""
List <devices|layouts|colorschemes|paths|keys>
devices: Montras nomojn de aparatoj por filteblaj fenestraĵoj
layouts: Listigas enkonstruajn aranĝojn
colorschemes: Listas enkonstruitajn kloraj skemoj
paths: Enlistigu agordajn serĉajn vojojn de agordo
widgets: Fenestraĵoj uzeblaj en aranĝo
keys: Montri la klavarajn ligojn."""
write="Skribu defaŭltan agordan dosieron."
[config.err]
configsyntax="0| malbona agordo dosiero-sintakso; estu ŜLOSI=VALORO, estis {0}"
deprecation="1| linio {0}: '{1}' malakceptas. Ignorita {1}={2}"
line="2| linio #{0}: {1}"
tempscale="3| malvalida TempScale-valoro {0}"
[error]
configparse="4| malsukcesis pari agordi dosiero: {0}"
cliparse="5| analizante CLI-argumentojn: {0}"
logsetup="6| malsukcesis agordi registro dosiero: {0}"
unknownopt="7| Nekonata opcio \"{0}\"; provu layouts, colorschemes, keys, paths, aŭ devices"
writefail="8| Malsukcesis skribi agordan dosieron: {0}"
checklog="9| eraroj renkontitaj; de {0}:"
metricsetup="10| eraro agordante {0} metrikojn: {1}"
nometrics="11| neniuj metrikoj por {0} {1}"
fatalfetch="12| fatala eraro elprenanta {0} info: {1}"
recovfetch="13| reakirebla eraro elprenanta {0} info; saltante {0}: {1}"
nodevfound="14| neniu uzebla {0} trovita"
setuperr="15| eraro agordante {0}: {1}"
colorschemefile="16| malsukcesis trovi kloraj skemoj dosiero {0} en {1}"
colorschemeread="17| malsukcesis legi kloraj skemoj dosiero {0}: {1}"
colorschemeparse="18| Fiaskis analizi kloraj skemoj dosiero: {0}"
findlayout="19| malsukcesis legi kloraj skemoj dosiero {0}: {1}"
logopen="20| malsukcesis malfermi enskribi dosieron {0}: {1}"
table="21| Tabla fenestraĵo TopRow-valoro malpli ol 0. TopRow: {0}"
nohostname="22| Ne povis akiri hostname: {0}"
[layout.error]
widget="23| Malvalida fenestra nomo {0}. Devas esti unu el {1}"
format="24| Eraro pri aranĝo sur linio {0}: formato devas esti {1}. Eraro analizante {2} kiel int. Vorto estis {3}. Uzante vicon alteco de 1."
slashes="25| Averto pri aranĝo sur linio {0}: tro multaj '/' en vorto {1}; ignorante kroman rubon."
[widget.label]
disk=" Disk Usado "
cpu=" CPU Usado "
gauge=" Potencnivelo "
battery=" Bateria Statuso "
batt=" Baterio "
temp=" Temperaturoj "
net=" Reta Usado "
netint=" Reta Usado: {0} "
mem=" Memoro Usado "
[widget.net.err]
netactivity="26| malsukcesis ricevi retactiveco de gopsutil: {0}"
negvalrecv="27| eraro: negativa valoro por ĵus ricevitaj retdatumoj de gopsutil. RecentBytesRecv: {0}"
negvalsent="28| eraro: negativa valoro por ĵus senditaj retdatumoj de gopsutil. RecentBytesSent: {0}"
[widget.disk]
disk="Disko"
mount="Monto"
used="Uzita"
free="Senpaga"
rs="R/s"
ws="W/s"
[widget.proc]
filter=" Filtrilo: "
label=" Procezoj "
[widget.proc.header]
count="Kalkulo"
command="Komando"
cpu="CPU%"
mem="Mem%"
pid="PID"
[widget.proc.err]
count="29| malsukcesis akiri CPU-kalkuladon de gopsutil: {0}"
retrieve="30| ne sukcesis akiri procezojn: {0}"
ps="31| malsukcesis plenumi komandon 'ps': {0}"
gopsutil="32| malsukcesis akiri procezojn de gopsutilo: {0}"
pidconv="33| malsukcesis konverti PID al int: {0}. linio: {1}"
cpuconv="34| malsukcesis konverti CPU-uzon al flosilo: {0}. linio: {1}"
memconv="35| malsukcesis konverti Mem-uzon al flosilo: {0}. linio: {1}"
getcmd="36| malsukcesis akiri procezan komandon de gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
cpupercent="37| malsukcesis ricevi uzadon de proceso cpu de gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
mempercent="38| malsukcesis ricevi uzadon de proceza memoro de gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
parse="39| ne sukcesis analizi eliron: {0}"

View File

@ -0,0 +1,180 @@
configfile="CFG FLE"
usage="egasU: {0} [snoitpo]\n\nsnoitpO:\n"
total="latoT"
[help]
paths="redro ni ,rof dehcraes era ,elif gifnoc eht dna ,stuoyal & semehcsroloc elbadaoL:"
log="ni si elif gol ehT {0}"
written="ot nettirw gifnoC {0}"
help="""
>c-C< ro q :tiuQ
:noitagivan ssecorP
pu :>pU< dna k -
nwod :>nwoD< dna j -
pu egap flah :>u-C< -
nwod egap flah :>d-C< -
pu egap lluf :>b-C< -
nwod egap lluf :>f-C< -
pot ot pmuj :>emoH< dna gg -
mottob ot pmuj :>dnE< dna G -
:snoitca ssecorP
gnipuorg ssecorp elggot :>baT< -
)51( MRETGIS htiw sessecorp fo puorg ro ssecorp detceles llik :dd -
)3( TIUQGIS htiw sessecorp fo puorg ro ssecorp detceles llik :3d -
)9( LLIKGIS htiw sessecorp fo puorg ro ssecorp detceles llik :9d -
:gnitros ssecorP
UPC :c -
meM :m -
DIP :p -
:gniretlif ssecorP
retlif gnitide trats :/ -
:)gnitide elihw( -
retlif tpecca :>retnE< -
retlif raelc :>epacsE< dna >c-C< -
:gnilacs hparg meM dna UPC
ni elacs :h -
tuo elacs :l -
:krowteN
dnoces rep setyb delacs dna spbm neewteb elggot :b -
"""
# TRANSLATORS: Please don't translate the layout **names**
layouts = """stuoyal ni-tliuB:
tluafed
laminim
yrettab
knisnehctik"""
# TRANSLATORS: Please don't translate the colorcheme **names**
colorschemes = """semehcsroloc ni-tliuB:
tluafed
)dnuorgkcab etihw rof( krad-tluafed
deziralos
krad-61deziralos
thgil-61deziralos
iakonom
eciv"""
# TRANSLATORS: Please don't translate the widget **names**
widgets = """stuoyal ni desu eb nac taht stegdiW:
hparg daol UPC - upc
hparg esu yromem paws & lacisyhP - mem
serutarepmet rosneS - pmet
esu noititrap ksid lacisyhP - ksid
rab yrettab A - rewop
daol krowteN - ten
tsil ssecorp evitcaretnI - scorp"""
[args]
help=".neercs siht wohS"
color=".emehcsroloc a teS"
scale="0> ,rotcaf elacs hparG"
version=".tixe dna noisrev tnirP"
percpu=".tegdiw UPC eht ni UPC hcae wohS"
cpuavg=".tegdiw UPC eht ni UPC egareva wohS"
temp=".tiehnerhaf ni serutarepmet wohS.tiehnerhaf ni serutarepmet wohS"
statusbar=".emit eht htiw rabsutats a wohS"
rate=".sm001 yreve hserfer = \"sm001\" .etunim yreve hserfer = \"m1\" .detpecca stinu emit tsoM .ycneuqerf hserfeR"
layout="Name of layout spec file for the UI. Use \"-\" to pipe."
net="gnisu derongi eb osla nac secafretnI .seulav detarapes ammoc gnisu denifed eb nac secafretni lareveS .ecafretni krowten tceleS \"!\""
export=".trop deificeps eht no tropxe rof scirtem elbanE"
mbps=".spbm sa etar krowten wohS"
test=".edoc eruliaf/sseccus htiw stixe dna stset snuR"
conffile=")TNEMUGRA TSRIF EB TSUM( tluafed fo daetsni esu ot elif gifnoC"
list="""
>syek|shtap|semehcsroloc|stuoyal|secived< tsiL
stegdiw elbaretlif rof seman ecived tuo stnirP :secived
stuoyal ni-dliub stsiL :stuoyal
semehcsroloc ni-tliub stsiL :semehcsroloc
shtap hcraes elif noitarugifnoc tuo tsiL :shtap
tuoyal a ni desu eb nac taht stegdiW :stegdiw
.sgnidnib draobyek eht wohS :syek """
write=".elif gifnoc tluafed a tuo etirW"
[config.err]
configsyntax="0| saw ,EULAV=YEK eb dluohs ;xatnys elif gifnoc dab {0}"
deprecation="1| enil {0}: '{1}' derongI .detacerped si {1}={2}"
line="2| enil #{0}: {1}"
tempscale="3| eulav elacSpmeT dilavni {0}"
[error]
configparse="4| elif gifnoc esrap ot deliaf: {0}"
cliparse="8| sgra ILC gnisrap: {0}"
logsetup="9| elif gol putes ot deliaf: {0}"
unknownopt="10| noitpo nwonknU \"{0}\"; secived ro ,shtap ,syek ,semehcsroloc ,stuoyal yrt\n"
writefail="11| elif noitarugifnoc etirw ot deliaF: {0}"
checklog="12| morf ;deretnuocne srorre {0}:"
metricsetup="13| pu gnittes rorre {0} scirtem: {1}"
nometrics="14| rof scirtem on {0} {1}"
fatalfetch="15| gnihctef rorre lataf {0} ofni: {1}"
recovfetch="16| gnihctef rorre elbarevocer {0} gnippiks ;ofni {0}: {1}"
nodevfound="17| elbasu on {0} dnuof"
setuperr="18| pu gnittes rorre {0}: {1}"
colorschemefile="19| elif emehcsroloc dnif ot deliaf {0} ni {1}"
colorschemeread="20| elif emehcsroloc daer ot deliaf {0}: {1}"
colorschemeparse="21| elif emehcsroloc esrap ot deliaf: {0}"
findlayout="22| elif emehcsroloc daer ot deliaf {0}: {1}"
logopen="22| elif gol nepo ot deliaf {0}: {1}"
table="22| woRpoT .0 naht ssel eulav woRpoT tegdiw elbat: {0}"
nohostname="22| emantsoh teg ton dluoc: {0}"
[layout.error]
widget="23| eman tegdiw dilavnI {0}. fo eno eb tsuM {1}"
format="24| enil no rorre tuoyaL {0}: eb tsum tamrof {1}. gnisrap rorrE {2} saw droW .tni a sa {3}. 1 fo thgieh wor a gnisU."
slashes="25| enil no gninraw tuoyaL {0}: drow ni '/' ynam oot {1}; knuj artxe gnirongi."
[widget.label]
disk=" egasU ksiD "
cpu=" egasU UPC "
gauge=" leveL rewoP "
battery=" sutatS yrettaB "
batt=" yrettaB "
temp=" serutarepmeT "
net=" egasU krowteN "
netint=" egasU krowteN: {0} "
mem=" egasU yromeM "
[widget.net.err]
netactivity="26| lituspog morf ytivitca krowten teg ot deliaf: {0}"
negvalrecv="27| :vceRsetyBtnecer .lituspog morf atad krowten deviecer yltnecer rof eulav evitagen :rorre {0}"
negvalsent="28| :tneSsetyBtnecer .lituspog morf atad krowten tnes yltnecer rof eulav evitagen :rorre {0}"
[widget.disk]
disk="ksiD"
mount="tnuoM"
used="desU"
free="eerF"
rs="s/R"
ws="s/W"
[widget.proc]
filter=" :retliF "
label=" sessecorP "
[widget.proc.header]
count="tnuoC"
command="dnammoC"
cpu="%UPC"
mem="%meM"
pid="DIP"
[widget.proc.err]
count="29| :lituspog morf tnuoc UPC teg ot deliaf {0}"
retrieve="30| :sessecorp eveirter ot deliaf {0}"
ps="31| :dnammoc 'sp' etucexe ot deliaf {0}"
gopsutil="32| :lituspog morf sessecorp teg ot deliaf {0}"
pidconv="33| :tni ot DIP trevnoc ot deliaf {0}. enil: {1}"
cpuconv="34| :taolf ot egasu UPC trevnoc ot deliaf {0}. :enil {1}"
memconv="35| :taolf ot egasu meM trevnoc ot deliaf {0}. :enil {1}"
getcmd="36| :lituspog morf dnammoc ssecorp teg ot deliaf {0}. corPsp: {1}. i: {2}. dip: {3}"
cpupercent="37| lituspog morf egasu upc ssecorp teg ot deliaf: {0}. corPsp: {1}. i: {2}. dip: {3}"
mempercent="38| spog morf egasu yroemem ssecorp teg ot deliafutil: {0}. corPsp: {1}. i: {2}. dip: {3}"
parse="39| tuptuo esrap ot deliaf: {0}"

View File

@ -0,0 +1,180 @@
configfile="配置文件"
usage="使用方法: {0} [选项]\n\n选项:\n"
total="总计"
[help]
paths="按顺序从以下位置优先读取配色方案、布局方案和配置文件:"
log="日志文件位于 {0}"
written="配置文件已写入 {0}"
help="""
退: q or <C-c>
:
- k <Up>:
- j <Down>:
- <C-u>:
- <C-d>:
- <C-b>:
- <C-f>:
- gg <Home>:
- G <End>:
:
- <Tab>:
- dd: SIGTERM (15)
- d3: SIGTERM (3)
- d9: SIGTERM (9)
:
- c: CPU
- m:
- p:
:
- /:
- ():
- <Enter>:
- <C-c> <Escape>:
CPU :
- h:
- l:
:
- b: mbps
"""
# TRANSLATORS: Please don't translate the layout **names**
layouts = """:
default
minimal
battery
kitchensink"""
# TRANSLATORS: Please don't translate the colorcheme **names**
colorschemes = """:
default
default-dark ()
solarized
solarized16-dark
solarized16-light
monokai
vice"""
# TRANSLATORS: Please don't translate the widget **names**
widgets = """:
cpu - CPU
mem - 使
temp -
disk - 使
power -
net -
procs - """
[args]
help="显示当前内容。"
color="配色方案。"
scale="图形比例尺度,>0"
version="显示版本并退出。"
percpu="在 CPU 组件中显示每个 CPU。"
cpuavg="在 CPU 组件中平均 CPU。"
temp="显示华氏温度。"
statusbar="显示时间状态栏。"
rate="刷新频率。常见的时间单位皆可用。\"1m\" = 每分钟刷新。\"100ms\" = 每100毫秒刷新。"
layout="布局描述文件名。使用 \"-\" 连接。"
net="选择网卡。多个网卡用逗号分隔。使用 \"!\" 忽略指定网卡。"
export="在指定端口上启用指标输出。"
mbps="显示网速为 mbps。"
test="执行测试并返回成功或失败码。"
conffile="用于替代缺省参数的配置文件(必须是第一个参数)"
list="""
<devices|layouts|colorschemes|paths|keys>
devices:
layouts:
colorschemes:
paths:
widgets:
keys: """
write="将当前配置写入缺省配置文件。"
[config.err]
configsyntax="0| bad config file syntax; should be KEY=VALUE, was {0}"
deprecation="1| line {0}: '{1}' is deprecated. Ignored {1}={2}"
line="2| line #{0}: {1}"
tempscale="3| invalid TempScale value {0}"
[error]
configparse="4| failed to parse config file: {0}"
cliparse="5| parsing CLI args: {0}"
logsetup="6| failed to setup log file: {0}"
unknownopt="7| Unknown option \"{0}\"; try layouts, colorschemes, keys, paths, or devices\n"
writefail="8| Failed to write configuration file: {0}"
checklog="9| errors encountered; from {0}:"
metricsetup="10| error setting up {0} metrics: {1}"
nometrics="11| no metrics for {0} {1}"
fatalfetch="12| fatal error fetching {0} info: {1}"
recovfetch="13| recoverable error fetching {0} info; skipping {0}: {1}"
nodevfound="14| no usable {0} found"
setuperr="15| error setting up {0}: {1}"
colorschemefile="16| failed to find colorscheme file {0} in {1}"
colorschemeread="17| failed to read colorscheme file {0}: {1}"
colorschemeparse="18| failed to parse colorscheme file: {0}"
findlayout="19| failed to read colorscheme file {0}: {1}"
logopen="20| failed to open log file {0}: {1}"
table="21| table widget TopRow value less than 0. TopRow: {0}"
nohostname="22| could not get hostname: {0}"
[layout.error]
widget="23| Invalid widget name {0}. Must be one of {1}"
format="24| Layout error on line {0}: format must be {1}. Error parsing {2} as a int. Word was {3}. Using a row height of 1."
slashes="25| Layout warning on line {0}: too many '/' in word {1}; ignoring extra junk."
[widget.label]
disk=" 磁盘使用率 "
cpu=" CPU 使用率 "
gauge=" 电量 "
battery=" 电池状态 "
batt=" 电池 "
temp=" 温度 "
net=" 网络使用率 "
netint=" 网络使用率: {0} "
mem=" 内存使用率 "
[widget.net.err]
netactivity="26| failed to get network activity from gopsutil: {0}"
negvalrecv="27| error: negative value for recently received network data from gopsutil. recentBytesRecv: {0}"
negvalsent="28| error: negative value for recently sent network data from gopsutil. recentBytesSent: {0}"
[widget.disk]
disk="磁盘"
mount="文件系统"
used="已使用"
free="空闲"
rs="读/秒"
ws="写/秒"
[widget.proc]
filter=" 过滤器: "
label=" 进程 "
[widget.proc.header]
count="个数"
command="命令"
cpu="CPU%"
mem="内存%"
pid="进程标识"
[widget.proc.err]
count="29| failed to get CPU count from gopsutil: {0}"
retrieve="30| failed to retrieve processes: {0}"
ps="31| failed to execute 'ps' command: {0}"
gopsutil="32| failed to get processes from gopsutil: {0}"
pidconv="33| failed to convert PID to int: {0}. line: {1}"
cpuconv="34| failed to convert CPU usage to float: {0}. line: {1}"
memconv="35| failed to convert Mem usage to float: {0}. line: {1}"
getcmd="36| failed to get process command from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
cpupercent="37| failed to get process cpu usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
mempercent="38| failed to get process memeory usage from gopsutil: {0}. psProc: {1}. i: {2}. pid: {3}"
parse="39| failed to parse output: {0}"

View File

@ -1,44 +0,0 @@
configfile="Config file"
usage="Usage: {0} [options]\n\nOptions:\n"
[help]
paths="Loadable colorschemes & layouts, and the config file, are searched for, in order:"
log="The log file is in {0}"
written="Config written to {0}"
[args]
help="Show this screen."
color="Set a colorscheme."
scale="Graph scale factor, >0"
version="Print version and exit."
percpu="Show each CPU in the CPU widget."
cpuavg="Show average CPU in the CPU widget."
temp="Show temperatures in fahrenheit.Show temperatures in fahrenheit."
statusbar="Show a statusbar with the time."
rate="Refresh frequency. Most time units accepted. \"1m\" = refresh every minute. \"100ms\" = refresh every 100ms."
layout="Name of layout spec file for the UI. Use \"-\" to pipe."
net="Select network interface. Several interfaces can be defined using comma separated values. Interfaces can also be ignored using \"!\""
export="Enable metrics for export on the specified port."
mbps="Show network rate as mbps."
test="Runs tests and exits with success/failure code."
conffile="Config file to use instead of default (MUST BE FIRST ARGUMENT)"
list="""
List <devices|layouts|colorschemes|paths|keys>
devices: Prints out device names for filterable widgets
layouts: Lists build-in layouts
colorschemes: Lists built-in colorschemes
paths: List out configuration file search paths
widgets: Widgets that can be used in a layout
keys: Show the keyboard bindings."""
write="Write out a default config file."
[errors]
configparse="failed to parse config file: {0}"
cliparse="parsing CLI args: {0}"
logsetup="failed to setup log file: {0}"
unknownopt="Unknown option \"{0}\"; try layouts, colorschemes, keys, paths, or devices\n"
writefail="Failed to write configuration file: {0}"
checklog="errors encountered; from {0}:"

View File

@ -1,44 +0,0 @@
configfile="配置文件"
usage="使用方法: {0} [选项]\n\n选项:\n"
[help]
paths="按顺序从以下位置优先读取配色方案、布局方案和配置文件:"
log="日志文件位于 {0}"
written="配置文件已写入 {0}"
[args]
help="显示当前内容。"
color="制定配色方案。"
scale="图形缩放比例,>0"
version="显示版本并退出。"
percpu="在 CPU 组件中显示每个 CPU。"
cpuavg="在 CPU 组件中平均 CPU。"
temp="显示华氏温度。"
statusbar="显示时间状态栏。"
rate="刷新频率。常见的时间单位皆可用。\"1m\" = 每分钟刷新。\"100ms\" = 每100毫秒刷新。"
layout="布局描述文件名。使用 \"-\" 连接。"
net="选择网卡。多个网卡用逗号分隔。使用 \"!\" 忽略指定网卡。"
export="在指定端口上启用指标输出。"
mbps="显示网速为 mbps."
test="执行测试并返回成功或失败码。"
conffile="用于替代缺省参数的配置文件(必须是第一个参数)"
list="""
List <devices|layouts|colorschemes|paths|keys>
devices:
layouts:
colorschemes:
paths:
widgets:
keys: """
write="将当前配置写入缺省配置文件。"
[errors]
configparse="无法解析配置文件: {0}"
cliparse="解析命令行参数: {0}"
logsetup="无法创建日志文件: {0}"
unknownopt="不认识 \"{0}\"; 请使用 layouts, colorschemes, keys, paths, 或 devices\n"
writefail="无法写入配置文件: {0}"
checklog="出错了; 位于 {0}:"

View File

@ -23,7 +23,7 @@ func NewBatteryWidget(horizontalScale int) *BatteryWidget {
LineGraph: ui.NewLineGraph(), LineGraph: ui.NewLineGraph(),
updateInterval: time.Minute, updateInterval: time.Minute,
} }
self.Title = " Battery Status " self.Title = tr.Value("widget.label.battery")
self.HorizontalScale = horizontalScale self.HorizontalScale = horizontalScale
// intentional duplicate // intentional duplicate
@ -45,7 +45,7 @@ func NewBatteryWidget(horizontalScale int) *BatteryWidget {
func (b *BatteryWidget) EnableMetric() { func (b *BatteryWidget) EnableMetric() {
bats, err := battery.GetAll() bats, err := battery.GetAll()
if err != nil { if err != nil {
log.Printf("error setting up metrics: %v", err) log.Printf(tr.Value("error.metricsetup", "batt", err.Error()))
return return
} }
for i, _ := range bats { for i, _ := range bats {
@ -60,7 +60,7 @@ func (b *BatteryWidget) EnableMetric() {
} }
func makeID(i int) string { func makeID(i int) string {
return "Batt" + strconv.Itoa(i) return tr.Value("widget.label.batt") + strconv.Itoa(i)
} }
func (b *BatteryWidget) Scale(i int) { func (b *BatteryWidget) Scale(i int) {
@ -72,7 +72,7 @@ func (b *BatteryWidget) update() {
if err != nil { if err != nil {
switch errt := err.(type) { switch errt := err.(type) {
case battery.ErrFatal: case battery.ErrFatal:
log.Printf("fatal error fetching battery info: %v", err) log.Printf(tr.Value("error.fatalfetch", "batt", err.Error()))
return return
case battery.Errors: case battery.Errors:
batts := make([]*battery.Battery, 0) batts := make([]*battery.Battery, 0)
@ -80,11 +80,11 @@ func (b *BatteryWidget) update() {
if e == nil { if e == nil {
batts = append(batts, batteries[i]) batts = append(batts, batteries[i])
} else { } else {
log.Printf("recoverable error fetching battery info; skipping battery: %v", e) log.Printf(tr.Value("error.recovfetch"), "batt", e.Error())
} }
} }
if len(batts) < 1 { if len(batts) < 1 {
log.Print("no usable batteries found") log.Print(tr.Value("error.nodevfound", "batt"))
return return
} }
batteries = batts batteries = batts

View File

@ -18,7 +18,7 @@ type BatteryGauge struct {
func NewBatteryGauge() *BatteryGauge { func NewBatteryGauge() *BatteryGauge {
self := &BatteryGauge{Gauge: termui.NewGauge()} self := &BatteryGauge{Gauge: termui.NewGauge()}
self.Title = " Power Level " self.Title = tr.Value("widget.label.gauge")
self.update() self.update()

View File

@ -35,7 +35,7 @@ func NewCPUWidget(updateInterval time.Duration, horizontalScale int, showAverage
average: ewma.NewMovingAverage(), average: ewma.NewMovingAverage(),
} }
self.LabelStyles[AVRG] = termui.ModifierBold self.LabelStyles[AVRG] = termui.ModifierBold
self.Title = " CPU Usage " self.Title = tr.Value("widget.label.cpu")
self.HorizontalScale = horizontalScale self.HorizontalScale = horizontalScale
if !(self.ShowAverageLoad || self.ShowPerCPULoad) { if !(self.ShowAverageLoad || self.ShowPerCPULoad) {

View File

@ -37,8 +37,9 @@ func NewDiskWidget() *DiskWidget {
updateInterval: time.Second, updateInterval: time.Second,
Partitions: make(map[string]*Partition), Partitions: make(map[string]*Partition),
} }
self.Title = " Disk Usage " self.Table.Tr = tr
self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"} self.Title = tr.Value("widget.label.disk")
self.Header = []string{tr.Value("widget.disk.disk"), tr.Value("widget.disk.mount"), tr.Value("widget.disk.used"), tr.Value("widget.disk.free"), tr.Value("widget.disk.rs"), tr.Value("widget.disk.ws")}
self.ColGap = 2 self.ColGap = 2
self.ColResizer = func() { self.ColResizer = func() {
self.ColWidths = []int{ self.ColWidths = []int{
@ -73,7 +74,7 @@ func (disk *DiskWidget) EnableMetric() {
func (disk *DiskWidget) update() { func (disk *DiskWidget) update() {
partitions, err := psDisk.Partitions(false) partitions, err := psDisk.Partitions(false)
if err != nil { if err != nil {
log.Printf("failed to get disk partitions from gopsutil: %v", err) log.Printf(tr.Value("error.setup", "disk-partitions", err.Error()))
return return
} }
@ -118,7 +119,7 @@ func (disk *DiskWidget) update() {
for _, partition := range disk.Partitions { for _, partition := range disk.Partitions {
usage, err := psDisk.Usage(partition.MountPoint) usage, err := psDisk.Usage(partition.MountPoint)
if err != nil { if err != nil {
log.Printf("failed to get partition usage statistics from gopsutil: %v. partition: %v", err, partition) log.Printf(tr.Value("error.recovfetch", "partition-"+partition.MountPoint+"-usage", err.Error()))
continue continue
} }
partition.UsedPercent = uint32(usage.UsedPercent + 0.5) partition.UsedPercent = uint32(usage.UsedPercent + 0.5)
@ -127,7 +128,7 @@ func (disk *DiskWidget) update() {
ioCounters, err := psDisk.IOCounters(partition.Device) ioCounters, err := psDisk.IOCounters(partition.Device)
if err != nil { if err != nil {
log.Printf("failed to get partition read/write info from gopsutil: %v. partition: %v", err, partition) log.Printf(tr.Value("error.recovfetch", "partition-"+partition.Device+"-rw", err.Error()))
continue continue
} }
ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)] ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)]

View File

@ -5,52 +5,19 @@ import (
"strings" "strings"
ui "github.com/gizak/termui/v3" ui "github.com/gizak/termui/v3"
lingo "github.com/jdkeke142/lingo-toml"
) )
// KEYBINDS is the help text for the in-program shortcuts var tr lingo.Translations
const KEYBINDS = ` var keyBinds string
Quit: q or <C-c>
Process navigation:
- k and <Up>: up
- j and <Down>: down
- <C-u>: half page up
- <C-d>: half page down
- <C-b>: full page up
- <C-f>: full page down
- gg and <Home>: jump to top
- G and <End>: jump to bottom
Process actions:
- <Tab>: toggle process grouping
- dd: kill selected process or group of processes with SIGTERM (15)
- d3: kill selected process or group of processes with SIGQUIT (3)
- d9: kill selected process or group of processes with SIGKILL (9)
Process sorting:
- c: CPU
- m: Mem
- p: PID
Process filtering:
- /: start editing filter
- (while editing):
- <Enter>: accept filter
- <C-c> and <Escape>: clear filter
CPU and Mem graph scaling:
- h: scale in
- l: scale out
Network:
- b: toggle between mbps and scaled bytes per second
`
type HelpMenu struct { type HelpMenu struct {
ui.Block ui.Block
} }
func NewHelpMenu() *HelpMenu { func NewHelpMenu(tra lingo.Translations) *HelpMenu {
tr = tra
keyBinds = tr.Value("help.help")
return &HelpMenu{ return &HelpMenu{
Block: *ui.NewBlock(), Block: *ui.NewBlock(),
} }
@ -58,12 +25,12 @@ func NewHelpMenu() *HelpMenu {
func (help *HelpMenu) Resize(termWidth, termHeight int) { func (help *HelpMenu) Resize(termWidth, termHeight int) {
textWidth := 53 textWidth := 53
for _, line := range strings.Split(KEYBINDS, "\n") { for _, line := range strings.Split(keyBinds, "\n") {
if textWidth < len(line) { if textWidth < len(line) {
textWidth = len(line) + 2 textWidth = len(line) + 2
} }
} }
textHeight := strings.Count(KEYBINDS, "\n") + 1 textHeight := strings.Count(keyBinds, "\n") + 1
x := (termWidth - textWidth) / 2 x := (termWidth - textWidth) / 2
y := (termHeight - textHeight) / 2 y := (termHeight - textHeight) / 2
@ -73,7 +40,7 @@ func (help *HelpMenu) Resize(termWidth, termHeight int) {
func (help *HelpMenu) Draw(buf *ui.Buffer) { func (help *HelpMenu) Draw(buf *ui.Buffer) {
help.Block.Draw(buf) help.Block.Draw(buf)
for y, line := range strings.Split(KEYBINDS, "\n") { for y, line := range strings.Split(keyBinds, "\n") {
for x, rune := range line { for x, rune := range line {
buf.SetCell( buf.SetCell(
ui.NewCell(rune, ui.Theme.Default), ui.NewCell(rune, ui.Theme.Default),

View File

@ -21,7 +21,7 @@ func NewMemWidget(updateInterval time.Duration, horizontalScale int) *MemWidget
LineGraph: ui.NewLineGraph(), LineGraph: ui.NewLineGraph(),
updateInterval: updateInterval, updateInterval: updateInterval,
} }
widg.Title = " Memory Usage " widg.Title = tr.Value("widget.label.mem")
widg.HorizontalScale = horizontalScale widg.HorizontalScale = horizontalScale
mems := make(map[string]devices.MemoryInfo) mems := make(map[string]devices.MemoryInfo)
devices.UpdateMem(mems) devices.UpdateMem(mems)

View File

@ -47,9 +47,9 @@ func NewNetWidget(netInterface string) *NetWidget {
updateInterval: time.Second, updateInterval: time.Second,
NetInterface: strings.Split(netInterface, ","), NetInterface: strings.Split(netInterface, ","),
} }
self.Title = " Network Usage " self.Title = tr.Value("widget.label.net")
if netInterface != "all" { if netInterface != "all" {
self.Title = fmt.Sprintf(" Network Usage: %s ", netInterface) self.Title = tr.Value("widget.label.netint", netInterface)
} }
self.update() self.update()
@ -73,7 +73,7 @@ func (net *NetWidget) EnableMetric() {
func (net *NetWidget) update() { func (net *NetWidget) update() {
interfaces, err := psNet.IOCounters(true) interfaces, err := psNet.IOCounters(true)
if err != nil { if err != nil {
log.Printf("failed to get network activity from gopsutil: %v", err) log.Println(tr.Value("widget.net.err.netactivity", err.Error()))
return return
} }
@ -114,12 +114,14 @@ func (net *NetWidget) update() {
recentBytesSent = totalBytesSent - net.totalBytesSent recentBytesSent = totalBytesSent - net.totalBytesSent
if int(recentBytesRecv) < 0 { if int(recentBytesRecv) < 0 {
log.Printf("error: negative value for recently received network data from gopsutil. recentBytesRecv: %v", recentBytesRecv) v := fmt.Sprintf("%d", recentBytesRecv)
log.Println(tr.Value("widget.net.err.negvalrecv", v))
// recover from error // recover from error
recentBytesRecv = 0 recentBytesRecv = 0
} }
if int(recentBytesSent) < 0 { if int(recentBytesSent) < 0 {
log.Printf("error: negative value for recently sent network data from gopsutil. recentBytesSent: %v", recentBytesSent) v := fmt.Sprintf("%d", recentBytesSent)
log.Printf(tr.Value("widget.net.err.negvalsent", v))
// recover from error // recover from error
recentBytesSent = 0 recentBytesSent = 0
} }
@ -160,7 +162,7 @@ func (net *NetWidget) update() {
recentConverted, unitRecent = utils.ConvertBytes(recent) recentConverted, unitRecent = utils.ConvertBytes(recent)
} }
net.Lines[i].Title1 = fmt.Sprintf(" Total %s: %5.1f %s", label, totalConverted, unitTotal) net.Lines[i].Title1 = fmt.Sprintf(" %s %s: %5.1f %s", tr.Value("total"), label, totalConverted, unitTotal)
net.Lines[i].Title2 = fmt.Sprintf(format, rate, recentConverted, unitRecent) net.Lines[i].Title2 = fmt.Sprintf(format, rate, recentConverted, unitRecent)
} }
} }

View File

@ -50,7 +50,7 @@ type ProcWidget struct {
func NewProcWidget() *ProcWidget { func NewProcWidget() *ProcWidget {
cpuCount, err := devices.CpuCount() cpuCount, err := devices.CpuCount()
if err != nil { if err != nil {
log.Printf("failed to get CPU count from gopsutil: %v", err) log.Println(tr.Value("error.proc.err.count", err.Error()))
} }
self := &ProcWidget{ self := &ProcWidget{
Table: ui.NewTable(), Table: ui.NewTable(),
@ -62,14 +62,14 @@ func NewProcWidget() *ProcWidget {
} }
self.entry = &ui.Entry{ self.entry = &ui.Entry{
Style: self.TitleStyle, Style: self.TitleStyle,
Label: " Filter: ", Label: tr.Value("widget.proc.filter"),
Value: "", Value: "",
UpdateCallback: func(val string) { UpdateCallback: func(val string) {
self.filter = val self.filter = val
self.update() self.update()
}, },
} }
self.Title = " Processes " self.Title = tr.Value("widget.proc.label")
self.ShowCursor = true self.ShowCursor = true
self.ShowLocation = true self.ShowLocation = true
self.ColGap = 3 self.ColGap = 3
@ -136,7 +136,7 @@ func (proc *ProcWidget) filterProcs(procs []Proc) []Proc {
func (proc *ProcWidget) update() { func (proc *ProcWidget) update() {
procs, err := getProcs() procs, err := getProcs()
if err != nil { if err != nil {
log.Printf("failed to retrieve processes: %v", err) log.Printf(tr.Value("widget.proc.error.retrieve", err.Error()))
return return
} }
@ -156,10 +156,15 @@ func (proc *ProcWidget) update() {
// sortProcs sorts either the grouped or ungrouped []Process based on the sortMethod. // sortProcs sorts either the grouped or ungrouped []Process based on the sortMethod.
// Called with every update, when the sort method is changed, and when processes are grouped and ungrouped. // Called with every update, when the sort method is changed, and when processes are grouped and ungrouped.
func (proc *ProcWidget) sortProcs() { func (proc *ProcWidget) sortProcs() {
proc.Header = []string{"Count", "Command", "CPU%", "Mem%"} proc.Header = []string{
tr.Value("widget.proc.header.count"),
tr.Value("widget.proc.header.command"),
tr.Value("widget.proc.header.cpu"),
tr.Value("widget.proc.header.mem"),
}
if !proc.showGroupedProcs { if !proc.showGroupedProcs {
proc.Header[0] = "PID" proc.Header[0] = tr.Value("widget.proc.header.pid")
} }
var procs *[]Proc var procs *[]Proc

View File

@ -26,13 +26,13 @@ type processList struct {
func getProcs() ([]Proc, error) { func getProcs() ([]Proc, error) {
output, err := exec.Command("ps", "-axo pid,comm,%cpu,%mem,args", "--libxo", "json").Output() output, err := exec.Command("ps", "-axo pid,comm,%cpu,%mem,args", "--libxo", "json").Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute 'ps' command: %v", err) return nil, fmt.Errorf(tr.Value("widget.proc.err.ps", err.Error()))
} }
list := processList{} list := processList{}
err = json.Unmarshal(output, &list) err = json.Unmarshal(output, &list)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to unmarshal json. %s", err) return nil, fmt.Errorf(tr.Value("widget.proc.err.parse", err.Error()))
} }
procs := []Proc{} procs := []Proc{}
@ -42,15 +42,18 @@ func getProcs() ([]Proc, error) {
} }
pid, err := strconv.Atoi(strings.TrimSpace(process.Pid)) pid, err := strconv.Atoi(strings.TrimSpace(process.Pid))
if err != nil { if err != nil {
log.Printf("failed to convert first field to int: %v. split: %v", err, process) sp := fmt.Sprintf("%v", process)
log.Printf(tr.Value("widget.proc.err.pidconv", err.Error(), sp))
} }
cpu, err := strconv.ParseFloat(utils.ConvertLocalizedString(process.CPU), 32) cpu, err := strconv.ParseFloat(utils.ConvertLocalizedString(process.CPU), 32)
if err != nil { if err != nil {
log.Printf("failed to convert third field to float: %v. split: %v", err, process) sp := fmt.Sprintf("%v", process)
log.Printf(tr.Value("widget.proc.err.cpuconv", err.Error(), sp))
} }
mem, err := strconv.ParseFloat(utils.ConvertLocalizedString(process.Mem), 32) mem, err := strconv.ParseFloat(utils.ConvertLocalizedString(process.Mem), 32)
if err != nil { if err != nil {
log.Printf("failed to convert fourth field to float: %v. split: %v", err, process) sp := fmt.Sprintf("%v", process)
log.Printf(tr.Value("widget.proc.err.memconv", err.Error(), sp))
} }
proc := Proc{ proc := Proc{
Pid: pid, Pid: pid,

View File

@ -11,7 +11,7 @@ import (
func getProcs() ([]Proc, error) { func getProcs() ([]Proc, error) {
output, err := exec.Command("ps", "-axo", "pid:10,comm:50,pcpu:5,pmem:5,args").Output() output, err := exec.Command("ps", "-axo", "pid:10,comm:50,pcpu:5,pmem:5,args").Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute 'ps' command: %v", err) return nil, fmt.Errorf(tr.Value("widget.proc.err.ps", err.Error()))
} }
// converts to []string, removing trailing newline and header // converts to []string, removing trailing newline and header
@ -22,15 +22,15 @@ func getProcs() ([]Proc, error) {
log.Printf("line is '%s', pid is '%s', cpu is '%s', mem is '%s'", line, strings.TrimSpace(line[0:10]), strings.TrimSpace(line[63:68]), strings.TrimSpace(line[69:74])) log.Printf("line is '%s', pid is '%s', cpu is '%s', mem is '%s'", line, strings.TrimSpace(line[0:10]), strings.TrimSpace(line[63:68]), strings.TrimSpace(line[69:74]))
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10])) pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
if err != nil { if err != nil {
log.Printf("failed to convert PID to int: %v. line: %v", err, line) log.Println(tr.Value("widget.proc.err.pidconv", err.Error(), line))
} }
cpu, err := strconv.ParseFloat(strings.TrimSpace(line[63:68]), 64) cpu, err := strconv.ParseFloat(strings.TrimSpace(line[63:68]), 64)
if err != nil { if err != nil {
log.Printf("failed to convert CPU usage to float: %v. line: %v", err, line) log.Println(tr.Value("widget.proc.err.cpuconv", err.Error(), line))
} }
mem, err := strconv.ParseFloat(strings.TrimSpace(line[69:74]), 64) mem, err := strconv.ParseFloat(strings.TrimSpace(line[69:74]), 64)
if err != nil { if err != nil {
log.Printf("failed to convert Mem usage to float: %v. line: %v", err, line) log.Println(tr.Value("widget.proc.err.memconv", err.Error(), line))
} }
proc := Proc{ proc := Proc{
Pid: pid, Pid: pid,

View File

@ -23,7 +23,7 @@ func getProcs() ([]Proc, error) {
keywords := fmt.Sprintf("pid=%s,comm=%s,pcpu=%s,pmem=%s,args", ten, fifty, five, five) keywords := fmt.Sprintf("pid=%s,comm=%s,pcpu=%s,pmem=%s,args", ten, fifty, five, five)
output, err := exec.Command("ps", "-caxo", keywords).Output() output, err := exec.Command("ps", "-caxo", keywords).Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute 'ps' command: %v", err) return nil, fmt.Errorf(tr.Value("widget.proc.err.ps", err.Error()))
} }
// converts to []string and removes the header // converts to []string and removes the header
@ -33,15 +33,15 @@ func getProcs() ([]Proc, error) {
for _, line := range linesOfProcStrings { for _, line := range linesOfProcStrings {
pid, err := strconv.Atoi(strings.TrimSpace(line[0:10])) pid, err := strconv.Atoi(strings.TrimSpace(line[0:10]))
if err != nil { if err != nil {
log.Printf("failed to convert first field to int: %v. split: %v", err, line) log.Println(tr.Value("widget.proc.err.pidconv", err.Error(), line))
} }
cpu, err := strconv.ParseFloat(utils.ConvertLocalizedString(strings.TrimSpace(line[63:68])), 64) cpu, err := strconv.ParseFloat(utils.ConvertLocalizedString(strings.TrimSpace(line[63:68])), 64)
if err != nil { if err != nil {
log.Printf("failed to convert third field to float: %v. split: %v", err, line) log.Println(tr.Value("widget.proc.err.cpuconv", err.Error(), line))
} }
mem, err := strconv.ParseFloat(utils.ConvertLocalizedString(strings.TrimSpace(line[69:74])), 64) mem, err := strconv.ParseFloat(utils.ConvertLocalizedString(strings.TrimSpace(line[69:74])), 64)
if err != nil { if err != nil {
log.Printf("failed to convert fourth field to float: %v. split: %v", err, line) log.Println(tr.Value("widget.proc.err.memconv", err.Error(), line))
} }
proc := Proc{ proc := Proc{
Pid: pid, Pid: pid,

View File

@ -3,14 +3,15 @@ package widgets
import ( import (
"fmt" "fmt"
"log" "log"
"strconv"
psProc "github.com/shirou/gopsutil/process" "github.com/shirou/gopsutil/process"
) )
func getProcs() ([]Proc, error) { func getProcs() ([]Proc, error) {
psProcs, err := psProc.Processes() psProcs, err := process.Processes()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get processes from gopsutil: %v", err) return nil, fmt.Errorf(tr.Value("widget.proc.err.gopsutil", err.Error()))
} }
procs := make([]Proc, len(psProcs)) procs := make([]Proc, len(psProcs))
@ -18,15 +19,24 @@ func getProcs() ([]Proc, error) {
pid := psProc.Pid pid := psProc.Pid
command, err := psProc.Name() command, err := psProc.Name()
if err != nil { if err != nil {
log.Printf("failed to get process command from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid) sps := fmt.Sprintf("%v", psProc)
si := strconv.Itoa(i)
spid := fmt.Sprintf("%d", pid)
log.Println(tr.Value("widget.proc.err.getcmd", err.Error(), sps, si, spid))
} }
cpu, err := psProc.CPUPercent() cpu, err := psProc.CPUPercent()
if err != nil { if err != nil {
log.Printf("failed to get process cpu usage from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid) sps := fmt.Sprintf("%v", psProc)
si := strconv.Itoa(i)
spid := fmt.Sprintf("%d", pid)
log.Println(tr.Value("widget.proc.err.cpupercent", err.Error(), sps, si, spid))
} }
mem, err := psProc.MemoryPercent() mem, err := psProc.MemoryPercent()
if err != nil { if err != nil {
log.Printf("failed to get process memeory usage from gopsutil: %v. psProc: %v. i: %v. pid: %v", err, psProc, i, pid) sps := fmt.Sprintf("%v", psProc)
si := strconv.Itoa(i)
spid := fmt.Sprintf("%d", pid)
log.Println(tr.Value("widget.proc.err.mempercent", err.Error(), sps, si, spid))
} }
procs[i] = Proc{ procs[i] = Proc{

View File

@ -24,7 +24,7 @@ func (sb *StatusBar) Draw(buf *ui.Buffer) {
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
log.Printf("could not get hostname: %v", err) log.Printf(tr.Value("error.nohostname", err.Error()))
return return
} }
buf.SetString( buf.SetString(

View File

@ -39,7 +39,7 @@ func NewTempWidget(tempScale TempScale, filter []string) *TempWidget {
TempThreshold: 80, TempThreshold: 80,
TempScale: tempScale, TempScale: tempScale,
} }
self.Title = " Temperatures " self.Title = tr.Value("widget.label.temp")
if len(filter) > 0 { if len(filter) > 0 {
for _, t := range filter { for _, t := range filter {
self.Data[t] = 0 self.Data[t] = 0