Support for select field option text translations
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Generated
+10
-1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -19,6 +19,8 @@ const (
|
||||
moduleFieldOptionIsUnique = "isUnique"
|
||||
moduleFieldOptionIsUniqueMultiValue = "isUniqueMultiValue"
|
||||
|
||||
moduleFieldOptionOptions = "options"
|
||||
|
||||
moduleFieldNumberOptionPrecision = "precision"
|
||||
moduleFieldNumberOptionPrecisionMin uint = 0
|
||||
moduleFieldNumberOptionPrecisionMax uint = 6
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
+8
-8
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user