summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/nicksnyder/go-i18n
diff options
context:
space:
mode:
authorWim <wim@42.be>2016-04-10 23:39:38 +0200
committerWim <wim@42.be>2016-04-10 23:39:38 +0200
commitde4c7804101a47a01d0c9b88ea34d2b153e2b6b9 (patch)
treefa379bc2e706951a913e0c7009d9906e42363655 /vendor/github.com/nicksnyder/go-i18n
parent6b18257185b1830bd2eff83fae30bdd2055f78b0 (diff)
downloadmatterbridge-msglm-de4c7804101a47a01d0c9b88ea34d2b153e2b6b9.tar.gz
matterbridge-msglm-de4c7804101a47a01d0c9b88ea34d2b153e2b6b9.tar.bz2
matterbridge-msglm-de4c7804101a47a01d0c9b88ea34d2b153e2b6b9.zip
Vendor libs
Diffstat (limited to 'vendor/github.com/nicksnyder/go-i18n')
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE19
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go315
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go152
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go132
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go143
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go99
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go119
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go40
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go74
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go567
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go78
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go57
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go61
-rw-r--r--vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go83
14 files changed, 1939 insertions, 0 deletions
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE b/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE
new file mode 100644
index 00000000..609cce79
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go b/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go
new file mode 100644
index 00000000..e93db95d
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/bundle/bundle.go
@@ -0,0 +1,315 @@
+// Package bundle manages translations for multiple languages.
+package bundle
+
+import (
+ "encoding/json"
+ "fmt"
+ "gopkg.in/yaml.v2"
+ "io/ioutil"
+ "reflect"
+
+ "path/filepath"
+
+ "github.com/nicksnyder/go-i18n/i18n/language"
+ "github.com/nicksnyder/go-i18n/i18n/translation"
+)
+
+// TranslateFunc is a copy of i18n.TranslateFunc to avoid a circular dependency.
+type TranslateFunc func(translationID string, args ...interface{}) string
+
+// Bundle stores the translations for multiple languages.
+type Bundle struct {
+ // The primary translations for a language tag and translation id.
+ translations map[string]map[string]translation.Translation
+
+ // Translations that can be used when an exact language match is not possible.
+ fallbackTranslations map[string]map[string]translation.Translation
+}
+
+// New returns an empty bundle.
+func New() *Bundle {
+ return &Bundle{
+ translations: make(map[string]map[string]translation.Translation),
+ fallbackTranslations: make(map[string]map[string]translation.Translation),
+ }
+}
+
+// MustLoadTranslationFile is similar to LoadTranslationFile
+// except it panics if an error happens.
+func (b *Bundle) MustLoadTranslationFile(filename string) {
+ if err := b.LoadTranslationFile(filename); err != nil {
+ panic(err)
+ }
+}
+
+// LoadTranslationFile loads the translations from filename into memory.
+//
+// The language that the translations are associated with is parsed from the filename (e.g. en-US.json).
+//
+// Generally you should load translation files once during your program's initialization.
+func (b *Bundle) LoadTranslationFile(filename string) error {
+ buf, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ return b.ParseTranslationFileBytes(filename, buf)
+}
+
+// ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf.
+//
+// It is useful for parsing translation files embedded with go-bindata.
+func (b *Bundle) ParseTranslationFileBytes(filename string, buf []byte) error {
+ basename := filepath.Base(filename)
+ langs := language.Parse(basename)
+ switch l := len(langs); {
+ case l == 0:
+ return fmt.Errorf("no language found in %q", basename)
+ case l > 1:
+ return fmt.Errorf("multiple languages found in filename %q: %v; expected one", basename, langs)
+ }
+ translations, err := parseTranslations(filename, buf)
+ if err != nil {
+ return err
+ }
+ b.AddTranslation(langs[0], translations...)
+ return nil
+}
+
+func parseTranslations(filename string, buf []byte) ([]translation.Translation, error) {
+ var unmarshalFunc func([]byte, interface{}) error
+ switch format := filepath.Ext(filename); format {
+ case ".json":
+ unmarshalFunc = json.Unmarshal
+ case ".yaml":
+ unmarshalFunc = yaml.Unmarshal
+ default:
+ return nil, fmt.Errorf("unsupported file extension %s", format)
+ }
+
+ var translationsData []map[string]interface{}
+ if len(buf) > 0 {
+ if err := unmarshalFunc(buf, &translationsData); err != nil {
+ return nil, err
+ }
+ }
+
+ translations := make([]translation.Translation, 0, len(translationsData))
+ for i, translationData := range translationsData {
+ t, err := translation.NewTranslation(translationData)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse translation #%d in %s because %s\n%v", i, filename, err, translationData)
+ }
+ translations = append(translations, t)
+ }
+ return translations, nil
+}
+
+// AddTranslation adds translations for a language.
+//
+// It is useful if your translations are in a format not supported by LoadTranslationFile.
+func (b *Bundle) AddTranslation(lang *language.Language, translations ...translation.Translation) {
+ if b.translations[lang.Tag] == nil {
+ b.translations[lang.Tag] = make(map[string]translation.Translation, len(translations))
+ }
+ currentTranslations := b.translations[lang.Tag]
+ for _, newTranslation := range translations {
+ if currentTranslation := currentTranslations[newTranslation.ID()]; currentTranslation != nil {
+ currentTranslations[newTranslation.ID()] = currentTranslation.Merge(newTranslation)
+ } else {
+ currentTranslations[newTranslation.ID()] = newTranslation
+ }
+ }
+
+ // lang can provide translations for less specific language tags.
+ for _, tag := range lang.MatchingTags() {
+ b.fallbackTranslations[tag] = currentTranslations
+ }
+}
+
+// Translations returns all translations in the bundle.
+func (b *Bundle) Translations() map[string]map[string]translation.Translation {
+ return b.translations
+}
+
+// LanguageTags returns the tags of all languages that that have been added.
+func (b *Bundle) LanguageTags() []string {
+ var tags []string
+ for k := range b.translations {
+ tags = append(tags, k)
+ }
+ return tags
+}
+
+// LanguageTranslationIDs returns the ids of all translations that have been added for a given language.
+func (b *Bundle) LanguageTranslationIDs(languageTag string) []string {
+ var ids []string
+ for id := range b.translations[languageTag] {
+ ids = append(ids, id)
+ }
+ return ids
+}
+
+// MustTfunc is similar to Tfunc except it panics if an error happens.
+func (b *Bundle) MustTfunc(pref string, prefs ...string) TranslateFunc {
+ tfunc, err := b.Tfunc(pref, prefs...)
+ if err != nil {
+ panic(err)
+ }
+ return tfunc
+}
+
+// MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens.
+func (b *Bundle) MustTfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language) {
+ tfunc, language, err := b.TfuncAndLanguage(pref, prefs...)
+ if err != nil {
+ panic(err)
+ }
+ return tfunc, language
+}
+
+// Tfunc is similar to TfuncAndLanguage except is doesn't return the Language.
+func (b *Bundle) Tfunc(pref string, prefs ...string) (TranslateFunc, error) {
+ tfunc, _, err := b.TfuncAndLanguage(pref, prefs...)
+ return tfunc, err
+}
+
+// TfuncAndLanguage returns a TranslateFunc for the first Language that
+// has a non-zero number of translations in the bundle.
+//
+// The returned Language matches the the first language preference that could be satisfied,
+// but this may not strictly match the language of the translations used to satisfy that preference.
+//
+// For example, the user may request "zh". If there are no translations for "zh" but there are translations
+// for "zh-cn", then the translations for "zh-cn" will be used but the returned Language will be "zh".
+//
+// It can parse languages from Accept-Language headers (RFC 2616),
+// but it assumes weights are monotonically decreasing.
+func (b *Bundle) TfuncAndLanguage(pref string, prefs ...string) (TranslateFunc, *language.Language, error) {
+ lang := b.supportedLanguage(pref, prefs...)
+ var err error
+ if lang == nil {
+ err = fmt.Errorf("no supported languages found %#v", append(prefs, pref))
+ }
+ return func(translationID string, args ...interface{}) string {
+ return b.translate(lang, translationID, args...)
+ }, lang, err
+}
+
+// supportedLanguage returns the first language which
+// has a non-zero number of translations in the bundle.
+func (b *Bundle) supportedLanguage(pref string, prefs ...string) *language.Language {
+ lang := b.translatedLanguage(pref)
+ if lang == nil {
+ for _, pref := range prefs {
+ lang = b.translatedLanguage(pref)
+ if lang != nil {
+ break
+ }
+ }
+ }
+ return lang
+}
+
+func (b *Bundle) translatedLanguage(src string) *language.Language {
+ langs := language.Parse(src)
+ for _, lang := range langs {
+ if len(b.translations[lang.Tag]) > 0 ||
+ len(b.fallbackTranslations[lang.Tag]) > 0 {
+ return lang
+ }
+ }
+ return nil
+}
+
+func (b *Bundle) translate(lang *language.Language, translationID string, args ...interface{}) string {
+ if lang == nil {
+ return translationID
+ }
+
+ translations := b.translations[lang.Tag]
+ if translations == nil {
+ translations = b.fallbackTranslations[lang.Tag]
+ if translations == nil {
+ return translationID
+ }
+ }
+
+ translation := translations[translationID]
+ if translation == nil {
+ return translationID
+ }
+
+ var data interface{}
+ var count interface{}
+ if argc := len(args); argc > 0 {
+ if isNumber(args[0]) {
+ count = args[0]
+ if argc > 1 {
+ data = args[1]
+ }
+ } else {
+ data = args[0]
+ }
+ }
+
+ if count != nil {
+ if data == nil {
+ data = map[string]interface{}{"Count": count}
+ } else {
+ dataMap := toMap(data)
+ dataMap["Count"] = count
+ data = dataMap
+ }
+ }
+
+ p, _ := lang.Plural(count)
+ template := translation.Template(p)
+ if template == nil {
+ return translationID
+ }
+
+ s := template.Execute(data)
+ if s == "" {
+ return translationID
+ }
+ return s
+}
+
+func isNumber(n interface{}) bool {
+ switch n.(type) {
+ case int, int8, int16, int32, int64, string:
+ return true
+ }
+ return false
+}
+
+func toMap(input interface{}) map[string]interface{} {
+ if data, ok := input.(map[string]interface{}); ok {
+ return data
+ }
+ v := reflect.ValueOf(input)
+ switch v.Kind() {
+ case reflect.Ptr:
+ return toMap(v.Elem().Interface())
+ case reflect.Struct:
+ return structToMap(v)
+ default:
+ return nil
+ }
+}
+
+// Converts the top level of a struct to a map[string]interface{}.
+// Code inspired by github.com/fatih/structs.
+func structToMap(v reflect.Value) map[string]interface{} {
+ out := make(map[string]interface{})
+ t := v.Type()
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ if field.PkgPath != "" {
+ // unexported field. skip.
+ continue
+ }
+ out[field.Name] = v.FieldByName(field.Name).Interface()
+ }
+ return out
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go b/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go
new file mode 100644
index 00000000..f9684296
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/i18n.go
@@ -0,0 +1,152 @@
+// Package i18n supports string translations with variable substitution and CLDR pluralization.
+// It is intended to be used in conjunction with the goi18n command, although that is not strictly required.
+//
+// Initialization
+//
+// Your Go program should load translations during its initialization.
+// i18n.MustLoadTranslationFile("path/to/fr-FR.all.json")
+// If your translations are in a file format not supported by (Must)?LoadTranslationFile,
+// then you can use the AddTranslation function to manually add translations.
+//
+// Fetching a translation
+//
+// Use Tfunc or MustTfunc to fetch a TranslateFunc that will return the translated string for a specific language.
+// func handleRequest(w http.ResponseWriter, r *http.Request) {
+// cookieLang := r.Cookie("lang")
+// acceptLang := r.Header.Get("Accept-Language")
+// defaultLang = "en-US" // known valid language
+// T, err := i18n.Tfunc(cookieLang, acceptLang, defaultLang)
+// fmt.Println(T("Hello world"))
+// }
+//
+// Usually it is a good idea to identify strings by a generic id rather than the English translation,
+// but the rest of this documentation will continue to use the English translation for readability.
+// T("Hello world") // ok
+// T("programGreeting") // better!
+//
+// Variables
+//
+// TranslateFunc supports strings that have variables using the text/template syntax.
+// T("Hello {{.Person}}", map[string]interface{}{
+// "Person": "Bob",
+// })
+//
+// Pluralization
+//
+// TranslateFunc supports the pluralization of strings using the CLDR pluralization rules defined here:
+// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+// T("You have {{.Count}} unread emails.", 2)
+// T("I am {{.Count}} meters tall.", "1.7")
+//
+// Plural strings may also have variables.
+// T("{{.Person}} has {{.Count}} unread emails", 2, map[string]interface{}{
+// "Person": "Bob",
+// })
+//
+// Sentences with multiple plural components can be supported with nesting.
+// T("{{.Person}} has {{.Count}} unread emails in the past {{.Timeframe}}.", 3, map[string]interface{}{
+// "Person": "Bob",
+// "Timeframe": T("{{.Count}} days", 2),
+// })
+//
+// Templates
+//
+// You can use the .Funcs() method of a text/template or html/template to register a TranslateFunc
+// for usage inside of that template.
+package i18n
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/bundle"
+ "github.com/nicksnyder/go-i18n/i18n/language"
+ "github.com/nicksnyder/go-i18n/i18n/translation"
+)
+
+// TranslateFunc returns the translation of the string identified by translationID.
+//
+// If there is no translation for translationID, then the translationID itself is returned.
+// This makes it easy to identify missing translations in your app.
+//
+// If translationID is a non-plural form, then the first variadic argument may be a map[string]interface{}
+// or struct that contains template data.
+//
+// If translationID is a plural form, then the first variadic argument must be an integer type
+// (int, int8, int16, int32, int64) or a float formatted as a string (e.g. "123.45").
+// The second variadic argument may be a map[string]interface{} or struct that contains template data.
+type TranslateFunc func(translationID string, args ...interface{}) string
+
+// IdentityTfunc returns a TranslateFunc that always returns the translationID passed to it.
+//
+// It is a useful placeholder when parsing a text/template or html/template
+// before the actual Tfunc is available.
+func IdentityTfunc() TranslateFunc {
+ return func(translationID string, args ...interface{}) string {
+ return translationID
+ }
+}
+
+var defaultBundle = bundle.New()
+
+// MustLoadTranslationFile is similar to LoadTranslationFile
+// except it panics if an error happens.
+func MustLoadTranslationFile(filename string) {
+ defaultBundle.MustLoadTranslationFile(filename)
+}
+
+// LoadTranslationFile loads the translations from filename into memory.
+//
+// The language that the translations are associated with is parsed from the filename (e.g. en-US.json).
+//
+// Generally you should load translation files once during your program's initialization.
+func LoadTranslationFile(filename string) error {
+ return defaultBundle.LoadTranslationFile(filename)
+}
+
+// ParseTranslationFileBytes is similar to LoadTranslationFile except it parses the bytes in buf.
+//
+// It is useful for parsing translation files embedded with go-bindata.
+func ParseTranslationFileBytes(filename string, buf []byte) error {
+ return defaultBundle.ParseTranslationFileBytes(filename, buf)
+}
+
+// AddTranslation adds translations for a language.
+//
+// It is useful if your translations are in a format not supported by LoadTranslationFile.
+func AddTranslation(lang *language.Language, translations ...translation.Translation) {
+ defaultBundle.AddTranslation(lang, translations...)
+}
+
+// LanguageTags returns the tags of all languages that have been added.
+func LanguageTags() []string {
+ return defaultBundle.LanguageTags()
+}
+
+// LanguageTranslationIDs returns the ids of all translations that have been added for a given language.
+func LanguageTranslationIDs(languageTag string) []string {
+ return defaultBundle.LanguageTranslationIDs(languageTag)
+}
+
+// MustTfunc is similar to Tfunc except it panics if an error happens.
+func MustTfunc(languageSource string, languageSources ...string) TranslateFunc {
+ return TranslateFunc(defaultBundle.MustTfunc(languageSource, languageSources...))
+}
+
+// Tfunc returns a TranslateFunc that will be bound to the first language which
+// has a non-zero number of translations.
+//
+// It can parse languages from Accept-Language headers (RFC 2616).
+func Tfunc(languageSource string, languageSources ...string) (TranslateFunc, error) {
+ tfunc, err := defaultBundle.Tfunc(languageSource, languageSources...)
+ return TranslateFunc(tfunc), err
+}
+
+// MustTfuncAndLanguage is similar to TfuncAndLanguage except it panics if an error happens.
+func MustTfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language) {
+ tfunc, lang := defaultBundle.MustTfuncAndLanguage(languageSource, languageSources...)
+ return TranslateFunc(tfunc), lang
+}
+
+// TfuncAndLanguage is similar to Tfunc except it also returns the language which TranslateFunc is bound to.
+func TfuncAndLanguage(languageSource string, languageSources ...string) (TranslateFunc, *language.Language, error) {
+ tfunc, lang, err := defaultBundle.TfuncAndLanguage(languageSource, languageSources...)
+ return TranslateFunc(tfunc), lang, err
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go
new file mode 100644
index 00000000..5d6b6ad4
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/main.go
@@ -0,0 +1,132 @@
+package main
+
+import (
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "text/template"
+)
+
+var usage = `%[1]s generates Go code to support CLDR plural rules.
+
+Usage: %[1]s [options]
+
+Options:
+
+`
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, usage, os.Args[0])
+ flag.PrintDefaults()
+ }
+ var in, cout, tout string
+ flag.StringVar(&in, "i", "plurals.xml", "the input XML file containing CLDR plural rules")
+ flag.StringVar(&cout, "cout", "", "the code output file")
+ flag.StringVar(&tout, "tout", "", "the test output file")
+ flag.BoolVar(&verbose, "v", false, "verbose output")
+ flag.Parse()
+
+ buf, err := ioutil.ReadFile(in)
+ if err != nil {
+ fatalf("failed to read file: %s", err)
+ }
+
+ var data SupplementalData
+ if err := xml.Unmarshal(buf, &data); err != nil {
+ fatalf("failed to unmarshal xml: %s", err)
+ }
+
+ count := 0
+ for _, pg := range data.PluralGroups {
+ count += len(pg.SplitLocales())
+ }
+ infof("parsed %d locales", count)
+
+ if cout != "" {
+ file := openWritableFile(cout)
+ if err := codeTemplate.Execute(file, data); err != nil {
+ fatalf("unable to execute code template because %s", err)
+ } else {
+ infof("generated %s", cout)
+ }
+ } else {
+ infof("not generating code file (use -cout)")
+ }
+
+ if tout != "" {
+ file := openWritableFile(tout)
+ if err := testTemplate.Execute(file, data); err != nil {
+ fatalf("unable to execute test template because %s", err)
+ } else {
+ infof("generated %s", tout)
+ }
+ } else {
+ infof("not generating test file (use -tout)")
+ }
+}
+
+func openWritableFile(name string) *os.File {
+ file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ fatalf("failed to write file %s because %s", name, err)
+ }
+ return file
+}
+
+var codeTemplate = template.Must(template.New("spec").Parse(`package language
+// This file is generated by i18n/language/codegen/generate.sh
+
+func init() {
+{{range .PluralGroups}}
+ registerPluralSpec({{printf "%#v" .SplitLocales}}, &PluralSpec{
+ Plurals: newPluralSet({{range $i, $e := .PluralRules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}),
+ PluralFunc: func(ops *operands) Plural { {{range .PluralRules}}{{if .GoCondition}}
+ // {{.Condition}}
+ if {{.GoCondition}} {
+ return {{.CountTitle}}
+ }{{end}}{{end}}
+ return Other
+ },
+ }){{end}}
+}
+`))
+
+var testTemplate = template.Must(template.New("spec").Parse(`package language
+// This file is generated by i18n/language/codegen/generate.sh
+
+import "testing"
+
+{{range .PluralGroups}}
+func Test{{.Name}}(t *testing.T) {
+ var tests []pluralTest
+ {{range .PluralRules}}
+ {{if .IntegerExamples}}tests = appendIntegerTests(tests, {{.CountTitle}}, {{printf "%#v" .IntegerExamples}}){{end}}
+ {{if .DecimalExamples}}tests = appendDecimalTests(tests, {{.CountTitle}}, {{printf "%#v" .DecimalExamples}}){{end}}
+ {{end}}
+ locales := {{printf "%#v" .SplitLocales}}
+ for _, locale := range locales {
+ runTests(t, locale, tests)
+ }
+}
+{{end}}
+`))
+
+func infof(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, format+"\n", args...)
+}
+
+var verbose bool
+
+func verbosef(format string, args ...interface{}) {
+ if verbose {
+ infof(format, args...)
+ }
+}
+
+func fatalf(format string, args ...interface{}) {
+ infof("fatal: "+format+"\n", args...)
+ os.Exit(1)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go
new file mode 100644
index 00000000..9d39053c
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/codegen/xml.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+ "encoding/xml"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// SupplementalData is the top level struct of plural.xml
+type SupplementalData struct {
+ XMLName xml.Name `xml:"supplementalData"`
+ PluralGroups []PluralGroup `xml:"plurals>pluralRules"`
+}
+
+// PluralGroup is a group of locales with the same plural rules.
+type PluralGroup struct {
+ Locales string `xml:"locales,attr"`
+ PluralRules []PluralRule `xml:"pluralRule"`
+}
+
+// Name returns a unique name for this plural group.
+func (pg *PluralGroup) Name() string {
+ n := strings.Title(pg.Locales)
+ return strings.Replace(n, " ", "", -1)
+}
+
+// SplitLocales returns all the locales in the PluralGroup as a slice.
+func (pg *PluralGroup) SplitLocales() []string {
+ return strings.Split(pg.Locales, " ")
+}
+
+// PluralRule is the rule for a single plural form.
+type PluralRule struct {
+ Count string `xml:"count,attr"`
+ Rule string `xml:",innerxml"`
+}
+
+// CountTitle returns the title case of the PluralRule's count.
+func (pr *PluralRule) CountTitle() string {
+ return strings.Title(pr.Count)
+}
+
+// Condition returns the condition where the PluralRule applies.
+func (pr *PluralRule) Condition() string {
+ i := strings.Index(pr.Rule, "@")
+ return pr.Rule[:i]
+}
+
+// Examples returns the integer and decimal exmaples for the PLuralRule.
+func (pr *PluralRule) Examples() (integer []string, decimal []string) {
+ ex := strings.Replace(pr.Rule, ", …", "", -1)
+ ddelim := "@decimal"
+ if i := strings.Index(ex, ddelim); i > 0 {
+ dex := strings.TrimSpace(ex[i+len(ddelim):])
+ decimal = strings.Split(dex, ", ")
+ ex = ex[:i]
+ }
+ idelim := "@integer"
+ if i := strings.Index(ex, idelim); i > 0 {
+ iex := strings.TrimSpace(ex[i+len(idelim):])
+ integer = strings.Split(iex, ", ")
+ }
+ return integer, decimal
+}
+
+// IntegerExamples returns the integer exmaples for the PLuralRule.
+func (pr *PluralRule) IntegerExamples() []string {
+ integer, _ := pr.Examples()
+ return integer
+}
+
+// DecimalExamples returns the decimal exmaples for the PLuralRule.
+func (pr *PluralRule) DecimalExamples() []string {
+ _, decimal := pr.Examples()
+ return decimal
+}
+
+var relationRegexp = regexp.MustCompile("([niftvw])(?: % ([0-9]+))? (!=|=)(.*)")
+
+// GoCondition converts the XML condition to valid Go code.
+func (pr *PluralRule) GoCondition() string {
+ var ors []string
+ for _, and := range strings.Split(pr.Condition(), "or") {
+ var ands []string
+ for _, relation := range strings.Split(and, "and") {
+ parts := relationRegexp.FindStringSubmatch(relation)
+ if parts == nil {
+ continue
+ }
+ lvar, lmod, op, rhs := strings.Title(parts[1]), parts[2], parts[3], strings.TrimSpace(parts[4])
+ if op == "=" {
+ op = "=="
+ }
+ lvar = "ops." + lvar
+ var rhor []string
+ var rany []string
+ for _, rh := range strings.Split(rhs, ",") {
+ if parts := strings.Split(rh, ".."); len(parts) == 2 {
+ from, to := parts[0], parts[1]
+ if lvar == "ops.N" {
+ if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("ops.NmodInRange(%s, %s, %s)", lmod, from, to))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("ops.NinRange(%s, %s)", from, to))
+ }
+ } else if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("intInRange(%s %% %s, %s, %s)", lvar, lmod, from, to))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("intInRange(%s, %s, %s)", lvar, from, to))
+ }
+ } else {
+ rany = append(rany, rh)
+ }
+ }
+
+ if len(rany) > 0 {
+ rh := strings.Join(rany, ",")
+ if lvar == "ops.N" {
+ if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("ops.NmodEqualsAny(%s, %s)", lmod, rh))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("ops.NequalsAny(%s)", rh))
+ }
+ } else if lmod != "" {
+ rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s %% %s, %s)", lvar, lmod, rh))
+ } else {
+ rhor = append(rhor, fmt.Sprintf("intEqualsAny(%s, %s)", lvar, rh))
+ }
+ }
+ r := strings.Join(rhor, " || ")
+ if len(rhor) > 1 {
+ r = "(" + r + ")"
+ }
+ if op == "!=" {
+ r = "!" + r
+ }
+ ands = append(ands, r)
+ }
+ ors = append(ors, strings.Join(ands, " && "))
+ }
+ return strings.Join(ors, " ||\n")
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go
new file mode 100644
index 00000000..9a155efc
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/language.go
@@ -0,0 +1,99 @@
+// Package language defines languages that implement CLDR pluralization.
+package language
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Language is a written human language.
+type Language struct {
+ // Tag uniquely identifies the language as defined by RFC 5646.
+ //
+ // Most language tags are a two character language code (ISO 639-1)
+ // optionally followed by a dash and a two character country code (ISO 3166-1).
+ // (e.g. en, pt-br)
+ Tag string
+ *PluralSpec
+}
+
+func (l *Language) String() string {
+ return l.Tag
+}
+
+// MatchingTags returns the set of language tags that map to this Language.
+// e.g. "zh-hans-cn" yields {"zh", "zh-hans", "zh-hans-cn"}
+// BUG: This should be computed once and stored as a field on Language for efficiency,
+// but this would require changing how Languages are constructed.
+func (l *Language) MatchingTags() []string {
+ parts := strings.Split(l.Tag, "-")
+ var prefix, matches []string
+ for _, part := range parts {
+ prefix = append(prefix, part)
+ match := strings.Join(prefix, "-")
+ matches = append(matches, match)
+ }
+ return matches
+}
+
+// Parse returns a slice of supported languages found in src or nil if none are found.
+// It can parse language tags and Accept-Language headers.
+func Parse(src string) []*Language {
+ var langs []*Language
+ start := 0
+ for end, chr := range src {
+ switch chr {
+ case ',', ';', '.':
+ tag := strings.TrimSpace(src[start:end])
+ if spec := getPluralSpec(tag); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(tag), spec})
+ }
+ start = end + 1
+ }
+ }
+ if start > 0 {
+ tag := strings.TrimSpace(src[start:])
+ if spec := getPluralSpec(tag); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(tag), spec})
+ }
+ return dedupe(langs)
+ }
+ if spec := getPluralSpec(src); spec != nil {
+ langs = append(langs, &Language{NormalizeTag(src), spec})
+ }
+ return langs
+}
+
+func dedupe(langs []*Language) []*Language {
+ found := make(map[string]struct{}, len(langs))
+ deduped := make([]*Language, 0, len(langs))
+ for _, lang := range langs {
+ if _, ok := found[lang.Tag]; !ok {
+ found[lang.Tag] = struct{}{}
+ deduped = append(deduped, lang)
+ }
+ }
+ return deduped
+}
+
+// MustParse is similar to Parse except it panics instead of retuning a nil Language.
+func MustParse(src string) []*Language {
+ langs := Parse(src)
+ if len(langs) == 0 {
+ panic(fmt.Errorf("unable to parse language from %q", src))
+ }
+ return langs
+}
+
+// Add adds support for a new language.
+func Add(l *Language) {
+ tag := NormalizeTag(l.Tag)
+ pluralSpecs[tag] = l.PluralSpec
+}
+
+// NormalizeTag returns a language tag with all lower-case characters
+// and dashes "-" instead of underscores "_"
+func NormalizeTag(tag string) string {
+ tag = strings.ToLower(tag)
+ return strings.Replace(tag, "_", "-", -1)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go
new file mode 100644
index 00000000..877bcc89
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/operands.go
@@ -0,0 +1,119 @@
+package language
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type operands struct {
+ N float64 // absolute value of the source number (integer and decimals)
+ I int64 // integer digits of n
+ V int64 // number of visible fraction digits in n, with trailing zeros
+ W int64 // number of visible fraction digits in n, without trailing zeros
+ F int64 // visible fractional digits in n, with trailing zeros
+ T int64 // visible fractional digits in n, without trailing zeros
+}
+
+// NmodEqualAny returns true if o represents an integer equal to any of the arguments.
+func (o *operands) NequalsAny(any ...int64) bool {
+ for _, i := range any {
+ if o.I == i && o.T == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// NmodEqualAny returns true if o represents an integer equal to any of the arguments modulo mod.
+func (o *operands) NmodEqualsAny(mod int64, any ...int64) bool {
+ modI := o.I % mod
+ for _, i := range any {
+ if modI == i && o.T == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+// NmodInRange returns true if o represents an integer in the closed interval [from, to].
+func (o *operands) NinRange(from, to int64) bool {
+ return o.T == 0 && from <= o.I && o.I <= to
+}
+
+// NmodInRange returns true if o represents an integer in the closed interval [from, to] modulo mod.
+func (o *operands) NmodInRange(mod, from, to int64) bool {
+ modI := o.I % mod
+ return o.T == 0 && from <= modI && modI <= to
+}
+
+func newOperands(v interface{}) (*operands, error) {
+ switch v := v.(type) {
+ case int:
+ return newOperandsInt64(int64(v)), nil
+ case int8:
+ return newOperandsInt64(int64(v)), nil
+ case int16:
+ return newOperandsInt64(int64(v)), nil
+ case int32:
+ return newOperandsInt64(int64(v)), nil
+ case int64:
+ return newOperandsInt64(v), nil
+ case string:
+ return newOperandsString(v)
+ case float32, float64:
+ return nil, fmt.Errorf("floats should be formatted into a string")
+ default:
+ return nil, fmt.Errorf("invalid type %T; expected integer or string", v)
+ }
+}
+
+func newOperandsInt64(i int64) *operands {
+ if i < 0 {
+ i = -i
+ }
+ return &operands{float64(i), i, 0, 0, 0, 0}
+}
+
+func newOperandsString(s string) (*operands, error) {
+ if s[0] == '-' {
+ s = s[1:]
+ }
+ n, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return nil, err
+ }
+ ops := &operands{N: n}
+ parts := strings.SplitN(s, ".", 2)
+ ops.I, err = strconv.ParseInt(parts[0], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ if len(parts) == 1 {
+ return ops, nil
+ }
+ fraction := parts[1]
+ ops.V = int64(len(fraction))
+ for i := ops.V - 1; i >= 0; i-- {
+ if fraction[i] != '0' {
+ ops.W = i + 1
+ break
+ }
+ }
+ if ops.V > 0 {
+ f, err := strconv.ParseInt(fraction, 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.F = f
+ }
+ if ops.W > 0 {
+ t, err := strconv.ParseInt(fraction[:ops.W], 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.T = t
+ }
+ return ops, nil
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go
new file mode 100644
index 00000000..1f3ea5c6
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/plural.go
@@ -0,0 +1,40 @@
+package language
+
+import (
+ "fmt"
+)
+
+// Plural represents a language pluralization form as defined here:
+// http://cldr.unicode.org/index/cldr-spec/plural-rules
+type Plural string
+
+// All defined plural categories.
+const (
+ Invalid Plural = "invalid"
+ Zero = "zero"
+ One = "one"
+ Two = "two"
+ Few = "few"
+ Many = "many"
+ Other = "other"
+)
+
+// NewPlural returns src as a Plural
+// or Invalid and a non-nil error if src is not a valid Plural.
+func NewPlural(src string) (Plural, error) {
+ switch src {
+ case "zero":
+ return Zero, nil
+ case "one":
+ return One, nil
+ case "two":
+ return Two, nil
+ case "few":
+ return Few, nil
+ case "many":
+ return Many, nil
+ case "other":
+ return Other, nil
+ }
+ return Invalid, fmt.Errorf("invalid plural category %s", src)
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go
new file mode 100644
index 00000000..fc352268
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec.go
@@ -0,0 +1,74 @@
+package language
+
+import "strings"
+
+// PluralSpec defines the CLDR plural rules for a language.
+// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+// http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type PluralSpec struct {
+ Plurals map[Plural]struct{}
+ PluralFunc func(*operands) Plural
+}
+
+var pluralSpecs = make(map[string]*PluralSpec)
+
+func normalizePluralSpecID(id string) string {
+ id = strings.Replace(id, "_", "-", -1)
+ id = strings.ToLower(id)
+ return id
+}
+
+func registerPluralSpec(ids []string, ps *PluralSpec) {
+ for _, id := range ids {
+ id = normalizePluralSpecID(id)
+ pluralSpecs[id] = ps
+ }
+}
+
+// Plural returns the plural category for number as defined by
+// the language's CLDR plural rules.
+func (ps *PluralSpec) Plural(number interface{}) (Plural, error) {
+ ops, err := newOperands(number)
+ if err != nil {
+ return Invalid, err
+ }
+ return ps.PluralFunc(ops), nil
+}
+
+// getPluralSpec returns the PluralSpec that matches the longest prefix of tag.
+// It returns nil if no PluralSpec matches tag.
+func getPluralSpec(tag string) *PluralSpec {
+ tag = NormalizeTag(tag)
+ subtag := tag
+ for {
+ if spec := pluralSpecs[subtag]; spec != nil {
+ return spec
+ }
+ end := strings.LastIndex(subtag, "-")
+ if end == -1 {
+ return nil
+ }
+ subtag = subtag[:end]
+ }
+}
+
+func newPluralSet(plurals ...Plural) map[Plural]struct{} {
+ set := make(map[Plural]struct{}, len(plurals))
+ for _, plural := range plurals {
+ set[plural] = struct{}{}
+ }
+ return set
+}
+
+func intInRange(i, from, to int64) bool {
+ return from <= i && i <= to
+}
+
+func intEqualsAny(i int64, any ...int64) bool {
+ for _, a := range any {
+ if i == a {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go
new file mode 100644
index 00000000..c9b4f266
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/language/pluralspec_gen.go
@@ -0,0 +1,567 @@
+package language
+
+// This file is generated by i18n/language/codegen/generate.sh
+
+func init() {
+
+ registerPluralSpec([]string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "zh"}, &PluralSpec{
+ Plurals: newPluralSet(Other),
+ PluralFunc: func(ops *operands) Plural {
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"am", "as", "bn", "fa", "gu", "hi", "kn", "mr", "zu"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ff", "fr", "hy", "kab"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0,1
+ if intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "it", "ji", "nl", "sv", "sw", "ur", "yi"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"si"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0,1 or i = 0 and f = 1
+ if ops.NequalsAny(0, 1) ||
+ intEqualsAny(ops.I, 0) && intEqualsAny(ops.F, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ak", "bh", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..1
+ if ops.NinRange(0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"tzm"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..1 or n = 11..99
+ if ops.NinRange(0, 1) ||
+ ops.NinRange(11, 99) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0..2 and n != 2
+ if ops.NinRange(0, 2) && !ops.NequalsAny(2) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pt_PT"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1 and v = 0
+ if ops.NequalsAny(1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"da"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1 or t != 0 and i = 0,1
+ if ops.NequalsAny(1) ||
+ !intEqualsAny(ops.T, 0) && intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"is"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0
+ if intEqualsAny(ops.T, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ !intEqualsAny(ops.T, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 or f % 10 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) ||
+ intEqualsAny(ops.F%10, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"fil", "tl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I, 1, 2, 3) ||
+ intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I%10, 4, 6, 9) ||
+ !intEqualsAny(ops.V, 0) && !intEqualsAny(ops.F%10, 4, 6, 9) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lv", "prg"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19
+ if ops.NmodEqualsAny(10, 0) ||
+ ops.NmodInRange(100, 11, 19) ||
+ intEqualsAny(ops.V, 2) && intInRange(ops.F%100, 11, 19) {
+ return Zero
+ }
+ // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11) ||
+ intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) ||
+ !intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lag"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // i = 0,1 and n != 0
+ if intEqualsAny(ops.I, 0, 1) && !ops.NequalsAny(0) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ksh"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"iu", "kw", "naq", "se", "sma", "smi", "smj", "smn", "sms"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"shi"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2..10
+ if ops.NinRange(2, 10) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mo", "ro"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v != 0 or n = 0 or n != 1 and n % 100 = 1..19
+ if !intEqualsAny(ops.V, 0) ||
+ ops.NequalsAny(0) ||
+ !ops.NequalsAny(1) && ops.NmodInRange(100, 1, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"bs", "hr", "sh", "sr"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) ||
+ intInRange(ops.F%10, 2, 4) && !intInRange(ops.F%100, 12, 14) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"gd"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1,11
+ if ops.NequalsAny(1, 11) {
+ return One
+ }
+ // n = 2,12
+ if ops.NequalsAny(2, 12) {
+ return Two
+ }
+ // n = 3..10,13..19
+ if ops.NinRange(3, 10) || ops.NinRange(13, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"sl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or v != 0
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ !intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"dsb", "hsb"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 100 = 1 or f % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) ||
+ intEqualsAny(ops.F%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2 or f % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) ||
+ intEqualsAny(ops.F%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or f % 100 = 3..4
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ intInRange(ops.F%100, 3, 4) {
+ return Few
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"he", "iw"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2 and v = 0
+ if intEqualsAny(ops.I, 2) && intEqualsAny(ops.V, 0) {
+ return Two
+ }
+ // v = 0 and n != 0..10 and n % 10 = 0
+ if intEqualsAny(ops.V, 0) && !ops.NinRange(0, 10) && ops.NmodEqualsAny(10, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"cs", "sk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2..4 and v = 0
+ if intInRange(ops.I, 2, 4) && intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"pl"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14
+ if intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I, 1) && intInRange(ops.I%10, 0, 1) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 12, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"be"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11) {
+ return One
+ }
+ // n % 10 = 2..4 and n % 100 != 12..14
+ if ops.NmodInRange(10, 2, 4) && !ops.NmodInRange(100, 12, 14) {
+ return Few
+ }
+ // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14
+ if ops.NmodEqualsAny(10, 0) ||
+ ops.NmodInRange(10, 5, 9) ||
+ ops.NmodInRange(100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"lt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11..19
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodInRange(100, 11, 19) {
+ return One
+ }
+ // n % 10 = 2..9 and n % 100 != 11..19
+ if ops.NmodInRange(10, 2, 9) && !ops.NmodInRange(100, 11, 19) {
+ return Few
+ }
+ // f != 0
+ if !intEqualsAny(ops.F, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"mt"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 0 or n % 100 = 2..10
+ if ops.NequalsAny(0) ||
+ ops.NmodInRange(100, 2, 10) {
+ return Few
+ }
+ // n % 100 = 11..19
+ if ops.NmodInRange(100, 11, 19) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ru", "uk"}, &PluralSpec{
+ Plurals: newPluralSet(One, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1 and i % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 0) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"br"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n % 10 = 1 and n % 100 != 11,71,91
+ if ops.NmodEqualsAny(10, 1) && !ops.NmodEqualsAny(100, 11, 71, 91) {
+ return One
+ }
+ // n % 10 = 2 and n % 100 != 12,72,92
+ if ops.NmodEqualsAny(10, 2) && !ops.NmodEqualsAny(100, 12, 72, 92) {
+ return Two
+ }
+ // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99
+ if (ops.NmodInRange(10, 3, 4) || ops.NmodEqualsAny(10, 9)) && !(ops.NmodInRange(100, 10, 19) || ops.NmodInRange(100, 70, 79) || ops.NmodInRange(100, 90, 99)) {
+ return Few
+ }
+ // n != 0 and n % 1000000 = 0
+ if !ops.NequalsAny(0) && ops.NmodEqualsAny(1000000, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ga"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n = 3..6
+ if ops.NinRange(3, 6) {
+ return Few
+ }
+ // n = 7..10
+ if ops.NinRange(7, 10) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"gv"}, &PluralSpec{
+ Plurals: newPluralSet(One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // v = 0 and i % 10 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) {
+ return One
+ }
+ // v = 0 and i % 10 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 0,20,40,60,80
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 0, 20, 40, 60, 80) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"ar"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n % 100 = 3..10
+ if ops.NmodInRange(100, 3, 10) {
+ return Few
+ }
+ // n % 100 = 11..99
+ if ops.NmodInRange(100, 11, 99) {
+ return Many
+ }
+ return Other
+ },
+ })
+ registerPluralSpec([]string{"cy"}, &PluralSpec{
+ Plurals: newPluralSet(Zero, One, Two, Few, Many, Other),
+ PluralFunc: func(ops *operands) Plural {
+ // n = 0
+ if ops.NequalsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NequalsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NequalsAny(2) {
+ return Two
+ }
+ // n = 3
+ if ops.NequalsAny(3) {
+ return Few
+ }
+ // n = 6
+ if ops.NequalsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+}
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go
new file mode 100644
index 00000000..4f579d16
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/plural_translation.go
@@ -0,0 +1,78 @@
+package translation
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+type pluralTranslation struct {
+ id string
+ templates map[language.Plural]*template
+}
+
+func (pt *pluralTranslation) MarshalInterface() interface{} {
+ return map[string]interface{}{
+ "id": pt.id,
+ "translation": pt.templates,
+ }
+}
+
+func (pt *pluralTranslation) ID() string {
+ return pt.id
+}
+
+func (pt *pluralTranslation) Template(pc language.Plural) *template {
+ return pt.templates[pc]
+}
+
+func (pt *pluralTranslation) UntranslatedCopy() Translation {
+ return &pluralTranslation{pt.id, make(map[language.Plural]*template)}
+}
+
+func (pt *pluralTranslation) Normalize(l *language.Language) Translation {
+ // Delete plural categories that don't belong to this language.
+ for pc := range pt.templates {
+ if _, ok := l.Plurals[pc]; !ok {
+ delete(pt.templates, pc)
+ }
+ }
+ // Create map entries for missing valid categories.
+ for pc := range l.Plurals {
+ if _, ok := pt.templates[pc]; !ok {
+ pt.templates[pc] = mustNewTemplate("")
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Backfill(src Translation) Translation {
+ for pc, t := range pt.templates {
+ if t == nil || t.src == "" {
+ pt.templates[pc] = src.Template(language.Other)
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Merge(t Translation) Translation {
+ other, ok := t.(*pluralTranslation)
+ if !ok || pt.ID() != t.ID() {
+ return t
+ }
+ for pluralCategory, template := range other.templates {
+ if template != nil && template.src != "" {
+ pt.templates[pluralCategory] = template
+ }
+ }
+ return pt
+}
+
+func (pt *pluralTranslation) Incomplete(l *language.Language) bool {
+ for pc := range l.Plurals {
+ if t := pt.templates[pc]; t == nil || t.src == "" {
+ return true
+ }
+ }
+ return false
+}
+
+var _ = Translation(&pluralTranslation{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go
new file mode 100644
index 00000000..1010e594
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/single_translation.go
@@ -0,0 +1,57 @@
+package translation
+
+import (
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+type singleTranslation struct {
+ id string
+ template *template
+}
+
+func (st *singleTranslation) MarshalInterface() interface{} {
+ return map[string]interface{}{
+ "id": st.id,
+ "translation": st.template,
+ }
+}
+
+func (st *singleTranslation) ID() string {
+ return st.id
+}
+
+func (st *singleTranslation) Template(pc language.Plural) *template {
+ return st.template
+}
+
+func (st *singleTranslation) UntranslatedCopy() Translation {
+ return &singleTranslation{st.id, mustNewTemplate("")}
+}
+
+func (st *singleTranslation) Normalize(language *language.Language) Translation {
+ return st
+}
+
+func (st *singleTranslation) Backfill(src Translation) Translation {
+ if st.template == nil || st.template.src == "" {
+ st.template = src.Template(language.Other)
+ }
+ return st
+}
+
+func (st *singleTranslation) Merge(t Translation) Translation {
+ other, ok := t.(*singleTranslation)
+ if !ok || st.ID() != t.ID() {
+ return t
+ }
+ if other.template != nil && other.template.src != "" {
+ st.template = other.template
+ }
+ return st
+}
+
+func (st *singleTranslation) Incomplete(l *language.Language) bool {
+ return st.template == nil || st.template.src == ""
+}
+
+var _ = Translation(&singleTranslation{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go
new file mode 100644
index 00000000..c8756fa4
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/template.go
@@ -0,0 +1,61 @@
+package translation
+
+import (
+ "bytes"
+ "encoding"
+ "strings"
+ gotemplate "text/template"
+)
+
+type template struct {
+ tmpl *gotemplate.Template
+ src string
+}
+
+func newTemplate(src string) (*template, error) {
+ var tmpl template
+ err := tmpl.parseTemplate(src)
+ return &tmpl, err
+}
+
+func mustNewTemplate(src string) *template {
+ t, err := newTemplate(src)
+ if err != nil {
+ panic(err)
+ }
+ return t
+}
+
+func (t *template) String() string {
+ return t.src
+}
+
+func (t *template) Execute(args interface{}) string {
+ if t.tmpl == nil {
+ return t.src
+ }
+ var buf bytes.Buffer
+ if err := t.tmpl.Execute(&buf, args); err != nil {
+ return err.Error()
+ }
+ return buf.String()
+}
+
+func (t *template) MarshalText() ([]byte, error) {
+ return []byte(t.src), nil
+}
+
+func (t *template) UnmarshalText(src []byte) error {
+ return t.parseTemplate(string(src))
+}
+
+func (t *template) parseTemplate(src string) (err error) {
+ t.src = src
+ if strings.Contains(src, "{{") {
+ t.tmpl, err = gotemplate.New(src).Parse(src)
+ }
+ return
+}
+
+var _ = encoding.TextMarshaler(&template{})
+var _ = encoding.TextUnmarshaler(&template{})
diff --git a/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go
new file mode 100644
index 00000000..fa93180b
--- /dev/null
+++ b/vendor/github.com/nicksnyder/go-i18n/i18n/translation/translation.go
@@ -0,0 +1,83 @@
+// Package translation defines the interface for a translation.
+package translation
+
+import (
+ "fmt"
+
+ "github.com/nicksnyder/go-i18n/i18n/language"
+)
+
+// Translation is the interface that represents a translated string.
+type Translation interface {
+ // MarshalInterface returns the object that should be used
+ // to serialize the translation.
+ MarshalInterface() interface{}
+ ID() string
+ Template(language.Plural) *template
+ UntranslatedCopy() Translation
+ Normalize(language *language.Language) Translation
+ Backfill(src Translation) Translation
+ Merge(Translation) Translation
+ Incomplete(l *language.Language) bool
+}
+
+// SortableByID implements sort.Interface for a slice of translations.
+type SortableByID []Translation
+
+func (a SortableByID) Len() int { return len(a) }
+func (a SortableByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a SortableByID) Less(i, j int) bool { return a[i].ID() < a[j].ID() }
+
+// NewTranslation reflects on data to create a new Translation.
+//
+// data["id"] must be a string and data["translation"] must be either a string
+// for a non-plural translation or a map[string]interface{} for a plural translation.
+func NewTranslation(data map[string]interface{}) (Translation, error) {
+ id, ok := data["id"].(string)
+ if !ok {
+ return nil, fmt.Errorf(`missing "id" key`)
+ }
+ var pluralObject map[string]interface{}
+ switch translation := data["translation"].(type) {
+ case string:
+ tmpl, err := newTemplate(translation)
+ if err != nil {
+ return nil, err
+ }
+ return &singleTranslation{id, tmpl}, nil
+ case map[interface{}]interface{}:
+ // The YAML parser uses interface{} keys so we first convert them to string keys.
+ pluralObject = make(map[string]interface{})
+ for k, v := range translation {
+ kStr, ok := k.(string)
+ if !ok {
+ return nil, fmt.Errorf(`invalid plural category type %T; expected string`, k)
+ }
+ pluralObject[kStr] = v
+ }
+ case map[string]interface{}:
+ pluralObject = translation
+ case nil:
+ return nil, fmt.Errorf(`missing "translation" key`)
+ default:
+ return nil, fmt.Errorf(`unsupported type for "translation" key %T`, translation)
+ }
+
+ templates := make(map[language.Plural]*template, len(pluralObject))
+ for k, v := range pluralObject {
+ pc, err := language.NewPlural(k)
+ if err != nil {
+ return nil, err
+ }
+ str, ok := v.(string)
+ if !ok {
+ return nil, fmt.Errorf(`plural category "%s" has value of type %T; expected string`, pc, v)
+ }
+ tmpl, err := newTemplate(str)
+ if err != nil {
+ return nil, err
+ }
+ templates[pc] = tmpl
+ }
+ return &pluralTranslation{id, templates}, nil
+}