Localize time.Format
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 26 Jul 2021 16:28:57 +0000 (18:28 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 27 Jul 2021 17:02:48 +0000 (19:02 +0200)
Fixes #8797

common/htime/time.go [new file with mode: 0644]
common/htime/time_test.go [new file with mode: 0644]
docs/content/en/functions/dateformat.md
go.mod
go.sum
hugolib/dates_test.go [new file with mode: 0644]
langs/language.go
tpl/time/init.go
tpl/time/init_test.go
tpl/time/time.go
tpl/time/time_test.go

diff --git a/common/htime/time.go b/common/htime/time.go
new file mode 100644 (file)
index 0000000..cd294b3
--- /dev/null
@@ -0,0 +1,127 @@
+// 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 htime
+
+import (
+       "strings"
+       "time"
+
+       "github.com/go-playground/locales"
+)
+
+var (
+       longDayNames = []string{
+               "Sunday",
+               "Monday",
+               "Tuesday",
+               "Wednesday",
+               "Thursday",
+               "Friday",
+               "Saturday",
+       }
+
+       shortDayNames = []string{
+               "Sun",
+               "Mon",
+               "Tue",
+               "Wed",
+               "Thu",
+               "Fri",
+               "Sat",
+       }
+
+       shortMonthNames = []string{
+               "Jan",
+               "Feb",
+               "Mar",
+               "Apr",
+               "May",
+               "Jun",
+               "Jul",
+               "Aug",
+               "Sep",
+               "Oct",
+               "Nov",
+               "Dec",
+       }
+
+       longMonthNames = []string{
+               "January",
+               "February",
+               "March",
+               "April",
+               "May",
+               "June",
+               "July",
+               "August",
+               "September",
+               "October",
+               "November",
+               "December",
+       }
+)
+
+func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
+       if ltr == nil {
+               panic("must provide a locales.Translator")
+       }
+       return TimeFormatter{
+               ltr: ltr,
+       }
+}
+
+// TimeFormatter is locale aware.
+type TimeFormatter struct {
+       ltr locales.Translator
+}
+
+func (f TimeFormatter) Format(t time.Time, layout string) string {
+       if layout == "" {
+               return ""
+       }
+
+       if layout[0] == ':' {
+               // It may be one of Hugo's custom layouts.
+               switch strings.ToLower(layout[1:]) {
+               case "date_full":
+                       return f.ltr.FmtDateFull(t)
+               case "date_long":
+                       return f.ltr.FmtDateLong(t)
+               case "date_medium":
+                       return f.ltr.FmtDateMedium(t)
+               case "date_short":
+                       return f.ltr.FmtDateShort(t)
+               case "time_full":
+                       return f.ltr.FmtTimeFull(t)
+               case "time_long":
+                       return f.ltr.FmtTimeLong(t)
+               case "time_medium":
+                       return f.ltr.FmtTimeMedium(t)
+               case "time_short":
+                       return f.ltr.FmtTimeShort(t)
+               }
+       }
+
+       s := t.Format(layout)
+
+       monthIdx := t.Month() - 1 // Month() starts at 1.
+       dayIdx := t.Weekday()
+
+       s = strings.ReplaceAll(s, longMonthNames[monthIdx], f.ltr.MonthWide(t.Month()))
+       s = strings.ReplaceAll(s, shortMonthNames[monthIdx], f.ltr.MonthAbbreviated(t.Month()))
+       s = strings.ReplaceAll(s, longDayNames[dayIdx], f.ltr.WeekdayWide(t.Weekday()))
+       s = strings.ReplaceAll(s, shortDayNames[dayIdx], f.ltr.WeekdayAbbreviated(t.Weekday()))
+
+       return s
+}
diff --git a/common/htime/time_test.go b/common/htime/time_test.go
new file mode 100644 (file)
index 0000000..3830277
--- /dev/null
@@ -0,0 +1,111 @@
+// 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 htime
+
+import (
+       "testing"
+       "time"
+
+       translators "github.com/bep/gotranslators"
+       qt "github.com/frankban/quicktest"
+)
+
+func TestTimeFormatter(t *testing.T) {
+       c := qt.New(t)
+
+       june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
+       june06 = june06.Add(7777 * time.Second)
+
+       c.Run("Norsk nynorsk", func(c *qt.C) {
+               f := NewTimeFormatter(translators.Get("nn"))
+
+               c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "onsdag juni 6 2018")
+               c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "on. juni 6 2018")
+               c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "on. on.")
+       })
+
+       c.Run("Custom layouts Norsk nynorsk", func(c *qt.C) {
+               f := NewTimeFormatter(translators.Get("nn"))
+
+               c.Assert(f.Format(june06, ":date_full"), qt.Equals, "onsdag 6. juni 2018")
+               c.Assert(f.Format(june06, ":date_long"), qt.Equals, "6. juni 2018")
+               c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "6. juni 2018")
+               c.Assert(f.Format(june06, ":date_short"), qt.Equals, "06.06.2018")
+
+               c.Assert(f.Format(june06, ":time_full"), qt.Equals, "kl. 02:09:37 UTC")
+               c.Assert(f.Format(june06, ":time_long"), qt.Equals, "02:09:37 UTC")
+               c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "02:09:37")
+               c.Assert(f.Format(june06, ":time_short"), qt.Equals, "02:09")
+
+       })
+
+       c.Run("Custom layouts English", func(c *qt.C) {
+               f := NewTimeFormatter(translators.Get("en"))
+
+               c.Assert(f.Format(june06, ":date_full"), qt.Equals, "Wednesday, June 6, 2018")
+               c.Assert(f.Format(june06, ":date_long"), qt.Equals, "June 6, 2018")
+               c.Assert(f.Format(june06, ":date_medium"), qt.Equals, "Jun 6, 2018")
+               c.Assert(f.Format(june06, ":date_short"), qt.Equals, "6/6/18")
+
+               c.Assert(f.Format(june06, ":time_full"), qt.Equals, "2:09:37 am UTC")
+               c.Assert(f.Format(june06, ":time_long"), qt.Equals, "2:09:37 am UTC")
+               c.Assert(f.Format(june06, ":time_medium"), qt.Equals, "2:09:37 am")
+               c.Assert(f.Format(june06, ":time_short"), qt.Equals, "2:09 am")
+
+       })
+
+       c.Run("English", func(c *qt.C) {
+               f := NewTimeFormatter(translators.Get("en"))
+
+               c.Assert(f.Format(june06, "Monday Jan 2 2006"), qt.Equals, "Wednesday Jun 6 2018")
+               c.Assert(f.Format(june06, "Mon January 2 2006"), qt.Equals, "Wed June 6 2018")
+               c.Assert(f.Format(june06, "Mon Mon"), qt.Equals, "Wed Wed")
+       })
+
+}
+
+func BenchmarkTimeFormatter(b *testing.B) {
+       june06, _ := time.Parse("2006-Jan-02", "2018-Jun-06")
+
+       b.Run("Native", func(b *testing.B) {
+               for i := 0; i < b.N; i++ {
+                       got := june06.Format("Monday Jan 2 2006")
+                       if got != "Wednesday Jun 6 2018" {
+                               b.Fatalf("invalid format, got %q", got)
+                       }
+               }
+       })
+
+       b.Run("Localized", func(b *testing.B) {
+               f := NewTimeFormatter(translators.Get("nn"))
+               b.ResetTimer()
+               for i := 0; i < b.N; i++ {
+                       got := f.Format(june06, "Monday Jan 2 2006")
+                       if got != "onsdag juni 6 2018" {
+                               b.Fatalf("invalid format, got %q", got)
+                       }
+               }
+       })
+
+       b.Run("Localized Custom", func(b *testing.B) {
+               f := NewTimeFormatter(translators.Get("nn"))
+               b.ResetTimer()
+               for i := 0; i < b.N; i++ {
+                       got := f.Format(june06, ":date_medium")
+                       if got != "6. juni 2018" {
+                               b.Fatalf("invalid format, got %q", got)
+                       }
+               }
+       })
+}
index e5fb3608d530008c8d7d05fbb1395c58997ec9cb..d56a91f028c6371f56c02cc62cb3069f366e4b88 100644 (file)
@@ -1,6 +1,6 @@
 ---
