-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
898 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package cache | ||
|
||
import ( | ||
"bytes" | ||
"encoding/gob" | ||
"time" | ||
) | ||
|
||
// Cache cache | ||
type Cache struct { | ||
Store Store `inject:""` | ||
} | ||
|
||
//Set cache item | ||
func (p *Cache) Set(key string, val interface{}, ttl time.Duration) error { | ||
var buf bytes.Buffer | ||
enc := gob.NewEncoder(&buf) | ||
if err := enc.Encode(val); err != nil { | ||
return err | ||
} | ||
return p.Store.Set(key, buf.Bytes(), ttl) | ||
} | ||
|
||
//Get get from cache | ||
func (p *Cache) Get(key string, val interface{}) error { | ||
bys, err := p.Store.Get(key) | ||
if err != nil { | ||
return err | ||
} | ||
var buf bytes.Buffer | ||
dec := gob.NewDecoder(&buf) | ||
buf.Write(bys) | ||
return dec.Decode(val) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package redis | ||
|
||
import ( | ||
"time" | ||
|
||
_redis "github.com/garyburd/redigo/redis" | ||
) | ||
|
||
// Store cache by redis | ||
type Store struct { | ||
Pool *_redis.Pool `inject:""` | ||
} | ||
|
||
// Status status | ||
func (p *Store) Status() (string, error) { | ||
c := p.Pool.Get() | ||
defer c.Close() | ||
return _redis.String(c.Do("INFO")) | ||
} | ||
|
||
//Flush clear cache items | ||
func (p *Store) Flush() error { | ||
c := p.Pool.Get() | ||
defer c.Close() | ||
keys, err := _redis.Values(c.Do("KEYS", "*")) | ||
if err == nil && len(keys) > 0 { | ||
_, err = c.Do("DEL", keys...) | ||
} | ||
return err | ||
} | ||
|
||
//Keys list cache items | ||
func (p *Store) Keys() ([]string, error) { | ||
c := p.Pool.Get() | ||
defer c.Close() | ||
return _redis.Strings(c.Do("KEYS", "*")) | ||
} | ||
|
||
// Set set bytes | ||
func (p *Store) Set(key string, val []byte, ttl time.Duration) error { | ||
c := p.Pool.Get() | ||
defer c.Close() | ||
_, err := c.Do("SET", key, val, "EX", int(ttl/time.Second)) | ||
return err | ||
} | ||
|
||
// Get get bytes | ||
func (p *Store) Get(key string) ([]byte, error) { | ||
c := p.Pool.Get() | ||
defer c.Close() | ||
return _redis.Bytes(c.Do("GET", key)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package cache | ||
|
||
import "time" | ||
|
||
// Store store | ||
type Store interface { | ||
Set(key string, val []byte, ttl time.Duration) error | ||
Get(key string) ([]byte, error) | ||
Flush() error | ||
Keys() ([]string, error) | ||
Status() (string, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package i18n | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/kapmahc/h2o" | ||
"golang.org/x/text/language" | ||
) | ||
|
||
const ( | ||
// LOCALE locale key | ||
LOCALE = "locale" | ||
) | ||
|
||
// Middleware detect language from http request | ||
func (p *I18n) Middleware(c *h2o.Context) error { | ||
langs, err := p.Store.Languages() | ||
if err != nil { | ||
return err | ||
} | ||
var tags []language.Tag | ||
for _, l := range langs { | ||
tags = append(tags, language.Make(l)) | ||
} | ||
matcher := language.NewMatcher(tags) | ||
tag, _, _ := matcher.Match(language.Make(p.detect(c.Request))) | ||
c.Set(LOCALE, tag.String()) | ||
return nil | ||
} | ||
|
||
func (p *I18n) detect(r *http.Request) string { | ||
// 1. Check URL arguments. | ||
if lang := r.URL.Query().Get(LOCALE); lang != "" { | ||
return lang | ||
} | ||
|
||
// 2. Get language information from cookies. | ||
if ck, er := r.Cookie(LOCALE); er == nil { | ||
return ck.Value | ||
} | ||
|
||
// 3. Get language information from 'Accept-Language'. | ||
if al := r.Header.Get("Accept-Language"); len(al) > 4 { | ||
return al[:5] // Only compare first 5 letters. | ||
} | ||
|
||
return "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package i18n | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"html/template" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
"github.com/go-ini/ini" | ||
"github.com/kapmahc/h2o" | ||
"github.com/kapmahc/h2o/cache" | ||
"golang.org/x/text/language" | ||
) | ||
|
||
// I18n i18n | ||
type I18n struct { | ||
Store Store `inject:""` | ||
Cache *cache.Cache `inject:""` | ||
} | ||
|
||
// F format message | ||
func (p *I18n) F(lang, code string, obj interface{}) (string, error) { | ||
msg, err := p.Store.Get(lang, code) | ||
if err != nil { | ||
return "", err | ||
} | ||
tpl, err := template.New("").Parse(msg) | ||
if err != nil { | ||
return "", err | ||
} | ||
var buf bytes.Buffer | ||
err = tpl.Execute(&buf, obj) | ||
return buf.String(), err | ||
} | ||
|
||
//E create an i18n error | ||
func (p *I18n) E(status int, lang, code string, args ...interface{}) error { | ||
return &h2o.HTTPError{ | ||
Message: p.T(lang, code, args...), | ||
Status: status, | ||
} | ||
} | ||
|
||
//T translate by lang tag | ||
func (p *I18n) T(lang, code string, args ...interface{}) string { | ||
msg := p.get(lang, code) | ||
if msg == "" { | ||
return code | ||
} | ||
return fmt.Sprintf(msg, args...) | ||
} | ||
|
||
// All all items | ||
func (p *I18n) All(lang string) (map[string]interface{}, error) { | ||
rt := make(map[string]interface{}) | ||
|
||
items, err := p.Store.All(lang) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for k, v := range items { | ||
codes := strings.Split(k, ".") | ||
tmp := rt | ||
for i, c := range codes { | ||
if i+1 == len(codes) { | ||
tmp[c] = v | ||
} else { | ||
if tmp[c] == nil { | ||
tmp[c] = make(map[string]interface{}) | ||
} | ||
tmp = tmp[c].(map[string]interface{}) | ||
} | ||
} | ||
|
||
} | ||
return rt, nil | ||
} | ||
|
||
// Load sync records | ||
func (p *I18n) Load(dir string) error { | ||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { | ||
const ext = ".ini" | ||
name := info.Name() | ||
if info.Mode().IsRegular() && filepath.Ext(name) == ext { | ||
log.Debugf("Find locale file %s", path) | ||
if err != nil { | ||
return err | ||
} | ||
lang := name[0 : len(name)-len(ext)] | ||
if _, err := language.Parse(lang); err != nil { | ||
return err | ||
} | ||
cfg, err := ini.Load(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
items := cfg.Section(ini.DEFAULT_SECTION).KeysHash() | ||
for k, v := range items { | ||
if err := p.Store.Set(lang, k, v, false); err != nil { | ||
return err | ||
} | ||
} | ||
log.Infof("find %d items", len(items)) | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
// Set update locale | ||
func (p *I18n) Set(lang, code, message string) error { | ||
key := p.key(lang, code) | ||
if err := p.Store.Set(lang, code, message, true); err != nil { | ||
return err | ||
} | ||
return p.Cache.Set(key, message, defaultTTL) | ||
} | ||
|
||
func (p *I18n) key(lang, code string) string { | ||
return lang + "://locales/" + code | ||
} | ||
|
||
func (p *I18n) get(lang, code string) string { | ||
key := p.key(lang, code) | ||
var msg string | ||
err := p.Cache.Get(key, &msg) | ||
if err == nil { | ||
return msg | ||
} | ||
msg, err = p.Store.Get(lang, code) | ||
if err == nil { | ||
p.Cache.Set(key, msg, defaultTTL) | ||
return msg | ||
} | ||
return "" | ||
} | ||
|
||
const ( | ||
defaultTTL = time.Hour * 24 * 30 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package orm | ||
|
||
import "time" | ||
|
||
//Model locale model | ||
type Model struct { | ||
ID uint `gorm:"primary_key"` | ||
Lang string `gorm:"not null;type:varchar(8);index;unique_index:idx_locales_lang_code"` | ||
Code string `gorm:"not null;index;type:VARCHAR(255);unique_index:idx_locales_lang_code"` | ||
Message string `gorm:"not null;type:varchar(1024)"` | ||
CreatedAt time.Time `json:"createdAt"` | ||
UpdatedAt time.Time `json:"updatedAt"` | ||
} | ||
|
||
// TableName table name | ||
func (Model) TableName() string { | ||
return "locales" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package orm | ||
|
||
import "github.com/jinzhu/gorm" | ||
|
||
// Store gorm-store | ||
type Store struct { | ||
Db *gorm.DB `inject:""` | ||
} | ||
|
||
//Set set locale | ||
func (p *Store) Set(lang, code, message string, override bool) error { | ||
var l Model | ||
if p.Db.Where("lang = ? AND code = ?", lang, code).First(&l).RecordNotFound() { | ||
l.Lang = lang | ||
l.Code = code | ||
l.Message = message | ||
return p.Db.Create(&l).Error | ||
} | ||
if override { | ||
l.Message = message | ||
return p.Db.Save(&l).Error | ||
} | ||
|
||
return nil | ||
} | ||
|
||
//Del del locale | ||
func (p *Store) Del(lang, code string) error { | ||
return p.Db.Where("lang = ? AND code = ?", lang, code).Delete(Model{}).Error | ||
} | ||
|
||
// Get get message | ||
func (p *Store) Get(lang, code string) (string, error) { | ||
var l Model | ||
if err := p.Db. | ||
Select("message"). | ||
Where("lang = ? AND code = ?", lang, code). | ||
First(&l).Error; err != nil { | ||
return "", err | ||
} | ||
return l.Message, nil | ||
} | ||
|
||
// Languages languages | ||
func (p *Store) Languages() ([]string, error) { | ||
var val []string | ||
err := p.Db.Model(&Model{}).Pluck("DISTINCT lang", &val).Error | ||
return val, err | ||
} | ||
|
||
// All list all items | ||
func (p *Store) All(lang string) (map[string]string, error) { | ||
var items []Model | ||
if err := p.Db. | ||
Select([]string{"code", "message"}). | ||
Where("lang = ?", lang). | ||
Find(&items).Error; err != nil { | ||
return nil, err | ||
} | ||
|
||
rt := make(map[string]string) | ||
for _, l := range items { | ||
rt[l.Code] = l.Message | ||
} | ||
return rt, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package i18n | ||
|
||
// Store store | ||
type Store interface { | ||
Set(lang, code, message string, override bool) error | ||
Get(lang, code string) (string, error) | ||
All(lang string) (map[string]string, error) | ||
Del(lang, code string) error | ||
Languages() ([]string, error) | ||
} |
Oops, something went wrong.