Skip to content

Commit bb27662

Browse files
committed
move common po.go/mo.go code to domain.go
adjust tests to reduce duplication
1 parent 62ce4e8 commit bb27662

13 files changed

+558
-713
lines changed

domain.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package gotext
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"encoding/gob"
7+
"net/textproto"
8+
"strconv"
9+
"strings"
10+
"sync"
11+
12+
"golang.org/x/text/language"
13+
14+
"github.com/leonelquinteros/gotext/plurals"
15+
)
16+
17+
// Domain has all the common functions for dealing with a gettext domain
18+
// it's initialized with a GettextFile (which represents either a Po or Mo file)
19+
type Domain struct {
20+
Headers textproto.MIMEHeader
21+
22+
// Language header
23+
Language string
24+
tag language.Tag
25+
26+
// Plural-Forms header
27+
PluralForms string
28+
29+
// Parsed Plural-Forms header values
30+
nplurals int
31+
plural string
32+
pluralforms plurals.Expression
33+
34+
// Storage
35+
translations map[string]*Translation
36+
contexts map[string]map[string]*Translation
37+
pluralTranslations map[string]*Translation
38+
39+
// Sync Mutex
40+
trMutex sync.RWMutex
41+
pluralMutex sync.RWMutex
42+
43+
// Parsing buffers
44+
trBuffer *Translation
45+
ctxBuffer string
46+
}
47+
48+
func NewDomain() *Domain {
49+
domain := new(Domain)
50+
51+
domain.translations = make(map[string]*Translation)
52+
domain.contexts = make(map[string]map[string]*Translation)
53+
domain.pluralTranslations = make(map[string]*Translation)
54+
55+
return domain
56+
}
57+
58+
func (do *Domain) pluralForm(n int) int {
59+
// do we really need locking here? not sure how this plurals.Expression works, so sticking with it for now
60+
do.pluralMutex.RLock()
61+
defer do.pluralMutex.RUnlock()
62+
63+
// Failure fallback
64+
if do.pluralforms == nil {
65+
/* Use the Germanic plural rule. */
66+
if n == 1 {
67+
return 0
68+
}
69+
return 1
70+
}
71+
return do.pluralforms.Eval(uint32(n))
72+
}
73+
74+
// parseHeaders retrieves data from previously parsed headers. it's called by both Mo and Po when parsing
75+
func (do *Domain) parseHeaders() {
76+
// Make sure we end with 2 carriage returns.
77+
empty := ""
78+
if _, ok := do.translations[empty]; ok {
79+
empty = do.translations[empty].Get()
80+
}
81+
raw := empty + "\n\n"
82+
83+
// Read
84+
reader := bufio.NewReader(strings.NewReader(raw))
85+
tp := textproto.NewReader(reader)
86+
87+
var err error
88+
89+
do.Headers, err = tp.ReadMIMEHeader()
90+
if err != nil {
91+
return
92+
}
93+
94+
// Get/save needed headers
95+
do.Language = do.Headers.Get("Language")
96+
do.tag = language.Make(do.Language)
97+
do.PluralForms = do.Headers.Get("Plural-Forms")
98+
99+
// Parse Plural-Forms formula
100+
if do.PluralForms == "" {
101+
return
102+
}
103+
104+
// Split plural form header value
105+
pfs := strings.Split(do.PluralForms, ";")
106+
107+
// Parse values
108+
for _, i := range pfs {
109+
vs := strings.SplitN(i, "=", 2)
110+
if len(vs) != 2 {
111+
continue
112+
}
113+
114+
switch strings.TrimSpace(vs[0]) {
115+
case "nplurals":
116+
do.nplurals, _ = strconv.Atoi(vs[1])
117+
118+
case "plural":
119+
do.plural = vs[1]
120+
121+
if expr, err := plurals.Compile(do.plural); err == nil {
122+
do.pluralforms = expr
123+
}
124+
125+
}
126+
}
127+
}
128+
129+
func (do *Domain) Get(str string, vars ...interface{}) string {
130+
// Sync read
131+
do.trMutex.RLock()
132+
defer do.trMutex.RUnlock()
133+
134+
if do.translations != nil {
135+
if _, ok := do.translations[str]; ok {
136+
return Printf(do.translations[str].Get(), vars...)
137+
}
138+
}
139+
140+
// Return the same we received by default
141+
return Printf(str, vars...)
142+
}
143+
144+
// GetN retrieves the (N)th plural form of Translation for the given string.
145+
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
146+
func (do *Domain) GetN(str, plural string, n int, vars ...interface{}) string {
147+
// Sync read
148+
do.trMutex.RLock()
149+
defer do.trMutex.RUnlock()
150+
151+
if do.translations != nil {
152+
if _, ok := do.translations[str]; ok {
153+
return Printf(do.translations[str].GetN(do.pluralForm(n)), vars...)
154+
}
155+
}
156+
157+
// Parse plural forms to distinguish between plural and singular
158+
if do.pluralForm(n) == 0 {
159+
return Printf(str, vars...)
160+
}
161+
return Printf(plural, vars...)
162+
}
163+
164+
// GetC retrieves the corresponding Translation for a given string in the given context.
165+
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
166+
func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
167+
do.trMutex.RLock()
168+
defer do.trMutex.RUnlock()
169+
170+
if do.contexts != nil {
171+
if _, ok := do.contexts[ctx]; ok {
172+
if do.contexts[ctx] != nil {
173+
if _, ok := do.contexts[ctx][str]; ok {
174+
return Printf(do.contexts[ctx][str].Get(), vars...)
175+
}
176+
}
177+
}
178+
}
179+
180+
// Return the string we received by default
181+
return Printf(str, vars...)
182+
}
183+
184+
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
185+
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
186+
func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
187+
do.trMutex.RLock()
188+
defer do.trMutex.RUnlock()
189+
190+
if do.contexts != nil {
191+
if _, ok := do.contexts[ctx]; ok {
192+
if do.contexts[ctx] != nil {
193+
if _, ok := do.contexts[ctx][str]; ok {
194+
return Printf(do.contexts[ctx][str].GetN(do.pluralForm(n)), vars...)
195+
}
196+
}
197+
}
198+
}
199+
200+
if n == 1 {
201+
return Printf(str, vars...)
202+
}
203+
return Printf(plural, vars...)
204+
}
205+
206+
// MarshalBinary implements encoding.BinaryMarshaler interface
207+
func (do *Domain) MarshalBinary() ([]byte, error) {
208+
obj := new(TranslatorEncoding)
209+
obj.Headers = do.Headers
210+
obj.Language = do.Language
211+
obj.PluralForms = do.PluralForms
212+
obj.Nplurals = do.nplurals
213+
obj.Plural = do.plural
214+
obj.Translations = do.translations
215+
obj.Contexts = do.contexts
216+
217+
var buff bytes.Buffer
218+
encoder := gob.NewEncoder(&buff)
219+
err := encoder.Encode(obj)
220+
221+
return buff.Bytes(), err
222+
}
223+
224+
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
225+
func (do *Domain) UnmarshalBinary(data []byte) error {
226+
buff := bytes.NewBuffer(data)
227+
obj := new(TranslatorEncoding)
228+
229+
decoder := gob.NewDecoder(buff)
230+
err := decoder.Decode(obj)
231+
if err != nil {
232+
return err
233+
}
234+
235+
do.Headers = obj.Headers
236+
do.Language = obj.Language
237+
do.PluralForms = obj.PluralForms
238+
do.nplurals = obj.Nplurals
239+
do.plural = obj.Plural
240+
do.translations = obj.Translations
241+
do.contexts = obj.Contexts
242+
243+
if expr, err := plurals.Compile(do.plural); err == nil {
244+
do.pluralforms = expr
245+
}
246+
247+
return nil
248+
}

