summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/text/message/message.go
blob: 32ff3ef9003d7c436a584a1367f6c0c24693764d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// 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/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)
}