summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/text/feature/plural
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/text/feature/plural')
-rw-r--r--vendor/golang.org/x/text/feature/plural/example_test.go46
-rwxr-xr-xvendor/golang.org/x/text/feature/plural/message.go244
-rw-r--r--vendor/golang.org/x/text/feature/plural/message_test.go197
-rw-r--r--vendor/golang.org/x/text/feature/plural/plural.go34
-rw-r--r--vendor/golang.org/x/text/feature/plural/plural_test.go17
5 files changed, 531 insertions, 7 deletions
diff --git a/vendor/golang.org/x/text/feature/plural/example_test.go b/vendor/golang.org/x/text/feature/plural/example_test.go
new file mode 100644
index 000000000..c75408c0e
--- /dev/null
+++ b/vendor/golang.org/x/text/feature/plural/example_test.go
@@ -0,0 +1,46 @@
+// Copyright 2017 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 plural_test
+
+import (
+ "golang.org/x/text/feature/plural"
+ "golang.org/x/text/language"
+ "golang.org/x/text/message"
+)
+
+func ExampleSelect() {
+ // Manually set some translations. This is typically done programmatically.
+ message.Set(language.English, "%d files remaining",
+ plural.Selectf(1, "%d",
+ "=0", "done!",
+ plural.One, "one file remaining",
+ plural.Other, "%[1]d files remaining",
+ ))
+ message.Set(language.Dutch, "%d files remaining",
+ plural.Selectf(1, "%d",
+ "=0", "klaar!",
+ // One can also use a string instead of a Kind
+ "one", "nog één bestand te gaan",
+ "other", "nog %[1]d bestanden te gaan",
+ ))
+
+ p := message.NewPrinter(language.English)
+ p.Printf("%d files remaining", 5)
+ p.Println()
+ p.Printf("%d files remaining", 1)
+ p.Println()
+
+ p = message.NewPrinter(language.Dutch)
+ p.Printf("%d files remaining", 1)
+ p.Println()
+ p.Printf("%d files remaining", 0)
+ p.Println()
+
+ // Output:
+ // 5 files remaining
+ // one file remaining
+ // nog één bestand te gaan
+ // klaar!
+}
diff --git a/vendor/golang.org/x/text/feature/plural/message.go b/vendor/golang.org/x/text/feature/plural/message.go
new file mode 100755
index 000000000..f931f8a6a
--- /dev/null
+++ b/vendor/golang.org/x/text/feature/plural/message.go
@@ -0,0 +1,244 @@
+// Copyright 2017 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 plural
+
+import (
+ "fmt"
+ "io/ioutil"
+ "reflect"
+ "strconv"
+
+ "golang.org/x/text/internal/catmsg"
+ "golang.org/x/text/internal/number"
+ "golang.org/x/text/language"
+ "golang.org/x/text/message/catalog"
+)
+
+// TODO: consider deleting this interface. Maybe VisibleDigits is always
+// sufficient and practical.
+
+// Interface is used for types that can determine their own plural form.
+type Interface interface {
+ // PluralForm reports the plural form for the given language of the
+ // underlying value. It also returns the integer value. If the integer value
+ // is larger than fits in n, PluralForm may return a value modulo
+ // 10,000,000.
+ PluralForm(t language.Tag, scale int) (f Form, n int)
+}
+
+// Selectf returns the first case for which its selector is a match for the
+// arg-th substitution argument to a formatting call, formatting it as indicated
+// by format.
+//
+// The cases argument are pairs of selectors and messages. Selectors are of type
+// string or Form. Messages are of type string or catalog.Message. A selector
+// matches an argument if:
+// - it is "other" or Other
+// - it matches the plural form of the argument: "zero", "one", "two", "few",
+// or "many", or the equivalent Form
+// - it is of the form "=x" where x is an integer that matches the value of
+// the argument.
+// - it is of the form "<x" where x is an integer that is larger than the
+// argument.
+//
+// The format argument determines the formatting parameters for which to
+// determine the plural form. This is especially relevant for non-integer
+// values.
+//
+// The format string may be "", in which case a best-effort attempt is made to
+// find a reasonable representation on which to base the plural form. Examples
+// of format strings are:
+// - %.2f decimal with scale 2
+// - %.2e scientific notation with precision 3 (scale + 1)
+// - %d integer
+func Selectf(arg int, format string, cases ...interface{}) catalog.Message {
+ var p parser
+ // Intercept the formatting parameters of format by doing a dummy print.
+ fmt.Fprintf(ioutil.Discard, format, &p)
+ m := &message{arg, kindDefault, 0, cases}
+ switch p.verb {
+ case 'g':
+ m.kind = kindPrecision
+ m.scale = p.scale
+ case 'f':
+ m.kind = kindScale
+ m.scale = p.scale
+ case 'e':
+ m.kind = kindScientific
+ m.scale = p.scale
+ case 'd':
+ m.kind = kindScale
+ m.scale = 0
+ default:
+ // TODO: do we need to handle errors?
+ }
+ return m
+}
+
+type parser struct {
+ verb rune
+ scale int
+}
+
+func (p *parser) Format(s fmt.State, verb rune) {
+ p.verb = verb
+ p.scale = -1
+ if prec, ok := s.Precision(); ok {
+ p.scale = prec
+ }
+}
+
+type message struct {
+ arg int
+ kind int
+ scale int
+ cases []interface{}
+}
+
+const (
+ // Start with non-ASCII to allow skipping values.
+ kindDefault = 0x80 + iota
+ kindScale // verb f, number of fraction digits follows
+ kindScientific // verb e, number of fraction digits follows
+ kindPrecision // verb g, number of significant digits follows
+)
+
+var handle = catmsg.Register("golang.org/x/text/feature/plural:plural", execute)
+
+func (m *message) Compile(e *catmsg.Encoder) error {
+ e.EncodeMessageType(handle)
+
+ e.EncodeUint(uint64(m.arg))
+
+ e.EncodeUint(uint64(m.kind))
+ if m.kind > kindDefault {
+ e.EncodeUint(uint64(m.scale))
+ }
+
+ forms := validForms(cardinal, e.Language())
+
+ for i := 0; i < len(m.cases); {
+ if err := compileSelector(e, forms, m.cases[i]); err != nil {
+ return err
+ }
+ if i++; i >= len(m.cases) {
+ return fmt.Errorf("plural: no message defined for selector %v", m.cases[i-1])
+ }
+ var msg catalog.Message
+ switch x := m.cases[i].(type) {
+ case string:
+ msg = catalog.String(x)
+ case catalog.Message:
+ msg = x
+ default:
+ return fmt.Errorf("plural: message of type %T; must be string or catalog.Message", x)
+ }
+ if err := e.EncodeMessage(msg); err != nil {
+ return err
+ }
+ i++
+ }
+ return nil
+}
+
+func compileSelector(e *catmsg.Encoder, valid []Form, selector interface{}) error {
+ form := Other
+ switch x := selector.(type) {
+ case string:
+ if x == "" {
+ return fmt.Errorf("plural: empty selector")
+ }
+ if c := x[0]; c == '=' || c == '<' {
+ val, err := strconv.ParseUint(x[1:], 10, 16)
+ if err != nil {
+ return fmt.Errorf("plural: invalid number in selector %q: %v", selector, err)
+ }
+ e.EncodeUint(uint64(c))
+ e.EncodeUint(val)
+ return nil
+ }
+ var ok bool
+ form, ok = countMap[x]
+ if !ok {
+ return fmt.Errorf("plural: invalid plural form %q", selector)
+ }
+ case Form:
+ form = x
+ default:
+ return fmt.Errorf("plural: selector of type %T; want string or Form", selector)
+ }
+
+ ok := false
+ for _, f := range valid {
+ if f == form {
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ return fmt.Errorf("plural: form %q not supported for language %q", selector, e.Language())
+ }
+ e.EncodeUint(uint64(form))
+ return nil
+}
+
+func execute(d *catmsg.Decoder) bool {
+ lang := d.Language()
+ argN := int(d.DecodeUint())
+ kind := int(d.DecodeUint())
+ scale := -1 // default
+ if kind > kindDefault {
+ scale = int(d.DecodeUint())
+ }
+ form := Other
+ n := -1
+ if arg := d.Arg(argN); arg == nil {
+ // Default to Other.
+ } else if x, ok := arg.(number.VisibleDigits); ok {
+ d := x.Digits(nil, lang, scale)
+ form, n = cardinal.matchDisplayDigits(lang, &d)
+ } else if x, ok := arg.(Interface); ok {
+ // This covers lists and formatters from the number package.
+ form, n = x.PluralForm(lang, scale)
+ } else {
+ var f number.Formatter
+ switch kind {
+ case kindScale:
+ f.InitDecimal(lang)
+ f.SetScale(scale)
+ case kindScientific:
+ f.InitScientific(lang)
+ f.SetScale(scale)
+ case kindPrecision:
+ f.InitDecimal(lang)
+ f.SetPrecision(scale)
+ case kindDefault:
+ // sensible default
+ f.InitDecimal(lang)
+ if k := reflect.TypeOf(arg).Kind(); reflect.Int <= k && k <= reflect.Uintptr {
+ f.SetScale(0)
+ } else {
+ f.SetScale(2)
+ }
+ }
+ var dec number.Decimal // TODO: buffer in Printer
+ dec.Convert(f.RoundingContext, arg)
+ v := number.FormatDigits(&dec, f.RoundingContext)
+ if !v.NaN && !v.Inf {
+ form, n = cardinal.matchDisplayDigits(d.Language(), &v)
+ }
+ }
+ for !d.Done() {
+ f := d.DecodeUint()
+ if (f == '=' && n == int(d.DecodeUint())) ||
+ (f == '<' && 0 <= n && n < int(d.DecodeUint())) ||
+ form == Form(f) ||
+ Other == Form(f) {
+ return d.ExecuteMessage()
+ }
+ d.SkipMessage()
+ }
+ return false
+}
diff --git a/vendor/golang.org/x/text/feature/plural/message_test.go b/vendor/golang.org/x/text/feature/plural/message_test.go
new file mode 100644
index 000000000..b5bc47e87
--- /dev/null
+++ b/vendor/golang.org/x/text/feature/plural/message_test.go
@@ -0,0 +1,197 @@
+// Copyright 2017 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 plural
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "golang.org/x/text/internal/catmsg"
+ "golang.org/x/text/language"
+ "golang.org/x/text/message/catalog"
+)
+
+func TestSelect(t *testing.T) {
+ lang := language.English
+ type test struct {
+ arg interface{}
+ result string
+ err string
+ }
+ testCases := []struct {
+ desc string
+ msg catalog.Message
+ err string
+ tests []test
+ }{{
+ desc: "basic",
+ msg: Selectf(1, "%d", "one", "foo", "other", "bar"),
+ tests: []test{
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ {arg: 2, result: "bar"},
+ {arg: opposite(1), result: "bar"},
+ {arg: opposite(2), result: "foo"},
+ {arg: "unknown", result: "bar"}, // other
+ },
+ }, {
+ desc: "comparisons",
+ msg: Selectf(1, "%d",
+ "=0", "zero",
+ "=1", "one",
+ "one", "cannot match", // never matches
+ "<5", "<5", // never matches
+ "=5", "=5",
+ Other, "other"),
+ tests: []test{
+ {arg: 0, result: "zero"},
+ {arg: 1, result: "one"},
+ {arg: 2, result: "<5"},
+ {arg: 4, result: "<5"},
+ {arg: 5, result: "=5"},
+ {arg: 6, result: "other"},
+ {arg: "unknown", result: "other"},
+ },
+ }, {
+ desc: "fractions",
+ msg: Selectf(1, "%.2f", "one", "foo", "other", "bar"),
+ tests: []test{
+ // fractions are always plural in english
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "bar"},
+ },
+ }, {
+ desc: "decimal without fractions",
+ msg: Selectf(1, "%.0f", "one", "foo", "other", "bar"),
+ tests: []test{
+ // fractions are always plural in english
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ },
+ }, {
+ desc: "scientific",
+ msg: Selectf(1, "%.0e", "one", "foo", "other", "bar"),
+ tests: []test{
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ },
+ }, {
+ desc: "variable",
+ msg: Selectf(1, "%.1g", "one", "foo", "other", "bar"),
+ tests: []test{
+ // fractions are always plural in english
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ {arg: 2, result: "bar"},
+ },
+ }, {
+ desc: "default",
+ msg: Selectf(1, "", "one", "foo", "other", "bar"),
+ tests: []test{
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ {arg: 2, result: "bar"},
+ {arg: 1.0, result: "bar"},
+ },
+ }, {
+ desc: "nested",
+ msg: Selectf(1, "", "other", Selectf(2, "", "one", "foo", "other", "bar")),
+ tests: []test{
+ {arg: 0, result: "bar"},
+ {arg: 1, result: "foo"},
+ {arg: 2, result: "bar"},
+ },
+ }, {
+ desc: "arg unavailable",
+ msg: Selectf(100, "%.2f", "one", "foo", "other", "bar"),
+ tests: []test{{arg: 1, result: "bar"}},
+ }, {
+ desc: "no match",
+ msg: Selectf(1, "%.2f", "one", "foo"),
+ tests: []test{{arg: 0, result: "bar", err: catmsg.ErrNoMatch.Error()}},
+ }, {
+ desc: "error invalid form",
+ err: `invalid plural form "excessive"`,
+ msg: Selectf(1, "%d", "excessive", "foo"),
+ }, {
+ desc: "error form not used by language",
+ err: `form "many" not supported for language "en"`,
+ msg: Selectf(1, "%d", "many", "foo"),
+ }, {
+ desc: "error invalid selector",
+ err: `selector of type int; want string or Form`,
+ msg: Selectf(1, "%d", 1, "foo"),
+ }, {
+ desc: "error missing message",
+ err: `no message defined for selector one`,
+ msg: Selectf(1, "%d", "one"),
+ }, {
+ desc: "error invalid number",
+ err: `invalid number in selector "<1.00"`,
+ msg: Selectf(1, "%d", "<1.00"),
+ }, {
+ desc: "error empty selector",
+ err: `empty selector`,
+ msg: Selectf(1, "%d", "", "foo"),
+ }, {
+ desc: "error invalid message",
+ err: `message of type int; must be string or catalog.Message`,
+ msg: Selectf(1, "%d", "one", 3),
+ }, {
+ desc: "nested error",
+ err: `empty selector`,
+ msg: Selectf(1, "", "other", Selectf(2, "", "")),
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ data, err := catmsg.Compile(lang, nil, tc.msg)
+ chkError(t, err, tc.err)
+ for _, tx := range tc.tests {
+ t.Run(fmt.Sprint(tx.arg), func(t *testing.T) {
+ r := renderer{arg: tx.arg}
+ d := catmsg.NewDecoder(lang, &r, nil)
+ err := d.Execute(data)
+ chkError(t, err, tx.err)
+ if r.result != tx.result {
+ t.Errorf("got %q; want %q", r.result, tx.result)
+ }
+ })
+ }
+ })
+ }
+}
+
+func chkError(t *testing.T, got error, want string) {
+ if (got == nil && want != "") ||
+ (got != nil && (want == "" || !strings.Contains(got.Error(), want))) {
+ t.Fatalf("got %v; want %v", got, want)
+ }
+ if got != nil {
+ t.SkipNow()
+ }
+}
+
+type renderer struct {
+ arg interface{}
+ result string
+}
+
+func (r *renderer) Render(s string) { r.result += s }
+func (r *renderer) Arg(i int) interface{} {
+ if i > 10 { // Allow testing "arg unavailable" path
+ return nil
+ }
+ return r.arg
+}
+
+type opposite int
+
+func (o opposite) PluralForm(lang language.Tag, scale int) (Form, int) {
+ if o == 1 {
+ return Other, 1
+ }
+ return One, int(o)
+}
diff --git a/vendor/golang.org/x/text/feature/plural/plural.go b/vendor/golang.org/x/text/feature/plural/plural.go
index 2b4cfe321..61faf187d 100644
--- a/vendor/golang.org/x/text/feature/plural/plural.go
+++ b/vendor/golang.org/x/text/feature/plural/plural.go
@@ -13,6 +13,7 @@
package plural
import (
+ "golang.org/x/text/internal/number"
"golang.org/x/text/language"
)
@@ -109,21 +110,25 @@ func getIntApprox(digits []byte, start, end, nMod, big int) (n int) {
// 123 []byte{1, 2, 3} 3 0
// 123.4 []byte{1, 2, 3, 4} 3 1
// 123.40 []byte{1, 2, 3, 4} 3 2
-// 100000 []byte{1} 6......0
-// 100000.00 []byte{1} 6......3
+// 100000 []byte{1} 6 0
+// 100000.00 []byte{1} 6 3
func (p *Rules) MatchDigits(t language.Tag, digits []byte, exp, scale int) Form {
index, _ := language.CompactIndex(t)
- endN := len(digits) + exp
// Differentiate up to including mod 1000000 for the integer part.
- n := getIntApprox(digits, 0, endN, 6, 1000000)
+ n := getIntApprox(digits, 0, exp, 6, 1000000)
// Differentiate up to including mod 100 for the fractional part.
- f := getIntApprox(digits, endN, endN+scale, 2, 100)
+ f := getIntApprox(digits, exp, exp+scale, 2, 100)
return matchPlural(p, index, n, f, scale)
}
+func (p *Rules) matchDisplayDigits(t language.Tag, d *number.Digits) (Form, int) {
+ n := getIntApprox(d.Digits, 0, int(d.Exp), 6, 1000000)
+ return p.MatchDigits(t, d.Digits, int(d.Exp), d.NumFracDigits()), n
+}
+
func validForms(p *Rules, t language.Tag) (forms []Form) {
index, _ := language.CompactIndex(t)
offset := p.langToIndex[index]
@@ -145,6 +150,25 @@ func (p *Rules) matchComponents(t language.Tag, n, f, scale int) Form {
return matchPlural(p, index, n, f, scale)
}
+// MatchPlural returns the plural form for the given language and plural
+// operands (as defined in
+// http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules):
+// where
+// n absolute value of the source number (integer and decimals)
+// input
+// i integer digits of n.
+// v number of visible fraction digits in n, with trailing zeros.
+// w number of visible fraction digits in n, without trailing zeros.
+// f visible fractional digits in n, with trailing zeros (f = t * 10^(v-w))
+// t visible fractional digits in n, without trailing zeros.
+//
+// If any of the operand values is too large to fit in an int, it is okay to
+// pass the value modulo 10,000,000.
+func (p *Rules) MatchPlural(lang language.Tag, i, v, w, f, t int) Form {
+ index, _ := language.CompactIndex(lang)
+ return matchPlural(p, index, i, f, v)
+}
+
func matchPlural(p *Rules, index int, n, f, v int) Form {
nMask := p.inclusionMasks[n%maxMod]
// Compute the fMask inline in the rules below, as it is relatively rare.
diff --git a/vendor/golang.org/x/text/feature/plural/plural_test.go b/vendor/golang.org/x/text/feature/plural/plural_test.go
index e5524c59a..b3cf4c449 100644
--- a/vendor/golang.org/x/text/feature/plural/plural_test.go
+++ b/vendor/golang.org/x/text/feature/plural/plural_test.go
@@ -28,6 +28,8 @@ func TestGetIntApprox(t *testing.T) {
{"123", 0, 2, 2, 12},
{"123", 3, 4, 2, 0},
{"12345", 3, 4, 2, 4},
+ {"40", 0, 1, 2, 4},
+ {"1", 0, 7, 2, big},
{"123", 0, 5, 2, big},
{"123", 0, 5, 3, big},
@@ -114,7 +116,7 @@ func testPlurals(t *testing.T, p *Rules, testCases []pluralTest) {
for i := range digits {
digits[i] -= '0'
}
- if f := p.MatchDigits(tag, digits, 0, 0); f != Form(tc.form) {
+ if f := p.MatchDigits(tag, digits, len(digits), 0); f != Form(tc.form) {
t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
}
})
@@ -139,14 +141,25 @@ func testPlurals(t *testing.T, p *Rules, testCases []pluralTest) {
num := fmt.Sprintf("%[1]d.%0[3]*[2]d", n/m, n%m, scale)
name := fmt.Sprintf("%s:dec(%s)", loc, num)
t.Run(name, func(t *testing.T) {
+ ff := n % m
+ tt := ff
+ w := scale
+ for tt > 0 && tt%10 == 0 {
+ w--
+ tt /= 10
+ }
+ if f := p.MatchPlural(tag, n/m, scale, w, ff, tt); f != Form(tc.form) {
+ t.Errorf("MatchPlural: got %v; want %v", f, Form(tc.form))
+ }
if f := p.matchComponents(tag, n/m, n%m, scale); f != Form(tc.form) {
t.Errorf("matchComponents: got %v; want %v", f, Form(tc.form))
}
+ exp := strings.IndexByte(num, '.')
digits := []byte(strings.Replace(num, ".", "", 1))
for i := range digits {
digits[i] -= '0'
}
- if f := p.MatchDigits(tag, digits, -scale, scale); f != Form(tc.form) {
+ if f := p.MatchDigits(tag, digits, exp, scale); f != Form(tc.form) {
t.Errorf("MatchDigits: got %v; want %v", f, Form(tc.form))
}
})