diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2022-04-03 17:46:48 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-03 17:46:48 +0800 |
commit | d242511e86c3a6d8a7013100845d2cdc8eb5252c (patch) | |
tree | 391b7fa946d32b44f06a274453e4b4d1a19fe8ee /modules/translation/i18n/i18n.go | |
parent | 27c34dd011cceb8232d1c3307f87b53a147c75c3 (diff) | |
download | gitea-d242511e86c3a6d8a7013100845d2cdc8eb5252c.tar.gz gitea-d242511e86c3a6d8a7013100845d2cdc8eb5252c.zip |
Remove legacy unmaintained packages, refactor to support change default locale (#19308)
Remove two unmaintained vendor packages `i18n` and `paginater`. Changes:
* Rewrite `i18n` package with a more clear fallback mechanism. Fix an unstable `Tr` behavior, add more tests.
* Refactor the legacy `Paginater` to `Paginator`, test cases are kept unchanged.
Trivial enhancement (no breaking for end users):
* Use the first locale in LANGS setting option as the default, add a log to prevent from surprising users.
Diffstat (limited to 'modules/translation/i18n/i18n.go')
-rw-r--r-- | modules/translation/i18n/i18n.go | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go new file mode 100644 index 0000000000..664e457ecf --- /dev/null +++ b/modules/translation/i18n/i18n.go @@ -0,0 +1,143 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package i18n + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "code.gitea.io/gitea/modules/log" + + "gopkg.in/ini.v1" +) + +var ( + ErrLocaleAlreadyExist = errors.New("lang already exists") + + DefaultLocales = NewLocaleStore() +) + +type locale struct { + store *LocaleStore + langName string + langDesc string + messages *ini.File +} + +type LocaleStore struct { + // at the moment, all these fields are readonly after initialization + langNames []string + langDescs []string + localeMap map[string]*locale + defaultLang string +} + +func NewLocaleStore() *LocaleStore { + return &LocaleStore{localeMap: make(map[string]*locale)} +} + +// AddLocaleByIni adds locale by ini into the store +func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { + if _, ok := ls.localeMap[langName]; ok { + return ErrLocaleAlreadyExist + } + iniFile, err := ini.LoadSources(ini.LoadOptions{ + IgnoreInlineComment: true, + UnescapeValueCommentSymbols: true, + }, localeFile, otherLocaleFiles...) + if err == nil { + iniFile.BlockMode = false + lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile} + ls.langNames = append(ls.langNames, lc.langName) + ls.langDescs = append(ls.langDescs, lc.langDesc) + ls.localeMap[lc.langName] = lc + } + return err +} + +func (ls *LocaleStore) HasLang(langName string) bool { + _, ok := ls.localeMap[langName] + return ok +} + +func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { + return ls.langNames, ls.langDescs +} + +// SetDefaultLang sets default language as a fallback +func (ls *LocaleStore) SetDefaultLang(lang string) { + ls.defaultLang = lang +} + +// Tr translates content to target language. fall back to default language. +func (ls *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { + l, ok := ls.localeMap[lang] + if !ok { + l, ok = ls.localeMap[ls.defaultLang] + } + if ok { + return l.Tr(trKey, trArgs...) + } + return trKey +} + +// Tr translates content to locale language. fall back to default language. +func (l *locale) Tr(trKey string, trArgs ...interface{}) string { + var section string + + idx := strings.IndexByte(trKey, '.') + if idx > 0 { + section = trKey[:idx] + trKey = trKey[idx+1:] + } + + trMsg := trKey + if trIni, err := l.messages.Section(section).GetKey(trKey); err == nil { + trMsg = trIni.Value() + } else if l.store.defaultLang != "" && l.langName != l.store.defaultLang { + // try to fall back to default + if defaultLocale, ok := l.store.localeMap[l.store.defaultLang]; ok { + if trIni, err = defaultLocale.messages.Section(section).GetKey(trKey); err == nil { + trMsg = trIni.Value() + } + } + } + + if len(trArgs) > 0 { + fmtArgs := make([]interface{}, 0, len(trArgs)) + for _, arg := range trArgs { + val := reflect.ValueOf(arg) + if val.Kind() == reflect.Slice { + // 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 + // now, we restrict the strange behavior and only support: + // 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...) + // 2. Tr(lang, key, args...) as Sprintf(msg, args...) + if len(trArgs) == 1 { + for i := 0; i < val.Len(); i++ { + fmtArgs = append(fmtArgs, val.Index(i).Interface()) + } + } else { + log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs) + break + } + } else { + fmtArgs = append(fmtArgs, arg) + } + } + return fmt.Sprintf(trMsg, fmtArgs...) + } + return trMsg +} + +func ResetDefaultLocales() { + DefaultLocales = NewLocaleStore() +} + +// Tr use default locales to translate content to target language. +func Tr(lang, trKey string, trArgs ...interface{}) string { + return DefaultLocales.Tr(lang, trKey, trArgs...) +} |