diff --git a/perxis-proto b/perxis-proto
index 7a5f5018db5d7f84435e1f98a0e94cac9ddaa9ba..63073e8bf4bf41368a71e7e2e207625c4c67838d 160000
--- a/perxis-proto
+++ b/perxis-proto
@@ -1 +1 @@
-Subproject commit 7a5f5018db5d7f84435e1f98a0e94cac9ddaa9ba
+Subproject commit 63073e8bf4bf41368a71e7e2e207625c4c67838d
diff --git a/pkg/items/item.go b/pkg/items/item.go
index 96c476cbdd15c49f47255929fbab577eb2605b6f..52cc0e36d4fe38892c7c8bba0f080803236a339a 100644
--- a/pkg/items/item.go
+++ b/pkg/items/item.go
@@ -3,13 +3,14 @@ package items
 import (
 	"context"
 	"fmt"
-	"reflect"
 	"time"
 
 	"git.perx.ru/perxis/perxis-go/pkg/data"
 	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
 	"git.perx.ru/perxis/perxis-go/pkg/schema"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/localizer"
 	pb "git.perx.ru/perxis/perxis-go/proto/items"
 	"google.golang.org/protobuf/types/known/structpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
@@ -161,30 +162,28 @@ func (i *Item) ToMap() map[string]interface{} {
 	}
 }
 
