Sessions: locale-aware csv formatting (#5136)

This commit is contained in:
andig 2022-11-15 10:15:24 +01:00 • committed by GitHub
parent ea2c3d53fe
commit 3f8eba029b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 17 deletions

View file

@ -232,5 +232,5 @@ type FeatureDescriber interface {
// CsvWriter converts to csv
type CsvWriter interface {
WriteCsv(context.Context, io.Writer)
WriteCsv(context.Context, io.Writer) error
}

View file

@ -5,7 +5,6 @@ import (
"encoding/csv"
"fmt"
"io"
"strconv"
"strings"
"time"
@ -13,6 +12,9 @@ import (
"github.com/evcc-io/evcc/util/locale"
"github.com/fatih/structs"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
)
// Session is a single charging session
@ -23,7 +25,7 @@ type Session struct {
Loadpoint string `json:"loadpoint"`
Identifier string `json:"identifier"`
Vehicle string `json:"vehicle"`
Odometer float64 `json:"odometer"`
Odometer float64 `json:"odometer" format:"int"`
MeterStart float64 `json:"meterStart" csv:"Meter Start (kWh)" gorm:"column:meter_start_kwh"`
MeterStop float64 `json:"meterStop" csv:"Meter Stop (kWh)" gorm:"column:meter_end_kwh"`
ChargedEnergy float64 `json:"chargedEnergy" csv:"Charged Energy (kWh)" gorm:"column:charged_kwh"`
@ -43,7 +45,7 @@ type Sessions []Session
var _ api.CsvWriter = (*Sessions)(nil)
func (t *Sessions) writeHeader(ctx context.Context, ww *csv.Writer) {
func (t *Sessions) writeHeader(ctx context.Context, ww *csv.Writer) error {
localizer := locale.Localizer
if val := ctx.Value(locale.Locale).(string); val != "" {
localizer = i18n.NewLocalizer(locale.Bundle, val, locale.Language)
@ -70,10 +72,11 @@ func (t *Sessions) writeHeader(ctx context.Context, ww *csv.Writer) {
row = append(row, caption)
}
_ = ww.Write(row)
return ww.Write(row)
}
func (t *Sessions) writeRow(ww *csv.Writer, r Session) {
func (t *Sessions) writeRow(ww *csv.Writer, mp *message.Printer, r Session) error {
var row []string
for _, f := range structs.Fields(r) {
if f.Tag("csv") == "-" {
@ -81,10 +84,16 @@ func (t *Sessions) writeRow(ww *csv.Writer, r Session) {
}
var val string
format := f.Tag("format")
switch v := f.Value().(type) {
case float64:
val = strconv.FormatFloat(v, 'f', 3, 64)
switch format {
case "int":
val = mp.Sprint(number.Decimal(v, number.MaxFractionDigits(0)))
default:
val = mp.Sprint(number.Decimal(v, number.MaxFractionDigits(3)))
}
case time.Time:
if !v.IsZero() {
val = v.Local().Format("2006-01-02 15:04:05")
@ -96,17 +105,45 @@ func (t *Sessions) writeRow(ww *csv.Writer, r Session) {
row = append(row, val)
}
_ = ww.Write(row)
return ww.Write(row)
}
// WriteCsv implements the api.CsvWriter interface
func (t *Sessions) WriteCsv(ctx context.Context, w io.Writer) {
ww := csv.NewWriter(w)
t.writeHeader(ctx, ww)
func (t *Sessions) WriteCsv(ctx context.Context, w io.Writer) error {
if _, err := w.Write([]byte{0xEF, 0xBB, 0xBF}); err != nil {
return err
}
// get context language
lang := locale.Language
if language, ok := ctx.Value(locale.Locale).(string); ok && language != "" {
lang = language
}
tag, err := language.Parse(lang)
if err != nil {
return err
}
ww := csv.NewWriter(w)
// set separator according to locale
if b, _ := tag.Base(); b.String() == language.German.String() {
ww.Comma = ';'
}
if err := t.writeHeader(ctx, ww); err != nil {
return err
}
mp := message.NewPrinter(tag)
for _, r := range *t {
t.writeRow(ww, r)
if err := t.writeRow(ww, mp, r); err != nil {
return err
}
}
ww.Flush()
return ww.Error()
}

View file

@ -19,6 +19,7 @@ import (
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/locale"
"github.com/gorilla/mux"
"golang.org/x/text/language"
)
func indexHandler() http.HandlerFunc {
@ -75,7 +76,7 @@ func csvResult(ctx context.Context, w http.ResponseWriter, res any) {
w.Header().Set("Content-Disposition", `attachment; filename="sessions.csv"`)
if ww, ok := res.(api.CsvWriter); ok {
ww.WriteCsv(ctx, w)
_ = ww.WriteCsv(ctx, w)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
@ -193,8 +194,13 @@ func sessionHandler(w http.ResponseWriter, r *http.Request) {
}
if r.URL.Query().Get("format") == "csv" {
accept := r.Header.Get("Accept-Language")
ctx := context.WithValue(context.Background(), locale.Locale, accept)
// get request language
lang := r.Header.Get("Accept-Language")
if tags, _, err := language.ParseAcceptLanguage(lang); err == nil && len(tags) > 0 {
lang = tags[0].String()
}
ctx := context.WithValue(context.Background(), locale.Locale, lang)
csvResult(ctx, w, &res)
return
}

View file

@ -36,9 +36,9 @@ func Init() error {
}
}
Language, err := jibber_jabber.DetectLanguage()
Language, err = jibber_jabber.DetectLanguage()
if err != nil {
Language = "de"
Language = language.German.String()
}
Localizer = i18n.NewLocalizer(Bundle, Language)