Skip to content

Commit e61b390

Browse files
authored
Unify and simplify TrN for i18n (#18141)
Refer: #18135 (comment) Now we have a unique and simple `TrN`, and make the fix of PR #18135 also use the better `TrN` logic.
1 parent 88da7a7 commit e61b390

File tree

19 files changed

+148
-136
lines changed

19 files changed

+148
-136
lines changed

modules/csv/csv_test.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"encoding/csv"
1010
"io"
11+
"strconv"
1112
"strings"
1213
"testing"
1314

@@ -21,14 +22,21 @@ func TestCreateReader(t *testing.T) {
2122
assert.Equal(t, ',', rd.Comma)
2223
}
2324

24-
//nolint
25+
func decodeSlashes(t *testing.T, s string) string {
26+
s = strings.ReplaceAll(s, "\n", "\\n")
27+
s = strings.ReplaceAll(s, "\"", "\\\"")
28+
decoded, err := strconv.Unquote(`"` + s + `"`)
29+
assert.NoError(t, err, "unable to decode string")
30+
return decoded
31+
}
32+
2533
func TestCreateReaderAndDetermineDelimiter(t *testing.T) {
2634
var cases = []struct {
2735
csv string
2836
expectedRows [][]string
2937
expectedDelimiter rune
3038
}{
31-
// case 0 - semicolon delmited
39+
// case 0 - semicolon delimited
3240
{
3341
csv: `a;b;c
3442
1;2;3
@@ -47,11 +55,11 @@ a, b c
4755
e f
4856
g h i
4957
j l
50-
m n,
58+
m n,\t
5159
p q r
5260
u
5361
v w x
54-
y
62+
y\t\t
5563
`,
5664
expectedRows: [][]string{
5765
{"col1", "col2", "col3"},
@@ -74,7 +82,7 @@ y
7482
a, b, c
7583
d,e,f
7684
,h, i
77-
j, ,
85+
j, ,\x20
7886
, , `,
7987
expectedRows: [][]string{
8088
{"col1", "col2", "col3"},
@@ -89,7 +97,7 @@ j, ,
8997
}
9098

9199
for n, c := range cases {
92-
rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(c.csv))
100+
rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(decodeSlashes(t, c.csv)))
93101
assert.NoError(t, err, "case %d: should not throw error: %v\n", n, err)
94102
assert.EqualValues(t, c.expectedDelimiter, rd.Comma, "case %d: delimiter should be '%c', got '%c'", n, c.expectedDelimiter, rd.Comma)
95103
rows, err := rd.ReadAll()
@@ -222,7 +230,7 @@ John Doe [email protected] This,note,had,a,lot,of,commas,to,test,delimters`,
222230
}
223231

224232
for n, c := range cases {
225-
delimiter := determineDelimiter(&markup.RenderContext{Filename: c.filename}, []byte(c.csv))
233+
delimiter := determineDelimiter(&markup.RenderContext{Filename: c.filename}, []byte(decodeSlashes(t, c.csv)))
226234
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
227235
}
228236
}
@@ -287,7 +295,7 @@ abc | |123
287295
}
288296

289297
for n, c := range cases {
290-
modifiedText := removeQuotedString(c.text)
298+
modifiedText := removeQuotedString(decodeSlashes(t, c.text))
291299
assert.EqualValues(t, c.expectedText, modifiedText, "case %d: modified text should be equal", n)
292300
}
293301
}
@@ -353,7 +361,7 @@ John Doe [email protected] This,note,had,a,lot,of,commas,to,test,delimters`,
353361
quoted,
354362
text," a
355363
2 "some,
356-
quoted,
364+
quoted,\t
357365
text," b
358366
3 "some,
359367
quoted,
@@ -442,7 +450,7 @@ jkl`,
442450
}
443451

