hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

lang.go (6856B)

    1 // Copyright 2017 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 // Package lang provides template functions for content internationalization.
   15 package lang
   16 
   17 import (
   18 	"fmt"
   19 	"math"
   20 	"strconv"
   21 	"strings"
   22 
   23 	"errors"
   24 
   25 	"github.com/gohugoio/locales"
   26 	translators "github.com/gohugoio/localescompressed"
   27 
   28 	"github.com/gohugoio/hugo/common/hreflect"
   29 	"github.com/gohugoio/hugo/deps"
   30 	"github.com/spf13/cast"
   31 )
   32 
   33 // New returns a new instance of the lang-namespaced template functions.
   34 func New(deps *deps.Deps, translator locales.Translator) *Namespace {
   35 	return &Namespace{
   36 		translator: translator,
   37 		deps:       deps,
   38 	}
   39 }
   40 
   41 // Namespace provides template functions for the "lang" namespace.
   42 type Namespace struct {
   43 	translator locales.Translator
   44 	deps       *deps.Deps
   45 }
   46 
   47 // Translate returns a translated string for id.
   48 func (ns *Namespace) Translate(id any, args ...any) (string, error) {
   49 	var templateData any
   50 
   51 	if len(args) > 0 {
   52 		if len(args) > 1 {
   53 			return "", fmt.Errorf("wrong number of arguments, expecting at most 2, got %d", len(args)+1)
   54 		}
   55 		templateData = args[0]
   56 	}
   57 
   58 	sid, err := cast.ToStringE(id)
   59 	if err != nil {
   60 		return "", nil
   61 	}
   62 
   63 	return ns.deps.Translate(sid, templateData), nil
   64 }
   65 
   66 // FormatNumber formats number with the given precision for the current language.
   67 func (ns *Namespace) FormatNumber(precision, number any) (string, error) {
   68 	p, n, err := ns.castPrecisionNumber(precision, number)
   69 	if err != nil {
   70 		return "", err
   71 	}
   72 	return ns.translator.FmtNumber(n, p), nil
   73 }
   74 
   75 // FormatPercent formats number with the given precision for the current language.
   76 // Note that the number is assumed to be a percentage.
   77 func (ns *Namespace) FormatPercent(precision, number any) (string, error) {
   78 	p, n, err := ns.castPrecisionNumber(precision, number)
   79 	if err != nil {
   80 		return "", err
   81 	}
   82 	return ns.translator.FmtPercent(n, p), nil
   83 }
   84 
   85 // FormatCurrency returns the currency representation of number for the given currency and precision
   86 // for the current language.
   87 //
   88 // The return value is formatted with at least two decimal places.
   89 func (ns *Namespace) FormatCurrency(precision, currency, number any) (string, error) {
   90 	p, n, err := ns.castPrecisionNumber(precision, number)
   91 	if err != nil {
   92 		return "", err
   93 	}
   94 	c := translators.GetCurrency(cast.ToString(currency))
   95 	if c < 0 {
   96 		return "", fmt.Errorf("unknown currency code: %q", currency)
   97 	}
   98 	return ns.translator.FmtCurrency(n, p, c), nil
   99 }
  100 
  101 // FormatAccounting returns the currency representation of number for the given currency and precision
  102 // for the current language in accounting notation.
  103 //
  104 // The return value is formatted with at least two decimal places.
  105 func (ns *Namespace) FormatAccounting(precision, currency, number any) (string, error) {
  106 	p, n, err := ns.castPrecisionNumber(precision, number)
  107 	if err != nil {
  108 		return "", err
  109 	}
  110 	c := translators.GetCurrency(cast.ToString(currency))
  111 	if c < 0 {
  112 		return "", fmt.Errorf("unknown currency code: %q", currency)
  113 	}
  114 	return ns.translator.FmtAccounting(n, p, c), nil
  115 }
  116 
  117 func (ns *Namespace) castPrecisionNumber(precision, number any) (uint64, float64, error) {
  118 	p, err := cast.ToUint64E(precision)
  119 	if err != nil {
  120 		return 0, 0, err
  121 	}
  122 
  123 	// Sanity check.
  124 	if p > 20 {
  125 		return 0, 0, fmt.Errorf("invalid precision: %d", precision)
  126 	}
  127 
  128 	n, err := cast.ToFloat64E(number)
  129 	if err != nil {
  130 		return 0, 0, err
  131 	}
  132 	return p, n, nil
  133 }
  134 
  135 // FormatNumberCustom formats a number with the given precision using the
  136 // negative, decimal, and grouping options.  The `options`
  137 // parameter is a string consisting of `<negative> <decimal> <grouping>`.  The
  138 // default `options` value is `- . ,`.
  139 //
  140 // Note that numbers are rounded up at 5 or greater.
  141 // So, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.
  142 //
  143 // For a simpler function that adapts to the current language, see FormatNumber.
  144 func (ns *Namespace) FormatNumberCustom(precision, number any, options ...any) (string, error) {
  145 	prec, err := cast.ToIntE(precision)
  146 	if err != nil {
  147 		return "", err
  148 	}
  149 
  150 	n, err := cast.ToFloat64E(number)
  151 	if err != nil {
  152 		return "", err
  153 	}
  154 
  155 	var neg, dec, grp string
  156 
  157 	if len(options) == 0 {
  158 		// defaults
  159 		neg, dec, grp = "-", ".", ","
  160 	} else {
  161 		delim := " "
  162 
  163 		if len(options) == 2 {
  164 			// custom delimiter
  165 			s, err := cast.ToStringE(options[1])
  166 			if err != nil {
  167 				return "", nil
  168 			}
  169 
  170 			delim = s
  171 		}
  172 
  173 		s, err := cast.ToStringE(options[0])
  174 		if err != nil {
  175 			return "", nil
  176 		}
  177 
  178 		rs := strings.Split(s, delim)
  179 		switch len(rs) {
  180 		case 0:
  181 		case 1:
  182 			neg = rs[0]
  183 		case 2:
  184 			neg, dec = rs[0], rs[1]
  185 		case 3:
  186 			neg, dec, grp = rs[0], rs[1], rs[2]
  187 		default:
  188 			return "", errors.New("too many fields in options parameter to NumFmt")
  189 		}
  190 	}
  191 
  192 	exp := math.Pow(10.0, float64(prec))
  193 	r := math.Round(n*exp) / exp
  194 
  195 	// Logic from MIT Licensed github.com/gohugoio/locales/
  196 	// Original Copyright (c) 2016 Go Playground
  197 
  198 	s := strconv.FormatFloat(math.Abs(r), 'f', prec, 64)
  199 	L := len(s) + 2 + len(s[:len(s)-1-prec])/3
  200 
  201 	var count int
  202 	inWhole := prec == 0
  203 	b := make([]byte, 0, L)
  204 
  205 	for i := len(s) - 1; i >= 0; i-- {
  206 		if s[i] == '.' {
  207 			for j := len(dec) - 1; j >= 0; j-- {
  208 				b = append(b, dec[j])
  209 			}
  210 			inWhole = true
  211 			continue
  212 		}
  213 
  214 		if inWhole {
  215 			if count == 3 {
  216 				for j := len(grp) - 1; j >= 0; j-- {
  217 					b = append(b, grp[j])
  218 				}
  219 				count = 1
  220 			} else {
  221 				count++
  222 			}
  223 		}
  224 
  225 		b = append(b, s[i])
  226 	}
  227 
  228 	if n < 0 {
  229 		for j := len(neg) - 1; j >= 0; j-- {
  230 			b = append(b, neg[j])
  231 		}
  232 	}
  233 
  234 	// reverse
  235 	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
  236 		b[i], b[j] = b[j], b[i]
  237 	}
  238 
  239 	return string(b), nil
  240 }
  241 
  242 // NumFmt is deprecated, use FormatNumberCustom.
  243 // We renamed this in Hugo 0.87.
  244 // Deprecated: Use FormatNumberCustom
  245 func (ns *Namespace) NumFmt(precision, number any, options ...any) (string, error) {
  246 	return ns.FormatNumberCustom(precision, number, options...)
  247 }
  248 
  249 type pagesLanguageMerger interface {
  250 	MergeByLanguageInterface(other any) (any, error)
  251 }
  252 
  253 // Merge creates a union of pages from two languages.
  254 func (ns *Namespace) Merge(p2, p1 any) (any, error) {
  255 	if !hreflect.IsTruthful(p1) {
  256 		return p2, nil
  257 	}
  258 	if !hreflect.IsTruthful(p2) {
  259 		return p1, nil
  260 	}
  261 	merger, ok := p1.(pagesLanguageMerger)
  262 	if !ok {
  263 		return nil, fmt.Errorf("language merge not supported for %T", p1)
  264 	}
  265 	return merger.MergeByLanguageInterface(p2)
  266 }