mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-20 14:31:19 +02:00
Merge remote-tracking branch 'upstream/main' into limit-repo-size
This commit is contained in:
@@ -132,18 +132,10 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
|
||||
},
|
||||
}
|
||||
|
||||
type nullLocale struct{}
|
||||
|
||||
func (nullLocale) Language() string { return "" }
|
||||
func (nullLocale) Tr(key string, _ ...interface{}) string { return key }
|
||||
func (nullLocale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { return "" }
|
||||
|
||||
var _ (translation.Locale) = nullLocale{}
|
||||
|
||||
func TestEscapeControlString(t *testing.T) {
|
||||
for _, tt := range escapeControlTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, result := EscapeControlString(tt.text, nullLocale{})
|
||||
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
|
||||
if !reflect.DeepEqual(*status, tt.status) {
|
||||
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
|
||||
}
|
||||
@@ -179,7 +171,7 @@ func TestEscapeControlReader(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := strings.NewReader(tt.text)
|
||||
output := &strings.Builder{}
|
||||
status, err := EscapeControlReader(input, output, nullLocale{})
|
||||
status, err := EscapeControlReader(input, output, &translation.MockLocale{})
|
||||
result := output.String()
|
||||
if err != nil {
|
||||
t.Errorf("EscapeControlReader(): err = %v", err)
|
||||
@@ -201,5 +193,5 @@ func TestEscapeControlReader_panic(t *testing.T) {
|
||||
for i := 0; i < 6826; i++ {
|
||||
bs = append(bs, []byte("—")...)
|
||||
}
|
||||
_, _ = EscapeControlString(string(bs), nullLocale{})
|
||||
_, _ = EscapeControlString(string(bs), &translation.MockLocale{})
|
||||
}
|
||||
|
||||
+2
-15
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -550,20 +551,6 @@ a|"he said, ""here I am"""`,
|
||||
}
|
||||
}
|
||||
|
||||
type mockLocale struct{}
|
||||
|
||||
func (l mockLocale) Language() string {
|
||||
return "en"
|
||||
}
|
||||
|
||||
func (l mockLocale) Tr(s string, _ ...interface{}) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
|
||||
return key1
|
||||
}
|
||||
|
||||
func TestFormatError(t *testing.T) {
|
||||
cases := []struct {
|
||||
err error
|
||||
@@ -591,7 +578,7 @@ func TestFormatError(t *testing.T) {
|
||||
}
|
||||
|
||||
for n, c := range cases {
|
||||
message, err := FormatError(c.err, mockLocale{})
|
||||
message, err := FormatError(c.err, &translation.MockLocale{})
|
||||
if c.expectsError {
|
||||
assert.Error(t, err, "case %d: expected an error to be returned", n)
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@ package issues
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -120,10 +121,11 @@ func (b *MeilisearchIndexer) Delete(ids ...int64) error {
|
||||
// Search searches for issues by given conditions.
|
||||
// Returns the matching issue IDs
|
||||
func (b *MeilisearchIndexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) {
|
||||
filter := make([][]string, 0, len(repoIDs))
|
||||
repoFilters := make([]string, 0, len(repoIDs))
|
||||
for _, repoID := range repoIDs {
|
||||
filter = append(filter, []string{"repo_id = " + strconv.FormatInt(repoID, 10)})
|
||||
repoFilters = append(repoFilters, "repo_id = "+strconv.FormatInt(repoID, 10))
|
||||
}
|
||||
filter := strings.Join(repoFilters, " OR ")
|
||||
searchRes, err := b.client.Index(b.indexerName).Search(keyword, &meilisearch.SearchRequest{
|
||||
Filter: filter,
|
||||
Limit: int64(limit),
|
||||
|
||||
@@ -132,7 +132,6 @@ func NewFuncMap() []template.FuncMap {
|
||||
// -----------------------------------------------------------------
|
||||
// time / number / format
|
||||
"FileSize": base.FileSize,
|
||||
"LocaleNumber": LocaleNumber,
|
||||
"CountFmt": base.FormatNumberSI,
|
||||
"TimeSince": timeutil.TimeSince,
|
||||
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
||||
@@ -782,12 +781,6 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
|
||||
return a
|
||||
}
|
||||
|
||||
// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number
|
||||
func LocaleNumber(v interface{}) template.HTML {
|
||||
num, _ := util.ToInt64(v)
|
||||
return template.HTML(fmt.Sprintf(`<gitea-locale-number data-number="%d">%d</gitea-locale-number>`, num, num))
|
||||
}
|
||||
|
||||
// Eval the expression and return the result, see the comment of eval.Expr for details.
|
||||
// To use this helper function in templates, pass each token as a separate parameter.
|
||||
//
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
chi "github.com/go-chi/chi/v5"
|
||||
@@ -34,7 +35,7 @@ func MockContext(t *testing.T, path string) *context.Context {
|
||||
Values: make(url.Values),
|
||||
},
|
||||
Resp: context.NewResponse(resp),
|
||||
Locale: &mockLocale{},
|
||||
Locale: &translation.MockLocale{},
|
||||
}
|
||||
defer ctx.Close()
|
||||
|
||||
@@ -91,20 +92,6 @@ func LoadGitRepo(t *testing.T, ctx *context.Context) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
type mockLocale struct{}
|
||||
|
||||
func (l mockLocale) Language() string {
|
||||
return "en"
|
||||
}
|
||||
|
||||
func (l mockLocale) Tr(s string, _ ...interface{}) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
|
||||
return key1
|
||||
}
|
||||
|
||||
type mockResponseWriter struct {
|
||||
httptest.ResponseRecorder
|
||||
size int
|
||||
|
||||
@@ -114,11 +114,25 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
|
||||
return strings.TrimPrefix(timeStr, ", ")
|
||||
}
|
||||
|
||||
func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML {
|
||||
friendlyText := then.Format("2006-01-02 15:04:05 +07:00")
|
||||
|
||||
// document: https://github.com/github/relative-time-element
|
||||
attrs := `tense="past"`
|
||||
isFuture := now.Before(then)
|
||||
if isFuture {
|
||||
attrs = `tense="future"`
|
||||
}
|
||||
|
||||
// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
|
||||
htm := fmt.Sprintf(`<relative-time class="time-since" prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
|
||||
attrs, then.Format(time.RFC3339), friendlyText)
|
||||
return template.HTML(htm)
|
||||
}
|
||||
|
||||
// TimeSince renders relative time HTML given a time.Time
|
||||
func TimeSince(then time.Time, lang translation.Locale) template.HTML {
|
||||
timestamp := then.UTC().Format(time.RFC3339)
|
||||
// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
|
||||
return template.HTML(fmt.Sprintf(`<relative-time class="time-since" prefix="%s" datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`, lang.Tr("on_date"), timestamp, timestamp))
|
||||
return timeSinceUnix(then, time.Now(), lang)
|
||||
}
|
||||
|
||||
// TimeSinceUnix renders relative time HTML given a TimeStamp
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package translation
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MockLocale provides a mocked locale without any translations
|
||||
type MockLocale struct{}
|
||||
|
||||
var _ Locale = (*MockLocale)(nil)
|
||||
|
||||
func (l MockLocale) Language() string {
|
||||
return "en"
|
||||
}
|
||||
|
||||
func (l MockLocale) Tr(s string, _ ...interface{}) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (l MockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
|
||||
return key1
|
||||
}
|
||||
|
||||
func (l MockLocale) PrettyNumber(v any) string {
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
@@ -15,17 +15,20 @@ import (
|
||||
"code.gitea.io/gitea/modules/translation/i18n"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"golang.org/x/text/number"
|
||||
)
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var ContextKey interface{} = &contextKey{}
|
||||
var ContextKey any = &contextKey{}
|
||||
|
||||
// Locale represents an interface to translation
|
||||
type Locale interface {
|
||||
Language() string
|
||||
Tr(string, ...interface{}) string
|
||||
TrN(cnt interface{}, key1, keyN string, args ...interface{}) string
|
||||
Tr(string, ...any) string
|
||||
TrN(cnt any, key1, keyN string, args ...any) string
|
||||
PrettyNumber(v any) string
|
||||
}
|
||||
|
||||
// LangType represents a lang type
|
||||
@@ -135,6 +138,7 @@ func Match(tags ...language.Tag) language.Tag {
|
||||
type locale struct {
|
||||
i18n.Locale
|
||||
Lang, LangName string // these fields are used directly in templates: .i18n.Lang
|
||||
msgPrinter *message.Printer
|
||||
}
|
||||
|
||||
// NewLocale return a locale
|
||||
@@ -147,13 +151,24 @@ func NewLocale(lang string) Locale {
|
||||
langName := "unknown"
|
||||
if l, ok := allLangMap[lang]; ok {
|
||||
langName = l.Name
|
||||
} else if len(setting.Langs) > 0 {
|
||||
lang = setting.Langs[0]
|
||||
langName = setting.Names[0]
|
||||
}
|
||||
|
||||
i18nLocale, _ := i18n.GetLocale(lang)
|
||||
return &locale{
|
||||
l := &locale{
|
||||
Locale: i18nLocale,
|
||||
Lang: lang,
|
||||
LangName: langName,
|
||||
}
|
||||
if langTag, err := language.Parse(lang); err != nil {
|
||||
log.Error("Failed to parse language tag from name %q: %v", l.Lang, err)
|
||||
l.msgPrinter = message.NewPrinter(language.English)
|
||||
} else {
|
||||
l.msgPrinter = message.NewPrinter(langTag)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *locale) Language() string {
|
||||
@@ -199,7 +214,7 @@ var trNLangRules = map[string]func(int64) int{
|
||||
}
|
||||
|
||||
// TrN returns translated message for plural text translation
|
||||
func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string {
|
||||
func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string {
|
||||
var c int64
|
||||
if t, ok := cnt.(int); ok {
|
||||
c = int64(t)
|
||||
@@ -223,3 +238,8 @@ func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) st
|
||||
}
|
||||
return l.Tr(keyN, args...)
|
||||
}
|
||||
|
||||
func (l *locale) PrettyNumber(v any) string {
|
||||
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
|
||||
return l.msgPrinter.Sprintf("%v", number.Decimal(v))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package translation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/translation/i18n"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPrettyNumber(t *testing.T) {
|
||||
// TODO: make this package friendly to testing
|
||||
|
||||
i18n.ResetDefaultLocales()
|
||||
|
||||
allLangMap = make(map[string]*LangType)
|
||||
allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"}
|
||||
|
||||
l := NewLocale("id-ID")
|
||||
assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000))
|
||||
|
||||
l = NewLocale("nosuch")
|
||||
assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000))
|
||||
}
|
||||
Reference in New Issue
Block a user