diff options
Diffstat (limited to 'vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go')
-rw-r--r-- | vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go new file mode 100644 index 000000000..f9d516fa6 --- /dev/null +++ b/vendor/github.com/nicksnyder/go-i18n/v2/i18n/localizer.go @@ -0,0 +1,198 @@ +package i18n + +import ( + "fmt" + + "text/template" + + "github.com/nicksnyder/go-i18n/v2/internal" + "github.com/nicksnyder/go-i18n/v2/internal/plural" + "golang.org/x/text/language" +) + +// Localizer provides Localize and MustLocalize methods that return localized messages. +type Localizer struct { + // bundle contains the messages that can be returned by the Localizer. + bundle *Bundle + + // tags is the list of language tags that the Localizer checks + // in order when localizing a message. + tags []language.Tag +} + +// NewLocalizer returns a new Localizer that looks up messages +// 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), + } +} + +func parseTags(langs []string) []language.Tag { + tags := []language.Tag{} + for _, lang := range langs { + t, _, err := language.ParseAcceptLanguage(lang) + if err != nil { + continue + } + tags = append(tags, t...) + } + return tags +} + +// LocalizeConfig configures a call to the Localize method on Localizer. +type LocalizeConfig struct { + // MessageID is the id of the message to lookup. + // This field is ignored if DefaultMessage is set. + MessageID string + + // TemplateData is the data passed when executing the message's template. + // If TemplateData is nil and PluralCount is not nil, then the message template + // will be executed with data that contains the plural count. + TemplateData interface{} + + // PluralCount determines which plural form of the message is used. + PluralCount interface{} + + // DefaultMessage is used if the message is not found in any message files. + DefaultMessage *Message + + // Funcs is used to extend the Go template engines built in functions + Funcs template.FuncMap +} + +type invalidPluralCountErr struct { + messageID string + pluralCount interface{} + err error +} + +func (e *invalidPluralCountErr) Error() string { + return fmt.Sprintf("invalid plural count %#v for message id %q: %s", e.pluralCount, e.messageID, e.err) +} + +type messageNotFoundErr struct { + messageID string +} + +func (e *messageNotFoundErr) Error() string { + return fmt.Sprintf("message %q not found", e.messageID) +} + +type pluralizeErr struct { + messageID string + tag language.Tag +} + +func (e *pluralizeErr) Error() string { + return fmt.Sprintf("unable to pluralize %q because there no plural rule for %q", e.messageID, e.tag) +} + +// Localize returns a localized message. +func (l *Localizer) Localize(lc *LocalizeConfig) (string, error) { + messageID := lc.MessageID + if lc.DefaultMessage != nil { + messageID = lc.DefaultMessage.ID + } + + var operands *plural.Operands + templateData := lc.TemplateData + if lc.PluralCount != nil { + var err error + operands, err = plural.NewOperands(lc.PluralCount) + if err != nil { + return "", &invalidPluralCountErr{messageID: messageID, pluralCount: lc.PluralCount, err: err} + } + if templateData == nil { + templateData = map[string]interface{}{ + "PluralCount": lc.PluralCount, + } + } + } + tag, template := l.getTemplate(messageID, lc.DefaultMessage) + if template == nil { + return "", &messageNotFoundErr{messageID: messageID} + } + pluralForm := l.pluralForm(tag, operands) + if pluralForm == plural.Invalid { + return "", &pluralizeErr{messageID: messageID, tag: tag} + } + return template.Execute(pluralForm, templateData, lc.Funcs) +} + +func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *internal.MessageTemplate) { + // Fast path. + // Optimistically assume this message id is defined in each language. + fastTag, template := l.matchTemplate(id, l.bundle.matcher, l.bundle.tags) + if template != nil { + return fastTag, template + } + if fastTag == l.bundle.DefaultLanguage { + if defaultMessage == nil { + return fastTag, nil + } + return fastTag, internal.NewMessageTemplate(defaultMessage) + } + if len(l.bundle.tags) > 1 { + // 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)) + if l.bundle.DefaultLanguage != fastTag { + foundTags = append(foundTags, l.bundle.DefaultLanguage) + } + for t, templates := range l.bundle.messageTemplates { + if t == fastTag { + // We already tried this tag in the fast path + continue + } + template := templates[id] + if template == nil || template.Other == "" { + continue + } + foundTags = append(foundTags, t) + } + tag, template := l.matchTemplate(id, language.NewMatcher(foundTags), foundTags) + if template != nil { + return tag, template + } + } + if defaultMessage == nil { + return l.bundle.DefaultLanguage, nil + } + return l.bundle.DefaultLanguage, internal.NewMessageTemplate(defaultMessage) +} + +func (l *Localizer) matchTemplate(id string, matcher language.Matcher, tags []language.Tag) (language.Tag, *internal.MessageTemplate) { + _, i, _ := matcher.Match(l.tags...) + tag := tags[i] + templates := l.bundle.messageTemplates[tag] + if templates != nil && templates[id] != nil { + return tag, templates[id] + } + return tag, nil +} + +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) +} + +// MustLocalize is similar to Localize, except it panics if an error happens. +func (l *Localizer) MustLocalize(lc *LocalizeConfig) string { + localized, err := l.Localize(lc) + if err != nil { + panic(err) + } + return localized +} |