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 }