444452
for n, c := range cases {
445-
delimiter := guessDelimiter([]byte(c.csv))
453+
delimiter := guessDelimiter([]byte(decodeSlashes(t, c.csv)))
446454
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
447455
}
448456
}
@@ -459,7 +467,7 @@ func TestGuessFromBeforeAfterQuotes(t *testing.T) {
459467
quoted,
460468
text," a
461469
2 "some,
462-
quoted,
470+
quoted,\t
463471
text," b
464472
3 "some,
465473
quoted,
@@ -534,7 +542,7 @@ a|"he said, ""here I am"""`,
534542
}
535543

536544
for n, c := range cases {
537-
delimiter := guessFromBeforeAfterQuotes([]byte(c.csv))
545+
delimiter := guessFromBeforeAfterQuotes([]byte(decodeSlashes(t, c.csv)))
538546
assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter)
539547
}
540548
}
@@ -549,6 +557,10 @@ func (l mockLocale) Tr(s string, _ ...interface{}) string {
549557
return s
550558
}
551559

560+
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
561+
return key1
562+
}
563+
552564
func TestFormatError(t *testing.T) {
553565
var cases = []struct {
554566
err error

modules/templates/helper.go

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ func NewFuncMap() []template.FuncMap {
239239
"DisableImportLocal": func() bool {
240240
return !setting.ImportLocalPaths
241241
},
242-
"TrN": TrN,
243242
"Dict": func(values ...interface{}) (map[string]interface{}, error) {
244243
if len(values)%2 != 0 {
245244
return nil, errors.New("invalid dict call")
@@ -857,69 +856,6 @@ func DiffLineTypeToStr(diffType int) string {
857856
return "same"
858857
}
859858

860-
// Language specific rules for translating plural texts
861-
var trNLangRules = map[string]func(int64) int{
862-
"en-US": func(cnt int64) int {
863-
if cnt == 1 {
864-
return 0
865-
}
866-
return 1
867-
},
868-
"lv-LV": func(cnt int64) int {
869-
if cnt%10 == 1 && cnt%100 != 11 {
870-
return 0
871-
}
872-
return 1
873-
},
874-
"ru-RU": func(cnt int64) int {
875-
if cnt%10 == 1 && cnt%100 != 11 {
876-
return 0
877-
}
878-
return 1
879-
},
880-
"zh-CN": func(cnt int64) int {
881-
return 0
882-
},
883-
"zh-HK": func(cnt int64) int {
884-
return 0
885-
},
886-
"zh-TW": func(cnt int64) int {
887-
return 0
888-
},
889-
"fr-FR": func(cnt int64) int {
890-
if cnt > -2 && cnt < 2 {
891-
return 0
892-
}
893-
return 1
894-
},
895-
}
896-
897-
// TrN returns key to be used for plural text translation
898-
func TrN(lang string, cnt interface{}, key1, keyN string) string {
899-
var c int64
900-
if t, ok := cnt.(int); ok {
901-
c = int64(t)
902-
} else if t, ok := cnt.(int16); ok {
903-
c = int64(t)
904-
} else if t, ok := cnt.(int32); ok {
905-
c = int64(t)
906-
} else if t, ok := cnt.(int64); ok {
907-
c = t
908-
} else {
909-
return keyN
910-
}
911-
912-
ruleFunc, ok := trNLangRules[lang]
913-
if !ok {
914-
ruleFunc = trNLangRules["en-US"]
915-
}
916-
917-
if ruleFunc(c) == 0 {
918-
return key1
919-
}
920-
return keyN
921-
}
922-
923859
// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from
924860
func MigrationIcon(hostname string) string {
925861
switch hostname {

modules/test/context_tests.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ func (l mockLocale) Tr(s string, _ ...interface{}) string {
103103
return s
104104
}
105105

106+
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
107+
return key1
108+
}
109+
106110
type mockResponseWriter struct {
107111
httptest.ResponseRecorder
108112
size int

modules/translation/translation.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
type Locale interface {
1818
Language() string
1919
Tr(string, ...interface{}) string
20+
TrN(cnt interface{}, key1, keyN string, args ...interface{}) string
2021
}
2122

2223
// LangType represents a lang type
@@ -99,3 +100,67 @@ func (l *locale) Language() string {
99100
func (l *locale) Tr(format string, args ...interface{}) string {
100101
return i18n.Tr(l.Lang, format, args...)
101102
}
103+
104+
// Language specific rules for translating plural texts
105+
var trNLangRules = map[string]func(int64) int{
106+
// the default rule is "en-US" if a language isn't listed here
107+
"en-US": func(cnt int64) int {
108+
if cnt == 1 {
109+
return 0
110+
}
111+
return 1
112+
},
113+
"lv-LV": func(cnt int64) int {
114+
if cnt%10 == 1 && cnt%100 != 11 {
115+
return 0
116+
}
117+
return 1
118+
},
119+
"ru-RU": func(cnt int64) int {
120+
if cnt%10 == 1 && cnt%100 != 11 {
121+
return 0
122+
}
123+
return 1
124+
},
125+
"zh-CN": func(cnt int64) int {
126+
return 0
127+
},
128+
"zh-HK": func(cnt int64) int {
129+
return 0
130+
},
131+
"zh-TW": func(cnt int64) int {
132+
return 0
133+
},
134+
"fr-FR": func(cnt int64) int {
135+
if cnt > -2 && cnt < 2 {
136+
return 0
137+
}
138+
return 1
139+
},
140+
}
141+
142+
// TrN returns translated message for plural text translation
143+
func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string {
144+
var c int64
145+
if t, ok := cnt.(int); ok {
146+
c = int64(t)
147+
} else if t, ok := cnt.(int16); ok {
148+
c = int64(t)
149+
} else if t, ok := cnt.(int32); ok {
150+
c = int64(t)
151+
} else if t, ok := cnt.(int64); ok {
152+
c = t
153+
} else {
154+
return l.Tr(keyN, args...)
155+
}
156+
157+
ruleFunc, ok := trNLangRules[l.Lang]
158+
if !ok {
159+
ruleFunc = trNLangRules["en-US"]
160+
}
161+
162+
if ruleFunc(c) == 0 {
163+
return l.Tr(key1, args...)
164+
}
165+
return l.Tr(keyN, args...)
166+
}

routers/web/repo/migrate.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,8 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error,
8181
case migrations.IsTwoFactorAuthError(err):
8282
ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form)
8383
case repo_model.IsErrReachLimitOfRepo(err):
84-
var msg string
8584
maxCreationLimit := owner.MaxCreationLimit()
86-
if maxCreationLimit == 1 {
87-
msg = ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit)
88-
} else {
89-
msg = ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit)
90-
}
85+
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
9186
ctx.RenderWithErr(msg, tpl, form)
9287
case repo_model.IsErrRepoAlreadyExist(err):
9388
ctx.Data["Err_RepoName"] = true

routers/web/repo/repo.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,8 @@ func Create(ctx *context.Context) {
162162
func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form interface{}) {
163163
switch {
164164
case repo_model.IsErrReachLimitOfRepo(err):
165-
var msg string
166165
maxCreationLimit := owner.MaxCreationLimit()
167-
if maxCreationLimit == 1 {
168-
msg = ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit)
169-
} else {
170-
msg = ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit)
171-
}
166+
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
172167
ctx.RenderWithErr(msg, tpl, form)
173168
case repo_model.IsErrRepoAlreadyExist(err):
174169
ctx.Data["Err_RepoName"] = true

routers/web/repo/setting.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -610,11 +610,8 @@ func SettingsPost(ctx *context.Context) {
610610

611611
if !ctx.Repo.Owner.CanCreateRepo() {
612612
maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
613-
if maxCreationLimit == 1 {
614-
ctx.Flash.Error(ctx.Tr("repo.form.reach_limit_of_creation_1", maxCreationLimit))
615-
} else {
616-
ctx.Flash.Error(ctx.Tr("repo.form.reach_limit_of_creation_n", maxCreationLimit))
617-
}
613+
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
614+
ctx.Flash.Error(msg)
618615
ctx.Redirect(repo.Link() + "/settings")
619616
return
620617
}

services/mailer/mail.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s
7878
// helper
7979
"i18n": locale,
8080
"Str2html": templates.Str2html,
81-
"TrN": templates.TrN,
8281
}
8382

8483
var content bytes.Buffer
@@ -129,7 +128,6 @@ func SendActivateEmailMail(u *user_model.User, email *user_model.EmailAddress) {
129128
// helper
130129
"i18n": locale,
131130
"Str2html": templates.Str2html,
132-
"TrN": templates.TrN,
133131
}
134132

135133
var content bytes.Buffer
@@ -160,7 +158,6 @@ func SendRegisterNotifyMail(u *user_model.User) {
160158
// helper
161159
"i18n": locale,
162160
"Str2html": templates.Str2html,
163-
"TrN": templates.TrN,
164161
}
165162

166163
var content bytes.Buffer
@@ -194,7 +191,6 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
194191
// helper
195192
"i18n": locale,
196193
"Str2html": templates.Str2html,
197-
"TrN": templates.TrN,
198194
}
199195

200196
var content bytes.Buffer
@@ -278,7 +274,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
278274
// helper
279275
"i18n": locale,
280276
"Str2html": templates.Str2html,
281-
"TrN": templates.TrN,
282277
}
283278

284279
var mailSubject bytes.Buffer

services/mailer/mail_release.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ func mailNewRelease(lang string, tos []string, rel *models.Release) {
7676
// helper
7777
"i18n": locale,
7878
"Str2html": templates.Str2html,
79-
"TrN": templates.TrN,
8079
}
8180

8281
var mailBody bytes.Buffer

services/mailer/mail_repo.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
7171
// helper
7272
"i18n": locale,
7373
"Str2html": templates.Str2html,
74-
"TrN": templates.TrN,
7574
}
7675

7776
if err := bodyTemplates.ExecuteTemplate(&content, string(mailRepoTransferNotify), data); err != nil {

0 commit comments

Comments
 (0)