diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 90baf0ad9d49..39ffb987cc4c 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -30,16 +30,22 @@ type locale struct { type LocaleStore struct { // After initializing has finished, these fields are read-only. - langNames []string - langDescs []string - langOffsets []int - offsetToTranslationMap []map[string]string - localeMap map[string]*locale - defaultLang string + langNames []string + langDescs []string + + langOffsets []int + translationKeys []string + keyToOffset map[string]int + translationValues []string + + localeMap map[string]*locale + + defaultLang string + defaultLangKeysLen int } func NewLocaleStore() *LocaleStore { - return &LocaleStore{localeMap: make(map[string]*locale)} + return &LocaleStore{localeMap: make(map[string]*locale), keyToOffset: make(map[string]int)} } // AddLocaleByIni adds locale by ini into the store @@ -63,15 +69,44 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte // For development, live-reload of the translation files is important. // For production, we can do some expensive work and then make the querying fast. if setting.IsProd { - keyToValue := map[string]string{} - // Go trough all keys and store key->value into a map. - for _, section := range iniFile.Sections() { - for _, key := range section.Keys() { - keyToValue[strings.TrimPrefix(section.Name()+"."+key.Name(), "DEFAULT.")] = key.Value() + // If the language is the default language, then we go trough all keys. These keys + // will become the keys that we consider to support and take into account while going + // trough querying translation keys. + if langName == ls.defaultLang { + idx := 0 + // Store all key, value into two slices. + for _, section := range iniFile.Sections() { + for _, key := range section.Keys() { + ls.translationKeys = append(ls.translationKeys, section.Name()+"#"+key.Name()) + ls.translationValues = append(ls.translationValues, key.Value()) + + ls.keyToOffset[strings.TrimPrefix(section.Name()+"."+key.Name(), "DEFAULT.")] = idx + idx++ + } + } + + ls.defaultLangKeysLen = len(ls.translationKeys) + } else { + // Go trough all the keys that the defaultLang has and append it to translationValues. + // If the lang doesn't have a value for the translation, use the defaultLang's one. + for i := 0; i < ls.defaultLangKeysLen; i++ { + splitted := strings.SplitN(ls.translationKeys[i], "#", 2) + // TODO: optimize for repeated sequential access of section. + section, err := iniFile.GetSection(splitted[0]) + if err != nil { + // Section not found? Use the defaultLang's value for this translation key. + ls.translationValues = append(ls.translationValues, ls.translationValues[i]) + continue + } + key, err := section.GetKey(splitted[1]) + if err != nil { + // Key not found? Use the defaultLang's value for this translation key. + ls.translationValues = append(ls.translationValues, ls.translationValues[i]) + continue + } + ls.translationValues = append(ls.translationValues, key.Value()) } } - // Append the key->value to the offsetToTranslationMap variable. - ls.offsetToTranslationMap = append(ls.offsetToTranslationMap, keyToValue) // Help Go's GC. iniFile = nil @@ -175,12 +210,12 @@ func Tr(lang, trKey string, trArgs ...interface{}) string { } func TrOffset(offset int, trKey string, trArgs ...interface{}) string { - languageTranslationMap := DefaultLocales.offsetToTranslationMap[offset] - trMsg, ok := languageTranslationMap[trKey] - if !ok { - return trKey - } + // Get the offset of the translation key. + keyOffset := DefaultLocales.keyToOffset[trKey] + // Now adjust to use the language's translation of the key. + keyOffset += offset * DefaultLocales.defaultLangKeysLen + trMsg := DefaultLocales.translationValues[keyOffset] if len(trArgs) > 0 { fmtArgs := make([]interface{}, 0, len(trArgs)) for _, arg := range trArgs {