Skip to content

Commit

Permalink
Remove i18n.NewBundle and use struct initialization instead (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicksnyder authored May 8, 2018
1 parent 012ec0b commit 8c6996e
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 49 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import "github.com/nicksnyder/go-i18n/v2/i18n"
Create a Bundle to use for the lifetime of your application.

```go
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
```

Create a Localizer to use for a set of language preferences.
Expand Down
2 changes: 1 addition & 1 deletion v2/example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var page = template.Must(template.New("").Parse(`
`))

func main() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
// No need to load active.en.toml since we are providing default translations.
// bundle.MustLoadMessageFile("active.en.toml")
Expand Down
4 changes: 2 additions & 2 deletions v2/goi18n/extract_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestExtract(t *testing.T) {
import "github.com/nicksnyder/go-i18n/v2/i18n"
func main() {
bundle := i18n.NewBundle()
bundle := &i18n.Bundle{}
l := i18n.NewLocalizer(bundle, "en")
l.Localize(&i18n.LocalizeConfig{MessageID: "Plural ID"})
}
Expand All @@ -69,7 +69,7 @@ func TestExtract(t *testing.T) {
import "github.com/nicksnyder/go-i18n/v2/i18n"
func main() {
bundle := i18n.NewBundle()
bundle := &i18n.Bundle{}
l := i18n.NewLocalizer(bundle, "en")
l.MustLocalize(&i18n.LocalizeConfig{MessageID: "Plural ID"})
}
Expand Down
36 changes: 15 additions & 21 deletions v2/i18n/bundle.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package i18n

import (
"encoding/json"
"fmt"
"io/ioutil"

Expand All @@ -18,34 +17,31 @@ type UnmarshalFunc = internal.UnmarshalFunc
// Most applications only need a single bundle
// that is initialized early in the application's lifecycle.
type Bundle struct {
// DefaultLanguage is the default language of the bundle.
DefaultLanguage language.Tag

// UnmarshalFuncs is a map of file extensions to UnmarshalFuncs.
UnmarshalFuncs map[string]UnmarshalFunc

messageTemplates map[language.Tag]map[string]*internal.MessageTemplate
pluralRules plural.Rules
unmarshalFuncs map[string]UnmarshalFunc
defaultTag language.Tag
tags []language.Tag
matcher language.Matcher
}

// NewBundle returns a new bundle that contains the
// CLDR plural rules and a json unmarshaler.
func NewBundle(defaultTag language.Tag) *Bundle {
b := &Bundle{
defaultTag: defaultTag,
pluralRules: plural.DefaultRules(),
unmarshalFuncs: map[string]UnmarshalFunc{
"json": json.Unmarshal,
},
func (b *Bundle) init() {
if b.pluralRules == nil {
b.pluralRules = plural.DefaultRules()
}
b.addTag(defaultTag)
return b
b.addTag(b.DefaultLanguage)
}

// RegisterUnmarshalFunc registers an UnmarshalFunc for format.
func (b *Bundle) RegisterUnmarshalFunc(format string, unmarshalFunc UnmarshalFunc) {
if b.unmarshalFuncs == nil {
b.unmarshalFuncs = make(map[string]UnmarshalFunc)
if b.UnmarshalFuncs == nil {
b.UnmarshalFuncs = make(map[string]UnmarshalFunc)
}
b.unmarshalFuncs[format] = unmarshalFunc
b.UnmarshalFuncs[format] = unmarshalFunc
}

// LoadMessageFile loads the bytes from path
Expand Down Expand Up @@ -75,7 +71,7 @@ type MessageFile = internal.MessageFile
//
// The language tag of the file is everything after the second to last "." or after the last path separator, but before the format.
func (b *Bundle) ParseMessageFileBytes(buf []byte, path string) (*MessageFile, error) {
messageFile, err := internal.ParseMessageFileBytes(buf, path, b.unmarshalFuncs)
messageFile, err := internal.ParseMessageFileBytes(buf, path, b.UnmarshalFuncs)
if err != nil {
return nil, err
}
Expand All @@ -96,9 +92,7 @@ func (b *Bundle) MustParseMessageFileBytes(buf []byte, path string) {
// AddMessages adds messages for a language.
// It is useful if your messages are in a format not supported by ParseMessageFileBytes.
func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error {
if b.pluralRules == nil {
b.pluralRules = plural.DefaultRules()
}
b.init()
pluralRule := b.pluralRules.Rule(tag)
if pluralRule == nil {
return fmt.Errorf("no plural rule registered for %s", tag)
Expand Down
8 changes: 2 additions & 6 deletions v2/i18n/bundle_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package i18n

import (
"encoding/json"
"reflect"
"testing"

Expand Down Expand Up @@ -34,7 +33,7 @@ var everythingMessage = internal.MustNewMessage(map[string]string{
})

func TestPseudoLanguage(t *testing.T) {
bundle := NewBundle(language.English)
bundle := &Bundle{DefaultLanguage: language.English}
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
expected := "simple simple"
bundle.MustParseMessageFileBytes([]byte(`
Expand All @@ -52,7 +51,7 @@ simple = "simple simple"
}

func TestPseudoLanguagePlural(t *testing.T) {
bundle := NewBundle(language.English)
bundle := &Bundle{DefaultLanguage: language.English}
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
bundle.MustParseMessageFileBytes([]byte(`
[everything]
Expand Down Expand Up @@ -82,7 +81,6 @@ zero = "zero translation"

func TestJSON(t *testing.T) {
var bundle Bundle
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
bundle.MustParseMessageFileBytes([]byte(`{
"simple": "simple translation",
"detail": {
Expand Down Expand Up @@ -163,7 +161,6 @@ other = "other translation"

func TestV1Format(t *testing.T) {
var bundle Bundle
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
bundle.MustParseMessageFileBytes([]byte(`[
{
"id": "simple",
Expand Down Expand Up @@ -191,7 +188,6 @@ func TestV1Format(t *testing.T) {

func TestV1FlatFormat(t *testing.T) {
var bundle Bundle
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
bundle.MustParseMessageFileBytes([]byte(`{
"simple": {
"other": "simple translation"
Expand Down
2 changes: 1 addition & 1 deletion v2/i18n/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// according to a set of locale preferences.
//
// Create a Bundle to use for the lifetime of your application.
// bundle := i18n.NewBundle(language.English)
// bundle := &i18n.Bundle{DefaultLanguage: language.English}
//
// Create a Localizer to use for a set of language preferences.
// func(w http.ResponseWriter, r *http.Request) {
Expand Down
12 changes: 6 additions & 6 deletions v2/i18n/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func ExampleLocalizer_MustLocalize() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
localizer := i18n.NewLocalizer(bundle, "en")
fmt.Println(localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
Expand All @@ -22,7 +22,7 @@ func ExampleLocalizer_MustLocalize() {
}

func ExampleLocalizer_MustLocalize_noDefaultMessage() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
bundle.MustParseMessageFileBytes([]byte(`
HelloWorld = "Hello World!"
Expand All @@ -45,7 +45,7 @@ HelloWorld = "Hola Mundo!"
}

func ExampleLocalizer_MustLocalize_plural() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
localizer := i18n.NewLocalizer(bundle, "en")
catsMessage := &i18n.Message{
ID: "Cats",
Expand All @@ -71,7 +71,7 @@ func ExampleLocalizer_MustLocalize_plural() {
}

func ExampleLocalizer_MustLocalize_template() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
localizer := i18n.NewLocalizer(bundle, "en")
helloPersonMessage := &i18n.Message{
ID: "HelloPerson",
Expand All @@ -86,7 +86,7 @@ func ExampleLocalizer_MustLocalize_template() {
}

func ExampleLocalizer_MustLocalize_plural_template() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
localizer := i18n.NewLocalizer(bundle, "en")
personCatsMessage := &i18n.Message{
ID: "PersonCats",
Expand Down Expand Up @@ -124,7 +124,7 @@ func ExampleLocalizer_MustLocalize_plural_template() {
}

func ExampleLocalizer_MustLocalize_customTemplateDelims() {
bundle := i18n.NewBundle(language.English)
bundle := &i18n.Bundle{DefaultLanguage: language.English}
localizer := i18n.NewLocalizer(bundle, "en")
helloPersonMessage := &i18n.Message{
ID: "HelloPerson",
Expand Down
11 changes: 6 additions & 5 deletions v2/i18n/localizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Localizer struct {
// in the bundle according to the language preferences in langs.
// It can parse Accept-Language headers as defined in http://www.ietf.org/rfc/rfc2616.txt.
func NewLocalizer(bundle *Bundle, langs ...string) *Localizer {
bundle.init()
return &Localizer{
bundle: bundle,
tags: parseTags(langs),
Expand Down Expand Up @@ -129,7 +130,7 @@ func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Ta
if template != nil {
return fastTag, template
}
if fastTag == l.bundle.defaultTag {
if fastTag == l.bundle.DefaultLanguage {
if defaultMessage == nil {
return fastTag, nil
}
Expand All @@ -141,8 +142,8 @@ func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Ta
// 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))
if l.bundle.defaultTag != fastTag {
foundTags = append(foundTags, l.bundle.defaultTag)
if l.bundle.DefaultLanguage != fastTag {
foundTags = append(foundTags, l.bundle.DefaultLanguage)
}
for t, templates := range l.bundle.messageTemplates {
if t == fastTag {
Expand All @@ -161,9 +162,9 @@ func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Ta
}
}
if defaultMessage == nil {
return l.bundle.defaultTag, nil
return l.bundle.DefaultLanguage, nil
}
return l.bundle.defaultTag, internal.NewMessageTemplate(defaultMessage)
return l.bundle.DefaultLanguage, internal.NewMessageTemplate(defaultMessage)
}

func (l *Localizer) matchTemplate(id string, matcher language.Matcher, tags []language.Tag) (language.Tag, *internal.MessageTemplate) {
Expand Down
2 changes: 1 addition & 1 deletion v2/i18n/localizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ func TestLocalizer_Localize(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
bundle := NewBundle(testCase.defaultLanguage)
bundle := &Bundle{DefaultLanguage: testCase.defaultLanguage}
for tag, messages := range testCase.messages {
bundle.AddMessages(tag, messages...)
}
Expand Down
12 changes: 7 additions & 5 deletions v2/internal/parse.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internal

import (
"encoding/json"
"fmt"
"os"

Expand Down Expand Up @@ -30,12 +31,13 @@ func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]Un
if len(buf) == 0 {
return messageFile, nil
}
var unmarshalFunc UnmarshalFunc
if unmarshalFuncs != nil {
unmarshalFunc = unmarshalFuncs[messageFile.Format]
}
unmarshalFunc := unmarshalFuncs[messageFile.Format]
if unmarshalFunc == nil {
return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format)
if messageFile.Format == "json" {
unmarshalFunc = json.Unmarshal
} else {
return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format)
}
}
var raw interface{}
if err := unmarshalFunc(buf, &raw); err != nil {
Expand Down
50 changes: 50 additions & 0 deletions v2/internal/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package internal

import (
"reflect"
"testing"

"golang.org/x/text/language"
)

func TestParseMessageFileBytes(t *testing.T) {
testCases := []struct {
file string
path string
unmarshalFuncs map[string]UnmarshalFunc
messageFile *MessageFile
err error
}{
{
file: `{"hello": "world"}`,
path: "en.json",
messageFile: &MessageFile{
Path: "en.json",
Tag: language.English,
Format: "json",
Messages: []*Message{{
ID: "hello",
Other: "world",
}},
},
},
}
for _, testCase := range testCases {
actual, err := ParseMessageFileBytes([]byte(testCase.file), testCase.path, testCase.unmarshalFuncs)
if err != testCase.err {
t.Fatalf("expected error %#v; got %#v", testCase.err, err)
}
if actual.Path != testCase.messageFile.Path {
t.Fatalf("expected path %q; got %q", testCase.messageFile.Path, actual.Path)
}
if actual.Tag != testCase.messageFile.Tag {
t.Fatalf("expected tag %q; got %q", testCase.messageFile.Tag, actual.Tag)
}
if actual.Format != testCase.messageFile.Format {
t.Fatalf("expected format %q; got %q", testCase.messageFile.Format, actual.Format)
}
if !reflect.DeepEqual(actual.Messages, testCase.messageFile.Messages) {
t.Fatalf("expected %#v; got %#v", testCase.messageFile.Messages, actual.Messages)
}
}
}

0 comments on commit 8c6996e

Please sign in to comment.