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.

translation.go 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package translation
  5. import (
  6. "context"
  7. "sort"
  8. "strings"
  9. "sync"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/options"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/translation/i18n"
  14. "code.gitea.io/gitea/modules/watcher"
  15. "golang.org/x/text/language"
  16. )
  17. // Locale represents an interface to translation
  18. type Locale interface {
  19. Language() string
  20. Tr(string, ...interface{}) string
  21. TrN(cnt interface{}, key1, keyN string, args ...interface{}) string
  22. }
  23. // LangType represents a lang type
  24. type LangType struct {
  25. Lang, Name string // these fields are used directly in templates: {{range .AllLangs}}{{.Lang}}{{.Name}}{{end}}
  26. }
  27. var (
  28. lock *sync.RWMutex
  29. matcher language.Matcher
  30. allLangs []*LangType
  31. allLangMap map[string]*LangType
  32. supportedTags []language.Tag
  33. )
  34. // AllLangs returns all supported languages sorted by name
  35. func AllLangs() []*LangType {
  36. return allLangs
  37. }
  38. // InitLocales loads the locales
  39. func InitLocales(ctx context.Context) {
  40. if lock != nil {
  41. lock.Lock()
  42. defer lock.Unlock()
  43. } else if !setting.IsProd && lock == nil {
  44. lock = &sync.RWMutex{}
  45. }
  46. refreshLocales := func() {
  47. i18n.ResetDefaultLocales()
  48. localeNames, err := options.Dir("locale")
  49. if err != nil {
  50. log.Fatal("Failed to list locale files: %v", err)
  51. }
  52. localeData := make(map[string][]byte, len(localeNames))
  53. for _, name := range localeNames {
  54. localeData[name], err = options.Locale(name)
  55. if err != nil {
  56. log.Fatal("Failed to load %s locale file. %v", name, err)
  57. }
  58. }
  59. supportedTags = make([]language.Tag, len(setting.Langs))
  60. for i, lang := range setting.Langs {
  61. supportedTags[i] = language.Raw.Make(lang)
  62. }
  63. matcher = language.NewMatcher(supportedTags)
  64. for i := range setting.Names {
  65. var localeDataBase []byte
  66. if i == 0 && setting.Langs[0] != "en-US" {
  67. // Only en-US has complete translations. When use other language as default, the en-US should still be used as fallback.
  68. localeDataBase = localeData["locale_en-US.ini"]
  69. if localeDataBase == nil {
  70. log.Fatal("Failed to load locale_en-US.ini file.")
  71. }
  72. }
  73. key := "locale_" + setting.Langs[i] + ".ini"
  74. if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localeDataBase, localeData[key]); err != nil {
  75. log.Error("Failed to set messages to %s: %v", setting.Langs[i], err)
  76. }
  77. }
  78. if len(setting.Langs) != 0 {
  79. defaultLangName := setting.Langs[0]
  80. if defaultLangName != "en-US" {
  81. log.Info("Use the first locale (%s) in LANGS setting option as default", defaultLangName)
  82. }
  83. i18n.DefaultLocales.SetDefaultLang(defaultLangName)
  84. }
  85. }
  86. refreshLocales()
  87. langs, descs := i18n.DefaultLocales.ListLangNameDesc()
  88. allLangs = make([]*LangType, 0, len(langs))
  89. allLangMap = map[string]*LangType{}
  90. for i, v := range langs {
  91. l := &LangType{v, descs[i]}
  92. allLangs = append(allLangs, l)
  93. allLangMap[v] = l
  94. }
  95. // Sort languages case-insensitive according to their name - needed for the user settings
  96. sort.Slice(allLangs, func(i, j int) bool {
  97. return strings.ToLower(allLangs[i].Name) < strings.ToLower(allLangs[j].Name)
  98. })
  99. if !setting.IsProd {
  100. watcher.CreateWatcher(ctx, "Locales", &watcher.CreateWatcherOpts{
  101. PathsCallback: options.WalkLocales,
  102. BetweenCallback: func() {
  103. lock.Lock()
  104. defer lock.Unlock()
  105. refreshLocales()
  106. },
  107. })
  108. }
  109. }
  110. // Match matches accept languages
  111. func Match(tags ...language.Tag) language.Tag {
  112. _, i, _ := matcher.Match(tags...)
  113. return supportedTags[i]
  114. }
  115. // locale represents the information of localization.
  116. type locale struct {
  117. i18n.Locale
  118. Lang, LangName string // these fields are used directly in templates: .i18n.Lang
  119. }
  120. // NewLocale return a locale
  121. func NewLocale(lang string) Locale {
  122. if lock != nil {
  123. lock.RLock()
  124. defer lock.RUnlock()
  125. }
  126. langName := "unknown"
  127. if l, ok := allLangMap[lang]; ok {
  128. langName = l.Name
  129. }
  130. i18nLocale, _ := i18n.GetLocale(lang)
  131. return &locale{
  132. Locale: i18nLocale,
  133. Lang: lang,
  134. LangName: langName,
  135. }
  136. }
  137. func (l *locale) Language() string {
  138. return l.Lang
  139. }
  140. // Language specific rules for translating plural texts
  141. var trNLangRules = map[string]func(int64) int{
  142. // the default rule is "en-US" if a language isn't listed here
  143. "en-US": func(cnt int64) int {
  144. if cnt == 1 {
  145. return 0
  146. }
  147. return 1
  148. },
  149. "lv-LV": func(cnt int64) int {
  150. if cnt%10 == 1 && cnt%100 != 11 {
  151. return 0
  152. }
  153. return 1
  154. },
  155. "ru-RU": func(cnt int64) int {
  156. if cnt%10 == 1 && cnt%100 != 11 {
  157. return 0
  158. }
  159. return 1
  160. },
  161. "zh-CN": func(cnt int64) int {
  162. return 0
  163. },
  164. "zh-HK": func(cnt int64) int {
  165. return 0
  166. },
  167. "zh-TW": func(cnt int64) int {
  168. return 0
  169. },
  170. "fr-FR": func(cnt int64) int {
  171. if cnt > -2 && cnt < 2 {
  172. return 0
  173. }
  174. return 1
  175. },
  176. }
  177. // TrN returns translated message for plural text translation
  178. func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string {
  179. var c int64
  180. if t, ok := cnt.(int); ok {
  181. c = int64(t)
  182. } else if t, ok := cnt.(int16); ok {
  183. c = int64(t)
  184. } else if t, ok := cnt.(int32); ok {
  185. c = int64(t)
  186. } else if t, ok := cnt.(int64); ok {
  187. c = t
  188. } else {
  189. return l.Tr(keyN, args...)
  190. }
  191. ruleFunc, ok := trNLangRules[l.Lang]
  192. if !ok {
  193. ruleFunc = trNLangRules["en-US"]
  194. }
  195. if ruleFunc(c) == 0 {
  196. return l.Tr(key1, args...)
  197. }
  198. return l.Tr(keyN, args...)
  199. }