aboutsummaryrefslogtreecommitdiff
path: root/ki18n/ki18n.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-05-12 17:12:07 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-05-12 17:12:07 +0200
commit19cfae39af4e7cbb8a0719e64d619ba0248f9d76 (patch)
treeaf289634fac81e78ebe92029062eb13016622b8a /ki18n/ki18n.go
parent783028348a17803ba2e0d2745229d57c0626a464 (diff)
ki18n: derive language from request context via strategy chain
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 <noreply@anthropic.com>
Diffstat (limited to 'ki18n/ki18n.go')
-rw-r--r--ki18n/ki18n.go73
1 files changed, 69 insertions, 4 deletions
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...))
-}