Files
HeatGuard/internal/server/i18n.go
vikingowl d5452409b6 feat: rewrite to stateless web app with IndexedDB frontend
Replace CLI + SQLite architecture with a Go web server + vanilla JS
frontend using IndexedDB for all client-side data storage.

- Remove: cli, store, report, static packages
- Add: compute engine (BuildDashboard), server package, web UI
- Add: setup page with CRUD for profiles, rooms, devices, occupants, AC
- Add: dashboard with SVG temperature timeline, risk analysis, care checklist
- Add: i18n support (English/German) with server-side Go templates
- Add: LLM provider selection UI with client-side API key storage
- Add: per-room indoor temperature, edit buttons, language-aware AI summary
2026-02-09 13:31:38 +01:00

101 lines
2.1 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"strings"
)
// translations holds the loaded translation maps keyed by language code.
type translations struct {
langs map[string]map[string]any // e.g. {"en": {...}, "de": {...}}
}
func loadTranslations(enJSON, deJSON []byte) (*translations, error) {
t := &translations{langs: make(map[string]map[string]any)}
var en map[string]any
if err := json.Unmarshal(enJSON, &en); err != nil {
return nil, fmt.Errorf("parse en.json: %w", err)
}
t.langs["en"] = en
var de map[string]any
if err := json.Unmarshal(deJSON, &de); err != nil {
return nil, fmt.Errorf("parse de.json: %w", err)
}
t.langs["de"] = de
return t, nil
}
// get resolves a dotted key (e.g. "setup.rooms.title") from the translation map.
func (t *translations) get(lang, key string) string {
m, ok := t.langs[lang]
if !ok {
m = t.langs["en"]
}
if m == nil {
return key
}
return resolve(m, key)
}
func resolve(m map[string]any, key string) string {
parts := strings.Split(key, ".")
var current any = m
for _, p := range parts {
cm, ok := current.(map[string]any)
if !ok {
return key
}
current, ok = cm[p]
if !ok {
return key
}
}
s, ok := current.(string)
if !ok {
return key
}
return s
}
// supportedLangs are the available languages.
var supportedLangs = []string{"en", "de"}
// detectLanguage determines the active language from query param, cookie, or Accept-Language header.
func detectLanguage(r *http.Request) string {
// 1. Query parameter
if lang := r.URL.Query().Get("lang"); isSupported(lang) {
return lang
}
// 2. Cookie
if c, err := r.Cookie("heatguard_lang"); err == nil && isSupported(c.Value) {
return c.Value
}
// 3. Accept-Language header
accept := r.Header.Get("Accept-Language")
for _, part := range strings.Split(accept, ",") {
lang := strings.TrimSpace(strings.SplitN(part, ";", 2)[0])
lang = strings.SplitN(lang, "-", 2)[0]
if isSupported(lang) {
return lang
}
}
return "en"
}
func isSupported(lang string) bool {
for _, l := range supportedLangs {
if l == lang {
return true
}
}
return false
}