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
19 changed files with 148 additions and 136 deletions
|
@ -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...)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue