diff --git a/assets/channel.go b/assets/channel.go index 5cb3a8033..c46ddbf9c 100644 --- a/assets/channel.go +++ b/assets/channel.go @@ -3,8 +3,8 @@ package assets import ( "fmt" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/goflow/envs" ) // ChannelUUID is the UUID of a channel @@ -41,7 +41,7 @@ type Channel interface { Schemes() []string Roles() []ChannelRole Parent() *ChannelReference - Country() envs.Country + Country() i18n.Country MatchPrefixes() []string AllowInternational() bool } diff --git a/assets/static/channel.go b/assets/static/channel.go index bb53a90bc..63e8c7ec5 100644 --- a/assets/static/channel.go +++ b/assets/static/channel.go @@ -1,9 +1,9 @@ package static import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" ) // Channel is a JSON serializable implementation of a channel asset @@ -14,7 +14,7 @@ type Channel struct { Schemes_ []string `json:"schemes" validate:"min=1"` Roles_ []assets.ChannelRole `json:"roles" validate:"min=1,dive,eq=send|eq=receive|eq=call|eq=answer|eq=ussd"` Parent_ *assets.ChannelReference `json:"parent" validate:"omitempty,dive"` - Country_ envs.Country `json:"country,omitempty"` + Country_ i18n.Country `json:"country,omitempty"` MatchPrefixes_ []string `json:"match_prefixes,omitempty"` AllowInternational_ bool `json:"allow_international,omitempty"` } @@ -33,7 +33,7 @@ func NewChannel(uuid assets.ChannelUUID, name string, address string, schemes [] } // NewTelChannel creates a new tel channel -func NewTelChannel(uuid assets.ChannelUUID, name string, address string, roles []assets.ChannelRole, parent *assets.ChannelReference, country envs.Country, matchPrefixes []string, allowInternational bool) assets.Channel { +func NewTelChannel(uuid assets.ChannelUUID, name string, address string, roles []assets.ChannelRole, parent *assets.ChannelReference, country i18n.Country, matchPrefixes []string, allowInternational bool) assets.Channel { return &Channel{ UUID_: uuid, Name_: name, @@ -66,7 +66,7 @@ func (c *Channel) Roles() []assets.ChannelRole { return c.Roles_ } func (c *Channel) Parent() *assets.ChannelReference { return c.Parent_ } // Country returns this channel's associated country code (if any) -func (c *Channel) Country() envs.Country { return c.Country_ } +func (c *Channel) Country() i18n.Country { return c.Country_ } // MatchPrefixes returns this channel's match prefixes values used for selecting a channel for a URN (if any) func (c *Channel) MatchPrefixes() []string { return c.MatchPrefixes_ } diff --git a/assets/static/channel_test.go b/assets/static/channel_test.go index 1e00d0eb8..0e2b4878c 100644 --- a/assets/static/channel_test.go +++ b/assets/static/channel_test.go @@ -3,9 +3,9 @@ package static_test import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/utils" "github.com/stretchr/testify/assert" @@ -26,7 +26,7 @@ func TestChannel(t *testing.T) { assert.Equal(t, []string{"tel"}, channel.Schemes()) assert.Equal(t, []assets.ChannelRole{assets.ChannelRoleSend}, channel.Roles()) assert.Nil(t, channel.Parent()) - assert.Equal(t, envs.NilCountry, channel.Country()) + assert.Equal(t, i18n.NilCountry, channel.Country()) assert.Nil(t, channel.MatchPrefixes()) assert.True(t, channel.AllowInternational()) @@ -44,7 +44,7 @@ func TestChannel(t *testing.T) { false, ) - assert.Equal(t, envs.Country("RW"), channel.Country()) + assert.Equal(t, i18n.Country("RW"), channel.Country()) assert.Equal(t, []string{"+25079"}, channel.MatchPrefixes()) assert.False(t, channel.AllowInternational()) } diff --git a/assets/static/template.go b/assets/static/template.go index 27b57a3ef..60de1be16 100644 --- a/assets/static/template.go +++ b/assets/static/template.go @@ -1,8 +1,8 @@ package static import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" ) // Template is a JSON serializable implementation of a template asset @@ -40,13 +40,13 @@ func (t *Template) Translations() []assets.TemplateTranslation { type TemplateTranslation struct { Channel_ assets.ChannelReference `json:"channel" validate:"required"` Content_ string `json:"content" validate:"required"` - Locale_ envs.Locale `json:"locale" validate:"required"` + Locale_ i18n.Locale `json:"locale" validate:"required"` Namespace_ string `json:"namespace"` VariableCount_ int `json:"variable_count"` } // NewTemplateTranslation creates a new template translation -func NewTemplateTranslation(channel assets.ChannelReference, locale envs.Locale, content string, variableCount int, namespace string) *TemplateTranslation { +func NewTemplateTranslation(channel assets.ChannelReference, locale i18n.Locale, content string, variableCount int, namespace string) *TemplateTranslation { return &TemplateTranslation{ Channel_: channel, Content_: content, @@ -63,7 +63,7 @@ func (t *TemplateTranslation) Content() string { return t.Content_ } func (t *TemplateTranslation) Namespace() string { return t.Namespace_ } // Language returns the locale this translation is in -func (t *TemplateTranslation) Locale() envs.Locale { return t.Locale_ } +func (t *TemplateTranslation) Locale() i18n.Locale { return t.Locale_ } // VariableCount returns the number of variables in this template func (t *TemplateTranslation) VariableCount() int { return t.VariableCount_ } diff --git a/assets/static/template_test.go b/assets/static/template_test.go index 129227378..46dc13165 100644 --- a/assets/static/template_test.go +++ b/assets/static/template_test.go @@ -3,10 +3,9 @@ package static import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" - "github.com/stretchr/testify/assert" ) @@ -16,9 +15,9 @@ func TestTemplate(t *testing.T) { UUID: assets.ChannelUUID("ffffffff-9b24-92e1-ffff-ffffb207cdb4"), } - translation := NewTemplateTranslation(channel, envs.Locale("eng-US"), "Hello {{1}}", 1, "0162a7f4_dfe4_4c96_be07_854d5dba3b2b") + translation := NewTemplateTranslation(channel, i18n.Locale("eng-US"), "Hello {{1}}", 1, "0162a7f4_dfe4_4c96_be07_854d5dba3b2b") assert.Equal(t, channel, translation.Channel()) - assert.Equal(t, envs.Locale("eng-US"), translation.Locale()) + assert.Equal(t, i18n.Locale("eng-US"), translation.Locale()) assert.Equal(t, "Hello {{1}}", translation.Content()) assert.Equal(t, 1, translation.VariableCount()) assert.Equal(t, "0162a7f4_dfe4_4c96_be07_854d5dba3b2b", translation.Namespace()) diff --git a/assets/template.go b/assets/template.go index 7f98e8928..b0284fec4 100644 --- a/assets/template.go +++ b/assets/template.go @@ -3,8 +3,8 @@ package assets import ( "fmt" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/goflow/envs" ) // TemplateUUID is the UUID of a template @@ -45,7 +45,7 @@ type Template interface { // TemplateTranslation represents a single translation for a specific template and channel type TemplateTranslation interface { Content() string - Locale() envs.Locale + Locale() i18n.Locale Namespace() string VariableCount() int Channel() ChannelReference diff --git a/cmd/docgen/docs/base.go b/cmd/docgen/docs/base.go index fa5180176..8367f15b9 100644 --- a/cmd/docgen/docs/base.go +++ b/cmd/docgen/docs/base.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/nyaruka/gocommon/dates" - "github.com/nyaruka/goflow/utils/i18n" + "github.com/nyaruka/goflow/utils/po" "github.com/pkg/errors" ) @@ -47,7 +47,7 @@ func Generate(baseDir, outputDir, localeDir string) error { fmt.Printf(" > Found %d tagged items with tag %s\n", len(v), k) } - locales := i18n.NewLibrary(localeDir, srcLocale) + locales := po.NewLibrary(localeDir, srcLocale) // keep track of all unique strings we look up via gettext msgIDs := make(map[string]bool) @@ -64,9 +64,9 @@ func Generate(baseDir, outputDir, localeDir string) error { } // use the unique messages set to create a new POT file - pot := i18n.NewPO(i18n.NewPOHeader("Generated by goflow docgen", dates.Now(), srcLocale)) + pot := po.NewPO(po.NewPOHeader("Generated by goflow docgen", dates.Now(), srcLocale)) for msgID := range msgIDs { - pot.AddEntry(&i18n.POEntry{MsgID: msgID}) + pot.AddEntry(&po.POEntry{MsgID: msgID}) } pot.Sort() @@ -79,7 +79,7 @@ func Generate(baseDir, outputDir, localeDir string) error { } // generates all documentation a given language by invoking all generators -func generateForLang(baseDir, outputDir string, items map[string][]*TaggedItem, locales *i18n.Library, locale string, msgUsed func(string)) error { +func generateForLang(baseDir, outputDir string, items map[string][]*TaggedItem, locales *po.Library, locale string, msgUsed func(string)) error { fmt.Printf("Generating docs in '%s'\n", locale) // en_US -> en-us diff --git a/cmd/flowrunner/main.go b/cmd/flowrunner/main.go index f4215b8be..620f91e3f 100644 --- a/cmd/flowrunner/main.go +++ b/cmd/flowrunner/main.go @@ -12,6 +12,7 @@ import ( "time" "github.com/buger/jsonparser" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/urns" @@ -72,7 +73,7 @@ func main() { engine := createEngine(witToken) - repro, err := RunFlow(engine, assetsPath, flowUUID, initialMsg, envs.Language(contactLang), os.Stdin, os.Stdout) + repro, err := RunFlow(engine, assetsPath, flowUUID, initialMsg, i18n.Language(contactLang), os.Stdin, os.Stdout) if err != nil { fmt.Println(err.Error()) @@ -103,7 +104,7 @@ func createEngine(witToken string) flows.Engine { } // RunFlow steps through a flow -func RunFlow(eng flows.Engine, assetsPath string, flowUUID assets.FlowUUID, initialMsg string, contactLang envs.Language, in io.Reader, out io.Writer) (*Repro, error) { +func RunFlow(eng flows.Engine, assetsPath string, flowUUID assets.FlowUUID, initialMsg string, contactLang i18n.Language, in io.Reader, out io.Writer) (*Repro, error) { assetsJSON, err := os.ReadFile(assetsPath) if err != nil { return nil, errors.Wrapf(err, "error reading assets file '%s'", assetsPath) @@ -141,7 +142,7 @@ func RunFlow(eng flows.Engine, assetsPath string, flowUUID assets.FlowUUID, init // create our environment la, _ := time.LoadLocation("America/Los_Angeles") - languages := []envs.Language{flow.Language(), contact.Language()} + languages := []i18n.Language{flow.Language(), contact.Language()} env := envs.NewBuilder().WithTimezone(la).WithAllowedLanguages(languages).Build() repro := &Repro{} diff --git a/cmd/flowxgettext/main.go b/cmd/flowxgettext/main.go index 5ebdf6b37..92184d5c5 100644 --- a/cmd/flowxgettext/main.go +++ b/cmd/flowxgettext/main.go @@ -8,7 +8,7 @@ import ( "os" "github.com/buger/jsonparser" - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/definition" "github.com/nyaruka/goflow/flows/definition/migrations" @@ -32,13 +32,13 @@ func main() { flags.PrintDefaults() os.Exit(1) } - if err := FlowXGetText(envs.Language(lang), excludeArgs, args, os.Stdout); err != nil { + if err := FlowXGetText(i18n.Language(lang), excludeArgs, args, os.Stdout); err != nil { fmt.Println(err.Error()) os.Exit(1) } } -func FlowXGetText(lang envs.Language, excludeArgs bool, paths []string, writer io.Writer) error { +func FlowXGetText(lang i18n.Language, excludeArgs bool, paths []string, writer io.Writer) error { sources, err := loadFlows(paths) if err != nil { return err diff --git a/cmd/flowxgettext/main_test.go b/cmd/flowxgettext/main_test.go index 170925e17..ef4ef39ab 100644 --- a/cmd/flowxgettext/main_test.go +++ b/cmd/flowxgettext/main_test.go @@ -6,8 +6,8 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" main "github.com/nyaruka/goflow/cmd/flowxgettext" - "github.com/nyaruka/goflow/envs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -18,7 +18,7 @@ func TestFlowXGetText(t *testing.T) { out := &strings.Builder{} - err := main.FlowXGetText(envs.Language("fra"), false, []string{"../../test/testdata/runner/two_questions.json"}, out) + err := main.FlowXGetText(i18n.Language("fra"), false, []string{"../../test/testdata/runner/two_questions.json"}, out) require.NoError(t, err) assert.Contains(t, out.String(), ` diff --git a/contactql/parser.go b/contactql/parser.go index 776d04ba0..52d271dfb 100644 --- a/contactql/parser.go +++ b/contactql/parser.go @@ -8,6 +8,7 @@ import ( "time" "github.com/antlr/antlr4/runtime/Go/antlr/v4" + "github.com/nyaruka/gocommon/i18n" gen "github.com/nyaruka/goflow/antlr/gen/contactql" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" @@ -202,7 +203,7 @@ func (c *Condition) validate(env envs.Environment, resolver Resolver) error { } } else if c.propKey == AttributeLanguage { if c.value != "" { - _, err := envs.ParseLanguage(c.value) + _, err := i18n.ParseLanguage(c.value) if err != nil { return NewQueryError(ErrInvalidLanguage, "'%s' is not a valid language code", c.value).withExtra("value", c.value) } diff --git a/envs/country.go b/envs/country.go deleted file mode 100644 index aed90cdc4..000000000 --- a/envs/country.go +++ /dev/null @@ -1,37 +0,0 @@ -package envs - -import ( - "database/sql/driver" - - "github.com/go-playground/validator/v10" - "github.com/nyaruka/goflow/utils" - "github.com/nyaruka/null/v2" - "github.com/nyaruka/phonenumbers" -) - -func init() { - utils.RegisterValidatorAlias("country", "len=2", func(validator.FieldError) string { - return "is not a valid country code" - }) -} - -// Country is a ISO 3166-1 alpha-2 country code -type Country string - -// NilCountry represents our nil, or unknown country -var NilCountry = Country("") - -// DeriveCountryFromTel attempts to derive a country code (e.g. RW) from a phone number -func DeriveCountryFromTel(number string) Country { - parsed, err := phonenumbers.Parse(number, "") - if err != nil { - return "" - } - return Country(phonenumbers.GetRegionCodeForNumber(parsed)) -} - -// Place nicely with NULLs if persisting to a database or JSON -func (c *Country) Scan(value any) error { return null.ScanString(value, c) } -func (c Country) Value() (driver.Value, error) { return null.StringValue(c) } -func (c Country) MarshalJSON() ([]byte, error) { return null.MarshalString(c) } -func (c *Country) UnmarshalJSON(b []byte) error { return null.UnmarshalString(b, c) } diff --git a/envs/country_test.go b/envs/country_test.go deleted file mode 100644 index dd8469a43..000000000 --- a/envs/country_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package envs_test - -import ( - "testing" - - "github.com/nyaruka/goflow/envs" - - "github.com/stretchr/testify/assert" -) - -func TestDeriveCountryFromTel(t *testing.T) { - assert.Equal(t, envs.Country("RW"), envs.DeriveCountryFromTel("+250788383383")) - assert.Equal(t, envs.Country("EC"), envs.DeriveCountryFromTel("+593979000000")) - assert.Equal(t, envs.NilCountry, envs.DeriveCountryFromTel("1234")) - - v, err := envs.Country("RW").Value() - assert.NoError(t, err) - assert.Equal(t, "RW", v) - - v, err = envs.NilCountry.Value() - assert.NoError(t, err) - assert.Nil(t, v) - - var c envs.Country - assert.NoError(t, c.Scan("RW")) - assert.Equal(t, envs.Country("RW"), c) - - assert.NoError(t, c.Scan(nil)) - assert.Equal(t, envs.NilCountry, c) -} diff --git a/envs/environment.go b/envs/environment.go index 04c45f61e..813646c54 100644 --- a/envs/environment.go +++ b/envs/environment.go @@ -4,11 +4,22 @@ import ( "encoding/json" "time" + "github.com/go-playground/validator/v10" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/utils" ) +func init() { + utils.RegisterValidatorAlias("language", "len=3", func(validator.FieldError) string { + return "is not a valid language code" + }) + utils.RegisterValidatorAlias("country", "len=2", func(validator.FieldError) string { + return "is not a valid country code" + }) +} + type RedactionPolicy string const ( @@ -31,14 +42,14 @@ type Environment interface { DateFormat() DateFormat TimeFormat() TimeFormat Timezone() *time.Location - AllowedLanguages() []Language - DefaultCountry() Country + AllowedLanguages() []i18n.Language + DefaultCountry() i18n.Country NumberFormat() *NumberFormat InputCollation() Collation RedactionPolicy() RedactionPolicy - DefaultLanguage() Language - DefaultLocale() Locale + DefaultLanguage() i18n.Language + DefaultLocale() i18n.Locale LocationResolver() LocationResolver @@ -52,33 +63,33 @@ type environment struct { dateFormat DateFormat timeFormat TimeFormat timezone *time.Location - allowedLanguages []Language - defaultCountry Country + allowedLanguages []i18n.Language + defaultCountry i18n.Country numberFormat *NumberFormat redactionPolicy RedactionPolicy inputCollation Collation } -func (e *environment) DateFormat() DateFormat { return e.dateFormat } -func (e *environment) TimeFormat() TimeFormat { return e.timeFormat } -func (e *environment) Timezone() *time.Location { return e.timezone } -func (e *environment) AllowedLanguages() []Language { return e.allowedLanguages } -func (e *environment) DefaultCountry() Country { return e.defaultCountry } -func (e *environment) NumberFormat() *NumberFormat { return e.numberFormat } -func (e *environment) InputCollation() Collation { return e.inputCollation } -func (e *environment) RedactionPolicy() RedactionPolicy { return e.redactionPolicy } +func (e *environment) DateFormat() DateFormat { return e.dateFormat } +func (e *environment) TimeFormat() TimeFormat { return e.timeFormat } +func (e *environment) Timezone() *time.Location { return e.timezone } +func (e *environment) AllowedLanguages() []i18n.Language { return e.allowedLanguages } +func (e *environment) DefaultCountry() i18n.Country { return e.defaultCountry } +func (e *environment) NumberFormat() *NumberFormat { return e.numberFormat } +func (e *environment) InputCollation() Collation { return e.inputCollation } +func (e *environment) RedactionPolicy() RedactionPolicy { return e.redactionPolicy } // DefaultLanguage is the first allowed language -func (e *environment) DefaultLanguage() Language { +func (e *environment) DefaultLanguage() i18n.Language { if len(e.allowedLanguages) > 0 { return e.allowedLanguages[0] } - return NilLanguage + return i18n.NilLanguage } // DefaultLocale combines the default languages and countries into a locale -func (e *environment) DefaultLocale() Locale { - return NewLocale(e.DefaultLanguage(), e.DefaultCountry()) +func (e *environment) DefaultLocale() i18n.Locale { + return i18n.NewLocale(e.DefaultLanguage(), e.DefaultCountry()) } func (e *environment) LocationResolver() LocationResolver { return nil } @@ -101,9 +112,9 @@ type envEnvelope struct { DateFormat DateFormat `json:"date_format" validate:"date_format"` TimeFormat TimeFormat `json:"time_format" validate:"time_format"` Timezone string `json:"timezone"` - AllowedLanguages []Language `json:"allowed_languages,omitempty" validate:"omitempty,dive,language"` + AllowedLanguages []i18n.Language `json:"allowed_languages,omitempty" validate:"omitempty,dive,language"` NumberFormat *NumberFormat `json:"number_format,omitempty"` - DefaultCountry Country `json:"default_country,omitempty" validate:"omitempty,country"` + DefaultCountry i18n.Country `json:"default_country,omitempty" validate:"omitempty,country"` InputCollation Collation `json:"input_collation"` RedactionPolicy RedactionPolicy `json:"redaction_policy" validate:"omitempty,eq=none|eq=urns"` } @@ -170,7 +181,7 @@ func NewBuilder() *EnvironmentBuilder { timeFormat: TimeFormatHourMinute, timezone: time.UTC, allowedLanguages: nil, - defaultCountry: NilCountry, + defaultCountry: i18n.NilCountry, numberFormat: DefaultNumberFormat, inputCollation: CollationDefault, redactionPolicy: RedactionPolicyNone, @@ -195,12 +206,12 @@ func (b *EnvironmentBuilder) WithTimezone(timezone *time.Location) *EnvironmentB return b } -func (b *EnvironmentBuilder) WithAllowedLanguages(allowedLanguages []Language) *EnvironmentBuilder { +func (b *EnvironmentBuilder) WithAllowedLanguages(allowedLanguages []i18n.Language) *EnvironmentBuilder { b.env.allowedLanguages = allowedLanguages return b } -func (b *EnvironmentBuilder) WithDefaultCountry(defaultCountry Country) *EnvironmentBuilder { +func (b *EnvironmentBuilder) WithDefaultCountry(defaultCountry i18n.Country) *EnvironmentBuilder { b.env.defaultCountry = defaultCountry return b } @@ -222,3 +233,23 @@ func (b *EnvironmentBuilder) WithRedactionPolicy(redactionPolicy RedactionPolicy // Build returns the final environment func (b *EnvironmentBuilder) Build() Environment { return b.env } + +// deprecated - can remove when gocommon's date formatting takes an i18n.Locale +func ToBCP47(l i18n.Locale) string { + if l == i18n.NilLocale { + return "" + } + + lang, country := l.Split() + lang2 := lang.ISO639_1() + + // not all languages have a 2-letter code + if lang2 == "" { + return "" + } + + if country != i18n.NilCountry { + lang2 += "-" + string(country) + } + return lang2 +} diff --git a/envs/environment_test.go b/envs/environment_test.go index 8f6250854..706aa985b 100644 --- a/envs/environment_test.go +++ b/envs/environment_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/envs" @@ -42,9 +43,9 @@ func TestEnvironmentMarshaling(t *testing.T) { assert.Equal(t, envs.DateFormatYearMonthDay, env.DateFormat()) assert.Equal(t, envs.TimeFormatHourMinute, env.TimeFormat()) assert.Equal(t, envs.DefaultNumberFormat, env.NumberFormat()) - assert.Equal(t, envs.NilLanguage, env.DefaultLanguage()) + assert.Equal(t, i18n.NilLanguage, env.DefaultLanguage()) assert.Nil(t, env.AllowedLanguages()) - assert.Equal(t, envs.NilCountry, env.DefaultCountry()) + assert.Equal(t, i18n.NilCountry, env.DefaultCountry()) assert.Nil(t, env.LocationResolver()) // can create with valid values @@ -59,10 +60,10 @@ func TestEnvironmentMarshaling(t *testing.T) { assert.Equal(t, envs.DateFormatDayMonthYear, env.DateFormat()) assert.Equal(t, envs.TimeFormatHourMinuteSecond, env.TimeFormat()) assert.Equal(t, kgl, env.Timezone()) - assert.Equal(t, envs.Language("eng"), env.DefaultLanguage()) - assert.Equal(t, []envs.Language{envs.Language("eng"), envs.Language("fra")}, env.AllowedLanguages()) - assert.Equal(t, envs.Country("RW"), env.DefaultCountry()) - assert.Equal(t, "en-RW", env.DefaultLocale().ToBCP47()) + assert.Equal(t, i18n.Language("eng"), env.DefaultLanguage()) + assert.Equal(t, []i18n.Language{i18n.Language("eng"), i18n.Language("fra")}, env.AllowedLanguages()) + assert.Equal(t, i18n.Country("RW"), env.DefaultCountry()) + assert.Equal(t, i18n.Locale("eng-RW"), env.DefaultLocale()) assert.Equal(t, envs.CollationDefault, env.InputCollation()) assert.Equal(t, envs.RedactionPolicyNone, env.RedactionPolicy()) assert.Nil(t, env.LocationResolver()) @@ -103,8 +104,8 @@ func TestEnvironmentBuilder(t *testing.T) { WithDateFormat(envs.DateFormatDayMonthYear). WithTimeFormat(envs.TimeFormatHourMinuteSecond). WithTimezone(kgl). - WithAllowedLanguages([]envs.Language{envs.Language("fra"), envs.Language("eng")}). - WithDefaultCountry(envs.Country("RW")). + WithAllowedLanguages([]i18n.Language{i18n.Language("fra"), i18n.Language("eng")}). + WithDefaultCountry(i18n.Country("RW")). WithNumberFormat(&envs.NumberFormat{DecimalSymbol: "'"}). WithRedactionPolicy(envs.RedactionPolicyURNs). Build() @@ -112,8 +113,8 @@ func TestEnvironmentBuilder(t *testing.T) { assert.Equal(t, envs.DateFormatDayMonthYear, env.DateFormat()) assert.Equal(t, envs.TimeFormatHourMinuteSecond, env.TimeFormat()) assert.Equal(t, kgl, env.Timezone()) - assert.Equal(t, []envs.Language{envs.Language("fra"), envs.Language("eng")}, env.AllowedLanguages()) - assert.Equal(t, envs.Country("RW"), env.DefaultCountry()) + assert.Equal(t, []i18n.Language{i18n.Language("fra"), i18n.Language("eng")}, env.AllowedLanguages()) + assert.Equal(t, i18n.Country("RW"), env.DefaultCountry()) assert.Equal(t, &envs.NumberFormat{DecimalSymbol: "'"}, env.NumberFormat()) assert.Equal(t, envs.RedactionPolicyURNs, env.RedactionPolicy()) assert.Nil(t, env.LocationResolver()) diff --git a/envs/language.go b/envs/language.go deleted file mode 100644 index 1e381c753..000000000 --- a/envs/language.go +++ /dev/null @@ -1,43 +0,0 @@ -package envs - -import ( - "database/sql/driver" - - "github.com/go-playground/validator/v10" - "github.com/nyaruka/goflow/utils" - "github.com/nyaruka/null/v2" - "github.com/pkg/errors" - "golang.org/x/text/language" -) - -func init() { - utils.RegisterValidatorAlias("language", "len=3", func(validator.FieldError) string { - return "is not a valid language code" - }) -} - -// Language is our internal representation of a language -type Language string - -// NilLanguage represents our nil, or unknown language -var NilLanguage = Language("") - -// ParseLanguage returns a new Language for the passed in language string, or an error if not found -func ParseLanguage(lang string) (Language, error) { - if len(lang) != 3 { - return NilLanguage, errors.Errorf("iso-639-3 codes must be 3 characters, got: %s", lang) - } - - base, err := language.ParseBase(lang) - if err != nil { - return NilLanguage, errors.Errorf("unrecognized language code: %s", lang) - } - - return Language(base.ISO3()), nil -} - -// Place nicely with NULLs if persisting to a database or JSON -func (l *Language) Scan(value any) error { return null.ScanString(value, l) } -func (l Language) Value() (driver.Value, error) { return null.StringValue(l) } -func (l Language) MarshalJSON() ([]byte, error) { return null.MarshalString(l) } -func (l *Language) UnmarshalJSON(b []byte) error { return null.UnmarshalString(b, l) } diff --git a/envs/language_test.go b/envs/language_test.go deleted file mode 100644 index bcec87e8b..000000000 --- a/envs/language_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package envs_test - -import ( - "testing" - - "github.com/nyaruka/goflow/envs" - - "github.com/stretchr/testify/assert" -) - -func TestLanguage(t *testing.T) { - lang, err := envs.ParseLanguage("ENG") - assert.NoError(t, err) - assert.Equal(t, envs.Language("eng"), lang) - - _, err = envs.ParseLanguage("base") - assert.EqualError(t, err, "iso-639-3 codes must be 3 characters, got: base") - - _, err = envs.ParseLanguage("xzx") - assert.EqualError(t, err, "unrecognized language code: xzx") - - v, err := envs.Language("eng").Value() - assert.NoError(t, err) - assert.Equal(t, "eng", v) - - v, err = envs.NilLanguage.Value() - assert.NoError(t, err) - assert.Nil(t, v) - - var l envs.Language - assert.NoError(t, l.Scan("eng")) - assert.Equal(t, envs.Language("eng"), l) - - assert.NoError(t, l.Scan(nil)) - assert.Equal(t, envs.NilLanguage, l) -} diff --git a/envs/locale.go b/envs/locale.go deleted file mode 100644 index a47c250c0..000000000 --- a/envs/locale.go +++ /dev/null @@ -1,73 +0,0 @@ -package envs - -import ( - "database/sql/driver" - "fmt" - "strings" - - "github.com/nyaruka/null/v2" - "golang.org/x/text/language" -) - -// Locale is the combination of a language and optional country, e.g. US English, Brazilian Portuguese, encoded as the -// language code followed by the country code, e.g. eng-US, por-BR -type Locale string - -// NewLocale creates a new locale -func NewLocale(l Language, c Country) Locale { - if l == NilLanguage { - return NilLocale - } - if c == NilCountry { - return Locale(l) // e.g. "eng", "por" - } - return Locale(fmt.Sprintf("%s-%s", l, c)) // e.g. "eng-US", "por-BR" -} - -// ToBCP47 returns the BCP47 code, e.g. en-US, pt, pt-BR -func (l Locale) ToBCP47() string { - if l == NilLocale { - return "" - } - - lang, country := l.ToParts() - - base, err := language.ParseBase(string(lang)) - if err != nil { - return "" - } - code := base.String() - - // not all languages have a 2-letter code - if len(code) != 2 { - return "" - } - - if country != NilCountry { - code += "-" + string(country) - } - return code -} - -func (l Locale) ToParts() (Language, Country) { - if l == NilLocale || len(l) < 3 { - return NilLanguage, NilCountry - } - - parts := strings.SplitN(string(l), "-", 2) - lang := Language(parts[0]) - country := NilCountry - if len(parts) > 1 { - country = Country(parts[1]) - } - - return lang, country -} - -var NilLocale = Locale("") - -// Place nicely with NULLs if persisting to a database or JSON -func (l *Locale) Scan(value any) error { return null.ScanString(value, l) } -func (l Locale) Value() (driver.Value, error) { return null.StringValue(l) } -func (l Locale) MarshalJSON() ([]byte, error) { return null.MarshalString(l) } -func (l *Locale) UnmarshalJSON(b []byte) error { return null.UnmarshalString(b, l) } diff --git a/envs/locale_test.go b/envs/locale_test.go deleted file mode 100644 index c8864becc..000000000 --- a/envs/locale_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package envs_test - -import ( - "testing" - - "github.com/nyaruka/goflow/envs" - "github.com/stretchr/testify/assert" -) - -func TestLocale(t *testing.T) { - assert.Equal(t, envs.Locale(""), envs.NewLocale("", "")) - assert.Equal(t, envs.Locale(""), envs.NewLocale("", "US")) // invalid without language - assert.Equal(t, envs.Locale("eng"), envs.NewLocale("eng", "")) // valid without country - assert.Equal(t, envs.Locale("eng-US"), envs.NewLocale("eng", "US")) - - l, c := envs.Locale("eng-US").ToParts() - assert.Equal(t, envs.Language("eng"), l) - assert.Equal(t, envs.Country("US"), c) - - l, c = envs.NilLocale.ToParts() - assert.Equal(t, envs.NilLanguage, l) - assert.Equal(t, envs.NilCountry, c) - - v, err := envs.NewLocale("eng", "US").Value() - assert.NoError(t, err) - assert.Equal(t, "eng-US", v) - - v, err = envs.NilLanguage.Value() - assert.NoError(t, err) - assert.Nil(t, v) - - var lc envs.Locale - assert.NoError(t, lc.Scan("eng-US")) - assert.Equal(t, envs.Locale("eng-US"), lc) - - assert.NoError(t, lc.Scan(nil)) - assert.Equal(t, envs.NilLocale, lc) -} - -func TestToBCP47(t *testing.T) { - tests := []struct { - locale envs.Locale - bcp47 string - }{ - {``, ``}, - {`cat`, `ca`}, - {`deu`, `de`}, - {`eng`, `en`}, - {`fin`, `fi`}, - {`fra`, `fr`}, - {`jpn`, `ja`}, - {`kor`, `ko`}, - {`pol`, `pl`}, - {`por`, `pt`}, - {`rus`, `ru`}, - {`spa`, `es`}, - {`swe`, `sv`}, - {`zho`, `zh`}, - {`eng-US`, `en-US`}, - {`spa-EC`, `es-EC`}, - {`zho-CN`, `zh-CN`}, - - {`yue`, ``}, // has no 2-letter represention - {`und`, ``}, - {`mul`, ``}, - {`xyz`, ``}, // is not a language - } - - for _, tc := range tests { - assert.Equal(t, tc.bcp47, tc.locale.ToBCP47()) - } -} diff --git a/excellent/types/date.go b/excellent/types/date.go index 640f75956..1b85fad90 100644 --- a/excellent/types/date.go +++ b/excellent/types/date.go @@ -44,7 +44,7 @@ func (x XDate) Format(env envs.Environment) string { // FormatCustom provides customised formatting func (x XDate) FormatCustom(env envs.Environment, layout string) (string, error) { - return x.Native().Format(layout, env.DefaultLocale().ToBCP47()) + return x.Native().Format(layout, envs.ToBCP47(env.DefaultLocale())) } // MarshalJSON is called when a struct containing this type is marshaled diff --git a/excellent/types/date_test.go b/excellent/types/date_test.go index ef7b9133a..95e209f17 100644 --- a/excellent/types/date_test.go +++ b/excellent/types/date_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/pkg/errors" @@ -13,7 +14,7 @@ import ( func TestXDate(t *testing.T) { env := envs.NewBuilder().WithDateFormat(envs.DateFormatDayMonthYear).Build() - env2 := envs.NewBuilder().WithDateFormat(envs.DateFormatYearMonthDay).WithAllowedLanguages([]envs.Language{"spa"}).Build() + env2 := envs.NewBuilder().WithDateFormat(envs.DateFormatYearMonthDay).WithAllowedLanguages([]i18n.Language{"spa"}).Build() d1 := types.NewXDate(dates.NewDate(2019, 2, 20)) assert.Equal(t, `date`, d1.Describe()) diff --git a/excellent/types/datetime.go b/excellent/types/datetime.go index e705eff93..1631fb53f 100644 --- a/excellent/types/datetime.go +++ b/excellent/types/datetime.go @@ -53,7 +53,7 @@ func (x XDateTime) FormatCustom(env envs.Environment, layout string, tz *time.Lo dt = dt.In(tz) } - return dates.Format(dt, layout, env.DefaultLocale().ToBCP47(), dates.DateTimeLayouts) + return dates.Format(dt, layout, envs.ToBCP47(env.DefaultLocale()), dates.DateTimeLayouts) } // String returns the native string representation of this type diff --git a/excellent/types/datetime_test.go b/excellent/types/datetime_test.go index 33bff6868..f642d5d1d 100644 --- a/excellent/types/datetime_test.go +++ b/excellent/types/datetime_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" @@ -14,7 +15,7 @@ import ( func TestXDateTime(t *testing.T) { env := envs.NewBuilder().WithDateFormat(envs.DateFormatDayMonthYear).Build() - env2 := envs.NewBuilder().WithDateFormat(envs.DateFormatYearMonthDay).WithAllowedLanguages([]envs.Language{"spa"}).Build() + env2 := envs.NewBuilder().WithDateFormat(envs.DateFormatYearMonthDay).WithAllowedLanguages([]i18n.Language{"spa"}).Build() assert.True(t, types.NewXDateTime(time.Date(2018, 4, 9, 17, 1, 30, 123456789, time.UTC)).Truthy()) diff --git a/excellent/types/time.go b/excellent/types/time.go index a948a5db4..9d4f76613 100644 --- a/excellent/types/time.go +++ b/excellent/types/time.go @@ -44,7 +44,7 @@ func (x XTime) Format(env envs.Environment) string { // FormatCustom provides customised formatting func (x XTime) FormatCustom(env envs.Environment, layout string) (string, error) { - return x.Native().Format(layout, env.DefaultLocale().ToBCP47()) + return x.Native().Format(layout, envs.ToBCP47(env.DefaultLocale())) } // MarshalJSON is called when a struct containing this type is marshaled diff --git a/flows/actions/base.go b/flows/actions/base.go index 19fe3e989..a36c82232 100644 --- a/flows/actions/base.go +++ b/flows/actions/base.go @@ -8,11 +8,11 @@ import ( "strings" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" @@ -83,7 +83,7 @@ func (a *baseAction) Validate() error { return nil } func (a *baseAction) LocalizationUUID() uuids.UUID { return uuids.UUID(a.UUID_) } // helper function for actions that send a message (text + attachments) that must be localized and evalulated -func (a *baseAction) evaluateMessage(run flows.Run, languages []envs.Language, actionText string, actionAttachments []string, actionQuickReplies []string, logEvent flows.EventCallback) (string, []utils.Attachment, []string, envs.Language) { +func (a *baseAction) evaluateMessage(run flows.Run, languages []i18n.Language, actionText string, actionAttachments []string, actionQuickReplies []string, logEvent flows.EventCallback) (string, []utils.Attachment, []string, i18n.Language) { // localize and evaluate the message text localizedText, txtLang := run.GetTextArray(uuids.UUID(a.UUID()), "text", []string{actionText}, languages) evaluatedText, err := run.EvaluateTemplate(localizedText[0]) @@ -127,7 +127,7 @@ func (a *baseAction) evaluateMessage(run flows.Run, languages []envs.Language, a // although it's possible for the different parts of the message to have different languages, we want to resolve // a single language based on what the user actually provided for this message - var lang envs.Language + var lang i18n.Language if localizedText[0] != "" { lang = txtLang } else if len(translatedAttachments) > 0 { @@ -397,8 +397,8 @@ func resolveUser(run flows.Run, ref *assets.UserReference, logEvent flows.EventC return user } -func currentLocale(run flows.Run, lang envs.Language) envs.Locale { - return envs.NewLocale(lang, run.Session().MergedEnvironment().DefaultCountry()) +func currentLocale(run flows.Run, lang i18n.Language) i18n.Locale { + return i18n.NewLocale(lang, run.Session().MergedEnvironment().DefaultCountry()) } //------------------------------------------------------------------------------------------ diff --git a/flows/actions/base_test.go b/flows/actions/base_test.go index c5849f2d2..393299fb5 100644 --- a/flows/actions/base_test.go +++ b/flows/actions/base_test.go @@ -11,6 +11,7 @@ import ( "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -180,12 +181,12 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) { // and switch their language if tc.Localization != nil { - contact.SetLanguage(envs.Language("spa")) + contact.SetLanguage(i18n.Language("spa")) } } envBuilder := envs.NewBuilder(). - WithAllowedLanguages([]envs.Language{"eng", "spa"}). + WithAllowedLanguages([]i18n.Language{"eng", "spa"}). WithDefaultCountry("RW") if tc.RedactURNs { @@ -816,7 +817,7 @@ func TestStartSessionLoopProtection(t *testing.T) { require.NoError(t, err) flow := assets.NewFlowReference("5472a1c3-63e1-484f-8485-cc8ecb16a058", "Inception") - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) eng := engine.NewBuilder().Build() _, sprint, err := eng.NewSession(sa, triggers.NewBuilder(env, flow, contact).Manual().Build()) @@ -943,7 +944,7 @@ func TestStartSessionLoopProtectionWithInput(t *testing.T) { require.NoError(t, err) flow := assets.NewFlowReference("5472a1c3-63e1-484f-8485-cc8ecb16a058", "Inception") - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) eng := engine.NewBuilder().Build() session, sprint, err := eng.NewSession(sa, triggers.NewBuilder(env, flow, contact).Manual().Build()) diff --git a/flows/actions/send_broadcast.go b/flows/actions/send_broadcast.go index db6047a28..081830879 100644 --- a/flows/actions/send_broadcast.go +++ b/flows/actions/send_broadcast.go @@ -1,9 +1,9 @@ package actions import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" ) @@ -68,11 +68,11 @@ func (a *SendBroadcastAction) Execute(run flows.Run, step flows.Step, logModifie } translations := make(flows.BroadcastTranslations) - languages := append([]envs.Language{run.Flow().Language()}, run.Flow().Localization().Languages()...) + languages := append([]i18n.Language{run.Flow().Language()}, run.Flow().Localization().Languages()...) // evaluate the broadcast in each language we have translations for for _, language := range languages { - languages := []envs.Language{language, run.Flow().Language()} + languages := []i18n.Language{language, run.Flow().Language()} evaluatedText, evaluatedAttachments, evaluatedQuickReplies, _ := a.evaluateMessage(run, languages, a.Text, a.Attachments, a.QuickReplies, logEvent) translations[language] = &flows.BroadcastTranslation{ diff --git a/flows/actions/send_msg.go b/flows/actions/send_msg.go index b70617435..07b05bf0e 100644 --- a/flows/actions/send_msg.go +++ b/flows/actions/send_msg.go @@ -1,10 +1,10 @@ package actions import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" ) @@ -102,7 +102,7 @@ func (a *SendMsgAction) Execute(run flows.Run, step flows.Step, logModifier flow var templating *flows.MsgTemplating if a.Templating != nil { // looks for a translation in the contact locale or environment default - locales := []envs.Locale{ + locales := []i18n.Locale{ run.Session().MergedEnvironment().DefaultLocale(), run.Session().Environment().DefaultLocale(), } diff --git a/flows/actions/set_contact_language.go b/flows/actions/set_contact_language.go index 96bd3e7bd..87e6bc716 100644 --- a/flows/actions/set_contact_language.go +++ b/flows/actions/set_contact_language.go @@ -3,7 +3,7 @@ package actions import ( "strings" - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" "github.com/nyaruka/goflow/flows/modifiers" @@ -59,9 +59,9 @@ func (a *SetContactLanguageAction) Execute(run flows.Run, step flows.Step, logMo } // language must be empty or valid language code - lang := envs.NilLanguage + lang := i18n.NilLanguage if language != "" { - lang, err = envs.ParseLanguage(language) + lang, err = i18n.ParseLanguage(language) if err != nil { logEvent(events.NewError(err)) return nil diff --git a/flows/channel.go b/flows/channel.go index 2345df230..230770602 100644 --- a/flows/channel.go +++ b/flows/channel.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" @@ -112,7 +113,7 @@ func (s *ChannelAssets) GetForURN(urn *ContactURN, role assets.ChannelRole) *Cha // tel is a special case because we do number based matching if urn.URN().Scheme() == urns.TelScheme { - countryCode := envs.DeriveCountryFromTel(urn.URN().Path()) + countryCode := i18n.DeriveCountryFromTel(urn.URN().Path()) candidates := make([]*Channel, 0) for _, ch := range s.all { diff --git a/flows/contact.go b/flows/contact.go index 597e911f5..8cdbbecf7 100644 --- a/flows/contact.go +++ b/flows/contact.go @@ -8,6 +8,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -48,7 +49,7 @@ type Contact struct { uuid ContactUUID id ContactID name string - language envs.Language + language i18n.Language status ContactStatus timezone *time.Location createdOn time.Time @@ -68,7 +69,7 @@ func NewContact( uuid ContactUUID, id ContactID, name string, - language envs.Language, + language i18n.Language, status ContactStatus, timezone *time.Location, createdOn time.Time, @@ -105,7 +106,7 @@ func NewContact( } // NewEmptyContact creates a new empy contact with the passed in name, language and location -func NewEmptyContact(sa SessionAssets, name string, language envs.Language, timezone *time.Location) *Contact { +func NewEmptyContact(sa SessionAssets, name string, language i18n.Language, timezone *time.Location) *Contact { return &Contact{ uuid: ContactUUID(uuids.New()), name: name, @@ -159,40 +160,40 @@ func (c *Contact) UUID() ContactUUID { return c.uuid } func (c *Contact) ID() ContactID { return c.id } // SetLanguage sets the language for this contact -func (c *Contact) SetLanguage(lang envs.Language) { c.language = lang } +func (c *Contact) SetLanguage(lang i18n.Language) { c.language = lang } // Language gets the language for this contact -func (c *Contact) Language() envs.Language { return c.language } +func (c *Contact) Language() i18n.Language { return c.language } // Country gets the country for this contact.. // // TODO: currently this is derived from their preferred channel or any tel URNs but probably should become an explicit // field at some point -func (c *Contact) Country() envs.Country { +func (c *Contact) Country() i18n.Country { ch := c.PreferredChannel() - if ch != nil && ch.Country() != envs.NilCountry { + if ch != nil && ch.Country() != i18n.NilCountry { return ch.Country() } for _, u := range c.urns { if u.urn.Scheme() == urns.TelScheme { - c := envs.DeriveCountryFromTel(u.urn.Path()) - if c != envs.NilCountry { + c := i18n.DeriveCountryFromTel(u.urn.Path()) + if c != i18n.NilCountry { return c } } } - return envs.NilCountry + return i18n.NilCountry } // Locale gets the locale for this contact, using the environment country if contact doesn't have one -func (c *Contact) Locale(env envs.Environment) envs.Locale { +func (c *Contact) Locale(env envs.Environment) i18n.Locale { country := c.Country() - if country == envs.NilCountry { + if country == i18n.NilCountry { country = env.DefaultCountry() } - return envs.NewLocale(c.language, country) + return i18n.NewLocale(c.language, country) } // Status returns the contact status @@ -497,7 +498,7 @@ func (c *Contact) QueryProperty(env envs.Environment, key string, propType conta } return nil case contactql.AttributeLanguage: - if c.language != envs.NilLanguage { + if c.language != i18n.NilLanguage { return []any{string(c.language)} } return nil @@ -582,7 +583,7 @@ type contactEnvelope struct { UUID ContactUUID `json:"uuid" validate:"required,uuid4"` ID ContactID `json:"id,omitempty"` Name string `json:"name,omitempty"` - Language envs.Language `json:"language,omitempty"` + Language i18n.Language `json:"language,omitempty"` Status ContactStatus `json:"status,omitempty" validate:"omitempty,contact_status"` Stopped bool `json:"stopped,omitempty"` Blocked bool `json:"blocked,omitempty"` diff --git a/flows/contact_test.go b/flows/contact_test.go index 1bc9eddd5..4b19b59ce 100644 --- a/flows/contact_test.go +++ b/flows/contact_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -68,7 +69,7 @@ func TestContact(t *testing.T) { flows.ContactUUID(uuids.New()), flows.ContactID(12345), "Joe Bloggs", - envs.Language("eng"), + i18n.Language("eng"), flows.ContactStatusActive, tz, time.Date(2017, 12, 15, 10, 0, 0, 0, time.UTC), @@ -96,10 +97,10 @@ func TestContact(t *testing.T) { assert.Equal(t, "Joe Bloggs", contact.Name()) assert.Equal(t, flows.ContactID(12345), contact.ID()) assert.Equal(t, tz, contact.Timezone()) - assert.Equal(t, envs.Language("eng"), contact.Language()) + assert.Equal(t, i18n.Language("eng"), contact.Language()) assert.Equal(t, android, contact.PreferredChannel()) - assert.Equal(t, envs.Country("US"), contact.Country()) - assert.Equal(t, "en-US", contact.Locale(env).ToBCP47()) + assert.Equal(t, i18n.Country("US"), contact.Country()) + assert.Equal(t, i18n.Locale("eng-US"), contact.Locale(env)) contact.SetStatus(flows.ContactStatusStopped) assert.Equal(t, flows.ContactStatusStopped, contact.Status()) @@ -156,14 +157,14 @@ func TestContact(t *testing.T) { assert.Equal(t, "Joe Bloggs", clone.Name()) assert.Equal(t, flows.ContactID(12345), clone.ID()) assert.Equal(t, tz, clone.Timezone()) - assert.Equal(t, envs.Language("eng"), clone.Language()) - assert.Equal(t, envs.Country("US"), clone.Country()) + assert.Equal(t, i18n.Language("eng"), clone.Language()) + assert.Equal(t, i18n.Country("US"), clone.Country()) assert.Equal(t, android, clone.PreferredChannel()) assert.NotNil(t, contact.Ticket()) // country can be resolved from tel urns if there's no preferred channel clone.UpdatePreferredChannel(nil) - assert.Equal(t, envs.Country("US"), clone.Country()) + assert.Equal(t, i18n.Country("US"), clone.Country()) // can also clone a null contact! mrNil := (*flows.Contact)(nil) @@ -226,7 +227,7 @@ func TestContactFormat(t *testing.T) { sa, _ := engine.NewSessionAssets(env, static.NewEmptySource(), nil) // name takes precedence if set - contact := flows.NewEmptyContact(sa, "Joe", envs.NilLanguage, nil) + contact := flows.NewEmptyContact(sa, "Joe", i18n.NilLanguage, nil) contact.AddURN(urns.URN("twitter:joey"), nil) assert.Equal(t, "Joe", contact.Format(env)) @@ -236,7 +237,7 @@ func TestContactFormat(t *testing.T) { flows.ContactUUID(uuids.New()), flows.ContactID(1234), "", - envs.NilLanguage, + i18n.NilLanguage, flows.ContactStatusActive, nil, time.Now(), @@ -256,7 +257,7 @@ func TestContactFormat(t *testing.T) { assert.Equal(t, "1234", contact.Format(anonEnv)) // if we don't have name or URNs, then empty string - contact = flows.NewEmptyContact(sa, "", envs.NilLanguage, nil) + contact = flows.NewEmptyContact(sa, "", i18n.NilLanguage, nil) assert.Equal(t, "", contact.Format(env)) } @@ -271,7 +272,7 @@ func TestContactSetPreferredChannel(t *testing.T) { twitter1 := test.NewChannel("Twitter", "nyaruka", []string{"twitter", "twitterid"}, roles, nil) twitter2 := test.NewChannel("Twitter", "nyaruka", []string{"twitter", "twitterid"}, roles, nil) - contact := flows.NewEmptyContact(sa, "Joe", envs.NilLanguage, nil) + contact := flows.NewEmptyContact(sa, "Joe", i18n.NilLanguage, nil) contact.AddURN(urns.URN("twitter:joey"), nil) contact.AddURN(urns.URN("tel:+12345678999"), nil) contact.AddURN(urns.URN("tel:+18005555777"), nil) @@ -326,7 +327,7 @@ func TestReevaluateQueryBasedGroups(t *testing.T) { for _, tc := range tests { envBuilder := envs.NewBuilder(). - WithAllowedLanguages([]envs.Language{"eng", "spa"}). + WithAllowedLanguages([]i18n.Language{"eng", "spa"}). WithDefaultCountry("RW") if tc.RedactURNs { @@ -389,7 +390,7 @@ func TestContactEqual(t *testing.T) { assert.True(t, contact1.Equal(contact2)) - contact2.SetLanguage(envs.NilLanguage) + contact2.SetLanguage(i18n.NilLanguage) assert.False(t, contact1.Equal(contact2)) } diff --git a/flows/definition/flow.go b/flows/definition/flow.go index b84a43cfd..02a91f933 100644 --- a/flows/definition/flow.go +++ b/flows/definition/flow.go @@ -3,6 +3,8 @@ package definition import ( "encoding/json" + "github.com/Masterminds/semver" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -13,8 +15,6 @@ import ( "github.com/nyaruka/goflow/flows/inspect" "github.com/nyaruka/goflow/flows/inspect/issues" "github.com/nyaruka/goflow/utils" - - "github.com/Masterminds/semver" "github.com/pkg/errors" ) @@ -32,7 +32,7 @@ type flow struct { uuid assets.FlowUUID name string specVersion *semver.Version - language envs.Language + language i18n.Language flowType flows.FlowType revision int expireAfterMinutes int @@ -50,7 +50,7 @@ type flow struct { } // NewFlow creates a new flow -func NewFlow(uuid assets.FlowUUID, name string, language envs.Language, flowType flows.FlowType, revision int, expireAfterMinutes int, localization flows.Localization, nodes []flows.Node, ui json.RawMessage, a assets.Flow) (flows.Flow, error) { +func NewFlow(uuid assets.FlowUUID, name string, language i18n.Language, flowType flows.FlowType, revision int, expireAfterMinutes int, localization flows.Localization, nodes []flows.Node, ui json.RawMessage, a assets.Flow) (flows.Flow, error) { f := &flow{ uuid: uuid, name: name, @@ -81,7 +81,7 @@ func (f *flow) UUID() assets.FlowUUID { return f.uuid } func (f *flow) Name() string { return f.name } func (f *flow) SpecVersion() *semver.Version { return f.specVersion } func (f *flow) Revision() int { return f.revision } -func (f *flow) Language() envs.Language { return f.language } +func (f *flow) Language() i18n.Language { return f.language } func (f *flow) Type() flows.FlowType { return f.flowType } func (f *flow) ExpireAfterMinutes() int { return f.expireAfterMinutes } func (f *flow) Nodes() []flows.Node { return f.nodes } @@ -152,7 +152,7 @@ func (f *flow) Reference(withRevision bool) *assets.FlowReference { // ExtractTemplates extracts all non-empty templates func (f *flow) ExtractTemplates() []string { templates := make([]string, 0) - include := func(a flows.Action, r flows.Router, l envs.Language, t string) { + include := func(a flows.Action, r flows.Router, l i18n.Language, t string) { if t != "" { templates = append(templates, t) } @@ -185,7 +185,7 @@ func (f *flow) ExtractLocalizables() []string { // ChangeLanguage changes the language of the flow saving the current flow text as a translation and replacing it with // the specified translation. It returns an error if there are missing translations. -func (f *flow) ChangeLanguage(lang envs.Language) (flows.Flow, error) { +func (f *flow) ChangeLanguage(lang i18n.Language) (flows.Flow, error) { // make a copy of the flow copy, err := f.copy() if err != nil { @@ -245,7 +245,7 @@ func (f *flow) extract() ([]flows.ExtractedTemplate, []flows.ExtractedReference, assetRefs := make([]flows.ExtractedReference, 0) parentRefs := make(map[string]bool) - recordAssetRef := func(n flows.Node, a flows.Action, r flows.Router, l envs.Language, ref assets.Reference) { + recordAssetRef := func(n flows.Node, a flows.Action, r flows.Router, l i18n.Language, ref assets.Reference) { if ref != nil && !ref.Variable() { er := flows.NewExtractedReference(n, a, r, l, ref) assetRefs = append(assetRefs, er) @@ -253,7 +253,7 @@ func (f *flow) extract() ([]flows.ExtractedTemplate, []flows.ExtractedReference, } for _, n := range f.nodes { - n.EnumerateTemplates(f.Localization(), func(a flows.Action, r flows.Router, l envs.Language, t string) { + n.EnumerateTemplates(f.Localization(), func(a flows.Action, r flows.Router, l i18n.Language, t string) { templates = append(templates, flows.NewExtractedTemplate(n, a, r, l, t)) ars, prs := inspect.ExtractFromTemplate(t) for _, ref := range ars { @@ -263,7 +263,7 @@ func (f *flow) extract() ([]flows.ExtractedTemplate, []flows.ExtractedReference, parentRefs[r] = true } }) - n.EnumerateDependencies(f.Localization(), func(a flows.Action, r flows.Router, l envs.Language, ref assets.Reference) { + n.EnumerateDependencies(f.Localization(), func(a flows.Action, r flows.Router, l i18n.Language, ref assets.Reference) { recordAssetRef(n, a, r, l, ref) }) } @@ -311,7 +311,7 @@ var _ flows.Flow = (*flow)(nil) type flowEnvelope struct { migrations.Header13 - Language envs.Language `json:"language" validate:"required,language"` + Language i18n.Language `json:"language" validate:"required,language"` Type flows.FlowType `json:"type" validate:"required,flow_type"` Revision int `json:"revision"` ExpireAfterMinutes int `json:"expire_after_minutes"` diff --git a/flows/definition/flow_test.go b/flows/definition/flow_test.go index 19e426fee..174350eb4 100644 --- a/flows/definition/flow_test.go +++ b/flows/definition/flow_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/Masterminds/semver" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -20,7 +21,6 @@ import ( "github.com/nyaruka/goflow/flows/routers/waits" "github.com/nyaruka/goflow/flows/routers/waits/hints" "github.com/nyaruka/goflow/test" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -205,7 +205,7 @@ func TestNewFlow(t *testing.T) { flow, err := definition.NewFlow( assets.FlowUUID("8ca44c09-791d-453a-9799-a70dd3303306"), "Test Flow", // name - envs.Language("eng"), // base language + i18n.Language("eng"), // base language flows.FlowTypeMessaging, 123, // revision 30, // expires after minutes @@ -650,7 +650,7 @@ func TestChangeLanguage(t *testing.T) { flow, err := test.LoadFlowFromAssets(env, "testdata/change_language.json", "19cad1f2-9110-4271-98d4-1b968bf19410") require.NoError(t, err) - assertLanguageChange := func(lang envs.Language) { + assertLanguageChange := func(lang i18n.Language) { copy, err := flow.ChangeLanguage(lang) assert.NoError(t, err) diff --git a/flows/definition/legacy/definition.go b/flows/definition/legacy/definition.go index 52b2d4095..2d727c792 100644 --- a/flows/definition/legacy/definition.go +++ b/flows/definition/legacy/definition.go @@ -8,11 +8,11 @@ import ( "strconv" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/definition/legacy/expressions" "github.com/nyaruka/goflow/utils" @@ -28,7 +28,7 @@ import ( // Flow is a flow in the legacy format type Flow struct { - BaseLanguage envs.Language `json:"base_language"` + BaseLanguage i18n.Language `json:"base_language"` FlowType string `json:"flow_type"` RuleSets []RuleSet `json:"rule_sets" validate:"dive"` ActionSets []ActionSet `json:"action_sets" validate:"dive"` @@ -205,7 +205,7 @@ type Action struct { Label string `json:"label"` // set language - Language envs.Language `json:"lang"` + Language i18n.Language `json:"lang"` // add label action Labels []LabelReference `json:"labels"` @@ -309,7 +309,7 @@ var testTypeMappings = map[string]string{ } // migrates the given legacy action to a new action -func migrateAction(baseLanguage envs.Language, a Action, localization migratedLocalization, baseMediaURL string) (migratedAction, error) { +func migrateAction(baseLanguage i18n.Language, a Action, localization migratedLocalization, baseMediaURL string) (migratedAction, error) { switch a.Type { case "add_label": labels := make([]*assets.LabelReference, len(a.Labels)) @@ -372,7 +372,7 @@ func migrateAction(baseLanguage envs.Language, a Action, localization migratedLo return newStartSessionAction(a.UUID, flowRef, []urns.URN{}, contacts, groups, variables, createContact), nil case "reply", "send": media := make(Translations) - var quickReplies map[envs.Language][]string + var quickReplies map[i18n.Language][]string msg, err := ReadTranslations(a.Msg) if err != nil { @@ -512,7 +512,7 @@ func migrateAction(baseLanguage envs.Language, a Action, localization migratedLo } // migrates the given legacy rulset to a node with a router -func migrateRuleSet(lang envs.Language, r RuleSet, validDests map[uuids.UUID]bool, localization migratedLocalization) (migratedNode, UINodeType, NodeUIConfig, error) { +func migrateRuleSet(lang i18n.Language, r RuleSet, validDests map[uuids.UUID]bool, localization migratedLocalization) (migratedNode, UINodeType, NodeUIConfig, error) { var newActions []migratedAction var router migratedRouter var wait migratedWait @@ -753,7 +753,7 @@ type categoryAndExit struct { } // migrates a set of legacy rules to sets of categories, cases and exits -func migrateRules(baseLanguage envs.Language, r RuleSet, validDests map[uuids.UUID]bool, localization migratedLocalization, uiConfig NodeUIConfig) ([]migratedCase, []migratedCategory, uuids.UUID, uuids.UUID, []migratedExit, error) { +func migrateRules(baseLanguage i18n.Language, r RuleSet, validDests map[uuids.UUID]bool, localization migratedLocalization, uiConfig NodeUIConfig) ([]migratedCase, []migratedCategory, uuids.UUID, uuids.UUID, []migratedExit, error) { cases := make([]migratedCase, 0, len(r.Rules)) categories := make([]migratedCategory, 0, len(r.Rules)) exits := make([]migratedExit, 0, len(r.Rules)) @@ -832,7 +832,7 @@ func migrateRules(baseLanguage envs.Language, r RuleSet, validDests map[uuids.UU } // migrates the given legacy rule to a router case -func migrateRule(baseLanguage envs.Language, r Rule, category migratedCategory, localization migratedLocalization) (migratedCase, map[string]any, error) { +func migrateRule(baseLanguage i18n.Language, r Rule, category migratedCategory, localization migratedLocalization) (migratedCase, map[string]any, error) { newType := testTypeMappings[r.Test.Type] var arguments []string var err error @@ -974,7 +974,7 @@ func migrateRule(baseLanguage envs.Language, r Rule, category migratedCategory, } // migrates the given legacy actionset to a node with a set of migrated actions and a single exit -func migrateActionSet(lang envs.Language, a ActionSet, validDests map[uuids.UUID]bool, localization migratedLocalization, baseMediaURL string) (migratedNode, error) { +func migrateActionSet(lang i18n.Language, a ActionSet, validDests map[uuids.UUID]bool, localization migratedLocalization, baseMediaURL string) (migratedNode, error) { actions := make([]migratedAction, len(a.Actions)) // migrate each action diff --git a/flows/definition/legacy/utils.go b/flows/definition/legacy/utils.go index d3959eb37..b382dd407 100644 --- a/flows/definition/legacy/utils.go +++ b/flows/definition/legacy/utils.go @@ -5,15 +5,14 @@ import ( "fmt" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/utils" - "github.com/pkg/errors" ) // Translations is an inline translation map used for localization -type Translations map[envs.Language]string +type Translations map[i18n.Language]string // ReadTranslations reads a translations map func ReadTranslations(data json.RawMessage) (Translations, error) { @@ -25,7 +24,7 @@ func ReadTranslations(data json.RawMessage) (Translations, error) { } // Base looks up the translation in the given base language, or "base" -func (t Translations) Base(baseLanguage envs.Language) string { +func (t Translations) Base(baseLanguage i18n.Language) string { val, exists := t[baseLanguage] if exists { return val @@ -45,7 +44,7 @@ func (t *Translations) UnmarshalJSON(data []byte) error { return nil } - asMap := make(map[envs.Language]string) + asMap := make(map[i18n.Language]string) if err := jsonx.Unmarshal(data, &asMap); err != nil { return err } @@ -57,10 +56,9 @@ func (t *Translations) UnmarshalJSON(data []byte) error { // TransformTranslations transforms a list of single item translations into a map of multi-item translations, e.g. // // [{"eng": "yes", "fra": "oui"}, {"eng": "no", "fra": "non"}] becomes {"eng": ["yes", "no"], "fra": ["oui", "non"]} -// -func TransformTranslations(items []Translations) map[envs.Language][]string { +func TransformTranslations(items []Translations) map[i18n.Language][]string { // re-organize into a map of arrays - transformed := make(map[envs.Language][]string) + transformed := make(map[i18n.Language][]string) for i := range items { for language, translation := range items[i] { diff --git a/flows/definition/legacy/utils_test.go b/flows/definition/legacy/utils_test.go index 9b7de40ff..3f6770d09 100644 --- a/flows/definition/legacy/utils_test.go +++ b/flows/definition/legacy/utils_test.go @@ -3,8 +3,8 @@ package legacy_test import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows/definition/legacy" "github.com/stretchr/testify/assert" @@ -33,7 +33,7 @@ func TestTranslations(t *testing.T) { {"eng": "Maybe"}, {"eng": "Never", "fra": "Jamas"}, } - assert.Equal(t, map[envs.Language][]string{ + assert.Equal(t, map[i18n.Language][]string{ "eng": {"Yes", "No", "Maybe", "Never"}, "fra": {"Oui", "Non", "", "Jamas"}, }, legacy.TransformTranslations(translationSet)) diff --git a/flows/definition/legacy/v13.go b/flows/definition/legacy/v13.go index 63d174f98..0b2dfa5ae 100644 --- a/flows/definition/legacy/v13.go +++ b/flows/definition/legacy/v13.go @@ -1,13 +1,12 @@ package legacy import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/definition/legacy/expressions" - "github.com/shopspring/decimal" ) @@ -78,9 +77,9 @@ func (n migratedNode) UUID() uuids.UUID { return n["uuid"].(uuids.UUID) } -type migratedLocalization map[envs.Language]map[uuids.UUID]map[string][]string +type migratedLocalization map[i18n.Language]map[uuids.UUID]map[string][]string -func (l migratedLocalization) addTranslation(lang envs.Language, itemUUID uuids.UUID, property string, translated []string) { +func (l migratedLocalization) addTranslation(lang i18n.Language, itemUUID uuids.UUID, property string, translated []string) { _, found := l[lang] if !found { l[lang] = make(map[uuids.UUID]map[string][]string) @@ -95,7 +94,7 @@ func (l migratedLocalization) addTranslation(lang envs.Language, itemUUID uuids. langTranslations[itemUUID][property] = translated } -func (l migratedLocalization) addTranslationMap(baseLanguage envs.Language, mapped Translations, uuid uuids.UUID, property string) string { +func (l migratedLocalization) addTranslationMap(baseLanguage i18n.Language, mapped Translations, uuid uuids.UUID, property string) string { var inBaseLanguage string for language, item := range mapped { expression, _ := expressions.MigrateTemplate(item, nil) @@ -109,7 +108,7 @@ func (l migratedLocalization) addTranslationMap(baseLanguage envs.Language, mapp return inBaseLanguage } -func (l migratedLocalization) addTranslationMultiMap(baseLanguage envs.Language, mapped map[envs.Language][]string, uuid uuids.UUID, property string) []string { +func (l migratedLocalization) addTranslationMultiMap(baseLanguage i18n.Language, mapped map[i18n.Language][]string, uuid uuids.UUID, property string) []string { var inBaseLanguage []string for language, items := range mapped { templates := make([]string, len(items)) diff --git a/flows/definition/localization.go b/flows/definition/localization.go index 54c9e2d93..eb61f66dc 100644 --- a/flows/definition/localization.go +++ b/flows/definition/localization.go @@ -3,9 +3,9 @@ package definition import ( "encoding/json" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" ) @@ -65,7 +65,7 @@ func (t languageTranslation) Enumerate(callback func(uuids.UUID, string, []strin } // our top level container for all the translations for all languages -type localization map[envs.Language]languageTranslation +type localization map[i18n.Language]languageTranslation // NewLocalization creates a new empty localization func NewLocalization() flows.Localization { @@ -73,8 +73,8 @@ func NewLocalization() flows.Localization { } // Languages gets the list of languages included in this localization -func (l localization) Languages() []envs.Language { - languages := make([]envs.Language, 0, len(l)) +func (l localization) Languages() []i18n.Language { + languages := make([]i18n.Language, 0, len(l)) for lang := range l { languages = append(languages, lang) } @@ -82,7 +82,7 @@ func (l localization) Languages() []envs.Language { } // GetItemTranslation gets an item translation -func (l localization) GetItemTranslation(lang envs.Language, itemUUID uuids.UUID, property string) []string { +func (l localization) GetItemTranslation(lang i18n.Language, itemUUID uuids.UUID, property string) []string { translation, exists := l[lang] if exists { return translation.getTextArray(itemUUID, property) @@ -91,7 +91,7 @@ func (l localization) GetItemTranslation(lang envs.Language, itemUUID uuids.UUID } // SetItemTranslation sets an item translation -func (l localization) SetItemTranslation(lang envs.Language, itemUUID uuids.UUID, property string, translated []string) { +func (l localization) SetItemTranslation(lang i18n.Language, itemUUID uuids.UUID, property string, translated []string) { _, found := l[lang] if !found { l[lang] = make(languageTranslation) diff --git a/flows/definition/node.go b/flows/definition/node.go index b5acf094a..2c7fe8ed2 100644 --- a/flows/definition/node.go +++ b/flows/definition/node.go @@ -3,10 +3,10 @@ package definition import ( "encoding/json" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/actions" "github.com/nyaruka/goflow/flows/inspect" @@ -82,30 +82,30 @@ func (n *node) Validate(flow flows.Flow, seenUUIDs map[uuids.UUID]bool) error { } // EnumerateTemplates enumerates all expressions on this object -func (n *node) EnumerateTemplates(localization flows.Localization, include func(flows.Action, flows.Router, envs.Language, string)) { +func (n *node) EnumerateTemplates(localization flows.Localization, include func(flows.Action, flows.Router, i18n.Language, string)) { for _, action := range n.actions { - inspect.Templates(action, localization, func(l envs.Language, t string) { + inspect.Templates(action, localization, func(l i18n.Language, t string) { include(action, nil, l, t) }) } if n.router != nil { - n.router.EnumerateTemplates(localization, func(l envs.Language, t string) { + n.router.EnumerateTemplates(localization, func(l i18n.Language, t string) { include(nil, n.router, l, t) }) } } // EnumerateDependencies enumerates all dependencies on this object -func (n *node) EnumerateDependencies(localization flows.Localization, include func(flows.Action, flows.Router, envs.Language, assets.Reference)) { +func (n *node) EnumerateDependencies(localization flows.Localization, include func(flows.Action, flows.Router, i18n.Language, assets.Reference)) { for _, action := range n.actions { - inspect.Dependencies(action, localization, func(l envs.Language, r assets.Reference) { + inspect.Dependencies(action, localization, func(l i18n.Language, r assets.Reference) { include(action, nil, l, r) }) } if n.router != nil { - n.router.EnumerateDependencies(localization, func(l envs.Language, r assets.Reference) { + n.router.EnumerateDependencies(localization, func(l i18n.Language, r assets.Reference) { include(nil, n.router, l, r) }) } diff --git a/flows/engine/session_test.go b/flows/engine/session_test.go index e3cf8d809..1d958f703 100644 --- a/flows/engine/session_test.go +++ b/flows/engine/session_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -356,7 +357,7 @@ func TestSessionHistory(t *testing.T) { require.NoError(t, err) flow := assets.NewFlowReference("5472a1c3-63e1-484f-8485-cc8ecb16a058", "Inception") - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) // trigger session manually which will have no history eng := engine.NewBuilder().Build() diff --git a/flows/environment.go b/flows/environment.go index 7356a7c42..222369660 100644 --- a/flows/environment.go +++ b/flows/environment.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" "golang.org/x/exp/slices" @@ -109,29 +110,29 @@ func (e *sessionEnvironment) Timezone() *time.Location { return e.Environment.Timezone() } -func (e *sessionEnvironment) DefaultLanguage() envs.Language { +func (e *sessionEnvironment) DefaultLanguage() i18n.Language { contact := e.session.Contact() // if we have a contact and they have a language and it's an allowed language that overrides the base environment's languuage - if contact != nil && contact.Language() != envs.NilLanguage && slices.Contains(e.AllowedLanguages(), contact.Language()) { + if contact != nil && contact.Language() != i18n.NilLanguage && slices.Contains(e.AllowedLanguages(), contact.Language()) { return contact.Language() } return e.Environment.DefaultLanguage() } -func (e *sessionEnvironment) DefaultCountry() envs.Country { +func (e *sessionEnvironment) DefaultCountry() i18n.Country { contact := e.session.Contact() // if we have a contact and they have a preferred channel with a country that overrides the base environment's country if contact != nil { cc := contact.Country() - if cc != envs.NilCountry { + if cc != i18n.NilCountry { return cc } } return e.Environment.DefaultCountry() } -func (e *sessionEnvironment) DefaultLocale() envs.Locale { - return envs.NewLocale(e.DefaultLanguage(), e.DefaultCountry()) +func (e *sessionEnvironment) DefaultLocale() i18n.Locale { + return i18n.NewLocale(e.DefaultLanguage(), e.DefaultCountry()) } diff --git a/flows/environment_test.go b/flows/environment_test.go index 1b81f17a4..a1cace425 100644 --- a/flows/environment_test.go +++ b/flows/environment_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" "github.com/nyaruka/goflow/envs" @@ -74,7 +75,7 @@ func TestAssetsEnvironment(t *testing.T) { sa, err := engine.NewSessionAssets(env, source, nil) require.NoError(t, err) - contact := flows.NewEmptyContact(sa, "", envs.NilLanguage, nil) + contact := flows.NewEmptyContact(sa, "", i18n.NilLanguage, nil) trigger := triggers.NewBuilder(env, assets.NewFlowReference("76f0a02f-3b75-4b86-9064-e9195e1b3a02", "Test"), contact).Manual().Build() eng := engine.NewBuilder().Build() @@ -83,7 +84,7 @@ func TestAssetsEnvironment(t *testing.T) { require.NoError(t, err) aenv := flows.NewAssetsEnvironment(env, session.Assets().Locations()) - assert.Equal(t, envs.Country("RW"), aenv.DefaultCountry()) + assert.Equal(t, i18n.Country("RW"), aenv.DefaultCountry()) require.NotNil(t, aenv.LocationResolver()) kigali := aenv.LocationResolver().LookupLocation("Rwanda > Kigali City") @@ -113,7 +114,7 @@ func TestSessionEnvironment(t *testing.T) { tzUK, _ := time.LoadLocation("Europe/London") env := envs.NewBuilder(). - WithAllowedLanguages([]envs.Language{"eng", "fra", "kin"}). + WithAllowedLanguages([]i18n.Language{"eng", "fra", "kin"}). WithDefaultCountry("RW"). WithTimezone(tzRW). Build() @@ -134,31 +135,31 @@ func TestSessionEnvironment(t *testing.T) { // main environment on the session has the values we started with serializedEnv := session.Environment() - assert.Equal(t, envs.Language("eng"), serializedEnv.DefaultLanguage()) - assert.Equal(t, []envs.Language{"eng", "fra", "kin"}, serializedEnv.AllowedLanguages()) - assert.Equal(t, envs.Country("RW"), serializedEnv.DefaultCountry()) - assert.Equal(t, "en-RW", serializedEnv.DefaultLocale().ToBCP47()) + assert.Equal(t, i18n.Language("eng"), serializedEnv.DefaultLanguage()) + assert.Equal(t, []i18n.Language{"eng", "fra", "kin"}, serializedEnv.AllowedLanguages()) + assert.Equal(t, i18n.Country("RW"), serializedEnv.DefaultCountry()) + assert.Equal(t, i18n.Locale("eng-RW"), serializedEnv.DefaultLocale()) assert.Equal(t, tzRW, serializedEnv.Timezone()) // merged environment on the session has values from the contact mergedEnv := session.MergedEnvironment() - assert.Equal(t, envs.Language("fra"), mergedEnv.DefaultLanguage()) - assert.Equal(t, []envs.Language{"eng", "fra", "kin"}, mergedEnv.AllowedLanguages()) - assert.Equal(t, envs.Country("US"), mergedEnv.DefaultCountry()) - assert.Equal(t, "fr-US", mergedEnv.DefaultLocale().ToBCP47()) + assert.Equal(t, i18n.Language("fra"), mergedEnv.DefaultLanguage()) + assert.Equal(t, []i18n.Language{"eng", "fra", "kin"}, mergedEnv.AllowedLanguages()) + assert.Equal(t, i18n.Country("US"), mergedEnv.DefaultCountry()) + assert.Equal(t, i18n.Locale("fra-US"), mergedEnv.DefaultLocale()) assert.Equal(t, tzEC, mergedEnv.Timezone()) assert.NotNil(t, mergedEnv.LocationResolver()) // can make changes to contact - session.Contact().SetLanguage(envs.Language("kin")) + session.Contact().SetLanguage(i18n.Language("kin")) session.Contact().SetTimezone(tzUK) // and environment reflects those changes - assert.Equal(t, envs.Language("kin"), mergedEnv.DefaultLanguage()) + assert.Equal(t, i18n.Language("kin"), mergedEnv.DefaultLanguage()) assert.Equal(t, tzUK, mergedEnv.Timezone()) // if contact language is not an allowed language it won't be used - session.Contact().SetLanguage(envs.Language("spa")) - assert.Equal(t, envs.Language("eng"), mergedEnv.DefaultLanguage()) - assert.Equal(t, "en-US", mergedEnv.DefaultLocale().ToBCP47()) + session.Contact().SetLanguage(i18n.Language("spa")) + assert.Equal(t, i18n.Language("eng"), mergedEnv.DefaultLanguage()) + assert.Equal(t, i18n.Locale("eng-US"), mergedEnv.DefaultLocale()) } diff --git a/flows/events/base_test.go b/flows/events/base_test.go index f3a865ee2..c90bf37ee 100644 --- a/flows/events/base_test.go +++ b/flows/events/base_test.go @@ -12,6 +12,7 @@ import ( "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -106,7 +107,7 @@ func TestEventMarshaling(t *testing.T) { "eng": {Text: "Hello", Attachments: nil, QuickReplies: nil}, "spa": {Text: "Hola", Attachments: nil, QuickReplies: nil}, }, - envs.Language("eng"), + i18n.Language("eng"), []*assets.GroupReference{ assets.NewGroupReference(assets.GroupUUID("5f9fd4f7-4b0f-462a-a598-18bfc7810412"), "Supervisors"), }, @@ -260,7 +261,7 @@ func TestEventMarshaling(t *testing.T) { }`, }, { - events.NewContactLanguageChanged(envs.Language("fra")), + events.NewContactLanguageChanged(i18n.Language("fra")), `{ "created_on": "2018-10-18T14:20:30.000123456Z", "language": "fra", @@ -452,7 +453,7 @@ func TestEventMarshaling(t *testing.T) { "Hi there", nil, nil, nil, flows.NilMsgTopic, - envs.NilLocale, + i18n.NilLocale, flows.NilUnsendableReason, ), ), diff --git a/flows/events/broadcast_created.go b/flows/events/broadcast_created.go index ee6ef680b..f75af11b2 100644 --- a/flows/events/broadcast_created.go +++ b/flows/events/broadcast_created.go @@ -1,9 +1,9 @@ package events import ( + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" ) @@ -41,7 +41,7 @@ type BroadcastCreatedEvent struct { BaseEvent Translations flows.BroadcastTranslations `json:"translations" validate:"min=1,dive"` - BaseLanguage envs.Language `json:"base_language" validate:"required"` + BaseLanguage i18n.Language `json:"base_language" validate:"required"` Groups []*assets.GroupReference `json:"groups,omitempty" validate:"dive"` Contacts []*flows.ContactReference `json:"contacts,omitempty" validate:"dive"` ContactQuery string `json:"contact_query,omitempty"` @@ -49,7 +49,7 @@ type BroadcastCreatedEvent struct { } // NewBroadcastCreated creates a new outgoing msg event for the given recipients -func NewBroadcastCreated(translations flows.BroadcastTranslations, baseLanguage envs.Language, groups []*assets.GroupReference, contacts []*flows.ContactReference, contactQuery string, urns []urns.URN) *BroadcastCreatedEvent { +func NewBroadcastCreated(translations flows.BroadcastTranslations, baseLanguage i18n.Language, groups []*assets.GroupReference, contacts []*flows.ContactReference, contactQuery string, urns []urns.URN) *BroadcastCreatedEvent { return &BroadcastCreatedEvent{ BaseEvent: NewBaseEvent(TypeBroadcastCreated), Translations: translations, diff --git a/flows/events/contact_language_changed.go b/flows/events/contact_language_changed.go index 1219f60f9..4b17458d4 100644 --- a/flows/events/contact_language_changed.go +++ b/flows/events/contact_language_changed.go @@ -1,7 +1,7 @@ package events import ( - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/flows" ) @@ -28,7 +28,7 @@ type ContactLanguageChangedEvent struct { } // NewContactLanguageChanged returns a new contact language changed event -func NewContactLanguageChanged(language envs.Language) *ContactLanguageChangedEvent { +func NewContactLanguageChanged(language i18n.Language) *ContactLanguageChangedEvent { return &ContactLanguageChangedEvent{ BaseEvent: NewBaseEvent(TypeContactLanguageChanged), Language: string(language), diff --git a/flows/history_test.go b/flows/history_test.go index 16ae1b8d4..525d050ca 100644 --- a/flows/history_test.go +++ b/flows/history_test.go @@ -3,6 +3,7 @@ package flows_test import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" "github.com/nyaruka/goflow/envs" @@ -35,7 +36,7 @@ func TestHistory(t *testing.T) { require.NoError(t, err) flow := assets.NewFlowReference("5472a1c3-63e1-484f-8485-cc8ecb16a058", "Inception") - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) eng := engine.NewBuilder().Build() session, _, err := eng.NewSession(sa, triggers.NewBuilder(env, flow, contact).Manual().Build()) diff --git a/flows/info.go b/flows/info.go index 3863d07a4..184434921 100644 --- a/flows/info.go +++ b/flows/info.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/utils" ) @@ -13,7 +13,7 @@ type baseExtractedItem struct { Node Node Action Action Router Router - Language envs.Language + Language i18n.Language } // ExtractedTemplate is a template and its location in a flow @@ -24,7 +24,7 @@ type ExtractedTemplate struct { } // NewExtractedTemplate creates a new extracted template -func NewExtractedTemplate(n Node, a Action, r Router, l envs.Language, t string) ExtractedTemplate { +func NewExtractedTemplate(n Node, a Action, r Router, l i18n.Language, t string) ExtractedTemplate { return ExtractedTemplate{ baseExtractedItem: baseExtractedItem{Node: n, Action: a, Router: r, Language: l}, Template: t, @@ -39,7 +39,7 @@ type ExtractedReference struct { } // NewExtractedReference creates a new extracted reference -func NewExtractedReference(n Node, a Action, r Router, l envs.Language, ref assets.Reference) ExtractedReference { +func NewExtractedReference(n Node, a Action, r Router, l i18n.Language, ref assets.Reference) ExtractedReference { return ExtractedReference{ baseExtractedItem: baseExtractedItem{Node: n, Action: a, Router: r, Language: l}, Reference: ref, diff --git a/flows/inspect/dependencies.go b/flows/inspect/dependencies.go index d851f0a94..54c2cdb54 100644 --- a/flows/inspect/dependencies.go +++ b/flows/inspect/dependencies.go @@ -4,9 +4,9 @@ import ( "fmt" "reflect" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" ) @@ -98,15 +98,15 @@ func CheckReference(sa flows.SessionAssets, ref assets.Reference) bool { // DependencyContainer allows flow objects to declare other dependencies type DependencyContainer interface { - Dependencies(flows.Localization, func(envs.Language, assets.Reference)) + Dependencies(flows.Localization, func(i18n.Language, assets.Reference)) } // Dependencies extracts dependencies -func Dependencies(s any, localization flows.Localization, include func(envs.Language, assets.Reference)) { +func Dependencies(s any, localization flows.Localization, include func(i18n.Language, assets.Reference)) { dependencies(reflect.ValueOf(s), localization, include) } -func dependencies(v reflect.Value, localization flows.Localization, include func(envs.Language, assets.Reference)) { +func dependencies(v reflect.Value, localization flows.Localization, include func(i18n.Language, assets.Reference)) { walk( v, func(sv reflect.Value) { @@ -123,7 +123,7 @@ func dependencies(v reflect.Value, localization flows.Localization, include func ) } -func extractAssetReferences(v reflect.Value, include func(envs.Language, assets.Reference)) { +func extractAssetReferences(v reflect.Value, include func(i18n.Language, assets.Reference)) { if v.Kind() == reflect.Slice { // field is a slice of asset references for i := 0; i < v.Len(); i++ { @@ -133,7 +133,7 @@ func extractAssetReferences(v reflect.Value, include func(envs.Language, assets. // field is a single asset reference asRef, isRef := v.Interface().(assets.Reference) if isRef && asRef != nil && !asRef.Variable() { - include(envs.NilLanguage, asRef) + include(i18n.NilLanguage, asRef) } } } diff --git a/flows/inspect/dependencies_test.go b/flows/inspect/dependencies_test.go index cb624681f..97632cd10 100644 --- a/flows/inspect/dependencies_test.go +++ b/flows/inspect/dependencies_test.go @@ -3,6 +3,7 @@ package inspect_test import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" @@ -35,20 +36,20 @@ func TestDependencies(t *testing.T) { node2 := definition.NewNode("7c959933-4c30-4277-9810-adc95a459bd0", nil, router2, nil) refs := []flows.ExtractedReference{ - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewChannelReference("8286545d-d1a1-4eff-a3ad-a11ddf4bb20a", "Android")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewClassifierReference("2138cddc-118a-49ae-b290-98e03ad0573b", "Booking")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, flows.NewContactReference("0b099519-0889-4c74-b744-9122272f346a", "Bob")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewFieldReference("gender", "Gender")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewFlowReference("4f932672-7995-47f0-96e6-faf5abd2d81d", "Registration")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewGlobalReference("org_name", "Org Name")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewGroupReference("46057a92-6580-4e93-af36-2bb9c9d61e51", "Testers")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewGroupReference("377c3101-a7fc-47b1-9136-980348e362c0", "Customers")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewLabelReference("31c06b7c-010d-4f91-9590-d3fbdc2fb7ac", "Spam")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewTemplateReference("ff958d30-f50e-48ab-a524-37ed1e9620d9", "Welcome")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewTicketerReference("fb9cab80-4450-4a9d-ba9b-cb8df40dd233", "Support")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewTopicReference("531d3fc7-64f4-4170-927d-b477e8145dd3", "Weather")), - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, assets.NewUserReference("jim@nyaruka.com", "Jim")), - flows.NewExtractedReference(node2, nil, router2, envs.NilLanguage, assets.NewGlobalReference("org_name", "Org Name")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewChannelReference("8286545d-d1a1-4eff-a3ad-a11ddf4bb20a", "Android")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewClassifierReference("2138cddc-118a-49ae-b290-98e03ad0573b", "Booking")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, flows.NewContactReference("0b099519-0889-4c74-b744-9122272f346a", "Bob")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewFieldReference("gender", "Gender")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewFlowReference("4f932672-7995-47f0-96e6-faf5abd2d81d", "Registration")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewGlobalReference("org_name", "Org Name")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewGroupReference("46057a92-6580-4e93-af36-2bb9c9d61e51", "Testers")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewGroupReference("377c3101-a7fc-47b1-9136-980348e362c0", "Customers")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewLabelReference("31c06b7c-010d-4f91-9590-d3fbdc2fb7ac", "Spam")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewTemplateReference("ff958d30-f50e-48ab-a524-37ed1e9620d9", "Welcome")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewTicketerReference("fb9cab80-4450-4a9d-ba9b-cb8df40dd233", "Support")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewTopicReference("531d3fc7-64f4-4170-927d-b477e8145dd3", "Weather")), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, assets.NewUserReference("jim@nyaruka.com", "Jim")), + flows.NewExtractedReference(node2, nil, router2, i18n.NilLanguage, assets.NewGlobalReference("org_name", "Org Name")), } // if our assets only includes a single group, the other assets should be reported as missing @@ -149,7 +150,7 @@ func TestDependencies(t *testing.T) { // panic if we get a dependency type we don't recognize assert.Panics(t, func() { inspect.NewDependencies([]flows.ExtractedReference{ - flows.NewExtractedReference(node1, action1, nil, envs.NilLanguage, &unknownAssetType{}), + flows.NewExtractedReference(node1, action1, nil, i18n.NilLanguage, &unknownAssetType{}), }, sa) }) } diff --git a/flows/inspect/issues/base.go b/flows/inspect/issues/base.go index dc015dd14..fa727b919 100644 --- a/flows/inspect/issues/base.go +++ b/flows/inspect/issues/base.go @@ -3,7 +3,7 @@ package issues import ( "sort" - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/flows" ) @@ -21,12 +21,12 @@ type baseIssue struct { Type_ string `json:"type"` NodeUUID_ flows.NodeUUID `json:"node_uuid"` ActionUUID_ flows.ActionUUID `json:"action_uuid,omitempty"` - Language_ envs.Language `json:"language,omitempty"` + Language_ i18n.Language `json:"language,omitempty"` Description_ string `json:"description"` } // creates a new base issue -func newBaseIssue(typeName string, nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language envs.Language, description string) baseIssue { +func newBaseIssue(typeName string, nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language i18n.Language, description string) baseIssue { return baseIssue{ Type_: typeName, NodeUUID_: nodeUUID, @@ -46,7 +46,7 @@ func (p *baseIssue) NodeUUID() flows.NodeUUID { return p.NodeUUID_ } func (p *baseIssue) ActionUUID() flows.ActionUUID { return p.ActionUUID_ } // Language returns the translation language if the issue was found in a translation -func (p *baseIssue) Language() envs.Language { return p.Language_ } +func (p *baseIssue) Language() i18n.Language { return p.Language_ } // Description returns the description of the issue func (p *baseIssue) Description() string { return p.Description_ } diff --git a/flows/inspect/issues/base_test.go b/flows/inspect/issues/base_test.go index ef99f25a1..cf0a5c0dc 100644 --- a/flows/inspect/issues/base_test.go +++ b/flows/inspect/issues/base_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" @@ -116,6 +117,6 @@ func TestIssues(t *testing.T) { assert.Equal(t, issues.TypeMissingDependency, info.Issues[0].Type()) assert.Equal(t, flows.NodeUUID("a58be63b-907d-4a1a-856b-0bb5579d7507"), info.Issues[0].NodeUUID()) assert.Equal(t, flows.ActionUUID("f01d693b-2af2-49fb-9e38-146eb00937e9"), info.Issues[0].ActionUUID()) - assert.Equal(t, envs.NilLanguage, info.Issues[0].Language()) + assert.Equal(t, i18n.NilLanguage, info.Issues[0].Language()) assert.Equal(t, "missing field dependency 'county'", info.Issues[0].Description()) } diff --git a/flows/inspect/issues/invalid_regex.go b/flows/inspect/issues/invalid_regex.go index 30d83c658..c0c481264 100644 --- a/flows/inspect/issues/invalid_regex.go +++ b/flows/inspect/issues/invalid_regex.go @@ -4,7 +4,7 @@ import ( "fmt" "regexp" - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/excellent" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/inspect" @@ -25,7 +25,7 @@ type InvalidRegex struct { Regex string `json:"regex"` } -func newInvalidRegex(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language envs.Language, regex string) *InvalidRegex { +func newInvalidRegex(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language i18n.Language, regex string) *InvalidRegex { return &InvalidRegex{ baseIssue: newBaseIssue( TypeInvalidRegex, @@ -40,7 +40,7 @@ func newInvalidRegex(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, langu // InvalidRegexCheck checks for invalid regexes func InvalidRegexCheck(sa flows.SessionAssets, flow flows.Flow, tpls []flows.ExtractedTemplate, refs []flows.ExtractedReference, report func(flows.Issue)) { - checkTemplate := func(n flows.Node, a flows.Action, l envs.Language, t string) { + checkTemplate := func(n flows.Node, a flows.Action, l i18n.Language, t string) { // only check if template doesn't contain expressions if !excellent.HasExpressions(t, flows.RunContextTopLevels) { _, err := regexp.Compile(t) @@ -62,7 +62,7 @@ func InvalidRegexCheck(sa flows.SessionAssets, flow flows.Flow, tpls []flows.Ext if kase.Type == "has_pattern" && len(kase.Arguments) > 0 { checkTemplate(node, nil, "", kase.Arguments[0]) - inspect.Translations(flow.Localization(), kase.LocalizationUUID(), "arguments", func(l envs.Language, t string) { + inspect.Translations(flow.Localization(), kase.LocalizationUUID(), "arguments", func(l i18n.Language, t string) { checkTemplate(node, nil, l, t) }) } diff --git a/flows/inspect/issues/legacy_vars.go b/flows/inspect/issues/legacy_vars.go index e473a224e..b46092234 100644 --- a/flows/inspect/issues/legacy_vars.go +++ b/flows/inspect/issues/legacy_vars.go @@ -1,7 +1,7 @@ package issues import ( - "github.com/nyaruka/goflow/envs" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/actions" ) @@ -20,7 +20,7 @@ type LegacyVars struct { Vars []string `json:"vars"` } -func newLegacyVars(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language envs.Language, vars []string) *LegacyVars { +func newLegacyVars(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language i18n.Language, vars []string) *LegacyVars { return &LegacyVars{ baseIssue: newBaseIssue( TypeLegacyVars, @@ -41,7 +41,7 @@ func LegacyVarsCheck(sa flows.SessionAssets, flow flows.Flow, tpls []flows.Extra if a.Type() == actions.TypeStartSession { action := a.(*actions.StartSessionAction) if len(action.LegacyVars) > 0 { - report(newLegacyVars(node.UUID(), a.UUID(), envs.NilLanguage, action.LegacyVars)) + report(newLegacyVars(node.UUID(), a.UUID(), i18n.NilLanguage, action.LegacyVars)) } } } diff --git a/flows/inspect/issues/missing_dependency.go b/flows/inspect/issues/missing_dependency.go index 7f787d26e..316f5ad0b 100644 --- a/flows/inspect/issues/missing_dependency.go +++ b/flows/inspect/issues/missing_dependency.go @@ -3,8 +3,8 @@ package issues import ( "fmt" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/inspect" ) @@ -23,7 +23,7 @@ type MissingDependency struct { Dependency assets.TypedReference `json:"dependency"` } -func newMissingDependency(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language envs.Language, ref assets.Reference) *MissingDependency { +func newMissingDependency(nodeUUID flows.NodeUUID, actionUUID flows.ActionUUID, language i18n.Language, ref assets.Reference) *MissingDependency { return &MissingDependency{ baseIssue: newBaseIssue( TypeMissingDependency, diff --git a/flows/inspect/templates.go b/flows/inspect/templates.go index 580253ad1..d6df275f0 100644 --- a/flows/inspect/templates.go +++ b/flows/inspect/templates.go @@ -4,22 +4,22 @@ import ( "reflect" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/tools" "github.com/nyaruka/goflow/flows" ) // Templates extracts template values by reading engine tags on a struct -func Templates(s any, localization flows.Localization, include func(envs.Language, string)) { +func Templates(s any, localization flows.Localization, include func(i18n.Language, string)) { templateValues(reflect.ValueOf(s), localization, include) } -func templateValues(v reflect.Value, localization flows.Localization, include func(envs.Language, string)) { +func templateValues(v reflect.Value, localization flows.Localization, include func(i18n.Language, string)) { walk(v, nil, func(sv reflect.Value, fv reflect.Value, ef *EngineField) { if ef.Evaluated { - extractTemplates(fv, envs.NilLanguage, include) + extractTemplates(fv, i18n.NilLanguage, include) // if this field is also localized, each translation is a template and needs to be included if ef.Localized && localization != nil { @@ -31,7 +31,7 @@ func templateValues(v reflect.Value, localization flows.Localization, include fu }) } -func Translations(localization flows.Localization, itemUUID uuids.UUID, property string, include func(envs.Language, string)) { +func Translations(localization flows.Localization, itemUUID uuids.UUID, property string, include func(i18n.Language, string)) { for _, lang := range localization.Languages() { for _, v := range localization.GetItemTranslation(lang, itemUUID, property) { include(lang, v) @@ -41,7 +41,7 @@ func Translations(localization flows.Localization, itemUUID uuids.UUID, property // Evaluated tags can be applied to fields of type string, slices of string or map of strings. // This method extracts template values from any such field. -func extractTemplates(v reflect.Value, lang envs.Language, include func(envs.Language, string)) { +func extractTemplates(v reflect.Value, lang i18n.Language, include func(i18n.Language, string)) { switch typed := v.Interface().(type) { case map[string]string: for _, i := range typed { diff --git a/flows/inspect/templates_test.go b/flows/inspect/templates_test.go index 571c50c9b..df7cea7af 100644 --- a/flows/inspect/templates_test.go +++ b/flows/inspect/templates_test.go @@ -6,9 +6,9 @@ import ( "sort" "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/actions" "github.com/nyaruka/goflow/flows/definition" @@ -29,24 +29,24 @@ func (t *testFlowThing) LocalizationUUID() uuids.UUID { func TestTemplates(t *testing.T) { l := definition.NewLocalization() - l.SetItemTranslation(envs.Language("spa"), uuids.UUID("f50df34b-18f8-489b-b8e8-ccb14d720641"), "foo", []string{"Hola"}) + l.SetItemTranslation(i18n.Language("spa"), uuids.UUID("f50df34b-18f8-489b-b8e8-ccb14d720641"), "foo", []string{"Hola"}) thing := &testFlowThing{UUID: uuids.UUID("f50df34b-18f8-489b-b8e8-ccb14d720641"), Foo: "Hello", Bar: "World"} - templates := make(map[envs.Language][]string) - inspect.Templates(thing, l, func(l envs.Language, t string) { + templates := make(map[i18n.Language][]string) + inspect.Templates(thing, l, func(l i18n.Language, t string) { templates[l] = append(templates[l], t) }) - assert.Equal(t, map[envs.Language][]string{"": {"Hello", "World"}, "spa": {"Hola"}}, templates) + assert.Equal(t, map[i18n.Language][]string{"": {"Hello", "World"}, "spa": {"Hola"}}, templates) // can also extract from slice of things - templates = make(map[envs.Language][]string) - inspect.Templates([]*testFlowThing{thing}, l, func(l envs.Language, t string) { + templates = make(map[i18n.Language][]string) + inspect.Templates([]*testFlowThing{thing}, l, func(l i18n.Language, t string) { templates[l] = append(templates[l], t) }) - assert.Equal(t, map[envs.Language][]string{"": {"Hello", "World"}, "spa": {"Hola"}}, templates) + assert.Equal(t, map[i18n.Language][]string{"": {"Hello", "World"}, "spa": {"Hola"}}, templates) // or a slice of actions actions := []flows.Action{ @@ -54,12 +54,12 @@ func TestTemplates(t *testing.T) { actions.NewSetContactLanguage(flows.ActionUUID("d5ecd045-a15f-467c-925a-54bcdc726b9f"), "Gibberish"), } - templates = make(map[envs.Language][]string) - inspect.Templates(actions, nil, func(l envs.Language, t string) { + templates = make(map[i18n.Language][]string) + inspect.Templates(actions, nil, func(l i18n.Language, t string) { templates[l] = append(templates[l], t) }) - assert.Equal(t, map[envs.Language][]string{"": {"Bob", "Gibberish"}}, templates) + assert.Equal(t, map[i18n.Language][]string{"": {"Bob", "Gibberish"}}, templates) } func TestTemplatePaths(t *testing.T) { diff --git a/flows/interfaces.go b/flows/interfaces.go index 407e90f87..1c7d061ad 100644 --- a/flows/interfaces.go +++ b/flows/interfaces.go @@ -4,6 +4,7 @@ import ( "encoding/json" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/contactql" @@ -129,7 +130,7 @@ type Flow interface { UUID() assets.FlowUUID Name() string Revision() int - Language() envs.Language + Language() i18n.Language Type() FlowType ExpireAfterMinutes() int Localization() Localization @@ -143,7 +144,7 @@ type Flow interface { Inspect(sa SessionAssets) *Inspection ExtractTemplates() []string ExtractLocalizables() []string - ChangeLanguage(envs.Language) (Flow, error) + ChangeLanguage(i18n.Language) (Flow, error) } // Node is a single node in a flow @@ -155,8 +156,8 @@ type Node interface { Validate(Flow, map[uuids.UUID]bool) error - EnumerateTemplates(Localization, func(Action, Router, envs.Language, string)) - EnumerateDependencies(Localization, func(Action, Router, envs.Language, assets.Reference)) + EnumerateTemplates(Localization, func(Action, Router, i18n.Language, string)) + EnumerateDependencies(Localization, func(Action, Router, i18n.Language, assets.Reference)) EnumerateResults(func(Action, Router, *ResultInfo)) EnumerateLocalizables(func(uuids.UUID, string, []string, func([]string))) } @@ -194,8 +195,8 @@ type Router interface { Route(Run, Step, EventCallback) (ExitUUID, string, error) RouteTimeout(Run, Step, EventCallback) (ExitUUID, error) - EnumerateTemplates(Localization, func(envs.Language, string)) - EnumerateDependencies(Localization, func(envs.Language, assets.Reference)) + EnumerateTemplates(Localization, func(i18n.Language, string)) + EnumerateDependencies(Localization, func(i18n.Language, assets.Reference)) EnumerateResults(func(*ResultInfo)) EnumerateLocalizables(func(uuids.UUID, string, []string, func([]string))) } @@ -230,9 +231,9 @@ type Hint interface { // Localization provide a way to get the translations for a specific language type Localization interface { - GetItemTranslation(envs.Language, uuids.UUID, string) []string - SetItemTranslation(envs.Language, uuids.UUID, string, []string) - Languages() []envs.Language + GetItemTranslation(i18n.Language, uuids.UUID, string) []string + SetItemTranslation(i18n.Language, uuids.UUID, string, []string) + Languages() []i18n.Language } // Trigger represents something which can initiate a session with the flow engine @@ -423,8 +424,8 @@ type Run interface { EvaluateTemplate(string) (string, error) RootContext(envs.Environment) map[string]types.XValue - GetText(uuids.UUID, string, string) (string, envs.Language) - GetTextArray(uuids.UUID, string, []string, []envs.Language) ([]string, envs.Language) + GetText(uuids.UUID, string, string) (string, i18n.Language) + GetTextArray(uuids.UUID, string, []string, []i18n.Language) ([]string, i18n.Language) Snapshot() RunSummary Parent() RunSummary @@ -454,6 +455,6 @@ type Issue interface { NodeUUID() NodeUUID ActionUUID() ActionUUID - Language() envs.Language + Language() i18n.Language Description() string } diff --git a/flows/modifiers/base_test.go b/flows/modifiers/base_test.go index 2469cbd9b..d188def07 100644 --- a/flows/modifiers/base_test.go +++ b/flows/modifiers/base_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -159,7 +160,7 @@ func TestConstructors(t *testing.T) { }`, }, { - modifiers.NewLanguage(envs.Language("fra")), + modifiers.NewLanguage(i18n.Language("fra")), `{ "type": "language", "language": "fra" diff --git a/flows/modifiers/language.go b/flows/modifiers/language.go index d9c84b46e..111dffc9a 100644 --- a/flows/modifiers/language.go +++ b/flows/modifiers/language.go @@ -3,6 +3,7 @@ package modifiers import ( "encoding/json" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" @@ -21,11 +22,11 @@ const TypeLanguage string = "language" type LanguageModifier struct { baseModifier - Language envs.Language `json:"language"` + Language i18n.Language `json:"language"` } // NewLanguage creates a new language modifier -func NewLanguage(language envs.Language) *LanguageModifier { +func NewLanguage(language i18n.Language) *LanguageModifier { return &LanguageModifier{ baseModifier: newBaseModifier(TypeLanguage), Language: language, diff --git a/flows/msg.go b/flows/msg.go index 8ab87500e..edf7ee0ca 100644 --- a/flows/msg.go +++ b/flows/msg.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/go-playground/validator/v10" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -65,7 +66,7 @@ type MsgOut struct { QuickReplies_ []string `json:"quick_replies,omitempty"` Templating_ *MsgTemplating `json:"templating,omitempty"` Topic_ MsgTopic `json:"topic,omitempty"` - Locale_ envs.Locale `json:"locale,omitempty"` + Locale_ i18n.Locale `json:"locale,omitempty"` UnsendableReason_ UnsendableReason `json:"unsendable_reason,omitempty"` } @@ -83,7 +84,7 @@ func NewMsgIn(uuid MsgUUID, urn urns.URN, channel *assets.ChannelReference, text } // NewMsgOut creates a new outgoing message -func NewMsgOut(urn urns.URN, channel *assets.ChannelReference, text string, attachments []utils.Attachment, quickReplies []string, templating *MsgTemplating, topic MsgTopic, locale envs.Locale, reason UnsendableReason) *MsgOut { +func NewMsgOut(urn urns.URN, channel *assets.ChannelReference, text string, attachments []utils.Attachment, quickReplies []string, templating *MsgTemplating, topic MsgTopic, locale i18n.Locale, reason UnsendableReason) *MsgOut { return &MsgOut{ BaseMsg: BaseMsg{ UUID_: MsgUUID(uuids.New()), @@ -101,7 +102,7 @@ func NewMsgOut(urn urns.URN, channel *assets.ChannelReference, text string, atta } // NewIVRMsgOut creates a new outgoing message for IVR -func NewIVRMsgOut(urn urns.URN, channel *assets.ChannelReference, text string, audioURL string, locale envs.Locale) *MsgOut { +func NewIVRMsgOut(urn urns.URN, channel *assets.ChannelReference, text string, audioURL string, locale i18n.Locale) *MsgOut { var attachments []utils.Attachment if audioURL != "" { attachments = []utils.Attachment{utils.Attachment(fmt.Sprintf("audio:%s", audioURL))} @@ -162,7 +163,7 @@ func (m *MsgOut) Templating() *MsgTemplating { return m.Templating_ } func (m *MsgOut) Topic() MsgTopic { return m.Topic_ } // Locale returns the locale of this message (if any) -func (m *MsgOut) Locale() envs.Locale { return m.Locale_ } +func (m *MsgOut) Locale() i18n.Locale { return m.Locale_ } // UnsendableReason returns the reason this message can't be sent (if any) func (m *MsgOut) UnsendableReason() UnsendableReason { return m.UnsendableReason_ } @@ -199,12 +200,12 @@ type BroadcastTranslation struct { QuickReplies []string `json:"quick_replies,omitempty"` } -type BroadcastTranslations map[envs.Language]*BroadcastTranslation +type BroadcastTranslations map[i18n.Language]*BroadcastTranslation // ForContact is a utility to help callers select the translation for a contact -func (b BroadcastTranslations) ForContact(e envs.Environment, c *Contact, baseLanguage envs.Language) (*BroadcastTranslation, envs.Language) { +func (b BroadcastTranslations) ForContact(e envs.Environment, c *Contact, baseLanguage i18n.Language) (*BroadcastTranslation, i18n.Language) { // first try the contact language if it is valid - if c.Language() != envs.NilLanguage && slices.Contains(e.AllowedLanguages(), c.Language()) { + if c.Language() != i18n.NilLanguage && slices.Contains(e.AllowedLanguages(), c.Language()) { t := b[c.Language()] if t != nil { return t, c.Language() diff --git a/flows/msg_test.go b/flows/msg_test.go index e218e707b..ee1f5f76a 100644 --- a/flows/msg_test.go +++ b/flows/msg_test.go @@ -3,6 +3,7 @@ package flows_test import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -126,9 +127,9 @@ func TestBroadcastTranslations(t *testing.T) { "fra": &flows.BroadcastTranslation{Text: "Bonjour"}, "spa": &flows.BroadcastTranslation{Text: "Hola"}, } - baseLanguage := envs.Language("eng") + baseLanguage := i18n.Language("eng") - assertTranslation := func(contactLanguage envs.Language, allowedLanguages []envs.Language, expectedText string, expectedLang envs.Language) { + assertTranslation := func(contactLanguage i18n.Language, allowedLanguages []i18n.Language, expectedText string, expectedLang i18n.Language) { env := envs.NewBuilder().WithAllowedLanguages(allowedLanguages).Build() sa, err := engine.NewSessionAssets(env, static.NewEmptySource(), nil) require.NoError(t, err) @@ -140,11 +141,11 @@ func TestBroadcastTranslations(t *testing.T) { assert.Equal(t, expectedLang, lang) } - assertTranslation("eng", []envs.Language{"eng"}, "Hello", "eng") // uses contact language - assertTranslation("fra", []envs.Language{"eng", "fra"}, "Bonjour", "fra") // uses contact language - assertTranslation("kin", []envs.Language{"eng", "spa"}, "Hello", "eng") // uses default flow language - assertTranslation("kin", []envs.Language{"spa", "eng"}, "Hola", "spa") // uses default flow language - assertTranslation("kin", []envs.Language{"kin"}, "Hello", "eng") // uses base language + assertTranslation("eng", []i18n.Language{"eng"}, "Hello", "eng") // uses contact language + assertTranslation("fra", []i18n.Language{"eng", "fra"}, "Bonjour", "fra") // uses contact language + assertTranslation("kin", []i18n.Language{"eng", "spa"}, "Hello", "eng") // uses default flow language + assertTranslation("kin", []i18n.Language{"spa", "eng"}, "Hola", "spa") // uses default flow language + assertTranslation("kin", []i18n.Language{"kin"}, "Hello", "eng") // uses base language val, err := bcastTrans.Value() assert.NoError(t, err) diff --git a/flows/resumes/base_test.go b/flows/resumes/base_test.go index 072403ed4..8423ac52a 100644 --- a/flows/resumes/base_test.go +++ b/flows/resumes/base_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -97,7 +98,7 @@ func testResumeType(t *testing.T, assetsJSON json.RawMessage, typeName string) { // start a waiting session env := envs.NewBuilder().Build() eng := engine.NewBuilder().Build() - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) tb := triggers.NewBuilder(env, flow.Reference(false), contact).Manual() if flow.Type() == flows.FlowTypeVoice { channel := sa.Channels().Get("a78930fe-6a40-4aa8-99c3-e61b02f45ca1") diff --git a/flows/routers/base.go b/flows/routers/base.go index 1bfef830d..b696ecce2 100644 --- a/flows/routers/base.go +++ b/flows/routers/base.go @@ -5,16 +5,15 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" "github.com/nyaruka/goflow/flows/routers/waits" "github.com/nyaruka/goflow/utils" - "github.com/pkg/errors" ) @@ -67,11 +66,11 @@ func (r *baseRouter) AllowTimeout() bool { func (r *baseRouter) ResultName() string { return r.resultName } // EnumerateTemplates enumerates all expressions on this object and its children -func (r *baseRouter) EnumerateTemplates(localization flows.Localization, include func(envs.Language, string)) { +func (r *baseRouter) EnumerateTemplates(localization flows.Localization, include func(i18n.Language, string)) { } // EnumerateDependencies enumerates all dependencies on this object -func (r *baseRouter) EnumerateDependencies(localization flows.Localization, include func(envs.Language, assets.Reference)) { +func (r *baseRouter) EnumerateDependencies(localization flows.Localization, include func(i18n.Language, assets.Reference)) { } // EnumerateResults enumerates all potential results on this object diff --git a/flows/routers/cases/tests_test.go b/flows/routers/cases/tests_test.go index 6585cf0b4..5102d065a 100644 --- a/flows/routers/cases/tests_test.go +++ b/flows/routers/cases/tests_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" "github.com/nyaruka/goflow/envs" @@ -504,7 +505,7 @@ func TestTests(t *testing.T) { WithDateFormat(envs.DateFormatDayMonthYear). WithTimeFormat(envs.TimeFormatHourMinuteSecond). WithTimezone(kgl). - WithDefaultCountry(envs.Country("RW")). + WithDefaultCountry(i18n.Country("RW")). Build() source, err := static.NewSource([]byte(assetsJSON)) @@ -513,7 +514,7 @@ func TestTests(t *testing.T) { sa, err := engine.NewSessionAssets(env, source, nil) require.NoError(t, err) - contact := flows.NewEmptyContact(sa, "", envs.NilLanguage, nil) + contact := flows.NewEmptyContact(sa, "", i18n.NilLanguage, nil) trigger := triggers.NewBuilder(env, assets.NewFlowReference("76f0a02f-3b75-4b86-9064-e9195e1b3a02", "Test"), contact).Manual().Build() eng := engine.NewBuilder().Build() @@ -606,7 +607,7 @@ func TestHasPhone(t *testing.T) { {"oui", "CD", ""}, } - env := envs.NewBuilder().WithDefaultCountry(envs.Country("RW")).Build() + env := envs.NewBuilder().WithDefaultCountry(i18n.Country("RW")).Build() for _, tc := range tests { var actual, expected types.XValue diff --git a/flows/routers/switch.go b/flows/routers/switch.go index cf36a4529..caa07dc4a 100644 --- a/flows/routers/switch.go +++ b/flows/routers/switch.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/inspect" @@ -47,7 +47,7 @@ func NewCase(uuid uuids.UUID, type_ string, arguments []string, categoryUUID flo func (c *Case) LocalizationUUID() uuids.UUID { return uuids.UUID(c.UUID) } // Dependencies enumerates the dependencies on this case -func (c *Case) Dependencies(localization flows.Localization, include func(envs.Language, assets.Reference)) { +func (c *Case) Dependencies(localization flows.Localization, include func(i18n.Language, assets.Reference)) { groupRef := func(args []string) assets.Reference { // if we have two args, the second is name name := "" @@ -59,7 +59,7 @@ func (c *Case) Dependencies(localization flows.Localization, include func(envs.L // currently only the HAS_GROUP router test can produce a dependency if c.Type == "has_group" && len(c.Arguments) > 0 { - include(envs.NilLanguage, groupRef(c.Arguments)) + include(i18n.NilLanguage, groupRef(c.Arguments)) // the group UUID might be different in different translations for _, lang := range localization.Languages() { @@ -219,14 +219,14 @@ func (r *SwitchRouter) matchCase(run flows.Run, step flows.Step, operand types.X } // EnumerateTemplates enumerates all expressions on this object and its children -func (r *SwitchRouter) EnumerateTemplates(localization flows.Localization, include func(envs.Language, string)) { - include(envs.NilLanguage, r.operand) +func (r *SwitchRouter) EnumerateTemplates(localization flows.Localization, include func(i18n.Language, string)) { + include(i18n.NilLanguage, r.operand) inspect.Templates(r.cases, localization, include) } // EnumerateDependencies enumerates all dependencies on this object and its children -func (r *SwitchRouter) EnumerateDependencies(localization flows.Localization, include func(envs.Language, assets.Reference)) { +func (r *SwitchRouter) EnumerateDependencies(localization flows.Localization, include func(i18n.Language, assets.Reference)) { inspect.Dependencies(r.cases, localization, include) } diff --git a/flows/runs/run.go b/flows/runs/run.go index 7b597f03f..3c3649c3a 100644 --- a/flows/runs/run.go +++ b/flows/runs/run.go @@ -5,6 +5,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" @@ -327,18 +328,18 @@ func (r *run) EvaluateTemplate(template string) (string, error) { } // get the ordered list of languages to be used for localization in this run -func (r *run) getLanguages() []envs.Language { - languages := make([]envs.Language, 0, 3) +func (r *run) getLanguages() []i18n.Language { + languages := make([]i18n.Language, 0, 3) // if contact has an allowed language, it takes priority contactLanguage := r.session.MergedEnvironment().DefaultLanguage() - if contactLanguage != envs.NilLanguage { + if contactLanguage != i18n.NilLanguage { languages = append(languages, contactLanguage) } // next we include the default language if it's different to the contact language defaultLanguage := r.session.Environment().DefaultLanguage() - if defaultLanguage != envs.NilLanguage && defaultLanguage != contactLanguage { + if defaultLanguage != i18n.NilLanguage && defaultLanguage != contactLanguage { languages = append(languages, defaultLanguage) } @@ -348,17 +349,17 @@ func (r *run) getLanguages() []envs.Language { } // GetText is a convenience version of GetTextArray for a single text values -func (r *run) GetText(uuid uuids.UUID, key string, native string) (string, envs.Language) { +func (r *run) GetText(uuid uuids.UUID, key string, native string) (string, i18n.Language) { textArray, lang := r.getText(uuid, key, []string{native}, nil) return textArray[0], lang } // GetTextArray returns the localized value for the given flow definition value -func (r *run) GetTextArray(uuid uuids.UUID, key string, native []string, languages []envs.Language) ([]string, envs.Language) { +func (r *run) GetTextArray(uuid uuids.UUID, key string, native []string, languages []i18n.Language) ([]string, i18n.Language) { return r.getText(uuid, key, native, languages) } -func (r *run) getText(uuid uuids.UUID, key string, native []string, languages []envs.Language) ([]string, envs.Language) { +func (r *run) getText(uuid uuids.UUID, key string, native []string, languages []i18n.Language) ([]string, i18n.Language) { nativeLang := r.Flow().Language() // if a preferred language list wasn't provided, default to the run preferred languages diff --git a/flows/runs/run_test.go b/flows/runs/run_test.go index 078f08c05..da182eac0 100644 --- a/flows/runs/run_test.go +++ b/flows/runs/run_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -330,8 +331,8 @@ func TestTranslation(t *testing.T) { tcs := []struct { description string - envLangs []envs.Language - contactLang envs.Language + envLangs []i18n.Language + contactLang i18n.Language msgAction []byte expectedText string expectedAttachments []utils.Attachment @@ -339,7 +340,7 @@ func TestTranslation(t *testing.T) { }{ { description: "contact language is valid and is flow base language, msg action has all fields", - envLangs: []envs.Language{"eng", "spa"}, + envLangs: []i18n.Language{"eng", "spa"}, contactLang: "eng", msgAction: msgAction1, expectedText: "Hello", @@ -351,7 +352,7 @@ func TestTranslation(t *testing.T) { }, { description: "contact language is valid and translations exist, msg action has all fields", - envLangs: []envs.Language{"eng", "spa"}, + envLangs: []i18n.Language{"eng", "spa"}, contactLang: "spa", msgAction: msgAction1, expectedText: "Hola", @@ -362,7 +363,7 @@ func TestTranslation(t *testing.T) { }, { description: "contact language is allowed but no translations exist, msg action has all fields", - envLangs: []envs.Language{"eng", "spa", "kin"}, + envLangs: []i18n.Language{"eng", "spa", "kin"}, contactLang: "kin", msgAction: msgAction1, expectedText: "Hello", @@ -374,7 +375,7 @@ func TestTranslation(t *testing.T) { }, { description: "contact language is not allowed and translations exist, msg action has all fields", - envLangs: []envs.Language{"eng"}, + envLangs: []i18n.Language{"eng"}, contactLang: "spa", msgAction: msgAction1, expectedText: "Hello", @@ -386,7 +387,7 @@ func TestTranslation(t *testing.T) { }, { description: "contact language is valid and is flow base language, msg action only has text", - envLangs: []envs.Language{"eng", "spa"}, + envLangs: []i18n.Language{"eng", "spa"}, contactLang: "eng", msgAction: msgAction2, expectedText: "Hello", @@ -395,7 +396,7 @@ func TestTranslation(t *testing.T) { }, { description: "contact language is valid and translations exist, msg action only has text", - envLangs: []envs.Language{"eng", "spa"}, + envLangs: []i18n.Language{"eng", "spa"}, contactLang: "spa", msgAction: msgAction2, expectedText: "Hola", @@ -406,7 +407,7 @@ func TestTranslation(t *testing.T) { }, { description: "attachments and quick replies translations are single empty strings and should be ignored", - envLangs: []envs.Language{"eng", "fra"}, + envLangs: []i18n.Language{"eng", "fra"}, contactLang: "fra", msgAction: msgAction1, expectedText: "Bonjour", diff --git a/flows/template.go b/flows/template.go index 1fecafbb7..aa5e29d76 100644 --- a/flows/template.go +++ b/flows/template.go @@ -5,8 +5,8 @@ import ( "regexp" "strings" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" - "github.com/nyaruka/goflow/envs" ) // Template represents messaging templates used by channels types such as WhatsApp @@ -31,14 +31,14 @@ func (t *Template) Reference() *assets.TemplateReference { } // FindTranslation finds the matching translation for the passed in channel and languages (in priority order) -func (t *Template) FindTranslation(channel assets.ChannelUUID, locales []envs.Locale) *TemplateTranslation { +func (t *Template) FindTranslation(channel assets.ChannelUUID, locales []i18n.Locale) *TemplateTranslation { // first iterate through and find all translations that are for this channel - candidatesByLocale := make(map[envs.Locale]*TemplateTranslation) - candidatesByLang := make(map[envs.Language]*TemplateTranslation) + candidatesByLocale := make(map[i18n.Locale]*TemplateTranslation) + candidatesByLang := make(map[i18n.Language]*TemplateTranslation) for _, tr := range t.Template.Translations() { if tr.Channel().UUID == channel { tt := NewTemplateTranslation(tr) - lang, _ := tt.Locale().ToParts() + lang, _ := tt.Locale().Split() candidatesByLocale[tt.Locale()] = tt candidatesByLang[lang] = tt @@ -55,7 +55,7 @@ func (t *Template) FindTranslation(channel assets.ChannelUUID, locales []envs.Lo // if that fails look for language match for _, locale := range locales { - lang, _ := locale.ToParts() + lang, _ := locale.Split() tt := candidatesByLang[lang] if tt != nil { return tt @@ -122,7 +122,7 @@ func (a *TemplateAssets) Get(uuid assets.TemplateUUID) *Template { // FindTranslation looks through our list of templates to find the template matching the passed in uuid // If no template or translation is found then empty string is returned -func (a *TemplateAssets) FindTranslation(uuid assets.TemplateUUID, channel *assets.ChannelReference, locales []envs.Locale) *TemplateTranslation { +func (a *TemplateAssets) FindTranslation(uuid assets.TemplateUUID, channel *assets.ChannelReference, locales []i18n.Locale) *TemplateTranslation { // no channel, can't match to a template if channel == nil { return nil diff --git a/flows/template_test.go b/flows/template_test.go index a9910b253..87093c488 100644 --- a/flows/template_test.go +++ b/flows/template_test.go @@ -3,9 +3,9 @@ package flows import ( "testing" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" - "github.com/nyaruka/goflow/envs" "github.com/stretchr/testify/assert" ) @@ -23,7 +23,7 @@ func TestTemplateTranslation(t *testing.T) { channel := assets.NewChannelReference("0bce5fd3-c215-45a0-bcb8-2386eb194175", "Test Channel") for i, tc := range tcs { - tt := NewTemplateTranslation(static.NewTemplateTranslation(*channel, envs.Locale("eng-US"), tc.Content, len(tc.Variables), "a6a8863e_7879_4487_ad24_5e2ea429027c")) + tt := NewTemplateTranslation(static.NewTemplateTranslation(*channel, i18n.Locale("eng-US"), tc.Content, len(tc.Variables), "a6a8863e_7879_4487_ad24_5e2ea429027c")) result := tt.Substitute(tc.Variables) assert.Equal(t, tc.Expected, result, "%d: unexpected template substitution", i) } @@ -31,9 +31,9 @@ func TestTemplateTranslation(t *testing.T) { func TestTemplates(t *testing.T) { channel1 := assets.NewChannelReference("0bce5fd3-c215-45a0-bcb8-2386eb194175", "Test Channel") - tt1 := static.NewTemplateTranslation(*channel1, envs.Locale("eng"), "Hello {{1}}", 1, "") - tt2 := static.NewTemplateTranslation(*channel1, envs.Locale("spa-EC"), "Que tal {{1}}", 1, "") - tt3 := static.NewTemplateTranslation(*channel1, envs.Locale("spa-ES"), "Hola {{1}}", 1, "") + tt1 := static.NewTemplateTranslation(*channel1, i18n.Locale("eng"), "Hello {{1}}", 1, "") + tt2 := static.NewTemplateTranslation(*channel1, i18n.Locale("spa-EC"), "Que tal {{1}}", 1, "") + tt3 := static.NewTemplateTranslation(*channel1, i18n.Locale("spa-ES"), "Hola {{1}}", 1, "") template := NewTemplate(static.NewTemplate("c520cbda-e118-440f-aaf6-c0485088384f", "greeting", []*static.TemplateTranslation{tt1, tt2, tt3})) tas := NewTemplateAssets([]assets.Template{template}) @@ -41,49 +41,49 @@ func TestTemplates(t *testing.T) { tcs := []struct { UUID assets.TemplateUUID Channel *assets.ChannelReference - Locales []envs.Locale + Locales []i18n.Locale Variables []string Expected string }{ { "c520cbda-e118-440f-aaf6-c0485088384f", channel1, - []envs.Locale{"eng-US", "spa-CO"}, + []i18n.Locale{"eng-US", "spa-CO"}, []string{"Chef"}, "Hello Chef", }, { "c520cbda-e118-440f-aaf6-c0485088384f", channel1, - []envs.Locale{"eng", "spa-CO"}, + []i18n.Locale{"eng", "spa-CO"}, []string{"Chef"}, "Hello Chef", }, { "c520cbda-e118-440f-aaf6-c0485088384f", channel1, - []envs.Locale{"deu-DE", "spa-ES"}, + []i18n.Locale{"deu-DE", "spa-ES"}, []string{"Chef"}, "Hola Chef", }, { "c520cbda-e118-440f-aaf6-c0485088384f", nil, - []envs.Locale{"deu-DE", "spa-ES"}, + []i18n.Locale{"deu-DE", "spa-ES"}, []string{"Chef"}, "", }, { "c520cbda-e118-440f-aaf6-c0485088384f", channel1, - []envs.Locale{"deu-DE"}, + []i18n.Locale{"deu-DE"}, []string{"Chef"}, "", }, { "8c5d4910-114a-4521-ba1d-bde8b024865a", channel1, - []envs.Locale{"eng-US", "spa-ES"}, + []i18n.Locale{"eng-US", "spa-ES"}, []string{"Chef"}, "", }, diff --git a/flows/translation/flows.go b/flows/translation/flows.go index eef5b0da4..8fa8853c6 100644 --- a/flows/translation/flows.go +++ b/flows/translation/flows.go @@ -9,11 +9,12 @@ import ( "strings" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/utils" - "github.com/nyaruka/goflow/utils/i18n" + "github.com/nyaruka/goflow/utils/po" ) // describes the location of a piece of extracted text @@ -42,27 +43,27 @@ type localizedText struct { Unique bool } -func getBaseLanguage(set []flows.Flow) envs.Language { +func getBaseLanguage(set []flows.Flow) i18n.Language { if len(set) == 0 { - return envs.NilLanguage + return i18n.NilLanguage } baseLanguage := set[0].Language() for _, flow := range set[1:] { if baseLanguage != flow.Language() { - return envs.NilLanguage + return i18n.NilLanguage } } return baseLanguage } // ExtractFromFlows extracts a PO file from a set of flows -func ExtractFromFlows(initialComment string, translationsLanguage envs.Language, excludeProperties []string, sources ...flows.Flow) (*i18n.PO, error) { +func ExtractFromFlows(initialComment string, translationsLanguage i18n.Language, excludeProperties []string, sources ...flows.Flow) (*po.PO, error) { // check all flows have same base language baseLanguage := getBaseLanguage(sources) - if baseLanguage == envs.NilLanguage { + if baseLanguage == i18n.NilLanguage { return nil, errors.New("can't extract from flows with differing base languages") } else if translationsLanguage == baseLanguage { - translationsLanguage = envs.NilLanguage // we'll create a POT in the base language (i.e. no translations) + translationsLanguage = i18n.NilLanguage // we'll create a POT in the base language (i.e. no translations) } extracted := findLocalizedText(translationsLanguage, excludeProperties, sources) @@ -72,7 +73,7 @@ func ExtractFromFlows(initialComment string, translationsLanguage envs.Language, return poFromExtracted(sources, initialComment, translationsLanguage, merged), nil } -func findLocalizedText(translationsLanguage envs.Language, excludeProperties []string, sources []flows.Flow) []*localizedText { +func findLocalizedText(translationsLanguage i18n.Language, excludeProperties []string, sources []flows.Flow) []*localizedText { exclude := utils.Set(excludeProperties) extracted := make([]*localizedText, 0) @@ -90,12 +91,12 @@ func findLocalizedText(translationsLanguage envs.Language, excludeProperties []s return extracted } -func extractFromProperty(translationsLanguage envs.Language, flow flows.Flow, uuid uuids.UUID, property string, texts []string) []*localizedText { +func extractFromProperty(translationsLanguage i18n.Language, flow flows.Flow, uuid uuids.UUID, property string, texts []string) []*localizedText { extracted := make([]*localizedText, 0) // look up target translation if we have a translation language targets := make([]string, len(texts)) - if translationsLanguage != envs.NilLanguage { + if translationsLanguage != i18n.NilLanguage { translation := flow.Localization().GetItemTranslation(translationsLanguage, uuid, property) if translation != nil { for t := range targets { @@ -189,16 +190,16 @@ func majorityTranslation(extracted []*localizedText) string { return majority } -func poFromExtracted(sources []flows.Flow, initialComment string, lang envs.Language, extracted []*localizedText) *i18n.PO { +func poFromExtracted(sources []flows.Flow, initialComment string, lang i18n.Language, extracted []*localizedText) *po.PO { flowUUIDs := make([]string, len(sources)) for i, f := range sources { flowUUIDs[i] = string(f.UUID()) } - header := i18n.NewPOHeader(initialComment, dates.Now(), envs.NewLocale(lang, envs.NilCountry).ToBCP47()) + header := po.NewPOHeader(initialComment, dates.Now(), envs.ToBCP47(i18n.NewLocale(lang, i18n.NilCountry))) header.Custom["Source-Flows"] = strings.Join(flowUUIDs, "; ") header.Custom["Language-3"] = string(lang) - po := i18n.NewPO(header) + p := po.NewPO(header) for _, ext := range extracted { references := make([]string, len(ext.Locations)) @@ -212,8 +213,8 @@ func poFromExtracted(sources []flows.Flow, initialComment string, lang envs.Lang context = ext.Locations[0].MsgContext() } - entry := &i18n.POEntry{ - Comment: i18n.POComment{ + entry := &po.POEntry{ + Comment: po.POComment{ References: references, }, MsgContext: context, @@ -221,22 +222,22 @@ func poFromExtracted(sources []flows.Flow, initialComment string, lang envs.Lang MsgStr: ext.Translation, } - po.AddEntry(entry) + p.AddEntry(entry) } - return po + return p } // ImportIntoFlows imports translations from the given PO into the given flows -func ImportIntoFlows(po *i18n.PO, translationsLanguage envs.Language, excludeProperties []string, targets ...flows.Flow) error { +func ImportIntoFlows(p *po.PO, translationsLanguage i18n.Language, excludeProperties []string, targets ...flows.Flow) error { baseLanguage := getBaseLanguage(targets) - if baseLanguage == envs.NilLanguage { + if baseLanguage == i18n.NilLanguage { return errors.New("can't import into flows with differing base languages") } else if translationsLanguage == baseLanguage { return errors.New("can't import as the flow base language") } - updates := CalculateFlowUpdates(po, translationsLanguage, excludeProperties, targets...) + updates := CalculateFlowUpdates(p, translationsLanguage, excludeProperties, targets...) applyUpdates(updates, translationsLanguage) @@ -256,7 +257,7 @@ func (u *TranslationUpdate) String() string { } // CalculateFlowUpdates calculates what updates should be made to translations in the given flows -func CalculateFlowUpdates(po *i18n.PO, translationsLanguage envs.Language, excludeProperties []string, targets ...flows.Flow) []*TranslationUpdate { +func CalculateFlowUpdates(p *po.PO, translationsLanguage i18n.Language, excludeProperties []string, targets ...flows.Flow) []*TranslationUpdate { localized := findLocalizedText(translationsLanguage, excludeProperties, targets) localizedByContext := make(map[string][]*localizedText) localizedByMsgID := make(map[string][]*localizedText) @@ -268,7 +269,7 @@ func CalculateFlowUpdates(po *i18n.PO, translationsLanguage envs.Language, exclu } updates := make([]*TranslationUpdate, 0) - addUpdate := func(lt *localizedText, e *i18n.POEntry) { + addUpdate := func(lt *localizedText, e *po.POEntry) { // only update if translation has actually changed if lt.Translation != e.MsgStr { updates = append(updates, &TranslationUpdate{ @@ -281,7 +282,7 @@ func CalculateFlowUpdates(po *i18n.PO, translationsLanguage envs.Language, exclu } // create all context-less updates - for _, entry := range po.Entries { + for _, entry := range p.Entries { if entry.MsgContext == "" { for _, lt := range localizedByMsgID[entry.MsgID] { addUpdate(lt, entry) @@ -290,7 +291,7 @@ func CalculateFlowUpdates(po *i18n.PO, translationsLanguage envs.Language, exclu } // create more specific context based updates - for _, entry := range po.Entries { + for _, entry := range p.Entries { if entry.MsgContext != "" { for _, lt := range localizedByContext[entry.MsgContext] { // only update if base text is still the same @@ -319,7 +320,7 @@ func CalculateFlowUpdates(po *i18n.PO, translationsLanguage envs.Language, exclu return deduped } -func applyUpdates(updates []*TranslationUpdate, translationsLanguage envs.Language) { +func applyUpdates(updates []*TranslationUpdate, translationsLanguage i18n.Language) { for _, update := range updates { localization := update.textLocation.Flow.Localization() texts := localization.GetItemTranslation(translationsLanguage, update.UUID, update.Property) diff --git a/flows/translation/flows_test.go b/flows/translation/flows_test.go index af370db2b..5998936b5 100644 --- a/flows/translation/flows_test.go +++ b/flows/translation/flows_test.go @@ -8,16 +8,16 @@ import ( "testing" "time" + "github.com/buger/jsonparser" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/translation" "github.com/nyaruka/goflow/test" - "github.com/nyaruka/goflow/utils/i18n" - - "github.com/buger/jsonparser" + "github.com/nyaruka/goflow/utils/po" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -29,49 +29,49 @@ func TestExtractFromFlows(t *testing.T) { tests := []struct { assets string flowUUIDs []assets.FlowUUID - lang envs.Language + lang i18n.Language excludeProps []string po string }{ { "../../test/testdata/runner/two_questions.json", []assets.FlowUUID{assets.FlowUUID(`615b8a0f-588c-4d20-a05f-363b0b4ce6f4`)}, - envs.NilLanguage, // generate POT without translations + i18n.NilLanguage, // generate POT without translations nil, "exports/two_questions.po", }, { "../../test/testdata/runner/two_questions.json", []assets.FlowUUID{assets.FlowUUID(`615b8a0f-588c-4d20-a05f-363b0b4ce6f4`)}, - envs.Language("eng"), // is languiage of flow, thus also generates POT without translations + i18n.Language("eng"), // is languiage of flow, thus also generates POT without translations nil, "exports/two_questions.po", }, { "../../test/testdata/runner/two_questions.json", []assets.FlowUUID{assets.FlowUUID(`615b8a0f-588c-4d20-a05f-363b0b4ce6f4`)}, - envs.Language(`fra`), + i18n.Language(`fra`), nil, "exports/two_questions.fr.po", }, { "../../test/testdata/runner/two_questions.json", []assets.FlowUUID{assets.FlowUUID(`615b8a0f-588c-4d20-a05f-363b0b4ce6f4`)}, - envs.Language(`fra`), + i18n.Language(`fra`), []string{"arguments"}, "exports/two_questions.noargs.fr.po", }, { "testdata/translation_mismatches.json", []assets.FlowUUID{assets.FlowUUID(`19cad1f2-9110-4271-98d4-1b968bf19410`)}, - envs.Language(`spa`), + i18n.Language(`spa`), nil, "exports/translation_mismatches.noargs.es.po", }, { "testdata/multiple_flows.json", []assets.FlowUUID{`c426f38b-d940-4353-a081-362295938bbe`, `bc6a3e73-d5e2-4658-943c-0c24adc8dc0f`}, - envs.Language(`spa`), + i18n.Language(`spa`), nil, "exports/multiple_flows.es.po", }, @@ -125,52 +125,52 @@ func TestImportIntoFlows(t *testing.T) { flow, err := sa.Flows().Get("19cad1f2-9110-4271-98d4-1b968bf19410") require.NoError(t, err) - po := i18n.NewPO(nil) + p := po.NewPO(nil) // all instances of "Red" should be translated as "Rojo" (ignores the one which is already "Rojo") - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgID: "Red", MsgStr: "Rojo", }) // all instances of "Blue" should be translated as "Azul oscura" - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgID: "Blue", MsgStr: "Azul oscura", }) // except the quick reply instance of "Blue" should be translated as "Azul clara" - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgContext: "e42deebf-90fa-4636-81cb-d247a3d3ba75/quick_replies:1", MsgID: "Blue", MsgStr: "Azul clara", }) // context-less entry which will be ignored because it doesn't match any text - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgID: "Murky Green", MsgStr: "Verde", }) // entry which will be ignored because its context doesn't match anything in the flow - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgContext: "38c6ce0b-a746-48ae-ac64-f5f1163d80db/quick_replies:10", MsgID: "Lazy Pink", MsgStr: "Rosada", }) - updates := translation.CalculateFlowUpdates(po, envs.Language("spa"), []string{"quick_replies"}, flow) + updates := translation.CalculateFlowUpdates(p, i18n.Language("spa"), []string{"quick_replies"}, flow) assert.Equal(t, 2, len(updates)) assert.Equal(t, `Translated/d1ce3c92-7025-4607-a910-444361a6b9b3/name:0 "Roja" -> "Rojo"`, updates[0].String()) assert.Equal(t, `Translated/43f7e69e-727d-4cfe-81b8-564e7833052b/name:0 "Azul" -> "Azul oscura"`, updates[1].String()) - updates = translation.CalculateFlowUpdates(po, envs.Language("spa"), []string{}, flow) + updates = translation.CalculateFlowUpdates(p, i18n.Language("spa"), []string{}, flow) assert.Equal(t, 3, len(updates)) assert.Equal(t, `Translated/d1ce3c92-7025-4607-a910-444361a6b9b3/name:0 "Roja" -> "Rojo"`, updates[0].String()) assert.Equal(t, `Translated/e42deebf-90fa-4636-81cb-d247a3d3ba75/quick_replies:1 "Azul" -> "Azul clara"`, updates[1].String()) assert.Equal(t, `Translated/43f7e69e-727d-4cfe-81b8-564e7833052b/name:0 "Azul" -> "Azul oscura"`, updates[2].String()) - err = translation.ImportIntoFlows(po, envs.Language("spa"), []string{}, flow) + err = translation.ImportIntoFlows(p, i18n.Language("spa"), []string{}, flow) require.NoError(t, err) localJSON := jsonx.MustMarshal(flow.Localization()) @@ -224,10 +224,10 @@ func TestImportNewTranslationIntoFlows(t *testing.T) { poData, err := os.ReadFile("testdata/imports/two_questions.es.po") require.NoError(t, err) - po, err := i18n.ReadPO(bytes.NewReader(poData)) + p, err := po.ReadPO(bytes.NewReader(poData)) require.NoError(t, err) - err = translation.ImportIntoFlows(po, "spa", []string{"arguments"}, flow) + err = translation.ImportIntoFlows(p, "spa", []string{"arguments"}, flow) require.NoError(t, err) localJSON := jsonx.MustMarshal(flow.Localization()) diff --git a/flows/triggers/base_test.go b/flows/triggers/base_test.go index 1d3c29817..c8b38d393 100644 --- a/flows/triggers/base_test.go +++ b/flows/triggers/base_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -188,7 +189,7 @@ func TestTriggerMarshaling(t *testing.T) { user := sa.Users().Get("bob@nyaruka.com") ticket := flows.NewTicket("276c2e43-d6f9-4c36-8e54-b5af5039acf6", ticketer, weather, "Where are my shoes?", "123456", user) - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) contact.AddURN(urns.URN("tel:+12065551212"), nil) eng := engine.NewBuilder().Build() @@ -374,7 +375,7 @@ func TestTriggerSessionInitialization(t *testing.T) { flow := assets.NewFlowReference(assets.FlowUUID("7c37d7e5-6468-4b31-8109-ced2ef8b5ddc"), "Registration") - contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Bob", i18n.Language("eng"), nil) contact.AddURN(urns.URN("tel:+12065551212"), nil) params := types.NewXObject(map[string]types.XValue{"foo": types.NewXText("bar")}) @@ -424,7 +425,7 @@ func TestTriggerContext(t *testing.T) { flow := assets.NewFlowReference(assets.FlowUUID("7c37d7e5-6468-4b31-8109-ced2ef8b5ddc"), "Registration") user := sa.Users().Get("bob@nyaruka.com") - contact := flows.NewEmptyContact(sa, "Jim", envs.Language("eng"), nil) + contact := flows.NewEmptyContact(sa, "Jim", i18n.Language("eng"), nil) contact.AddURN(urns.URN("tel:+12065551212"), nil) params := types.NewXObject(map[string]types.XValue{"foo": types.NewXText("bar")}) diff --git a/go.mod b/go.mod index c34c3b798..1458af5c7 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,7 @@ require ( github.com/blevesearch/segment v0.9.1 github.com/buger/jsonparser v1.1.1 github.com/go-playground/validator/v10 v10.14.1 - github.com/nyaruka/gocommon v1.39.1 - github.com/nyaruka/null/v2 v2.0.3 - github.com/nyaruka/phonenumbers v1.1.8 + github.com/nyaruka/gocommon v1.41.0 github.com/olivere/elastic/v7 v7.0.32 github.com/pkg/errors v0.9.1 github.com/sergi/go-diff v1.3.1 @@ -33,6 +31,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/nyaruka/null/v2 v2.0.3 // indirect + github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/sys v0.11.0 // indirect diff --git a/go.sum b/go.sum index 286cdbc8e..14d7fc375 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/nyaruka/gocommon v1.39.1 h1:C1LM6y0iGe3oTJPknb+FcNyz8yl4+CdsvRD36wAai38= -github.com/nyaruka/gocommon v1.39.1/go.mod h1:wdDXnl5UrjFHOmsxJNID4h4U92u4hFto8xsXFBRfzdA= +github.com/nyaruka/gocommon v1.41.0 h1:ixojG8fiUoE1ksIOKy/opg3kMby6uptrmqLugEcX89o= +github.com/nyaruka/gocommon v1.41.0/go.mod h1:cJ2XmEX+FDOzBvE19IW+hG8EFVsSrNgCp7NrxAlP4Xg= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/phonenumbers v1.1.8 h1:mjFu85FeoH2Wy18aOMUvxqi1GgAqiQSJsa/cCC5yu2s= diff --git a/mobile/bindings.go b/mobile/bindings.go index ec80542a9..4761253fc 100644 --- a/mobile/bindings.go +++ b/mobile/bindings.go @@ -4,6 +4,7 @@ import ( "encoding/json" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/goflow/assets" @@ -48,9 +49,9 @@ func NewEnvironment(dateFormat string, timeFormat string, timezone string, allow return nil, err } - langs := make([]envs.Language, allowedLanguages.Length()) + langs := make([]i18n.Language, allowedLanguages.Length()) for i := 0; i < allowedLanguages.Length(); i++ { - langs[i] = envs.Language(allowedLanguages.Get(i)) + langs[i] = i18n.Language(allowedLanguages.Get(i)) } return &Environment{ @@ -59,7 +60,7 @@ func NewEnvironment(dateFormat string, timeFormat string, timezone string, allow WithTimeFormat(envs.TimeFormat(timeFormat)). WithTimezone(tz). WithAllowedLanguages(langs). - WithDefaultCountry(envs.Country(defaultCountry)). + WithDefaultCountry(i18n.Country(defaultCountry)). WithRedactionPolicy(envs.RedactionPolicy(redactionPolicy)). Build(), }, nil @@ -101,7 +102,7 @@ type Contact struct { // NewEmptyContact creates a new contact func NewEmptyContact(sa *SessionAssets) *Contact { return &Contact{ - target: flows.NewEmptyContact(sa.target, "", envs.NilLanguage, nil), + target: flows.NewEmptyContact(sa.target, "", i18n.NilLanguage, nil), } } diff --git a/services/classification/bothub/service.go b/services/classification/bothub/service.go index 25741c394..a561fb12f 100644 --- a/services/classification/bothub/service.go +++ b/services/classification/bothub/service.go @@ -27,9 +27,17 @@ func NewService(httpClient *http.Client, httpRetries *httpx.RetryConfig, classif } func (s *service) Classify(env envs.Environment, input string, logHTTP flows.HTTPLogCallback) (*flows.Classification, error) { - localeStr := strings.ReplaceAll(strings.ToLower(env.DefaultLocale().ToBCP47()), "-", "_") // en-US -> en_us + // eng-US -> en_us + lang := env.DefaultLanguage().ISO639_1() + if lang == "" { + lang = "en" + } + country := strings.ToLower(string(env.DefaultCountry())) + if country != "" { + lang += ("_" + country) + } - response, trace, err := s.client.Parse(input, localeStr) + response, trace, err := s.client.Parse(input, lang) if trace != nil { logHTTP(flows.NewHTTPLog(trace, flows.HTTPStatusFromCode, s.redactor)) } diff --git a/services/classification/bothub/service_test.go b/services/classification/bothub/service_test.go index f6dcc5db8..a91fdbae9 100644 --- a/services/classification/bothub/service_test.go +++ b/services/classification/bothub/service_test.go @@ -7,6 +7,7 @@ import ( "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" @@ -70,7 +71,7 @@ func TestService(t *testing.T) { "f96abf2f-3b53-4766-8ea6-09a655222a02", ) - env := envs.NewBuilder().WithAllowedLanguages([]envs.Language{"spa"}).WithDefaultCountry("US").Build() + env := envs.NewBuilder().WithAllowedLanguages([]i18n.Language{"spa"}).WithDefaultCountry("US").Build() httpLogger := &flows.HTTPLogger{} classification, err := svc.Classify(env, "book my flight to Quito", httpLogger.Log) diff --git a/test/assets.go b/test/assets.go index 336c59254..39350940c 100644 --- a/test/assets.go +++ b/test/assets.go @@ -3,6 +3,7 @@ package test import ( "os" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/assets/static" @@ -42,7 +43,7 @@ func NewChannel(name string, address string, schemes []string, roles []assets.Ch return flows.NewChannel(static.NewChannel(assets.ChannelUUID(uuids.New()), name, address, schemes, roles, parent)) } -func NewTelChannel(name string, address string, roles []assets.ChannelRole, parent *assets.ChannelReference, country envs.Country, matchPrefixes []string, allowInternational bool) *flows.Channel { +func NewTelChannel(name string, address string, roles []assets.ChannelRole, parent *assets.ChannelReference, country i18n.Country, matchPrefixes []string, allowInternational bool) *flows.Channel { return flows.NewChannel(static.NewTelChannel(assets.ChannelUUID(uuids.New()), name, address, roles, parent, country, matchPrefixes, allowInternational)) } diff --git a/test/session.go b/test/session.go index 9d24f9662..4d820170f 100644 --- a/test/session.go +++ b/test/session.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/gocommon/uuids" @@ -592,7 +593,7 @@ type SessionBuilder struct { contactUUID flows.ContactUUID contactID flows.ContactID contactName string - contactLang envs.Language + contactLang i18n.Language contactURN urns.URN triggerMsg string } @@ -601,7 +602,7 @@ func NewSessionBuilder() *SessionBuilder { env := envs.NewBuilder(). WithDateFormat(envs.DateFormatDayMonthYear). WithDefaultCountry("US"). - WithAllowedLanguages([]envs.Language{"eng", "spa"}). + WithAllowedLanguages([]i18n.Language{"eng", "spa"}). WithInputCollation(envs.CollationConfusables). Build() @@ -643,7 +644,7 @@ func (b *SessionBuilder) WithFlow(flowUUID assets.FlowUUID) *SessionBuilder { return b } -func (b *SessionBuilder) WithContact(uuid flows.ContactUUID, id flows.ContactID, name string, lang envs.Language, urn urns.URN) *SessionBuilder { +func (b *SessionBuilder) WithContact(uuid flows.ContactUUID, id flows.ContactID, name string, lang i18n.Language, urn urns.URN) *SessionBuilder { b.contactUUID = uuid b.contactID = id b.contactName = name diff --git a/utils/i18n/library.go b/utils/po/library.go similarity index 99% rename from utils/i18n/library.go rename to utils/po/library.go index a31bca011..1bca720e9 100644 --- a/utils/i18n/library.go +++ b/utils/po/library.go @@ -1,4 +1,4 @@ -package i18n +package po import ( "errors" diff --git a/utils/i18n/library_test.go b/utils/po/library_test.go similarity index 91% rename from utils/i18n/library_test.go rename to utils/po/library_test.go index fb8bf14de..e2f72a609 100644 --- a/utils/i18n/library_test.go +++ b/utils/po/library_test.go @@ -1,12 +1,11 @@ -package i18n_test +package po_test import ( "os" "path" "testing" - "github.com/nyaruka/goflow/utils/i18n" - + "github.com/nyaruka/goflow/utils/po" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,7 +29,7 @@ func TestLibrary(t *testing.T) { os.WriteFile(path.Join(libraryDir, "en", "simple.po"), poEN, 0700) os.WriteFile(path.Join(libraryDir, "es", "simple.po"), poES, 0700) - library := i18n.NewLibrary(libraryDir, "en") + library := po.NewLibrary(libraryDir, "en") assert.Equal(t, libraryDir, library.Path()) assert.Equal(t, "en", library.SrcLanguage()) @@ -47,7 +46,7 @@ func TestLibrary(t *testing.T) { assert.Equal(t, "Blue", en.GetText("", "Blue")) // add new entry - en.AddEntry(&i18n.POEntry{MsgID: "Green"}) + en.AddEntry(&po.POEntry{MsgID: "Green"}) err = library.Update("simple", en) require.NoError(t, err) diff --git a/utils/i18n/po.go b/utils/po/po.go similarity index 99% rename from utils/i18n/po.go rename to utils/po/po.go index 43ba760bc..1d1f8e300 100644 --- a/utils/i18n/po.go +++ b/utils/po/po.go @@ -1,4 +1,4 @@ -package i18n +package po import ( "bufio" diff --git a/utils/i18n/po_test.go b/utils/po/po_test.go similarity index 55% rename from utils/i18n/po_test.go rename to utils/po/po_test.go index 9bae788bd..0d8a14903 100644 --- a/utils/i18n/po_test.go +++ b/utils/po/po_test.go @@ -1,4 +1,4 @@ -package i18n_test +package po_test import ( "os" @@ -8,8 +8,7 @@ import ( "time" "github.com/nyaruka/goflow/test" - "github.com/nyaruka/goflow/utils/i18n" - + "github.com/nyaruka/goflow/utils/po" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,11 +21,11 @@ func TestComments(t *testing.T) { #: src/bar.go #, fuzzy,go-format` - c := i18n.POComment{} + c := po.POComment{} assert.Equal(t, "", c.String()) - assert.Equal(t, c, i18n.ParsePOComment("")) + assert.Equal(t, c, po.ParsePOComment("")) - c = i18n.POComment{ + c = po.POComment{ Translator: []string{"translator", ""}, Extracted: []string{"extracted"}, References: []string{"src/foo.go", "src/bar.go"}, @@ -34,33 +33,33 @@ func TestComments(t *testing.T) { } assert.Equal(t, text, c.String()) - assert.Equal(t, c, i18n.ParsePOComment(text)) + assert.Equal(t, c, po.ParsePOComment(text)) assert.True(t, c.HasFlag("fuzzy")) assert.True(t, c.HasFlag("go-format")) assert.False(t, c.HasFlag("python-format")) } func TestPOCreation(t *testing.T) { - header := i18n.NewPOHeader("Generated for testing", time.Date(2020, 3, 25, 11, 50, 30, 123456789, time.UTC), "es") + header := po.NewPOHeader("Generated for testing", time.Date(2020, 3, 25, 11, 50, 30, 123456789, time.UTC), "es") header.Custom["Foo"] = "Bar" - po := i18n.NewPO(header) + p := po.NewPO(header) - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgID: "Yes", MsgStr: "", }) - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgID: "Yes", MsgStr: "Si", }) - po.AddEntry(&i18n.POEntry{ + p.AddEntry(&po.POEntry{ MsgContext: "context1", MsgID: "No", MsgStr: "", }) - po.AddEntry(&i18n.POEntry{ - Comment: i18n.POComment{ + p.AddEntry(&po.POEntry{ + Comment: po.POComment{ Extracted: []string{"has_text"}, }, MsgContext: "context1", @@ -69,9 +68,9 @@ func TestPOCreation(t *testing.T) { }) b := &strings.Builder{} - po.Write(b) + p.Write(b) - assert.Equal(t, 2, len(po.Entries)) + assert.Equal(t, 2, len(p.Entries)) assert.Equal( t, `# Generated for testing # @@ -102,43 +101,43 @@ func TestReadAndWritePO(t *testing.T) { defer poFile.Close() - po, err := i18n.ReadPO(poFile) + p, err := po.ReadPO(poFile) require.NoError(t, err) - assert.Equal(t, "Testing\n", po.Header.InitialComment) - assert.True(t, time.Date(2020, 3, 25, 13, 57, 0, 0, time.UTC).Equal(po.Header.POTCreationDate)) - assert.Equal(t, "es", po.Header.Language) - assert.Equal(t, "1.0", po.Header.MIMEVersion) - assert.Equal(t, "text/plain; charset=UTF-8", po.Header.ContentType) + assert.Equal(t, "Testing\n", p.Header.InitialComment) + assert.True(t, time.Date(2020, 3, 25, 13, 57, 0, 0, time.UTC).Equal(p.Header.POTCreationDate)) + assert.Equal(t, "es", p.Header.Language) + assert.Equal(t, "1.0", p.Header.MIMEVersion) + assert.Equal(t, "text/plain; charset=UTF-8", p.Header.ContentType) - assert.Equal(t, 7, len(po.Entries)) - assert.Equal(t, []string{"Translated/43f7e69e-727d-4cfe-81b8-564e7833052b/name:0", "Translated/e42deebf-90fa-4636-81cb-d247a3d3ba75/quick_replies:1"}, po.Entries[0].Comment.References) - assert.Equal(t, "", po.Entries[0].MsgContext) - assert.Equal(t, "Blue", po.Entries[0].MsgID) - assert.Equal(t, "Azul", po.Entries[0].MsgStr) + assert.Equal(t, 7, len(p.Entries)) + assert.Equal(t, []string{"Translated/43f7e69e-727d-4cfe-81b8-564e7833052b/name:0", "Translated/e42deebf-90fa-4636-81cb-d247a3d3ba75/quick_replies:1"}, p.Entries[0].Comment.References) + assert.Equal(t, "", p.Entries[0].MsgContext) + assert.Equal(t, "Blue", p.Entries[0].MsgID) + assert.Equal(t, "Azul", p.Entries[0].MsgStr) - assert.Equal(t, "d1ce3c92-7025-4607-a910-444361a6b9b3/name:0", po.Entries[2].MsgContext) - assert.Equal(t, "Red", po.Entries[2].MsgID) - assert.Equal(t, "Roja", po.Entries[2].MsgStr) + assert.Equal(t, "d1ce3c92-7025-4607-a910-444361a6b9b3/name:0", p.Entries[2].MsgContext) + assert.Equal(t, "Red", p.Entries[2].MsgID) + assert.Equal(t, "Roja", p.Entries[2].MsgStr) // try handling an i/o error badReader := iotest.TimeoutReader(strings.NewReader(`# Generated`)) - _, err = i18n.ReadPO(badReader) + _, err = po.ReadPO(badReader) assert.EqualError(t, err, "timeout") // we can sort the entries - po.Sort() + p.Sort() - assert.Equal(t, "Blue", po.Entries[0].MsgID) - assert.Equal(t, "Other", po.Entries[1].MsgID) - assert.Equal(t, "Red", po.Entries[2].MsgID) - assert.Equal(t, "", po.Entries[2].MsgContext) - assert.Equal(t, "Red", po.Entries[3].MsgID) - assert.Equal(t, "d1ce3c92-7025-4607-a910-444361a6b9b3/name:0", po.Entries[3].MsgContext) + assert.Equal(t, "Blue", p.Entries[0].MsgID) + assert.Equal(t, "Other", p.Entries[1].MsgID) + assert.Equal(t, "Red", p.Entries[2].MsgID) + assert.Equal(t, "", p.Entries[2].MsgContext) + assert.Equal(t, "Red", p.Entries[3].MsgID) + assert.Equal(t, "d1ce3c92-7025-4607-a910-444361a6b9b3/name:0", p.Entries[3].MsgContext) // test writing the PO file b := &strings.Builder{} - po.Write(b) + p.Write(b) test.AssertSnapshot(t, "write_po", b.String()) } @@ -147,16 +146,16 @@ func TestGetText(t *testing.T) { require.NoError(t, err) defer poFile.Close() - po, err := i18n.ReadPO(poFile) + p, err := po.ReadPO(poFile) require.NoError(t, err) - assert.Equal(t, "Rojo", po.GetText("Male", "Red")) - assert.Equal(t, "Roja", po.GetText("Female", "Red")) - assert.Equal(t, "Red", po.GetText("", "Red")) - assert.Equal(t, "Azul", po.GetText("", "Blue")) - assert.Equal(t, "Missing", po.GetText("", "Missing")) - assert.Equal(t, "Not even an entry", po.GetText("", "Not even an entry")) - assert.Equal(t, "Green", po.GetText("", "Green")) // entry is ignored because it's fuzzy + assert.Equal(t, "Rojo", p.GetText("Male", "Red")) + assert.Equal(t, "Roja", p.GetText("Female", "Red")) + assert.Equal(t, "Red", p.GetText("", "Red")) + assert.Equal(t, "Azul", p.GetText("", "Blue")) + assert.Equal(t, "Missing", p.GetText("", "Missing")) + assert.Equal(t, "Not even an entry", p.GetText("", "Not even an entry")) + assert.Equal(t, "Green", p.GetText("", "Green")) // entry is ignored because it's fuzzy } func TestEncodeAndDecodePOString(t *testing.T) { @@ -188,7 +187,7 @@ func TestEncodeAndDecodePOString(t *testing.T) { } for _, tc := range tests { - assert.Equal(t, tc.encoded, i18n.EncodePOString(tc.original), "mismatch encoding: %s", tc.original) - assert.Equal(t, tc.original, i18n.DecodePOString(tc.encoded), "mismatch decoding: %s", tc.encoded) + assert.Equal(t, tc.encoded, po.EncodePOString(tc.original), "mismatch encoding: %s", tc.original) + assert.Equal(t, tc.original, po.DecodePOString(tc.encoded), "mismatch decoding: %s", tc.encoded) } } diff --git a/utils/i18n/testdata/TestReadAndWritePO_write_po.snap b/utils/po/testdata/TestReadAndWritePO_write_po.snap similarity index 100% rename from utils/i18n/testdata/TestReadAndWritePO_write_po.snap rename to utils/po/testdata/TestReadAndWritePO_write_po.snap diff --git a/utils/i18n/testdata/locale/en/simple.po b/utils/po/testdata/locale/en/simple.po similarity index 100% rename from utils/i18n/testdata/locale/en/simple.po rename to utils/po/testdata/locale/en/simple.po diff --git a/utils/i18n/testdata/locale/es/simple.po b/utils/po/testdata/locale/es/simple.po similarity index 100% rename from utils/i18n/testdata/locale/es/simple.po rename to utils/po/testdata/locale/es/simple.po diff --git a/utils/i18n/testdata/translation_mismatches.noargs.es.po b/utils/po/testdata/translation_mismatches.noargs.es.po similarity index 100% rename from utils/i18n/testdata/translation_mismatches.noargs.es.po rename to utils/po/testdata/translation_mismatches.noargs.es.po