Closed mehran-prs closed 4 years ago
Thank you for your attempt to add message translation support!
I noticed a global variable Lang
is used to determine which language should be used.
If the validation error messages are meant to be shown to end users (that's also the main reason why translation is wanted), the language in use would be determined by the request context. A global language setting would not work in this scenario.
you're welcome qiangxue,
OK, Each rule can return ValidationErr
that is something like this:
type ErrMessage struct {
Lang string
TranslationKey string
DefaultMessage string
Message string
Params []interface{}
}
func (e *ErrMessage) Lang(lang string) {
e.lang = lang
}
func (e ErrMessage) Error() string {
msg := MsgInLang(lang, e.translationKey, e.defaultMessage, e.message)
return fmt.Sprintf(msg, e.translationParams...)
}
and finally, after validating our data, we can set lang on erros and get translation in any language that we want.
Are you ok with this changes?
Hi @qiangxue
I changed source code, now each rule return ErrMessage
if data was invalid, it returns message after a call to the Error
(generate it on the fly, it now is relative to error's language).
If we want to get the error for validation rule in other languages, just need to call to ToLang(lang string)
method on the returned error.
Also if need to get the error in other languages for struct validation result, again just need to call to ToLang(lang string)
method on Errors
map.
priority to returning error message for each ErrMessage is :
Message
property of ErrMessage
).everything works the same as before and we have feature-rich validation errors now :)
@qiangxue can you see my PR, please?
@mehran-prs I have reviewed your code again. I really like the idea of enhancing the validation error definition so that it supports translation. However, I think i18n/translation doesn't belong to this library. There are existing libraries doing this.
Here's what I'm thinking:
Error()
method returns the actual error message using the default message generation approach, 4) methods exposing the template and the parameters so that they can be called if translation is needed. StringRule
doesn't need this because its message is not parameterized. This would avoid breaking BC.Errors
by adding a method like Translate
which takes a Translator
interface to translate the messages in Errors
. Note that Translate
doesn't implement any real translation logic. It delegates all those to the Translator (from another library). We should examine popular existing i18n libraries and define a sensible Translator
interface.I'd like to hear your comments. Also, before you spend time creating a major PR, can we discuss about the design first? I feel bad when denying a PR like this because I know you have spent a lot of time on it. Thank you!
You're right @qiangxue, it's better to have a translator interface instead of implementing i18n/translator. I had to talk to you before creating PR. go-i18n seems to be the most used translator package in Go and I gave it a try.
In addition to error struct fields that you said, I think it should also have:
translationKey
(to use as the key for finding the message in each language), plural
(type int|float) field that uses as the identifier to detect sentence should be in plural form or singular in the translator (e.g email must be at least one character
or email must be at least 4 characters
). I think something like this can be good for us:
Define a variable that keeps the prefix of validation keys to prevent key conflict with other translation keys.
var ValidationKeysPrefix="validation_"
On each rule have a Key
method, so users can customize their translation key per validation (This does what Error
method does for default error messages.).
// e.g on InRule :
func (r InRule) Key(key string) ErrMessage {
r.translationKey=key
return r
}
ErrMessage struct:
type ErrMessage struct {
translationKey string
params map[string]interface{}
plurar interface{}
message string // default message that using for Error() method.
}
// definition of methods that expose params,translationkey, ...
// Translate method on ErrMessage: func (e ErrMessage) Translate(t Translator) (string, error) { return t.TranslateSingleFieldErr(e) }
* `Translate` method on `Errors`
```go
func (e Errors) Translate(t Translator) (string, error) {
// use t.TranslateStructFieldErr(field string,err) on each error
}
Translator
interface
type Translator interface {
TranslateStructFieldErr(field string,err ErrMessage) (string,error)
TranslateSingleFieldErr(err ErrMessage) (string,error)
}
Are you ok with something like this?
I'm thinking about an even simpler solution which is still flexible enough to support i18n.
First define an Error struct:
type Error struct {
code string
message string
params map[string]interface{}
}
The Error struct implements the error interface and also the methods exposing its fields. This definition should be sufficient for representing most error messages that need to be translated.
For each validation rule, modify its Error()
method so that it can also accept an error
. The default error messages for the rules are represented using the Error
struct so that they can be translated, if needed.
For error messages that need more complex translation rules, users can create their own error types and pass them into the rules.
OK, So I implement it.
@qiangxue I updated my PR.
simple map in messages
is just for the package's default messages, this keeps the package cleaner.
If you think my repo needs to squash commits, just say.
Great job! Thank you very much for your contribution!
you're welcome Qiangxue :)
Hi, I added the translation feature and also modified all the rules to use this feature.
I assume each rule's
message
property is the custom message, so any user sets it, that rule returns the user's message, otherwise, return the translated message.Each rule can return the translated message if it does not exists return the English message, and if the English message does not exist for that rule, return rule's default message.
All functions work just like before Except one function:
I Changed
NewStringRule
function signature fromfunc validation.NewStringRule(validation stringValidator,message string)
tofunc validation.NewStringRule(validation stringValidator,ruleName string)
this is because we don't need the message here anymore, just need to get a name for that rule and get the rule's message from the translation map.
I also changed your package version from
3
to4
because ofNewStringRule
function's signature changes.