-func (i *Item) SetData(locale string, data map[string]interface{}) {
-	if locale != "" {
-		if i.Translations == nil {
-			i.Translations = make(map[string]map[string]interface{})
-		}
-		i.Translations[locale] = data
+func (i *Item) SetData(data map[string]interface{}, localizer *localizer.Localizer) (err error) {
+	if localizer != nil && localizer.LocaleID != locales.DefaultID {
+		i.Translations[localizer.LocaleID], err = localizer.ExtractTranslation(i.Data, i.Translations)
 		return
 	}
+
 	i.Data = data
+	return
 }
 
-func (i *Item) GetData(locale string) map[string]interface{} {
-	if locale != "" && i.Translations != nil {
-		translation, _ := i.Translations[locale]
-		return MergeData(i.Data, translation)
+func (i *Item) GetData(localizer *localizer.Localizer) (map[string]interface{}, error) {
+	if localizer != nil {
+		return localizer.Localize(i.Data, i.Translations)
 	}
-	return i.Data
+	return i.Data, nil
 }
 
 func (i Item) Encode(ctx context.Context, s *schema.Schema) (*Item, error) {
 	if i.Data != nil {
 		dt, err := schema.Encode(nil, s, i.Data)
 		if err != nil {
-			//return errors.WithField(err, "data")
+			// return errors.WithField(err, "data")
 			return nil, err
 		}
 		i.Data = dt.(map[string]interface{})
@@ -193,7 +192,7 @@ func (i Item) Encode(ctx context.Context, s *schema.Schema) (*Item, error) {
 		for l, v := range i.Translations {
 			dt, err := schema.Encode(nil, s, v)
 			if err != nil {
-				//return errors.WithField(err, fmt.Sprintf("translations.%s", l))
+				// return errors.WithField(err, fmt.Sprintf("translations.%s", l))
 				return nil, err
 			}
 			i.Translations[l] = dt.(map[string]interface{})
@@ -208,47 +207,16 @@ func (i Item) Decode(ctx context.Context, s *schema.Schema) (res *Item, err erro
 		i.Data, err = s.Decode(ctx, i.Data)
 		if err != nil {
 			return nil, err
-			//return errors.WithField(err, "data")
+			// return errors.WithField(err, "data")
 		}
 	}
 
 	return &i, nil
 }
 
-// MergeData дополняет отсутствующие данные из оригинальных данных
-func MergeData(data ...map[string]interface{}) map[string]interface{} {
-	merge := make(map[string]interface{})
-	for _, d := range data {
-		for k, v := range d {
-			merge[k] = v
-		}
-	}
-	return merge
-}
-
-// ClearData убирает данные которые не изменились по сравнению с оригинальными данными
-func ClearData(data ...map[string]interface{}) map[string]interface{} {
-	var clear map[string]interface{}
-
-	for _, d := range data {
-		if clear == nil {
-			clear = d
-			continue
-		}
-
-		for k, v := range d {
-			if reflect.DeepEqual(clear[k], v) {
-				delete(clear, k)
-			}
-		}
-	}
-
-	return clear
-}
-
 type ProcessDataFunc func(ctx context.Context, sch *schema.Schema, data map[string]interface{}) (map[string]interface{}, error)
 
-func (i Item) ProcessData(ctx context.Context, sch *schema.Schema, fn ProcessDataFunc, locales ...string) (*Item, error) {
+func (i Item) ProcessData(ctx context.Context, sch *schema.Schema, fn ProcessDataFunc, locales ...*locales.Locale) (*Item, error) {
 	if i.Data != nil {
 		dt, err := fn(ctx, sch, i.Data)
 		if err != nil {
@@ -259,14 +227,16 @@ func (i Item) ProcessData(ctx context.Context, sch *schema.Schema, fn ProcessDat
 
 	tr := make(map[string]map[string]interface{})
 	for _, l := range locales {
-
-		data := i.GetData(l)
+		data, err := i.GetData(localizer.NewLocalizer(sch, locales, l.ID))
+		if err != nil {
+			return nil, errors.WithField(err, fmt.Sprintf("translations.%s", l.ID))
+		}
 
 		dt, err := fn(ctx, sch, data)
 		if err != nil {
-			return nil, errors.WithField(err, fmt.Sprintf("translations.%s", l))
+			return nil, errors.WithField(err, fmt.Sprintf("translations.%s", l.ID))
 		}
-		tr[l] = dt
+		tr[l.ID] = dt
 
 	}
 
@@ -280,15 +250,12 @@ func (i Item) ProcessData(ctx context.Context, sch *schema.Schema, fn ProcessDat
 
 // IsSystemField возвращает являться ли поле системным
 func IsSystemField(field string) bool {
-	if data.Contains(field, SystemFields) {
-		return true
-	}
-	return false
+	return data.Contains(field, SystemFields)
 }
 
 // SetSystemField устанавливает значение системного поля
 func (i *Item) SetSystemField(field string, value interface{}) error {
-	ok := true
+	var ok bool
 	switch field {
 	case "id":
 		i.ID, ok = value.(string)
diff --git a/pkg/items/item_test.go b/pkg/items/item_test.go
index dfcc16ee1c7b40441b5df54acc48a12b276669ca..47e7f02be2014b3c94e4a7c0d29d4ccab7dbca63 100644
--- a/pkg/items/item_test.go
+++ b/pkg/items/item_test.go
@@ -13,14 +13,14 @@ import (
 func TestItem_Set(t *testing.T) {
 	item := &Item{}
 
-	item.Set("id", "id")
+	_ = item.Set("id", "id")
 	assert.Equal(t, "id", item.ID)
 	now := time.Now()
 
-	item.Set("created_at", now)
+	_ = item.Set("created_at", now)
 	assert.Equal(t, now, item.CreatedAt)
 
-	item.Set("a.b.c", 101)
+	_ = item.Set("a.b.c", 101)
 	assert.Equal(t, map[string]any{"a": map[string]any{"b": map[string]any{"c": 101}}}, item.Data)
 
 }
diff --git a/pkg/schema/field/field.go b/pkg/schema/field/field.go
index 45ddb40d128ab3f87ff7c41e3cedf75009ba3f98..32b17574e071e8d72eae3eba789c2bb2c6177faa 100644
--- a/pkg/schema/field/field.go
+++ b/pkg/schema/field/field.go
@@ -55,7 +55,7 @@ type Field struct {
 	Translations     []Translation `json:"translations,omitempty"`      // Переводы данных на разных языках
 	UI               *UI           `json:"ui,omitempty"`                // Опции пользовательского интерфейса
 	Includes         []Include     `json:"includes,omitempty"`          // Импорт схем
-	SingleLocale     bool          `json:"singleLocale,omitempty"`      // Без перевода
+	SingleLocale     bool          `json:"single_locale,omitempty"`     // Без перевода
 	Indexed          bool          `json:"indexed,omitempty"`           // Построить индекс для поля
 	Unique           bool          `json:"unique,omitempty"`            // Значение поля должны быть уникальными
 	TextSearch       bool          `json:"text_search,omitempty"`       // Значение поля доступны для полнотекстового поиска
@@ -335,11 +335,11 @@ func (f *Field) GetFields(filterFunc FilterFunc, pathPrefix ...string) (res []Pa
 		res = append(res, getFieldsArray(path, params, filterFunc)...)
 	}
 
-	//if len(pathPrefix) > 0 {
+	// if len(pathPrefix) > 0 {
 	//	for _, r := range res {
 	//		r.Path = strings.Join([]string{pathPrefix[0], r.Path}, FieldSeparator)
 	//	}
-	//}
+	// }
 
 	return res
 }
diff --git a/pkg/schema/localizer/localizer.go b/pkg/schema/localizer/localizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..a818f6e922993330ea0c4bca396b5a51c49bec37
--- /dev/null
+++ b/pkg/schema/localizer/localizer.go
@@ -0,0 +1,192 @@
+package localizer
+
+import (
+	"context"
+	"reflect"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/walk"
+)
+
+type Localizer struct {
+	Schema   *schema.Schema
+	Locales  map[string]*locales.Locale
+	LocaleID string
+}
+
+// NewLocalizer создает экземпляр локализатора. Требуется указать "загруженную" схему
+func NewLocalizer(s *schema.Schema, locs []*locales.Locale, localeID string) *Localizer {
+	if localeID == "" {
+		localeID = locales.DefaultID
+	}
+
+	loc := &Localizer{
+		Schema:   s,
+		Locales:  make(map[string]*locales.Locale, len(locs)),
+		LocaleID: localeID,
+	}
+
+	for _, l := range locs {
+		loc.Locales[l.ID] = l
+	}
+
+	return loc
+}
+
+// Localize Получить полные локализованные данные для локали `localeID`. Входные параметры:
+//   - `data map[string]interface{}` - данные основной локали
+//   - `translations map[string]map[string]interface{}` переводы
+//
+// При отсутствии каких-либо полей в переводе на `localeID` данные берутся сначала из fallback-локали,
+// если перевод отсутствует то из `data`
+func (l *Localizer) Localize(data map[string]interface{}, translations map[string]map[string]interface{}) (localized map[string]interface{}, err error) {
+	if l.LocaleID == locales.DefaultID {
+		return data, nil
+	}
+
+	target, fallback, err := l.getTargetAndFallBackLocales()
+	if err != nil {
+		return nil, err
+	}
+
+	// localize fallback -> target
+	fallbackData := data
+	var exist bool
+	if !fallback.IsDefault() {
+		if fd, exist := translations[fallback.ID]; exist {
+			// localize default -> fallback
+			fallbackData, err = l.localize(fd, data)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	if localized, exist = translations[target.ID]; !exist {
+		localized = make(map[string]interface{})
+	}
+
+	return l.localize(localized, fallbackData)
+}
+
+// ExtractTranslation Получить "просеянные" данные для локали localeID: все поля, значения которых совпадают
+// с переводом на fallback-локаль или основными данными, удаляются из перевода
+func (l *Localizer) ExtractTranslation(data map[string]interface{}, translations map[string]map[string]interface{}) (translation map[string]interface{}, err error) {
+	if l.LocaleID == locales.DefaultID {
+		return data, nil
+	}
+
+	target, fallback, err := l.getTargetAndFallBackLocales()
+	if err != nil {
+		return nil, err
+	}
+
+	fallbackData := data
+	var exist bool
+	if !fallback.IsDefault() {
+		if fallbackData, exist = translations[fallback.ID]; !exist {
+			fallbackData = data
+		}
+	}
+
+	// extract translation fallback -> target
+	if translation, exist = translations[target.ID]; !exist {
+		return make(map[string]interface{}), nil
+	}
+
+	return l.extractTranslation(translation, fallbackData)
+}
+
+func (l *Localizer) getTargetAndFallBackLocales() (target, fallback *locales.Locale, err error) {
+	if target = l.Locales[l.LocaleID]; target == nil || target.Disabled {
+		return nil, nil, errors.New("target locale not found or disabled")
+	}
+
+	if fallback = l.Locales[target.Fallback]; fallback == nil || fallback.Disabled {
+		fallback = l.Locales[locales.DefaultID]
+	}
+	return
+}
+
+func (l *Localizer) localize(target, fallback map[string]interface{}) (map[string]interface{}, error) {
+	if target == nil && fallback == nil {
+		return nil, nil
+	}
+
+	single := l.Schema.GetFields(func(f *field.Field, p string) bool {
+		return f.SingleLocale
+	})
+
+	cfg := &walk.WalkConfig{Fields: make(map[string]walk.FieldConfig, len(single))}
+	for _, sn := range single {
+		cfg.Fields[sn.Path] = walk.FieldConfig{Fn: walk.KeepSrc}
+	}
+
+	w := walk.NewWalker(l.Schema, cfg)
+	w.DefaultFn = localize
+
+	res, _, err := w.DataWalk(context.Background(), target, fallback)
+	if err != nil {
+		return nil, err
+	}
+
+	if res != nil {
+		return res.(map[string]interface{}), err
+	}
+
+	return nil, nil
+}
+
+func (l *Localizer) extractTranslation(target, fallback map[string]interface{}) (map[string]interface{}, error) {
+	if target == nil && fallback == nil {
+		return nil, nil
+	}
+
+	single := l.Schema.GetFields(func(f *field.Field, p string) bool {
+		return f.SingleLocale
+	})
+
+	cfg := &walk.WalkConfig{Fields: make(map[string]walk.FieldConfig, len(single))}
+	for _, sn := range single {
+		cfg.Fields[sn.Path] = walk.FieldConfig{Fn: walk.RemoveValue}
+	}
+
+	w := walk.NewWalker(l.Schema, cfg)
+	w.DefaultFn = extractTranslation
+
+	res, _, err := w.DataWalk(context.Background(), target, fallback)
+	if err != nil {
+		return nil, err
+	}
+
+	if res == nil {
+		return map[string]interface{}{}, nil
+	}
+
+	return res.(map[string]interface{}), nil
+}
+
+func localize(c *walk.WalkContext) (err error) {
+	if c.Dst != nil {
+		return
+	}
+
+	c.Dst = c.Src
+	c.Changed = true
+	return
+}
+
+func extractTranslation(c *walk.WalkContext) (err error) {
+	if c.Dst == nil {
+		return
+	}
+
+	if reflect.DeepEqual(c.Src, c.Dst) {
+		c.Dst = nil
+		c.Changed = true
+	}
+	return
+}
diff --git a/pkg/schema/localizer/localizer_test.go b/pkg/schema/localizer/localizer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6f5fbdd687bb5d613f18fb039340a9489f5aaf9c
--- /dev/null
+++ b/pkg/schema/localizer/localizer_test.go
@@ -0,0 +1,612 @@
+package localizer
+
+import (
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestLocalizer_localize(t *testing.T) {
+
+	s := schema.New(
+		"a", field.String(),
+		"b", field.Number(field.NumberFormatInt),
+		"c", field.String().SetSingleLocale(true),
+		"obj1", field.Object(
+			"a", field.String(),
+			"b", field.Number(field.NumberFormatInt),
+			"c", field.Number(field.NumberFormatInt).SetSingleLocale(true),
+			"obj2", field.Object(
+				"a", field.Number(field.NumberFormatInt),
+				"b", field.String(),
+				"c", field.String().SetSingleLocale(true),
+			),
+		),
+		"obj3", field.Object(
+			"a", field.String(),
+		).SetSingleLocale(true),
+		"slice", field.Array(field.String()),
+		"arr", field.Array(field.Object(
+			"num", field.Number(field.NumberFormatInt),
+		)),
+	)
+
+	tests := []struct {
+		name     string
+		fallback map[string]interface{}
+		target   map[string]interface{}
+		want     map[string]interface{}
+		wantErr  bool
+	}{
+		{
+			name:     "Nil",
+			fallback: nil,
+			target:   nil,
+			want:     nil,
+		},
+		{
+			name:     "Empty",
+			fallback: map[string]interface{}{},
+			target:   map[string]interface{}{},
+			want:     map[string]interface{}{},
+		},
+		{
+			name: "Target Empty",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{},
+			want: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+		},
+		{
+			name:     "Fallback Empty",
+			fallback: map[string]interface{}{},
+			target: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+					},
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+		},
+		{
+			name: "Equal",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+		},
+		{
+			name: "Target no single",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+					},
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+		},
+		{
+			name: "Success",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{
+				"a": "ru",
+				"b": 11,
+				"obj1": map[string]interface{}{
+					"a": "obj1_ru",
+					"b": 22,
+					"obj2": map[string]interface{}{
+						"a": 33,
+						"b": "obj1_obj2_ru",
+					},
+				},
+				"slice": []interface{}{"ru_s3"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 1},
+					map[string]interface{}{"num": 2},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "ru",
+				"b": 11,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_ru",
+					"b": 22,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 33,
+						"b": "obj1_obj2_ru",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"ru_s3"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 1},
+					map[string]interface{}{"num": 2},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			l := &Localizer{
+				Schema: s,
+			}
+			got, err := l.localize(tt.target, tt.fallback)
+			if !tt.wantErr {
+				require.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+				return
+			}
+			require.Error(t, err)
+		})
+	}
+}
+
+func TestLocalizer_deLocalize(t *testing.T) {
+
+	s := schema.New(
+		"a", field.String(),
+		"b", field.Number(field.NumberFormatInt),
+		"c", field.String().SetSingleLocale(true),
+		"obj1", field.Object(
+			"a", field.String(),
+			"b", field.Number(field.NumberFormatInt),
+			"c", field.Number(field.NumberFormatInt).SetSingleLocale(true),
+			"obj2", field.Object(
+				"a", field.Number(field.NumberFormatInt),
+				"b", field.String(),
+				"c", field.String().SetSingleLocale(true),
+			),
+		),
+		"obj3", field.Object(
+			"a", field.String(),
+		).SetSingleLocale(true),
+		"slice", field.Array(field.String()),
+		"arr", field.Array(field.Object(
+			"num", field.Number(field.NumberFormatInt),
+		)),
+	)
+
+	tests := []struct {
+		name     string
+		fallback map[string]interface{}
+		target   map[string]interface{}
+		want     map[string]interface{}
+		wantErr  bool
+	}{
+		{
+			name:     "Nil",
+			fallback: nil,
+			target:   nil,
+			want:     nil,
+		},
+		{
+			name:     "Empty",
+			fallback: map[string]interface{}{},
+			target:   map[string]interface{}{},
+			want:     map[string]interface{}{},
+		},
+		{
+			name: "Target Empty",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{},
+			want:   map[string]interface{}{},
+		},
+		{
+			name:     "Fallback Empty",
+			fallback: map[string]interface{}{},
+			target: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+					},
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+		},
+		{
+			name: "Equal",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			want: map[string]interface{}{},
+		},
+		{
+			name: "Success",
+			fallback: map[string]interface{}{
+				"a": "en",
+				"b": 1,
+				"c": "single",
+				"obj1": map[string]interface{}{
+					"a": "obj1_en",
+					"b": 2,
+					"c": 20,
+					"obj2": map[string]interface{}{
+						"a": 3,
+						"b": "obj1_obj2_en",
+						"c": "obj1_obj2_single",
+					},
+				},
+				"obj3": map[string]interface{}{
+					"a": "obj3_en",
+				},
+				"slice": []interface{}{"en_s1", "en_s2"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 11},
+					map[string]interface{}{"num": 22},
+				},
+			},
+			target: map[string]interface{}{
+				"a": "ru",
+				"b": 11,
+				"obj1": map[string]interface{}{
+					"a": "obj1_ru",
+					"b": 22,
+					"obj2": map[string]interface{}{
+						"a": 33,
+						"b": "obj1_obj2_ru",
+					},
+				},
+				"slice": []interface{}{"ru_s3"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 1},
+					map[string]interface{}{"num": 2},
+				},
+			},
+			want: map[string]interface{}{
+				"a": "ru",
+				"b": 11,
+				"obj1": map[string]interface{}{
+					"a": "obj1_ru",
+					"b": 22,
+					"obj2": map[string]interface{}{
+						"a": 33,
+						"b": "obj1_obj2_ru",
+					},
+				},
+				"slice": []interface{}{"ru_s3"},
+				"arr": []interface{}{
+					map[string]interface{}{"num": 1},
+					map[string]interface{}{"num": 2},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			l := &Localizer{
+				Schema: s,
+			}
+			got, err := l.extractTranslation(tt.target, tt.fallback)
+			if !tt.wantErr {
+				require.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+				return
+			}
+			require.Error(t, err)
+		})
+	}
+}
diff --git a/pkg/schema/walk/fn.go b/pkg/schema/walk/fn.go
index 9b1d4f22316cf37cae1af824d98f611b7dc0bcf7..a4157783278318096cd5c3a9ee8a67ab388223a8 100644
--- a/pkg/schema/walk/fn.go
+++ b/pkg/schema/walk/fn.go
@@ -14,3 +14,13 @@ func KeepSrc(c *WalkContext) (err error) {
 	c.Changed = true
 	return
 }
+
+func RemoveValue(c *WalkContext) (err error) {
+	if c.Dst == nil {
+		return
+	}
+
+	c.Dst = nil
+	c.Changed = true
+	return
+}