// 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" "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 " 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 }