added TranslateEntity

This commit is contained in:
Jörn Friedrich Dreyer
2024-08-06 14:25:30 +02:00
committed by jkoberg
parent fdca5e126b
commit 464c780c82
2 changed files with 291 additions and 0 deletions
+122
View File
@@ -6,6 +6,7 @@ import (
"errors"
"io/fs"
"os"
"reflect"
"github.com/leonelquinteros/gotext"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
@@ -52,6 +53,127 @@ func (t Translator) Translate(str, locale string) string {
return t.Locale(locale).Get(str)
}
type field func() (string, []any)
func TranslateField(fieldName string, fn ...any) field {
return func() (string, []any) {
return fieldName, fn
}
}
func TranslateLocation(t Translator, locale string) func(string, ...any) string {
return t.Locale(locale).Get
}
// TranslateEntity function tranlate all described fields in the struct in the given locale including nested structs
//
// type InnreStruct struct {
// Description string
// DisplayName *string
// }
//
// type TopLevelStruct struct {
// Description string
// DisplayName *string
// SubStruct *InnreStruct
// }
//
// TranslateEntity(tt.args.structPtr, translateFunc(),
// TranslateField("Description"),
// TranslateField("DisplayName"),
// TranslateField("SubStruct",
// TranslateField("Description"),
// TranslateField("DisplayName")))
func TranslateEntity(entity any, tr func(string, ...any) string, fields ...any) error {
value := reflect.ValueOf(entity)
if value.Kind() != reflect.Ptr || !value.IsNil() && value.Elem().Kind() != reflect.Struct {
// must be a pointer to a struct
return ErrStructPointer
}
if value.IsNil() {
// treat a nil struct pointer as valid
return nil
}
translateInner(value, tr, fields...)
return nil
}
func translateInner(value reflect.Value, tr func(string, ...any) string, fields ...any) {
for _, fl := range fields {
if _, ok := fl.(field); ok {
translateField(value, tr, fl.(field))
}
}
}
func translateField(value reflect.Value, tr func(string, ...any) string, fl field) {
if !value.IsValid() {
return
}
fieldName, fields := fl()
// exported field
if value.Kind() == reflect.Ptr {
if value.IsNil() {
return
}
value = value.Elem()
}
innerValue := value.FieldByName(fieldName)
if !innerValue.IsValid() {
return
}
if isStruct(innerValue) {
translateInner(innerValue, tr, fields...)
return
}
translateStringField(value, tr, fieldName)
}
func translateStringField(value reflect.Value, tr func(string, ...any) string, fieldName string) {
if value.Kind() == reflect.Ptr {
if value.IsNil() {
return
}
value = value.Elem()
}
// exported field
f := value.FieldByName(fieldName)
if f.IsValid() {
if f.Kind() == reflect.Ptr {
if f.IsNil() {
return
}
f = f.Elem()
}
// A Value can be changed only if it is
// addressable and was not obtained by
// the use of unexported struct fields.
if f.CanSet() {
// change value
if f.Kind() == reflect.String {
val := tr(f.String())
if val == "" {
return
}
f.SetString(val)
}
}
}
}
func isStruct(r reflect.Value) bool {
if r.Kind() == reflect.Ptr {
r = r.Elem()
}
return r.Kind() == reflect.Struct
}
var (
// ErrStructPointer is the error that a struct being validated is not specified as a pointer.
ErrStructPointer = errors.New("only a pointer to a struct can be validated")
ErrUnsupportedType = errors.New("unsupported type")
)
// Locale returns the gotext.Locale, use `.Get` method to translate strings
func (t Translator) Locale(locale string) *gotext.Locale {
l := gotext.NewLocaleFS(locale, t.fs)
+169
View File
@@ -0,0 +1,169 @@
package l10n
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTranslateStruct(t *testing.T) {
type InnreStruct struct {
Description string
DisplayName *string
}
type TopLevelStruct struct {
Description string
DisplayName *string
SubStruct *InnreStruct
}
toStrPointer := func(str string) *string {
return &str
}
type args struct {
structPtr interface{}
request []any
}
tests := []struct {
name string
args args
expected any
wantErr bool
}{
{
name: "empty struct",
args: args{
structPtr: &TopLevelStruct{},
request: []any{
TranslateField("Description"),
TranslateField("DisplayName"),
TranslateField("NotExistingSubStructName",
TranslateField("Description"),
TranslateField("DisplayName")),
},
},
expected: &TopLevelStruct{},
},
{
name: "empty struct",
args: args{
structPtr: &TopLevelStruct{},
request: []any{
TranslateField("Description"),
TranslateField("DisplayName"),
TranslateField("SubStruct",
TranslateField("Description"),
TranslateField("DisplayName"))},
},
expected: &TopLevelStruct{},
},
{
name: "empty struct, not existing field",
args: args{
structPtr: &TopLevelStruct{
Description: "description",
DisplayName: toStrPointer("displayName"),
},
request: []any{
TranslateField("NotExistingFieldName"),
TranslateField("SubStruct",
TranslateField("NotExistingFieldName"))},
},
expected: &TopLevelStruct{
Description: "description",
DisplayName: toStrPointer("displayName"),
},
},
{
name: "inner struct DisplayName empy",
args: args{
structPtr: &TopLevelStruct{
Description: "description",
DisplayName: toStrPointer("displayName"),
},
request: []any{TranslateField("Description"),
TranslateField("DisplayName"),
TranslateField("SubStruct",
TranslateField("Description"),
TranslateField("DisplayName"))},
},
expected: &TopLevelStruct{
Description: "new Description",
DisplayName: toStrPointer("new DisplayName"),
},
},
{
name: "inner struct full",
args: args{
structPtr: &TopLevelStruct{
Description: "description",
DisplayName: toStrPointer("displayName"),
},
request: []any{TranslateField("Description"),
TranslateField("DisplayName"),
TranslateField("SubStruct",
TranslateField("Description"),
TranslateField("DisplayName"))},
},
expected: &TopLevelStruct{
Description: "new Description",
DisplayName: toStrPointer("new DisplayName"),
},
},
{
name: "full struct",
args: args{
structPtr: &TopLevelStruct{
Description: "description",
DisplayName: toStrPointer("displayName"),
SubStruct: &InnreStruct{
Description: "inner",
DisplayName: toStrPointer("innerDisplayName"),
},
},
request: []any{
TranslateField("Description"),
TranslateField("DisplayName"),
TranslateField("SubStruct",
TranslateField("Description"),
TranslateField("DisplayName"))},
},
expected: &TopLevelStruct{
Description: "new Description",
DisplayName: toStrPointer("new DisplayName"),
SubStruct: &InnreStruct{
Description: "new Inner",
DisplayName: toStrPointer("new InnerDisplayName"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := TranslateEntity(tt.args.structPtr, mock(), tt.args.request...)
if (err != nil) != tt.wantErr {
t.Errorf("TranslateEntity() error = %v, wantErr %v", err, tt.wantErr)
}
assert.Equal(t, tt.expected, tt.args.structPtr)
})
}
}
func mock() func(string, ...interface{}) string {
return func(s string, i ...interface{}) string {
switch s {
case "description":
return "new Description"
case "displayName":
return "new DisplayName"
case "inner":
return "new Inner"
case "innerDisplayName":
return "new InnerDisplayName"
}
return s
}
}