plural.go (5420B)
1 package internal 2 3 import ( 4 "strconv" 5 6 "golang.org/x/text/feature/plural" 7 "golang.org/x/text/message" 8 "golang.org/x/text/message/catalog" 9 ) 10 11 // PluralCounter if completes by an input argument of a message to render, 12 // then the plural renderer will resolve the plural count 13 // and any variables' counts. This is useful when the data is not a type of Map or integers. 14 type PluralCounter interface { 15 // PluralCount returns the plural count of the message. 16 // If returns -1 then this is not a valid plural message. 17 PluralCount() int 18 // VarCount should return the variable count, based on the variable name. 19 VarCount(name string) int 20 } 21 22 // PluralMessage holds the registered Form and the corresponding Renderer. 23 // It is used on the `Message.AddPlural` method. 24 type PluralMessage struct { 25 Form PluralForm 26 Renderer Renderer 27 } 28 29 type independentPluralRenderer struct { 30 key string 31 printer *message.Printer 32 } 33 34 func newIndependentPluralRenderer(c *Catalog, loc *Locale, key string, msgs ...catalog.Message) (Renderer, error) { 35 builder := catalog.NewBuilder(catalog.Fallback(c.Locales[0].tag)) 36 if err := builder.Set(loc.tag, key, msgs...); err != nil { 37 return nil, err 38 } 39 printer := message.NewPrinter(loc.tag, message.Catalog(builder)) 40 return &independentPluralRenderer{key, printer}, nil 41 } 42 43 func (m *independentPluralRenderer) Render(args ...interface{}) (string, error) { 44 return m.printer.Sprintf(m.key, args...), nil 45 } 46 47 // A PluralFormDecoder should report and return whether 48 // a specific "key" is a plural one. This function 49 // can be implemented and set on the `Options` to customize 50 // the plural forms and their behavior in general. 51 // 52 // See the `DefaultPluralFormDecoder` package-level 53 // variable for the default implementation one. 54 type PluralFormDecoder func(loc *Locale, key string) (PluralForm, bool) 55 56 // DefaultPluralFormDecoder is the default `PluralFormDecoder`. 57 // Supprots "zero", "one", "two", "other", "=x", "<x", ">x". 58 var DefaultPluralFormDecoder = func(_ *Locale, key string) (PluralForm, bool) { 59 if isDefaultPluralForm(key) { 60 return pluralForm(key), true 61 } 62 63 return nil, false 64 } 65 66 func isDefaultPluralForm(s string) bool { 67 switch s { 68 case "zero", "one", "two", "other": 69 return true 70 default: 71 if len(s) > 1 { 72 ch := s[0] 73 if ch == '=' || ch == '<' || ch == '>' { 74 if isDigit(s[1]) { 75 return true 76 } 77 } 78 } 79 80 return false 81 } 82 } 83 84 // A PluralForm is responsible to decode 85 // locale keys to plural forms and match plural forms 86 // based on the given pluralCount. 87 // 88 // See `pluralForm` package-level type for a default implementation. 89 type PluralForm interface { 90 String() string 91 // the string is a verified plural case's raw string value. 92 // Field for priority on which order to register the plural cases. 93 Less(next PluralForm) bool 94 MatchPlural(pluralCount int) bool 95 } 96 97 type pluralForm string 98 99 func (f pluralForm) String() string { 100 return string(f) 101 } 102 103 func (f pluralForm) Less(next PluralForm) bool { 104 form1 := f.String() 105 form2 := next.String() 106 107 // Order by 108 // - equals, 109 // - less than 110 // - greater than 111 // - "zero", "one", "two" 112 // - rest is last "other". 113 dig1, typ1, hasDig1 := formAtoi(form1) 114 if typ1 == eq { 115 return true 116 } 117 118 dig2, typ2, hasDig2 := formAtoi(form2) 119 if typ2 == eq { 120 return false 121 } 122 123 // digits smaller, number. 124 if hasDig1 { 125 return !hasDig2 || dig1 < dig2 126 } 127 128 if hasDig2 { 129 return false 130 } 131 132 if form1 == "other" { 133 return false // other go to last. 134 } 135 136 if form2 == "other" { 137 return true 138 } 139 140 if form1 == "zero" { 141 return true 142 } 143 144 if form2 == "zero" { 145 return false 146 } 147 148 if form1 == "one" { 149 return true 150 } 151 152 if form2 == "one" { 153 return false 154 } 155 156 if form1 == "two" { 157 return true 158 } 159 160 if form2 == "two" { 161 return false 162 } 163 164 return false 165 } 166 167 func (f pluralForm) MatchPlural(pluralCount int) bool { 168 switch f { 169 case "other": 170 return true 171 case "=0", "zero": 172 return pluralCount == 0 173 case "=1", "one": 174 return pluralCount == 1 175 case "=2", "two": 176 return pluralCount == 2 177 default: 178 // <5 or =5 179 180 n, typ, ok := formAtoi(string(f)) 181 if !ok { 182 return false 183 } 184 185 switch typ { 186 case eq: 187 return n == pluralCount 188 case lt: 189 return pluralCount < n 190 case gt: 191 return pluralCount > n 192 default: 193 return false 194 } 195 } 196 } 197 198 func makeSelectfVars(text string, vars []Var, insidePlural bool) ([]catalog.Message, []Var) { 199 newVars := sortVars(text, vars) 200 newVars = removeVarsDuplicates(newVars) 201 msgs := selectfVars(newVars, insidePlural) 202 return msgs, newVars 203 } 204 205 func selectfVars(vars []Var, insidePlural bool) []catalog.Message { 206 msgs := make([]catalog.Message, 0, len(vars)) 207 for _, variable := range vars { 208 argth := variable.Argth 209 if insidePlural { 210 argth++ 211 } 212 213 msg := catalog.Var(variable.Name, plural.Selectf(argth, variable.Format, variable.Cases...)) 214 // fmt.Printf("%s:%d | cases | %#+v\n", variable.Name, variable.Argth, variable.Cases) 215 msgs = append(msgs, msg) 216 } 217 218 return msgs 219 } 220 221 const ( 222 eq uint8 = iota + 1 223 lt 224 gt 225 ) 226 227 func formType(ch byte) uint8 { 228 switch ch { 229 case '=': 230 return eq 231 case '<': 232 return lt 233 case '>': 234 return gt 235 } 236 237 return 0 238 } 239 240 func formAtoi(form string) (int, uint8, bool) { 241 if len(form) < 2 { 242 return -1, 0, false 243 } 244 245 typ := formType(form[0]) 246 if typ == 0 { 247 return -1, 0, false 248 } 249 250 dig, err := strconv.Atoi(form[1:]) 251 if err != nil { 252 return -1, 0, false 253 } 254 return dig, typ, true 255 } 256 257 func isDigit(ch byte) bool { 258 return '0' <= ch && ch <= '9' 259 }