Merge pull request '[gitea] cherry-pick' (#2353) from earl-warren/forgejo:wip-gitea-cherry-pick into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2353
Reviewed-by: Otto <otto@codeberg.org>
This commit is contained in:
Earl Warren 2024-02-17 10:49:47 +00:00
commit 7ea1ef2c2b
94 changed files with 478 additions and 377 deletions

View file

@ -289,6 +289,7 @@ package "code.gitea.io/gitea/modules/timeutil"
package "code.gitea.io/gitea/modules/translation" package "code.gitea.io/gitea/modules/translation"
func (MockLocale).Language func (MockLocale).Language
func (MockLocale).TrString
func (MockLocale).Tr func (MockLocale).Tr
func (MockLocale).TrN func (MockLocale).TrN
func (MockLocale).PrettyNumber func (MockLocale).PrettyNumber

View file

@ -10,10 +10,19 @@ tasks:
- name: Run backend - name: Run backend
command: | command: |
gp sync-await setup gp sync-await setup
if [ ! -f custom/conf/app.ini ]
then # Get the URL and extract the domain
url=$(gp url 3000)
domain=$(echo $url | awk -F[/:] '{print $4}')
if [ -f custom/conf/app.ini ]; then
sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
else
mkdir -p custom/conf/ mkdir -p custom/conf/
echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi fi
export TAGS="sqlite sqlite_unlock_notify" export TAGS="sqlite sqlite_unlock_notify"

View file

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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -225,6 +225,10 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
for _, comment := range comments { for _, comment := range comments {
comment.Assignee = assignees[comment.AssigneeID] comment.Assignee = assignees[comment.AssigneeID]
if comment.Assignee == nil {
comment.AssigneeID = user_model.GhostUserID
comment.Assignee = user_model.NewGhostUser()
}
} }
return nil return nil
} }

View file

@ -159,6 +159,14 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) {
return err return err
} }
r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID) r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID)
if err != nil {
if !user_model.IsErrUserNotExist(err) {
return fmt.Errorf("GetPossibleUserByID [%d]: %w", r.ReviewerID, err)
}
r.ReviewerID = user_model.GhostUserID
r.Reviewer = user_model.NewGhostUser()
return nil
}
return err return err
} }

View file

@ -594,9 +594,7 @@ func GetOrgByID(ctx context.Context, id int64) (*Organization, error) {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, user_model.ErrUserNotExist{ return nil, user_model.ErrUserNotExist{
UID: id, UID: id,
Name: "",
KeyID: 0,
} }
} }
return u, nil return u, nil

View file

@ -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")
} }

View file

@ -31,9 +31,8 @@ func (err ErrUserAlreadyExist) Unwrap() error {
// ErrUserNotExist represents a "UserNotExist" kind of error. // ErrUserNotExist represents a "UserNotExist" kind of error.
type ErrUserNotExist struct { type ErrUserNotExist struct {
UID int64 UID int64
Name string Name string
KeyID int64
} }
// IsErrUserNotExist checks if an error is a ErrUserNotExist. // IsErrUserNotExist checks if an error is a ErrUserNotExist.
@ -43,7 +42,7 @@ func IsErrUserNotExist(err error) bool {
} }
func (err ErrUserNotExist) Error() string { func (err ErrUserNotExist) Error() string {
return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID) return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
} }
// Unwrap unwraps this error as a ErrNotExist error // Unwrap unwraps this error as a ErrNotExist error

View file

@ -847,7 +847,7 @@ func GetUserByID(ctx context.Context, id int64) (*User, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrUserNotExist{id, "", 0} return nil, ErrUserNotExist{UID: id}
} }
return u, nil return u, nil
} }
@ -897,14 +897,14 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
// GetUserByNameCtx returns user by given name. // GetUserByNameCtx returns user by given name.
func GetUserByName(ctx context.Context, name string) (*User, error) { func GetUserByName(ctx context.Context, name string) (*User, error) {
if len(name) == 0 { if len(name) == 0 {
return nil, ErrUserNotExist{0, name, 0} return nil, ErrUserNotExist{Name: name}
} }
u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual}
has, err := db.GetEngine(ctx).Get(u) has, err := db.GetEngine(ctx).Get(u)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, ErrUserNotExist{0, name, 0} return nil, ErrUserNotExist{Name: name}
} }
return u, nil return u, nil
} }
@ -1045,7 +1045,7 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []
// GetUserByEmail returns the user object by given e-mail if exists. // GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(ctx context.Context, email string) (*User, error) { func GetUserByEmail(ctx context.Context, email string) (*User, error) {
if len(email) == 0 { if len(email) == 0 {
return nil, ErrUserNotExist{0, email, 0} return nil, ErrUserNotExist{Name: email}
} }
email = strings.ToLower(email) email = strings.ToLower(email)
@ -1072,7 +1072,7 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
} }
} }
return nil, ErrUserNotExist{0, email, 0} return nil, ErrUserNotExist{Name: email}
} }
// GetUser checks if a user already exists // GetUser checks if a user already exists
@ -1083,7 +1083,7 @@ func GetUser(ctx context.Context, user *User) (bool, error) {
// GetUserByOpenID returns the user object by given OpenID if exists. // GetUserByOpenID returns the user object by given OpenID if exists.
func GetUserByOpenID(ctx context.Context, uri string) (*User, error) { func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
if len(uri) == 0 { if len(uri) == 0 {
return nil, ErrUserNotExist{0, uri, 0} return nil, ErrUserNotExist{Name: uri}
} }
uri, err := openid.Normalize(uri) uri, err := openid.Normalize(uri)
@ -1103,7 +1103,7 @@ func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
return GetUserByID(ctx, oid.UID) return GetUserByID(ctx, oid.UID)
} }
return nil, ErrUserNotExist{0, uri, 0} return nil, ErrUserNotExist{Name: uri}
} }
// GetAdminUser returns the first administrator // GetAdminUser returns the first administrator

View file

@ -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())
} }

