forked from toolshed/abra
		
	
		
			
				
	
	
		
			867 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			867 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package gotext
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"encoding/gob"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/leonelquinteros/gotext/plurals"
 | 
						|
)
 | 
						|
 | 
						|
// Domain has all the common functions for dealing with a gettext domain
 | 
						|
// it's initialized with a GettextFile (which represents either a Po or Mo file)
 | 
						|
type Domain struct {
 | 
						|
	Headers HeaderMap
 | 
						|
 | 
						|
	// Language header
 | 
						|
	Language string
 | 
						|
 | 
						|
	// Plural-Forms header
 | 
						|
	PluralForms string
 | 
						|
 | 
						|
	// Preserve comments at head of PO for round-trip
 | 
						|
	headerComments []string
 | 
						|
 | 
						|
	// Parsed Plural-Forms header values
 | 
						|
	nplurals    int
 | 
						|
	plural      string
 | 
						|
	pluralforms plurals.Expression
 | 
						|
 | 
						|
	// Storage
 | 
						|
	translations        map[string]*Translation
 | 
						|
	contextTranslations map[string]map[string]*Translation
 | 
						|
	pluralTranslations  map[string]*Translation
 | 
						|
 | 
						|
	// Sync Mutex
 | 
						|
	trMutex     sync.RWMutex
 | 
						|
	pluralMutex sync.RWMutex
 | 
						|
 | 
						|
	// Parsing buffers
 | 
						|
	trBuffer  *Translation
 | 
						|
	ctxBuffer string
 | 
						|
	refBuffer string
 | 
						|
 | 
						|
	customPluralResolver func(int) int
 | 
						|
}
 | 
						|
 | 
						|
// HeaderMap preserves MIMEHeader behaviour, without the canonicalisation
 | 
						|
type HeaderMap map[string][]string
 | 
						|
 | 
						|
// Add key/value pair to HeaderMap
 | 
						|
func (m HeaderMap) Add(key, value string) {
 | 
						|
	m[key] = append(m[key], value)
 | 
						|
}
 | 
						|
 | 
						|
// Del key from HeaderMap
 | 
						|
func (m HeaderMap) Del(key string) {
 | 
						|
	delete(m, key)
 | 
						|
}
 | 
						|
 | 
						|
// Get value for key from HeaderMap
 | 
						|
