langs/i18n: Revise the plural implementation

There were some issues introduced with the plural counting when we upgraded from v1 to v2 of go-i18n.

This commit improves that situation given the following rules:

* A single integer argument is used as plural count and passed to the i18n template as a int type with a `.Count` method. The latter is to preserve compability with v1.
* Else the plural count is either fetched from the `Count`/`count` field/method/map or from the value itself.
* Any data type is accepted, if it can be converted to an integer, that value is used.

The above means that you can now do pass a single integer and both of the below will work:

```
{{ . }} minutes to read
{{ .Count }} minutes to read
```

Fixes #8454
Closes #7822
See https://github.com/gohugoio/hugoDocs/issues/1410
This commit is contained in:
Bjørn Erik Pedersen
2021-04-22 09:57:24 +02:00
parent 243951ebe9
commit 537c905ec1
2 changed files with 164 additions and 8 deletions

View File

@@ -17,6 +17,8 @@ import (
"reflect"
"strings"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
@@ -69,17 +71,15 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
currentLangKey := strings.ToLower(strings.TrimPrefix(currentLangStr, artificialLangTagPrefix))
localizer := i18n.NewLocalizer(bndl, currentLangStr)
t.translateFuncs[currentLangKey] = func(translationID string, templateData interface{}) string {
var pluralCount interface{}
pluralCount := getPluralCount(templateData)
if templateData != nil {
tp := reflect.TypeOf(templateData)
if hreflect.IsNumber(tp.Kind()) {
pluralCount = templateData
// This was how go-i18n worked in v1.
templateData = map[string]interface{}{
"Count": templateData,
}
if hreflect.IsInt(tp.Kind()) {
// This was how go-i18n worked in v1,
// and we keep it like this to avoid breaking
// lots of sites in the wild.
templateData = intCount(cast.ToInt(templateData))
}
}
@@ -109,3 +109,49 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
}
}
}
// intCount wraps the Count method.
type intCount int
func (c intCount) Count() int {
return int(c)
}
const countFieldName = "Count"
func getPluralCount(o interface{}) int {
if o == nil {
return 0
}
switch v := o.(type) {
case map[string]interface{}:
for k, vv := range v {
if strings.EqualFold(k, countFieldName) {
return cast.ToInt(vv)
}
}
default:
vv := reflect.Indirect(reflect.ValueOf(v))
if vv.Kind() == reflect.Interface && !vv.IsNil() {
vv = vv.Elem()
}
tp := vv.Type()
if tp.Kind() == reflect.Struct {
f := vv.FieldByName(countFieldName)
if f.IsValid() {
return cast.ToInt(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 cast.ToInt(o)
}
return 0
}