feat(config): locale settings (language/timezone) in global page and config
This commit is contained in:
parent
5663364b12
commit
6944223d35
7 changed files with 125 additions and 13 deletions
|
|
@ -244,6 +244,9 @@ smtp:
|
|||
imap:
|
||||
host: "${IMAP_HOST}"
|
||||
port: ${IMAP_PORT}
|
||||
locale:
|
||||
language: "en"
|
||||
timezone: "UTC"
|
||||
session:
|
||||
secret: "${SESSION_KEY}"
|
||||
expiry_minutes: 60
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
<h2 style="font-size:1.15rem;font-weight:600;margin-bottom:20px">Global Settings</h2>
|
||||
if msg != "" {
|
||||
|
|
@ -55,6 +55,29 @@ templ GlobalSettings(smtpHost, smtpPort, smtpUser, smtpPass, imapHost, imapPort,
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<h3 style="font-size:0.95rem;font-weight:600;margin-bottom:12px">Localization</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Default Language</label>
|
||||
<select name="lang" class="input">
|
||||
<option value="en" if lang == "en" { selected }>English</option>
|
||||
<option value="de" if lang == "de" { selected }>Deutsch</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Default Timezone</label>
|
||||
<select name="tz" class="input">
|
||||
<option value="UTC" if tz == "UTC" { selected }>UTC</option>
|
||||
<option value="Europe/London" if tz == "Europe/London" { selected }>London</option>
|
||||
<option value="Europe/Berlin" if tz == "Europe/Berlin" { selected }>Berlin</option>
|
||||
<option value="Europe/Paris" if tz == "Europe/Paris" { selected }>Paris</option>
|
||||
<option value="America/New_York" if tz == "America/New_York" { selected }>New York</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" style="width:100%">Save Settings</button>
|
||||
</form>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "\"></div></div></div><button type=\"submit\" class=\"btn\" style=\"width:100%\">Save Settings</button></form>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"></div></div></div><div class=\"form-card\"><h3 style=\"font-size:0.95rem;font-weight:600;margin-bottom:12px\">Localization</h3><div class=\"form-row\"><div class=\"form-group\"><label>Default Language</label> <select name=\"lang\" class=\"input\"><option value=\"en\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if lang == "en" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ">English</option> <option value=\"de\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if lang == "de" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ">Deutsch</option></select></div><div class=\"form-group\"><label>Default Timezone</label> <select name=\"tz\" class=\"input\"><option value=\"UTC\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if tz == "UTC" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, ">UTC</option> <option value=\"Europe/London\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if tz == "Europe/London" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, ">London</option> <option value=\"Europe/Berlin\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if tz == "Europe/Berlin" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ">Berlin</option> <option value=\"Europe/Paris\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if tz == "Europe/Paris" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, ">Paris</option> <option value=\"America/New_York\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if tz == "America/New_York" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, ">New York</option></select></div></div></div><button type=\"submit\" class=\"btn\" style=\"width:100%\">Save Settings</button></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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 != "" {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue