URI: 
       langs/i18n: Revise the plural implementation - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit 537c905ec103dc5adaf8a1b2ccdef5da7cc660fd
   DIR parent 243951ebe9715d3da3968e96e6f60dcd53e25d92
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Thu, 22 Apr 2021 09:57:24 +0200
       
       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
       
       Diffstat:
         M langs/i18n/i18n.go                  |      62 +++++++++++++++++++++++++++----
         M langs/i18n/i18n_test.go             |     110 +++++++++++++++++++++++++++++++
       
       2 files changed, 164 insertions(+), 8 deletions(-)
       ---
   DIR diff --git a/langs/i18n/i18n.go b/langs/i18n/i18n.go
       @@ -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
       +}
   DIR diff --git a/langs/i18n/i18n_test.go b/langs/i18n/i18n_test.go
       @@ -142,6 +142,20 @@ other = "{{ .Count }} minutes to read"
                        expectedFlag: "One minute to read",
                },
                {
       +                name: "readingTime-many-dot",
       +                data: map[string][]byte{
       +                        "en.toml": []byte(`[readingTime]
       +one = "One minute to read"
       +other = "{{ . }} minutes to read"
       +`),
       +                },
       +                args:         21,
       +                lang:         "en",
       +                id:           "readingTime",
       +                expected:     "21 minutes to read",
       +                expectedFlag: "21 minutes to read",
       +        },
       +        {
                        name: "readingTime-many",
                        data: map[string][]byte{
                                "en.toml": []byte(`[readingTime]
       @@ -155,6 +169,62 @@ other = "{{ .Count }} minutes to read"
                        expected:     "21 minutes to read",
                        expectedFlag: "21 minutes to read",
                },
       +        // Issue #8454
       +        {
       +                name: "readingTime-map-one",
       +                data: map[string][]byte{
       +                        "en.toml": []byte(`[readingTime]
       +one = "One minute to read"
       +other = "{{ .Count }} minutes to read"
       +`),
       +                },
       +                args:         map[string]interface{}{"Count": 1},
       +                lang:         "en",
       +                id:           "readingTime",
       +                expected:     "One minute to read",
       +                expectedFlag: "One minute to read",
       +        },
       +        {
       +                name: "readingTime-string-one",
       +                data: map[string][]byte{
       +                        "en.toml": []byte(`[readingTime]
       +one = "One minute to read"
       +other = "{{ . }} minutes to read"
       +`),
       +                },
       +                args:         "1",
       +                lang:         "en",
       +                id:           "readingTime",
       +                expected:     "One minute to read",
       +                expectedFlag: "One minute to read",
       +        },
       +        {
       +                name: "readingTime-map-many",
       +                data: map[string][]byte{
       +                        "en.toml": []byte(`[readingTime]
       +one = "One minute to read"
       +other = "{{ .Count }} minutes to read"
       +`),
       +                },
       +                args:         map[string]interface{}{"Count": 21},
       +                lang:         "en",
       +                id:           "readingTime",
       +                expected:     "21 minutes to read",
       +                expectedFlag: "21 minutes to read",
       +        },
       +        {
       +                name: "argument-float",
       +                data: map[string][]byte{
       +                        "en.toml": []byte(`[float]
       +other = "Number is {{ . }}"
       +`),
       +                },
       +                args:         22.5,
       +                lang:         "en",
       +                id:           "float",
       +                expected:     "Number is 22.5",
       +                expectedFlag: "Number is 22.5",
       +        },
                // Same id and translation in current language
                // https://github.com/gohugoio/hugo/issues/2607
                {
       @@ -246,6 +316,46 @@ func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) strin
                return f(test.id, test.args)
        }
        
       +type countField struct {
       +        Count int
       +}
       +
       +type noCountField struct {
       +        Counts int
       +}
       +
       +type countMethod struct {
       +}
       +
       +func (c countMethod) Count() int {
       +        return 32
       +}
       +
       +func TestGetPluralCount(t *testing.T) {
       +        c := qt.New(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": 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: 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(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(nil), qt.Equals, 0)
       +}
       +
        func prepareTranslationProvider(t testing.TB, test i18nTest, cfg config.Provider) *TranslationProvider {
                c := qt.New(t)
                fs := hugofs.NewMem(cfg)