aboutsummaryrefslogtreecommitdiffhomepage
path: root/langs
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2021-04-24 12:26:51 +0200
committerBjørn Erik Pedersen <[email protected]>2021-04-25 11:12:30 +0200
commiteebde0c2ac4964e91d26d8b0cf0ac43afcfd207f (patch)
tree64535728c467d3e01f3b9b08d37576b89f669bf3 /langs
parente4dc9a82b557a417b1552c533b0df605c6ff1cc0 (diff)
downloadhugo-eebde0c2ac4964e91d26d8b0cf0ac43afcfd207f.tar.gz
hugo-eebde0c2ac4964e91d26d8b0cf0ac43afcfd207f.zip
langs/i18n: Improve plural handling of floats
The go-i18n library expects plural counts with floats to be represented as strings. Fixes #8464
Diffstat (limited to 'langs')
-rw-r--r--langs/i18n/i18n.go42
-rw-r--r--langs/i18n/i18n_test.go101
2 files changed, 121 insertions, 22 deletions
diff --git a/langs/i18n/i18n.go b/langs/i18n/i18n.go
index 17462bc56..75f0bdaaa 100644
--- a/langs/i18n/i18n.go
+++ b/langs/i18n/i18n.go
@@ -119,16 +119,17 @@ func (c intCount) Count() int {
const countFieldName = "Count"
-func getPluralCount(o interface{}) int {
- if o == nil {
+// getPluralCount gets the plural count as a string (floats) or an integer.
+func getPluralCount(v interface{}) interface{} {
+ if v == nil {
return 0
}
- switch v := o.(type) {
+ switch v := v.(type) {
case map[string]interface{}:
for k, vv := range v {
if strings.EqualFold(k, countFieldName) {
- return cast.ToInt(vv)
+ return toPluralCountValue(vv)
}
}
default:
@@ -141,17 +142,40 @@ func getPluralCount(o interface{}) int {
if tp.Kind() == reflect.Struct {
f := vv.FieldByName(countFieldName)
if f.IsValid() {
- return cast.ToInt(f.Interface())
+ return toPluralCountValue(f.Interface())
}
m := vv.MethodByName(countFieldName)
if m.IsValid() && m.Type().NumIn() == 0 && m.Type().NumOut() == 1 {
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
+ }
}
diff --git a/langs/i18n/i18n_test.go b/langs/i18n/i18n_test.go
index 8a2335c92..278ab4446 100644
--- a/langs/i18n/i18n_test.go
+++ b/langs/i18n/i18n_test.go
@@ -18,6 +18,8 @@ import (
"path/filepath"
"testing"
+ "github.com/gohugoio/hugo/common/types"
+
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/tpl/tplimpl"
@@ -287,7 +289,6 @@ one = "abc"`),
name: "dotted-bare-key",
data: map[string][]byte{
"en.toml": []byte(`"shop_nextPage.one" = "Show Me The Money"
-
`),
},
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 {
tp := prepareTranslationProvider(t, test, cfg)
f := tp.t.Func(test.lang)
@@ -317,7 +390,7 @@ func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) strin
}
type countField struct {
- Count int
+ Count interface{}
}
type noCountField struct {
@@ -327,8 +400,8 @@ type noCountField struct {
type countMethod struct {
}
-func (c countMethod) Count() int {
- return 32
+func (c countMethod) Count() interface{} {
+ return 32.5
}
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": 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{}{"Counts": 32}), qt.Equals, 0)
c.Assert(getPluralCount("foo"), qt.Equals, 0)
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(noCountField{Counts: 23}), qt.Equals, 0)
- c.Assert(getPluralCount(countMethod{}), qt.Equals, 32)
- c.Assert(getPluralCount(&countMethod{}), qt.Equals, 32)
+ c.Assert(getPluralCount(countMethod{}), qt.Equals, "32.5")
+ c.Assert(getPluralCount(&countMethod{}), qt.Equals, "32.5")
c.Assert(getPluralCount(1234), qt.Equals, 1234)
- c.Assert(getPluralCount(1234.4), qt.Equals, 1234)
- c.Assert(getPluralCount(1234.6), qt.Equals, 1234)
- c.Assert(getPluralCount(0.6), qt.Equals, 0)
- c.Assert(getPluralCount(1.0), qt.Equals, 1)
- c.Assert(getPluralCount("1234"), qt.Equals, 1234)
+ c.Assert(getPluralCount(1234.4), qt.Equals, "1234.4")
+ c.Assert(getPluralCount(1234.0), qt.Equals, "1234.0")
+ c.Assert(getPluralCount("1234"), qt.Equals, "1234")
+ c.Assert(getPluralCount("0.5"), qt.Equals, "0.5")
c.Assert(getPluralCount(nil), qt.Equals, 0)
}