Unify and simplify TrN for i18n (#18141)
Refer: https://github.com/go-gitea/gitea/pull/18135#issuecomment-1003246099 Now we have a unique and simple `TrN`, and make the fix of PR #18135 also use the better `TrN` logic.
This commit is contained in:
		
							parent
							
								
									88da7a7174
								
							
						
					
					
						commit
						e61b390d54
					
				|  | @ -8,6 +8,7 @@ import ( | |||
| 	"bytes" | ||||
| 	"encoding/csv" | ||||
| 	"io" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 
 | ||||
|  | @ -21,14 +22,21 @@ func TestCreateReader(t *testing.T) { | |||
| 	assert.Equal(t, ',', rd.Comma) | ||||
| } | ||||
| 
 | ||||
| //nolint
 | ||||
| func decodeSlashes(t *testing.T, s string) string { | ||||
| 	s = strings.ReplaceAll(s, "\n", "\\n") | ||||
| 	s = strings.ReplaceAll(s, "\"", "\\\"") | ||||
| 	decoded, err := strconv.Unquote(`"` + s + `"`) | ||||
| 	assert.NoError(t, err, "unable to decode string") | ||||
| 	return decoded | ||||
| } | ||||
| 
 | ||||
| func TestCreateReaderAndDetermineDelimiter(t *testing.T) { | ||||
| 	var cases = []struct { | ||||
| 		csv               string | ||||
| 		expectedRows      [][]string | ||||
| 		expectedDelimiter rune | ||||
| 	}{ | ||||
| 		// case 0 - semicolon delmited
 | ||||
| 		// case 0 - semicolon delimited
 | ||||
| 		{ | ||||
| 			csv: `a;b;c | ||||
| 1;2;3 | ||||
|  | @ -47,11 +55,11 @@ a,	b	c | |||
| 	e	f | ||||
| g	h	i | ||||
| j		l | ||||
| m	n,	 | ||||
| m	n,\t | ||||
| p	q	r | ||||
| 		u | ||||
| v	w	x | ||||
| y		 | ||||
| y\t\t | ||||
| 		`, | ||||
| 			expectedRows: [][]string{ | ||||
| 				{"col1", "col2", "col3"}, | ||||
|  | @ -74,7 +82,7 @@ y | |||
|  a, b, c | ||||
| d,e,f | ||||
|  ,h, i | ||||
| j, ,  | ||||
| j, ,\x20 | ||||
|  , , `, | ||||
| 			expectedRows: [][]string{ | ||||
| 				{"col1", "col2", "col3"}, | ||||
|  | @ -89,7 +97,7 @@ j, , | |||
| 	} | ||||
| 
 | ||||
| 	for n, c := range cases { | ||||
| 		rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(c.csv)) | ||||
| 		rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(decodeSlashes(t, c.csv))) | ||||
| 		assert.NoError(t, err, "case %d: should not throw error: %v\n", n, err) | ||||
| 		assert.EqualValues(t, c.expectedDelimiter, rd.Comma, "case %d: delimiter should be '%c', got '%c'", n, c.expectedDelimiter, rd.Comma) | ||||
| 		rows, err := rd.ReadAll() | ||||
|  | @ -222,7 +230,7 @@ John Doe	john@doe.com	This,note,had,a,lot,of,commas,to,test,delimters`, | |||
| 	} | ||||
| 
 | ||||
| 	for n, c := range cases { | ||||
| 		delimiter := determineDelimiter(&markup.RenderContext{Filename: c.filename}, []byte(c.csv)) | ||||
| 		delimiter := determineDelimiter(&markup.RenderContext{Filename: c.filename}, []byte(decodeSlashes(t, c.csv))) | ||||
| 		assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) | ||||
| 	} | ||||
| } | ||||
|  | @ -287,7 +295,7 @@ abc   | |123 | |||
| 	} | ||||
| 
 | ||||
| 	for n, c := range cases { | ||||
| 		modifiedText := removeQuotedString(c.text) | ||||
| 		modifiedText := removeQuotedString(decodeSlashes(t, c.text)) | ||||
| 		assert.EqualValues(t, c.expectedText, modifiedText, "case %d: modified text should be equal", n) | ||||
| 	} | ||||
| } | ||||
|  | @ -353,7 +361,7 @@ John Doe	john@doe.com	This,note,had,a,lot,of,commas,to,test,delimters`, | |||
| 	quoted, | ||||
| text,"	a | ||||
| 2	"some, | ||||
| quoted,	 | ||||
| quoted,\t | ||||
| 	text,"	b | ||||
| 3	"some, | ||||
| quoted, | ||||
|  | @ -442,7 +450,7 @@ jkl`, | |||
| 	} | ||||
| 
 | ||||
