github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/translation/i18n/i18n.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package i18n 7 8 import ( 9 "errors" 10 "fmt" 11 "reflect" 12 "strings" 13 14 "github.com/gitbundle/modules/log" 15 16 "gopkg.in/ini.v1" 17 ) 18 19 var ( 20 ErrLocaleAlreadyExist = errors.New("lang already exists") 21 22 DefaultLocales = NewLocaleStore() 23 ) 24 25 type locale struct { 26 store *LocaleStore 27 langName string 28 langDesc string 29 messages *ini.File 30 } 31 32 type LocaleStore struct { 33 // at the moment, all these fields are readonly after initialization 34 langNames []string 35 langDescs []string 36 localeMap map[string]*locale 37 defaultLang string 38 } 39 40 func NewLocaleStore() *LocaleStore { 41 return &LocaleStore{localeMap: make(map[string]*locale)} 42 } 43 44 // AddLocaleByIni adds locale by ini into the store 45 func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { 46 if _, ok := ls.localeMap[langName]; ok { 47 return ErrLocaleAlreadyExist 48 } 49 iniFile, err := ini.LoadSources(ini.LoadOptions{ 50 IgnoreInlineComment: true, 51 UnescapeValueCommentSymbols: true, 52 }, localeFile, otherLocaleFiles...) 53 if err == nil { 54 iniFile.BlockMode = false 55 lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile} 56 ls.langNames = append(ls.langNames, lc.langName) 57 ls.langDescs = append(ls.langDescs, lc.langDesc) 58 ls.localeMap[lc.langName] = lc 59 } 60 return err 61 } 62 63 func (ls *LocaleStore) HasLang(langName string) bool { 64 _, ok := ls.localeMap[langName] 65 return ok 66 } 67 68 func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { 69 return ls.langNames, ls.langDescs 70 } 71 72 // SetDefaultLang sets default language as a fallback 73 func (ls *LocaleStore) SetDefaultLang(lang string) { 74 ls.defaultLang = lang 75 } 76 77 // Tr translates content to target language. fall back to default language. 78 func (ls *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { 79 l, ok := ls.localeMap[lang] 80 if !ok { 81 l, ok = ls.localeMap[ls.defaultLang] 82 } 83 if ok { 84 return l.Tr(trKey, trArgs...) 85 } 86 return trKey 87 } 88 89 // Tr translates content to locale language. fall back to default language. 90 func (l *locale) Tr(trKey string, trArgs ...interface{}) string { 91 var section string 92 93 idx := strings.IndexByte(trKey, '.') 94 if idx > 0 { 95 section = trKey[:idx] 96 trKey = trKey[idx+1:] 97 } 98 99 trMsg := trKey 100 if trIni, err := l.messages.Section(section).GetKey(trKey); err == nil { 101 trMsg = trIni.Value() 102 } else if l.store.defaultLang != "" && l.langName != l.store.defaultLang { 103 // try to fall back to default 104 if defaultLocale, ok := l.store.localeMap[l.store.defaultLang]; ok { 105 if trIni, err = defaultLocale.messages.Section(section).GetKey(trKey); err == nil { 106 trMsg = trIni.Value() 107 } 108 } 109 } 110 111 if len(trArgs) > 0 { 112 fmtArgs := make([]interface{}, 0, len(trArgs)) 113 for _, arg := range trArgs { 114 val := reflect.ValueOf(arg) 115 if val.Kind() == reflect.Slice { 116 // before, it can accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f), it's an unstable behavior 117 // now, we restrict the strange behavior and only support: 118 // 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...) 119 // 2. Tr(lang, key, args...) as Sprintf(msg, args...) 120 if len(trArgs) == 1 { 121 for i := 0; i < val.Len(); i++ { 122 fmtArgs = append(fmtArgs, val.Index(i).Interface()) 123 } 124 } else { 125 log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs) 126 break 127 } 128 } else { 129 fmtArgs = append(fmtArgs, arg) 130 } 131 } 132 return fmt.Sprintf(trMsg, fmtArgs...) 133 } 134 return trMsg 135 } 136 137 func ResetDefaultLocales() { 138 DefaultLocales = NewLocaleStore() 139 } 140 141 // Tr use default locales to translate content to target language. 142 func Tr(lang, trKey string, trArgs ...interface{}) string { 143 return DefaultLocales.Tr(lang, trKey, trArgs...) 144 }