domain_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package gotext
2+
3+
import "testing"
4+
5+
//since both Po and Mo just pass-through to Domain for MarshalBinary and UnmarshalBinary, test it here
6+
func TestBinaryEncoding(t *testing.T) {
7+
// Create po objects
8+
po := NewPo()
9+
po2 := NewPo()
10+
11+
// Parse file
12+
po.ParseFile("fixtures/en_US/default.po")
13+
14+
buff, err := po.GetDomain().MarshalBinary()
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
19+
err = po2.GetDomain().UnmarshalBinary(buff)
20+
if err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
// Test translations
25+
tr := po2.Get("My text")
26+
if tr != translatedText {
27+
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
28+
}
29+
// Test translations
30+
tr = po2.Get("language")
31+
if tr != "en_US" {
32+
t.Errorf("Expected 'en_US' but got '%s'", tr)
33+
}
34+
}

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ module github.com/leonelquinteros/gotext
22

33
// go: no requirements found in Gopkg.lock
44

5-
require golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
5+
require (
6+
golang.org/x/text v0.3.0
7+
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
8+
)
69

710
go 1.13

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
77
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
88
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
99
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
10+
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
1011
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1112
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs=
1213
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=

gotext_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ msgstr "Another text on another domain"
139139

140140
// Test translations
141141
tr := Get("My text")
142-
if tr != "Translated text" {
143-
t.Errorf("Expected 'Translated text' but got '%s'", tr)
142+
if tr != translatedText {
143+
t.Errorf("Expected '%s' but got '%s'", translatedText, tr)
144144
}
145145

146146
v := "Variable"
@@ -252,16 +252,15 @@ msgstr[1] ""
252252
}
253253

