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.

422 lines
9.5KB

  1. /*
  2. * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
  3. * Licensed under the MIT License. See LICENSE file in the project root for full license information.
  4. */
  5. package gotext
  6. import (
  7. "bufio"
  8. "bytes"
  9. "encoding/binary"
  10. "io/ioutil"
  11. "net/textproto"
  12. "os"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "github.com/leonelquinteros/gotext/plurals"
  17. )
  18. const (
  19. MoMagicLittleEndian = 0x950412de
  20. MoMagicBigEndian = 0xde120495
  21. EotSeparator = "\x04" // msgctxt and msgid separator
  22. NulSeparator = "\x00" // msgid and msgstr separator
  23. )
  24. /*
  25. Mo parses the content of any MO file and provides all the Translation functions needed.
  26. It's the base object used by all package methods.
  27. And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
  28. Example:
  29. import (
  30. "fmt"
  31. "github.com/leonelquinteros/gotext"
  32. )
  33. func main() {
  34. // Create po object
  35. po := gotext.NewMoTranslator()
  36. // Parse .po file
  37. po.ParseFile("/path/to/po/file/translations.mo")
  38. // Get Translation
  39. fmt.Println(po.Get("Translate this"))
  40. }
  41. */
  42. type Mo struct {
  43. // Headers storage
  44. Headers textproto.MIMEHeader
  45. // Language header
  46. Language string
  47. // Plural-Forms header
  48. PluralForms string
  49. // Parsed Plural-Forms header values
  50. nplurals int
  51. plural string
  52. pluralforms plurals.Expression
  53. // Storage
  54. translations map[string]*Translation
  55. contexts map[string]map[string]*Translation
  56. // Sync Mutex
  57. sync.RWMutex
  58. // Parsing buffers
  59. trBuffer *Translation
  60. ctxBuffer string
  61. }
  62. func NewMoTranslator() Translator {
  63. return new(Mo)
  64. }
  65. // ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
  66. func (mo *Mo) ParseFile(f string) {
  67. // Check if file exists
  68. info, err := os.Stat(f)
  69. if err != nil {
  70. return
  71. }
  72. // Check that isn't a directory
  73. if info.IsDir() {
  74. return
  75. }
  76. // Parse file content
  77. data, err := ioutil.ReadFile(f)
  78. if err != nil {
  79. return
  80. }
  81. mo.Parse(data)
  82. }
  83. // Parse loads the translations specified in the provided string (str)
  84. func (mo *Mo) Parse(buf []byte) {
  85. // Lock while parsing
  86. mo.Lock()
  87. // Init storage
  88. if mo.translations == nil {
  89. mo.translations = make(map[string]*Translation)
  90. mo.contexts = make(map[string]map[string]*Translation)
  91. }
  92. r := bytes.NewReader(buf)
  93. var magicNumber uint32
  94. if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
  95. return
  96. // return fmt.Errorf("gettext: %v", err)
  97. }
  98. var bo binary.ByteOrder
  99. switch magicNumber {
  100. case MoMagicLittleEndian:
  101. bo = binary.LittleEndian
  102. case MoMagicBigEndian:
  103. bo = binary.BigEndian
  104. default:
  105. return
  106. // return fmt.Errorf("gettext: %v", "invalid magic number")
  107. }
  108. var header struct {
  109. MajorVersion uint16
  110. MinorVersion uint16
  111. MsgIdCount uint32
  112. MsgIdOffset uint32
  113. MsgStrOffset uint32
  114. HashSize uint32
  115. HashOffset uint32
  116. }
  117. if err := binary.Read(r, bo, &header); err != nil {
  118. return
  119. // return fmt.Errorf("gettext: %v", err)
  120. }
  121. if v := header.MajorVersion; v != 0 && v != 1 {
  122. return
  123. // return fmt.Errorf("gettext: %v", "invalid version number")
  124. }
  125. if v := header.MinorVersion; v != 0 && v != 1 {
  126. return
  127. // return fmt.Errorf("gettext: %v", "invalid version number")
  128. }
  129. msgIdStart := make([]uint32, header.MsgIdCount)
  130. msgIdLen := make([]uint32, header.MsgIdCount)
  131. if _, err := r.Seek(int64(header.MsgIdOffset), 0); err != nil {
  132. return
  133. // return fmt.Errorf("gettext: %v", err)
  134. }
  135. for i := 0; i < int(header.MsgIdCount); i++ {
  136. if err := binary.Read(r, bo, &msgIdLen[i]); err != nil {
  137. return
  138. // return fmt.Errorf("gettext: %v", err)
  139. }
  140. if err := binary.Read(r, bo, &msgIdStart[i]); err != nil {
  141. return
  142. // return fmt.Errorf("gettext: %v", err)
  143. }
  144. }
  145. msgStrStart := make([]int32, header.MsgIdCount)
  146. msgStrLen := make([]int32, header.MsgIdCount)
  147. if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
  148. return
  149. // return fmt.Errorf("gettext: %v", err)
  150. }
  151. for i := 0; i < int(header.MsgIdCount); i++ {
  152. if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
  153. return
  154. // return fmt.Errorf("gettext: %v", err)
  155. }
  156. if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
  157. return
  158. // return fmt.Errorf("gettext: %v", err)
  159. }
  160. }
  161. for i := 0; i < int(header.MsgIdCount); i++ {
  162. if _, err := r.Seek(int64(msgIdStart[i]), 0); err != nil {
  163. return
  164. // return fmt.Errorf("gettext: %v", err)
  165. }
  166. msgIdData := make([]byte, msgIdLen[i])
  167. if _, err := r.Read(msgIdData); err != nil {
  168. return
  169. // return fmt.Errorf("gettext: %v", err)
  170. }
  171. if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
  172. return
  173. // return fmt.Errorf("gettext: %v", err)
  174. }
  175. msgStrData := make([]byte, msgStrLen[i])
  176. if _, err := r.Read(msgStrData); err != nil {
  177. return
  178. // return fmt.Errorf("gettext: %v", err)
  179. }
  180. if len(msgIdData) == 0 {
  181. mo.addTranslation(msgIdData, msgStrData)
  182. } else {
  183. mo.addTranslation(msgIdData, msgStrData)
  184. }
  185. }
  186. // Unlock to parse headers
  187. mo.Unlock()
  188. // Parse headers
  189. mo.parseHeaders()
  190. return
  191. // return nil
  192. }
  193. func (mo *Mo) addTranslation(msgid, msgstr []byte) {
  194. translation := NewTranslation()
  195. var msgctxt []byte
  196. var msgidPlural []byte
  197. d := bytes.Split(msgid, []byte(EotSeparator))
  198. if len(d) == 1 {
  199. msgid = d[0]
  200. } else {
  201. msgid, msgctxt = d[1], d[0]
  202. }
  203. dd := bytes.Split(msgid, []byte(NulSeparator))
  204. if len(dd) > 1 {
  205. msgid = dd[0]
  206. dd = dd[1:]
  207. }
  208. translation.ID = string(msgid)
  209. msgidPlural = bytes.Join(dd, []byte(NulSeparator))
  210. if len(msgidPlural) > 0 {
  211. translation.PluralID = string(msgidPlural)
  212. }
  213. ddd := bytes.Split(msgstr, []byte(NulSeparator))
  214. if len(ddd) > 0 {
  215. for i, s := range ddd {
  216. translation.Trs[i] = string(s)
  217. }
  218. }
  219. if len(msgctxt) > 0 {
  220. // With context...
  221. if _, ok := mo.contexts[string(msgctxt)]; !ok {
  222. mo.contexts[string(msgctxt)] = make(map[string]*Translation)
  223. }
  224. mo.contexts[string(msgctxt)][translation.ID] = translation
  225. } else {
  226. mo.translations[translation.ID] = translation
  227. }
  228. }
  229. // parseHeaders retrieves data from previously parsed headers
  230. func (mo *Mo) parseHeaders() {
  231. // Make sure we end with 2 carriage returns.
  232. raw := mo.Get("") + "\n\n"
  233. // Read
  234. reader := bufio.NewReader(strings.NewReader(raw))
  235. tp := textproto.NewReader(reader)
  236. var err error
  237. // Sync Headers write.
  238. mo.Lock()
  239. defer mo.Unlock()
  240. mo.Headers, err = tp.ReadMIMEHeader()
  241. if err != nil {
  242. return
  243. }
  244. // Get/save needed headers
  245. mo.Language = mo.Headers.Get("Language")
  246. mo.PluralForms = mo.Headers.Get("Plural-Forms")
  247. // Parse Plural-Forms formula
  248. if mo.PluralForms == "" {
  249. return
  250. }
  251. // Split plural form header value
  252. pfs := strings.Split(mo.PluralForms, ";")
  253. // Parse values
  254. for _, i := range pfs {
  255. vs := strings.SplitN(i, "=", 2)
  256. if len(vs) != 2 {
  257. continue
  258. }
  259. switch strings.TrimSpace(vs[0]) {
  260. case "nplurals":
  261. mo.nplurals, _ = strconv.Atoi(vs[1])
  262. case "plural":
  263. mo.plural = vs[1]
  264. if expr, err := plurals.Compile(mo.plural); err == nil {
  265. mo.pluralforms = expr
  266. }
  267. }
  268. }
  269. }
  270. // pluralForm calculates the plural form index corresponding to n.
  271. // Returns 0 on error
  272. func (mo *Mo) pluralForm(n int) int {
  273. mo.RLock()
  274. defer mo.RUnlock()
  275. // Failure fallback
  276. if mo.pluralforms == nil {
  277. /* Use the Germanic plural rule. */
  278. if n == 1 {
  279. return 0
  280. } else {
  281. return 1
  282. }
  283. }
  284. return mo.pluralforms.Eval(uint32(n))
  285. }
  286. // Get retrieves the corresponding Translation for the given string.
  287. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  288. func (mo *Mo) Get(str string, vars ...interface{}) string {
  289. // Sync read
  290. mo.RLock()
  291. defer mo.RUnlock()
  292. if mo.translations != nil {
  293. if _, ok := mo.translations[str]; ok {
  294. return Printf(mo.translations[str].Get(), vars...)
  295. }
  296. }
  297. // Return the same we received by default
  298. return Printf(str, vars...)
  299. }
  300. // GetN retrieves the (N)th plural form of Translation for the given string.
  301. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  302. func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
  303. // Sync read
  304. mo.RLock()
  305. defer mo.RUnlock()
  306. if mo.translations != nil {
  307. if _, ok := mo.translations[str]; ok {
  308. return Printf(mo.translations[str].GetN(mo.pluralForm(n)), vars...)
  309. }
  310. }
  311. if n == 1 {
  312. return Printf(str, vars...)
  313. }
  314. return Printf(plural, vars...)
  315. }
  316. // GetC retrieves the corresponding Translation for a given string in the given context.
  317. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  318. func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
  319. // Sync read
  320. mo.RLock()
  321. defer mo.RUnlock()
  322. if mo.contexts != nil {
  323. if _, ok := mo.contexts[ctx]; ok {
  324. if mo.contexts[ctx] != nil {
  325. if _, ok := mo.contexts[ctx][str]; ok {
  326. return Printf(mo.contexts[ctx][str].Get(), vars...)
  327. }
  328. }
  329. }
  330. }
  331. // Return the string we received by default
  332. return Printf(str, vars...)
  333. }
  334. // GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
  335. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  336. func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
  337. // Sync read
  338. mo.RLock()
  339. defer mo.RUnlock()
  340. if mo.contexts != nil {
  341. if _, ok := mo.contexts[ctx]; ok {
  342. if mo.contexts[ctx] != nil {
  343. if _, ok := mo.contexts[ctx][str]; ok {
  344. return Printf(mo.contexts[ctx][str].GetN(mo.pluralForm(n)), vars...)
  345. }
  346. }
  347. }
  348. }
  349. if n == 1 {
  350. return Printf(str, vars...)
  351. }
  352. return Printf(plural, vars...)
  353. }