From 24abae5c77edd42191f0ca1dc5d4e70271fff12d Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 21 Jun 2022 03:12:19 +0200 Subject: [PATCH 01/27] Prototyping --- modules/translation/i18n/i18n.go | 69 +++++++++++++++++++++++++----- modules/translation/translation.go | 14 +++--- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 664e457ecf79f..b17ed38491bc7 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "gopkg.in/ini.v1" ) @@ -24,16 +25,18 @@ var ( 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 + // After initializing has finished, these fields are read-only. + langNames []string + langDescs []string + translationKeys []string + translationValues []string + localeMap map[string]*locale + defaultLang string + defaultLangKeysLen int } func NewLocaleStore() *LocaleStore { @@ -50,11 +53,55 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte 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 + // Common code between production and development. + ls.langNames = append(ls.langNames, langName) + ls.langDescs = append(ls.langDescs, langDesc) + + // Make a distinquishment between production and development. + // 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 { + // 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 { + // 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.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], "#", 1) + // 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()) + } + } + // Help Go's GC. + iniFile = nil + } else { + // Add the language to the localeMap. + iniFile.BlockMode = false + lc := &locale{store: ls, langName: langName, messages: iniFile} + ls.localeMap[lc.langName] = lc + } } return err } diff --git a/modules/translation/translation.go b/modules/translation/translation.go index da9d9b9b6851c..05ae7b59fc4d7 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -74,12 +74,7 @@ func InitLocales() { } matcher = language.NewMatcher(supportedTags) - for i := range setting.Names { - key := "locale_" + setting.Langs[i] + ".ini" - if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { - log.Error("Failed to set messages to %s: %v", setting.Langs[i], err) - } - } + if len(setting.Langs) != 0 { defaultLangName := setting.Langs[0] if defaultLangName != "en-US" { @@ -88,6 +83,13 @@ func InitLocales() { i18n.DefaultLocales.SetDefaultLang(defaultLangName) } + for i := range setting.Names { + key := "locale_" + setting.Langs[i] + ".ini" + if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { + log.Error("Failed to set messages to %s: %v", setting.Langs[i], err) + } + } + langs, descs := i18n.DefaultLocales.ListLangNameDesc() allLangs = make([]*LangType, 0, len(langs)) allLangMap = map[string]*LangType{} From 48b9b21e7843c7f8b7fbffa35d6ae8fdd2e4323e Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 22 Jun 2022 23:11:00 +0200 Subject: [PATCH 02/27] Start work on creating offsets --- modules/translation/i18n/i18n.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index b17ed38491bc7..2fce66ab65554 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -32,6 +32,7 @@ type LocaleStore struct { // After initializing has finished, these fields are read-only. langNames []string langDescs []string + langOffsets []int translationKeys []string translationValues []string localeMap map[string]*locale @@ -96,6 +97,9 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte } // Help Go's GC. iniFile = nil + + // Specify the offset for translationValues. + ls.langOffsets = append(ls.langOffsets, len(ls.langOffsets)) } else { // Add the language to the localeMap. iniFile.BlockMode = false From 8b7ca1c6f14314adf541f75ffe995a3a6ca88193 Mon Sep 17 00:00:00 2001 From: Gusted Date: Wed, 22 Jun 2022 23:19:48 +0200 Subject: [PATCH 03/27] Modify tests --- modules/translation/i18n/i18n.go | 8 ++-- modules/translation/i18n/i18n_test.go | 55 ++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 2fce66ab65554..bf811dd1c381f 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -69,7 +69,9 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte // 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()) + key := strings.TrimPrefix(section.Name()+"."+key.Name(). "DEFAULT.") + + ls.translationKeys = append(ls.translationKeys, key) ls.translationValues = append(ls.translationValues, key.Value()) } } @@ -115,8 +117,8 @@ func (ls *LocaleStore) HasLang(langName string) bool { return ok } -func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { - return ls.langNames, ls.langDescs +func (ls *LocaleStore) ListLangNameDescOffsets() (names, desc []string, offsets []int) { + return ls.langNames, ls.langDescs, ls.langOffsets } // SetDefaultLang sets default language as a fallback diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go index 70066016cfe43..de50c1a8d241e 100644 --- a/modules/translation/i18n/i18n_test.go +++ b/modules/translation/i18n/i18n_test.go @@ -7,6 +7,7 @@ package i18n import ( "testing" + "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" ) @@ -50,7 +51,59 @@ sub = Changed Sub String result = ls.Tr("lang2", "section.mixed") assert.Equal(t, `test value; more text`, result) - langs, descs := ls.ListLangNameDesc() + langs, descs, offsets := ls.ListLangNameDescOffsets() assert.Equal(t, []string{"lang1", "lang2"}, langs) assert.Equal(t, []string{"Lang1", "Lang2"}, descs) + assert.Equal(t, []int{0, 1}, offsets) +} + +func Test_TrProd(t *testing.T) { + setting.IsProd = true + defer func() { + setting.IsProd = false + }() + + testData1 := []byte(` +.dot.name = Dot Name +fmt = %[1]s %[2]s + +[section] +sub = Sub String +mixed = test value; more text +`) + + testData2 := []byte(` +fmt = %[2]s %[1]s + +[section] +sub = Changed Sub String +`) + + ls := NewLocaleStore() + assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1)) + assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2)) + ls.SetDefaultLang("lang1") + + result := ls.Tr("lang1", "fmt", "a", "b") + assert.Equal(t, "a b", result) + + result = ls.Tr("lang2", "fmt", "a", "b") + assert.Equal(t, "b a", result) + + result = ls.Tr("lang1", "section.sub") + assert.Equal(t, "Sub String", result) + + result = ls.Tr("lang2", "section.sub") + assert.Equal(t, "Changed Sub String", result) + + result = ls.Tr("", ".dot.name") + assert.Equal(t, "Dot Name", result) + + result = ls.Tr("lang2", "section.mixed") + assert.Equal(t, `test value; more text`, result) + + langs, descs, offsets := ls.ListLangNameDescOffsets() + assert.Equal(t, []string{"lang1", "lang2"}, langs) + assert.Equal(t, []string{"Lang1", "Lang2"}, descs) + assert.Equal(t, []int{0, 1}, offsets) } From 8427dbdb925a705fbf8c4fd47bf00a96bfe69fc4 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 23 Jun 2022 00:37:48 +0200 Subject: [PATCH 04/27] Start prototyping with actual MPH --- go.mod | 1 + go.sum | 1 + modules/mph/mph.go | 139 +++++++++++++++++++++++++++++ modules/translation/i18n/i18n.go | 59 +++++++++--- modules/translation/translation.go | 11 ++- 5 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 modules/mph/mph.go diff --git a/go.mod b/go.mod index e9b4194c79936..0aca966aeb3b9 100644 --- a/go.mod +++ b/go.mod @@ -80,6 +80,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 github.com/sergi/go-diff v1.2.0 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 + github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 github.com/stretchr/testify v1.7.1 github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 diff --git a/go.sum b/go.sum index ae4d06d1f9916..3bcaf444e23b4 100644 --- a/go.sum +++ b/go.sum @@ -1422,6 +1422,7 @@ github.com/soheilhy/cmux v0.1.5-0.20210205191134-5ec6847320e5/go.mod h1:T7TcVDs9 github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= diff --git a/modules/mph/mph.go b/modules/mph/mph.go new file mode 100644 index 0000000000000..213184ed7f657 --- /dev/null +++ b/modules/mph/mph.go @@ -0,0 +1,139 @@ +// 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 mph + +import ( + "sort" + + "github.com/spaolacci/murmur3" +) + +// A ConstructedHashFunction holds information about the built perfect hash function. +type ConstructedHashFunction struct { + levelZeroBuckets []uint32 + levelZeroMask int + levelOneEntries []uint32 + levelOneMask int +} + +// Build builds a perfect hash function from keys using the "Hash, displace, and compress". +// Given the use-case of this +// Ref: http://cmph.sourceforge.net/papers/esa09.pdf. +func Build(keys []string) *ConstructedHashFunction { + // These values are not described in the paper as the paper allows any universal hash + // function(with certain bounds) in which case we use murmur3 and mask to avoid "overflow". + + // Construct values for the first level of hash function. + // This is used to map the strings to `nextPow2(len(keys)/4)` amount of buckets. + levelZeroBuckets := make([]uint32, nextPow2(len(keys)/4)) + levelZeroMask := len(levelZeroBuckets) - 1 + // Construct values for the second level of hash functions. + // This is used for the hash function to find the index within a specific bucket. + levelOneEntries := make([]uint32, nextPow2(len(keys))) + levelOneMask := len(levelOneEntries) - 1 + + // Create temporary buckets. + tempBuckets := make([][]int, len(levelZeroBuckets)) + + // Construct a simple perfect hash function. Not every bucket would be used, + // which is fine as we will fix that when we transform it into a compressed form + // and thereby making it a minimal perfect hash function. + for i, s := range keys { + n := int(murmur3.Sum32([]byte(s))) & levelZeroMask + tempBuckets[n] = append(tempBuckets[n], i) + } + + // Covert it to indexBuckets which can be sorted on the bucket's amount of values. + // We also hereby filter out the empty bucket. + var buckets indexBuckets + for n, vals := range tempBuckets { + if len(vals) > 0 { + buckets = append(buckets, indexBucket{n, vals}) + } + } + + // Sort the buckets by their size in descending order. + sort.Sort(buckets) + + // Now go trough each bucket and kind of brute force your way into making a perfect hash again. + // We want to find the displace value here which is a seed value which makes the hash function within + // the bucker a perfect hash function(no collision between the keys). + + // Store for each entry within a bucket if a key has hashed to that entry. + tempEvaluatedIdx := make([]int, len(levelOneEntries)) + + // Store all hashed indexes, so it's easier to clean the variable up. + var evaluatedIndexs []int + for _, bucket := range buckets { + // Always start from zero and work your way up their. + seed := uint32(0) + trySeed: + // Reset temporary hashed indexes. + evaluatedIndexs = evaluatedIndexs[:0] + for _, i := range bucket.values { + // Create the hash for this value. + n := int(murmur3.Sum32WithSeed([]byte(keys[i]), seed)) & levelOneMask + // Check if this hash is a collision. + if tempEvaluatedIdx[n] != 0 { + // A collision, reset everything and try a new seed. + for _, n := range evaluatedIndexs { + tempEvaluatedIdx[n] = 0 + } + seed++ + goto trySeed + } + // Mark this index has being used. + tempEvaluatedIdx[n] = i + + // Add the index to the evaluated indexes. + evaluatedIndexs = append(evaluatedIndexs, n) + } + for value, idx := range tempEvaluatedIdx { + levelOneEntries[value] = uint32(idx) + } + + // No collisions detected, save this seed for this bucket. + levelZeroBuckets[int(bucket.originialIdx)] = uint32(seed) + } + + // Return the table. + return &ConstructedHashFunction{ + levelZeroBuckets: levelZeroBuckets, + levelZeroMask: levelZeroMask, + levelOneEntries: levelOneEntries, + levelOneMask: levelOneMask, + } +} + +// General purpose fast method of finding the next power of two. +// Unless Go decides to expose more specialized bit methods to find the +// first one in a number, this is as best we get without hacking around. +func nextPow2(n int) int { + for i := 1; ; i <<= 1 { + if i >= n { + return i + } + } +} + +// Get searches for s in t and returns its index. +func (chf *ConstructedHashFunction) Get(s string) uint32 { + // Find the bucket the key is stored in. + bucketIdx := int(murmur3.Sum32([]byte(s))) & chf.levelZeroMask + seed := chf.levelZeroBuckets[bucketIdx] + // Get the index within the bucket. + idx := int(murmur3.Sum32WithSeed([]byte(s), seed)) & chf.levelOneMask + return chf.levelOneEntries[idx] +} + +type indexBucket struct { + originialIdx int + values []int +} + +type indexBuckets []indexBucket + +func (s indexBuckets) Len() int { return len(s) } +func (s indexBuckets) Less(a, z int) bool { return len(s[a].values) > len(s[z].values) } +func (s indexBuckets) Swap(a, z int) { s[a], s[z] = s[z], s[a] } diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index bf811dd1c381f..6b456451a09d1 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/mph" "code.gitea.io/gitea/modules/setting" "gopkg.in/ini.v1" @@ -30,14 +31,17 @@ type locale struct { type LocaleStore struct { // After initializing has finished, these fields are read-only. - langNames []string - langDescs []string - langOffsets []int - translationKeys []string - translationValues []string - localeMap map[string]*locale - defaultLang string - defaultLangKeysLen int + langNames []string + langDescs []string + langOffsets []int + // Hashed values of the keys. Used for the construction of the mph. + translationKeysHashed []string + translationKeys []string + translationValues []string + hashFunction *mph.ConstructedHashFunction + localeMap map[string]*locale + defaultLang string + defaultLangKeysLen int } func NewLocaleStore() *LocaleStore { @@ -69,13 +73,14 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte // Store all key, value into two slices. for _, section := range iniFile.Sections() { for _, key := range section.Keys() { - key := strings.TrimPrefix(section.Name()+"."+key.Name(). "DEFAULT.") - - ls.translationKeys = append(ls.translationKeys, key) + ls.translationKeys = append(ls.translationKeys, section.Name()+"#"+key.Name()) + ls.translationKeysHashed = append(ls.translationKeysHashed, strings.TrimPrefix(section.Name()+"."+key.Name(), "DEFAULT.")) ls.translationValues = append(ls.translationValues, key.Value()) } } + ls.defaultLangKeysLen = len(ls.translationKeys) + ls.hashFunction = mph.Build(ls.translationKeysHashed) } 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. @@ -101,7 +106,7 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte iniFile = nil // Specify the offset for translationValues. - ls.langOffsets = append(ls.langOffsets, len(ls.langOffsets)) + ls.langOffsets = append(ls.langOffsets, len(ls.langOffsets)+1) } else { // Add the language to the localeMap. iniFile.BlockMode = false @@ -194,3 +199,33 @@ func ResetDefaultLocales() { func Tr(lang, trKey string, trArgs ...interface{}) string { return DefaultLocales.Tr(lang, trKey, trArgs...) } + +func TrOffset(offset int, trKey string, trArgs ...interface{}) string { + idx := DefaultLocales.hashFunction.Get(trKey) * uint32(offset) + trMsg := DefaultLocales.translationValues[idx] + + 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 +} diff --git a/modules/translation/translation.go b/modules/translation/translation.go index 05ae7b59fc4d7..854c52c1ec673 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -26,6 +26,7 @@ type Locale interface { // LangType represents a lang type type LangType struct { Lang, Name string // these fields are used directly in templates: {{range .AllLangs}}{{.Lang}}{{.Name}}{{end}} + Offset int } var ( @@ -90,11 +91,11 @@ func InitLocales() { } } - langs, descs := i18n.DefaultLocales.ListLangNameDesc() + langs, descs, offsets := i18n.DefaultLocales.ListLangNameDescOffsets() allLangs = make([]*LangType, 0, len(langs)) allLangMap = map[string]*LangType{} for i, v := range langs { - l := &LangType{v, descs[i]} + l := &LangType{v, descs[i], offsets[i]} allLangs = append(allLangs, l) allLangMap[v] = l } @@ -114,17 +115,21 @@ func Match(tags ...language.Tag) language.Tag { // locale represents the information of localization. type locale struct { Lang, LangName string // these fields are used directly in templates: .i18n.Lang + Offset int } // NewLocale return a locale func NewLocale(lang string) Locale { langName := "unknown" + offset := 0 if l, ok := allLangMap[lang]; ok { langName = l.Name + offset = l.Offset } return &locale{ Lang: lang, LangName: langName, + Offset: offset, } } @@ -135,7 +140,7 @@ func (l *locale) Language() string { // Tr translates content to target language. func (l *locale) Tr(format string, args ...interface{}) string { if setting.IsProd { - return i18n.Tr(l.Lang, format, args...) + return i18n.TrOffset(l.Offset, format, args...) } // in development, we should show an error if a translation key is missing From 12605ecf644903ee4228037a06942aa97b7d3bdd Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 23 Jun 2022 02:55:18 +0200 Subject: [PATCH 05/27] Twiddle around --- modules/mph/mph.go | 14 ++++----- modules/mph/mph_test.go | 63 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 modules/mph/mph_test.go diff --git a/modules/mph/mph.go b/modules/mph/mph.go index 213184ed7f657..63c85e181c3b8 100644 --- a/modules/mph/mph.go +++ b/modules/mph/mph.go @@ -61,7 +61,7 @@ func Build(keys []string) *ConstructedHashFunction { // the bucker a perfect hash function(no collision between the keys). // Store for each entry within a bucket if a key has hashed to that entry. - tempEvaluatedIdx := make([]int, len(levelOneEntries)) + tempEvaluatedIdx := make([]bool, len(levelOneEntries)) // Store all hashed indexes, so it's easier to clean the variable up. var evaluatedIndexs []int @@ -75,22 +75,22 @@ func Build(keys []string) *ConstructedHashFunction { // Create the hash for this value. n := int(murmur3.Sum32WithSeed([]byte(keys[i]), seed)) & levelOneMask // Check if this hash is a collision. - if tempEvaluatedIdx[n] != 0 { + if tempEvaluatedIdx[n] { // A collision, reset everything and try a new seed. for _, n := range evaluatedIndexs { - tempEvaluatedIdx[n] = 0 + tempEvaluatedIdx[n] = false } seed++ goto trySeed } // Mark this index has being used. - tempEvaluatedIdx[n] = i + tempEvaluatedIdx[n] = true // Add the index to the evaluated indexes. evaluatedIndexs = append(evaluatedIndexs, n) - } - for value, idx := range tempEvaluatedIdx { - levelOneEntries[value] = uint32(idx) + + // This somehow doesn't cause conflicts. So we just leave it here. + levelOneEntries[n] = uint32(i) } // No collisions detected, save this seed for this bucket. diff --git a/modules/mph/mph_test.go b/modules/mph/mph_test.go new file mode 100644 index 0000000000000..77056d1cef04f --- /dev/null +++ b/modules/mph/mph_test.go @@ -0,0 +1,63 @@ +// 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 mph + +import ( + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +func BenchmarkConstruction(b *testing.B) { + // Make around 5k keys. + keys := make([]string, 5_000) + buffer := make([]byte, 4) + for i := uint32(0); i < 5_000; i++ { + binary.LittleEndian.PutUint32(buffer, i) + keys[i] = string(buffer) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Build(keys) + } +} + +func TestPerfectHashFunction_Random(t *testing.T) { + // Make around 5k keys. + keys := make([]string, 5_000) + buffer := make([]byte, 4) + for i := uint32(0); i < 5_000; i++ { + binary.LittleEndian.PutUint32(buffer, i) + keys[i] = string(buffer) + } + + // Build the perfect hash. + phf := Build(keys) + + buffer = make([]byte, 4) + + // Check if the given indexes correspond to the correct key. + for i := uint32(0); i < 5_000; i++ { + binary.LittleEndian.PutUint32(buffer, i) + assert.Equal(t, i, phf.Get(string(buffer))) + } +} + +var uniqueStrings = []string{ + "Hello, world", "FOSS", "Gitea", "gi21t", "gamining", "emacs", "slices", + "pointers", "bit twidling", "馃榾", "e ee ", "lolz", "A quite long sentence!", +} + +func TestPerfectHashFunction_Static(t *testing.T) { + // Build the perfect hash. + phf := Build(uniqueStrings) + + // Check if the given indexes correspond to the correct key. + for i := 0; i < len(uniqueStrings); i++ { + assert.Equal(t, uint32(i), phf.Get(uniqueStrings[i])) + } +} From b25d0d5f60a4b49f8ceeba215347c3334910b64b Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 23 Jun 2022 03:02:03 +0200 Subject: [PATCH 06/27] Twiddle around comments --- modules/mph/mph.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/mph/mph.go b/modules/mph/mph.go index 63c85e181c3b8..ab5bb64c0c2b9 100644 --- a/modules/mph/mph.go +++ b/modules/mph/mph.go @@ -22,11 +22,13 @@ type ConstructedHashFunction struct { // Ref: http://cmph.sourceforge.net/papers/esa09.pdf. func Build(keys []string) *ConstructedHashFunction { // These values are not described in the paper as the paper allows any universal hash - // function(with certain bounds) in which case we use murmur3 and mask to avoid "overflow". + // function(with certain bounds) in which case we use murmur3 and mask to avoid "overflow", + // this could be replaced by module but this is a more performant easy "hack" to do this. // Construct values for the first level of hash function. - // This is used to map the strings to `nextPow2(len(keys)/4)` amount of buckets. - levelZeroBuckets := make([]uint32, nextPow2(len(keys)/4)) + // This is used to map the strings to `nextPow2(len(keys)/3)` amount of buckets. + // This seems to be across benchmarks one of faster values for the use-case. + levelZeroBuckets := make([]uint32, nextPow2(len(keys)/3)) levelZeroMask := len(levelZeroBuckets) - 1 // Construct values for the second level of hash functions. // This is used for the hash function to find the index within a specific bucket. From f70b31a2c670bac0d66b1cc4f4be74e86fc38d13 Mon Sep 17 00:00:00 2001 From: Gusted Date: Thu, 23 Jun 2022 03:33:50 +0200 Subject: [PATCH 07/27] Convert templates --- modules/templates/helper.go | 2 - modules/timeutil/since.go | 70 +++++++++---------- modules/timeutil/since_test.go | 8 +-- routers/web/repo/blame.go | 2 +- routers/web/repo/issue_content_history.go | 3 +- services/cron/cron.go | 3 +- services/cron/setting.go | 12 ++-- services/cron/tasks.go | 6 +- templates/admin/cron.tmpl | 2 +- templates/admin/process-row.tmpl | 2 +- templates/admin/stacktrace-row.tmpl | 2 +- templates/explore/repo_list.tmpl | 2 +- templates/package/shared/list.tmpl | 2 +- templates/package/shared/versionlist.tmpl | 2 +- templates/package/view.tmpl | 2 +- templates/repo/activity.tmpl | 12 ++-- templates/repo/branch/list.tmpl | 6 +- templates/repo/commit_page.tmpl | 4 +- templates/repo/commits_list.tmpl | 4 +- templates/repo/diff/comments.tmpl | 2 +- templates/repo/issue/milestone_issues.tmpl | 2 +- templates/repo/issue/milestones.tmpl | 2 +- templates/repo/issue/view_content.tmpl | 2 +- .../repo/issue/view_content/comments.tmpl | 6 +- templates/repo/issue/view_content/pull.tmpl | 6 +- templates/repo/issue/view_title.tmpl | 4 +- templates/repo/projects/list.tmpl | 2 +- templates/repo/projects/view.tmpl | 2 +- templates/repo/release/list.tmpl | 4 +- templates/repo/settings/lfs.tmpl | 2 +- templates/repo/settings/lfs_file_find.tmpl | 2 +- templates/repo/settings/lfs_locks.tmpl | 2 +- templates/repo/view_list.tmpl | 4 +- templates/repo/wiki/pages.tmpl | 2 +- templates/repo/wiki/revision.tmpl | 2 +- templates/repo/wiki/view.tmpl | 2 +- templates/shared/issuelist.tmpl | 2 +- templates/shared/searchbottom.tmpl | 2 +- templates/user/dashboard/feeds.tmpl | 2 +- templates/user/dashboard/milestones.tmpl | 2 +- .../user/settings/security/webauthn.tmpl | 2 +- 41 files changed, 99 insertions(+), 105 deletions(-) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 99b1979964ee0..676fc529c8020 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -105,7 +105,6 @@ func NewFuncMap() []template.FuncMap { "Str2html": Str2html, "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, - "RawTimeSince": timeutil.RawTimeSince, "FileSize": base.FileSize, "PrettyNumber": base.PrettyNumber, "JsPrettyNumber": JsPrettyNumber, @@ -484,7 +483,6 @@ func NewTextFuncMap() []texttmpl.FuncMap { }, "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, - "RawTimeSince": timeutil.RawTimeSince, "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC1123Z) }, diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index 38b12829ad463..d5ee7d881da23 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation/i18n" ) @@ -87,88 +88,88 @@ func computeTimeDiffFloor(diff int64, lang string) (int64, string) { return diff, diffStr } -func computeTimeDiff(diff int64, lang string) (int64, string) { +func computeTimeDiff(diff int64, lang translation.Locale) (int64, string) { diffStr := "" switch { case diff <= 0: diff = 0 - diffStr = i18n.Tr(lang, "tool.now") + diffStr = lang.Tr("tool.now") case diff < 2: diff = 0 - diffStr = i18n.Tr(lang, "tool.1s") + diffStr = lang.Tr("tool.1s") case diff < 1*Minute: - diffStr = i18n.Tr(lang, "tool.seconds", diff) + diffStr = lang.Tr("tool.seconds", diff) diff = 0 case diff < Minute+Minute/2: diff -= 1 * Minute - diffStr = i18n.Tr(lang, "tool.1m") + diffStr = lang.Tr("tool.1m") case diff < 1*Hour: minutes := round(float64(diff) / Minute) if minutes > 1 { - diffStr = i18n.Tr(lang, "tool.minutes", minutes) + diffStr = lang.Tr("tool.minutes", minutes) } else { - diffStr = i18n.Tr(lang, "tool.1m") + diffStr = lang.Tr("tool.1m") } diff -= diff / Minute * Minute case diff < Hour+Hour/2: diff -= 1 * Hour - diffStr = i18n.Tr(lang, "tool.1h") + diffStr = lang.Tr("tool.1h") case diff < 1*Day: hours := round(float64(diff) / Hour) if hours > 1 { - diffStr = i18n.Tr(lang, "tool.hours", hours) + diffStr = lang.Tr("tool.hours", hours) } else { - diffStr = i18n.Tr(lang, "tool.1h") + diffStr = lang.Tr("tool.1h") } diff -= diff / Hour * Hour case diff < Day+Day/2: diff -= 1 * Day - diffStr = i18n.Tr(lang, "tool.1d") + diffStr = lang.Tr("tool.1d") case diff < 1*Week: days := round(float64(diff) / Day) if days > 1 { - diffStr = i18n.Tr(lang, "tool.days", days) + diffStr = lang.Tr("tool.days", days) } else { - diffStr = i18n.Tr(lang, "tool.1d") + diffStr = lang.Tr("tool.1d") } diff -= diff / Day * Day case diff < Week+Week/2: diff -= 1 * Week - diffStr = i18n.Tr(lang, "tool.1w") + diffStr = lang.Tr("tool.1w") case diff < 1*Month: weeks := round(float64(diff) / Week) if weeks > 1 { - diffStr = i18n.Tr(lang, "tool.weeks", weeks) + diffStr = lang.Tr("tool.weeks", weeks) } else { - diffStr = i18n.Tr(lang, "tool.1w") + diffStr = lang.Tr("tool.1w") } diff -= diff / Week * Week case diff < 1*Month+Month/2: diff -= 1 * Month - diffStr = i18n.Tr(lang, "tool.1mon") + diffStr = lang.Tr("tool.1mon") case diff < 1*Year: months := round(float64(diff) / Month) if months > 1 { - diffStr = i18n.Tr(lang, "tool.months", months) + diffStr = lang.Tr("tool.months", months) } else { - diffStr = i18n.Tr(lang, "tool.1mon") + diffStr = lang.Tr("tool.1mon") } diff -= diff / Month * Month case diff < Year+Year/2: diff -= 1 * Year - diffStr = i18n.Tr(lang, "tool.1y") + diffStr = lang.Tr("tool.1y") default: years := round(float64(diff) / Year) if years > 1 { - diffStr = i18n.Tr(lang, "tool.years", years) + diffStr = lang.Tr("tool.years", years) } else { - diffStr = i18n.Tr(lang, "tool.1y") + diffStr = lang.Tr("tool.1y") } diff -= (diff / Year) * Year } @@ -209,11 +210,11 @@ func timeSincePro(then, now time.Time, lang string) string { return strings.TrimPrefix(timeStr, ", ") } -func timeSince(then, now time.Time, lang string) string { +func timeSince(then, now time.Time, lang translation.Locale) string { return timeSinceUnix(then.Unix(), now.Unix(), lang) } -func timeSinceUnix(then, now int64, lang string) string { +func timeSinceUnix(then, now int64, lang translation.Locale) string { lbl := "tool.ago" diff := now - then if then > now { @@ -221,36 +222,31 @@ func timeSinceUnix(then, now int64, lang string) string { diff = then - now } if diff <= 0 { - return i18n.Tr(lang, "tool.now") + return lang.Tr("tool.now") } _, diffStr := computeTimeDiff(diff, lang) - return i18n.Tr(lang, lbl, diffStr) -} - -// RawTimeSince retrieves i18n key of time since t -func RawTimeSince(t time.Time, lang string) string { - return timeSince(t, time.Now(), lang) + return lang.Tr(lbl, diffStr) } // TimeSince calculates the time interval and generate user-friendly string. -func TimeSince(then time.Time, lang string) template.HTML { +func TimeSince(then time.Time, lang translation.Locale) template.HTML { return htmlTimeSince(then, time.Now(), lang) } -func htmlTimeSince(then, now time.Time, lang string) template.HTML { +func htmlTimeSince(then, now time.Time, lang translation.Locale) template.HTML { return template.HTML(fmt.Sprintf(`%s`, - then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang)), + then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang.Language())), timeSince(then, now, lang))) } // TimeSinceUnix calculates the time interval and generate user-friendly string. -func TimeSinceUnix(then TimeStamp, lang string) template.HTML { +func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML { return htmlTimeSinceUnix(then, TimeStamp(time.Now().Unix()), lang) } -func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { +func htmlTimeSinceUnix(then, now TimeStamp, lang translation.Locale) template.HTML { return template.HTML(fmt.Sprintf(`%s`, - then.FormatInLocation(GetTimeFormat(lang), setting.DefaultUILocation), + then.FormatInLocation(GetTimeFormat(lang.Language()), setting.DefaultUILocation), timeSinceUnix(int64(then), int64(now), lang))) } diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go index 49951b6e4100b..aabb034a8a488 100644 --- a/modules/timeutil/since_test.go +++ b/modules/timeutil/since_test.go @@ -42,15 +42,15 @@ func TestMain(m *testing.M) { } func TestTimeSince(t *testing.T) { - assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en")) + assert.Equal(t, "now", timeSince(BaseDate, BaseDate, translation.NewLocale("en-US"))) // test that each diff in `diffs` yields the expected string test := func(expected string, diffs ...time.Duration) { t.Run(expected, func(t *testing.T) { for _, diff := range diffs { - actual := timeSince(BaseDate, BaseDate.Add(diff), "en") + actual := timeSince(BaseDate, BaseDate.Add(diff), translation.NewLocale("en-US")) assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual) - actual = timeSince(BaseDate.Add(diff), BaseDate, "en") + actual = timeSince(BaseDate.Add(diff), BaseDate, translation.NewLocale("en-US")) assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual) } }) @@ -138,7 +138,7 @@ func TestComputeTimeDiff(t *testing.T) { test := func(base int64, str string, offsets ...int64) { for _, offset := range offsets { t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) { - diff, diffStr := computeTimeDiff(base+offset, "en") + diff, diffStr := computeTimeDiff(base+offset, translation.NewLocale("en")) assert.Equal(t, offset, diff) assert.Equal(t, str, diffStr) }) diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index e96e2142d295a..06c43aec19fb6 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -255,7 +255,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m commitCnt++ // User avatar image - commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale.Language()) + commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale) var avatar string if commit.User != nil { diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index d8a21c7fd7b44..c09e81eb7b3dd 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -55,7 +55,6 @@ func GetContentHistoryList(ctx *context.Context) { // render history list to HTML for frontend dropdown items: (name, value) // name is HTML of "avatar + userName + userAction + timeSince" // value is historyId - lang := ctx.Locale.Language() var results []map[string]interface{} for _, item := range items { var actionText string @@ -67,7 +66,7 @@ func GetContentHistoryList(ctx *context.Context) { } else { actionText = ctx.Locale.Tr("repo.issues.content_history.edited") } - timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, lang) + timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale) username := item.UserName if setting.UI.DefaultShowFullName && strings.TrimSpace(item.UserFullName) != "" { diff --git a/services/cron/cron.go b/services/cron/cron.go index ebbcd75b6df63..8e19e04416d03 100644 --- a/services/cron/cron.go +++ b/services/cron/cron.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/translation" "github.com/gogs/cron" ) @@ -63,7 +64,7 @@ type TaskTableRow struct { task *Task } -func (t *TaskTableRow) FormatLastMessage(locale string) string { +func (t *TaskTableRow) FormatLastMessage(locale translation.Locale) string { if t.Status == "finished" { return t.task.GetConfig().FormatMessage(locale, t.Name, t.Status, t.LastDoer) } diff --git a/services/cron/setting.go b/services/cron/setting.go index 9b59a562f709a..eb13242e94114 100644 --- a/services/cron/setting.go +++ b/services/cron/setting.go @@ -7,7 +7,7 @@ package cron import ( "time" - "code.gitea.io/gitea/modules/translation/i18n" + "code.gitea.io/gitea/modules/translation" ) // Config represents a basic configuration interface that cron task @@ -15,7 +15,7 @@ type Config interface { IsEnabled() bool DoRunAtStart() bool GetSchedule() string - FormatMessage(locale, name, status, doer string, args ...interface{}) string + FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string DoNoticeOnSuccess() bool } @@ -69,9 +69,9 @@ func (b *BaseConfig) DoNoticeOnSuccess() bool { // FormatMessage returns a message for the task // Please note the `status` string will be concatenated with `admin.dashboard.cron.` and `admin.dashboard.task.` to provide locale messages. Similarly `name` will be composed with `admin.dashboard.` to provide the locale name for the task. -func (b *BaseConfig) FormatMessage(locale, name, status, doer string, args ...interface{}) string { +func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string { realArgs := make([]interface{}, 0, len(args)+2) - realArgs = append(realArgs, i18n.Tr(locale, "admin.dashboard."+name)) + realArgs = append(realArgs, locale.Tr("admin.dashboard."+name)) if doer == "" { realArgs = append(realArgs, "(Cron)") } else { @@ -81,7 +81,7 @@ func (b *BaseConfig) FormatMessage(locale, name, status, doer string, args ...in realArgs = append(realArgs, args...) } if doer == "" { - return i18n.Tr(locale, "admin.dashboard.cron."+status, realArgs...) + return locale.Tr("admin.dashboard.cron."+status, realArgs...) } - return i18n.Tr(locale, "admin.dashboard.task."+status, realArgs...) + return locale.Tr("admin.dashboard.task."+status, realArgs...) } diff --git a/services/cron/tasks.go b/services/cron/tasks.go index 2252ad21e2549..1b73db66020d9 100644 --- a/services/cron/tasks.go +++ b/services/cron/tasks.go @@ -94,7 +94,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) { doerName = doer.Name } - ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage("en-US", t.Name, "process", doerName)) + ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "process", doerName)) defer finished() if err := t.fun(ctx, doer, config); err != nil { @@ -114,7 +114,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) { t.LastDoer = doerName t.lock.Unlock() - if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "cancelled", doerName, message)); err != nil { + if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "cancelled", doerName, message)); err != nil { log.Error("CreateNotice: %v", err) } return @@ -127,7 +127,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) { t.lock.Unlock() if config.DoNoticeOnSuccess() { - if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "finished", doerName)); err != nil { + if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "finished", doerName)); err != nil { log.Error("CreateNotice: %v", err) } } diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl index a73813ef88045..cf3532c77d3f8 100644 --- a/templates/admin/cron.tmpl +++ b/templates/admin/cron.tmpl @@ -24,7 +24,7 @@ {{DateFmtLong .Next}} {{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}} {{.ExecTimes}} - {{if eq .Status "" }}鈥攞{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}} + {{if eq .Status "" }}鈥攞{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}} {{end}} diff --git a/templates/admin/process-row.tmpl b/templates/admin/process-row.tmpl index 2191677a5cee7..d072a649f25b0 100644 --- a/templates/admin/process-row.tmpl +++ b/templates/admin/process-row.tmpl @@ -3,7 +3,7 @@
{{if eq .Process.Type "request"}}{{svg "octicon-globe" 16 }}{{else if eq .Process.Type "system"}}{{svg "octicon-cpu" 16 }}{{else}}{{svg "octicon-terminal" 16 }}{{end}}
{{.Process.Description}}
-
{{TimeSince .Process.Start .root.i18n.Lang}}
+
{{TimeSince .Process.Start .root.i18n}}
{{if ne .Process.Type "system"}} diff --git a/templates/admin/stacktrace-row.tmpl b/templates/admin/stacktrace-row.tmpl index a21ef72d6327f..af7ea36230d02 100644 --- a/templates/admin/stacktrace-row.tmpl +++ b/templates/admin/stacktrace-row.tmpl @@ -13,7 +13,7 @@
{{.Process.Description}}
-
{{if ne .Process.Type "none"}}{{TimeSince .Process.Start .root.i18n.Lang}}{{end}}
+
{{if ne .Process.Type "none"}}{{TimeSince .Process.Start .root.i18n}}{{end}}
{{if or (eq .Process.Type "request") (eq .Process.Type "normal") }} diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 1bc831d9df311..c0e371b8a0bbb 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -60,7 +60,7 @@ {{end}}
{{end}} -

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

+

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n}}

