Refactor locale&string&template related code (#29165)
Clarify when "string" should be used (and be escaped), and when "template.HTML" should be used (no need to escape) And help PRs like #29059 , to render the error messages correctly.
This commit is contained in:
@ -97,7 +97,7 @@ func (r *ActionRunner) StatusName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ActionRunner) StatusLocaleName(lang translation.Locale) 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 {
|
func (r *ActionRunner) IsOnline() bool {
|
||||||
|
@ -41,7 +41,7 @@ func (s Status) String() string {
|
|||||||
|
|
||||||
// LocaleString returns the locale string name of the Status
|
// LocaleString returns the locale string name of the Status
|
||||||
func (s Status) LocaleString(lang translation.Locale) string {
|
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
|
// IsDone returns whether the Status is final
|
||||||
|
@ -194,7 +194,7 @@ func (status *CommitStatus) APIURL(ctx context.Context) string {
|
|||||||
|
|
||||||
// LocaleString returns the locale string name of the Status
|
// LocaleString returns the locale string name of the Status
|
||||||
func (status *CommitStatus) LocaleString(lang translation.Locale) string {
|
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
|
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc
|
||||||
|
@ -210,12 +210,12 @@ const (
|
|||||||
|
|
||||||
// LocaleString returns the locale string name of the role
|
// LocaleString returns the locale string name of the role
|
||||||
func (r RoleInRepo) LocaleString(lang translation.Locale) string {
|
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
|
// LocaleHelper returns the locale tooltip of the role
|
||||||
func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
|
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.
|
// Comment represents a comment in commit and issue page.
|
||||||
|
@ -17,13 +17,13 @@ const (
|
|||||||
func (o OwnerType) LocaleString(locale translation.Locale) string {
|
func (o OwnerType) LocaleString(locale translation.Locale) string {
|
||||||
switch o {
|
switch o {
|
||||||
case OwnerTypeSystemGlobal:
|
case OwnerTypeSystemGlobal:
|
||||||
return locale.Tr("concept_system_global")
|
return locale.TrString("concept_system_global")
|
||||||
case OwnerTypeIndividual:
|
case OwnerTypeIndividual:
|
||||||
return locale.Tr("concept_user_individual")
|
return locale.TrString("concept_user_individual")
|
||||||
case OwnerTypeRepository:
|
case OwnerTypeRepository:
|
||||||
return locale.Tr("concept_code_repository")
|
return locale.TrString("concept_code_repository")
|
||||||
case OwnerTypeOrganization:
|
case OwnerTypeOrganization:
|
||||||
return locale.Tr("concept_user_organization")
|
return locale.TrString("concept_user_organization")
|
||||||
}
|
}
|
||||||
return locale.Tr("unknown")
|
return locale.TrString("unknown")
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"html/template"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -121,15 +122,15 @@ func Generate(n int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildComplexityError builds the error message when password complexity checks fail
|
// 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
|
var buffer bytes.Buffer
|
||||||
buffer.WriteString(locale.Tr("form.password_complexity"))
|
buffer.WriteString(locale.TrString("form.password_complexity"))
|
||||||
buffer.WriteString("<ul>")
|
buffer.WriteString("<ul>")
|
||||||
for _, c := range requiredList {
|
for _, c := range requiredList {
|
||||||
buffer.WriteString("<li>")
|
buffer.WriteString("<li>")
|
||||||
buffer.WriteString(locale.Tr(c.TrNameOne))
|
buffer.WriteString(locale.TrString(c.TrNameOne))
|
||||||
buffer.WriteString("</li>")
|
buffer.WriteString("</li>")
|
||||||
}
|
}
|
||||||
buffer.WriteString("</ul>")
|
buffer.WriteString("</ul>")
|
||||||
return buffer.String()
|
return template.HTML(buffer.String())
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ func (e *escapeStreamer) ambiguousRune(r, c rune) error {
|
|||||||
Val: "ambiguous-code-point",
|
Val: "ambiguous-code-point",
|
||||||
}, html.Attribute{
|
}, html.Attribute{
|
||||||
Key: "data-tooltip-content",
|
Key: "data-tooltip-content",
|
||||||
Val: e.locale.Tr("repo.ambiguous_character", r, c),
|
Val: e.locale.TrString("repo.ambiguous_character", r, c),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
|||||||
// NotFound handles 404s for APIContext
|
// NotFound handles 404s for APIContext
|
||||||
// String will replace message, errors will be added to a slice
|
// String will replace message, errors will be added to a slice
|
||||||
func (ctx *APIContext) NotFound(objs ...any) {
|
func (ctx *APIContext) NotFound(objs ...any) {
|
||||||
message := ctx.Tr("error.not_found")
|
message := ctx.Locale.TrString("error.not_found")
|
||||||
var errors []string
|
var errors []string
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
// Ignore nil
|
// Ignore nil
|
||||||
|
@ -6,6 +6,7 @@ package context
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"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...)
|
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...)
|
return b.Locale.TrN(cnt, key1, keyN, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"html"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"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{}
|
type webContextKeyType struct{}
|
||||||
|
|
||||||
var WebContextKey = webContextKeyType{}
|
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
|
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) {
|
func (ctx *Context) JSONError(msg any) {
|
||||||
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg})
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
// 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 {
|
if form != nil {
|
||||||
middleware.AssignForm(form, ctx.Data)
|
middleware.AssignForm(form, ctx.Data)
|
||||||
}
|
}
|
||||||
ctx.Flash.ErrorMsg = msg
|
ctx.Flash.Error(msg, true)
|
||||||
ctx.Data["Flash"] = ctx.Flash
|
|
||||||
ctx.HTML(http.StatusOK, tpl)
|
ctx.HTML(http.StatusOK, tpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -85,7 +86,7 @@ func (r *Repository) CanCreateBranch() bool {
|
|||||||
func RepoMustNotBeArchived() func(ctx *Context) {
|
func RepoMustNotBeArchived() func(ctx *Context) {
|
||||||
return func(ctx *Context) {
|
return func(ctx *Context) {
|
||||||
if ctx.Repo.Repository.IsArchived {
|
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")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +123,9 @@ func guessDelimiter(data []byte) rune {
|
|||||||
func FormatError(err error, locale translation.Locale) (string, error) {
|
func FormatError(err error, locale translation.Locale) (string, error) {
|
||||||
if perr, ok := err.(*stdcsv.ParseError); ok {
|
if perr, ok := err.(*stdcsv.ParseError); ok {
|
||||||
if perr.Err == stdcsv.ErrFieldCount {
|
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
|
return "", err
|
||||||
|
@ -804,7 +804,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// indicate that in the text by appending (comment)
|
// indicate that in the text by appending (comment)
|
||||||
if m[4] != -1 && m[5] != -1 {
|
if m[4] != -1 && m[5] != -1 {
|
||||||
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
|
||||||
text += " " + locale.Tr("repo.from_comment")
|
text += " " + locale.TrString("repo.from_comment")
|
||||||
} else {
|
} else {
|
||||||
text += " (comment)"
|
text += " (comment)"
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]str
|
|||||||
details.SetAttributeString(k, []byte(v))
|
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)
|
details.AppendChild(details, summary)
|
||||||
ul := ast.NewList('-')
|
ul := ast.NewList('-')
|
||||||
details.AppendChild(details, ul)
|
details.AppendChild(details, ul)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package migration
|
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)
|
type Messenger func(key string, args ...any)
|
||||||
|
|
||||||
// NilMessenger represents an empty formatting function
|
// NilMessenger represents an empty formatting function
|
||||||
|
@ -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.
|
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
|
||||||
"Eval": Eval,
|
"Eval": Eval,
|
||||||
"Safe": Safe,
|
"Safe": Safe,
|
||||||
"Escape": html.EscapeString,
|
"Escape": Escape,
|
||||||
"QueryEscape": url.QueryEscape,
|
"QueryEscape": url.QueryEscape,
|
||||||
"JSEscape": template.JSEscapeString,
|
"JSEscape": template.JSEscapeString,
|
||||||
"Str2html": Str2html, // TODO: rename it to SanitizeHTML
|
"Str2html": Str2html, // TODO: rename it to SanitizeHTML
|
||||||
@ -159,7 +159,7 @@ func NewFuncMap() template.FuncMap {
|
|||||||
"RenderCodeBlock": RenderCodeBlock,
|
"RenderCodeBlock": RenderCodeBlock,
|
||||||
"RenderIssueTitle": RenderIssueTitle,
|
"RenderIssueTitle": RenderIssueTitle,
|
||||||
"RenderEmoji": RenderEmoji,
|
"RenderEmoji": RenderEmoji,
|
||||||
"RenderEmojiPlain": emoji.ReplaceAliases,
|
"RenderEmojiPlain": RenderEmojiPlain,
|
||||||
"ReactionToEmoji": ReactionToEmoji,
|
"ReactionToEmoji": ReactionToEmoji,
|
||||||
|
|
||||||
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
"RenderMarkdownToHtml": RenderMarkdownToHtml,
|
||||||
@ -180,13 +180,45 @@ func NewFuncMap() template.FuncMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safe render raw as HTML
|
// Safe render raw as HTML
|
||||||
func Safe(raw string) template.HTML {
|
func Safe(s any) template.HTML {
|
||||||
return template.HTML(raw)
|
switch v := s.(type) {
|
||||||
|
case string:
|
||||||
|
return template.HTML(v)
|
||||||
|
case template.HTML:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unexpected type %T", s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Str2html render Markdown text to HTML
|
// Str2html sanitizes the input by pre-defined markdown rules
|
||||||
func Str2html(raw string) template.HTML {
|
func Str2html(s any) template.HTML {
|
||||||
return template.HTML(markup.Sanitize(raw))
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
|
||||||
|
@ -28,54 +28,54 @@ func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) {
|
|||||||
switch {
|
switch {
|
||||||
case diff <= 0:
|
case diff <= 0:
|
||||||
diff = 0
|
diff = 0
|
||||||
diffStr = lang.Tr("tool.now")
|
diffStr = lang.TrString("tool.now")
|
||||||
case diff < 2:
|
case diff < 2:
|
||||||
diff = 0
|
diff = 0
|
||||||
diffStr = lang.Tr("tool.1s")
|
diffStr = lang.TrString("tool.1s")
|
||||||
case diff < 1*Minute:
|
case diff < 1*Minute:
|
||||||
diffStr = lang.Tr("tool.seconds", diff)
|
diffStr = lang.TrString("tool.seconds", diff)
|
||||||
diff = 0
|
diff = 0
|
||||||
|
|
||||||
case diff < 2*Minute:
|
case diff < 2*Minute:
|
||||||
diff -= 1 * Minute
|
diff -= 1 * Minute
|
||||||
diffStr = lang.Tr("tool.1m")
|
diffStr = lang.TrString("tool.1m")
|
||||||
case diff < 1*Hour:
|
case diff < 1*Hour:
|
||||||
diffStr = lang.Tr("tool.minutes", diff/Minute)
|
diffStr = lang.TrString("tool.minutes", diff/Minute)
|
||||||
diff -= diff / Minute * Minute
|
diff -= diff / Minute * Minute
|
||||||
|
|
||||||
case diff < 2*Hour:
|
case diff < 2*Hour:
|
||||||
diff -= 1 * Hour
|
diff -= 1 * Hour
|
||||||
diffStr = lang.Tr("tool.1h")
|
diffStr = lang.TrString("tool.1h")
|
||||||
case diff < 1*Day:
|
case diff < 1*Day:
|
||||||
diffStr = lang.Tr("tool.hours", diff/Hour)
|
diffStr = lang.TrString("tool.hours", diff/Hour)
|
||||||
diff -= diff / Hour * Hour
|
diff -= diff / Hour * Hour
|
||||||
|
|
||||||
case diff < 2*Day:
|
case diff < 2*Day:
|
||||||
diff -= 1 * Day
|
diff -= 1 * Day
|
||||||
diffStr = lang.Tr("tool.1d")
|
diffStr = lang.TrString("tool.1d")
|
||||||
case diff < 1*Week:
|
case diff < 1*Week:
|
||||||
diffStr = lang.Tr("tool.days", diff/Day)
|
diffStr = lang.TrString("tool.days", diff/Day)
|
||||||
diff -= diff / Day * Day
|
diff -= diff / Day * Day
|
||||||
|
|
||||||
case diff < 2*Week:
|
case diff < 2*Week:
|
||||||
diff -= 1 * Week
|
diff -= 1 * Week
|
||||||
diffStr = lang.Tr("tool.1w")
|
diffStr = lang.TrString("tool.1w")
|
||||||
case diff < 1*Month:
|
case diff < 1*Month:
|
||||||
diffStr = lang.Tr("tool.weeks", diff/Week)
|
diffStr = lang.TrString("tool.weeks", diff/Week)
|
||||||
diff -= diff / Week * Week
|
diff -= diff / Week * Week
|
||||||
|
|
||||||
case diff < 2*Month:
|
case diff < 2*Month:
|
||||||
diff -= 1 * Month
|
diff -= 1 * Month
|
||||||
diffStr = lang.Tr("tool.1mon")
|
diffStr = lang.TrString("tool.1mon")
|
||||||
case diff < 1*Year:
|
case diff < 1*Year:
|
||||||
diffStr = lang.Tr("tool.months", diff/Month)
|
diffStr = lang.TrString("tool.months", diff/Month)
|
||||||
diff -= diff / Month * Month
|
diff -= diff / Month * Month
|
||||||
|
|
||||||
case diff < 2*Year:
|
case diff < 2*Year:
|
||||||
diff -= 1 * Year
|
diff -= 1 * Year
|
||||||
diffStr = lang.Tr("tool.1y")
|
diffStr = lang.TrString("tool.1y")
|
||||||
default:
|
default:
|
||||||
diffStr = lang.Tr("tool.years", diff/Year)
|
diffStr = lang.TrString("tool.years", diff/Year)
|
||||||
diff -= (diff / Year) * Year
|
diff -= (diff / Year) * Year
|
||||||
}
|
}
|
||||||
return diff, diffStr
|
return diff, diffStr
|
||||||
@ -97,10 +97,10 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
|
|||||||
diff := now.Unix() - then.Unix()
|
diff := now.Unix() - then.Unix()
|
||||||
|
|
||||||
if then.After(now) {
|
if then.After(now) {
|
||||||
return lang.Tr("tool.future")
|
return lang.TrString("tool.future")
|
||||||
}
|
}
|
||||||
if diff == 0 {
|
if diff == 0 {
|
||||||
return lang.Tr("tool.now")
|
return lang.TrString("tool.now")
|
||||||
}
|
}
|
||||||
|
|
||||||
var timeStr, diffStr string
|
var timeStr, diffStr string
|
||||||
@ -115,7 +115,7 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string {
|
|||||||
return strings.TrimPrefix(timeStr, ", ")
|
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")
|
friendlyText := then.Format("2006-01-02 15:04:05 -07:00")
|
||||||
|
|
||||||
// document: https://github.com/github/relative-time-element
|
// document: https://github.com/github/relative-time-element
|
||||||
|
@ -4,26 +4,25 @@
|
|||||||
package i18n
|
package i18n
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultLocales = NewLocaleStore()
|
var DefaultLocales = NewLocaleStore()
|
||||||
|
|
||||||
type Locale interface {
|
type Locale interface {
|
||||||
// Tr translates a given key and arguments for a language
|
// TrString translates a given key and arguments for a language
|
||||||
Tr(trKey string, trArgs ...any) string
|
TrString(trKey string, trArgs ...any) string
|
||||||
// Has reports if a locale has a translation for a given key
|
// TrHTML translates a given key and arguments for a language, string arguments are escaped to HTML
|
||||||
Has(trKey string) bool
|
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
|
// LocaleStore provides the functions common to all locale stores
|
||||||
type LocaleStore interface {
|
type LocaleStore interface {
|
||||||
io.Closer
|
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 sets the default language to fall back to
|
||||||
SetDefaultLang(lang string)
|
SetDefaultLang(lang string)
|
||||||
// ListLangNameDesc provides paired slices of language names to descriptors
|
// ListLangNameDesc provides paired slices of language names to descriptors
|
||||||
@ -45,7 +44,7 @@ func ResetDefaultLocales() {
|
|||||||
DefaultLocales = NewLocaleStore()
|
DefaultLocales = NewLocaleStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLocales returns the locale from the default locales
|
// GetLocale returns the locale from the default locales
|
||||||
func GetLocale(lang string) (Locale, bool) {
|
func GetLocale(lang string) (Locale, bool) {
|
||||||
return DefaultLocales.Locale(lang)
|
return DefaultLocales.Locale(lang)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ fmt = %[1]s %[2]s
|
|||||||
|
|
||||||
[section]
|
[section]
|
||||||
sub = Sub String
|
sub = Sub String
|
||||||
mixed = test value; <span style="color: red\; background: none;">more text</span>
|
mixed = test value; <span style="color: red\; background: none;">%s</span>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
testData2 := []byte(`
|
testData2 := []byte(`
|
||||||
@ -32,29 +32,33 @@ sub = Changed Sub String
|
|||||||
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
|
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
|
||||||
ls.SetDefaultLang("lang1")
|
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)
|
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)
|
assert.Equal(t, "b a", result)
|
||||||
|
|
||||||
result = ls.Tr("lang1", "section.sub")
|
result = lang1.TrString("section.sub")
|
||||||
assert.Equal(t, "Sub String", result)
|
assert.Equal(t, "Sub String", result)
|
||||||
|
|
||||||
result = ls.Tr("lang2", "section.sub")
|
result = lang2.TrString("section.sub")
|
||||||
assert.Equal(t, "Changed Sub String", result)
|
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)
|
assert.Equal(t, "Dot Name", result)
|
||||||
|
|
||||||
result = ls.Tr("lang2", "section.mixed")
|
result2 := lang2.TrHTML("section.mixed", "a&b")
|
||||||
assert.Equal(t, `test value; <span style="color: red; background: none;">more text</span>`, result)
|
assert.EqualValues(t, `test value; <span style="color: red; background: none;">a&b</span>`, result2)
|
||||||
|
|
||||||
langs, descs := ls.ListLangNameDesc()
|
langs, descs := ls.ListLangNameDesc()
|
||||||
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
|
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
|
||||||
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
|
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
|
||||||
|
|
||||||
found := ls.Has("lang1", "no-such")
|
found := lang1.HasKey("no-such")
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.NoError(t, ls.Close())
|
assert.NoError(t, ls.Close())
|
||||||
}
|
}
|
||||||
@ -72,9 +76,10 @@ c=22
|
|||||||
|
|
||||||
ls := NewLocaleStore()
|
ls := NewLocaleStore()
|
||||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
|
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
|
||||||
assert.Equal(t, "11", ls.Tr("lang1", "a"))
|
lang1, _ := ls.Locale("lang1")
|
||||||
assert.Equal(t, "21", ls.Tr("lang1", "b"))
|
assert.Equal(t, "11", lang1.TrString("a"))
|
||||||
assert.Equal(t, "22", ls.Tr("lang1", "c"))
|
assert.Equal(t, "21", lang1.TrString("b"))
|
||||||
|
assert.Equal(t, "22", lang1.TrString("c"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocaleStoreQuirks(t *testing.T) {
|
func TestLocaleStoreQuirks(t *testing.T) {
|
||||||
@ -110,8 +115,9 @@ func TestLocaleStoreQuirks(t *testing.T) {
|
|||||||
for _, testData := range testDataList {
|
for _, testData := range testDataList {
|
||||||
ls := NewLocaleStore()
|
ls := NewLocaleStore()
|
||||||
err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
|
err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
|
||||||
|
lang1, _ := ls.Locale("lang1")
|
||||||
assert.NoError(t, err, testData.hint)
|
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())
|
assert.NoError(t, ls.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ package i18n
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"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
|
idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Locale = (*locale)(nil)
|
||||||
|
|
||||||
type localeStore struct {
|
type localeStore struct {
|
||||||
// After initializing has finished, these fields are read-only.
|
// After initializing has finished, these fields are read-only.
|
||||||
langNames []string
|
langNames []string
|
||||||
@ -85,20 +89,6 @@ func (store *localeStore) SetDefaultLang(lang string) {
|
|||||||
store.defaultLang = lang
|
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
|
// Locale returns the locale for the lang or the default language
|
||||||
func (store *localeStore) Locale(lang string) (Locale, bool) {
|
func (store *localeStore) Locale(lang string) (Locale, bool) {
|
||||||
l, found := store.localeMap[lang]
|
l, found := store.localeMap[lang]
|
||||||
@ -113,13 +103,11 @@ func (store *localeStore) Locale(lang string) (Locale, bool) {
|
|||||||
return l, found
|
return l, found
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements io.Closer
|
|
||||||
func (store *localeStore) Close() error {
|
func (store *localeStore) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tr translates content to locale language. fall back to default language.
|
func (l *locale) TrString(trKey string, trArgs ...any) string {
|
||||||
func (l *locale) Tr(trKey string, trArgs ...any) string {
|
|
||||||
format := trKey
|
format := trKey
|
||||||
|
|
||||||
idx, ok := l.store.trKeyToIdxMap[trKey]
|
idx, ok := l.store.trKeyToIdxMap[trKey]
|
||||||
@ -141,8 +129,23 @@ func (l *locale) Tr(trKey string, trArgs ...any) string {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has returns whether a key is present in this locale or not
|
func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
|
||||||
func (l *locale) Has(trKey string) bool {
|
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]
|
idx, ok := l.store.trKeyToIdxMap[trKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package translation
|
package translation
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
)
|
||||||
|
|
||||||
// MockLocale provides a mocked locale without any translations
|
// MockLocale provides a mocked locale without any translations
|
||||||
type MockLocale struct{}
|
type MockLocale struct{}
|
||||||
@ -14,12 +17,16 @@ func (l MockLocale) Language() string {
|
|||||||
return "en"
|
return "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l MockLocale) Tr(s string, _ ...any) string {
|
func (l MockLocale) TrString(s string, _ ...any) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l MockLocale) TrN(_cnt any, key1, _keyN string, _args ...any) string {
|
func (l MockLocale) Tr(s string, a ...any) template.HTML {
|
||||||
return key1
|
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 {
|
func (l MockLocale) PrettyNumber(v any) string {
|
||||||
|
@ -5,6 +5,7 @@ package translation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"html/template"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -27,8 +28,11 @@ var ContextKey any = &contextKey{}
|
|||||||
// Locale represents an interface to translation
|
// Locale represents an interface to translation
|
||||||
type Locale interface {
|
type Locale interface {
|
||||||
Language() string
|
Language() string
|
||||||
Tr(string, ...any) string
|
TrString(string, ...any) string
|
||||||
TrN(cnt any, key1, keyN string, args ...any) string
|
|
||||||
|
Tr(key string, args ...any) template.HTML
|
||||||
|
TrN(cnt any, key1, keyN string, args ...any) template.HTML
|
||||||
|
|
||||||
PrettyNumber(v any) string
|
PrettyNumber(v any) string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +148,8 @@ type locale struct {
|
|||||||
msgPrinter *message.Printer
|
msgPrinter *message.Printer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Locale = (*locale)(nil)
|
||||||
|
|
||||||
// NewLocale return a locale
|
// NewLocale return a locale
|
||||||
func NewLocale(lang string) Locale {
|
func NewLocale(lang string) Locale {
|
||||||
if lock != nil {
|
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
|
// 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
|
var c int64
|
||||||
if t, ok := cnt.(int); ok {
|
if t, ok := cnt.(int); ok {
|
||||||
c = int64(t)
|
c = int64(t)
|
||||||
|
@ -104,40 +104,40 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
|
|||||||
|
|
||||||
trName := field.Tag.Get("locale")
|
trName := field.Tag.Get("locale")
|
||||||
if len(trName) == 0 {
|
if len(trName) == 0 {
|
||||||
trName = l.Tr("form." + field.Name)
|
trName = l.TrString("form." + field.Name)
|
||||||
} else {
|
} else {
|
||||||
trName = l.Tr(trName)
|
trName = l.TrString(trName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch errs[0].Classification {
|
switch errs[0].Classification {
|
||||||
case binding.ERR_REQUIRED:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
case binding.ERR_EMAIL:
|
||||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
data["ErrorMsg"] = trName + l.TrString("form.email_error")
|
||||||
case binding.ERR_URL:
|
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:
|
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:
|
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:
|
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:
|
case validation.ErrUsername:
|
||||||
data["ErrorMsg"] = trName + l.Tr("form.username_error")
|
data["ErrorMsg"] = trName + l.TrString("form.username_error")
|
||||||
case validation.ErrInvalidGroupTeamMap:
|
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:
|
default:
|
||||||
msg := errs[0].Classification
|
msg := errs[0].Classification
|
||||||
if msg != "" && errs[0].Message != "" {
|
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
|
msg += errs[0].Message
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
msg = l.Tr("form.unknown_error")
|
msg = l.TrString("form.unknown_error")
|
||||||
}
|
}
|
||||||
data["ErrorMsg"] = trName + ": " + msg
|
data["ErrorMsg"] = trName + ": " + msg
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "net/url"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
// Flash represents a one time data transfer between two requests.
|
// Flash represents a one time data transfer between two requests.
|
||||||
type Flash struct {
|
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
|
// Error sets error message
|
||||||
func (f *Flash) Error(msg string, current ...bool) {
|
func (f *Flash) Error(msg any, current ...bool) {
|
||||||
f.ErrorMsg = msg
|
f.ErrorMsg = flashMsgStringOrHTML(msg)
|
||||||
f.set("error", msg, current...)
|
f.set("error", f.ErrorMsg, current...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning sets warning message
|
// Warning sets warning message
|
||||||
func (f *Flash) Warning(msg string, current ...bool) {
|
func (f *Flash) Warning(msg any, current ...bool) {
|
||||||
f.WarningMsg = msg
|
f.WarningMsg = flashMsgStringOrHTML(msg)
|
||||||
f.set("warning", msg, current...)
|
f.set("warning", f.WarningMsg, current...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info sets info message
|
// Info sets info message
|
||||||
func (f *Flash) Info(msg string, current ...bool) {
|
func (f *Flash) Info(msg any, current ...bool) {
|
||||||
f.InfoMsg = msg
|
f.InfoMsg = flashMsgStringOrHTML(msg)
|
||||||
f.set("info", msg, current...)
|
f.set("info", f.InfoMsg, current...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success sets success message
|
// Success sets success message
|
||||||
func (f *Flash) Success(msg string, current ...bool) {
|
func (f *Flash) Success(msg any, current ...bool) {
|
||||||
f.SuccessMsg = msg
|
f.SuccessMsg = flashMsgStringOrHTML(msg)
|
||||||
f.set("success", msg, current...)
|
f.set("success", f.SuccessMsg, current...)
|
||||||
}
|
}
|
||||||
|
@ -762,13 +762,13 @@ func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.Ch
|
|||||||
}
|
}
|
||||||
message := ""
|
message := ""
|
||||||
if len(createFiles) != 0 {
|
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 {
|
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 {
|
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")
|
return strings.Trim(message, "\n")
|
||||||
}
|
}
|
||||||
|
@ -395,7 +395,7 @@ func CreateIssueComment(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,16 +210,16 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
|
|||||||
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
|
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
|
||||||
if util.IsEmptyString(form.SSPISeparatorReplacement) {
|
if util.IsEmptyString(form.SSPISeparatorReplacement) {
|
||||||
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
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) {
|
if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
|
||||||
ctx.Data["Err_SSPISeparatorReplacement"] = true
|
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) {
|
if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) {
|
||||||
ctx.Data["Err_SSPIDefaultLanguage"] = true
|
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{
|
return &sspi.Source{
|
||||||
|
@ -37,7 +37,7 @@ func ForgotPasswd(ctx *context.Context) {
|
|||||||
ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
|
ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title")
|
||||||
|
|
||||||
if setting.MailService == nil {
|
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.Data["IsResetDisable"] = true
|
||||||
ctx.HTML(http.StatusOK, tplForgotPassword)
|
ctx.HTML(http.StatusOK, tplForgotPassword)
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,7 @@ package feed
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -79,119 +80,120 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
|
|
||||||
// title
|
// title
|
||||||
title = act.ActUser.DisplayName() + " "
|
title = act.ActUser.DisplayName() + " "
|
||||||
|
var titleExtra template.HTML
|
||||||
switch act.OpType {
|
switch act.OpType {
|
||||||
case activities_model.ActionCreateRepo:
|
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)
|
link.Href = act.GetRepoAbsoluteLink(ctx)
|
||||||
case activities_model.ActionRenameRepo:
|
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)
|
link.Href = act.GetRepoAbsoluteLink(ctx)
|
||||||
case activities_model.ActionCommitRepo:
|
case activities_model.ActionCommitRepo:
|
||||||
link.Href = toBranchLink(ctx, act)
|
link.Href = toBranchLink(ctx, act)
|
||||||
if len(act.Content) != 0 {
|
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 {
|
} 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:
|
case activities_model.ActionCreateIssue:
|
||||||
link.Href = toIssueLink(ctx, act)
|
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:
|
case activities_model.ActionCreatePullRequest:
|
||||||
link.Href = toPullLink(ctx, act)
|
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:
|
case activities_model.ActionTransferRepo:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
case activities_model.ActionPushTag:
|
||||||
link.Href = toTagLink(ctx, act)
|
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:
|
case activities_model.ActionCommentIssue:
|
||||||
issueLink := toIssueLink(ctx, act)
|
issueLink := toIssueLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = issueLink
|
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:
|
case activities_model.ActionMergePullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
pullLink := toPullLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = pullLink
|
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:
|
case activities_model.ActionAutoMergePullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
pullLink := toPullLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = pullLink
|
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:
|
case activities_model.ActionCloseIssue:
|
||||||
issueLink := toIssueLink(ctx, act)
|
issueLink := toIssueLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = issueLink
|
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:
|
case activities_model.ActionReopenIssue:
|
||||||
issueLink := toIssueLink(ctx, act)
|
issueLink := toIssueLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = issueLink
|
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:
|
case activities_model.ActionClosePullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
pullLink := toPullLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = pullLink
|
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:
|
case activities_model.ActionReopenPullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
pullLink := toPullLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = pullLink
|
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:
|
case activities_model.ActionDeleteTag:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
case activities_model.ActionDeleteBranch:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
case activities_model.ActionMirrorSyncPush:
|
||||||
srcLink := toSrcLink(ctx, act)
|
srcLink := toSrcLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = srcLink
|
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:
|
case activities_model.ActionMirrorSyncCreate:
|
||||||
srcLink := toSrcLink(ctx, act)
|
srcLink := toSrcLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = srcLink
|
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:
|
case activities_model.ActionMirrorSyncDelete:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
case activities_model.ActionApprovePullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
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:
|
case activities_model.ActionRejectPullRequest:
|
||||||
pullLink := toPullLink(ctx, act)
|
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:
|
case activities_model.ActionCommentPull:
|
||||||
pullLink := toPullLink(ctx, act)
|
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:
|
case activities_model.ActionPublishRelease:
|
||||||
releaseLink := toReleaseLink(ctx, act)
|
releaseLink := toReleaseLink(ctx, act)
|
||||||
if link.Href == "#" {
|
if link.Href == "#" {
|
||||||
link.Href = releaseLink
|
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:
|
case activities_model.ActionPullReviewDismissed:
|
||||||
pullLink := toPullLink(ctx, act)
|
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:
|
case activities_model.ActionStarRepo:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
case activities_model.ActionWatchRepo:
|
||||||
link.Href = act.GetRepoAbsoluteLink(ctx)
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unknown action type: %v", act.OpType)
|
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:
|
case activities_model.ActionCloseIssue, activities_model.ActionReopenIssue, activities_model.ActionClosePullRequest, activities_model.ActionReopenPullRequest:
|
||||||
desc = act.GetIssueTitle(ctx)
|
desc = act.GetIssueTitle(ctx)
|
||||||
case activities_model.ActionPullReviewDismissed:
|
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 {
|
if len(content) == 0 {
|
||||||
@ -241,7 +243,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = append(items, &feeds.Item{
|
items = append(items, &feeds.Item{
|
||||||
Title: title,
|
Title: template.HTMLEscapeString(title) + string(titleExtra),
|
||||||
Link: link,
|
Link: link,
|
||||||
Description: desc,
|
Description: desc,
|
||||||
IsPermaLink: "false",
|
IsPermaLink: "false",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user