podman
420 строк · 12.5 Кб
1package ut
2
3import (
4"fmt"
5"strconv"
6"strings"
7
8"github.com/go-playground/locales"
9)
10
11const (
12paramZero = "{0}"
13paramOne = "{1}"
14unknownTranslation = ""
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
21type Translator interface {
22locales.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'
27Add(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'
34AddCardinal(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...
42AddOrdinal(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'
47AddRange(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
50T(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
54C(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
58O(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
62R(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.
66VerifyTranslations() error
67}
68
69var _ Translator = new(translator)
70var _ locales.Translator = new(translator)
71
72type translator struct {
73locales.Translator
74translations map[interface{}]*transText
75cardinalTanslations map[interface{}][]*transText // array index is mapped to locales.PluralRule index + the locales.PluralRuleUnknown
76ordinalTanslations map[interface{}][]*transText
77rangeTanslations map[interface{}][]*transText
78}
79
80type transText struct {
81text string
82indexes []int
83}
84
85func newTranslator(trans locales.Translator) Translator {
86return &translator{
87Translator: trans,
88translations: make(map[interface{}]*transText), // translation text broken up by byte index
89cardinalTanslations: make(map[interface{}][]*transText),
90ordinalTanslations: make(map[interface{}][]*transText),
91rangeTanslations: 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'
98func (t *translator) Add(key interface{}, text string, override bool) error {
99
100if _, ok := t.translations[key]; ok && !override {
101return &ErrConflictingTranslation{locale: t.Locale(), key: key, text: text}
102}
103
104lb := strings.Count(text, "{")
105rb := strings.Count(text, "}")
106
107if lb != rb {
108return &ErrMissingBracket{locale: t.Locale(), key: key, text: text}
109}
110
111trans := &transText{
112text: text,
113}
114
115var idx int
116
117for i := 0; i < lb; i++ {
118s := "{" + strconv.Itoa(i) + "}"
119idx = strings.Index(text, s)
120if idx == -1 {
121return &ErrBadParamSyntax{locale: t.Locale(), param: s, key: key, text: text}
122}
123
124trans.indexes = append(trans.indexes, idx)
125trans.indexes = append(trans.indexes, idx+len(s))
126}
127
128t.translations[key] = trans
129
130return 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'
138func (t *translator) AddCardinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
139
140var verified bool
141
142// verify plural rule exists for locale
143for _, pr := range t.PluralsCardinal() {
144if pr == rule {
145verified = true
146break
147}
148}
149
150if !verified {
151return &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
154tarr, ok := t.cardinalTanslations[key]
155if ok {
156// verify not adding a conflicting record
157if len(tarr) > 0 && tarr[rule] != nil && !override {
158return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
159}
160
161} else {
162tarr = make([]*transText, 7)
163t.cardinalTanslations[key] = tarr
164}
165
166trans := &transText{
167text: text,
168indexes: make([]int, 2),
169}
170
171tarr[rule] = trans
172
173idx := strings.Index(text, paramZero)
174if idx == -1 {
175tarr[rule] = nil
176return &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
179trans.indexes[0] = idx
180trans.indexes[1] = idx + len(paramZero)
181
182return 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...
190func (t *translator) AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error {
191
192var verified bool
193
194// verify plural rule exists for locale
195for _, pr := range t.PluralsOrdinal() {
196if pr == rule {
197verified = true
198break
199}
200}
201
202if !verified {
203return &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
206tarr, ok := t.ordinalTanslations[key]
207if ok {
208// verify not adding a conflicting record
209if len(tarr) > 0 && tarr[rule] != nil && !override {
210return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
211}
212
213} else {
214tarr = make([]*transText, 7)
215t.ordinalTanslations[key] = tarr
216}
217
218trans := &transText{
219text: text,
220indexes: make([]int, 2),
221}
222
223tarr[rule] = trans
224
225idx := strings.Index(text, paramZero)
226if idx == -1 {
227tarr[rule] = nil
228return &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
231trans.indexes[0] = idx
232trans.indexes[1] = idx + len(paramZero)
233
234return 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'
240func (t *translator) AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error {
241
242var verified bool
243
244// verify plural rule exists for locale
245for _, pr := range t.PluralsRange() {
246if pr == rule {
247verified = true
248break
249}
250}
251
252if !verified {
253return &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
256tarr, ok := t.rangeTanslations[key]
257if ok {
258// verify not adding a conflicting record
259if len(tarr) > 0 && tarr[rule] != nil && !override {
260return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text}
261}
262
263} else {
264tarr = make([]*transText, 7)
265t.rangeTanslations[key] = tarr
266}
267
268trans := &transText{
269text: text,
270indexes: make([]int, 4),
271}
272
273tarr[rule] = trans
274
275idx := strings.Index(text, paramZero)
276if idx == -1 {
277tarr[rule] = nil
278return &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
281trans.indexes[0] = idx
282trans.indexes[1] = idx + len(paramZero)
283
284idx = strings.Index(text, paramOne)
285if idx == -1 {
286tarr[rule] = nil
287return &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
290trans.indexes[2] = idx
291trans.indexes[3] = idx + len(paramOne)
292
293return nil
294}
295
296// T creates the translation for the locale given the 'key' and params passed in
297func (t *translator) T(key interface{}, params ...string) (string, error) {
298
299trans, ok := t.translations[key]
300if !ok {
301return unknownTranslation, ErrUnknowTranslation
302}
303
304b := make([]byte, 0, 64)
305
306var start, end, count int
307
308for i := 0; i < len(trans.indexes); i++ {
309end = trans.indexes[i]
310b = append(b, trans.text[start:end]...)
311b = append(b, params[count]...)
312i++
313start = trans.indexes[i]
314count++
315}
316
317b = append(b, trans.text[start:]...)
318
319return 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
323func (t *translator) C(key interface{}, num float64, digits uint64, param string) (string, error) {
324
325tarr, ok := t.cardinalTanslations[key]
326if !ok {
327return unknownTranslation, ErrUnknowTranslation
328}
329
330rule := t.CardinalPluralRule(num, digits)
331
332trans := tarr[rule]
333
334b := make([]byte, 0, 64)
335b = append(b, trans.text[:trans.indexes[0]]...)
336b = append(b, param...)
337b = append(b, trans.text[trans.indexes[1]:]...)
338
339return 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
343func (t *translator) O(key interface{}, num float64, digits uint64, param string) (string, error) {
344
345tarr, ok := t.ordinalTanslations[key]
346if !ok {
347return unknownTranslation, ErrUnknowTranslation
348}
349
350rule := t.OrdinalPluralRule(num, digits)
351
352trans := tarr[rule]
353
354b := make([]byte, 0, 64)
355b = append(b, trans.text[:trans.indexes[0]]...)
356b = append(b, param...)
357b = append(b, trans.text[trans.indexes[1]:]...)
358
359return 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
364func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error) {
365
366tarr, ok := t.rangeTanslations[key]
367if !ok {
368return unknownTranslation, ErrUnknowTranslation
369}
370
371rule := t.RangePluralRule(num1, digits1, num2, digits2)
372
373trans := tarr[rule]
374
375b := make([]byte, 0, 64)
376b = append(b, trans.text[:trans.indexes[0]]...)
377b = append(b, param1...)
378b = append(b, trans.text[trans.indexes[1]:trans.indexes[2]]...)
379b = append(b, param2...)
380b = append(b, trans.text[trans.indexes[3]:]...)
381
382return string(b), nil
383}
384
385// VerifyTranslations checks to ensures that no plural rules have been
386// missed within the translations.
387func (t *translator) VerifyTranslations() error {
388
389for k, v := range t.cardinalTanslations {
390
391for _, rule := range t.PluralsCardinal() {
392
393if v[rule] == nil {
394return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k}
395}
396}
397}
398
399for k, v := range t.ordinalTanslations {
400
401for _, rule := range t.PluralsOrdinal() {
402
403if v[rule] == nil {
404return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k}
405}
406}
407}
408
409for k, v := range t.rangeTanslations {
410
411for _, rule := range t.PluralsRange() {
412
413if v[rule] == nil {
414return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k}
415}
416}
417}
418
419return nil
420}
421