Skip to content

Commit

Permalink
Sessions: translate csv column names (evcc-io#4761)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Oct 13, 2022
1 parent d0df523 commit 3353178
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 18 deletions.
3 changes: 2 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"context"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -231,5 +232,5 @@ type FeatureDescriber interface {

// CsvWriter converts to csv
type CsvWriter interface {
WriteCsv(io.Writer)
WriteCsv(context.Context, io.Writer)
}
8 changes: 8 additions & 0 deletions assets/i18n/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package i18n

import (
"embed"
)

//go:embed *.toml
var LocaleFS embed.FS
10 changes: 10 additions & 0 deletions assets/i18n/de.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
10 changes: 10 additions & 0 deletions assets/i18n/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
10 changes: 10 additions & 0 deletions assets/i18n/lt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
6 changes: 6 additions & 0 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
42 changes: 30 additions & 12 deletions core/db/session.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
10 changes: 7 additions & 3 deletions server/http_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}

Expand Down
6 changes: 6 additions & 0 deletions util/locale/internal/types.go
Original file line number Diff line number Diff line change
@@ -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 changes: 61 additions & 0 deletions util/locale/locale.go
Original file line number Diff line number Diff line change
@@ -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,
})
}

0 comments on commit 3353178

Please sign in to comment.