Skip to content

Commit

Permalink
Add support for decoding content-language header from request
Browse files Browse the repository at this point in the history
  • Loading branch information
darh authored and tjerman committed Sep 22, 2021
1 parent 1e02164 commit 4276000
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 18 deletions.
2 changes: 1 addition & 1 deletion auth/handlers/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions compose/service/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compose/service/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions compose/service/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
21 changes: 18 additions & 3 deletions pkg/locale/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
36 changes: 32 additions & 4 deletions pkg/locale/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions pkg/locale/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 4276000

Please sign in to comment.