summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/text/message/catalog
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/text/message/catalog')
-rw-r--r--vendor/golang.org/x/text/message/catalog/catalog.go369
-rw-r--r--vendor/golang.org/x/text/message/catalog/catalog_test.go296
-rw-r--r--vendor/golang.org/x/text/message/catalog/dict.go129
-rw-r--r--vendor/golang.org/x/text/message/catalog/go19.go15
-rw-r--r--vendor/golang.org/x/text/message/catalog/gopre19.go23
5 files changed, 832 insertions, 0 deletions
diff --git a/vendor/golang.org/x/text/message/catalog/catalog.go b/vendor/golang.org/x/text/message/catalog/catalog.go
new file mode 100644
index 0000000..34a30d3
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog/catalog.go
@@ -0,0 +1,369 @@
+// 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.
+
+// Package catalog defines collections of translated format strings.
+//
+// This package mostly defines types for populating catalogs with messages. The
+// catmsg package contains further definitions for creating custom message and
+// dictionary types as well as packages that use Catalogs.
+//
+// Package catalog defines various interfaces: Dictionary, Loader, and Message.
+// A Dictionary maintains a set of translations of format strings for a single
+// language. The Loader interface defines a source of dictionaries. A
+// translation of a format string is represented by a Message.
+//
+//
+// Catalogs
+//
+// A Catalog defines a programmatic interface for setting message translations.
+// It maintains a set of per-language dictionaries with translations for a set
+// of keys. For message translation to function properly, a translation should
+// be defined for each key for each supported language. A dictionary may be
+// underspecified, though, if there is a parent language that already defines
+// the key. For example, a Dictionary for "en-GB" could leave out entries that
+// are identical to those in a dictionary for "en".
+//
+//
+// Messages
+//
+// A Message is a format string which varies on the value of substitution
+// variables. For instance, to indicate the number of results one could want "no
+// results" if there are none, "1 result" if there is 1, and "%d results" for
+// any other number. Catalog is agnostic to the kind of format strings that are
+// used: for instance, messages can follow either the printf-style substitution
+// from package fmt or use templates.
+//
+// A Message does not substitute arguments in the format string. This job is
+// reserved for packages that render strings, such as message, that use Catalogs
+// to selected string. This separation of concerns allows Catalog to be used to
+// store any kind of formatting strings.
+//
+//
+// Selecting messages based on linguistic features of substitution arguments
+//
+// Messages may vary based on any linguistic features of the argument values.
+// The most common one is plural form, but others exist.
+//
+// Selection messages are provided in packages that provide support for a
+// specific linguistic feature. The following snippet uses plural.Select:
+//
+// catalog.Set(language.English, "You are %d minute(s) late.",
+// plural.Select(1,
+// "one", "You are 1 minute late.",
+// "other", "You are %d minutes late."))
+//
+// In this example, a message is stored in the Catalog where one of two messages
+// is selected based on the first argument, a number. The first message is
+// selected if the argument is singular (identified by the selector "one") and
+// the second message is selected in all other cases. The selectors are defined
+// by the plural rules defined in CLDR. The selector "other" is special and will
+// always match. Each language always defines one of the linguistic categories
+// to be "other." For English, singular is "one" and plural is "other".
+//
+// Selects can be nested. This allows selecting sentences based on features of
+// multiple arguments or multiple linguistic properties of a single argument.
+//
+//
+// String interpolation
+//
+// There is often a lot of commonality between the possible variants of a
+// message. For instance, in the example above the word "minute" varies based on
+// the plural catogory of the argument, but the rest of the sentence is
+// identical. Using interpolation the above message can be rewritten as:
+//
+// catalog.Set(language.English, "You are %d minute(s) late.",
+// catalog.Var("minutes",
+// plural.Select(1, "one", "minute", "other", "minutes")),
+// catalog.String("You are %[1]d ${minutes} late."))
+//
+// Var is defined to return the variable name if the message does not yield a
+// match. This allows us to further simplify this snippet to
+//
+// catalog.Set(language.English, "You are %d minute(s) late.",
+// catalog.Var("minutes", plural.Select(1, "one", "minute")),
+// catalog.String("You are %d ${minutes} late."))
+//
+// Overall this is still only a minor improvement, but things can get a lot more
+// unwieldy if more than one linguistic feature is used to determine a message
+// variant. Consider the following example:
+//
+// // argument 1: list of hosts, argument 2: list of guests
+// catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.",
+// catalog.Var("their",
+// plural.Select(1,
+// "one", gender.Select(1, "female", "her", "other", "his"))),
+// catalog.Var("invites", plural.Select(1, "one", "invite"))
+// catalog.String("%[1]v ${invites} %[2]v to ${their} party.")),
+//
+// Without variable substitution, this would have to be written as
+//
+// // argument 1: list of hosts, argument 2: list of guests
+// catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.",
+// plural.Select(1,
+// "one", gender.Select(1,
+// "female", "%[1]v invites %[2]v to her party."
+// "other", "%[1]v invites %[2]v to his party."),
+// "other", "%[1]v invites %[2]v to their party.")
+//
+// Not necessarily shorter, but using variables there is less duplication and
+// the messages are more maintenance friendly. Moreover, languages may have up
+// to six plural forms. This makes the use of variables more welcome.
+//
+// Different messages using the same inflections can reuse variables by moving
+// them to macros. Using macros we can rewrite the message as:
+//
+// // argument 1: list of hosts, argument 2: list of guests
+// catalog.SetString(language.English, "%[1]v invite(s) %[2]v to their party.",
+// "%[1]v ${invites(1)} %[2]v to ${their(1)} party.")
+//
+// Where the following macros were defined separately.
+//
+// catalog.SetMacro(language.English, "invites", plural.Select(1, "one", "invite"))
+// catalog.SetMacro(language.English, "their", plural.Select(1,
+// "one", gender.Select(1, "female", "her", "other", "his"))),
+//
+// Placeholders use parentheses and the arguments to invoke a macro.
+//
+//
+// Looking up messages
+//
+// Message lookup using Catalogs is typically only done by specialized packages
+// and is not something the user should be concerned with. For instance, to
+// express the tardiness of a user using the related message we defined earlier,
+// the user may use the package message like so:
+//
+// p := message.NewPrinter(language.English)
+// p.Printf("You are %d minute(s) late.", 5)
+//
+// Which would print:
+// You are 5 minutes late.
+//
+//
+// This package is UNDER CONSTRUCTION and its API may change.
+package catalog // import "golang.org/x/text/message/catalog"
+
+// TODO:
+// Some way to freeze a catalog.
+// - Locking on each lockup turns out to be about 50% of the total running time
+// for some of the benchmarks in the message package.
+// Consider these:
+// - Sequence type to support sequences in user-defined messages.
+// - Garbage collection: Remove dictionaries that can no longer be reached
+// as other dictionaries have been added that cover all possible keys.
+
+import (
+ "errors"
+ "fmt"
+
+ "golang.org/x/text/internal"
+
+ "golang.org/x/text/internal/catmsg"
+ "golang.org/x/text/language"
+)
+
+// 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 {
+ 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.
+// // This can be changed inserting a Catalog used for setting, which implements
+// // Loader, used for setting in the chain.
+// func Catalogs(d ...Loader) Option {
+// return nil
+// }
+//
+// func Delims(start, end string) Option {}
+//
+// func Dict(tag language.Tag, d ...Dictionary) Option
+
+// NewBuilder returns an empty mutable Catalog.
+func NewBuilder(opts ...Option) *Builder {
+ c := &Builder{}
+ for _, o := range opts {
+ o(&c.options)
+ }
+ return c
+}
+
+// SetString is shorthand for Set(tag, key, String(msg)).
+func (c *Builder) SetString(tag language.Tag, key string, msg string) error {
+ return c.set(tag, key, &c.index, String(msg))
+}
+
+// Set sets the translation for the given language and key.
+//
+// When evaluation this message, the first Message in the sequence to msgs to
+// evaluate to a string will be the message returned.
+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 *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")
+
+// String specifies a plain message string. It can be used as fallback if no
+// other strings match or as a simple standalone message.
+//
+// It is an error to pass more than one String in a message sequence.
+func String(name string) Message {
+ return catmsg.String(name)
+}
+
+// Var sets a variable that may be substituted in formatting patterns using
+// named substitution of the form "${name}". The name argument is used as a
+// fallback if the statements do not produce a match. The statement sequence may
+// not contain any Var calls.
+//
+// The name passed to a Var must be unique within message sequence.
+func Var(name string, msg ...Message) Message {
+ return &catmsg.Var{Name: name, Message: firstInSequence(msg)}
+}
+
+// Context returns a Context for formatting messages.
+// Only one Message may be formatted per context at any given time.
+func (b *Builder) Context(tag language.Tag, r catmsg.Renderer) *Context {
+ return &Context{
+ cat: b,
+ tag: 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 // 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.lookup(c.tag, key)
+ if !ok {
+ return ErrNotFound
+ }
+ return c.dec.Execute(data)
+}
diff --git a/vendor/golang.org/x/text/message/catalog/catalog_test.go b/vendor/golang.org/x/text/message/catalog/catalog_test.go
new file mode 100644
index 0000000..08bfdc7
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog/catalog_test.go
@@ -0,0 +1,296 @@
+// 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.
+
+package catalog
+
+import (
+ "bytes"
+ "path"
+ "reflect"
+ "strings"
+ "testing"
+
+ "golang.org/x/text/internal/catmsg"
+ "golang.org/x/text/language"
+)
+
+type entry struct {
+ tag, key string
+ msg interface{}
+}
+
+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{
+ {"en", "hello", "Hello!"},
+ },
+ lookup: []entry{
+ {"und", "hello", ""},
+ {"nl", "hello", ""},
+ {"en", "hello", "Hello!"},
+ {"en-US", "hello", "Hello!"},
+ {"en-GB", "hello", "Hello!"},
+ {"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{
+ {"en", "hello", "Hello!"},
+ {"en-GB", "hello", "Hellø!"},
+ {"en-US", "hello", "Howdy!"},
+ {"en", "greetings", "Greetings!"},
+ {"gsw", "hello", "Grüetzi!"},
+ },
+ lookup: []entry{
+ {"und", "hello", ""},
+ {"nl", "hello", ""},
+ {"en", "hello", "Hello!"},
+ {"en-US", "hello", "Howdy!"},
+ {"en-GB", "hello", "Hellø!"},
+ {"en-oxendict", "hello", "Hello!"},
+ {"en-US-oxendict-u-ms-metric", "hello", "Howdy!"},
+
+ {"und", "greetings", ""},
+ {"nl", "greetings", ""},
+ {"en", "greetings", "Greetings!"},
+ {"en-US", "greetings", "Greetings!"},
+ {"en-GB", "greetings", "Greetings!"},
+ {"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{
+ {"en", "hello %s", []Message{
+ Var("person", String("Jane")),
+ String("Hello ${person}!"),
+ }},
+ {"en", "hello error", []Message{
+ Var("person", String("Jane")),
+ noMatchMessage{}, // trigger sequence path.
+ String("Hello ${person."),
+ }},
+ {"en", "fallback to var value", []Message{
+ Var("you", noMatchMessage{}, noMatchMessage{}),
+ String("Hello ${you}."),
+ }},
+ {"en", "scopes", []Message{
+ Var("person1", String("Mark")),
+ Var("person2", String("Jane")),
+ Var("couple",
+ Var("person1", String("Joe")),
+ String("${person1} and ${person2}")),
+ String("Hello ${couple}."),
+ }},
+ {"en", "missing var", String("Hello ${missing}.")},
+ },
+ lookup: []entry{
+ {"en", "hello %s", "Hello Jane!"},
+ {"en", "hello error", "Hello $!(MISSINGBRACE)"},
+ {"en", "fallback to var value", "Hello you."},
+ {"en", "scopes", "Hello Joe and Jane."},
+ {"en", "missing var", "Hello missing."},
+ },
+ tags: langs("en"),
+}, {
+ desc: "macros",
+ cat: []entry{
+ {"en", "macro1", String("Hello ${macro1(1)}.")},
+ {"en", "macro2", String("Hello ${ macro1(2) }!")},
+ {"en", "macroWS", String("Hello ${ macro1( 2 ) }!")},
+ {"en", "missing", String("Hello ${ missing(1 }.")},
+ {"en", "badnum", String("Hello ${ badnum(1b) }.")},
+ {"en", "undefined", String("Hello ${ undefined(1) }.")},
+ {"en", "macroU", String("Hello ${ macroU(2) }!")},
+ },
+ lookup: []entry{
+ {"en", "macro1", "Hello Joe."},
+ {"en", "macro2", "Hello Joe!"},
+ {"en-US", "macroWS", "Hello Joe!"},
+ {"en-NL", "missing", "Hello $!(MISSINGPAREN)."},
+ {"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 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)
+ switch msg := e.msg.(type) {
+ case string:
+
+ cat.SetString(tag, e.key, msg)
+ case Message:
+ cat.Set(tag, e.key, msg)
+ case []Message:
+ cat.Set(tag, e.key, msg...)
+ }
+ }
+ setMacros(cat)
+ return cat
+}
+
+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(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) {
+ tag := language.MustParse(e.tag)
+ buf := testRenderer{}
+ ctx := cat.Context(tag, &buf)
+ want := e.msg.(string)
+ err := ctx.Execute(e.key)
+ gotFound := err != ErrNotFound
+ wantFound := want != ""
+ if gotFound != wantFound {
+ t.Fatalf("err: got %v (%v); want %v", gotFound, err, wantFound)
+ }
+ if got := buf.buf.String(); got != want {
+ t.Errorf("Lookup:\ngot %q\nwant %q", got, want)
+ }
+ })
+ }
+ }
+ }
+ t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
+ t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
+}
+
+type testRenderer struct {
+ buf bytes.Buffer
+}
+
+func (f *testRenderer) Arg(i int) interface{} { return nil }
+func (f *testRenderer) Render(s string) { f.buf.WriteString(s) }
+
+var msgNoMatch = catmsg.Register("no match", func(d *catmsg.Decoder) bool {
+ return false // no match
+})
+
+type noMatchMessage struct{}
+
+func (noMatchMessage) Compile(e *catmsg.Encoder) error {
+ e.EncodeMessageType(msgNoMatch)
+ return catmsg.ErrIncomplete
+}
diff --git a/vendor/golang.org/x/text/message/catalog/dict.go b/vendor/golang.org/x/text/message/catalog/dict.go
new file mode 100644
index 0000000..a0eb818
--- /dev/null
+++ b/vendor/golang.org/x/text/message/catalog/dict.go
@@ -0,0 +1,129 @@
+// 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.
+
+package catalog
+
+import (
+ "sync"
+
+ "golang.org/x/text/internal"
+ "golang.org/x/text/internal/catmsg"
+ "golang.org/x/text/language"
+)
+
+// TODO:
+// Dictionary returns a Dictionary that returns the first Message, using the
+// given language tag, that matches:
+// 1. the last one registered by one of the Set methods
+// 2. returned by one of the Loaders
+// 3. repeat from 1. using the parent language
+// This approach allows messages to be underspecified.
+// func (c *Catalog) Dictionary(tag language.Tag) (Dictionary, error) {
+// // TODO: verify dictionary exists.
+// return &dict{&c.index, tag}, nil
+// }
+
+type dict struct {
+ s *store
+ tag language.Tag // TODO: make compact tag.
+}
+
+func (d *dict) Lookup(key string) (data string, ok bool) {
+ return d.s.lookup(d.tag, key)
+}
+
+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()
+ defer s.mutex.Unlock()
+
+ m := s.index[tag]
+ if m == nil {
+ m = msgMap{}
+ if s.index == nil {
+ s.index = map[language.Tag]msgMap{}
+ }
+ c.matcher = nil
+ s.index[tag] = m
+ }
+
+ m[key] = data
+ 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
+}
+
+type msgMap map[string]string
+
+func (s *store) lookup(tag language.Tag, key string) (data string, ok bool) {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+
+ for ; ; tag = tag.Parent() {
+ if msgs, ok := s.index[tag]; ok {
+ if msg, ok := msgs[key]; ok {
+ return msg, true
+ }
+ }
+ if tag == language.Und {
+ break
+ }
+ }
+ return "", false
+}
+
+// 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 {
+ if t != b.options.fallback {
+ tags = append(tags, t)
+ }
+ }
+ 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 0000000..147fc7c
--- /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 0000000..a9753b9
--- /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)
+}