mirror of
https://github.com/kataras/iris.git
synced 2026-01-08 04:21:57 +00:00
New: i18n pluralization and variables support and more...
fixes: #1649, #1648, #1641, #1650 relative to: #1597
This commit is contained in:
261
i18n/internal/plural.go
Normal file
261
i18n/internal/plural.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
|
||||
"golang.org/x/text/feature/plural"
|
||||
"golang.org/x/text/message"
|
||||
"golang.org/x/text/message/catalog"
|
||||
)
|
||||
|
||||
// PluralCounter if completes by an input argument of a message to render,
|
||||
// then the plural renderer will resolve the plural count
|
||||
// and any variables' counts. This is useful when the data is not a type of Map or integers.
|
||||
type PluralCounter interface {
|
||||
// PluralCount returns the plural count of the message.
|
||||
// If returns -1 then this is not a valid plural message.
|
||||
PluralCount() int
|
||||
// VarCount should return the variable count, based on the variable name.
|
||||
VarCount(name string) int
|
||||
}
|
||||
|
||||
// PluralMessage holds the registered Form and the corresponding Renderer.
|
||||
// It is used on the `Message.AddPlural` method.
|
||||
type PluralMessage struct {
|
||||
Form PluralForm
|
||||
Renderer Renderer
|
||||
}
|
||||
|
||||
type independentPluralRenderer struct {
|
||||
key string
|
||||
printer *message.Printer
|
||||
}
|
||||
|
||||
func newIndependentPluralRenderer(c *Catalog, loc *Locale, key string, msgs ...catalog.Message) (Renderer, error) {
|
||||
builder := catalog.NewBuilder(catalog.Fallback(c.Locales[0].tag))
|
||||
if err := builder.Set(loc.tag, key, msgs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
printer := message.NewPrinter(loc.tag, message.Catalog(builder))
|
||||
return &independentPluralRenderer{key, printer}, nil
|
||||
}
|
||||
|
||||
func (m *independentPluralRenderer) Render(args ...interface{}) (string, error) {
|
||||
return m.printer.Sprintf(m.key, args...), nil
|
||||
}
|
||||
|
||||
// A PluralFormDecoder should report and return whether
|
||||
// a specific "key" is a plural one. This function
|
||||
// can be implemented and set on the `Options` to customize
|
||||
// the plural forms and their behavior in general.
|
||||
//
|
||||
// See the `DefaultPluralFormDecoder` package-level
|
||||
// variable for the default implementation one.
|
||||
type PluralFormDecoder func(loc context.Locale, key string) (PluralForm, bool)
|
||||
|
||||
// DefaultPluralFormDecoder is the default `PluralFormDecoder`.
|
||||
// Supprots "zero", "one", "two", "other", "=x", "<x", ">x".
|
||||
var DefaultPluralFormDecoder = func(_ context.Locale, key string) (PluralForm, bool) {
|
||||
if isDefaultPluralForm(key) {
|
||||
return pluralForm(key), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func isDefaultPluralForm(s string) bool {
|
||||
switch s {
|
||||
case "zero", "one", "two", "other":
|
||||
return true
|
||||
default:
|
||||
if len(s) > 1 {
|
||||
ch := s[0]
|
||||
if ch == '=' || ch == '<' || ch == '>' {
|
||||
if isDigit(s[1]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// A PluralForm is responsible to decode
|
||||
// locale keys to plural forms and match plural forms
|
||||
// based on the given pluralCount.
|
||||
//
|
||||
// See `pluralForm` package-level type for a default implementation.
|
||||
type PluralForm interface {
|
||||
String() string
|
||||
// the string is a verified plural case's raw string value.
|
||||
// Field for priority on which order to register the plural cases.
|
||||
Less(next PluralForm) bool
|
||||
MatchPlural(pluralCount int) bool
|
||||
}
|
||||
|
||||
type pluralForm string
|
||||
|
||||
func (f pluralForm) String() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
func (f pluralForm) Less(next PluralForm) bool {
|
||||
form1 := f.String()
|
||||
form2 := next.String()
|
||||
|
||||
// Order by
|
||||
// - equals,
|
||||
// - less than
|
||||
// - greater than
|
||||
// - "zero", "one", "two"
|
||||
// - rest is last "other".
|
||||
dig1, typ1, hasDig1 := formAtoi(form1)
|
||||
if typ1 == eq {
|
||||
return true
|
||||
}
|
||||
|
||||
dig2, typ2, hasDig2 := formAtoi(form2)
|
||||
if typ2 == eq {
|
||||
return false
|
||||
}
|
||||
|
||||
// digits smaller, number.
|
||||
if hasDig1 {
|
||||
return !hasDig2 || dig1 < dig2
|
||||
}
|
||||
|
||||
if hasDig2 {
|
||||
return false
|
||||
}
|
||||
|
||||
if form1 == "other" {
|
||||
return false // other go to last.
|
||||
}
|
||||
|
||||
if form2 == "other" {
|
||||
return true
|
||||
}
|
||||
|
||||
if form1 == "zero" {
|
||||
return true
|
||||
}
|
||||
|
||||
if form2 == "zero" {
|
||||
return false
|
||||
}
|
||||
|
||||
if form1 == "one" {
|
||||
return true
|
||||
}
|
||||
|
||||
if form2 == "one" {
|
||||
return false
|
||||
}
|
||||
|
||||
if form1 == "two" {
|
||||
return true
|
||||
}
|
||||
|
||||
if form2 == "two" {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (f pluralForm) MatchPlural(pluralCount int) bool {
|
||||
switch f {
|
||||
case "other":
|
||||
return true
|
||||
case "=0", "zero":
|
||||
return pluralCount == 0
|
||||
case "=1", "one":
|
||||
return pluralCount == 1
|
||||
case "=2", "two":
|
||||
return pluralCount == 2
|
||||
default:
|
||||
// <5 or =5
|
||||
|
||||
n, typ, ok := formAtoi(string(f))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case eq:
|
||||
return n == pluralCount
|
||||
case lt:
|
||||
return pluralCount < n
|
||||
case gt:
|
||||
return pluralCount > n
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeSelectfVars(text string, vars []Var, insidePlural bool) ([]catalog.Message, []Var) {
|
||||
newVars := sortVars(text, vars)
|
||||
newVars = removeVarsDuplicates(newVars)
|
||||
msgs := selectfVars(newVars, insidePlural)
|
||||
return msgs, newVars
|
||||
}
|
||||
|
||||
func selectfVars(vars []Var, insidePlural bool) []catalog.Message {
|
||||
msgs := make([]catalog.Message, 0, len(vars))
|
||||
for _, variable := range vars {
|
||||
argth := variable.Argth
|
||||
if insidePlural {
|
||||
argth++
|
||||
}
|
||||
|
||||
msg := catalog.Var(variable.Name, plural.Selectf(argth, variable.Format, variable.Cases...))
|
||||
// fmt.Printf("%s:%d | cases | %#+v\n", variable.Name, variable.Argth, variable.Cases)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
const (
|
||||
eq uint8 = iota + 1
|
||||
lt
|
||||
gt
|
||||
)
|
||||
|
||||
func formType(ch byte) uint8 {
|
||||
switch ch {
|
||||
case '=':
|
||||
return eq
|
||||
case '<':
|
||||
return lt
|
||||
case '>':
|
||||
return gt
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func formAtoi(form string) (int, uint8, bool) {
|
||||
if len(form) < 2 {
|
||||
return -1, 0, false
|
||||
}
|
||||
|
||||
typ := formType(form[0])
|
||||
if typ == 0 {
|
||||
return -1, 0, false
|
||||
}
|
||||
|
||||
dig, err := strconv.Atoi(form[1:])
|
||||
if err != nil {
|
||||
return -1, 0, false
|
||||
}
|
||||
return dig, typ, true
|
||||
}
|
||||
|
||||
func isDigit(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
Reference in New Issue
Block a user