package i18n import ( "context" "embed" "encoding/json" "net/http" "strings" gi18n "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" ) //go:embed locales/*.json var localeFS embed.FS const ( CookieName = "lang" ) type ctxKey string const ( ctxKeyLang ctxKey = "lang" ctxKeyLocalizer ctxKey = "localizer" ) // Bundle initializes the go-i18n bundle and loads embedded locale files. func Bundle() (*gi18n.Bundle, error) { b := gi18n.NewBundle(language.English) b.RegisterUnmarshalFunc("json", json.Unmarshal) // Load embedded message files for _, name := range []string{"locales/en.json", "locales/fr.json"} { data, err := localeFS.ReadFile(name) if err != nil { return nil, err } if _, err := b.ParseMessageFileBytes(data, name); err != nil { return nil, err } } return b, nil } // LanguageFromRequest resolves the preferred language from cookie or Accept-Language. func LanguageFromRequest(r *http.Request) string { // 1. Cookie preference if c, err := r.Cookie(CookieName); err == nil { if c.Value == "fr" { return "fr" } return "en" } // 2. Accept-Language header al := r.Header.Get("Accept-Language") if strings.HasPrefix(strings.ToLower(al), "fr") { return "fr" } return "en" } // LanguageMiddleware injects `lang` and a gi18n.Localizer in the request context. func LanguageMiddleware(bundle *gi18n.Bundle) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lang := LanguageFromRequest(r) loc := gi18n.NewLocalizer(bundle, lang) ctx := context.WithValue(r.Context(), ctxKeyLang, lang) ctx = context.WithValue(ctx, ctxKeyLocalizer, loc) next.ServeHTTP(w, r.WithContext(ctx)) }) } } // FromContext returns (lang, localizer) if present. func FromContext(ctx context.Context) (string, *gi18n.Localizer) { var lang string if v := ctx.Value(ctxKeyLang); v != nil { if s, ok := v.(string); ok { lang = s } } var loc *gi18n.Localizer if v := ctx.Value(ctxKeyLocalizer); v != nil { if l, ok := v.(*gi18n.Localizer); ok { loc = l } } if lang == "" { lang = "en" } if loc == nil { loc = gi18n.NewLocalizer(gi18n.NewBundle(language.English), lang) } return lang, loc } // LangFromContext returns the resolved language code from context. func LangFromContext(ctx context.Context) string { lang, _ := FromContext(ctx) return lang } // Localize returns the localized message for a given message ID. func Localize(ctx context.Context, id string) string { _, loc := FromContext(ctx) msg, err := loc.Localize(&gi18n.LocalizeConfig{MessageID: id}) if err != nil { return id } return msg } // TranslatorFromRequest returns a simple `func(id string) string` based on request context. func TranslatorFromRequest(r *http.Request) func(string) string { _, loc := FromContext(r.Context()) return func(id string) string { msg, err := loc.Localize(&gi18n.LocalizeConfig{MessageID: id}) if err != nil { // Fallback to message id return id } return msg } }