diff --git a/models/actions/runner.go b/models/actions/runner.go index 4103ba44776..b646146ee67 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -97,7 +97,7 @@ func (r *ActionRunner) StatusName() string { } func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string { - return lang.Tr("actions.runners.status." + r.StatusName()) + return lang.TrString("actions.runners.status." + r.StatusName()) } func (r *ActionRunner) IsOnline() bool { diff --git a/models/actions/status.go b/models/actions/status.go index c97578f2acf..eda22341378 100644 --- a/models/actions/status.go +++ b/models/actions/status.go @@ -41,7 +41,7 @@ func (s Status) String() string { // LocaleString returns the locale string name of the Status func (s Status) LocaleString(lang translation.Locale) string { - return lang.Tr("actions.status." + s.String()) + return lang.TrString("actions.status." + s.String()) } // IsDone returns whether the Status is final diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 1118b6cc8cb..2d1d1bcb06d 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -194,7 +194,7 @@ func (status *CommitStatus) APIURL(ctx context.Context) string { // LocaleString returns the locale string name of the Status func (status *CommitStatus) LocaleString(lang translation.Locale) string { - return lang.Tr("repo.commitstatus." + status.State.String()) + return lang.TrString("repo.commitstatus." + status.State.String()) } // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc diff --git a/models/issues/comment.go b/models/issues/comment.go index a586caf1b57..fa0eb3cc0f1 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -210,12 +210,12 @@ const ( // LocaleString returns the locale string name of the role func (r RoleInRepo) LocaleString(lang translation.Locale) string { - return lang.Tr("repo.issues.role." + string(r)) + return lang.TrString("repo.issues.role." + string(r)) } // LocaleHelper returns the locale tooltip of the role func (r RoleInRepo) LocaleHelper(lang translation.Locale) string { - return lang.Tr("repo.issues.role." + string(r) + "_helper") + return lang.TrString("repo.issues.role." + string(r) + "_helper") } // Comment represents a comment in commit and issue page. diff --git a/models/shared/types/ownertype.go b/models/shared/types/ownertype.go index e6fe4e4cfda..a1d46c986f9 100644 --- a/models/shared/types/ownertype.go +++ b/models/shared/types/ownertype.go @@ -17,13 +17,13 @@ const ( func (o OwnerType) LocaleString(locale translation.Locale) string { switch o { case OwnerTypeSystemGlobal: - return locale.Tr("concept_system_global") + return locale.TrString("concept_system_global") case OwnerTypeIndividual: - return locale.Tr("concept_user_individual") + return locale.TrString("concept_user_individual") case OwnerTypeRepository: - return locale.Tr("concept_code_repository") + return locale.TrString("concept_code_repository") case OwnerTypeOrganization: - return locale.Tr("concept_user_organization") + return locale.TrString("concept_user_organization") } - return locale.Tr("unknown") + return locale.TrString("unknown") } diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index 2c7205b7082..27074358a91 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -8,6 +8,7 @@ import ( "context" "crypto/rand" "errors" + "html/template" "math/big" "strings" "sync" @@ -121,15 +122,15 @@ func Generate(n int) (string, error) { } // BuildComplexityError builds the error message when password complexity checks fail -func BuildComplexityError(locale translation.Locale) string { +func BuildComplexityError(locale translation.Locale) template.HTML { var buffer bytes.Buffer - buffer.WriteString(locale.Tr("form.password_complexity")) + buffer.WriteString(locale.TrString("form.password_complexity")) buffer.WriteString("") - return buffer.String() + return template.HTML(buffer.String()) } diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 3f08fd94a49..29943eb8580 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -173,7 +173,7 @@ func (e *escapeStreamer) ambiguousRune(r, c rune) error { Val: "ambiguous-code-point", }, html.Attribute{ Key: "data-tooltip-content", - Val: e.locale.Tr("repo.ambiguous_character", r, c), + Val: e.locale.TrString("repo.ambiguous_character", r, c), }); err != nil { return err } diff --git a/modules/context/api.go b/modules/context/api.go index e226264a873..f8bc682fedb 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -245,7 +245,7 @@ func APIContexter() func(http.Handler) http.Handler { // NotFound handles 404s for APIContext // String will replace message, errors will be added to a slice func (ctx *APIContext) NotFound(objs ...any) { - message := ctx.Tr("error.not_found") + message := ctx.Locale.TrString("error.not_found") var errors []string for _, obj := range objs { // Ignore nil diff --git a/modules/context/base.go b/modules/context/base.go index 8df1dde866d..fa05850a166 100644 --- a/modules/context/base.go +++ b/modules/context/base.go @@ -6,6 +6,7 @@ package context import ( "context" "fmt" + "html/template" "io" "net/http" "net/url" @@ -286,11 +287,11 @@ func (b *Base) cleanUp() { } } -func (b *Base) Tr(msg string, args ...any) string { +func (b *Base) Tr(msg string, args ...any) template.HTML { return b.Locale.Tr(msg, args...) } -func (b *Base) TrN(cnt any, key1, keyN string, args ...any) string { +func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML { return b.Locale.TrN(cnt, key1, keyN, args...) } diff --git a/modules/context/context.go b/modules/context/context.go index d19c5d11983..4d367b3242c 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -6,7 +6,7 @@ package context import ( "context" - "html" + "fmt" "html/template" "io" "net/http" @@ -71,16 +71,6 @@ func init() { }) } -// TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString. -// This is useful if the locale message is intended to only produce HTML content. -func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string { - trArgs := make([]any, len(args)) - for i, arg := range args { - trArgs[i] = html.EscapeString(arg) - } - return ctx.Locale.Tr(msg, trArgs...) -} - type webContextKeyType struct{} var WebContextKey = webContextKeyType{} @@ -253,6 +243,13 @@ func (ctx *Context) JSONOK() { ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it } -func (ctx *Context) JSONError(msg string) { - ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg}) +func (ctx *Context) JSONError(msg any) { + switch v := msg.(type) { + case string: + ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"}) + case template.HTML: + ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"}) + default: + panic(fmt.Sprintf("unsupported type: %T", msg)) + } } diff --git a/modules/context/context_response.go b/modules/context/context_response.go index 5729865561e..d9102b77bdb 100644 --- a/modules/context/context_response.go +++ b/modules/context/context_response.go @@ -98,12 +98,11 @@ func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (stri } // RenderWithErr used for page has form validation but need to prompt error to users. -func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form any) { +func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) { if form != nil { middleware.AssignForm(form, ctx.Data) } - ctx.Flash.ErrorMsg = msg - ctx.Data["Flash"] = ctx.Flash + ctx.Flash.Error(msg, true) ctx.HTML(http.StatusOK, tpl) } diff --git a/modules/context/repo.go b/modules/context/repo.go index 75ebfec7054..3ff7209c4cb 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -6,6 +6,7 @@ package context import ( "context" + "errors" "fmt" "html" "net/http" @@ -85,7 +86,7 @@ func (r *Repository) CanCreateBranch() bool { func RepoMustNotBeArchived() func(ctx *Context) { return func(ctx *Context) { if ctx.Repo.Repository.IsArchived { - ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title"))) + ctx.NotFound("IsArchived", errors.New(ctx.Locale.TrString("repo.archive.title"))) } } } diff --git a/modules/csv/csv.go b/modules/csv/csv.go index c5497befe70..35c5d6ab67e 100644 --- a/modules/csv/csv.go +++ b/modules/csv/csv.go @@ -123,9 +123,9 @@ func guessDelimiter(data []byte) rune { func FormatError(err error, locale translation.Locale) (string, error) { if perr, ok := err.(*stdcsv.ParseError); ok { if perr.Err == stdcsv.ErrFieldCount { - return locale.Tr("repo.error.csv.invalid_field_count", perr.Line), nil + return locale.TrString("repo.error.csv.invalid_field_count", perr.Line), nil } - return locale.Tr("repo.error.csv.unexpected", perr.Line, perr.Column), nil + return locale.TrString("repo.error.csv.unexpected", perr.Line, perr.Column), nil } return "", err diff --git a/modules/markup/html.go b/modules/markup/html.go index 33dc1e9086a..b7291823b50 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -804,7 +804,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { // indicate that in the text by appending (comment) if m[4] != -1 && m[5] != -1 { if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok { - text += " " + locale.Tr("repo.from_comment") + text += " " + locale.TrString("repo.from_comment") } else { text += " (comment)" } diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index 9602040931a..38f744a25ff 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -21,7 +21,7 @@ func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]str details.SetAttributeString(k, []byte(v)) } - summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc")))) + summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).TrString("toc")))) details.AppendChild(details, summary) ul := ast.NewList('-') details.AppendChild(details, ul) diff --git a/modules/migration/messenger.go b/modules/migration/messenger.go index 924aac97694..6f9cad3f104 100644 --- a/modules/migration/messenger.go +++ b/modules/migration/messenger.go @@ -3,7 +3,7 @@ package migration -// Messenger is a formatting function similar to i18n.Tr +// Messenger is a formatting function similar to i18n.TrString type Messenger func(key string, args ...any) // NilMessenger represents an empty formatting function diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 96cdd9ca46c..9ff5d8927f3 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -36,7 +36,7 @@ func NewFuncMap() template.FuncMap { "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names. "Eval": Eval, "Safe": Safe, - "Escape": html.EscapeString, + "Escape": Escape, "QueryEscape": url.QueryEscape, "JSEscape": template.JSEscapeString, "Str2html": Str2html, // TODO: rename it to SanitizeHTML @@ -159,7 +159,7 @@ func NewFuncMap() template.FuncMap { "RenderCodeBlock": RenderCodeBlock, "RenderIssueTitle": RenderIssueTitle, "RenderEmoji": RenderEmoji, - "RenderEmojiPlain": emoji.ReplaceAliases, + "RenderEmojiPlain": RenderEmojiPlain, "ReactionToEmoji": ReactionToEmoji, "RenderMarkdownToHtml": RenderMarkdownToHtml, @@ -180,13 +180,45 @@ func NewFuncMap() template.FuncMap { } // Safe render raw as HTML -func Safe(raw string) template.HTML { - return template.HTML(raw) +func Safe(s any) template.HTML { + switch v := s.(type) { + case string: + return template.HTML(v) + case template.HTML: + return v + } + panic(fmt.Sprintf("unexpected type %T", s)) +} + +// Str2html sanitizes the input by pre-defined markdown rules +func Str2html(s any) template.HTML { + switch v := s.(type) { + case string: + return template.HTML(markup.Sanitize(v)) + case template.HTML: + return template.HTML(markup.Sanitize(string(v))) + } + panic(fmt.Sprintf("unexpected type %T", s)) } -// Str2html render Markdown text to HTML -func Str2html(raw string) template.HTML { - return template.HTML(markup.Sanitize(raw)) +func Escape(s any) template.HTML { + switch v := s.(type) { + case string: + return template.HTML(html.EscapeString(v)) + case template.HTML: + return v + } + panic(fmt.Sprintf("unexpected type %T", s)) +} + +func RenderEmojiPlain(s any) any { + switch v := s.(type) { + case string: + return emoji.ReplaceAliases(v) + case template.HTML: + return template.HTML(emoji.ReplaceAliases(string(v))) + } + panic(fmt.Sprintf("unexpected type %T", s)) } // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index 1cb3c4f288b..dfaa0e3e3aa 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -28,54 +28,54 @@ func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) { switch { case diff <= 0: diff = 0 - diffStr = lang.Tr("tool.now") + diffStr = lang.TrString("tool.now") case diff < 2: diff = 0 - diffStr = lang.Tr("tool.1s") + diffStr = lang.TrString("tool.1s") case diff < 1*Minute: - diffStr = lang.Tr("tool.seconds", diff) + diffStr = lang.TrString("tool.seconds", diff) diff = 0 case diff < 2*Minute: diff -= 1 * Minute - diffStr = lang.Tr("tool.1m") + diffStr = lang.TrString("tool.1m") case diff < 1*Hour: - diffStr = lang.Tr("tool.minutes", diff/Minute) + diffStr = lang.TrString("tool.minutes", diff/Minute) diff -= diff / Minute * Minute case diff < 2*Hour: diff -= 1 * Hour - diffStr = lang.Tr("tool.1h") + diffStr = lang.TrString("tool.1h") case diff < 1*Day: - diffStr = lang.Tr("tool.hours", diff/Hour) + diffStr = lang.TrString("tool.hours", diff/Hour) diff -= diff / Hour * Hour case diff < 2*Day: diff -= 1 * Day - diffStr = lang.Tr("tool.1d") + diffStr = lang.TrString("tool.1d") case diff < 1*Week: - diffStr = lang.Tr("tool.days", diff/Day) + diffStr = lang.TrString("tool.days", diff/Day) diff -= diff / Day * Day case diff < 2*Week: diff -= 1 * Week - diffStr = lang.Tr("tool.1w") + diffStr = lang.TrString("tool.1w") case diff < 1*Month: - diffStr = lang.Tr("tool.weeks", diff/Week) + diffStr = lang.TrString("tool.weeks", diff/Week) diff -= diff / Week * Week case diff < 2*Month: diff -= 1 * Month - diffStr = lang.Tr("tool.1mon") + diffStr = lang.TrString("tool.1mon") case diff < 1*Year: - diffStr = lang.Tr("tool.months", diff/Month) + diffStr = lang.TrString("tool.months", diff/Month) diff -= diff / Month * Month case diff < 2*Year: diff -= 1 * Year - diffStr = lang.Tr("tool.1y") + diffStr = lang.TrString("tool.1y") default: - diffStr = lang.Tr("tool.years", diff/Year) + diffStr = lang.TrString("tool.years", diff/Year) diff -= (diff / Year) * Year } return diff, diffStr @@ -97,10 +97,10 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string { diff := now.Unix() - then.Unix() if then.After(now) { - return lang.Tr("tool.future") + return lang.TrString("tool.future") } if diff == 0 { - return lang.Tr("tool.now") + return lang.TrString("tool.now") } var timeStr, diffStr string @@ -115,7 +115,7 @@ 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 { +func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML { friendlyText := then.Format("2006-01-02 15:04:05 -07:00") // document: https://github.com/github/relative-time-element diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go index 42475545b34..1555cd961e5 100644 --- a/modules/translation/i18n/i18n.go +++ b/modules/translation/i18n/i18n.go @@ -4,26 +4,25 @@ package i18n import ( + "html/template" "io" ) var DefaultLocales = NewLocaleStore() type Locale interface { - // Tr translates a given key and arguments for a language - Tr(trKey string, trArgs ...any) string - // Has reports if a locale has a translation for a given key - Has(trKey string) bool + // TrString translates a given key and arguments for a language + TrString(trKey string, trArgs ...any) string + // TrHTML translates a given key and arguments for a language, string arguments are escaped to HTML + TrHTML(trKey string, trArgs ...any) template.HTML + // HasKey reports if a locale has a translation for a given key + HasKey(trKey string) bool } // LocaleStore provides the functions common to all locale stores type LocaleStore interface { io.Closer - // Tr translates a given key and arguments for a language - Tr(lang, trKey string, trArgs ...any) string - // Has reports if a locale has a translation for a given key - Has(lang, trKey string) bool // SetDefaultLang sets the default language to fall back to SetDefaultLang(lang string) // ListLangNameDesc provides paired slices of language names to descriptors @@ -45,7 +44,7 @@ func ResetDefaultLocales() { DefaultLocales = NewLocaleStore() } -// GetLocales returns the locale from the default locales +// GetLocale returns the locale from the default locales func GetLocale(lang string) (Locale, bool) { return DefaultLocales.Locale(lang) } diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go index 1d1be43318d..ffe69a74dfd 100644 --- a/modules/translation/i18n/i18n_test.go +++ b/modules/translation/i18n/i18n_test.go @@ -17,7 +17,7 @@ fmt = %[1]s %[2]s [section] sub = Sub String -mixed = test value; more text +mixed = test value; %s `) testData2 := []byte(` @@ -32,29 +32,33 @@ sub = Changed Sub String assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil)) ls.SetDefaultLang("lang1") - result := ls.Tr("lang1", "fmt", "a", "b") + lang1, _ := ls.Locale("lang1") + lang2, _ := ls.Locale("lang2") + + result := lang1.TrString("fmt", "a", "b") assert.Equal(t, "a b", result) - result = ls.Tr("lang2", "fmt", "a", "b") + result = lang2.TrString("fmt", "a", "b") assert.Equal(t, "b a", result) - result = ls.Tr("lang1", "section.sub") + result = lang1.TrString("section.sub") assert.Equal(t, "Sub String", result) - result = ls.Tr("lang2", "section.sub") + result = lang2.TrString("section.sub") assert.Equal(t, "Changed Sub String", result) - result = ls.Tr("", ".dot.name") + langNone, _ := ls.Locale("none") + result = langNone.TrString(".dot.name") assert.Equal(t, "Dot Name", result) - result = ls.Tr("lang2", "section.mixed") - assert.Equal(t, `test value; more text`, result) + result2 := lang2.TrHTML("section.mixed", "a&b") + assert.EqualValues(t, `test value; a&b`, result2) langs, descs := ls.ListLangNameDesc() assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs) assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs) - found := ls.Has("lang1", "no-such") + found := lang1.HasKey("no-such") assert.False(t, found) assert.NoError(t, ls.Close()) } @@ -72,9 +76,10 @@ c=22 ls := NewLocaleStore() assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2)) - assert.Equal(t, "11", ls.Tr("lang1", "a")) - assert.Equal(t, "21", ls.Tr("lang1", "b")) - assert.Equal(t, "22", ls.Tr("lang1", "c")) + lang1, _ := ls.Locale("lang1") + assert.Equal(t, "11", lang1.TrString("a")) + assert.Equal(t, "21", lang1.TrString("b")) + assert.Equal(t, "22", lang1.TrString("c")) } func TestLocaleStoreQuirks(t *testing.T) { @@ -110,8 +115,9 @@ func TestLocaleStoreQuirks(t *testing.T) { for _, testData := range testDataList { ls := NewLocaleStore() err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil) + lang1, _ := ls.Locale("lang1") assert.NoError(t, err, testData.hint) - assert.Equal(t, testData.out, ls.Tr("lang1", "a"), testData.hint) + assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint) assert.NoError(t, ls.Close()) } diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index f5a951a79f1..69cc9fd91da 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -5,6 +5,8 @@ package i18n import ( "fmt" + "html/template" + "slices" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -18,6 +20,8 @@ type locale struct { idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap } +var _ Locale = (*locale)(nil) + type localeStore struct { // After initializing has finished, these fields are read-only. langNames []string @@ -85,20 +89,6 @@ func (store *localeStore) SetDefaultLang(lang string) { store.defaultLang = lang } -// Tr translates content to target language. fall back to default language. -func (store *localeStore) Tr(lang, trKey string, trArgs ...any) string { - l, _ := store.Locale(lang) - - return l.Tr(trKey, trArgs...) -} - -// Has returns whether the given language has a translation for the provided key -func (store *localeStore) Has(lang, trKey string) bool { - l, _ := store.Locale(lang) - - return l.Has(trKey) -} - // Locale returns the locale for the lang or the default language func (store *localeStore) Locale(lang string) (Locale, bool) { l, found := store.localeMap[lang] @@ -113,13 +103,11 @@ func (store *localeStore) Locale(lang string) (Locale, bool) { return l, found } -// Close implements io.Closer func (store *localeStore) Close() error { return nil } -// Tr translates content to locale language. fall back to default language. -func (l *locale) Tr(trKey string, trArgs ...any) string { +func (l *locale) TrString(trKey string, trArgs ...any) string { format := trKey idx, ok := l.store.trKeyToIdxMap[trKey] @@ -141,8 +129,23 @@ func (l *locale) Tr(trKey string, trArgs ...any) string { return msg } -// Has returns whether a key is present in this locale or not -func (l *locale) Has(trKey string) bool { +func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML { + args := slices.Clone(trArgs) + for i, v := range args { + switch v := v.(type) { + case string: + args[i] = template.HTML(template.HTMLEscapeString(v)) + case fmt.Stringer: + args[i] = template.HTMLEscapeString(v.String()) + default: // int, float, include template.HTML + // do nothing, just use it + } + } + return template.HTML(l.TrString(trKey, args...)) +} + +// HasKey returns whether a key is present in this locale or not +func (l *locale) HasKey(trKey string) bool { idx, ok := l.store.trKeyToIdxMap[trKey] if !ok { return false diff --git a/modules/translation/mock.go b/modules/translation/mock.go index 2d0cb173246..1f0559f38d0 100644 --- a/modules/translation/mock.go +++ b/modules/translation/mock.go @@ -3,7 +3,10 @@ package translation -import "fmt" +import ( + "fmt" + "html/template" +) // MockLocale provides a mocked locale without any translations type MockLocale struct{} @@ -14,12 +17,16 @@ func (l MockLocale) Language() string { return "en" } -func (l MockLocale) Tr(s string, _ ...any) string { +func (l MockLocale) TrString(s string, _ ...any) string { return s } -func (l MockLocale) TrN(_cnt any, key1, _keyN string, _args ...any) string { - return key1 +func (l MockLocale) Tr(s string, a ...any) template.HTML { + return template.HTML(s) +} + +func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { + return template.HTML(key1) } func (l MockLocale) PrettyNumber(v any) string { diff --git a/modules/translation/translation.go b/modules/translation/translation.go index dba4de6607d..b7c18f610ad 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -5,6 +5,7 @@ package translation import ( "context" + "html/template" "sort" "strings" "sync" @@ -27,8 +28,11 @@ var ContextKey any = &contextKey{} // Locale represents an interface to translation type Locale interface { Language() string - Tr(string, ...any) string - TrN(cnt any, key1, keyN string, args ...any) string + TrString(string, ...any) string + + Tr(key string, args ...any) template.HTML + TrN(cnt any, key1, keyN string, args ...any) template.HTML + PrettyNumber(v any) string } @@ -144,6 +148,8 @@ type locale struct { msgPrinter *message.Printer } +var _ Locale = (*locale)(nil) + // NewLocale return a locale func NewLocale(lang string) Locale { if lock != nil { @@ -216,8 +222,12 @@ var trNLangRules = map[string]func(int64) int{ }, } +func (l *locale) Tr(s string, args ...any) template.HTML { + return l.TrHTML(s, args...) +} + // TrN returns translated message for plural text translation -func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string { +func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML { var c int64 if t, ok := cnt.(int); ok { c = int64(t) diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index d9bcdf3b2ad..43e1bbc70e3 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -104,40 +104,40 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo trName := field.Tag.Get("locale") if len(trName) == 0 { - trName = l.Tr("form." + field.Name) + trName = l.TrString("form." + field.Name) } else { - trName = l.Tr(trName) + trName = l.TrString(trName) } switch errs[0].Classification { case binding.ERR_REQUIRED: - data["ErrorMsg"] = trName + l.Tr("form.require_error") + data["ErrorMsg"] = trName + l.TrString("form.require_error") case binding.ERR_ALPHA_DASH: - data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error") + data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_error") case binding.ERR_ALPHA_DASH_DOT: - data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error") + data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_dot_error") case validation.ErrGitRefName: - data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error") + data["ErrorMsg"] = trName + l.TrString("form.git_ref_name_error") case binding.ERR_SIZE: - data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field)) + data["ErrorMsg"] = trName + l.TrString("form.size_error", GetSize(field)) case binding.ERR_MIN_SIZE: - data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field)) + data["ErrorMsg"] = trName + l.TrString("form.min_size_error", GetMinSize(field)) case binding.ERR_MAX_SIZE: - data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field)) + data["ErrorMsg"] = trName + l.TrString("form.max_size_error", GetMaxSize(field)) case binding.ERR_EMAIL: - data["ErrorMsg"] = trName + l.Tr("form.email_error") + data["ErrorMsg"] = trName + l.TrString("form.email_error") case binding.ERR_URL: - data["ErrorMsg"] = trName + l.Tr("form.url_error", errs[0].Message) + data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message) case binding.ERR_INCLUDE: - data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) + data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field)) case validation.ErrGlobPattern: - data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) + data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message) case validation.ErrRegexPattern: - data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) + data["ErrorMsg"] = trName + l.TrString("form.regex_pattern_error", errs[0].Message) case validation.ErrUsername: - data["ErrorMsg"] = trName + l.Tr("form.username_error") + data["ErrorMsg"] = trName + l.TrString("form.username_error") case validation.ErrInvalidGroupTeamMap: - data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message) + data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message) default: msg := errs[0].Classification if msg != "" && errs[0].Message != "" { @@ -146,7 +146,7 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo msg += errs[0].Message if msg == "" { - msg = l.Tr("form.unknown_error") + msg = l.TrString("form.unknown_error") } data["ErrorMsg"] = trName + ": " + msg } diff --git a/modules/web/middleware/flash.go b/modules/web/middleware/flash.go index 41f3aac27c5..88da2049a41 100644 --- a/modules/web/middleware/flash.go +++ b/modules/web/middleware/flash.go @@ -3,7 +3,11 @@ package middleware -import "net/url" +import ( + "fmt" + "html/template" + "net/url" +) // Flash represents a one time data transfer between two requests. type Flash struct { @@ -26,26 +30,36 @@ func (f *Flash) set(name, msg string, current ...bool) { } } +func flashMsgStringOrHTML(msg any) string { + switch v := msg.(type) { + case string: + return v + case template.HTML: + return string(v) + } + panic(fmt.Sprintf("unknown type: %T", msg)) +} + // Error sets error message -func (f *Flash) Error(msg string, current ...bool) { - f.ErrorMsg = msg - f.set("error", msg, current...) +func (f *Flash) Error(msg any, current ...bool) { + f.ErrorMsg = flashMsgStringOrHTML(msg) + f.set("error", f.ErrorMsg, current...) } // Warning sets warning message -func (f *Flash) Warning(msg string, current ...bool) { - f.WarningMsg = msg - f.set("warning", msg, current...) +func (f *Flash) Warning(msg any, current ...bool) { + f.WarningMsg = flashMsgStringOrHTML(msg) + f.set("warning", f.WarningMsg, current...) } // Info sets info message -func (f *Flash) Info(msg string, current ...bool) { - f.InfoMsg = msg - f.set("info", msg, current...) +func (f *Flash) Info(msg any, current ...bool) { + f.InfoMsg = flashMsgStringOrHTML(msg) + f.set("info", f.InfoMsg, current...) } // Success sets success message -func (f *Flash) Success(msg string, current ...bool) { - f.SuccessMsg = msg - f.set("success", msg, current...) +func (f *Flash) Success(msg any, current ...bool) { + f.SuccessMsg = flashMsgStringOrHTML(msg) + f.set("success", f.SuccessMsg, current...) } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 065d6bf8b2c..370e4753f3c 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -762,13 +762,13 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch } message := "" if len(createFiles) != 0 { - message += ctx.Tr("repo.editor.add", strings.Join(createFiles, ", ")+"\n") + message += ctx.Locale.TrString("repo.editor.add", strings.Join(createFiles, ", ")+"\n") } if len(updateFiles) != 0 { - message += ctx.Tr("repo.editor.update", strings.Join(updateFiles, ", ")+"\n") + message += ctx.Locale.TrString("repo.editor.update", strings.Join(updateFiles, ", ")+"\n") } if len(deleteFiles) != 0 { - message += ctx.Tr("repo.editor.delete", strings.Join(deleteFiles, ", ")) + message += ctx.Locale.TrString("repo.editor.delete", strings.Join(deleteFiles, ", ")) } return strings.Trim(message, "\n") } diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 4db2c68a798..2b7a8f7ba1c 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -395,7 +395,7 @@ func CreateIssueComment(ctx *context.APIContext) { } if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin { - ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked"))) + ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked"))) return } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 2cf63c646d3..7fdd18dfae9 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -210,16 +210,16 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) { if util.IsEmptyString(form.SSPISeparatorReplacement) { ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.require_error")) + return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error")) } if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) { ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.alpha_dash_dot_error")) + return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error")) } if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) { ctx.Data["Err_SSPIDefaultLanguage"] = true - return nil, errors.New(ctx.Tr("form.lang_select_error")) + return nil, errors.New(ctx.Locale.TrString("form.lang_select_error")) } return &sspi.Source{ diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 5af1696a64e..c23379b87ad 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -37,7 +37,7 @@ func ForgotPasswd(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") if setting.MailService == nil { - log.Warn(ctx.Tr("auth.disable_forgot_password_mail_admin")) + log.Warn("no mail service configured") ctx.Data["IsResetDisable"] = true ctx.HTML(http.StatusOK, tplForgotPassword) return diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go index 66b01d36809..1e040ed819e 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -6,6 +6,7 @@ package feed import ( "fmt" "html" + "html/template" "net/http" "net/url" "strconv" @@ -79,119 +80,120 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio // title title = act.ActUser.DisplayName() + " " + var titleExtra template.HTML switch act.OpType { case activities_model.ActionCreateRepo: - title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.create_repo", act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) link.Href = act.GetRepoAbsoluteLink(ctx) case activities_model.ActionRenameRepo: - title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.rename_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) link.Href = act.GetRepoAbsoluteLink(ctx) case activities_model.ActionCommitRepo: link.Href = toBranchLink(ctx, act) if len(act.Content) != 0 { - title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.commit_repo", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) } else { - title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.create_branch", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetBranch(), act.ShortRepoPath(ctx)) } case activities_model.ActionCreateIssue: link.Href = toIssueLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionCreatePullRequest: link.Href = toPullLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionTransferRepo: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.transfer_repo", act.GetContent(), act.GetRepoAbsoluteLink(ctx), act.ShortRepoPath(ctx)) case activities_model.ActionPushTag: link.Href = toTagLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.push_tag", act.GetRepoAbsoluteLink(ctx), link.Href, act.GetTag(), act.ShortRepoPath(ctx)) case activities_model.ActionCommentIssue: issueLink := toIssueLink(ctx, act) if link.Href == "#" { link.Href = issueLink } - title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionMergePullRequest: pullLink := toPullLink(ctx, act) if link.Href == "#" { link.Href = pullLink } - title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionAutoMergePullRequest: pullLink := toPullLink(ctx, act) if link.Href == "#" { link.Href = pullLink } - title += ctx.TrHTMLEscapeArgs("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.auto_merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionCloseIssue: issueLink := toIssueLink(ctx, act) if link.Href == "#" { link.Href = issueLink } - title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionReopenIssue: issueLink := toIssueLink(ctx, act) if link.Href == "#" { link.Href = issueLink } - title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionClosePullRequest: pullLink := toPullLink(ctx, act) if link.Href == "#" { link.Href = pullLink } - title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionReopenPullRequest: pullLink := toPullLink(ctx, act) if link.Href == "#" { link.Href = pullLink } - title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionDeleteTag: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.delete_tag", act.GetRepoAbsoluteLink(ctx), act.GetTag(), act.ShortRepoPath(ctx)) case activities_model.ActionDeleteBranch: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.delete_branch", act.GetRepoAbsoluteLink(ctx), html.EscapeString(act.GetBranch()), act.ShortRepoPath(ctx)) case activities_model.ActionMirrorSyncPush: srcLink := toSrcLink(ctx, act) if link.Href == "#" { link.Href = srcLink } - title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.mirror_sync_push", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) case activities_model.ActionMirrorSyncCreate: srcLink := toSrcLink(ctx, act) if link.Href == "#" { link.Href = srcLink } - title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.mirror_sync_create", act.GetRepoAbsoluteLink(ctx), srcLink, act.GetBranch(), act.ShortRepoPath(ctx)) case activities_model.ActionMirrorSyncDelete: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.mirror_sync_delete", act.GetRepoAbsoluteLink(ctx), act.GetBranch(), act.ShortRepoPath(ctx)) case activities_model.ActionApprovePullRequest: pullLink := toPullLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionRejectPullRequest: pullLink := toPullLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionCommentPull: pullLink := toPullLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx)) case activities_model.ActionPublishRelease: releaseLink := toReleaseLink(ctx, act) if link.Href == "#" { link.Href = releaseLink } - title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content) + titleExtra = ctx.Locale.Tr("action.publish_release", act.GetRepoAbsoluteLink(ctx), releaseLink, act.ShortRepoPath(ctx), act.Content) case activities_model.ActionPullReviewDismissed: pullLink := toPullLink(ctx, act) - title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1]) + titleExtra = ctx.Locale.Tr("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(ctx), act.GetIssueInfos()[1]) case activities_model.ActionStarRepo: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.starred_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) case activities_model.ActionWatchRepo: link.Href = act.GetRepoAbsoluteLink(ctx) - title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) + titleExtra = ctx.Locale.Tr("action.watched_repo", act.GetRepoAbsoluteLink(ctx), act.GetRepoPath(ctx)) default: return nil, fmt.Errorf("unknown action type: %v", act.OpType) } @@ -233,7 +235,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest: desc = act.GetIssueTitle(ctx) case activities_model.ActionPullReviewDismissed: - desc = ctx.Tr("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2] + desc = ctx.Locale.TrString("action.review_dismissed_reason") + "\n\n" + act.GetIssueInfos()[2] } } if len(content) == 0 { @@ -241,7 +243,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio } items = append(items, &feeds.Item{ - Title: title, + Title: template.HTMLEscapeString(title) + string(titleExtra), Link: link, Description: desc, IsPermaLink: "false", diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go index 04f84c0c8d4..3feca68d61a 100644 --- a/routers/web/feed/profile.go +++ b/routers/web/feed/profile.go @@ -56,7 +56,7 @@ func showUserFeed(ctx *context.Context, formatType string) { } feed := &feeds.Feed{ - Title: ctx.Tr("home.feed_of", ctx.ContextUser.DisplayName()), + Title: ctx.Locale.TrString("home.feed_of", ctx.ContextUser.DisplayName()), Link: &feeds.Link{Href: ctx.ContextUser.HTMLURL()}, Description: ctxUserDescription, Created: time.Now(), diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go index 57b0c927665..558c03dba74 100644 --- a/routers/web/feed/release.go +++ b/routers/web/feed/release.go @@ -28,10 +28,10 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas var link *feeds.Link if isReleasesOnly { - title = ctx.Tr("repo.release.releases_for", repo.FullName()) + title = ctx.Locale.TrString("repo.release.releases_for", repo.FullName()) link = &feeds.Link{Href: repo.HTMLURL() + "/release"} } else { - title = ctx.Tr("repo.release.tags_for", repo.FullName()) + title = ctx.Locale.TrString("repo.release.tags_for", repo.FullName()) link = &feeds.Link{Href: repo.HTMLURL() + "/tags"} } diff --git a/routers/web/feed/repo.go b/routers/web/feed/repo.go index 5fcad267791..51c24510c7e 100644 --- a/routers/web/feed/repo.go +++ b/routers/web/feed/repo.go @@ -27,7 +27,7 @@ func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType } feed := &feeds.Feed{ - Title: ctx.Tr("home.feed_of", repo.FullName()), + Title: ctx.Locale.TrString("home.feed_of", repo.FullName()), Link: &feeds.Link{Href: repo.HTMLURL()}, Description: repo.Description, Created: time.Now(), diff --git a/routers/web/org/org.go b/routers/web/org/org.go index 52f8df8a1c7..1e4544730e1 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -29,7 +29,7 @@ func Create(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_org") ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode if !ctx.Doer.CanCreateOrganization() { - ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed"))) + ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed"))) return } ctx.HTML(http.StatusOK, tplCreateOrg) @@ -41,7 +41,7 @@ func CreatePost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_org") if !ctx.Doer.CanCreateOrganization() { - ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed"))) + ctx.ServerError("Not allowed", errors.New(ctx.Locale.TrString("org.form.create_org_not_allowed"))) return } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index f65cc6e6799..f062127d24f 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -353,7 +353,7 @@ func ViewProject(ctx *context.Context) { } if boards[0].ID == 0 { - boards[0].Title = ctx.Tr("repo.projects.type.uncategorized") + boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized") } issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) @@ -679,7 +679,7 @@ func MoveIssues(ctx *context.Context) { board = &project_model.Board{ ID: 0, ProjectID: project.ID, - Title: ctx.Tr("repo.projects.type.uncategorized"), + Title: ctx.Locale.TrString("repo.projects.type.uncategorized"), } } else { board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 5f6a1ec36a6..19aca267117 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -100,7 +100,7 @@ func List(ctx *context.Context) { } wf, err := model.ReadWorkflow(bytes.NewReader(content)) if err != nil { - workflow.ErrMsg = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", err.Error()) + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) workflows = append(workflows, workflow) continue } @@ -115,7 +115,7 @@ func List(ctx *context.Context) { continue } if !allRunnerLabels.Contains(ro) { - workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro) + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", ro) break } } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 9cda30d23dc..59fb25b6801 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -168,8 +168,8 @@ func ViewPost(ctx *context_module.Context) { Link: run.RefLink(), } resp.State.Run.Commit = ViewCommit{ - LocaleCommit: ctx.Tr("actions.runs.commit"), - LocalePushedBy: ctx.Tr("actions.runs.pushed_by"), + LocaleCommit: ctx.Locale.TrString("actions.runs.commit"), + LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"), ShortSha: base.ShortSha(run.CommitSHA), Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), Pusher: pusher, @@ -194,7 +194,7 @@ func ViewPost(ctx *context_module.Context) { resp.State.CurrentJob.Title = current.Name resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale) if run.NeedApproval { - resp.State.CurrentJob.Detail = ctx.Locale.Tr("actions.need_approval_desc") + resp.State.CurrentJob.Detail = ctx.Locale.TrString("actions.need_approval_desc") } resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 25dd8812198..8de54d569fd 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -104,9 +104,9 @@ func CherryPickPost(ctx *context.Context) { message := strings.TrimSpace(form.CommitSummary) if message == "" { if form.Revert { - message = ctx.Tr("repo.commit.revert-header", sha) + message = ctx.Locale.TrString("repo.commit.revert-header", sha) } else { - message = ctx.Tr("repo.commit.cherry-pick-header", sha) + message = ctx.Locale.TrString("repo.commit.cherry-pick-header", sha) } } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index a3593815b8b..67d41cf8079 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -126,7 +126,7 @@ func setCsvCompareContext(ctx *context.Context) { return CsvDiffResult{nil, ""} } - errTooLarge := errors.New(ctx.Locale.Tr("repo.error.csv.too_large")) + errTooLarge := errors.New(ctx.Locale.TrString("repo.error.csv.too_large")) csvReaderFromCommit := func(ctx *markup.RenderContext, blob *git.Blob) (*csv.Reader, io.Closer, error) { if blob == nil { diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 85d40e7820b..bc3cb8801d0 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -262,9 +262,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b message := strings.TrimSpace(form.CommitSummary) if len(message) == 0 { if isNewFile { - message = ctx.Tr("repo.editor.add", form.TreePath) + message = ctx.Locale.TrString("repo.editor.add", form.TreePath) } else { - message = ctx.Tr("repo.editor.update", form.TreePath) + message = ctx.Locale.TrString("repo.editor.update", form.TreePath) } } form.CommitMessage = strings.TrimSpace(form.CommitMessage) @@ -415,7 +415,7 @@ func DiffPreviewPost(ctx *context.Context) { } if diff.NumFiles == 0 { - ctx.PlainText(http.StatusOK, ctx.Tr("repo.editor.no_changes_to_show")) + ctx.PlainText(http.StatusOK, ctx.Locale.TrString("repo.editor.no_changes_to_show")) return } ctx.Data["File"] = diff.Files[0] @@ -482,7 +482,7 @@ func DeleteFilePost(ctx *context.Context) { message := strings.TrimSpace(form.CommitSummary) if len(message) == 0 { - message = ctx.Tr("repo.editor.delete", ctx.Repo.TreePath) + message = ctx.Locale.TrString("repo.editor.delete", ctx.Repo.TreePath) } form.CommitMessage = strings.TrimSpace(form.CommitMessage) if len(form.CommitMessage) > 0 { @@ -691,7 +691,7 @@ func UploadFilePost(ctx *context.Context) { if dir == "" { dir = "/" } - message = ctx.Tr("repo.editor.upload_files_to_dir", dir) + message = ctx.Locale.TrString("repo.editor.upload_files_to_dir", dir) } form.CommitMessage = strings.TrimSpace(form.CommitMessage) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index a85f6e76665..d5e49960a1e 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1036,7 +1036,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string }) if err != nil { log.Debug("render flash error: %v", err) - flashError = ctx.Tr("repo.issues.choose.ignore_invalid_templates") + flashError = ctx.Locale.TrString("repo.issues.choose.ignore_invalid_templates") } return flashError } @@ -1655,7 +1655,7 @@ func ViewIssue(ctx *context.Context) { } ghostMilestone := &issues_model.Milestone{ ID: -1, - Name: ctx.Tr("repo.issues.deleted_milestone"), + Name: ctx.Locale.TrString("repo.issues.deleted_milestone"), } if comment.OldMilestoneID > 0 && comment.OldMilestone == nil { comment.OldMilestone = ghostMilestone @@ -1672,7 +1672,7 @@ func ViewIssue(ctx *context.Context) { ghostProject := &project_model.Project{ ID: -1, - Title: ctx.Tr("repo.issues.deleted_project"), + Title: ctx.Locale.TrString("repo.issues.deleted_project"), } if comment.OldProjectID > 0 && comment.OldProject == nil { diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 0f376db1450..0939af487c1 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -56,12 +56,12 @@ func GetContentHistoryList(ctx *context.Context) { for _, item := range items { var actionText string if item.IsDeleted { - actionTextDeleted := ctx.Locale.Tr("repo.issues.content_history.deleted") + actionTextDeleted := ctx.Locale.TrString("repo.issues.content_history.deleted") actionText = "" + actionTextDeleted + "" } else if item.IsFirstCreated { - actionText = ctx.Locale.Tr("repo.issues.content_history.created") + actionText = ctx.Locale.TrString("repo.issues.content_history.created") } else { - actionText = ctx.Locale.Tr("repo.issues.content_history.edited") + actionText = ctx.Locale.TrString("repo.issues.content_history.edited") } username := item.UserName diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index e0d49e44e18..742f12114d5 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -123,7 +123,7 @@ func TestDeleteLabel(t *testing.T) { assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2}) - assert.Equal(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) + assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) } func TestUpdateIssueLabel_Clear(t *testing.T) { diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index c04435cf1b4..00bd45aaec1 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -79,7 +79,7 @@ func NewDiffPatchPost(ctx *context.Context) { // `message` will be both the summary and message combined message := strings.TrimSpace(form.CommitSummary) if len(message) == 0 { - message = ctx.Tr("repo.editor.patch") + message = ctx.Locale.TrString("repo.editor.patch") } form.CommitMessage = strings.TrimSpace(form.CommitMessage) diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 001f0752c36..cc0127e7e17 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -315,7 +315,7 @@ func ViewProject(ctx *context.Context) { } if boards[0].ID == 0 { - boards[0].Title = ctx.Tr("repo.projects.type.uncategorized") + boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized") } issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) @@ -633,7 +633,7 @@ func MoveIssues(ctx *context.Context) { board = &project_model.Board{ ID: 0, ProjectID: project.ID, - Title: ctx.Tr("repo.projects.type.uncategorized"), + Title: ctx.Locale.TrString("repo.projects.type.uncategorized"), } } else { board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index b265cf47548..365d9bf258f 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -723,7 +723,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C type pullCommitList struct { Commits []pull_service.CommitInfo `json:"commits"` LastReviewCommitSha string `json:"last_review_commit_sha"` - Locale map[string]string `json:"locale"` + Locale map[string]any `json:"locale"` } // GetPullCommits get all commits for given pull request @@ -741,7 +741,7 @@ func GetPullCommits(ctx *context.Context) { } // Get the needed locale - resp.Locale = map[string]string{ + resp.Locale = map[string]any{ "lang": ctx.Locale.Language(), "show_all_commits": ctx.Tr("repo.pulls.show_all_commits"), "stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)), diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index b93460d1696..217f2dea6d0 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -219,9 +219,9 @@ func SubmitReview(ctx *context.Context) { if issue.IsPoster(ctx.Doer.ID) { var translated string if reviewType == issues_model.ReviewTypeApprove { - translated = ctx.Tr("repo.issues.review.self.approval") + translated = ctx.Locale.TrString("repo.issues.review.self.approval") } else { - translated = ctx.Tr("repo.issues.review.self.rejection") + translated = ctx.Locale.TrString("repo.issues.review.self.rejection") } ctx.Flash.Error(translated) diff --git a/routers/web/repo/setting/avatar.go b/routers/web/repo/setting/avatar.go index 02c807b775d..44468d2666c 100644 --- a/routers/web/repo/setting/avatar.go +++ b/routers/web/repo/setting/avatar.go @@ -38,7 +38,7 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { defer r.Close() if form.Avatar.Size > setting.Avatar.MaxFileSize { - return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024)) + return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024)) } data, err := io.ReadAll(r) @@ -47,7 +47,7 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { } st := typesniffer.DetectContentType(data) if !(st.IsImage() && !st.IsSvgImage()) { - return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) + return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_not_a_image")) } if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil { return fmt.Errorf("UploadAvatar: %w", err) diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 98d6977b81f..85068f0ab2a 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -68,7 +68,7 @@ func SettingsProtectedBranch(c *context.Context) { } c.Data["PageIsSettingsBranches"] = true - c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + rule.RuleName + c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName users, err := access_model.GetRepoReaders(c, c.Repo.Repository) if err != nil { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 75051d19959..15f22237a8e 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -739,7 +739,7 @@ func checkHomeCodeViewable(ctx *context.Context) { } } - ctx.NotFound("Home", fmt.Errorf(ctx.Tr("units.error.no_unit_allowed_repo"))) + ctx.NotFound("Home", fmt.Errorf(ctx.Locale.TrString("units.error.no_unit_allowed_repo"))) } func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 5e7b971e67b..49e95faaba2 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -714,7 +714,7 @@ func NewWikiPost(ctx *context.Context) { wikiName := wiki_service.UserTitleToWebPath("", form.Title) if len(form.Message) == 0 { - form.Message = ctx.Tr("repo.editor.add", form.Title) + form.Message = ctx.Locale.TrString("repo.editor.add", form.Title) } if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.Message); err != nil { @@ -766,7 +766,7 @@ func EditWikiPost(ctx *context.Context) { newWikiName := wiki_service.UserTitleToWebPath("", form.Title) if len(form.Message) == 0 { - form.Message = ctx.Tr("repo.editor.update", form.Title) + form.Message = ctx.Locale.TrString("repo.editor.update", form.Title) } if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil { diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 83fc4d7162f..b7abbcbc002 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -85,7 +85,7 @@ func Dashboard(ctx *context.Context) { page = 1 } - ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Tr("dashboard") + ctx.Data["Title"] = ctxUser.DisplayName() + " - " + ctx.Locale.TrString("dashboard") ctx.Data["PageIsDashboard"] = true ctx.Data["PageIsNews"] = true cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 95b350528c0..24a807d518f 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -126,7 +126,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser * defer fr.Close() if form.Avatar.Size > setting.Avatar.MaxFileSize { - return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024)) + return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024)) } data, err := io.ReadAll(fr) @@ -136,7 +136,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser * st := typesniffer.DetectContentType(data) if !(st.IsImage() && !st.IsSvgImage()) { - return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) + return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_not_a_image")) } if err = user_service.UploadAvatar(ctx, ctxUser, data); err != nil { return fmt.Errorf("UploadAvatar: %w", err) @@ -389,7 +389,7 @@ func UpdateUserLang(ctx *context.Context) { middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0) log.Trace("User settings updated: %s", ctx.Doer.Name) - ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).Tr("settings.update_language_success")) + ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).TrString("settings.update_language_success")) ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") } diff --git a/routers/web/user/task.go b/routers/web/user/task.go index f35f40e6a0f..bec68c5f209 100644 --- a/routers/web/user/task.go +++ b/routers/web/user/task.go @@ -39,7 +39,7 @@ func TaskStatus(ctx *context.Context) { Args: []any{task.Message}, } } - message = ctx.Tr(translatableMessage.Format, translatableMessage.Args...) + message = ctx.Locale.TrString(translatableMessage.Format, translatableMessage.Args...) } ctx.JSON(http.StatusOK, map[string]any{ diff --git a/routers/web/web.go b/routers/web/web.go index 92cf5132b45..ba5c86cc7e3 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -155,7 +155,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont if ctx.Doer.MustChangePassword { if ctx.Req.URL.Path != "/user/settings/change_password" { if strings.HasPrefix(ctx.Req.UserAgent(), "git") { - ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password")) + ctx.Error(http.StatusUnauthorized, ctx.Locale.TrString("auth.must_change_password")) return } ctx.Data["Title"] = ctx.Tr("auth.must_change_password") diff --git a/services/cron/setting.go b/services/cron/setting.go index 0656307cba0..6dad88830ab 100644 --- a/services/cron/setting.go +++ b/services/cron/setting.go @@ -70,7 +70,7 @@ func (b *BaseConfig) DoNoticeOnSuccess() bool { // 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 translation.Locale, name, status, doer string, args ...any) string { realArgs := make([]any, 0, len(args)+2) - realArgs = append(realArgs, locale.Tr("admin.dashboard."+name)) + realArgs = append(realArgs, locale.TrString("admin.dashboard."+name)) if doer == "" { realArgs = append(realArgs, "(Cron)") } else { @@ -80,7 +80,7 @@ func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer realArgs = append(realArgs, args...) } if doer == "" { - return locale.Tr("admin.dashboard.cron."+status, realArgs...) + return locale.TrString("admin.dashboard.cron."+status, realArgs...) } - return locale.Tr("admin.dashboard.task."+status, realArgs...) + return locale.TrString("admin.dashboard.task."+status, realArgs...) } diff --git a/services/cron/tasks.go b/services/cron/tasks.go index f0956a97d88..f8a7444c49e 100644 --- a/services/cron/tasks.go +++ b/services/cron/tasks.go @@ -159,7 +159,7 @@ func RegisterTask(name string, config Config, fun func(context.Context, *user_mo log.Debug("Registering task: %s", name) i18nKey := "admin.dashboard." + name - if value := translation.NewLocale("en-US").Tr(i18nKey); value == i18nKey { + if value := translation.NewLocale("en-US").TrString(i18nKey); value == i18nKey { return fmt.Errorf("translation is missing for task %q, please add translation for %q", name, i18nKey) } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 60fa0ab3630..98d556b9461 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -314,7 +314,7 @@ func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) bind errs = append(errs, binding.Error{ FieldNames: []string{"Channel"}, Classification: "", - Message: ctx.Tr("repo.settings.add_webhook.invalid_channel_name"), + Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_channel_name"), }) } return middleware.Validate(errs, ctx.Data, f, ctx.Locale) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index ca27336f926..38973ea9352 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -94,7 +94,7 @@ func SendActivateAccountMail(locale translation.Locale, u *user_model.User) { // No mail service configured return } - sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.activate_account"), "activate account") + sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.activate_account"), "activate account") } // SendResetPasswordMail sends a password reset mail to the user @@ -104,7 +104,7 @@ func SendResetPasswordMail(u *user_model.User) { return } locale := translation.NewLocale(u.Language) - sendUserMail(u.Language, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.Tr("mail.reset_password"), "recover account") + sendUserMail(u.Language, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.reset_password"), "recover account") } // SendActivateEmailMail sends confirmation email to confirm new email address @@ -130,7 +130,7 @@ func SendActivateEmailMail(u *user_model.User, email string) { return } - msg := NewMessage(email, locale.Tr("mail.activate_email"), content.String()) + msg := NewMessage(email, locale.TrString("mail.activate_email"), content.String()) msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID) SendAsync(msg) @@ -158,7 +158,7 @@ func SendRegisterNotifyMail(u *user_model.User) { return } - msg := NewMessage(u.Email, locale.Tr("mail.register_notify"), content.String()) + msg := NewMessage(u.Email, locale.TrString("mail.register_notify"), content.String()) msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID) SendAsync(msg) @@ -173,7 +173,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) locale := translation.NewLocale(u.Language) repoName := repo.FullName() - subject := locale.Tr("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName) + subject := locale.TrString("mail.repo.collaborator.added.subject", doer.DisplayName(), repoName) data := map[string]any{ "locale": locale, "Subject": subject, diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 5e8e5b6af38..6682774a04b 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -68,7 +68,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo return } - subject := locale.Tr("mail.release.new.subject", rel.TagName, rel.Repo.FullName()) + subject := locale.TrString("mail.release.new.subject", rel.TagName, rel.Repo.FullName()) mailMeta := map[string]any{ "locale": locale, "Release": rel, diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index b89dcd43b55..e0d55bb1200 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -56,11 +56,11 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U content bytes.Buffer ) - destination := locale.Tr("mail.repo.transfer.to_you") - subject := locale.Tr("mail.repo.transfer.subject_to_you", doer.DisplayName(), repo.FullName()) + destination := locale.TrString("mail.repo.transfer.to_you") + subject := locale.TrString("mail.repo.transfer.subject_to_you", doer.DisplayName(), repo.FullName()) if newOwner.IsOrganization() { destination = newOwner.DisplayName() - subject = locale.Tr("mail.repo.transfer.subject_to", doer.DisplayName(), repo.FullName(), destination) + subject = locale.TrString("mail.repo.transfer.subject_to", doer.DisplayName(), repo.FullName(), destination) } data := map[string]any{ diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go index ab32beeface..ceecefa50fa 100644 --- a/services/mailer/mail_team_invite.go +++ b/services/mailer/mail_team_invite.go @@ -50,7 +50,7 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod inviteURL = fmt.Sprintf("%suser/login?redirect_to=%s", setting.AppURL, inviteRedirect) } - subject := locale.Tr("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName()) + subject := locale.TrString("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName()) mailMeta := map[string]any{ "locale": locale, "Inviter": inviter, diff --git a/templates/mail/issue/assigned.tmpl b/templates/mail/issue/assigned.tmpl index d02ea399189..e80bd2fc31c 100644 --- a/templates/mail/issue/assigned.tmpl +++ b/templates/mail/issue/assigned.tmpl @@ -13,9 +13,9 @@

{{if .IsPull}} - {{.locale.Tr "mail.issue_assigned.pull" .Doer.Name $link $repo_url | Str2html}} + {{.locale.Tr "mail.issue_assigned.pull" .Doer.Name ($link|Safe) ($repo_url|Safe)}} {{else}} - {{.locale.Tr "mail.issue_assigned.issue" .Doer.Name $link $repo_url | Str2html}} + {{.locale.Tr "mail.issue_assigned.issue" .Doer.Name ($link|Safe) ($repo_url|Safe)}} {{end}}

diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 7ec48c67344..582e9864fb3 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -56,18 +56,18 @@ {{$mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix ctx.Locale}} {{if .Issue.OriginalAuthor}} {{.Issue.OriginalAuthor}} - {{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}} + {{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}} {{else}} {{.Issue.PullRequest.Merger.GetDisplayName}} - {{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}} + {{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}} {{end}} {{else}} {{if .Issue.OriginalAuthor}} - {{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref | Safe}} + {{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}} {{else}} {{.Issue.Poster.GetDisplayName}} - {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref | Safe}} + {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}} {{end}} diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index 1148b3ad399..2d69dfcfd73 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -309,7 +309,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) { // all groups the user is a member of, the user filter is modified accordingly inside // the addAuthSourceLDAP based on the value of the groupFilter u := otherLDAPUsers[0] - testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect")) + testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) auth.SyncExternalUsers(context.Background(), true) @@ -362,7 +362,7 @@ func TestLDAPUserSigninFailed(t *testing.T) { addAuthSourceLDAP(t, "", "") u := otherLDAPUsers[0] - testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect")) + testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) } func TestLDAPUserSSHKeySync(t *testing.T) { diff --git a/tests/integration/branches_test.go b/tests/integration/branches_test.go index 99d7eef7069..e148fe2d6f4 100644 --- a/tests/integration/branches_test.go +++ b/tests/integration/branches_test.go @@ -37,7 +37,7 @@ func TestUndoDeleteBranch(t *testing.T) { htmlDoc, name := branchAction(t, ".restore-branch-button") assert.Contains(t, htmlDoc.doc.Find(".ui.positive.message").Text(), - translation.NewLocale("en-US").Tr("repo.branch.restore_success", name), + translation.NewLocale("en-US").TrString("repo.branch.restore_success", name), ) }) } @@ -46,7 +46,7 @@ func deleteBranch(t *testing.T) { htmlDoc, name := branchAction(t, ".delete-branch-button") assert.Contains(t, htmlDoc.doc.Find(".ui.positive.message").Text(), - translation.NewLocale("en-US").Tr("repo.branch.deletion_success", name), + translation.NewLocale("en-US").TrString("repo.branch.deletion_success", name), ) } diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 5205df2f8e7..a04b4c98cd5 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -219,7 +219,7 @@ func TestCantMergeWorkInProgress(t *testing.T) { text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text()) assert.NotEmpty(t, text, "Can't find WIP text") - assert.Contains(t, text, translation.NewLocale("en-US").Tr("repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text") + assert.Contains(t, text, translation.NewLocale("en-US").TrString("repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text") assert.Contains(t, text, "[wip]", "Unable to find WIP text") }) } diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 42d0d00e786..04de0c123fa 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -86,7 +86,7 @@ func TestCreateRelease(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.stable"), 4) } func TestCreateReleasePreRelease(t *testing.T) { @@ -95,7 +95,7 @@ func TestCreateReleasePreRelease(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.prerelease"), 4) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.prerelease"), 4) } func TestCreateReleaseDraft(t *testing.T) { @@ -104,7 +104,7 @@ func TestCreateReleaseDraft(t *testing.T) { session := loginUser(t, "user2") createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.draft"), 4) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.draft"), 4) } func TestCreateReleasePaging(t *testing.T) { @@ -124,11 +124,11 @@ func TestCreateReleasePaging(t *testing.T) { } createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true) - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").Tr("repo.release.draft"), 10) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").TrString("repo.release.draft"), 10) // Check that user4 does not see draft and still see 10 latest releases session2 := loginUser(t, "user4") - checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").Tr("repo.release.stable"), 10) + checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").TrString("repo.release.stable"), 10) } func TestViewReleaseListNoLogin(t *testing.T) { diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 91674ddc82d..baa8da4b759 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -52,37 +52,37 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) { OldRefSubURL: "branch/master", NewBranch: "feature/test1", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test1"), }, { OldRefSubURL: "branch/master", NewBranch: "", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.require_error"), + FlashMessage: translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.require_error"), }, { OldRefSubURL: "branch/master", NewBranch: "feature=test1", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature=test1"), }, { OldRefSubURL: "branch/master", NewBranch: strings.Repeat("b", 101), ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.max_size_error", "100"), + FlashMessage: translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.max_size_error", "100"), }, { OldRefSubURL: "branch/master", NewBranch: "master", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_already_exists", "master"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.branch_already_exists", "master"), }, { OldRefSubURL: "branch/master", NewBranch: "master/test", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_name_conflict", "master/test", "master"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.branch_name_conflict", "master/test", "master"), }, { OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0", @@ -93,21 +93,21 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) { OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", NewBranch: "feature/test3", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test3"), }, { OldRefSubURL: "branch/master", NewBranch: "v1.0.0", CreateRelease: "v1.0.0", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.tag_collision", "v1.0.0"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.tag_collision", "v1.0.0"), }, { OldRefSubURL: "tag/v1.0.0", NewBranch: "feature/test4", CreateRelease: "v1.0.1", ExpectedStatus: http.StatusSeeOther, - FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"), + FlashMessage: translation.NewLocale("en-US").TrString("repo.branch.create_success", "feature/test4"), }, } for _, test := range tests { diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 2584b88f651..77e19bba963 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -49,10 +49,10 @@ func TestSignin(t *testing.T) { password string message string }{ - {username: "wrongUsername", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, - {username: "wrongUsername", password: "password", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, - {username: "user15", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, - {username: "user1@example.com", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")}, + {username: "wrongUsername", password: "wrongPassword", message: translation.NewLocale("en-US").TrString("form.username_password_incorrect")}, + {username: "wrongUsername", password: "password", message: translation.NewLocale("en-US").TrString("form.username_password_incorrect")}, + {username: "user15", password: "wrongPassword", message: translation.NewLocale("en-US").TrString("form.username_password_incorrect")}, + {username: "user1@example.com", password: "wrongPassword", message: translation.NewLocale("en-US").TrString("form.username_password_incorrect")}, } for _, s := range samples { diff --git a/tests/integration/signup_test.go b/tests/integration/signup_test.go index f983f98ad8b..859f873f851 100644 --- a/tests/integration/signup_test.go +++ b/tests/integration/signup_test.go @@ -68,9 +68,9 @@ func TestSignupEmail(t *testing.T) { wantStatus int wantMsg string }{ - {"exampleUser@example.com\r\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, - {"exampleUser@example.com\r", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, - {"exampleUser@example.com\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")}, + {"exampleUser@example.com\r\n", http.StatusOK, translation.NewLocale("en-US").TrString("form.email_invalid")}, + {"exampleUser@example.com\r", http.StatusOK, translation.NewLocale("en-US").TrString("form.email_invalid")}, + {"exampleUser@example.com\n", http.StatusOK, translation.NewLocale("en-US").TrString("form.email_invalid")}, {"exampleUser@example.com", http.StatusSeeOther, ""}, } diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index d8e4c64e854..c30733b1b07 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -85,7 +85,7 @@ func TestRenameInvalidUsername(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) assert.Contains(t, htmlDoc.doc.Find(".ui.negative.message").Text(), - translation.NewLocale("en-US").Tr("form.username_error"), + translation.NewLocale("en-US").TrString("form.username_error"), ) unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername}) @@ -147,7 +147,7 @@ func TestRenameReservedUsername(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) assert.Contains(t, htmlDoc.doc.Find(".ui.negative.message").Text(), - translation.NewLocale("en-US").Tr("user.form.name_reserved", reservedUsername), + translation.NewLocale("en-US").TrString("user.form.name_reserved", reservedUsername), ) unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername})