diff --git a/domain.go b/domain.go new file mode 100644 index 0000000..b52fd8a --- /dev/null +++ b/domain.go @@ -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 +} diff --git a/domain_test.go b/domain_test.go new file mode 100644 index 0000000..617d4ab --- /dev/null +++ b/domain_test.go @@ -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) + } +} diff --git a/go.mod b/go.mod index 3768d5b..21f5da6 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index a5b842c..988c1f6 100644 --- a/go.sum +++ b/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= diff --git a/gotext_test.go b/gotext_test.go index e02cd44..31ece80 100644 --- a/gotext_test.go +++ b/gotext_test.go @@ -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" { diff --git a/helper_test.go b/helper_test.go index c65e7cb..1fdd066 100644 --- a/helper_test.go +++ b/helper_test.go @@ -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) } -} \ No newline at end of file +} diff --git a/locale.go b/locale.go index 56f18a3..a982046 100644 --- a/locale.go +++ b/locale.go @@ -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 { diff --git a/locale_test.go b/locale_test.go index 9ceeb74..4194da6 100644 --- a/locale_test.go +++ b/locale_test.go @@ -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") diff --git a/mo.go b/mo.go index deb21ab..27a5a96 100644 --- a/mo.go +++ b/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 -} diff --git a/mo_test.go b/mo_test.go index 5a9f627..810b8e3 100644 --- a/mo_test.go +++ b/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") diff --git a/po.go b/po.go index 368672a..85d478f 100644 --- a/po.go +++ b/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 -} diff --git a/po_test.go b/po_test.go index 24830f4..6cbc907 100644 --- a/po_test.go +++ b/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("Expected '%s' but got '%s'", translatedText, tr) } v := "Variable" @@ -244,7 +249,7 @@ msgstr[2] "TR Plural 2: %s" ` // Create po object - po := new(Po) + po := NewPo() po.Parse([]byte(str)) v := "Var" @@ -274,7 +279,7 @@ msgstr[2] "TR Plural 2: %s" ` // Create po object - po := new(Po) + po := NewPo() po.Parse([]byte(str)) v := "Var" @@ -307,7 +312,7 @@ msgstr "Translated example" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) @@ -317,9 +322,10 @@ msgstr "Translated example" t.Errorf("Expected 'Language: en' but got '%s'", po.Language) } + do := po.GetDomain() // Check headers expected - if po.PluralForms != "nplurals=2; plural=(n != 1);" { - t.Errorf("Expected 'Plural-Forms: nplurals=2; plural=(n != 1);' but got '%s'", po.PluralForms) + if do.PluralForms != "nplurals=2; plural=(n != 1);" { + t.Errorf("Expected 'Plural-Forms: nplurals=2; plural=(n != 1);' but got '%s'", do.PluralForms) } } @@ -331,7 +337,7 @@ msgstr "Translated example" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) @@ -342,6 +348,23 @@ msgstr "Translated example" } } +type pluralTest struct { + form, num int +} + +func pluralExpected(t *testing.T, pluralTests []pluralTest, domain *Domain) { + t.Helper() + for _, pt := range pluralTests { + pt := pt + t.Run(fmt.Sprintf("pluralForm(%d)", pt.num), func(t *testing.T) { + n := domain.pluralForm(pt.num) + if n != pt.form { + t.Errorf("Expected %d for pluralForm(%d), got %d", pt.form, pt.num, n) + } + }) + } +} + func TestPluralFormsSingle(t *testing.T) { // Single form str := ` @@ -359,32 +382,20 @@ msgstr[3] "Plural form 3" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) - // Check plural form - n := po.pluralForm(0) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(0), got %d", n) - } - n = po.pluralForm(1) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(1), got %d", n) - } - n = po.pluralForm(2) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(2), got %d", n) - } - n = po.pluralForm(3) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(3), got %d", n) - } - n = po.pluralForm(50) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(50), got %d", n) + pluralTests := []pluralTest{ + {form: 0, num: 0}, + {form: 0, num: 1}, + {form: 0, num: 2}, + {form: 0, num: 3}, + {form: 0, num: 50}, } + + pluralExpected(t, pluralTests, po.GetDomain()) } func TestPluralForms2(t *testing.T) { @@ -404,28 +415,19 @@ msgstr[3] "Plural form 3" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) - // Check plural form - n := po.pluralForm(0) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(0), got %d", n) - } - n = po.pluralForm(1) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(1), got %d", n) - } - n = po.pluralForm(2) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(2), got %d", n) - } - n = po.pluralForm(3) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(3), got %d", n) + pluralTests := []pluralTest{ + {form: 1, num: 0}, + {form: 0, num: 1}, + {form: 1, num: 2}, + {form: 1, num: 3}, } + + pluralExpected(t, pluralTests, po.GetDomain()) } func TestPluralForms3(t *testing.T) { @@ -445,36 +447,21 @@ msgstr[3] "Plural form 3" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) - // Check plural form - n := po.pluralForm(0) - if n != 2 { - t.Errorf("Expected 2 for pluralForm(0), got %d", n) - } - n = po.pluralForm(1) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(1), got %d", n) - } - n = po.pluralForm(2) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(2), got %d", n) - } - n = po.pluralForm(3) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(3), got %d", n) - } - n = po.pluralForm(100) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(100), got %d", n) - } - n = po.pluralForm(49) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(3), got %d", n) + pluralTests := []pluralTest{ + {form: 2, num: 0}, + {form: 0, num: 1}, + {form: 1, num: 2}, + {form: 1, num: 3}, + {form: 1, num: 100}, + {form: 1, num: 49}, } + + pluralExpected(t, pluralTests, po.GetDomain()) } func TestPluralFormsSpecial(t *testing.T) { @@ -495,32 +482,20 @@ msgstr[3] "Plural form 3" ` // Create po object - po := new(Po) + po := NewPo() // Parse po.Parse([]byte(str)) - // Check plural form - n := po.pluralForm(1) - if n != 0 { - t.Errorf("Expected 0 for pluralForm(1), got %d", n) - } - n = po.pluralForm(2) - if n != 1 { - t.Errorf("Expected 1 for pluralForm(2), got %d", n) - } - n = po.pluralForm(4) - if n != 1 { - t.Errorf("Expected 4 for pluralForm(4), got %d", n) - } - n = po.pluralForm(0) - if n != 2 { - t.Errorf("Expected 2 for pluralForm(2), got %d", n) - } - n = po.pluralForm(1000) - if n != 2 { - t.Errorf("Expected 2 for pluralForm(1000), got %d", n) + pluralTests := []pluralTest{ + {form: 0, num: 1}, + {form: 1, num: 2}, + {form: 1, num: 4}, + {form: 2, num: 0}, + {form: 2, num: 1000}, } + + pluralExpected(t, pluralTests, po.GetDomain()) } func TestTranslationObject(t *testing.T) { @@ -560,7 +535,7 @@ msgstr[2] "And this is the second plural form: %s" ` // Create Po object - po := new(Po) + po := NewPo() // Create sync channels pc := make(chan bool) @@ -588,7 +563,7 @@ msgstr[2] "And this is the second plural form: %s" func TestNewPoTranslatorRace(t *testing.T) { // Create Po object - mo := NewPoTranslator() + po := NewPo() // Create sync channels pc := make(chan bool) @@ -599,48 +574,18 @@ func TestNewPoTranslatorRace(t *testing.T) { // Parse file mo.ParseFile("fixtures/en_US/default.po") done <- true - }(mo, pc) + }(po, pc) // Read some Translation on a goroutine go func(mo Translator, done chan bool) { mo.Get("My text") done <- true - }(mo, rc) + }(po, rc) // Read something at top level - mo.Get("My text") + po.Get("My text") // Wait for goroutines to finish <-pc <-rc } - -func TestPoBinaryEncoding(t *testing.T) { - // Create po objects - po := new(Po) - po2 := new(Po) - - // Parse file - po.ParseFile("fixtures/en_US/default.po") - - buff, err := po.MarshalBinary() - if err != nil { - t.Fatal(err) - } - - err = po2.UnmarshalBinary(buff) - if err != nil { - t.Fatal(err) - } - - // Test translations - tr := po2.Get("My text") - if tr != "Translated text" { - t.Errorf("Expected 'Translated text' but got '%s'", tr) - } - // Test translations - tr = po2.Get("language") - if tr != "en_US" { - t.Errorf("Expected 'en_US' but got '%s'", tr) - } -} diff --git a/translator.go b/translator.go index 982a600..69c211c 100644 --- a/translator.go +++ b/translator.go @@ -5,7 +5,12 @@ package gotext -import "net/textproto" +import ( + "errors" + "io/ioutil" + "net/textproto" + "os" +) // Translator interface is used by Locale and Po objects.Translator // It contains all methods needed to parse translation sources and obtain corresponding translations. @@ -20,6 +25,7 @@ type Translator interface { MarshalBinary() ([]byte, error) UnmarshalBinary([]byte) error + GetDomain() *Domain } // TranslatorEncoding is used as intermediary storage to encode Translator objects to Gob. @@ -42,18 +48,36 @@ type TranslatorEncoding struct { Contexts map[string]map[string]*Translation } -// GetTranslator is used to recover a Translator object after unmarshaling the TranslatorEncoding object. -// Internally uses a Po object as it should be switcheable with Mo objects without problem. -// External Translator implementations should be able to serialize into a TranslatorEncoding object in order to unserialize into a Po-compatible object. +// GetTranslator is used to recover a Translator object after unmarshalling the TranslatorEncoding object. +// Internally uses a Po object as it should be switchable with Mo objects without problem. +// External Translator implementations should be able to serialize into a TranslatorEncoding object in order to +// deserialize into a Po-compatible object. func (te *TranslatorEncoding) GetTranslator() Translator { - po := new(Po) - po.Headers = te.Headers - po.Language = te.Language - po.PluralForms = te.PluralForms - po.nplurals = te.Nplurals - po.plural = te.Plural - po.translations = te.Translations - po.contexts = te.Contexts + po := NewPo() + po.domain = NewDomain() + po.domain.Headers = te.Headers + po.domain.Language = te.Language + po.domain.PluralForms = te.PluralForms + po.domain.nplurals = te.Nplurals + po.domain.plural = te.Plural + po.domain.translations = te.Translations + po.domain.contexts = te.Contexts return po } + +//getFileData reads a file and returns the byte slice after doing some basic sanity checking +func getFileData(f string) ([]byte, error) { + // Check if file exists + info, err := os.Stat(f) + if err != nil { + return nil, err + } + + // Check that isn't a directory + if info.IsDir() { + return nil, errors.New("cannot parse a directory") + } + + return ioutil.ReadFile(f) +}