✨ add i18n support
This commit is contained in:
16
common/i18n/bundle.go
Normal file
16
common/i18n/bundle.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
i18n2 "github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func NewBundle(tag language.Tag, configs ...string) *i18n2.Bundle {
|
||||
bundle := i18n2.NewBundle(tag)
|
||||
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
for _, file := range configs {
|
||||
bundle.LoadMessageFile(file)
|
||||
}
|
||||
return bundle
|
||||
}
|
62
common/i18n/cache.go
Normal file
62
common/i18n/cache.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
i18n2 "github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
/*
|
||||
type Cache interface {
|
||||
GetLocalizer() (*i18n2.Localizer, bool)
|
||||
SetLocalizer(l *i18n2.Localizer)
|
||||
}
|
||||
*/
|
||||
|
||||
func getLocalizer(ctx context.Context) (*i18n2.Localizer, bool) {
|
||||
v := ctx.Value(I18nKey)
|
||||
if l, b := v.(*i18n2.Localizer); b {
|
||||
return l, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func withRequest(r *http.Request, currentLang language.Tag, bundle *i18n2.Bundle) *http.Request {
|
||||
|
||||
accept := r.Header.Get(defaultLangHeaderKey)
|
||||
localizer := i18n2.NewLocalizer(bundle, accept)
|
||||
ctx := setLocalizer(r.Context(), localizer)
|
||||
ctx = setCurrentLang(ctx, currentLang)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, defaultLangHeaderKey, accept)
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
func setCurrentLang(ctx context.Context, currentLang language.Tag) context.Context {
|
||||
return context.WithValue(ctx, I18nCurrentLangKey, currentLang)
|
||||
}
|
||||
|
||||
func setLocalizer(ctx context.Context, l *i18n2.Localizer) context.Context {
|
||||
return context.WithValue(ctx, I18nKey, l)
|
||||
}
|
||||
|
||||
func IsHasI18n(ctx context.Context) bool {
|
||||
v := ctx.Value(I18nKey)
|
||||
if v != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// func isHasI18n(ctx context.Context) bool {
|
||||
// if use, exist := ctx.Value(isUseI18n).(bool); exist {
|
||||
// return use
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// func setHasI18n(ctx context.Context, use bool) context.Context {
|
||||
// return context.WithValue(ctx, isUseI18n, use)
|
||||
// }
|
61
common/i18n/i18n.go
Normal file
61
common/i18n/i18n.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
i18n2 "github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func FormatText(ctx context.Context, msgId string, defaultText string) string {
|
||||
return FormatTextWithData(ctx, msgId, defaultText, nil)
|
||||
}
|
||||
|
||||
func FormatTextWithData(ctx context.Context, msgId string, defaultText string, args map[string]interface{}) string {
|
||||
return FormatMessage(ctx, &i18n2.Message{
|
||||
ID: msgId,
|
||||
Other: defaultText,
|
||||
}, args)
|
||||
}
|
||||
|
||||
func FormatMessage(ctx context.Context, message *i18n2.Message, args map[string]interface{}) string {
|
||||
if localizer, ok := getLocalizer(ctx); ok {
|
||||
return localizer.MustLocalize(&i18n2.LocalizeConfig{
|
||||
DefaultMessage: message,
|
||||
TemplateData: args,
|
||||
})
|
||||
}
|
||||
return formatInternalMessage(message, args)
|
||||
}
|
||||
|
||||
func formatInternalMessage(message *i18n2.Message, args map[string]interface{}) string {
|
||||
if args == nil {
|
||||
return message.Other
|
||||
}
|
||||
tpl := i18n2.NewMessageTemplate(message)
|
||||
msg, err := tpl.Execute("other", args, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func FetchCurrentLanguageFromCtx(ctx context.Context) (*language.Tag, bool) {
|
||||
v := ctx.Value(I18nCurrentLangKey)
|
||||
if l, b := v.(language.Tag); b {
|
||||
return &l, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func LocalizedString(ctx context.Context, defaultValue string, langMap map[language.Tag]string) string {
|
||||
langTag, tagExists := FetchCurrentLanguageFromCtx(ctx)
|
||||
if !tagExists {
|
||||
return defaultValue
|
||||
}
|
||||
str, ok := langMap[*langTag]
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
return str
|
||||
}
|
21
common/i18n/keys.go
Normal file
21
common/i18n/keys.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package i18n
|
||||
|
||||
const I18nKey = "i18n"
|
||||
const I18nCurrentLangKey = "lang"
|
||||
|
||||
var (
|
||||
defaultLangHeaderKey = "Accept-Language"
|
||||
defaultErrCode uint32 = 10001
|
||||
)
|
||||
|
||||
// SetDefaultLangHeaderKey sets the default value of the lang header key.
|
||||
// need to be set before use
|
||||
func SetDefaultLangHeaderKey(key string) {
|
||||
defaultLangHeaderKey = key
|
||||
}
|
||||
|
||||
// SetDefaultErrCode sets the default value of the err code.
|
||||
// need to be set before use
|
||||
func SetDefaultErrCode(code uint32) {
|
||||
defaultErrCode = code
|
||||
}
|
28
common/i18n/middleware.go
Normal file
28
common/i18n/middleware.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type I18nMiddleware struct {
|
||||
supportTags []language.Tag
|
||||
localizationFiles []string
|
||||
}
|
||||
|
||||
func NewI18nMiddleware(supportTags []language.Tag, localizationFiles []string) *I18nMiddleware {
|
||||
return &I18nMiddleware{
|
||||
supportTags: supportTags,
|
||||
localizationFiles: localizationFiles,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *I18nMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
lang := r.Header.Get(defaultLangHeaderKey)
|
||||
langTag := MatchCurrentLanguageTag(lang, m.supportTags)
|
||||
bundle := NewBundle(langTag, m.localizationFiles...)
|
||||
next(w, withRequest(r, langTag, bundle))
|
||||
}
|
||||
}
|
14
common/i18n/parse.go
Normal file
14
common/i18n/parse.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package i18n
|
||||
|
||||
import "golang.org/x/text/language"
|
||||
|
||||
func MatchCurrentLanguageTag(accept string, supportTags []language.Tag) language.Tag {
|
||||
langTags, _, err := language.ParseAcceptLanguage(accept)
|
||||
if err != nil {
|
||||
langTags = []language.Tag{language.English}
|
||||
}
|
||||
var matcher = language.NewMatcher(supportTags)
|
||||
_, i, _ := matcher.Match(langTags...)
|
||||
tag := supportTags[i]
|
||||
return tag
|
||||
}
|
74
common/i18n/rpc_interceptor.go
Normal file
74
common/i18n/rpc_interceptor.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
i18n2 "github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var rpcInterceptorDebug = false
|
||||
|
||||
func WithRpcInterceptorDebug(debug bool) {
|
||||
rpcInterceptorDebug = debug
|
||||
}
|
||||
|
||||
type I18nGrpcInterceptor struct {
|
||||
supportTags []language.Tag
|
||||
localizationFiles []string
|
||||
}
|
||||
|
||||
func NewI18nGrpcInterceptor(supportTags []language.Tag, localizationFiles []string) *I18nGrpcInterceptor {
|
||||
if len(supportTags) == 0 {
|
||||
panic("supportTags can not be empty")
|
||||
}
|
||||
return &I18nGrpcInterceptor{
|
||||
supportTags: supportTags,
|
||||
localizationFiles: localizationFiles,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *I18nGrpcInterceptor) Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
ctx, err = i.saveLocalize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
func (i *I18nGrpcInterceptor) saveLocalize(ctx context.Context) (context.Context, error) {
|
||||
respHeader, ok := metadata.FromIncomingContext(ctx)
|
||||
// lang := "en-US" //language.English
|
||||
langTag := i.supportTags[0]
|
||||
lang := langTag.String()
|
||||
if ok {
|
||||
langs := respHeader.Get(defaultLangHeaderKey)
|
||||
if len(langs) == 0 {
|
||||
// return nil, status.Error(codes.Code(defaultErrCode), "can not correct get language")
|
||||
if rpcInterceptorDebug {
|
||||
log.Printf("can not correct get language")
|
||||
}
|
||||
} else {
|
||||
lang = langs[0]
|
||||
langTag = MatchCurrentLanguageTag(lang, i.supportTags)
|
||||
}
|
||||
} else {
|
||||
if rpcInterceptorDebug {
|
||||
log.Printf("can not correct get metadata1")
|
||||
}
|
||||
// return nil, status.Error(codes.Code(defaultErrCode), "can not correct get metadata1")
|
||||
}
|
||||
|
||||
// lang := langs[0]
|
||||
// langTag := MatchCurrentLanguageTag(lang, i.supportTags)
|
||||
bundle := NewBundle(langTag, i.localizationFiles...)
|
||||
localizer := i18n2.NewLocalizer(bundle, lang)
|
||||
ctx = setLocalizer(ctx, localizer)
|
||||
|
||||
// Append the language metadata to the outgoing context
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, defaultLangHeaderKey, lang)
|
||||
return ctx, nil
|
||||
}
|
Reference in New Issue
Block a user