-title: dateFormat
-description: Converts the textual representation of the `datetime` into the specified format.
+title: time.Format
+description: Converts a date/time to a localized string.
 godocref: https://golang.org/pkg/time/
 date: 2017-02-01
 publishdate: 2017-02-01
@@ -10,23 +10,47 @@ menu:
   docs:
     parent: "functions"
 keywords: [dates,time,strings]
-signature: ["dateFormat LAYOUT INPUT"]
+signature: ["time.Format LAYOUT INPUT"]
 workson: []
 hugoversion:
 relatedfuncs: [Format,now,Unix,time]
 deprecated: false
 ---
 
-`dateFormat` converts a timestamp string `INPUT` into the format specified by the `LAYOUT` string.
+`time.Format` (alias `dateFormat`) converts either a `time.Time` object (e.g. `.Date`) or a timestamp string `INPUT` into the format specified by the `LAYOUT` string.
 
+```go-html-template
+{{ time.Format "Monday, Jan 2, 2006" "2015-01-21" }} → "Wednesday, Jan 21, 2015"
 ```
-{{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }} → "Wednesday, Jan 21, 2015"
-```
 
-{{% warning %}}
-As of v0.19 of Hugo, the `dateFormat` function is *not* supported as part of Hugo's [multilingual feature](/content-management/multilingual/).
-{{% /warning %}}
+Note that since Hugo 0.87.0, `time.Format` will return a localized string for the currrent language. {{< new-in "0.87.0" >}}
+
+The `LAYOUT` string can be either:
 
-See [Go’s Layout String](/functions/format/#gos-layout-string) to learn about how the `LAYOUT` string has to be formatted. There are also some useful examples.
+* [Go’s Layout String](/functions/format/#gos-layout-string) to learn about how the `LAYOUT` string has to be formatted. There are also some useful examples.
+* A custom Hugo layout identifier (see full list below)
 
 See the [`time` function](/functions/time/) to convert a timestamp string to a Go `time.Time` type value.
+
+
+## Date/time formatting layouts
+
+{{< new-in "0.87.0" >}}
+
+Go's date layout strings can be hard to reason about, especially with multiple languages. Since Hugo 0.87.0 you can alternatively use some predefined layout idenfifiers that will output localized dates or times:
+
+```go-html-template
+{{ .Date | time.Format ":date_long" }}
+```
+
+The full list of custom layouts with examples for English:
+
+* `:date_full` => `Wednesday, June 6, 2018`
+* `:date_long` => `June 6, 2018`
+* `:date_medium` => `Jun 6, 2018`
+* `:date_short` => `6/6/18`
+
+* `:time_full` => `2:09:37 am UTC`
+* `:time_long` => `2:09:37 am UTC`
+* `:time_medium` => `2:09:37 am`
+* `:time_short` => `2:09 am`
diff --git a/go.mod b/go.mod
index dbefd12a798fe4b65aa7e96c9cb14b67f8ef2055..3d0919af780da03b8a432ccb9b61696bb7170bec 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
        github.com/bep/gitmap v1.1.2
        github.com/bep/godartsass v0.12.0
        github.com/bep/golibsass v1.0.0
+       github.com/bep/gotranslators v0.0.0-20210726170149-50377fc92c80
        github.com/bep/gowebp v0.1.0
        github.com/bep/tmc v0.5.1
        github.com/cli/safeexec v1.0.0
@@ -24,6 +25,7 @@ require (
        github.com/fsnotify/fsnotify v1.4.9
        github.com/getkin/kin-openapi v0.67.0
        github.com/ghodss/yaml v1.0.0
+       github.com/go-playground/locales v0.13.0
        github.com/gobuffalo/flect v0.2.3
        github.com/gobwas/glob v0.2.3
        github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013
diff --git a/go.sum b/go.sum
index 3671a7b58a89537ac6f49726f8c05a664b16f89e..0350e4e1dfd8fc189a805437797fc0f5db13388d 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -134,10 +134,14 @@ github.com/bep/godartsass v0.12.0 h1:VvGLA4XpXUjKvp53SI05YFLhRFJ78G+Ybnlaz6Oul7E
 github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4=
 github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
 github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
+github.com/bep/gotranslators v0.0.0-20210726170149-50377fc92c80 h1:FuOr7TE02FmHwf0HbOzfN0UyQfHoZd1R3PVuYduFU6U=
+github.com/bep/gotranslators v0.0.0-20210726170149-50377fc92c80/go.mod h1:/tUOv4Jdczp4ZggwBAQriNN97HsQdG1Gm+yV0PsIGD8=
 github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
 github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
 github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
 github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
+github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
+github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -210,6 +214,8 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/gobuffalo/flect v0.2.3 h1:f/ZukRnSNA/DUpSNDadko7Qc0PhGvsew35p/2tu+CRY=
 github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
diff --git a/hugolib/dates_test.go b/hugolib/dates_test.go
new file mode 100644 (file)
index 0000000..fa03bc1
--- /dev/null
@@ -0,0 +1,56 @@
+// 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 hugolib
+
+import (
+       "testing"
+)
+
+func TestDateFormatMultilingual(t *testing.T) {
+       b := newTestSitesBuilder(t)
+       b.WithConfigFile("toml", `
+baseURL = "https://example.org"
+
+defaultContentLanguage = "en"
+defaultContentLanguageInSubDir = true
+
+[languages]
+[languages.en]
+weight=10
+[languages.nn]
+weight=20
+       
+`)
+
+       pageWithDate := `---
+title: Page
+date: 2021-07-18
+---    
+`
+
+       b.WithContent(
+               "_index.en.md", pageWithDate,
+               "_index.nn.md", pageWithDate,
+       )
+
+       b.WithTemplatesAdded("index.html", `
+Date: {{ .Date | time.Format ":date_long" }}
+       `)
+
+       b.Build(BuildCfg{})
+
+       b.AssertFileContent("public/en/index.html", `Date: July 18, 2021`)
+       b.AssertFileContent("public/nn/index.html", `Date: 18. juli 2021`)
+
+}
index a86d82ba3f3aae5e8e8acf06df8b73046291369e..5f5e8ddef95dd8ef981c70f56d09cae274ce8cfe 100644 (file)
@@ -18,6 +18,8 @@ import (
        "strings"
        "sync"
 
+       translators "github.com/bep/gotranslators"
+       "github.com/go-playground/locales"
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/config"
 )
@@ -68,6 +70,11 @@ type Language struct {
        params    map[string]interface{}
        paramsMu  sync.Mutex
        paramsSet bool
+
+       // Used for date formatting etc. We don't want this exported to the
+       // templates.
+       // TODO(bep) do the same for some of the others.
+       translator locales.Translator
 }
 
 func (l *Language) String() string {
@@ -86,8 +93,22 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
 
        localCfg := config.New()
        compositeConfig := config.NewCompositeConfig(cfg, localCfg)
+       translator := translators.Get(lang)
+       if translator == nil {
+               translator = translators.Get(cfg.GetString("defaultContentLanguage"))
+               if translator == nil {
+                       translator = translators.Get("en")
+               }
+       }
 
-       l := &Language{Lang: lang, ContentDir: cfg.GetString("contentDir"), Cfg: cfg, LocalCfg: localCfg, Provider: compositeConfig, params: params}
+       l := &Language{
+               Lang:       lang,
+               ContentDir: cfg.GetString("contentDir"),
+               Cfg:        cfg, LocalCfg: localCfg,
+               Provider:   compositeConfig,
+               params:     params,
+               translator: translator,
+       }
        return l
 }
 
@@ -222,3 +243,10 @@ func (l *Language) IsSet(key string) bool {
        }
        return l.Cfg.IsSet(key)
 }
+
+// Internal access to unexported Language fields.
+// This construct is to prevent them from leaking to the templates.
+
+func GetTranslator(l *Language) locales.Translator {
+       return l.translator
+}
index 23e5263bac95588eac77cd2d6db0a57826b3d553..775878f718e91e093de5951b4a327e892ec89494 100644 (file)
@@ -15,6 +15,7 @@ package time
 
 import (
        "github.com/gohugoio/hugo/deps"
+       "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/tpl/internal"
 )
 
@@ -22,7 +23,10 @@ const name = "time"
 
 func init() {
        f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
-               ctx := New()
+               if d.Language == nil {
+                       panic("Language must be set")
+               }
+               ctx := New(langs.GetTranslator(d.Language))
 
                ns := &internal.TemplateFuncsNamespace{
                        Name: name,
index 672b035472d31684a6e7a1ab5d0e4ee8056a33cf..8c00ec51fb059b934d721672fa6136a54484ab39 100644 (file)
@@ -16,6 +16,9 @@ package time
 import (
        "testing"
 
+       "github.com/gohugoio/hugo/config"
+       "github.com/gohugoio/hugo/langs"
+
        "github.com/gohugoio/hugo/htesting/hqt"
 
        qt "github.com/frankban/quicktest"
@@ -29,7 +32,9 @@ func TestInit(t *testing.T) {
        var ns *internal.TemplateFuncsNamespace
 
        for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
-               ns = nsf(&deps.Deps{})
+               ns = nsf(&deps.Deps{
+                       Language: langs.NewDefaultLanguage(config.New()),
+               })
                if ns.Name == name {
                        found = true
                        break
index 8ed4606d6e61a017737156a93219ebaa3c0f9495..6fb901cc3aa21df205b7fa7eae6865c4f6c75861 100644 (file)
@@ -18,6 +18,10 @@ import (
        "fmt"
        _time "time"
 
+       "github.com/gohugoio/hugo/common/htime"
+
+       "github.com/go-playground/locales"
+
        "github.com/spf13/cast"
 )
 
@@ -49,12 +53,16 @@ var timeFormats = []string{
 }
 
 // New returns a new instance of the time-namespaced template functions.
-func New() *Namespace {
-       return &Namespace{}
+func New(translator locales.Translator) *Namespace {
+       return &Namespace{
+               timeFormatter: htime.NewTimeFormatter(translator),
+       }
 }
 
 // Namespace provides template functions for the "time" namespace.
-type Namespace struct{}
+type Namespace struct {
+       timeFormatter htime.TimeFormatter
+}
 
 // AsTime converts the textual representation of the datetime string into
 // a time.Time interface.
@@ -105,7 +113,7 @@ func (ns *Namespace) Format(layout string, v interface{}) (string, error) {
                return "", err
        }
 
-       return t.Format(layout), nil
+       return ns.timeFormatter.Format(t, layout), nil
 }
 
 // Now returns the current local time.
index d9e11287816d101254ac073c1b3469bdaafe8688..ed689f9a8021b16c7f59e8030ccdaebdd4aa0603 100644 (file)
@@ -16,12 +16,14 @@ package time
 import (
        "testing"
        "time"
+
+       translators "github.com/bep/gotranslators"
 )
 
 func TestTimeLocation(t *testing.T) {
        t.Parallel()
 
-       ns := New()
+       ns := New(translators.Get("en"))
 
        for i, test := range []struct {
                value    string
@@ -59,7 +61,7 @@ func TestTimeLocation(t *testing.T) {
 func TestFormat(t *testing.T) {
        t.Parallel()
 
-       ns := New()
+       ns := New(translators.Get("en"))
 
        for i, test := range []struct {
                layout string
@@ -76,6 +78,8 @@ func TestFormat(t *testing.T) {
                {time.RFC1123, time.Date(2016, time.March, 3, 4, 5, 0, 0, time.UTC), "Thu, 03 Mar 2016 04:05:00 UTC"},
                {time.RFC3339, "Thu, 03 Mar 2016 04:05:00 UTC", "2016-03-03T04:05:00Z"},
                {time.RFC1123, "2016-03-03T04:05:00Z", "Thu, 03 Mar 2016 04:05:00 UTC"},
+               // Custom layouts, as introduced in Hugo 0.87.
+               {":date_medium", "2015-01-21", "Jan 21, 2015"},
        } {
                result, err := ns.Format(test.layout, test.value)
                if b, ok := test.expect.(bool); ok && !b {
@@ -97,7 +101,7 @@ func TestFormat(t *testing.T) {
 func TestDuration(t *testing.T) {
        t.Parallel()
 
-       ns := New()
+       ns := New(translators.Get("en"))
 
        for i, test := range []struct {
                unit   interface{}