diff --git a/pkg/collections/observer.go b/pkg/collections/observer.go
index 8d4a6b7d14ec687c97eec6b0406fcc32d515aca2..8df5c084312d9099e200997ed27a9cde921cbb72 100644
--- a/pkg/collections/observer.go
+++ b/pkg/collections/observer.go
@@ -14,8 +14,14 @@ type CollectionUpdatedObserver interface {
 	OnCollectionUpdated(ctx context.Context, before, after *Collection) (delayedTaskID string, err error)
 }
 
+// CollectionPreSetSchemaObserver интерфейс наблюдателя вызываемый перед изменением схемы коллекции.
+// Инициировать оповещение наблюдателя может вызов методов `Collection.SetSchema`
+type CollectionPreSetSchemaObserver interface {
+	OnCollectionPreSetSchema(ctx context.Context, before, coll *Collection) (delayedTaskID string, err error)
+}
+
 // CollectionSetSchemaObserver интерфейс наблюдателя вызываемый при изменении схемы коллекции.
-// Инициировать оповещение наблюдателя может вызов методов `Collection.Schema`
+// Инициировать оповещение наблюдателя может вызов методов `Collection.SetSchema`
 type CollectionSetSchemaObserver interface {
 	OnCollectionSetSchema(ctx context.Context, before, coll *Collection) (delayedTaskID string, err error)
 }
diff --git a/pkg/items/item.go b/pkg/items/item.go
index c0a0ef935f60e14a5c2543e2fac98f1a9a52c41e..1fc48d610401f41b333a9751a07a38455ba192db 100644
--- a/pkg/items/item.go
+++ b/pkg/items/item.go
@@ -19,6 +19,7 @@ var (
 	ErrNotSystemField = errors.New("not a system field")
 	ErrIncorrectValue = errors.New("incorrect value")
 	ErrIncorrectField = errors.New("incorrect field")
+	ErrReservedField  = errors.New("cannot use reserved field name")
 )
 
 type State int
