diff --git a/pkg/locales/middleware/caching_middleware.go b/pkg/locales/middleware/caching_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..8b4635ab4a0214f99407b67020737724ebf3c841
--- /dev/null
+++ b/pkg/locales/middleware/caching_middleware.go
@@ -0,0 +1,53 @@
+package service
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	service "git.perx.ru/perxis/perxis-go/pkg/locales"
+)
+
+func CachingMiddleware(cache *cache.Cache) Middleware {
+	return func(next service.Locales) service.Locales {
+		return &cachingMiddleware{
+			cache: cache,
+			next:  next,
+		}
+	}
+}
+
+type cachingMiddleware struct {
+	cache *cache.Cache
+	next  service.Locales
+}
+
+func (m cachingMiddleware) Create(ctx context.Context, locale *service.Locale) (loc *service.Locale, err error) {
+
+	loc, err = m.next.Create(ctx, locale)
+	if err == nil {
+		m.cache.Remove(loc.SpaceID)
+	}
+	return loc, err
+}
+
+func (m cachingMiddleware) List(ctx context.Context, spaceId string) (locales []*service.Locale, err error) {
+
+	value, e := m.cache.Get(spaceId)
+	if e == nil {
+		return value.([]*service.Locale), err
+	}
+	locales, err = m.next.List(ctx, spaceId)
+	if err == nil {
+		m.cache.Set(spaceId, locales)
+	}
+	return locales, err
+}
+
+func (m cachingMiddleware) Delete(ctx context.Context, spaceId string, localeId string) (err error) {
+
+	err = m.next.Delete(ctx, spaceId, localeId)
+	if err == nil {
+		m.cache.Remove(spaceId)
+	}
+	return err
+}
diff --git a/pkg/locales/middleware/caching_middleware_test.go b/pkg/locales/middleware/caching_middleware_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..de5e7a9f742b6336ecd5a67d529ed468236838f4
--- /dev/null
+++ b/pkg/locales/middleware/caching_middleware_test.go
@@ -0,0 +1,130 @@
+package service
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	locmocks "git.perx.ru/perxis/perxis-go/pkg/locales/mocks"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestLocalesCache(t *testing.T) {
+
+	const (
+		loc1    = "loc1"
+		loc2    = "loc2"
+		spaceID = "spaceID"
+		size    = 5
+		ttl     = 20 * time.Millisecond
+	)
+
+	ctx := context.Background()
+
+	t.Run("List from Cache", func(t *testing.T) {
+		loc := &locmocks.Locales{}
+
+		svc := CachingMiddleware(cache.NewCache(size, ttl))(loc)
+
+		loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
+
+		vl1, err := svc.List(ctx, spaceID)
+		require.NoError(t, err)
+
+		vl2, err := svc.List(ctx, spaceID)
+		require.NoError(t, err)
+		assert.Same(t, vl1[0], vl2[0], "Ожидается что при повторном запросе объекты будут получены из кэша.")
+
+		loc.AssertExpectations(t)
+	})
+
+	t.Run("Invalidate cache", func(t *testing.T) {
+		t.Run("After Delete", func(t *testing.T) {
+			loc := &locmocks.Locales{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(loc)
+
+			loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
+
+			vl1, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается что при повторном запросе объекты будут получены из кэша.")
+
+			loc.On("Delete", mock.Anything, spaceID, loc1).Return(nil).Once()
+
+			err = svc.Delete(ctx, spaceID, loc1)
+			require.NoError(t, err)
+
+			loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{}, nil).Once()
+
+			vl3, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Len(t, vl3, 0, "Ожидается что после удаление объекты будут удалены из кеша.")
+
+			loc.AssertExpectations(t)
+		})
+
+		t.Run("After Create", func(t *testing.T) {
+			loc := &locmocks.Locales{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(loc)
+
+			loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
+
+			vl1, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается что при повторном запросе объекты будут получены из кэша.")
+
+			loc.On("Create", mock.Anything, mock.Anything).Return(&locales.Locale{ID: loc2, Name: "name2", SpaceID: spaceID}, nil).Once()
+
+			_, err = svc.Create(ctx, &locales.Locale{ID: loc2, Name: "name2", SpaceID: spaceID})
+			require.NoError(t, err)
+
+			loc.On("List", mock.Anything, spaceID).
+				Return([]*locales.Locale{
+					{ID: loc1, Name: "name1", SpaceID: spaceID},
+					{ID: loc2, Name: "name2", SpaceID: spaceID},
+				}, nil).Once()
+
+			vl3, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Len(t, vl3, 2, "Ожидается что после создания нового объекта данные будут удалены из кеша и получены из сервиса.")
+
+			loc.AssertExpectations(t)
+		})
+
+		t.Run("After TTL expired", func(t *testing.T) {
+			loc := &locmocks.Locales{}
+
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(loc)
+
+			loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
+
+			vl1, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+
+			vl2, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.Same(t, vl1[0], vl2[0], "Ожидается что при повторном запросе объекты будут получены из кэша.")
+
+			time.Sleep(2 * ttl)
+			loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
+
+			vl3, err := svc.List(ctx, spaceID)
+			require.NoError(t, err)
+			assert.NotSame(t, vl2[0], vl3[0], "Ожидается что элементы будут получены из кэша.")
+
+			loc.AssertExpectations(t)
+		})
+	})
+}
diff --git a/pkg/locales/middleware/error_logging_middleware.go b/pkg/locales/middleware/error_logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..171482568af8d1988bb1e2e6625884e78683c71e
--- /dev/null
+++ b/pkg/locales/middleware/error_logging_middleware.go
@@ -0,0 +1,60 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/error_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements locales.Locales that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   locales.Locales
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the locales.Locales with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next locales.Locales) locales.Locales {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Create(ctx context.Context, locale *locales.Locale) (created *locales.Locale, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Create(ctx, locale)
+}
+
+func (m *errorLoggingMiddleware) Delete(ctx context.Context, spaceId string, localeId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Delete(ctx, spaceId, localeId)
+}
+
+func (m *errorLoggingMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.List(ctx, spaceId)
+}
diff --git a/pkg/locales/middleware/logging_middleware.go b/pkg/locales/middleware/logging_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e9a16b14ba1b2c619271d746245de4f1ddbcaae
--- /dev/null
+++ b/pkg/locales/middleware/logging_middleware.go
@@ -0,0 +1,142 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/access_log
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// loggingMiddleware implements locales.Locales that is instrumented with logging
+type loggingMiddleware struct {
+	logger *zap.Logger
+	next   locales.Locales
+}
+
+// LoggingMiddleware instruments an implementation of the locales.Locales with simple logging
+func LoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next locales.Locales) locales.Locales {
+		return &loggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *loggingMiddleware) Create(ctx context.Context, locale *locales.Locale) (created *locales.Locale, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"locale": locale} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Create.Request", fields...)
+
+	created, err = m.next.Create(ctx, locale)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"created": created,
+		"err":     err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Create.Response", fields...)
+
+	return created, err
+}
+
+func (m *loggingMiddleware) Delete(ctx context.Context, spaceId string, localeId string) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":      ctx,
+		"spaceId":  spaceId,
+		"localeId": localeId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Request", fields...)
+
+	err = m.next.Delete(ctx, spaceId, localeId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Delete.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":     ctx,
+		"spaceId": spaceId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("List.Request", fields...)
+
+	locales, err = m.next.List(ctx, spaceId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"locales": locales,
+		"err":     err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("List.Response", fields...)
+
+	return locales, err
+}
diff --git a/pkg/locales/middleware/middleware.go b/pkg/locales/middleware/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..726b535247256a109f0fc5aa100e0a61cc928555
--- /dev/null
+++ b/pkg/locales/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/middleware
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/middleware -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	"go.uber.org/zap"
+)
+
+type Middleware func(locales.Locales) locales.Locales
+
+func WithLog(s locales.Locales, logger *zap.Logger, log_access bool) locales.Locales {
+	if logger == nil {
+		logger = zap.NewNop()
+	}
+
+	logger = logger.Named("Locales")
+	s = ErrorLoggingMiddleware(logger)(s)
+	if log_access {
+		s = LoggingMiddleware(logger)(s)
+	}
+	s = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/pkg/locales/middleware/recovering_middleware.go b/pkg/locales/middleware/recovering_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..44b198550418034e1963fec5ccbd405e0ea12ef4
--- /dev/null
+++ b/pkg/locales/middleware/recovering_middleware.go
@@ -0,0 +1,67 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/recovery
+// gowrap: http://github.com/hexdigest/gowrap
+
+package service
+
+//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/recovery -o recovering_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/locales"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements locales.Locales that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   locales.Locales
+}
+
+// RecoveringMiddleware instruments an implementation of the locales.Locales with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next locales.Locales) locales.Locales {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Create(ctx context.Context, locale *locales.Locale) (created *locales.Locale, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Create(ctx, locale)
+}
+
+func (m *recoveringMiddleware) Delete(ctx context.Context, spaceId string, localeId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Delete(ctx, spaceId, localeId)
+}
+
+func (m *recoveringMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.List(ctx, spaceId)
+}