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 }