{{else}} diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index ce2b57e4a0d50..eb2beac9e7078 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -29,7 +29,7 @@ {{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}
- {{$timeStr := TimeSinceUnix .Version.CreatedUnix $.i18n.Lang}} + {{$timeStr := TimeSinceUnix .Version.CreatedUnix $.i18n}} {{$hasRepositoryAccess := false}} {{if .Repository}} {{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}} diff --git a/templates/package/shared/versionlist.tmpl b/templates/package/shared/versionlist.tmpl index 59f7cd1647a96..3d9ae5ef44403 100644 --- a/templates/package/shared/versionlist.tmpl +++ b/templates/package/shared/versionlist.tmpl @@ -21,7 +21,7 @@ {{.Version.LowerVersion}}
- {{$.i18n.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix $.i18n.Lang) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}} + {{$.i18n.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix $.i18n) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}}
diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index efad9f9b8fe9a..9a3157c284d21 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -9,7 +9,7 @@

{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})

- {{$timeStr := TimeSinceUnix .PackageDescriptor.Version.CreatedUnix $.i18n.Lang}} + {{$timeStr := TimeSinceUnix .PackageDescriptor.Version.CreatedUnix $.i18n}} {{if .HasRepositoryAccess}} {{.i18n.Tr "packages.published_by_in" $timeStr .PackageDescriptor.Creator.HomeLink (.PackageDescriptor.Creator.GetDisplayName | Escape) .PackageDescriptor.Repository.HTMLURL (.PackageDescriptor.Repository.FullName | Escape) | Safe}} {{else}} diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl index 36108dddcb789..c6fca86774df1 100644 --- a/templates/repo/activity.tmpl +++ b/templates/repo/activity.tmpl @@ -131,7 +131,7 @@ {{if not .IsTag}} {{.Title | RenderEmoji}} {{end}} - {{TimeSinceUnix .CreatedUnix $.i18n.Lang}} + {{TimeSinceUnix .CreatedUnix $.i18n}}

