diff --git a/generators/newapp/templates/actions/app.go.tmpl b/generators/newapp/templates/actions/app.go.tmpl index 1e25d4359..571bdf2c3 100644 --- a/generators/newapp/templates/actions/app.go.tmpl +++ b/generators/newapp/templates/actions/app.go.tmpl @@ -1,8 +1,10 @@ package actions import ( + "log" "github.com/gobuffalo/buffalo" "github.com/gobuffalo/buffalo/middleware" + "github.com/gobuffalo/buffalo/middleware/i18n" {{ if .withPop }} "{{.modelsPath}}" {{ end }} @@ -35,6 +37,13 @@ func App() *buffalo.App { app.Use(middleware.PopTransaction(models.DB)) {{ end }} + // Setup and use translations: + t, err := i18n.New(packr.NewBox("../locales"), "en-US") + if err != nil { + log.Fatal(err) + } + app.Use(t.Middleware()) + app.GET("/", HomeHandler) app.ServeFiles("/assets", packr.NewBox("../public/assets")) diff --git a/generators/newapp/templates/locales/all.en-us.yaml.tmpl b/generators/newapp/templates/locales/all.en-us.yaml.tmpl new file mode 100644 index 000000000..a80d2be63 --- /dev/null +++ b/generators/newapp/templates/locales/all.en-us.yaml.tmpl @@ -0,0 +1,2 @@ +- id: welcome_greeting + translation: "Welcome to Buffalo (EN)" diff --git a/generators/newapp/templates/templates/index.html.tmpl b/generators/newapp/templates/templates/index.html.tmpl index 12aad48b6..bc9c3e251 100644 --- a/generators/newapp/templates/templates/index.html.tmpl +++ b/generators/newapp/templates/templates/index.html.tmpl @@ -3,7 +3,7 @@
-

Welcome to Buffalo! [v{{.version}}]

+

<%= t("welcome_greeting") %> [v{{.version}}]

https://github.com/gobuffalo/buffalo

diff --git a/middleware/i18n/i18n.go b/middleware/i18n/i18n.go new file mode 100644 index 000000000..4a40ec36f --- /dev/null +++ b/middleware/i18n/i18n.go @@ -0,0 +1,126 @@ +package i18n + +import ( + "log" + + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/packr" + "github.com/gobuffalo/plush" + "github.com/nicksnyder/go-i18n/i18n" + "github.com/nicksnyder/go-i18n/i18n/language" + "github.com/nicksnyder/go-i18n/i18n/translation" + "github.com/pkg/errors" +) + +// LanguageFinder can be implemented for custom finding of search +// languages. This can be useful if you want to load a user's langugage +// from something like a database. See Middleware() for more information +// on how the default implementation searches for languages. +type LanguageFinder func(*Translator, buffalo.Context) []string + +// Translator for handling all your i18n needs. +type Translator struct { + // Box - where are the files? + Box packr.Box + // DefaultLanguage - default is "en-US" + DefaultLanguage string + // CookieName - name of the cookie to find the desired language. + // default is "lang" + CookieName string + // HelperName - name of the view helper. default is "t" + HelperName string + LanguageFinder LanguageFinder +} + +// Load translations from the t.Box. +func (t *Translator) Load() error { + return t.Box.Walk(func(path string, f packr.File) error { + b, err := t.Box.MustBytes(path) + if err != nil { + log.Fatal(err) + return errors.WithStack(err) + } + return i18n.ParseTranslationFileBytes(path, b) + }) +} + +// AddTranslation directly, without using a file. This is useful if you wish to load translations +// from a database, instead of disk. +func (t *Translator) AddTranslation(lang *language.Language, translations ...translation.Translation) { + i18n.AddTranslation(lang, translations...) +} + +// New Translator. Requires a packr.Box that points to the location +// of the translation files, as well as a default language. This will +// 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", + HelperName: "t", + LanguageFinder: defaultLanguageFinder, + } + return t, t.Load() +} + +// Middleware for loading the translations for the language(s) +// selected. By default languages are loaded in the following order: +// +// Cookie - "lang" +// Header - "Accept-Language" +// Default - "en-US" +// +// These values can be changed on the Translator itself. In development +// model the translation files will be reloaded on each request. +func (t *Translator) Middleware() buffalo.MiddlewareFunc { + return func(next buffalo.Handler) buffalo.Handler { + return func(c buffalo.Context) error { + + // in development reload the translations + if c.Value("env").(string) == "development" { + err := t.Load() + if err != nil { + return err + } + } + + langs := t.LanguageFinder(t, c) + + c.Set("languages", langs) + + // set up the helper function for the views: + c.Set(t.HelperName, func(s string, help plush.HelperContext) (string, error) { + T, err := i18n.Tfunc(langs[0], langs[1:]...) + if err != nil { + return "", err + } + return T(s), nil + }) + return next(c) + } + } +} + +func defaultLanguageFinder(t *Translator, c buffalo.Context) []string { + langs := []string{} + + r := c.Request() + + // try to get the language from a cookie: + if cookie, err := r.Cookie(t.CookieName); err == nil { + if cookie.Value != "" { + langs = append(langs, cookie.Value) + } + } + + // try to get the language from a header: + acceptLang := r.Header.Get("Accept-Language") + if acceptLang != "" { + langs = append(langs, acceptLang) + } + + // try to get the language from the session: + langs = append(langs, t.DefaultLanguage) + return langs +} diff --git a/middleware/i18n/i18n_test.go b/middleware/i18n/i18n_test.go new file mode 100644 index 000000000..d40021bf9 --- /dev/null +++ b/middleware/i18n/i18n_test.go @@ -0,0 +1,39 @@ +package i18n + +import ( + "log" + "testing" + + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/buffalo/render" + "github.com/gobuffalo/packr" + "github.com/markbates/willie" + "github.com/stretchr/testify/require" +) + +func app() *buffalo.App { + app := buffalo.Automatic(buffalo.Options{}) + + r := render.New(render.Options{ + TemplatesBox: packr.NewBox("./templates"), + }) + + // Setup and use translations: + t, err := New(packr.NewBox("./locales"), "en-US") + if err != nil { + log.Fatal(err) + } + app.Use(t.Middleware()) + app.GET("/", func(c buffalo.Context) error { + return c.Render(200, r.HTML("index.html")) + }) + return app +} + +func Test_i18n(t *testing.T) { + r := require.New(t) + + w := willie.New(app()) + res := w.Request("/").Get() + r.Equal("Hello, World!\n", res.Body.String()) +} diff --git a/middleware/i18n/locales/test.en-us.yaml b/middleware/i18n/locales/test.en-us.yaml new file mode 100644 index 000000000..0c9d8d909 --- /dev/null +++ b/middleware/i18n/locales/test.en-us.yaml @@ -0,0 +1,2 @@ +- id: greeting + translation: "Hello, World!" diff --git a/middleware/i18n/templates/index.html b/middleware/i18n/templates/index.html new file mode 100644 index 000000000..367fc96d2 --- /dev/null +++ b/middleware/i18n/templates/index.html @@ -0,0 +1 @@ +<%= t("greeting") %>