From 4276000add9c3887fd911cb384866bc7a125252c Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Wed, 15 Sep 2021 20:23:44 +0200 Subject: [PATCH] Add support for decoding content-language header from request --- auth/handlers/handler.go | 2 +- compose/service/module.go | 4 ++-- compose/service/namespace.go | 2 +- compose/service/page.go | 6 +++--- pkg/locale/context.go | 21 ++++++++++++++++++--- pkg/locale/http.go | 36 ++++++++++++++++++++++++++++++++---- pkg/locale/service.go | 8 ++++---- 7 files changed, 61 insertions(+), 18 deletions(-) diff --git a/auth/handlers/handler.go b/auth/handlers/handler.go index e5e0a49083..61bc741fb9 100644 --- a/auth/handlers/handler.go +++ b/auth/handlers/handler.go @@ -239,7 +239,7 @@ func (h *AuthHandlers) handle(fn handlerFn) http.HandlerFunc { // because we need request's context to detect the language from! ttf = func(t *template.Template) *template.Template { return t.Funcs(map[string]interface{}{ - "language": func() string { return locale.GetLanguageFromContext(req.Context()).String() }, + "language": func() string { return locale.GetAcceptLanguageFromContext(req.Context()).String() }, "tr": func(key string, pp ...interface{}) template.HTML { ss := make([]string, len(pp)) for i := range pp { diff --git a/compose/service/module.go b/compose/service/module.go index 74956fc582..159914e7b0 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -131,7 +131,7 @@ func (svc module) Find(ctx context.Context, filter types.ModuleFilter) (set type } // i18n - tag := locale.GetLanguageFromContext(ctx) + tag := locale.GetAcceptLanguageFromContext(ctx) set.Walk(func(m *types.Module) error { m.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, m.ResourceTranslation())) @@ -353,7 +353,7 @@ func (svc module) updater(ctx context.Context, namespaceID, moduleID uint64, act tt = append(tt, f.EncodeTranslations()...) } - tt.SetLanguage(locale.GetLanguageFromContext(ctx)) + tt.SetLanguage(locale.GetAcceptLanguageFromContext(ctx)) err = svc.locale.Upsert(ctx, tt) if err != nil { return err diff --git a/compose/service/namespace.go b/compose/service/namespace.go index 6db47a33a7..9b98abb7e6 100644 --- a/compose/service/namespace.go +++ b/compose/service/namespace.go @@ -110,7 +110,7 @@ func (svc namespace) Find(ctx context.Context, filter types.NamespaceFilter) (se } // i18n - tag := locale.GetLanguageFromContext(ctx) + tag := locale.GetAcceptLanguageFromContext(ctx) set.Walk(func(n *types.Namespace) error { n.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, n.ResourceTranslation())) return nil diff --git a/compose/service/page.go b/compose/service/page.go index 1550667ef8..b8cdebe640 100644 --- a/compose/service/page.go +++ b/compose/service/page.go @@ -144,7 +144,7 @@ func (svc page) search(ctx context.Context, filter types.PageFilter) (set types. } // i18n - tag := locale.GetLanguageFromContext(ctx) + tag := locale.GetAcceptLanguageFromContext(ctx) set.Walk(func(p *types.Page) error { p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, p.ResourceTranslation())) return nil @@ -362,7 +362,7 @@ func (svc page) updater(ctx context.Context, namespaceID, pageID uint64, action // i18n tt := p.EncodeTranslations() - tt.SetLanguage(locale.GetLanguageFromContext(ctx)) + tt.SetLanguage(locale.GetAcceptLanguageFromContext(ctx)) err = svc.locale.Upsert(ctx, tt) if err != nil { return err @@ -403,7 +403,7 @@ func (svc page) lookup(ctx context.Context, namespaceID uint64, lookup func(*pag return err } - p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(locale.GetLanguageFromContext(ctx), p.ResourceTranslation())) + p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(locale.GetAcceptLanguageFromContext(ctx), p.ResourceTranslation())) aProps.setPage(p) diff --git a/pkg/locale/context.go b/pkg/locale/context.go index 5e5e80d039..be4b4bf4e8 100644 --- a/pkg/locale/context.go +++ b/pkg/locale/context.go @@ -6,15 +6,30 @@ import ( "golang.org/x/text/language" ) -func SetLanguageToContext(ctx context.Context, code language.Tag) context.Context { +func SetAcceptLanguageToContext(ctx context.Context, code language.Tag) context.Context { return context.WithValue(ctx, language.Tag{}, code) } -// GetLanguageFromContext always returns language, either valid or default -func GetLanguageFromContext(ctx context.Context) language.Tag { +// GetAcceptLanguageFromContext always returns language, either valid or default +func GetAcceptLanguageFromContext(ctx context.Context) language.Tag { if tag, ok := ctx.Value(language.Tag{}).(language.Tag); ok { return tag } else { return defaultLanguage } } + +type contentLanguageCtx struct{} + +func SetContentLanguageToContext(ctx context.Context, code language.Tag) context.Context { + return context.WithValue(ctx, contentLanguageCtx{}, code) +} + +// GetContentLanguageFromContext always returns language, either valid or default +func GetContentLanguageFromContext(ctx context.Context) language.Tag { + if tag, ok := ctx.Value(contentLanguageCtx{}).(language.Tag); ok { + return tag + } else { + return language.Und + } +} diff --git a/pkg/locale/http.go b/pkg/locale/http.go index a3c8bd84c1..3a269d4d1a 100644 --- a/pkg/locale/http.go +++ b/pkg/locale/http.go @@ -8,20 +8,48 @@ import ( ) const AcceptLanguageHeader = "Accept-Language" +const ContentLanguageHeader = "Content-Language" func DetectLanguage(ll *service) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - next.ServeHTTP(w, upgradeRequest(ll, r)) + var ctx = r.Context() + + // resolve accept-language header + ctx = SetAcceptLanguageToContext(ctx, resolveAcceptLanguageHeaders(ll, r)) + + // resolve content-language header + ctx = SetContentLanguageToContext(ctx, resolveContentLanguageHeaders(r.Header, ll.Default().Tag)) + + next.ServeHTTP(w, r.WithContext(ctx)) }) } } -func upgradeRequest(ll *service, r *http.Request) *http.Request { - return r.WithContext(SetLanguageToContext(r.Context(), detectLanguage(ll, r))) +// reads Content-Language headers from the request and returns +// parsed value as language.Tag +// +// There are 4 valid scenarios for : +// - lang == 'skip': returns und & services will (likely) ignore all translatable content +// - invalid language: (same as skip) +// - valid language: returns valid language; services will treat translatable content from the payload as translations +// - no header: returns default language; (same as valid language) +func resolveContentLanguageHeaders(h http.Header, def language.Tag) language.Tag { + var cLang = h.Get(ContentLanguageHeader) + + if cLang == "skip" || cLang == "" { + // more than 1 header or value equal to skip + return language.Und + } + + if tag, err := language.Parse(cLang); err != nil { + return language.Und + } else { + return tag + } } -func detectLanguage(ll *service, r *http.Request) (tag language.Tag) { +func resolveAcceptLanguageHeaders(ll *service, r *http.Request) (tag language.Tag) { if ll.opt.DevelopmentMode { if err := ll.ReloadStatic(); err != nil { // when in development mode, refresh languages for every request diff --git a/pkg/locale/service.go b/pkg/locale/service.go index 08b4d6430f..f1b3a0606a 100644 --- a/pkg/locale/service.go +++ b/pkg/locale/service.go @@ -302,7 +302,7 @@ func (svc *service) LocalizedList(ctx context.Context) []*Language { var ( l *Language ll = make([]*Language, 0, len(svc.set)) - reqLang = GetLanguageFromContext(ctx) + reqLang = GetAcceptLanguageFromContext(ctx) ) for _, tag := range svc.tags { @@ -365,7 +365,7 @@ func (svc *service) EncodeExternal(w io.Writer, app string, ll ...language.Tag) // Language is picked from the context func (svc *service) NS(ctx context.Context, ns string) func(key string, rr ...string) string { var ( - tag = GetLanguageFromContext(ctx) + tag = GetAcceptLanguageFromContext(ctx) ) return func(key string, rr ...string) string { @@ -377,7 +377,7 @@ func (svc *service) NS(ctx context.Context, ns string) func(key string, rr ...st // // Language is picked from the context func (svc *service) T(ctx context.Context, ns, key string, rr ...string) string { - return svc.t(GetLanguageFromContext(ctx), ns, key, rr...) + return svc.t(GetAcceptLanguageFromContext(ctx), ns, key, rr...) } // T returns translated key from namespaces using list of replacement pairs @@ -392,7 +392,7 @@ func (svc *service) TFor(tag language.Tag, ns, key string, rr ...string) string // Language is picked from the context func (svc *service) TResource(ctx context.Context, ns, key string, rr ...string) string { - return svc.tResource(GetLanguageFromContext(ctx), ns, key, rr...) + return svc.tResource(GetAcceptLanguageFromContext(ctx), ns, key, rr...) } // TResourceFor returns translated key for resource using list of replacement pairs