OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / text / message / catalog / catalog.go
1 // Copyright 2017 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package catalog defines collections of translated format strings.
6 //
7 // This package mostly defines types for populating catalogs with messages. The
8 // catmsg package contains further definitions for creating custom message and
9 // dictionary types as well as packages that use Catalogs.
10 //
11 // Package catalog defines various interfaces: Dictionary, Loader, and Message.
12 // A Dictionary maintains a set of translations of format strings for a single
13 // language. The Loader interface defines a source of dictionaries. A
14 // translation of a format string is represented by a Message.
15 //
16 //
17 // Catalogs
18 //
19 // A Catalog defines a programmatic interface for setting message translations.
20 // It maintains a set of per-language dictionaries with translations for a set
21 // of keys. For message translation to function properly, a translation should
22 // be defined for each key for each supported language. A dictionary may be
23 // underspecified, though, if there is a parent language that already defines
24 // the key. For example, a Dictionary for "en-GB" could leave out entries that
25 // are identical to those in a dictionary for "en".
26 //
27 //
28 // Messages
29 //
30 // A Message is a format string which varies on the value of substitution
31 // variables. For instance, to indicate the number of results one could want "no
32 // results" if there are none, "1 result" if there is 1, and "%d results" for
33 // any other number. Catalog is agnostic to the kind of format strings that are
34 // used: for instance, messages can follow either the printf-style substitution
35 // from package fmt or use templates.
36 //
37 // A Message does not substitute arguments in the format string. This job is
38 // reserved for packages that render strings, such as message, that use Catalogs
39 // to selected string. This separation of concerns allows Catalog to be used to
40 // store any kind of formatting strings.
41 //
42 //
43 // Selecting messages based on linguistic features of substitution arguments
44 //
45 // Messages may vary based on any linguistic features of the argument values.
46 // The most common one is plural form, but others exist.
47 //
48 // Selection messages are provided in packages that provide support for a
49 // specific linguistic feature. The following snippet uses plural.Select:
50 //
51 //   catalog.Set(language.English, "You are %d minute(s) late.",
52 //       plural.Select(1,
53 //           "one", "You are 1 minute late.",
54 //           "other", "You are %d minutes late."))
55 //
56 // In this example, a message is stored in the Catalog where one of two messages
57 // is selected based on the first argument, a number. The first message is
58 // selected if the argument is singular (identified by the selector "one") and
59 // the second message is selected in all other cases. The selectors are defined
60 // by the plural rules defined in CLDR. The selector "other" is special and will
61 // always match. Each language always defines one of the linguistic categories
62 // to be "other." For English, singular is "one" and plural is "other".
63 //
64 // Selects can be nested. This allows selecting sentences based on features of
65 // multiple arguments or multiple linguistic properties of a single argument.
66 //
67 //
68 // String interpolation
69 //
70 // There is often a lot of commonality between the possible variants of a
71 // message. For instance, in the example above the word "minute" varies based on
72 // the plural catogory of the argument, but the rest of the sentence is
73 // identical. Using interpolation the above message can be rewritten as:
74 //
75 //   catalog.Set(language.English, "You are %d minute(s) late.",
76 //       catalog.Var("minutes",
77 //           plural.Select(1, "one", "minute", "other", "minutes")),
78 //       catalog.String("You are %[1]d ${minutes} late."))
79 //
80 // Var is defined to return the variable name if the message does not yield a
81 // match. This allows us to further simplify this snippet to
82 //
83 //   catalog.Set(language.English, "You are %d minute(s) late.",
84 //       catalog.Var("minutes", plural.Select(1, "one", "minute")),
85 //       catalog.String("You are %d ${minutes} late."))
86 //
87 // Overall this is still only a minor improvement, but things can get a lot more
88 // unwieldy if more than one linguistic feature is used to determine a message
89 // variant. Consider the following example:
90 //
91 //   // argument 1: list of hosts, argument 2: list of guests
92 //   catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.",
93 //     catalog.Var("their",
94 //         plural.Select(1,
95 //             "one", gender.Select(1, "female", "her", "other", "his"))),
96 //     catalog.Var("invites", plural.Select(1, "one", "invite"))
97 //     catalog.String("%[1]v ${invites} %[2]v to ${their} party.")),
98 //
99 // Without variable substitution, this would have to be written as
100 //
101 //   // argument 1: list of hosts, argument 2: list of guests
102 //   catalog.Set(language.English, "%[1]v invite(s) %[2]v to their party.",
103 //     plural.Select(1,
104 //         "one", gender.Select(1,
105 //             "female", "%[1]v invites %[2]v to her party."
106 //             "other", "%[1]v invites %[2]v to his party."),
107 //         "other", "%[1]v invites %[2]v to their party.")
108 //
109 // Not necessarily shorter, but using variables there is less duplication and
110 // the messages are more maintenance friendly. Moreover, languages may have up
111 // to six plural forms. This makes the use of variables more welcome.
112 //
113 // Different messages using the same inflections can reuse variables by moving
114 // them to macros. Using macros we can rewrite the message as:
115 //
116 //   // argument 1: list of hosts, argument 2: list of guests
117 //   catalog.SetString(language.English, "%[1]v invite(s) %[2]v to their party.",
118 //       "%[1]v ${invites(1)} %[2]v to ${their(1)} party.")
119 //
120 // Where the following macros were defined separately.
121 //
122 //   catalog.SetMacro(language.English, "invites", plural.Select(1, "one", "invite"))
123 //   catalog.SetMacro(language.English, "their", plural.Select(1,
124 //      "one", gender.Select(1, "female", "her", "other", "his"))),
125 //
126 // Placeholders use parentheses and the arguments to invoke a macro.
127 //
128 //
129 // Looking up messages
130 //
131 // Message lookup using Catalogs is typically only done by specialized packages
132 // and is not something the user should be concerned with. For instance, to
133 // express the tardiness of a user using the related message we defined earlier,
134 // the user may use the package message like so:
135 //
136 //   p := message.NewPrinter(language.English)
137 //   p.Printf("You are %d minute(s) late.", 5)
138 //
139 // Which would print:
140 //   You are 5 minutes late.
141 //
142 //
143 // This package is UNDER CONSTRUCTION and its API may change.
144 package catalog // import "golang.org/x/text/message/catalog"
145
146 // TODO:
147 // Some way to freeze a catalog.
148 // - Locking on each lockup turns out to be about 50% of the total running time
149 //   for some of the benchmarks in the message package.
150 // Consider these:
151 // - Sequence type to support sequences in user-defined messages.
152 // - Garbage collection: Remove dictionaries that can no longer be reached
153 //   as other dictionaries have been added that cover all possible keys.
154
155 import (
156         "errors"
157         "fmt"
158
159         "golang.org/x/text/internal/catmsg"
160         "golang.org/x/text/language"
161 )
162
163 // A Catalog holds translations for messages for supported languages.
164 type Catalog struct {
165         options
166
167         index  store
168         macros store
169 }
170
171 type options struct{}
172
173 // An Option configures Catalog behavior.
174 type Option func(*options)
175
176 // TODO:
177 // // Catalogs specifies one or more sources for a Catalog.
178 // // Lookups are in order.
179 // // This can be changed inserting a Catalog used for setting, which implements
180 // // Loader, used for setting in the chain.
181 // func Catalogs(d ...Loader) Option {
182 //      return nil
183 // }
184 //
185 // func Delims(start, end string) Option {}
186 //
187 // func Dict(tag language.Tag, d ...Dictionary) Option
188
189 // New returns a new Catalog.
190 func New(opts ...Option) *Catalog {
191         c := &Catalog{}
192         for _, o := range opts {
193                 o(&c.options)
194         }
195         return c
196 }
197
198 // Languages returns all languages for which the Catalog contains variants.
199 func (c *Catalog) Languages() []language.Tag {
200         return c.index.languages()
201 }
202
203 // SetString is shorthand for Set(tag, key, String(msg)).
204 func (c *Catalog) SetString(tag language.Tag, key string, msg string) error {
205         return c.set(tag, key, &c.index, String(msg))
206 }
207
208 // Set sets the translation for the given language and key.
209 //
210 // When evaluation this message, the first Message in the sequence to msgs to
211 // evaluate to a string will be the message returned.
212 func (c *Catalog) Set(tag language.Tag, key string, msg ...Message) error {
213         return c.set(tag, key, &c.index, msg...)
214 }
215
216 // SetMacro defines a Message that may be substituted in another message.
217 // The arguments to a macro Message are passed as arguments in the
218 // placeholder the form "${foo(arg1, arg2)}".
219 func (c *Catalog) SetMacro(tag language.Tag, name string, msg ...Message) error {
220         return c.set(tag, name, &c.macros, msg...)
221 }
222
223 // ErrNotFound indicates there was no message for the given key.
224 var ErrNotFound = errors.New("catalog: message not found")
225
226 // A Message holds a collection of translations for the same phrase that may
227 // vary based on the values of substitution arguments.
228 type Message interface {
229         catmsg.Message
230 }
231
232 // String specifies a plain message string. It can be used as fallback if no
233 // other strings match or as a simple standalone message.
234 //
235 // It is an error to pass more than one String in a message sequence.
236 func String(name string) Message {
237         return catmsg.String(name)
238 }
239
240 // Var sets a variable that may be substituted in formatting patterns using
241 // named substitution of the form "${name}". The name argument is used as a
242 // fallback if the statements do not produce a match. The statement sequence may
243 // not contain any Var calls.
244 //
245 // The name passed to a Var must be unique within message sequence.
246 func Var(name string, msg ...Message) Message {
247         return &catmsg.Var{Name: name, Message: firstInSequence(msg)}
248 }
249
250 // firstInSequence is a message type that prints the first message in the
251 // sequence that resolves to a match for the given substitution arguments.
252 type firstInSequence []Message
253
254 func (s firstInSequence) Compile(e *catmsg.Encoder) error {
255         e.EncodeMessageType(catmsg.First)
256         err := catmsg.ErrIncomplete
257         for i, m := range s {
258                 if err == nil {
259                         return fmt.Errorf("catalog: message argument %d is complete and blocks subsequent messages", i-1)
260                 }
261                 err = e.EncodeMessage(m)
262         }
263         return err
264 }
265
266 // Context returns a Context for formatting messages.
267 // Only one Message may be formatted per context at any given time.
268 func (c *Catalog) Context(tag language.Tag, r catmsg.Renderer) *Context {
269         return &Context{
270                 cat: c,
271                 tag: tag,
272                 dec: catmsg.NewDecoder(tag, r, &dict{&c.macros, tag}),
273         }
274 }
275
276 // A Context is used for evaluating Messages.
277 // Only one Message may be formatted per context at any given time.
278 type Context struct {
279         cat *Catalog
280         tag language.Tag
281         dec *catmsg.Decoder
282 }
283
284 // Execute looks up and executes the message with the given key.
285 // It returns ErrNotFound if no message could be found in the index.
286 func (c *Context) Execute(key string) error {
287         data, ok := c.cat.index.lookup(c.tag, key)
288         if !ok {
289                 return ErrNotFound
290         }
291         return c.dec.Execute(data)
292 }