translator.go raw

   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  }
 421