diff options
Diffstat (limited to 'vendor/golang.org/x/text/message/catalog')
-rw-r--r-- | vendor/golang.org/x/text/message/catalog/catalog.go | 161 | ||||
-rw-r--r-- | vendor/golang.org/x/text/message/catalog/catalog_test.go | 148 | ||||
-rw-r--r-- | vendor/golang.org/x/text/message/catalog/dict.go | 49 | ||||
-rw-r--r-- | vendor/golang.org/x/text/message/catalog/go19.go | 15 | ||||
-rw-r--r-- | vendor/golang.org/x/text/message/catalog/gopre19.go | 23 |
5 files changed, 326 insertions, 70 deletions
diff --git a/vendor/golang.org/x/text/message/catalog/catalog.go b/vendor/golang.org/x/text/message/catalog/catalog.go index 957444c10..34a30d3c8 100644 --- a/vendor/golang.org/x/text/message/catalog/catalog.go +++ b/vendor/golang.org/x/text/message/catalog/catalog.go @@ -156,23 +156,127 @@ import ( "errors" "fmt" + "golang.org/x/text/internal" + "golang.org/x/text/internal/catmsg" "golang.org/x/text/language" ) -// A Catalog holds translations for messages for supported languages. -type Catalog struct { +// A Catalog allows lookup of translated messages. +type Catalog interface { + // Languages returns all languages for which the Catalog contains variants. + Languages() []language.Tag + + // Matcher returns a Matcher for languages from this Catalog. + Matcher() language.Matcher + + // A Context is used for evaluating Messages. + Context(tag language.Tag, r catmsg.Renderer) *Context + + // This method also makes Catalog a private interface. + lookup(tag language.Tag, key string) (data string, ok bool) +} + +// NewFromMap creates a Catalog from the given map. If a Dictionary is +// underspecified the entry is retrieved from a parent language. +func NewFromMap(dictionaries map[string]Dictionary, opts ...Option) (Catalog, error) { + options := options{} + for _, o := range opts { + o(&options) + } + c := &catalog{ + dicts: map[language.Tag]Dictionary{}, + } + _, hasFallback := dictionaries[options.fallback.String()] + if hasFallback { + // TODO: Should it be okay to not have a fallback language? + // Catalog generators could enforce there is always a fallback. + c.langs = append(c.langs, options.fallback) + } + for lang, dict := range dictionaries { + tag, err := language.Parse(lang) + if err != nil { + return nil, fmt.Errorf("catalog: invalid language tag %q", lang) + } + if _, ok := c.dicts[tag]; ok { + return nil, fmt.Errorf("catalog: duplicate entry for tag %q after normalization", tag) + } + c.dicts[tag] = dict + if !hasFallback || tag != options.fallback { + c.langs = append(c.langs, tag) + } + } + if hasFallback { + internal.SortTags(c.langs[1:]) + } else { + internal.SortTags(c.langs) + } + c.matcher = language.NewMatcher(c.langs) + return c, nil +} + +// A Dictionary is a source of translations for a single language. +type Dictionary interface { + // Lookup returns a message compiled with catmsg.Compile for the given key. + // It returns false for ok if such a message could not be found. + Lookup(key string) (data string, ok bool) +} + +type catalog struct { + langs []language.Tag + dicts map[language.Tag]Dictionary + macros store + matcher language.Matcher +} + +func (c *catalog) Languages() []language.Tag { return c.langs } +func (c *catalog) Matcher() language.Matcher { return c.matcher } + +func (c *catalog) lookup(tag language.Tag, key string) (data string, ok bool) { + for ; ; tag = tag.Parent() { + if dict, ok := c.dicts[tag]; ok { + if data, ok := dict.Lookup(key); ok { + return data, true + } + } + if tag == language.Und { + break + } + } + return "", false +} + +// Context returns a Context for formatting messages. +// Only one Message may be formatted per context at any given time. +func (c *catalog) Context(tag language.Tag, r catmsg.Renderer) *Context { + return &Context{ + cat: c, + tag: tag, + dec: catmsg.NewDecoder(tag, r, &dict{&c.macros, tag}), + } +} + +// A Builder allows building a Catalog programmatically. +type Builder struct { options + matcher language.Matcher index store macros store } -type options struct{} +type options struct { + fallback language.Tag +} // An Option configures Catalog behavior. type Option func(*options) +// Fallback specifies the default fallback language. The default is Und. +func Fallback(tag language.Tag) Option { + return func(o *options) { o.fallback = tag } +} + // TODO: // // Catalogs specifies one or more sources for a Catalog. // // Lookups are in order. @@ -186,22 +290,17 @@ type Option func(*options) // // func Dict(tag language.Tag, d ...Dictionary) Option -// New returns a new Catalog. -func New(opts ...Option) *Catalog { - c := &Catalog{} +// NewBuilder returns an empty mutable Catalog. +func NewBuilder(opts ...Option) *Builder { + c := &Builder{} for _, o := range opts { o(&c.options) } return c } -// Languages returns all languages for which the Catalog contains variants. -func (c *Catalog) Languages() []language.Tag { - return c.index.languages() -} - // SetString is shorthand for Set(tag, key, String(msg)). -func (c *Catalog) SetString(tag language.Tag, key string, msg string) error { +func (c *Builder) SetString(tag language.Tag, key string, msg string) error { return c.set(tag, key, &c.index, String(msg)) } @@ -209,26 +308,20 @@ func (c *Catalog) SetString(tag language.Tag, key string, msg string) error { // // When evaluation this message, the first Message in the sequence to msgs to // evaluate to a string will be the message returned. -func (c *Catalog) Set(tag language.Tag, key string, msg ...Message) error { +func (c *Builder) Set(tag language.Tag, key string, msg ...Message) error { return c.set(tag, key, &c.index, msg...) } // SetMacro defines a Message that may be substituted in another message. // The arguments to a macro Message are passed as arguments in the // placeholder the form "${foo(arg1, arg2)}". -func (c *Catalog) SetMacro(tag language.Tag, name string, msg ...Message) error { +func (c *Builder) SetMacro(tag language.Tag, name string, msg ...Message) error { return c.set(tag, name, &c.macros, msg...) } // ErrNotFound indicates there was no message for the given key. var ErrNotFound = errors.New("catalog: message not found") -// A Message holds a collection of translations for the same phrase that may -// vary based on the values of substitution arguments. -type Message interface { - catmsg.Message -} - // String specifies a plain message string. It can be used as fallback if no // other strings match or as a simple standalone message. // @@ -247,44 +340,28 @@ func Var(name string, msg ...Message) Message { return &catmsg.Var{Name: name, Message: firstInSequence(msg)} } -// firstInSequence is a message type that prints the first message in the -// sequence that resolves to a match for the given substitution arguments. -type firstInSequence []Message - -func (s firstInSequence) Compile(e *catmsg.Encoder) error { - e.EncodeMessageType(catmsg.First) - err := catmsg.ErrIncomplete - for i, m := range s { - if err == nil { - return fmt.Errorf("catalog: message argument %d is complete and blocks subsequent messages", i-1) - } - err = e.EncodeMessage(m) - } - return err -} - // Context returns a Context for formatting messages. // Only one Message may be formatted per context at any given time. -func (c *Catalog) Context(tag language.Tag, r catmsg.Renderer) *Context { +func (b *Builder) Context(tag language.Tag, r catmsg.Renderer) *Context { return &Context{ - cat: c, + cat: b, tag: tag, - dec: catmsg.NewDecoder(tag, r, &dict{&c.macros, tag}), + dec: catmsg.NewDecoder(tag, r, &dict{&b.macros, tag}), } } // A Context is used for evaluating Messages. // Only one Message may be formatted per context at any given time. type Context struct { - cat *Catalog - tag language.Tag + cat Catalog + tag language.Tag // TODO: use compact index. dec *catmsg.Decoder } // Execute looks up and executes the message with the given key. // It returns ErrNotFound if no message could be found in the index. func (c *Context) Execute(key string) error { - data, ok := c.cat.index.lookup(c.tag, key) + data, ok := c.cat.lookup(c.tag, key) if !ok { return ErrNotFound } diff --git a/vendor/golang.org/x/text/message/catalog/catalog_test.go b/vendor/golang.org/x/text/message/catalog/catalog_test.go index 97ab4d88a..08bfdc7ce 100644 --- a/vendor/golang.org/x/text/message/catalog/catalog_test.go +++ b/vendor/golang.org/x/text/message/catalog/catalog_test.go @@ -6,11 +6,11 @@ package catalog import ( "bytes" - "fmt" + "path" "reflect" + "strings" "testing" - "golang.org/x/text/internal" "golang.org/x/text/internal/catmsg" "golang.org/x/text/language" ) @@ -20,17 +20,33 @@ type entry struct { msg interface{} } -var testCases = []struct { - desc string - cat []entry - lookup []entry -}{{ +func langs(s string) []language.Tag { + t, _, _ := language.ParseAcceptLanguage(s) + return t +} + +type testCase struct { + desc string + cat []entry + lookup []entry + fallback string + match []string + tags []language.Tag +} + +var testCases = []testCase{{ desc: "empty catalog", lookup: []entry{ {"en", "key", ""}, {"en", "", ""}, {"nl", "", ""}, }, + match: []string{ + "gr -> und", + "en-US -> und", + "af -> und", + }, + tags: nil, // not an empty list. }, { desc: "one entry", cat: []entry{ @@ -45,6 +61,11 @@ var testCases = []struct { {"en-oxendict", "hello", "Hello!"}, {"en-oxendict-u-ms-metric", "hello", "Hello!"}, }, + match: []string{ + "gr -> en", + "en-US -> en", + }, + tags: langs("en"), }, { desc: "hierarchical languages", cat: []entry{ @@ -52,6 +73,7 @@ var testCases = []struct { {"en-GB", "hello", "Hellø!"}, {"en-US", "hello", "Howdy!"}, {"en", "greetings", "Greetings!"}, + {"gsw", "hello", "Grüetzi!"}, }, lookup: []entry{ {"und", "hello", ""}, @@ -70,6 +92,12 @@ var testCases = []struct { {"en-oxendict", "greetings", "Greetings!"}, {"en-US-oxendict-u-ms-metric", "greetings", "Greetings!"}, }, + fallback: "gsw", + match: []string{ + "gr -> gsw", + "en-US -> en-US", + }, + tags: langs("gsw, en, en-GB, en-US"), }, { desc: "variables", cat: []entry{ @@ -103,6 +131,7 @@ var testCases = []struct { {"en", "scopes", "Hello Joe and Jane."}, {"en", "missing var", "Hello missing."}, }, + tags: langs("en"), }, { desc: "macros", cat: []entry{ @@ -122,16 +151,29 @@ var testCases = []struct { {"en", "badnum", "Hello $!(BADNUM)."}, {"en", "undefined", "Hello undefined."}, {"en", "macroU", "Hello macroU!"}, - }}} + }, + tags: langs("en"), +}} + +func setMacros(b *Builder) { + b.SetMacro(language.English, "macro1", String("Joe")) + b.SetMacro(language.Und, "macro2", String("${macro1(1)}")) + b.SetMacro(language.English, "macroU", noMatchMessage{}) +} + +type buildFunc func(t *testing.T, tc testCase) Catalog -func initCat(entries []entry) (*Catalog, []language.Tag) { - tags := []language.Tag{} - cat := New() - for _, e := range entries { +func initBuilder(t *testing.T, tc testCase) Catalog { + options := []Option{} + if tc.fallback != "" { + options = append(options, Fallback(language.MustParse(tc.fallback))) + } + cat := NewBuilder(options...) + for _, e := range tc.cat { tag := language.MustParse(e.tag) - tags = append(tags, tag) switch msg := e.msg.(type) { case string: + cat.SetString(tag, e.key, msg) case Message: cat.Set(tag, e.key, msg) @@ -139,23 +181,81 @@ func initCat(entries []entry) (*Catalog, []language.Tag) { cat.Set(tag, e.key, msg...) } } - return cat, internal.UniqueTags(tags) + setMacros(cat) + return cat } -func TestCatalog(t *testing.T) { - for _, tc := range testCases { - t.Run(fmt.Sprintf("%s", tc.desc), func(t *testing.T) { - cat, wantTags := initCat(tc.cat) - cat.SetMacro(language.English, "macro1", String("Joe")) - cat.SetMacro(language.Und, "macro2", String("${macro1(1)}")) - cat.SetMacro(language.English, "macroU", noMatchMessage{}) +type dictionary map[string]string +func (d dictionary) Lookup(key string) (data string, ok bool) { + data, ok = d[key] + return data, ok +} + +func initCatalog(t *testing.T, tc testCase) Catalog { + m := map[string]Dictionary{} + for _, e := range tc.cat { + m[e.tag] = dictionary{} + } + for _, e := range tc.cat { + var msg Message + switch x := e.msg.(type) { + case string: + msg = String(x) + case Message: + msg = x + case []Message: + msg = firstInSequence(x) + } + data, _ := catmsg.Compile(language.MustParse(e.tag), nil, msg) + m[e.tag].(dictionary)[e.key] = data + } + options := []Option{} + if tc.fallback != "" { + options = append(options, Fallback(language.MustParse(tc.fallback))) + } + c, err := NewFromMap(m, options...) + if err != nil { + t.Fatal(err) + } + // TODO: implement macros for fixed catalogs. + b := NewBuilder() + setMacros(b) + c.(*catalog).macros.index = b.macros.index + return c +} + +func TestMatcher(t *testing.T) { + test := func(t *testing.T, init buildFunc) { + for _, tc := range testCases { + for _, s := range tc.match { + a := strings.Split(s, "->") + t.Run(path.Join(tc.desc, a[0]), func(t *testing.T) { + cat := init(t, tc) + got, _ := language.MatchStrings(cat.Matcher(), a[0]) + want := language.MustParse(strings.TrimSpace(a[1])) + if got != want { + t.Errorf("got %q; want %q", got, want) + } + }) + } + } + } + t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) + t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) +} + +func TestCatalog(t *testing.T) { + test := func(t *testing.T, init buildFunc) { + for _, tc := range testCases { + cat := init(t, tc) + wantTags := tc.tags if got := cat.Languages(); !reflect.DeepEqual(got, wantTags) { t.Errorf("%s:Languages: got %v; want %v", tc.desc, got, wantTags) } for _, e := range tc.lookup { - t.Run(fmt.Sprintf("%s/%s", e.tag, e.key), func(t *testing.T) { + t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) { tag := language.MustParse(e.tag) buf := testRenderer{} ctx := cat.Context(tag, &buf) @@ -171,8 +271,10 @@ func TestCatalog(t *testing.T) { } }) } - }) + } } + t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) + t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) } type testRenderer struct { diff --git a/vendor/golang.org/x/text/message/catalog/dict.go b/vendor/golang.org/x/text/message/catalog/dict.go index 1810fabc6..a0eb81810 100644 --- a/vendor/golang.org/x/text/message/catalog/dict.go +++ b/vendor/golang.org/x/text/message/catalog/dict.go @@ -33,7 +33,11 @@ func (d *dict) Lookup(key string) (data string, ok bool) { return d.s.lookup(d.tag, key) } -func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) error { +func (b *Builder) lookup(tag language.Tag, key string) (data string, ok bool) { + return b.index.lookup(tag, key) +} + +func (c *Builder) set(tag language.Tag, key string, s *store, msg ...Message) error { data, err := catmsg.Compile(tag, &dict{&c.macros, tag}, firstInSequence(msg)) s.mutex.Lock() @@ -45,6 +49,7 @@ func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) er if s.index == nil { s.index = map[language.Tag]msgMap{} } + c.matcher = nil s.index[tag] = m } @@ -52,6 +57,23 @@ func (c *Catalog) set(tag language.Tag, key string, s *store, msg ...Message) er return err } +func (c *Builder) Matcher() language.Matcher { + c.index.mutex.RLock() + m := c.matcher + c.index.mutex.RUnlock() + if m != nil { + return m + } + + c.index.mutex.Lock() + if c.matcher == nil { + c.matcher = language.NewMatcher(c.unlockedLanguages()) + } + m = c.matcher + c.index.mutex.Unlock() + return m +} + type store struct { mutex sync.RWMutex index map[language.Tag]msgMap @@ -76,15 +98,32 @@ func (s *store) lookup(tag language.Tag, key string) (data string, ok bool) { return "", false } -// Languages returns all languages for which the store contains variants. -func (s *store) languages() []language.Tag { +// Languages returns all languages for which the Catalog contains variants. +func (b *Builder) Languages() []language.Tag { + s := &b.index s.mutex.RLock() defer s.mutex.RUnlock() + return b.unlockedLanguages() +} + +func (b *Builder) unlockedLanguages() []language.Tag { + s := &b.index + if len(s.index) == 0 { + return nil + } tags := make([]language.Tag, 0, len(s.index)) + _, hasFallback := s.index[b.options.fallback] + offset := 0 + if hasFallback { + tags = append(tags, b.options.fallback) + offset = 1 + } for t := range s.index { - tags = append(tags, t) + if t != b.options.fallback { + tags = append(tags, t) + } } - internal.SortTags(tags) + internal.SortTags(tags[offset:]) return tags } diff --git a/vendor/golang.org/x/text/message/catalog/go19.go b/vendor/golang.org/x/text/message/catalog/go19.go new file mode 100644 index 000000000..147fc7cf5 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/go19.go @@ -0,0 +1,15 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +package catalog + +import "golang.org/x/text/internal/catmsg" + +// A Message holds a collection of translations for the same phrase that may +// vary based on the values of substitution arguments. +type Message = catmsg.Message + +type firstInSequence = catmsg.FirstOf diff --git a/vendor/golang.org/x/text/message/catalog/gopre19.go b/vendor/golang.org/x/text/message/catalog/gopre19.go new file mode 100644 index 000000000..a9753b905 --- /dev/null +++ b/vendor/golang.org/x/text/message/catalog/gopre19.go @@ -0,0 +1,23 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +package catalog + +import "golang.org/x/text/internal/catmsg" + +// A Message holds a collection of translations for the same phrase that may +// vary based on the values of substitution arguments. +type Message interface { + catmsg.Message +} + +func firstInSequence(m []Message) catmsg.Message { + a := []catmsg.Message{} + for _, m := range m { + a = append(a, m) + } + return catmsg.FirstOf(a) +} |