Skip to content

Commit

Permalink
Merge pull request #1271 from nyaruka/merged_bcast_content
Browse files Browse the repository at this point in the history
Rework broadcast translation selection to merge across text, attachments and quick replies
  • Loading branch information
rowanseymour authored Jul 3, 2024
2 parents 2194de6 + 1756219 commit 9ac75b7
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 31 deletions.
45 changes: 32 additions & 13 deletions flows/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,22 +198,41 @@ type MsgContent struct {

type BroadcastTranslations map[i18n.Language]*MsgContent

// ForContact is a utility to help callers select the translation for a contact
func (b BroadcastTranslations) ForContact(e envs.Environment, c *Contact, baseLanguage i18n.Language) (*MsgContent, i18n.Language) {
// first try the contact language if it is valid
// ForContact is a utility to help callers get the message content for a contact
func (b BroadcastTranslations) ForContact(e envs.Environment, c *Contact, baseLanguage i18n.Language) (*MsgContent, i18n.Locale) {
// get the set of languages to merge translations from
languages := make([]i18n.Language, 0, 3)

// highest priority is the contact language if it is valid
if c.Language() != i18n.NilLanguage && slices.Contains(e.AllowedLanguages(), c.Language()) {
t := b[c.Language()]
if t != nil {
return t, c.Language()
}
languages = append(languages, c.Language())
}

// then the default workspace language, then the base language
languages = append(languages, e.DefaultLanguage(), baseLanguage)

content := &MsgContent{}
language := i18n.NilLanguage
country := e.DefaultCountry()
if c.Country() != i18n.NilCountry {
country = c.Country()
}

// second try the default flow language
t := b[e.DefaultLanguage()]
if t != nil {
return t, e.DefaultLanguage()
for _, lang := range languages {
trans := b[lang]
if trans != nil {
if content.Text == "" && trans.Text != "" {
content.Text = trans.Text
language = lang
}
if len(content.Attachments) == 0 && len(trans.Attachments) > 0 {
content.Attachments = trans.Attachments
}
if len(content.QuickReplies) == 0 && len(trans.QuickReplies) > 0 {
content.QuickReplies = trans.QuickReplies
}
}
}

// finally return the base language
return b[baseLanguage], baseLanguage
return content, i18n.NewLocale(language, country)
}
98 changes: 80 additions & 18 deletions flows/msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,30 +122,92 @@ func TestIVRMsgOut(t *testing.T) {
}

func TestBroadcastTranslations(t *testing.T) {
bcastTrans := flows.BroadcastTranslations{
"eng": &flows.MsgContent{Text: "Hello"},
"fra": &flows.MsgContent{Text: "Bonjour"},
"spa": &flows.MsgContent{Text: "Hola"},
tcs := []struct {
env envs.Environment
translations flows.BroadcastTranslations
baseLanguage i18n.Language
contactLanguage i18n.Language
expectedContent *flows.MsgContent
expectedLocale i18n.Locale
}{
{ // 0: uses contact language
env: envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("US").Build(),
translations: flows.BroadcastTranslations{
"eng": &flows.MsgContent{Text: "Hello"},
"spa": &flows.MsgContent{Text: "Hola"},
},
baseLanguage: "eng",
contactLanguage: "spa",
expectedContent: &flows.MsgContent{Text: "Hola"},
expectedLocale: "spa-US",
},
{ // 1: ignores contact language because it's not in allowed languages, uses env default
env: envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("RW").Build(),
translations: flows.BroadcastTranslations{
"eng": &flows.MsgContent{Text: "Hello"},
"kin": &flows.MsgContent{Text: "Muraho"},
},
baseLanguage: "eng",
contactLanguage: "kin",
expectedContent: &flows.MsgContent{Text: "Hello"},
expectedLocale: "eng-RW",
},
{ // 2: ignores contact language because it's not translations, uses env default
env: envs.NewBuilder().WithAllowedLanguages("spa", "fra", "eng").WithDefaultCountry("US").Build(),
translations: flows.BroadcastTranslations{
"eng": &flows.MsgContent{Text: "Hello"},
"spa": &flows.MsgContent{Text: "Hola"},
},
baseLanguage: "eng",
contactLanguage: "kin",
expectedContent: &flows.MsgContent{Text: "Hola"},
expectedLocale: "spa-US",
},
{ // 3: ignores contact language because it's not translations, uses base language
env: envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("US").Build(),
translations: flows.BroadcastTranslations{
"fra": &flows.MsgContent{Text: "Bonjour"},
},
baseLanguage: "fra",
contactLanguage: "eng",
expectedContent: &flows.MsgContent{Text: "Bonjour"},
expectedLocale: "fra-US",
},
{ // 4: merges content from different translations
env: envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("US").Build(),
translations: flows.BroadcastTranslations{
"eng": &flows.MsgContent{Text: "Hello", Attachments: []utils.Attachment{"image/jpeg:https://example.com/hello.jpg"}, QuickReplies: []string{"Yes", "No"}},
"spa": &flows.MsgContent{Text: "Hola"},
},
baseLanguage: "eng",
contactLanguage: "spa",
expectedContent: &flows.MsgContent{Text: "Hola", Attachments: []utils.Attachment{"image/jpeg:https://example.com/hello.jpg"}, QuickReplies: []string{"Yes", "No"}},
expectedLocale: "spa-US",
},
{ // 5: merges content from different translations
env: envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("US").Build(),
translations: flows.BroadcastTranslations{
"eng": &flows.MsgContent{QuickReplies: []string{"Yes", "No"}},
"spa": &flows.MsgContent{Attachments: []utils.Attachment{"image/jpeg:https://example.com/hola.jpg"}},
"kin": &flows.MsgContent{Text: "Muraho"},
},
baseLanguage: "kin",
contactLanguage: "spa",
expectedContent: &flows.MsgContent{Text: "Muraho", Attachments: []utils.Attachment{"image/jpeg:https://example.com/hola.jpg"}, QuickReplies: []string{"Yes", "No"}},
expectedLocale: "kin-US",
},
}
baseLanguage := i18n.Language("eng")

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)
for i, tc := range tcs {
sa, err := engine.NewSessionAssets(tc.env, static.NewEmptySource(), nil)
require.NoError(t, err)

contact := flows.NewEmptyContact(sa, "Bob", contactLanguage, nil)
trans, lang := bcastTrans.ForContact(env, contact, baseLanguage)
contact := flows.NewEmptyContact(sa, "Bob", tc.contactLanguage, nil)
content, locale := tc.translations.ForContact(tc.env, contact, tc.baseLanguage)

assert.Equal(t, expectedText, trans.Text)
assert.Equal(t, expectedLang, lang)
assert.Equal(t, tc.expectedContent, content, "%d: content mismatch", i)
assert.Equal(t, tc.expectedLocale, locale, "%d: locale mismatch", i)
}

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
}

func TestMsgTemplating(t *testing.T) {
Expand Down

0 comments on commit 9ac75b7

Please sign in to comment.