diff --git a/pkg/items/mocks/Decoder.go b/pkg/items/mocks/Decoder.go
index b1dbd9aa526cf4fe4a62d29f652c3e2a513924b3..fcc4d502ee3d57504560a69c35ebdea06460b595 100644
--- a/pkg/items/mocks/Decoder.go
+++ b/pkg/items/mocks/Decoder.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -16,6 +16,10 @@ type Decoder struct {
 func (_m *Decoder) Decode(value interface{}, item *items.Item) error {
 	ret := _m.Called(value, item)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Decode")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(interface{}, *items.Item) error); ok {
 		r0 = rf(value, item)
diff --git a/pkg/items/mocks/Encoder.go b/pkg/items/mocks/Encoder.go
index c19203b2448930eb75e62195837a1b22cd9cfe42..cd2dd5c9762c84131974b6e612086ad61d3699f3 100644
--- a/pkg/items/mocks/Encoder.go
+++ b/pkg/items/mocks/Encoder.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -16,6 +16,10 @@ type Encoder struct {
 func (_m *Encoder) Encode(item *items.Item) (interface{}, error) {
 	ret := _m.Called(item)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Encode")
+	}
+
 	var r0 interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(*items.Item) (interface{}, error)); ok {
diff --git a/pkg/items/mocks/ItemObserver.go b/pkg/items/mocks/ItemObserver.go
index d826e04045f01e504200e742afa16e68181bc6c9..e3451e680e643ac49343830a4713b93d6ad174c6 100644
--- a/pkg/items/mocks/ItemObserver.go
+++ b/pkg/items/mocks/ItemObserver.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
diff --git a/pkg/items/mocks/ItemReadObserver.go b/pkg/items/mocks/ItemReadObserver.go
index 212f09ad7a046086f1cc99230699a304ec2a9878..c6876a5a3aada6942bf07fdfdfcee35f626353c9 100644
--- a/pkg/items/mocks/ItemReadObserver.go
+++ b/pkg/items/mocks/ItemReadObserver.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -18,6 +18,10 @@ type ItemReadObserver struct {
 func (_m *ItemReadObserver) OnPostFind(ctx context.Context, _a1 []*items.Item, total int) ([]*items.Item, int, error) {
 	ret := _m.Called(ctx, _a1, total)
 
+	if len(ret) == 0 {
+		panic("no return value specified for OnPostFind")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -51,6 +55,10 @@ func (_m *ItemReadObserver) OnPostFind(ctx context.Context, _a1 []*items.Item, t
 func (_m *ItemReadObserver) OnPostGet(ctx context.Context, item *items.Item) (*items.Item, error) {
 	ret := _m.Called(ctx, item)
 
+	if len(ret) == 0 {
+		panic("no return value specified for OnPostGet")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item) (*items.Item, error)); ok {
@@ -77,6 +85,10 @@ func (_m *ItemReadObserver) OnPostGet(ctx context.Context, item *items.Item) (*i
 func (_m *ItemReadObserver) OnPreFind(ctx context.Context, spaceId string, envId string, collectionId string, filter *items.Filter, options *items.FindOptions) error {
 	ret := _m.Called(ctx, spaceId, envId, collectionId, filter, options)
 
+	if len(ret) == 0 {
+		panic("no return value specified for OnPreFind")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *items.Filter, *items.FindOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, filter, options)
@@ -91,6 +103,10 @@ func (_m *ItemReadObserver) OnPreFind(ctx context.Context, spaceId string, envId
 func (_m *ItemReadObserver) OnPreGet(ctx context.Context, spaceId string, envId string, collectionId string, itemId string) error {
 	ret := _m.Called(ctx, spaceId, envId, collectionId, itemId)
 
+	if len(ret) == 0 {
+		panic("no return value specified for OnPreGet")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId)
diff --git a/pkg/items/mocks/Items.go b/pkg/items/mocks/Items.go
index 9bbd8948668a7ec665fdefd2f5e086cb235d579d..e134822e2869693fa3801affac8660eb357cd70c 100644
--- a/pkg/items/mocks/Items.go
+++ b/pkg/items/mocks/Items.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -27,6 +27,10 @@ func (_m *Items) Aggregate(ctx context.Context, spaceId string, envId string, co
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Aggregate")
+	}
+
 	var r0 map[string]interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *items.Filter, ...*items.AggregateOptions) (map[string]interface{}, error)); ok {
@@ -60,6 +64,10 @@ func (_m *Items) AggregatePublished(ctx context.Context, spaceId string, envId s
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for AggregatePublished")
+	}
+
 	var r0 map[string]interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *items.Filter, ...*items.AggregatePublishedOptions) (map[string]interface{}, error)); ok {
@@ -93,6 +101,10 @@ func (_m *Items) Archive(ctx context.Context, item *items.Item, options ...*item
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Archive")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.ArchiveOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -114,6 +126,10 @@ func (_m *Items) Create(ctx context.Context, item *items.Item, opts ...*items.Cr
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Create")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.CreateOptions) (*items.Item, error)); ok {
@@ -147,6 +163,10 @@ func (_m *Items) Delete(ctx context.Context, item *items.Item, options ...*items
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Delete")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -168,6 +188,10 @@ func (_m *Items) Find(ctx context.Context, spaceId string, envId string, collect
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Find")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -208,6 +232,10 @@ func (_m *Items) FindArchived(ctx context.Context, spaceId string, envId string,
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for FindArchived")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -248,6 +276,10 @@ func (_m *Items) FindPublished(ctx context.Context, spaceId string, envId string
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for FindPublished")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -288,6 +320,10 @@ func (_m *Items) Get(ctx context.Context, spaceId string, envId string, collecti
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Get")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.GetOptions) (*items.Item, error)); ok {
@@ -321,6 +357,10 @@ func (_m *Items) GetPublished(ctx context.Context, spaceId string, envId string,
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for GetPublished")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.GetPublishedOptions) (*items.Item, error)); ok {
@@ -354,6 +394,10 @@ func (_m *Items) GetRevision(ctx context.Context, spaceId string, envId string,
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for GetRevision")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, ...*items.GetRevisionOptions) (*items.Item, error)); ok {
@@ -387,6 +431,10 @@ func (_m *Items) Introspect(ctx context.Context, item *items.Item, opts ...*item
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Introspect")
+	}
+
 	var r0 *items.Item
 	var r1 *schema.Schema
 	var r2 error
@@ -429,6 +477,10 @@ func (_m *Items) ListRevisions(ctx context.Context, spaceId string, envId string
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for ListRevisions")
+	}
+
 	var r0 []*items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.ListRevisionsOptions) ([]*items.Item, error)); ok {
@@ -462,6 +514,10 @@ func (_m *Items) Publish(ctx context.Context, item *items.Item, options ...*item
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Publish")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.PublishOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -483,6 +539,10 @@ func (_m *Items) Unarchive(ctx context.Context, item *items.Item, options ...*it
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Unarchive")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UnarchiveOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -504,6 +564,10 @@ func (_m *Items) Undelete(ctx context.Context, item *items.Item, options ...*ite
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Undelete")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UndeleteOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -525,6 +589,10 @@ func (_m *Items) Unpublish(ctx context.Context, item *items.Item, options ...*it
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Unpublish")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UnpublishOptions) error); ok {
 		r0 = rf(ctx, item, options...)
@@ -546,6 +614,10 @@ func (_m *Items) Update(ctx context.Context, item *items.Item, options ...*items
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Update")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UpdateOptions) error); ok {
 		r0 = rf(ctx, item, options...)
diff --git a/pkg/items/mocks/Middleware.go b/pkg/items/mocks/Middleware.go
index 1cde36c075701f58cd108a677fc73537cf25bf52..0bebef848868bd06d6539b5159f5973f6a39315e 100644
--- a/pkg/items/mocks/Middleware.go
+++ b/pkg/items/mocks/Middleware.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -17,6 +17,10 @@ type Middleware struct {
 func (_m *Middleware) Execute(_a0 items.Items) items.Items {
 	ret := _m.Called(_a0)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Execute")
+	}
+
 	var r0 items.Items
 	if rf, ok := ret.Get(0).(func(items.Items) items.Items); ok {
 		r0 = rf(_a0)
diff --git a/pkg/items/mocks/PreSaver.go b/pkg/items/mocks/PreSaver.go
index 729c1ebfc4e7e60e2920b082e22f35617bf22e4e..3acc32732fcea3df459a5234fb5c952e823edfaa 100644
--- a/pkg/items/mocks/PreSaver.go
+++ b/pkg/items/mocks/PreSaver.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -20,6 +20,10 @@ type PreSaver struct {
 func (_m *PreSaver) PreSave(ctx context.Context, f *field.Field, v interface{}, itemCtx *items.Context) (interface{}, bool, error) {
 	ret := _m.Called(ctx, f, v, itemCtx)
 
+	if len(ret) == 0 {
+		panic("no return value specified for PreSave")
+	}
+
 	var r0 interface{}
 	var r1 bool
 	var r2 error
diff --git a/pkg/items/mocks/ProcessDataFunc.go b/pkg/items/mocks/ProcessDataFunc.go
index 6ba38c984c7bba69461acc206393ebb66ba1cc07..7689da0df40591ba68ca8a6eb3a160d475a81c90 100644
--- a/pkg/items/mocks/ProcessDataFunc.go
+++ b/pkg/items/mocks/ProcessDataFunc.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -19,6 +19,10 @@ type ProcessDataFunc struct {
 func (_m *ProcessDataFunc) Execute(ctx context.Context, sch *schema.Schema, data map[string]interface{}) (map[string]interface{}, error) {
 	ret := _m.Called(ctx, sch, data)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Execute")
+	}
+
 	var r0 map[string]interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *schema.Schema, map[string]interface{}) (map[string]interface{}, error)); ok {
diff --git a/pkg/items/mocks/Storage.go b/pkg/items/mocks/Storage.go
index 6f8082b98690dab97058c6775f3a53c2c655c80f..583310ecf41764565afbde535cef5c779b0ab975 100644
--- a/pkg/items/mocks/Storage.go
+++ b/pkg/items/mocks/Storage.go
@@ -1,4 +1,4 @@
-// Code generated by mockery v2.33.3. DO NOT EDIT.
+// Code generated by mockery v2.43.2. DO NOT EDIT.
 
 package mocks
 
@@ -9,6 +9,8 @@ import (
 
 	items "git.perx.ru/perxis/perxis-go/pkg/items"
 
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+
 	mock "github.com/stretchr/testify/mock"
 )
 
@@ -28,6 +30,10 @@ func (_m *Storage) Aggregate(ctx context.Context, coll *collections.Collection,
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Aggregate")
+	}
+
 	var r0 map[string]interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, *items.Filter, ...*items.AggregateOptions) (map[string]interface{}, error)); ok {
@@ -61,6 +67,10 @@ func (_m *Storage) AggregatePublished(ctx context.Context, coll *collections.Col
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for AggregatePublished")
+	}
+
 	var r0 map[string]interface{}
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, *items.Filter, ...*items.AggregatePublishedOptions) (map[string]interface{}, error)); ok {
@@ -94,6 +104,10 @@ func (_m *Storage) Archive(ctx context.Context, archived *items.Item, options ..
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Archive")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.ArchiveOptions) error); ok {
 		r0 = rf(ctx, archived, options...)
@@ -108,6 +122,10 @@ func (_m *Storage) Archive(ctx context.Context, archived *items.Item, options ..
 func (_m *Storage) ChangeRevisionsItemID(ctx context.Context, spaceId string, envId string, collectionId string, itemId string, newItemId string) error {
 	ret := _m.Called(ctx, spaceId, envId, collectionId, itemId, newItemId)
 
+	if len(ret) == 0 {
+		panic("no return value specified for ChangeRevisionsItemID")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId, newItemId)
@@ -129,6 +147,10 @@ func (_m *Storage) Copy(ctx context.Context, src *collections.Collection, dst *c
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Copy")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, *collections.Collection, ...string) error); ok {
 		r0 = rf(ctx, src, dst, itemSets...)
@@ -150,6 +172,10 @@ func (_m *Storage) Create(ctx context.Context, coll *collections.Collection, ite
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Create")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, *items.Item, ...*items.CreateOptions) (*items.Item, error)); ok {
@@ -176,6 +202,10 @@ func (_m *Storage) Create(ctx context.Context, coll *collections.Collection, ite
 func (_m *Storage) CreateRevision(ctx context.Context, spaceId string, envId string, collectionId string, itemId string) error {
 	ret := _m.Called(ctx, spaceId, envId, collectionId, itemId)
 
+	if len(ret) == 0 {
+		panic("no return value specified for CreateRevision")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId)
@@ -197,6 +227,10 @@ func (_m *Storage) Find(ctx context.Context, coll *collections.Collection, filte
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Find")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -237,6 +271,10 @@ func (_m *Storage) FindArchived(ctx context.Context, coll *collections.Collectio
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for FindArchived")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -277,6 +315,10 @@ func (_m *Storage) FindPublished(ctx context.Context, coll *collections.Collecti
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for FindPublished")
+	}
+
 	var r0 []*items.Item
 	var r1 int
 	var r2 error
@@ -317,6 +359,10 @@ func (_m *Storage) GetRevision(ctx context.Context, coll *collections.Collection
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for GetRevision")
+	}
+
 	var r0 *items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, string, string, ...*items.GetRevisionOptions) (*items.Item, error)); ok {
@@ -339,13 +385,24 @@ func (_m *Storage) GetRevision(ctx context.Context, coll *collections.Collection
 	return r0, r1
 }
 
-// Init provides a mock function with given fields: ctx, collection
-func (_m *Storage) Init(ctx context.Context, collection *collections.Collection) error {
-	ret := _m.Called(ctx, collection)
+// Init provides a mock function with given fields: ctx, collection, _a2
+func (_m *Storage) Init(ctx context.Context, collection *collections.Collection, _a2 ...*locales.Locale) error {
+	_va := make([]interface{}, len(_a2))
+	for _i := range _a2 {
+		_va[_i] = _a2[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, collection)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Init")
+	}
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection) error); ok {
-		r0 = rf(ctx, collection)
+	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, ...*locales.Locale) error); ok {
+		r0 = rf(ctx, collection, _a2...)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -364,6 +421,10 @@ func (_m *Storage) ListRevisions(ctx context.Context, coll *collections.Collecti
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for ListRevisions")
+	}
+
 	var r0 []*items.Item
 	var r1 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, string, ...*items.ListRevisionsOptions) ([]*items.Item, error)); ok {
@@ -397,6 +458,10 @@ func (_m *Storage) Publish(ctx context.Context, published *items.Item, options .
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Publish")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.PublishOptions) error); ok {
 		r0 = rf(ctx, published, options...)
@@ -418,6 +483,10 @@ func (_m *Storage) RemoveArchived(ctx context.Context, spaceId string, envId str
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for RemoveArchived")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId, options...)
@@ -439,6 +508,10 @@ func (_m *Storage) RemoveItems(ctx context.Context, spaceId string, envId string
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for RemoveItems")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId, options...)
@@ -460,6 +533,10 @@ func (_m *Storage) RemovePublished(ctx context.Context, spaceId string, envId st
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for RemovePublished")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId, options...)
@@ -481,6 +558,10 @@ func (_m *Storage) RemoveRevision(ctx context.Context, spaceId string, envId str
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for RemoveRevision")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, revision, options...)
@@ -502,6 +583,10 @@ func (_m *Storage) RemoveRevisions(ctx context.Context, spaceId string, envId st
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for RemoveRevisions")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, ...*items.DeleteOptions) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId, itemId, options...)
@@ -516,6 +601,10 @@ func (_m *Storage) RemoveRevisions(ctx context.Context, spaceId string, envId st
 func (_m *Storage) Reset(ctx context.Context, spaceId string, envId string, collectionId string) error {
 	ret := _m.Called(ctx, spaceId, envId, collectionId)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Reset")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
 		r0 = rf(ctx, spaceId, envId, collectionId)
@@ -537,6 +626,10 @@ func (_m *Storage) Unarchive(ctx context.Context, unarchived *items.Item, option
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Unarchive")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UnarchiveOptions) error); ok {
 		r0 = rf(ctx, unarchived, options...)
@@ -558,6 +651,10 @@ func (_m *Storage) Unpublish(ctx context.Context, unpublished *items.Item, optio
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Unpublish")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *items.Item, ...*items.UnpublishOptions) error); ok {
 		r0 = rf(ctx, unpublished, options...)
@@ -579,6 +676,10 @@ func (_m *Storage) Update(ctx context.Context, coll *collections.Collection, ite
 	_ca = append(_ca, _va...)
 	ret := _m.Called(_ca...)
 
+	if len(ret) == 0 {
+		panic("no return value specified for Update")
+	}
+
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, *collections.Collection, *items.Item, ...*items.UpdateOptions) error); ok {
 		r0 = rf(ctx, coll, item, options...)
diff --git a/pkg/items/storage.go b/pkg/items/storage.go
index 64548a009369fad6e74530e2ae5f52dd013e7997..19b98c9c840c2c4a9d2e38a443555eb02116e7fc 100644
--- a/pkg/items/storage.go
+++ b/pkg/items/storage.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
 )
 
 type Storage interface {
@@ -83,7 +84,7 @@ type Storage interface {
 	Copy(ctx context.Context, src, dst *collections.Collection, itemSets ...string) error
 
 	Reset(ctx context.Context, spaceId, envId, collectionId string) error
-	Init(ctx context.Context, collection *collections.Collection) error
+	Init(ctx context.Context, collection *collections.Collection, locales ...*locales.Locale) error
 
 	// Aggregate выполняет агрегацию данных
 	Aggregate(ctx context.Context, coll *collections.Collection, filter *Filter, options ...*AggregateOptions) (result map[string]interface{}, err error)
diff --git a/pkg/locales/locale.go b/pkg/locales/locale.go
index 107c7d9cb987da65a7156d1d3df2ae5e447bc48d..48c31f79116f7485d5e9fd0197b8eefcf76a50fd 100644
--- a/pkg/locales/locale.go
+++ b/pkg/locales/locale.go
@@ -1,5 +1,7 @@
 package locales
 
+import "golang.org/x/text/language"
+
 const (
 	DefaultID        = "default" // DefaultID идентификатор локали по умолчанию
 	DefaultDirection = "ltr"     // DefaultDirection направление письма по умолчанию
@@ -18,6 +20,25 @@ type Locale struct {
 	Disabled   bool   `json:"disabled" bson:"disabled"`       // Запретить использование локали. Нельзя создавать и редактировать контент для данной локали (кроме default)
 }
 
-func (locale Locale) IsDefault() bool {
+func (locale *Locale) IsDefault() bool {
 	return locale.ID == DefaultID
 }
+
+// Возвращает язык локали, например "en", "ru"
+func (locale *Locale) GetLanguage() string {
+	lang, err := language.Parse(locale.Code)
+	if err != nil {
+		return ""
+	}
+
+	base, _ := lang.Base()
+	return base.String()
+}
+
+func GetIDs(locales []*Locale) []string {
+	result := make([]string, 0, len(locales))
+	for _, locale := range locales {
+		result = append(result, locale.ID)
+	}
+	return result
+}
diff --git a/pkg/locales/locale_test.go b/pkg/locales/locale_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..18a030603a01aa5879cc20c5d5161a08bbb6888d
--- /dev/null
+++ b/pkg/locales/locale_test.go
@@ -0,0 +1,38 @@
+package locales
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestLocale_GetLanguage(t *testing.T) {
+	tests := []struct {
+		code     string
+		wantLang string
+	}{
+		{
+			code:     "ru",
+			wantLang: "ru",
+		},
+		{
+			code:     "ru_RU",
+			wantLang: "ru",
+		},
+		{
+			code:     "en-US",
+			wantLang: "en",
+		},
+		{
+			code:     "incorrect-code",
+			wantLang: "",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.code, func(t *testing.T) {
+			locale := &Locale{Code: tt.code}
+			require.Equal(t, tt.wantLang, locale.GetLanguage())
+		})
+	}
+}
diff --git a/pkg/locales/observer.go b/pkg/locales/observer.go
new file mode 100644
index 0000000000000000000000000000000000000000..732e7e6f9384a1ed37a767c2e4e77dc381b64b00
--- /dev/null
+++ b/pkg/locales/observer.go
@@ -0,0 +1,11 @@
+package locales
+
+import "context"
+
+type LocaleCreatedObserver interface {
+	OnLocaleCreated(ctx context.Context, locale *Locale) (delayedTaskID string, err error)
+}
+
+type LocaleDeletedObserver interface {
+	OnLocaleDeleted(ctx context.Context, locale *Locale) (delayedTaskID string, err error)
+}