i18n.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package i18n
  2. import (
  3. "notabug.org/apiote/amuse/config"
  4. "bytes"
  5. "fmt"
  6. "golang.org/x/text/language"
  7. "html/template"
  8. "os"
  9. "path/filepath"
  10. "reflect"
  11. "regexp"
  12. "strings"
  13. "time"
  14. "github.com/BurntSushi/toml"
  15. "github.com/bytesparadise/libasciidoc"
  16. "github.com/bytesparadise/libasciidoc/pkg/configuration"
  17. "notabug.org/apiote/gott"
  18. )
  19. type Translation struct {
  20. Global map[string]string
  21. Index map[string]string
  22. Search map[string]string
  23. Film map[string]string
  24. Serie map[string]string
  25. Person map[string]string
  26. Book map[string]string
  27. BookSerie map[string]string
  28. About map[string]string
  29. Signup map[string]string
  30. Signedup map[string]string
  31. Login map[string]string
  32. Loggedout map[string]string
  33. Watchlist map[string]string
  34. Readlist map[string]string
  35. Tvqueue map[string]string
  36. Experiences map[string]string
  37. Error map[string]string
  38. }
  39. var serverLangs []language.Tag
  40. func LoadServerLangs() error {
  41. var tags []string
  42. var defaultLocale string
  43. defaultLocaleIndex := -1
  44. err := filepath.Walk(config.DataHome+"/i18n", func(path string, info os.FileInfo, err error) error {
  45. if !info.IsDir() {
  46. if filepath.Ext(path) == ".toml" {
  47. tag := strings.Replace(filepath.Base(path), ".toml", "", 1)
  48. if tag == "default" {
  49. p, err := os.Readlink(path)
  50. if err != nil {
  51. return err
  52. }
  53. defaultLocale = strings.Replace(filepath.Base(p), ".toml", "", 1)
  54. } else {
  55. tags = append(tags, tag)
  56. }
  57. }
  58. }
  59. return nil
  60. })
  61. if err != nil {
  62. return err
  63. }
  64. for i, tag := range tags {
  65. if tag == defaultLocale {
  66. defaultLocaleIndex = i
  67. }
  68. }
  69. tags[0], tags[defaultLocaleIndex] = tags[defaultLocaleIndex], tags[0]
  70. for _, tag := range tags {
  71. serverLangs = append(serverLangs, language.Make(tag))
  72. }
  73. return nil
  74. }
  75. func Match(acceptLanguages []language.Tag) (language.Tag, error) {
  76. var matcher = language.NewMatcher(serverLangs)
  77. tag, _, _ := matcher.Match(acceptLanguages...)
  78. b, s, r := tag.Raw()
  79. t, err := language.Compose(b, s, r)
  80. return t, err
  81. }
  82. func loadStringsFile(args ...interface{}) (interface{}, error) {
  83. var (
  84. strings Translation
  85. err error
  86. )
  87. if langTag, ok := args[0].(language.Tag); ok {
  88. language := langTag.String()
  89. _, err = toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", language+".toml"), &strings)
  90. }
  91. args[1] = strings
  92. return gott.Tuple(args), err
  93. }
  94. func loadDefaultStringsFile(args ...interface{}) (interface{}, error) {
  95. var strings Translation
  96. _, err := toml.DecodeFile(filepath.Join(config.DataHome+"/i18n", "default.toml"), &strings)
  97. args[2] = strings
  98. return gott.Tuple(args), err
  99. }
  100. func loadStrings(args ...interface{}) interface{} {
  101. strings := args[1].(Translation)
  102. defaultTranslation := args[2].(Translation)
  103. stringsValue := reflect.ValueOf(&strings).Elem()
  104. stringsType := stringsValue.Type()
  105. for i := 0; i < stringsValue.NumField(); i++ {
  106. stringsField := stringsValue.Field(i)
  107. if stringsField.IsNil() {
  108. stringsField.Set(reflect.MakeMap(reflect.TypeOf(map[string]string{})))
  109. }
  110. defaultValue := reflect.ValueOf(&defaultTranslation).Elem()
  111. for key, entry := range defaultValue.FieldByName(stringsType.Field(i).Name).Interface().(map[string]string) {
  112. if stringsField.Interface().(map[string]string)[key] == "" {
  113. stringsField.Interface().(map[string]string)[key] = entry
  114. }
  115. }
  116. }
  117. args[1] = strings
  118. return gott.Tuple(args)
  119. }
  120. func LoadStrings(language language.Tag) (Translation, error) {
  121. r, err := gott.
  122. NewResult(gott.Tuple{language, Translation{}, Translation{}}).
  123. Bind(loadStringsFile).
  124. Bind(loadDefaultStringsFile).
  125. Map(loadStrings).
  126. Finish()
  127. if err == nil {
  128. return r.(gott.Tuple)[1].(Translation), nil
  129. } else {
  130. return Translation{}, err
  131. }
  132. }
  133. func FormatDate(date time.Time, format string, translation map[string]string) string {
  134. format = strings.ReplaceAll(format, "%a", translation[date.Weekday().String()+"_short"])
  135. format = strings.ReplaceAll(format, "%A", translation[date.Weekday().String()])
  136. format = strings.ReplaceAll(format, "%b", translation[date.Month().String()+"_short"])
  137. format = strings.ReplaceAll(format, "%B", translation[date.Month().String()])
  138. // %c intentionally ommitted
  139. format = strings.ReplaceAll(format, "%C", fmt.Sprintf("%d", date.Year()/100))
  140. format = strings.ReplaceAll(format, "%d", fmt.Sprintf("%02d", date.Day()))
  141. format = strings.ReplaceAll(format, "%D", fmt.Sprintf("%02d/%02d/%02d", date.Month(), date.Day(), date.Year()%100))
  142. format = strings.ReplaceAll(format, "%e", fmt.Sprintf("%2d", date.Day()))
  143. format = strings.ReplaceAll(format, "%h", translation[date.Month().String()+"_short"])
  144. format = strings.ReplaceAll(format, "%H", fmt.Sprintf("%02d", date.Hour()))
  145. hour := date.Hour() % 12
  146. if hour == 0 {
  147. hour = 12
  148. }
  149. format = strings.ReplaceAll(format, "%I", fmt.Sprintf("%02d", hour))
  150. format = strings.ReplaceAll(format, "%j", fmt.Sprintf("%03d", date.YearDay()))
  151. format = strings.ReplaceAll(format, "%m", fmt.Sprintf("%02d", date.Month()))
  152. format = strings.ReplaceAll(format, "%M", fmt.Sprintf("%02d", date.Minute()))
  153. format = strings.ReplaceAll(format, "%n", "\n")
  154. // todo %p
  155. // %r intentionally ommitted
  156. format = strings.ReplaceAll(format, "%S", fmt.Sprintf("%02d", date.Second()))
  157. format = strings.ReplaceAll(format, "%t", "\t")
  158. format = strings.ReplaceAll(format, "%T", fmt.Sprintf("%02d:%02d:%02d", date.Hour(), date.Minute(), date.Second()%100))
  159. weekday := date.Weekday()
  160. if weekday == 0 {
  161. weekday = 7
  162. }
  163. format = strings.ReplaceAll(format, "%u", fmt.Sprintf("%d", weekday))
  164. // todo %U
  165. // todo %V
  166. format = strings.ReplaceAll(format, "%w", fmt.Sprintf("%d", date.Weekday()))
  167. // todo %W
  168. // %x intentionally ommitted
  169. // %X intentionally ommitted
  170. format = strings.ReplaceAll(format, "%y", fmt.Sprintf("%d", date.Year()%100))
  171. format = strings.ReplaceAll(format, "%Y", fmt.Sprintf("%d", date.Year()))
  172. zone, _ := date.Zone()
  173. format = strings.ReplaceAll(format, "%Z", zone)
  174. format = strings.ReplaceAll(format, "%%", "%")
  175. return format
  176. }
  177. func FormatDateNice(datetime time.Time, strings Translation, timezone string) string {
  178. t := time.Now()
  179. location, err := time.LoadLocation(timezone)
  180. if err != nil {
  181. return strings.Global["unknown"]
  182. }
  183. midnightToday := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, location)
  184. midnightYester := midnightToday.Add(-24 * time.Hour)
  185. midnightEreyester := midnightYester.Add(-24 * time.Hour)
  186. midnightWeek := midnightToday.Add(-24 * 7 * time.Hour)
  187. midnightYear := time.Date(t.Year(), 1, 1, 0, 0, 0, 0, location)
  188. var dateFormat string
  189. if datetime.After(midnightToday) {
  190. dateFormat = strings.Global["experience_format_today"]
  191. } else if datetime.After(midnightYester) {
  192. dateFormat = strings.Global["experience_format_yesterday"]
  193. } else if datetime.After(midnightEreyester) {
  194. dateFormat = strings.Global["experience_format_ereyester"]
  195. } else if datetime.After(midnightWeek) {
  196. dateFormat = strings.Global["experience_format_week"]
  197. } else if datetime.After(midnightYear) {
  198. dateFormat = strings.Global["experience_format_year"]
  199. } else {
  200. dateFormat = strings.Global["experience_format_earlier"]
  201. }
  202. date := FormatDate(datetime, dateFormat, strings.Global)
  203. return date
  204. }
  205. func RenderAsciiDoc(asciidoc string) template.HTML {
  206. r := strings.NewReader(asciidoc)
  207. w := bytes.NewBuffer([]byte{})
  208. config := configuration.NewConfiguration()
  209. libasciidoc.ConvertToHTML(r, w, config)
  210. output := bytes.ReplaceAll(w.Bytes(), []byte("\n"), []byte(""))
  211. divRegex, err := regexp.Compile("<\\/?div[^>]*>")
  212. if err != nil {
  213. return template.HTML("<span style=\"color: red;\">error rendering asciidoc (div regex)</span>")
  214. }
  215. pRegex, err := regexp.Compile("<\\/?p>")
  216. if err != nil {
  217. return template.HTML("<span style=\"color: red;\">error rendering asciidoc (p regex)</span>")
  218. }
  219. output = divRegex.ReplaceAll(output, []byte(""))
  220. output = pRegex.ReplaceAll(output, []byte(""))
  221. return template.HTML(output)
  222. }
  223. func GetErrorData(code int, translation Translation, part string) string {
  224. key := fmt.Sprintf("%d_%s", code, part)
  225. return translation.Error[key]
  226. }