Skip to content

Commit 532878b

Browse files
Dean KarnDean Karn
authored andcommitted
initial translation/custom error code
1 parent 6f98212 commit 532878b

File tree

6 files changed

+238
-3
lines changed

6 files changed

+238
-3
lines changed

errors.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import (
55
"fmt"
66
"reflect"
77
"strings"
8+
9+
"github.com/go-playground/universal-translator"
810
)
911

1012
const (
1113
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
1214
)
1315

16+
type ValidationErrorsTranslations map[string]string
17+
1418
// InvalidValidationError describes an invalid argument passed to
1519
// `Struct`, `StructExcept`, StructPartial` or `Field`
1620
type InvalidValidationError struct {
@@ -39,18 +43,32 @@ func (ve ValidationErrors) Error() string {
3943

4044
buff := bytes.NewBufferString("")
4145

42-
var err *fieldError
46+
var fe *fieldError
4347

4448
for i := 0; i < len(ve); i++ {
4549

46-
err = ve[i].(*fieldError)
47-
buff.WriteString(err.Error())
50+
fe = ve[i].(*fieldError)
51+
buff.WriteString(fe.Error())
4852
buff.WriteString("\n")
4953
}
5054

5155
return strings.TrimSpace(buff.String())
5256
}
5357

58+
func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations {
59+
60+
trans := make(ValidationErrorsTranslations)
61+
62+
var fe *fieldError
63+
64+
for i := 0; i < len(ve); i++ {
65+
fe = ve[i].(*fieldError)
66+
trans[fe.ns] = fe.Translate(ut)
67+
}
68+
69+
return trans
70+
}
71+
5472
// FieldError contains all functions to get error details
5573
type FieldError interface {
5674

@@ -118,6 +136,13 @@ type FieldError interface {
118136
//
119137
// // eg. time.Time's type is time.Time
120138
Type() reflect.Type
139+
140+
// returns the FieldError's translated error
141+
// from the provided 'ut.Translator' and registered 'TranslationFunc'
142+
//
143+
// NOTE: is not registered translation can be found it returns the same
144+
// as calling fe.Error()
145+
Translate(ut ut.Translator) string
121146
}
122147

123148
// compile time interface checks
@@ -128,6 +153,7 @@ var _ error = new(fieldError)
128153
// with other properties that may be needed for error message creation
129154
// it complies with the FieldError interface
130155
type fieldError struct {
156+
v *Validate
131157
tag string
132158
actualTag string
133159
ns string
@@ -202,3 +228,23 @@ func (fe *fieldError) Type() reflect.Type {
202228
func (fe *fieldError) Error() string {
203229
return fmt.Sprintf(fieldErrMsg, fe.ns, fe.Field(), fe.tag)
204230
}
231+
232+
// Translate returns the FieldError's translated error
233+
// from the provided 'ut.Translator' and registered 'TranslationFunc'
234+
//
235+
// NOTE: is not registered translation can be found it returns the same
236+
// as calling fe.Error()
237+
func (fe *fieldError) Translate(ut ut.Translator) string {
238+
239+
m, ok := fe.v.transTagFunc[ut.Locale()]
240+
if !ok {
241+
return fe.Error()
242+
}
243+
244+
fn, ok := m[fe.tag]
245+
if !ok {
246+
return fe.Error()
247+
}
248+
249+
return fn(ut, fe)
250+
}

struct_level.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
117117

118118
v.errs = append(v.errs,
119119
&fieldError{
120+
v: v.v,
120121
tag: tag,
121122
actualTag: tag,
122123
ns: v.str1,
@@ -132,6 +133,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
132133

133134
v.errs = append(v.errs,
134135
&fieldError{
136+
v: v.v,
135137
tag: tag,
136138
actualTag: tag,
137139
ns: v.str1,

translations.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package validator
2+
3+
import "github.com/go-playground/universal-translator"
4+
5+
// TranslationFunc is the function type used to register or override
6+
// custom translations
7+
type TranslationFunc func(ut ut.Translator, fe FieldError) string
8+
9+
// RegisterTranslationsFunc allows for registering of translations
10+
// for a 'ut.Translator' for use withing the 'TranslationFunc'
11+
type RegisterTranslationsFunc func(ut ut.Translator) error

validator.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
113113

114114
v.errs = append(v.errs,
115115
&fieldError{
116+
v: v.v,
116117
tag: ct.aliasTag,
117118
actualTag: ct.tag,
118119
ns: v.str1,
@@ -129,6 +130,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns
129130

130131
v.errs = append(v.errs,
131132
&fieldError{
133+
v: v.v,
132134
tag: ct.aliasTag,
133135
actualTag: ct.tag,
134136
ns: v.str1,
@@ -323,6 +325,7 @@ OUTER:
323325

324326
v.errs = append(v.errs,
325327
&fieldError{
328+
v: v.v,
326329
tag: ct.aliasTag,
327330
actualTag: ct.actualAliasTag,
328331
ns: v.str1,
@@ -342,6 +345,7 @@ OUTER:
342345

343346
v.errs = append(v.errs,
344347
&fieldError{
348+
v: v.v,
345349
tag: tVal,
346350
actualTag: tVal,
347351
ns: v.str1,
@@ -381,6 +385,7 @@ OUTER:
381385

382386
v.errs = append(v.errs,
383387
&fieldError{
388+
v: v.v,
384389
tag: ct.aliasTag,
385390
actualTag: ct.tag,
386391
ns: v.str1,

validator_instance.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"strings"
88
"sync"
99
"time"
10+
11+
"github.com/go-playground/universal-translator"
1012
)
1113

1214
const (
@@ -53,6 +55,7 @@ type Validate struct {
5355
customFuncs map[reflect.Type]CustomTypeFunc
5456
aliases map[string]string
5557
validations map[string]Func
58+
transTagFunc map[string]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
5659
tagCache *tagCache
5760
structCache *structCache
5861
}
@@ -189,6 +192,27 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
189192
v.hasCustomFuncs = true
190193
}
191194

195+
func (v *Validate) RegisterTranslation(tag string, ut ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
196+
197+
if v.transTagFunc == nil {
198+
v.transTagFunc = make(map[string]map[string]TranslationFunc)
199+
}
200+
201+
if err = registerFn(ut); err != nil {
202+
return
203+
}
204+
205+
m, ok := v.transTagFunc[ut.Locale()]
206+
if !ok {
207+
m = make(map[string]TranslationFunc)
208+
v.transTagFunc[ut.Locale()] = m
209+
}
210+
211+
m[tag] = translationFn
212+
213+
return
214+
}
215+
192216
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
193217
//
194218
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.

validator_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111
"time"
1212

1313
. "gopkg.in/go-playground/assert.v1"
14+
15+
"github.com/go-playground/locales/en"
16+
"github.com/go-playground/locales/fr"
17+
"github.com/go-playground/locales/nl"
18+
"github.com/go-playground/universal-translator"
1419
)
1520

1621
// NOTES:
@@ -6413,3 +6418,145 @@ func TestRequired(t *testing.T) {
64136418
NotEqual(t, err, nil)
64146419
AssertError(t, err.(ValidationErrors), "Test.Value", "Test.Value", "Value", "Value", "required")
64156420
}
6421+
6422+
func TestTranslations(t *testing.T) {
6423+
en := en.New()
6424+
uni := ut.New(en, en, fr.New())
6425+
6426+
trans, _ := uni.GetTranslator("en")
6427+
fr, _ := uni.GetTranslator("fr")
6428+
6429+
validate := New()
6430+
err := validate.RegisterTranslation("required", trans,
6431+
func(ut ut.Translator) (err error) {
6432+
6433+
// using this stype because multiple translation may have to be added for the full translation
6434+
if err = ut.Add("required", "{0} is a required field", false); err != nil {
6435+
return
6436+
}
6437+
6438+
return
6439+
6440+
}, func(ut ut.Translator, fe FieldError) string {
6441+
6442+
t, err := ut.T(fe.Tag(), fe.Field())
6443+
if err != nil {
6444+
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
6445+
return fe.(*fieldError).Error()
6446+
}
6447+
6448+
return t
6449+
})
6450+
Equal(t, err, nil)
6451+
6452+
err = validate.RegisterTranslation("required", fr,
6453+
func(ut ut.Translator) (err error) {
6454+
6455+
// using this stype because multiple translation may have to be added for the full translation
6456+
if err = ut.Add("required", "{0} est un champ obligatoire", false); err != nil {
6457+
return
6458+
}
6459+
6460+
return
6461+
6462+
}, func(ut ut.Translator, fe FieldError) string {
6463+
6464+
t, err := ut.T(fe.Tag(), fe.Field())
6465+
if err != nil {
6466+
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
6467+
return fe.(*fieldError).Error()
6468+
}
6469+
6470+
return t
6471+
})
6472+
6473+
Equal(t, err, nil)
6474+
6475+
type Test struct {
6476+
Value interface{} `validate:"required"`
6477+
}
6478+
6479+
var test Test
6480+
6481+
err = validate.Struct(test)
6482+
NotEqual(t, err, nil)
6483+
6484+
errs := err.(ValidationErrors)
6485+
Equal(t, len(errs), 1)
6486+
6487+
fe := errs[0]
6488+
Equal(t, fe.Tag(), "required")
6489+
Equal(t, fe.Namespace(), "Test.Value")
6490+
Equal(t, fe.Translate(trans), fmt.Sprintf("%s is a required field", fe.Field()))
6491+
Equal(t, fe.Translate(fr), fmt.Sprintf("%s est un champ obligatoire", fe.Field()))
6492+
6493+
nl := nl.New()
6494+
uni2 := ut.New(nl, nl)
6495+
trans2, _ := uni2.GetTranslator("nl")
6496+
Equal(t, fe.Translate(trans2), "Key: 'Test.Value' Error:Field validation for 'Value' failed on the 'required' tag")
6497+
6498+
terrs := errs.Translate(trans)
6499+
Equal(t, len(terrs), 1)
6500+
6501+
v, ok := terrs["Test.Value"]
6502+
Equal(t, ok, true)
6503+
Equal(t, v, fmt.Sprintf("%s is a required field", fe.Field()))
6504+
6505+
terrs = errs.Translate(fr)
6506+
Equal(t, len(terrs), 1)
6507+
6508+
v, ok = terrs["Test.Value"]
6509+
Equal(t, ok, true)
6510+
Equal(t, v, fmt.Sprintf("%s est un champ obligatoire", fe.Field()))
6511+
6512+
type Test2 struct {
6513+
Value string `validate:"gt=1"`
6514+
}
6515+
6516+
var t2 Test2
6517+
6518+
err = validate.Struct(t2)
6519+
NotEqual(t, err, nil)
6520+
6521+
errs = err.(ValidationErrors)
6522+
Equal(t, len(errs), 1)
6523+
6524+
fe = errs[0]
6525+
Equal(t, fe.Tag(), "gt")
6526+
Equal(t, fe.Namespace(), "Test2.Value")
6527+
Equal(t, fe.Translate(trans), "Key: 'Test2.Value' Error:Field validation for 'Value' failed on the 'gt' tag")
6528+
}
6529+
6530+
func TestTranslationErrors(t *testing.T) {
6531+
6532+
en := en.New()
6533+
uni := ut.New(en, en, fr.New())
6534+
6535+
trans, _ := uni.GetTranslator("en")
6536+
trans.Add("required", "{0} is a required field", false) // using translator outside of validator also
6537+
6538+
validate := New()
6539+
err := validate.RegisterTranslation("required", trans,
6540+
func(ut ut.Translator) (err error) {
6541+
6542+
// using this stype because multiple translation may have to be added for the full translation
6543+
if err = ut.Add("required", "{0} is a required field", false); err != nil {
6544+
return
6545+
}
6546+
6547+
return
6548+
6549+
}, func(ut ut.Translator, fe FieldError) string {
6550+
6551+
t, err := ut.T(fe.Tag(), fe.Field())
6552+
if err != nil {
6553+
fmt.Printf("warning: error translating FieldError: %#v", fe.(*fieldError))
6554+
return fe.(*fieldError).Error()
6555+
}
6556+
6557+
return t
6558+
})
6559+
6560+
NotEqual(t, err, nil)
6561+
Equal(t, err.Error(), "error: conflicting key 'required' rule 'Unknown' with text '{0} is a required field', value being ignored")
6562+
}

0 commit comments

Comments
 (0)