diff options
-rw-r--r-- | v2/go.mod | 6 | ||||
-rw-r--r-- | v2/go.sum | 19 | ||||
-rw-r--r-- | v2/i18n/bundle.go | 8 | ||||
-rw-r--r-- | v2/i18n/localizer.go | 86 | ||||
-rw-r--r-- | v2/i18n/localizer_test.go | 84 |
5 files changed, 109 insertions, 94 deletions
@@ -3,11 +3,7 @@ module github.com/nicksnyder/go-i18n/v2 go 1.9 require ( - github.com/BurntSushi/toml v0.3.0 - golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 // indirect - golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect - golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect + github.com/BurntSushi/toml v0.3.1 golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c // indirect gopkg.in/yaml.v2 v2.2.1 ) @@ -1,21 +1,10 @@ -github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= -github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38 h1:yr7ItWHARpqySNZjEh5mPMHrw3xPR9tMnomFZVcO1mQ= -golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v2/i18n/bundle.go b/v2/i18n/bundle.go index 6c6f5ce..513c127 100644 --- a/v2/i18n/bundle.go +++ b/v2/i18n/bundle.go @@ -134,3 +134,11 @@ func (b *Bundle) addTag(tag language.Tag) { func (b *Bundle) LanguageTags() []language.Tag { return b.tags } + +func (b *Bundle) getMessageTemplate(tag language.Tag, id string) *MessageTemplate { + templates := b.messageTemplates[tag] + if templates == nil { + return nil + } + return templates[id] +} diff --git a/v2/i18n/localizer.go b/v2/i18n/localizer.go index 5d3b0df..17261e2 100644 --- a/v2/i18n/localizer.go +++ b/v2/i18n/localizer.go @@ -2,7 +2,6 @@ package i18n import ( "fmt" - "text/template" "github.com/nicksnyder/go-i18n/v2/internal/plural" @@ -74,11 +73,12 @@ func (e *invalidPluralCountErr) Error() string { // MessageNotFoundErr is returned from Localize when a message could not be found. type MessageNotFoundErr struct { + tag language.Tag messageID string } func (e *MessageNotFoundErr) Error() string { - return fmt.Sprintf("message %q not found", e.messageID) + return fmt.Sprintf("message %q not found in language %q", e.messageID, e.tag) } type pluralizeErr struct { @@ -146,82 +146,62 @@ func (l *Localizer) LocalizeWithTag(lc *LocalizeConfig) (string, language.Tag, e } } - tag, template := l.getTemplate(messageID, lc.DefaultMessage) + tag, template, err := l.getMessageTemplate(messageID, lc.DefaultMessage) if template == nil { - return "", language.Und, &MessageNotFoundErr{messageID: messageID} + return "", language.Und, err } pluralForm := l.pluralForm(tag, operands) - if pluralForm == plural.Invalid { - return "", language.Und, &pluralizeErr{messageID: messageID, tag: tag} - } + msg, err2 := template.Execute(pluralForm, templateData, lc.Funcs) + if err2 != nil { + if err == nil { + err = err2 + } - msg, err := template.Execute(pluralForm, templateData, lc.Funcs) - if err != nil { // Attempt to fallback to "Other" pluralization in case translations are incomplete. if pluralForm != plural.Other { - msg2, err2 := template.Execute(plural.Other, templateData, lc.Funcs) - if err2 == nil { - return msg2, tag, err + msg2, err3 := template.Execute(plural.Other, templateData, lc.Funcs) + if err3 == nil { + msg = msg2 } } - return "", language.Und, err } - return msg, tag, nil + return msg, tag, err } -func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *MessageTemplate) { - // Fast path. - // Optimistically assume this message id is defined in each language. - fastTag, template := l.matchTemplate(id, defaultMessage, l.bundle.matcher, l.bundle.tags) - if template != nil { - return fastTag, template +func (l *Localizer) getMessageTemplate(id string, defaultMessage *Message) (language.Tag, *MessageTemplate, error) { + _, i, _ := l.bundle.matcher.Match(l.tags...) + tag := l.bundle.tags[i] + mt := l.bundle.getMessageTemplate(tag, id) + if mt != nil { + return tag, mt, nil } - if len(l.bundle.tags) <= 1 { - return l.bundle.defaultLanguage, nil - } - - // Slow path. - // We didn't find a translation for the tag suggested by the default matcher - // so we need to create a new matcher that contains only the tags in the bundle - // that have this message. - foundTags := make([]language.Tag, 0, len(l.bundle.messageTemplates)+1) - foundTags = append(foundTags, l.bundle.defaultLanguage) - - for t, templates := range l.bundle.messageTemplates { - template := templates[id] - if template == nil || template.Other == "" { - continue + if tag == l.bundle.defaultLanguage { + if defaultMessage == nil { + return language.Und, nil, &MessageNotFoundErr{tag: tag, messageID: id} } - foundTags = append(foundTags, t) + return tag, NewMessageTemplate(defaultMessage), nil } - return l.matchTemplate(id, defaultMessage, language.NewMatcher(foundTags), foundTags) -} - -func (l *Localizer) matchTemplate(id string, defaultMessage *Message, matcher language.Matcher, tags []language.Tag) (language.Tag, *MessageTemplate) { - _, i, _ := matcher.Match(l.tags...) - tag := tags[i] - templates := l.bundle.messageTemplates[tag] - if templates != nil && templates[id] != nil { - return tag, templates[id] + // Fallback to default language in bundle. + mt = l.bundle.getMessageTemplate(l.bundle.defaultLanguage, id) + if mt != nil { + return l.bundle.defaultLanguage, mt, &MessageNotFoundErr{tag: tag, messageID: id} } - if tag == l.bundle.defaultLanguage && defaultMessage != nil { - return tag, NewMessageTemplate(defaultMessage) + + // Fallback to default message. + if defaultMessage == nil { + return language.Und, nil, &MessageNotFoundErr{tag: tag, messageID: id} } - return tag, nil + return l.bundle.defaultLanguage, NewMessageTemplate(defaultMessage), &MessageNotFoundErr{tag: tag, messageID: id} } func (l *Localizer) pluralForm(tag language.Tag, operands *plural.Operands) plural.Form { if operands == nil { return plural.Other } - pluralRule := l.bundle.pluralRules.Rule(tag) - if pluralRule == nil { - return plural.Invalid - } - return pluralRule.PluralFormFunc(operands) + return l.bundle.pluralRules.Rule(tag).PluralFormFunc(operands) } // MustLocalize is similar to Localize, except it panics if an error happens. diff --git a/v2/i18n/localizer_test.go b/v2/i18n/localizer_test.go index 4194914..3dedfb6 100644 --- a/v2/i18n/localizer_test.go +++ b/v2/i18n/localizer_test.go @@ -1,6 +1,7 @@ package i18n import ( + "fmt" "reflect" "testing" @@ -52,28 +53,19 @@ func localizerTests() []localizerTest { defaultLanguage: language.English, acceptLangs: []string{"en"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, - expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.English, messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "empty translation without fallback", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ - language.Spanish: {{ID: "HelloWorld"}}, - }, - acceptLangs: []string{"es"}, - conf: &LocalizeConfig{MessageID: "HelloWorld"}, - expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, - }, - { - name: "empty translation with fallback", - defaultLanguage: language.English, - messages: map[language.Tag][]*Message{ language.English: {{ID: "HelloWorld", Other: "Hello World!"}}, language.Spanish: {{ID: "HelloWorld"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.Spanish, messageID: "HelloWorld"}, expectedLocalized: "Hello World!", }, { @@ -84,26 +76,38 @@ func localizerTests() []localizerTest { }, acceptLangs: []string{"en"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, - expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.English, messageID: "HelloWorld"}, expectedLocalized: "", }, { - name: "missing translation from not default language", + name: "missing translations from not default language", defaultLanguage: language.English, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, - expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.English, messageID: "HelloWorld"}, + expectedLocalized: "", + }, + { + name: "missing translation from not default language", + defaultLanguage: language.English, + messages: map[language.Tag][]*Message{ + language.Spanish: {{ID: "SomethingElse", Other: "other"}}, + }, + acceptLangs: []string{"es"}, + conf: &LocalizeConfig{MessageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.Spanish, messageID: "HelloWorld"}, expectedLocalized: "", }, { name: "missing translation not default language with other translation", defaultLanguage: language.English, messages: map[language.Tag][]*Message{ - language.French: {{ID: "HelloWorld", Other: "other"}}, + language.French: {{ID: "HelloWorld", Other: "other"}}, + language.Spanish: {{ID: "SomethingElse", Other: "other"}}, }, acceptLangs: []string{"es"}, conf: &LocalizeConfig{MessageID: "HelloWorld"}, - expectedErr: &MessageNotFoundErr{messageID: "HelloWorld"}, + expectedErr: &MessageNotFoundErr{tag: language.Spanish, messageID: "HelloWorld"}, expectedLocalized: "", }, { @@ -513,7 +517,7 @@ func localizerTests() []localizerTest { expectedLocalized: "Nick has 2.5 cats", }, { - name: "test slow path", + name: "no fallback", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ @@ -529,10 +533,10 @@ func localizerTests() []localizerTest { conf: &LocalizeConfig{ MessageID: "Hello", }, - expectedLocalized: "Hello!", + expectedErr: &MessageNotFoundErr{tag: language.AmericanEnglish, messageID: "Hello"}, }, { - name: "test slow path default message", + name: "fallback default message", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ @@ -552,9 +556,10 @@ func localizerTests() []localizerTest { }, }, expectedLocalized: "Hola!", + expectedErr: &MessageNotFoundErr{tag: language.AmericanEnglish, messageID: "Hello"}, }, { - name: "test slow path no message", + name: "no fallback default message", defaultLanguage: language.Spanish, messages: map[language.Tag][]*Message{ language.English: {{ @@ -570,7 +575,7 @@ func localizerTests() []localizerTest { conf: &LocalizeConfig{ MessageID: "Hello", }, - expectedErr: &MessageNotFoundErr{messageID: "Hello"}, + expectedErr: &MessageNotFoundErr{tag: language.AmericanEnglish, messageID: "Hello"}, }, } } @@ -625,3 +630,40 @@ func BenchmarkLocalizer_Localize(b *testing.B) { }) } } + +func TestMessageNotFoundError(t *testing.T) { + actual := (&MessageNotFoundErr{tag: language.AmericanEnglish, messageID: "hello"}).Error() + expected := `message "hello" not found in language "en-US"` + if actual != expected { + t.Fatalf("expected %q; got %q", expected, actual) + } +} + +func TestMessageIDMismatchError(t *testing.T) { + actual := (&messageIDMismatchErr{messageID: "hello", defaultMessageID: "world"}).Error() + expected := `message id "hello" does not match default message id "world"` + if actual != expected { + t.Fatalf("expected %q; got %q", expected, actual) + } +} + +func TestInvalidPluralCountError(t *testing.T) { + actual := (&invalidPluralCountErr{messageID: "hello", pluralCount: "blah", err: fmt.Errorf("error")}).Error() + expected := `invalid plural count "blah" for message id "hello": error` + if actual != expected { + t.Fatalf("expected %q; got %q", expected, actual) + } +} + +func TestMustLocalize(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatalf("MustLocalize did not panic") + } + }() + bundle := NewBundle(language.English) + localizer := NewLocalizer(bundle) + localizer.MustLocalize(&LocalizeConfig{ + MessageID: "hello", + }) +} |