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  }