From e61b390d545919244141b699b28e3fbc42adc66f Mon Sep 17 00:00:00 2001
From: wxiaoguang 
Date: Sun, 2 Jan 2022 11:33:57 +0800
Subject: [PATCH] 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.
---
 modules/csv/csv_test.go                       | 36 ++++++----
 modules/templates/helper.go                   | 64 ------------------
 modules/test/context_tests.go                 |  4 ++
 modules/translation/translation.go            | 65 +++++++++++++++++++
 routers/web/repo/migrate.go                   |  7 +-
 routers/web/repo/repo.go                      |  7 +-
 routers/web/repo/setting.go                   |  7 +-
 services/mailer/mail.go                       |  5 --
 services/mailer/mail_release.go               |  1 -
 services/mailer/mail_repo.go                  |  1 -
 templates/mail/issue/default.tmpl             |  2 +-
 templates/repo/activity.tmpl                  | 55 ++++++++++------
 templates/repo/blame.tmpl                     |  2 +-
 templates/repo/create.tmpl                    |  2 +-
 .../repo/issue/view_content/comments.tmpl     |  6 +-
 templates/repo/issue/view_content/pull.tmpl   |  4 +-
 templates/repo/sub_menu.tmpl                  |  6 +-
 templates/repo/view_file.tmpl                 |  2 +-
 templates/shared/issuelist.tmpl               |  8 +--
 19 files changed, 148 insertions(+), 136 deletions(-)
diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go
index d72c3e3a7..1612a6695 100644
--- a/modules/csv/csv_test.go
+++ b/modules/csv/csv_test.go
@@ -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
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index a05c0c1a9..fc07b49c7 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -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 {
diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go
index 1f893122f..62ec21f6f 100644
--- a/modules/test/context_tests.go
+++ b/modules/test/context_tests.go
@@ -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
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
index 77cc9ac7f..af1e5d25d 100644
--- a/modules/translation/translation.go
+++ b/modules/translation/translation.go
@@ -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...)
+}
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index 1bbb192b2..23e5b21b4 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -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
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 8e0998225..6bd16ff2b 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -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
diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go
index 57a195ee6..7ff83cc3d 100644
--- a/routers/web/repo/setting.go
+++ b/routers/web/repo/setting.go
@@ -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
 		}
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index 0a5573707..20552be58 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -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
diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go
index 02aa0f312..ee4c6f3a5 100644
--- a/services/mailer/mail_release.go
+++ b/services/mailer/mail_release.go
@@ -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
diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go
index 51b16aa7e..a5343f812 100644
--- a/services/mailer/mail_repo.go
+++ b/services/mailer/mail_repo.go
@@ -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 {
diff --git a/templates/mail/issue/default.tmpl b/templates/mail/issue/default.tmpl
index e01ec667e..aa91861d1 100644
--- a/templates/mail/issue/default.tmpl
+++ b/templates/mail/issue/default.tmpl
@@ -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}}
 		
 	{{end}}
diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl
index c67925e42..3086ca8e8 100644
--- a/templates/repo/activity.tmpl
+++ b/templates/repo/activity.tmpl
@@ -41,7 +41,7 @@
 						
 					
 					{{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 }}
 				
 			{{end}}
 			{{if .Permission.CanRead $.UnitTypeIssues}}
@@ -56,7 +56,7 @@
 						
 					
 					{{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 }}
 				
 			{{end}}
 		
@@ -64,21 +64,21 @@
 			{{if .Permission.CanRead $.UnitTypePullRequests}}
 				
 					{{svg "octicon-git-pull-request"}} {{.Activity.MergedPRCount}}
-					{{.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"}}
 				
 				
 					{{svg "octicon-git-branch"}} {{.Activity.OpenedPRCount}}
-					{{.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"}}
 				
 			{{end}}
 			{{if .Permission.CanRead $.UnitTypeIssues}}
 				
 					{{svg "octicon-issue-closed"}} {{.Activity.ClosedIssueCount}}
-					{{.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"}}
 				
 				
 					{{svg "octicon-issue-opened"}} {{.Activity.OpenedIssueCount}}
-					{{.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"}}
 				
 			{{end}}
 		
@@ -94,19 +94,19 @@
 				
 					
 						{{.i18n.Tr "repo.activity.git_stats_exclude_merges" }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n") .Activity.Code.AuthorCount }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n") }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCount }}
+						{{.i18n.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n" .Activity.Code.AuthorCount}}
+						{{.i18n.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n"}}
+						{{.i18n.TrN .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCount}}
 						{{.i18n.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n") .Activity.Code.CommitCountInAllBranches }}
+						{{.i18n.TrN .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCountInAllBranches}}
 						{{.i18n.Tr "repo.activity.git_stats_push_to_all_branches" }}
 						{{.i18n.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n") .Activity.Code.ChangedFiles }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n") }}
+						{{.i18n.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n" .Activity.Code.ChangedFiles}}
+						{{.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" }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n") .Activity.Code.Additions }}
+						{{.i18n.TrN .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n" .Activity.Code.Additions}}
 						{{.i18n.Tr "repo.activity.git_stats_and_deletions" }}
-						{{.i18n.Tr (TrN .i18n.Lang .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n") .Activity.Code.Deletions }}.
+						{{.i18n.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}.
 					
 					
 						
@@ -118,7 +118,10 @@
 		{{if gt .Activity.PublishedReleaseCount 0}}
 			
 			
 				{{range .Activity.PublishedReleases}}
@@ -137,7 +140,10 @@
 		{{if gt .Activity.MergedPRCount 0}}
 			
 			
 				{{range .Activity.MergedPRs}}
@@ -153,7 +159,10 @@
 		{{if gt .Activity.OpenedPRCount 0}}
 			
 			
 				{{range .Activity.OpenedPRs}}
@@ -169,7 +178,10 @@
 		{{if gt .Activity.ClosedIssueCount 0}}
 			
 			
 				{{range .Activity.ClosedIssues}}
@@ -185,7 +197,10 @@
 		{{if gt .Activity.OpenedIssueCount 0}}
 			
 			
 				{{range .Activity.OpenedIssues}}
@@ -201,7 +216,7 @@
 		{{if gt .Activity.UnresolvedIssueCount 0}}
 			
 			
 				{{.i18n.Tr "repo.activity.unresolved_conv_desc"}}
diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl
index 0b37f41c2..cdd31c0eb 100644
--- a/templates/repo/blame.tmpl
+++ b/templates/repo/blame.tmpl
@@ -3,7 +3,7 @@