template.go 5.4 KB

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