3
0

Improve lang-detection on backend

This commit is contained in:
Denis Arh
2021-08-25 16:10:53 +02:00
parent e4a59a5f03
commit 04d274fb70
7 changed files with 80 additions and 30 deletions
+1 -1
View File
@@ -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)
+2 -4
View File
@@ -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 }}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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...)
}
+6 -3
View File
@@ -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)
+7
View File
@@ -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