You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

i18n_test.go 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package i18n
  4. import (
  5. "html/template"
  6. "strings"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. )
  10. func TestLocaleStore(t *testing.T) {
  11. testData1 := []byte(`
  12. .dot.name = Dot Name
  13. fmt = %[1]s %[2]s
  14. [section]
  15. sub = Sub String
  16. mixed = test value; <span style="color: red\; background: none;">%s</span>
  17. `)
  18. testData2 := []byte(`
  19. fmt = %[2]s %[1]s
  20. [section]
  21. sub = Changed Sub String
  22. `)
  23. ls := NewLocaleStore()
  24. assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, nil))
  25. assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
  26. ls.SetDefaultLang("lang1")
  27. lang1, _ := ls.Locale("lang1")
  28. lang2, _ := ls.Locale("lang2")
  29. result := lang1.TrString("fmt", "a", "b")
  30. assert.Equal(t, "a b", result)
  31. result = lang2.TrString("fmt", "a", "b")
  32. assert.Equal(t, "b a", result)
  33. result = lang1.TrString("section.sub")
  34. assert.Equal(t, "Sub String", result)
  35. result = lang2.TrString("section.sub")
  36. assert.Equal(t, "Changed Sub String", result)
  37. langNone, _ := ls.Locale("none")
  38. result = langNone.TrString(".dot.name")
  39. assert.Equal(t, "Dot Name", result)
  40. result2 := lang2.TrHTML("section.mixed", "a&b")
  41. assert.EqualValues(t, `test value; <span style="color: red; background: none;">a&amp;b</span>`, result2)
  42. langs, descs := ls.ListLangNameDesc()
  43. assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
  44. assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
  45. found := lang1.HasKey("no-such")
  46. assert.False(t, found)
  47. assert.NoError(t, ls.Close())
  48. }
  49. func TestLocaleStoreMoreSource(t *testing.T) {
  50. testData1 := []byte(`
  51. a=11
  52. b=12
  53. `)
  54. testData2 := []byte(`
  55. b=21
  56. c=22
  57. `)
  58. ls := NewLocaleStore()
  59. assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
  60. lang1, _ := ls.Locale("lang1")
  61. assert.Equal(t, "11", lang1.TrString("a"))
  62. assert.Equal(t, "21", lang1.TrString("b"))
  63. assert.Equal(t, "22", lang1.TrString("c"))
  64. }
  65. type stringerPointerReceiver struct {
  66. s string
  67. }
  68. func (s *stringerPointerReceiver) String() string {
  69. return s.s
  70. }
  71. type stringerStructReceiver struct {
  72. s string
  73. }
  74. func (s stringerStructReceiver) String() string {
  75. return s.s
  76. }
  77. type errorStructReceiver struct {
  78. s string
  79. }
  80. func (e errorStructReceiver) Error() string {
  81. return e.s
  82. }
  83. type errorPointerReceiver struct {
  84. s string
  85. }
  86. func (e *errorPointerReceiver) Error() string {
  87. return e.s
  88. }
  89. func TestLocaleWithTemplate(t *testing.T) {
  90. ls := NewLocaleStore()
  91. assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", []byte(`key=<a>%s</a>`), nil))
  92. lang1, _ := ls.Locale("lang1")
  93. tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
  94. tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
  95. cases := []struct {
  96. in any
  97. want string
  98. }{
  99. {"<str>", "<a>&lt;str&gt;</a>"},
  100. {[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"},
  101. {template.HTML("<html>"), "<a><html></a>"},
  102. {stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{&lt;stringerPointerReceiver&gt;}</a>"},
  103. {&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a>&lt;stringerPointerReceiver ptr&gt;</a>"},
  104. {stringerStructReceiver{"<stringerStructReceiver>"}, "<a>&lt;stringerStructReceiver&gt;</a>"},
  105. {&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a>&lt;stringerStructReceiver ptr&gt;</a>"},
  106. {errorStructReceiver{"<errorStructReceiver>"}, "<a>&lt;errorStructReceiver&gt;</a>"},
  107. {&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a>&lt;errorStructReceiver ptr&gt;</a>"},
  108. {errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{&lt;errorPointerReceiver&gt;}</a>"},
  109. {&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a>&lt;errorPointerReceiver ptr&gt;</a>"},
  110. }
  111. buf := &strings.Builder{}
  112. for _, c := range cases {
  113. buf.Reset()
  114. assert.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
  115. assert.Equal(t, c.want, buf.String())
  116. }
  117. }
  118. func TestLocaleStoreQuirks(t *testing.T) {
  119. const nl = "\n"
  120. q := func(q1, s string, q2 ...string) string {
  121. return q1 + s + strings.Join(q2, "")
  122. }
  123. testDataList := []struct {
  124. in string
  125. out string
  126. hint string
  127. }{
  128. {` xx`, `xx`, "simple, no quote"},
  129. {`" xx"`, ` xx`, "simple, double-quote"},
  130. {`' xx'`, ` xx`, "simple, single-quote"},
  131. {"` xx`", ` xx`, "simple, back-quote"},
  132. {`x\"y`, `x\"y`, "no unescape, simple"},
  133. {q(`"`, `x\"y`, `"`), `"x\"y"`, "unescape, double-quote"},
  134. {q(`'`, `x\"y`, `'`), `x\"y`, "no unescape, single-quote"},
  135. {q("`", `x\"y`, "`"), `x\"y`, "no unescape, back-quote"},
  136. {q(`"`, `x\"y`) + nl + "b=", `"x\"y`, "half open, double-quote"},
  137. {q(`'`, `x\"y`) + nl + "b=", `'x\"y`, "half open, single-quote"},
  138. {q("`", `x\"y`) + nl + "b=`", `x\"y` + nl + "b=", "half open, back-quote, multi-line"},
  139. {`x ; y`, `x ; y`, "inline comment (;)"},
  140. {`x # y`, `x # y`, "inline comment (#)"},
  141. {`x \; y`, `x ; y`, `inline comment (\;)`},
  142. {`x \# y`, `x # y`, `inline comment (\#)`},
  143. }
  144. for _, testData := range testDataList {
  145. ls := NewLocaleStore()
  146. err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
  147. lang1, _ := ls.Locale("lang1")
  148. assert.NoError(t, err, testData.hint)
  149. assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint)
  150. assert.NoError(t, ls.Close())
  151. }
  152. // TODO: Crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes
  153. // and Crowdin always outputs quoted strings if there are quotes in the strings.
  154. // So, Gitea's `key="quoted" unquoted` content shouldn't be used on Crowdin directly,
  155. // it should be converted to `key="\"quoted\" unquoted"` first.
  156. // TODO: We can not use UnescapeValueDoubleQuotes=true, because there are a lot of back-quotes in en-US.ini,
  157. // then Crowdin will output:
  158. // > key = "`x \" y`"
  159. // Then Gitea will read a string with back-quotes, which is incorrect.
  160. // TODO: Crowdin might generate multi-line strings, quoted by double-quote, it's not supported by LocaleStore
  161. // LocaleStore uses back-quote for multi-line strings, it's not supported by Crowdin.
  162. // TODO: Crowdin doesn't support back-quote as string quoter, it mainly uses double-quote
  163. // so, the following line will be parsed as: value="`first", comment="second`" on Crowdin
  164. // > a = `first; second`
  165. }