github.com/wfusion/gofusion@v1.1.14/i18n/i18n.go (about) 1 package i18n 2 3 import ( 4 "sync" 5 6 "github.com/BurntSushi/toml" 7 "github.com/nicksnyder/go-i18n/v2/i18n" 8 "github.com/pkg/errors" 9 "github.com/spf13/cast" 10 "golang.org/x/text/language" 11 "gopkg.in/yaml.v3" 12 13 "github.com/wfusion/gofusion/common/utils" 14 "github.com/wfusion/gofusion/common/utils/clone" 15 "github.com/wfusion/gofusion/common/utils/serialize/json" 16 ) 17 18 var ( 19 Bundle *bundle[int] 20 21 locker sync.RWMutex 22 defaultLang = language.Chinese 23 defaultErrorMessages = map[language.Tag]string{ 24 language.Chinese: "系统错误,请稍后重试", 25 language.English: "System error! Please try again later", 26 } 27 ) 28 29 type bundle[T comparable] struct { 30 dup *utils.Set[T] 31 bundle *i18n.Bundle 32 33 vars map[T][]string 34 mutex sync.RWMutex 35 } 36 37 func NewBundle[T comparable](lang language.Tag) Localizable[T] { 38 b := &bundle[T]{ 39 dup: utils.NewSet[T](), 40 bundle: i18n.NewBundle(lang), 41 vars: make(map[T][]string), 42 } 43 44 b.bundle.RegisterUnmarshalFunc("json", json.Unmarshal) 45 b.bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) 46 b.bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) 47 b.bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal) 48 49 return b 50 } 51 52 type addMessagesOption struct { 53 vars []string 54 } 55 56 func Var(vars ...string) utils.OptionFunc[addMessagesOption] { 57 return func(o *addMessagesOption) { 58 o.vars = vars 59 } 60 } 61 62 func (i *bundle[T]) AddMessages(code T, trans map[language.Tag]*Message, opts ...utils.OptionExtender) Localizable[T] { 63 i.checkDuplicated(code, trans) 64 65 i.mutex.Lock() 66 defer i.mutex.Unlock() 67 68 o := utils.ApplyOptions[addMessagesOption](opts...) 69 id := cast.ToString(code) 70 i.vars[code] = o.vars 71 for lang, msg := range trans { 72 i.bundle.MustAddMessages(lang, &i18n.Message{ 73 ID: id, 74 Hash: msg.Hash, 75 Description: msg.Description, 76 LeftDelim: msg.LeftDelim, 77 RightDelim: msg.RightDelim, 78 Zero: msg.Zero, 79 One: msg.One, 80 Two: msg.Two, 81 Few: msg.Few, 82 Many: msg.Many, 83 Other: msg.Other, 84 }) 85 } 86 return i 87 } 88 89 func (i *bundle[T]) checkDuplicated(code T, trans map[language.Tag]*Message) { 90 if !i.dup.Contains(code) { 91 i.dup.Insert(code) 92 return 93 } 94 if trans == nil { 95 panic(errors.Errorf("%+v %s code translation is empty", code, cast.ToString(code))) 96 } 97 98 // panic if duplicated 99 var ( 100 cfg = &i18n.LocalizeConfig{MessageID: cast.ToString(code)} 101 existMsgEn = i18n.NewLocalizer(i.bundle, language.English.String()).MustLocalize(cfg) 102 existMsgCn = i18n.NewLocalizer(i.bundle, language.Chinese.String()).MustLocalize(cfg) 103 dupMsgCn, dupMsgEn string 104 ) 105 if dupMsg, ok := trans[language.Chinese]; ok { 106 dupMsgCn = dupMsg.Other 107 } 108 if dupMsg, ok := trans[language.English]; ok { 109 dupMsgEn = dupMsg.Other 110 } 111 112 panic(errors.Errorf("%s(%s)(%+v)(%v) is duplicated with %s(%s)", 113 dupMsgCn, dupMsgEn, code, cast.ToString(code), existMsgCn, existMsgEn)) 114 } 115 116 type localizeOption struct { 117 lang language.Tag 118 langs []string 119 pluralCount any 120 templateData map[string]any 121 } 122 123 func Param(data map[string]any) utils.OptionFunc[localizeOption] { 124 return func(o *localizeOption) { 125 o.templateData = data 126 } 127 } 128 129 func Plural(pluralCount any) utils.OptionFunc[localizeOption] { 130 return func(o *localizeOption) { 131 o.pluralCount = pluralCount 132 } 133 } 134 135 func Lang(lang language.Tag) utils.OptionFunc[localizeOption] { 136 return func(o *localizeOption) { 137 o.lang = lang 138 } 139 } 140 141 func Langs(langs []string) utils.OptionFunc[localizeOption] { 142 return func(o *localizeOption) { 143 if len(langs) > 0 { 144 o.lang, _ = language.Parse(langs[0]) 145 } 146 o.langs = clone.SliceComparable(langs) 147 } 148 } 149 150 func (i *bundle[T]) Localize(code T, opts ...utils.OptionExtender) (message string) { 151 option := utils.ApplyOptions[localizeOption](opts...) 152 if option.templateData == nil && len(i.vars) > 0 { 153 option.templateData = make(map[string]any, len(i.vars)) 154 } 155 156 // TODO: Access the third-party internationalization platform to obtain text 157 cfg := &i18n.LocalizeConfig{ 158 MessageID: cast.ToString(code), 159 TemplateData: option.templateData, 160 PluralCount: option.pluralCount, 161 } 162 163 i.mutex.RLock() 164 defer i.mutex.RUnlock() 165 // Assign an empty string to a variable to avoid rendering < no value > data 166 for _, v := range i.vars[code] { 167 if _, ok := option.templateData[v]; !ok { 168 option.templateData[v] = "" 169 } 170 } 171 message, err := i18n.NewLocalizer(i.bundle, option.langs...).Localize(cfg) 172 if err == nil { 173 return 174 } 175 message, ok := defaultErrorMessages[option.lang] 176 if ok { 177 return 178 } 179 180 return defaultErrorMessages[defaultLang] 181 }