mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
tpl/lang: Add new localized versions of lang.FormatNumber etc.
Fixes #8820
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/docshelper"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/tpl/internal"
|
||||
)
|
||||
@@ -25,10 +26,12 @@ import (
|
||||
// This file provides documentation support and is randomly put into this package.
|
||||
func init() {
|
||||
docsProvider := func() docshelper.DocProvider {
|
||||
cfg := config.New()
|
||||
d := &deps.Deps{
|
||||
Cfg: config.New(),
|
||||
Cfg: cfg,
|
||||
Log: loggers.NewErrorLogger(),
|
||||
BuildStartListeners: &deps.Listeners{},
|
||||
Language: langs.NewDefaultLanguage(cfg),
|
||||
Site: page.NewDummyHugoSite(newTestConfig()),
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@ package lang
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/tpl/internal"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,7 @@ const name = "lang"
|
||||
|
||||
func init() {
|
||||
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
|
||||
ctx := New(d)
|
||||
ctx := New(d, langs.GetTranslator(d.Language))
|
||||
|
||||
ns := &internal.TemplateFuncsNamespace{
|
||||
Name: name,
|
||||
@@ -34,16 +35,45 @@ func init() {
|
||||
[][2]string{},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.NumFmt,
|
||||
ns.AddMethodMapping(ctx.FormatNumber,
|
||||
nil,
|
||||
[][2]string{
|
||||
{`{{ lang.NumFmt 2 12345.6789 }}`, `12,345.68`},
|
||||
{`{{ lang.NumFmt 2 12345.6789 "- , ." }}`, `12.345,68`},
|
||||
{`{{ lang.NumFmt 6 -12345.6789 "- ." }}`, `-12345.678900`},
|
||||
{`{{ lang.NumFmt 0 -12345.6789 "- . ," }}`, `-12,346`},
|
||||
{`{{ -98765.4321 | lang.NumFmt 2 }}`, `-98,765.43`},
|
||||
{`{{ 512.5032 | lang.FormatNumber 2 }}`, `512.50`},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.FormatPercent,
|
||||
nil,
|
||||
[][2]string{
|
||||
{`{{ 512.5032 | lang.FormatPercent 2 }}`, `512.50%`},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.FormatCurrency,
|
||||
nil,
|
||||
[][2]string{
|
||||
{`{{ 512.5032 | lang.FormatCurrency 2 "USD" }}`, `$512.50`},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.FormatAccounting,
|
||||
nil,
|
||||
[][2]string{
|
||||
{`{{ 512.5032 | lang.FormatAccounting 2 "NOK" }}`, `NOK512.50`},
|
||||
},
|
||||
)
|
||||
|
||||
ns.AddMethodMapping(ctx.FormatNumberCustom,
|
||||
nil,
|
||||
[][2]string{
|
||||
{`{{ lang.FormatNumberCustom 2 12345.6789 }}`, `12,345.68`},
|
||||
{`{{ lang.FormatNumberCustom 2 12345.6789 "- , ." }}`, `12.345,68`},
|
||||
{`{{ lang.FormatNumberCustom 6 -12345.6789 "- ." }}`, `-12345.678900`},
|
||||
{`{{ lang.FormatNumberCustom 0 -12345.6789 "- . ," }}`, `-12,346`},
|
||||
{`{{ -98765.4321 | lang.FormatNumberCustom 2 }}`, `-98,765.43`},
|
||||
},
|
||||
)
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
|
@@ -16,6 +16,9 @@ package lang
|
||||
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
|
||||
|
@@ -20,6 +20,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
translators "github.com/bep/gotranslators"
|
||||
"github.com/go-playground/locales"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
@@ -27,15 +29,17 @@ import (
|
||||
)
|
||||
|
||||
// New returns a new instance of the lang-namespaced template functions.
|
||||
func New(deps *deps.Deps) *Namespace {
|
||||
func New(deps *deps.Deps, translator locales.Translator) *Namespace {
|
||||
return &Namespace{
|
||||
deps: deps,
|
||||
translator: translator,
|
||||
deps: deps,
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides template functions for the "lang" namespace.
|
||||
type Namespace struct {
|
||||
deps *deps.Deps
|
||||
translator locales.Translator
|
||||
deps *deps.Deps
|
||||
}
|
||||
|
||||
// Translate returns a translated string for id.
|
||||
@@ -57,14 +61,81 @@ func (ns *Namespace) Translate(id interface{}, args ...interface{}) (string, err
|
||||
return ns.deps.Translate(sid, templateData), nil
|
||||
}
|
||||
|
||||
// NumFmt formats a number with the given precision using the
|
||||
// FormatNumber formats number with the given precision for the current language.
|
||||
func (ns *Namespace) FormatNumber(precision, number interface{}) (string, error) {
|
||||
p, n, err := ns.castPrecisionNumber(precision, number)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ns.translator.FmtNumber(n, p), nil
|
||||
}
|
||||
|
||||
// FormatPercent formats number with the given precision for the current language.
|
||||
// Note that the number is assumbed to be percent.
|
||||
func (ns *Namespace) FormatPercent(precision, number interface{}) (string, error) {
|
||||
p, n, err := ns.castPrecisionNumber(precision, number)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ns.translator.FmtPercent(n, p), nil
|
||||
}
|
||||
|
||||
// FormatCurrency returns the currency reprecentation of number for the given currency and precision
|
||||
// for the current language.
|
||||
func (ns *Namespace) FormatCurrency(precision, currency, number interface{}) (string, error) {
|
||||
p, n, err := ns.castPrecisionNumber(precision, number)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c := translators.GetCurrency(cast.ToString(currency))
|
||||
if c < 0 {
|
||||
return "", fmt.Errorf("unknown currency code: %q", currency)
|
||||
}
|
||||
return ns.translator.FmtCurrency(n, p, c), nil
|
||||
}
|
||||
|
||||
// FormatAccounting returns the currency reprecentation of number for the given currency and precision
|
||||
// for the current language in accounting notation.
|
||||
func (ns *Namespace) FormatAccounting(precision, currency, number interface{}) (string, error) {
|
||||
p, n, err := ns.castPrecisionNumber(precision, number)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c := translators.GetCurrency(cast.ToString(currency))
|
||||
if c < 0 {
|
||||
return "", fmt.Errorf("unknown currency code: %q", currency)
|
||||
}
|
||||
return ns.translator.FmtAccounting(n, p, c), nil
|
||||
}
|
||||
|
||||
func (ns *Namespace) castPrecisionNumber(precision, number interface{}) (uint64, float64, error) {
|
||||
p, err := cast.ToUint64E(precision)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// Sanity check.
|
||||
if p > 20 {
|
||||
return 0, 0, fmt.Errorf("invalid precision: %d", precision)
|
||||
}
|
||||
|
||||
n, err := cast.ToFloat64E(number)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return p, n, nil
|
||||
}
|
||||
|
||||
// FormatNumberCustom formats a number with the given precision using the
|
||||
// negative, decimal, and grouping options. The `options`
|
||||
// parameter is a string consisting of `<negative> <decimal> <grouping>`. The
|
||||
// default `options` value is `- . ,`.
|
||||
//
|
||||
// Note that numbers are rounded up at 5 or greater.
|
||||
// So, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.
|
||||
func (ns *Namespace) NumFmt(precision, number interface{}, options ...interface{}) (string, error) {
|
||||
//
|
||||
// For a simpler function that adapts to the current language, see FormatNumberCustom.
|
||||
func (ns *Namespace) FormatNumberCustom(precision, number interface{}, options ...interface{}) (string, error) {
|
||||
prec, err := cast.ToIntE(precision)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -162,6 +233,13 @@ func (ns *Namespace) NumFmt(precision, number interface{}, options ...interface{
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// NumFmt is deprecated, use FormatNumberCustom.
|
||||
// We renamed this in Hugo 0.87.
|
||||
// Deprecated: Use FormatNumberCustom
|
||||
func (ns *Namespace) NumFmt(precision, number interface{}, options ...interface{}) (string, error) {
|
||||
return ns.FormatNumberCustom(precision, number, options...)
|
||||
}
|
||||
|
||||
type pagesLanguageMerger interface {
|
||||
MergeByLanguageInterface(other interface{}) (interface{}, error)
|
||||
}
|
||||
|
@@ -3,15 +3,16 @@ package lang
|
||||
import (
|
||||
"testing"
|
||||
|
||||
translators "github.com/bep/gotranslators"
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
)
|
||||
|
||||
func TestNumFormat(t *testing.T) {
|
||||
func TestNumFmt(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
ns := New(&deps.Deps{})
|
||||
ns := New(&deps.Deps{}, nil)
|
||||
|
||||
cases := []struct {
|
||||
prec int
|
||||
@@ -49,12 +50,12 @@ func TestNumFormat(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if len(cas.runes) == 0 {
|
||||
s, err = ns.NumFmt(cas.prec, cas.n)
|
||||
s, err = ns.FormatNumberCustom(cas.prec, cas.n)
|
||||
} else {
|
||||
if cas.delim == "" {
|
||||
s, err = ns.NumFmt(cas.prec, cas.n, cas.runes)
|
||||
s, err = ns.FormatNumberCustom(cas.prec, cas.n, cas.runes)
|
||||
} else {
|
||||
s, err = ns.NumFmt(cas.prec, cas.n, cas.runes, cas.delim)
|
||||
s, err = ns.FormatNumberCustom(cas.prec, cas.n, cas.runes, cas.delim)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,3 +63,45 @@ func TestNumFormat(t *testing.T) {
|
||||
c.Assert(s, qt.Equals, cas.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatNumbers(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
nsNn := New(&deps.Deps{}, translators.GetTranslator("nn"))
|
||||
nsEn := New(&deps.Deps{}, translators.GetTranslator("en"))
|
||||
pi := 3.14159265359
|
||||
|
||||
c.Run("FormatNumber", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
got, err := nsNn.FormatNumber(3, pi)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, "3,142")
|
||||
|
||||
got, err = nsEn.FormatNumber(3, pi)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, "3.142")
|
||||
})
|
||||
|
||||
c.Run("FormatPercent", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
got, err := nsEn.FormatPercent(3, 67.33333)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, "67.333%")
|
||||
})
|
||||
|
||||
c.Run("FormatCurrency", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
got, err := nsEn.FormatCurrency(2, "USD", 20000)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, "$20,000.00")
|
||||
})
|
||||
|
||||
c.Run("FormatAccounting", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
got, err := nsEn.FormatAccounting(2, "USD", 20000)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, "$20,000.00")
|
||||
})
|
||||
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ func TestTimeLocation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
loc, _ := time.LoadLocation("America/Antigua")
|
||||
ns := New(translators.Get("en"), loc)
|
||||
ns := New(translators.GetTranslator("en"), loc)
|
||||
|
||||
for i, test := range []struct {
|
||||
value string
|
||||
@@ -67,7 +67,7 @@ func TestTimeLocation(t *testing.T) {
|
||||
func TestFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ns := New(translators.Get("en"), time.UTC)
|
||||
ns := New(translators.GetTranslator("en"), time.UTC)
|
||||
|
||||
for i, test := range []struct {
|
||||
layout string
|
||||
@@ -107,7 +107,7 @@ func TestFormat(t *testing.T) {
|
||||
func TestDuration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ns := New(translators.Get("en"), time.UTC)
|
||||
ns := New(translators.GetTranslator("en"), time.UTC)
|
||||
|
||||
for i, test := range []struct {
|
||||
unit interface{}
|
||||
|
Reference in New Issue
Block a user