taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

template.go (5511B)


      1 package internal
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"strconv"
      7 	"strings"
      8 	"sync"
      9 	"text/template"
     10 
     11 	"golang.org/x/text/message/catalog"
     12 )
     13 
     14 const (
     15 	// VarsKey is the key for the message's variables, per locale(global) or per key (local).
     16 	VarsKey = "Vars"
     17 	// PluralCountKey is the key for the template's message pluralization.
     18 	PluralCountKey = "PluralCount"
     19 	// VarCountKeySuffix is the key suffix for the template's variable's pluralization,
     20 	// e.g. HousesCount for ${Houses}.
     21 	VarCountKeySuffix = "Count"
     22 	// VarsKeySuffix is the key which the template message's variables
     23 	// are stored with,
     24 	// e.g. welcome.human.other_vars
     25 	VarsKeySuffix = "_vars"
     26 )
     27 
     28 // Template is a Renderer which renders template messages.
     29 type Template struct {
     30 	*Message
     31 	tmpl    *template.Template
     32 	bufPool *sync.Pool
     33 }
     34 
     35 // NewTemplate returns a new Template message based on the
     36 // catalog and the base translation Message. See `Locale.Load` method.
     37 func NewTemplate(c *Catalog, m *Message) (*Template, error) {
     38 	tmpl, err := template.New(m.Key).
     39 		Delims(m.Locale.Options.Left, m.Locale.Options.Right).
     40 		Funcs(m.Locale.FuncMap).
     41 		Parse(m.Value)
     42 
     43 	if err != nil {
     44 		return nil, err
     45 	}
     46 
     47 	if err := registerTemplateVars(c, m); err != nil {
     48 		return nil, fmt.Errorf("template vars: <%s = %s>: %w", m.Key, m.Value, err)
     49 	}
     50 
     51 	bufPool := &sync.Pool{
     52 		New: func() interface{} {
     53 			return new(bytes.Buffer)
     54 		},
     55 	}
     56 
     57 	t := &Template{
     58 		Message: m,
     59 		tmpl:    tmpl,
     60 		bufPool: bufPool,
     61 	}
     62 
     63 	return t, nil
     64 }
     65 
     66 func registerTemplateVars(c *Catalog, m *Message) error {
     67 	if len(m.Vars) == 0 {
     68 		return nil
     69 	}
     70 
     71 	msgs := selectfVars(m.Vars, false)
     72 
     73 	variableText := ""
     74 
     75 	for _, variable := range m.Vars {
     76 		variableText += variable.Literal + " "
     77 	}
     78 
     79 	variableText = variableText[0 : len(variableText)-1]
     80 
     81 	fullKey := m.Key + "." + VarsKeySuffix
     82 
     83 	return c.Set(m.Locale.tag, fullKey, append(msgs, catalog.String(variableText))...)
     84 }
     85 
     86 // Render completes the Renderer interface.
     87 // It renders a template message.
     88 // Each key has its own Template, plurals too.
     89 func (t *Template) Render(args ...interface{}) (string, error) {
     90 	var (
     91 		data   interface{}
     92 		result string
     93 	)
     94 
     95 	argsLength := len(args)
     96 
     97 	if argsLength > 0 {
     98 		data = args[0]
     99 	}
    100 
    101 	buf := t.bufPool.Get().(*bytes.Buffer)
    102 	buf.Reset()
    103 
    104 	if err := t.tmpl.Execute(buf, data); err != nil {
    105 		t.bufPool.Put(buf)
    106 		return "", err
    107 	}
    108 
    109 	result = buf.String()
    110 	t.bufPool.Put(buf)
    111 
    112 	if len(t.Vars) > 0 {
    113 		// get the variables plurals.
    114 		if argsLength > 1 {
    115 			// if has more than the map/struct
    116 			// then let's assume the user passes variable counts by raw integer arguments.
    117 			args = args[1:]
    118 		} else if data != nil {
    119 			// otherwise try to resolve them by the map(%var_name%Count)/struct(PlrualCounter).
    120 			args = findVarsCount(data, t.Vars)
    121 		}
    122 		result = t.replaceTmplVars(result, args...)
    123 	}
    124 
    125 	return result, nil
    126 }
    127 
    128 func findVarsCount(data interface{}, vars []Var) (args []interface{}) {
    129 	if data == nil {
    130 		return nil
    131 	}
    132 
    133 	switch dataValue := data.(type) {
    134 	case PluralCounter:
    135 		for _, v := range vars {
    136 			if count := dataValue.VarCount(v.Name); count >= 0 {
    137 				args = append(args, count)
    138 			}
    139 		}
    140 	case Map:
    141 		for _, v := range vars {
    142 			varCountKey := v.Name + VarCountKeySuffix
    143 			if value, ok := dataValue[varCountKey]; ok {
    144 				args = append(args, value)
    145 			}
    146 		}
    147 	case map[string]string:
    148 		for _, v := range vars {
    149 			varCountKey := v.Name + VarCountKeySuffix
    150 			if value, ok := dataValue[varCountKey]; ok {
    151 				if count, err := strconv.Atoi(value); err == nil {
    152 					args = append(args, count)
    153 				}
    154 			}
    155 		}
    156 	case map[string]int:
    157 		for _, v := range vars {
    158 			varCountKey := v.Name + VarCountKeySuffix
    159 			if value, ok := dataValue[varCountKey]; ok {
    160 				args = append(args, value)
    161 			}
    162 		}
    163 	default:
    164 		return nil
    165 	}
    166 
    167 	return
    168 }
    169 
    170 func findPluralCount(data interface{}) (int, bool) {
    171 	if data == nil {
    172 		return -1, false
    173 	}
    174 
    175 	switch dataValue := data.(type) {
    176 	case PluralCounter:
    177 		if count := dataValue.PluralCount(); count >= 0 {
    178 			return count, true
    179 		}
    180 	case Map:
    181 		if v, ok := dataValue[PluralCountKey]; ok {
    182 			if count, ok := v.(int); ok {
    183 				return count, true
    184 			}
    185 		}
    186 	case map[string]string:
    187 		if v, ok := dataValue[PluralCountKey]; ok {
    188 			count, err := strconv.Atoi(v)
    189 			if err != nil {
    190 				return -1, false
    191 			}
    192 
    193 			return count, true
    194 		}
    195 
    196 	case map[string]int:
    197 		if count, ok := dataValue[PluralCountKey]; ok {
    198 			return count, true
    199 		}
    200 	case int:
    201 		return dataValue, true // when this is not a template data, the caller's argument should be args[1:] now.
    202 	case int64:
    203 		count := int(dataValue)
    204 		return count, true
    205 	}
    206 
    207 	return -1, false
    208 }
    209 
    210 func (t *Template) replaceTmplVars(result string, args ...interface{}) string {
    211 	varsKey := t.Key + "." + VarsKeySuffix
    212 	translationVarsText := t.Locale.Printer.Sprintf(varsKey, args...)
    213 	if translationVarsText != "" {
    214 		translatioVars := strings.Split(translationVarsText, " ")
    215 		for i, variable := range t.Vars {
    216 			result = strings.Replace(result, variable.Literal, translatioVars[i], 1)
    217 		}
    218 	}
    219 
    220 	return result
    221 }
    222 
    223 func stringIsTemplateValue(value, left, right string) bool {
    224 	leftIdx, rightIdx := strings.Index(value, left), strings.Index(value, right)
    225 	return leftIdx != -1 && rightIdx > leftIdx
    226 }
    227 
    228 func getFuncs(loc *Locale) template.FuncMap {
    229 	// set the template funcs for this locale.
    230 	funcs := template.FuncMap{
    231 		"tr": loc.GetMessage,
    232 	}
    233 
    234 	if getFuncs := loc.Options.Funcs; getFuncs != nil {
    235 		// set current locale's template's funcs.
    236 		for k, v := range getFuncs(loc) {
    237 			funcs[k] = v
    238 		}
    239 	}
    240 
    241 	return funcs
    242 }