diff --git a/middleware/i18n/i18n.go b/middleware/i18n/i18n.go index d81317f3c..b342f0dcf 100644 --- a/middleware/i18n/i18n.go +++ b/middleware/i18n/i18n.go @@ -30,10 +30,8 @@ type Translator struct { // default is "lang" SessionName string // HelperName - name of the view helper. default is "t" - HelperName string - // HelperNamePlural - name of the view plural helper. default is "tp" - HelperNamePlural string - LanguageFinder LanguageFinder + HelperName string + LanguageFinder LanguageFinder } // Load translations from the t.Box. @@ -59,13 +57,12 @@ func (t *Translator) AddTranslation(lang *language.Language, translations ...tra // also call t.Load() and load the translations from disk. func New(box packr.Box, language string) (*Translator, error) { t := &Translator{ - Box: box, - DefaultLanguage: language, - CookieName: "lang", - SessionName: "lang", - HelperName: "t", - HelperNamePlural: "tp", - LanguageFinder: defaultLanguageFinder, + Box: box, + DefaultLanguage: language, + CookieName: "lang", + SessionName: "lang", + HelperName: "t", + LanguageFinder: defaultLanguageFinder, } return t, t.Load() } @@ -74,6 +71,7 @@ func New(box packr.Box, language string) (*Translator, error) { // selected. By default languages are loaded in the following order: // // Cookie - "lang" +// Session - "lang" // Header - "Accept-Language" // Default - "en-US" // @@ -91,45 +89,52 @@ func (t *Translator) Middleware() buffalo.MiddlewareFunc { } } + // set languages in context, if not set yet + if langs := c.Value("languages"); langs == nil { + c.Set("languages", t.LanguageFinder(t, c)) + } + + // set translator + if T := c.Value("T"); T == nil { + langs := c.Value("languages").([]string) + T, err := i18n.Tfunc(langs[0], langs[1:]...) + if err != nil { + return err + } + c.Set("T", T) + } + // set up the helper function for the views: - c.Set(t.HelperName, func(s string) (string, error) { - return t.Translate(c, s) - }) - c.Set(t.HelperNamePlural, func(s string, i interface{}) (string, error) { - return t.TranslatePlural(c, s, i) + c.Set(t.HelperName, func(s string, i ...interface{}) string { + return t.Translate(c, s, i...) }) return next(c) } } } -// Translate translates a string given a Context -// s is the translation ID -func (t *Translator) Translate(c buffalo.Context, s string) (string, error) { - if langs := c.Value("languages"); langs == nil { - c.Set("languages", t.LanguageFinder(t, c)) - } - langs := c.Value("languages").([]string) - T, err := i18n.Tfunc(langs[0], langs[1:]...) - if err != nil { - return "", err - } - return T(s, c.Data()), nil -} - -// TranslatePlural is the plural version of Translate -// s is the translation ID -// i must be an integer type (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45") -func (t *Translator) TranslatePlural(c buffalo.Context, s string, i interface{}) (string, error) { - if langs := c.Value("languages"); langs == nil { - c.Set("languages", t.LanguageFinder(t, c)) - } - langs := c.Value("languages").([]string) - T, err := i18n.Tfunc(langs[0], langs[1:]...) - if err != nil { - return "", err - } - return T(s, i, c.Data()), nil +// Translate returns the translation of the string identified by translationID. +// +// See https://github.com/nicksnyder/go-i18n +// +// If there is no translation for translationID, then the translationID itself is returned. +// This makes it easy to identify missing translations in your app. +// +// If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{} +// or struct that contains template data. +// +// If translationID is a plural form, the function accepts two parameter signatures +// 1. T(count int, data struct{}) +// The first variadic argument must be an integer type +// (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45"). +// The second variadic argument may be a map[string]interface{} or struct{} that contains template data. +// 2. T(data struct{}) +// data must be a struct{} or map[string]interface{} that contains a Count field and the template data, +// Count field must be an integer type (int, int8, int16, int32, int64) +// or a float formatted as a string (e.g. "123.45"). +func (t *Translator) Translate(c buffalo.Context, translationID string, args ...interface{}) string { + T := c.Value("T").(i18n.TranslateFunc) + return T(translationID, args...) } func defaultLanguageFinder(t *Translator, c buffalo.Context) []string { diff --git a/middleware/i18n/i18n_test.go b/middleware/i18n/i18n_test.go index 4e0c804f2..e9f3da576 100644 --- a/middleware/i18n/i18n_test.go +++ b/middleware/i18n/i18n_test.go @@ -11,6 +11,11 @@ import ( "github.com/stretchr/testify/require" ) +type User struct { + FirstName string + LastName string +} + func app() *buffalo.App { app := buffalo.New(buffalo.Options{}) @@ -30,6 +35,13 @@ func app() *buffalo.App { app.GET("/plural", func(c buffalo.Context) error { return c.Render(200, r.HTML("plural.html")) }) + app.GET("/format", func(c buffalo.Context) error { + usersList := make([]User, 0) + usersList = append(usersList, User{"Mark", "Bates"}) + usersList = append(usersList, User{"Chuck", "Berry"}) + c.Set("Users", usersList) + return c.Render(200, r.HTML("format.html")) + }) return app } @@ -69,4 +81,23 @@ func Test_i18n_plural_fr(t *testing.T) { req.Headers["Accept-Language"] = "fr-fr" res := req.Get() r.Equal("Bonjour, tout seul !\nBonjour, 5 personnes !\n", res.Body.String()) -} \ No newline at end of file +} + +func Test_i18n_format(t *testing.T) { + r := require.New(t) + + w := willie.New(app()) + res := w.Request("/format").Get() + r.Equal("Hello Mark!\n\n\t* Mr. Mark Bates\n\n\t* Mr. Chuck Berry\n", res.Body.String()) +} + +func Test_i18n_format_fr(t *testing.T) { + r := require.New(t) + + w := willie.New(app()) + req := w.Request("/format") + // Set language as "french" + req.Headers["Accept-Language"] = "fr-fr" + res := req.Get() + r.Equal("Bonjour Mark !\n\n\t* M. Mark Bates\n\n\t* M. Chuck Berry\n", res.Body.String()) +} diff --git a/middleware/i18n/locales/test.en-us.yaml b/middleware/i18n/locales/test.en-us.yaml index e684150c7..2b32516fd 100644 --- a/middleware/i18n/locales/test.en-us.yaml +++ b/middleware/i18n/locales/test.en-us.yaml @@ -5,3 +5,9 @@ translation: one: "Hello, alone!" other: "Hello, {{.Count}} people!" + +- id: test-format + translation: "Hello {{.Name}}!" + +- id: test-format-loop + translation: "Mr. {{.FirstName}} {{.LastName}}" \ No newline at end of file diff --git a/middleware/i18n/locales/test.fr-fr.yaml b/middleware/i18n/locales/test.fr-fr.yaml index 3c1dd3add..bbb625201 100644 --- a/middleware/i18n/locales/test.fr-fr.yaml +++ b/middleware/i18n/locales/test.fr-fr.yaml @@ -5,3 +5,9 @@ translation: one: "Bonjour, tout seul !" other: "Bonjour, {{.Count}} personnes !" + +- id: test-format + translation: "Bonjour {{.Name}} !" + +- id: test-format-loop + translation: "M. {{.FirstName}} {{.LastName}}" \ No newline at end of file diff --git a/middleware/i18n/templates/format.html b/middleware/i18n/templates/format.html new file mode 100644 index 000000000..289f3d1f6 --- /dev/null +++ b/middleware/i18n/templates/format.html @@ -0,0 +1,4 @@ +<%= t("test-format", {Name: "Mark"}) %> +<%= for (u) in Users { %> + * <%= t("test-format-loop", u) %> +<% } %> \ No newline at end of file diff --git a/middleware/i18n/templates/plural.html b/middleware/i18n/templates/plural.html index 1d8572de7..fa6f39a21 100644 --- a/middleware/i18n/templates/plural.html +++ b/middleware/i18n/templates/plural.html @@ -1,2 +1,2 @@ -<%= tp("greeting-plural", 1) %> -<%= tp("greeting-plural", 5) %> +<%= t("greeting-plural", 1) %> +<%= t("greeting-plural", 5) %>