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}}
-
{{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 @@
-
{{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 }}
- {{ $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 @@