133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package locale
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"go.uber.org/zap"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
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) {
|
|
var ctx = r.Context()
|
|
|
|
// resolve accept-language header
|
|
// Accept-Language specifies the language of the response payload.
|
|
ctx = SetAcceptLanguageToContext(ctx, resolveAcceptLanguageHeaders(r, ll))
|
|
|
|
// resolve content-language header
|
|
// Content-Language specifies the language of the request payload.
|
|
ctx = SetContentLanguageToContext(ctx, resolveContentLanguageHeaders(r.Header, ll))
|
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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, ll *service) language.Tag {
|
|
if ll == nil || ll.Default() == nil {
|
|
return language.Und
|
|
}
|
|
|
|
var cLang = h.Get(ContentLanguageHeader)
|
|
|
|
if cLang == "" {
|
|
return ll.Default().Tag
|
|
}
|
|
|
|
if cLang == "skip" {
|
|
// 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 resolveAcceptLanguageHeaders(r *http.Request, ll *service) (tag language.Tag) {
|
|
if ll == nil || ll.Default() == nil {
|
|
// locale service does not have anything loaded...
|
|
return language.Und
|
|
}
|
|
|
|
if ll.opt.DevelopmentMode {
|
|
if err := ll.ReloadStatic(); err != nil {
|
|
// when in development mode, refresh languages for every request
|
|
ll.log.Error("failed to load locales", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
var (
|
|
raw string
|
|
)
|
|
|
|
// try to detect the language from the request's query string:
|
|
if ll.opt.QueryStringParam != "" {
|
|
raw = r.URL.Query().Get(ll.opt.QueryStringParam)
|
|
}
|
|
|
|
// try to detect the language from the request's headers:
|
|
if len(raw) == 0 {
|
|
raw = r.Header.Get(AcceptLanguageHeader)
|
|
}
|
|
|
|
if len(raw) == 0 {
|
|
// no need for lang detection
|
|
return ll.Default().Tag
|
|
}
|
|
|
|
// parse & ignore errors
|
|
var (
|
|
preferred = ll.Default().Tag
|
|
supported = ll.Tags()
|
|
|
|
accepted, _, err = language.ParseAcceptLanguage(raw)
|
|
)
|
|
|
|
if err == nil {
|
|
// ignoring index & confidence
|
|
preferred, _, _ = language.NewMatcher(supported).Match(accepted...)
|
|
|
|
var match bool
|
|
for _, s := range supported {
|
|
if s == preferred {
|
|
match = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !match {
|
|
base, _ := preferred.Base()
|
|
preferred = language.MustParse(base.String())
|
|
}
|
|
}
|
|
|
|
if ll.opt.DevelopmentMode {
|
|
ll.log.Debug(
|
|
"language detected",
|
|
zap.String("preferred", preferred.String()),
|
|
zap.String("raw", raw),
|
|
)
|
|
}
|
|
|
|
// new request with new context
|
|
return preferred
|
|
|
|
}
|