254254
func TestMoAndPoTranslator(t *testing.T) {
255-
256255
fixPath, _ := filepath.Abs("./fixtures/")
257256

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

260259
// Check default domain Translation
261260
SetDomain("default")
262261
tr := Get("My text")
263-
if tr != "Translated text" {
264-
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
262+
if tr != translatedText {
263+
t.Errorf("Expected '%s'. Got '%s'", translatedText, tr)
265264
}
266265
tr = Get("language")
267266
if tr != "en_GB" {
@@ -274,8 +273,8 @@ func TestMoAndPoTranslator(t *testing.T) {
274273
// Check default domain Translation
275274
SetDomain("default")
276275
tr = Get("My text")
277-
if tr != "Translated text" {
278-
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
276+
if tr != translatedText {
277+
t.Errorf("Expected '%s'. Got '%s'", translatedText, tr)
279278
}
280279
tr = Get("language")
281280
if tr != "en_AU" {

helper_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ import (
1111
)
1212

1313
func TestSimplifiedLocale(t *testing.T) {
14-
tr :=SimplifiedLocale("de_DE@euro")
14+
tr := SimplifiedLocale("de_DE@euro")
1515
if tr != "de_DE" {
1616
t.Errorf("Expected 'de_DE' but got '%s'", tr)
1717
}
1818

19-
tr =SimplifiedLocale("de_DE.UTF-8")
19+
tr = SimplifiedLocale("de_DE.UTF-8")
2020
if tr != "de_DE" {
2121
t.Errorf("Expected 'de_DE' but got '%s'", tr)
2222
}
2323

24-
tr =SimplifiedLocale("de_DE:latin1")
24+
tr = SimplifiedLocale("de_DE:latin1")
2525
if tr != "de_DE" {
2626
t.Errorf("Expected 'de_DE' but got '%s'", tr)
2727
}
@@ -97,10 +97,10 @@ func TestNPrintf(t *testing.T) {
9797
func TestSprintfFloatsWithPrecision(t *testing.T) {
9898
pat := "%(float)f / %(floatprecision).1f / %(long)g / %(longprecision).3g"
9999
params := map[string]interface{}{
100-
"float": 5.034560,
100+
"float": 5.034560,
101101
"floatprecision": 5.03456,
102-
"long": 5.03456,
103-
"longprecision": 5.03456,
102+
"long": 5.03456,
103+
"longprecision": 5.03456,
104104
}
105105

106106
s := Sprintf(pat, params)
@@ -109,4 +109,4 @@ func TestSprintfFloatsWithPrecision(t *testing.T) {
109109
if s != expectedresult {
110110
t.Errorf("result should be (%v) but is (%v)", expectedresult, s)
111111
}
112-
}
112+
}

0 commit comments

Comments
 (0)