diff options
author | Nick Snyder <nickdsnyder@gmail.com> | 2019-05-07 22:45:26 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-07 22:45:26 +0300 |
commit | 1c44ce2d4dc76f3033da222b1497415c4f7dc19b (patch) | |
tree | a65a8282f0a0a11c26594e759ec4016974fff289 | |
parent | 14847a9af8ca81e09347a0e596871bd237c8e1aa (diff) |
export message (#170)
-rw-r--r-- | v2/goi18n/extract_command.go | 7 | ||||
-rw-r--r-- | v2/goi18n/marshal.go | 6 | ||||
-rw-r--r-- | v2/goi18n/merge_command.go | 34 | ||||
-rw-r--r-- | v2/i18n/bundle.go | 16 | ||||
-rw-r--r-- | v2/i18n/bundle_test.go | 9 | ||||
-rw-r--r-- | v2/i18n/localizer.go | 7 | ||||
-rw-r--r-- | v2/i18n/message.go | 219 | ||||
-rw-r--r-- | v2/i18n/message_template.go (renamed from v2/internal/message_template.go) | 13 | ||||
-rw-r--r-- | v2/i18n/message_template_test.go (renamed from v2/internal/message_template_test.go) | 2 | ||||
-rw-r--r-- | v2/i18n/message_test.go (renamed from v2/internal/message_test.go) | 2 | ||||
-rw-r--r-- | v2/i18n/parse.go (renamed from v2/internal/parse.go) | 5 | ||||
-rw-r--r-- | v2/i18n/parse_test.go (renamed from v2/internal/parse_test.go) | 2 | ||||
-rw-r--r-- | v2/internal/message.go | 221 | ||||
-rw-r--r-- | v2/internal/template.go | 2 | ||||
-rw-r--r-- | v2/internal/template_test.go | 6 |
15 files changed, 268 insertions, 283 deletions
diff --git a/v2/goi18n/extract_command.go b/v2/goi18n/extract_command.go index 87debbb..b7bee37 100644 --- a/v2/goi18n/extract_command.go +++ b/v2/goi18n/extract_command.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/nicksnyder/go-i18n/v2/internal" ) func usageExtract() { @@ -100,9 +99,9 @@ func (ec *extractCommand) execute() error { return err } } - messageTemplates := map[string]*internal.MessageTemplate{} + messageTemplates := map[string]*i18n.MessageTemplate{} for _, m := range messages { - if mt := internal.NewMessageTemplate(m); mt != nil { + if mt := i18n.NewMessageTemplate(m); mt != nil { messageTemplates[m.ID] = mt } } @@ -229,7 +228,7 @@ func (e *extractor) extractMessage(cl *ast.CompositeLit) { if messageID := data["MessageID"]; messageID != "" { data["ID"] = messageID } - e.messages = append(e.messages, internal.MustNewMessage(data)) + e.messages = append(e.messages, i18n.MustNewMessage(data)) } func extractStringLiteral(expr ast.Expr) (string, bool) { diff --git a/v2/goi18n/marshal.go b/v2/goi18n/marshal.go index 5fd908b..751b698 100644 --- a/v2/goi18n/marshal.go +++ b/v2/goi18n/marshal.go @@ -7,13 +7,13 @@ import ( "path/filepath" "github.com/BurntSushi/toml" - "github.com/nicksnyder/go-i18n/v2/internal" + "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) -func writeFile(outdir, label string, langTag language.Tag, format string, messageTemplates map[string]*internal.MessageTemplate, sourceLanguage bool) (path string, content []byte, err error) { +func writeFile(outdir, label string, langTag language.Tag, format string, messageTemplates map[string]*i18n.MessageTemplate, sourceLanguage bool) (path string, content []byte, err error) { v := marshalValue(messageTemplates, sourceLanguage) content, err = marshal(v, format) if err != nil { @@ -23,7 +23,7 @@ func writeFile(outdir, label string, langTag language.Tag, format string, messag return } -func marshalValue(messageTemplates map[string]*internal.MessageTemplate, sourceLanguage bool) interface{} { +func marshalValue(messageTemplates map[string]*i18n.MessageTemplate, sourceLanguage bool) interface{} { v := make(map[string]interface{}, len(messageTemplates)) for id, template := range messageTemplates { if other := template.PluralTemplates[plural.Other]; sourceLanguage && len(template.PluralTemplates) == 1 && diff --git a/v2/goi18n/merge_command.go b/v2/goi18n/merge_command.go index 7a02598..6e6041f 100644 --- a/v2/goi18n/merge_command.go +++ b/v2/goi18n/merge_command.go @@ -108,21 +108,21 @@ type fileSystemOp struct { } func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdir, outputFormat string) (*fileSystemOp, error) { - unmerged := make(map[language.Tag][]map[string]*internal.MessageTemplate) - sourceMessageTemplates := make(map[string]*internal.MessageTemplate) - unmarshalFuncs := map[string]internal.UnmarshalFunc{ + unmerged := make(map[language.Tag][]map[string]*i18n.MessageTemplate) + sourceMessageTemplates := make(map[string]*i18n.MessageTemplate) + unmarshalFuncs := map[string]i18n.UnmarshalFunc{ "json": json.Unmarshal, "toml": toml.Unmarshal, "yaml": yaml.Unmarshal, } for path, content := range messageFiles { - mf, err := internal.ParseMessageFileBytes(content, path, unmarshalFuncs) + mf, err := i18n.ParseMessageFileBytes(content, path, unmarshalFuncs) if err != nil { return nil, fmt.Errorf("failed to load message file %s: %s", path, err) } - templates := map[string]*internal.MessageTemplate{} + templates := map[string]*i18n.MessageTemplate{} for _, m := range mf.Messages { - templates[m.ID] = internal.NewMessageTemplate(m) + templates[m.ID] = i18n.NewMessageTemplate(m) } if mf.Tag == sourceLanguageTag { for _, template := range templates { @@ -141,7 +141,7 @@ func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdi } pluralRules := plural.DefaultRules() - all := make(map[language.Tag]map[string]*internal.MessageTemplate) + all := make(map[language.Tag]map[string]*i18n.MessageTemplate) all[sourceLanguageTag] = sourceMessageTemplates for _, srcTemplate := range sourceMessageTemplates { for dstLangTag, messageTemplates := range unmerged { @@ -155,11 +155,11 @@ func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdi continue } if all[dstLangTag] == nil { - all[dstLangTag] = make(map[string]*internal.MessageTemplate) + all[dstLangTag] = make(map[string]*i18n.MessageTemplate) } dstMessageTemplate := all[dstLangTag][srcTemplate.ID] if dstMessageTemplate == nil { - dstMessageTemplate = &internal.MessageTemplate{ + dstMessageTemplate = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: srcTemplate.ID, Description: srcTemplate.Description, @@ -193,10 +193,10 @@ func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdi } } - translate := make(map[language.Tag]map[string]*internal.MessageTemplate) - active := make(map[language.Tag]map[string]*internal.MessageTemplate) + translate := make(map[language.Tag]map[string]*i18n.MessageTemplate) + active := make(map[language.Tag]map[string]*i18n.MessageTemplate) for langTag, messageTemplates := range all { - active[langTag] = make(map[string]*internal.MessageTemplate) + active[langTag] = make(map[string]*i18n.MessageTemplate) if langTag == sourceLanguageTag { active[langTag] = messageTemplates continue @@ -212,7 +212,7 @@ func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdi activeMessageTemplate, translateMessageTemplate := activeDst(srcMessageTemplate, messageTemplate, pluralRule) if translateMessageTemplate != nil { if translate[langTag] == nil { - translate[langTag] = make(map[string]*internal.MessageTemplate) + translate[langTag] = make(map[string]*i18n.MessageTemplate) } translate[langTag][messageTemplate.ID] = translateMessageTemplate } @@ -246,7 +246,7 @@ func merge(messageFiles map[string][]byte, sourceLanguageTag language.Tag, outdi } // activeDst returns the active part of the dst and whether dst is a complete translation of src. -func activeDst(src, dst *internal.MessageTemplate, pluralRule *plural.Rule) (active *internal.MessageTemplate, translateMessageTemplate *internal.MessageTemplate) { +func activeDst(src, dst *i18n.MessageTemplate, pluralRule *plural.Rule) (active *i18n.MessageTemplate, translateMessageTemplate *i18n.MessageTemplate) { pluralForms := pluralRule.PluralForms if len(src.PluralTemplates) == 1 { pluralForms = map[plural.Form]struct{}{ @@ -257,7 +257,7 @@ func activeDst(src, dst *internal.MessageTemplate, pluralRule *plural.Rule) (act dt := dst.PluralTemplates[pluralForm] if dt == nil || dt.Src == "" { if translateMessageTemplate == nil { - translateMessageTemplate = &internal.MessageTemplate{ + translateMessageTemplate = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: src.ID, Description: src.Description, @@ -270,7 +270,7 @@ func activeDst(src, dst *internal.MessageTemplate, pluralRule *plural.Rule) (act continue } if active == nil { - active = &internal.MessageTemplate{ + active = &i18n.MessageTemplate{ Message: &i18n.Message{ ID: src.ID, Description: src.Description, @@ -284,7 +284,7 @@ func activeDst(src, dst *internal.MessageTemplate, pluralRule *plural.Rule) (act return } -func hash(t *internal.MessageTemplate) string { +func hash(t *i18n.MessageTemplate) string { h := sha1.New() io.WriteString(h, t.Description) io.WriteString(h, t.PluralTemplates[plural.Other].Src) diff --git a/v2/i18n/bundle.go b/v2/i18n/bundle.go index a25eb87..9977cde 100644 --- a/v2/i18n/bundle.go +++ b/v2/i18n/bundle.go @@ -4,14 +4,13 @@ import ( "fmt" "io/ioutil" - "github.com/nicksnyder/go-i18n/v2/internal" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" ) // UnmarshalFunc unmarshals data into v. -type UnmarshalFunc = internal.UnmarshalFunc +type UnmarshalFunc func(data []byte, v interface{}) error // Bundle stores a set of messages and pluralization rules. // Most applications only need a single bundle @@ -21,7 +20,7 @@ type UnmarshalFunc = internal.UnmarshalFunc type Bundle struct { defaultLanguage language.Tag unmarshalFuncs map[string]UnmarshalFunc - messageTemplates map[language.Tag]map[string]*internal.MessageTemplate + messageTemplates map[language.Tag]map[string]*MessageTemplate pluralRules plural.Rules tags []language.Tag matcher language.Matcher @@ -62,16 +61,13 @@ func (b *Bundle) MustLoadMessageFile(path string) { } } -// MessageFile represents a parsed message file. -type MessageFile = internal.MessageFile - // ParseMessageFileBytes parses the bytes in buf to add translations to the bundle. // // The format of the file is everything after the last ".". // // 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 := ParseMessageFileBytes(buf, path, b.unmarshalFuncs) if err != nil { return nil, err } @@ -97,14 +93,14 @@ func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error { return fmt.Errorf("no plural rule registered for %s", tag) } if b.messageTemplates == nil { - b.messageTemplates = map[language.Tag]map[string]*internal.MessageTemplate{} + b.messageTemplates = map[language.Tag]map[string]*MessageTemplate{} } if b.messageTemplates[tag] == nil { - b.messageTemplates[tag] = map[string]*internal.MessageTemplate{} + b.messageTemplates[tag] = map[string]*MessageTemplate{} b.addTag(tag) } for _, m := range messages { - b.messageTemplates[tag][m.ID] = internal.NewMessageTemplate(m) + b.messageTemplates[tag][m.ID] = NewMessageTemplate(m) } return nil } diff --git a/v2/i18n/bundle_test.go b/v2/i18n/bundle_test.go index e14c846..327cc08 100644 --- a/v2/i18n/bundle_test.go +++ b/v2/i18n/bundle_test.go @@ -5,23 +5,22 @@ import ( "testing" "github.com/BurntSushi/toml" - "github.com/nicksnyder/go-i18n/v2/internal" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) -var simpleMessage = internal.MustNewMessage(map[string]string{ +var simpleMessage = MustNewMessage(map[string]string{ "id": "simple", "other": "simple translation", }) -var detailMessage = internal.MustNewMessage(map[string]string{ +var detailMessage = MustNewMessage(map[string]string{ "id": "detail", "description": "detail description", "other": "detail translation", }) -var everythingMessage = internal.MustNewMessage(map[string]string{ +var everythingMessage = MustNewMessage(map[string]string{ "id": "everything", "description": "everything description", "zero": "zero translation", @@ -216,7 +215,7 @@ func TestV1FlatFormat(t *testing.T) { } func expectMessage(t *testing.T, bundle *Bundle, tag language.Tag, messageID string, message *Message) { - expected := internal.NewMessageTemplate(message) + expected := NewMessageTemplate(message) actual := bundle.messageTemplates[tag][messageID] if !reflect.DeepEqual(actual, expected) { t.Errorf("bundle.MessageTemplates[%q][%q] = %#v; want %#v", tag, messageID, actual, expected) diff --git a/v2/i18n/localizer.go b/v2/i18n/localizer.go index e02f66b..e149f45 100644 --- a/v2/i18n/localizer.go +++ b/v2/i18n/localizer.go @@ -5,7 +5,6 @@ import ( "text/template" - "github.com/nicksnyder/go-i18n/v2/internal" "github.com/nicksnyder/go-i18n/v2/internal/plural" "golang.org/x/text/language" ) @@ -145,7 +144,7 @@ func (l *Localizer) LocalizeWithTag(lc *LocalizeConfig) (string, language.Tag, e return msg, tag, nil } -func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *internal.MessageTemplate) { +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) @@ -175,7 +174,7 @@ func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Ta 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, *internal.MessageTemplate) { +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] @@ -183,7 +182,7 @@ func (l *Localizer) matchTemplate(id string, defaultMessage *Message, matcher la return tag, templates[id] } if tag == l.bundle.defaultLanguage && defaultMessage != nil { - return tag, internal.NewMessageTemplate(defaultMessage) + return tag, NewMessageTemplate(defaultMessage) } return tag, nil } diff --git a/v2/i18n/message.go b/v2/i18n/message.go index 7c881a6..f8f789a 100644 --- a/v2/i18n/message.go +++ b/v2/i18n/message.go @@ -1,6 +1,221 @@ package i18n -import "github.com/nicksnyder/go-i18n/v2/internal" +import ( + "fmt" + "strings" +) // Message is a string that can be localized. -type Message = internal.Message +type Message struct { + // ID uniquely identifies the message. + ID string + + // Hash uniquely identifies the content of the message + // that this message was translated from. + Hash string + + // Description describes the message to give additional + // context to translators that may be relevant for translation. + Description string + + // LeftDelim is the left Go template delimiter. + LeftDelim string + + // RightDelim is the right Go template delimiter.`` + RightDelim string + + // Zero is the content of the message for the CLDR plural form "zero". + Zero string + + // One is the content of the message for the CLDR plural form "one". + One string + + // Two is the content of the message for the CLDR plural form "two". + Two string + + // Few is the content of the message for the CLDR plural form "few". + Few string + + // Many is the content of the message for the CLDR plural form "many". + Many string + + // Other is the content of the message for the CLDR plural form "other". + Other string +} + +// NewMessage parses data and returns a new message. +func NewMessage(data interface{}) (*Message, error) { + m := &Message{} + if err := m.unmarshalInterface(data); err != nil { + return nil, err + } + return m, nil +} + +// MustNewMessage is similar to NewMessage except it panics if an error happens. +func MustNewMessage(data interface{}) *Message { + m, err := NewMessage(data) + if err != nil { + panic(err) + } + return m +} + +// unmarshalInterface unmarshals a message from data. +func (m *Message) unmarshalInterface(v interface{}) error { + strdata, err := stringMap(v) + if err != nil { + return err + } + for k, v := range strdata { + switch strings.ToLower(k) { + case "id": + m.ID = v + case "description": + m.Description = v + case "hash": + m.Hash = v + case "leftdelim": + m.LeftDelim = v + case "rightdelim": + m.RightDelim = v + case "zero": + m.Zero = v + case "one": + m.One = v + case "two": + m.Two = v + case "few": + m.Few = v + case "many": + m.Many = v + case "other": + m.Other = v + } + } + return nil +} + +type keyTypeErr struct { + key interface{} +} + +func (err *keyTypeErr) Error() string { + return fmt.Sprintf("expected key to be a string but got %#v", err.key) +} + +type valueTypeErr struct { + value interface{} +} + +func (err *valueTypeErr) Error() string { + return fmt.Sprintf("unsupported type %#v", err.value) +} + +func stringMap(v interface{}) (map[string]string, error) { + switch value := v.(type) { + case string: + return map[string]string{ + "other": value, + }, nil + case map[string]string: + return value, nil + case map[string]interface{}: + strdata := make(map[string]string, len(value)) + for k, v := range value { + err := stringSubmap(k, v, strdata) + if err != nil { + return nil, err + } + } + return strdata, nil + case map[interface{}]interface{}: + strdata := make(map[string]string, len(value)) + for k, v := range value { + kstr, ok := k.(string) + if !ok { + return nil, &keyTypeErr{key: k} + } + err := stringSubmap(kstr, v, strdata) + if err != nil { + return nil, err + } + } + return strdata, nil + default: + return nil, &valueTypeErr{value: value} + } +} + +func stringSubmap(k string, v interface{}, strdata map[string]string) error { + if k == "translation" { + switch vt := v.(type) { + case string: + strdata["other"] = vt + default: + v1Message, err := stringMap(v) + if err != nil { + return err + } + for kk, vv := range v1Message { + strdata[kk] = vv + } + } + return nil + } + + switch vt := v.(type) { + case string: + strdata[k] = vt + return nil + case nil: + return nil + default: + return fmt.Errorf("expected value for key %q be a string but got %#v", k, v) + } +} + +// isMessage tells whether the given data is a message, or a map containing +// nested messages. +// A map is assumed to be a message if it contains any of the "reserved" keys: +// "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other" +// with a string value. +// e.g., +// - {"message": {"description": "world"}} is a message +// - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored) +// - {"notmessage": {"description": {"hello": "world"}}} is not +// - {"notmessage": {"foo": "bar"}} is not +func isMessage(v interface{}) bool { + reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"} + switch data := v.(type) { + case string: + return true + case map[string]interface{}: + for _, key := range reservedKeys { + val, ok := data[key] + if !ok { + continue + } + _, ok = val.(string) + if !ok { + continue + } + // v is a message if it contains a "reserved" key holding a string value + return true + } + case map[interface{}]interface{}: + for _, key := range reservedKeys { + val, ok := data[key] + if !ok { + continue + } + _, ok = val.(string) + if !ok { + continue + } + // v is a message if it contains a "reserved" key holding a string value + return true + } + } + return false +} diff --git a/v2/internal/message_template.go b/v2/i18n/message_template.go index 134dae7..65a16cb 100644 --- a/v2/internal/message_template.go +++ b/v2/i18n/message_template.go @@ -1,4 +1,4 @@ -package internal +package i18n import ( "bytes" @@ -6,18 +6,19 @@ import ( "text/template" + "github.com/nicksnyder/go-i18n/v2/internal" "github.com/nicksnyder/go-i18n/v2/internal/plural" ) // MessageTemplate is an executable template for a message. type MessageTemplate struct { *Message - PluralTemplates map[plural.Form]*Template + PluralTemplates map[plural.Form]*internal.Template } // NewMessageTemplate returns a new message template. func NewMessageTemplate(m *Message) *MessageTemplate { - pluralTemplates := map[plural.Form]*Template{} + pluralTemplates := map[plural.Form]*internal.Template{} setPluralTemplate(pluralTemplates, plural.Zero, m.Zero) setPluralTemplate(pluralTemplates, plural.One, m.One) setPluralTemplate(pluralTemplates, plural.Two, m.Two) @@ -33,9 +34,9 @@ func NewMessageTemplate(m *Message) *MessageTemplate { } } -func setPluralTemplate(pluralTemplates map[plural.Form]*Template, pluralForm plural.Form, src string) { +func setPluralTemplate(pluralTemplates map[plural.Form]*internal.Template, pluralForm plural.Form, src string) { if src != "" { - pluralTemplates[pluralForm] = &Template{Src: src} + pluralTemplates[pluralForm] = &internal.Template{Src: src} } } @@ -57,7 +58,7 @@ func (mt *MessageTemplate) Execute(pluralForm plural.Form, data interface{}, fun messageID: mt.Message.ID, } } - if err := t.parse(mt.LeftDelim, mt.RightDelim, funcs); err != nil { + if err := t.Parse(mt.LeftDelim, mt.RightDelim, funcs); err != nil { return "", err } if t.Template == nil { diff --git a/v2/internal/message_template_test.go b/v2/i18n/message_template_test.go index a32708c..d920cd4 100644 --- a/v2/internal/message_template_test.go +++ b/v2/i18n/message_template_test.go @@ -1,4 +1,4 @@ -package internal +package i18n import ( "reflect" diff --git a/v2/internal/message_test.go b/v2/i18n/message_test.go index cab5735..b37063a 100644 --- a/v2/internal/message_test.go +++ b/v2/i18n/message_test.go @@ -1,4 +1,4 @@ -package internal +package i18n import ( "reflect" diff --git a/v2/internal/parse.go b/v2/i18n/parse.go index da37d43..57dd7fe 100644 --- a/v2/internal/parse.go +++ b/v2/i18n/parse.go @@ -1,4 +1,4 @@ -package internal +package i18n import ( "encoding/json" @@ -9,9 +9,6 @@ import ( "golang.org/x/text/language" ) -// UnmarshalFunc unmarshals data into v. -type UnmarshalFunc func(data []byte, v interface{}) error - // MessageFile represents a parsed message file. type MessageFile struct { Path string diff --git a/v2/internal/parse_test.go b/v2/i18n/parse_test.go index 5581b27..3272e52 100644 --- a/v2/internal/parse_test.go +++ b/v2/i18n/parse_test.go @@ -1,4 +1,4 @@ -package internal +package i18n import ( "reflect" diff --git a/v2/internal/message.go b/v2/internal/message.go deleted file mode 100644 index 9274404..0000000 --- a/v2/internal/message.go +++ /dev/null @@ -1,221 +0,0 @@ -package internal - -import ( - "fmt" - "strings" -) - -// Message is a string that can be localized. -type Message struct { - // ID uniquely identifies the message. - ID string - - // Hash uniquely identifies the content of the message - // that this message was translated from. - Hash string - - // Description describes the message to give additional - // context to translators that may be relevant for translation. - Description string - - // LeftDelim is the left Go template delimiter. - LeftDelim string - - // RightDelim is the right Go template delimiter.`` - RightDelim string - - // Zero is the content of the message for the CLDR plural form "zero". - Zero string - - // One is the content of the message for the CLDR plural form "one". - One string - - // Two is the content of the message for the CLDR plural form "two". - Two string - - // Few is the content of the message for the CLDR plural form "few". - Few string - - // Many is the content of the message for the CLDR plural form "many". - Many string - - // Other is the content of the message for the CLDR plural form "other". - Other string -} - -// NewMessage parses data and returns a new message. -func NewMessage(data interface{}) (*Message, error) { - m := &Message{} - if err := m.unmarshalInterface(data); err != nil { - return nil, err - } - return m, nil -} - -// MustNewMessage is similar to NewMessage except it panics if an error happens. -func MustNewMessage(data interface{}) *Message { - m, err := NewMessage(data) - if err != nil { - panic(err) - } - return m -} - -// unmarshalInterface unmarshals a message from data. -func (m *Message) unmarshalInterface(v interface{}) error { - strdata, err := stringMap(v) - if err != nil { - return err - } - for k, v := range strdata { - switch strings.ToLower(k) { - case "id": - m.ID = v - case "description": - m.Description = v - case "hash": - m.Hash = v - case "leftdelim": - m.LeftDelim = v - case "rightdelim": - m.RightDelim = v - case "zero": - m.Zero = v - case "one": - m.One = v - case "two": - m.Two = v - case "few": - m.Few = v - case "many": - m.Many = v - case "other": - m.Other = v - } - } - return nil -} - -type keyTypeErr struct { - key interface{} -} - -func (err *keyTypeErr) Error() string { - return fmt.Sprintf("expected key to be a string but got %#v", err.key) -} - -type valueTypeErr struct { - value interface{} -} - -func (err *valueTypeErr) Error() string { - return fmt.Sprintf("unsupported type %#v", err.value) -} - -func stringMap(v interface{}) (map[string]string, error) { - switch value := v.(type) { - case string: - return map[string]string{ - "other": value, - }, nil - case map[string]string: - return value, nil - case map[string]interface{}: - strdata := make(map[string]string, len(value)) - for k, v := range value { - err := stringSubmap(k, v, strdata) - if err != nil { - return nil, err - } - } - return strdata, nil - case map[interface{}]interface{}: - strdata := make(map[string]string, len(value)) - for k, v := range value { - kstr, ok := k.(string) - if !ok { - return nil, &keyTypeErr{key: k} - } - err := stringSubmap(kstr, v, strdata) - if err != nil { - return nil, err - } - } - return strdata, nil - default: - return nil, &valueTypeErr{value: value} - } -} - -func stringSubmap(k string, v interface{}, strdata map[string]string) error { - if k == "translation" { - switch vt := v.(type) { - case string: - strdata["other"] = vt - default: - v1Message, err := stringMap(v) - if err != nil { - return err - } - for kk, vv := range v1Message { - strdata[kk] = vv - } - } - return nil - } - - switch vt := v.(type) { - case string: - strdata[k] = vt - return nil - case nil: - return nil - default: - return fmt.Errorf("expected value for key %q be a string but got %#v", k, v) - } -} - -// isMessage tells whether the given data is a message, or a map containing -// nested messages. -// A map is assumed to be a message if it contains any of the "reserved" keys: -// "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other" -// with a string value. -// e.g., -// - {"message": {"description": "world"}} is a message -// - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored) -// - {"notmessage": {"description": {"hello": "world"}}} is not -// - {"notmessage": {"foo": "bar"}} is not -func isMessage(v interface{}) bool { - reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"} - switch data := v.(type) { - case string: - return true - case map[string]interface{}: - for _, key := range reservedKeys { - val, ok := data[key] - if !ok { - continue - } - _, ok = val.(string) - if !ok { - continue - } - // v is a message if it contains a "reserved" key holding a string value - return true - } - case map[interface{}]interface{}: - for _, key := range reservedKeys { - val, ok := data[key] - if !ok { - continue - } - _, ok = val.(string) - if !ok { - continue - } - // v is a message if it contains a "reserved" key holding a string value - return true - } - } - return false -} diff --git a/v2/internal/template.go b/v2/internal/template.go index 2ef1eea..4079f52 100644 --- a/v2/internal/template.go +++ b/v2/internal/template.go @@ -12,7 +12,7 @@ type Template struct { ParseErr *error } -func (t *Template) parse(leftDelim, rightDelim string, funcs gotemplate.FuncMap) error { +func (t *Template) Parse(leftDelim, rightDelim string, funcs gotemplate.FuncMap) error { if t.ParseErr == nil { if strings.Contains(t.Src, leftDelim) { gt, err := gotemplate.New("").Funcs(funcs).Delims(leftDelim, rightDelim).Parse(t.Src) diff --git a/v2/internal/template_test.go b/v2/internal/template_test.go index 68e60d0..dcc7b27 100644 --- a/v2/internal/template_test.go +++ b/v2/internal/template_test.go @@ -9,7 +9,7 @@ import ( func TestParse(t *testing.T) { tmpl := &Template{Src: "hello"} - if err := tmpl.parse("", "", nil); err != nil { + if err := tmpl.Parse("", "", nil); err != nil { t.Fatal(err) } if tmpl.ParseErr == nil { @@ -23,7 +23,7 @@ func TestParse(t *testing.T) { func TestParseError(t *testing.T) { expectedErr := fmt.Errorf("expected error") tmpl := &Template{ParseErr: &expectedErr} - if err := tmpl.parse("", "", nil); err != expectedErr { + if err := tmpl.Parse("", "", nil); err != expectedErr { t.Fatalf("expected %#v; got %#v", expectedErr, err) } } @@ -35,7 +35,7 @@ func TestParseWithFunc(t *testing.T) { return "bar" }, } - if err := tmpl.parse("", "", funcs); err != nil { + if err := tmpl.Parse("", "", funcs); err != nil { t.Fatal(err) } if tmpl.ParseErr == nil { |