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.
5 // Package catmsg contains support types for package x/text/message/catalog.
7 // This package contains the low-level implementations of Message used by the
8 // catalog package and provides primitives for other packages to implement their
9 // own. For instance, the plural package provides functionality for selecting
10 // translation strings based on the plural category of substitution arguments.
13 // Encoding and Decoding
15 // Catalogs store Messages encoded as a single string. Compiling a message into
16 // a string both results in compacter representation and speeds up evaluation.
18 // A Message must implement a Compile method to convert its arbitrary
19 // representation to a string. The Compile method takes an Encoder which
20 // facilitates serializing the message. Encoders also provide more context of
21 // the messages's creation (such as for which language the message is intended),
22 // which may not be known at the time of the creation of the message.
24 // Each message type must also have an accompanying decoder registered to decode
25 // the message. This decoder takes a Decoder argument which provides the
26 // counterparts for the decoding.
31 // A Decoder must be initialized with a Renderer implementation. These
32 // implementations must be provided by packages that use Catalogs, typically
33 // formatting packages such as x/text/message. A typical user will not need to
34 // worry about this type; it is only relevant to packages that do string
35 // formatting and want to use the catalog package to handle localized strings.
37 // A package that uses catalogs for selecting strings receives selection results
38 // as sequence of substrings passed to the Renderer. The following snippet shows
39 // how to express the above example using the message package.
41 // message.Set(language.English, "You are %d minute(s) late.",
42 // catalog.Var("minutes", plural.Select(1, "one", "minute")),
43 // catalog.String("You are %[1]d ${minutes} late."))
45 // p := message.NewPrinter(language.English)
46 // p.Printf("You are %d minute(s) late.", 5) // always 5 minutes late.
48 // To evaluate the Printf, package message wraps the arguments in a Renderer
49 // that is passed to the catalog for message decoding. The call sequence that
50 // results from evaluating the above message, assuming the person is rather
53 // Render("You are %[1]d ")
58 // The calls to Arg is caused by the plural.Select execution, which evaluates
59 // the argument to determine whether the singular or plural message form should
60 // be selected. The calls to Render reports the partial results to the message
61 // package for further evaluation.
71 "golang.org/x/text/language"
74 // A Handle refers to a registered message type.
77 // First is used as a Handle to EncodeMessageType, followed by a series of calls
78 // to EncodeMessage, to implement selecting the first matching Message.
80 // TODO: this can be removed once we either can use type aliases or if the
81 // internals of this package are merged with the catalog package.
82 var First Handle = msgFirst
84 // A Handler decodes and evaluates data compiled by a Message and sends the
85 // result to the Decoder. The output may depend on the value of the substitution
86 // arguments, accessible by the Decoder's Arg method. The Handler returns false
87 // if there is no translation for the given substitution arguments.
88 type Handler func(d *Decoder) bool
90 // Register records the existence of a message type and returns a Handle that
91 // can be used in the Encoder's EncodeMessageType method to create such
92 // messages. The prefix of the name should be the package path followed by
93 // an optional disambiguating string.
94 // Register will panic if a handle for the same name was already registered.
95 func Register(name string, handler Handler) Handle {
99 if _, ok := names[name]; ok {
100 panic(fmt.Errorf("catmsg: handler for %q already exists", name))
102 h := Handle(len(handlers))
104 handlers = append(handlers, handler)
108 // These handlers require fixed positions in the handlers slice.
110 msgVars Handle = iota
117 const prefix = "golang.org/x/text/internal/catmsg."
121 names = map[string]Handle{
122 prefix + "Vars": msgVars,
123 prefix + "First": msgFirst,
124 prefix + "Raw": msgRaw,
125 prefix + "String": msgString,
127 handlers = make([]Handler, numFixed)
131 // This handler is a message type wrapper that initializes a decoder
132 // with a variable block. This message type, if present, is always at the
133 // start of an encoded message.
134 handlers[msgVars] = func(d *Decoder) bool {
135 blockSize := int(d.DecodeUint())
136 d.vars = d.data[:blockSize]
137 d.data = d.data[blockSize:]
138 return d.executeMessage()
141 // First takes the first message in a sequence that results in a match for
142 // the given substitution arguments.
143 handlers[msgFirst] = func(d *Decoder) bool {
145 if d.ExecuteMessage() {
152 handlers[msgRaw] = func(d *Decoder) bool {
157 // A String message alternates between a string constant and a variable
159 handlers[msgString] = func(d *Decoder) bool {
161 if str := d.DecodeString(); str != "" {
167 d.ExecuteSubstitution()
174 // ErrIncomplete indicates a compiled message does not define translations
175 // for all possible argument values. If this message is returned, evaluating
176 // a message may result in the ErrNoMatch error.
177 ErrIncomplete = errors.New("catmsg: incomplete message; may not give result for all inputs")
179 // ErrNoMatch indicates no translation message matched the given input
180 // parameters when evaluating a message.
181 ErrNoMatch = errors.New("catmsg: no translation for inputs")
184 // A Message holds a collection of translations for the same phrase that may
185 // vary based on the values of substitution arguments.
186 type Message interface {
187 // Compile encodes the format string(s) of the message as a string for later
190 // The first call Compile makes on the encoder must be EncodeMessageType.
191 // The handle passed to this call may either be a handle returned by
192 // Register to encode a single custom message, or HandleFirst followed by
193 // a sequence of calls to EncodeMessage.
195 // Compile must return ErrIncomplete if it is possible for evaluation to
196 // not match any translation for a given set of formatting parameters.
197 // For example, selecting a translation based on plural form may not yield
198 // a match if the form "Other" is not one of the selectors.
200 // Compile may return any other application-specific error. For backwards
201 // compatibility with package like fmt, which often do not do sanity
202 // checking of format strings ahead of time, Compile should still make an
203 // effort to have some sensible fallback in case of an error.
204 Compile(e *Encoder) error
207 // Compile converts a Message to a data string that can be stored in a Catalog.
208 // The resulting string can subsequently be decoded by passing to the Execute
209 // method of a Decoder.
210 func Compile(tag language.Tag, macros Dictionary, m Message) (data string, err error) {
211 // TODO: pass macros so they can be used for validation.
212 v := &Encoder{inBody: true} // encoder for variables
214 e := &Encoder{root: v, parent: v, tag: tag} // encoder for messages
216 // This package serves te message package, which in turn is meant to be a
217 // drop-in replacement for fmt. With the fmt package, format strings are
218 // evaluated lazily and errors are handled by substituting strings in the
219 // result, rather then returning an error. Dealing with multiple languages
220 // makes it more important to check errors ahead of time. We chose to be
221 // consistent and compatible and allow graceful degradation in case of
223 buf := e.buf[stripPrefix(e.buf):]
225 // Prepend variable block.
226 b := make([]byte, 1+maxVarintBytes+len(v.buf)+len(buf))
228 b = b[:1+encodeUint(b[1:], uint64(len(v.buf)))]
229 b = append(b, v.buf...)
230 b = append(b, buf...)
236 return string(buf), err
239 // Var defines a message that can be substituted for a placeholder of the same
240 // name. If an expression does not result in a string after evaluation, Name is
241 // used as the substitution. For example:
244 // Message: plural.Select(1, "one", "minute"),
246 // will resolve to minute for singular and minutes for plural forms.
252 var errIsVar = errors.New("catmsg: variable used as message")
254 // Compile implements Message.
256 // Note that this method merely registers a variable; it does not create an
258 func (v *Var) Compile(e *Encoder) error {
259 if err := e.addVar(v.Name, v.Message); err != nil {
262 // Using a Var by itself is an error. If it is in a sequence followed by
263 // other messages referring to it, this error will be ignored.
267 // Raw is a message consisting of a single format string that is passed as is
270 // Note that a Renderer may still do its own variable substitution.
273 // Compile implements Message.
274 func (r Raw) Compile(e *Encoder) (err error) {
275 e.EncodeMessageType(msgRaw)
276 // Special case: raw strings don't have a size encoding and so don't use
278 e.buf = append(e.buf, r...)
282 // String is a message consisting of a single format string which contains
283 // placeholders that may be substituted with variables.
285 // Variable substitutions are marked with placeholders and a variable name of
286 // the form ${name}. Any other substitutions such as Go templates or
287 // printf-style substitutions are left to be done by the Renderer.
289 // When evaluation a string interpolation, a Renderer will receive separate
290 // calls for each placeholder and interstitial string. For example, for the
291 // message: "%[1]v ${invites} %[2]v to ${their} party." The sequence of calls
293 // d.Render("%[1]v ")
295 // d.Render(resultOfInvites)
296 // d.Render(" %[2]v to ")
298 // d.Render(resultOfTheir)
299 // d.Render(" party.")
300 // where the messages for "invites" and "their" both use a plural.Select
301 // referring to the first argument.
303 // Strings may also invoke macros. Macros are essentially variables that can be
304 // reused. Macros may, for instance, be used to make selections between
305 // different conjugations of a verb. See the catalog package description for an
306 // overview of macros.
309 // Compile implements Message. It parses the placeholder formats and returns
311 func (s String) Compile(e *Encoder) (err error) {
313 const subStart = "${"
318 i := strings.Index(msg[p:], subStart)
322 b = append(b, msg[p:p+i]...)
323 p += i + len(subStart)
324 if i = strings.IndexByte(msg[p:], '}'); i == -1 {
325 b = append(b, "$!(MISSINGBRACE)"...)
326 err = fmt.Errorf("catmsg: missing '}'")
330 name := strings.TrimSpace(msg[p : p+i])
331 if q := strings.IndexByte(name, '('); q == -1 {
334 e.EncodeMessageType(msgString)
336 e.EncodeString(string(b))
337 e.EncodeSubstitution(name)
339 } else if j := strings.IndexByte(name[q:], ')'); j == -1 {
340 // TODO: what should the error be?
341 b = append(b, "$!(MISSINGPAREN)"...)
342 err = fmt.Errorf("catmsg: missing ')'")
343 } else if x, sErr := strconv.ParseUint(strings.TrimSpace(name[q+1:q+j]), 10, 32); sErr != nil {
344 // TODO: handle more than one argument
345 b = append(b, "$!(BADNUM)"...)
346 err = fmt.Errorf("catmsg: invalid number %q", strings.TrimSpace(name[q+1:q+j]))
350 e.EncodeMessageType(msgString)
352 e.EncodeString(string(b))
353 e.EncodeSubstitution(name[:q], int(x))
358 b = append(b, msg[p:]...)
360 // Simplify string to a raw string.
361 Raw(string(b)).Compile(e)
362 } else if len(b) > 0 {
363 e.EncodeString(string(b))