2021-08-12 21:03:24 +02:00
package ut
import (
"fmt"
"strconv"
"strings"
"github.com/go-playground/locales"
)
const (
paramZero = "{0}"
paramOne = "{1}"
unknownTranslation = ""
)
// Translator is universal translators
// translator instance which is a thin wrapper
// around locales.Translator instance providing
// some extra functionality
type Translator interface {
locales . Translator
// adds a normal translation for a particular language/locale
// {#} is the only replacement type accepted and are ad infinitum
// eg. one: '{0} day left' other: '{0} days left'
Add ( key interface { } , text string , override bool ) error
// adds a cardinal plural translation for a particular language/locale
// {0} is the only replacement type accepted and only one variable is accepted as
// multiple cannot be used for a plural rule determination, unless it is a range;
// see AddRange below.
// eg. in locale 'en' one: '{0} day left' other: '{0} days left'
AddCardinal ( key interface { } , text string , rule locales . PluralRule , override bool ) error
// adds an ordinal plural translation for a particular language/locale
// {0} is the only replacement type accepted and only one variable is accepted as
// multiple cannot be used for a plural rule determination, unless it is a range;
// see AddRange below.
// eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring'
// - 1st, 2nd, 3rd...
AddOrdinal ( key interface { } , text string , rule locales . PluralRule , override bool ) error
// adds a range plural translation for a particular language/locale
// {0} and {1} are the only replacement types accepted and only these are accepted.
// eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
AddRange ( key interface { } , text string , rule locales . PluralRule , override bool ) error
// creates the translation for the locale given the 'key' and params passed in
T ( key interface { } , params ... string ) ( string , error )
// creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments
// and param passed in
C ( key interface { } , num float64 , digits uint64 , param string ) ( string , error )
// creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments
// and param passed in
O ( key interface { } , num float64 , digits uint64 , param string ) ( string , error )
// creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and
// 'digit2' arguments and 'param1' and 'param2' passed in
R ( key interface { } , num1 float64 , digits1 uint64 , num2 float64 , digits2 uint64 , param1 , param2 string ) ( string , error )
// VerifyTranslations checks to ensures that no plural rules have been
// missed within the translations.
VerifyTranslations ( ) error
}
var _ Translator = new ( translator )
var _ locales . Translator = new ( translator )
type translator struct {
locales . Translator
translations map [ interface { } ] * transText
cardinalTanslations map [ interface { } ] [ ] * transText // array index is mapped to locales.PluralRule index + the locales.PluralRuleUnknown
ordinalTanslations map [ interface { } ] [ ] * transText
rangeTanslations map [ interface { } ] [ ] * transText
}
type transText struct {
text string
indexes [ ] int
}
func newTranslator ( trans locales . Translator ) Translator {
return & translator {
Translator : trans ,
translations : make ( map [ interface { } ] * transText ) , // translation text broken up by byte index
cardinalTanslations : make ( map [ interface { } ] [ ] * transText ) ,
ordinalTanslations : make ( map [ interface { } ] [ ] * transText ) ,
rangeTanslations : make ( map [ interface { } ] [ ] * transText ) ,
}
}
// Add adds a normal translation for a particular language/locale
// {#} is the only replacement type accepted and are ad infinitum
// eg. one: '{0} day left' other: '{0} days left'
func ( t * translator ) Add ( key interface { } , text string , override bool ) error {
if _ , ok := t . translations [ key ] ; ok && ! override {
return & ErrConflictingTranslation { locale : t . Locale ( ) , key : key , text : text }
}
lb := strings . Count ( text , "{" )
rb := strings . Count ( text , "}" )
if lb != rb {
return & ErrMissingBracket { locale : t . Locale ( ) , key : key , text : text }
}
trans := & transText {
text : text ,
}
var idx int
for i := 0 ; i < lb ; i ++ {
s := "{" + strconv . Itoa ( i ) + "}"
idx = strings . Index ( text , s )
if idx == - 1 {
return & ErrBadParamSyntax { locale : t . Locale ( ) , param : s , key : key , text : text }
}
trans . indexes = append ( trans . indexes , idx )
trans . indexes = append ( trans . indexes , idx + len ( s ) )
}
t . translations [ key ] = trans
return nil
}
// AddCardinal adds a cardinal plural translation for a particular language/locale
// {0} is the only replacement type accepted and only one variable is accepted as
// multiple cannot be used for a plural rule determination, unless it is a range;
// see AddRange below.
// eg. in locale 'en' one: '{0} day left' other: '{0} days left'
func ( t * translator ) AddCardinal ( key interface { } , text string , rule locales . PluralRule , override bool ) error {
var verified bool
// verify plural rule exists for locale
for _ , pr := range t . PluralsCardinal ( ) {
if pr == rule {
verified = true
break
}
}
if ! verified {
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 ) }
}
tarr , ok := t . cardinalTanslations [ key ]
if ok {
// verify not adding a conflicting record
if len ( tarr ) > 0 && tarr [ rule ] != nil && ! override {
return & ErrConflictingTranslation { locale : t . Locale ( ) , key : key , rule : rule , text : text }
}
} else {
2021-09-10 14:42:14 +02:00
tarr = make ( [ ] * transText , 7 )
2021-08-12 21:03:24 +02:00
t . cardinalTanslations [ key ] = tarr
}
trans := & transText {
text : text ,
2021-09-10 14:42:14 +02:00
indexes : make ( [ ] int , 2 ) ,
2021-08-12 21:03:24 +02:00
}
tarr [ rule ] = trans
idx := strings . Index ( text , paramZero )
if idx == - 1 {
tarr [ rule ] = nil
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 ) }
}
trans . indexes [ 0 ] = idx
trans . indexes [ 1 ] = idx + len ( paramZero )
return nil
}
// AddOrdinal adds an ordinal plural translation for a particular language/locale
// {0} is the only replacement type accepted and only one variable is accepted as
// multiple cannot be used for a plural rule determination, unless it is a range;
// see AddRange below.
// eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring' - 1st, 2nd, 3rd...
func ( t * translator ) AddOrdinal ( key interface { } , text string , rule locales . PluralRule , override bool ) error {
var verified bool
// verify plural rule exists for locale
for _ , pr := range t . PluralsOrdinal ( ) {
if pr == rule {
verified = true
break
}
}
if ! verified {
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 ) }
}
tarr , ok := t . ordinalTanslations [ key ]
if ok {
// verify not adding a conflicting record
if len ( tarr ) > 0 && tarr [ rule ] != nil && ! override {
return & ErrConflictingTranslation { locale : t . Locale ( ) , key : key , rule : rule , text : text }
}
} else {
2021-09-10 14:42:14 +02:00
tarr = make ( [ ] * transText , 7 )
2021-08-12 21:03:24 +02:00
t . ordinalTanslations [ key ] = tarr
}
trans := & transText {
text : text ,
2021-09-10 14:42:14 +02:00
indexes : make ( [ ] int , 2 ) ,
2021-08-12 21:03:24 +02:00
}
tarr [ rule ] = trans
idx := strings . Index ( text , paramZero )
if idx == - 1 {
tarr [ rule ] = nil
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 ) }
}
trans . indexes [ 0 ] = idx
trans . indexes [ 1 ] = idx + len ( paramZero )
return nil
}
// AddRange adds a range plural translation for a particular language/locale
// {0} and {1} are the only replacement types accepted and only these are accepted.
// eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left'
func ( t * translator ) AddRange ( key interface { } , text string , rule locales . PluralRule , override bool ) error {
var verified bool
// verify plural rule exists for locale
for _ , pr := range t . PluralsRange ( ) {
if pr == rule {
verified = true
break
}
}
if ! verified {
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 ) }
}
tarr , ok := t . rangeTanslations [ key ]
if ok {
// verify not adding a conflicting record
if len ( tarr ) > 0 && tarr [ rule ] != nil && ! override {
return & ErrConflictingTranslation { locale : t . Locale ( ) , key : key , rule : rule , text : text }
}
} else {
2021-09-10 14:42:14 +02:00
tarr = make ( [ ] * transText , 7 )
2021-08-12 21:03:24 +02:00
t . rangeTanslations [ key ] = tarr
}
trans := & transText {
text : text ,
2021-09-10 14:42:14 +02:00
indexes : make ( [ ] int , 4 ) ,
2021-08-12 21:03:24 +02:00
}
tarr [ rule ] = trans
idx := strings . Index ( text , paramZero )
if idx == - 1 {
tarr [ rule ] = nil
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 ) }
}
trans . indexes [ 0 ] = idx
trans . indexes [ 1 ] = idx + len ( paramZero )
idx = strings . Index ( text , paramOne )
if idx == - 1 {
tarr [ rule ] = nil
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 ) }
}
trans . indexes [ 2 ] = idx
trans . indexes [ 3 ] = idx + len ( paramOne )
return nil
}
// T creates the translation for the locale given the 'key' and params passed in
func ( t * translator ) T ( key interface { } , params ... string ) ( string , error ) {
trans , ok := t . translations [ key ]
if ! ok {
return unknownTranslation , ErrUnknowTranslation
}
b := make ( [ ] byte , 0 , 64 )
var start , end , count int
for i := 0 ; i < len ( trans . indexes ) ; i ++ {
end = trans . indexes [ i ]
b = append ( b , trans . text [ start : end ] ... )
b = append ( b , params [ count ] ... )
i ++
start = trans . indexes [ i ]
count ++
}
b = append ( b , trans . text [ start : ] ... )
return string ( b ) , nil
}
// C creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
func ( t * translator ) C ( key interface { } , num float64 , digits uint64 , param string ) ( string , error ) {
tarr , ok := t . cardinalTanslations [ key ]
if ! ok {
return unknownTranslation , ErrUnknowTranslation
}
rule := t . CardinalPluralRule ( num , digits )
trans := tarr [ rule ]
b := make ( [ ] byte , 0 , 64 )
b = append ( b , trans . text [ : trans . indexes [ 0 ] ] ... )
b = append ( b , param ... )
b = append ( b , trans . text [ trans . indexes [ 1 ] : ] ... )
return string ( b ) , nil
}
// O creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in
func ( t * translator ) O ( key interface { } , num float64 , digits uint64 , param string ) ( string , error ) {
tarr , ok := t . ordinalTanslations [ key ]
if ! ok {
return unknownTranslation , ErrUnknowTranslation
}
rule := t . OrdinalPluralRule ( num , digits )
trans := tarr [ rule ]
b := make ( [ ] byte , 0 , 64 )
b = append ( b , trans . text [ : trans . indexes [ 0 ] ] ... )
b = append ( b , param ... )
b = append ( b , trans . text [ trans . indexes [ 1 ] : ] ... )
return string ( b ) , nil
}
// R creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and 'digit2' arguments
// and 'param1' and 'param2' passed in
func ( t * translator ) R ( key interface { } , num1 float64 , digits1 uint64 , num2 float64 , digits2 uint64 , param1 , param2 string ) ( string , error ) {
tarr , ok := t . rangeTanslations [ key ]
if ! ok {
return unknownTranslation , ErrUnknowTranslation
}
rule := t . RangePluralRule ( num1 , digits1 , num2 , digits2 )
trans := tarr [ rule ]
b := make ( [ ] byte , 0 , 64 )
b = append ( b , trans . text [ : trans . indexes [ 0 ] ] ... )
b = append ( b , param1 ... )
b = append ( b , trans . text [ trans . indexes [ 1 ] : trans . indexes [ 2 ] ] ... )
b = append ( b , param2 ... )
b = append ( b , trans . text [ trans . indexes [ 3 ] : ] ... )
return string ( b ) , nil
}
// VerifyTranslations checks to ensures that no plural rules have been
// missed within the translations.
func ( t * translator ) VerifyTranslations ( ) error {
for k , v := range t . cardinalTanslations {
for _ , rule := range t . PluralsCardinal ( ) {
if v [ rule ] == nil {
return & ErrMissingPluralTranslation { locale : t . Locale ( ) , translationType : "plural" , rule : rule , key : k }
}
}
}
for k , v := range t . ordinalTanslations {
for _ , rule := range t . PluralsOrdinal ( ) {
if v [ rule ] == nil {
return & ErrMissingPluralTranslation { locale : t . Locale ( ) , translationType : "ordinal" , rule : rule , key : k }
}
}
}
for k , v := range t . rangeTanslations {
for _ , rule := range t . PluralsRange ( ) {
if v [ rule ] == nil {
return & ErrMissingPluralTranslation { locale : t . Locale ( ) , translationType : "range" , rule : rule , key : k }
}
}
}
return nil
}