123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- package main
- import (
- "context"
- "html/template"
- "log"
- "net/http"
- "time"
- "github.com/go-playground/locales"
- "github.com/go-playground/locales/currency"
- "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"
- )
- var (
- tmpls *template.Template
- utrans *ut.UniversalTranslator
- transKey = struct {
- name string
- }{
- name: "transKey",
- }
- )
- // Translator wraps ut.Translator in order to handle errors transparently
- // it is totally optional but recommended as it can now be used directly in
- // templates and nobody can add translations where they're not supposed to.
- type Translator interface {
- locales.Translator
- // creates the translation for the locale given the 'key' and params passed in.
- // wraps ut.Translator.T to handle errors
- T(key interface{}, params ...string) string
- // creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments
- // and param passed in.
- // wraps ut.Translator.C to handle errors
- C(key interface{}, num float64, digits uint64, param string) string
- // creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments
- // and param passed in.
- // wraps ut.Translator.O to handle errors
- O(key interface{}, num float64, digits uint64, param string) string
- // creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and
- // 'digit2' arguments and 'param1' and 'param2' passed in
- // wraps ut.Translator.R to handle errors
- R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string
- // Currency returns the type used by the given locale.
- Currency() currency.Type
- }
- // implements Translator interface definition above.
- type translator struct {
- locales.Translator
- trans ut.Translator
- }
- var _ Translator = (*translator)(nil)
- func (t *translator) T(key interface{}, params ...string) string {
- s, err := t.trans.T(key, params...)
- if err != nil {
- log.Printf("issue translating key: '%v' error: '%s'", key, err)
- }
- return s
- }
- func (t *translator) C(key interface{}, num float64, digits uint64, param string) string {
- s, err := t.trans.C(key, num, digits, param)
- if err != nil {
- log.Printf("issue translating cardinal key: '%v' error: '%s'", key, err)
- }
- return s
- }
- func (t *translator) O(key interface{}, num float64, digits uint64, param string) string {
- s, err := t.trans.C(key, num, digits, param)
- if err != nil {
- log.Printf("issue translating ordinal key: '%v' error: '%s'", key, err)
- }
- return s
- }
- func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string {
- s, err := t.trans.R(key, num1, digits1, num2, digits2, param1, param2)
- if err != nil {
- log.Printf("issue translating range key: '%v' error: '%s'", key, err)
- }
- return s
- }
- func (t *translator) Currency() currency.Type {
- // choose your own locale. The reason it isn't mapped for you is because many
- // countries have multiple currencies; it's up to you and you're application how
- // and which currencies to use. I recommend adding a function it to to your custon translator
- // interface like defined above.
- switch t.Locale() {
- case "en":
- return currency.USD
- case "fr":
- return currency.EUR
- default:
- return currency.USD
- }
- }
- func main() {
- 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)
- log.Println("Running on Port :8080")
- log.Println("Try me with URL http://localhost:8080/?locale=en")
- log.Println("and then http://localhost:8080/?locale=fr")
- 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).(Translator)
- s := struct {
- Trans Translator
- Now time.Time
- PositiveNum float64
- NegativeNum float64
- Percent float64
- }{
- Trans: t,
- Now: time.Now(),
- PositiveNum: 1234576.45,
- NegativeNum: -35900394.34,
- Percent: 96.76,
- }
- 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) {
- // there are many ways to check, this is just checking for query param &
- // Accept-Language header but can be expanded to Cookie's etc....
- 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, &translator{trans: t, Translator: t.(locales.Translator)}))
- next(w, r)
- }
- }
- func setup() {
- err := utrans.Import(ut.FormatJSON, "translations")
- if err != nil {
- log.Fatal(err)
- }
- err = utrans.VerifyTranslations()
- if err != nil {
- log.Fatal(err)
- }
- }
|