@@ -1,16 +1,23 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"github.com/gin-gonic/gin" | |||
"zxq.co/ripple/hanayo/modules/locale" | |||
) | |||
// T translates a string into the language specified by the request. | |||
func T(c *gin.Context, s string, args ...interface{}) string { | |||
return fmt.Sprintf(s, args...) | |||
return locale.Get(getLang(c), s, args...) | |||
} | |||
func (b *baseTemplateData) T(s string, args ...interface{}) string { | |||
return T(b.Gin, s, args...) | |||
} | |||
func getLang(c *gin.Context) []string { | |||
s, _ := c.Cookie("language") | |||
if s != "" { | |||
return []string{s} | |||
} | |||
return locale.ParseHeader(c.Request.Header.Get("Accept-Language")) | |||
} |
@@ -0,0 +1,46 @@ | |||
package locale | |||
import ( | |||
"fmt" | |||
"io/ioutil" | |||
"strings" | |||
"github.com/leonelquinteros/gotext" | |||
) | |||
var languageMap = make(map[string]*gotext.Po, 20) | |||
func loadLanguages() { | |||
files, err := ioutil.ReadDir("./data/locales") | |||
if err != nil { | |||
fmt.Println("loadLanguages", err) | |||
return | |||
} | |||
for _, file := range files { | |||
if file.Name() == "templates.pot" || file.Name() == "." || file.Name() == ".." { | |||
continue | |||
} | |||
po := new(gotext.Po) | |||
po.ParseFile("./data/locales/" + file.Name()) | |||
langName := strings.TrimPrefix(strings.TrimSuffix(file.Name(), ".po"), "templates-") | |||
languageMap[langName] = po | |||
} | |||
} | |||
func init() { | |||
loadLanguages() | |||
} | |||
// Get retrieves a string from a language | |||
func Get(langs []string, str string, vars ...interface{}) string { | |||
for _, lang := range langs { | |||
l := languageMap[lang] | |||
if l != nil { | |||
return l.Get(str, vars...) | |||
} | |||
} | |||
return fmt.Sprintf(str, vars...) | |||
} |
@@ -0,0 +1,38 @@ | |||
package locale | |||
import ( | |||
"sort" | |||
"strconv" | |||
"strings" | |||
) | |||
// ParseHeader parses an Accept-Language header, and sorts the values. | |||
func ParseHeader(header string) []string { | |||
if header == "" { | |||
return nil | |||
} | |||
parts := strings.Split(header, ",") | |||
sort.Slice(parts, func(i, j int) bool { | |||
return getQuality(parts[i]) > getQuality(parts[j]) | |||
}) | |||
for idx, val := range parts { | |||
parts[idx] = strings.Replace(strings.SplitN(val, ";q=", 2)[0], "-", "_", 1) | |||
} | |||
return parts | |||
} | |||
func getQuality(s string) float32 { | |||
idx := strings.Index(s, ";q=") | |||
if idx == -1 { | |||
return 1 | |||
} | |||
f, err := strconv.ParseFloat(s[idx+3:], 32) | |||
if err != nil { | |||
return 1 | |||
} | |||
return float32(f) | |||
} |
@@ -0,0 +1,45 @@ | |||
package locale | |||
import ( | |||
"reflect" | |||
"testing" | |||
) | |||
func TestParseHeader(t *testing.T) { | |||
tt := []struct { | |||
In string | |||
Out []string | |||
}{ | |||
{ | |||
"en", | |||
[]string{"en"}, | |||
}, | |||
{ | |||
"en-GB", | |||
[]string{"en_GB"}, | |||
}, | |||
{ | |||
"en-GB;q=0.5,it", | |||
[]string{"it", "en_GB"}, | |||
}, | |||
{ | |||
"en-GB;q=0.5,it,pl;q=0.2", | |||
[]string{"it", "en_GB", "pl"}, | |||
}, | |||
{ | |||
"en-GB;q=0.5,pl;q=xd", | |||
[]string{"pl", "en_GB"}, | |||
}, | |||
{ | |||
"", | |||
nil, | |||
}, | |||
} | |||
for _, el := range tt { | |||
got := ParseHeader(el.In) | |||
if !reflect.DeepEqual(got, el.Out) { | |||
t.Errorf("got %v want %v", got, el.Out) | |||
} | |||
} | |||
} |