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}}]
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") %>