func (m HeaderMap) Get(key string) string {
 | 
						|
	if m == nil {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	v := m[key]
 | 
						|
	if len(v) == 0 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return v[0]
 | 
						|
}
 | 
						|
 | 
						|
// Set key/value pair in HeaderMap
 | 
						|
func (m HeaderMap) Set(key, value string) {
 | 
						|
	m[key] = []string{value}
 | 
						|
}
 | 
						|
 | 
						|
// Values returns all values for a given key from HeaderMap
 | 
						|
func (m HeaderMap) Values(key string) []string {
 | 
						|
	if m == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return m[key]
 | 
						|
}
 | 
						|
 | 
						|
// NewDomain creates a new Domain instance
 | 
						|
func NewDomain() *Domain {
 | 
						|
	domain := new(Domain)
 | 
						|
 | 
						|
	domain.Headers = make(HeaderMap)
 | 
						|
	domain.headerComments = make([]string, 0)
 | 
						|
	domain.translations = make(map[string]*Translation)
 | 
						|
	domain.contextTranslations = make(map[string]map[string]*Translation)
 | 
						|
	domain.pluralTranslations = make(map[string]*Translation)
 | 
						|
 | 
						|
	return domain
 | 
						|
}
 | 
						|
 | 
						|
// SetPluralResolver sets a custom plural resolver function
 | 
						|
func (do *Domain) SetPluralResolver(f func(int) int) {
 | 
						|
	do.customPluralResolver = f
 | 
						|
}
 | 
						|
 | 
						|
func (do *Domain) pluralForm(n int) int {
 | 
						|
	// do we really need locking here? not sure how this plurals.Expression works, so sticking with it for now
 | 
						|
	do.pluralMutex.RLock()
 | 
						|
	defer do.pluralMutex.RUnlock()
 | 
						|
 | 
						|
	// Failure fallback
 | 
						|
	if do.pluralforms == nil {
 | 
						|
		if do.customPluralResolver != nil {
 | 
						|
			return do.customPluralResolver(n)
 | 
						|
		}
 | 
						|
 | 
						|
		/* Use the Germanic plural rule.  */
 | 
						|
		if n == 1 {
 | 
						|
			return 0
 | 
						|
		}
 | 
						|
		return 1
 | 
						|
	}
 | 
						|
	return do.pluralforms.Eval(uint32(n))
 | 
						|
}
 | 
						|
 | 
						|
// parseHeaders retrieves data from previously parsed headers. it's called by both Mo and Po when parsing
 | 
						|
func (do *Domain) parseHeaders() {
 | 
						|
	raw := ""
 | 
						|
	if _, ok := do.translations[raw]; ok {
 | 
						|
		raw = do.translations[raw].Get()
 | 
						|
	}
 | 
						|
 | 
						|
	// textproto.ReadMIMEHeader() forces keys through CanonicalMIMEHeaderKey(); must read header manually to have one-to-one round-trip of keys
 | 
						|
	languageKey := "Language"
 | 
						|
	pluralFormsKey := "Plural-Forms"
 | 
						|
 | 
						|
	rawLines := strings.Split(raw, "\n")
 | 
						|
	for _, line := range rawLines {
 | 
						|
		if len(line) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		colonIdx := strings.Index(line, ":")
 | 
						|
		if colonIdx < 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		key := line[:colonIdx]
 | 
						|
		lowerKey := strings.ToLower(key)
 | 
						|
		if lowerKey == strings.ToLower(languageKey) {
 | 
						|
			languageKey = key
 | 
						|
		} else if lowerKey == strings.ToLower(pluralFormsKey) {
 | 
						|
			pluralFormsKey = key
 | 
						|
		}
 | 
						|
 | 
						|
		value := strings.TrimSpace(line[colonIdx+1:])
 | 
						|
		do.Headers.Add(key, value)
 | 
						|
	}
 | 
						|
 | 
						|
	// Get/save needed headers
 | 
						|
	do.Language = do.Headers.Get(languageKey)
 | 
						|
	do.PluralForms = do.Headers.Get(pluralFormsKey)
 | 
						|
 | 
						|
	// Parse Plural-Forms formula
 | 
						|
	if do.PluralForms == "" {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Split plural form header value
 | 
						|
	pfs := strings.Split(do.PluralForms, ";")
 | 
						|
 | 
						|
	// Parse values
 | 
						|
	for _, i := range pfs {
 | 
						|
		vs := strings.SplitN(i, "=", 2)
 | 
						|
		if len(vs) != 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		switch strings.TrimSpace(vs[0]) {
 | 
						|
		case "nplurals":
 | 
						|
			do.nplurals, _ = strconv.Atoi(vs[1])
 | 
						|
 | 
						|
		case "plural":
 | 
						|
			do.plural = vs[1]
 | 
						|
 | 
						|
			if expr, err := plurals.Compile(do.plural); err == nil {
 | 
						|
				do.pluralforms = expr
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DropStaleTranslations drops any translations stored that have not been Set*()
 | 
						|
// since 'po' was initialised
 | 
						|
func (do *Domain) DropStaleTranslations() {
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	for name, ctx := range do.contextTranslations {
 | 
						|
		for id, trans := range ctx {
 | 
						|
			if trans.IsStale() {
 | 
						|
				delete(ctx, id)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if len(ctx) == 0 {
 | 
						|
			delete(do.contextTranslations, name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for id, trans := range do.translations {
 | 
						|
		if trans.IsStale() {
 | 
						|
			delete(do.translations, id)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SetRefs set source references for a given translation
 | 
						|
func (do *Domain) SetRefs(str string, refs []string) {
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	if trans, ok := do.translations[str]; ok {
 | 
						|
		trans.Refs = refs
 | 
						|
	} else {
 | 
						|
		trans = NewTranslation()
 | 
						|
		trans.ID = str
 | 
						|
		trans.SetRefs(refs)
 | 
						|
		do.translations[str] = trans
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetRefs get source references for a given translation
 | 
						|
func (do *Domain) GetRefs(str string) []string {
 | 
						|
	// Sync read
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations != nil {
 | 
						|
		if trans, ok := do.translations[str]; ok {
 | 
						|
			return trans.Refs
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Set the translation of a given string
 | 
						|
func (do *Domain) Set(id, str string) {
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	if trans, ok := do.translations[id]; ok {
 | 
						|
		trans.Set(str)
 | 
						|
	} else {
 | 
						|
		trans = NewTranslation()
 | 
						|
		trans.ID = id
 | 
						|
		trans.Set(str)
 | 
						|
		do.translations[id] = trans
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Get retrieves the Translation for the given string.
 | 
						|
func (do *Domain) Get(str string, vars ...interface{}) string {
 | 
						|
	// Sync read
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations != nil {
 | 
						|
		if _, ok := do.translations[str]; ok {
 | 
						|
			return FormatString(do.translations[str].Get(), vars...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Return the same we received by default
 | 
						|
	return FormatString(str, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// Append retrieves the Translation for the given string.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) Append(b []byte, str string, vars ...interface{}) []byte {
 | 
						|
	// Sync read
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations != nil {
 | 
						|
		if _, ok := do.translations[str]; ok {
 | 
						|
			return Appendf(b, do.translations[str].Get(), vars...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Return the same we received by default
 | 
						|
	return Appendf(b, str, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// SetN sets the (N)th plural form for the given string
 | 
						|
func (do *Domain) SetN(id, plural string, n int, str string) {
 | 
						|
	// Get plural form _before_ lock down
 | 
						|
	pluralForm := do.pluralForm(n)
 | 
						|
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	if trans, ok := do.translations[id]; ok {
 | 
						|
		trans.SetN(pluralForm, str)
 | 
						|
	} else {
 | 
						|
		trans = NewTranslation()
 | 
						|
		trans.ID = id
 | 
						|
		trans.PluralID = plural
 | 
						|
		trans.SetN(pluralForm, str)
 | 
						|
		do.translations[id] = trans
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetN retrieves the (N)th plural form of Translation for the given string.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) GetN(str, plural string, n int, vars ...interface{}) string {
 | 
						|
	// Sync read
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations != nil {
 | 
						|
		if _, ok := do.translations[str]; ok {
 | 
						|
			return FormatString(do.translations[str].GetN(do.pluralForm(n)), vars...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse plural forms to distinguish between plural and singular
 | 
						|
	if do.pluralForm(n) == 0 {
 | 
						|
		return FormatString(str, vars...)
 | 
						|
	}
 | 
						|
	return FormatString(plural, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// AppendN adds the (N)th plural form of Translation for the given string.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
 | 
						|
	// Sync read
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations != nil {
 | 
						|
		if _, ok := do.translations[str]; ok {
 | 
						|
			return Appendf(b, do.translations[str].GetN(do.pluralForm(n)), vars...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse plural forms to distinguish between plural and singular
 | 
						|
	if do.pluralForm(n) == 0 {
 | 
						|
		return Appendf(b, str, vars...)
 | 
						|
	}
 | 
						|
	return Appendf(b, plural, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// SetC sets the translation for the given string in the given context
 | 
						|
func (do *Domain) SetC(id, ctx, str string) {
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	if context, ok := do.contextTranslations[ctx]; ok {
 | 
						|
		if trans, hasTrans := context[id]; hasTrans {
 | 
						|
			trans.Set(str)
 | 
						|
		} else {
 | 
						|
			trans = NewTranslation()
 | 
						|
			trans.ID = id
 | 
						|
			trans.Set(str)
 | 
						|
			context[id] = trans
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		trans := NewTranslation()
 | 
						|
		trans.ID = id
 | 
						|
		trans.Set(str)
 | 
						|
		do.contextTranslations[ctx] = map[string]*Translation{
 | 
						|
			id: trans,
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetC retrieves the corresponding Translation for a given string in the given context.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.contextTranslations != nil {
 | 
						|
		if _, ok := do.contextTranslations[ctx]; ok {
 | 
						|
			if do.contextTranslations[ctx] != nil {
 | 
						|
				if _, ok := do.contextTranslations[ctx][str]; ok {
 | 
						|
					return FormatString(do.contextTranslations[ctx][str].Get(), vars...)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Return the string we received by default
 | 
						|
	return FormatString(str, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// AppendC retrieves the corresponding Translation for a given string in the given context.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.contextTranslations != nil {
 | 
						|
		if _, ok := do.contextTranslations[ctx]; ok {
 | 
						|
			if do.contextTranslations[ctx] != nil {
 | 
						|
				if _, ok := do.contextTranslations[ctx][str]; ok {
 | 
						|
					return Appendf(b, do.contextTranslations[ctx][str].Get(), vars...)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Return the string we received by default
 | 
						|
	return Appendf(b, str, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// SetNC sets the (N)th plural form for the given string in the given context
 | 
						|
func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
 | 
						|
	// Get plural form _before_ lock down
 | 
						|
	pluralForm := do.pluralForm(n)
 | 
						|
 | 
						|
	do.trMutex.Lock()
 | 
						|
	do.pluralMutex.Lock()
 | 
						|
	defer do.trMutex.Unlock()
 | 
						|
	defer do.pluralMutex.Unlock()
 | 
						|
 | 
						|
	if context, ok := do.contextTranslations[ctx]; ok {
 | 
						|
		if trans, hasTrans := context[id]; hasTrans {
 | 
						|
			trans.SetN(pluralForm, str)
 | 
						|
		} else {
 | 
						|
			trans = NewTranslation()
 | 
						|
			trans.ID = id
 | 
						|
			trans.SetN(pluralForm, str)
 | 
						|
			context[id] = trans
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		trans := NewTranslation()
 | 
						|
		trans.ID = id
 | 
						|
		trans.SetN(pluralForm, str)
 | 
						|
		do.contextTranslations[ctx] = map[string]*Translation{
 | 
						|
			id: trans,
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.contextTranslations != nil {
 | 
						|
		if _, ok := do.contextTranslations[ctx]; ok {
 | 
						|
			if do.contextTranslations[ctx] != nil {
 | 
						|
				if _, ok := do.contextTranslations[ctx][str]; ok {
 | 
						|
					return FormatString(do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if n == 1 {
 | 
						|
		return FormatString(str, vars...)
 | 
						|
	}
 | 
						|
	return FormatString(plural, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// AppendNC retrieves the (N)th plural form of Translation for the given string in the given context.
 | 
						|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
 | 
						|
func (do *Domain) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.contextTranslations != nil {
 | 
						|
		if _, ok := do.contextTranslations[ctx]; ok {
 | 
						|
			if do.contextTranslations[ctx] != nil {
 | 
						|
				if _, ok := do.contextTranslations[ctx][str]; ok {
 | 
						|
					return Appendf(b, do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if n == 1 {
 | 
						|
		return Appendf(b, str, vars...)
 | 
						|
	}
 | 
						|
	return Appendf(b, plural, vars...)
 | 
						|
}
 | 
						|
 | 
						|
// IsTranslated reports whether a string is translated
 | 
						|
func (do *Domain) IsTranslated(str string) bool {
 | 
						|
	return do.IsTranslatedN(str, 1)
 | 
						|
}
 | 
						|
 | 
						|
// IsTranslatedN reports whether a plural string is translated
 | 
						|
func (do *Domain) IsTranslatedN(str string, n int) bool {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.translations == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	tr, ok := do.translations[str]
 | 
						|
	if !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return tr.IsTranslatedN(do.pluralForm(n))
 | 
						|
}
 | 
						|
 | 
						|
// IsTranslatedC reports whether a context string is translated
 | 
						|
func (do *Domain) IsTranslatedC(str, ctx string) bool {
 | 
						|
	return do.IsTranslatedNC(str, 1, ctx)
 | 
						|
}
 | 
						|
 | 
						|
// IsTranslatedNC reports whether a plural context string is translated
 | 
						|
func (do *Domain) IsTranslatedNC(str string, n int, ctx string) bool {
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	if do.contextTranslations == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	translations, ok := do.contextTranslations[ctx]
 | 
						|
	if !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	tr, ok := translations[str]
 | 
						|
	if !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return tr.IsTranslatedN(do.pluralForm(n))
 | 
						|
}
 | 
						|
 | 
						|
// GetTranslations returns a copy of every translation in the domain. It does not support contexts.
 | 
						|
func (do *Domain) GetTranslations() map[string]*Translation {
 | 
						|
	all := make(map[string]*Translation, len(do.translations))
 | 
						|
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	for msgID, trans := range do.translations {
 | 
						|
		newTrans := NewTranslation()
 | 
						|
		newTrans.ID = trans.ID
 | 
						|
		newTrans.PluralID = trans.PluralID
 | 
						|
		newTrans.dirty = trans.dirty
 | 
						|
		if len(trans.Refs) > 0 {
 | 
						|
			newTrans.Refs = make([]string, len(trans.Refs))
 | 
						|
			copy(newTrans.Refs, trans.Refs)
 | 
						|
		}
 | 
						|
		for k, v := range trans.Trs {
 | 
						|
			newTrans.Trs[k] = v
 | 
						|
		}
 | 
						|
		all[msgID] = newTrans
 | 
						|
	}
 | 
						|
 | 
						|
	return all
 | 
						|
}
 | 
						|
 | 
						|
// GetCtxTranslations returns a copy of every translation in the domain with context
 | 
						|
func (do *Domain) GetCtxTranslations() map[string]map[string]*Translation {
 | 
						|
	all := make(map[string]map[string]*Translation, len(do.contextTranslations))
 | 
						|
 | 
						|
	do.trMutex.RLock()
 | 
						|
	defer do.trMutex.RUnlock()
 | 
						|
 | 
						|
	for ctx, translations := range do.contextTranslations {
 | 
						|
		for msgID, trans := range translations {
 | 
						|
			newTrans := NewTranslation()
 | 
						|
			newTrans.ID = trans.ID
 | 
						|
			newTrans.PluralID = trans.PluralID
 | 
						|
			newTrans.dirty = trans.dirty
 | 
						|
			if len(trans.Refs) > 0 {
 | 
						|
				newTrans.Refs = make([]string, len(trans.Refs))
 | 
						|
				copy(newTrans.Refs, trans.Refs)
 | 
						|
			}
 | 
						|
			for k, v := range trans.Trs {
 | 
						|
				newTrans.Trs[k] = v
 | 
						|
			}
 | 
						|
 | 
						|
			if all[ctx] == nil {
 | 
						|
				all[ctx] = make(map[string]*Translation)
 | 
						|
			}
 | 
						|
 | 
						|
			all[ctx][msgID] = newTrans
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	return all
 | 
						|
}
 | 
						|
 | 
						|
// SourceReference is a struct to hold source reference information
 | 
						|
type SourceReference struct {
 | 
						|
	path    string
 | 
						|
	line    int
 | 
						|
	context string
 | 
						|
	trans   *Translation
 | 
						|
}
 | 
						|
 | 
						|
func extractPathAndLine(ref string) (string, int) {
 | 
						|
	var path string
 | 
						|
	var line int
 | 
						|
	colonIdx := strings.IndexRune(ref, ':')
 | 
						|
	if colonIdx >= 0 {
 | 
						|
		path = ref[:colonIdx]
 | 
						|
		line, _ = strconv.Atoi(ref[colonIdx+1:])
 | 
						|
	} else {
 | 
						|
		path = ref
 | 
						|
		line = 0
 | 
						|
	}
 | 
						|
	return path, line
 | 
						|
}
 | 
						|
 | 
						|
// MarshalText implements encoding.TextMarshaler interface
 | 
						|
// Assists round-trip of POT/PO content
 | 
						|
func (do *Domain) MarshalText() ([]byte, error) {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	if len(do.headerComments) > 0 {
 | 
						|
		buf.WriteString(strings.Join(do.headerComments, "\n"))
 | 
						|
		buf.WriteByte(byte('\n'))
 | 
						|
	}
 | 
						|
	buf.WriteString("msgid \"\"\nmsgstr \"\"")
 | 
						|
 | 
						|
	// Standard order consistent with xgettext
 | 
						|
	headerOrder := map[string]int{
 | 
						|
		"project-id-version":        0,
 | 
						|
		"report-msgid-bugs-to":      1,
 | 
						|
		"pot-creation-date":         2,
 | 
						|
		"po-revision-date":          3,
 | 
						|
		"last-translator":           4,
 | 
						|
		"language-team":             5,
 | 
						|
		"language":                  6,
 | 
						|
		"mime-version":              7,
 | 
						|
		"content-type":              9,
 | 
						|
		"content-transfer-encoding": 10,
 | 
						|
		"plural-forms":              11,
 | 
						|
	}
 | 
						|
 | 
						|
	headerKeys := make([]string, 0, len(do.Headers))
 | 
						|
 | 
						|
	for k := range do.Headers {
 | 
						|
		headerKeys = append(headerKeys, k)
 | 
						|
	}
 | 
						|
 | 
						|
	sort.Slice(headerKeys, func(i, j int) bool {
 | 
						|
		var iOrder int
 | 
						|
		var jOrder int
 | 
						|
		var ok bool
 | 
						|
		if iOrder, ok = headerOrder[strings.ToLower(headerKeys[i])]; !ok {
 | 
						|
			iOrder = 8
 | 
						|
		}
 | 
						|
 | 
						|
		if jOrder, ok = headerOrder[strings.ToLower(headerKeys[j])]; !ok {
 | 
						|
			jOrder = 8
 | 
						|
		}
 | 
						|
 | 
						|
		if iOrder < jOrder {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		if iOrder > jOrder {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return headerKeys[i] < headerKeys[j]
 | 
						|
	})
 | 
						|
 | 
						|
	for _, k := range headerKeys {
 | 
						|
		// Access Headers map directly so as not to canonicalise
 | 
						|
		v := do.Headers[k]
 | 
						|
 | 
						|
		for _, value := range v {
 | 
						|
			buf.WriteString("\n\"" + k + ": " + value + "\\n\"")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Just as with headers, output translations in consistent order (to minimise diffs between round-trips), with (first) source reference taking priority, followed by context and finally ID
 | 
						|
	references := make([]SourceReference, 0)
 | 
						|
	for name, ctx := range do.contextTranslations {
 | 
						|
		for id, trans := range ctx {
 | 
						|
			if id == "" {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if len(trans.Refs) > 0 {
 | 
						|
				path, line := extractPathAndLine(trans.Refs[0])
 | 
						|
				references = append(references, SourceReference{
 | 
						|
					path,
 | 
						|
					line,
 | 
						|
					name,
 | 
						|
					trans,
 | 
						|
				})
 | 
						|
			} else {
 | 
						|
				references = append(references, SourceReference{
 | 
						|
					"",
 | 
						|
					0,
 | 
						|
					name,
 | 
						|
					trans,
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for id, trans := range do.translations {
 | 
						|
		if id == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if len(trans.Refs) > 0 {
 | 
						|
			path, line := extractPathAndLine(trans.Refs[0])
 | 
						|
			references = append(references, SourceReference{
 | 
						|
				path,
 | 
						|
				line,
 | 
						|
				"",
 | 
						|
				trans,
 | 
						|
			})
 | 
						|
		} else {
 | 
						|
			references = append(references, SourceReference{
 | 
						|
				"",
 | 
						|
				0,
 | 
						|
				"",
 | 
						|
				trans,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	sort.Slice(references, func(i, j int) bool {
 | 
						|
		if references[i].path < references[j].path {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		if references[i].path > references[j].path {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if references[i].line < references[j].line {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		if references[i].line > references[j].line {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		if references[i].context < references[j].context {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		if references[i].context > references[j].context {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return references[i].trans.ID < references[j].trans.ID
 | 
						|
	})
 | 
						|
 | 
						|
	for _, ref := range references {
 | 
						|
		trans := ref.trans
 | 
						|
		if len(trans.Refs) > 0 {
 | 
						|
			buf.WriteString("\n\n#: " + strings.Join(trans.Refs, " "))
 | 
						|
		} else {
 | 
						|
			buf.WriteByte(byte('\n'))
 | 
						|
		}
 | 
						|
 | 
						|
		if ref.context == "" {
 | 
						|
			buf.WriteString("\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
 | 
						|
		} else {
 | 
						|
			buf.WriteString("\nmsgctxt \"" + EscapeSpecialCharacters(ref.context) + "\"\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
 | 
						|
		}
 | 
						|
 | 
						|
		if trans.PluralID == "" {
 | 
						|
			buf.WriteString("\nmsgstr \"" + EscapeSpecialCharacters(trans.Trs[0]) + "\"")
 | 
						|
		} else {
 | 
						|
			buf.WriteString("\nmsgid_plural \"" + trans.PluralID + "\"")
 | 
						|
			for i, tr := range trans.Trs {
 | 
						|
				buf.WriteString("\nmsgstr[" + EscapeSpecialCharacters(strconv.Itoa(i)) + "] \"" + tr + "\"")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return buf.Bytes(), nil
 | 
						|
}
 | 
						|
 | 
						|
// EscapeSpecialCharacters escapes special characters in a string
 | 
						|
func EscapeSpecialCharacters(s string) string {
 | 
						|
	s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
 | 
						|
 | 
						|
	if strings.Count(s, "\n") == 0 {
 | 
						|
		return s
 | 
						|
	}
 | 
						|
 | 
						|
	// Handle EOL and multi-lines
 | 
						|
	// Only one line, but finishing with \n
 | 
						|
	if strings.Count(s, "\n") == 1 && strings.HasSuffix(s, "\n") {
 | 
						|
		return strings.ReplaceAll(s, "\n", "\\n")
 | 
						|
	}
 | 
						|
 | 
						|
	elems := strings.Split(s, "\n")
 | 
						|
	// Skip last element for multiline which is an empty
 | 
						|
	var shouldEndWithEOL bool
 | 
						|
	if elems[len(elems)-1] == "" {
 | 
						|
		elems = elems[:len(elems)-1]
 | 
						|
		shouldEndWithEOL = true
 | 
						|
	}
 | 
						|
	data := []string{(`"`)}
 | 
						|
	for i, v := range elems {
 | 
						|
		l := fmt.Sprintf(`"%s\n"`, v)
 | 
						|
		// Last element without EOL
 | 
						|
		if i == len(elems)-1 && !shouldEndWithEOL {
 | 
						|
			l = fmt.Sprintf(`"%s"`, v)
 | 
						|
		}
 | 
						|
		// Remove finale " to last element as the whole string will be quoted
 | 
						|
		if i == len(elems)-1 {
 | 
						|
			l = strings.TrimSuffix(l, `"`)
 | 
						|
		}
 | 
						|
		data = append(data, l)
 | 
						|
	}
 | 
						|
	return strings.Join(data, "\n")
 | 
						|
}
 | 
						|
 | 
						|
// MarshalBinary implements encoding.BinaryMarshaler interface
 | 
						|
func (do *Domain) MarshalBinary() ([]byte, error) {
 | 
						|
	obj := new(TranslatorEncoding)
 | 
						|
	obj.Headers = do.Headers
 | 
						|
	obj.Language = do.Language
 | 
						|
	obj.PluralForms = do.PluralForms
 | 
						|
	obj.Nplurals = do.nplurals
 | 
						|
	obj.Plural = do.plural
 | 
						|
	obj.Translations = do.translations
 | 
						|
	obj.Contexts = do.contextTranslations
 | 
						|
 | 
						|
	var buff bytes.Buffer
 | 
						|
	encoder := gob.NewEncoder(&buff)
 | 
						|
	err := encoder.Encode(obj)
 | 
						|
 | 
						|
	return buff.Bytes(), err
 | 
						|
}
 | 
						|
 | 
						|
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
 | 
						|
func (do *Domain) UnmarshalBinary(data []byte) error {
 | 
						|
	buff := bytes.NewBuffer(data)
 | 
						|
	obj := new(TranslatorEncoding)
 | 
						|
 | 
						|
	decoder := gob.NewDecoder(buff)
 | 
						|
	err := decoder.Decode(obj)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	do.Headers = obj.Headers
 | 
						|
	do.Language = obj.Language
 | 
						|
	do.PluralForms = obj.PluralForms
 | 
						|
	do.nplurals = obj.Nplurals
 | 
						|
	do.plural = obj.Plural
 | 
						|
	do.translations = obj.Translations
 | 
						|
	do.contextTranslations = obj.Contexts
 | 
						|
 | 
						|
	if expr, err := plurals.Compile(do.plural); err == nil {
 | 
						|
		do.pluralforms = expr
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |