Skip to content

Commit

Permalink
Unify and simplify TrN for i18n (#18141)
Browse files Browse the repository at this point in the history
Refer: #18135 (comment)

Now we have a unique and simple `TrN`, and make the fix of PR #18135 also use the better `TrN` logic.
  • Loading branch information
wxiaoguang authored Jan 2, 2022
1 parent 88da7a7 commit e61b390
Show file tree
Hide file tree
Showing 19 changed files with 148 additions and 136 deletions.
36 changes: 24 additions & 12 deletions modules/csv/csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"encoding/csv"
"io"
"strconv"
"strings"
"testing"

Expand All @@ -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
Expand All @@ -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"},
Expand All @@ -74,7 +82,7 @@ y
a, b, c
d,e,f
,h, i
j, ,
j, ,\x20
, , `,
expectedRows: [][]string{
{"col1", "col2", "col3"},
Expand All @@ -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()
Expand Down Expand Up @@ -222,7 +230,7 @@ John Doe [email protected] 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)
}
}
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -353,7 +361,7 @@ John Doe [email protected] This,note,had,a,lot,of,commas,to,test,delimters`,
quoted,
text," a
2 "some,
quoted,
quoted,\t
text," b
3 "some,
quoted,
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -459,7 +467,7 @@ func TestGuessFromBeforeAfterQuotes(t *testing.T) {
quoted,
text," a
2 "some,
quoted,
quoted,\t
text," b
3 "some,
quoted,
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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
Expand Down
64 changes: 0 additions & 64 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions modules/test/context_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions modules/translation/translation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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...)
}
7 changes: 1 addition & 6 deletions routers/web/repo/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 1 addition & 6 deletions routers/web/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions routers/web/repo/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 0 additions & 5 deletions services/mailer/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -160,7 +158,6 @@ func SendRegisterNotifyMail(u *user_model.User) {
// helper
"i18n": locale,
"Str2html": templates.Str2html,
"TrN": templates.TrN,
}

var content bytes.Buffer
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -278,7 +274,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
// helper
"i18n": locale,
"Str2html": templates.Str2html,
"TrN": templates.TrN,
}

var mailSubject bytes.Buffer
Expand Down
1 change: 0 additions & 1 deletion services/mailer/mail_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion services/mailer/mail_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit e61b390

Please sign in to comment.