Browse Source

Make Locale and Po objects serializable. Closes #23

tags/v1.4.0
Leonel Quinteros 2 years ago
parent
commit
4cbf30d337
8 changed files with 303 additions and 4 deletions
  1. +5
    -1
      gotext.go
  2. +66
    -0
      locale.go
  3. +39
    -0
      locale_test.go
  4. +45
    -0
      mo.go
  5. +30
    -0
      mo_test.go
  6. +46
    -0
      po.go
  7. +30
    -3
      po_test.go
  8. +42
    -0
      translator.go

+ 5
- 1
gotext.go View File

@@ -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
- 0
locale.go View File

@@ -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
}

+ 39
- 0
locale_test.go View File

@@ -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
- 0
mo.go View File

@@ -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
- 0
mo_test.go View File

@@ -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
- 0
po.go View File

@@ -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
}

+ 30
- 3
po_test.go View File

@@ -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)
}
}

+ 42
- 0
translator.go View File

@@ -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…
Cancel
Save