mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 17:02:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			420 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			420 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ut
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/go-playground/locales"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	paramZero          = "{0}"
 | |
| 	paramOne           = "{1}"
 | |
| 	unknownTranslation = ""
 | |
| )
 | |
| 
 | |
| // Translator is universal translators
 | |
| // translator instance which is a thin wrapper
 | |
| // around locales.Translator instance providing
 | |
| // some extra functionality
 | |
| type Translator interface {
 | |
| 	locales.Translator
 | |
| 
 | |
| 	// adds a normal translation for a particular language/locale
 | |
| 	// {#} is the only replacement type accepted and are ad infinitum
 | |
| 	// eg. one: '{0} day left' other: '{0} days left'
 | |
| 	Add(key interface{}, text string, override bool) error
 | |
| 
 | |
| 	// adds a cardinal plural translation for a particular language/locale
 | |
| 	// {0} is the only replacement type accepted and only one variable is accepted as
 | |
| 	// multiple cannot be used for a plural rule determination, unless it is a range;
 | |
| 	// see AddRange below.
 | |
| 	// eg. in locale 'en' one: '{0} day left' other: '{0} days left'
 | |
| 	AddCardinal(key interface{}, text string, rule locales.PluralRule, override bool) error
 | |
| 
 | |
| 	// adds an ordinal plural translation for a particular language/locale
 | |
| 	// {0} is the only replacement type accepted and only one variable is accepted as
 | |
| 	// multiple cannot be used for a plural rule determination, unless it is a range;
 | |
| 	// see AddRange below.
 | |
| 	// eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring'
 | |
| 	// - 1st, 2nd, 3rd...
 | |
| 	AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error
 | |
| 
 | |
| 	// adds a range plural translation for a particular language/locale
 | |
| 	// {0} and {1} are the only replacement types accepted and only these are accepted.
 | |
| 	// eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
 | |
| 	AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error
 | |
| 
 | |
| 	// creates the translation for the locale given the 'key' and params passed in
 | |
| 	T(key interface{}, params ...string) (string, error)
 | |
| 
 | |
| 	// creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments
 | |
| 	//  and param passed in
 | |
| 	C(key interface{}, num float64, digits uint64, param string) (string, error)
 | |
| 
 | |
| 	// creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments
 | |
| 	// and param passed in
 | |
| 	O(key interface{}, num float64, digits uint64, param string) (string, error)
 | |
| 
 | |
| 	//  creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and
 | |
| 	//  'digit2' arguments and 'param1' and 'param2' passed in
 | |
| 	R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error)
 | |
| 
 | |
| 	// VerifyTranslations checks to ensures that no plural rules have been
 | |
| 	// missed within the translations.
 | |
| 	VerifyTranslations() error
 | |
| }
 | |
| 
 | |
| var _ Translator = new(translator)
 | |
| var _ locales.Translator = new(translator)
 | |
| 
 | |
| type translator struct {
 | |
| 	locales.Translator
 | |
| 	translations        map[interface{}]*transText
 | |
| 	cardinalTanslations map[interface{}][]*transText // array index is mapped to locales.PluralRule index + the locales.PluralRuleUnknown
 | |
| 	ordinalTanslations  map[interface{}][]*transText
 | |
| 	rangeTanslations    map[interface{}][]*transText
 | |
| }
 | |
| 
 | |
| type transText struct {
 | |
| 	text    string
 | |
| 	indexes []int
 | |
| }
 | |
| 
 | |
