github.com/go-xe2/third@v1.0.3/golang.org/x/text/internal/catmsg/catmsg.go (about) 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 catmsg contains support types for package x/text/message/catalog. 6 // 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. 11 // 12 // 13 // Encoding and Decoding 14 // 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. 17 // 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. 23 // 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. 27 // 28 // 29 // Renderers 30 // 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. 36 // 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. 40 // 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.")) 44 // 45 // p := message.NewPrinter(language.English) 46 // p.Printf("You are %d minute(s) late.", 5) // always 5 minutes late. 47 // 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 51 // tardy, is: 52 // 53 // Render("You are %[1]d ") 54 // Arg(1) 55 // Render("minutes") 56 // Render(" late.") 57 // 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. 62 package catmsg 63 64 import ( 65 "errors" 66 "fmt" 67 "strconv" 68 "strings" 69 "sync" 70 71 "github.com/go-xe2/third/golang.org/x/text/language" 72 ) 73 74 // A Handle refers to a registered message type. 75 type Handle int 76 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. 79 // 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 83 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 89 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 { 96 mutex.Lock() 97 defer mutex.Unlock() 98 99 if _, ok := names[name]; ok { 100 panic(fmt.Errorf("catmsg: handler for %q already exists", name)) 101 } 102 h := Handle(len(handlers)) 103 names[name] = h 104 handlers = append(handlers, handler) 105 return h 106 } 107 108 // These handlers require fixed positions in the handlers slice. 109 const ( 110 msgVars Handle = iota 111 msgFirst 112 msgRaw 113 msgString 114 numFixed 115 ) 116 117 const prefix = "github.com/go-xe2/third/golang.org/x/text/internal/catmsg." 118 119 var ( 120 mutex sync.Mutex 121 names = map[string]Handle{ 122 prefix + "Vars": msgVars, 123 prefix + "First": msgFirst, 124 prefix + "Raw": msgRaw, 125 prefix + "String": msgString, 126 } 127 handlers = make([]Handler, numFixed) 128 ) 129 130 func init() { 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() 139 } 140 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 { 144 for !d.Done() { 145 if d.ExecuteMessage() { 146 return true 147 } 148 } 149 return false 150 } 151 152 handlers[msgRaw] = func(d *Decoder) bool { 153 d.Render(d.data) 154 return true 155 } 156 157 // A String message alternates between a string constant and a variable 158 // substitution. 159 handlers[msgString] = func(d *Decoder) bool { 160 for !d.Done() { 161 if str := d.DecodeString(); str != "" { 162 d.Render(str) 163 } 164 if d.Done() { 165 break 166 } 167 d.ExecuteSubstitution() 168 } 169 return true 170 } 171 } 172 173 var ( 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") 178 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") 182 ) 183 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 188 // evaluation. 189 // 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. 194 // 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. 199 // 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 205 } 206 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 213 v.root = v 214 e := &Encoder{root: v, parent: v, tag: tag} // encoder for messages 215 err = m.Compile(e) 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 222 // errors. 223 buf := e.buf[stripPrefix(e.buf):] 224 if len(v.buf) > 0 { 225 // Prepend variable block. 226 b := make([]byte, 1+maxVarintBytes+len(v.buf)+len(buf)) 227 b[0] = byte(msgVars) 228 b = b[:1+encodeUint(b[1:], uint64(len(v.buf)))] 229 b = append(b, v.buf...) 230 b = append(b, buf...) 231 buf = b 232 } 233 if err == nil { 234 err = v.err 235 } 236 return string(buf), err 237 } 238 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: 242 // Var{ 243 // Name: "minutes", 244 // Message: plural.Select(1, "one", "minute"), 245 // } 246 // will resolve to minute for singular and minutes for plural forms. 247 type Var struct { 248 Name string 249 Message Message 250 } 251 252 var errIsVar = errors.New("catmsg: variable used as message") 253 254 // Compile implements Message. 255 // 256 // Note that this method merely registers a variable; it does not create an 257 // encoded message. 258 func (v *Var) Compile(e *Encoder) error { 259 if err := e.addVar(v.Name, v.Message); err != nil { 260 return err 261 } 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. 264 return errIsVar 265 } 266 267 // Raw is a message consisting of a single format string that is passed as is 268 // to the Renderer. 269 // 270 // Note that a Renderer may still do its own variable substitution. 271 type Raw string 272 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 277 // EncodeString. 278 e.buf = append(e.buf, r...) 279 return nil 280 } 281 282 // String is a message consisting of a single format string which contains 283 // placeholders that may be substituted with variables. 284 // 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. 288 // 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 292 // is: 293 // d.Render("%[1]v ") 294 // d.Arg(1) 295 // d.Render(resultOfInvites) 296 // d.Render(" %[2]v to ") 297 // d.Arg(2) 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. 302 // 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. 307 type String string 308 309 // Compile implements Message. It parses the placeholder formats and returns 310 // any error. 311 func (s String) Compile(e *Encoder) (err error) { 312 msg := string(s) 313 const subStart = "${" 314 hasHeader := false 315 p := 0 316 b := []byte{} 317 for { 318 i := strings.Index(msg[p:], subStart) 319 if i == -1 { 320 break 321 } 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 '}'") 327 p = len(msg) 328 break 329 } 330 name := strings.TrimSpace(msg[p : p+i]) 331 if q := strings.IndexByte(name, '('); q == -1 { 332 if !hasHeader { 333 hasHeader = true 334 e.EncodeMessageType(msgString) 335 } 336 e.EncodeString(string(b)) 337 e.EncodeSubstitution(name) 338 b = b[:0] 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])) 347 } else { 348 if !hasHeader { 349 hasHeader = true 350 e.EncodeMessageType(msgString) 351 } 352 e.EncodeString(string(b)) 353 e.EncodeSubstitution(name[:q], int(x)) 354 b = b[:0] 355 } 356 p += i + 1 357 } 358 b = append(b, msg[p:]...) 359 if !hasHeader { 360 // Simplify string to a raw string. 361 Raw(string(b)).Compile(e) 362 } else if len(b) > 0 { 363 e.EncodeString(string(b)) 364 } 365 return err 366 }