gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

translator.go (12829B)


      1 package ut
      2 
      3 import (
      4 	"fmt"
      5 	"strconv"
      6 	"strings"
      7 
      8 	"github.com/go-playground/locales"
      9 )
     10 
     11 const (
     12 	paramZero          = "{0}"
     13 	paramOne           = "{1}"
     14 	unknownTranslation = ""
     15 )
     16 
     17 // Translator is universal translators
     18 // translator instance which is a thin wrapper
     19 // around locales.Translator instance providing
     20 // some extra functionality
     21 type Translator interface {
     22 	locales.Translator
     23 
     24 	// adds a normal translation for a particular language/locale
     25 	// {#} is the only replacement type accepted and are ad infinitum
     26 	// eg. one: '{0} day left' other: '{0} days left'
     27 	Add(key interface{}, text string, override bool) error
     28 
     29 	// adds a cardinal plural translation for a particular language/locale
     30 	// {0} is the only replacement type accepted and only one variable is accepted as
     31 	// multiple cannot be used for a plural rule determination, unless it is a range;
     32 	// see AddRange below.
     33 	// eg. in locale 'en' one: '{0} day left' other: '{0} days left'
     34 	AddCardinal(key interface{}, text string, rule locales.PluralRule, override bool) error
     35 
     36 	// adds an ordinal plural translation for a particular language/locale
     37 	// {0} is the only replacement type accepted and only one variable is accepted as
     38 	// multiple cannot be used for a plural rule determination, unless it is a range;
     39 	// see AddRange below.
     40 	// eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring'
     41 	// - 1st, 2nd, 3rd...
     42 	AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error
     43 
     44 	// adds a range plural translation for a particular language/locale
     45 	// {0} and {1} are the only replacement types accepted and only these are accepted.
     46 	// eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
     47 	AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error
     48 
     49 	// creates the translation for the locale given the 'key' and params passed in
     50 	T(key interface{}, params ...string) (string, error)
     51 
     52 	// creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments
     53 	//  and param passed in
     54 	C(key interface{}, num float64, digits uint64, param string) (string, error)
     55 
     56 	// creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments
     57 	// and param passed in
     58 	O(key interface{}, num float64, digits uint64, param string) (string, error)
     59 
     60 	//  creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and
     61 	//  'digit2' arguments and 'param1' and 'param2' passed in
     62 	R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error)
     63 
     64 	// VerifyTranslations checks to ensures that no plural rules have been
     65 	// missed within the translations.
     66 	VerifyTranslations() error
     67 }
     68 
     69 var _ Translator = new(translator)
     70 var _ locales.Translator = new(translator)
     71 
     72 type translator struct {
     73 	locales.Translator
     74 	translations        map[interface{}]*transText
     75 	cardinalTanslations map[interface{}][]*transText // array index is mapped to locales.PluralRule index + the locales.PluralRuleUnknown
     76 	ordinalTanslations  map[interface{}][]*transText
     77 	rangeTanslations    map[interface{}][]*transText
     78 }
     79 
     80 type transText struct {
     81 	text    string
     82 	indexes []int
     83 }
     84 
     85 func newTranslator(trans locales.Translator) Translator {
     86 	return &translator{
     87 		Translator:          trans,
     88 		translations:        make(map[interface{}]*transText), // translation text broken up by byte index
     89 		cardinalTanslations: make(map[interface{}][]*transText),
     90 		ordinalTanslations:  make(map[interface{}][]*transText),
     91 		rangeTanslations:    make(map[interface{}][]*transText),
     92 	}
     93 }
     94 
     95 // Add adds a normal translation for a particular language/locale
     96 // {#} is the only replacement type accepted and are ad infinitum
     97 // eg. one: '{0} day left' other: '{0} days left'
     98 func (t *translator) Add(key interface{}, text string, override bool) error {
     99 
    100 	if _, ok := t.translations[key]; ok && !override {
    101 		return &ErrConflictingTranslation{locale: t.Locale(), key: key, text: text}
    102 	}
    103 
    104 	lb := strings.Count(text, "{")
    105 	rb := strings.Count(text, "}")
    106 
    107 	if lb != rb {
    108 		return &ErrMissingBracket{locale: t.Locale(), key: key, text: text}
    109 	}
    110 
    111 	trans := &transText{
    112 		text: text,
    113 	}
    114 
    115 	var idx int
    116 
    117 	for i := 0; i < lb; i++ {
    118 		s := "{" + strconv.Itoa(i) + "}"
    119 		idx = strings.Index(text, s)
    120 		if idx == -1 {
    121 			return &ErrBadParamSyntax{locale: t.Locale(), param: s, key: key, text: text}
    122 		}
    123 
    124 		trans.indexes = append(trans.indexes, idx)
    125 		trans.indexes = append(trans.indexes, idx+len(s))
    126 	}
    127 
    128 	t.translations[key] = trans
    129 
    130 	return nil
    131 }
    132 
    133 // AddCardinal adds a cardinal plural translation for a particular language/locale
    134 // {0} is the only replacement type accepted and only one variable is accepted as
    135 // multiple cannot be used for a plural rule determination, unless it is a range;
    136 // see AddRange below.
    137 // eg. in locale 'en' one: '{0} day left' other: '{0} days left'
    138 func (t *translator) AddCardinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
    139 
    140 	var verified bool
    141 
    142 	// verify plural rule exists for locale
    143 	for _, pr := range t.PluralsCardinal() {
    144 		if pr == rule {
    145 			verified = true
    146 			break
    147 		}
    148 	}
    149 
    150 	if !verified {
    151 		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)}
    152 	}
    153 
    154 	tarr, ok := t.cardinalTanslations[key]
    155 	if ok {
    156 		// verify not adding a conflicting record
    157 		if len(tarr) > 0 && tarr[rule] != nil && !override {
    158 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
    159 		}
    160 
    161 	} else {
    162 		tarr = make([]*transText, 7)
    163 		t.cardinalTanslations[key] = tarr
    164 	}
    165 
    166 	trans := &transText{
    167 		text:    text,
    168 		indexes: make([]int, 2),
    169 	}
    170 
    171 	tarr[rule] = trans
    172 
    173 	idx := strings.Index(text, paramZero)
    174 	if idx == -1 {
    175 		tarr[rule] = nil
    176 		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)}
    177 	}
    178 
    179 	trans.indexes[0] = idx
    180 	trans.indexes[1] = idx + len(paramZero)
    181 
    182 	return nil
    183 }
    184 
    185 // AddOrdinal adds an ordinal plural translation for a particular language/locale
    186 // {0} is the only replacement type accepted and only one variable is accepted as
    187 // multiple cannot be used for a plural rule determination, unless it is a range;
    188 // see AddRange below.
    189 // eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring' - 1st, 2nd, 3rd...
    190 func (t *translator) AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
    191 
    192 	var verified bool
    193 
    194 	// verify plural rule exists for locale
    195 	for _, pr := range t.PluralsOrdinal() {
    196 		if pr == rule {
    197 			verified = true
    198 			break
    199 		}
    200 	}
    201 
    202 	if !verified {
    203 		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)}
    204 	}
    205 
    206 	tarr, ok := t.ordinalTanslations[key]
    207 	if ok {
    208 		// verify not adding a conflicting record
    209 		if len(tarr) > 0 && tarr[rule] != nil && !override {
    210 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
    211 		}
    212 
    213 	} else {
    214 		tarr = make([]*transText, 7)
    215 		t.ordinalTanslations[key] = tarr
    216 	}
    217 
    218 	trans := &transText{
    219 		text:    text,
    220 		indexes: make([]int, 2),
    221 	}
    222 
    223 	tarr[rule] = trans
    224 
    225 	idx := strings.Index(text, paramZero)
    226 	if idx == -1 {
    227 		tarr[rule] = nil
    228 		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)}
    229 	}
    230 
    231 	trans.indexes[0] = idx
    232 	trans.indexes[1] = idx + len(paramZero)
    233 
    234 	return nil
    235 }
    236 
    237 // AddRange adds a range plural translation for a particular language/locale
    238 // {0} and {1} are the only replacement types accepted and only these are accepted.
    239 // eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
    240 func (t *translator) AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error {
    241 
    242 	var verified bool
    243 
    244 	// verify plural rule exists for locale
    245 	for _, pr := range t.PluralsRange() {
    246 		if pr == rule {
    247 			verified = true
    248 			break
    249 		}
    250 	}
    251 
    252 	if !verified {
    253 		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)}
    254 	}
    255 
    256 	tarr, ok := t.rangeTanslations[key]
    257 	if ok {
    258 		// verify not adding a conflicting record
    259 		if len(tarr) > 0 && tarr[rule] != nil && !override {
    260 			return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
    261 		}
    262 
    263 	} else {
    264 		tarr = make([]*transText, 7)
    265 		t.rangeTanslations[key] = tarr
    266 	}
    267 
    268 	trans := &transText{
    269 		text:    text,
    270 		indexes: make([]int, 4),
    271 	}
    272 
    273 	tarr[rule] = trans
    274 
    275 	idx := strings.Index(text, paramZero)
    276 	if idx == -1 {
    277 		tarr[rule] = nil
    278 		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)}
    279 	}
    280 
    281 	trans.indexes[0] = idx
    282 	trans.indexes[1] = idx + len(paramZero)
    283 
    284 	idx = strings.Index(text, paramOne)
    285 	if idx == -1 {
    286 		tarr[rule] = nil
    287 		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)}
    288 	}
    289 
    290 	trans.indexes[2] = idx
    291 	trans.indexes[3] = idx + len(paramOne)
    292 
    293 	return nil
    294 }
    295 
    296 // T creates the translation for the locale given the 'key' and params passed in
    297 func (t *translator) T(key interface{}, params ...string) (string, error) {
    298 
    299 	trans, ok := t.translations[key]
    300 	if !ok {
    301 		return unknownTranslation, ErrUnknowTranslation
    302 	}
    303 
    304 	b := make([]byte, 0, 64)
    305 
    306 	var start, end, count int
    307 
    308 	for i := 0; i < len(trans.indexes); i++ {
    309 		end = trans.indexes[i]
    310 		b = append(b, trans.text[start:end]...)
    311 		b = append(b, params[count]...)
    312 		i++
    313 		start = trans.indexes[i]
    314 		count++
    315 	}
    316 
    317 	b = append(b, trans.text[start:]...)
    318 
    319 	return string(b), nil
    320 }
    321 
    322 // C creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
    323 func (t *translator) C(key interface{}, num float64, digits uint64, param string) (string, error) {
    324 
    325 	tarr, ok := t.cardinalTanslations[key]
    326 	if !ok {
    327 		return unknownTranslation, ErrUnknowTranslation
    328 	}
    329 
    330 	rule := t.CardinalPluralRule(num, digits)
    331 
    332 	trans := tarr[rule]
    333 
    334 	b := make([]byte, 0, 64)
    335 	b = append(b, trans.text[:trans.indexes[0]]...)
    336 	b = append(b, param...)
    337 	b = append(b, trans.text[trans.indexes[1]:]...)
    338 
    339 	return string(b), nil
    340 }
    341 
    342 // O creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
    343 func (t *translator) O(key interface{}, num float64, digits uint64, param string) (string, error) {
    344 
    345 	tarr, ok := t.ordinalTanslations[key]
    346 	if !ok {
    347 		return unknownTranslation, ErrUnknowTranslation
    348 	}
    349 
    350 	rule := t.OrdinalPluralRule(num, digits)
    351 
    352 	trans := tarr[rule]
    353 
    354 	b := make([]byte, 0, 64)
    355 	b = append(b, trans.text[:trans.indexes[0]]...)
    356 	b = append(b, param...)
    357 	b = append(b, trans.text[trans.indexes[1]:]...)
    358 
    359 	return string(b), nil
    360 }
    361 
    362 // R creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and 'digit2' arguments
    363 // and 'param1' and 'param2' passed in
    364 func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error) {
    365 
    366 	tarr, ok := t.rangeTanslations[key]
    367 	if !ok {
    368 		return unknownTranslation, ErrUnknowTranslation
    369 	}
    370 
    371 	rule := t.RangePluralRule(num1, digits1, num2, digits2)
    372 
    373 	trans := tarr[rule]
    374 
    375 	b := make([]byte, 0, 64)
    376 	b = append(b, trans.text[:trans.indexes[0]]...)
    377 	b = append(b, param1...)
    378 	b = append(b, trans.text[trans.indexes[1]:trans.indexes[2]]...)
    379 	b = append(b, param2...)
    380 	b = append(b, trans.text[trans.indexes[3]:]...)
    381 
    382 	return string(b), nil
    383 }
    384 
    385 // VerifyTranslations checks to ensures that no plural rules have been
    386 // missed within the translations.
    387 func (t *translator) VerifyTranslations() error {
    388 
    389 	for k, v := range t.cardinalTanslations {
    390 
    391 		for _, rule := range t.PluralsCardinal() {
    392 
    393 			if v[rule] == nil {
    394 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k}
    395 			}
    396 		}
    397 	}
    398 
    399 	for k, v := range t.ordinalTanslations {
    400 
    401 		for _, rule := range t.PluralsOrdinal() {
    402 
    403 			if v[rule] == nil {
    404 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k}
    405 			}
    406 		}
    407 	}
    408 
    409 	for k, v := range t.rangeTanslations {
    410 
    411 		for _, rule := range t.PluralsRange() {
    412 
    413 			if v[rule] == nil {
    414 				return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k}
    415 			}
    416 		}
    417 	}
    418 
    419 	return nil
    420 }