8 "github.com/go-playground/locales"
14 unknownTranslation = ""
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 {
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
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
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'
42 AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error
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
49 // creates the translation for the locale given the 'key' and params passed in
50 T(key interface{}, params ...string) (string, error)
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)
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)
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)
64 // VerifyTranslations checks to ensures that no plural rules have been
65 // missed within the translations.
66 VerifyTranslations() error
69 var _ Translator = new(translator)
70 var _ locales.Translator = new(translator)
72 type translator struct {
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
80 type transText struct {
85 func newTranslator(trans locales.Translator) Translator {
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),
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 {
100 if _, ok := t.translations[key]; ok && !override {
101 return &ErrConflictingTranslation{locale: t.Locale(), key: key, text: text}
104 lb := strings.Count(text, "{")
105 rb := strings.Count(text, "}")
108 return &ErrMissingBracket{locale: t.Locale(), key: key, text: text}
117 for i := 0; i < lb; i++ {
118 s := "{" + strconv.Itoa(i) + "}"
119 idx = strings.Index(text, s)
121 return &ErrBadParamSyntax{locale: t.Locale(), param: s, key: key, text: text}
124 trans.indexes = append(trans.indexes, idx)
125 trans.indexes = append(trans.indexes, idx+len(s))
128 t.translations[key] = trans
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 {
142 // verify plural rule exists for locale
143 for _, pr := range t.PluralsCardinal() {
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)}
154 tarr, ok := t.cardinalTanslations[key]
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}
162 tarr = make([]*transText, 7, 7)
163 t.cardinalTanslations[key] = tarr
168 indexes: make([]int, 2, 2),
173 idx := strings.Index(text, paramZero)
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)}
179 trans.indexes[0] = idx
180 trans.indexes[1] = idx + len(paramZero)
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 {
194 // verify plural rule exists for locale
195 for _, pr := range t.PluralsOrdinal() {
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)}
206 tarr, ok := t.ordinalTanslations[key]
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}
214 tarr = make([]*transText, 7, 7)
215 t.ordinalTanslations[key] = tarr
220 indexes: make([]int, 2, 2),
225 idx := strings.Index(text, paramZero)
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)}
231 trans.indexes[0] = idx
232 trans.indexes[1] = idx + len(paramZero)
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 {
244 // verify plural rule exists for locale
245 for _, pr := range t.PluralsRange() {
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)}
256 tarr, ok := t.rangeTanslations[key]
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}
264 tarr = make([]*transText, 7, 7)
265 t.rangeTanslations[key] = tarr
270 indexes: make([]int, 4, 4),
275 idx := strings.Index(text, paramZero)
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)}
281 trans.indexes[0] = idx
282 trans.indexes[1] = idx + len(paramZero)
284 idx = strings.Index(text, paramOne)
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)}
290 trans.indexes[2] = idx
291 trans.indexes[3] = idx + len(paramOne)
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) {
299 trans, ok := t.translations[key]
301 return unknownTranslation, ErrUnknowTranslation
304 b := make([]byte, 0, 64)
306 var start, end, count int
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]...)
313 start = trans.indexes[i]
317 b = append(b, trans.text[start:]...)
319 return string(b), nil
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) {
325 tarr, ok := t.cardinalTanslations[key]
327 return unknownTranslation, ErrUnknowTranslation
330 rule := t.CardinalPluralRule(num, digits)
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]:]...)
339 return string(b), nil
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) {
345 tarr, ok := t.ordinalTanslations[key]
347 return unknownTranslation, ErrUnknowTranslation
350 rule := t.OrdinalPluralRule(num, digits)
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]:]...)
359 return string(b), nil
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) {
366 tarr, ok := t.rangeTanslations[key]
368 return unknownTranslation, ErrUnknowTranslation
371 rule := t.RangePluralRule(num1, digits1, num2, digits2)
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]:]...)
382 return string(b), nil
385 // VerifyTranslations checks to ensures that no plural rules have been
386 // missed within the translations.
387 func (t *translator) VerifyTranslations() error {
389 for k, v := range t.cardinalTanslations {
391 for _, rule := range t.PluralsCardinal() {
394 return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k}
399 for k, v := range t.ordinalTanslations {
401 for _, rule := range t.PluralsOrdinal() {
404 return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k}
409 for k, v := range t.rangeTanslations {
411 for _, rule := range t.PluralsRange() {
414 return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k}