github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/tpl/lang/lang.go (about)

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