code.gitea.io/gitea@v1.22.3/modules/translation/i18n/localestore.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package i18n
     5  
     6  import (
     7  	"fmt"
     8  	"html/template"
     9  	"slices"
    10  
    11  	"code.gitea.io/gitea/modules/log"
    12  	"code.gitea.io/gitea/modules/setting"
    13  )
    14  
    15  // This file implements the static LocaleStore that will not watch for changes
    16  
    17  type locale struct {
    18  	store       *localeStore
    19  	langName    string
    20  	idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
    21  }
    22  
    23  var _ Locale = (*locale)(nil)
    24  
    25  type localeStore struct {
    26  	// After initializing has finished, these fields are read-only.
    27  	langNames []string
    28  	langDescs []string
    29  
    30  	localeMap     map[string]*locale
    31  	trKeyToIdxMap map[string]int
    32  
    33  	defaultLang string
    34  }
    35  
    36  // NewLocaleStore creates a static locale store
    37  func NewLocaleStore() LocaleStore {
    38  	return &localeStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)}
    39  }
    40  
    41  // AddLocaleByIni adds locale by ini into the store
    42  func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error {
    43  	if _, ok := store.localeMap[langName]; ok {
    44  		return ErrLocaleAlreadyExist
    45  	}
    46  
    47  	store.langNames = append(store.langNames, langName)
    48  	store.langDescs = append(store.langDescs, langDesc)
    49  
    50  	l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string)}
    51  	store.localeMap[l.langName] = l
    52  
    53  	iniFile, err := setting.NewConfigProviderForLocale(source, moreSource)
    54  	if err != nil {
    55  		return fmt.Errorf("unable to load ini: %w", err)
    56  	}
    57  
    58  	for _, section := range iniFile.Sections() {
    59  		for _, key := range section.Keys() {
    60  			var trKey string
    61  			if section.Name() == "" || section.Name() == "DEFAULT" {
    62  				trKey = key.Name()
    63  			} else {
    64  				trKey = section.Name() + "." + key.Name()
    65  			}
    66  			idx, ok := store.trKeyToIdxMap[trKey]
    67  			if !ok {
    68  				idx = len(store.trKeyToIdxMap)
    69  				store.trKeyToIdxMap[trKey] = idx
    70  			}
    71  			l.idxToMsgMap[idx] = key.Value()
    72  		}
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  func (store *localeStore) HasLang(langName string) bool {
    79  	_, ok := store.localeMap[langName]
    80  	return ok
    81  }
    82  
    83  func (store *localeStore) ListLangNameDesc() (names, desc []string) {
    84  	return store.langNames, store.langDescs
    85  }
    86  
    87  // SetDefaultLang sets default language as a fallback
    88  func (store *localeStore) SetDefaultLang(lang string) {
    89  	store.defaultLang = lang
    90  }
    91  
    92  // Locale returns the locale for the lang or the default language
    93  func (store *localeStore) Locale(lang string) (Locale, bool) {
    94  	l, found := store.localeMap[lang]
    95  	if !found {
    96  		var ok bool
    97  		l, ok = store.localeMap[store.defaultLang]
    98  		if !ok {
    99  			// no default - return an empty locale
   100  			l = &locale{store: store, idxToMsgMap: make(map[int]string)}
   101  		}
   102  	}
   103  	return l, found
   104  }
   105  
   106  func (store *localeStore) Close() error {
   107  	return nil
   108  }
   109  
   110  func (l *locale) TrString(trKey string, trArgs ...any) string {
   111  	format := trKey
   112  
   113  	idx, ok := l.store.trKeyToIdxMap[trKey]
   114  	if ok {
   115  		if msg, ok := l.idxToMsgMap[idx]; ok {
   116  			format = msg // use the found translation
   117  		} else if def, ok := l.store.localeMap[l.store.defaultLang]; ok {
   118  			// try to use default locale's translation
   119  			if msg, ok := def.idxToMsgMap[idx]; ok {
   120  				format = msg
   121  			}
   122  		}
   123  	}
   124  
   125  	msg, err := Format(format, trArgs...)
   126  	if err != nil {
   127  		log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)
   128  	}
   129  	return msg
   130  }
   131  
   132  func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
   133  	args := slices.Clone(trArgs)
   134  	for i, v := range args {
   135  		switch v := v.(type) {
   136  		case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
   137  			// for most basic types (including template.HTML which is safe), just do nothing and use it
   138  		case string:
   139  			args[i] = template.HTMLEscapeString(v)
   140  		case fmt.Stringer:
   141  			args[i] = template.HTMLEscapeString(v.String())
   142  		default:
   143  			args[i] = template.HTMLEscapeString(fmt.Sprint(v))
   144  		}
   145  	}
   146  	return template.HTML(l.TrString(trKey, args...))
   147  }
   148  
   149  // HasKey returns whether a key is present in this locale or not
   150  func (l *locale) HasKey(trKey string) bool {
   151  	idx, ok := l.store.trKeyToIdxMap[trKey]
   152  	if !ok {
   153  		return false
   154  	}
   155  	_, ok = l.idxToMsgMap[idx]
   156  	return ok
   157  }