{{end}}
@@ -150,7 +150,7 @@

{{$.i18n.Tr "repo.activity.merged_prs_label"}} #{{.Index}} {{.Issue.Title | RenderEmoji}} - {{TimeSinceUnix .MergedUnix $.i18n.Lang}} + {{TimeSinceUnix .MergedUnix $.i18n}}

{{end}} @@ -169,7 +169,7 @@

{{$.i18n.Tr "repo.activity.opened_prs_label"}} #{{.Index}} {{.Issue.Title | RenderEmoji}} - {{TimeSinceUnix .Issue.CreatedUnix $.i18n.Lang}} + {{TimeSinceUnix .Issue.CreatedUnix $.i18n}}

{{end}} @@ -188,7 +188,7 @@

{{$.i18n.Tr "repo.activity.closed_issue_label"}} #{{.Index}} {{.Title | RenderEmoji}} - {{TimeSinceUnix .ClosedUnix $.i18n.Lang}} + {{TimeSinceUnix .ClosedUnix $.i18n}}

{{end}} @@ -207,7 +207,7 @@

{{$.i18n.Tr "repo.activity.new_issue_label"}} #{{.Index}} {{.Title | RenderEmoji}} - {{TimeSinceUnix .CreatedUnix $.i18n.Lang}} + {{TimeSinceUnix .CreatedUnix $.i18n}}

