Plural-Forms formula support. Headers parsing. Multiline strings support.

This commit is contained in:
Leonel Quinteros
2016-07-15 19:04:59 -03:00
parent af707140e3
commit 74daa24696
8 changed files with 424 additions and 54 deletions

138
po.go
View File

@@ -1,8 +1,11 @@
package gotext
import (
"bufio"
"fmt"
"github.com/mattn/anko/vm"
"io/ioutil"
"net/textproto"
"os"
"strconv"
"strings"
@@ -44,8 +47,8 @@ func (t *translation) getN(n int) string {
/*
Po parses the content of any PO file and provides all the translation functions needed.
It's the base object used by all packafe methods.
And it's safe for concurrent use by multiple goroutines by using the sync package for write locking.
It's the base object used by all package methods.
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
Example:
@@ -64,6 +67,19 @@ Example:
*/
type Po struct {
// Headers
RawHeaders string
// Language header
Language string
// Plural-Forms header
PluralForms string
// Parsed Plural-Forms header values
nplurals int
plural string
// Storage
translations map[string]*translation
contexts map[string]map[string]*translation
@@ -123,7 +139,7 @@ func (po *Po) Parse(str string) {
}
// Skip invalid lines
if !strings.HasPrefix(l, "msgctxt") && !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
if !strings.HasPrefix(l, "\"") && !strings.HasPrefix(l, "msgctxt") && !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
continue
}
@@ -198,21 +214,21 @@ func (po *Po) Parse(str string) {
// Check for indexed translation forms
if strings.HasPrefix(l, "[") {
in := strings.Index(l, "]")
if in == -1 {
idx := strings.Index(l, "]")
if idx == -1 {
// Skip wrong index formatting
continue
}
// Parse index
i, err := strconv.Atoi(l[1:in])
i, err := strconv.Atoi(l[1:idx])
if err != nil {
// Skip wrong index formatting
continue
}
// Parse translation string
tr.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[in+1:]))
tr.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
// Loop
continue
@@ -220,6 +236,31 @@ func (po *Po) Parse(str string) {
// Save single translation form under 0 index
tr.trs[0], _ = strconv.Unquote(l)
// Loop
continue
}
// Multi line strings and headers
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
// Check for multiline from previously set msgid
if tr.id != "" {
// Append to last translation found
uq, _ := strconv.Unquote(l)
tr.trs[len(tr.trs)-1] += uq
// Loop
continue
}
// Otherwise is a header
h, err := strconv.Unquote(strings.TrimSpace(l))
if err != nil {
continue
}
po.RawHeaders += h
continue
}
}
@@ -237,6 +278,79 @@ func (po *Po) Parse(str string) {
}
po.Unlock()
}
// Parse headers
po.RawHeaders += "\n\n"
reader := bufio.NewReader(strings.NewReader(po.RawHeaders))
tp := textproto.NewReader(reader)
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
return
}
// Get/save needed headers
po.Language = mimeHeader.Get("Language")
po.PluralForms = mimeHeader.Get("Plural-Forms")
// Parse Plural-Forms formula
if po.PluralForms == "" {
return
}
// Split plural form header value
pfs := strings.Split(po.PluralForms, ";")
// Parse values
for _, i := range pfs {
vs := strings.SplitN(i, "=", 2)
if len(vs) != 2 {
continue
}
switch strings.TrimSpace(vs[0]) {
case "nplurals":
po.nplurals, _ = strconv.Atoi(vs[1])
case "plural":
po.plural = vs[1]
}
}
}
// pluralForm calculates the plural form index corresponding to n.
// Returns 0 on error
func (po *Po) pluralForm(n int) int {
// Failsafe
if po.nplurals < 1 {
return 0
}
if po.plural == "" {
return 0
}
// Init compiler
var env = vm.NewEnv()
env.Define("n", n)
// Run script
plural, err := env.Execute(po.plural)
if err != nil {
return 0
}
if plural.Type().Name() == "bool" {
if plural.Bool() {
return 1
} else {
return 0
}
}
if int(plural.Int()) > po.nplurals {
return 0
}
return int(plural.Int())
}
// Get retrieves the corresponding translation for the given string.
@@ -256,8 +370,7 @@ func (po *Po) Get(str string, vars ...interface{}) string {
return fmt.Sprintf(str, vars...)
}
// GetN retrieves the (N)th plural form translation for the given string.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetN retrieves the (N)th plural form of translation for the given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
// Sync read
@@ -266,7 +379,7 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
if po.translations != nil {
if _, ok := po.translations[str]; ok {
return fmt.Sprintf(po.translations[str].getN(n), vars...)
return fmt.Sprintf(po.translations[str].getN(po.pluralForm(n)), vars...)
}
}
@@ -295,8 +408,7 @@ func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
return fmt.Sprintf(str, vars...)
}
// GetNC retrieves the (N)th plural form translation for the given string in the given context.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// GetNC retrieves the (N)th plural form of translation for the given string in the given context.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
// Sync read
@@ -307,7 +419,7 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
if _, ok := po.contexts[ctx]; ok {
if po.contexts[ctx] != nil {
if _, ok := po.contexts[ctx][str]; ok {
return fmt.Sprintf(po.contexts[ctx][str].getN(n), vars...)
return fmt.Sprintf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
}
}
}