Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
Refactor i18n middleware language finders
Browse files Browse the repository at this point in the history
* Split language finders into separate functions
* Refactor LanguageFinder type
* Generalize Language finders options
* Add a new path prefix language finding strategy
* Ensure setups with default config won't break
  • Loading branch information
stanislas-m committed Mar 9, 2018
1 parent d8da217 commit 3750d95
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 24 deletions.
96 changes: 72 additions & 24 deletions middleware/i18n/i18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ import (
// languages. This can be useful if you want to load a user's language
// 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
type LanguageFinder func(LanguageFinderOptions, buffalo.Context) []string

// LanguageFinderOptions is a map of options for a LanguageFinder.
type LanguageFinderOptions map[string]interface{}

// Translator for handling all your i18n needs.
type Translator struct {
// Box - where are the files?
Box packr.Box
// DefaultLanguage - default is passed as a parameter on New.
DefaultLanguage string
// CookieName - name of the cookie to find the desired language.
// default is "lang"
CookieName string
// SessionName - name of the session to find the desired language.
// default is "lang"
SessionName string
// HelperName - name of the view helper. default is "t"
HelperName string
LanguageFinder LanguageFinder
HelperName string
// LanguageFinders - a sorted list of user language extractors.
LanguageFinders []LanguageFinder
// LanguageFinderOptions - a map with options to give to LanguageFinders.
LanguageFinderOptions LanguageFinderOptions
}

// Load translations from the t.Box.
Expand Down Expand Up @@ -70,10 +70,17 @@ func New(box packr.Box, language string) (*Translator, error) {
t := &Translator{
Box: box,
DefaultLanguage: language,
CookieName: "lang",
SessionName: "lang",
HelperName: "t",
LanguageFinder: defaultLanguageFinder,
LanguageFinderOptions: LanguageFinderOptions{
"CookieName": "lang",
"SessionName": "lang",
"URLPrefixName": "lang",
},
LanguageFinders: []LanguageFinder{
CookieLanguageFinder,
SessionLanguageFinder,
HeaderLanguageFinder,
},
}
return t, t.Load()
}
Expand Down Expand Up @@ -102,7 +109,7 @@ 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))
c.Set("languages", t.LanguageFinder(c))
}

// set translator
Expand Down Expand Up @@ -156,31 +163,72 @@ func (t *Translator) AvailableLanguages() []string {
return lt
}

func defaultLanguageFinder(t *Translator, c buffalo.Context) []string {
// LanguageFinder calls each LanguageFinder in the sorted list, then gather
// the result languages slice.
func (t *Translator) LanguageFinder(c buffalo.Context) []string {
langs := []string{}
for _, finder := range t.LanguageFinders {
langs = append(langs, finder(t.LanguageFinderOptions, c)...)
}
// Add default language, even if no language finder is defined
langs = append(langs, t.DefaultLanguage)
return langs
}

r := c.Request()

// CookieLanguageFinder is a LanguageFinder implementation, using a cookie.
func CookieLanguageFinder(o LanguageFinderOptions, c buffalo.Context) []string {
langs := make([]string, 0)
// 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)
if cookieName := o["CookieName"].(string); cookieName != "" {
if cookie, err := c.Request().Cookie(cookieName); err == nil {
if cookie.Value != "" {
langs = append(langs, cookie.Value)
}
}
} else {
c.Logger().Error("i18n middleware: \"CookieName\" is not defined in LanguageFinderOptions")
}
return langs
}

// SessionLanguageFinder is a LanguageFinder implementation, using a session.
func SessionLanguageFinder(o LanguageFinderOptions, c buffalo.Context) []string {
langs := make([]string, 0)
// try to get the language from the session
if s := c.Session().Get(t.SessionName); s != nil {
langs = append(langs, s.(string))
if sessionName := o["SessionName"].(string); sessionName != "" {
if s := c.Session().Get(sessionName); s != nil {
langs = append(langs, s.(string))
}
} else {
c.Logger().Error("i18n middleware: \"SessionName\" is not defined in LanguageFinderOptions")
}
return langs
}

// HeaderLanguageFinder is a LanguageFinder implementation, using a HTTP Accept-Language
// header.
func HeaderLanguageFinder(o LanguageFinderOptions, c buffalo.Context) []string {
langs := make([]string, 0)
// try to get the language from a header:
acceptLang := r.Header.Get("Accept-Language")
acceptLang := c.Request().Header.Get("Accept-Language")
if acceptLang != "" {
langs = append(langs, parseAcceptLanguage(acceptLang)...)
}
return langs
}

// finally set the default app language as fallback
langs = append(langs, t.DefaultLanguage)
// URLPrefixLanguageFinder is a LanguageFinder implementation, using a prefix in the URL.
func URLPrefixLanguageFinder(o LanguageFinderOptions, c buffalo.Context) []string {
langs := make([]string, 0)
// try to get the language from an URL prefix:
if urlPrefixName := o["URLPrefixName"].(string); urlPrefixName != "" {
paramLang := c.Param(urlPrefixName)
if paramLang != "" && strings.HasPrefix(c.Request().URL.Path, fmt.Sprintf("/%s", paramLang)) {
langs = append(langs, paramLang)
}
} else {
c.Logger().Error("i18n middleware: \"URLPrefixName\" is not defined in LanguageFinderOptions")
}
return langs
}

Expand Down
19 changes: 19 additions & 0 deletions middleware/i18n/i18n_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func app() *buffalo.App {
if err != nil {
log.Fatal(err)
}
// Setup URL prefix Language finder
t.LanguageFinders = append(t.LanguageFinders, URLPrefixLanguageFinder)

app.Use(t.Middleware())
app.GET("/", func(c buffalo.Context) error {
return c.Render(200, r.HTML("index.html"))
Expand Down Expand Up @@ -58,6 +61,9 @@ func app() *buffalo.App {
}
app.Middleware.Skip(t.Middleware(), noI18n)
app.GET("/localized-disabled", noI18n)
app.GET("/{lang:fr|en}/index", func(c buffalo.Context) error {
return c.Render(200, r.HTML("index.html"))
})
return app
}

Expand Down Expand Up @@ -160,3 +166,16 @@ func Test_i18n_availableLanguages(t *testing.T) {
res := w.Request("/languages-list").Get()
r.Equal("[\"en-us\",\"fr-fr\"]", strings.TrimSpace(res.Body.String()))
}

func Test_i18n_URL_prefix(t *testing.T) {
r := require.New(t)

w := willie.New(app())
req := w.Request("/fr/index")
res := req.Get()
r.Equal("Bonjour à tous !", strings.TrimSpace(res.Body.String()))

req = w.Request("/en/index")
res = req.Get()
r.Equal("Hello, World!", strings.TrimSpace(res.Body.String()))
}

0 comments on commit 3750d95

Please sign in to comment.