Sessions: translate csv column names (#4761)

This commit is contained in:
andig 2022-10-13 12:58:00 +02:00 • committed by GitHub
parent d0df52305b
commit 3353178160
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 152 additions and 18 deletions

View file

@ -1,6 +1,7 @@
package api
import (
"context"
"fmt"
"io"
"net/http"
@ -231,5 +232,5 @@ type FeatureDescriber interface {
// CsvWriter converts to csv
type CsvWriter interface {
WriteCsv(io.Writer)
WriteCsv(context.Context, io.Writer)
}

8
assets/i18n/assets.go Normal file
View file

@ -0,0 +1,8 @@
package i18n
import (
"embed"
)
//go:embed *.toml
var LocaleFS embed.FS

View file

@ -183,6 +183,16 @@ vehicle = "Fahrzeug"
energy = "Geladen"
date = "Beendet"
[sessions.csv]
loadpoint = "Ladepunkt"
vehicle = "Fahrzeug"
identifier = "Kennung"
chargedenergy = "Energie (kWh)"
meterstart = "Anfangszählerstand (kWh)"
meterstop = "Endzählerstand (kWh)"
created = "Startzeit"
finished = "Endzeit"
[offline]
message = "Keine Verbindung zum Server."
reload = "Reload?"

View file

@ -183,6 +183,16 @@ vehicle = "Vehicle"
energy = "Charged"
date = "Finished"
[sessions.csv]
loadpoint = "Loadpoint"
vehicle = "Vehicle"
identifier = "Identifier"
chargedenergy = "Energy (kWh)"
meterstart = "Meter Start (kWh)"
meterstop = "Meter Stop (kWh)"
created = "Created"
finished = "Finished"
[offline]
message = "No connection to server."
reload = "Reload?"

View file

@ -183,6 +183,16 @@ vehicle = "Automobilis"
energy = "Įkrauta"
date = "Baigta"
[sessions.csv]
loadpoint = "Įkroviklis"
vehicle = "Automobilis"
identifier = "Identifikatorius"
chargedenergy = "Energija (kWh)"
meterstart = "Skaitiklis pradžioje (kWh)"
meterstop = "Skaitiklis pabaigoje (kWh)"
created = "Pradėta"
finished = "Pabaigta"
[offline]
message = "Nėra ryšio su serveriu."
reload = "Perkrauti?"

View file

@ -21,6 +21,7 @@ import (
"github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/locale"
"github.com/evcc-io/evcc/util/machine"
"github.com/evcc-io/evcc/util/pipe"
"github.com/evcc-io/evcc/util/request"
@ -73,6 +74,11 @@ func configureEnvironment(cmd *cobra.Command, conf config) (err error) {
err = sponsor.ConfigureSponsorship(conf.SponsorToken)
}
// setup translations
if err == nil {
err = locale.Init()
}
// setup persistence
if err == nil && conf.Database.Dsn != "" {
err = configureDatabase(conf.Database)

View file

@ -1,14 +1,18 @@
package db
import (
"context"
"encoding/csv"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util/locale"
"github.com/fatih/structs"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
// Session is a single charging session
@ -36,15 +40,29 @@ type Sessions []Session
var _ api.CsvWriter = (*Sessions)(nil)
func (t *Sessions) writeHeader(ww *csv.Writer) {
func (t *Sessions) writeHeader(ctx context.Context, ww *csv.Writer) {
localizer := locale.Localizer
if val := ctx.Value(locale.Locale).(string); val != "" {
localizer = i18n.NewLocalizer(locale.Bundle, val, locale.Language)
}
var row []string
for _, f := range structs.Fields(Session{}) {
caption := f.Tag("csv")
switch {
case caption == "-":
csv := f.Tag("csv")
if csv == "-" {
continue
case caption == "":
caption = f.Name()
}
caption, err := localizer.Localize(&locale.Config{
MessageID: "sessions.csv." + strings.ToLower(f.Name()),
})
if err != nil {
if csv != "" {
caption = csv
} else {
caption = f.Name()
}
}
row = append(row, caption)
@ -59,17 +77,17 @@ func (t *Sessions) writeRow(ww *csv.Writer, r Session) {
continue
}
val := fmt.Sprintf("%v", f.Value())
var val string
switch v := f.Value().(type) {
case float64:
val = strconv.FormatFloat(v, 'f', 3, 64)
case time.Time:
if v.IsZero() {
val = ""
} else {
if !v.IsZero() {
val = v.Local().Format("2006-01-02 15:04:05")
}
default:
val = fmt.Sprintf("%v", f.Value())
}
row = append(row, val)
@ -79,9 +97,9 @@ func (t *Sessions) writeRow(ww *csv.Writer, r Session) {
}
// WriteCsv implements the api.CsvWriter interface
func (t *Sessions) WriteCsv(w io.Writer) {
func (t *Sessions) WriteCsv(ctx context.Context, w io.Writer) {
ww := csv.NewWriter(w)
t.writeHeader(ww)
t.writeHeader(ctx, ww)
for _, r := range *t {
t.writeRow(ww, r)

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View file

@ -20,7 +20,7 @@
<meta name="theme-color" content="#020318" />
<title>evcc</title>
<script type="module" crossorigin src="./assets/index.15ca0e2e.js"></script>
<script type="module" crossorigin src="./assets/index.34c57bb3.js"></script>
<link rel="stylesheet" href="./assets/index.0f7f1beb.css">
</head>

View file

@ -1,6 +1,7 @@
package server
import (
"context"
"encoding/json"
"errors"
"fmt"
@ -16,6 +17,7 @@ import (
"github.com/evcc-io/evcc/core/site"
dbserver "github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/locale"
"github.com/gorilla/mux"
)
@ -68,12 +70,12 @@ func jsonError(w http.ResponseWriter, status int, err error) {
jsonWrite(w, map[string]interface{}{"error": err.Error()})
}
func csvResult(w http.ResponseWriter, res any) {
func csvResult(ctx context.Context, w http.ResponseWriter, res any) {
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", `attachment; filename="sessions.csv"`)
if ww, ok := res.(api.CsvWriter); ok {
ww.WriteCsv(w)
ww.WriteCsv(ctx, w)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
@ -163,7 +165,9 @@ func sessionHandler(w http.ResponseWriter, r *http.Request) {
}
if r.URL.Query().Get("format") == "csv" {
csvResult(w, &res)
accept := r.Header.Get("Accept-Language")
ctx := context.WithValue(context.Background(), locale.Locale, accept)
csvResult(ctx, w, &res)
return
}

View file

@ -0,0 +1,6 @@
package internal
// ContextKey is just an empty struct. It exists so context values can be
// an immutable public variable with a unique type. It's immutable
// because nobody else can create a ContextKey, being unexported.
type ContextKey struct{}

61
util/locale/locale.go Normal file
View file

@ -0,0 +1,61 @@
package locale
import (
"fmt"
"github.com/BurntSushi/toml"
"github.com/cloudfoundry/jibber_jabber"
assets "github.com/evcc-io/evcc/assets/i18n"
"github.com/evcc-io/evcc/util/locale/internal"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
type Config = i18n.LocalizeConfig
var (
Locale internal.ContextKey
Bundle *i18n.Bundle
Language string
Localizer *i18n.Localizer
)
func Init() error {
Bundle = i18n.NewBundle(language.English)
Bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
dir, err := assets.LocaleFS.ReadDir(".")
if err != nil {
panic(err)
}
for _, d := range dir {
if _, err := Bundle.LoadMessageFileFS(assets.LocaleFS, d.Name()); err != nil {
return fmt.Errorf("loading locales failed: %w", err)
}
}
Language, err := jibber_jabber.DetectLanguage()
if err != nil {
Language = "de"
}
Localizer = i18n.NewLocalizer(Bundle, Language)
return nil
}
func Localize(lc *Config) string {
msg, _, err := Localizer.LocalizeWithTag(lc)
if err != nil {
msg = lc.MessageID
}
return msg
}
func LocalizeID(id string) string {
return Localize(&Config{
MessageID: id,
})
}