From 19cfae39af4e7cbb8a0719e64d619ba0248f9d76 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Tue, 12 May 2026 17:12:07 +0200 Subject: ki18n: derive language from request context via strategy chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace Tr(lang, ...) with Tr(ctx, ...) — language is now resolved once per request by LangMiddleware and stored in context. Callers no longer need to thread a lang string through to templates. Adds Strategy interface with CookieStrategy and AcceptLanguageStrategy built-ins; middleware chains them in order, falling back to en-GB. Co-Authored-By: Claude Sonnet 4.6 --- ki18n/ki18n.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 4 deletions(-) (limited to 'ki18n/ki18n.go') diff --git a/ki18n/ki18n.go b/ki18n/ki18n.go index f2bed8d..8f20ad4 100644 --- a/ki18n/ki18n.go +++ b/ki18n/ki18n.go @@ -1,12 +1,81 @@ package ki18n import ( + "context" "io/fs" + "net/http" "github.com/a-h/templ" "github.com/kataras/i18n" ) +type contextKey struct{} + +const defaultLang = "en-GB" + +type Strategy interface { + Detect(r *http.Request) string +} + +type cookieStrategy struct{ name string } + +func (s cookieStrategy) Detect(r *http.Request) string { + c, err := r.Cookie(s.name) + if err != nil { + return "" + } + return c.Value +} + +func CookieStrategy(name string) Strategy { + return cookieStrategy{name: name} +} + +type acceptLanguageStrategy struct{} + +func (acceptLanguageStrategy) Detect(r *http.Request) string { + lang := i18n.Default.GetLocale(r).Language() + if lang == defaultLang { + if r.Header.Get("Accept-Language") == "" { + return "" + } + } + return lang +} + +var AcceptLanguageStrategy Strategy = acceptLanguageStrategy{} + +func LangMiddleware(strategies ...Strategy) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lang := "" + for _, s := range strategies { + if l := s.Detect(r); l != "" { + lang = l + break + } + } + if lang == "" { + lang = defaultLang + } + ctx := context.WithValue(r.Context(), contextKey{}, lang) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +func Tr(ctx context.Context, format string, args ...any) templ.Component { + lang, _ := ctx.Value(contextKey{}).(string) + if lang == "" { + lang = defaultLang + } + msg := i18n.Tr(lang, format, args...) + if msg == "" { + msg = format + } + return templ.Raw(msg) +} + func Init(fs fs.FS) error { loader, err := i18n.FS(fs, "*/*.yml") if err != nil { @@ -19,7 +88,3 @@ func Init(fs fs.FS) error { i18n.Default = inst return nil } - -func Tr(lang string, format string, args ...any) templ.Component { - return templ.Raw(i18n.Tr(lang, format, args...)) -} -- cgit v1.3