* remove unnecessary web context data fields, and unify the i18n/translation related functions to `Locale` * in development, show an error if a translation key is missing * remove the unnecessary loops `for _, lang := range translation.AllLangs()` for every request, which improves the performance slightly * use `ctx.Locale.Language()` instead of `ctx.Data["Lang"].(string)` * add more comments about how the Locale/LangType fields are usedtags/v1.18.0-dev
"gitea.com/go-chi/session" | "gitea.com/go-chi/session" | ||||
chi "github.com/go-chi/chi/v5" | chi "github.com/go-chi/chi/v5" | ||||
"github.com/unknwon/com" | "github.com/unknwon/com" | ||||
"github.com/unknwon/i18n" | |||||
"github.com/unrolled/render" | "github.com/unrolled/render" | ||||
"golang.org/x/crypto/pbkdf2" | "golang.org/x/crypto/pbkdf2" | ||||
) | ) | ||||
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() | ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() | ||||
ctx.Data["i18n"] = locale | ctx.Data["i18n"] = locale | ||||
ctx.Data["Tr"] = i18n.Tr | |||||
ctx.Data["Lang"] = locale.Language() | |||||
ctx.Data["AllLangs"] = translation.AllLangs() | ctx.Data["AllLangs"] = translation.AllLangs() | ||||
for _, lang := range translation.AllLangs() { | |||||
if lang.Lang == locale.Language() { | |||||
ctx.Data["LangName"] = lang.Name | |||||
break | |||||
} | |||||
} | |||||
next.ServeHTTP(ctx.Resp, ctx.Req) | next.ServeHTTP(ctx.Resp, ctx.Req) | ||||
// LangType represents a lang type | // LangType represents a lang type | ||||
type LangType struct { | type LangType struct { | ||||
Lang, Name string | |||||
Lang, Name string // these fields are used directly in templates: {{range .AllLangs}}{{.Lang}}{{.Name}}{{end}} | |||||
} | } | ||||
var ( | var ( | ||||
matcher language.Matcher | matcher language.Matcher | ||||
allLangs []LangType | |||||
allLangs []*LangType | |||||
allLangMap map[string]*LangType | |||||
supportedTags []language.Tag | supportedTags []language.Tag | ||||
) | ) | ||||
// AllLangs returns all supported languages sorted by name | // AllLangs returns all supported languages sorted by name | ||||
func AllLangs() []LangType { | |||||
func AllLangs() []*LangType { | |||||
return allLangs | return allLangs | ||||
} | } | ||||
} | } | ||||
i18n.SetDefaultLang("en-US") | i18n.SetDefaultLang("en-US") | ||||
allLangs = make([]LangType, 0, i18n.Count()-1) | |||||
allLangs = make([]*LangType, 0, i18n.Count()) | |||||
allLangMap = map[string]*LangType{} | |||||
langs := i18n.ListLangs() | langs := i18n.ListLangs() | ||||
names := i18n.ListLangDescs() | |||||
descs := i18n.ListLangDescs() | |||||
for i, v := range langs { | for i, v := range langs { | ||||
allLangs = append(allLangs, LangType{v, names[i]}) | |||||
l := &LangType{v, descs[i]} | |||||
allLangs = append(allLangs, l) | |||||
allLangMap[v] = l | |||||
} | } | ||||
// Sort languages case insensitive according to their name - needed for the user settings | |||||
// Sort languages case-insensitive according to their name - needed for the user settings | |||||
sort.Slice(allLangs, func(i, j int) bool { | sort.Slice(allLangs, func(i, j int) bool { | ||||
return strings.ToLower(allLangs[i].Name) < strings.ToLower(allLangs[j].Name) | return strings.ToLower(allLangs[i].Name) < strings.ToLower(allLangs[j].Name) | ||||
}) | }) | ||||
// locale represents the information of localization. | // locale represents the information of localization. | ||||
type locale struct { | type locale struct { | ||||
Lang string | |||||
Lang, LangName string // these fields are used directly in templates: .i18n.Lang | |||||
} | } | ||||
// NewLocale return a locale | // NewLocale return a locale | ||||
func NewLocale(lang string) Locale { | func NewLocale(lang string) Locale { | ||||
langName := "unknown" | |||||
if l, ok := allLangMap[lang]; ok { | |||||
langName = l.Name | |||||
} | |||||
return &locale{ | return &locale{ | ||||
Lang: lang, | |||||
Lang: lang, | |||||
LangName: langName, | |||||
} | } | ||||
} | } | ||||
// Tr translates content to target language. | // Tr translates content to target language. | ||||
func (l *locale) Tr(format string, args ...interface{}) string { | func (l *locale) Tr(format string, args ...interface{}) string { | ||||
return i18n.Tr(l.Lang, format, args...) | |||||
if setting.IsProd { | |||||
return i18n.Tr(l.Lang, format, args...) | |||||
} | |||||
// in development, we should show an error if a translation key is missing | |||||
s, ok := TryTr(l.Lang, format, args...) | |||||
if !ok { | |||||
log.Error("missing i18n translation key: %q", format) | |||||
} | |||||
return s | |||||
} | } | ||||
// Language specific rules for translating plural texts | // Language specific rules for translating plural texts |
Render: rnd, | Render: rnd, | ||||
Session: session.GetSession(req), | Session: session.GetSession(req), | ||||
Data: map[string]interface{}{ | Data: map[string]interface{}{ | ||||
"i18n": locale, | |||||
"Title": locale.Tr("install.install"), | "Title": locale.Tr("install.install"), | ||||
"PageIsInstall": true, | "PageIsInstall": true, | ||||
"DbTypeNames": getDbTypeNames(), | "DbTypeNames": getDbTypeNames(), | ||||
"i18n": locale, | |||||
"Language": locale.Language(), | |||||
"Lang": locale.Language(), | |||||
"AllLangs": translation.AllLangs(), | "AllLangs": translation.AllLangs(), | ||||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), | |||||
"PageStartTime": startTime, | "PageStartTime": startTime, | ||||
"PasswordHashAlgorithms": user_model.AvailableHashAlgorithms, | "PasswordHashAlgorithms": user_model.AvailableHashAlgorithms, | ||||
}, | }, | ||||
} | } | ||||
for _, lang := range translation.AllLangs() { | |||||
if lang.Lang == locale.Language() { | |||||
ctx.Data["LangName"] = lang.Name | |||||
break | |||||
} | |||||
} | |||||
ctx.Req = context.WithContext(req, &ctx) | ctx.Req = context.WithContext(req, &ctx) | ||||
next.ServeHTTP(resp, ctx.Req) | next.ServeHTTP(resp, ctx.Req) | ||||
}) | }) |
commitCnt++ | commitCnt++ | ||||
// User avatar image | // User avatar image | ||||
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string)) | |||||
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale.Language()) | |||||
var avatar string | var avatar string | ||||
if commit.User != nil { | if commit.User != nil { |
return | return | ||||
} | } | ||||
lang := ctx.Data["Lang"].(string) | |||||
lang := ctx.Locale.Language() | |||||
editedHistoryCountMap, _ := issuesModel.QueryIssueContentHistoryEditedCountMap(db.DefaultContext, issue.ID) | editedHistoryCountMap, _ := issuesModel.QueryIssueContentHistoryEditedCountMap(db.DefaultContext, issue.ID) | ||||
ctx.JSON(http.StatusOK, map[string]interface{}{ | ctx.JSON(http.StatusOK, map[string]interface{}{ | ||||
"i18n": map[string]interface{}{ | "i18n": map[string]interface{}{ | ||||
// render history list to HTML for frontend dropdown items: (name, value) | // render history list to HTML for frontend dropdown items: (name, value) | ||||
// name is HTML of "avatar + userName + userAction + timeSince" | // name is HTML of "avatar + userName + userAction + timeSince" | ||||
// value is historyId | // value is historyId | ||||
lang := ctx.Data["Lang"].(string) | |||||
lang := ctx.Locale.Language() | |||||
var results []map[string]interface{} | var results []map[string]interface{} | ||||
for _, item := range items { | for _, item := range items { | ||||
var actionText string | var actionText string | ||||
if item.IsDeleted { | if item.IsDeleted { | ||||
actionTextDeleted := i18n.Tr(lang, "repo.issues.content_history.deleted") | |||||
actionTextDeleted := ctx.Locale.Tr("repo.issues.content_history.deleted") | |||||
actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>" | actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>" | ||||
} else if item.IsFirstCreated { | } else if item.IsFirstCreated { | ||||
actionText = i18n.Tr(lang, "repo.issues.content_history.created") | |||||
actionText = ctx.Locale.Tr("repo.issues.content_history.created") | |||||
} else { | } else { | ||||
actionText = i18n.Tr(lang, "repo.issues.content_history.edited") | |||||
actionText = ctx.Locale.Tr("repo.issues.content_history.edited") | |||||
} | } | ||||
timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, lang) | timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, lang) | ||||
results = append(results, map[string]interface{}{ | results = append(results, map[string]interface{}{ |
<div class="df ac"> | <div class="df ac"> | ||||
<div class="content f1"> | <div class="content f1"> | ||||
<div class="header">{{.Process.Description}}</div> | <div class="header">{{.Process.Description}}</div> | ||||
<div class="description"><span title="{{DateFmtLong .Process.Start}}">{{TimeSince .Process.Start .root.Lang}}</span></div> | |||||
<div class="description"><span title="{{DateFmtLong .Process.Start}}">{{TimeSince .Process.Start .root.i18n.Lang}}</span></div> | |||||
</div> | </div> | ||||
<div> | <div> | ||||
<a class="delete-button icon" href="" data-url="{{.root.Link}}/cancel/{{.Process.PID}}" data-id="{{.Process.PID}}" data-name="{{.Process.Description}}">{{svg "octicon-trash" 16 "text-red"}}</a> | <a class="delete-button icon" href="" data-url="{{.root.Link}}/cancel/{{.Process.PID}}" data-id="{{.Process.PID}}" data-name="{{.Process.Description}}">{{svg "octicon-trash" 16 "text-red"}}</a> |
{{end}} | {{end}} | ||||
<div class="ui language bottom floating slide up dropdown link item"> | <div class="ui language bottom floating slide up dropdown link item"> | ||||
{{svg "octicon-globe"}} | {{svg "octicon-globe"}} | ||||
<div class="text">{{.LangName}}</div> | |||||
<div class="text">{{.i18n.LangName}}</div> | |||||
<div class="menu language-menu"> | <div class="menu language-menu"> | ||||
{{range .AllLangs}} | {{range .AllLangs}} | ||||
<a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}" class="item {{if eq $.Lang .Lang}}active selected{{end}}">{{.Name}}</a> | |||||
<a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}" class="item {{if eq $.i18n.Lang .Lang}}active selected{{end}}">{{.Name}}</a> | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> |
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html lang="{{.Lang}}" class="theme-{{.SignedUser.Theme}}"> | |||||
<html lang="{{.i18n.Lang}}" class="theme-{{.SignedUser.Theme}}"> | |||||
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> |
{{if not .IsTag}} | {{if not .IsTag}} | ||||
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji}}</a> | <a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji}}</a> | ||||
{{end}} | {{end}} | ||||
{{TimeSinceUnix .CreatedUnix $.Lang}} | |||||
{{TimeSinceUnix .CreatedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<p class="desc"> | <p class="desc"> | ||||
<span class="ui purple label">{{$.i18n.Tr "repo.activity.merged_prs_label"}}</span> | <span class="ui purple label">{{$.i18n.Tr "repo.activity.merged_prs_label"}}</span> | ||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a> | #{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a> | ||||
{{TimeSinceUnix .MergedUnix $.Lang}} | |||||
{{TimeSinceUnix .MergedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<p class="desc"> | <p class="desc"> | ||||
<span class="ui green label">{{$.i18n.Tr "repo.activity.opened_prs_label"}}</span> | <span class="ui green label">{{$.i18n.Tr "repo.activity.opened_prs_label"}}</span> | ||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a> | #{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | RenderEmoji}}</a> | ||||
{{TimeSinceUnix .Issue.CreatedUnix $.Lang}} | |||||
{{TimeSinceUnix .Issue.CreatedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<p class="desc"> | <p class="desc"> | ||||
<span class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</span> | <span class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</span> | ||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | #{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | ||||
{{TimeSinceUnix .ClosedUnix $.Lang}} | |||||
{{TimeSinceUnix .ClosedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<p class="desc"> | <p class="desc"> | ||||
<span class="ui green label">{{$.i18n.Tr "repo.activity.new_issue_label"}}</span> | <span class="ui green label">{{$.i18n.Tr "repo.activity.new_issue_label"}}</span> | ||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | #{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | ||||
{{TimeSinceUnix .CreatedUnix $.Lang}} | |||||
{{TimeSinceUnix .CreatedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
{{else}} | {{else}} | ||||
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | ||||
{{end}} | {{end}} | ||||
{{TimeSinceUnix .UpdatedUnix $.Lang}} | |||||
{{TimeSinceUnix .UpdatedUnix $.i18n.Lang}} | |||||
</p> | </p> | ||||
{{end}} | {{end}} | ||||
</div> | </div> |
{{avatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "mr-3"}} | {{avatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "mr-3"}} | ||||
<strong>{{.Commit.Author.Name}}</strong> | <strong>{{.Commit.Author.Name}}</strong> | ||||
{{end}} | {{end}} | ||||
<span class="text grey ml-3" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span> | |||||
<span class="text grey ml-3" id="authored-time">{{TimeSince .Commit.Author.When $.i18n.Lang}}</span> | |||||
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}} | {{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}} | ||||
<span class="text grey mx-3">{{.i18n.Tr "repo.diff.committed_by"}}</span> | <span class="text grey mx-3">{{.i18n.Tr "repo.diff.committed_by"}}</span> | ||||
{{if ne .Verification.CommittingUser.ID 0}} | {{if ne .Verification.CommittingUser.ID 0}} | ||||
{{else}} | {{else}} | ||||
<strong>{{.NoteCommit.Author.Name}}</strong> | <strong>{{.NoteCommit.Author.Name}}</strong> | ||||
{{end}} | {{end}} | ||||
<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When $.Lang}}</span> | |||||
<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When $.i18n.Lang}}</span> | |||||
</div> | </div> | ||||
<div class="ui bottom attached info segment git-notes"> | <div class="ui bottom attached info segment git-notes"> | ||||
<pre class="commit-body">{{RenderNote $.Context .Note $.RepoLink $.Repository.ComposeMetas}}</pre> | <pre class="commit-body">{{RenderNote $.Context .Note $.RepoLink $.Repository.ComposeMetas}}</pre> |
{{end}} | {{end}} | ||||
</td> | </td> | ||||
{{if .Committer}} | {{if .Committer}} | ||||
<td class="text right aligned">{{TimeSince .Committer.When $.Lang}}</td> | |||||
<td class="text right aligned">{{TimeSince .Committer.When $.i18n.Lang}}</td> | |||||
{{else}} | {{else}} | ||||
<td class="text right aligned">{{TimeSince .Author.When $.Lang}}</td> | |||||
<td class="text right aligned">{{TimeSince .Author.When $.i18n.Lang}}</td> | |||||
{{end}} | {{end}} | ||||
</tr> | </tr> | ||||
{{end}} | {{end}} |
{{range .comments}} | {{range .comments}} | ||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.root.i18n.Lang }} | |||||
<div class="comment" id="{{.HashTag}}"> | <div class="comment" id="{{.HashTag}}"> | ||||
{{if .OriginalAuthor }} | {{if .OriginalAuthor }} | ||||
<span class="avatar"><img src="{{AppSubUrl}}/assets/img/avatar_default.png"></span> | <span class="avatar"><img src="{{AppSubUrl}}/assets/img/avatar_default.png"></span> |
</div> | </div> | ||||
<div class="ui one column stackable grid"> | <div class="ui one column stackable grid"> | ||||
<div class="column"> | <div class="column"> | ||||
{{ $closedDate:= TimeSinceUnix .Milestone.ClosedDateUnix $.Lang }} | |||||
{{ $closedDate:= TimeSinceUnix .Milestone.ClosedDateUnix $.i18n.Lang }} | |||||
{{if .IsClosed}} | {{if .IsClosed}} | ||||
{{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | ||||
{{else}} | {{else}} |
</div> | </div> | ||||
</div> | </div> | ||||
<div class="meta"> | <div class="meta"> | ||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} | |||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.i18n.Lang }} | |||||
{{if .IsClosed}} | {{if .IsClosed}} | ||||
{{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | ||||
{{else}} | {{else}} |
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/> | <input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/> | ||||
<input type="hidden" id="type" value="{{.IssueType}}"> | <input type="hidden" id="type" value="{{.IssueType}}"> | ||||
{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.i18n.Lang }} | |||||
<div class="twelve wide column comment-list prevent-before-timeline"> | <div class="twelve wide column comment-list prevent-before-timeline"> | ||||
<ui class="ui timeline"> | <ui class="ui timeline"> | ||||
<div id="{{.Issue.HashTag}}" class="timeline-item comment first"> | <div id="{{.Issue.HashTag}}" class="timeline-item comment first"> |
{{ template "base/alert" }} | {{ template "base/alert" }} | ||||
{{range .Issue.Comments}} | {{range .Issue.Comments}} | ||||
{{if call $.ShouldShowCommentType .Type}} | {{if call $.ShouldShowCommentType .Type}} | ||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.i18n.Lang }} | |||||
<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, | <!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, | ||||
5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, | 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, | ||||
{{else if eq .RefAction 2 }} | {{else if eq .RefAction 2 }} | ||||
{{ $refTr = "repo.issues.ref_reopening_from" }} | {{ $refTr = "repo.issues.ref_reopening_from" }} | ||||
{{end}} | {{end}} | ||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .CreatedUnix $.i18n.Lang }} | |||||
<div class="timeline-item event" id="{{.HashTag}}"> | <div class="timeline-item event" id="{{.HashTag}}"> | ||||
<span class="badge">{{svg "octicon-bookmark"}}</span> | <span class="badge">{{svg "octicon-bookmark"}}</span> | ||||
<a href="{{.Poster.HomeLink}}"> | <a href="{{.Poster.HomeLink}}"> | ||||
<div id="code-comments-{{(index $comms 0).ID}}" class="comment-code-cloud ui segment{{if $resolved}} hide{{end}}"> | <div id="code-comments-{{(index $comms 0).ID}}" class="comment-code-cloud ui segment{{if $resolved}} hide{{end}}"> | ||||
<div class="ui comments mb-0"> | <div class="ui comments mb-0"> | ||||
{{range $comms}} | {{range $comms}} | ||||
{{ $createdSubStr:= TimeSinceUnix .CreatedUnix $.Lang }} | |||||
{{ $createdSubStr:= TimeSinceUnix .CreatedUnix $.i18n.Lang }} | |||||
<div class="comment code-comment pb-4" id="{{.HashTag}}"> | <div class="comment code-comment pb-4" id="{{.HashTag}}"> | ||||
<div class="content"> | <div class="content"> | ||||
<div class="header comment-header"> | <div class="header comment-header"> |
<div class="ui segment"> | <div class="ui segment"> | ||||
<h4>{{$.i18n.Tr "repo.issues.review.reviewers"}}</h4> | <h4>{{$.i18n.Tr "repo.issues.review.reviewers"}}</h4> | ||||
{{range .PullReviewers}} | {{range .PullReviewers}} | ||||
{{ $createdStr:= TimeSinceUnix .Review.UpdatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .Review.UpdatedUnix $.i18n.Lang }} | |||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
<div class="review-item"> | <div class="review-item"> | ||||
<div class="review-item-left"> | <div class="review-item-left"> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
{{range .OriginalReviews}} | {{range .OriginalReviews}} | ||||
{{ $createdStr:= TimeSinceUnix .UpdatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .UpdatedUnix $.i18n.Lang }} | |||||
<div class="ui divider"></div> | <div class="ui divider"></div> | ||||
<div class="review-item"> | <div class="review-item"> | ||||
<div class="review-item-left"> | <div class="review-item-left"> |
{{$baseHref = printf "<a href=\"%s\">%s</a>" (.BaseBranchHTMLURL | Escape) $baseHref}} | {{$baseHref = printf "<a href=\"%s\">%s</a>" (.BaseBranchHTMLURL | Escape) $baseHref}} | ||||
{{end}} | {{end}} | ||||
{{if .Issue.PullRequest.HasMerged}} | {{if .Issue.PullRequest.HasMerged}} | ||||
{{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.Lang }} | |||||
{{ $mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix $.i18n.Lang }} | |||||
{{if .Issue.OriginalAuthor }} | {{if .Issue.OriginalAuthor }} | ||||
{{.Issue.OriginalAuthor}} | {{.Issue.OriginalAuthor}} | ||||
<span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}}</span> | <span class="pull-desc">{{$.i18n.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr | Safe}}</span> | ||||
</span> | </span> | ||||
{{end}} | {{end}} | ||||
{{else}} | {{else}} | ||||
{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.Lang }} | |||||
{{ $createdStr:= TimeSinceUnix .Issue.CreatedUnix $.i18n.Lang }} | |||||
<span class="time-desc"> | <span class="time-desc"> | ||||
{{if .Issue.OriginalAuthor }} | {{if .Issue.OriginalAuthor }} | ||||
{{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.OriginalAuthor|Escape) | Safe}} | {{$.i18n.Tr "repo.issues.opened_by_fake" $createdStr (.Issue.OriginalAuthor|Escape) | Safe}} |
<li class="item"> | <li class="item"> | ||||
{{svg "octicon-project"}} <a href="{{$.RepoLink}}/projects/{{.ID}}">{{.Title}}</a> | {{svg "octicon-project"}} <a href="{{$.RepoLink}}/projects/{{.ID}}">{{.Title}}</a> | ||||
<div class="meta"> | <div class="meta"> | ||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} | |||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.i18n.Lang }} | |||||
{{if .IsClosed }} | {{if .IsClosed }} | ||||
{{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | ||||
{{end}} | {{end}} |
<div class="meta my-2"> | <div class="meta my-2"> | ||||
<span class="text light grey"> | <span class="text light grey"> | ||||
#{{.Index}} | #{{.Index}} | ||||
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }} | |||||
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.i18n.Lang }} | |||||
{{if .OriginalAuthor }} | {{if .OriginalAuthor }} | ||||
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}} | {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}} | ||||
{{else if gt .Poster.ID 0}} | {{else if gt .Poster.ID 0}} |
<li class="ui grid"> | <li class="ui grid"> | ||||
<div class="ui four wide column meta mt-2"> | <div class="ui four wide column meta mt-2"> | ||||
{{if .IsTag}} | {{if .IsTag}} | ||||
{{if .CreatedUnix}}<span class="time">{{TimeSinceUnix .CreatedUnix $.Lang}}</span>{{end}} | |||||
{{if .CreatedUnix}}<span class="time">{{TimeSinceUnix .CreatedUnix $.i18n.Lang}}</span>{{end}} | |||||
{{else}} | {{else}} | ||||
{{if .IsDraft}} | {{if .IsDraft}} | ||||
<span class="ui yellow label">{{$.i18n.Tr "repo.release.draft"}}</span> | <span class="ui yellow label">{{$.i18n.Tr "repo.release.draft"}}</span> | ||||
{{$.i18n.Tr "repo.released_this"}} | {{$.i18n.Tr "repo.released_this"}} | ||||
</span> | </span> | ||||
{{if .CreatedUnix}} | {{if .CreatedUnix}} | ||||
<span class="time">{{TimeSinceUnix .CreatedUnix $.Lang}}</span> | |||||
<span class="time">{{TimeSinceUnix .CreatedUnix $.i18n.Lang}}</span> | |||||
{{end}} | {{end}} | ||||
{{if not .IsDraft}} | {{if not .IsDraft}} | ||||
| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.Target | PathEscapeSegments}}">{{$.i18n.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.i18n.Tr "repo.release.ahead.target" .Target}}</span> | | <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.Target | PathEscapeSegments}}">{{$.i18n.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{$.i18n.Tr "repo.release.ahead.target" .Target}}</span> |
</span> | </span> | ||||
</td> | </td> | ||||
<td>{{FileSize .Size}}</td> | <td>{{FileSize .Size}}</td> | ||||
<td>{{TimeSince .CreatedUnix.AsTime $.Lang}}</td> | |||||
<td>{{TimeSince .CreatedUnix.AsTime $.i18n.Lang}}</td> | |||||
<td class="right aligned"> | <td class="right aligned"> | ||||
<a class="ui primary show-panel button" href="{{$.Link}}/find?oid={{.Oid}}&size={{.Size}}">{{$.i18n.Tr "repo.settings.lfs_findcommits"}}</a> | <a class="ui primary show-panel button" href="{{$.Link}}/find?oid={{.Oid}}&size={{.Size}}">{{$.i18n.Tr "repo.settings.lfs_findcommits"}}</a> | ||||
<button class="ui basic show-modal icon button" data-modal="#delete-{{.Oid}}"> | <button class="ui basic show-modal icon button" data-modal="#delete-{{.Oid}}"> |
{{$.i18n.Tr "repo.diff.commit"}} | {{$.i18n.Tr "repo.diff.commit"}} | ||||
<a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{.SHA}}">{{ShortSha .SHA}}</a> | <a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{.SHA}}">{{ShortSha .SHA}}</a> | ||||
</td> | </td> | ||||
<td>{{TimeSince .When $.Lang}}</td> | |||||
<td>{{TimeSince .When $.i18n.Lang}}</td> | |||||
</tr> | </tr> | ||||
{{else}} | {{else}} | ||||
<tr> | <tr> |
{{$lock.Owner.DisplayName}} | {{$lock.Owner.DisplayName}} | ||||
</a> | </a> | ||||
</td> | </td> | ||||
<td>{{TimeSince .Created $.Lang}}</td> | |||||
<td>{{TimeSince .Created $.i18n.Lang}}</td> | |||||
<td class="right aligned"> | <td class="right aligned"> | ||||
<form action="{{$.LFSFilesLink}}/locks/{{$lock.ID}}/unlock" method="POST"> | <form action="{{$.LFSFilesLink}}/locks/{{$lock.ID}}/unlock" method="POST"> | ||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} |
</span> | </span> | ||||
{{end}} | {{end}} | ||||
</th> | </th> | ||||
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When $.Lang}}{{end}}{{end}}</th> | |||||
<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When $.i18n.Lang}}{{end}}{{end}}</th> | |||||
</tr> | </tr> | ||||
</thead> | </thead> | ||||
<tbody> | <tbody> | ||||
{{end}} | {{end}} | ||||
</span> | </span> | ||||
</td> | </td> | ||||
<td class="text right age three wide">{{if $commit}}{{TimeSince $commit.Committer.When $.Lang}}{{end}}</td> | |||||
<td class="text right age three wide">{{if $commit}}{{TimeSince $commit.Committer.When $.i18n.Lang}}{{end}}</td> | |||||
</tr> | </tr> | ||||
{{end}} | {{end}} | ||||
</tbody> | </tbody> |
{{svg "octicon-file"}} | {{svg "octicon-file"}} | ||||
<a href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a> | <a href="{{$.RepoLink}}/wiki/{{.SubURL}}">{{.Name}}</a> | ||||
</td> | </td> | ||||
{{$timeSince := TimeSinceUnix .UpdatedUnix $.Lang}} | |||||
{{$timeSince := TimeSinceUnix .UpdatedUnix $.i18n.Lang}} | |||||
<td class="text right grey">{{$.i18n.Tr "repo.wiki.last_updated" $timeSince | Safe}}</td> | <td class="text right grey">{{$.i18n.Tr "repo.wiki.last_updated" $timeSince | Safe}}</td> | ||||
</tr> | </tr> | ||||
{{end}} | {{end}} |
<a class="file-revisions-btn ui basic button" title="{{.i18n.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}" ><span>{{.revision}}</span> {{svg "octicon-home"}}</a> | <a class="file-revisions-btn ui basic button" title="{{.i18n.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}" ><span>{{.revision}}</span> {{svg "octicon-home"}}</a> | ||||
{{$title}} | {{$title}} | ||||
<div class="ui sub header word-break"> | <div class="ui sub header word-break"> | ||||
{{$timeSince := TimeSince .Author.When $.Lang}} | |||||
{{$timeSince := TimeSince .Author.When $.i18n.Lang}} | |||||
{{.i18n.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}} | {{.i18n.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}} | ||||
</div> | </div> | ||||
</div> | </div> |
<a class="file-revisions-btn ui basic button" title="{{.i18n.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" ><span>{{.CommitCount}}</span> {{svg "octicon-history"}}</a> | <a class="file-revisions-btn ui basic button" title="{{.i18n.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" ><span>{{.CommitCount}}</span> {{svg "octicon-history"}}</a> | ||||
{{$title}} | {{$title}} | ||||
<div class="ui sub header"> | <div class="ui sub header"> | ||||
{{$timeSince := TimeSince .Author.When $.Lang}} | |||||
{{$timeSince := TimeSince .Author.When $.i18n.Lang}} | |||||
{{.i18n.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}} | {{.i18n.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}} | ||||
</div> | </div> | ||||
</div> | </div> |
#{{.Index}} | #{{.Index}} | ||||
{{end}} | {{end}} | ||||
</a> | </a> | ||||
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }} | |||||
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.i18n.Lang }} | |||||
{{if .OriginalAuthor }} | {{if .OriginalAuthor }} | ||||
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}} | {{$.i18n.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}} | ||||
{{else if gt .Poster.ID 0}} | {{else if gt .Poster.ID 0}} |
</div> | </div> | ||||
</div> | </div> | ||||
<div class="meta"> | <div class="meta"> | ||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} | |||||
{{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.i18n.Lang }} | |||||
{{if .IsClosed}} | {{if .IsClosed}} | ||||
{{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | {{svg "octicon-clock"}} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} | ||||
{{else}} | {{else}} |
<div class="content"> | <div class="content"> | ||||
<strong>{{.Name}}</strong> | <strong>{{.Name}}</strong> | ||||
</div> | </div> | ||||
<span class="time">{{TimeSinceUnix .CreatedUnix $.Lang}}</span> | |||||
<span class="time">{{TimeSinceUnix .CreatedUnix $.i18n.Lang}}</span> | |||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
</div> | </div> |