-
Notifications
You must be signed in to change notification settings - Fork 58
/
po.go
219 lines (174 loc) · 4.62 KB
/
po.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package gotext
import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
)
type translation struct {
id string
pluralId string
trs map[int]string
}
func newTranslation() *translation {
tr := new(translation)
tr.trs = make(map[int]string)
return tr
}
func (t *translation) get() string {
// Look for translation index 0
if _, ok := t.trs[0]; ok {
return t.trs[0]
}
// Return unstranlated id by default
return t.id
}
func (t *translation) getN(n int) string {
// Look for translation index
if _, ok := t.trs[n]; ok {
return t.trs[n]
}
// Return unstranlated plural by default
return t.pluralId
}
/*
Po parses the content of any PO file and provides all the translation functions needed.
It's the base object used by all packafe methods.
And it's safe for concurrent use by multiple goroutines by using the sync package for write locking.
Example:
import "github.com/leonelquinteros/gotext"
func main() {
// Create po object
po := new(gotext.Po)
// Parse .po file
po.ParseFile("/path/to/po/file/translations.po")
// Get translation
println(po.Get("Translate this"))
}
*/
type Po struct {
// Storage
translations map[string]*translation
// Sync Mutex
sync.RWMutex
}
// 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)
if err != nil {
return
}
po.Parse(string(data))
}
// Parse loads the translations specified in the provided string (str)
func (po *Po) Parse(str string) {
if po.translations == nil {
po.Lock()
po.translations = make(map[string]*translation)
po.Unlock()
}
lines := strings.Split(str, "\n")
tr := newTranslation()
for _, l := range lines {
// Trim spaces
l = strings.TrimSpace(l)
// Skip empty lines
if l == "" {
continue
}
// Skip invalid lines
if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") {
continue
}
// Buffer msgid and continue
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
// Save current translation buffer.
po.Lock()
po.translations[tr.id] = tr
po.Unlock()
// Flush buffer
tr = newTranslation()
// Set id
tr.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
// Loop
continue
}
// Check for plural form
if strings.HasPrefix(l, "msgid_plural") {
tr.pluralId, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
// Loop
continue
}
// Save translation
if strings.HasPrefix(l, "msgstr") {
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
// Check for indexed translation forms
if strings.HasPrefix(l, "[") {
in := strings.Index(l, "]")
if in == -1 {
// Skip wrong index formatting
continue
}
// Parse index
i, err := strconv.Atoi(l[1:in])
if err != nil {
// Skip wrong index formatting
continue
}
// Parse translation string
tr.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[in+1:]))
// Loop
continue
}
// Save single translation form under 0 index
tr.trs[0], _ = strconv.Unquote(l)
}
}
// Save last translation buffer.
if tr.id != "" {
po.Lock()
po.translations[tr.id] = tr
po.Unlock()
}
}
// 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 fmt.Sprintf(po.translations[str].get(), vars...)
}
}
// Return the same we received by default
return fmt.Sprintf(str, vars...)
}
// GetN retrieves the (N)th plural form translation for the given string.
// If n == 0, usually the singular form of the string is returned as defined in the PO file.
// 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 fmt.Sprintf(po.translations[str].getN(n), vars...)
}
}
// Return the plural string we received by default
return fmt.Sprintf(plural, vars...)
}