mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-29 23:20:49 +03:00
langs/i18n: Improve plural handling of floats
The go-i18n library expects plural counts with floats to be represented as strings. Fixes #8464
This commit is contained in:
parent
e4dc9a82b5
commit
eebde0c2ac
3 changed files with 127 additions and 22 deletions
|
@ -27,6 +27,12 @@ type RLocker interface {
|
||||||
RUnlock()
|
RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyValue is a interface{} tuple.
|
||||||
|
type KeyValue struct {
|
||||||
|
Key interface{}
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// KeyValueStr is a string tuple.
|
// KeyValueStr is a string tuple.
|
||||||
type KeyValueStr struct {
|
type KeyValueStr struct {
|
||||||
Key string
|
Key string
|
||||||
|
|
|
@ -119,16 +119,17 @@ func (c intCount) Count() int {
|
||||||
|
|
||||||
const countFieldName = "Count"
|
const countFieldName = "Count"
|
||||||
|
|
||||||
func getPluralCount(o interface{}) int {
|
// getPluralCount gets the plural count as a string (floats) or an integer.
|
||||||
if o == nil {
|
func getPluralCount(v interface{}) interface{} {
|
||||||
|
if v == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := o.(type) {
|
switch v := v.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
for k, vv := range v {
|
for k, vv := range v {
|
||||||
if strings.EqualFold(k, countFieldName) {
|
if strings.EqualFold(k, countFieldName) {
|
||||||
return cast.ToInt(vv)
|
return toPluralCountValue(vv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -141,17 +142,40 @@ func getPluralCount(o interface{}) int {
|
||||||
if tp.Kind() == reflect.Struct {
|
if tp.Kind() == reflect.Struct {
|
||||||
f := vv.FieldByName(countFieldName)
|
f := vv.FieldByName(countFieldName)
|
||||||
if f.IsValid() {
|
if f.IsValid() {
|
||||||
return cast.ToInt(f.Interface())
|
return toPluralCountValue(f.Interface())
|
||||||
}
|
}
|
||||||
m := vv.MethodByName(countFieldName)
|
m := vv.MethodByName(countFieldName)
|
||||||
if m.IsValid() && m.Type().NumIn() == 0 && m.Type().NumOut() == 1 {
|
if m.IsValid() && m.Type().NumIn() == 0 && m.Type().NumOut() == 1 {
|
||||||
c := m.Call(nil)
|
c := m.Call(nil)
|
||||||
return cast.ToInt(c[0].Interface())
|
return toPluralCountValue(c[0].Interface())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cast.ToInt(o)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return toPluralCountValue(v)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// go-i18n expects floats to be represented by string.
|
||||||
|
func toPluralCountValue(in interface{}) interface{} {
|
||||||
|
k := reflect.TypeOf(in).Kind()
|
||||||
|
switch {
|
||||||
|
case hreflect.IsFloat(k):
|
||||||
|
f := cast.ToString(in)
|
||||||
|
if !strings.Contains(f, ".") {
|
||||||
|
f += ".0"
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
case k == reflect.String:
|
||||||
|
if _, err := cast.ToFloat64E(in); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
// A non-numeric value.
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
if i, err := cast.ToIntE(in); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/types"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/modules"
|
"github.com/gohugoio/hugo/modules"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||||
|
@ -287,7 +289,6 @@ one = "abc"`),
|
||||||
name: "dotted-bare-key",
|
name: "dotted-bare-key",
|
||||||
data: map[string][]byte{
|
data: map[string][]byte{
|
||||||
"en.toml": []byte(`"shop_nextPage.one" = "Show Me The Money"
|
"en.toml": []byte(`"shop_nextPage.one" = "Show Me The Money"
|
||||||
|
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
args: nil,
|
args: nil,
|
||||||
|
@ -310,6 +311,78 @@ one = "abc"`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPlural(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
lang string
|
||||||
|
id string
|
||||||
|
templ string
|
||||||
|
variants []types.KeyValue
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "English",
|
||||||
|
lang: "en",
|
||||||
|
id: "hour",
|
||||||
|
templ: `
|
||||||
|
[hour]
|
||||||
|
one = "{{ . }} hour"
|
||||||
|
other = "{{ . }} hours"`,
|
||||||
|
variants: []types.KeyValue{
|
||||||
|
{Key: 1, Value: "1 hour"},
|
||||||
|
{Key: "1", Value: "1 hour"},
|
||||||
|
{Key: 1.5, Value: "1.5 hours"},
|
||||||
|
{Key: "1.5", Value: "1.5 hours"},
|
||||||
|
{Key: 2, Value: "2 hours"},
|
||||||
|
{Key: "2", Value: "2 hours"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Polish",
|
||||||
|
lang: "pl",
|
||||||
|
id: "day",
|
||||||
|
templ: `
|
||||||
|
[day]
|
||||||
|
one = "{{ . }} miesiąc"
|
||||||
|
few = "{{ . }} miesiące"
|
||||||
|
many = "{{ . }} miesięcy"
|
||||||
|
other = "{{ . }} miesiąca"
|
||||||
|
`,
|
||||||
|
variants: []types.KeyValue{
|
||||||
|
{Key: 1, Value: "1 miesiąc"},
|
||||||
|
{Key: 2, Value: "2 miesiące"},
|
||||||
|
{Key: 100, Value: "100 miesięcy"},
|
||||||
|
{Key: "100.0", Value: "100.0 miesiąca"},
|
||||||
|
{Key: 100.0, Value: "100 miesiąca"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
|
||||||
|
c.Run(test.name, func(c *qt.C) {
|
||||||
|
cfg := getConfig()
|
||||||
|
fs := hugofs.NewMem(cfg)
|
||||||
|
|
||||||
|
err := afero.WriteFile(fs.Source, filepath.Join("i18n", test.lang+".toml"), []byte(test.templ), 0755)
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
|
tp := NewTranslationProvider()
|
||||||
|
depsCfg := newDepsConfig(tp, cfg, fs)
|
||||||
|
d, err := deps.New(depsCfg)
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(d.LoadResources(), qt.IsNil)
|
||||||
|
|
||||||
|
f := tp.t.Func(test.lang)
|
||||||
|
|
||||||
|
for _, variant := range test.variants {
|
||||||
|
c.Assert(f(test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) string {
|
func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) string {
|
||||||
tp := prepareTranslationProvider(t, test, cfg)
|
tp := prepareTranslationProvider(t, test, cfg)
|
||||||
f := tp.t.Func(test.lang)
|
f := tp.t.Func(test.lang)
|
||||||
|
@ -317,7 +390,7 @@ func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) strin
|
||||||
}
|
}
|
||||||
|
|
||||||
type countField struct {
|
type countField struct {
|
||||||
Count int
|
Count interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type noCountField struct {
|
type noCountField struct {
|
||||||
|
@ -327,8 +400,8 @@ type noCountField struct {
|
||||||
type countMethod struct {
|
type countMethod struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c countMethod) Count() int {
|
func (c countMethod) Count() interface{} {
|
||||||
return 32
|
return 32.5
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPluralCount(t *testing.T) {
|
func TestGetPluralCount(t *testing.T) {
|
||||||
|
@ -336,23 +409,25 @@ func TestGetPluralCount(t *testing.T) {
|
||||||
|
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"Count": 32}), qt.Equals, 32)
|
c.Assert(getPluralCount(map[string]interface{}{"Count": 32}), qt.Equals, 32)
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"Count": 1}), qt.Equals, 1)
|
c.Assert(getPluralCount(map[string]interface{}{"Count": 1}), qt.Equals, 1)
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"Count": "32"}), qt.Equals, 32)
|
c.Assert(getPluralCount(map[string]interface{}{"Count": 1.5}), qt.Equals, "1.5")
|
||||||
|
c.Assert(getPluralCount(map[string]interface{}{"Count": "32"}), qt.Equals, "32")
|
||||||
|
c.Assert(getPluralCount(map[string]interface{}{"Count": "32.5"}), qt.Equals, "32.5")
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"count": 32}), qt.Equals, 32)
|
c.Assert(getPluralCount(map[string]interface{}{"count": 32}), qt.Equals, 32)
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"Count": "32"}), qt.Equals, 32)
|
c.Assert(getPluralCount(map[string]interface{}{"Count": "32"}), qt.Equals, "32")
|
||||||
c.Assert(getPluralCount(map[string]interface{}{"Counts": 32}), qt.Equals, 0)
|
c.Assert(getPluralCount(map[string]interface{}{"Counts": 32}), qt.Equals, 0)
|
||||||
c.Assert(getPluralCount("foo"), qt.Equals, 0)
|
c.Assert(getPluralCount("foo"), qt.Equals, 0)
|
||||||
c.Assert(getPluralCount(countField{Count: 22}), qt.Equals, 22)
|
c.Assert(getPluralCount(countField{Count: 22}), qt.Equals, 22)
|
||||||
|
c.Assert(getPluralCount(countField{Count: 1.5}), qt.Equals, "1.5")
|
||||||
c.Assert(getPluralCount(&countField{Count: 22}), qt.Equals, 22)
|
c.Assert(getPluralCount(&countField{Count: 22}), qt.Equals, 22)
|
||||||
c.Assert(getPluralCount(noCountField{Counts: 23}), qt.Equals, 0)
|
c.Assert(getPluralCount(noCountField{Counts: 23}), qt.Equals, 0)
|
||||||
c.Assert(getPluralCount(countMethod{}), qt.Equals, 32)
|
c.Assert(getPluralCount(countMethod{}), qt.Equals, "32.5")
|
||||||
c.Assert(getPluralCount(&countMethod{}), qt.Equals, 32)
|
c.Assert(getPluralCount(&countMethod{}), qt.Equals, "32.5")
|
||||||
|
|
||||||
c.Assert(getPluralCount(1234), qt.Equals, 1234)
|
c.Assert(getPluralCount(1234), qt.Equals, 1234)
|
||||||
c.Assert(getPluralCount(1234.4), qt.Equals, 1234)
|
c.Assert(getPluralCount(1234.4), qt.Equals, "1234.4")
|
||||||
c.Assert(getPluralCount(1234.6), qt.Equals, 1234)
|
c.Assert(getPluralCount(1234.0), qt.Equals, "1234.0")
|
||||||
c.Assert(getPluralCount(0.6), qt.Equals, 0)
|
c.Assert(getPluralCount("1234"), qt.Equals, "1234")
|
||||||
c.Assert(getPluralCount(1.0), qt.Equals, 1)
|
c.Assert(getPluralCount("0.5"), qt.Equals, "0.5")
|
||||||
c.Assert(getPluralCount("1234"), qt.Equals, 1234)
|
|
||||||
c.Assert(getPluralCount(nil), qt.Equals, 0)
|
c.Assert(getPluralCount(nil), qt.Equals, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue