Localize all the GroupBy*Date methods

Fixes #9745
This commit is contained in:
Bjørn Erik Pedersen 2022-04-05 09:57:58 +02:00
parent e0a882fd3b
commit 658e11ebaa
13 changed files with 144 additions and 42 deletions

View file

@ -73,6 +73,9 @@ type HugoSites struct {
// Render output formats for all sites. // Render output formats for all sites.
renderFormats output.Formats renderFormats output.Formats
// The currently rendered Site.
currentSite *Site
*deps.Deps *deps.Deps
gitInfo *gitInfo gitInfo *gitInfo

View file

@ -289,6 +289,7 @@ func (h *HugoSites) render(config *BuildCfg) error {
i := 0 i := 0
for _, s := range h.Sites { for _, s := range h.Sites {
h.currentSite = s
for siteOutIdx, renderFormat := range s.renderFormats { for siteOutIdx, renderFormat := range s.renderFormats {
siteRenderContext.outIdx = siteOutIdx siteRenderContext.outIdx = siteOutIdx
siteRenderContext.sitesOutIdx = i siteRenderContext.sitesOutIdx = i

View file

@ -738,6 +738,11 @@ func (s *SiteInfo) Sites() page.Sites {
return s.s.h.siteInfos() return s.s.h.siteInfos()
} }
// Current returns the currently rendered Site.
func (s *SiteInfo) Current() page.Site {
return s.s.h.currentSite.Info
}
func (s *SiteInfo) String() string { func (s *SiteInfo) String() string {
return fmt.Sprintf("Site(%q)", s.title) return fmt.Sprintf("Site(%q)", s.title)
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config"
"github.com/gohugoio/locales" "github.com/gohugoio/locales"
@ -77,7 +78,8 @@ type Language struct {
// Used for date formatting etc. We don't want these exported to the // Used for date formatting etc. We don't want these exported to the
// templates. // templates.
// TODO(bep) do the same for some of the others. // TODO(bep) do the same for some of the others.
translator locales.Translator translator locales.Translator
timeFormatter htime.TimeFormatter
location *time.Location location *time.Location
@ -113,9 +115,10 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
Lang: lang, Lang: lang,
ContentDir: cfg.GetString("contentDir"), ContentDir: cfg.GetString("contentDir"),
Cfg: cfg, LocalCfg: localCfg, Cfg: cfg, LocalCfg: localCfg,
Provider: compositeConfig, Provider: compositeConfig,
params: params, params: params,
translator: translator, translator: translator,
timeFormatter: htime.NewTimeFormatter(translator),
} }
if err := l.loadLocation(cfg.GetString("timeZone")); err != nil { if err := l.loadLocation(cfg.GetString("timeZone")); err != nil {
@ -260,6 +263,10 @@ func (l *Language) IsSet(key string) bool {
// Internal access to unexported Language fields. // Internal access to unexported Language fields.
// This construct is to prevent them from leaking to the templates. // This construct is to prevent them from leaking to the templates.
func GetTimeFormatter(l *Language) htime.TimeFormatter {
return l.timeFormatter
}
func GetTranslator(l *Language) locales.Translator { func GetTranslator(l *Language) locales.Translator {
return l.translator return l.translator
} }

View file

@ -0,0 +1,72 @@
// Copyright 2021 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package page_test
import (
"testing"
"github.com/gohugoio/hugo/hugolib"
)
func TestGroupByLocalizedDate(t *testing.T) {
files := `
-- config.toml --
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
title = 'My blog'
weight = 1
[languages.fr]
title = 'Mon blogue'
weight = 2
[languages.nn]
title = 'Bloggen min'
weight = 3
-- content/p1.md --
---
title: "Post 1"
date: "2020-01-01"
---
-- content/p2.md --
---
title: "Post 2"
date: "2020-02-01"
---
-- content/p1.fr.md --
---
title: "Post 1"
date: "2020-01-01"
---
-- content/p2.fr.md --
---
title: "Post 2"
date: "2020-02-01"
---
-- layouts/index.html --
{{ range $k, $v := site.RegularPages.GroupByDate "January, 2006" }}{{ $k }}|{{ $v.Key }}|{{ $v.Pages }}{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: true,
}).Build()
b.AssertFileContent("public/en/index.html", "0|February, 2020|Pages(1)1|January, 2020|Pages(1)")
b.AssertFileContent("public/fr/index.html", "0|février, 2020|Pages(1)1|janvier, 2020|Pages(1)")
}

View file

@ -14,10 +14,11 @@
package page package page
import ( import (
"github.com/gohugoio/hugo/common/hugo"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/gohugoio/hugo/common/hugo"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
) )

View file

@ -26,6 +26,7 @@ import (
"github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/compare" "github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/resources/resource"
) )
@ -219,7 +220,7 @@ func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) {
return r, nil return r, nil
} }
func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p Page) string, order ...string) (PagesGroup, error) { func (p Pages) groupByDateField(format string, sorter func(p Pages) Pages, getDate func(p Page) time.Time, order ...string) (PagesGroup, error) {
if len(p) < 1 { if len(p) < 1 {
return nil, nil return nil, nil
} }
@ -234,16 +235,24 @@ func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p Pag
return nil, nil return nil, nil
} }
date := formatter(sp[0].(Page)) firstPage := sp[0].(Page)
date := getDate(firstPage)
// Pages may be a mix of multiple languages, so we need to use the language
// for the currently rendered Site.
currentSite := firstPage.Site().Current()
formatter := langs.GetTimeFormatter(currentSite.Language())
formatted := formatter.Format(date, format)
var r []PageGroup var r []PageGroup
r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)}) r = append(r, PageGroup{Key: formatted, Pages: make(Pages, 0)})
r[0].Pages = append(r[0].Pages, sp[0]) r[0].Pages = append(r[0].Pages, sp[0])
i := 0 i := 0
for _, e := range sp[1:] { for _, e := range sp[1:] {
date = formatter(e.(Page)) date = getDate(e.(Page))
if r[i].Key.(string) != date { formatted := formatter.Format(date, format)
r = append(r, PageGroup{Key: date}) if r[i].Key.(string) != formatted {
r = append(r, PageGroup{Key: formatted})
i++ i++
} }
r[i].Pages = append(r[i].Pages, e) r[i].Pages = append(r[i].Pages, e)
@ -259,10 +268,10 @@ func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) {
sorter := func(p Pages) Pages { sorter := func(p Pages) Pages {
return p.ByDate() return p.ByDate()
} }
formatter := func(p Page) string { getDate := func(p Page) time.Time {
return p.Date().Format(format) return p.Date()
} }
return p.groupByDateField(sorter, formatter, order...) return p.groupByDateField(format, sorter, getDate, order...)
} }
// GroupByPublishDate groups by the given page's PublishDate value in // GroupByPublishDate groups by the given page's PublishDate value in
@ -273,10 +282,10 @@ func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, e
sorter := func(p Pages) Pages { sorter := func(p Pages) Pages {
return p.ByPublishDate() return p.ByPublishDate()
} }
formatter := func(p Page) string { getDate := func(p Page) time.Time {
return p.PublishDate().Format(format) return p.PublishDate()
} }
return p.groupByDateField(sorter, formatter, order...) return p.groupByDateField(format, sorter, getDate, order...)
} }
// GroupByExpiryDate groups by the given page's ExpireDate value in // GroupByExpiryDate groups by the given page's ExpireDate value in
@ -287,10 +296,10 @@ func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, er
sorter := func(p Pages) Pages { sorter := func(p Pages) Pages {
return p.ByExpiryDate() return p.ByExpiryDate()
} }
formatter := func(p Page) string { getDate := func(p Page) time.Time {
return p.ExpiryDate().Format(format) return p.ExpiryDate()
} }
return p.groupByDateField(sorter, formatter, order...) return p.groupByDateField(format, sorter, getDate, order...)
} }
// GroupByLastmod groups by the given page's Lastmod value in // GroupByLastmod groups by the given page's Lastmod value in
@ -301,10 +310,10 @@ func (p Pages) GroupByLastmod(format string, order ...string) (PagesGroup, error
sorter := func(p Pages) Pages { sorter := func(p Pages) Pages {
return p.ByLastmod() return p.ByLastmod()
} }
formatter := func(p Page) string { getDate := func(p Page) time.Time {
return p.Lastmod().Format(format) return p.Lastmod()
} }
return p.groupByDateField(sorter, formatter, order...) return p.groupByDateField(format, sorter, getDate, order...)
} }
// GroupByParamDate groups by a date set as a param on the page in // GroupByParamDate groups by a date set as a param on the page in
@ -340,10 +349,10 @@ func (p Pages) GroupByParamDate(key string, format string, order ...string) (Pag
pageBy(pdate).Sort(r) pageBy(pdate).Sort(r)
return r return r
} }
formatter := func(p Page) string { getDate := func(p Page) time.Time {
return dates[p].Format(format) return dates[p]
} }
return p.groupByDateField(sorter, formatter, order...) return p.groupByDateField(format, sorter, getDate, order...)
} }
// ProbablyEq wraps compare.ProbablyEqer // ProbablyEq wraps compare.ProbablyEqer

View file

@ -18,18 +18,17 @@ import (
"testing" "testing"
"time" "time"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/resources/resource"
"github.com/google/go-cmp/cmp"
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
) )
var eq = qt.CmpEquals(hqt.DeepAllowUnexported( var eq = qt.CmpEquals(
&testPage{}, cmp.Comparer(func(p1, p2 testPage) bool {
&source.FileInfo{}, return p1.path == p2.path && p1.weight == p2.weight
)) }),
)
func TestDefaultSort(t *testing.T) { func TestDefaultSort(t *testing.T) {
t.Parallel() t.Parallel()

View file

@ -37,6 +37,7 @@ type Site interface {
ServerPort() int ServerPort() int
Title() string Title() string
Sites() Sites Sites() Sites
Current() Site
Hugo() hugo.Info Hugo() hugo.Info
BaseURL() template.URL BaseURL() template.URL
Taxonomies() any Taxonomies() any
@ -82,6 +83,10 @@ func (t testSite) Sites() Sites {
return nil return nil
} }
func (t testSite) Current() Site {
return t
}
func (t testSite) IsServer() bool { func (t testSite) IsServer() bool {
return false return false
} }

View file

@ -64,6 +64,7 @@ func newTestPageWithFile(filename string) *testPage {
currentSection: &testPage{ currentSection: &testPage{
sectionEntries: []string{"a", "b", "c"}, sectionEntries: []string{"a", "b", "c"},
}, },
site: testSite{l: langs.NewDefaultLanguage(config.New())},
} }
} }

View file

@ -28,7 +28,7 @@ func init() {
if d.Language == nil { if d.Language == nil {
panic("Language must be set") panic("Language must be set")
} }
ctx := New(langs.GetTranslator(d.Language), langs.GetLocation(d.Language)) ctx := New(langs.GetTimeFormatter(d.Language), langs.GetLocation(d.Language))
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, Name: name,

View file

@ -21,15 +21,13 @@ import (
"github.com/gohugoio/hugo/common/htime" "github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/locales"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
// New returns a new instance of the time-namespaced template functions. // New returns a new instance of the time-namespaced template functions.
func New(translator locales.Translator, location *time.Location) *Namespace { func New(timeFormatter htime.TimeFormatter, location *time.Location) *Namespace {
return &Namespace{ return &Namespace{
timeFormatter: htime.NewTimeFormatter(translator), timeFormatter: timeFormatter,
location: location, location: location,
} }
} }

View file

@ -20,6 +20,7 @@ import (
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/htime"
translators "github.com/gohugoio/localescompressed" translators "github.com/gohugoio/localescompressed"
) )
@ -27,7 +28,7 @@ func TestTimeLocation(t *testing.T) {
t.Parallel() t.Parallel()
loc, _ := time.LoadLocation("America/Antigua") loc, _ := time.LoadLocation("America/Antigua")
ns := New(translators.GetTranslator("en"), loc) ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), loc)
for i, test := range []struct { for i, test := range []struct {
name string name string
@ -86,7 +87,7 @@ func TestFormat(t *testing.T) {
c.Run("UTC", func(c *qt.C) { c.Run("UTC", func(c *qt.C) {
c.Parallel() c.Parallel()
ns := New(translators.GetTranslator("en"), time.UTC) ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC)
for i, test := range []struct { for i, test := range []struct {
layout string layout string
@ -129,7 +130,7 @@ func TestFormat(t *testing.T) {
loc, err := time.LoadLocation("America/Los_Angeles") loc, err := time.LoadLocation("America/Los_Angeles")
c.Assert(err, qt.IsNil) c.Assert(err, qt.IsNil)
ns := New(translators.GetTranslator("en"), loc) ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), loc)
d, err := ns.Format(":time_full", "2020-03-09T11:00:00") d, err := ns.Format(":time_full", "2020-03-09T11:00:00")
@ -143,7 +144,7 @@ func TestFormat(t *testing.T) {
func TestDuration(t *testing.T) { func TestDuration(t *testing.T) {
t.Parallel() t.Parallel()
ns := New(translators.GetTranslator("en"), time.UTC) ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC)
for i, test := range []struct { for i, test := range []struct {
unit any unit any