View file

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

View file

@ -247,7 +247,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

View file

@ -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...)
} }

View file

@ -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))
}
} }

View file

@ -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)
} }

View file

@ -6,6 +6,7 @@ package context
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"html" "html"
"net/http" "net/http"
@ -110,7 +111,7 @@ func (r *Repository) AllUnitsEnabled(ctx context.Context) 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")))
} }
} }
} }

View file

@ -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

View file

@ -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)"
} }

View file

@ -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)

View file

@ -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

View file

@ -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
@ -162,7 +162,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,
@ -183,13 +183,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

View file

@ -4,6 +4,7 @@
package templates package templates
import ( import (
"html/template"
"strings" "strings"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -17,8 +18,14 @@ func NewStringUtils() *StringUtils {
return &stringUtils return &stringUtils
} }
func (su *StringUtils) HasPrefix(s, prefix string) bool { func (su *StringUtils) HasPrefix(s any, prefix string) bool {
return strings.HasPrefix(s, prefix) switch v := s.(type) {
case string:
return strings.HasPrefix(v, prefix)
case template.HTML:
return strings.HasPrefix(string(v), prefix)
}
return false
} }
func (su *StringUtils) Contains(s, substr string) bool { func (su *StringUtils) Contains(s, substr string) bool {

View file

@ -0,0 +1,20 @@
// Copyright Earl Warren <contact@earl-warren.org>
// SPDX-License-Identifier: MIT
package templates
import (
"html/template"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_StringUtils_HasPrefix(t *testing.T) {
su := &StringUtils{}
assert.True(t, su.HasPrefix("ABC", "A"))
assert.False(t, su.HasPrefix("ABC", "B"))
assert.True(t, su.HasPrefix(template.HTML("ABC"), "A"))
assert.False(t, su.HasPrefix(template.HTML("ABC"), "B"))
assert.False(t, su.HasPrefix(123, "B"))
}

View file

@ -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

View file

@ -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)
} }

View file

@ -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&amp;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())
} }

View file

@ -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
@ -88,20 +92,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]
@ -116,13 +106,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]
@ -144,8 +132,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

View file

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

View file

@ -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)

View file

@ -105,44 +105,44 @@ 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:
if setting.Service.AllowDotsInUsernames { if setting.Service.AllowDotsInUsernames {
data["ErrorMsg"] = trName + l.Tr("form.username_error") data["ErrorMsg"] = trName + l.TrString("form.username_error")
} else { } else {
data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots") data["ErrorMsg"] = trName + l.TrString("form.username_error_no_dots")
} }
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 != "" {
@ -151,7 +151,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
} }

View file

@ -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...)
} }

View file

@ -779,13 +779,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")
} }

View file

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

View file

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

View file

@ -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

View file

@ -6,6 +6,7 @@ package feed
import ( import (
"fmt" "fmt"
"html" "html"
"html/template"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -80,119 +81,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)
} }
@ -234,7 +236,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 {
@ -243,7 +245,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
// It's a common practice for feed generators to use plain text titles. // It's a common practice for feed generators to use plain text titles.
// See https://codeberg.org/forgejo/forgejo/pulls/1595 // See https://codeberg.org/forgejo/forgejo/pulls/1595
plainTitle, err := html2text.FromString(title, html2text.Options{OmitLinks: true}) plainTitle, err := html2text.FromString(title+" "+string(titleExtra), html2text.Options{OmitLinks: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -56,7 +56,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
} }
feed := &feeds.Feed{ 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()}, Link: &feeds.Link{Href: ctx.ContextUser.HTMLURL()},
Description: ctxUserDescription, Description: ctxUserDescription,
Created: time.Now(), Created: time.Now(),

View file

@ -28,10 +28,10 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas
var link *feeds.Link var link *feeds.Link
if isReleasesOnly { 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"} link = &feeds.Link{Href: repo.HTMLURL() + "/release"}
} else { } 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"} link = &feeds.Link{Href: repo.HTMLURL() + "/tags"}
} }

View file

@ -27,7 +27,7 @@ func ShowRepoFeed(ctx *context.Context, repo *repo_model.Repository, formatType
} }
feed := &feeds.Feed{ 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()}, Link: &feeds.Link{Href: repo.HTMLURL()},
Description: repo.Description, Description: repo.Description,
Created: time.Now(), Created: time.Now(),

View file

@ -29,7 +29,7 @@ func Create(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org") ctx.Data["Title"] = ctx.Tr("new_org")
ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode
if !ctx.Doer.CanCreateOrganization() { 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 return
} }
ctx.HTML(http.StatusOK, tplCreateOrg) ctx.HTML(http.StatusOK, tplCreateOrg)
@ -41,7 +41,7 @@ func CreatePost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org") ctx.Data["Title"] = ctx.Tr("new_org")
if !ctx.Doer.CanCreateOrganization() { 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 return
} }

View file

@ -353,7 +353,7 @@ func ViewProject(ctx *context.Context) {
} }
if boards[0].ID == 0 { 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) issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@ -679,7 +679,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{ board = &project_model.Board{
ID: 0, ID: 0,
ProjectID: project.ID, ProjectID: project.ID,
Title: ctx.Tr("repo.projects.type.uncategorized"), Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
} }
} else { } else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))

View file

@ -100,7 +100,7 @@ func List(ctx *context.Context) {
} }
wf, err := model.ReadWorkflow(bytes.NewReader(content)) wf, err := model.ReadWorkflow(bytes.NewReader(content))
if err != nil { 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) workflows = append(workflows, workflow)
continue continue
} }
@ -115,7 +115,7 @@ func List(ctx *context.Context) {
continue continue
} }
if !allRunnerLabels.Contains(ro) { 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 break
} }
} }

View file

