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 }