{{end}} @@ -231,7 +231,7 @@ {{else}} {{.Title | RenderEmoji}} {{end}} - {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}} + {{TimeSinceUnix .UpdatedUnix $.i18n}}

{{end}} diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 6c34ba4c19398..d36a69e5be98a 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -18,7 +18,7 @@ {{svg "octicon-shield-lock"}} {{end}} {{.DefaultBranch}} -

{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .DefaultBranchBranch.Commit.ID.String}}{{RenderCommitMessage $.Context .DefaultBranchBranch.Commit.CommitMessage .RepoLink .Repository.ComposeMetas}} 路 {{.i18n.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.Commit.Committer.When .i18n.Lang}}

+

{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .DefaultBranchBranch.Commit.ID.String}}{{RenderCommitMessage $.Context .DefaultBranchBranch.Commit.CommitMessage .RepoLink .Repository.ComposeMetas}} 路 {{.i18n.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.Commit.Committer.When .i18n}}

{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} @@ -53,13 +53,13 @@ {{if .IsDeleted}} {{.Name}} -

{{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.i18n.Lang}}

+

{{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.i18n}}

{{else}} {{if .IsProtected}} {{svg "octicon-shield-lock"}} {{end}} {{.Name}} -

{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .Commit.ID.String}}{{RenderCommitMessage $.Context .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}} 路 {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}

