Skip to content

Commit

Permalink
Add better locale matching
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Sep 1, 2023
1 parent d26cae2 commit cecf580
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 0 deletions.
19 changes: 19 additions & 0 deletions envs/locale.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func NewLocale(l Language, c Country) Locale {
return Locale(fmt.Sprintf("%s-%s", l, c)) // e.g. "eng-US", "por-BR"
}

func (l Locale) ToTag() language.Tag {
return language.MustParse(string(l))
}

// ToBCP47 returns the BCP47 code, e.g. en-US, pt, pt-BR
func (l Locale) ToBCP47() string {
if l == NilLocale {
Expand Down Expand Up @@ -71,3 +75,18 @@ 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) }

// LocaleBestMatch returns the best match between a list of preferred locales and a list of available BCP 47 language tags.
// The provided tags don't necessarily use 3 char language codes like our locales do.
func LocaleBestMatch(preferred []Locale, available []language.Tag) language.Tag {
prefTags := make([]language.Tag, len(preferred))
for i := range preferred {
prefTags[i] = preferred[i].ToTag()
}

m := language.NewMatcher(available)

// see https://github.com/golang/go/issues/24211
_, idx, _ := m.Match(prefTags...)
return available[idx]
}
24 changes: 24 additions & 0 deletions envs/locale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/nyaruka/goflow/envs"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
)

func TestLocale(t *testing.T) {
Expand Down Expand Up @@ -70,3 +71,26 @@ func TestToBCP47(t *testing.T) {
assert.Equal(t, tc.bcp47, tc.locale.ToBCP47())
}
}

func TestLocaleBestMatch(t *testing.T) {
p := language.MustParse

tests := []struct {
preferred []envs.Locale
available []language.Tag
best language.Tag
}{
{preferred: []envs.Locale{"eng-US"}, available: []language.Tag{p("es_EC"), p("en-US")}, best: p("en-US")},
{preferred: []envs.Locale{"eng-US"}, available: []language.Tag{p("es"), p("en")}, best: p("en")},
{preferred: []envs.Locale{"eng"}, available: []language.Tag{p("es-US"), p("en-UK")}, best: p("en-UK")},
{preferred: []envs.Locale{"eng", "fra"}, available: []language.Tag{p("fr-CA"), p("en-RW")}, best: p("en-RW")},
{preferred: []envs.Locale{"eng", "fra"}, available: []language.Tag{p("fra-CA"), p("eng-RW")}, best: p("eng-RW")},
{preferred: []envs.Locale{"fra", "eng"}, available: []language.Tag{p("fra-CA"), p("eng-RW")}, best: p("fra-CA")},
{preferred: []envs.Locale{"spa"}, available: []language.Tag{p("es-EC"), p("es-MX"), p("es-ES")}, best: p("es-ES")},
{preferred: []envs.Locale{}, available: []language.Tag{p("es_EC"), p("en-US")}, best: p("es_EC")},
}

for _, tc := range tests {
assert.Equal(t, tc.best, envs.LocaleBestMatch(tc.preferred, tc.available), "locale mismatch for preferred=%v available=%s", tc.preferred, tc.available)
}
}

0 comments on commit cecf580

Please sign in to comment.