-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Project Focus? #7
Comments
Hey @jayd3e Glad you're enjoying validator and interested in this project also :) I'll try to answer your questions and give a little history, sorry if this is long-winded. Yes this is still actively maintained 😃 I created the universal-translator and locales package which it uses as I could not find any translation library which fully handled plural translations properly; I also wanted to separate the l10n information into it's own package so that the whole community could benefit and implement i18n however they saw fit. A comparison is a little hard because I think our two projects may have different end goals but right now I think the biggest differences are:
I'll answer the validator integration at the end with some code samples; I'd say the benefits are that my end goal should be a complete and easy to use full translation system; so here are my future goals:
My big issues I've been having are:
So for now, until I reach my end goals, there's just a little bit of boilerplate needed to get this all set up; but really not that much. So for validator, I'll give you an example of an html site that implements translations and integrates into validator. // home.tmpl
{{ define "home" }}
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<p>My Locale is "{{ .Trans.Locale }}"</p>
<p>Error from validator: "{{ .ErrName }}"</p>
</body>
</html>
{{ end }} small web app: note: most of this would be split out.. package main
import (
"context"
"html/template"
"log"
"net/http"
"time"
"github.com/go-playground/locales"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/fr"
"github.com/go-playground/pure"
"github.com/go-playground/pure/examples/middleware/logging-recovery"
"github.com/go-playground/universal-translator"
"github.com/go-playground/validator"
)
var (
tmpls *template.Template
utrans *ut.UniversalTranslator
validate *validator.Validate
transKey = struct {
name string
}{
name: "transKey",
}
)
func main() {
validate = validator.New()
en := en.New()
utrans = ut.New(en, en, fr.New())
setup()
tmpls, _ = template.ParseFiles("home.tmpl")
r := pure.New()
r.Use(middleware.LoggingAndRecovery(true), translatorMiddleware)
r.Get("/", home)
http.ListenAndServe(":8080", r.Serve())
}
func home(w http.ResponseWriter, r *http.Request) {
// get locale translator ( could be wrapped into a helper function )
t := r.Context().Value(transKey).(ut.Translator)
// validator example
type User struct {
Name string `validate:"required"`
}
var user User
s := struct {
Trans ut.Translator
Now time.Time
ErrName string
}{
Trans: t,
Now: time.Now(),
}
errs := validate.Struct(user)
if errs != nil {
ve := errs.(validator.ValidationErrors)
translatedErrs := ve.Translate(t)
s.ErrName = translatedErrs["User.Name"]
}
if err := tmpls.ExecuteTemplate(w, "home", s); err != nil {
log.Fatal(err)
}
}
func translatorMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
locale := params.Get("locale")
var t ut.Translator
if len(locale) > 0 {
var found bool
if t, found = utrans.GetTranslator(locale); found {
goto END
}
}
// get and parse the "Accept-Language" http header and return an array
t, _ = utrans.FindTranslator(pure.AcceptedLanguages(r)...)
END:
// I would normally wrap ut.Translator with one with my own functions in order
// to handle errors and be able to use all functions from translator within the templates.
r = r.WithContext(context.WithValue(r.Context(), transKey, t))
next(w, r)
}
}
func setup() {
en, _ := utrans.FindTranslator("en")
en.AddCardinal("days-left", "There is {0} day left", locales.PluralRuleOne, false)
en.AddCardinal("days-left", "There are {0} days left", locales.PluralRuleOther, false)
// add english translation for 'required' validation
validate.RegisterTranslation("required", en, func(ut ut.Translator) (err error) {
if err = ut.Add("required", "{0} is a required field", false); err != nil {
return
}
return
},
func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field())
if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe)
return fe.(error).Error()
}
return t
},
)
fr, _ := utrans.FindTranslator("fr")
fr.AddCardinal("days-left", "Il reste {0} jour", locales.PluralRuleOne, false)
fr.AddCardinal("days-left", "Il reste {0} jours", locales.PluralRuleOther, false)
// add french translation for 'required' validation
validate.RegisterTranslation("required", fr, func(ut ut.Translator) (err error) {
if err = ut.Add("required", "{0} est un champ obligatoire", false); err != nil {
return
}
return
},
func(ut ut.Translator, fe validator.FieldError) string {
t, err := ut.T(fe.Tag(), fe.Field())
if err != nil {
log.Printf("warning: error translating FieldError: %#v", fe)
return fe.(error).Error()
}
return t
},
)
} just hit the site with It is kind of a pain right to register validator translations, however this is usually a one time thing and the you don't even think about them anymore. I have english translations in the validator package already, looking for help to add more locales and it's allot easier to register them: en_translations.RegisterDefaultTranslations(validate, en) see here I know it seems like allot and wrapping the ut.Translator once more would be a few more lines of code, but this example is an i18 aware application and integrated validator with translations in under 200 lines of code; I'd say that's a bargain 😄 I hope this wasn't too long and answered some questions, please feel free to ask anything else. |
Wow I'm blown away by this response! Thank you so much for your attention to detail and the complete response. You've definitely won a user over. Excited to integrate universal-translator into my app. So I don't have a ton in response, because I mostly just absorbed all of that and can now go use everything you mentioned. The one piece of feedback I can give you is on your roadmap(the initial three bullets you outlined). As it stands now, universal translator is very focused and I think that's the way it should stay. As I was going through everything, the one major feature that I felt like it was lacking was reading from a file. I think every modern i18n library these days should be able to very easily read from a file to populate the translation rules. This is a huge need, especially when you start farming out translation work to other people. I would say this is the biggest thing that is lacking. As for the webapp and live reload, I don't think they are needed and I wouldn't spend my time on them. There are already live reload solutions for Go programs that people can use and creating an admin interface provides VERY negligible benefit to just using a file. In fact I would prefer a file over a web interface, b/c a file is easier to share with a translator than a local web interface. If I were you, I would b-line it to supporting files. Just my two cents. |
Thanks @jayd3e for the feedback, perhaps I will focus on the file import and export 1st. |
Hey @jayd3e thanks for all your feedback, so I've done a preliminary file import/export in this branch and would love your input if you have some time :) if it looks good then I'll add some more format's other than just JSON and allow importing from an io.Reader option as well for embedded or in memory files. Thanks in advance! update: here are some samples, they can be combined or organised however the user desires in one or many files and the different types do not have to be separated, everything can be in same array. Normal Translations/Text Substitutions[
{
"locale": "en",
"key": "test_trans",
"trans": "Welcome {0} to the {1}."
},
{
"locale": "en",
"key": -1,
"trans": "Welcome {0}"
}
] Cardinal Translations[
{
"locale": "en",
"key": "cardinal_test",
"trans": "You have {0} day left.",
"type": "Cardinal",
"rule": "One"
},
{
"locale": "en",
"key": "cardinal_test",
"trans": "You have {0} days left.",
"type": "Cardinal",
"rule": "Other"
}
] Ordinal Translations[
{
"locale": "en",
"key": "day",
"trans": "{0}st",
"type": "Ordinal",
"rule": "One"
},
{
"locale": "en",
"key": "day",
"trans": "{0}nd",
"type": "Ordinal",
"rule": "Two"
},
{
"locale": "en",
"key": "day",
"trans": "{0}rd",
"type": "Ordinal",
"rule": "Few"
},
{
"locale": "en",
"key": "day",
"trans": "{0}th",
"type": "Ordinal",
"rule": "Other"
}
] Range Translations[
{
"locale": "nl",
"key": "day",
"trans": "er {0}-{1} dag vertrokken",
"type": "Range",
"rule": "One"
},
{
"locale": "nl",
"key": "day",
"trans": "er zijn {0}-{1} dagen over",
"type": "Range",
"rule": "Other"
}
] |
Update: I have added a file Import & Export logic in release 0.15.0 see README for more. |
Hey! So I was checking out the different options in the golang ecosystem for translation, and it looks like it really comes down to go-i18n and this project. I just wanted to better understand the tradeoffs and benefits of the two projects. It appears as though go-i18n may be easier to get started with, but the thing that keeps me coming back to universal-translator is its integration with validator(which I'm a big fan of). The thing that's hardest for me to understand with validator, is how to go from a list of
FieldError
s to an actual list of messages that can be displayed in a template. Any thoughts on this would be very helpful.In the end, my biggest outstanding questions would be, what are the major benefits of going with this project as opposed to others over the long run? Is this project still actively maintained?
The text was updated successfully, but these errors were encountered: