Merge branch '2021.9.x-feature-select-options-i18n' into 2021.9.x

This commit is contained in:
Denis Arh
2021-12-17 13:05:09 +01:00
8 changed files with 277 additions and 39 deletions
+59 -8
View File
@@ -14,7 +14,8 @@ import (
func (svc resourceTranslationsManager) moduleExtended(ctx context.Context, res *types.Module) (out locale.ResourceTranslationSet, err error) {
var (
k types.LocaleKey
k types.LocaleKey
set locale.ResourceTranslationSet
)
for _, tag := range svc.locale.Tags() {
@@ -57,19 +58,26 @@ func (svc resourceTranslationsManager) moduleExtended(ctx context.Context, res *
Msg: svc.locale.TResourceFor(tag, f.ResourceTranslation(), k.Path),
})
// Extra field bits
converted, err := svc.moduleFieldValidatorErrorHandler(ctx, tag, f, k.Path)
// Expressions
set, err = svc.moduleFieldExpressionsHandler(ctx, tag, f)
if err != nil {
return nil, err
}
out = append(out, converted...)
out = append(out, set...)
// Extra field bits
set, err = svc.moduleFieldOptionsHandler(ctx, tag, f)
if err != nil {
return nil, err
}
out = append(out, set...)
}
}
return out, nil
}
func (svc resourceTranslationsManager) moduleFieldValidatorErrorHandler(ctx context.Context, tag language.Tag, f *types.ModuleField, k string) (locale.ResourceTranslationSet, error) {
func (svc resourceTranslationsManager) moduleFieldExpressionsHandler(ctx context.Context, tag language.Tag, f *types.ModuleField) (locale.ResourceTranslationSet, error) {
out := make(locale.ResourceTranslationSet, 0, 10)
for i, v := range f.Expressions.Validators {
@@ -77,7 +85,8 @@ func (svc resourceTranslationsManager) moduleFieldValidatorErrorHandler(ctx cont
rpl := strings.NewReplacer(
"{{validatorID}}", strconv.FormatUint(vContentID, 10),
)
tKey := rpl.Replace(k)
tKey := rpl.Replace(types.LocaleKeyModuleFieldValidatorError.Path)
out = append(out, &locale.ResourceTranslation{
Resource: f.ResourceTranslation(),
@@ -90,6 +99,48 @@ func (svc resourceTranslationsManager) moduleFieldValidatorErrorHandler(ctx cont
return out, nil
}
func (svc resourceTranslationsManager) moduleFieldOptionsHandler(ctx context.Context, tag language.Tag, f *types.ModuleField) (locale.ResourceTranslationSet, error) {
out := make(locale.ResourceTranslationSet, 0, 10)
optsUnknown, has := f.Options["options"]
if !has {
return nil, nil
}
optsSlice, is := optsUnknown.([]interface{})
if !is {
return nil, nil
}
for _, optUnknown := range optsSlice {
var value string
// what is this we're dealing with?
// slice of strings (values) or map (value+text)
switch opt := optUnknown.(type) {
case string:
value = opt
case map[string]interface{}:
value, is = opt["value"].(string)
if !is {
continue
}
}
trKey := strings.NewReplacer("{{value}}", value).Replace(types.LocaleKeyModuleFieldOptionsOptionTexts.Path)
out = append(out, &locale.ResourceTranslation{
Resource: f.ResourceTranslation(),
Lang: tag.String(),
Key: trKey,
Msg: svc.locale.TResourceFor(tag, f.ResourceTranslation(), trKey),
})
}
return out, nil
}
func (svc resourceTranslationsManager) pageExtended(ctx context.Context, res *types.Page) (out locale.ResourceTranslationSet, err error) {
var (
k types.LocaleKey
@@ -121,7 +172,7 @@ func (svc resourceTranslationsManager) pageExtended(ctx context.Context, res *ty
switch block.Kind {
case "Automation":
aux, err := svc.pageExtendedAutomatinBlock(tag, res, block, pbContentID, k)
aux, err := svc.pageExtendedAutomationBlock(tag, res, block, pbContentID, k)
if err != nil {
return nil, err
}
@@ -134,7 +185,7 @@ func (svc resourceTranslationsManager) pageExtended(ctx context.Context, res *ty
return
}
func (svc resourceTranslationsManager) pageExtendedAutomatinBlock(tag language.Tag, res *types.Page, block types.PageBlock, blockID uint64, k types.LocaleKey) (locale.ResourceTranslationSet, error) {
func (svc resourceTranslationsManager) pageExtendedAutomationBlock(tag language.Tag, res *types.Page, block types.PageBlock, blockID uint64, k types.LocaleKey) (locale.ResourceTranslationSet, error) {
out := make(locale.ResourceTranslationSet, 0, 10)
bb, _ := block.Options["buttons"].([]interface{})
+24 -16
View File
@@ -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)
}
@@ -485,11 +493,11 @@ func (svc module) handleUpdate(ctx context.Context, upd *types.Module) moduleUpd
}
// Get max validatorID for later use
vvID := make([]uint64, len(res.Fields))
for i, f := range res.Fields {
vvID := make(map[uint64]uint64)
for _, f := range res.Fields {
for _, v := range f.Expressions.Validators {
if vvID[i] < v.ValidatorID {
vvID[i] = v.ValidatorID
if vvID[f.ID] < v.ValidatorID {
vvID[f.ID] = v.ValidatorID
}
}
}
@@ -529,11 +537,11 @@ func (svc module) handleUpdate(ctx context.Context, upd *types.Module) moduleUpd
}
// Assure validatorIDs
for i, f := range res.Fields {
for _, f := range res.Fields {
for j, v := range f.Expressions.Validators {
if v.ValidatorID == 0 {
vvID[i] += 1
v.ValidatorID = vvID[i]
vvID[f.ID] += 1
v.ValidatorID = vvID[f.ID]
f.Expressions.Validators[j] = v
changes |= moduleFieldsChanged
+10 -1
View File
@@ -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
}
+91 -6
View File
@@ -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]
+2
View File
@@ -19,6 +19,8 @@ const (
moduleFieldOptionIsUnique = "isUnique"
moduleFieldOptionIsUniqueMultiValue = "isUniqueMultiValue"
moduleFieldOptionOptions = "options"
moduleFieldNumberOptionPrecision = "precision"
moduleFieldNumberOptionPrecisionMin uint = 0
moduleFieldNumberOptionPrecisionMax uint = 6
+78
View File
@@ -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"])
}
})
}
}
+8 -8
View File
@@ -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
+5
View File
@@ -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
}