From 6944223d3591f21f75923ebdde3638790e02878a Mon Sep 17 00:00:00 2001 From: cclohmar Date: Mon, 15 Jun 2026 04:55:10 +0000 Subject: [PATCH] feat(config): locale settings (language/timezone) in global page and config --- install.sh | 3 + src/core/admin/templates/global.templ | 25 +++++++- src/core/admin/templates/global_templ.go | 74 +++++++++++++++++++++++- src/core/admin/ui.go | 10 +++- src/core/config/config.go | 6 ++ src/core/ui/handler.go | 18 +++--- src/main.go | 2 +- 7 files changed, 125 insertions(+), 13 deletions(-) diff --git a/install.sh b/install.sh index e1d8a2f..3780bc3 100755 --- a/install.sh +++ b/install.sh @@ -244,6 +244,9 @@ smtp: imap: host: "${IMAP_HOST}" port: ${IMAP_PORT} +locale: + language: "en" + timezone: "UTC" session: secret: "${SESSION_KEY}" expiry_minutes: 60 diff --git a/src/core/admin/templates/global.templ b/src/core/admin/templates/global.templ index 6e82842..b3143a3 100644 --- a/src/core/admin/templates/global.templ +++ b/src/core/admin/templates/global.templ @@ -1,6 +1,6 @@ package templates -templ GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, nextwksURL, authURL, msg string) { +templ GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, nextwksURL, authURL, lang, tz, msg string) { @Layout("global") {

Global Settings

if msg != "" { @@ -55,6 +55,29 @@ templ GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, +
+

Localization

+
+
+ + +
+
+ + +
+
+
+ } diff --git a/src/core/admin/templates/global_templ.go b/src/core/admin/templates/global_templ.go index 0c676bc..8258867 100644 --- a/src/core/admin/templates/global_templ.go +++ b/src/core/admin/templates/global_templ.go @@ -8,7 +8,7 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, nextwksURL, authURL, msg string) templ.Component { +func GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, nextwksURL, authURL, lang, tz, msg string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -168,7 +168,77 @@ func GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort, if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\">

Localization

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/src/core/admin/ui.go b/src/core/admin/ui.go index 20104bc..2f08a99 100644 --- a/src/core/admin/ui.go +++ b/src/core/admin/ui.go @@ -48,7 +48,8 @@ func (h *Handler) adminGlobal(w http.ResponseWriter, r *http.Request) { cfg := readConfigRaw(h.configPath) component := templates.GlobalSettings( cfg["smtp_host"], cfg["smtp_port"], cfg["smtp_user"], cfg["smtp_pass"], - cfg["imap_host"], cfg["imap_port"], cfg["nextwks_url"], cfg["auth_url"], "", + cfg["imap_host"], cfg["imap_port"], cfg["nextwks_url"], cfg["auth_url"], + cfg["lang"], cfg["tz"], "", ) component.Render(r.Context(), w) } @@ -61,7 +62,8 @@ func (h *Handler) adminGlobalSave(w http.ResponseWriter, r *http.Request) { cfg := readConfigRaw(h.configPath) component := templates.GlobalSettings( cfg["smtp_host"], cfg["smtp_port"], cfg["smtp_user"], cfg["smtp_pass"], - cfg["imap_host"], cfg["imap_port"], cfg["nextwks_url"], cfg["auth_url"], msg, + cfg["imap_host"], cfg["imap_port"], cfg["nextwks_url"], cfg["auth_url"], + cfg["lang"], cfg["tz"], msg, ) component.Render(r.Context(), w) } @@ -88,6 +90,8 @@ func readConfigRaw(path string) rawConfig { cfg["nextwks_url"] = trimSuffix(cfg["nextwks_url"], "/auth/callback") } cfg["auth_url"] = getNested(m, "oidc", "issuer_url") + cfg["lang"] = getNested(m, "locale", "language") + cfg["tz"] = getNested(m, "locale", "timezone") return cfg } @@ -105,6 +109,8 @@ func writeConfigRaw(path string, r *http.Request) string { setNested(m, r.FormValue("smtp_pass"), "smtp", "password") setNested(m, r.FormValue("imap_host"), "imap", "host") setNested(m, r.FormValue("imap_port"), "imap", "port") + setNested(m, r.FormValue("lang"), "locale", "language") + setNested(m, r.FormValue("tz"), "locale", "timezone") nextwks := r.FormValue("nextwks_url") if nextwks != "" { setNested(m, nextwks+"/auth/callback", "oidc", "redirect_url") diff --git a/src/core/config/config.go b/src/core/config/config.go index ecdff42..08cda95 100644 --- a/src/core/config/config.go +++ b/src/core/config/config.go @@ -16,6 +16,7 @@ type Config struct { OIDC OIDCConfig `yaml:"oidc"` SMTP SMTPConfig `yaml:"smtp"` IMAP IMAPConfig `yaml:"imap"` + Locale LocaleConfig `yaml:"locale"` Session SessionConfig `yaml:"session"` } @@ -61,6 +62,11 @@ type IMAPConfig struct { Port int `yaml:"port"` } +type LocaleConfig struct { + Language string `yaml:"language"` + Timezone string `yaml:"timezone"` +} + type SessionConfig struct { Secret string `yaml:"secret"` ExpiryMinutes int `yaml:"expiry_minutes"` diff --git a/src/core/ui/handler.go b/src/core/ui/handler.go index 8f447af..04682e8 100644 --- a/src/core/ui/handler.go +++ b/src/core/ui/handler.go @@ -17,11 +17,12 @@ type PageCtx struct { } type Handler struct { - appDir string + appDir string + defaultLang string } -func NewHandler(appDir string) *Handler { - return &Handler{appDir: appDir} +func NewHandler(appDir string, defaultLang string) *Handler { + return &Handler{appDir: appDir, defaultLang: defaultLang} } func (h *Handler) RegisterRoutes(mux *http.ServeMux, authGate func(http.Handler) http.Handler) { @@ -36,21 +37,24 @@ func (h *Handler) launcherPage(w http.ResponseWriter, r *http.Request) { return } - ctx := getContext(r) + ctx := h.getContext(r) apps := DefaultApps(ctx.Role) component := LauncherPage(ctx, apps) component.Render(r.Context(), w) } -func getContext(r *http.Request) PageCtx { +func (h *Handler) getContext(r *http.Request) PageCtx { userID, _ := auth.GetUserID(r) role, _ := r.Context().Value(auth.ContextRole).(string) if role == "" { role = "user" } - // Detect language: cookie > header > default - lang := "en" + // Detect language: cookie > header > config default + lang := h.defaultLang + if lang == "" { + lang = "en" + } if cookie, err := r.Cookie("lang"); err == nil { lang = cookie.Value } else if al := r.Header.Get("Accept-Language"); al != "" { diff --git a/src/main.go b/src/main.go index 2fa7466..6156ac3 100644 --- a/src/main.go +++ b/src/main.go @@ -98,7 +98,7 @@ func main() { if appDir == "." { appDir = "./" } - uiHandler := ui.NewHandler(appDir) + uiHandler := ui.NewHandler(appDir, cfg.Locale.Language) // Setup HTTP router mux := http.NewServeMux()