+

{{svg "octicon-git-commit" 16 "mr-2"}}{{ShortSha .Commit.ID.String}}{{RenderCommitMessage $.Context .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}} 路 {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n}}

{{end}} diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 1e6296836338f..96190e3abf235 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -156,7 +156,7 @@ {{avatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "mr-3"}} {{.Commit.Author.Name}} {{end}} - {{TimeSince .Commit.Author.When $.i18n.Lang}} + {{TimeSince .Commit.Author.When $.i18n}} {{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}} {{.i18n.Tr "repo.diff.committed_by"}} {{if ne .Verification.CommittingUser.ID 0}} @@ -277,7 +277,7 @@ {{else}} {{.NoteCommit.Author.Name}} {{end}} - {{TimeSince .NoteCommit.Author.When $.i18n.Lang}} + {{TimeSince .NoteCommit.Author.When $.i18n}}
{{RenderNote $.Context .Note $.RepoLink $.Repository.ComposeMetas}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 65baee6b63478..d94d313071238 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -76,9 +76,9 @@ {{end}} {{if .Committer}} - {{TimeSince .Committer.When $.i18n.Lang}} + {{TimeSince .Committer.When $.i18n}} {{else}} - {{TimeSince .Author.When $.i18n.Lang}} + {{TimeSince .Author.When $.i18n}} {{end}} {{end}} diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl index 6a581ba04a072..912c9d72ff848 100644 --- a/templates/repo/diff/comments.tmpl +++ b/templates/repo/diff/comments.tmpl @@ -1,6 +1,6 @@ {{range .comments}} -{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.i18n.Lang }} +{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.i18n }}
{{if .OriginalAuthor }} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index cb2779db32411..ca08b44fcacee 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -22,7 +22,7 @@
- {{ $closedDate:= TimeSinceUnix .Milestone.ClosedDateUnix $.i18n.Lang }} + {{ $closedDate:= TimeSinceUnix .Milestone.ClosedDateUnix $.i18n }} {{if .IsClosed}} {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} {{else}} diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 235044cb17bf2..6185ff3703d28 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -71,7 +71,7 @@
- {{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.i18n.Lang }} + {{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.i18n }} {{if .IsClosed}} {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} {{else}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 52353d46d9945..a1a53c80f21f3 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -15,7 +15,7 @@ - {{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.i18n.Lang }} + {{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.i18n }}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 0258a9f9691c0..2dbc34e029057 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -1,7 +1,7 @@ {{ template "base/alert" }} {{range .Issue.Comments}} {{if call $.ShouldShowCommentType .Type}} - {{ $createdStr:= TimeSinceUnix .CreatedUnix $.i18n.Lang }} + {{ $createdStr:= TimeSinceUnix .CreatedUnix $.i18n }}