...

Source file src/github.com/go-playground/universal-translator/translator.go

Documentation: github.com/go-playground/universal-translator

     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  

View as plain text