| // Copyright 2015 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 message implements formatted I/O for localized strings with functions |
| // analogous to the fmt's print functions. |
| // |
| // NOTE: Under construction. See https://golang.org/design/text/12750-localization |
| // and its corresponding proposal issue https://golang.org/issues/12750. |
| package message // import "golang.org/x/text/message" |
| |
| import ( |
| "fmt" |
| "io" |
| "strings" |
| |
| "golang.org/x/text/internal/format" |
| "golang.org/x/text/language" |
| ) |
| |
| // A Printer implements language-specific formatted I/O analogous to the fmt |
| // package. Only one goroutine may use a Printer at the same time. |
| type Printer struct { |
| tag language.Tag |
| |
| cat *Catalog |
| |
| // NOTE: limiting one goroutine per Printer allows for many optimizations |
| // and simplifications. We can consider removing this restriction down the |
| // road if it the benefits do not seem to outweigh the disadvantages. |
| } |
| |
| // NewPrinter returns a Printer that formats messages tailored to language t. |
| func NewPrinter(t language.Tag) *Printer { |
| return DefaultCatalog.Printer(t) |
| } |
| |
| // Sprint is like fmt.Sprint, but using language-specific formatting. |
| func (p *Printer) Sprint(a ...interface{}) string { |
| return fmt.Sprint(p.bindArgs(a)...) |
| } |
| |
| // Fprint is like fmt.Fprint, but using language-specific formatting. |
| func (p *Printer) Fprint(w io.Writer, a ...interface{}) (n int, err error) { |
| return fmt.Fprint(w, p.bindArgs(a)...) |
| } |
| |
| // Print is like fmt.Print, but using language-specific formatting. |
| func (p *Printer) Print(a ...interface{}) (n int, err error) { |
| return fmt.Print(p.bindArgs(a)...) |
| } |
| |
| // Sprintln is like fmt.Sprintln, but using language-specific formatting. |
| func (p *Printer) Sprintln(a ...interface{}) string { |
| return fmt.Sprintln(p.bindArgs(a)...) |
| } |
| |
| // Fprintln is like fmt.Fprintln, but using language-specific formatting. |
| func (p *Printer) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { |
| return fmt.Fprintln(w, p.bindArgs(a)...) |
| } |
| |
| // Println is like fmt.Println, but using language-specific formatting. |
| func (p *Printer) Println(a ...interface{}) (n int, err error) { |
| return fmt.Println(p.bindArgs(a)...) |
| } |
| |
| // Sprintf is like fmt.Sprintf, but using language-specific formatting. |
| func (p *Printer) Sprintf(key Reference, a ...interface{}) string { |
| msg, hasSub := p.lookup(key) |
| if !hasSub { |
| return fmt.Sprintf(msg) // work around limitation of fmt |
| } |
| return fmt.Sprintf(msg, p.bindArgs(a)...) |
| } |
| |
| // Fprintf is like fmt.Fprintf, but using language-specific formatting. |
| func (p *Printer) Fprintf(w io.Writer, key Reference, a ...interface{}) (n int, err error) { |
| msg, hasSub := p.lookup(key) |
| if !hasSub { |
| return fmt.Fprintf(w, msg) // work around limitation of fmt |
| } |
| return fmt.Fprintf(w, msg, p.bindArgs(a)...) |
| } |
| |
| // Printf is like fmt.Printf, but using language-specific formatting. |
| func (p *Printer) Printf(key Reference, a ...interface{}) (n int, err error) { |
| msg, hasSub := p.lookup(key) |
| if !hasSub { |
| return fmt.Printf(msg) // work around limitation of fmt |
| } |
| return fmt.Printf(msg, p.bindArgs(a)...) |
| } |
| |
| func (p *Printer) lookup(r Reference) (msg string, hasSub bool) { |
| var id string |
| switch v := r.(type) { |
| case string: |
| id, msg = v, v |
| case key: |
| id, msg = v.id, v.fallback |
| default: |
| panic("key argument is not a Reference") |
| } |
| if s, ok := p.cat.get(p.tag, id); ok { |
| msg = s |
| } |
| // fmt does not allow all arguments to be dropped in a format string. It |
| // only allows arguments to be dropped if at least one of the substitutions |
| // uses the positional marker (e.g. %[1]s). This hack works around this. |
| // TODO: This is only an approximation of the parsing of substitution |
| // patterns. Make more precise once we know if we can get by with fmt's |
| // formatting, which may not be the case. |
| for i := 0; i < len(msg)-1; i++ { |
| if msg[i] == '%' { |
| for i++; i < len(msg); i++ { |
| if strings.IndexByte("[]#+- *01234567890.", msg[i]) < 0 { |
| break |
| } |
| } |
| if i < len(msg) && msg[i] != '%' { |
| hasSub = true |
| break |
| } |
| } |
| } |
| return msg, hasSub |
| } |
| |
| // A Reference is a string or a message reference. |
| type Reference interface { |
| } |
| |
| // Key creates a message Reference for a message where the given id is used for |
| // message lookup and the fallback is returned when no matches are found. |
| func Key(id string, fallback string) Reference { |
| return key{id, fallback} |
| } |
| |
| type key struct { |
| id, fallback string |
| } |
| |
| // bindArgs wraps arguments with implementation of fmt.Formatter, if needed. |
| func (p *Printer) bindArgs(a []interface{}) []interface{} { |
| out := make([]interface{}, len(a)) |
| for i, x := range a { |
| switch v := x.(type) { |
| case fmt.Formatter: |
| // Wrap the value with a Formatter that augments the State with |
| // language-specific attributes. |
| out[i] = &value{v, p} |
| |
| // NOTE: as we use fmt.Formatter, we can't distinguish between |
| // regular and localized formatters, so we always need to wrap it. |
| |
| // TODO: handle |
| // - numbers |
| // - lists |
| // - time? |
| default: |
| out[i] = x |
| } |
| } |
| return out |
| } |
| |
| // state implements "golang.org/x/text/internal/format".State. |
| type state struct { |
| fmt.State |
| p *Printer |
| } |
| |
| func (s *state) Language() language.Tag { return s.p.tag } |
| |
| var _ format.State = &state{} |
| |
| type value struct { |
| x fmt.Formatter |
| p *Printer |
| } |
| |
| func (v *value) Format(s fmt.State, verb rune) { |
| v.x.Format(&state{s, v.p}, verb) |
| } |