@ -209,8 +209,8 @@ func ViewPost(ctx *context_module.Context) {
Link: run.RefLink(), Link: run.RefLink(),
} }
resp.State.Run.Commit = ViewCommit{ resp.State.Run.Commit = ViewCommit{
LocaleCommit: ctx.Tr("actions.runs.commit"), LocaleCommit: ctx.Locale.TrString("actions.runs.commit"),
LocalePushedBy: ctx.Tr("actions.runs.pushed_by"), LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"),
ShortSha: base.ShortSha(run.CommitSHA), ShortSha: base.ShortSha(run.CommitSHA),
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
Pusher: pusher, Pusher: pusher,
@ -235,7 +235,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.CurrentJob.Title = current.Name resp.State.CurrentJob.Title = current.Name
resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale) resp.State.CurrentJob.Detail = current.Status.LocaleString(ctx.Locale)
if run.NeedApproval { 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.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 resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json

View file

@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
files_service "code.gitea.io/gitea/services/repository/files"
) )
type blameRow struct { type blameRow struct {
@ -218,31 +219,11 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) { func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink repoLink := ctx.Repo.RepoLink
language := "" language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
if err != nil {
indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID) log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
if err == nil {
defer deleteTemporaryFile()
filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{
CachedOnly: true,
Attributes: []string{"linguist-language", "gitlab-language"},
Filenames: []string{ctx.Repo.TreePath},
IndexFile: indexFilename,
WorkTree: worktree,
})
if err != nil {
log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
}
language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"]
if language == "" || language == "unspecified" {
language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"]
}
if language == "unspecified" {
language = ""
}
} }
lines := make([]string, 0) lines := make([]string, 0)
rows := make([]*blameRow, 0) rows := make([]*blameRow, 0)
escapeStatus := &charset.EscapeStatus{} escapeStatus := &charset.EscapeStatus{}

View file

@ -104,9 +104,9 @@ func CherryPickPost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary) message := strings.TrimSpace(form.CommitSummary)
if message == "" { if message == "" {
if form.Revert { if form.Revert {
message = ctx.Tr("repo.commit.revert-header", sha) message = ctx.Locale.TrString("repo.commit.revert-header", sha)
} else { } else {
message = ctx.Tr("repo.commit.cherry-pick-header", sha) message = ctx.Locale.TrString("repo.commit.cherry-pick-header", sha)
} }
} }

View file

