code.gitea.io/gitea@v1.19.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  
     9  	"code.gitea.io/gitea/modules/log"
    10  
    11  	"gopkg.in/ini.v1"
    12  )
    13  
    14  // This file implements the static LocaleStore that will not watch for changes
    15  
    16  type locale struct {
    17  	store       *localeStore
    18  	langName    string
    19  	idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
    20  }
    21  
    22  type localeStore struct {
    23  	// After initializing has finished, these fields are read-only.
    24  	langNames []string
    25  	langDescs []string
    26  
    27  	localeMap     map[string]*locale
    28  	trKeyToIdxMap map[string]int
    29  
    30  	defaultLang string
    31  }
    32  
    33  // NewLocaleStore creates a static locale store
    34  func NewLocaleStore() LocaleStore {
    35  	return &localeStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)}
    36  }
    37  
    38  // AddLocaleByIni adds locale by ini into the store
    39  func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, moreSource []byte) error {
    40  	if _, ok := store.localeMap[langName]; ok {
    41  		return ErrLocaleAlreadyExist
    42  	}
    43  
    44  	store.langNames = append(store.langNames, langName)
    45  	store.langDescs = append(store.langDescs, langDesc)
    46  
    47  	l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string)}
    48  	store.localeMap[l.langName] = l
    49  
    50  	iniFile, err := ini.LoadSources(ini.LoadOptions{
    51  		IgnoreInlineComment:         true,
    52  		UnescapeValueCommentSymbols: true,
    53  	}, source, moreSource)
    54  	if err != nil {
    55  		return fmt.Errorf("unable to load ini: %w", err)
    56  	}
    57  	iniFile.BlockMode = false
    58  
    59  	for _, section := range iniFile.Sections() {
    60  		for _, key := range section.Keys() {
    61  			var trKey string
    62  			if section.Name() == "" || section.Name() == "DEFAULT" {
    63  				trKey = key.Name()
    64  			} else {
    65  				trKey = section.Name() + "." + key.Name()
    66  			}
    67  			idx, ok := store.trKeyToIdxMap[trKey]
    68  			if !ok {
    69  				idx = len(store.trKeyToIdxMap)
    70  				store.trKeyToIdxMap[trKey] = idx
    71  			}
    72  			l.idxToMsgMap[idx] = key.Value()
    73  		}
    74  	}
    75  	iniFile = nil
    76  
    77  	return nil
    78  }
    79  
    80  func (store *localeStore) HasLang(langName string) bool {
    81  	_, ok := store.localeMap[langName]
    82  	return ok
    83  }
    84  
    85  func (store *localeStore) ListLangNameDesc() (names, desc []string) {
    86  	return store.langNames, store.langDescs
    87  }
    88  
    89  // SetDefaultLang sets default language as a fallback
    90  func (store *localeStore) SetDefaultLang(lang string) {
    91  	store.defaultLang = lang
    92  }
    93  
    94  // Tr translates content to target language. fall back to default language.
    95  func (store *localeStore) Tr(lang, trKey string, trArgs ...interface{}) string {
    96  	l, _ := store.Locale(lang)
    97  
    98  	return l.Tr(trKey, trArgs...)
    99  }
   100  
   101  // Has returns whether the given language has a translation for the provided key
   102  func (store *localeStore) Has(lang, trKey string) bool {
   103  	l, _ := store.Locale(lang)
   104  
   105  	return l.Has(trKey)
   106  }
   107  
   108  // Locale returns the locale for the lang or the default language
   109  func (store *localeStore) Locale(lang string) (Locale, bool) {
   110  	l, found := store.localeMap[lang]
   111  	if !found {
   112  		var ok bool
   113  		l, ok = store.localeMap[store.defaultLang]
   114  		if !ok {
   115  			// no default - return an empty locale
   116  			l = &locale{store: store, idxToMsgMap: make(map[int]string)}
   117  		}
   118  	}
   119  	return l, found
   120  }
   121  
   122  // Close implements io.Closer
   123  func (store *localeStore) Close() error {
   124  	return nil
   125  }
   126  
   127  // Tr translates content to locale language. fall back to default language.
   128  func (l *locale) Tr(trKey string, trArgs ...interface{}) string {
   129  	format := trKey
   130  
   131  	idx, ok := l.store.trKeyToIdxMap[trKey]
   132  	if ok {
   133  		if msg, ok := l.idxToMsgMap[idx]; ok {
   134  			format = msg // use the found translation
   135  		} else if def, ok := l.store.localeMap[l.store.defaultLang]; ok {
   136  			// try to use default locale's translation
   137  			if msg, ok := def.idxToMsgMap[idx]; ok {
   138  				format = msg
   139  			}
   140  		}
   141  	}
   142  
   143  	msg, err := Format(format, trArgs...)
   144  	if err != nil {
   145  		log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)
   146  	}
   147  	return msg
   148  }
   149  
   150  // Has returns whether a key is present in this locale or not
   151  func (l *locale) Has(trKey string) bool {
   152  	idx, ok := l.store.trKeyToIdxMap[trKey]
   153  	if !ok {
   154  		return false
   155  	}
   156  	_, ok = l.idxToMsgMap[idx]
   157  	return ok
   158  }