github.com/orderbynull/buffalo@v0.11.1/middleware/i18n/i18n.go (about) 1 package i18n 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "sort" 7 "strings" 8 9 "github.com/gobuffalo/buffalo" 10 "github.com/gobuffalo/packr" 11 "github.com/nicksnyder/go-i18n/i18n" 12 "github.com/nicksnyder/go-i18n/i18n/language" 13 "github.com/nicksnyder/go-i18n/i18n/translation" 14 "github.com/pkg/errors" 15 ) 16 17 // LanguageFinder can be implemented for custom finding of search 18 // languages. This can be useful if you want to load a user's language 19 // from something like a database. See Middleware() for more information 20 // on how the default implementation searches for languages. 21 type LanguageFinder func(*Translator, buffalo.Context) []string 22 23 // Translator for handling all your i18n needs. 24 type Translator struct { 25 // Box - where are the files? 26 Box packr.Box 27 // DefaultLanguage - default is passed as a parameter on New. 28 DefaultLanguage string 29 // CookieName - name of the cookie to find the desired language. 30 // default is "lang" 31 CookieName string 32 // SessionName - name of the session to find the desired language. 33 // default is "lang" 34 SessionName string 35 // HelperName - name of the view helper. default is "t" 36 HelperName string 37 LanguageFinder LanguageFinder 38 } 39 40 // Load translations from the t.Box. 41 func (t *Translator) Load() error { 42 return t.Box.Walk(func(path string, f packr.File) error { 43 b, err := t.Box.MustBytes(path) 44 if err != nil { 45 return errors.Wrapf(err, "unable to read locale file %s", path) 46 } 47 48 base := filepath.Base(path) 49 dir := filepath.Dir(path) 50 51 // Add a prefix to the loaded string, to avoid collision with an ISO lang code 52 err = i18n.ParseTranslationFileBytes(fmt.Sprintf("%sbuff%s", dir, base), b) 53 if err != nil { 54 return errors.Wrapf(err, "unable to parse locale file %s", base) 55 } 56 return nil 57 }) 58 } 59 60 // AddTranslation directly, without using a file. This is useful if you wish to load translations 61 // from a database, instead of disk. 62 func (t *Translator) AddTranslation(lang *language.Language, translations ...translation.Translation) { 63 i18n.AddTranslation(lang, translations...) 64 } 65 66 // New Translator. Requires a packr.Box that points to the location 67 // of the translation files, as well as a default language. This will 68 // also call t.Load() and load the translations from disk. 69 func New(box packr.Box, language string) (*Translator, error) { 70 t := &Translator{ 71 Box: box, 72 DefaultLanguage: language, 73 CookieName: "lang", 74 SessionName: "lang", 75 HelperName: "t", 76 LanguageFinder: defaultLanguageFinder, 77 } 78 return t, t.Load() 79 } 80 81 // Middleware for loading the translations for the language(s) 82 // selected. By default languages are loaded in the following order: 83 // 84 // Cookie - "lang" 85 // Session - "lang" 86 // Header - "Accept-Language" 87 // Default - "en-US" 88 // 89 // These values can be changed on the Translator itself. In development 90 // model the translation files will be reloaded on each request. 91 func (t *Translator) Middleware() buffalo.MiddlewareFunc { 92 return func(next buffalo.Handler) buffalo.Handler { 93 return func(c buffalo.Context) error { 94 95 // in development reload the translations 96 if c.Value("env").(string) == "development" { 97 err := t.Load() 98 if err != nil { 99 return err 100 } 101 } 102 103 // set languages in context, if not set yet 104 if langs := c.Value("languages"); langs == nil { 105 c.Set("languages", t.LanguageFinder(t, c)) 106 } 107 108 // set translator 109 if T := c.Value("T"); T == nil { 110 langs := c.Value("languages").([]string) 111 T, err := i18n.Tfunc(langs[0], langs[1:]...) 112 if err != nil { 113 c.Logger().Warn(err) 114 c.Logger().Warn("Your locale files are probably empty or missing") 115 } 116 c.Set("T", T) 117 } 118 119 // set up the helper function for the views: 120 c.Set(t.HelperName, func(s string, i ...interface{}) string { 121 return t.Translate(c, s, i...) 122 }) 123 return next(c) 124 } 125 } 126 } 127 128 // Translate returns the translation of the string identified by translationID. 129 // 130 // See https://github.com/nicksnyder/go-i18n 131 // 132 // If there is no translation for translationID, then the translationID itself is returned. 133 // This makes it easy to identify missing translations in your app. 134 // 135 // If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{} 136 // or struct that contains template data. 137 // 138 // If translationID is a plural form, the function accepts two parameter signatures 139 // 1. T(count int, data struct{}) 140 // The first variadic argument must be an integer type 141 // (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45"). 142 // The second variadic argument may be a map[string]interface{} or struct{} that contains template data. 143 // 2. T(data struct{}) 144 // data must be a struct{} or map[string]interface{} that contains a Count field and the template data, 145 // Count field must be an integer type (int, int8, int16, int32, int64) 146 // or a float formatted as a string (e.g. "123.45"). 147 func (t *Translator) Translate(c buffalo.Context, translationID string, args ...interface{}) string { 148 T := c.Value("T").(i18n.TranslateFunc) 149 return T(translationID, args...) 150 } 151 152 // AvailableLanguages gets the list of languages provided by the app 153 func (t *Translator) AvailableLanguages() []string { 154 lt := i18n.LanguageTags() 155 sort.Strings(lt) 156 return lt 157 } 158 159 func defaultLanguageFinder(t *Translator, c buffalo.Context) []string { 160 langs := []string{} 161 162 r := c.Request() 163 164 // try to get the language from a cookie: 165 if cookie, err := r.Cookie(t.CookieName); err == nil { 166 if cookie.Value != "" { 167 langs = append(langs, cookie.Value) 168 } 169 } 170 171 // try to get the language from the session 172 if s := c.Session().Get(t.SessionName); s != nil { 173 langs = append(langs, s.(string)) 174 } 175 176 // try to get the language from a header: 177 acceptLang := r.Header.Get("Accept-Language") 178 if acceptLang != "" { 179 langs = append(langs, parseAcceptLanguage(acceptLang)...) 180 } 181 182 // finally set the default app language as fallback 183 langs = append(langs, t.DefaultLanguage) 184 return langs 185 } 186 187 // Inspired from https://siongui.github.io/2015/02/22/go-parse-accept-language/ 188 // Parse an Accept-Language string to get usable lang values for i18n system 189 func parseAcceptLanguage(acptLang string) []string { 190 var lqs []string 191 192 langQStrs := strings.Split(acptLang, ",") 193 for _, langQStr := range langQStrs { 194 trimedLangQStr := strings.Trim(langQStr, " ") 195 196 langQ := strings.Split(trimedLangQStr, ";") 197 lq := langQ[0] 198 lqs = append(lqs, lq) 199 } 200 return lqs 201 }