diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 9b27ae5e3..09e1a331a 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -73,6 +73,9 @@ type HugoSites struct { // Render output formats for all sites. renderFormats output.Formats + // The currently rendered Site. + currentSite *Site + *deps.Deps gitInfo *gitInfo diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 6f3955b80..bf52277a9 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -289,6 +289,7 @@ func (h *HugoSites) render(config *BuildCfg) error { i := 0 for _, s := range h.Sites { + h.currentSite = s for siteOutIdx, renderFormat := range s.renderFormats { siteRenderContext.outIdx = siteOutIdx siteRenderContext.sitesOutIdx = i diff --git a/hugolib/site.go b/hugolib/site.go index 59326cab6..efa936830 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -738,6 +738,11 @@ func (s *SiteInfo) Sites() page.Sites { 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 { return fmt.Sprintf("Site(%q)", s.title) } diff --git a/langs/language.go b/langs/language.go index 3bb131be1..0df2914a1 100644 --- a/langs/language.go +++ b/langs/language.go @@ -21,6 +21,7 @@ import ( "github.com/pkg/errors" + "github.com/gohugoio/hugo/common/htime" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" "github.com/gohugoio/locales" @@ -77,7 +78,8 @@ type Language struct { // Used for date formatting etc. We don't want these exported to the // templates. // TODO(bep) do the same for some of the others. - translator locales.Translator + translator locales.Translator + timeFormatter htime.TimeFormatter location *time.Location @@ -113,9 +115,10 @@ func NewLanguage(lang string, cfg config.Provider) *Language { Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, LocalCfg: localCfg, - Provider: compositeConfig, - params: params, - translator: translator, + Provider: compositeConfig, + params: params, + translator: translator, + timeFormatter: htime.NewTimeFormatter(translator), } 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. // 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 { return l.translator } diff --git a/resources/page/integration_test.go b/resources/page/integration_test.go new file mode 100644 index 000000000..285b14342 --- /dev/null +++ b/resources/page/integration_test.go @@ -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)") +} diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go index 846ab2c03..4a59dc502 100644 --- a/resources/page/page_matcher_test.go +++ b/resources/page/page_matcher_test.go @@ -14,10 +14,11 @@ package page import ( - "github.com/gohugoio/hugo/common/hugo" "path/filepath" "testing" + "github.com/gohugoio/hugo/common/hugo" + qt "github.com/frankban/quicktest" ) diff --git a/resources/page/pagegroup.go b/resources/page/pagegroup.go index 18c98e70e..601af606a 100644 --- a/resources/page/pagegroup.go +++ b/resources/page/pagegroup.go @@ -26,6 +26,7 @@ import ( "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/hreflect" "github.com/gohugoio/hugo/compare" + "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/resources/resource" ) @@ -219,7 +220,7 @@ func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) { 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 { return nil, nil } @@ -234,16 +235,24 @@ func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p Pag 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 - 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]) i := 0 for _, e := range sp[1:] { - date = formatter(e.(Page)) - if r[i].Key.(string) != date { - r = append(r, PageGroup{Key: date}) + date = getDate(e.(Page)) + formatted := formatter.Format(date, format) + if r[i].Key.(string) != formatted { + r = append(r, PageGroup{Key: formatted}) i++ } 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 { return p.ByDate() } - formatter := func(p Page) string { - return p.Date().Format(format) + getDate := func(p Page) time.Time { + 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 @@ -273,10 +282,10 @@ func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, e sorter := func(p Pages) Pages { return p.ByPublishDate() } - formatter := func(p Page) string { - return p.PublishDate().Format(format) + getDate := func(p Page) time.Time { + 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 @@ -287,10 +296,10 @@ func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, er sorter := func(p Pages) Pages { return p.ByExpiryDate() } - formatter := func(p Page) string { - return p.ExpiryDate().Format(format) + getDate := func(p Page) time.Time { + 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 @@ -301,10 +310,10 @@ func (p Pages) GroupByLastmod(format string, order ...string) (PagesGroup, error sorter := func(p Pages) Pages { return p.ByLastmod() } - formatter := func(p Page) string { - return p.Lastmod().Format(format) + getDate := func(p Page) time.Time { + 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 @@ -340,10 +349,10 @@ func (p Pages) GroupByParamDate(key string, format string, order ...string) (Pag pageBy(pdate).Sort(r) return r } - formatter := func(p Page) string { - return dates[p].Format(format) + getDate := func(p Page) time.Time { + return dates[p] } - return p.groupByDateField(sorter, formatter, order...) + return p.groupByDateField(format, sorter, getDate, order...) } // ProbablyEq wraps compare.ProbablyEqer diff --git a/resources/page/pages_sort_test.go b/resources/page/pages_sort_test.go index 85a1cda15..cf4e339ee 100644 --- a/resources/page/pages_sort_test.go +++ b/resources/page/pages_sort_test.go @@ -18,18 +18,17 @@ import ( "testing" "time" - "github.com/gohugoio/hugo/htesting/hqt" - "github.com/gohugoio/hugo/source" - "github.com/gohugoio/hugo/resources/resource" + "github.com/google/go-cmp/cmp" qt "github.com/frankban/quicktest" ) -var eq = qt.CmpEquals(hqt.DeepAllowUnexported( - &testPage{}, - &source.FileInfo{}, -)) +var eq = qt.CmpEquals( + cmp.Comparer(func(p1, p2 testPage) bool { + return p1.path == p2.path && p1.weight == p2.weight + }), +) func TestDefaultSort(t *testing.T) { t.Parallel() diff --git a/resources/page/site.go b/resources/page/site.go index 3cc9989ee..b84f17914 100644 --- a/resources/page/site.go +++ b/resources/page/site.go @@ -37,6 +37,7 @@ type Site interface { ServerPort() int Title() string Sites() Sites + Current() Site Hugo() hugo.Info BaseURL() template.URL Taxonomies() any @@ -82,6 +83,10 @@ func (t testSite) Sites() Sites { return nil } +func (t testSite) Current() Site { + return t +} + func (t testSite) IsServer() bool { return false } diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go index cee1f99e5..30b8e4dff 100644 --- a/resources/page/testhelpers_test.go +++ b/resources/page/testhelpers_test.go @@ -64,6 +64,7 @@ func newTestPageWithFile(filename string) *testPage { currentSection: &testPage{ sectionEntries: []string{"a", "b", "c"}, }, + site: testSite{l: langs.NewDefaultLanguage(config.New())}, } } diff --git a/tpl/time/init.go b/tpl/time/init.go index a76348b7a..4bb2ddf67 100644 --- a/tpl/time/init.go +++ b/tpl/time/init.go @@ -28,7 +28,7 @@ func init() { if d.Language == nil { 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{ Name: name, diff --git a/tpl/time/time.go b/tpl/time/time.go index 66ea721fa..f82d63c44 100644 --- a/tpl/time/time.go +++ b/tpl/time/time.go @@ -21,15 +21,13 @@ import ( "github.com/gohugoio/hugo/common/htime" - "github.com/gohugoio/locales" - "github.com/spf13/cast" ) // 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{ - timeFormatter: htime.NewTimeFormatter(translator), + timeFormatter: timeFormatter, location: location, } } diff --git a/tpl/time/time_test.go b/tpl/time/time_test.go index f368ea43f..9001f6b6b 100644 --- a/tpl/time/time_test.go +++ b/tpl/time/time_test.go @@ -20,6 +20,7 @@ import ( qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/common/htime" translators "github.com/gohugoio/localescompressed" ) @@ -27,7 +28,7 @@ func TestTimeLocation(t *testing.T) { t.Parallel() loc, _ := time.LoadLocation("America/Antigua") - ns := New(translators.GetTranslator("en"), loc) + ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), loc) for i, test := range []struct { name string @@ -86,7 +87,7 @@ func TestFormat(t *testing.T) { c.Run("UTC", func(c *qt.C) { c.Parallel() - ns := New(translators.GetTranslator("en"), time.UTC) + ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC) for i, test := range []struct { layout string @@ -129,7 +130,7 @@ func TestFormat(t *testing.T) { loc, err := time.LoadLocation("America/Los_Angeles") 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") @@ -143,7 +144,7 @@ func TestFormat(t *testing.T) { func TestDuration(t *testing.T) { t.Parallel() - ns := New(translators.GetTranslator("en"), time.UTC) + ns := New(htime.NewTimeFormatter(translators.GetTranslator("en")), time.UTC) for i, test := range []struct { unit any