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:
Silvan
2020-07-08 09:48:11 +02:00
committed by GitHub
parent b863e7c407
commit c0f85c2733
40 changed files with 17383 additions and 18102 deletions

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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
}
}

View 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
}

View File

@@ -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{}) {

View File

@@ -2,12 +2,13 @@ package handler
import (
"context"
"time"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"time"
"github.com/caos/logging"
@@ -120,10 +121,6 @@ func (u *User) ProcessOrg(event *models.Event) (err error) {
default:
return u.view.ProcessedUserSequence(event.Sequence)
}
if err != nil {
return err
}
return nil
}
func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error {

View File

@@ -8,7 +8,7 @@ import (
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/cockroachdb/cockroach-go/crdb"
"github.com/cockroachdb/cockroach-go/v2/crdb"
)
const insertStmt = "INSERT INTO eventstore.events " +

View File

@@ -5,7 +5,7 @@ import (
"database/sql"
"fmt"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/cockroachdb/cockroach-go/crdb"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"time"
)

View File

@@ -2,12 +2,13 @@ package handler
import (
"context"
"time"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"time"
"github.com/caos/logging"
@@ -104,10 +105,6 @@ func (u *User) ProcessOrg(event *models.Event) (err error) {
default:
return u.view.ProcessedUserSequence(event.Sequence)
}
if err != nil {
return err
}
return nil
}
func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error {

View File

@@ -1,6 +1,8 @@
package eventsourcing
import (
"net/http"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
es_int "github.com/caos/zitadel/internal/eventstore"
@@ -12,7 +14,6 @@ import (
es_org "github.com/caos/zitadel/internal/org/repository/eventsourcing"
es_usr "github.com/caos/zitadel/internal/user/repository/eventsourcing"
"golang.org/x/text/language"
"net/http"
)
type Config struct {
@@ -51,12 +52,12 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults) (
}
org := es_org.StartOrg(es_org.OrgConfig{Eventstore: es, IAMDomain: conf.Domain}, systemDefaults)
i18n, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: conf.DefaultLanguage})
translator, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: conf.DefaultLanguage})
if err != nil {
return nil, err
}
eventstoreRepos := handler.EventstoreRepos{UserEvents: user, OrgEvents: org}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, eventstoreRepos, systemDefaults, i18n, dir)
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, eventstoreRepos, systemDefaults, translator, dir)
return &EsRepository{
spool,

View File

@@ -20,14 +20,14 @@ const (
)
type Renderer struct {
Templates map[string]*template.Template
i18n *i18n.Translator
Templates map[string]*template.Template
translator *i18n.Translator
}
func NewRenderer(dir http.FileSystem, tmplMapping map[string]string, funcs map[string]interface{}, translatorConfig i18n.TranslatorConfig) (*Renderer, error) {
var err error
r := new(Renderer)
r.i18n, err = i18n.NewTranslator(dir, translatorConfig)
r.translator, err = i18n.NewTranslator(dir, translatorConfig)
if err != nil {
return nil, err
}
@@ -43,14 +43,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(dir http.FileSystem, tmplMapping map[string]string, funcs map[string]interface{}) error {

View File

@@ -114,4 +114,171 @@ Errors:
Token:
NotFound: Token konnte nicht gefunden werden
UserSession:
NotFound: UserSession konnte nicht gefunden werden
NotFound: Benutzer Sitzung konnte nicht gefunden werden
EventTypes:
user:
added: Benutzer hinzugefügt
selfregistered: Benutzer hat sich selbst registriert
initialization:
code:
added: Initialisierungscode generiert
sent: Initialiseriungscode versendet
check:
succeeded: Benutzerinitialisierung erfolgreich
failed: Benutzerinitialisierung fehlgeschlagen
username:
reserved: Benutzername reserviert
released: Benutzername freigegeben
email:
reserved: E-Mail-Adresse reserviert
released: E-Mail-Adresse freigegeben
changed: E-Mail-Adresse geändert
verified: E-Mail-Adresse verifiziert
verification:
failed: Verifikation der E-Mail-Adresse fehlgeschlagen
code:
added: E-Mail Code generiert
sent: E-Mail Code gesendet
locked: Benutzer gesperrt
unlocked: Benutzer entsperrt
deactivated: Benutzer deaktiviert
reactivated: Benutzer reaktiviert
removed: Benutzer entfernt
password:
changed: Passwort geändert
code:
added: Passwort Code generiert
sent: Passwort Code versendet
check:
succeeded: Passwortvalidierung erfolgreich
failed: Passwortvalidierung fehlgeschlagen
phone:
changed: Telefonnummer geändert
verified: Telefonnummer verifiziert
verification:
failed: Verifikation der Telefonnummer fehlgeschlagen
code:
added: Telefon Code hinzugefügt
sent: Telefon Code versendet
profile:
changed: Benutzerprofil geändert
address:
changed: Adresse des Benutzers geändert
mfa:
otp:
added: Multifaktor OTP hinzugefügt
verified: Multifaktor OTP verifiziert
removed: Multifaktor OTP entfernt
check:
succeeded: Multifaktor OTP Verifikation erfolgreich
failed: Multifaktor OTP Verifikation fehlgeschlagen
init:
skipped: Multifaktor Initialisierung übersprungen
signed:
out: Benutzer erfolgreich abgemeldet
grant:
added: Berechtigung hinzugefügt
changed: Berechtigung geändert
removed: Berechtigung entfernt
deactivated: Berechtigung deaktiviert
reactivated: Berechtigung reaktiviert
reserved: Berechtigung reserviert
released: Berechtigung freigegeben
cascade:
removed: Berechtigung entfernt
changed: Berechtigung geändert
org:
added: Organisation hinzugefügt
changed: Organisation geändert
deactivated: Organisation deaktiviert
reactivated: Organisation reaktiviert
removed: Organisation entfernt
domain:
added: Domäne hinzugefügt
verified: Domäne verifiziert
removed: Domäne entfernt
primary:
set: Primäre Domäne gesetzt
name:
reserved: Name der Organisation reserviert
released: Name der Organisation freigegeben
domain:
reserved: Domäne reserviert
released: Domäne freigegeben
member:
added: Organisationsmitglied hinzugefügt
changed: Organisationsmitglied geändert
removed: Organisationsmitglied entfernt
iam:
policy:
added: System Richtlinie der Organisation hinzugefügt
changed: System Richtlinie der Organisation geändert
removed: System Richtlinie der Organisation entfernt
project:
added: Projekt hinzugefügt
changed: Project geändert
deactivated: Projekt deaktiviert
reactivated: Projekt reaktiviert
removed: Projekt entfernt
member:
added: Projektmitglied hinzugefügt
changed: Projektmitglied geändert
removed: Projektmitglied entfernt
role:
added: Projektrolle hinzugefügt
changed: Projektrolle geändert
removed: Projektrolle entfernt
grant:
added: Verwaltungszugriff hinzugefügt
changed: Verwaltungszugriff geändert
removed: Verwaltungszugriff entfernt
deactivated: Verwaltungszugriff deaktiviert
reactivated: Verwaltungszugriff reaktiviert
cascade:
changed: Verwaltungszugriff geändert
member:
added: Verwaltungszugriffsmitglied hinzugefügt
changed: Verwaltungszugriffsmitglied geändert
removed: Verwaltungszugriffsmitglied entfernt
application:
added: Applikation hinzugefügt
changed: Applikation geändert
removed: Applikation entfernt
deactivated: Applikation deaktiviert
reactivated: Applikation reaktiviert
config:
oidc:
added: OIDC Konfiguration hinzugefügt
changed: OIDC Konfiguration geändert
secret:
changed: OIDC Client Secret geändert
check:
succeeded: OIDC Client Secret Validierung erfolgreich
failed: OIDC Client Secret Validierung fehlgeschlagen
policy:
password:
complexity:
added: Passwortkomplexitätsrichtline hinzugefügt
changed: Passwortkomplexitätsrichtline geändert
age:
added: Passwortaltersrichtlinie hinzugefügt
changed: Passwortaltersrichtlinie geändert
lockout:
added: Passwortaussperrrichtlinie hizugefügt
changed: Passwortaussperrrichtlinie geändert
iam:
setup:
started: ZITADEL Initialisierung gestartet
done: ZITADEL Initialisierung abgeschlossen
global:
org:
set: Globale Organisation von ZITADEL gesetzt
project:
iam:
set: ZITADEL Projekt gesetzt
member:
added: ZITADEL Mitglied hinzugefügt
changed: ZITADEL Mitglied geändert
removed: ZITADEL Mitglied entfernt
key_pair:
added: Schlüsselpaar hinzugefügt

View File

@@ -115,3 +115,170 @@ Errors:
NotFound: Token not found
UserSession:
NotFound: UserSession not found
EventTypes:
user:
added: User added
selfregistered: User registered himself
initialization:
code:
added: Initialisation code generated
sent: Initialisation code sent
check:
succeeded: Initialisation check succeded
failed: Initialisation check failed
username:
reserved: Username reserved
released: Username released
email:
reserved: Email address reserved
released: Email address released
changed: Email address changed
verified: Email address verified
verification:
failed: Email address verification failed
code:
added: Email address verification code generated
sent: Email address verification code sent
locked: User locked
unlocked: User unlocked
deactivated: User deactivated
reactivated: User reactivated
removed: User removed
password:
changed: Password changed
code:
added: Password code generated
sent: Password code sent
check:
succeeded: Password check succeeded
failed: Password check failed
phone:
changed: Phone number changed
verified: Phone number verified
verification:
failed: Phone number verification failed
code:
added: Phone number code generated
sent: Phone number code sent
profile:
changed: User profile changed
address:
changed: User address changed
mfa:
otp:
added: Multifactor OTP added
verified: Multifactor OTP verified
removed: Multifactor OTP removed
check:
succeeded: Multifactor OTP check succeeded
failed: Multifactor OTP check failed
init:
skipped: Multifactor initialisation skipped
signed:
out: User signed out
grant:
added: Authorization added
changed: Authorization changed
removed: Authorization removed
deactivated: Authorization deactivated
reactivated: Authorization reactivated
reserved: Authorization reserved
released: Authorization released
cascade:
removed: Authorization removed
changed: Authorization changed
org:
added: Organization added
changed: Organization changed
deactivated: Organization deactivated
reactivated: Organization reactivated
removed: Organization removed
domain:
added: Domain added
verified: Domain verified
removed: Domain removed
primary:
set: Primary domain set
name:
reserved: Organization name reserved
released: Organization name released
domain:
reserved: Domain reserved
released: Domain released
member:
added: Organization member added
changed: Organization member changed
removed: Organization member removed
iam:
policy:
added: System policy added
changed: System policy changed
removed: System policy removed
project:
added: Project added
changed: Project changed
deactivated: Project deactivated
reactivated: Project reactivated
removed: Project removed
member:
added: Project member added
changed: Project member changed
removed: Project member removed
role:
added: Project role added
changed: Project role changed
removed: Project role removed
grant:
added: Management access added
changed: Management access changed
removed: Management access removed
deactivated: Management access deactivated
reactivated: Management access reactivated
cascade:
changed: Management access changed
member:
added: Management access member added
changed: Management access member changed
removed: Management access member removed
application:
added: Application added
changed: Application changed
removed: Application removed
deactivated: Application deactivated
reactivated: Application reactivated
config:
oidc:
added: OIDC Configuration added
changed: OIDC Configuration chnaged
secret:
changed: OIDC secret changed
check:
succeeded: OIDC secret check succeeded
failed: OIDC secret check failed
policy:
password:
complexity:
added: Password complexity policy added
changed: Password complexity policy changed
age:
added: Password age policy added
changed: Password age policy changed
lockout:
added: Password lockout policy added
changed: Password lockout policy changed
iam:
setup:
started: ZITADEL setup started
done: ZITADEL setup done
global:
org:
set: Global org set
project:
iam:
set: ZITADEL project set
member:
added: ZITADEL member added
changed: ZITADEL member changed
removed: ZITADEL member removed
key_pair:
added: Key pair added

View File

@@ -40,7 +40,7 @@ func (s *Span) SetStatusByError(err error) {
}
func statusFromError(err error) trace.Status {
code, msg, _, _ := grpc.Extract(err)
code, msg, _, _ := grpc.ExtractCaosError(err)
return trace.Status{Code: int32(code), Message: msg}
}