@ -126,7 +126,7 @@ func setCsvCompareContext(ctx *context.Context) {
return CsvDiffResult{nil, ""} 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) { csvReaderFromCommit := func(ctx *markup.RenderContext, blob *git.Blob) (*csv.Reader, io.Closer, error) {
if blob == nil { if blob == nil {

View file

@ -300,9 +300,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
message := strings.TrimSpace(form.CommitSummary) message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 { if len(message) == 0 {
if isNewFile { if isNewFile {
message = ctx.Tr("repo.editor.add", form.TreePath) message = ctx.Locale.TrString("repo.editor.add", form.TreePath)
} else { } else {
message = ctx.Tr("repo.editor.update", form.TreePath) message = ctx.Locale.TrString("repo.editor.update", form.TreePath)
} }
} }
form.CommitMessage = strings.TrimSpace(form.CommitMessage) form.CommitMessage = strings.TrimSpace(form.CommitMessage)
@ -479,7 +479,7 @@ func DiffPreviewPost(ctx *context.Context) {
} }
if diff.NumFiles == 0 { 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 return
} }
ctx.Data["File"] = diff.Files[0] ctx.Data["File"] = diff.Files[0]
@ -546,7 +546,7 @@ func DeleteFilePost(ctx *context.Context) {
message := strings.TrimSpace(form.CommitSummary) message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 { 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) form.CommitMessage = strings.TrimSpace(form.CommitMessage)
if len(form.CommitMessage) > 0 { if len(form.CommitMessage) > 0 {
@ -755,7 +755,7 @@ func UploadFilePost(ctx *context.Context) {
if dir == "" { if dir == "" {
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) form.CommitMessage = strings.TrimSpace(form.CommitMessage)

View file

@ -1042,7 +1042,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) string
}) })
if err != nil { if err != nil {
log.Debug("render flash error: %v", err) 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 return flashError
} }
@ -1664,7 +1664,7 @@ func ViewIssue(ctx *context.Context) {
} }
ghostMilestone := &issues_model.Milestone{ ghostMilestone := &issues_model.Milestone{
ID: -1, ID: -1,
Name: ctx.Tr("repo.issues.deleted_milestone"), Name: ctx.Locale.TrString("repo.issues.deleted_milestone"),
} }
if comment.OldMilestoneID > 0 && comment.OldMilestone == nil { if comment.OldMilestoneID > 0 && comment.OldMilestone == nil {
comment.OldMilestone = ghostMilestone comment.OldMilestone = ghostMilestone
@ -1681,7 +1681,7 @@ func ViewIssue(ctx *context.Context) {
ghostProject := &project_model.Project{ ghostProject := &project_model.Project{
ID: -1, ID: -1,
Title: ctx.Tr("repo.issues.deleted_project"), Title: ctx.Locale.TrString("repo.issues.deleted_project"),
} }
if comment.OldProjectID > 0 && comment.OldProject == nil { if comment.OldProjectID > 0 && comment.OldProject == nil {

View file

@ -56,12 +56,12 @@ func GetContentHistoryList(ctx *context.Context) {
for _, item := range items { for _, item := range items {
var actionText string var actionText string
if item.IsDeleted { if item.IsDeleted {
actionTextDeleted := ctx.Locale.Tr("repo.issues.content_history.deleted") actionTextDeleted := ctx.Locale.TrString("repo.issues.content_history.deleted")
actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>" actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>"
} else if item.IsFirstCreated { } else if item.IsFirstCreated {
actionText = ctx.Locale.Tr("repo.issues.content_history.created") actionText = ctx.Locale.TrString("repo.issues.content_history.created")
} else { } else {
actionText = ctx.Locale.Tr("repo.issues.content_history.edited") actionText = ctx.Locale.TrString("repo.issues.content_history.edited")
} }
username := item.UserName username := item.UserName

View file

@ -123,7 +123,7 @@ func TestDeleteLabel(t *testing.T) {
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2}) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2})
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 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) { func TestUpdateIssueLabel_Clear(t *testing.T) {

View file

@ -79,7 +79,7 @@ func NewDiffPatchPost(ctx *context.Context) {
// `message` will be both the summary and message combined // `message` will be both the summary and message combined
message := strings.TrimSpace(form.CommitSummary) message := strings.TrimSpace(form.CommitSummary)
if len(message) == 0 { if len(message) == 0 {
message = ctx.Tr("repo.editor.patch") message = ctx.Locale.TrString("repo.editor.patch")
} }
form.CommitMessage = strings.TrimSpace(form.CommitMessage) form.CommitMessage = strings.TrimSpace(form.CommitMessage)

View file

@ -315,7 +315,7 @@ func ViewProject(ctx *context.Context) {
} }
if boards[0].ID == 0 { 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) issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
@ -633,7 +633,7 @@ func MoveIssues(ctx *context.Context) {
board = &project_model.Board{ board = &project_model.Board{
ID: 0, ID: 0,
ProjectID: project.ID, ProjectID: project.ID,
Title: ctx.Tr("repo.projects.type.uncategorized"), Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
} }
} else { } else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))

View file

@ -733,7 +733,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C
type pullCommitList struct { type pullCommitList struct {
Commits []pull_service.CommitInfo `json:"commits"` Commits []pull_service.CommitInfo `json:"commits"`
LastReviewCommitSha string `json:"last_review_commit_sha"` 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 // GetPullCommits get all commits for given pull request
@ -751,7 +751,7 @@ func GetPullCommits(ctx *context.Context) {
} }
// Get the needed locale // Get the needed locale
resp.Locale = map[string]string{ resp.Locale = map[string]any{
"lang": ctx.Locale.Language(), "lang": ctx.Locale.Language(),
"show_all_commits": ctx.Tr("repo.pulls.show_all_commits"), "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)), "stats_num_commits": ctx.TrN(len(commits), "repo.activity.git_stats_commit_1", "repo.activity.git_stats_commit_n", len(commits)),

View file

@ -209,9 +209,9 @@ func SubmitReview(ctx *context.Context) {
if issue.IsPoster(ctx.Doer.ID) { if issue.IsPoster(ctx.Doer.ID) {
var translated string var translated string
if reviewType == issues_model.ReviewTypeApprove { if reviewType == issues_model.ReviewTypeApprove {
translated = ctx.Tr("repo.issues.review.self.approval") translated = ctx.Locale.TrString("repo.issues.review.self.approval")
} else { } else {
translated = ctx.Tr("repo.issues.review.self.rejection") translated = ctx.Locale.TrString("repo.issues.review.self.rejection")
} }
ctx.Flash.Error(translated) ctx.Flash.Error(translated)

View file

@ -38,7 +38,7 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error {
defer r.Close() defer r.Close()
if form.Avatar.Size > setting.Avatar.MaxFileSize { 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) data, err := io.ReadAll(r)
@ -47,7 +47,7 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error {
} }
st := typesniffer.DetectContentType(data) st := typesniffer.DetectContentType(data)
if !(st.IsImage() && !st.IsSvgImage()) { 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 { if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil {
return fmt.Errorf("UploadAvatar: %w", err) return fmt.Errorf("UploadAvatar: %w", err)

View file

@ -68,7 +68,7 @@ func SettingsProtectedBranch(c *context.Context) {
} }
c.Data["PageIsSettingsBranches"] = true 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) users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
if err != nil { if err != nil {

View file

@ -49,6 +49,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/feed"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/model"
@ -557,31 +558,11 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
} }
ctx.Data["NumLinesSet"] = true ctx.Data["NumLinesSet"] = true
language := "" language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
if err != nil {
indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID) log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
if err == nil {
defer deleteTemporaryFile()
filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{
CachedOnly: true,
Attributes: []string{"linguist-language", "gitlab-language"},
Filenames: []string{ctx.Repo.TreePath},
IndexFile: indexFilename,
WorkTree: worktree,
})
if err != nil {
log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
}
language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"]
if language == "" || language == "unspecified" {
language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"]
}
if language == "unspecified" {
language = ""
}
} }
fileContent, lexerName, err := highlight.File(blob.Name(), language, buf) fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
ctx.Data["LexerName"] = lexerName ctx.Data["LexerName"] = lexerName
if err != nil { if err != nil {
@ -762,7 +743,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) { func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {

View file

@ -714,7 +714,7 @@ func NewWikiPost(ctx *context.Context) {
wikiName := wiki_service.UserTitleToWebPath("", form.Title) wikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 { 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 { 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) newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(form.Message) == 0 { 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 { if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil {

View file

@ -85,7 +85,7 @@ func Dashboard(ctx *context.Context) {
page = 1 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["PageIsDashboard"] = true
ctx.Data["PageIsNews"] = true ctx.Data["PageIsNews"] = true
cnt, _ := organization.GetOrganizationCount(ctx, ctxUser) cnt, _ := organization.GetOrganizationCount(ctx, ctxUser)

View file

@ -126,7 +126,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser *
defer fr.Close() defer fr.Close()
if form.Avatar.Size > setting.Avatar.MaxFileSize { 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) data, err := io.ReadAll(fr)
@ -136,7 +136,7 @@ func UpdateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, ctxUser *
st := typesniffer.DetectContentType(data) st := typesniffer.DetectContentType(data)
if !(st.IsImage() && !st.IsSvgImage()) { 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 { if err = user_service.UploadAvatar(ctx, ctxUser, data); err != nil {
return fmt.Errorf("UploadAvatar: %w", err) return fmt.Errorf("UploadAvatar: %w", err)
@ -389,7 +389,7 @@ func UpdateUserLang(ctx *context.Context) {
middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0) middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0)
log.Trace("User settings updated: %s", ctx.Doer.Name) 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") ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
} }

View file

@ -39,7 +39,7 @@ func TaskStatus(ctx *context.Context) {
Args: []any{task.Message}, 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{ ctx.JSON(http.StatusOK, map[string]any{

View file

@ -152,7 +152,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont
if ctx.Doer.MustChangePassword { if ctx.Doer.MustChangePassword {
if ctx.Req.URL.Path != "/user/settings/change_password" { if ctx.Req.URL.Path != "/user/settings/change_password" {
if strings.HasPrefix(ctx.Req.UserAgent(), "git") { 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 return
} }
ctx.Data["Title"] = ctx.Tr("auth.must_change_password") ctx.Data["Title"] = ctx.Tr("auth.must_change_password")

View file

@ -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. // 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 { func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer string, args ...any) string {
realArgs := make([]any, 0, len(args)+2) 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 == "" { if doer == "" {
realArgs = append(realArgs, "(Cron)") realArgs = append(realArgs, "(Cron)")
} else { } else {
@ -80,7 +80,7 @@ func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer
realArgs = append(realArgs, args...) realArgs = append(realArgs, args...)
} }
if doer == "" { 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...)
} }

View file

@ -159,7 +159,7 @@ func RegisterTask(name string, config Config, fun func(context.Context, *user_mo
log.Debug("Registering task: %s", name) log.Debug("Registering task: %s", name)
i18nKey := "admin.dashboard." + 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) return fmt.Errorf("translation is missing for task %q, please add translation for %q", name, i18nKey)
} }

View file

@ -325,7 +325,7 @@ func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) bind
errs = append(errs, binding.Error{ errs = append(errs, binding.Error{
FieldNames: []string{"Channel"}, FieldNames: []string{"Channel"},
Classification: "", 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) return middleware.Validate(errs, ctx.Data, f, ctx.Locale)

View file

@ -94,7 +94,7 @@ func SendActivateAccountMail(locale translation.Locale, u *user_model.User) {
// No mail service configured // No mail service configured
return 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 // SendResetPasswordMail sends a password reset mail to the user
@ -104,7 +104,7 @@ func SendResetPasswordMail(u *user_model.User) {
return return
} }
locale := translation.NewLocale(u.Language) 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 // SendActivateEmailMail sends confirmation email to confirm new email address
@ -130,7 +130,7 @@ func SendActivateEmailMail(u *user_model.User, email string) {
return 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) msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)
SendAsync(msg) SendAsync(msg)
@ -158,7 +158,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
return 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) msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)
SendAsync(msg) SendAsync(msg)
@ -173,7 +173,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
locale := translation.NewLocale(u.Language) locale := translation.NewLocale(u.Language)
repoName := repo.FullName() 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{ data := map[string]any{
"locale": locale, "locale": locale,
"Subject": subject, "Subject": subject,

View file

@ -52,8 +52,8 @@ func mailNewUser(ctx context.Context, u *user_model.User, lang string, tos []str
locale := translation.NewLocale(lang) locale := translation.NewLocale(lang)
manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(u.ID, 10) manageUserURL := setting.AppURL + "admin/users/" + strconv.FormatInt(u.ID, 10)
subject := locale.Tr("mail.admin.new_user.subject", u.Name) subject := locale.TrString("mail.admin.new_user.subject", u.Name)
body := locale.Tr("mail.admin.new_user.text", manageUserURL) body := locale.TrString("mail.admin.new_user.text", manageUserURL)
mailMeta := map[string]any{ mailMeta := map[string]any{
"NewUser": u, "NewUser": u,
"NewUserUrl": u.HTMLURL(), "NewUserUrl": u.HTMLURL(),

View file

@ -68,7 +68,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
return 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{ mailMeta := map[string]any{
"locale": locale, "locale": locale,
"Release": rel, "Release": rel,

View file

@ -56,11 +56,11 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
content bytes.Buffer content bytes.Buffer
) )
destination := locale.Tr("mail.repo.transfer.to_you") destination := locale.TrString("mail.repo.transfer.to_you")
subject := locale.Tr("mail.repo.transfer.subject_to_you", doer.DisplayName(), repo.FullName()) subject := locale.TrString("mail.repo.transfer.subject_to_you", doer.DisplayName(), repo.FullName())
if newOwner.IsOrganization() { if newOwner.IsOrganization() {
destination = newOwner.DisplayName() 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{ data := map[string]any{

View file

@ -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) 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{ mailMeta := map[string]any{
"locale": locale, "locale": locale,
"Inviter": inviter, "Inviter": inviter,

View file

@ -270,3 +270,34 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
Content: content, Content: content,
}, nil }, nil
} }
// TryGetContentLanguage tries to get the (linguist) language of the file content
func TryGetContentLanguage(gitRepo *git.Repository, commitID, treePath string) (string, error) {
indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(commitID)
if err != nil {
return "", err
}
defer deleteTemporaryFile()
filename2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{
CachedOnly: true,
Attributes: []string{"linguist-language", "gitlab-language"},
Filenames: []string{treePath},
IndexFile: indexFilename,
WorkTree: worktree,
})
if err != nil {
return "", err
}
language := filename2attribute2info[treePath]["linguist-language"]
if language == "" || language == "unspecified" {
language = filename2attribute2info[treePath]["gitlab-language"]
}
if language == "unspecified" {
language = ""
}
return language, nil
}

View file

@ -153,9 +153,7 @@ func ReplaceInactivePrimaryEmail(ctx context.Context, oldEmail string, email *us
return err return err
} else if !has { } else if !has {
return user_model.ErrUserNotExist{ return user_model.ErrUserNotExist{
UID: email.UID, UID: email.UID,
Name: "",
KeyID: 0,
} }
} }

View file

@ -13,9 +13,9 @@
<body> <body>
<p> <p>
{{if .IsPull}} {{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}} {{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}} {{end}}
</p> </p>
<div class="footer"> <div class="footer">

View file

@ -28,7 +28,7 @@
{{$newShortSha := ShortSha .Comment.NewCommit}} {{$newShortSha := ShortSha .Comment.NewCommit}}
{{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $newCommitUrl) (Escape $newShortSha)}} {{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $newCommitUrl) (Escape $newShortSha)}}
{{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink | Str2html}} {{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch ($oldCommitLink|Safe) ($newCommitLink|Safe)}}
{{else}} {{else}}
{{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}} {{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}}
{{end}} {{end}}

View file

@ -8,7 +8,7 @@
{{$url := printf "<a href='%[1]s'>%[2]s</a>" (Escape .Link) (Escape .Repo)}} {{$url := printf "<a href='%[1]s'>%[2]s</a>" (Escape .Link) (Escape .Repo)}}
<body> <body>
<p>{{.Subject}}. <p>{{.Subject}}.
{{.locale.Tr "mail.repo.transfer.body" $url | Str2html}} {{.locale.Tr "mail.repo.transfer.body" ($url|Safe)}}
</p> </p>
<p> <p>
--- ---

View file

@ -15,7 +15,7 @@
{{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.HTMLURL | Escape) (.Release.Repo.FullName | Escape)}} {{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.HTMLURL | Escape) (.Release.Repo.FullName | Escape)}}
<body> <body>
<p> <p>
{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url | Str2html}} {{.locale.Tr "mail.release.new.text" .Release.Publisher.Name ($release_url|Safe) ($repo_url|Safe)}}
</p> </p>
<h4>{{.locale.Tr "mail.release.title" .Release.Title}}</h4> <h4>{{.locale.Tr "mail.release.title" .Release.Title}}</h4>
<p> <p>

View file

@ -13,9 +13,9 @@
{{$shaurl := printf "%s/commit/%s" $.RepoLink (PathEscape .SHA)}} {{$shaurl := printf "%s/commit/%s" $.RepoLink (PathEscape .SHA)}}
{{$shalink := printf `<a class="ui primary sha label" href="%s">%s</a>` (Escape $shaurl) (ShortSha .SHA)}} {{$shalink := printf `<a class="ui primary sha label" href="%s">%s</a>` (Escape $shaurl) (ShortSha .SHA)}}
{{if eq .CherryPickType "revert"}} {{if eq .CherryPickType "revert"}}
{{ctx.Locale.Tr "repo.editor.revert" $shalink | Str2html}} {{ctx.Locale.Tr "repo.editor.revert" ($shalink|Safe)}}
{{else}} {{else}}
{{ctx.Locale.Tr "repo.editor.cherry_pick" $shalink | Str2html}} {{ctx.Locale.Tr "repo.editor.cherry_pick" ($shalink|Safe)}}
{{end}} {{end}}
<a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a> <a class="section" href="{{$.RepoLink}}">{{.Repository.FullName}}</a>
<div class="breadcrumb-divider">:</div> <div class="breadcrumb-divider">:</div>

View file

@ -595,11 +595,11 @@
{{$newProjectDisplayHtml = printf `<span data-tooltip-content="%s">%s</span>` (ctx.Locale.Tr $trKey | Escape) (.Project.Title | Escape)}} {{$newProjectDisplayHtml = printf `<span data-tooltip-content="%s">%s</span>` (ctx.Locale.Tr $trKey | Escape) (.Project.Title | Escape)}}
{{end}} {{end}}
{{if and (gt .OldProjectID 0) (gt .ProjectID 0)}} {{if and (gt .OldProjectID 0) (gt .ProjectID 0)}}
{{ctx.Locale.Tr "repo.issues.change_project_at" $oldProjectDisplayHtml $newProjectDisplayHtml $createdStr | Safe}} {{ctx.Locale.Tr "repo.issues.change_project_at" ($oldProjectDisplayHtml|Safe) ($newProjectDisplayHtml|Safe) $createdStr}}
{{else if gt .OldProjectID 0}} {{else if gt .OldProjectID 0}}
{{ctx.Locale.Tr "repo.issues.remove_project_at" $oldProjectDisplayHtml $createdStr | Safe}} {{ctx.Locale.Tr "repo.issues.remove_project_at" ($oldProjectDisplayHtml|Safe) $createdStr}}
{{else if gt .ProjectID 0}} {{else if gt .ProjectID 0}}
{{ctx.Locale.Tr "repo.issues.add_project_at" $newProjectDisplayHtml $createdStr | Safe}} {{ctx.Locale.Tr "repo.issues.add_project_at" ($newProjectDisplayHtml|Safe) $createdStr}}
{{end}} {{end}}
</span> </span>
</div> </div>

View file

@ -56,18 +56,18 @@
{{$mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix ctx.Locale}} {{$mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix ctx.Locale}}
{{if .Issue.OriginalAuthor}} {{if .Issue.OriginalAuthor}}
{{.Issue.OriginalAuthor}} {{.Issue.OriginalAuthor}}
<span class="pull-desc">{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}}</span> <span class="pull-desc">{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}}</span>
{{else}} {{else}}
<a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.GetDisplayName}}</a> <a {{if gt .Issue.PullRequest.Merger.ID 0}}href="{{.Issue.PullRequest.Merger.HomeLink}}"{{end}}>{{.Issue.PullRequest.Merger.GetDisplayName}}</a>
<span class="pull-desc">{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}}</span> <span class="pull-desc">{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}}</span>
{{end}} {{end}}
{{else}} {{else}}
{{if .Issue.OriginalAuthor}} {{if .Issue.OriginalAuthor}}
<span id="pull-desc" class="pull-desc">{{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref | Safe}}</span> <span id="pull-desc" class="pull-desc">{{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}}</span>
{{else}} {{else}}
<span id="pull-desc" class="pull-desc"> <span id="pull-desc" class="pull-desc">
<a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a> <a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
{{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref | Safe}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}}
</span> </span>
{{end}} {{end}}
<span id="pull-desc-edit" class="gt-hidden flex-text-block"> <span id="pull-desc-edit" class="gt-hidden flex-text-block">

View file

@ -9,21 +9,20 @@
{{end}} {{end}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.SignInLink}}" method="post"> <form class="ui form gt-max-width-36rem gt-m-auto" action="{{.SignInLink}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> <div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label> <label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required> <input id="user_name" class="gt-w-full" type="text" name="user_name" value="{{.user_name}}" autofocus required>
</div> </div>
{{if or (not .DisablePassword) .LinkAccountMode}} {{if or (not .DisablePassword) .LinkAccountMode}}
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label> <label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required> <input id="password" class="gt-w-full" name="password" type="password" value="{{.password}}" autocomplete="current-password" required>
</div> </div>
{{end}} {{end}}
{{if not .LinkAccountMode}} {{if not .LinkAccountMode}}
<div class="inline field"> <div class="inline field">
<label></label>
<div class="ui checkbox"> <div class="ui checkbox">
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label> <label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
<input name="remember" type="checkbox"> <input name="remember" type="checkbox">
@ -34,7 +33,6 @@
{{template "user/auth/captcha" .}} {{template "user/auth/captcha" .}}
<div class="inline field"> <div class="inline field">
<label></label>
<button class="ui primary button"> <button class="ui primary button">
{{if .LinkAccountMode}} {{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signin_submit"}} {{ctx.Locale.Tr "auth.oauth_signin_submit"}}
@ -47,7 +45,6 @@
{{if .ShowRegistrationButton}} {{if .ShowRegistrationButton}}
<div class="inline field"> <div class="inline field">
<label></label>
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now" | Str2html}}</a> <a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now" | Str2html}}</a>
</div> </div>
{{end}} {{end}}
@ -60,7 +57,7 @@
<div class="gt-df gt-fc gt-jc"> <div class="gt-df gt-fc gt-jc">
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3"> <div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $provider := .OAuth2Providers}} {{range $provider := .OAuth2Providers}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}"> <a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 gt-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}} {{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}} {{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a> </a>

View file

@ -8,7 +8,7 @@
OpenID OpenID
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form gt-m-auto" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="inline field"> <div class="inline field">
{{ctx.Locale.Tr "auth.openid_signin_desc"}} {{ctx.Locale.Tr "auth.openid_signin_desc"}}
@ -18,17 +18,15 @@
{{svg "fontawesome-openid"}} {{svg "fontawesome-openid"}}
OpenID URI OpenID URI
</label> </label>
<input id="openid" name="openid" value="{{.openid}}" autofocus required> <input id="openid" class="gt-w-full" name="openid" value="{{.openid}}" autofocus required>
</div> </div>
<div class="inline field"> <div class="inline field">
<label></label>
<div class="ui checkbox"> <div class="ui checkbox">
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label> <label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
<input name="remember" type="checkbox"> <input name="remember" type="checkbox">
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field">
<label></label>
<button class="ui primary button">{{ctx.Locale.Tr "sign_in"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "sign_in"}}</button>
</div> </div>
</form> </form>

View file

@ -7,7 +7,7 @@
{{end}} {{end}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.SignUpLink}}" method="post"> <form class="ui form gt-max-width-36rem gt-m-auto" action="{{.SignUpLink}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}} {{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
{{template "base/alert" .}} {{template "base/alert" .}}
@ -17,28 +17,27 @@
{{else}} {{else}}
<div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}"> <div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "username"}}</label> <label for="user_name">{{ctx.Locale.Tr "username"}}</label>
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required> <input id="user_name" class="gt-w-full" type="text" name="user_name" value="{{.user_name}}" autofocus required>
</div> </div>
<div class="required inline field {{if .Err_Email}}error{{end}}"> <div class="required inline field {{if .Err_Email}}error{{end}}">
<label for="email">{{ctx.Locale.Tr "email"}}</label> <label for="email">{{ctx.Locale.Tr "email"}}</label>
<input id="email" name="email" type="email" value="{{.email}}" required> <input id="email" class="gt-w-full" name="email" type="email" value="{{.email}}" required>
</div> </div>
{{if not .DisablePassword}} {{if not .DisablePassword}}
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}"> <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label> <label for="password">{{ctx.Locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required> <input id="password" class="gt-w-full" name="password" type="password" value="{{.password}}" autocomplete="new-password" required>
</div> </div>
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}"> <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="retype">{{ctx.Locale.Tr "re_type"}}</label> <label for="retype">{{ctx.Locale.Tr "re_type"}}</label>
<input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="new-password" required> <input id="retype" class="gt-w-full" name="retype" type="password" value="{{.retype}}" autocomplete="new-password" required>
</div> </div>
{{end}} {{end}}
{{template "user/auth/captcha" .}} {{template "user/auth/captcha" .}}
<div class="inline field"> <div class="inline field">
<label></label>
<button class="ui primary button"> <button class="ui primary button">
{{if .LinkAccountMode}} {{if .LinkAccountMode}}
{{ctx.Locale.Tr "auth.oauth_signup_submit"}} {{ctx.Locale.Tr "auth.oauth_signup_submit"}}
@ -50,7 +49,6 @@
{{if not .LinkAccountMode}} {{if not .LinkAccountMode}}
<div class="inline field"> <div class="inline field">
<label></label>
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.register_helper_msg"}}</a> <a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.register_helper_msg"}}</a>
</div> </div>
{{end}} {{end}}
@ -64,7 +62,7 @@
<div class="gt-df gt-fc gt-jc"> <div class="gt-df gt-fc gt-jc">
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3"> <div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $provider := .OAuth2Providers}} {{range $provider := .OAuth2Providers}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}"> <a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 gt-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
{{$provider.IconHTML 28}} {{$provider.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}} {{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
</a> </a>

View file

@ -309,7 +309,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) {
// all groups the user is a member of, the user filter is modified accordingly inside // all groups the user is a member of, the user filter is modified accordingly inside
// the addAuthSourceLDAP based on the value of the groupFilter // the addAuthSourceLDAP based on the value of the groupFilter
u := otherLDAPUsers[0] 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) auth.SyncExternalUsers(context.Background(), true)
@ -362,7 +362,7 @@ func TestLDAPUserSigninFailed(t *testing.T) {
addAuthSourceLDAP(t, "", "") addAuthSourceLDAP(t, "", "")
u := otherLDAPUsers[0] 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) { func TestLDAPUserSSHKeySync(t *testing.T) {

View file

@ -218,7 +218,7 @@ func TestCantMergeWorkInProgress(t *testing.T) {
text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text()) text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text())
assert.NotEmpty(t, text, "Can't find WIP 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") assert.Contains(t, text, "[wip]", "Unable to find WIP text")
}) })
} }

View file

@ -90,7 +90,7 @@ func TestCreateRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) 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 TestDeleteRelease(t *testing.T) { func TestDeleteRelease(t *testing.T) {
@ -137,7 +137,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) 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) { func TestCreateReleaseDraft(t *testing.T) {
@ -146,7 +146,7 @@ func TestCreateReleaseDraft(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) 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) { func TestCreateReleasePaging(t *testing.T) {
@ -166,11 +166,11 @@ func TestCreateReleasePaging(t *testing.T) {
} }
createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true) 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 // Check that user4 does not see draft and still see 10 latest releases
session2 := loginUser(t, "user4") 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) { func TestViewReleaseListNoLogin(t *testing.T) {
@ -265,7 +265,7 @@ func TestReleaseOnCommit(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewReleaseTarget(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", "65f1bf27bc3bf70f64657658635e66094edbcb4d", false, false) createNewReleaseTarget(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", "65f1bf27bc3bf70f64657658635e66094edbcb4d", 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 TestViewTagsList(t *testing.T) { func TestViewTagsList(t *testing.T) {

View file

@ -63,39 +63,39 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "feature/test1", NewBranch: "feature/test1",
ExpectedStatus: http.StatusSeeOther, 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"),
CheckBranch: true, CheckBranch: true,
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "", NewBranch: "",
ExpectedStatus: http.StatusSeeOther, 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", OldRefSubURL: "branch/master",
NewBranch: "feature=test1", NewBranch: "feature=test1",
ExpectedStatus: http.StatusSeeOther, 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"),
CheckBranch: true, CheckBranch: true,
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: strings.Repeat("b", 101), NewBranch: strings.Repeat("b", 101),
ExpectedStatus: http.StatusSeeOther, 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", OldRefSubURL: "branch/master",
NewBranch: "master", NewBranch: "master",
ExpectedStatus: http.StatusSeeOther, 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", OldRefSubURL: "branch/master",
NewBranch: "master/test", NewBranch: "master/test",
ExpectedStatus: http.StatusSeeOther, 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", OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0",
@ -106,7 +106,7 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
NewBranch: "feature/test3", NewBranch: "feature/test3",
ExpectedStatus: http.StatusSeeOther, 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"),
CheckBranch: true, CheckBranch: true,
}, },
{ {
@ -114,14 +114,14 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "v1.0.0", NewBranch: "v1.0.0",
CreateRelease: "v1.0.0", CreateRelease: "v1.0.0",
ExpectedStatus: http.StatusSeeOther, 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", OldRefSubURL: "tag/v1.0.0",
NewBranch: "feature/test4", NewBranch: "feature/test4",
CreateRelease: "v1.0.1", CreateRelease: "v1.0.1",
ExpectedStatus: http.StatusSeeOther, 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"),
CheckBranch: true, CheckBranch: true,
}, },
} }

View file

@ -49,10 +49,10 @@ func TestSignin(t *testing.T) {
password string password string
message string message string
}{ }{
{username: "wrongUsername", 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").Tr("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").Tr("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").Tr("form.username_password_incorrect")}, {username: "user1@example.com", password: "wrongPassword", message: translation.NewLocale("en-US").TrString("form.username_password_incorrect")},
} }
for _, s := range samples { for _, s := range samples {

View file

@ -69,9 +69,9 @@ func TestSignupEmail(t *testing.T) {
wantStatus int wantStatus int
wantMsg string wantMsg string
}{ }{
{"exampleUser@example.com\r\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").Tr("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").Tr("form.email_invalid")}, {"exampleUser@example.com\n", http.StatusOK, translation.NewLocale("en-US").TrString("form.email_invalid")},
{"exampleUser@example.com", http.StatusSeeOther, ""}, {"exampleUser@example.com", http.StatusSeeOther, ""},
} }

View file

@ -85,7 +85,7 @@ func TestRenameInvalidUsername(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(), 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}) unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
@ -147,7 +147,7 @@ func TestRenameReservedUsername(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(), 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}) unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername})

View file

@ -243,7 +243,6 @@ textarea:focus,
.user.forgot.password form, .user.forgot.password form,
.user.reset.password form, .user.reset.password form,
.user.link-account form, .user.link-account form,
.user.signin form,
.user.signup form { .user.signup form {
margin: auto; margin: auto;
width: 700px !important; width: 700px !important;
@ -279,7 +278,6 @@ textarea:focus,
.user.forgot.password form .inline.field > label, .user.forgot.password form .inline.field > label,
.user.reset.password form .inline.field > label, .user.reset.password form .inline.field > label,
.user.link-account form .inline.field > label, .user.link-account form .inline.field > label,
.user.signin form .inline.field > label,
.user.signup form .inline.field > label { .user.signup form .inline.field > label {
text-align: right; text-align: right;
width: 250px !important; width: 250px !important;

View file

@ -48,6 +48,7 @@ Gitea's private styles use `g-` prefix.
.gt-max-width-12rem { max-width: 12rem !important; } .gt-max-width-12rem { max-width: 12rem !important; }
.gt-max-width-24rem { max-width: 24rem !important; } .gt-max-width-24rem { max-width: 24rem !important; }
.gt-max-width-36rem { max-width: 36rem !important; }
/* below class names match Tailwind CSS */ /* below class names match Tailwind CSS */
.gt-break-all { word-break: break-all !important; } .gt-break-all { word-break: break-all !important; }