From de11e7d20af0c8d7a30926cced31772196d2a7d0 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Thu, 2 Dec 2021 13:18:05 +0100 Subject: [PATCH] Support for select field option text translations --- compose/service/module.go | 26 ++++--- compose/types/locale.gen.go | 11 ++- compose/types/module_field.go | 97 +++++++++++++++++++++++++-- compose/types/module_field_options.go | 2 + compose/types/module_field_test.go | 78 +++++++++++++++++++++ compose/types/type_labels.gen.go | 16 ++--- def/compose.module-field.yaml | 5 ++ 7 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 compose/types/module_field_test.go diff --git a/compose/service/module.go b/compose/service/module.go index 3539d8bb6..b43f3f7fd 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -145,15 +145,8 @@ func (svc module) Find(ctx context.Context, filter types.ModuleFilter) (set type return err } - // i18n - tag := locale.GetAcceptLanguageFromContext(ctx) set.Walk(func(m *types.Module) error { - m.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, m.ResourceTranslation())) - - m.Fields.Walk(func(mf *types.ModuleField) error { - mf.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, mf.ResourceTranslation())) - return nil - }) + svc.proc(ctx, m) return nil }) return nil @@ -220,6 +213,20 @@ func (svc module) FindByAny(ctx context.Context, namespaceID uint64, identifier return m, nil } +func (svc module) proc(ctx context.Context, m *types.Module) { + if svc.locale == nil || svc.locale.Locale() == nil { + return + } + + tag := locale.GetAcceptLanguageFromContext(ctx) + m.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, m.ResourceTranslation())) + + m.Fields.Walk(func(mf *types.ModuleField) error { + mf.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, mf.ResourceTranslation())) + return nil + }) +} + func (svc module) Create(ctx context.Context, new *types.Module) (*types.Module, error) { var ( ns *types.Namespace @@ -444,9 +451,10 @@ func (svc module) lookup(ctx context.Context, namespaceID uint64, lookup func(*m } return loadModuleFields(ctx, svc.store, m) - }() + svc.proc(ctx, m) + return m, svc.recordAction(ctx, aProps, ModuleActionLookup, err) } diff --git a/compose/types/locale.gen.go b/compose/types/locale.gen.go index aaa6e824e..3d33fceb1 100644 --- a/compose/types/locale.gen.go +++ b/compose/types/locale.gen.go @@ -14,8 +14,9 @@ package types import ( "fmt" - "github.com/cortezaproject/corteza-server/pkg/locale" "strconv" + + "github.com/cortezaproject/corteza-server/pkg/locale" ) type ( @@ -71,6 +72,12 @@ var ( Path: "expression.validator.{{validatorID}}.error", CustomHandler: "validatorError", } + LocaleKeyModuleFieldOptionsOptionTexts = LocaleKey{ + Name: "optionsOptionTexts", + Resource: ModuleFieldResourceTranslationType, + Path: "meta.options.{{value}}.text", + CustomHandler: "optionsOptionTexts", + } LocaleKeyModuleName = LocaleKey{ Name: "name", Resource: ModuleResourceTranslationType, @@ -154,6 +161,7 @@ func (r *ModuleField) DecodeTranslations(tt locale.ResourceTranslationIndex) { r.decodeTranslationsHintView(tt) r.decodeTranslationsHintEdit(tt) r.decodeTranslationsValidatorError(tt) + r.decodeTranslationsOptionsOptionTexts(tt) } func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) { @@ -171,6 +179,7 @@ func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) { out = append(out, r.encodeTranslationsHintView()...) out = append(out, r.encodeTranslationsHintEdit()...) out = append(out, r.encodeTranslationsValidatorError()...) + out = append(out, r.encodeTranslationsOptionsOptionTexts()...) return out } diff --git a/compose/types/module_field.go b/compose/types/module_field.go index e13428a02..2bd514705 100644 --- a/compose/types/module_field.go +++ b/compose/types/module_field.go @@ -103,6 +103,58 @@ func (f *ModuleField) decodeTranslationsHintEdit(tt locale.ResourceTranslationIn } } +// Decodes translations and modifies options +// +// Why "options-option-texts"? Because we're translating option txts under options key-value +func (f *ModuleField) decodeTranslationsOptionsOptionTexts(tt locale.ResourceTranslationIndex) { + var ( + tr *locale.ResourceTranslation + ) + + optsUnknown, has := f.Options["options"] + if !has { + return + } + + optsSlice, is := optsUnknown.([]interface{}) + if !is { + return + } + + for i, optUnknown := range optsSlice { + outOpt := map[string]string{} + + // what is this we're dealing with? slice of strings (values) or map (value+text) + switch opt := optUnknown.(type) { + case string: + // cast string (value) into map (value+text) + // and use value as text (as a fallback in case + // of missing translation) + outOpt["value"] = opt + outOpt["text"] = opt + + case map[string]interface{}: + outOpt["value"], is = opt["value"].(string) + if !is { + continue + } + + outOpt["text"], _ = opt["text"].(string) + } + + // find the translation for that value + // and update the option (effectively overwriting + // the original text value (in case of map option) + trKey := strings.NewReplacer("{{value}}", outOpt["value"]).Replace(LocaleKeyModuleFieldOptionsOptionTexts.Path) + if tr = tt.FindByKey(trKey); tr != nil { + outOpt["text"] = tr.Msg + } + + // Update slice item with translated option + optsSlice[i] = outOpt + } +} + func (m *ModuleField) encodeTranslationsValidatorError() (out locale.ResourceTranslationSet) { out = make(locale.ResourceTranslationSet, 0, 3) @@ -179,12 +231,45 @@ func (f *ModuleField) encodeTranslationsHintEdit() (out locale.ResourceTranslati return out } -func (m ModuleField) Clone() *ModuleField { - return &m +// extracts option texts and converts (encodes) them to translations +func (f *ModuleField) encodeTranslationsOptionsOptionTexts() (out locale.ResourceTranslationSet) { + out = make(locale.ResourceTranslationSet, 0, 3) + + optsUnknown, has := f.Options["options"] + if !has { + return + } + + optsSlice, is := optsUnknown.([]interface{}) + if !is { + return + } + + for _, optUnknown := range optsSlice { + // we only care about maps (items with value & text) + switch opt := optUnknown.(type) { + case map[string]interface{}: + value, _ := opt["value"].(string) + text, _ := opt["text"].(string) + + out = append(out, &locale.ResourceTranslation{ + Resource: f.ResourceTranslation(), + Key: strings.NewReplacer("{{value}}", value). + Replace(LocaleKeyModuleFieldOptionsOptionTexts.Path), + Msg: text, + }) + } + } + + return } -func (m ModuleField) setOptionKey(v interface{}, kk ...string) { - opt := m.Options +func (f ModuleField) Clone() *ModuleField { + return &f +} + +func (f ModuleField) setOptionKey(v interface{}, kk ...string) { + opt := f.Options for _, k := range kk[0 : len(kk)-1] { _, ok := opt[k] @@ -206,8 +291,8 @@ func (m ModuleField) setOptionKey(v interface{}, kk ...string) { opt[k] = v } -func (m ModuleField) getOptionKey(kk ...string) interface{} { - opt := m.Options +func (f ModuleField) getOptionKey(kk ...string) interface{} { + opt := f.Options for _, k := range kk[0 : len(kk)-1] { _, ok := opt[k] diff --git a/compose/types/module_field_options.go b/compose/types/module_field_options.go index 3261d9023..35492bae2 100644 --- a/compose/types/module_field_options.go +++ b/compose/types/module_field_options.go @@ -19,6 +19,8 @@ const ( moduleFieldOptionIsUnique = "isUnique" moduleFieldOptionIsUniqueMultiValue = "isUniqueMultiValue" + moduleFieldOptionOptions = "options" + moduleFieldNumberOptionPrecision = "precision" moduleFieldNumberOptionPrecisionMin uint = 0 moduleFieldNumberOptionPrecisionMax uint = 6 diff --git a/compose/types/module_field_test.go b/compose/types/module_field_test.go new file mode 100644 index 000000000..8746de13e --- /dev/null +++ b/compose/types/module_field_test.go @@ -0,0 +1,78 @@ +package types + +import ( + "testing" + + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/stretchr/testify/require" +) + +func TestModuleField_decodeTranslationsOptionsOptionTexts(t *testing.T) { + rti := locale.ResourceTranslationIndex{ + "meta.options.val1.text": &locale.ResourceTranslation{Msg: "TEXT-1"}, + } + tests := []struct { + name string + opts ModuleFieldOptions + out interface{} + }{ + // TODO: Add test cases. + {"all-nil", nil, nil}, + {"options-nil", + ModuleFieldOptions{ + "options": nil, + }, + nil, + }, + {"options-foo", + ModuleFieldOptions{ + "options": "foo", + }, + nil, + }, + {"options-foo-number", + ModuleFieldOptions{ + "options": []interface{}{1}, + }, + nil, + }, + {"options-valid-slice", + ModuleFieldOptions{ + "options": []interface{}{ + "val1", + "val2", + }, + }, + []interface{}{ + map[string]string{"value": "val1", "text": "TEXT-1"}, + map[string]string{"value": "val2", "text": "val2"}, + }, + }, + {"options-valid-map", + ModuleFieldOptions{ + "options": []interface{}{ + map[string]interface{}{"value": "val1", "text": "Text1"}, + map[string]interface{}{"value": "val2", "text": "Text2"}, + }, + }, + []interface{}{ + map[string]string{"value": "val1", "text": "TEXT-1"}, + map[string]string{"value": "val2", "text": "Text2"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + req = require.New(t) + f = &ModuleField{Options: tt.opts} + ) + + f.decodeTranslationsOptionsOptionTexts(rti) + + if tt.out != nil { + req.Equal(tt.out, f.Options["options"]) + } + }) + } +} diff --git a/compose/types/type_labels.gen.go b/compose/types/type_labels.gen.go index 22e9ab5c7..33c6ab529 100644 --- a/compose/types/type_labels.gen.go +++ b/compose/types/type_labels.gen.go @@ -57,17 +57,17 @@ func (m Module) LabelResourceID() uint64 { } // SetLabel adds new label to label map -func (m *ModuleField) SetLabel(key string, value string) { - if m.Labels == nil { - m.Labels = make(map[string]string) +func (f *ModuleField) SetLabel(key string, value string) { + if f.Labels == nil { + f.Labels = make(map[string]string) } - m.Labels[key] = value + f.Labels[key] = value } // GetLabels adds new label to label map -func (m ModuleField) GetLabels() map[string]string { - return m.Labels +func (f ModuleField) GetLabels() map[string]string { + return f.Labels } // GetLabels adds new label to label map @@ -76,8 +76,8 @@ func (ModuleField) LabelResourceKind() string { } // GetLabels adds new label to label map -func (m ModuleField) LabelResourceID() uint64 { - return m.ID +func (f ModuleField) LabelResourceID() uint64 { + return f.ID } // SetLabel adds new label to label map diff --git a/def/compose.module-field.yaml b/def/compose.module-field.yaml index 0277e2b33..b22fb9586 100644 --- a/def/compose.module-field.yaml +++ b/def/compose.module-field.yaml @@ -22,3 +22,8 @@ locale: - { name: hintView, path: meta.hint.view, custom: true, customHandler: hintView } - { name: hintEdit, path: meta.hint.edit, custom: true, customHandler: hintEdit } - { name: validatorError, path: "expression.validator.{{validatorID}}.error", custom: true, customHandler: validatorError } + - { name: optionsOptionTexts, + path: "meta.options.{{value}}.text", + custom: true, + customHandler: optionsOptionTexts + }