github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/shared/i18n/i18n.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package i18n 5 6 import ( 7 "fmt" 8 "html/template" 9 "io/ioutil" 10 "net/http" 11 "path/filepath" 12 "reflect" 13 "strings" 14 15 "github.com/mattermost/go-i18n/i18n" 16 17 "github.com/masterhung0112/hk_server/v5/shared/mlog" 18 ) 19 20 const defaultLocale = "en" 21 22 // TranslateFunc is the type of the translate functions 23 type TranslateFunc func(translationID string, args ...interface{}) string 24 25 // T is the translate function using the default server language as fallback language 26 var T TranslateFunc 27 28 // TDefault is the translate function using english as fallback language 29 var TDefault TranslateFunc 30 31 var locales map[string]string = make(map[string]string) 32 var defaultServerLocale string 33 var defaultClientLocale string 34 35 // TranslationsPreInit loads translations from filesystem if they are not 36 // loaded already and assigns english while loading server config 37 func TranslationsPreInit(translationsDir string) error { 38 if T != nil { 39 return nil 40 } 41 42 // Set T even if we fail to load the translations. Lots of shutdown handling code will 43 // segfault trying to handle the error, and the untranslated IDs are strictly better. 44 T = tfuncWithFallback(defaultLocale) 45 TDefault = tfuncWithFallback(defaultLocale) 46 47 return initTranslationsWithDir(translationsDir) 48 } 49 50 // InitTranslations set the defaults configured in the server and initialize 51 // the T function using the server default as fallback language 52 func InitTranslations(serverLocale, clientLocale string) error { 53 defaultServerLocale = serverLocale 54 defaultClientLocale = clientLocale 55 56 var err error 57 T, err = getTranslationsBySystemLocale() 58 return err 59 } 60 61 func initTranslationsWithDir(dir string) error { 62 files, _ := ioutil.ReadDir(dir) 63 for _, f := range files { 64 if filepath.Ext(f.Name()) == ".json" { 65 filename := f.Name() 66 locales[strings.Split(filename, ".")[0]] = filepath.Join(dir, filename) 67 68 if err := i18n.LoadTranslationFile(filepath.Join(dir, filename)); err != nil { 69 return err 70 } 71 } 72 } 73 74 return nil 75 } 76 77 func getTranslationsBySystemLocale() (TranslateFunc, error) { 78 locale := defaultServerLocale 79 if _, ok := locales[locale]; !ok { 80 mlog.Warn("Failed to load system translations for", mlog.String("locale", locale), mlog.String("attempting to fall back to default locale", defaultLocale)) 81 locale = defaultLocale 82 } 83 84 if locales[locale] == "" { 85 return nil, fmt.Errorf("failed to load system translations for '%v'", defaultLocale) 86 } 87 88 translations := tfuncWithFallback(locale) 89 if translations == nil { 90 return nil, fmt.Errorf("failed to load system translations") 91 } 92 93 mlog.Info("Loaded system translations", mlog.String("for locale", locale), mlog.String("from locale", locales[locale])) 94 return translations, nil 95 } 96 97 // GetUserTranslations get the translation function for an specific locale 98 func GetUserTranslations(locale string) TranslateFunc { 99 if _, ok := locales[locale]; !ok { 100 locale = defaultLocale 101 } 102 103 translations := tfuncWithFallback(locale) 104 return translations 105 } 106 107 // GetTranslationsAndLocaleFromRequest return the translation function and the 108 // locale based on a request headers 109 func GetTranslationsAndLocaleFromRequest(r *http.Request) (TranslateFunc, string) { 110 // This is for checking against locales like pt_BR or zn_CN 111 headerLocaleFull := strings.Split(r.Header.Get("Accept-Language"), ",")[0] 112 // This is for checking against locales like en, es 113 headerLocale := strings.Split(strings.Split(r.Header.Get("Accept-Language"), ",")[0], "-")[0] 114 defaultLocale := defaultClientLocale 115 if locales[headerLocaleFull] != "" { 116 translations := tfuncWithFallback(headerLocaleFull) 117 return translations, headerLocaleFull 118 } else if locales[headerLocale] != "" { 119 translations := tfuncWithFallback(headerLocale) 120 return translations, headerLocale 121 } else if locales[defaultLocale] != "" { 122 translations := tfuncWithFallback(defaultLocale) 123 return translations, headerLocale 124 } 125 126 translations := tfuncWithFallback(defaultLocale) 127 return translations, defaultLocale 128 } 129 130 // GetSupportedLocales return a map of locale code and the file path with the 131 // translations 132 func GetSupportedLocales() map[string]string { 133 return locales 134 } 135 136 func tfuncWithFallback(pref string) TranslateFunc { 137 t, _ := i18n.Tfunc(pref) 138 return func(translationID string, args ...interface{}) string { 139 if translated := t(translationID, args...); translated != translationID { 140 return translated 141 } 142 143 t, _ := i18n.Tfunc(defaultLocale) 144 return t(translationID, args...) 145 } 146 } 147 148 // TranslateAsHTML translates the translationID provided and return a 149 // template.HTML object 150 func TranslateAsHTML(t TranslateFunc, translationID string, args map[string]interface{}) template.HTML { 151 message := t(translationID, escapeForHTML(args)) 152 message = strings.Replace(message, "[[", "<strong>", -1) 153 message = strings.Replace(message, "]]", "</strong>", -1) 154 return template.HTML(message) 155 } 156 157 func escapeForHTML(arg interface{}) interface{} { 158 switch typedArg := arg.(type) { 159 case string: 160 return template.HTMLEscapeString(typedArg) 161 case *string: 162 return template.HTMLEscapeString(*typedArg) 163 case map[string]interface{}: 164 safeArg := make(map[string]interface{}, len(typedArg)) 165 for key, value := range typedArg { 166 safeArg[key] = escapeForHTML(value) 167 } 168 return safeArg 169 default: 170 mlog.Warn( 171 "Unable to escape value for HTML template", 172 mlog.Any("html_template", arg), 173 mlog.String("template_type", reflect.ValueOf(arg).Type().String()), 174 ) 175 return "" 176 } 177 } 178 179 // IdentityTfunc returns a translation function that don't translate, only 180 // returns the same id 181 func IdentityTfunc() TranslateFunc { 182 return func(translationID string, args ...interface{}) string { 183 return translationID 184 } 185 }