Improve lang-detection on backend
This commit is contained in:
+1
-1
@@ -101,7 +101,7 @@ func (app *CortezaApp) Setup() (err error) {
|
||||
localeLog = app.Log
|
||||
}
|
||||
|
||||
if languages, err = locale.New(localeLog, strings.Split(app.Opt.Locale.Path, ":")...); err != nil {
|
||||
if languages, err = locale.New(localeLog, app.Opt.Locale); err != nil {
|
||||
return err
|
||||
} else {
|
||||
locale.SetGlobal(languages)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="{{ language }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -12,9 +12,7 @@
|
||||
<!-- Nunito font -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600&display=swap" rel="stylesheet">
|
||||
<title>
|
||||
Corteza
|
||||
</title>
|
||||
<title>Corteza</title>
|
||||
</head>
|
||||
<body style="background: url({{ links.Assets }}/background.jpeg) no-repeat top;background-size: cover;background-attachment: fixed;">
|
||||
{{ if .user }}
|
||||
|
||||
@@ -232,8 +232,12 @@ func (h *AuthHandlers) handle(fn handlerFn) http.HandlerFunc {
|
||||
|
||||
var (
|
||||
// translator template function
|
||||
//
|
||||
// we're adding these functions on every request on all templates
|
||||
// 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 loc.Current(req.Context()).String() },
|
||||
"tr": func(key string, pp ...interface{}) template.HTML {
|
||||
ss := make([]string, len(pp))
|
||||
for i := range pp {
|
||||
|
||||
+43
-20
@@ -3,6 +3,7 @@ package locale
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
@@ -11,34 +12,56 @@ const AcceptLanguageHeader = "Accept-Language"
|
||||
func DetectLanguage(ll *Languages) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// parse & ignore errors
|
||||
var (
|
||||
preferred = ll.Default()
|
||||
supported = ll.Tags()
|
||||
|
||||
accepted, _, err = language.ParseAcceptLanguage(r.Header.Get(AcceptLanguageHeader))
|
||||
rawLanguageTag string
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
// ignoring index & confidence
|
||||
preferred, _, _ = language.NewMatcher(supported).Match(accepted...)
|
||||
// try to detect the language from the request's query string:
|
||||
if ll.Options().QueryStringParam != "" {
|
||||
rawLanguageTag = r.URL.Query().Get(ll.Options().QueryStringParam)
|
||||
}
|
||||
|
||||
var match bool
|
||||
for _, s := range supported {
|
||||
if s == preferred {
|
||||
match = true
|
||||
break
|
||||
// try to detect the language from the request's headers:
|
||||
if len(rawLanguageTag) == 0 {
|
||||
rawLanguageTag = r.Header.Get(AcceptLanguageHeader)
|
||||
}
|
||||
|
||||
if len(rawLanguageTag) > 0 {
|
||||
// parse & ignore errors
|
||||
var (
|
||||
preferred = ll.Default()
|
||||
supported = ll.Tags()
|
||||
|
||||
accepted, _, err = language.ParseAcceptLanguage(rawLanguageTag)
|
||||
)
|
||||
|
||||
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 !match {
|
||||
base, _ := preferred.Base()
|
||||
preferred = language.MustParse(base.String())
|
||||
}
|
||||
}
|
||||
// new request with new context
|
||||
r = r.WithContext(SetLanguageToContext(r.Context(), preferred))
|
||||
|
||||
// new request with new context
|
||||
r = r.WithContext(SetLanguageToContext(r.Context(), preferred))
|
||||
ll.log.Debug(
|
||||
"language detected",
|
||||
zap.String("preferred", preferred.String()),
|
||||
zap.String("raw", rawLanguageTag),
|
||||
)
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
+17
-2
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
@@ -45,6 +46,8 @@ type (
|
||||
|
||||
log *zap.Logger
|
||||
|
||||
opt options.LocaleOpt
|
||||
|
||||
// sources
|
||||
src []string
|
||||
ll map[language.Tag]*Language
|
||||
@@ -56,8 +59,12 @@ type (
|
||||
ErrorMetaKey struct{}
|
||||
)
|
||||
|
||||
func New(log *zap.Logger, src ...string) (*Languages, error) {
|
||||
ll := &Languages{src: src, log: log.Named("locale")}
|
||||
func New(log *zap.Logger, opt options.LocaleOpt) (*Languages, error) {
|
||||
ll := &Languages{
|
||||
opt: opt,
|
||||
src: strings.Split(opt.Path, ":"),
|
||||
log: log.Named("locale"),
|
||||
}
|
||||
return ll, ll.Reload()
|
||||
}
|
||||
|
||||
@@ -65,6 +72,10 @@ func (set *Languages) Default() language.Tag {
|
||||
return set.def
|
||||
}
|
||||
|
||||
func (set *Languages) Options() options.LocaleOpt {
|
||||
return set.opt
|
||||
}
|
||||
|
||||
func (set *Languages) Tags() (tt []language.Tag) {
|
||||
tt = make([]language.Tag, 0, len(set.ll))
|
||||
for t := range set.ll {
|
||||
@@ -144,6 +155,10 @@ func (set *Languages) GetNS(ctx context.Context, ns string) func(key string, rr
|
||||
}
|
||||
}
|
||||
|
||||
func (set *Languages) Current(ctx context.Context) language.Tag {
|
||||
return GetLanguageFromContext(ctx)
|
||||
}
|
||||
|
||||
func (set *Languages) Get(ctx context.Context, ns, key string, rr ...string) string {
|
||||
return set.get(GetLanguageFromContext(ctx), ns, key, rr...)
|
||||
}
|
||||
|
||||
Generated
+6
-3
@@ -10,14 +10,17 @@ package options
|
||||
|
||||
type (
|
||||
LocaleOpt struct {
|
||||
Path string `env:"LOCALE_PATH"`
|
||||
Log bool `env:"LOCALE_LOG"`
|
||||
Path string `env:"LOCALE_PATH"`
|
||||
QueryStringParam string `env:"LOCALE_QUERY_STRING_PARAM"`
|
||||
Log bool `env:"LOCALE_LOG"`
|
||||
}
|
||||
)
|
||||
|
||||
// Locale initializes and returns a LocaleOpt with default values
|
||||
func Locale() (o *LocaleOpt) {
|
||||
o = &LocaleOpt{}
|
||||
o = &LocaleOpt{
|
||||
QueryStringParam: "lng",
|
||||
}
|
||||
|
||||
fill(o)
|
||||
|
||||
|
||||
@@ -2,6 +2,13 @@ props:
|
||||
- name: path
|
||||
description: Path to locale config and translation files, separated by colon
|
||||
|
||||
- name: queryStringParam
|
||||
description: |
|
||||
Name of the query string parameter used to pass the language tag (it overrides Accept-Language header).
|
||||
Set it to empty string to disable detection from the query string.
|
||||
This parameter is ignored if only one language is enabled
|
||||
default: "lng"
|
||||
|
||||
- name: log
|
||||
type: bool
|
||||
description: Log locale related events and actions
|
||||
|
||||
Reference in New Issue
Block a user