| 	for n, c := range cases { | ||||
| 		delimiter := guessDelimiter([]byte(c.csv)) | ||||
| 		delimiter := guessDelimiter([]byte(decodeSlashes(t, c.csv))) | ||||
| 		assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) | ||||
| 	} | ||||
| } | ||||
|  | @ -459,7 +467,7 @@ func TestGuessFromBeforeAfterQuotes(t *testing.T) { | |||
| 	quoted, | ||||
| text,"	a | ||||
| 2	"some, | ||||
| quoted,	 | ||||
| quoted,\t | ||||
| 	text,"	b | ||||
| 3	"some, | ||||
| quoted, | ||||
|  | @ -534,7 +542,7 @@ a|"he said, ""here I am"""`, | |||
| 	} | ||||
| 
 | ||||
| 	for n, c := range cases { | ||||
| 		delimiter := guessFromBeforeAfterQuotes([]byte(c.csv)) | ||||
| 		delimiter := guessFromBeforeAfterQuotes([]byte(decodeSlashes(t, c.csv))) | ||||
| 		assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) | ||||
| 	} | ||||
| } | ||||
|  | @ -549,6 +557,10 @@ 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) { | ||||
| 	var cases = []struct { | ||||
| 		err             error | ||||
|  |  | |||
|  | @ -239,7 +239,6 @@ func NewFuncMap() []template.FuncMap { | |||
| 		"DisableImportLocal": func() bool { | ||||
| 			return !setting.ImportLocalPaths | ||||
| 		}, | ||||
| 		"TrN": TrN, | ||||
| 		"Dict": func(values ...interface{}) (map[string]interface{}, error) { | ||||
| 			if len(values)%2 != 0 { | ||||
| 				return nil, errors.New("invalid dict call") | ||||
|  | @ -857,69 +856,6 @@ func DiffLineTypeToStr(diffType int) string { | |||
| 	return "same" | ||||
| } | ||||
| 
 | ||||
| // Language specific rules for translating plural texts
 | ||||
| var trNLangRules = map[string]func(int64) int{ | ||||
| 	"en-US": func(cnt int64) int { | ||||
| 		if cnt == 1 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"lv-LV": func(cnt int64) int { | ||||
| 		if cnt%10 == 1 && cnt%100 != 11 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"ru-RU": func(cnt int64) int { | ||||
| 		if cnt%10 == 1 && cnt%100 != 11 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"zh-CN": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"zh-HK": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"zh-TW": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"fr-FR": func(cnt int64) int { | ||||
| 		if cnt > -2 && cnt < 2 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| // TrN returns key to be used for plural text translation
 | ||||
| func TrN(lang string, cnt interface{}, key1, keyN string) string { | ||||
| 	var c int64 | ||||
| 	if t, ok := cnt.(int); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int16); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int32); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int64); ok { | ||||
| 		c = t | ||||
| 	} else { | ||||
| 		return keyN | ||||
| 	} | ||||
| 
 | ||||
| 	ruleFunc, ok := trNLangRules[lang] | ||||
| 	if !ok { | ||||
| 		ruleFunc = trNLangRules["en-US"] | ||||
| 	} | ||||
| 
 | ||||
| 	if ruleFunc(c) == 0 { | ||||
| 		return key1 | ||||
| 	} | ||||
| 	return keyN | ||||
| } | ||||
| 
 | ||||
| // MigrationIcon returns a SVG name matching the service an issue/comment was migrated from
 | ||||
| func MigrationIcon(hostname string) string { | ||||
| 	switch hostname { | ||||
|  |  | |||
|  | @ -103,6 +103,10 @@ 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 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import ( | |||
| type Locale interface { | ||||
| 	Language() string | ||||
| 	Tr(string, ...interface{}) string | ||||
| 	TrN(cnt interface{}, key1, keyN string, args ...interface{}) string | ||||
| } | ||||
| 
 | ||||
| // LangType represents a lang type
 | ||||
|  | @ -99,3 +100,67 @@ func (l *locale) Language() string { | |||
| func (l *locale) Tr(format string, args ...interface{}) string { | ||||
| 	return i18n.Tr(l.Lang, format, args...) | ||||
| } | ||||
| 
 | ||||
| // Language specific rules for translating plural texts
 | ||||
| var trNLangRules = map[string]func(int64) int{ | ||||
| 	// the default rule is "en-US" if a language isn't listed here
 | ||||
| 	"en-US": func(cnt int64) int { | ||||
| 		if cnt == 1 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"lv-LV": func(cnt int64) int { | ||||
| 		if cnt%10 == 1 && cnt%100 != 11 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"ru-RU": func(cnt int64) int { | ||||
| 		if cnt%10 == 1 && cnt%100 != 11 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| 	"zh-CN": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"zh-HK": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"zh-TW": func(cnt int64) int { | ||||
| 		return 0 | ||||
| 	}, | ||||
| 	"fr-FR": func(cnt int64) int { | ||||
| 		if cnt > -2 && cnt < 2 { | ||||
| 			return 0 | ||||
| 		} | ||||
| 		return 1 | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| // TrN returns translated message for plural text translation
 | ||||
| func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { | ||||
| 	var c int64 | ||||
| 	if t, ok := cnt.(int); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int16); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int32); ok { | ||||
| 		c = int64(t) | ||||
| 	} else if t, ok := cnt.(int64); ok { | ||||
| 		c = t | ||||
| 	} else { | ||||
| 		return l.Tr(keyN, args...) | ||||
| 	} | ||||
| 
 | ||||
| 	ruleFunc, ok := trNLangRules[l.Lang] | ||||
| 	if !ok { | ||||
| 		ruleFunc = trNLangRules["en-US"] | ||||
| 	} | ||||
| 
 | ||||
| 	if ruleFunc(c) == 0 { | ||||
| 		return l.Tr(key1, args...) | ||||
| 	} | ||||
| 	return l.Tr(keyN, args...) | ||||
| } | ||||
|  |  | |||
|  | @ -81,13 +81,8 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, | |||
| 	case migrations.IsTwoFactorAuthError(err): | ||||
| 		ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form) | ||||
| 	case repo_model.IsErrReachLimitOfRepo(err): | ||||
| 		var msg string | ||||
| 		maxCreationLimit := owner.MaxCreationLimit() | ||||
| 		if maxCreationLimit == 1 { | ||||
| 			msg = ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit) | ||||
| 		} else { | ||||
| 			msg = ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit) | ||||
| 		} | ||||
| 		msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) | ||||
| 		ctx.RenderWithErr(msg, tpl, form) | ||||
| 	case repo_model.IsErrRepoAlreadyExist(err): | ||||
| 		ctx.Data["Err_RepoName"] = true | ||||
|  |  | |||
|  | @ -162,13 +162,8 @@ func Create(ctx *context.Context) { | |||
| func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form interface{}) { | ||||
| 	switch { | ||||
| 	case repo_model.IsErrReachLimitOfRepo(err): | ||||
| 		var msg string | ||||
| 		maxCreationLimit := owner.MaxCreationLimit() | ||||
| 		if maxCreationLimit == 1 { | ||||
| 			msg = ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit) | ||||
| 		} else { | ||||
| 			msg = ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit) | ||||
| 		} | ||||
| 		msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) | ||||
| 		ctx.RenderWithErr(msg, tpl, form) | ||||
| 	case repo_model.IsErrRepoAlreadyExist(err): | ||||
| 		ctx.Data["Err_RepoName"] = true | ||||
|  |  | |||
|  | @ -610,11 +610,8 @@ func SettingsPost(ctx *context.Context) { | |||
| 
 | ||||
| 		if !ctx.Repo.Owner.CanCreateRepo() { | ||||
| 			maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() | ||||
| 			if maxCreationLimit == 1 { | ||||
| 				ctx.Flash.Error(ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit)) | ||||
| 			} else { | ||||
| 				ctx.Flash.Error(ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit)) | ||||
| 			} | ||||
| 			msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) | ||||
| 			ctx.Flash.Error(msg) | ||||
| 			ctx.Redirect(repo.Link() + "/settings") | ||||
| 			return | ||||
| 		} | ||||
|  |  | |||
|  | @ -78,7 +78,6 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var content bytes.Buffer | ||||
|  | @ -129,7 +128,6 @@ func SendActivateEmailMail(u *user_model.User, email *user_model.EmailAddress) { | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var content bytes.Buffer | ||||
|  | @ -160,7 +158,6 @@ func SendRegisterNotifyMail(u *user_model.User) { | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var content bytes.Buffer | ||||
|  | @ -194,7 +191,6 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var content bytes.Buffer | ||||
|  | @ -278,7 +274,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var mailSubject bytes.Buffer | ||||
|  |  | |||
|  | @ -76,7 +76,6 @@ func mailNewRelease(lang string, tos []string, rel *models.Release) { | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	var mailBody bytes.Buffer | ||||
|  |  | |||
|  | @ -71,7 +71,6 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U | |||
| 		// helper
 | ||||
| 		"i18n":     locale, | ||||
| 		"Str2html": templates.Str2html, | ||||
| 		"TrN":      templates.TrN, | ||||
| 	} | ||||
| 
 | ||||
| 	if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil { | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ | |||
| 
 | ||||
| 				{{.i18n.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink | Str2html}} | ||||
| 			{{else}} | ||||
| 				{{.i18n.Tr (TrN .i18n.Lang (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n") .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}} | ||||
| 				{{.i18n.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}} | ||||
| 			{{end}} | ||||
| 		</p> | ||||
| 	{{end}} | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ | |||
| 						<a class="table-cell tiny background light grey"></a> | ||||
| 					</div> | ||||
| 					{{end}} | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n") .Activity.ActivePRCount | Safe }} | ||||
| 					{{.i18n.TrN .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n" .Activity.ActivePRCount | Safe }} | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 			{{if .Permission.CanRead $.UnitTypeIssues}} | ||||
|  | @ -56,7 +56,7 @@ | |||
| 						<a class="table-cell tiny background light grey"></a> | ||||
| 					</div> | ||||
| 					{{end}} | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n") .Activity.ActiveIssueCount | Safe }} | ||||
| 					{{.i18n.TrN .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n" .Activity.ActiveIssueCount | Safe }} | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 		</div> | ||||
|  | @ -64,21 +64,21 @@ | |||
| 			{{if .Permission.CanRead $.UnitTypePullRequests}} | ||||
| 				<a href="#merged-pull-requests" class="ui attached segment text center"> | ||||
| 					<span class="text purple">{{svg "octicon-git-pull-request"}}</span> <strong>{{.Activity.MergedPRCount}}</strong><br> | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n") }} | ||||
| 					{{.i18n.TrN .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n"}} | ||||
| 				</a> | ||||
| 				<a href="#proposed-pull-requests" class="ui attached segment text center"> | ||||
| 					<span class="text green">{{svg "octicon-git-branch"}}</span> <strong>{{.Activity.OpenedPRCount}}</strong><br> | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n") }} | ||||
| 					{{.i18n.TrN .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n"}} | ||||
| 				</a> | ||||
| 			{{end}} | ||||
| 			{{if .Permission.CanRead $.UnitTypeIssues}} | ||||
| 				<a href="#closed-issues" class="ui attached segment text center"> | ||||
| 					<span class="text red">{{svg "octicon-issue-closed"}}</span> <strong>{{.Activity.ClosedIssueCount}}</strong><br> | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n") }} | ||||
| 					{{.i18n.TrN .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n"}} | ||||
| 				</a> | ||||
| 				<a href="#new-issues" class="ui attached segment text center"> | ||||
| 					<span class="text green">{{svg "octicon-issue-opened"}}</span> <strong>{{.Activity.OpenedIssueCount}}</strong><br> | ||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n") }} | ||||
| 					{{.i18n.TrN .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n"}} | ||||
| 				</a> | ||||
| 			{{end}} | ||||
| 		</div> | ||||
|  | @ -94,19 +94,19 @@ | |||
| 				<div class="ui attached segment horizontal segments"> | ||||
| 					<div class="ui attached segment text"> | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_exclude_merges" }} | ||||
| 						<strong>{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n") .Activity.Code.AuthorCount }}</strong> | ||||
| 						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n") }} | ||||
| 						<strong>{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCount }}</strong> | ||||
| 						<strong>{{.i18n.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n" .Activity.Code.AuthorCount}}</strong> | ||||
| 						{{.i18n.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n"}} | ||||
| 						<strong>{{.i18n.TrN .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCount}}</strong> | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch }} | ||||
| 						<strong>{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCountInAllBranches }}</strong> | ||||
| 						<strong>{{.i18n.TrN .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCountInAllBranches}}</strong> | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_push_to_all_branches" }} | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch }} | ||||
| 						<strong>{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n") .Activity.Code.ChangedFiles }}</strong> | ||||
| 						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n") }} | ||||
| 						<strong>{{.i18n.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n" .Activity.Code.ChangedFiles}}</strong> | ||||
| 						{{.i18n.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n"}} | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_additions" }} | ||||
| 						<strong class="text green">{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n") .Activity.Code.Additions }}</strong> | ||||
| 						<strong class="text green">{{.i18n.TrN .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n" .Activity.Code.Additions}}</strong> | ||||
| 						{{.i18n.Tr "repo.activity.git_stats_and_deletions" }} | ||||
| 						<strong class="text red">{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n") .Activity.Code.Deletions }}</strong>. | ||||
| 						<strong class="text red">{{.i18n.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}</strong>. | ||||
| 					</div> | ||||
| 					<div class="ui attached segment"> | ||||
| 						<div id="repo-activity-top-authors-chart"></div> | ||||
|  | @ -118,7 +118,10 @@ | |||
| 		{{if gt .Activity.PublishedReleaseCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="published-releases"> | ||||
| 				<span class="text">{{svg "octicon-tag"}}</span> | ||||
| 				{{.i18n.Tr "repo.activity.title.releases_published_by" (.i18n.Tr (TrN .i18n.Lang .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n") .Activity.PublishedReleaseCount) (.i18n.Tr (TrN .i18n.Lang .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.PublishedReleaseAuthorCount) }} | ||||
| 				{{.i18n.Tr "repo.activity.title.releases_published_by" | ||||
| 					(.i18n.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount) | ||||
| 					(.i18n.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount) | ||||
| 				}} | ||||
| 			</h4> | ||||
| 			<div class="list"> | ||||
| 				{{range .Activity.PublishedReleases}} | ||||
|  | @ -137,7 +140,10 @@ | |||
| 		{{if gt .Activity.MergedPRCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="merged-pull-requests"> | ||||
| 				<span class="text">{{svg "octicon-git-pull-request"}}</span> | ||||
| 				{{.i18n.Tr "repo.activity.title.prs_merged_by" (.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n") .Activity.MergedPRCount) (.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.MergedPRAuthorCount) }} | ||||
| 				{{.i18n.Tr "repo.activity.title.prs_merged_by" | ||||
| 					(.i18n.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount) | ||||
| 					(.i18n.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount) | ||||
| 				}} | ||||
| 			</h4> | ||||
| 			<div class="list"> | ||||
| 				{{range .Activity.MergedPRs}} | ||||
|  | @ -153,7 +159,10 @@ | |||
| 		{{if gt .Activity.OpenedPRCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="proposed-pull-requests"> | ||||
| 				<span class="text">{{svg "octicon-git-branch"}}</span> | ||||
| 				{{.i18n.Tr "repo.activity.title.prs_opened_by" (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n") .Activity.OpenedPRCount) (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.OpenedPRAuthorCount) }} | ||||
| 				{{.i18n.Tr "repo.activity.title.prs_opened_by" | ||||
| 					(.i18n.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount) | ||||
| 					(.i18n.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount) | ||||
| 				}} | ||||
| 			</h4> | ||||
| 			<div class="list"> | ||||
| 				{{range .Activity.OpenedPRs}} | ||||
|  | @ -169,7 +178,10 @@ | |||
| 		{{if gt .Activity.ClosedIssueCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="closed-issues"> | ||||
| 				<span class="text">{{svg "octicon-issue-closed"}}</span> | ||||
| 				{{.i18n.Tr "repo.activity.title.issues_closed_from" (.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n") .Activity.ClosedIssueCount) (.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.ClosedIssueAuthorCount) }} | ||||
| 				{{.i18n.Tr "repo.activity.title.issues_closed_from" | ||||
| 					(.i18n.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount) | ||||
| 					(.i18n.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount) | ||||
| 				}} | ||||
| 			</h4> | ||||
| 			<div class="list"> | ||||
| 				{{range .Activity.ClosedIssues}} | ||||
|  | @ -185,7 +197,10 @@ | |||
| 		{{if gt .Activity.OpenedIssueCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="new-issues"> | ||||
| 				<span class="text">{{svg "octicon-issue-opened"}}</span> | ||||
| 				{{.i18n.Tr "repo.activity.title.issues_created_by" (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n") .Activity.OpenedIssueCount) (.i18n.Tr (TrN .i18n.Lang .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n") .Activity.OpenedIssueAuthorCount) }} | ||||
| 				{{.i18n.Tr "repo.activity.title.issues_created_by" | ||||
| 					(.i18n.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount) | ||||
| 					(.i18n.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount) | ||||
| 				}} | ||||
| 			</h4> | ||||
| 			<div class="list"> | ||||
| 				{{range .Activity.OpenedIssues}} | ||||
|  | @ -201,7 +216,7 @@ | |||
| 		{{if gt .Activity.UnresolvedIssueCount 0}} | ||||
| 			<h4 class="ui horizontal divider header" id="unresolved-conversations"> | ||||
| 				<span class="text">{{svg "octicon-comment-discussion"}}</span> | ||||
| 				{{.i18n.Tr (TrN .i18n.Lang .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n") .Activity.UnresolvedIssueCount }} | ||||
| 				{{.i18n.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}} | ||||
| 			</h4> | ||||
| 			<div class="text center desc"> | ||||
| 				{{.i18n.Tr "repo.activity.unresolved_conv_desc"}} | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| 		<div class="file-header-left df ac"> | ||||
| 			<div class="file-info text grey normal mono"> | ||||
| 				<div class="file-info-entry"> | ||||
| 					{{.NumLines}} {{.i18n.Tr (TrN .i18n.Lang .NumLines "repo.line" "repo.lines") }} | ||||
| 					{{.NumLines}} {{.i18n.TrN .NumLines "repo.line" "repo.lines"}} | ||||
| 				</div> | ||||
| 				<div class="file-info-entry">{{FileSize .FileSize}}</div> | ||||
| 			</div> | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 
 | ||||
| 					{{if not .CanCreateRepo}} | ||||
| 						<div class="ui negative message"> | ||||
| 							<p>{{.i18n.Tr (TrN .i18n.Lang .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n") .MaxCreationLimit}}</p> | ||||
| 							<p>{{.i18n.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p> | ||||
| 						</div> | ||||
| 					{{end}} | ||||
| 					<div class="inline required field {{if .Err_Owner}}error{{end}}"> | ||||
|  |  | |||
|  | @ -187,9 +187,9 @@ | |||
| 				<span class="text grey"> | ||||
| 					<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a> | ||||
| 					{{if and .AddedLabels (not .RemovedLabels)}} | ||||
| 						{{$.i18n.Tr (TrN $.i18n.Lang (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels") (RenderLabels .AddedLabels) $createdStr | Safe}} | ||||
| 						{{$.i18n.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels .AddedLabels) $createdStr | Safe}} | ||||
| 					{{else if and (not .AddedLabels) .RemovedLabels}} | ||||
| 						{{$.i18n.Tr (TrN $.i18n.Lang (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels") (RenderLabels .RemovedLabels) $createdStr | Safe}} | ||||
| 						{{$.i18n.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels .RemovedLabels) $createdStr | Safe}} | ||||
| 					{{else}} | ||||
| 						{{$.i18n.Tr "repo.issues.add_remove_labels" (RenderLabels .AddedLabels) (RenderLabels .RemovedLabels) $createdStr | Safe}} | ||||
| 					{{end}} | ||||
|  | @ -716,7 +716,7 @@ | |||
| 				{{ if .IsForcePush }} | ||||
| 					{{$.i18n.Tr "repo.issues.force_push_codes" ($.Issue.PullRequest.HeadBranch|Escape) (ShortSha .OldCommit) (($.Issue.Repo.CommitLink .OldCommit)|Escape)  (ShortSha .NewCommit) (($.Issue.Repo.CommitLink .NewCommit)|Escape) $createdStr | Safe}} | ||||
| 				{{else}} | ||||
| 					{{$.i18n.Tr (TrN $.i18n.Lang (len .Commits) "repo.issues.push_commit_1" "repo.issues.push_commits_n") (len .Commits) $createdStr | Safe}} | ||||
| 					{{$.i18n.TrN (len .Commits) "repo.issues.push_commit_1" "repo.issues.push_commits_n" (len .Commits) $createdStr | Safe}} | ||||
| 				{{end}} | ||||
| 			</span> | ||||
| 		</div> | ||||
|  |  | |||
|  | @ -224,7 +224,7 @@ | |||
| 				{{else if .IsBlockedByChangedProtectedFiles}} | ||||
| 					<div class="item"> | ||||
| 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||
| 						{{$.i18n.Tr (TrN $.i18n.Lang $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n") | Safe }} | ||||
| 						{{$.i18n.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n" | Safe }} | ||||
| 						<div class="ui ordered list"> | ||||
| 							{{range .ChangedProtectedFiles}} | ||||
| 								<div data-value="-" class="item">{{.}}</div> | ||||
|  | @ -555,7 +555,7 @@ | |||
| 				{{else if .IsBlockedByChangedProtectedFiles}} | ||||
| 					<div class="item text red"> | ||||
| 						<i class="icon icon-octicon">{{svg "octicon-x" 16}}</i> | ||||
| 						{{$.i18n.Tr (TrN $.i18n.Lang $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n") | Safe }} | ||||
| 						{{$.i18n.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n" | Safe }} | ||||
| 						<div class="ui ordered list"> | ||||
| 							{{range .ChangedProtectedFiles}} | ||||
| 								<div data-value="-" class="item">{{.}}</div> | ||||
|  |  | |||
|  | @ -3,14 +3,14 @@ | |||
| 		<div class="ui two horizontal center link list"> | ||||
| 			{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}} | ||||
| 				<div class="item{{if .PageIsCommits}} active{{end}}"> | ||||
| 					<a class="ui" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}">{{svg "octicon-history"}} <b>{{.CommitsCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .CommitsCount "repo.commit" "repo.commits") }}</a> | ||||
| 					<a class="ui" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}">{{svg "octicon-history"}} <b>{{.CommitsCount}}</b> {{.i18n.TrN .CommitsCount "repo.commit" "repo.commits"}}</a> | ||||
| 				</div> | ||||
| 				<div class="item{{if .PageIsBranches}} active{{end}}"> | ||||
| 					<a class="ui" href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .BranchesCount "repo.branch" "repo.branches") }}</a> | ||||
| 					<a class="ui" href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.i18n.TrN .BranchesCount "repo.branch" "repo.branches"}}</a> | ||||
| 				</div> | ||||
| 				{{if $.Permission.CanRead $.UnitTypeCode}} | ||||
| 					<div class="item"> | ||||
| 						<a class="ui" href="{{.RepoLink}}/tags">{{svg "octicon-tag"}} <b>{{.NumTags}}</b> {{.i18n.Tr (TrN .i18n.Lang .NumTags "repo.tag" "repo.tags") }}</a> | ||||
| 						<a class="ui" href="{{.RepoLink}}/tags">{{svg "octicon-tag"}} <b>{{.NumTags}}</b> {{.i18n.TrN .NumTags "repo.tag" "repo.tags"}}</a> | ||||
| 					</div> | ||||
| 				{{end}} | ||||
| 				<div class="item"> | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| 					{{end}} | ||||
| 					{{if .NumLinesSet}} | ||||
| 						<div class="file-info-entry"> | ||||
| 							{{.NumLines}} {{.i18n.Tr (TrN .i18n.Lang .NumLines "repo.line" "repo.lines") }} | ||||
| 							{{.NumLines}} {{.i18n.TrN .NumLines "repo.line" "repo.lines"}} | ||||
| 						</div> | ||||
| 					{{end}} | ||||
| 					{{if .FileSize}} | ||||
|  |  | |||
|  | @ -91,25 +91,25 @@ | |||
| 						{{if gt $approveOfficial 0}} | ||||
| 							<span class="approvals df ac"> | ||||
| 								{{svg "octicon-check" 14 "mr-1"}} | ||||
| 								{{$.i18n.Tr (TrN $.i18n.Lang $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n") $approveOfficial}} | ||||
| 								{{$.i18n.TrN $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n" $approveOfficial}} | ||||
| 							</span> | ||||
| 						{{end}} | ||||
| 						{{if gt $rejectOfficial 0}} | ||||
| 							<span class="rejects df ac"> | ||||
| 								{{svg "octicon-diff" 14 "mr-2"}} | ||||
| 								{{$.i18n.Tr (TrN $.i18n.Lang $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n") $rejectOfficial}} | ||||
| 								{{$.i18n.TrN $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n" $rejectOfficial}} | ||||
| 							</span> | ||||
| 						{{end}} | ||||
| 						{{if gt $waitingOfficial 0}} | ||||
| 							<span class="waiting df ac"> | ||||
| 								{{svg "octicon-eye" 14 "mr-2"}} | ||||
| 								{{$.i18n.Tr (TrN $.i18n.Lang $waitingOfficial "repo.pulls.waiting_count_1" "repo.pulls.waiting_count_n") $waitingOfficial}} | ||||
| 								{{$.i18n.TrN $waitingOfficial "repo.pulls.waiting_count_1" "repo.pulls.waiting_count_n" $waitingOfficial}} | ||||
| 							</span> | ||||
| 						{{end}} | ||||
| 						{{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} | ||||
| 							<span class="conflicting df ac"> | ||||
| 								{{svg "octicon-x" 14}} | ||||
| 								{{$.i18n.Tr (TrN $.i18n.Lang (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n") (len .PullRequest.ConflictedFiles)}} | ||||
| 								{{$.i18n.TrN (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n" (len .PullRequest.ConflictedFiles)}} | ||||
| 							</span> | ||||
| 						{{end}} | ||||
| 					{{end}} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue