number: add plural integration
also added consideration to use only
VisibleDigits interface instead of
plural.Interface.
Change-Id: Ibb2a9a0eddc3f6fe7689b7fa3268f2bf0c8a97dc
Reviewed-on: https://go-review.googlesource.com/60771
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/feature/plural/message.go b/feature/plural/message.go
index 22f5a26..f931f8a 100755
--- a/feature/plural/message.go
+++ b/feature/plural/message.go
@@ -16,6 +16,9 @@
"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
@@ -193,6 +196,9 @@
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)
diff --git a/feature/plural/message_test.go b/feature/plural/message_test.go
index 1a89bd5..b5bc47e 100644
--- a/feature/plural/message_test.go
+++ b/feature/plural/message_test.go
@@ -65,7 +65,7 @@
},
}, {
desc: "decimal without fractions",
- msg: Selectf(1, "%.of", "one", "foo", "other", "bar"),
+ msg: Selectf(1, "%.0f", "one", "foo", "other", "bar"),
tests: []test{
// fractions are always plural in english
{arg: 0, result: "bar"},
diff --git a/internal/number/format.go b/internal/number/format.go
index 0a5ffb5..910bdeb 100755
--- a/internal/number/format.go
+++ b/internal/number/format.go
@@ -16,6 +16,13 @@
// - allow user-defined superscript notation (such as <sup>4</sup>)
// - same for non-breaking spaces, like
+// A VisibleDigits computes digits, comma placement and trailing zeros as they
+// will be shown to the user.
+type VisibleDigits interface {
+ Digits(buf []byte, t language.Tag, scale int) Digits
+ // TODO: Do we also need to add the verb or pass a format.State?
+}
+
// Formatting proceeds along the following lines:
// 0) Compose rounding information from format and context.
// 1) Convert a number into a Decimal.
diff --git a/number/doc.go b/number/doc.go
index 97088d1..2ad8d43 100644
--- a/number/doc.go
+++ b/number/doc.go
@@ -22,7 +22,6 @@
// p.Printf("There are %v bikes per household.", number.Decimal(1.2))
// // Prints: Er zijn 1,2 fietsen per huishouden.
//
-// Provided that the printed translation is available.
//
// The width and scale specified in the formatting directives override the
// configuration of the formatter.
diff --git a/number/format.go b/number/format.go
index 62af651..1c3d41b 100755
--- a/number/format.go
+++ b/number/format.go
@@ -104,3 +104,19 @@
d.Convert(p.RoundingContext, f.value)
state.Write(p.Format(nil, &d))
}
+
+// Digits returns information about which logical digits will be presented to
+// the user. This information is relevant, for instance, to determine plural
+// forms.
+func (f Formatter) Digits(buf []byte, tag language.Tag, scale int) number.Digits {
+ var p number.Formatter
+ f.initFunc(&p, tag)
+ if scale >= 0 {
+ // TODO: this only works well for decimal numbers, which is generally
+ // fine.
+ p.SetScale(scale)
+ }
+ var d number.Decimal
+ d.Convert(p.RoundingContext, f.value)
+ return number.FormatDigits(&d, p.RoundingContext)
+}
diff --git a/number/format_test.go b/number/format_test.go
index 30be72d..3c67c5d 100644
--- a/number/format_test.go
+++ b/number/format_test.go
@@ -5,8 +5,10 @@
package number
import (
+ "fmt"
"testing"
+ "golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
@@ -43,3 +45,63 @@
})
}
}
+
+func TestDigits(t *testing.T) {
+ testCases := []struct {
+ f Formatter
+ scale int
+ want string
+ }{{
+ f: Decimal(3),
+ scale: 0,
+ want: "digits:[3] exp:1 comma:0 end:1",
+ }, {
+ f: Decimal(3.1),
+ scale: 0,
+ want: "digits:[3] exp:1 comma:0 end:1",
+ }, {
+ f: Scientific(3.1),
+ scale: 0,
+ want: "digits:[3] exp:1 comma:1 end:1",
+ }, {
+ f: Scientific(3.1),
+ scale: 3,
+ want: "digits:[3 1] exp:1 comma:1 end:4",
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ d := tc.f.Digits(nil, language.Croatian, tc.scale)
+ got := fmt.Sprintf("digits:%d exp:%d comma:%d end:%d", d.Digits, d.Exp, d.Comma, d.End)
+ if got != tc.want {
+ t.Errorf("got %v; want %v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestPluralIntegration(t *testing.T) {
+ testCases := []struct {
+ f Formatter
+ want string
+ }{{
+ f: Decimal(1),
+ want: "one: 1",
+ }, {
+ f: Decimal(5),
+ want: "other: 5",
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ message.Set(language.English, "num %f", plural.Selectf(1, "%f",
+ "one", "one: %f",
+ "other", "other: %f"))
+
+ p := message.NewPrinter(language.English)
+
+ got := p.Sprintf("num %f", tc.f)
+ if got != tc.want {
+ t.Errorf("got %v; want %v", got, tc.want)
+ }
+ })
+ }
+}
diff --git a/number/number.go b/number/number.go
index 0a6e62f..f5ca93b 100755
--- a/number/number.go
+++ b/number/number.go
@@ -21,14 +21,14 @@
scientificVerbs = "veg"
)
-// Decimal represents a number as a floating point decimal.
+// Decimal formats a number as a floating point decimal.
func Decimal(x interface{}, opts ...Option) Formatter {
return newFormatter(decimalOptions, opts, x)
}
var decimalOptions = newOptions(decimalVerbs, (*number.Formatter).InitDecimal)
-// Scientific prints a values in scientific format.
+// Scientific formats a number in scientific format.
func Scientific(x interface{}, opts ...Option) Formatter {
return newFormatter(scientificOptions, opts, x)
}
diff --git a/number/number_test.go b/number/number_test.go
index f380b7e..96b1acb 100644
--- a/number/number_test.go
+++ b/number/number_test.go
@@ -80,11 +80,11 @@
want: " 123",
}, {
desc: "format width pad option before",
- f: Decimal(123, PadRune('*'), FormatWidth(10)),
+ f: Decimal(123, Pad('*'), FormatWidth(10)),
want: "*******123",
}, {
desc: "format width pad option after",
- f: Decimal(123, FormatWidth(10), PadRune('*')),
+ f: Decimal(123, FormatWidth(10), Pad('*')),
want: "*******123",
}, {
desc: "format width illegal",
diff --git a/number/option.go b/number/option.go
index 2e73525..de96f8e 100644
--- a/number/option.go
+++ b/number/option.go
@@ -48,7 +48,7 @@
}
}
-// MaxFractionDigits specifies the maximum number of digits after the comma.
+// MaxFractionDigits specifies the maximum number of fractional digits.
func MaxFractionDigits(max int) Option {
return func(t language.Tag, f *number.Formatter) {
if max >= 1<<15 {
@@ -58,7 +58,7 @@
}
}
-// MinFractionDigits specifies the minimum number of digits after the comma.
+// MinFractionDigits specifies the minimum number of fractional digits.
func MinFractionDigits(min int) Option {
return func(t language.Tag, f *number.Formatter) {
if min >= 1<<8 {
@@ -161,8 +161,8 @@
}
}
-// PadRune sets the rune to be used for filling up to the format width.
-func PadRune(r rune) Option {
+// Pad sets the rune to be used for filling up to the format width.
+func Pad(r rune) Option {
return func(t language.Tag, f *number.Formatter) {
f.PadRune = r
}