diff --git a/cmd/admin/setup/03.go b/cmd/admin/setup/03.go
index 0cef02597..36a0fcbc2 100644
--- a/cmd/admin/setup/03.go
+++ b/cmd/admin/setup/03.go
@@ -6,6 +6,8 @@ import (
"fmt"
"strings"
+ "golang.org/x/text/language"
+
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
@@ -15,9 +17,10 @@ import (
)
type DefaultInstance struct {
- InstanceName string
- CustomDomain string
- Org command.OrgSetup
+ InstanceName string
+ CustomDomain string
+ DefaultLanguage language.Tag
+ Org command.OrgSetup
instanceSetup command.InstanceSetup
userEncryptionKey *crypto.KeyConfig
@@ -67,6 +70,7 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
mig.instanceSetup.InstanceName = mig.InstanceName
mig.instanceSetup.CustomDomain = mig.CustomDomain
+ mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage
mig.instanceSetup.Org = mig.Org
mig.instanceSetup.Org.Human.Email.Address = strings.TrimSpace(mig.instanceSetup.Org.Human.Email.Address)
if mig.instanceSetup.Org.Human.Email.Address == "" {
diff --git a/cmd/admin/setup/steps.yaml b/cmd/admin/setup/steps.yaml
index e81ce6ac7..b9e15dff3 100644
--- a/cmd/admin/setup/steps.yaml
+++ b/cmd/admin/setup/steps.yaml
@@ -1,6 +1,7 @@
S3DefaultInstance:
InstanceName: Localhost
CustomDomain: localhost
+ DefaultLanguage: en
Org:
Name: ZITADEL
Human:
diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml
index 71ce49ed8..98aad55b5 100644
--- a/cmd/defaults.yaml
+++ b/cmd/defaults.yaml
@@ -162,6 +162,7 @@ SystemDefaults:
DefaultInstance:
InstanceName:
+ DefaultLanguage: en
Org:
Name:
Human:
diff --git a/docs/docs/apis/proto/system.md b/docs/docs/apis/proto/system.md
index 83207df65..2d75c73df 100644
--- a/docs/docs/apis/proto/system.md
+++ b/docs/docs/apis/proto/system.md
@@ -233,6 +233,7 @@ failed event. You can find out if it worked on the `failure_count`
| owner_email | AddInstanceRequest.Email | - | message.required: true
|
| owner_profile | AddInstanceRequest.Profile | - | message.required: false
|
| owner_password | AddInstanceRequest.Password | - | message.required: false
|
+| default_language | string | - | string.max_len: 10
|
diff --git a/internal/api/grpc/admin/language.go b/internal/api/grpc/admin/language.go
index 6e2946399..73924a401 100644
--- a/internal/api/grpc/admin/language.go
+++ b/internal/api/grpc/admin/language.go
@@ -6,6 +6,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
+ "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/api/grpc/text"
caos_errors "github.com/zitadel/zitadel/internal/errors"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
@@ -20,19 +21,17 @@ func (s *Server) GetSupportedLanguages(ctx context.Context, req *admin_pb.GetSup
}
func (s *Server) SetDefaultLanguage(ctx context.Context, req *admin_pb.SetDefaultLanguageRequest) (*admin_pb.SetDefaultLanguageResponse, error) {
- _, err := language.Parse(req.Language)
+ lang, err := language.Parse(req.Language)
if err != nil {
return nil, caos_errors.ThrowInvalidArgument(err, "API-39nnf", "Errors.Language.Parse")
}
- //TODO: Will be added by silvan
- //details, err := s.command.SetDefaultLanguage(ctx, lang)
- //if err != nil {
- // return nil, err
- //}
- //return &admin_pb.SetDefaultLanguageResponse{
- // Details: object.DomainToChangeDetailsPb(details),
- //}, nil
- return nil, nil
+ details, err := s.command.SetDefaultLanguage(ctx, lang)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultLanguageResponse{
+ Details: object.DomainToChangeDetailsPb(details),
+ }, nil
}
func (s *Server) GetDefaultLanguage(ctx context.Context, _ *admin_pb.GetDefaultLanguageRequest) (*admin_pb.GetDefaultLanguageResponse, error) {
diff --git a/internal/api/grpc/system/instance_converter.go b/internal/api/grpc/system/instance_converter.go
index 6728b0eb4..fb812b62f 100644
--- a/internal/api/grpc/system/instance_converter.go
+++ b/internal/api/grpc/system/instance_converter.go
@@ -47,6 +47,9 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
defaultInstance.Org.Human.Password = req.OwnerPassword.Password
defaultInstance.Org.Human.PasswordChangeRequired = req.OwnerPassword.PasswordChangeRequired
}
+ if lang := language.Make(req.DefaultLanguage); lang != language.Und {
+ defaultInstance.DefaultLanguage = lang
+ }
return &defaultInstance
}
diff --git a/internal/command/instance.go b/internal/command/instance.go
index a1239bc6d..6adcfd886 100644
--- a/internal/command/instance.go
+++ b/internal/command/instance.go
@@ -4,6 +4,8 @@ import (
"context"
"time"
+ "golang.org/x/text/language"
+
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/ui/console"
"github.com/zitadel/zitadel/internal/command/preparation"
@@ -32,6 +34,7 @@ type InstanceSetup struct {
zitadel ZitadelConfig
InstanceName string
CustomDomain string
+ DefaultLanguage language.Tag
Org OrgSetup
SecretGenerators struct {
PasswordSaltCost uint
@@ -166,7 +169,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
projectAgg := project.NewAggregate(setup.zitadel.projectID, orgID)
validations := []preparation.Validation{
- addInstance(instanceAgg, setup.InstanceName),
+ addInstance(instanceAgg, setup.InstanceName, setup.DefaultLanguage),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeAppSecret, setup.SecretGenerators.ClientSecret),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeInitCode, setup.SecretGenerators.InitializeUserCode),
addSecretGeneratorConfig(instanceAgg, domain.SecretGeneratorTypeVerifyEmailCode, setup.SecretGenerators.EmailVerificationCode),
@@ -332,11 +335,30 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
}, nil
}
-func addInstance(a *instance.Aggregate, instanceName string) preparation.Validation {
+func (c *Commands) SetDefaultLanguage(ctx context.Context, defaultLanguage language.Tag) (*domain.ObjectDetails, error) {
+ instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
+ validation := c.prepareSetDefaultLanguage(instanceAgg, defaultLanguage)
+ cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
+ if err != nil {
+ return nil, err
+ }
+ events, err := c.eventstore.Push(ctx, cmds...)
+ if err != nil {
+ return nil, err
+ }
+ return &domain.ObjectDetails{
+ Sequence: events[len(events)-1].Sequence(),
+ EventDate: events[len(events)-1].CreationDate(),
+ ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
+ }, nil
+}
+
+func addInstance(a *instance.Aggregate, instanceName string, defaultLanguage language.Tag) preparation.Validation {
return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
instance.NewInstanceAddedEvent(ctx, &a.Aggregate, instanceName),
+ instance.NewDefaultLanguageSetEvent(ctx, &a.Aggregate, defaultLanguage),
}, nil
}, nil
}
@@ -385,3 +407,35 @@ func (c *Commands) setIAMProject(ctx context.Context, iamAgg *eventstore.Aggrega
}
return instance.NewIAMProjectSetEvent(ctx, iamAgg, projectID), nil
}
+
+func (c *Commands) prepareSetDefaultLanguage(a *instance.Aggregate, defaultLanguage language.Tag) preparation.Validation {
+ return func() (preparation.CreateCommands, error) {
+ if defaultLanguage == language.Und {
+ return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
+ }
+ return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
+ writeModel, err := getInstanceWriteModel(ctx, filter)
+ if err != nil {
+ return nil, err
+ }
+ if writeModel.DefaultLanguage == defaultLanguage {
+ return nil, errors.ThrowPreconditionFailed(nil, "INST-DS3rq", "Errors.Instance.NotChanged")
+ }
+ return []eventstore.Command{instance.NewDefaultLanguageSetEvent(ctx, &a.Aggregate, defaultLanguage)}, nil
+ }, nil
+ }
+}
+
+func getInstanceWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*InstanceWriteModel, error) {
+ writeModel := NewInstanceWriteModel(authz.GetInstance(ctx).InstanceID())
+ events, err := filter(ctx, writeModel.Query())
+ if err != nil {
+ return nil, err
+ }
+ if len(events) == 0 {
+ return writeModel, nil
+ }
+ writeModel.AppendEvents(events...)
+ err = writeModel.Reduce()
+ return writeModel, err
+}
diff --git a/internal/command/user_human.go b/internal/command/user_human.go
index f2b65b883..20a2d11aa 100644
--- a/internal/command/user_human.go
+++ b/internal/command/user_human.go
@@ -98,9 +98,6 @@ func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.Hash
return nil, errors.ThrowInvalidArgument(nil, "V2-zzad3", "Errors.Invalid.Argument")
}
- if human.PreferredLanguage == language.Und {
- return nil, errors.ThrowInvalidArgument(nil, "USER-Sfd11", "Errors.Invalid.Argument")
- }
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
}
diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go
index 9678aa9c6..c0a7f4cf8 100644
--- a/internal/command/user_human_test.go
+++ b/internal/command/user_human_test.go
@@ -2856,21 +2856,6 @@ func TestAddHumanCommand(t *testing.T) {
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument"),
},
},
- {
- name: "invalid preferred language",
- args: args{
- a: agg,
- human: &AddHuman{
- Username: "username",
- Email: Email{
- Address: "support@zitadel.ch",
- },
- },
- },
- want: Want{
- ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Sfd11", "Errors.Invalid.Argument"),
- },
- },
{
name: "invalid first name",
args: args{
diff --git a/internal/query/instance.go b/internal/query/instance.go
index dbb032101..6fb356786 100644
--- a/internal/query/instance.go
+++ b/internal/query/instance.go
@@ -281,6 +281,7 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
&lang,
&count,
)
+ instance.DefaultLang = language.Make(lang)
if err != nil {
return nil, err
}
@@ -378,6 +379,7 @@ func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Rows)
InstanceID: instance.ID,
})
}
+ instance.DefaultLang = language.Make(lang)
if err := rows.Close(); err != nil {
return nil, errors.ThrowInternal(err, "QUERY-Dfbe2", "Errors.Query.CloseRows")
}
diff --git a/internal/repository/instance/event_default_language.go b/internal/repository/instance/event_default_language.go
index 74ef81ee1..a596a0a5c 100644
--- a/internal/repository/instance/event_default_language.go
+++ b/internal/repository/instance/event_default_language.go
@@ -4,15 +4,15 @@ import (
"context"
"encoding/json"
- "github.com/zitadel/zitadel/internal/eventstore"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/errors"
+ "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
const (
- DefaultLanguageSetEventType eventstore.EventType = "iam.default.language.set"
+ DefaultLanguageSetEventType eventstore.EventType = "instance.default.language.set"
)
type DefaultLanguageSetEvent struct {
diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto
index b18e8b466..11d1b617c 100644
--- a/proto/zitadel/system.proto
+++ b/proto/zitadel/system.proto
@@ -342,6 +342,7 @@ message AddInstanceRequest {
Email owner_email = 5 [(validate.rules).message.required = true];
Profile owner_profile = 6 [(validate.rules).message.required = false];
Password owner_password = 7 [(validate.rules).message.required = false];
+ string default_language = 8 [(validate.rules).string = {max_len: 10}];
}
message AddInstanceResponse {