| func newTranslator(trans locales.Translator) Translator {
 | |
| 	return &translator{
 | |
| 		Translator:          trans,
 | |
| 		translations:        make(map[interface{}]*transText), // translation text broken up by byte index
 | |
| 		cardinalTanslations: make(map[interface{}][]*transText),
 | |
| 		ordinalTanslations:  make(map[interface{}][]*transText),
 | |
| 		rangeTanslations:    make(map[interface{}][]*transText),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Add adds a normal translation for a particular language/locale
 | |
| // {#} is the only replacement type accepted and are ad infinitum
 | |
| // eg. one: '{0} day left' other: '{0} days left'
 | |
| func (t *translator) Add(key interface{}, text string, override bool) error {
 | |
| 
 | |
| 	if _, ok := t.translations[key]; ok && !override {
 | |
| 		return &ErrConflictingTranslation{locale: t.Locale(), key: key, text: text}
 | |
| 	}
 | |
| 
 | |
| 	lb := strings.Count(text, "{")
 | |
| 	rb := strings.Count(text, "}")
 | |
| 
 | |
| 	if lb != rb {
 | |
| 		return &ErrMissingBracket{locale: t.Locale(), key: key, text: text}
 | |
| 	}
 | |
| 
 | |
| 	trans := &transText{
 | |
| 		text: text,
 | |
| 	}
 | |
| 
 | |
| 	var idx int
 | |
| 
 | |
| 	for i := 0; i < lb; i++ {
 | |
| 		s := "{" + strconv.Itoa(i) + "}"
 | |
| 		idx = strings.Index(text, s)
 | |
| 		if idx == -1 {
 | |
| 			return &ErrBadParamSyntax{locale: t.Locale(), param: s, key: key, text: text}
 | |
| 		}
 | |
| 
 | |
| 		trans.indexes = append(trans.indexes, idx)
 | |
| 		trans.indexes = append(trans.indexes, idx+len(s))
 | |
| 	}
 | |
| 
 | |
| 	t.translations[key] = trans
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // AddCardinal adds a cardinal plural translation for a particular language/locale
 | |
| // {0} is the only replacement type accepted and only one variable is accepted as
 | |
| // multiple cannot be used for a plural rule determination, unless it is a range;
 | |
| // see AddRange below.
 | |
| // eg. in locale 'en' one: '{0} day left' other: '{0} days left'
 | |
| func (t *translator) AddCardinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
 | |
| 
 | |
| 	var verified bool
 | |
| 
 | |
| 	// verify plural rule exists for locale
 | |
| 	for _, pr := range t.PluralsCardinal() {
 | |
| 		if pr == rule {
 | |
| 			verified = true
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !verified {
 | |
| 		return &ErrCardinalTranslation{text: fmt.Sprintf("error: cardinal plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	tarr, ok := t.cardinalTanslations[key]
 | |
| 	if ok {
 | |
| 		// verify not adding a conflicting record
 | |
| 		if len(tarr) > 0 && tarr[rule] != nil && !override {
 | |
| 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
 | |
| 		}
 | |
| 
 | |
| 	} else {
 | |
| 		tarr = make([]*transText, 7)
 | |
| 		t.cardinalTanslations[key] = tarr
 | |
| 	}
 | |
| 
 | |
| 	trans := &transText{
 | |
| 		text:    text,
 | |
| 		indexes: make([]int, 2),
 | |
| 	}
 | |
| 
 | |
| 	tarr[rule] = trans
 | |
| 
 | |
| 	idx := strings.Index(text, paramZero)
 | |
| 	if idx == -1 {
 | |
| 		tarr[rule] = nil
 | |
| 		return &ErrCardinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddCardinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	trans.indexes[0] = idx
 | |
| 	trans.indexes[1] = idx + len(paramZero)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // AddOrdinal adds an ordinal plural translation for a particular language/locale
 | |
| // {0} is the only replacement type accepted and only one variable is accepted as
 | |
| // multiple cannot be used for a plural rule determination, unless it is a range;
 | |
| // see AddRange below.
 | |
| // eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring' - 1st, 2nd, 3rd...
 | |
| func (t *translator) AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
 | |
| 
 | |
| 	var verified bool
 | |
| 
 | |
| 	// verify plural rule exists for locale
 | |
| 	for _, pr := range t.PluralsOrdinal() {
 | |
| 		if pr == rule {
 | |
| 			verified = true
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !verified {
 | |
| 		return &ErrOrdinalTranslation{text: fmt.Sprintf("error: ordinal plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	tarr, ok := t.ordinalTanslations[key]
 | |
| 	if ok {
 | |
| 		// verify not adding a conflicting record
 | |
| 		if len(tarr) > 0 && tarr[rule] != nil && !override {
 | |
| 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
 | |
| 		}
 | |
| 
 | |
| 	} else {
 | |
| 		tarr = make([]*transText, 7)
 | |
| 		t.ordinalTanslations[key] = tarr
 | |
| 	}
 | |
| 
 | |
| 	trans := &transText{
 | |
| 		text:    text,
 | |
| 		indexes: make([]int, 2),
 | |
| 	}
 | |
| 
 | |
| 	tarr[rule] = trans
 | |
| 
 | |
| 	idx := strings.Index(text, paramZero)
 | |
| 	if idx == -1 {
 | |
| 		tarr[rule] = nil
 | |
| 		return &ErrOrdinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddOrdinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	trans.indexes[0] = idx
 | |
| 	trans.indexes[1] = idx + len(paramZero)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // AddRange adds a range plural translation for a particular language/locale
 | |
| // {0} and {1} are the only replacement types accepted and only these are accepted.
 | |
| // eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
 | |
| func (t *translator) AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error {
 | |
| 
 | |
| 	var verified bool
 | |
| 
 | |
| 	// verify plural rule exists for locale
 | |
| 	for _, pr := range t.PluralsRange() {
 | |
| 		if pr == rule {
 | |
| 			verified = true
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !verified {
 | |
| 		return &ErrRangeTranslation{text: fmt.Sprintf("error: range plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	tarr, ok := t.rangeTanslations[key]
 | |
| 	if ok {
 | |
| 		// verify not adding a conflicting record
 | |
| 		if len(tarr) > 0 && tarr[rule] != nil && !override {
 | |
| 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
 | |
| 		}
 | |
| 
 | |
| 	} else {
 | |
| 		tarr = make([]*transText, 7)
 | |
| 		t.rangeTanslations[key] = tarr
 | |
| 	}
 | |
| 
 | |
| 	trans := &transText{
 | |
| 		text:    text,
 | |
| 		indexes: make([]int, 4),
 | |
| 	}
 | |
| 
 | |
| 	tarr[rule] = trans
 | |
| 
 | |
| 	idx := strings.Index(text, paramZero)
 | |
| 	if idx == -1 {
 | |
| 		tarr[rule] = nil
 | |
| 		return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, are you sure you're adding a Range Translation? locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	trans.indexes[0] = idx
 | |
| 	trans.indexes[1] = idx + len(paramZero)
 | |
| 
 | |
| 	idx = strings.Index(text, paramOne)
 | |
| 	if idx == -1 {
 | |
| 		tarr[rule] = nil
 | |
| 		return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, a Range Translation requires two parameters. locale: '%s' key: '%v' text: '%s'", paramOne, t.Locale(), key, text)}
 | |
| 	}
 | |
| 
 | |
| 	trans.indexes[2] = idx
 | |
| 	trans.indexes[3] = idx + len(paramOne)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // T creates the translation for the locale given the 'key' and params passed in
 | |
| func (t *translator) T(key interface{}, params ...string) (string, error) {
 | |
| 
 | |
| 	trans, ok := t.translations[key]
 | |
| 	if !ok {
 | |
| 		return unknownTranslation, ErrUnknowTranslation
 | |
| 	}
 | |
| 
 | |
| 	b := make([]byte, 0, 64)
 | |
| 
 | |
| 	var start, end, count int
 | |
| 
 | |
| 	for i := 0; i < len(trans.indexes); i++ {
 | |
| 		end = trans.indexes[i]
 | |
| 		b = append(b, trans.text[start:end]...)
 | |
| 		b = append(b, params[count]...)
 | |
| 		i++
 | |
| 		start = trans.indexes[i]
 | |
| 		count++
 | |
| 	}
 | |
| 
 | |
| 	b = append(b, trans.text[start:]...)
 | |
| 
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| // C creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
 | |
| func (t *translator) C(key interface{}, num float64, digits uint64, param string) (string, error) {
 | |
| 
 | |
| 	tarr, ok := t.cardinalTanslations[key]
 | |
| 	if !ok {
 | |
| 		return unknownTranslation, ErrUnknowTranslation
 | |
| 	}
 | |
| 
 | |
| 	rule := t.CardinalPluralRule(num, digits)
 | |
| 
 | |
| 	trans := tarr[rule]
 | |
| 
 | |
| 	b := make([]byte, 0, 64)
 | |
| 	b = append(b, trans.text[:trans.indexes[0]]...)
 | |
| 	b = append(b, param...)
 | |
| 	b = append(b, trans.text[trans.indexes[1]:]...)
 | |
| 
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| // O creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
 | |
| func (t *translator) O(key interface{}, num float64, digits uint64, param string) (string, error) {
 | |
| 
 | |
| 	tarr, ok := t.ordinalTanslations[key]
 | |
| 	if !ok {
 | |
| 		return unknownTranslation, ErrUnknowTranslation
 | |
| 	}
 | |
| 
 | |
| 	rule := t.OrdinalPluralRule(num, digits)
 | |
| 
 | |
| 	trans := tarr[rule]
 | |
| 
 | |
| 	b := make([]byte, 0, 64)
 | |
| 	b = append(b, trans.text[:trans.indexes[0]]...)
 | |
| 	b = append(b, param...)
 | |
| 	b = append(b, trans.text[trans.indexes[1]:]...)
 | |
| 
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| // R creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and 'digit2' arguments
 | |
| // and 'param1' and 'param2' passed in
 | |
| func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error) {
 | |
| 
 | |
| 	tarr, ok := t.rangeTanslations[key]
 | |
| 	if !ok {
 | |
| 		return unknownTranslation, ErrUnknowTranslation
 | |
| 	}
 | |
| 
 | |
| 	rule := t.RangePluralRule(num1, digits1, num2, digits2)
 | |
| 
 | |
| 	trans := tarr[rule]
 | |
| 
 | |
| 	b := make([]byte, 0, 64)
 | |
| 	b = append(b, trans.text[:trans.indexes[0]]...)
 | |
| 	b = append(b, param1...)
 | |
| 	b = append(b, trans.text[trans.indexes[1]:trans.indexes[2]]...)
 | |
| 	b = append(b, param2...)
 | |
| 	b = append(b, trans.text[trans.indexes[3]:]...)
 | |
| 
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| // VerifyTranslations checks to ensures that no plural rules have been
 | |
| // missed within the translations.
 | |
| func (t *translator) VerifyTranslations() error {
 | |
| 
 | |
| 	for k, v := range t.cardinalTanslations {
 | |
| 
 | |
| 		for _, rule := range t.PluralsCardinal() {
 | |
| 
 | |
| 			if v[rule] == nil {
 | |
| 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for k, v := range t.ordinalTanslations {
 | |
| 
 | |
| 		for _, rule := range t.PluralsOrdinal() {
 | |
| 
 | |
| 			if v[rule] == nil {
 | |
| 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for k, v := range t.rangeTanslations {
 | |
| 
 | |
| 		for _, rule := range t.PluralsRange() {
 | |
| 
 | |
| 			if v[rule] == nil {
 | |
| 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |