From b667634b32e240f3f3260ab7e304f72f4ff75659 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 14 Apr 2023 19:27:11 +0200 Subject: [PATCH 01/12] Fix meilisearch not working when searching across multiple repositories (#24109) This would happen in the issue and pull request dashboards, while the per repository lists worked fine. Use OR instead of AND for repo IDs. --- modules/indexer/issues/meilisearch.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/indexer/issues/meilisearch.go b/modules/indexer/issues/meilisearch.go index 5c45236e660..319dc3e30b2 100644 --- a/modules/indexer/issues/meilisearch.go +++ b/modules/indexer/issues/meilisearch.go @@ -6,6 +6,7 @@ package issues import ( "context" "strconv" + "strings" "sync" "time" @@ -120,10 +121,11 @@ func (b *MeilisearchIndexer) Delete(ids ...int64) error { // Search searches for issues by given conditions. // Returns the matching issue IDs func (b *MeilisearchIndexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) { - filter := make([][]string, 0, len(repoIDs)) + repoFilters := make([]string, 0, len(repoIDs)) for _, repoID := range repoIDs { - filter = append(filter, []string{"repo_id = " + strconv.FormatInt(repoID, 10)}) + repoFilters = append(repoFilters, "repo_id = "+strconv.FormatInt(repoID, 10)) } + filter := strings.Join(repoFilters, " OR ") searchRes, err := b.client.Index(b.indexerName).Search(keyword, &meilisearch.SearchRequest{ Filter: filter, Limit: int64(limit), From cfe3d6e9b507a9331328e55ff98a1f582abae185 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Apr 2023 02:18:28 +0800 Subject: [PATCH 02/12] Make more functions use ctx instead of db.DefaultContext (#24068) Continue the "ctx refactoring" work. There are still a lot db.DefaultContext, incorrect context could cause database deadlock errors. --- models/issues/assignees.go | 4 +-- models/issues/assignees_test.go | 6 ++-- models/issues/issue.go | 4 +-- models/issues/issue_xref_test.go | 2 +- routers/api/v1/repo/issue.go | 8 ++--- routers/api/v1/repo/pull.go | 2 +- routers/api/v1/repo/pull_review.go | 4 +-- routers/web/repo/issue.go | 16 ++++----- services/issue/assignee.go | 25 +++++++------ services/issue/assignee_test.go | 2 +- services/issue/comments.go | 8 ++--- services/issue/issue.go | 57 +++++++++++++++--------------- services/issue/issue_test.go | 6 ++-- services/pull/comment.go | 2 +- services/pull/pull.go | 6 ++-- services/pull/review.go | 4 +-- tests/integration/api_pull_test.go | 2 +- 17 files changed, 79 insertions(+), 79 deletions(-) diff --git a/models/issues/assignees.go b/models/issues/assignees.go index 16f675a83f8..fdd0d6f2274 100644 --- a/models/issues/assignees.go +++ b/models/issues/assignees.go @@ -63,8 +63,8 @@ func IsUserAssignedToIssue(ctx context.Context, issue *Issue, user *user_model.U } // ToggleIssueAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. -func ToggleIssueAssignee(issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ToggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *Comment, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return false, nil, err } diff --git a/models/issues/assignees_test.go b/models/issues/assignees_test.go index 8a2cef8acd8..2185f6fc424 100644 --- a/models/issues/assignees_test.go +++ b/models/issues/assignees_test.go @@ -24,17 +24,17 @@ func TestUpdateAssignee(t *testing.T) { // Assign multiple users user2, err := user_model.GetUserByID(db.DefaultContext, 2) assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user2.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user2.ID) assert.NoError(t, err) user3, err := user_model.GetUserByID(db.DefaultContext, 3) assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user3.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user3.ID) assert.NoError(t, err) user1, err := user_model.GetUserByID(db.DefaultContext, 1) // This user is already assigned (see the definition in fixtures), so running UpdateAssignee should unassign him assert.NoError(t, err) - _, _, err = issues_model.ToggleIssueAssignee(issue, &user_model.User{ID: 1}, user1.ID) + _, _, err = issues_model.ToggleIssueAssignee(db.DefaultContext, issue, &user_model.User{ID: 1}, user1.ID) assert.NoError(t, err) // Check if he got removed diff --git a/models/issues/issue.go b/models/issues/issue.go index 64b0edd3e7b..58360304766 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -743,8 +743,8 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, } // ChangeIssueTitle changes the title of this issue, as the given user. -func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, oldTitle string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go index 6d96c398d05..6e94c262723 100644 --- a/models/issues/issue_xref_test.go +++ b/models/issues/issue_xref_test.go @@ -83,7 +83,7 @@ func TestXRef_NeuterCrossReferences(t *testing.T) { d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) i.Title = "title2, no mentions" - assert.NoError(t, issues_model.ChangeIssueTitle(i, d, title)) + assert.NoError(t, issues_model.ChangeIssueTitle(db.DefaultContext, i, d, title)) ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}) assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 06bf06b4e84..5bf5fc8c8bc 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -651,7 +651,7 @@ func CreateIssue(ctx *context.APIContext) { form.Labels = make([]int64, 0) } - if err := issue_service.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil { + if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err) return @@ -752,7 +752,7 @@ func EditIssue(ctx *context.APIContext) { issue.Content = *form.Body } if form.Ref != nil { - err = issue_service.ChangeIssueRef(issue, ctx.Doer, *form.Ref) + err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref) if err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRef", err) return @@ -790,7 +790,7 @@ func EditIssue(ctx *context.APIContext) { oneAssignee = *form.Assignee } - err = issue_service.UpdateAssignees(issue, oneAssignee, form.Assignees, ctx.Doer) + err = issue_service.UpdateAssignees(ctx, issue, oneAssignee, form.Assignees, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err) return @@ -887,7 +887,7 @@ func DeleteIssue(ctx *context.APIContext) { return } - if err = issue_service.DeleteIssue(ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { + if err = issue_service.DeleteIssue(ctx, ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { ctx.Error(http.StatusInternalServerError, "DeleteIssueByID", err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 9b5ec0b3f8e..f4e2969d7d1 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -534,7 +534,7 @@ func EditPullRequest(ctx *context.APIContext) { // Send an empty array ([]) to clear all assignees from the Issue. if ctx.Repo.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { - err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.Doer) + err = issue_service.UpdateAssignees(ctx, issue, form.Assignee, form.Assignees, ctx.Doer) if err != nil { if user_model.IsErrUserNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err)) diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 8f4b9dafec6..a568cd565a7 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -706,7 +706,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } for _, reviewer := range reviewers { - comment, err := issue_service.ReviewRequest(pr.Issue, ctx.Doer, reviewer, isAdd) + comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd) if err != nil { ctx.Error(http.StatusInternalServerError, "ReviewRequest", err) return @@ -750,7 +750,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } for _, teamReviewer := range teamReviewers { - comment, err := issue_service.TeamReviewRequest(pr.Issue, ctx.Doer, teamReviewer, isAdd) + comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd) if err != nil { ctx.ServerError("TeamReviewRequest", err) return diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index d251f2043c0..fb61ec00d12 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -964,7 +964,7 @@ func DeleteIssue(ctx *context.Context) { return } - if err := issue_service.DeleteIssue(ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { + if err := issue_service.DeleteIssue(ctx, ctx.Doer, ctx.Repo.GitRepo, issue); err != nil { ctx.ServerError("DeleteIssueByID", err) return } @@ -1132,7 +1132,7 @@ func NewIssuePost(ctx *context.Context) { Ref: form.Ref, } - if err := issue_service.NewIssue(repo, issue, labelIDs, attachments, assigneeIDs); err != nil { + if err := issue_service.NewIssue(ctx, repo, issue, labelIDs, attachments, assigneeIDs); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err.Error()) return @@ -2013,7 +2013,7 @@ func UpdateIssueTitle(ctx *context.Context) { return } - if err := issue_service.ChangeTitle(issue, ctx.Doer, title); err != nil { + if err := issue_service.ChangeTitle(ctx, issue, ctx.Doer, title); err != nil { ctx.ServerError("ChangeTitle", err) return } @@ -2037,7 +2037,7 @@ func UpdateIssueRef(ctx *context.Context) { ref := ctx.FormTrim("ref") - if err := issue_service.ChangeIssueRef(issue, ctx.Doer, ref); err != nil { + if err := issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, ref); err != nil { ctx.ServerError("ChangeRef", err) return } @@ -2161,7 +2161,7 @@ func UpdateIssueAssignee(ctx *context.Context) { for _, issue := range issues { switch action { case "clear": - if err := issue_service.DeleteNotPassedAssignee(issue, ctx.Doer, []*user_model.User{}); err != nil { + if err := issue_service.DeleteNotPassedAssignee(ctx, issue, ctx.Doer, []*user_model.User{}); err != nil { ctx.ServerError("ClearAssignees", err) return } @@ -2182,7 +2182,7 @@ func UpdateIssueAssignee(ctx *context.Context) { return } - _, _, err = issue_service.ToggleAssignee(issue, ctx.Doer, assigneeID) + _, _, err = issue_service.ToggleAssignee(ctx, issue, ctx.Doer, assigneeID) if err != nil { ctx.ServerError("ToggleAssignee", err) return @@ -2269,7 +2269,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - _, err = issue_service.TeamReviewRequest(issue, ctx.Doer, team, action == "attach") + _, err = issue_service.TeamReviewRequest(ctx, issue, ctx.Doer, team, action == "attach") if err != nil { ctx.ServerError("TeamReviewRequest", err) return @@ -2307,7 +2307,7 @@ func UpdatePullReviewRequest(ctx *context.Context) { return } - _, err = issue_service.ReviewRequest(issue, ctx.Doer, reviewer, action == "attach") + _, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach") if err != nil { ctx.ServerError("ReviewRequest", err) return diff --git a/services/issue/assignee.go b/services/issue/assignee.go index e5e1456c3f1..4d0224d4bff 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -6,7 +6,6 @@ package issue import ( "context" - "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -18,7 +17,7 @@ import ( ) // DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array -func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { +func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { var found bool oriAssignes := make([]*user_model.User, len(issue.Assignees)) _ = copy(oriAssignes, issue.Assignees) @@ -34,7 +33,7 @@ func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, a if !found { // This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here - if _, _, err := ToggleAssignee(issue, doer, assignee.ID); err != nil { + if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil { return err } } @@ -44,25 +43,25 @@ func DeleteNotPassedAssignee(issue *issues_model.Issue, doer *user_model.User, a } // ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. -func ToggleAssignee(issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { - removed, comment, err = issues_model.ToggleIssueAssignee(issue, doer, assigneeID) +func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { + removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) if err != nil { return } - assignee, err1 := user_model.GetUserByID(db.DefaultContext, assigneeID) + assignee, err1 := user_model.GetUserByID(ctx, assigneeID) if err1 != nil { err = err1 return } - notification.NotifyIssueChangeAssignee(db.DefaultContext, doer, issue, assignee, removed, comment) + notification.NotifyIssueChangeAssignee(ctx, doer, issue, assignee, removed, comment) return removed, comment, err } // ReviewRequest add or remove a review request from a user for this PR, and make comment for it. -func ReviewRequest(issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { +func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { comment, err = issues_model.AddReviewRequest(issue, reviewer, doer) } else { @@ -74,7 +73,7 @@ func ReviewRequest(issue *issues_model.Issue, doer, reviewer *user_model.User, i } if comment != nil { - notification.NotifyPullReviewRequest(db.DefaultContext, doer, issue, reviewer, isAdd, comment) + notification.NotifyPullReviewRequest(ctx, doer, issue, reviewer, isAdd, comment) } return comment, err @@ -229,7 +228,7 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, } // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. -func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { +func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer) } else { @@ -245,11 +244,11 @@ func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewe } // notify all user in this team - if err = comment.LoadIssue(db.DefaultContext); err != nil { + if err = comment.LoadIssue(ctx); err != nil { return } - members, err := organization.GetTeamMembers(db.DefaultContext, &organization.SearchMembersOptions{ + members, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ TeamID: reviewer.ID, }) if err != nil { @@ -261,7 +260,7 @@ func TeamReviewRequest(issue *issues_model.Issue, doer *user_model.User, reviewe continue } comment.AssigneeID = member.ID - notification.NotifyPullReviewRequest(db.DefaultContext, doer, issue, member, isAdd, comment) + notification.NotifyPullReviewRequest(ctx, doer, issue, member, isAdd, comment) } return comment, err diff --git a/services/issue/assignee_test.go b/services/issue/assignee_test.go index 114ace078ed..43b24e1d1fc 100644 --- a/services/issue/assignee_test.go +++ b/services/issue/assignee_test.go @@ -31,7 +31,7 @@ func TestDeleteNotPassedAssignee(t *testing.T) { assert.True(t, isAssigned) // Clean everyone - err = DeleteNotPassedAssignee(issue, user1, []*user_model.User{}) + err = DeleteNotPassedAssignee(db.DefaultContext, issue, user1, []*user_model.User{}) assert.NoError(t, err) assert.EqualValues(t, 0, len(issue.Assignees)) diff --git a/services/issue/comments.go b/services/issue/comments.go index 1323fb47aa4..4fe07c17b9d 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -16,8 +16,8 @@ import ( ) // CreateComment creates comment of issue or commit. -func CreateComment(opts *issues_model.CreateCommentOptions) (comment *issues_model.Comment, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CreateComment(ctx context.Context, opts *issues_model.CreateCommentOptions) (comment *issues_model.Comment, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func CreateRefComment(doer *user_model.User, repo *repo_model.Repository, issue return nil } - _, err = CreateComment(&issues_model.CreateCommentOptions{ + _, err = CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeCommitRef, Doer: doer, Repo: repo, @@ -66,7 +66,7 @@ func CreateRefComment(doer *user_model.User, repo *repo_model.Repository, issue // CreateIssueComment creates a plain issue comment. func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content string, attachments []string) (*issues_model.Comment, error) { - comment, err := CreateComment(&issues_model.CreateCommentOptions{ + comment, err := CreateComment(ctx, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeComment, Doer: doer, Repo: repo, diff --git a/services/issue/issue.go b/services/issue/issue.go index b91ee4fc18b..d4f827e99af 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -4,6 +4,7 @@ package issue import ( + "context" "fmt" activities_model "code.gitea.io/gitea/models/activities" @@ -20,49 +21,49 @@ import ( ) // NewIssue creates new issue with labels for repository. -func NewIssue(repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error { +func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error { if err := issues_model.NewIssue(repo, issue, labelIDs, uuids); err != nil { return err } for _, assigneeID := range assigneeIDs { - if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil { + if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil { return err } } - mentions, err := issues_model.FindAndUpdateIssueMentions(db.DefaultContext, issue, issue.Poster, issue.Content) + mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content) if err != nil { return err } - notification.NotifyNewIssue(db.DefaultContext, issue, mentions) + notification.NotifyNewIssue(ctx, issue, mentions) if len(issue.Labels) > 0 { - notification.NotifyIssueChangeLabels(db.DefaultContext, issue.Poster, issue, issue.Labels, nil) + notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil) } if issue.Milestone != nil { - notification.NotifyIssueChangeMilestone(db.DefaultContext, issue.Poster, issue, 0) + notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0) } return nil } // ChangeTitle changes the title of this issue, as the given user. -func ChangeTitle(issue *issues_model.Issue, doer *user_model.User, title string) (err error) { +func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, title string) (err error) { oldTitle := issue.Title issue.Title = title - if err = issues_model.ChangeIssueTitle(issue, doer, oldTitle); err != nil { + if err = issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil { return } - notification.NotifyIssueChangeTitle(db.DefaultContext, doer, issue, oldTitle) + notification.NotifyIssueChangeTitle(ctx, doer, issue, oldTitle) return nil } // ChangeIssueRef changes the branch of this issue, as the given user. -func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string) error { +func ChangeIssueRef(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, ref string) error { oldRef := issue.Ref issue.Ref = ref @@ -70,7 +71,7 @@ func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string return err } - notification.NotifyIssueChangeRef(db.DefaultContext, doer, issue, oldRef) + notification.NotifyIssueChangeRef(ctx, doer, issue, oldRef) return nil } @@ -81,7 +82,7 @@ func ChangeIssueRef(issue *issues_model.Issue, doer *user_model.User, ref string // "assignees" (array): Logins for Users to assign to this issue. // Pass one or more user logins to replace the set of assignees on this Issue. // Send an empty array ([]) to clear all assignees from the Issue. -func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) { +func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) { var allNewAssignees []*user_model.User // Keep the old assignee thingy for compatibility reasons @@ -102,7 +103,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi // Loop through all assignees to add them for _, assigneeName := range multipleAssignees { - assignee, err := user_model.GetUserByName(db.DefaultContext, assigneeName) + assignee, err := user_model.GetUserByName(ctx, assigneeName) if err != nil { return err } @@ -111,7 +112,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi } // Delete all old assignees not passed - if err = DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil { + if err = DeleteNotPassedAssignee(ctx, issue, doer, allNewAssignees); err != nil { return err } @@ -121,7 +122,7 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi // has access to the repo. for _, assignee := range allNewAssignees { // Extra method to prevent double adding (which would result in removing) - err = AddAssigneeIfNotAssigned(issue, doer, assignee.ID) + err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID) if err != nil { return err } @@ -131,42 +132,42 @@ func UpdateAssignees(issue *issues_model.Issue, oneAssignee string, multipleAssi } // DeleteIssue deletes an issue -func DeleteIssue(doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue) error { +func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue) error { // load issue before deleting it - if err := issue.LoadAttributes(gitRepo.Ctx); err != nil { + if err := issue.LoadAttributes(ctx); err != nil { return err } - if err := issue.LoadPullRequest(gitRepo.Ctx); err != nil { + if err := issue.LoadPullRequest(ctx); err != nil { return err } // delete entries in database - if err := deleteIssue(issue); err != nil { + if err := deleteIssue(ctx, issue); err != nil { return err } // delete pull request related git data - if issue.IsPull { + if issue.IsPull && gitRepo != nil { if err := gitRepo.RemoveReference(fmt.Sprintf("%s%d/head", git.PullPrefix, issue.PullRequest.Index)); err != nil { return err } } - notification.NotifyDeleteIssue(gitRepo.Ctx, doer, issue) + notification.NotifyDeleteIssue(ctx, doer, issue) return nil } // AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue. // Also checks for access of assigned user -func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) { - assignee, err := user_model.GetUserByID(db.DefaultContext, assigneeID) +func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) { + assignee, err := user_model.GetUserByID(ctx, assigneeID) if err != nil { return err } // Check if the user is already assigned - isAssigned, err := issues_model.IsUserAssignedToIssue(db.DefaultContext, issue, assignee) + isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee) if err != nil { return err } @@ -175,7 +176,7 @@ func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, return nil } - valid, err := access_model.CanBeAssigned(db.DefaultContext, assignee, issue.Repo, issue.IsPull) + valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull) if err != nil { return err } @@ -183,7 +184,7 @@ func AddAssigneeIfNotAssigned(issue *issues_model.Issue, doer *user_model.User, return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name} } - _, _, err = ToggleAssignee(issue, doer, assigneeID) + _, _, err = ToggleAssignee(ctx, issue, doer, assigneeID) if err != nil { return err } @@ -206,8 +207,8 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i } // deleteIssue deletes the issue -func deleteIssue(issue *issues_model.Issue) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go index b67d2e2e79a..da0e97c23c2 100644 --- a/services/issue/issue_test.go +++ b/services/issue/issue_test.go @@ -44,7 +44,7 @@ func TestIssue_DeleteIssue(t *testing.T) { ID: issueIDs[2], } - err = deleteIssue(issue) + err = deleteIssue(db.DefaultContext, issue) assert.NoError(t, err) issueIDs, err = issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1) assert.NoError(t, err) @@ -55,7 +55,7 @@ func TestIssue_DeleteIssue(t *testing.T) { assert.NoError(t, err) issue, err = issues_model.GetIssueByID(db.DefaultContext, 4) assert.NoError(t, err) - err = deleteIssue(issue) + err = deleteIssue(db.DefaultContext, issue) assert.NoError(t, err) assert.EqualValues(t, 2, len(attachments)) for i := range attachments { @@ -78,7 +78,7 @@ func TestIssue_DeleteIssue(t *testing.T) { assert.NoError(t, err) assert.False(t, left) - err = deleteIssue(issue2) + err = deleteIssue(db.DefaultContext, issue2) assert.NoError(t, err) left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1) assert.NoError(t, err) diff --git a/services/pull/comment.go b/services/pull/comment.go index 933ad09a85e..24dfd8af0ce 100644 --- a/services/pull/comment.go +++ b/services/pull/comment.go @@ -90,7 +90,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *iss ops.Content = string(dataJSON) - comment, err = issue_service.CreateComment(ops) + comment, err = issue_service.CreateComment(ctx, ops) return comment, err } diff --git a/services/pull/pull.go b/services/pull/pull.go index fe2f002010b..55dfd3c1802 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -52,7 +52,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu } for _, assigneeID := range assigneeIDs { - if err := issue_service.AddAssigneeIfNotAssigned(pull, pull.Poster, assigneeID); err != nil { + if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil { return err } } @@ -122,7 +122,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu Content: string(dataJSON), } - _, _ = issue_service.CreateComment(ops) + _, _ = issue_service.CreateComment(ctx, ops) } return nil @@ -221,7 +221,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer OldRef: oldBranch, NewRef: targetBranch, } - if _, err = issue_service.CreateComment(options); err != nil { + if _, err = issue_service.CreateComment(ctx, options); err != nil { return fmt.Errorf("CreateChangeTargetBranchComment: %w", err) } diff --git a/services/pull/review.go b/services/pull/review.go index ba93b5e2f54..6feffe4ec4e 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -248,7 +248,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo return nil, err } } - return issue_service.CreateComment(&issues_model.CreateCommentOptions{ + return issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeCode, Doer: doer, Repo: repo, @@ -368,7 +368,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, return } - comment, err = issue_service.CreateComment(&issues_model.CreateCommentOptions{ + comment, err = issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ Doer: doer, Content: message, Type: issues_model.CommentTypeDismissReview, diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index 4427c610bfa..4b25f97dddb 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -67,7 +67,7 @@ func TestAPIMergePullWIP(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{Status: issues_model.PullRequestStatusMergeable}, unittest.Cond("has_merged = ?", false)) pr.LoadIssue(db.DefaultContext) - issue_service.ChangeTitle(pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) + issue_service.ChangeTitle(db.DefaultContext, pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) // force reload pr.LoadAttributes(db.DefaultContext) From ed81b608cb5bd94ef518393cdd724c4fac1215d4 Mon Sep 17 00:00:00 2001 From: Tyrone Yeh Date: Sat, 15 Apr 2023 02:48:36 +0800 Subject: [PATCH 03/12] Add option to search for users is active join a team (#24093) Adding a user in a team to enter a username gives a list of no active users --------- Co-authored-by: Jason Song --- routers/web/user/search.go | 1 + web_src/js/features/comp/SearchUserBox.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/web/user/search.go b/routers/web/user/search.go index c5c3aa75f08..bdc4116e377 100644 --- a/routers/web/user/search.go +++ b/routers/web/user/search.go @@ -24,6 +24,7 @@ func Search(ctx *context.Context) { Keyword: ctx.FormTrim("q"), UID: ctx.FormInt64("uid"), Type: user_model.UserTypeIndividual, + IsActive: ctx.FormOptionalBool("active"), ListOptions: listOptions, }) if err != nil { diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js index 0e9a005acf6..960b787fea6 100644 --- a/web_src/js/features/comp/SearchUserBox.js +++ b/web_src/js/features/comp/SearchUserBox.js @@ -11,7 +11,7 @@ export function initCompSearchUserBox() { $searchUserBox.search({ minCharacters: 2, apiSettings: { - url: `${appSubUrl}/user/search?q={query}`, + url: `${appSubUrl}/user/search?active=1&q={query}`, onResponse(response) { const items = []; const searchQuery = $searchUserBox.find('input').val(); From 2902d1e9d193a157bccd46cdda449de5443a2fd8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 15 Apr 2023 03:29:05 +0800 Subject: [PATCH 04/12] Sort repo topic labels by name (#24123) Close #24077 --- models/repo/topic.go | 4 +++- routers/web/repo/blame.go | 7 +------ web_src/js/features/repo-home.js | 5 +++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/models/repo/topic.go b/models/repo/topic.go index 05f50cfe463..88fe532be9a 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -194,14 +194,16 @@ func (opts *FindTopicOptions) toConds() builder.Cond { // FindTopics retrieves the topics via FindTopicOptions func FindTopics(opts *FindTopicOptions) ([]*Topic, int64, error) { sess := db.GetEngine(db.DefaultContext).Select("topic.*").Where(opts.toConds()) + orderBy := "topic.repo_count DESC" if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") + orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result } if opts.PageSize != 0 && opts.Page != 0 { sess = db.SetSessionPagination(sess, opts) } topics := make([]*Topic, 0, 10) - total, err := sess.Desc("topic.repo_count").FindAndCount(&topics) + total, err := sess.OrderBy(orderBy).FindAndCount(&topics) return topics, total, err } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 3546334ed64..0e232c194c2 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -12,7 +12,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -23,10 +22,6 @@ import ( "code.gitea.io/gitea/modules/util" ) -const ( - tplBlame base.TplName = "repo/home" -) - type blameRow struct { RowNumber int Avatar gotemplate.HTML @@ -140,7 +135,7 @@ func RefBlame(ctx *context.Context) { renderBlame(ctx, blameParts, commitNames, previousCommits) - ctx.HTML(http.StatusOK, tplBlame) + ctx.HTML(http.StatusOK, tplRepoHome) } func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]*user_model.UserCommit, map[string]string) { diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index e08e84b393c..55a27710542 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -41,6 +41,7 @@ export function initRepoTopicBar() { viewDiv.children('.topic').remove(); if (topics.length) { const topicArray = topics.split(','); + topicArray.sort(); for (let i = 0; i < topicArray.length; i++) { const link = $(''); link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`); @@ -57,12 +58,12 @@ export function initRepoTopicBar() { topicPrompts.formatPrompt = xhr.responseJSON.message; const {invalidTopics} = xhr.responseJSON; - const topicLables = topicDropdown.children('a.ui.label'); + const topicLabels = topicDropdown.children('a.ui.label'); for (const [index, value] of topics.split(',').entries()) { for (let i = 0; i < invalidTopics.length; i++) { if (invalidTopics[i] === value) { - topicLables.eq(index).removeClass('green').addClass('red'); + topicLabels.eq(index).removeClass('green').addClass('red'); } } } From 35e562d7bd5e984a5cb97f74f5753d2a9998c1d3 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sat, 15 Apr 2023 00:07:26 +0000 Subject: [PATCH 05/12] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 2 + options/locale/locale_ru-RU.ini | 81 +++++++++++++++++++++++++++------ options/locale/locale_zh-TW.ini | 4 ++ 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 4339b3d19ac..5649352254c 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -133,6 +133,8 @@ buttons.list.task.tooltip=Adicionar uma lista de tarefas buttons.mention.tooltip=Mencionar um utilizador ou uma equipa buttons.ref.tooltip=Referenciar uma questão ou um pedido de integração buttons.switch_to_legacy.tooltip=Usar o editor clássico +buttons.enable_monospace_font=Habilitar tipo de letra mono-espaçado +buttons.disable_monospace_font=Desabilitar tipo de letra mono-espaçado [filter] string.asc=A - Z diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index b74ed160945..da5a739c5b6 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -4,7 +4,7 @@ explore=Обзор help=Помощь logo=Логотип sign_in=Вход -sign_in_with=Войдите с помощью +sign_in_with=Войти с помощью sign_out=Выход sign_up=Регистрация link_account=Привязать аккаунт @@ -25,14 +25,14 @@ licenses=Лицензии return_to_gitea=Вернуться к Gitea username=Имя пользователя -email=Адрес эл. почты +email=Адрес электронной почты password=Пароль access_token=Токен доступа re_type=Введите пароль еще раз captcha=CAPTCHA twofa=Двухфакторная аутентификация twofa_scratch=Двухфакторный scratch-код -passcode=Пароль +passcode=Код webauthn_insert_key=Вставьте ваш ключ безопасности webauthn_sign_in=Нажмите кнопку на ключе безопасности. Если ваш ключ безопасности не имеет кнопки, вставьте его снова. @@ -41,11 +41,11 @@ webauthn_use_twofa=Используйте двухфакторный код с webauthn_error=Не удалось прочитать ваш ключ безопасности. webauthn_unsupported_browser=Ваш браузер в настоящее время не поддерживает WebAuthn. webauthn_error_unknown=Произошла неизвестная ошибка. Повторите попытку. -webauthn_error_insecure=`WebAuthn поддерживает только безопасные соединения. Для тестирования по HTTP, вы можете использовать "localhost" или "127.0.0.1"` +webauthn_error_insecure=`WebAuthn поддерживает только безопасные соединения. Для тестирования по HTTP можно использовать "localhost" или "127.0.0.1"` webauthn_error_unable_to_process=Сервер не смог обработать ваш запрос. -webauthn_error_duplicated=Представленный ключ не подходит для этого запроса. Если вы пытаетесь зарегистрировать его, убедитесь, что ключ ещё не зарегистрирован. -webauthn_error_empty=Вы должны указать имя для этого ключа. -webauthn_error_timeout=Тайм-аут достигнут до того, как ваш ключ был прочитан. Перезагрузите эту страницу и повторите попытку. +webauthn_error_duplicated=Данный ключ безопасности не разрешен для этого запроса. Пожалуйста, убедитесь, что ключ не регистрировался ранее. +webauthn_error_empty=Необходимо задать имя для этого ключа. +webauthn_error_timeout=Время истекло раньше, чем ключ был прочитан. Перезагрузите эту страницу и повторите попытку. webauthn_reload=Обновить repository=Репозиторий @@ -91,7 +91,7 @@ enabled=Включено disabled=Отключен copy=Скопировать -copy_url=Копировать URL +copy_url=Скопировать URL copy_content=Скопировать содержимое copy_branch=Скопировать имя ветки copy_success=Скопировано! @@ -106,7 +106,7 @@ step1=Шаг 1: step2=Шаг 2: error=Ошибка -error404=Страница, которую вы пытаетесь открыть, либо не существует, либо недостаточно прав для ее просмотра. +error404=Либо страница, которую вы пытаетесь открыть, не существует, либо у вас недостаточно прав для ее просмотра. never=Никогда @@ -128,8 +128,8 @@ string.desc=Я - А [error] occurred=Произошла ошибка report_message=Если вы уверены, что это баг Gitea, пожалуйста, поищите задачу на GitHub или создайте новую при необходимости. -missing_csrf=Некорректный запрос: CSRF токен отсутствует -invalid_csrf=Некорректный запрос: неверный CSRF токен +missing_csrf=Некорректный запрос: отсутствует токен CSRF +invalid_csrf=Некорректный запрос: неверный токен CSRF network_error=Ошибка сети [startpage] @@ -137,7 +137,7 @@ app_desc=Удобный сервис собственного хостинга install=Простой в установке install_desc=Просто запустите исполняемый файл для вашей платформы, разверните через Docker, или установите с помощью менеджера пакетов. platform=Кроссплатформенный -platform_desc=Gitea работает на любой операционной системе, которая может компилировать Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! +platform_desc=Gitea работает на любой платформе, поддерживаемой Go: Windows, macOS, Linux, ARM и т. д. Выбирайте, что вам больше нравится! lightweight=Легковесный lightweight_desc=Gitea имеет низкие системные требования и может работать на недорогом Raspberry Pi. Экономьте энергию вашей машины! license=Открытый исходный код @@ -154,7 +154,7 @@ host=Хост user=Имя пользователя password=Пароль db_name=Имя базы данных -db_helper=Для пользователей MySQL: пожалуйста, используйте движок InnoDB, и если вы используете "utf8mb4" - ваша версия InnoDB должна быть старше 5.6 . +db_helper=Для пользователей MySQL: пожалуйста, используйте хранилище InnoDB, и если вы используете "utf8mb4", версия InnoDB должна быть выше 5.6 . db_schema=Схема db_schema_helper=Оставьте пустым для значения по умолчанию ("public"). ssl_mode=SSL @@ -179,8 +179,8 @@ app_name=Название сайта app_name_helper=Здесь вы можете ввести название своей компании. repo_path=Путь до корня репозитория repo_path_helper=Все удалённые Git репозитории будут сохранены в эту директорию. -lfs_path=Корневой путь Git LFS -lfs_path_helper=В этой директории будут храниться файлы Git LFS. Оставьте пустым, чтобы отключить LFS. +lfs_path=Путь к корневому каталогу Git LFS +lfs_path_helper=В этом каталоге будут храниться файлы Git LFS. Оставьте пустым, чтобы отключить LFS. run_user=Запуск от имени пользователя run_user_helper=Введите имя пользователя операционной системы, под которым работает Gitea. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев. domain=Домен сервера @@ -287,6 +287,7 @@ search=Поиск code=Код search.type.tooltip=Тип поиска search.fuzzy=Неточный +search.fuzzy.tooltip=Включать результаты, которые не полностью соответствуют поисковому запросу search.match=Соответствие search.match.tooltip=Включать только результаты, которые точно соответствуют поисковому запросу code_search_unavailable=В настоящее время поиск по коду недоступен. Обратитесь к администратору сайта. @@ -1029,6 +1030,7 @@ issues=Задачи pulls=Запросы на слияние project_board=Проекты packages=Пакеты +actions=Действия labels=Метки org_labels_desc=Метки уровня организации, которые можно использовать с всеми репозиториями< / strong> в этой организации org_labels_desc_manage=управлять @@ -1247,6 +1249,7 @@ issues.choose.blank=По умолчанию issues.choose.blank_about=Создать запрос из шаблона по умолчанию. issues.choose.ignore_invalid_templates=Некорректные шаблоны были проигнорированы issues.choose.invalid_templates=Найден(ы) %v неверный(х) шаблон(ов) +issues.choose.invalid_config=Конфигурация задачи содержит ошибки: issues.no_ref=Не указана ветка или тэг issues.create=Добавить задачу issues.new_label=Новая метка @@ -1525,6 +1528,8 @@ pulls.allow_edits_from_maintainers=Разрешить редактировани pulls.allow_edits_from_maintainers_desc=Пользователи с доступом на запись в основную ветку могут отправлять изменения и в эту ветку pulls.allow_edits_from_maintainers_err=Не удалось обновить pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. +pulls.has_viewed_file=Просмотрено +pulls.viewed_files_label=%[1]d из %[2]d файлов просмотрено pulls.expand_files=Показать все файлы pulls.collapse_files=Свернуть все файлы pulls.compare_base=базовая ветка @@ -1572,6 +1577,7 @@ pulls.can_auto_merge_desc=Этот запрос на слияние может pulls.cannot_auto_merge_desc=Этот запрос на слияние не может быть объединён автоматически. pulls.cannot_auto_merge_helper=Пожалуйста, совершите слияние вручную для урегулирования конфликтов. pulls.num_conflicting_files_1=%d конфликтующий файл +pulls.num_conflicting_files_n=%d конфликтующих файлов pulls.approve_count_1=%d одобрение pulls.reject_count_1=%d запрос на изменение pulls.reject_count_n=%d запросов на изменение @@ -1620,10 +1626,23 @@ pulls.reopened_at=`переоткрыл этот запрос на слияни pulls.merge_instruction_hint=`Вы также можете просмотреть инструкции командной строки.` pulls.merge_instruction_step1_desc=В репозитории вашего проекта посмотрите новую ветку и протестируйте изменения. pulls.merge_instruction_step2_desc=Объединить изменения и обновить на Gitea. +pulls.clear_merge_message=Очистить сообщение о слиянии +pulls.clear_merge_message_hint=Очистка сообщения о слиянии удалит только содержимое сообщения коммита, но сохранит сгенерированные git добавки, такие как "Co-Authored-By …". +pulls.auto_merge_button_when_succeed=(При успешных проверках) +pulls.auto_merge_when_succeed=Слить автоматически после прохождения всех проверок +pulls.auto_merge_newly_scheduled=Запрос был запланирован для слияния после прохождения всех проверок. +pulls.auto_merge_has_pending_schedule=%[1]s запланировал этот запрос для автоматического слияния, когда все проверки пройдены %[2]s. +pulls.auto_merge_cancel_schedule=Отменить автоматическое слияние +pulls.auto_merge_not_scheduled=Этот запрос не запланирован для автоматического слияния. +pulls.auto_merge_canceled_schedule=Автоматическое слияние для этого запроса было отменено. +pulls.auto_merge_newly_scheduled_comment=`запланировал этот запрос для автоматического слияния после прохождения всех проверок %[1]s` +pulls.auto_merge_canceled_schedule_comment=`отменил автоматическое слияние этого запроса после прохождения всех проверок %[1]s` +pulls.delete.title=Удалить этот запрос на слияние? +pulls.delete.text=Вы действительно хотите удалить этот запрос на слияние? (Это навсегда удалит всё содержимое. Возможно, лучше закрыть запрос в архивных целях.) milestones.new=Новый этап milestones.closed=Закрыт %s @@ -1669,6 +1688,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан signing.wont_sign.approved=Слияние не будет подписано, так как PR не одобрен signing.wont_sign.not_signed_in=Вы не авторизовались +ext_wiki=Доступ к внешней вики ext_wiki.desc=Ссылка на внешнюю вики. wiki=Вики @@ -1765,6 +1785,7 @@ search=Поиск search.search_repo=Поиск по репозиторию search.type.tooltip=Тип поиска search.fuzzy=Неточный +search.fuzzy.tooltip=Включать результаты, которые не полностью соответствуют поисковому запросу search.match=Соответствие search.match.tooltip=Включать только результаты, которые точно соответствуют поисковому запросу search.results=Результаты поиска "%s" в %s @@ -1798,6 +1819,7 @@ settings.mirror_sync_in_progress=Синхронизируются репозит settings.site=Сайт settings.update_settings=Обновить настройки settings.branches.update_default_branch=Обновить ветку по умолчанию +settings.branches.add_new_rule=Добавить новое правило settings.advanced_settings=Расширенные настройки settings.wiki_desc=Включить Вики для репозитория settings.use_internal_wiki=Использовать встроенную вики-систему @@ -1816,15 +1838,22 @@ settings.tracker_url_format_error=Формат URL внешнего баг-тр settings.tracker_issue_style=Формат нумерации для внешней системы учета задач settings.tracker_issue_style.numeric=Цифровой settings.tracker_issue_style.alphanumeric=Буквенноцифровой +settings.tracker_issue_style.regexp=Регулярное выражение +settings.tracker_issue_style.regexp_pattern=Шаблон регулярного выражения +settings.tracker_issue_style.regexp_pattern_desc=Вместо {index} будет использоваться первая захваченная группа. settings.tracker_url_format_desc=Вы можете использовать шаблоны {user}, {repo} и {index} для имени пользователя, репозитория и номера задачи. settings.enable_timetracker=Включить отслеживание времени settings.allow_only_contributors_to_track_time=Учитывать только участников разработки в подсчёте времени settings.pulls_desc=Включить запросы на слияние settings.pulls.ignore_whitespace=Игнорировать незначащие изменения (пробелы, табуляция) при проверке на конфликты слияния settings.pulls.enable_autodetect_manual_merge=Включить автоопределение ручного слияния (Примечание: в некоторых особых случаях могут возникнуть ошибки) +settings.pulls.allow_rebase_update=Включить обновление ветки из запроса на слияние путём rebase settings.pulls.default_delete_branch_after_merge=Удалить ветку запроса после его слияния по умолчанию settings.pulls.default_allow_edits_from_maintainers=По умолчанию разрешать редактирование сопровождающими +settings.releases_desc=Включить релизы +settings.packages_desc=Включить реестр пакетов settings.projects_desc=Включить проекты репозитория +settings.actions_desc=Включить действия репозитория settings.admin_settings=Настройки администратора settings.admin_enable_health_check=Выполнять проверки целостности этого репозитория (git fsck) settings.admin_code_indexer=Индексатор кода @@ -1886,6 +1915,7 @@ settings.confirm_delete=Удалить репозиторий settings.add_collaborator=Добавить соавтора settings.add_collaborator_success=Соавтор добавлен. settings.add_collaborator_inactive_user=Невозможно добавить неактивного пользователя как соавтора. +settings.add_collaborator_owner=Невозможно добавить владельца в качестве соавтора. settings.add_collaborator_duplicate=Соавтор уже добавлен в этот репозиторий. settings.delete_collaborator=Удалить settings.collaborator_deletion=Удалить соавтора @@ -1917,6 +1947,7 @@ settings.webhook.headers=Заголовки settings.webhook.payload=Содержимое settings.webhook.body=Тело ответа settings.webhook.replay.description=Повторить этот веб-хук. +settings.webhook.delivery.success=Событие было добавлено в очередь доставки. Может пройти несколько секунд, прежде чем оно отобразится в истории. settings.githooks_desc=Git-хуки предоставляются самим Git. Вы можете изменять файлы хуков из списка ниже, чтобы настроить собственные операции. settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведёт к отключению хука. settings.githook_name=Название хукa @@ -1981,6 +2012,7 @@ settings.event_package_desc=Пакет создан или удален в ре settings.branch_filter=Фильтр веток settings.branch_filter_desc=Белый список ветвей для событий Push, создания ветвей и удаления ветвей, указанных в виде глоб-шаблона. Если пустой или *, то все событий для всех ветвей будут зарегистрированы. Перейдите по ссылке github.com/gobwas/glob на документацию по синтаксису. Примеры: master, {master,release*}. settings.authorization_header=Заголовок Authorization +settings.authorization_header_desc=Будет включён в качестве заголовка авторизации для запросов. Примеры: %s. settings.active=Активный settings.active_helper=Информация о происходящих событиях будет отправляться на URL этого веб-хука. settings.add_hook_success=Веб-хук был добавлен. @@ -2020,6 +2052,8 @@ settings.key_been_used=Идентичный ключ развёртывания settings.key_name_used=Ключ развёртывания с таким именем уже существует. settings.add_key_success=Новый ключ развёртывания '%s' успешно добавлен. settings.deploy_key_deletion=Удалить ключ развёртывания +settings.deploy_key_deletion_desc=Удаление ключа развёртывания сделает невозможным доступ к репозиторию с его помощью. Вы уверены? +settings.deploy_key_deletion_success=Ключ развёртывания был успешно удалён. settings.branches=Ветки settings.protected_branch=Защита веток settings.protected_branch.save_rule=Сохранить правило @@ -3035,11 +3069,20 @@ keywords=Ключевые слова details=Подробнее details.author=Автор details.project_site=Сайт проекта +details.repository_site=Сайт репозитория +details.documentation_site=Сайт документации details.license=Лицензия versions=Версии versions.view_all=Показать всё dependency.version=Версия +cargo.registry=Настройте этот реестр в файле конфигурации Cargo (например, ~/.cargo/config.toml): +cargo.install=Чтобы установить пакет с помощью Cargo, выполните следующую команду: +cargo.documentation=Для получения дополнительной информации о реестре Cargo смотрите документацию. +cargo.details.repository_site=Сайт репозитория +cargo.details.documentation_site=Сайт документации +chef.registry=Настройте этот реестр в своём файле ~/.chef/config.rb: chef.install=Чтобы установить пакет, выполните следующую команду: +chef.documentation=Для получения дополнительной информации о реестре Chef смотрите документацию. composer.registry=Настройте этот реестр в файле ~/.composer/config.json: composer.install=Чтобы установить пакет с помощью Composer, выполните следующую команду: composer.documentation=Для получения дополнительной информации о реестре Composer смотрите документацию. @@ -3048,6 +3091,11 @@ conan.details.repository=Репозиторий conan.registry=Настроить реестр из командной строки: conan.install=Чтобы установить пакет с помощью Conan, выполните следующую команду: conan.documentation=Для получения дополнительной информации о реестре Conan смотрите документацию. +conda.registry=Пропишите этот реестр в качестве репозитория Conda в своём файле .condarc: +conda.install=Чтобы установить пакет с помощью Conda, выполните следующую команду: +conda.documentation=Для получения дополнительной информации о реестре Conda смотрите документацию. +conda.details.repository_site=Сайт репозитория +conda.details.documentation_site=Сайт документации container.details.type=Тип образа container.details.platform=Платформа container.digest=Отпечаток: @@ -3061,6 +3109,7 @@ helm.registry=Настроить реестр из командной строк helm.install=Чтобы установить пакет, выполните следующую команду: helm.documentation=Для получения дополнительной информации о реестре Helm смотрите документацию. maven.install2=Выполнить через командную строку: +maven.download=Чтобы скачать зависимость, запустите в командной строке: nuget.registry=Настроить реестр из командной строки: nuget.install=Чтобы установить пакет с помощью NuGet, выполните следующую команду: nuget.documentation=Для получения дополнительной информации о реестре NuGet смотрите документацию. @@ -3071,6 +3120,8 @@ npm.dependencies=Зависимости npm.dependencies.peer=Одноранговые зависимости npm.dependencies.optional=Необязательные зависимости npm.details.tag=Тег +pub.install=Чтобы установить пакет с помощью Dart, выполните следующую команду: +pub.documentation=Для получения дополнительной информации о реестре Conda смотрите документацию. pypi.requires=Требуется Python pypi.documentation=Для получения дополнительной информации о реестре PyPI смотрите документацию. rubygems.install=Чтобы установить пакет с помощью gem, выполните следующую команду: diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 6dfbe25181a..a2eb097eb4a 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -120,6 +120,8 @@ footer.software=關於軟體 footer.links=連結 [editor] +buttons.enable_monospace_font=啟用等寬字型 +buttons.disable_monospace_font=停用等寬字型 [filter] string.asc=A - Z @@ -1748,6 +1750,8 @@ wiki.create_first_page=建立第一個頁面 wiki.page=頁面 wiki.filter_page=過濾頁面 wiki.new_page=頁面 +wiki.page_title=頁面標題 +wiki.page_content=頁面內容 wiki.default_commit_message=關於此次頁面修改的說明(非必要)。 wiki.save_page=儲存頁面 wiki.last_commit_info=%s 於 %s 修改了此頁面 From b4e952545b95953056f2645b38aa17d15bcd58ab Mon Sep 17 00:00:00 2001 From: Yarden Shoham Date: Sat, 15 Apr 2023 14:01:54 +0300 Subject: [PATCH 06/12] Remove untranslatable `on_date` key (#24106) - Follows #23988 - Fixes: #24074 by removing this key GitHub's `relative-time` elements allow us to force their rendering to `auto`, `past`, or `future` tense. We will never show an absolute date `on ...` in `TimeSince` ## Before ![image](https://user-images.githubusercontent.com/20454870/231735872-048c7bf3-6aa1-4113-929d-75a985c9922c.png) ## After ![image](https://user-images.githubusercontent.com/20454870/231736116-6ad47b63-77f4-4d3f-82a2-ee9a46ba2bd1.png) --------- Co-authored-by: wxiaoguang --- modules/timeutil/since.go | 20 ++++++++++++-- options/locale/locale_en-US.ini | 4 --- routers/web/devtest/devtest.go | 10 +++++++ templates/devtest/gitea-ui.tmpl | 49 +++++++++++++++++++++++++++++---- templates/package/view.tmpl | 6 ++-- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index bdde54c6175..e6a2519d212 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -114,11 +114,25 @@ func timeSincePro(then, now time.Time, lang translation.Locale) string { return strings.TrimPrefix(timeStr, ", ") } +func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML { + friendlyText := then.Format("2006-01-02 15:04:05 +07:00") + + // document: https://github.com/github/relative-time-element + attrs := `tense="past"` + isFuture := now.Before(then) + if isFuture { + attrs = `tense="future"` + } + + // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip + htm := fmt.Sprintf(`%s`, + attrs, then.Format(time.RFC3339), friendlyText) + return template.HTML(htm) +} + // TimeSince renders relative time HTML given a time.Time func TimeSince(then time.Time, lang translation.Locale) template.HTML { - timestamp := then.UTC().Format(time.RFC3339) - // declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip - return template.HTML(fmt.Sprintf(`%s`, lang.Tr("on_date"), timestamp, timestamp)) + return timeSinceUnix(then, time.Now(), lang) } // TimeSinceUnix renders relative time HTML given a TimeStamp diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f71ea824e97..c2c8f1e120c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -112,8 +112,6 @@ never = Never rss_feed = RSS Feed -on_date = on - [aria] navbar = Navigation Bar footer = Footer @@ -3129,8 +3127,6 @@ starred_repo = starred %[2]s watched_repo = started watching %[2]s [tool] -ago = %s ago -from_now = %s from now now = now future = future 1s = 1 second diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 784940909a6..48875e306d2 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -7,6 +7,7 @@ import ( "net/http" "path" "strings" + "time" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -32,5 +33,14 @@ func List(ctx *context.Context) { } func Tmpl(ctx *context.Context) { + now := time.Now() + ctx.Data["TimeNow"] = now + ctx.Data["TimePast5s"] = now.Add(-5 * time.Second) + ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second) + ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute) + ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute) + ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second) + ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second) + ctx.HTML(http.StatusOK, base.TplName("devtest"+path.Clean("/"+ctx.Params("sub")))) } diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index c5ab863d00d..1ab9ae7b7c7 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -1,12 +1,51 @@ {{template "base/head" .}} -
+
+
- - +

Tooltip

+
text with tooltip
+
text with interactive tooltip
+
- text with tooltip +

GiteaOriginUrl

+
+
- {{template "shared/combomarkdowneditor" .}} + +
+

LocaleNumber

+
{{LocaleNumber 1}}
+
{{LocaleNumber 12}}
+
{{LocaleNumber 123}}
+
{{LocaleNumber 1234}}
+
{{LocaleNumber 12345}}
+
{{LocaleNumber 123456}}
+
{{LocaleNumber 1234567}}
+
+ +
+

TimeSince

+
Now: {{TimeSince .TimeNow $.locale}}
+
5s past: {{TimeSince .TimePast5s $.locale}}
+
5s future: {{TimeSince .TimeFuture5s $.locale}}
+
2m past: {{TimeSince .TimePast2m $.locale}}
+
2m future: {{TimeSince .TimeFuture2m $.locale}}
+
1y past: {{TimeSince .TimePast1y $.locale}}
+
1y future: {{TimeSince .TimeFuture1y $.locale}}
+
+ +
+

ComboMarkdownEditor

+
ps: no JS code attached, so just a layout
+ {{template "shared/combomarkdowneditor" .}} +
+ +
{{template "base/footer" .}} diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 9677b8eb09d..7c622a91b3e 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -84,9 +84,9 @@ {{.locale.Tr "packages.versions.view_all"}}
{{range .LatestVersions}} -
- {{.Version}} - {{$.locale.Tr "on_date"}} {{.CreatedUnix.FormatDate}} +
+ {{.Version}} + {{template "shared/datetime/short" (dict "Datetime" (.CreatedUnix.FormatDate) "Fallback" (.CreatedUnix.FormatDate))}}
{{end}}
From fa3495183bf12c2414e18a3d3454067663c7aaa9 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Sat, 15 Apr 2023 22:52:44 +0900 Subject: [PATCH 07/12] Add migration to fix external unit access mode of owner/admin team (#24117) Fix the incorrect migration in #23675 and #24012 External Unit (Tracker and Wiki) access mode should be `read` in owner/admin team. --- models/migrations/migrations.go | 2 ++ models/migrations/v1_20/v253.go | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 models/migrations/v1_20/v253.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 35a18fb7f2b..42806a808f7 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -483,6 +483,8 @@ var migrations = []Migration{ NewMigration("Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode), // v252 -> v253 NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode), + // v253 -> v254 + NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_20/v253.go b/models/migrations/v1_20/v253.go new file mode 100644 index 00000000000..96c494bd8d9 --- /dev/null +++ b/models/migrations/v1_20/v253.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "code.gitea.io/gitea/modules/log" + + "xorm.io/xorm" +) + +func FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam(x *xorm.Engine) error { + type UnitType int + type AccessMode int + + type TeamUnit struct { + ID int64 `xorm:"pk autoincr"` + Type UnitType `xorm:"UNIQUE(s)"` + AccessMode AccessMode + } + + const ( + // AccessModeRead read access + AccessModeRead = 1 + + // Unit Type + TypeExternalWiki = 6 + TypeExternalTracker = 7 + ) + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + count, err := sess.Table("team_unit"). + Where("type IN (?, ?) AND access_mode > ?", TypeExternalWiki, TypeExternalTracker, AccessModeRead). + Update(&TeamUnit{ + AccessMode: AccessModeRead, + }) + if err != nil { + return err + } + log.Debug("Updated %d ExternalTracker and ExternalWiki access mode to belong to owner and admin", count) + + return sess.Commit() +} From 1af3dc6ee35b3c60e697bdb9160c03e6eb3700aa Mon Sep 17 00:00:00 2001 From: Jonathan Tran Date: Sun, 16 Apr 2023 03:27:23 -0400 Subject: [PATCH 08/12] Fix 2-dot direct compare to use the right base commit (#24133) For 2-dot direct compare, we should use the base commit in the title and templates, as is used elsewhere, not the common ancestor which is used for 3-dot compare. I believe that this change should have been included in #22949. --- routers/web/repo/compare.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index c49eb762d8a..92abe0ce259 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -551,7 +551,11 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.ServerError("GetCompareInfo", err) return nil } - ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase + if ci.DirectComparison { + ctx.Data["BeforeCommitID"] = ci.CompareInfo.BaseCommitID + } else { + ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase + } return ci } From 0e059846670190f0a2f4935fb74b2159e2d80181 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 16 Apr 2023 20:01:08 +0800 Subject: [PATCH 09/12] Set EasyMDE heading font-size to the same size as the resulting markdown (#24151) Fix #23816 According to my personal experience, the EasyMDE is still useful when writing a lot of contents, eg: the wiki page. It's not difficult to improve its heading styles, so let's make it. Before: image After: image --- web_src/css/editor-markdown.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/web_src/css/editor-markdown.css b/web_src/css/editor-markdown.css index 7d6c36635db..eb5c5d13b8c 100644 --- a/web_src/css/editor-markdown.css +++ b/web_src/css/editor-markdown.css @@ -44,6 +44,31 @@ max-height: calc(100vh - 200px); } +/* use the same styles as markup/content.css */ +.combo-markdown-editor .CodeMirror-scroll .cm-header-1 { + font-size: 2em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-2 { + font-size: 1.5em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-3 { + font-size: 1.25em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-4 { + font-size: 1em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-5 { + font-size: 0.875em; +} + +.combo-markdown-editor .CodeMirror-scroll .cm-header-6 { + font-size: 0.85em; +} + text-expander { display: block; position: relative; From 685b0ffa1969598a7b45b41af0a5566e5a74fabc Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 16 Apr 2023 21:58:22 +0800 Subject: [PATCH 10/12] Use 1.18's aria role for dropdown menus (#24144) According to erion's feedback, the 1.18 approach works with Safari (`role=menu` on the parent container), while the 1.19's approach doesn't work well with Safari+VoiceOver (although I tested it worked with Chrome a little better). I have tested this 1.18 approach could work for all Safari/Chrome+VoiceOver and Chrome+Talkback. Let's try to make it on try.gitea.io to see whether it helps Safari users. --- web_src/js/modules/aria/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/modules/aria/dropdown.js b/web_src/js/modules/aria/dropdown.js index 26c13524168..b1ff57ab858 100644 --- a/web_src/js/modules/aria/dropdown.js +++ b/web_src/js/modules/aria/dropdown.js @@ -148,8 +148,8 @@ function attachInit($dropdown) { // Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before. const isComboBox = $dropdown.find('input').length > 0; - dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'button'; - dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : 'menu'; + dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu'; + dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : ''; dropdown[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem'; attachDomEvents($dropdown, $focusable, $menu); From be7cd73439c2210bfd889915f74d66686e99dab6 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Mon, 17 Apr 2023 00:07:34 +0000 Subject: [PATCH 11/12] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 3 --- options/locale/locale_de-DE.ini | 3 --- options/locale/locale_el-GR.ini | 3 --- options/locale/locale_es-ES.ini | 3 --- options/locale/locale_fa-IR.ini | 3 --- options/locale/locale_fi-FI.ini | 3 --- options/locale/locale_fr-FR.ini | 3 --- options/locale/locale_hu-HU.ini | 3 --- options/locale/locale_id-ID.ini | 3 --- options/locale/locale_is-IS.ini | 2 -- options/locale/locale_it-IT.ini | 3 --- options/locale/locale_ja-JP.ini | 3 --- options/locale/locale_ko-KR.ini | 3 --- options/locale/locale_lv-LV.ini | 3 --- options/locale/locale_nl-NL.ini | 3 --- options/locale/locale_pl-PL.ini | 3 --- options/locale/locale_pt-BR.ini | 3 --- options/locale/locale_pt-PT.ini | 4 ---- options/locale/locale_ru-RU.ini | 3 --- options/locale/locale_si-LK.ini | 3 --- options/locale/locale_sk-SK.ini | 1 - options/locale/locale_sv-SE.ini | 3 --- options/locale/locale_tr-TR.ini | 3 --- options/locale/locale_uk-UA.ini | 3 --- options/locale/locale_zh-CN.ini | 3 --- options/locale/locale_zh-HK.ini | 3 --- options/locale/locale_zh-TW.ini | 3 --- 27 files changed, 79 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 5f6bfa76bca..2e720709f4f 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -110,7 +110,6 @@ never=Nikdy rss_feed=RSS kanál - [aria] navbar=Navigační lišta footer=Patička @@ -3027,8 +3026,6 @@ starred_repo=si oblíbil/a %[2]s watched_repo=začal/a sledovat %[2]s [tool] -ago=před %s -from_now=od teď %s now=nyní future=budoucí 1s=1 sekundou diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 28fc2477567..5b2eaf316f9 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -107,7 +107,6 @@ never=Niemals rss_feed=RSS Feed - [aria] [editor] @@ -2956,8 +2955,6 @@ starred_repo=markiert %[2]s watched_repo=beobachtet %[2]s [tool] -ago=vor %s -from_now=in %s now=jetzt future=Zukunft 1s=1 Sekunde diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 8b7d03dac32..e3fe183e595 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -110,7 +110,6 @@ never=Ποτέ rss_feed=Ροή RSS - [aria] navbar=Γραμμή Πλοήγησης footer=Υποσέλιδο @@ -3066,8 +3065,6 @@ starred_repo=έδωσε αστέρι στο %[2]s watched_repo=άρχισε να παρακολουθεί το %[2]s [tool] -ago=%s πριν -from_now=%s από τώρα now=τώρα future=μελλοντικό 1s=1 δευτερόλεπτο diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 719e4031fe3..00dcd046f64 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -107,7 +107,6 @@ never=Nunca rss_feed=Fuentes RSS - [aria] [editor] @@ -2990,8 +2989,6 @@ starred_repo=destacó %[2]s watched_repo=comenzó a seguir %[2]s [tool] -ago=hace %s -from_now=desde ahora %s now=ahora future=futuro 1s=1 segundo diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index b9be06c23dc..e4857e97db8 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -92,7 +92,6 @@ error404=صفحه موردنظر شما یا وجود ندارد%[2]s watched_repo=شروع به تماشای %[2]s کرد [tool] -ago=%s پیش -from_now=%s از هم اکنون now=حالا future=آینده 1s=۱ ثانیه diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 0d05ffba6cc..ffb0896f9f9 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -106,7 +106,6 @@ never=Ei koskaan rss_feed=RSS-syöte - [aria] [editor] @@ -1727,8 +1726,6 @@ compare_commits_general=Vertaa committeja create_branch=loi haaran %[3]s repossa %[4]s [tool] -ago=%s sitten -from_now=%s alkaen nyt now=nyt 1s=1 sekunti 1m=1 minuutti diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index fdda0d28918..689000709a3 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -107,7 +107,6 @@ never=Jamais rss_feed=Flux RSS - [aria] [editor] @@ -2522,8 +2521,6 @@ publish_release=`a publié "%[4]s" à %[3] review_dismissed_reason=Raison : [tool] -ago=il y a %s -from_now=dans %s now=maintenant future=futur 1s=1 seconde diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 7e35825ad6d..5692f144246 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -82,7 +82,6 @@ error404=Az elérni kívánt oldal vagy nem létezik, vagy %[3]s compare_commits=Bandingkan %d melakukan [tool] -ago=%s yang lalu -from_now=%s mulai sekarang now=sekarang future=masa depan 1s=1 detik diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index ade8d84efc5..d4695364d41 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -103,7 +103,6 @@ error404=Síðan sem þú ert að reyna að fá annað hvort er ekki til never=Aldrei - [aria] [editor] @@ -1319,7 +1318,6 @@ comment_pull=`gerði ummæli á sameiningarbeiðni %[3]s#%[2]s%[2]s watched_repo=ha iniziato a guardare %[2]s [tool] -ago=%s fa -from_now=%s da adesso now=ora future=futuro 1s=1 secondo diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 2976b2691f1..8bbc2a5a34f 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -112,7 +112,6 @@ never=無し rss_feed=RSSフィード - [aria] navbar=ナビゲーションバー footer=フッター @@ -3105,8 +3104,6 @@ starred_repo=が %[2]s にスターをつけました watched_repo=が %[2]s のウォッチを開始しました [tool] -ago=%s前 -from_now=今から%s後 now=たった今 future=未来 1s=1秒 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 8d868257a3c..5b2bc78a003 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -75,7 +75,6 @@ loading=불러오는 중... - [aria] [editor] @@ -1583,8 +1582,6 @@ transfer_repo=%s에서 %s로 저장소가 전송 compare_commits=%d 커밋들 비교 [tool] -ago=%s 전 -from_now=%s 지금부터 now=현재 future=미래 1s=1 초 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index c649a2670e0..01b5c783e75 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -110,7 +110,6 @@ never=Nekad rss_feed=RSS barotne - [aria] navbar=Navigācijas josla footer=Kājene @@ -3060,8 +3059,6 @@ starred_repo=atzīmēja ar zvaigznīti %[2]s watched_repo=sāka sekot %[2]s [tool] -ago=pirms %s -from_now=pēc %s no šī brīža now=tagad future=nākotnē 1s=1 sekundes diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 068f67dc51f..2a1074034f0 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -107,7 +107,6 @@ never=Nooit rss_feed=RSS Feed - [aria] [editor] @@ -2753,8 +2752,6 @@ compare_commits=Vergelijk %d commits compare_commits_general=Vergelijk commits [tool] -ago=%s geleden -from_now=%s vanaf nu now=nu future=toekomst 1s=1 seconde diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 0bb732b1594..5d89b21827f 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -105,7 +105,6 @@ error404=Strona, do której próbujesz dotrzeć nie istnieje lu never=Nigdy - [aria] [editor] @@ -2641,8 +2640,6 @@ mirror_sync_delete=synchronizuje i usuwa odwołanie %[2]s w %[2]s watched_repo=começou a observar %[2]s [tool] -ago=%s atrás -from_now=%s a partir de agora now=agora future=futuro 1s=1 segundo diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 5649352254c..85c243cabcd 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -112,8 +112,6 @@ never=Nunca rss_feed=Fonte RSS -on_date=no dia - [aria] navbar=Barra de navegação footer=Rodapé @@ -3129,8 +3127,6 @@ starred_repo=juntou %[2]s aos favoritos watched_repo=começou a vigiar %[2]s [tool] -ago=há %s -from_now=daqui a %s now=agora future=futuro 1s=1 segundo diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index da5a739c5b6..3bd988d72f2 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -112,7 +112,6 @@ never=Никогда rss_feed=RSS-лента - [aria] navbar=Панель навигации footer=Подвал @@ -2996,8 +2995,6 @@ starred_repo=добавил(а) %[2]s в избранное watched_repo=начала(а) наблюдение за %[2]s [tool] -ago=%s назад -from_now=%s с этого момента now=сейчас future=в будущем 1s=1 секунду diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 99a43974ef1..6771d118139 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -92,7 +92,6 @@ error404=ඔබ ළඟා වීමට උත්සාහ කරන පිටු never=කිසි විටෙකත් - [aria] [editor] @@ -2678,8 +2677,6 @@ create_branch=නිර්මාණය කරන ලද ශාඛාව %[2]sනැරඹීමට පටන් ගත්තා [tool] -ago=%s කලින් -from_now=%s මෙතැන් සිට now=දැන් future=අනාගතය 1s=තත්පර 1 diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 9e38834b7d7..76cc71c940a 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -106,7 +106,6 @@ never=Nikdy rss_feed=RSS kanál - [aria] [editor] diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 0d4a0036cf5..cf3056ae3d5 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -81,7 +81,6 @@ error404=Sidan du försöker nå finns inte eller så h - [aria] [editor] @@ -2110,8 +2109,6 @@ compare_commits_general=Jämför commits mirror_sync_delete=synkade och raderade referens %[2]s%[3]s från spegel [tool] -ago=%s sedan -from_now=%s från och med nu now=nu future=framtiden 1s=1 sekund diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index a5e7061d492..858fdea2317 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -110,7 +110,6 @@ never=Asla rss_feed=RSS Beslemesi - [aria] navbar=Gezinti Çubuğu footer=Alt Bilgi @@ -2988,8 +2987,6 @@ starred_repo=%[2]s deposuna yıldız bıraktı watched_repo=%[2]s deposunu izlemeye başladı [tool] -ago=%s önce -from_now=%s şu andan now=şimdi future=gelecek 1s=1 saniye diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 6b6aa67371d..33a3eaeab8e 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -93,7 +93,6 @@ error404=Сторінка, до якої ви намагаєтеся зверн never=Ніколи - [aria] [editor] @@ -2750,8 +2749,6 @@ starred_repo=додав %[2]s у обране watched_repo=почав слідкувати за %[2] [tool] -ago=%s тому -from_now=%s з цього моменту now=зараз future=в майбутньому 1s=1 секунда diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 7806a195baf..c9adca186fc 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -112,7 +112,6 @@ never=从不 rss_feed=RSS 订阅源 - [aria] navbar=导航栏 footer=页脚 @@ -3095,8 +3094,6 @@ starred_repo=点赞了 %[2]s watched_repo=开始关注 %[2]s [tool] -ago=%s前 -from_now=%s 之后 now=现在 future=将来 1s=1 秒 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 53ead462a9a..29530d9467c 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -51,7 +51,6 @@ enabled=已啟用 - [aria] [editor] @@ -926,8 +925,6 @@ transfer_repo=將儲存庫 %s 轉移至 %s compare_commits=比較 %d 提交 [tool] -ago=%s之前 -from_now=%s之後 now=現在 future=未來 1s=1 秒 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index a2eb097eb4a..ca3990af83c 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -112,7 +112,6 @@ never=從來沒有 rss_feed=RSS 摘要 - [aria] navbar=導航列 footer=頁尾 @@ -3109,8 +3108,6 @@ starred_repo=為 %[2]s 加上星號 watched_repo=開始關注 %[2]s [tool] -ago=%s前 -from_now=%s之後 now=現在 future=未來 1s=1 秒 From 7681d582cdae42d9322309ddf732117e6d332776 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 17 Apr 2023 11:37:23 +0800 Subject: [PATCH 12/12] Refactor locale number (#24134) Before, the `GiteaLocaleNumber.js` was just written as a a drop-in replacement for old `js-pretty-number`. Actually, we can use Golang's `text` package to format. This PR partially completes the TODOs in `GiteaLocaleNumber.js`: > if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component. > tooltip: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future. This PR also helps #24131 Screenshots:
![image](https://user-images.githubusercontent.com/2114189/232179420-b1b9974b-9d96-4408-b209-b80182c8b359.png) ![image](https://user-images.githubusercontent.com/2114189/232179416-14f36aa0-3f3e-4ac9-b366-7bd3a4464a11.png)
--- modules/charset/escape_test.go | 14 ++------- modules/csv/csv_test.go | 17 ++--------- modules/templates/helper.go | 7 ----- modules/test/context_tests.go | 17 ++--------- modules/translation/mock.go | 27 +++++++++++++++++ modules/translation/translation.go | 30 +++++++++++++++---- modules/translation/translation_test.go | 27 +++++++++++++++++ templates/devtest/gitea-ui.tmpl | 14 ++++----- templates/projects/list.tmpl | 8 ++--- templates/repo/issue/milestones.tmpl | 8 ++--- templates/repo/issue/openclose.tmpl | 4 +-- templates/repo/projects/list.tmpl | 8 ++--- templates/repo/release/list.tmpl | 4 +-- templates/repo/release/new.tmpl | 4 +-- templates/repo/sub_menu.tmpl | 2 +- templates/user/dashboard/issues.tmpl | 4 +-- templates/user/dashboard/milestones.tmpl | 8 ++--- web_src/js/webcomponents/GiteaLocaleNumber.js | 20 ------------- web_src/js/webcomponents/webcomponents.js | 1 - 19 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 modules/translation/mock.go create mode 100644 modules/translation/translation_test.go delete mode 100644 web_src/js/webcomponents/GiteaLocaleNumber.js diff --git a/modules/charset/escape_test.go b/modules/charset/escape_test.go index 26e82bf13ac..f63c5c5c52b 100644 --- a/modules/charset/escape_test.go +++ b/modules/charset/escape_test.go @@ -132,18 +132,10 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, }, } -type nullLocale struct{} - -func (nullLocale) Language() string { return "" } -func (nullLocale) Tr(key string, _ ...interface{}) string { return key } -func (nullLocale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { return "" } - -var _ (translation.Locale) = nullLocale{} - func TestEscapeControlString(t *testing.T) { for _, tt := range escapeControlTests { t.Run(tt.name, func(t *testing.T) { - status, result := EscapeControlString(tt.text, nullLocale{}) + status, result := EscapeControlString(tt.text, &translation.MockLocale{}) if !reflect.DeepEqual(*status, tt.status) { t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status) } @@ -179,7 +171,7 @@ func TestEscapeControlReader(t *testing.T) { t.Run(tt.name, func(t *testing.T) { input := strings.NewReader(tt.text) output := &strings.Builder{} - status, err := EscapeControlReader(input, output, nullLocale{}) + status, err := EscapeControlReader(input, output, &translation.MockLocale{}) result := output.String() if err != nil { t.Errorf("EscapeControlReader(): err = %v", err) @@ -201,5 +193,5 @@ func TestEscapeControlReader_panic(t *testing.T) { for i := 0; i < 6826; i++ { bs = append(bs, []byte("—")...) } - _, _ = EscapeControlString(string(bs), nullLocale{}) + _, _ = EscapeControlString(string(bs), &translation.MockLocale{}) } diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index 6b91a81fc47..f6e782a5a46 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/translation" "github.com/stretchr/testify/assert" ) @@ -550,20 +551,6 @@ a|"he said, ""here I am"""`, } } -type mockLocale struct{} - -func (l mockLocale) Language() string { - return "en" -} - -func (l mockLocale) Tr(s string, _ ...interface{}) string { - return s -} - -func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { - return key1 -} - func TestFormatError(t *testing.T) { cases := []struct { err error @@ -591,7 +578,7 @@ func TestFormatError(t *testing.T) { } for n, c := range cases { - message, err := FormatError(c.err, mockLocale{}) + message, err := FormatError(c.err, &translation.MockLocale{}) if c.expectsError { assert.Error(t, err, "case %d: expected an error to be returned", n) } else { diff --git a/modules/templates/helper.go b/modules/templates/helper.go index f93419fe873..27d6000daf2 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -132,7 +132,6 @@ func NewFuncMap() []template.FuncMap { // ----------------------------------------------------------------- // time / number / format "FileSize": base.FileSize, - "LocaleNumber": LocaleNumber, "CountFmt": base.FormatNumberSI, "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, @@ -782,12 +781,6 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa return a } -// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number -func LocaleNumber(v interface{}) template.HTML { - num, _ := util.ToInt64(v) - return template.HTML(fmt.Sprintf(`%d`, num, num)) -} - // Eval the expression and return the result, see the comment of eval.Expr for details. // To use this helper function in templates, pass each token as a separate parameter. // diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index 94dbd2f290d..e558bf1b9f1 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -18,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web/middleware" chi "github.com/go-chi/chi/v5" @@ -34,7 +35,7 @@ func MockContext(t *testing.T, path string) *context.Context { Values: make(url.Values), }, Resp: context.NewResponse(resp), - Locale: &mockLocale{}, + Locale: &translation.MockLocale{}, } defer ctx.Close() @@ -91,20 +92,6 @@ func LoadGitRepo(t *testing.T, ctx *context.Context) { assert.NoError(t, err) } -type mockLocale struct{} - -func (l mockLocale) Language() string { - return "en" -} - -func (l mockLocale) Tr(s string, _ ...interface{}) string { - return s -} - -func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { - return key1 -} - type mockResponseWriter struct { httptest.ResponseRecorder size int diff --git a/modules/translation/mock.go b/modules/translation/mock.go new file mode 100644 index 00000000000..6ce66166aa1 --- /dev/null +++ b/modules/translation/mock.go @@ -0,0 +1,27 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package translation + +import "fmt" + +// MockLocale provides a mocked locale without any translations +type MockLocale struct{} + +var _ Locale = (*MockLocale)(nil) + +func (l MockLocale) Language() string { + return "en" +} + +func (l MockLocale) Tr(s string, _ ...interface{}) string { + return s +} + +func (l MockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string { + return key1 +} + +func (l MockLocale) PrettyNumber(v any) string { + return fmt.Sprint(v) +} diff --git a/modules/translation/translation.go b/modules/translation/translation.go index 331da0f965c..56cf1df2d43 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -15,17 +15,20 @@ import ( "code.gitea.io/gitea/modules/translation/i18n" "golang.org/x/text/language" + "golang.org/x/text/message" + "golang.org/x/text/number" ) type contextKey struct{} -var ContextKey interface{} = &contextKey{} +var ContextKey any = &contextKey{} // Locale represents an interface to translation type Locale interface { Language() string - Tr(string, ...interface{}) string - TrN(cnt interface{}, key1, keyN string, args ...interface{}) string + Tr(string, ...any) string + TrN(cnt any, key1, keyN string, args ...any) string + PrettyNumber(v any) string } // LangType represents a lang type @@ -135,6 +138,7 @@ func Match(tags ...language.Tag) language.Tag { type locale struct { i18n.Locale Lang, LangName string // these fields are used directly in templates: .i18n.Lang + msgPrinter *message.Printer } // NewLocale return a locale @@ -147,13 +151,24 @@ func NewLocale(lang string) Locale { langName := "unknown" if l, ok := allLangMap[lang]; ok { langName = l.Name + } else if len(setting.Langs) > 0 { + lang = setting.Langs[0] + langName = setting.Names[0] } + i18nLocale, _ := i18n.GetLocale(lang) - return &locale{ + l := &locale{ Locale: i18nLocale, Lang: lang, LangName: langName, } + if langTag, err := language.Parse(lang); err != nil { + log.Error("Failed to parse language tag from name %q: %v", l.Lang, err) + l.msgPrinter = message.NewPrinter(language.English) + } else { + l.msgPrinter = message.NewPrinter(langTag) + } + return l } func (l *locale) Language() string { @@ -199,7 +214,7 @@ var trNLangRules = map[string]func(int64) int{ } // TrN returns translated message for plural text translation -func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { +func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string { var c int64 if t, ok := cnt.(int); ok { c = int64(t) @@ -223,3 +238,8 @@ func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) st } return l.Tr(keyN, args...) } + +func (l *locale) PrettyNumber(v any) string { + // TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format + return l.msgPrinter.Sprintf("%v", number.Decimal(v)) +} diff --git a/modules/translation/translation_test.go b/modules/translation/translation_test.go new file mode 100644 index 00000000000..83a40f14581 --- /dev/null +++ b/modules/translation/translation_test.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package translation + +import ( + "testing" + + "code.gitea.io/gitea/modules/translation/i18n" + + "github.com/stretchr/testify/assert" +) + +func TestPrettyNumber(t *testing.T) { + // TODO: make this package friendly to testing + + i18n.ResetDefaultLocales() + + allLangMap = make(map[string]*LangType) + allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"} + + l := NewLocale("id-ID") + assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000)) + + l = NewLocale("nosuch") + assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000)) +} diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index 1ab9ae7b7c7..2fe478a07d8 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -15,13 +15,13 @@

LocaleNumber

-
{{LocaleNumber 1}}
-
{{LocaleNumber 12}}
-
{{LocaleNumber 123}}
-
{{LocaleNumber 1234}}
-
{{LocaleNumber 12345}}
-
{{LocaleNumber 123456}}
-
{{LocaleNumber 1234567}}
+
{{.locale.PrettyNumber 1}}
+
{{.locale.PrettyNumber 12}}
+
{{.locale.PrettyNumber 123}}
+
{{.locale.PrettyNumber 1234}}
+
{{.locale.PrettyNumber 12345}}
+
{{.locale.PrettyNumber 123456}}
+
{{.locale.PrettyNumber 1234567}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 213bab70b66..ae9e3a0d118 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -13,11 +13,11 @@ @@ -46,9 +46,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{$.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{$.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}}
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}} diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 602461400b9..e54a72714a5 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -18,11 +18,11 @@
@@ -84,9 +84,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{$.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{$.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if .TotalTrackedTime}}{{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}}{{end}} {{if .UpdatedUnix}}{{svg "octicon-clock"}} {{$.locale.Tr "repo.milestones.update_ago" (TimeSinceUnix .UpdatedUnix $.locale) | Safe}}{{end}} diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index 6eb26b36c5d..045f5139746 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -5,10 +5,10 @@ {{else}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} {{end}} - {{LocaleNumber .IssueStats.OpenCount}} {{.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .IssueStats.OpenCount}} {{.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .IssueStats.ClosedCount}} {{.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .IssueStats.ClosedCount}} {{.locale.Tr "repo.issues.closed_title"}}
diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index fb5bc4f48d9..bbcc20dd7cb 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -15,11 +15,11 @@ @@ -48,9 +48,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if and $.CanWriteProjects (not $.Repository.IsArchived)}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 12aaa0bd717..8e1793a5bab 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -161,9 +161,9 @@
  • {{.Size | FileSize}} - + {{svg "octicon-info"}} - + {{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 7a4e28cffa1..ddedfd60869 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -71,9 +71,9 @@ {{.Size | FileSize}} - + {{svg "octicon-info"}} - + {{end}} diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl index 97fbabda412..9289295b1d7 100644 --- a/templates/repo/sub_menu.tmpl +++ b/templates/repo/sub_menu.tmpl @@ -4,7 +4,7 @@
    {{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}} diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index 99151597217..39eea2fc751 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -39,11 +39,11 @@
    @@ -104,9 +104,9 @@ {{end}} {{svg "octicon-issue-opened" 16 "gt-mr-3"}} - {{LocaleNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} + {{.locale.PrettyNumber .NumOpenIssues}} {{$.locale.Tr "repo.issues.open_title"}} {{svg "octicon-check" 16 "gt-mr-3"}} - {{LocaleNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} + {{.locale.PrettyNumber .NumClosedIssues}} {{$.locale.Tr "repo.issues.closed_title"}} {{if .TotalTrackedTime}} {{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}} {{end}} diff --git a/web_src/js/webcomponents/GiteaLocaleNumber.js b/web_src/js/webcomponents/GiteaLocaleNumber.js deleted file mode 100644 index 613aa673591..00000000000 --- a/web_src/js/webcomponents/GiteaLocaleNumber.js +++ /dev/null @@ -1,20 +0,0 @@ -// Convert a number to a locale string by data-number attribute. -// Or add a tooltip by data-number-in-tooltip attribute. JSON: {message: "count: %s", number: 123} -window.customElements.define('gitea-locale-number', class extends HTMLElement { - connectedCallback() { - // ideally, the number locale formatting and plural processing should be done by backend with translation strings. - // if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component. - const number = this.getAttribute('data-number'); - if (number) { - this.attachShadow({mode: 'open'}); - this.shadowRoot.textContent = new Intl.NumberFormat().format(Number(number)); - } - const numberInTooltip = this.getAttribute('data-number-in-tooltip'); - if (numberInTooltip) { - // TODO: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future - const {message, number} = JSON.parse(numberInTooltip); - const tooltipContent = message.replace(/%[ds]/, new Intl.NumberFormat().format(Number(number))); - this.setAttribute('data-tooltip-content', tooltipContent); - } - } -}); diff --git a/web_src/js/webcomponents/webcomponents.js b/web_src/js/webcomponents/webcomponents.js index 7e8135aa00c..123607282bc 100644 --- a/web_src/js/webcomponents/webcomponents.js +++ b/web_src/js/webcomponents/webcomponents.js @@ -1,4 +1,3 @@ import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon import '@github/relative-time-element'; -import './GiteaLocaleNumber.js'; import './GiteaOriginUrl.js';