220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
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...)
|
|
}
|