Skip to content

Commit

Permalink
move common po.go/mo.go code to domain.go
Browse files Browse the repository at this point in the history
adjust tests to reduce duplication
  • Loading branch information
razor-1 committed Sep 10, 2020
1 parent 62ce4e8 commit bb27662
Show file tree
Hide file tree
Showing 13 changed files with 558 additions and 713 deletions.
248 changes: 248 additions & 0 deletions domain.go
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions domain_test.go
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
13 changes: 6 additions & 7 deletions gotext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -252,16 +252,15 @@ msgstr[1] ""
}

func TestMoAndPoTranslator(t *testing.T) {

fixPath, _ := filepath.Abs("./fixtures/")

Configure(fixPath, "en_GB", "default")

// 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" {
Expand All @@ -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" {
Expand Down
14 changes: 7 additions & 7 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -109,4 +109,4 @@ func TestSprintfFloatsWithPrecision(t *testing.T) {
if s != expectedresult {
t.Errorf("result should be (%v) but is (%v)", expectedresult, s)
}
}
}
Loading

0 comments on commit bb27662

Please sign in to comment.