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 }