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.

557 lines
13KB

  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. "encoding/gob"
  11. "io/ioutil"
  12. "net/textproto"
  13. "os"
  14. "strconv"
  15. "strings"
  16. "sync"
  17. "git.deineagentur.com/DeineAgenturUG/gotext/plurals"
  18. )
  19. const (
  20. // MoMagicLittleEndian encoding
  21. MoMagicLittleEndian = 0x950412de
  22. // MoMagicBigEndian encoding
  23. MoMagicBigEndian = 0xde120495
  24. // EotSeparator msgctxt and msgid separator
  25. EotSeparator = "\x04"
  26. // NulSeparator msgid and msgstr separator
  27. NulSeparator = "\x00"
  28. )
  29. /*
  30. Mo parses the content of any MO file and provides all the Translation functions needed.
  31. It's the base object used by all package methods.
  32. And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
  33. Example:
  34. import (
  35. "fmt"
  36. "git.deineagentur.com/DeineAgenturUG/gotext"
  37. )
  38. func main() {
  39. // Create po object
  40. po := gotext.NewMoTranslator()
  41. // Parse .po file
  42. po.ParseFile("/path/to/po/file/translations.mo")
  43. // Get Translation
  44. fmt.Println(po.Get("Translate this"))
  45. }
  46. */
  47. type Mo struct {
  48. // Headers storage
  49. Headers textproto.MIMEHeader
  50. // Language header
  51. Language string
  52. // Plural-Forms header
  53. PluralForms string
  54. // Parsed Plural-Forms header values
  55. nplurals int
  56. plural string
  57. pluralforms plurals.Expression
  58. // Storage
  59. translations map[string]*Translation
  60. contexts map[string]map[string]*Translation
  61. // Sync Mutex
  62. sync.RWMutex
  63. // Parsing buffers
  64. trBuffer *Translation
  65. ctxBuffer string
  66. }
  67. // NewMoTranslator creates a new Mo object with the Translator interface
  68. func NewMoTranslator() Translator {
  69. return new(Mo)
  70. }
  71. // ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
  72. func (mo *Mo) ParseFile(f string) {
  73. // Check if file exists
  74. info, err := os.Stat(f)
  75. if err != nil {
  76. return
  77. }
  78. // Check that isn't a directory
  79. if info.IsDir() {
  80. return
  81. }
  82. // Parse file content
  83. data, err := ioutil.ReadFile(f)
  84. if err != nil {
  85. return
  86. }
  87. mo.Parse(data)
  88. }
  89. // Parse loads the translations specified in the provided string (str)
  90. func (mo *Mo) Parse(buf []byte) {
  91. // Lock while parsing
  92. mo.Lock()
  93. // Init storage
  94. if mo.translations == nil {
  95. mo.translations = make(map[string]*Translation)
  96. mo.contexts = make(map[string]map[string]*Translation)
  97. }
  98. r := bytes.NewReader(buf)
  99. var magicNumber uint32
  100. if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
  101. return
  102. // return fmt.Errorf("gettext: %v", err)
  103. }
  104. var bo binary.ByteOrder
  105. switch magicNumber {
  106. case MoMagicLittleEndian:
  107. bo = binary.LittleEndian
  108. case MoMagicBigEndian:
  109. bo = binary.BigEndian
  110. default:
  111. return
  112. // return fmt.Errorf("gettext: %v", "invalid magic number")
  113. }
  114. var header struct {
  115. MajorVersion uint16
  116. MinorVersion uint16
  117. MsgIDCount uint32
  118. MsgIDOffset uint32
  119. MsgStrOffset uint32
  120. HashSize uint32
  121. HashOffset uint32
  122. }
  123. if err := binary.Read(r, bo, &header); err != nil {
  124. return
  125. // return fmt.Errorf("gettext: %v", err)
  126. }
  127. if v := header.MajorVersion; v != 0 && v != 1 {
  128. return
  129. // return fmt.Errorf("gettext: %v", "invalid version number")
  130. }
  131. if v := header.MinorVersion; v != 0 && v != 1 {
  132. return
  133. // return fmt.Errorf("gettext: %v", "invalid version number")
  134. }
  135. msgIDStart := make([]uint32, header.MsgIDCount)
  136. msgIDLen := make([]uint32, header.MsgIDCount)
  137. if _, err := r.Seek(int64(header.MsgIDOffset), 0); err != nil {
  138. return
  139. // return fmt.Errorf("gettext: %v", err)
  140. }
  141. for i := 0; i < int(header.MsgIDCount); i++ {
  142. if err := binary.Read(r, bo, &msgIDLen[i]); err != nil {
  143. return
  144. // return fmt.Errorf("gettext: %v", err)
  145. }
  146. if err := binary.Read(r, bo, &msgIDStart[i]); err != nil {
  147. return
  148. // return fmt.Errorf("gettext: %v", err)
  149. }
  150. }
  151. msgStrStart := make([]int32, header.MsgIDCount)
  152. msgStrLen := make([]int32, header.MsgIDCount)
  153. if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
  154. return
  155. // return fmt.Errorf("gettext: %v", err)
  156. }
  157. for i := 0; i < int(header.MsgIDCount); i++ {
  158. if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
  159. return
  160. // return fmt.Errorf("gettext: %v", err)
  161. }
  162. if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
  163. return
  164. // return fmt.Errorf("gettext: %v", err)
  165. }
  166. }
  167. for i := 0; i < int(header.MsgIDCount); i++ {
  168. if _, err := r.Seek(int64(msgIDStart[i]), 0); err != nil {
  169. return
  170. // return fmt.Errorf("gettext: %v", err)
  171. }
  172. msgIDData := make([]byte, msgIDLen[i])
  173. if _, err := r.Read(msgIDData); err != nil {
  174. return
  175. // return fmt.Errorf("gettext: %v", err)
  176. }
  177. if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
  178. return
  179. // return fmt.Errorf("gettext: %v", err)
  180. }
  181. msgStrData := make([]byte, msgStrLen[i])
  182. if _, err := r.Read(msgStrData); err != nil {
  183. return
  184. // return fmt.Errorf("gettext: %v", err)
  185. }
  186. if len(msgIDData) == 0 {
  187. mo.addTranslation(msgIDData, msgStrData)
  188. } else {
  189. mo.addTranslation(msgIDData, msgStrData)
  190. }
  191. }
  192. // Unlock to parse headers
  193. mo.Unlock()
  194. // Parse headers
  195. mo.parseHeaders()
  196. return
  197. // return nil
  198. }
  199. func (mo *Mo) addTranslation(msgid, msgstr []byte) {
  200. translation := NewTranslation()
  201. var msgctxt []byte
  202. var msgidPlural []byte
  203. d := bytes.Split(msgid, []byte(EotSeparator))
  204. if len(d) == 1 {
  205. msgid = d[0]
  206. } else {
  207. msgid, msgctxt = d[1], d[0]
  208. }
  209. dd := bytes.Split(msgid, []byte(NulSeparator))
  210. if len(dd) > 1 {
  211. msgid = dd[0]
  212. dd = dd[1:]
  213. }
  214. translation.ID = string(msgid)
  215. msgidPlural = bytes.Join(dd, []byte(NulSeparator))
  216. if len(msgidPlural) > 0 {
  217. translation.PluralID = string(msgidPlural)
  218. }
  219. ddd := bytes.Split(msgstr, []byte(NulSeparator))
  220. if len(ddd) > 0 {
  221. for i, s := range ddd {
  222. translation.Trs[i] = string(s)
  223. }
  224. }
  225. if len(msgctxt) > 0 {
  226. // With context...
  227. if _, ok := mo.contexts[string(msgctxt)]; !ok {
  228. mo.contexts[string(msgctxt)] = make(map[string]*Translation)
  229. }
  230. mo.contexts[string(msgctxt)][translation.ID] = translation
  231. } else {
  232. mo.translations[translation.ID] = translation
  233. }
  234. }
  235. // parseHeaders retrieves data from previously parsed headers
  236. func (mo *Mo) parseHeaders() {
  237. // Make sure we end with 2 carriage returns.
  238. raw := mo.Get("") + "\n\n"
  239. // Read
  240. reader := bufio.NewReader(strings.NewReader(raw))
  241. tp := textproto.NewReader(reader)
  242. var err error
  243. // Sync Headers write.
  244. mo.Lock()
  245. defer mo.Unlock()
  246. mo.Headers, err = tp.ReadMIMEHeader()
  247. if err != nil {
  248. return
  249. }
  250. // Get/save needed headers
  251. mo.Language = mo.Headers.Get("Language")
  252. mo.PluralForms = mo.Headers.Get("Plural-Forms")
  253. // Parse Plural-Forms formula
  254. if mo.PluralForms == "" {
  255. return
  256. }
  257. // Split plural form header value
  258. pfs := strings.Split(mo.PluralForms, ";")
  259. // Parse values
  260. for _, i := range pfs {
  261. vs := strings.SplitN(i, "=", 2)
  262. if len(vs) != 2 {
  263. continue
  264. }
  265. switch strings.TrimSpace(vs[0]) {
  266. case "nplurals":
  267. mo.nplurals, _ = strconv.Atoi(vs[1])
  268. case "plural":
  269. mo.plural = vs[1]
  270. if expr, err := plurals.Compile(mo.plural); err == nil {
  271. mo.pluralforms = expr
  272. }
  273. }
  274. }
  275. }
  276. // pluralForm calculates the plural form index corresponding to n.
  277. // Returns 0 on error
  278. func (mo *Mo) pluralForm(n int) int {
  279. mo.RLock()
  280. defer mo.RUnlock()
  281. // Failure fallback
  282. if mo.pluralforms == nil {
  283. /* Use the Germanic plural rule. */
  284. if n == 1 {
  285. return 0
  286. }
  287. return 1
  288. }
  289. return mo.pluralforms.Eval(uint32(n))
  290. }
  291. // Get retrieves the corresponding Translation for the given string.
  292. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  293. func (mo *Mo) Get(str string, vars ...interface{}) string {
  294. // Sync read
  295. mo.RLock()
  296. defer mo.RUnlock()
  297. if mo.translations != nil {
  298. if _, ok := mo.translations[str]; ok {
  299. return Printf(mo.translations[str].Get(), vars...)
  300. }
  301. }
  302. // Return the same we received by default
  303. return Printf(str, vars...)
  304. }
  305. // GetN retrieves the (N)th plural form of Translation for the given string.
  306. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  307. func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
  308. // Sync read
  309. mo.RLock()
  310. defer mo.RUnlock()
  311. if mo.translations != nil {
  312. if _, ok := mo.translations[str]; ok {
  313. return Printf(mo.translations[str].GetN(mo.pluralForm(n)), vars...)
  314. }
  315. }
  316. if n == 1 {
  317. return Printf(str, vars...)
  318. }
  319. return Printf(plural, vars...)
  320. }
  321. // GetC retrieves the corresponding Translation for a given string in the given context.
  322. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  323. func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
  324. // Sync read
  325. mo.RLock()
  326. defer mo.RUnlock()
  327. if mo.contexts != nil {
  328. if _, ok := mo.contexts[ctx]; ok {
  329. if mo.contexts[ctx] != nil {
  330. if _, ok := mo.contexts[ctx][str]; ok {
  331. return Printf(mo.contexts[ctx][str].Get(), vars...)
  332. }
  333. }
  334. }
  335. }
  336. // Return the string we received by default
  337. return Printf(str, vars...)
  338. }
  339. // GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
  340. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  341. func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
  342. // Sync read
  343. mo.RLock()
  344. defer mo.RUnlock()
  345. if mo.contexts != nil {
  346. if _, ok := mo.contexts[ctx]; ok {
  347. if mo.contexts[ctx] != nil {
  348. if _, ok := mo.contexts[ctx][str]; ok {
  349. return Printf(mo.contexts[ctx][str].GetN(mo.pluralForm(n)), vars...)
  350. }
  351. }
  352. }
  353. }
  354. if n == 1 {
  355. return Printf(str, vars...)
  356. }
  357. return Printf(plural, vars...)
  358. }
  359. // GetE retrieves the corresponding Translation for the given string.
  360. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  361. // The second return value is true iff the string was found.
  362. func (mo *Mo) GetE(str string, vars ...interface{}) (string, bool) {
  363. // Sync read
  364. mo.RLock()
  365. defer mo.RUnlock()
  366. if mo.translations != nil {
  367. if _, ok := mo.translations[str]; ok {
  368. if fmt, ok := mo.translations[str].GetE(); ok {
  369. return Printf(fmt, vars...), true
  370. }
  371. }
  372. }
  373. return "", false
  374. }
  375. // GetNE retrieves the (N)th plural form of Translation for the given string.
  376. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  377. // The second return value is true iff the string was found.
  378. func (mo *Mo) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
  379. // Sync read
  380. mo.RLock()
  381. defer mo.RUnlock()
  382. if mo.translations != nil {
  383. if _, ok := mo.translations[str]; ok {
  384. if fmt, ok := mo.translations[str].GetNE(mo.pluralForm(n)); ok {
  385. return Printf(fmt, vars...), true
  386. }
  387. }
  388. }
  389. return "", false
  390. }
  391. // GetCE retrieves the corresponding Translation for a given string in the given context.
  392. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  393. // The second return value is true iff the string was found.
  394. func (mo *Mo) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
  395. // Sync read
  396. mo.RLock()
  397. defer mo.RUnlock()
  398. if mo.contexts != nil {
  399. if _, ok := mo.contexts[ctx]; ok {
  400. if mo.contexts[ctx] != nil {
  401. if _, ok := mo.contexts[ctx][str]; ok {
  402. if fmt, ok := mo.contexts[ctx][str].GetE(); ok {
  403. return Printf(fmt, vars...), true
  404. }
  405. }
  406. }
  407. }
  408. }
  409. return "", false
  410. }
  411. // GetNCE retrieves the (N)th plural form of Translation for the given string in the given context.
  412. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
  413. // The second return value is true iff the string was found.
  414. func (mo *Mo) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
  415. // Sync read
  416. mo.RLock()
  417. defer mo.RUnlock()
  418. if mo.contexts != nil {
  419. if _, ok := mo.contexts[ctx]; ok {
  420. if mo.contexts[ctx] != nil {
  421. if _, ok := mo.contexts[ctx][str]; ok {
  422. if fmt, ok := mo.contexts[ctx][str].GetNE(mo.pluralForm(n)); ok {
  423. return Printf(fmt, vars...), true
  424. }
  425. }
  426. }
  427. }
  428. }
  429. return "", false
  430. }
  431. // MarshalBinary implements encoding.BinaryMarshaler interface
  432. func (mo *Mo) MarshalBinary() ([]byte, error) {
  433. obj := new(TranslatorEncoding)
  434. obj.Headers = mo.Headers
  435. obj.Language = mo.Language
  436. obj.PluralForms = mo.PluralForms
  437. obj.Nplurals = mo.nplurals
  438. obj.Plural = mo.plural
  439. obj.Translations = mo.translations
  440. obj.Contexts = mo.contexts
  441. var buff bytes.Buffer
  442. encoder := gob.NewEncoder(&buff)
  443. err := encoder.Encode(obj)
  444. return buff.Bytes(), err
  445. }
  446. // UnmarshalBinary implements encoding.BinaryUnmarshaler interface
  447. func (mo *Mo) UnmarshalBinary(data []byte) error {
  448. buff := bytes.NewBuffer(data)
  449. obj := new(TranslatorEncoding)
  450. decoder := gob.NewDecoder(buff)
  451. err := decoder.Decode(obj)
  452. if err != nil {
  453. return err
  454. }
  455. mo.Headers = obj.Headers
  456. mo.Language = obj.Language
  457. mo.PluralForms = obj.PluralForms
  458. mo.nplurals = obj.Nplurals
  459. mo.plural = obj.Plural
  460. mo.translations = obj.Translations
  461. mo.contexts = obj.Contexts
  462. if expr, err := plurals.Compile(mo.plural); err == nil {
  463. mo.pluralforms = expr
  464. }
  465. return nil
  466. }