mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
feat: localized messages (#328)
* fix: project by id loads project from view and from eventstore * fix: correct search key for role * feat(auth): my user changes * fix: improve error handling in change converters * fix: log-id * feat(translations): event type translations * feat: localized translations * fix(translations): correct yaml format * chore: example * fix: remove unused code * correct checkSSL in sql * chore(modules): update * chore: refactor interceptors * fix: improvments * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/de.yaml Co-authored-by: Florian Forster <florian@caos.ch> * Update internal/static/i18n/en.yaml Co-authored-by: Florian Forster <florian@caos.ch> * chore(translations): start with upper case on Code * chore(middleware): move funcs * add message to grpc web generation * translation in mgmt and fixes * fix authoptions * fix console statik Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
@@ -2,6 +2,10 @@ package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/pkg/message"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -12,21 +16,26 @@ func CaosToGRPCError(err error, ctx context.Context, translator *i18n.Translator
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
code, msg, id, ok := Extract(err)
|
||||
code, key, id, ok := ExtractCaosError(err)
|
||||
if !ok {
|
||||
return status.Convert(err).Err()
|
||||
}
|
||||
msg := key
|
||||
if translator != nil {
|
||||
msg = translator.LocalizeFromCtx(ctx, msg, nil)
|
||||
msg = msg + "(" + id + ")"
|
||||
msg = translator.LocalizeFromCtx(ctx, key, nil)
|
||||
}
|
||||
return status.Error(code, msg)
|
||||
s, err := status.New(code, key).WithDetails(&message.ErrorDetail{Id: id, Message: msg})
|
||||
if err != nil {
|
||||
logging.Log("GRPC-gIeRw").WithError(err).Debug("unable to add detail")
|
||||
return status.New(code, key).Err()
|
||||
}
|
||||
|
||||
return s.Err()
|
||||
}
|
||||
|
||||
func Extract(err error) (c codes.Code, msg, id string, ok bool) {
|
||||
func ExtractCaosError(err error) (c codes.Code, msg, id string, ok bool) {
|
||||
switch caosErr := err.(type) {
|
||||
case *caos_errs.AlreadyExistsError:
|
||||
|
||||
return codes.AlreadyExists, caosErr.GetMessage(), caosErr.GetID(), true
|
||||
case *caos_errs.DeadlineExceededError:
|
||||
return codes.DeadlineExceeded, caosErr.GetMessage(), caosErr.GetID(), true
|
||||
|
@@ -2,9 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@@ -14,16 +12,10 @@ import (
|
||||
)
|
||||
|
||||
func ErrorHandler(defaultLanguage language.Tag) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
dir, err := fs.NewWithNamespace("zitadel")
|
||||
logging.Log("ERROR-7usEW").OnError(err).Panic("unable to get zitadel namespace")
|
||||
|
||||
i18n, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: defaultLanguage})
|
||||
if err != nil {
|
||||
logging.Log("ERROR-Sk8sf").OnError(err).Panic("unable to get i18n translator")
|
||||
}
|
||||
translator := newZitadelTranslator(defaultLanguage)
|
||||
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
resp, err := handler(ctx, req)
|
||||
return resp, grpc_util.CaosToGRPCError(err, ctx, i18n)
|
||||
return resp, grpc_util.CaosToGRPCError(err, ctx, translator)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
_ "github.com/caos/zitadel/internal/statik"
|
||||
)
|
||||
|
||||
func TranslationHandler(defaultLanguage language.Tag) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
translator := newZitadelTranslator(defaultLanguage)
|
||||
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
resp, err := handler(ctx, req)
|
||||
if loc, ok := resp.(localizers); ok {
|
||||
translateFields(ctx, loc, translator)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
41
internal/api/grpc/server/middleware/translator.go
Normal file
41
internal/api/grpc/server/middleware/translator.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type localizers interface {
|
||||
Localizers() []Localizer
|
||||
}
|
||||
type Localizer interface {
|
||||
LocalizationKey() string
|
||||
SetLocalizedMessage(string)
|
||||
}
|
||||
|
||||
func translateFields(ctx context.Context, object localizers, translator *i18n.Translator) {
|
||||
if translator == nil || object == nil {
|
||||
return
|
||||
}
|
||||
for _, field := range object.Localizers() {
|
||||
field.SetLocalizedMessage(translator.LocalizeFromCtx(ctx, field.LocalizationKey(), nil))
|
||||
}
|
||||
}
|
||||
|
||||
func newZitadelTranslator(defaultLanguage language.Tag) *i18n.Translator {
|
||||
return translatorFromNamespace("zitadel", defaultLanguage)
|
||||
}
|
||||
|
||||
func translatorFromNamespace(namespace string, defaultLanguage language.Tag) *i18n.Translator {
|
||||
dir, err := fs.NewWithNamespace(namespace)
|
||||
logging.LogWithFields("ERROR-7usEW", "namespace", namespace).OnError(err).Panic("unable to get namespace")
|
||||
|
||||
translator, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: defaultLanguage})
|
||||
logging.Log("ERROR-Sk8sf").OnError(err).Panic("unable to get i18n translator")
|
||||
|
||||
return translator
|
||||
}
|
@@ -14,14 +14,14 @@ const (
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
Templates map[string]*template.Template
|
||||
i18n *Translator
|
||||
Templates map[string]*template.Template
|
||||
translator *Translator
|
||||
}
|
||||
|
||||
func NewRenderer(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}, translatorConfig TranslatorConfig) (*Renderer, error) {
|
||||
var err error
|
||||
r := new(Renderer)
|
||||
r.i18n, err = NewTranslator(translatorConfig)
|
||||
r.translator, err = NewTranslator(translatorConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -36,14 +36,14 @@ func (r *Renderer) RenderTemplate(w http.ResponseWriter, req *http.Request, tmpl
|
||||
}
|
||||
|
||||
func (r *Renderer) Localize(id string, args map[string]interface{}) string {
|
||||
return r.i18n.Localize(id, args)
|
||||
return r.translator.Localize(id, args)
|
||||
}
|
||||
|
||||
func (r *Renderer) LocalizeFromRequest(req *http.Request, id string, args map[string]interface{}) string {
|
||||
return r.i18n.LocalizeFromRequest(req, id, args)
|
||||
return r.translator.LocalizeFromRequest(req, id, args)
|
||||
}
|
||||
func (r *Renderer) Lang(req *http.Request) language.Tag {
|
||||
return r.i18n.Lang(req)
|
||||
return r.translator.Lang(req)
|
||||
}
|
||||
|
||||
func (r *Renderer) loadTemplates(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}) {
|
||||
|
Reference in New Issue
Block a user