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.

587 lines
14KB

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