Make Locale and Po objects serializable. Closes #23
This commit is contained in:
parent
302c88af99
commit
4cbf30d337
8 changed files with 303 additions and 4 deletions
|
@ -23,6 +23,7 @@ For quick/simple translations you can use the package level functions directly.
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -45,14 +46,17 @@ type config struct {
|
|||
|
||||
var globalConfig *config
|
||||
|
||||
// Init default configuration
|
||||
func init() {
|
||||
// Init default configuration
|
||||
globalConfig = &config{
|
||||
domain: "default",
|
||||
language: "en_US",
|
||||
library: "/usr/local/share/locale",
|
||||
storage: nil,
|
||||
}
|
||||
|
||||
// Register Translator types for gob encoding
|
||||
gob.Register(TranslatorEncoding{})
|
||||
}
|
||||
|
||||
// loadStorage creates a new Locale object at package level based on the Global variables settings.
|
||||
|
|
66
locale.go
66
locale.go
|
@ -6,6 +6,8 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
@ -19,6 +21,8 @@ multiple languages at the same time by working with this object.
|
|||
Example:
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
@ -236,3 +240,65 @@ func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...inte
|
|||
// Return the same we received by default
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
|
||||
type LocaleEncoding struct {
|
||||
Path string
|
||||
Lang string
|
||||
Domains map[string][]byte
|
||||
DefaultDomain string
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding BinaryMarshaler interface
|
||||
func (l *Locale) MarshalBinary() ([]byte, error) {
|
||||
obj := new(LocaleEncoding)
|
||||
obj.DefaultDomain = l.defaultDomain
|
||||
obj.Domains = make(map[string][]byte)
|
||||
for k, v := range l.Domains {
|
||||
var err error
|
||||
obj.Domains[k], err = v.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
obj.Lang = l.lang
|
||||
obj.Path = l.path
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding BinaryUnmarshaler interface
|
||||
func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(LocaleEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.defaultDomain = obj.DefaultDomain
|
||||
l.lang = obj.Lang
|
||||
l.path = obj.Path
|
||||
|
||||
// Decode Domains
|
||||
l.Domains = make(map[string]Translator)
|
||||
for k, v := range obj.Domains {
|
||||
var tr TranslatorEncoding
|
||||
buff := bytes.NewBuffer(v)
|
||||
trDecoder := gob.NewDecoder(buff)
|
||||
err := trDecoder.Decode(&tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Domains[k] = tr.GetTranslator()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -485,3 +485,42 @@ func TestArabicTranslation(t *testing.T) {
|
|||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleBinaryEncoding(t *testing.T) {
|
||||
// Create Locale
|
||||
l := NewLocale("fixtures/", "en_US")
|
||||
l.AddDomain("default")
|
||||
|
||||
buff, err := l.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l2 := new(Locale)
|
||||
err = l2.UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check object properties
|
||||
if l.path != l2.path {
|
||||
t.Fatalf("path doesn't match: '%s' vs '%s'", l.path, l2.path)
|
||||
}
|
||||
if l.lang != l2.lang {
|
||||
t.Fatalf("lang doesn't match: '%s' vs '%s'", l.lang, l2.lang)
|
||||
}
|
||||
if l.defaultDomain != l2.defaultDomain {
|
||||
t.Fatalf("defaultDomain doesn't match: '%s' vs '%s'", l.defaultDomain, l2.defaultDomain)
|
||||
}
|
||||
|
||||
// Check translations
|
||||
if l.Get("My text") != l2.Get("My text") {
|
||||
t.Errorf("'%s' is different from '%s", l.Get("My text"), l2.Get("My text"))
|
||||
}
|
||||
if l.Get("More") != l2.Get("More") {
|
||||
t.Errorf("'%s' is different from '%s", l.Get("More"), l2.Get("More"))
|
||||
}
|
||||
if l.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE") != l2.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE") {
|
||||
t.Errorf("'%s' is different from '%s", l.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE"), l2.GetN("One with var: %s", "Several with vars: %s", 3, "VALUE"))
|
||||
}
|
||||
}
|
||||
|
|
45
mo.go
45
mo.go
|
@ -9,6 +9,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
|
@ -425,3 +426,47 @@ func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
30
mo_test.go
30
mo_test.go
|
@ -202,3 +202,33 @@ func TestNewMoTranslatorRace(t *testing.T) {
|
|||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestMoBinaryEncoding(t *testing.T) {
|
||||
// Create mo objects
|
||||
mo := new(Mo)
|
||||
mo2 := new(Mo)
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
buff, err := mo.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = mo2.UnmarshalBinary(buff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test translations
|
||||
tr := mo2.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo2.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
|
46
po.go
46
po.go
|
@ -7,6 +7,8 @@ package gotext
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
|
@ -451,3 +453,47 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
33
po_test.go
33
po_test.go
|
@ -9,11 +9,9 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
)
|
||||
|
||||
func TestPo_Get(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
|
@ -589,7 +587,6 @@ msgstr[2] "And this is the second plural form: %s"
|
|||
}
|
||||
|
||||
func TestNewPoTranslatorRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := NewPoTranslator()
|
||||
|
||||
|
@ -617,3 +614,33 @@ func TestNewPoTranslatorRace(t *testing.T) {
|
|||
<-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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
|
||||
package gotext
|
||||
|
||||
import "net/textproto"
|
||||
|
||||
// Translator interface is used by Locale and Po objects.Translator
|
||||
// It contains all methods needed to parse translation sources and obtain corresponding translations.
|
||||
// Also implements gob.GobEncoder/gob.DobDecoder interfaces to allow serialization of Locale objects.
|
||||
type Translator interface {
|
||||
ParseFile(f string)
|
||||
Parse(buf []byte)
|
||||
|
@ -14,4 +17,43 @@ type Translator interface {
|
|||
GetN(str, plural string, n int, vars ...interface{}) string
|
||||
GetC(str, ctx string, vars ...interface{}) string
|
||||
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
||||
|
||||
MarshalBinary() ([]byte, error)
|
||||
UnmarshalBinary([]byte) error
|
||||
}
|
||||
|
||||
// TranslatorEncoding is used as intermediary storage to encode Translator objects to Gob.
|
||||
type TranslatorEncoding struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
Nplurals int
|
||||
Plural string
|
||||
|
||||
// Storage
|
||||
Translations map[string]*Translation
|
||||
Contexts map[string]map[string]*Translation
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
|
||||
return po
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue