i18n.go 8.6 KB

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