tavern/web/i18n.go

181 lines
4.4 KiB
Go

package web
import (
"fmt"
"html/template"
"strings"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales"
"github.com/go-playground/locales/currency"
ut "github.com/go-playground/universal-translator"
"go.uber.org/zap"
)
type Translator interface {
locales.Translator
T(key interface{}, params ...string) string
THTML(key interface{}, params ...template.HTML) template.HTML
C(key interface{}, num float64, digits uint64, param string) string
O(key interface{}, num float64, digits uint64, param string) string
R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string
Currency() currency.Type
}
type translator struct {
locales.Translator
trans ut.Translator
logger *zap.Logger
enableSentry bool
}
var _ Translator = (*translator)(nil)
func (t *translator) T(key interface{}, params ...string) string {
keyValue := fmt.Sprintf("%s", key)
s, err := t.trans.T(keyValue, params...)
if err != nil {
t.logger.Warn("issue translating key",
zap.String("key", keyValue),
zap.String("locale", t.Locale()),
zap.Error(err))
if t.enableSentry {
hub := sentry.CurrentHub().Clone()
hub.Scope().SetExtras(map[string]interface{}{
"key": keyValue,
"locale": t.Locale(),
"param_count": len(params),
})
hub.CaptureException(err)
}
return keyValue
}
return s
}
func (t *translator) THTML(key interface{}, params ...template.HTML) template.HTML {
var stringParams []string
for _, param := range params {
stringParams = append(stringParams, string(param))
}
s, err := t.trans.T(key, stringParams...)
if err != nil {
t.logger.Warn("issue translating key", zap.String("key", fmt.Sprintf("%v", key)), zap.Error(err))
return template.HTML(fmt.Sprintf("%s", key))
}
return template.HTML(s)
}
func (t *translator) C(key interface{}, num float64, digits uint64, param string) string {
s, err := t.trans.C(key, num, digits, param)
if err != nil {
t.logger.Warn("issue translating cardinal key", zap.String("key", fmt.Sprintf("%v", key)), zap.Error(err))
}
return s
}
func (t *translator) O(key interface{}, num float64, digits uint64, param string) string {
s, err := t.trans.C(key, num, digits, param)
if err != nil {
t.logger.Warn("issue translating ordinal key", zap.String("key", fmt.Sprintf("%v", key)), zap.Error(err))
}
return s
}
func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string {
s, err := t.trans.R(key, num1, digits1, num2, digits2, param1, param2)
if err != nil {
t.logger.Warn("issue translating range key", zap.String("key", fmt.Sprintf("%v", key)), zap.Error(err))
}
return s
}
func (t *translator) Currency() currency.Type {
switch t.Locale() {
case "en":
return currency.USD
case "fr":
return currency.EUR
default:
return currency.USD
}
}
type i18nMiddleware struct {
utrans *ut.UniversalTranslator
logger *zap.Logger
domain string
enableSentry bool
}
func (h i18nMiddleware) findLocale(c *gin.Context) {
var langs []string
setCookie := false
if localeParam := c.Query("lang"); len(localeParam) > 0 {
setCookie = true
langs = append(langs, localeParam)
}
localeCookie, err := c.Cookie("lang")
if err == nil && len(localeCookie) > 0 {
langs = append(langs, localeCookie)
}
langs = append(langs, AcceptedLanguages(c)...)
langs = append(langs, "en")
t, found := h.utrans.FindTranslator(langs...)
if !found {
h.logger.Warn("no translation found for language", zap.Strings("langs", langs))
}
if localeCookie == "" || t.Locale() != localeCookie {
setCookie = true
}
if setCookie {
c.SetCookie("lang", t.Locale(), 0, "/", h.domain, true, true)
}
c.Set("locale", t.Locale())
c.Set("trans", &translator{
trans: t,
Translator: t.(locales.Translator),
logger: h.logger,
enableSentry: h.enableSentry,
})
c.Next()
}
func localeOrDefault(c *gin.Context) string {
ctxValue, ok := c.Get("locale")
if ok {
if locale, ok := ctxValue.(string); ok {
return locale
}
}
return "en"
}
func AcceptedLanguages(c *gin.Context) (languages []string) {
accepted := c.GetHeader("Accept-Language")
if accepted == "" {
return
}
options := strings.Split(accepted, ",")
l := len(options)
languages = make([]string, l)
for i := 0; i < l; i++ {
locale := strings.SplitN(options[i], ";", 2)
languages[i] = strings.Trim(locale[0], " ")
}
return
}