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