move common po.go/mo.go code to domain.go
adjust tests to reduce duplication
This commit is contained in:
parent
62ce4e85a3
commit
bb276626f3
13 changed files with 565 additions and 720 deletions
248
domain.go
Normal file
248
domain.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
// Domain has all the common functions for dealing with a gettext domain
|
||||
// it's initialized with a GettextFile (which represents either a Po or Mo file)
|
||||
type Domain struct {
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
tag language.Tag
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
pluralTranslations map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
trMutex sync.RWMutex
|
||||
pluralMutex sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
func NewDomain() *Domain {
|
||||
domain := new(Domain)
|
||||
|
||||
domain.translations = make(map[string]*Translation)
|
||||
domain.contexts = make(map[string]map[string]*Translation)
|
||||
domain.pluralTranslations = make(map[string]*Translation)
|
||||
|
||||
return domain
|
||||
}
|
||||
|
||||
func (do *Domain) pluralForm(n int) int {
|
||||
// do we really need locking here? not sure how this plurals.Expression works, so sticking with it for now
|
||||
do.pluralMutex.RLock()
|
||||
defer do.pluralMutex.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if do.pluralforms == nil {
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return do.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers. it's called by both Mo and Po when parsing
|
||||
func (do *Domain) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
empty := ""
|
||||
if _, ok := do.translations[empty]; ok {
|
||||
empty = do.translations[empty].Get()
|
||||
}
|
||||
raw := empty + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
do.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
do.Language = do.Headers.Get("Language")
|
||||
do.tag = language.Make(do.Language)
|
||||
do.PluralForms = do.Headers.Get("Plural-Forms")
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if do.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(do.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
do.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
do.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (do *Domain) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return Printf(do.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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 (do *Domain) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return Printf(do.translations[str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if do.pluralForm(n) == 0 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contexts != nil {
|
||||
if _, ok := do.contexts[ctx]; ok {
|
||||
if do.contexts[ctx] != nil {
|
||||
if _, ok := do.contexts[ctx][str]; ok {
|
||||
return Printf(do.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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 (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contexts != nil {
|
||||
if _, ok := do.contexts[ctx]; ok {
|
||||
if do.contexts[ctx] != nil {
|
||||
if _, ok := do.contexts[ctx][str]; ok {
|
||||
return Printf(do.contexts[ctx][str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (do *Domain) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = do.Headers
|
||||
obj.Language = do.Language
|
||||
obj.PluralForms = do.PluralForms
|
||||
obj.Nplurals = do.nplurals
|
||||
obj.Plural = do.plural
|
||||
obj.Translations = do.translations
|
||||
obj.Contexts = do.contexts
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (do *Domain) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
do.Headers = obj.Headers
|
||||
do.Language = obj.Language
|
||||
do.PluralForms = obj.PluralForms
|
||||
do.nplurals = obj.Nplurals
|
||||
do.plural = obj.Plural
|
||||
do.translations = obj.Translations
|
||||
do.contexts = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
34
domain_test.go
Normal file
34
domain_test.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package gotext
|
||||
|
||||
import "testing"
|
||||
|
||||
//since both Po and Mo just pass-through to Domain for MarshalBinary and UnmarshalBinary, test it here
|
||||
func TestBinaryEncoding(t *testing.T) {
|
||||
// Create po objects
|
||||
po := NewPo()
|
||||
po2 := NewPo()
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
||||
buff, err := po.GetDomain().MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = po2.GetDomain().UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test translations
|
||||
tr := po2.Get("My text")
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = po2.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
|
@ -2,6 +2,9 @@ module github.com/leonelquinteros/gotext
|
|||
|
||||
// go: no requirements found in Gopkg.lock
|
||||
|
||||
require golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
|
||||
require (
|
||||
golang.org/x/text v0.3.0
|
||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
|
1
go.sum
1
go.sum
|
@ -7,6 +7,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs=
|
||||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
|
|
@ -139,8 +139,8 @@ msgstr "Another text on another domain"
|
|||
|
||||
// Test translations
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
|
@ -252,7 +252,6 @@ msgstr[1] ""
|
|||
}
|
||||
|
||||
func TestMoAndPoTranslator(t *testing.T) {
|
||||
|
||||
fixPath, _ := filepath.Abs("./fixtures/")
|
||||
|
||||
Configure(fixPath, "en_GB", "default")
|
||||
|
@ -260,8 +259,8 @@ func TestMoAndPoTranslator(t *testing.T) {
|
|||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s'. Got '%s'", translatedText, tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_GB" {
|
||||
|
@ -274,8 +273,8 @@ func TestMoAndPoTranslator(t *testing.T) {
|
|||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr = Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s'. Got '%s'", translatedText, tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_AU" {
|
||||
|
|
|
@ -11,17 +11,17 @@ import (
|
|||
)
|
||||
|
||||
func TestSimplifiedLocale(t *testing.T) {
|
||||
tr :=SimplifiedLocale("de_DE@euro")
|
||||
tr := SimplifiedLocale("de_DE@euro")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE.UTF-8")
|
||||
tr = SimplifiedLocale("de_DE.UTF-8")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE:latin1")
|
||||
tr = SimplifiedLocale("de_DE:latin1")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
@ -97,10 +97,10 @@ func TestNPrintf(t *testing.T) {
|
|||
func TestSprintfFloatsWithPrecision(t *testing.T) {
|
||||
pat := "%(float)f / %(floatprecision).1f / %(long)g / %(longprecision).3g"
|
||||
params := map[string]interface{}{
|
||||
"float": 5.034560,
|
||||
"float": 5.034560,
|
||||
"floatprecision": 5.03456,
|
||||
"long": 5.03456,
|
||||
"longprecision": 5.03456,
|
||||
"long": 5.03456,
|
||||
"longprecision": 5.03456,
|
||||
}
|
||||
|
||||
s := Sprintf(pat, params)
|
||||
|
@ -109,4 +109,4 @@ func TestSprintfFloatsWithPrecision(t *testing.T) {
|
|||
if s != expectedresult {
|
||||
t.Errorf("result should be (%v) but is (%v)", expectedresult, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,13 +107,13 @@ func (l *Locale) AddDomain(dom string) {
|
|||
|
||||
file := l.findExt(dom, "po")
|
||||
if file != "" {
|
||||
poObj = new(Po)
|
||||
poObj = NewPo()
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
file = l.findExt(dom, "mo")
|
||||
if file != "" {
|
||||
poObj = new(Mo)
|
||||
poObj = NewMo()
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
|
|
|
@ -93,8 +93,8 @@ msgstr "More Translation"
|
|||
|
||||
// Test translations
|
||||
tr := l.GetD("my_domain", "My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
|
@ -474,7 +474,7 @@ msgstr[2] "And this is the second plural form: %s"
|
|||
|
||||
func TestAddTranslator(t *testing.T) {
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po := NewPo()
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
@ -487,8 +487,8 @@ func TestAddTranslator(t *testing.T) {
|
|||
|
||||
// Test translations
|
||||
tr := l.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = l.Get("language")
|
||||
|
|
319
mo.go
319
mo.go
|
@ -6,18 +6,9 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -57,52 +48,52 @@ Example:
|
|||
|
||||
*/
|
||||
type Mo struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
//these three public members are for backwards compatibility. they are just set to the value in the domain
|
||||
Headers textproto.MIMEHeader
|
||||
Language string
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
domain *Domain
|
||||
}
|
||||
|
||||
// NewMoTranslator creates a new Mo object with the Translator interface
|
||||
func NewMoTranslator() Translator {
|
||||
return new(Mo)
|
||||
//NewMo should always be used to instantiate a new Mo object
|
||||
func NewMo() *Mo {
|
||||
mo := new(Mo)
|
||||
mo.domain = NewDomain()
|
||||
|
||||
return mo
|
||||
}
|
||||
|
||||
func (mo *Mo) GetDomain() *Domain {
|
||||
return mo.domain
|
||||
}
|
||||
|
||||
//all of the Get functions are for convenience and aid in backwards compatibility
|
||||
func (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
return mo.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return mo.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||
return mo.domain.MarshalBinary()
|
||||
}
|
||||
|
||||
func (mo *Mo) UnmarshalBinary(data []byte) error {
|
||||
return mo.domain.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (mo *Mo) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that isn't a directory
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse file content
|
||||
data, err := ioutil.ReadFile(f)
|
||||
data, err := getFileData(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -110,16 +101,13 @@ func (mo *Mo) ParseFile(f string) {
|
|||
mo.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
// Parse loads the translations specified in the provided byte slice, in the GNU gettext .mo format
|
||||
func (mo *Mo) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
mo.Lock()
|
||||
|
||||
// Init storage
|
||||
if mo.translations == nil {
|
||||
mo.translations = make(map[string]*Translation)
|
||||
mo.contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
mo.domain.trMutex.Lock()
|
||||
mo.domain.pluralMutex.Lock()
|
||||
defer mo.domain.trMutex.Unlock()
|
||||
defer mo.domain.pluralMutex.Unlock()
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
|
@ -223,13 +211,14 @@ func (mo *Mo) Parse(buf []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unlock to parse headers
|
||||
mo.Unlock()
|
||||
|
||||
// Parse headers
|
||||
mo.parseHeaders()
|
||||
return
|
||||
// return nil
|
||||
mo.domain.parseHeaders()
|
||||
|
||||
// set values on this struct
|
||||
// this is for backwards compatibility
|
||||
mo.Language = mo.domain.Language
|
||||
mo.PluralForms = mo.domain.PluralForms
|
||||
mo.Headers = mo.domain.Headers
|
||||
}
|
||||
|
||||
func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
||||
|
@ -266,207 +255,11 @@ func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
|||
|
||||
if len(msgctxt) > 0 {
|
||||
// With context...
|
||||
if _, ok := mo.contexts[string(msgctxt)]; !ok {
|
||||
mo.contexts[string(msgctxt)] = make(map[string]*Translation)
|
||||
if _, ok := mo.domain.contexts[string(msgctxt)]; !ok {
|
||||
mo.domain.contexts[string(msgctxt)] = make(map[string]*Translation)
|
||||
}
|
||||
mo.contexts[string(msgctxt)][translation.ID] = translation
|
||||
mo.domain.contexts[string(msgctxt)][translation.ID] = translation
|
||||
} else {
|
||||
mo.translations[translation.ID] = translation
|
||||
mo.domain.translations[translation.ID] = translation
|
||||
}
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers
|
||||
func (mo *Mo) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
raw := mo.Get("") + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
// Sync Headers write.
|
||||
mo.Lock()
|
||||
defer mo.Unlock()
|
||||
|
||||
mo.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
mo.Language = mo.Headers.Get("Language")
|
||||
mo.PluralForms = mo.Headers.Get("Plural-Forms")
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if mo.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(mo.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
mo.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
mo.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(mo.plural); err == nil {
|
||||
mo.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm calculates the plural form index corresponding to n.
|
||||
// Returns 0 on error
|
||||
func (mo *Mo) pluralForm(n int) int {
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if mo.pluralforms == nil {
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
|
||||
}
|
||||
return mo.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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 (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
return Printf(mo.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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 (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.contexts != nil {
|
||||
if _, ok := mo.contexts[ctx]; ok {
|
||||
if mo.contexts[ctx] != nil {
|
||||
if _, ok := mo.contexts[ctx][str]; ok {
|
||||
return Printf(mo.contexts[ctx][str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = mo.Headers
|
||||
obj.Language = mo.Language
|
||||
obj.PluralForms = mo.PluralForms
|
||||
obj.Nplurals = mo.nplurals
|
||||
obj.Plural = mo.plural
|
||||
obj.Translations = mo.translations
|
||||
obj.Contexts = mo.contexts
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (mo *Mo) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mo.Headers = obj.Headers
|
||||
mo.Language = obj.Language
|
||||
mo.PluralForms = obj.PluralForms
|
||||
mo.nplurals = obj.Nplurals
|
||||
mo.plural = obj.Plural
|
||||
mo.translations = obj.Translations
|
||||
mo.contexts = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(mo.plural); err == nil {
|
||||
mo.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
33
mo_test.go
33
mo_test.go
|
@ -12,9 +12,8 @@ import (
|
|||
)
|
||||
|
||||
func TestMo_Get(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
// Create mo object
|
||||
mo := NewMo()
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
@ -24,8 +23,8 @@ func TestMo_Get(t *testing.T) {
|
|||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo.Get("language")
|
||||
|
@ -35,9 +34,8 @@ func TestMo_Get(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMo(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
// Create mo object
|
||||
mo := NewMo()
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
@ -47,8 +45,8 @@ func TestMo(t *testing.T) {
|
|||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
|
@ -144,9 +142,8 @@ func TestMo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMoRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := new(Mo)
|
||||
// Create mo object
|
||||
mo := NewMo()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
|
@ -176,7 +173,7 @@ func TestMoRace(t *testing.T) {
|
|||
func TestNewMoTranslatorRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := NewMoTranslator()
|
||||
mo := NewMo()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
|
@ -205,8 +202,8 @@ func TestNewMoTranslatorRace(t *testing.T) {
|
|||
|
||||
func TestMoBinaryEncoding(t *testing.T) {
|
||||
// Create mo objects
|
||||
mo := new(Mo)
|
||||
mo2 := new(Mo)
|
||||
mo := NewMo()
|
||||
mo2 := NewMo()
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
@ -223,8 +220,8 @@ func TestMoBinaryEncoding(t *testing.T) {
|
|||
|
||||
// Test translations
|
||||
tr := mo2.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo2.Get("language")
|
||||
|
|
349
po.go
349
po.go
|
@ -6,17 +6,9 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -44,30 +36,12 @@ Example:
|
|||
|
||||
*/
|
||||
type Po struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
//these three public members are for backwards compatibility. they are just set to the value in the domain
|
||||
Headers textproto.MIMEHeader
|
||||
Language string
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
domain *Domain
|
||||
}
|
||||
|
||||
type parseState int
|
||||
|
@ -80,26 +54,45 @@ const (
|
|||
msgStr
|
||||
)
|
||||
|
||||
// NewPoTranslator creates a new Po object with the Translator interface
|
||||
func NewPoTranslator() Translator {
|
||||
return new(Po)
|
||||
//NewPo should always be used to instantiate a new Po object
|
||||
func NewPo() *Po {
|
||||
po := new(Po)
|
||||
po.domain = NewDomain()
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func (po *Po) GetDomain() *Domain {
|
||||
return po.domain
|
||||
}
|
||||
|
||||
//all of these functions are for convenience and aid in backwards compatibility
|
||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
return po.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return po.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||
return po.domain.MarshalBinary()
|
||||
}
|
||||
|
||||
func (po *Po) UnmarshalBinary(data []byte) error {
|
||||
return po.domain.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (po *Po) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that isn't a directory
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse file content
|
||||
data, err := ioutil.ReadFile(f)
|
||||
data, err := getFileData(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -109,21 +102,22 @@ func (po *Po) ParseFile(f string) {
|
|||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (po *Po) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
po.Lock()
|
||||
|
||||
// Init storage
|
||||
if po.translations == nil {
|
||||
po.translations = make(map[string]*Translation)
|
||||
po.contexts = make(map[string]map[string]*Translation)
|
||||
if po.domain == nil {
|
||||
panic("po.domain must be set when calling Parse")
|
||||
}
|
||||
|
||||
// Lock while parsing
|
||||
po.domain.trMutex.Lock()
|
||||
po.domain.pluralMutex.Lock()
|
||||
defer po.domain.trMutex.Unlock()
|
||||
defer po.domain.pluralMutex.Unlock()
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
|
||||
// Init buffer
|
||||
po.trBuffer = NewTranslation()
|
||||
po.ctxBuffer = ""
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
po.domain.ctxBuffer = ""
|
||||
|
||||
state := head
|
||||
for _, l := range lines {
|
||||
|
@ -152,6 +146,7 @@ func (po *Po) Parse(buf []byte) {
|
|||
// Check for plural form
|
||||
if strings.HasPrefix(l, "msgid_plural") {
|
||||
po.parsePluralID(l)
|
||||
po.domain.pluralTranslations[po.domain.trBuffer.PluralID] = po.domain.trBuffer
|
||||
state = msgIDPlural
|
||||
continue
|
||||
}
|
||||
|
@ -173,34 +168,37 @@ func (po *Po) Parse(buf []byte) {
|
|||
// Save last Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Unlock to parse headers
|
||||
po.Unlock()
|
||||
|
||||
// Parse headers
|
||||
po.parseHeaders()
|
||||
po.domain.parseHeaders()
|
||||
|
||||
// set values on this struct
|
||||
// this is for backwards compatibility
|
||||
po.Language = po.domain.Language
|
||||
po.PluralForms = po.domain.PluralForms
|
||||
po.Headers = po.domain.Headers
|
||||
}
|
||||
|
||||
// saveBuffer takes the context and Translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
// With no context...
|
||||
if po.ctxBuffer == "" {
|
||||
po.translations[po.trBuffer.ID] = po.trBuffer
|
||||
if po.domain.ctxBuffer == "" {
|
||||
po.domain.translations[po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.contexts[po.ctxBuffer]; !ok {
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*Translation)
|
||||
if _, ok := po.domain.contexts[po.domain.ctxBuffer]; !ok {
|
||||
po.domain.contexts[po.domain.ctxBuffer] = make(map[string]*Translation)
|
||||
}
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.ID] = po.trBuffer
|
||||
po.domain.contexts[po.domain.ctxBuffer][po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.trBuffer.ID != "" {
|
||||
po.ctxBuffer = ""
|
||||
if po.domain.trBuffer.ID != "" {
|
||||
po.domain.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Flush Translation buffer
|
||||
po.trBuffer = NewTranslation()
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
}
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
|
@ -210,7 +208,7 @@ func (po *Po) parseContext(l string) {
|
|||
po.saveBuffer()
|
||||
|
||||
// Buffer context
|
||||
po.ctxBuffer, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgctxt")))
|
||||
po.domain.ctxBuffer, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgctxt")))
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
|
@ -220,12 +218,12 @@ func (po *Po) parseID(l string) {
|
|||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
po.domain.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
po.domain.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
|
@ -248,14 +246,14 @@ func (po *Po) parseMessage(l string) {
|
|||
}
|
||||
|
||||
// Parse Translation string
|
||||
po.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
po.domain.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single Translation form under 0 index
|
||||
po.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
po.domain.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
|
@ -266,19 +264,19 @@ func (po *Po) parseString(l string, state parseState) {
|
|||
switch state {
|
||||
case msgStr:
|
||||
// Append to last Translation found
|
||||
po.trBuffer.Trs[len(po.trBuffer.Trs)-1] += clean
|
||||
po.domain.trBuffer.Trs[len(po.domain.trBuffer.Trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.ID += clean
|
||||
po.domain.trBuffer.ID += clean
|
||||
|
||||
case msgIDPlural:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.PluralID += clean
|
||||
po.domain.trBuffer.PluralID += clean
|
||||
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
po.ctxBuffer += clean
|
||||
po.domain.ctxBuffer += clean
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -302,200 +300,3 @@ func (po *Po) isValidLine(l string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers
|
||||
func (po *Po) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
raw := po.Get("") + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
// Sync Headers write.
|
||||
po.Lock()
|
||||
defer po.Unlock()
|
||||
|
||||
po.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
po.Language = po.Headers.Get("Language")
|
||||
po.PluralForms = po.Headers.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]
|
||||
|
||||
if expr, err := plurals.Compile(po.plural); err == nil {
|
||||
po.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm calculates the plural form index corresponding to n.
|
||||
// Returns 0 on error
|
||||
func (po *Po) pluralForm(n int) int {
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if po.pluralforms == nil {
|
||||
/* Use Western plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return po.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding 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) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return Printf(po.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return Printf(po.translations[str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if po.pluralForm(n) == 0 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a 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) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return Printf(po.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// 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
|
||||
po.RLock()
|
||||
defer po.RUnlock()
|
||||
|
||||
if po.contexts != nil {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return Printf(po.contexts[ctx][str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if po.pluralForm(n) == 0 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = po.Headers
|
||||
obj.Language = po.Language
|
||||
obj.PluralForms = po.PluralForms
|
||||
obj.Nplurals = po.nplurals
|
||||
obj.Plural = po.plural
|
||||
obj.Translations = po.translations
|
||||
obj.Contexts = po.contexts
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (po *Po) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
po.Headers = obj.Headers
|
||||
po.Language = obj.Language
|
||||
po.PluralForms = obj.PluralForms
|
||||
po.nplurals = obj.Nplurals
|
||||
po.plural = obj.Plural
|
||||
po.translations = obj.Translations
|
||||
po.contexts = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(po.plural); err == nil {
|
||||
po.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
207
po_test.go
207
po_test.go
|
@ -6,14 +6,19 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
translatedText = "Translated text"
|
||||
)
|
||||
|
||||
func TestPo_Get(t *testing.T) {
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po := NewPo()
|
||||
|
||||
// Try to parse a directory
|
||||
po.ParseFile(path.Clean(os.TempDir()))
|
||||
|
@ -23,8 +28,8 @@ func TestPo_Get(t *testing.T) {
|
|||
|
||||
// Test translations
|
||||
tr := po.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = po.Get("language")
|
||||
|
@ -122,7 +127,7 @@ msgstr "More Translation"
|
|||
}
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po := NewPo()
|
||||
|
||||
// Try to parse a directory
|
||||
po.ParseFile(path.Clean(os.TempDir()))
|
||||
|
@ -132,8 +137,8 @@ msgstr "More Translation"
|
|||
|
||||
// Test translations
|
||||
tr := po.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
if tr != translatedText {
|
||||
t.Errorf( |