internal/server/i18n.go
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: Apache-2.0
package server
import (
"log"
"net/http"
"sync"
"github.com/X4Applegate/caddyui/internal/i18n"
"github.com/X4Applegate/caddyui/internal/models"
"github.com/X4Applegate/caddyui/web"
)
// v2.62.0 (issue #128): localization plumbing. See internal/i18n for the catalog
// format; this file picks each request's language and exposes the translate
// helper to the templates.
// settingDefaultLocale is the site-wide fallback language ("" = none).
const settingDefaultLocale = "default_locale"
var (
i18nOnce sync.Once
i18nBundle *i18n.Bundle
)
// translations returns the catalogs embedded in the binary. A broken catalog is
// a build mistake (the tests load every one), so it is logged and the app keeps
// running in English rather than refusing to start.
func translations() *i18n.Bundle {
i18nOnce.Do(func() {
b, err := i18n.Load(web.FS, "i18n")
if err != nil {
log.Printf("i18n: %v — falling back to built-in English keys", err)
b, _ = i18n.LoadFallback()
}
i18nBundle = b
})
return i18nBundle
}
// tFunc is the template helper: {{t $ "nav.proxy_hosts"}} — or with
// placeholders, {{t $ "x.count" "count" 3}}. The first argument is the page's
// root data (which carries "Lang") or a locale code; anything else means English.
func tFunc(ctx any, key string, args ...any) string {
lang := i18n.Default
switch v := ctx.(type) {
case map[string]any:
if l, ok := v["Lang"].(string); ok && l != "" {
lang = l
}
case string:
if v != "" {
lang = v
}
}
return translations().T(lang, key, args...)
}
// requestLocale picks the language for a request: the signed-in user's own
// choice, then the site default an administrator set, then the browser's
// Accept-Language, then English. Only languages with a catalog are returned.
//
// v2.62.2: the site default used to come AFTER the browser, so for anyone whose
// browser asks for English (which always exists) it never applied — choosing a
// default language in Settings visibly did nothing. An explicit default now
// wins over the browser; leaving it on "follow each browser" keeps the browser.
func (s *Server) requestLocale(r *http.Request, u *models.User) string {
b := translations()
if u != nil && u.Locale != "" && b.Has(u.Locale) {
return u.Locale
}
if s != nil && s.DB != nil {
if d := mustGetSetting(s.DB, settingDefaultLocale); d != "" && b.Has(d) {
return d
}
}
if r != nil {
if l := b.Match(i18n.ParseAcceptLanguage(r.Header.Get("Accept-Language"))...); l != "" {
return l
}
}
return i18n.Default
}
// tr translates key for the request's language — for messages built in Go,
// such as the flash and error texts handed to a page through a redirect.
func (s *Server) tr(r *http.Request, key string, args ...any) string {
return translations().T(s.requestLocale(r, s.currentUser(r)), key, args...)
}