2017-03-13 18:55:02 -04:00
|
|
|
// Copyright 2017 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.
|
|
|
|
|
2018-11-29 22:32:53 -05:00
|
|
|
// Package lang provides template functions for content internationalization.
|
2017-03-13 18:55:02 -04:00
|
|
|
package lang
|
|
|
|
|
|
|
|
import (
|
2023-03-04 08:43:23 -05:00
|
|
|
"context"
|
2018-03-15 04:37:30 -04:00
|
|
|
"fmt"
|
2016-12-28 23:09:31 -05:00
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2022-05-02 10:07:52 -04:00
|
|
|
"errors"
|
|
|
|
|
2021-08-02 10:24:04 -04:00
|
|
|
"github.com/gohugoio/locales"
|
2021-11-01 13:43:41 -04:00
|
|
|
translators "github.com/gohugoio/localescompressed"
|
2019-06-02 05:11:46 -04:00
|
|
|
|
2022-04-20 11:42:57 -04:00
|
|
|
"github.com/gohugoio/hugo/common/hreflect"
|
2017-06-13 12:42:45 -04:00
|
|
|
"github.com/gohugoio/hugo/deps"
|
2023-09-26 17:04:01 -04:00
|
|
|
"github.com/gohugoio/hugo/helpers"
|
2017-06-13 13:07:35 -04:00
|
|
|
"github.com/spf13/cast"
|
2017-03-13 18:55:02 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// New returns a new instance of the lang-namespaced template functions.
|
2021-07-28 06:28:52 -04:00
|
|
|
func New(deps *deps.Deps, translator locales.Translator) *Namespace {
|
2017-03-13 18:55:02 -04:00
|
|
|
return &Namespace{
|
2021-07-28 06:28:52 -04:00
|
|
|
translator: translator,
|
|
|
|
deps: deps,
|
2017-03-13 18:55:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Namespace provides template functions for the "lang" namespace.
|
|
|
|
type Namespace struct {
|
2021-07-28 06:28:52 -04:00
|
|
|
translator locales.Translator
|
|
|
|
deps *deps.Deps
|
2017-03-13 18:55:02 -04:00
|
|
|
}
|
|
|
|
|
2018-09-06 18:25:30 -04:00
|
|
|
// Translate returns a translated string for id.
|
2023-03-04 08:43:23 -05:00
|
|
|
func (ns *Namespace) Translate(ctx context.Context, id any, args ...any) (string, error) {
|
2022-03-17 17:03:27 -04:00
|
|
|
var templateData any
|
2019-06-02 05:11:46 -04:00
|
|
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
if len(args) > 1 {
|
2022-05-02 10:07:52 -04:00
|
|
|
return "", fmt.Errorf("wrong number of arguments, expecting at most 2, got %d", len(args)+1)
|
2019-06-02 05:11:46 -04:00
|
|
|
}
|
|
|
|
templateData = args[0]
|
|
|
|
}
|
|
|
|
|
2017-03-13 18:55:02 -04:00
|
|
|
sid, err := cast.ToStringE(id)
|
|
|
|
if err != nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2023-03-04 08:43:23 -05:00
|
|
|
return ns.deps.Translate(ctx, sid, templateData), nil
|
2017-03-13 18:55:02 -04:00
|
|
|
}
|
2016-12-28 23:09:31 -05:00
|
|
|
|
2021-07-28 06:28:52 -04:00
|
|
|
// FormatNumber formats number with the given precision for the current language.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) FormatNumber(precision, number any) (string, error) {
|
2021-07-28 06:28:52 -04:00
|
|
|
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.
|
2021-08-03 10:22:28 -04:00
|
|
|
// Note that the number is assumed to be a percentage.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) FormatPercent(precision, number any) (string, error) {
|
2021-07-28 06:28:52 -04:00
|
|
|
p, n, err := ns.castPrecisionNumber(precision, number)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return ns.translator.FmtPercent(n, p), nil
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:26:42 -04:00
|
|
|
// FormatCurrency returns the currency representation of number for the given currency and precision
|
2021-07-28 06:28:52 -04:00
|
|
|
// for the current language.
|
2021-09-01 20:26:42 -04:00
|
|
|
//
|
|
|
|
// The return value is formatted with at least two decimal places.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) FormatCurrency(precision, currency, number any) (string, error) {
|
2021-07-28 06:28:52 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-09-01 20:26:42 -04:00
|
|
|
// FormatAccounting returns the currency representation of number for the given currency and precision
|
2021-07-28 06:28:52 -04:00
|
|
|
// for the current language in accounting notation.
|
2021-09-01 20:26:42 -04:00
|
|
|
//
|
|
|
|
// The return value is formatted with at least two decimal places.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) FormatAccounting(precision, currency, number any) (string, error) {
|
2021-07-28 06:28:52 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) castPrecisionNumber(precision, number any) (uint64, float64, error) {
|
2021-07-28 06:28:52 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-03-20 12:39:33 -04:00
|
|
|
// FormatNumberCustom formats a number with the given precision. The first
|
|
|
|
// options parameter is a space-delimited string of characters to represent
|
|
|
|
// negativity, the decimal point, and grouping. The default value is `- . ,`.
|
|
|
|
// The second options parameter defines an alternate delimiting character.
|
2016-12-28 23:09:31 -05:00
|
|
|
//
|
|
|
|
// 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`.
|
2021-07-28 06:28:52 -04:00
|
|
|
//
|
2021-11-01 13:43:41 -04:00
|
|
|
// For a simpler function that adapts to the current language, see FormatNumber.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) FormatNumberCustom(precision, number any, options ...any) (string, error) {
|
2016-12-28 23:09:31 -05:00
|
|
|
prec, err := cast.ToIntE(precision)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := cast.ToFloat64E(number)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
var neg, dec, grp string
|
|
|
|
|
|
|
|
if len(options) == 0 {
|
2018-10-02 10:41:48 -04:00
|
|
|
// defaults
|
2016-12-28 23:09:31 -05:00
|
|
|
neg, dec, grp = "-", ".", ","
|
|
|
|
} else {
|
2018-10-02 10:41:48 -04:00
|
|
|
delim := " "
|
|
|
|
|
|
|
|
if len(options) == 2 {
|
|
|
|
// custom delimiter
|
|
|
|
s, err := cast.ToStringE(options[1])
|
|
|
|
if err != nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
delim = s
|
|
|
|
}
|
|
|
|
|
2016-12-28 23:09:31 -05:00
|
|
|
s, err := cast.ToStringE(options[0])
|
|
|
|
if err != nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2018-10-02 10:41:48 -04:00
|
|
|
rs := strings.Split(s, delim)
|
2016-12-28 23:09:31 -05:00
|
|
|
switch len(rs) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
neg = rs[0]
|
|
|
|
case 2:
|
|
|
|
neg, dec = rs[0], rs[1]
|
|
|
|
case 3:
|
|
|
|
neg, dec, grp = rs[0], rs[1], rs[2]
|
|
|
|
default:
|
|
|
|
return "", errors.New("too many fields in options parameter to NumFmt")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 18:13:23 -04:00
|
|
|
exp := math.Pow(10.0, float64(prec))
|
|
|
|
r := math.Round(n*exp) / exp
|
|
|
|
|
2021-08-02 10:24:04 -04:00
|
|
|
// Logic from MIT Licensed github.com/gohugoio/locales/
|
2016-12-28 23:09:31 -05:00
|
|
|
// Original Copyright (c) 2016 Go Playground
|
|
|
|
|
2020-04-01 18:13:23 -04:00
|
|
|
s := strconv.FormatFloat(math.Abs(r), 'f', prec, 64)
|
2016-12-28 23:09:31 -05:00
|
|
|
L := len(s) + 2 + len(s[:len(s)-1-prec])/3
|
|
|
|
|
|
|
|
var count int
|
|
|
|
inWhole := prec == 0
|
|
|
|
b := make([]byte, 0, L)
|
|
|
|
|
|
|
|
for i := len(s) - 1; i >= 0; i-- {
|
|
|
|
if s[i] == '.' {
|
|
|
|
for j := len(dec) - 1; j >= 0; j-- {
|
|
|
|
b = append(b, dec[j])
|
|
|
|
}
|
|
|
|
inWhole = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if inWhole {
|
|
|
|
if count == 3 {
|
|
|
|
for j := len(grp) - 1; j >= 0; j-- {
|
|
|
|
b = append(b, grp[j])
|
|
|
|
}
|
|
|
|
count = 1
|
|
|
|
} else {
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b = append(b, s[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
if n < 0 {
|
|
|
|
for j := len(neg) - 1; j >= 0; j-- {
|
|
|
|
b = append(b, neg[j])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reverse
|
|
|
|
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
b[i], b[j] = b[j], b[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(b), nil
|
|
|
|
}
|
2018-03-15 04:37:30 -04:00
|
|
|
|
2023-09-26 17:04:01 -04:00
|
|
|
// Deprecated: Use lang.FormatNumberCustom instead.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) NumFmt(precision, number any, options ...any) (string, error) {
|
2023-09-26 17:04:01 -04:00
|
|
|
helpers.Deprecated("lang.NumFmt", "Use lang.FormatNumberCustom instead.", false)
|
2021-07-28 06:28:52 -04:00
|
|
|
return ns.FormatNumberCustom(precision, number, options...)
|
|
|
|
}
|
|
|
|
|
2018-03-15 04:37:30 -04:00
|
|
|
type pagesLanguageMerger interface {
|
2022-03-17 17:03:27 -04:00
|
|
|
MergeByLanguageInterface(other any) (any, error)
|
2018-03-15 04:37:30 -04:00
|
|
|
}
|
|
|
|
|
2018-09-06 18:25:30 -04:00
|
|
|
// Merge creates a union of pages from two languages.
|
2022-03-17 17:03:27 -04:00
|
|
|
func (ns *Namespace) Merge(p2, p1 any) (any, error) {
|
2022-04-20 11:42:57 -04:00
|
|
|
if !hreflect.IsTruthful(p1) {
|
|
|
|
return p2, nil
|
|
|
|
}
|
|
|
|
if !hreflect.IsTruthful(p2) {
|
|
|
|
return p1, nil
|
|
|
|
}
|
2018-03-15 04:37:30 -04:00
|
|
|
merger, ok := p1.(pagesLanguageMerger)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("language merge not supported for %T", p1)
|
|
|
|
}
|
|
|
|
return merger.MergeByLanguageInterface(p2)
|
|
|
|
}
|