From 2b187d2882c6249b7bc21620c39566a4b176e3fd Mon Sep 17 00:00:00 2001
From: Andrew Thornton
Date: Tue, 7 Jun 2022 19:27:51 +0100
Subject: [PATCH 01/15] Add translatable interface
Signed-off-by: Andrew Thornton
---
modules/translation/i18n/i18n.go | 9 ++++++
modules/translation/i18n/translatable.go | 40 ++++++++++++++++++++++++
2 files changed, 49 insertions(+)
create mode 100644 modules/translation/i18n/translatable.go
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go
index 664e457ecf79f..842f4bb858840 100644
--- a/modules/translation/i18n/i18n.go
+++ b/modules/translation/i18n/i18n.go
@@ -128,8 +128,17 @@ func (l *locale) Tr(trKey string, trArgs ...interface{}) string {
fmtArgs = append(fmtArgs, arg)
}
}
+ for i, arg := range fmtArgs {
+ switch val := arg.(type) {
+ case TranslatableFormatted:
+ fmtArgs[i] = formatWrapper{l: l, t: val}
+ case TranslatableString:
+ fmtArgs[i] = stringWrapper{l: l, t: val}
+ }
+ }
return fmt.Sprintf(trMsg, fmtArgs...)
}
+
return trMsg
}
diff --git a/modules/translation/i18n/translatable.go b/modules/translation/i18n/translatable.go
new file mode 100644
index 0000000000000..2392a41a90a4f
--- /dev/null
+++ b/modules/translation/i18n/translatable.go
@@ -0,0 +1,40 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package i18n
+
+import "fmt"
+
+// Locale represents an interface to translation
+type Locale interface {
+ Tr(string, ...interface{}) string
+}
+
+// TranslatableFormatted structs provide their own translated string when formatted in translation
+type TranslatableFormatted interface {
+ TranslatedFormat(l Locale, s fmt.State, c rune)
+}
+
+// TranslatableString structs provide their own translated string when formatted as a string in translation
+type TranslatableString interface {
+ TranslatedString(l Locale) string
+}
+
+type formatWrapper struct {
+ l Locale
+ t TranslatableFormatted
+}
+
+func (f formatWrapper) Format(s fmt.State, c rune) {
+ f.t.TranslatedFormat(f.l, s, c)
+}
+
+type stringWrapper struct {
+ l Locale
+ t TranslatableString
+}
+
+func (s stringWrapper) String() string {
+ return s.t.TranslatedString(s.l)
+}
From 897f7aa10bd2494af604ec7235f6b4ab3068fc47 Mon Sep 17 00:00:00 2001
From: Andrew Thornton
Date: Tue, 7 Jun 2022 19:28:21 +0100
Subject: [PATCH 02/15] Add plural support using CLDR data
Signed-off-by: Andrew Thornton
---
.../i18n/common/dtd/ldmlSupplemental.dtd | 1307 +++++++++++++++++
modules/translation/i18n/i18n.go | 65 +-
modules/translation/i18n/plurals/form.go | 21 +
.../i18n/plurals/generate/condition_parser.go | 149 ++
.../i18n/plurals/generate/main/generate.go | 184 +++
.../i18n/plurals/generate/ordinals.xml | 166 +++
.../i18n/plurals/generate/plurals.xml | 253 ++++
.../translation/i18n/plurals/generate/xml.go | 139 ++
modules/translation/i18n/plurals/operands.go | 192 +++
modules/translation/i18n/plurals/rule_test.go | 127 ++
modules/translation/i18n/plurals/rules.go | 78 +
modules/translation/i18n/plurals/rules_gen.go | 1001 +++++++++++++
.../i18n/plurals/rules_gen_test.go | 1083 ++++++++++++++
.../translation/i18n/plurals/rules_test.go | 81 +
modules/translation/i18n/translatable.go | 9 +-
15 files changed, 4845 insertions(+), 10 deletions(-)
create mode 100644 modules/translation/i18n/common/dtd/ldmlSupplemental.dtd
create mode 100644 modules/translation/i18n/plurals/form.go
create mode 100644 modules/translation/i18n/plurals/generate/condition_parser.go
create mode 100644 modules/translation/i18n/plurals/generate/main/generate.go
create mode 100644 modules/translation/i18n/plurals/generate/ordinals.xml
create mode 100644 modules/translation/i18n/plurals/generate/plurals.xml
create mode 100644 modules/translation/i18n/plurals/generate/xml.go
create mode 100644 modules/translation/i18n/plurals/operands.go
create mode 100644 modules/translation/i18n/plurals/rule_test.go
create mode 100644 modules/translation/i18n/plurals/rules.go
create mode 100644 modules/translation/i18n/plurals/rules_gen.go
create mode 100644 modules/translation/i18n/plurals/rules_gen_test.go
create mode 100644 modules/translation/i18n/plurals/rules_test.go
diff --git a/modules/translation/i18n/common/dtd/ldmlSupplemental.dtd b/modules/translation/i18n/common/dtd/ldmlSupplemental.dtd
new file mode 100644
index 0000000000000..72afe4b5c4cac
--- /dev/null
+++ b/modules/translation/i18n/common/dtd/ldmlSupplemental.dtd
@@ -0,0 +1,1307 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go
index 842f4bb858840..3e93719d71f66 100644
--- a/modules/translation/i18n/i18n.go
+++ b/modules/translation/i18n/i18n.go
@@ -11,6 +11,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/translation/i18n/plurals"
"gopkg.in/ini.v1"
)
@@ -22,10 +23,11 @@ var (
)
type locale struct {
- store *LocaleStore
- langName string
- langDesc string
- messages *ini.File
+ store *LocaleStore
+ langName string
+ langDesc string
+ messages *ini.File
+ pluralRules map[plurals.RuleType]*plurals.Rule
}
type LocaleStore struct {
@@ -34,10 +36,11 @@ type LocaleStore struct {
langDescs []string
localeMap map[string]*locale
defaultLang string
+ pluralRules plurals.Rules
}
func NewLocaleStore() *LocaleStore {
- return &LocaleStore{localeMap: make(map[string]*locale)}
+ return &LocaleStore{localeMap: make(map[string]*locale), pluralRules: plurals.DefaultRules()}
}
// AddLocaleByIni adds locale by ini into the store
@@ -52,6 +55,12 @@ func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile inte
if err == nil {
iniFile.BlockMode = false
lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile}
+ lc.pluralRules = map[plurals.RuleType]*plurals.Rule{}
+ for typ, ruleMap := range ls.pluralRules {
+ rule := ruleMap[lc.langName]
+ lc.pluralRules[typ] = rule
+ }
+
ls.langNames = append(ls.langNames, lc.langName)
ls.langDescs = append(ls.langDescs, lc.langDesc)
ls.localeMap[lc.langName] = lc
@@ -132,7 +141,7 @@ func (l *locale) Tr(trKey string, trArgs ...interface{}) string {
switch val := arg.(type) {
case TranslatableFormatted:
fmtArgs[i] = formatWrapper{l: l, t: val}
- case TranslatableString:
+ case TranslatableStringer:
fmtArgs[i] = stringWrapper{l: l, t: val}
}
}
@@ -142,6 +151,50 @@ func (l *locale) Tr(trKey string, trArgs ...interface{}) string {
return trMsg
}
+func (l *locale) HasMessage(key string) bool {
+ var section string
+
+ idx := strings.IndexByte(key, '.')
+ if idx > 0 {
+ section = key[:idx]
+ key = key[idx+1:]
+ }
+
+ return l.messages.Section(section).HasKey(key)
+}
+
+func (l *locale) TrOrdinal(cnt interface{}, key string, args ...interface{}) string {
+ return l.TrPlurals(cnt, string(plurals.Ordinal), key, args...)
+}
+
+func (l *locale) TrPlural(cnt interface{}, key string, args ...interface{}) string {
+ return l.TrPlurals(cnt, string(plurals.Cardinal), key, args...)
+}
+
+func (l *locale) TrPlurals(cnt interface{}, ruleType, key string, args ...interface{}) string {
+ operands, err := plurals.NewOperands(cnt)
+ if err != nil {
+ // if we fail to parse fall back to the standard
+ return l.Tr(key, args...)
+ }
+
+ rule := l.pluralRules[plurals.RuleType(ruleType)]
+ if rule == nil {
+ // if we fail to parse fall back to the standard
+ return l.Tr(key, args...)
+ }
+
+ form := rule.PluralFormFunc(operands)
+
+ if form != plurals.Other && l.HasMessage(key+"_"+string(form)) {
+ // use this pluralized key
+ return l.Tr(key+"_"+string(form), args...)
+ }
+
+ // use the key as is
+ return l.Tr(key, args...)
+}
+
func ResetDefaultLocales() {
DefaultLocales = NewLocaleStore()
}
diff --git a/modules/translation/i18n/plurals/form.go b/modules/translation/i18n/plurals/form.go
new file mode 100644
index 0000000000000..575c52b45057f
--- /dev/null
+++ b/modules/translation/i18n/plurals/form.go
@@ -0,0 +1,21 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package plurals
+
+// Form represents a language pluralization form as defined here:
+// http://cldr.unicode.org/index/cldr-spec/plural-rules
+type Form string
+
+const (
+ Invalid Form = ""
+ Zero Form = "zero"
+ One Form = "one"
+ Two Form = "two"
+ Few Form = "few"
+ Many Form = "many"
+ Other Form = "other"
+)
diff --git a/modules/translation/i18n/plurals/generate/condition_parser.go b/modules/translation/i18n/plurals/generate/condition_parser.go
new file mode 100644
index 0000000000000..a5c20fe63a2bb
--- /dev/null
+++ b/modules/translation/i18n/plurals/generate/condition_parser.go
@@ -0,0 +1,149 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package generate
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// As noted above relation is a lot simpler than the original full rules imply:
+//
+// relation = expr ('=' | '!=') range_list
+// expr = operand ('%' value)?
+// operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' | 'e'
+//
+var relationRegexp = regexp.MustCompile(`([nieftvw])(?:\s*%\s*([0-9]+))?\s*(!=|=)(.*)`)
+
+// ConditionToGoString converts a CLDR plural rules to Go code.
+// See http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax
+func ConditionToGoString(condition string) string {
+ // the BNF does not allow for ors to be inside ands
+ // so a simple recursion will work
+
+ // condition = and_condition ('or' and_condition)*
+ var parsedOrConditions []string
+ for _, andCondition := range strings.Split(condition, "or") {
+ andCondition = strings.TrimSpace(andCondition)
+ if andCondition == "" {
+ continue
+ }
+ var parsedAndConditions []string
+
+ // again the BNF does not allow for ands to be inside relations
+ // so a simple recursion will work
+
+ // and_condition = relation ('and' relation)*
+ for _, relation := range strings.Split(andCondition, "and") {
+
+ // Now although the full BNF allows for a more a complex set of relations
+ // the files we are interested in are much simpler and their restricted BNF is
+ // as below
+
+ // relation = expr ('=' | '!=') range_list
+ // expr = operand ('%' modValue)?
+ // operand = 'n' | 'i' | 'e' | 'f' | 't' | 'v' | 'w'
+
+ // An operand here relates to how the input number N is after exponentiation is applied
+ //
+ // n the absolute value of N.
+ // i the integer digits of N.
+ // e the exponent value of N.
+ // v the number of visible fraction digits in N, with trailing zeros.
+ // w the number of visible fraction digits in N, without trailing zeros.
+ // f the visible fraction digits in N, with trailing zeros, expressed as an integer.
+ // t the visible fraction digits in N, without trailing zeros, expressed as an integer.
+ //
+ // This implies that at least in some languages 1.3 and 1.30 could have different plural forms.
+ parts := relationRegexp.FindStringSubmatch(relation)
+ if parts == nil {
+ continue
+ }
+
+ operand, modValue, relationType, ranges := strings.ToUpper(parts[1]), parts[2], parts[3], strings.TrimSpace(parts[4])
+
+ // Now we want to convert the condition string to something which will evaluate
+
+ // Now convert the operand to a field in the structure
+ operand = "ops." + operand
+
+ // ranges = (range | value) (',' range_list)*
+ // range = from'..'to (value..value)
+ // value = digit+
+ // digit = [0-9]
+ var parsedExprRanges []string
+ var values []string
+ for _, rangeValue := range strings.Split(ranges, ",") {
+ // check if contains ..
+ // range = value'..'value
+ if parts := strings.Split(rangeValue, ".."); len(parts) == 2 {
+ from, to := parts[0], parts[1]
+
+ // Now if we are testing the N operand because it could be a decimal number we need to use a different function
+ if operand == "ops.N" {
+ if modValue != "" {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("ops.NModInRange(%s, %s, %s)", modValue, from, to))
+ continue
+ }
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("ops.NInRange(%s, %s)", from, to))
+ continue
+ }
+
+ // Otherwise we can simply mod the operand value directly
+ if modValue != "" {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("intInRange(%s %% %s, %s, %s)", operand, modValue, from, to))
+ } else {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("intInRange(%s, %s, %s)", operand, from, to))
+ }
+ continue
+ }
+
+ // We have a plain value - collect them and test them together
+ values = append(values, rangeValue)
+ }
+
+ if len(values) > 0 {
+ valuesArgs := strings.Join(values, ",")
+
+ // Now if we are testing the N operand because it could be a decimal number we need to use a different function
+ if operand == "ops.N" {
+ if modValue != "" {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("ops.NModEqualsAny(%s, %s)", modValue, valuesArgs))
+ } else {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("ops.NEqualsAny(%s)", valuesArgs))
+ }
+ } else if modValue != "" {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("intEqualsAny(%s %% %s, %s)", operand, modValue, valuesArgs))
+ } else {
+ parsedExprRanges = append(parsedExprRanges, fmt.Sprintf("intEqualsAny(%s, %s)", operand, valuesArgs))
+ }
+ }
+
+ // join all the parsed Ranges together as Ors
+ parsedRelations := strings.Join(parsedExprRanges, " || ")
+
+ // Group them
+ if len(parsedExprRanges) > 1 {
+ parsedRelations = "(" + parsedRelations + ")"
+ }
+
+ // Handle not
+ if relationType == "!=" {
+ parsedRelations = "!" + parsedRelations
+ }
+
+ parsedAndConditions = append(parsedAndConditions, parsedRelations)
+ }
+ parsedAndCondition := strings.TrimSpace(strings.Join(parsedAndConditions, " && "))
+ if parsedAndCondition == "" {
+ continue
+ }
+ parsedOrConditions = append(parsedOrConditions, parsedAndCondition)
+ }
+ return strings.Join(parsedOrConditions, " ||\n")
+}
diff --git a/modules/translation/i18n/plurals/generate/main/generate.go b/modules/translation/i18n/plurals/generate/main/generate.go
new file mode 100644
index 0000000000000..66d16cfd0a169
--- /dev/null
+++ b/modules/translation/i18n/plurals/generate/main/generate.go
@@ -0,0 +1,184 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "go/format"
+ "os"
+ "text/template"
+
+ "code.gitea.io/gitea/modules/translation/i18n/plurals/generate"
+)
+
+var usage = `%[1]s parses and generates go code for CLDR plural.xml and ordinal.xml.xml
+
+Usage: %[1]s [-v] [-c code.go] [-t test.go] plurals.xml ordinals.xml
+
+`
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, usage, os.Args[0])
+ flag.PrintDefaults()
+ }
+
+ var codeOut, testOut string
+ flag.BoolVar(&verbose, "v", false, "verbose output")
+ flag.StringVar(&codeOut, "c", "", "file to output generated code")
+ flag.StringVar(&testOut, "t", "", "file to output generated tests")
+ flag.Parse()
+
+ args := flag.Args()
+
+ if len(args) == 0 {
+ args = []string{"plurals.xml"}
+ }
+
+ data := &generate.SupplementalData{}
+
+ for _, in := range args {
+ buf, err := os.ReadFile(in)
+ if err != nil {
+ fatalf("failed to read file: %s", err)
+ }
+
+ var individual generate.SupplementalData
+ if err := xml.Unmarshal(buf, &individual); err != nil {
+ fatalf("failed to unmarshal xml: %s", err)
+ }
+ data.Plurals = append(data.Plurals, individual.Plurals...)
+ }
+
+ if verbose {
+ count := 0
+ groups := 0
+ for _, plurals := range data.Plurals {
+ for _, localeGroups := range plurals.LocaleGroups {
+ count += len(localeGroups.SplitLocales())
+ groups++
+ }
+ }
+ verbosef("parsed %d locales in %d groups", count, groups)
+ }
+
+ if codeOut != "" {
+ if err := runTemplate(codeTemplate, codeOut, data); err != nil {
+ fatalf("failed to generate code: %v", err)
+ } else {
+ verbosef("generated %s", codeOut)
+ }
+ } else {
+ logf("not generating code file (use -c)")
+ }
+
+ if testOut != "" {
+ if err := runTemplate(testTemplate, testOut, data); err != nil {
+ fatalf("failed to generate test code: %v", err)
+ } else {
+ verbosef("generated %s", testOut)
+ }
+ } else {
+ logf("not generating test file (use -t)")
+ }
+}
+
+func runTemplate(t *template.Template, filename string, data *generate.SupplementalData) error {
+ buf := bytes.NewBuffer(nil)
+ if err := t.Execute(buf, data); err != nil {
+ return fmt.Errorf("unable to execute template: %w", err)
+ }
+ bs, err := format.Source(buf.Bytes())
+ if err != nil {
+ verbosef("Bad source:\n%s", buf.String())
+ return fmt.Errorf("unable to format source: %w", err)
+ }
+ file, err := os.Create(filename)
+ if err != nil {
+ return fmt.Errorf("failed to create file %s because %w", filename, err)
+ }
+ defer file.Close()
+ _, err = file.Write(bs)
+ if err != nil {
+ return fmt.Errorf("unable to write generated source: %w", err)
+ }
+ return nil
+}
+
+var codeTemplate = template.Must(template.New("codeTemplate").Parse(`// This file is generated by modules/translation/i18n/plurals/generate/main/generate.go DO NOT EDIT
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package plurals
+
+// DefaultRules returns a map of Rules generated from CLDR language data.
+func DefaultRules() Rules {
+ rules := Rules{}
+{{range $p, $plurals := .Plurals}}
+{{range .LocaleGroups}}
+ addPluralRules(rules, {{printf "%q" $plurals.Type}}, {{printf "%#v" .SplitLocales}}, &Rule{
+ PluralForms: newPluralFormSet({{range $i, $e := .Rules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}),
+ PluralFormFunc: func(ops *Operands) Form { {{range .Rules}}{{if .GoCondition}}
+ // {{.Condition}}
+ if {{.GoCondition}} {
+ return {{.CountTitle}}
+ }{{end}}{{end}}
+ return Other
+ },
+ }){{end}}
+ {{end}}
+ return rules
+}
+`))
+
+var testTemplate = template.Must(template.New("testTemplate").Parse(`// This file is generated by modules/translation/i18n/plurals/generate/main/generate.go DO NOT EDIT
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package plurals
+
+import "testing"
+
+{{range $p, $plurals := .Plurals}}
+{{range $i, $localeGroup := .LocaleGroups}}
+func Test{{$i}}{{$plurals.Type}}(t *testing.T) {
+ var tests []pluralFormTest
+ {{range $localeGroup.Rules}}
+ {{if .IntegerSamples}}tests = appendIntegerTests(tests, {{printf "%q" $plurals.Type}}, {{.CountTitle}}, {{printf "%#v" .IntegerSamples}}){{end}}
+ {{if .DecimalSamples}}tests = appendDecimalTests(tests, {{printf "%q" $plurals.Type}}, {{.CountTitle}}, {{printf "%#v" .DecimalSamples}}){{end}}
+ {{end}}
+ locales := {{printf "%#v" $localeGroup.SplitLocales}}
+ for _, locale := range locales {
+ runTests(t, locale, {{printf "%q" $plurals.Type}}, tests)
+ }
+}
+
+{{end}}
+{{end}}
+`))
+
+func logf(format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, format+"\n", args...)
+}
+
+var verbose bool
+
+func verbosef(format string, args ...interface{}) {
+ if verbose {
+ logf(format, args...)
+ }
+}
+
+func fatalf(format string, args ...interface{}) {
+ logf("fatal: "+format+"\n", args...)
+ os.Exit(1)
+}
diff --git a/modules/translation/i18n/plurals/generate/ordinals.xml b/modules/translation/i18n/plurals/generate/ordinals.xml
new file mode 100644
index 0000000000000..048069a62eb4d
--- /dev/null
+++ b/modules/translation/i18n/plurals/generate/ordinals.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+ @integer 0~15, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n % 10 = 1,2 and n % 100 != 11,12 @integer 1, 2, 21, 22, 31, 32, 41, 42, 51, 52, 61, 62, 71, 72, 81, 82, 101, 1001, …
+ @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1 @integer 1
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1,5 @integer 1, 5
+ @integer 0, 2~4, 6~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1..4 @integer 1~4
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n % 10 = 2,3 and n % 100 != 12,13 @integer 2, 3, 22, 23, 32, 33, 42, 43, 52, 53, 62, 63, 72, 73, 82, 83, 102, 1002, …
+ @integer 0, 1, 4~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n % 10 = 3 and n % 100 != 13 @integer 3, 23, 33, 43, 53, 63, 73, 83, 103, 1003, …
+ @integer 0~2, 4~16, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n % 10 = 6,9 or n = 10 @integer 6, 9, 10, 16, 19, 26, 29, 36, 39, 106, 1006, …
+ @integer 0~5, 7, 8, 11~15, 17, 18, 20, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n % 10 = 6 or n % 10 = 9 or n % 10 = 0 and n != 0 @integer 6, 9, 10, 16, 19, 20, 26, 29, 30, 36, 39, 40, 100, 1000, 10000, 100000, 1000000, …
+ @integer 0~5, 7, 8, 11~15, 17, 18, 21, 101, 1001, …
+
+
+ n = 11,8,80,800 @integer 8, 11, 80, 800
+ @integer 0~7, 9, 10, 12~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 11,8,80..89,800..899 @integer 8, 11, 80~89, 800~803
+ @integer 0~7, 9, 10, 12~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ i = 1 @integer 1
+ i = 0 or i % 100 = 2..20,40,60,80 @integer 0, 2~16, 102, 1002, …
+ @integer 21~36, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1 @integer 1
+ n % 10 = 4 and n % 100 != 14 @integer 4, 24, 34, 44, 54, 64, 74, 84, 104, 1004, …
+ @integer 0, 2, 3, 5~17, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1..4 or n % 100 = 1..4,21..24,41..44,61..64,81..84 @integer 1~4, 21~24, 41~44, 61~64, 101, 1001, …
+ n = 5 or n % 100 = 5 @integer 5, 105, 205, 305, 405, 505, 605, 705, 1005, …
+ @integer 0, 6~20, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …
+ n % 10 = 2 and n % 100 != 12 @integer 2, 22, 32, 42, 52, 62, 72, 82, 102, 1002, …
+ n % 10 = 3 and n % 100 != 13 @integer 3, 23, 33, 43, 53, 63, 73, 83, 103, 1003, …
+ @integer 0, 4~18, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1 @integer 1
+ n = 2,3 @integer 2, 3
+ n = 4 @integer 4
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1,11 @integer 1, 11
+ n = 2,12 @integer 2, 12
+ n = 3,13 @integer 3, 13
+ @integer 0, 4~10, 14~21, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1,3 @integer 1, 3
+ n = 2 @integer 2
+ n = 4 @integer 4
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …
+ i % 10 = 2 and i % 100 != 12 @integer 2, 22, 32, 42, 52, 62, 72, 82, 102, 1002, …
+ i % 10 = 7,8 and i % 100 != 17,18 @integer 7, 8, 27, 28, 37, 38, 47, 48, 57, 58, 67, 68, 77, 78, 87, 88, 107, 1007, …
+ @integer 0, 3~6, 9~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ i % 10 = 1,2,5,7,8 or i % 100 = 20,50,70,80 @integer 1, 2, 5, 7, 8, 11, 12, 15, 17, 18, 20~22, 25, 101, 1001, …
+ i % 10 = 3,4 or i % 1000 = 100,200,300,400,500,600,700,800,900 @integer 3, 4, 13, 14, 23, 24, 33, 34, 43, 44, 53, 54, 63, 64, 73, 74, 100, 1003, …
+ i = 0 or i % 10 = 6 or i % 100 = 40,60,90 @integer 0, 6, 16, 26, 36, 40, 46, 56, 106, 1006, …
+ @integer 9, 10, 19, 29, 30, 39, 49, 59, 69, 79, 109, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n = 1 @integer 1
+ n = 2,3 @integer 2, 3
+ n = 4 @integer 4
+ n = 6 @integer 6
+ @integer 0, 5, 7~20, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1,5,7,8,9,10 @integer 1, 5, 7~10
+ n = 2,3 @integer 2, 3
+ n = 4 @integer 4
+ n = 6 @integer 6
+ @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, …
+
+
+ n = 1,5,7..9 @integer 1, 5, 7~9
+ n = 2,3 @integer 2, 3
+ n = 4 @integer 4
+ n = 6 @integer 6
+ @integer 0, 10~24, 100, 1000, 10000, 100000, 1000000, …
+
+
+
+
+
+ n = 0,7,8,9 @integer 0, 7~9
+ n = 1 @integer 1
+ n = 2 @integer 2
+ n = 3,4 @integer 3, 4
+ n = 5,6 @integer 5, 6
+ @integer 10~25, 100, 1000, 10000, 100000, 1000000, …
+
+
+
diff --git a/modules/translation/i18n/plurals/generate/plurals.xml b/modules/translation/i18n/plurals/generate/plurals.xml
new file mode 100644
index 0000000000000..eaac2882532bd
--- /dev/null
+++ b/modules/translation/i18n/plurals/generate/plurals.xml
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+ @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ i = 0,1 @integer 0, 1 @decimal 0.0~1.5
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ i = 1 and v = 0 @integer 1
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0
+ @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …
+
+
+
+
+
+ n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …
+ @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …
+
+
+ n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000
+ i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000
+ @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04
+ n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00
+ @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ i = 1 and v = 0 @integer 1
+ v != 0 or n = 0 or n % 100 = 2..19 @integer 0, 2~16, 102, 1002, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @integer 20~35, 100, 1000, 10000, 100000, 1000000, …
+
+
+ v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …
+ v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ i = 0,1 @integer 0, 1 @decimal 0.0~1.5
+ e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 @integer 1000000, 1c6, 2c6, 3c6, 4c6, 5c6, 6c6, … @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …
+ @integer 2~17, 100, 1000, 10000, 100000, 1c3, 2c3, 3c3, 4c3, 5c3, 6c3, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0001c3, 1.1c3, 2.0001c3, 2.1c3, 3.0001c3, 3.1c3, …
+
+
+ i = 0..1 @integer 0, 1 @decimal 0.0~1.5
+ e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 @integer 1000000, 1c6, 2c6, 3c6, 4c6, 5c6, 6c6, … @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …
+ @integer 2~17, 100, 1000, 10000, 100000, 1c3, 2c3, 3c3, 4c3, 5c3, 6c3, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0001c3, 1.1c3, 2.0001c3, 2.1c3, 3.0001c3, 3.1c3, …
+
+
+ i = 1 and v = 0 @integer 1
+ e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 @integer 1000000, 1c6, 2c6, 3c6, 4c6, 5c6, 6c6, … @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1c3, 2c3, 3c3, 4c3, 5c3, 6c3, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0001c3, 1.1c3, 2.0001c3, 2.1c3, 3.0001c3, 3.1c3, …
+
+
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5 @integer 1000000, 1c6, 2c6, 3c6, 4c6, 5c6, 6c6, … @decimal 1.0000001c6, 1.1c6, 2.0000001c6, 2.1c6, 3.0000001c6, 3.1c6, …
+ @integer 0, 2~16, 100, 1000, 10000, 100000, 1c3, 2c3, 3c3, 4c3, 5c3, 6c3, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 1.0001c3, 1.1c3, 2.0001c3, 2.1c3, 3.0001c3, 3.1c3, …
+
+
+
+
+
+ n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000
+ n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000
+ n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00
+ @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …
+ v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …
+ v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+ v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …
+ v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …
+ v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ i = 1 and v = 0 @integer 1
+ i = 2 and v = 0 @integer 2
+ v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …
+ @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ i = 1 and v = 0 @integer 1
+ i = 2..4 and v = 0 @integer 2~4
+ v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+
+
+ i = 1 and v = 0 @integer 1
+ v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …
+ v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+ @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …
+ n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …
+ n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …
+
+
+ n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …
+ n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …
+ f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …
+ @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …
+ n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …
+ @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …
+ v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …
+ v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …
+ @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
+
+
+ n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …
+ n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …
+ n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …
+ n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, 1000000.0000, …
+ @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …
+
+
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000
+ n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000
+ n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000
+ @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …
+ v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …
+ v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …
+ v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+ @integer 3~10, 13~19, 23, 103, 1003, …
+
+
+
+
+
+ n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n % 100 = 2,22,42,62,82 or n % 1000 = 0 and n % 100000 = 1000..20000,40000,60000,80000 or n != 0 and n % 1000000 = 100000 @integer 2, 22, 42, 62, 82, 102, 122, 142, 1000, 10000, 100000, … @decimal 2.0, 22.0, 42.0, 62.0, 82.0, 102.0, 122.0, 142.0, 1000.0, 10000.0, 100000.0, …
+ n % 100 = 3,23,43,63,83 @integer 3, 23, 43, 63, 83, 103, 123, 143, 1003, … @decimal 3.0, 23.0, 43.0, 63.0, 83.0, 103.0, 123.0, 143.0, 1003.0, …
+ n != 1 and n % 100 = 1,21,41,61,81 @integer 21, 41, 61, 81, 101, 121, 141, 161, 1001, … @decimal 21.0, 41.0, 61.0, 81.0, 101.0, 121.0, 141.0, 161.0, 1001.0, …
+ @integer 4~19, 100, 1004, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.1, 1000000.0, …
+
+
+ n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000
+ n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …
+ n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …
+ @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+ n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000
+ n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000
+ n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000
+ n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000
+ n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000
+ @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …
+
+
+
diff --git a/modules/translation/i18n/plurals/generate/xml.go b/modules/translation/i18n/plurals/generate/xml.go
new file mode 100644
index 0000000000000..3cab9fb9b976a
--- /dev/null
+++ b/modules/translation/i18n/plurals/generate/xml.go
@@ -0,0 +1,139 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run main/generate.go -c ../rules_gen.go -t ../rules_gen_test.go plurals.xml ordinals.xml
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package generate
+
+import (
+ "encoding/xml"
+ "strings"
+)
+
+// SupplementalData is the root of plural.xml
+type SupplementalData struct {
+ XMLName xml.Name `xml:"supplementalData"`
+ Plurals []Plurals `xml:"plurals"`
+}
+
+// SupplementalData is the root of plural.xml
+type Plurals struct {
+ Type string `xml:"type,attr"`
+ LocaleGroups []LocaleGroup `xml:"pluralRules"`
+}
+
+// LocaleGroup is a group of locales with the same plural rules.
+type LocaleGroup struct {
+ Locales string `xml:"locales,attr"`
+ Rules []Rule `xml:"pluralRule"`
+}
+
+// SplitLocales returns all the locales in the PluralGroup as a slice.
+func (lg *LocaleGroup) SplitLocales() []string {
+ return strings.Split(lg.Locales, " ")
+}
+
+// Rule is a rule for a single plural form.
+type Rule struct {
+ // Count is one of `zero` | `one` | `two` | `few` | `many` | `other`
+ Count string `xml:"count,attr"`
+ // Rule looks like:
+ // (@integer (…)?)? (@decimal (…)?)?
+ // ">n % 10 = 1 and n % 100 != 11..19"
+ // "@integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …"
+ // "@decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …"
+ Rule string `xml:",innerxml"`
+}
+
+// CountTitle returns the title case of the pluralRule's count.
+func (r *Rule) CountTitle() string {
+ return strings.ToUpper(r.Count[0:1]) + r.Count[1:]
+}
+
+// Condition returns the condition where the pluralRule applies.
+// These look like "", ">n % 10 = 1 and n % 100 != 11..19" etc.
+//
+// The conditions themselves have the following syntax.
+//
+// condition = and_condition ('or' and_condition)*
+// and_condition = relation ('and' relation)*
+//
+// Now the next bit needs some adjustment
+//
+// relation = is_relation | in_relation | within_relation
+// is_relation = expr 'is' ('not')? value
+// ^------------ This is not present in plurals.xml/ordinals.xml
+// in_relation = expr (('not')? 'in' | '=' | '!=') range_list
+// ^^^^^^^^^^^^^^^^^^^^^ not in plurals.xml/ordinals.xml
+// within_relation = expr ('not')? 'within' range_list
+// ^------------- This is not present in plurals.xml/ordinals.xml
+//
+// So relation is really:
+//
+// relation = expr ('=' | '!=') range_list
+//
+// expr = operand (('mod' | '%') value)?
+// ^^^^^ not in plurals.xml/ordinals.xml
+// operand = 'n' | 'i' | 'f' | 't' | 'v' | 'w' | 'c' | 'e'
+// not in plurals.xml/ordinals.xml ^^^^^^^^^^^
+// range_list = (range | value) (',' range_list)*
+// range = value'..'value
+// value = digit+
+// digit = [0-9]
+func (r *Rule) Condition() string {
+ i := strings.Index(r.Rule, "@")
+ if i >= 0 {
+ return r.Rule[:i]
+ }
+ return r.Rule
+}
+
+// Samples returns the integer and decimal samples for the pluralRule
+//
+// samples = ('@integer' sampleList)?
+// ('@decimal' sampleList)?
+// sampleList = sampleRange (',' sampleRange)* (',' ('…'|'...'))?
+// sampleRange = sampleValue ('~' sampleValue)?
+// sampleValue = value ('.' digit+)? ([ce] digitPos digit+)?
+// value = digit+
+// digit = [0-9]
+// 1 = [1-9]
+func (r *Rule) Samples() (integer, decimal []string) {
+ // First of all remove the ellipses as they're not helpful
+ rule := strings.ReplaceAll(r.Rule, ", …", "")
+
+ // Now the we know that @decimal is always after the @integer section
+ ruleSplit := strings.SplitN(rule, " @decimal ", 2)
+ rule = ruleSplit[0]
+
+ if len(ruleSplit) > 1 {
+ decimal = strings.Split(ruleSplit[1], ", ")
+ }
+
+ ruleSplit = strings.SplitN(rule, " @integer ", 2)
+ if len(ruleSplit) > 1 {
+ integer = strings.Split(ruleSplit[1], ", ")
+ }
+
+ return integer, decimal
+}
+
+// IntegerSamples returns the integer exmaples for the PLuralRule.
+func (r *Rule) IntegerSamples() []string {
+ integer, _ := r.Samples()
+ return integer
+}
+
+// DecimalSamples returns the decimal exmaples for the PLuralRule.
+func (r *Rule) DecimalSamples() []string {
+ _, decimal := r.Samples()
+ return decimal
+}
+
+// GoCondition returns the converted CLDR plural rules to Go code
+func (r *Rule) GoCondition() string {
+ return ConditionToGoString(r.Condition())
+}
diff --git a/modules/translation/i18n/plurals/operands.go b/modules/translation/i18n/plurals/operands.go
new file mode 100644
index 0000000000000..6b6ba4dae9f3d
--- /dev/null
+++ b/modules/translation/i18n/plurals/operands.go
@@ -0,0 +1,192 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package plurals
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+func intInRange(i, from, to int64) bool {
+ return from <= i && i <= to
+}
+
+func intEqualsAny(i int64, any ...int64) bool {
+ for _, a := range any {
+ if i == a {
+ return true
+ }
+ }
+ return false
+}
+
+// Operands is a representation of http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type Operands struct {
+ N float64 // absolute value of the source number (integer and decimals)
+ I int64 // integer digits of n
+ E int64 // exponent
+ V int64 // number of visible fraction digits in n, with trailing zeros
+ W int64 // number of visible fraction digits in n, without trailing zeros
+ F int64 // visible fractional digits in n, with trailing zeros
+ T int64 // visible fractional digits in n, without trailing zeros
+}
+
+// NEqualsAny returns true if o represents an integer equal to any of the arguments.
+func (o *Operands) NEqualsAny(any ...int64) bool {
+ if o.T != 0 {
+ return false
+ }
+
+ return intEqualsAny(o.I, any...)
+}
+
+// NModEqualsAny returns true if o represents an integer equal to any of the arguments modulo mod.
+func (o *Operands) NModEqualsAny(mod int64, any ...int64) bool {
+ if o.T != 0 {
+ return false
+ }
+
+ modI := o.I % mod
+ return intEqualsAny(modI, any...)
+}
+
+// NInRange returns true if o represents an integer in the closed interval [from, to].
+func (o *Operands) NInRange(from, to int64) bool {
+ return o.T == 0 && intInRange(o.I, from, to)
+}
+
+// NModInRange returns true if o represents an integer in the closed interval [from, to] modulo mod.
+func (o *Operands) NModInRange(mod, from, to int64) bool {
+ modI := o.I % mod
+ return o.T == 0 && intInRange(modI, from, to)
+}
+
+// NewOperands returns the operands for number.
+func NewOperands(number interface{}) (*Operands, error) {
+ switch number := number.(type) {
+ case int:
+ return operandsFromInt64(int64(number)), nil
+ case int8:
+ return operandsFromInt64(int64(number)), nil
+ case int16:
+ return operandsFromInt64(int64(number)), nil
+ case int32:
+ return operandsFromInt64(int64(number)), nil
+ case int64:
+ return operandsFromInt64(number), nil
+ case string:
+ return operandsFromString(number)
+ case float32, float64:
+ return nil, fmt.Errorf("floats must be formatted as a string")
+ default:
+ return nil, fmt.Errorf("invalid type %T; expected integer or string", number)
+ }
+}
+
+func operandsFromInt64(i int64) *Operands {
+ if i < 0 {
+ i = -i
+ }
+ return &Operands{float64(i), i, 0, 0, 0, 0, 0}
+}
+
+func operandsFromString(s string) (*Operands, error) {
+ s = strings.TrimSpace(s)
+
+ // strip the sign
+ if s[0] == '-' {
+ s = s[1:]
+ }
+
+ ops := &Operands{}
+
+ // Now the problem is s could be in [1-9](.[0-9]+)?e[1-9][0-9]*
+ // We need to determine how many numbers after the decimal place remain.
+ s = strings.Replace(s, "e", "c", 1)
+ if parts := strings.SplitN(s, "c", 2); len(parts) == 2 {
+ if idx := strings.Index(parts[0], "."); idx >= 0 {
+ numberOfDecimalsPreExp := len(parts[0]) - idx - 1
+ exp, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return nil, err
+ }
+ ops.E = int64(exp)
+ if exp >= numberOfDecimalsPreExp {
+ s = parts[0][:idx] + parts[0][idx+1:]
+ exp -= numberOfDecimalsPreExp
+ s += strings.Repeat("0", exp)
+ } else {
+ s = parts[0][:idx] + parts[0][idx+1:len(parts[0])+exp-numberOfDecimalsPreExp] + "." + parts[0][len(parts[0])+exp-numberOfDecimalsPreExp:]
+ }
+ } else {
+ exp, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return nil, err
+ }
+ ops.E = int64(exp)
+
+ s = parts[0] + strings.Repeat("0", exp)
+ }
+ }
+
+ // attempt to parse as a float
+ n, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return nil, err
+ }
+
+ // ops.N is the value of the number
+ ops.N = n
+
+ // Now split at the "."
+ parts := strings.SplitN(s, ".", 2)
+
+ // ops.I is the integer floor of the number
+ ops.I, err = strconv.ParseInt(parts[0], 10, 64)
+ if err != nil {
+ return nil, err
+ }
+
+ // if there is no decimal part the rest of the parts of the operand is 0
+ if len(parts) == 1 {
+ return ops, nil
+ }
+
+ // parts[1] is the decimal part
+ fraction := parts[1]
+
+ // V is the number of visible fraction digits in n, with trailing zeros
+ ops.V = int64(len(fraction))
+ for i := ops.V - 1; i >= 0; i-- {
+ if fraction[i] != '0' {
+ // W is the number of visible fraction digits in n, without trailing zeros
+ ops.W = i + 1
+ break
+ }
+ }
+
+ if ops.V > 0 {
+ // F is the visible fractional digits in n, with trailing zeros
+ // we get this from the V
+ f, err := strconv.ParseInt(fraction, 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.F = f
+ }
+ if ops.W > 0 {
+ // T is visible fractional digits in n, without trailing zeros
+ // we get this from the W
+ t, err := strconv.ParseInt(fraction[:ops.W], 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ops.T = t
+ }
+ return ops, nil
+}
diff --git a/modules/translation/i18n/plurals/rule_test.go b/modules/translation/i18n/plurals/rule_test.go
new file mode 100644
index 0000000000000..9ec5107437353
--- /dev/null
+++ b/modules/translation/i18n/plurals/rule_test.go
@@ -0,0 +1,127 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package plurals
+
+import (
+ "strconv"
+ "strings"
+ "testing"
+)
+
+type pluralFormTest struct {
+ num interface{}
+ typ string
+ form Form
+}
+
+func runTests(t *testing.T, pluralRuleID, typ string, tests []pluralFormTest) {
+ if pluralRuleID == "root" {
+ return
+ }
+ pluralRules := DefaultRules()
+ if rule := pluralRules.RuleByType(RuleType(typ), pluralRuleID); rule != nil {
+ for _, test := range tests {
+ ops, err := NewOperands(test.num)
+ if err != nil {
+ t.Errorf("%s: NewOperands(%v) errored with %s", pluralRuleID, test.num, err)
+ break
+ }
+ if pluralForm := rule.PluralFormFunc(ops); pluralForm != test.form {
+ t.Errorf("%s:%v: PluralFormFunc(%#v) returned %q, %v; expected %q", pluralRuleID, test.num, ops, pluralForm, err, test.form)
+ }
+ }
+ } else {
+ t.Errorf("could not find plural rule for locale %s", pluralRuleID)
+ }
+}
+
+func appendIntegerTests(tests []pluralFormTest, typ string, form Form, examples []string) []pluralFormTest {
+ for _, ex := range expandExamples(examples) {
+ var i int64
+ if strings.Count(ex, "c") == 1 || strings.Count(ex, "e") == 1 {
+ ex = strings.Replace(ex, "e", "c", 1)
+ // Now the problem is s could be in [1-9](.[0-9]+)?e[1-9][0-9]*
+ // We need to determine how many numbers after the decimal place remain.
+ if parts := strings.SplitN(ex, "c", 2); len(parts) == 2 {
+ if idx := strings.Index(parts[0], "."); idx >= 0 {
+ numberOfDecimalsPreExp := len(parts[0]) - idx - 1
+ exp, err := strconv.Atoi(parts[1])
+ if err != nil {
+ panic(err)
+ }
+ if exp >= numberOfDecimalsPreExp {
+ ex = parts[0][:idx] + parts[0][idx+1:]
+ exp -= numberOfDecimalsPreExp
+ ex += strings.Repeat("0", exp)
+ } else {
+ ex = parts[0][:idx] + parts[0][idx+1:len(parts[0])+exp-numberOfDecimalsPreExp] + "." + parts[0][len(parts[0])+exp-numberOfDecimalsPreExp:]
+ }
+ } else {
+ exp, err := strconv.Atoi(parts[1])
+ if err != nil {
+ panic(err)
+ }
+ ex = parts[0] + strings.Repeat("0", exp)
+ }
+ }
+ }
+
+ var err error
+ i, err = strconv.ParseInt(ex, 10, 64)
+ if err != nil {
+ panic(err)
+ }
+ tests = append(tests, pluralFormTest{ex, typ, form}, pluralFormTest{i, typ, form})
+ }
+ return tests
+}
+
+func appendDecimalTests(tests []pluralFormTest, typ string, form Form, examples []string) []pluralFormTest {
+ for _, ex := range expandExamples(examples) {
+ ex = strings.Replace(ex, "c", "e", 1)
+ tests = append(tests, pluralFormTest{ex, typ, form})
+ }
+ return tests
+}
+
+func expandExamples(examples []string) []string {
+ var expanded []string
+ for _, ex := range examples {
+ ex = strings.Replace(ex, "c", "e", 1)
+ if parts := strings.Split(ex, "~"); len(parts) == 2 {
+ for ex := parts[0]; ; ex = increment(ex) {
+ expanded = append(expanded, ex)
+ if ex == parts[1] {
+ break
+ }
+ }
+ } else {
+ expanded = append(expanded, ex)
+ }
+ }
+ return expanded
+}
+
+func increment(dec string) string {
+ runes := []rune(dec)
+ carry := true
+ for i := len(runes) - 1; carry && i >= 0; i-- {
+ switch runes[i] {
+ case '.':
+ continue
+ case '9':
+ runes[i] = '0'
+ default:
+ runes[i]++
+ carry = false
+ }
+ }
+ if carry {
+ runes = append([]rune{'1'}, runes...)
+ }
+ return string(runes)
+}
diff --git a/modules/translation/i18n/plurals/rules.go b/modules/translation/i18n/plurals/rules.go
new file mode 100644
index 0000000000000..05ea88b700bfb
--- /dev/null
+++ b/modules/translation/i18n/plurals/rules.go
@@ -0,0 +1,78 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package plurals
+
+import (
+ "strings"
+)
+
+type RuleType string
+
+const (
+ Cardinal RuleType = "cardinal"
+ Ordinal RuleType = "ordinal"
+)
+
+// Rule defines the CLDR plural rules for a language.
+// http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+// http://unicode.org/reports/tr35/tr35-numbers.html#Operands
+type Rule struct {
+ PluralForms map[Form]struct{}
+ PluralFormFunc func(*Operands) Form
+}
+
+func addPluralRules(rules Rules, typ RuleType, ids []string, ps *Rule) {
+ for _, id := range ids {
+ if id == "root" {
+ continue
+ }
+ if rules[typ] == nil {
+ rules[typ] = map[string]*Rule{}
+ }
+ rules[typ][id] = ps
+ }
+}
+
+func newPluralFormSet(pluralForms ...Form) map[Form]struct{} {
+ set := make(map[Form]struct{}, len(pluralForms))
+ for _, plural := range pluralForms {
+ set[plural] = struct{}{}
+ }
+ return set
+}
+
+type Rules map[RuleType]map[string]*Rule
+
+// Rule returns the closest matching plural rule for the language tag
+// or nil if no rule could be found.
+func (r Rules) Rule(locale string) *Rule {
+ for {
+ if rule, ok := r["cardinal"][locale]; ok {
+ return rule
+ }
+ idx := strings.LastIndex(locale, "-")
+ if idx < 0 {
+ return r["cardinal"]["en"]
+ }
+ locale = locale[:idx]
+ }
+}
+
+// Rule returns the closest matching plural rule for the language tag
+// or nil if no rule could be found.
+func (r Rules) RuleByType(typ RuleType, locale string) *Rule {
+ for {
+ if rule, ok := r[typ][locale]; ok {
+ return rule
+ }
+ idx := strings.LastIndex(locale, "-")
+ if idx < 0 {
+ return r[typ]["en"]
+ }
+ locale = locale[:idx]
+ }
+}
diff --git a/modules/translation/i18n/plurals/rules_gen.go b/modules/translation/i18n/plurals/rules_gen.go
new file mode 100644
index 0000000000000..fde14ec90dd38
--- /dev/null
+++ b/modules/translation/i18n/plurals/rules_gen.go
@@ -0,0 +1,1001 @@
+// This file is generated by modules/translation/i18n/plurals/generate/main/generate.go DO NOT EDIT
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package plurals
+
+// DefaultRules returns a map of Rules generated from CLDR language data.
+func DefaultRules() Rules {
+ rules := Rules{}
+
+ addPluralRules(rules, "cardinal", []string{"bm", "bo", "dz", "hnj", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "osa", "root", "sah", "ses", "sg", "su", "th", "to", "tpi", "vi", "wo", "yo", "yue", "zh"}, &Rule{
+ PluralForms: newPluralFormSet(Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"am", "as", "bn", "doi", "fa", "gu", "hi", "kn", "pcm", "zu"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NEqualsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ff", "hy", "kab"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 0,1
+ if intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "ji", "lij", "nl", "sc", "scn", "sv", "sw", "ur", "yi"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"si"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0,1 or i = 0 and f = 1
+ if ops.NEqualsAny(0, 1) ||
+ intEqualsAny(ops.I, 0) && intEqualsAny(ops.F, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ak", "bho", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0..1
+ if ops.NInRange(0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"tzm"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0..1 or n = 11..99
+ if ops.NInRange(0, 1) ||
+ ops.NInRange(11, 99) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"af", "an", "asa", "az", "bal", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"da"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1 or t != 0 and i = 0,1
+ if ops.NEqualsAny(1) ||
+ !intEqualsAny(ops.T, 0) && intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"is"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0
+ if intEqualsAny(ops.T, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ !intEqualsAny(ops.T, 0) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"mk"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ceb", "fil", "tl"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I, 1, 2, 3) ||
+ intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I%10, 4, 6, 9) ||
+ !intEqualsAny(ops.V, 0) && !intEqualsAny(ops.F%10, 4, 6, 9) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"lv", "prg"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19
+ if ops.NModEqualsAny(10, 0) ||
+ ops.NModInRange(100, 11, 19) ||
+ intEqualsAny(ops.V, 2) && intInRange(ops.F%100, 11, 19) {
+ return Zero
+ }
+ // n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1
+ if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) ||
+ intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) ||
+ !intEqualsAny(ops.V, 2) && intEqualsAny(ops.F%10, 1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"lag"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0
+ if ops.NEqualsAny(0) {
+ return Zero
+ }
+ // i = 0,1 and n != 0
+ if intEqualsAny(ops.I, 0, 1) && !ops.NEqualsAny(0) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ksh"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0
+ if ops.NEqualsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"iu", "naq", "sat", "se", "sma", "smi", "smj", "smn", "sms"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"shi"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 0 or n = 1
+ if intEqualsAny(ops.I, 0) ||
+ ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2..10
+ if ops.NInRange(2, 10) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"mo", "ro"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v != 0 or n = 0 or n % 100 = 2..19
+ if !intEqualsAny(ops.V, 0) ||
+ ops.NEqualsAny(0) ||
+ ops.NModInRange(100, 2, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"bs", "hr", "sh", "sr"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) ||
+ intEqualsAny(ops.F%10, 1) && !intEqualsAny(ops.F%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) ||
+ intInRange(ops.F%10, 2, 4) && !intInRange(ops.F%100, 12, 14) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"fr"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 0,1
+ if intEqualsAny(ops.I, 0, 1) {
+ return One
+ }
+ // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5
+ if intEqualsAny(ops.E, 0) && !intEqualsAny(ops.I, 0) && intEqualsAny(ops.I%1000000, 0) && intEqualsAny(ops.V, 0) ||
+ !intInRange(ops.E, 0, 5) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"pt"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 0..1
+ if intInRange(ops.I, 0, 1) {
+ return One
+ }
+ // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5
+ if intEqualsAny(ops.E, 0) && !intEqualsAny(ops.I, 0) && intEqualsAny(ops.I%1000000, 0) && intEqualsAny(ops.V, 0) ||
+ !intInRange(ops.E, 0, 5) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"it", "pt_PT"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5
+ if intEqualsAny(ops.E, 0) && !intEqualsAny(ops.I, 0) && intEqualsAny(ops.I%1000000, 0) && intEqualsAny(ops.V, 0) ||
+ !intInRange(ops.E, 0, 5) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"es"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // e = 0 and i != 0 and i % 1000000 = 0 and v = 0 or e != 0..5
+ if intEqualsAny(ops.E, 0) && !intEqualsAny(ops.I, 0) && intEqualsAny(ops.I%1000000, 0) && intEqualsAny(ops.V, 0) ||
+ !intInRange(ops.E, 0, 5) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"gd"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,11
+ if ops.NEqualsAny(1, 11) {
+ return One
+ }
+ // n = 2,12
+ if ops.NEqualsAny(2, 12) {
+ return Two
+ }
+ // n = 3..10,13..19
+ if ops.NInRange(3, 10) || ops.NInRange(13, 19) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"sl"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or v != 0
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ !intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"dsb", "hsb"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 100 = 1 or f % 100 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 1) ||
+ intEqualsAny(ops.F%100, 1) {
+ return One
+ }
+ // v = 0 and i % 100 = 2 or f % 100 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 2) ||
+ intEqualsAny(ops.F%100, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 3..4 or f % 100 = 3..4
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 3, 4) ||
+ intInRange(ops.F%100, 3, 4) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"he", "iw"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2 and v = 0
+ if intEqualsAny(ops.I, 2) && intEqualsAny(ops.V, 0) {
+ return Two
+ }
+ // v = 0 and n != 0..10 and n % 10 = 0
+ if intEqualsAny(ops.V, 0) && !ops.NInRange(0, 10) && ops.NModEqualsAny(10, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"cs", "sk"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // i = 2..4 and v = 0
+ if intInRange(ops.I, 2, 4) && intEqualsAny(ops.V, 0) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"pl"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1 and v = 0
+ if intEqualsAny(ops.I, 1) && intEqualsAny(ops.V, 0) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14
+ if intEqualsAny(ops.V, 0) && !intEqualsAny(ops.I, 1) && intInRange(ops.I%10, 0, 1) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 12, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"be"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 1 and n % 100 != 11
+ if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) {
+ return One
+ }
+ // n % 10 = 2..4 and n % 100 != 12..14
+ if ops.NModInRange(10, 2, 4) && !ops.NModInRange(100, 12, 14) {
+ return Few
+ }
+ // n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14
+ if ops.NModEqualsAny(10, 0) ||
+ ops.NModInRange(10, 5, 9) ||
+ ops.NModInRange(100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"lt"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 1 and n % 100 != 11..19
+ if ops.NModEqualsAny(10, 1) && !ops.NModInRange(100, 11, 19) {
+ return One
+ }
+ // n % 10 = 2..9 and n % 100 != 11..19
+ if ops.NModInRange(10, 2, 9) && !ops.NModInRange(100, 11, 19) {
+ return Few
+ }
+ // f != 0
+ if !intEqualsAny(ops.F, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"mt"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 0 or n % 100 = 2..10
+ if ops.NEqualsAny(0) ||
+ ops.NModInRange(100, 2, 10) {
+ return Few
+ }
+ // n % 100 = 11..19
+ if ops.NModInRange(100, 11, 19) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ru", "uk"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 10 = 1 and i % 100 != 11
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) {
+ return One
+ }
+ // v = 0 and i % 10 = 2..4 and i % 100 != 12..14
+ if intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 2, 4) && !intInRange(ops.I%100, 12, 14) {
+ return Few
+ }
+ // v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 0) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%10, 5, 9) ||
+ intEqualsAny(ops.V, 0) && intInRange(ops.I%100, 11, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"br"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 1 and n % 100 != 11,71,91
+ if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11, 71, 91) {
+ return One
+ }
+ // n % 10 = 2 and n % 100 != 12,72,92
+ if ops.NModEqualsAny(10, 2) && !ops.NModEqualsAny(100, 12, 72, 92) {
+ return Two
+ }
+ // n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99
+ if (ops.NModInRange(10, 3, 4) || ops.NModEqualsAny(10, 9)) && !(ops.NModInRange(100, 10, 19) || ops.NModInRange(100, 70, 79) || ops.NModInRange(100, 90, 99)) {
+ return Few
+ }
+ // n != 0 and n % 1000000 = 0
+ if !ops.NEqualsAny(0) && ops.NModEqualsAny(1000000, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ga"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ // n = 3..6
+ if ops.NInRange(3, 6) {
+ return Few
+ }
+ // n = 7..10
+ if ops.NInRange(7, 10) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"gv"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // v = 0 and i % 10 = 1
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 1) {
+ return One
+ }
+ // v = 0 and i % 10 = 2
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%10, 2) {
+ return Two
+ }
+ // v = 0 and i % 100 = 0,20,40,60,80
+ if intEqualsAny(ops.V, 0) && intEqualsAny(ops.I%100, 0, 20, 40, 60, 80) {
+ return Few
+ }
+ // v != 0
+ if !intEqualsAny(ops.V, 0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"kw"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0
+ if ops.NEqualsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n % 100 = 2,22,42,62,82 or n % 1000 = 0 and n % 100000 = 1000..20000,40000,60000,80000 or n != 0 and n % 1000000 = 100000
+ if ops.NModEqualsAny(100, 2, 22, 42, 62, 82) ||
+ ops.NModEqualsAny(1000, 0) && (ops.NModInRange(100000, 1000, 20000) || ops.NModEqualsAny(100000, 40000, 60000, 80000)) ||
+ !ops.NEqualsAny(0) && ops.NModEqualsAny(1000000, 100000) {
+ return Two
+ }
+ // n % 100 = 3,23,43,63,83
+ if ops.NModEqualsAny(100, 3, 23, 43, 63, 83) {
+ return Few
+ }
+ // n != 1 and n % 100 = 1,21,41,61,81
+ if !ops.NEqualsAny(1) && ops.NModEqualsAny(100, 1, 21, 41, 61, 81) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"ar", "ars"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0
+ if ops.NEqualsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ // n % 100 = 3..10
+ if ops.NModInRange(100, 3, 10) {
+ return Few
+ }
+ // n % 100 = 11..99
+ if ops.NModInRange(100, 11, 99) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "cardinal", []string{"cy"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0
+ if ops.NEqualsAny(0) {
+ return Zero
+ }
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ // n = 3
+ if ops.NEqualsAny(3) {
+ return Few
+ }
+ // n = 6
+ if ops.NEqualsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+
+ addPluralRules(rules, "ordinal", []string{"af", "am", "an", "ar", "bg", "bs", "ce", "cs", "da", "de", "dsb", "el", "es", "et", "eu", "fa", "fi", "fy", "gl", "gsw", "he", "hr", "hsb", "ia", "id", "in", "is", "iw", "ja", "km", "kn", "ko", "ky", "lt", "lv", "ml", "mn", "my", "nb", "nl", "no", "pa", "pl", "prg", "ps", "pt", "root", "ru", "sd", "sh", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tpi", "tr", "ur", "uz", "yue", "zh", "zu"}, &Rule{
+ PluralForms: newPluralFormSet(Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"sv"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 1,2 and n % 100 != 11,12
+ if ops.NModEqualsAny(10, 1, 2) && !ops.NModEqualsAny(100, 11, 12) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"bal", "fil", "fr", "ga", "hy", "lo", "mo", "ms", "ro", "tl", "vi"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"hu"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,5
+ if ops.NEqualsAny(1, 5) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"ne"}, &Rule{
+ PluralForms: newPluralFormSet(One, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1..4
+ if ops.NInRange(1, 4) {
+ return One
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"be"}, &Rule{
+ PluralForms: newPluralFormSet(Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 2,3 and n % 100 != 12,13
+ if ops.NModEqualsAny(10, 2, 3) && !ops.NModEqualsAny(100, 12, 13) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"uk"}, &Rule{
+ PluralForms: newPluralFormSet(Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 3 and n % 100 != 13
+ if ops.NModEqualsAny(10, 3) && !ops.NModEqualsAny(100, 13) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"tk"}, &Rule{
+ PluralForms: newPluralFormSet(Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 6,9 or n = 10
+ if ops.NModEqualsAny(10, 6, 9) ||
+ ops.NEqualsAny(10) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"kk"}, &Rule{
+ PluralForms: newPluralFormSet(Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 6 or n % 10 = 9 or n % 10 = 0 and n != 0
+ if ops.NModEqualsAny(10, 6) ||
+ ops.NModEqualsAny(10, 9) ||
+ ops.NModEqualsAny(10, 0) && !ops.NEqualsAny(0) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"it", "sc", "scn"}, &Rule{
+ PluralForms: newPluralFormSet(Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 11,8,80,800
+ if ops.NEqualsAny(11, 8, 80, 800) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"lij"}, &Rule{
+ PluralForms: newPluralFormSet(Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 11,8,80..89,800..899
+ if ops.NInRange(80, 89) || ops.NInRange(800, 899) || ops.NEqualsAny(11, 8) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"ka"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i = 1
+ if intEqualsAny(ops.I, 1) {
+ return One
+ }
+ // i = 0 or i % 100 = 2..20,40,60,80
+ if intEqualsAny(ops.I, 0) ||
+ (intInRange(ops.I%100, 2, 20) || intEqualsAny(ops.I%100, 40, 60, 80)) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"sq"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n % 10 = 4 and n % 100 != 14
+ if ops.NModEqualsAny(10, 4) && !ops.NModEqualsAny(100, 14) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"kw"}, &Rule{
+ PluralForms: newPluralFormSet(One, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1..4 or n % 100 = 1..4,21..24,41..44,61..64,81..84
+ if ops.NInRange(1, 4) ||
+ (ops.NModInRange(100, 1, 4) || ops.NModInRange(100, 21, 24) || ops.NModInRange(100, 41, 44) || ops.NModInRange(100, 61, 64) || ops.NModInRange(100, 81, 84)) {
+ return One
+ }
+ // n = 5 or n % 100 = 5
+ if ops.NEqualsAny(5) ||
+ ops.NModEqualsAny(100, 5) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"en"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n % 10 = 1 and n % 100 != 11
+ if ops.NModEqualsAny(10, 1) && !ops.NModEqualsAny(100, 11) {
+ return One
+ }
+ // n % 10 = 2 and n % 100 != 12
+ if ops.NModEqualsAny(10, 2) && !ops.NModEqualsAny(100, 12) {
+ return Two
+ }
+ // n % 10 = 3 and n % 100 != 13
+ if ops.NModEqualsAny(10, 3) && !ops.NModEqualsAny(100, 13) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"mr"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2,3
+ if ops.NEqualsAny(2, 3) {
+ return Two
+ }
+ // n = 4
+ if ops.NEqualsAny(4) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"gd"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,11
+ if ops.NEqualsAny(1, 11) {
+ return One
+ }
+ // n = 2,12
+ if ops.NEqualsAny(2, 12) {
+ return Two
+ }
+ // n = 3,13
+ if ops.NEqualsAny(3, 13) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"ca"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,3
+ if ops.NEqualsAny(1, 3) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ // n = 4
+ if ops.NEqualsAny(4) {
+ return Few
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"mk"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i % 10 = 1 and i % 100 != 11
+ if intEqualsAny(ops.I%10, 1) && !intEqualsAny(ops.I%100, 11) {
+ return One
+ }
+ // i % 10 = 2 and i % 100 != 12
+ if intEqualsAny(ops.I%10, 2) && !intEqualsAny(ops.I%100, 12) {
+ return Two
+ }
+ // i % 10 = 7,8 and i % 100 != 17,18
+ if intEqualsAny(ops.I%10, 7, 8) && !intEqualsAny(ops.I%100, 17, 18) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"az"}, &Rule{
+ PluralForms: newPluralFormSet(One, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // i % 10 = 1,2,5,7,8 or i % 100 = 20,50,70,80
+ if intEqualsAny(ops.I%10, 1, 2, 5, 7, 8) ||
+ intEqualsAny(ops.I%100, 20, 50, 70, 80) {
+ return One
+ }
+ // i % 10 = 3,4 or i % 1000 = 100,200,300,400,500,600,700,800,900
+ if intEqualsAny(ops.I%10, 3, 4) ||
+ intEqualsAny(ops.I%1000, 100, 200, 300, 400, 500, 600, 700, 800, 900) {
+ return Few
+ }
+ // i = 0 or i % 10 = 6 or i % 100 = 40,60,90
+ if intEqualsAny(ops.I, 0) ||
+ intEqualsAny(ops.I%10, 6) ||
+ intEqualsAny(ops.I%100, 40, 60, 90) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"gu", "hi"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2,3
+ if ops.NEqualsAny(2, 3) {
+ return Two
+ }
+ // n = 4
+ if ops.NEqualsAny(4) {
+ return Few
+ }
+ // n = 6
+ if ops.NEqualsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"as", "bn"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,5,7,8,9,10
+ if ops.NEqualsAny(1, 5, 7, 8, 9, 10) {
+ return One
+ }
+ // n = 2,3
+ if ops.NEqualsAny(2, 3) {
+ return Two
+ }
+ // n = 4
+ if ops.NEqualsAny(4) {
+ return Few
+ }
+ // n = 6
+ if ops.NEqualsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"or"}, &Rule{
+ PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 1,5,7..9
+ if ops.NInRange(7, 9) || ops.NEqualsAny(1, 5) {
+ return One
+ }
+ // n = 2,3
+ if ops.NEqualsAny(2, 3) {
+ return Two
+ }
+ // n = 4
+ if ops.NEqualsAny(4) {
+ return Few
+ }
+ // n = 6
+ if ops.NEqualsAny(6) {
+ return Many
+ }
+ return Other
+ },
+ })
+ addPluralRules(rules, "ordinal", []string{"cy"}, &Rule{
+ PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
+ PluralFormFunc: func(ops *Operands) Form {
+ // n = 0,7,8,9
+ if ops.NEqualsAny(0, 7, 8, 9) {
+ return Zero
+ }
+ // n = 1
+ if ops.NEqualsAny(1) {
+ return One
+ }
+ // n = 2
+ if ops.NEqualsAny(2) {
+ return Two
+ }
+ // n = 3,4
+ if ops.NEqualsAny(3, 4) {
+ return Few
+ }
+ // n = 5,6
+ if ops.NEqualsAny(5, 6) {
+ return Many
+ }
+ return Other
+ },
+ })
+
+ return rules
+}
diff --git a/modules/translation/i18n/plurals/rules_gen_test.go b/modules/translation/i18n/plurals/rules_gen_test.go
new file mode 100644
index 0000000000000..b69f1bc1f3c22
--- /dev/null
+++ b/modules/translation/i18n/plurals/rules_gen_test.go
@@ -0,0 +1,1083 @@
+// This file is generated by modules/translation/i18n/plurals/generate/main/generate.go DO NOT EDIT
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package plurals
+
+import "testing"
+
+func Test0cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0~15", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"bm", "bo", "dz", "hnj", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "osa", "root", "sah", "ses", "sg", "su", "th", "to", "tpi", "vi", "wo", "yo", "yue", "zh"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test1cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~1.0", "0.00~0.04"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"1.1~2.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"am", "as", "bn", "doi", "fa", "gu", "hi", "kn", "pcm", "zu"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test2cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~1.5"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ff", "hy", "kab"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test3cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "ji", "lij", "nl", "sc", "scn", "sv", "sw", "ur", "yi"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test4cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0", "0.1", "1.0", "0.00", "0.01", "1.00", "0.000", "0.001", "1.000", "0.0000", "0.0001", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.2~0.9", "1.1~1.8", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"si"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test5cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0", "1.0", "0.00", "1.00", "0.000", "1.000", "0.0000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ak", "bho", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test6cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1", "11~24"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0", "1.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "19.0", "20.0", "21.0", "22.0", "23.0", "24.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~10", "100~106", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"tzm"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test7cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"af", "an", "asa", "az", "bal", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test8cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1~1.6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "2.0~3.4", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"da"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test9cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1~1.6", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"is"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test10cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "0.2~1.0", "1.2~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"mk"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test11cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0~3", "5", "7", "8", "10~13", "15", "17", "18", "20", "21", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~0.3", "0.5", "0.7", "0.8", "1.0~1.3", "1.5", "1.7", "1.8", "2.0", "2.1", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"4", "6", "9", "14", "16", "19", "24", "26", "104", "1004"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.4", "0.6", "0.9", "1.4", "1.6", "1.9", "2.4", "2.6", "10.4", "100.4", "1000.4"})
+
+ locales := []string{"ceb", "fil", "tl"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test12cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0", "10~20", "30", "40", "50", "60", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1", "1.0", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~9", "22~29", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.2~0.9", "1.2~1.9", "10.2", "100.2", "1000.2"})
+
+ locales := []string{"lv", "prg"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test13cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "0.00", "0.000", "0.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1~1.6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"lag"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test14cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "0.00", "0.000", "0.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ksh"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test15cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "2.00", "2.000", "2.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "3~17", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"iu", "naq", "sat", "se", "sma", "smi", "smj", "smn", "sms"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test16cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~1.0", "0.00~0.04"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~10"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "2.00", "3.00", "4.00", "5.00", "6.00", "7.00", "8.00"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"11~26", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"1.1~1.9", "2.1~2.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"shi"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test17cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"0", "2~16", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"20~35", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"mo", "ro"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test18cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"0.2~0.4", "1.2~1.4", "2.2~2.4", "3.2~3.4", "4.2~4.4", "5.2", "10.2", "100.2", "1000.2"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "0.5~1.0", "1.5~2.0", "2.5~2.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"bs", "hr", "sh", "sr"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test19cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~1.5"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"1000000", "1c6", "2c6", "3c6", "4c6", "5c6", "6c6"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"1.0000001c6", "1.1c6", "2.0000001c6", "2.1c6", "3.0000001c6", "3.1c6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1c3", "2c3", "3c3", "4c3", "5c3", "6c3"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0", "1.0001c3", "1.1c3", "2.0001c3", "2.1c3", "3.0001c3", "3.1c3"})
+
+ locales := []string{"fr"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test20cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"0", "1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.0~1.5"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"1000000", "1c6", "2c6", "3c6", "4c6", "5c6", "6c6"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"1.0000001c6", "1.1c6", "2.0000001c6", "2.1c6", "3.0000001c6", "3.1c6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"2~17", "100", "1000", "10000", "100000", "1c3", "2c3", "3c3", "4c3", "5c3", "6c3"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"2.0~3.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0", "1.0001c3", "1.1c3", "2.0001c3", "2.1c3", "3.0001c3", "3.1c3"})
+
+ locales := []string{"pt"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test21cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"1000000", "1c6", "2c6", "3c6", "4c6", "5c6", "6c6"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"1.0000001c6", "1.1c6", "2.0000001c6", "2.1c6", "3.0000001c6", "3.1c6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1c3", "2c3", "3c3", "4c3", "5c3", "6c3"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0", "1.0001c3", "1.1c3", "2.0001c3", "2.1c3", "3.0001c3", "3.1c3"})
+
+ locales := []string{"it", "pt_PT"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test22cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"1000000", "1c6", "2c6", "3c6", "4c6", "5c6", "6c6"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"1.0000001c6", "1.1c6", "2.0000001c6", "2.1c6", "3.0000001c6", "3.1c6"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1c3", "2c3", "3c3", "4c3", "5c3", "6c3"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0", "1.0001c3", "1.1c3", "2.0001c3", "2.1c3", "3.0001c3", "3.1c3"})
+
+ locales := []string{"es"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test23cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "11"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "11.0", "1.00", "11.00", "1.000", "11.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "12"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "12.0", "2.00", "12.00", "2.000", "12.000", "2.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3~10", "13~19"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "19.0", "3.00"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "20~34", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"gd"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test24cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "101", "201", "301", "401", "501", "601", "701", "1001"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "102", "202", "302", "402", "502", "602", "702", "1002"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3", "4", "103", "104", "203", "204", "303", "304", "403", "404", "503", "504", "603", "604", "703", "704", "1003"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"sl"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test25cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "101", "201", "301", "401", "501", "601", "701", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"0.1", "1.1", "2.1", "3.1", "4.1", "5.1", "6.1", "7.1", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "102", "202", "302", "402", "502", "602", "702", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"0.2", "1.2", "2.2", "3.2", "4.2", "5.2", "6.2", "7.2", "10.2", "100.2", "1000.2"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3", "4", "103", "104", "203", "204", "303", "304", "403", "404", "503", "504", "603", "604", "703", "704", "1003"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"0.3", "0.4", "1.3", "1.4", "2.3", "2.4", "3.3", "3.4", "4.3", "4.4", "5.3", "5.4", "6.3", "6.4", "7.3", "7.4", "10.3", "100.3", "1000.3"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "0.5~1.0", "1.5~2.0", "2.5~2.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"dsb", "hsb"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test26cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"20", "30", "40", "50", "60", "70", "80", "90", "100", "1000", "10000", "100000", "1000000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "3~17", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"he", "iw"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test27cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~4"})
+
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"cs", "sk"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test28cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"pl"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test29cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "71.0", "81.0", "101.0", "1001.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"2.0", "3.0", "4.0", "22.0", "23.0", "24.0", "32.0", "33.0", "102.0", "1002.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"0.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "11.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.1", "1000.1"})
+
+ locales := []string{"be"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test30cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "71.0", "81.0", "101.0", "1001.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~9", "22~29", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "22.0", "102.0", "1002.0"})
+
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.1", "1000.1"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "10~20", "30", "40", "50", "60", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0", "10.0", "11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"lt"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test31cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"0", "2~10", "102~107", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"0.0", "2.0", "3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "10.0", "102.0", "1002.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"11~19", "111~117", "1011"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "111.0", "1011.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"20~35", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"mt"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test32cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"2~4", "22~24", "32~34", "42~44", "52~54", "62", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ru", "uk"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test33cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "21", "31", "41", "51", "61", "81", "101", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "21.0", "31.0", "41.0", "51.0", "61.0", "81.0", "101.0", "1001.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "22", "32", "42", "52", "62", "82", "102", "1002"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "22.0", "32.0", "42.0", "52.0", "62.0", "82.0", "102.0", "1002.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3", "4", "9", "23", "24", "29", "33", "34", "39", "43", "44", "49", "103", "1003"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "4.0", "9.0", "23.0", "24.0", "29.0", "33.0", "34.0", "103.0", "1003.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"1000000.0", "1000000.00", "1000000.000", "1000000.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "5~8", "10~20", "100", "1000", "10000", "100000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.0", "100.0", "1000.0", "10000.0", "100000.0"})
+
+ locales := []string{"br"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test34cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "2.00", "2.000", "2.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3~6"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "4.0", "5.0", "6.0", "3.00", "4.00", "5.00", "6.00", "3.000", "4.000", "5.000", "6.000", "3.0000", "4.0000", "5.0000", "6.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"7~10"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"7.0", "8.0", "9.0", "10.0", "7.00", "8.00", "9.00", "10.00", "7.000", "8.000", "9.000", "10.000", "7.0000", "8.0000", "9.0000", "10.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"0", "11~25", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.0~0.9", "1.1~1.6", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ga"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test35cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1", "11", "21", "31", "41", "51", "61", "71", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "12", "22", "32", "42", "52", "62", "72", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"0", "20", "40", "60", "80", "100", "120", "140", "1000", "10000", "100000", "1000000"})
+
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"0.0~1.5", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"3~10", "13~19", "23", "103", "1003"})
+
+ locales := []string{"gv"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test36cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "0.00", "0.000", "0.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2", "22", "42", "62", "82", "102", "122", "142", "1000", "10000", "100000"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "22.0", "42.0", "62.0", "82.0", "102.0", "122.0", "142.0", "1000.0", "10000.0", "100000.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3", "23", "43", "63", "83", "103", "123", "143", "1003"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "23.0", "43.0", "63.0", "83.0", "103.0", "123.0", "143.0", "1003.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"21", "41", "61", "81", "101", "121", "141", "161", "1001"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"21.0", "41.0", "61.0", "81.0", "101.0", "121.0", "141.0", "161.0", "1001.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"4~19", "100", "1004", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.1", "1000000.0"})
+
+ locales := []string{"kw"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test37cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "0.00", "0.000", "0.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "2.00", "2.000", "2.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3~10", "103~110", "1003"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "4.0", "5.0", "6.0", "7.0", "8.0", "9.0", "10.0", "103.0", "1003.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"11~26", "111", "1011"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"11.0", "12.0", "13.0", "14.0", "15.0", "16.0", "17.0", "18.0", "111.0", "1011.0"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"100~102", "200~202", "300~302", "400~402", "500~502", "600", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.1", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"ar", "ars"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test38cardinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "cardinal", Zero, []string{"0"})
+ tests = appendDecimalTests(tests, "cardinal", Zero, []string{"0.0", "0.00", "0.000", "0.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", One, []string{"1"})
+ tests = appendDecimalTests(tests, "cardinal", One, []string{"1.0", "1.00", "1.000", "1.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Two, []string{"2"})
+ tests = appendDecimalTests(tests, "cardinal", Two, []string{"2.0", "2.00", "2.000", "2.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Few, []string{"3"})
+ tests = appendDecimalTests(tests, "cardinal", Few, []string{"3.0", "3.00", "3.000", "3.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Many, []string{"6"})
+ tests = appendDecimalTests(tests, "cardinal", Many, []string{"6.0", "6.00", "6.000", "6.0000"})
+
+ tests = appendIntegerTests(tests, "cardinal", Other, []string{"4", "5", "7~20", "100", "1000", "10000", "100000", "1000000"})
+ tests = appendDecimalTests(tests, "cardinal", Other, []string{"0.1~0.9", "1.1~1.7", "10.0", "100.0", "1000.0", "10000.0", "100000.0", "1000000.0"})
+
+ locales := []string{"cy"}
+ for _, locale := range locales {
+ runTests(t, locale, "cardinal", tests)
+ }
+}
+
+func Test0ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~15", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"af", "am", "an", "ar", "bg", "bs", "ce", "cs", "da", "de", "dsb", "el", "es", "et", "eu", "fa", "fi", "fy", "gl", "gsw", "he", "hr", "hsb", "ia", "id", "in", "is", "iw", "ja", "km", "kn", "ko", "ky", "lt", "lv", "ml", "mn", "my", "nb", "nl", "no", "pa", "pl", "prg", "ps", "pt", "root", "ru", "sd", "sh", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tpi", "tr", "ur", "uz", "yue", "zh", "zu"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test1ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "2", "21", "22", "31", "32", "41", "42", "51", "52", "61", "62", "71", "72", "81", "82", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "3~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"sv"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test2ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "2~16", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"bal", "fil", "fr", "ga", "hy", "lo", "mo", "ms", "ro", "tl", "vi"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test3ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "5"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "2~4", "6~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"hu"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test4ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1~4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"ne"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test5ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"2", "3", "22", "23", "32", "33", "42", "43", "52", "53", "62", "63", "72", "73", "82", "83", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "1", "4~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"be"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test6ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"3", "23", "33", "43", "53", "63", "73", "83", "103", "1003"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~2", "4~16", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"uk"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test7ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"6", "9", "10", "16", "19", "26", "29", "36", "39", "106", "1006"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~5", "7", "8", "11~15", "17", "18", "20", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"tk"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test8ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"6", "9", "10", "16", "19", "20", "26", "29", "30", "36", "39", "40", "100", "1000", "10000", "100000", "1000000"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~5", "7", "8", "11~15", "17", "18", "21", "101", "1001"})
+
+ locales := []string{"kk"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test9ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"8", "11", "80", "800"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~7", "9", "10", "12~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"it", "sc", "scn"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test10ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"8", "11", "80~89", "800~803"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0~7", "9", "10", "12~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"lij"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test11ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"0", "2~16", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"21~36", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"ka"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test12ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"4", "24", "34", "44", "54", "64", "74", "84", "104", "1004"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "2", "3", "5~17", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"sq"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test13ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1~4", "21~24", "41~44", "61~64", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"5", "105", "205", "305", "405", "505", "605", "705", "1005"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "6~20", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"kw"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test14ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "22", "32", "42", "52", "62", "72", "82", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"3", "23", "33", "43", "53", "63", "73", "83", "103", "1003"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "4~18", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"en"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test15ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "3"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"mr"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test16ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "11"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "12"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"3", "13"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "4~10", "14~21", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"gd"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test17ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "3"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "5~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"ca"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test18ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "21", "31", "41", "51", "61", "71", "81", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "22", "32", "42", "52", "62", "72", "82", "102", "1002"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"7", "8", "27", "28", "37", "38", "47", "48", "57", "58", "67", "68", "77", "78", "87", "88", "107", "1007"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "3~6", "9~19", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"mk"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test19ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "2", "5", "7", "8", "11", "12", "15", "17", "18", "20~22", "25", "101", "1001"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"3", "4", "13", "14", "23", "24", "33", "34", "43", "44", "53", "54", "63", "64", "73", "74", "100", "1003"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"0", "6", "16", "26", "36", "40", "46", "56", "106", "1006"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"9", "10", "19", "29", "30", "39", "49", "59", "69", "79", "109", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"az"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test20ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "3"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"6"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "5", "7~20", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"gu", "hi"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test21ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "5", "7~10"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "3"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"6"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "11~25", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"as", "bn"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test22ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1", "5", "7~9"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2", "3"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"6"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"0", "10~24", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"or"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
+
+func Test23ordinal(t *testing.T) {
+ var tests []pluralFormTest
+
+ tests = appendIntegerTests(tests, "ordinal", Zero, []string{"0", "7~9"})
+
+ tests = appendIntegerTests(tests, "ordinal", One, []string{"1"})
+
+ tests = appendIntegerTests(tests, "ordinal", Two, []string{"2"})
+
+ tests = appendIntegerTests(tests, "ordinal", Few, []string{"3", "4"})
+
+ tests = appendIntegerTests(tests, "ordinal", Many, []string{"5", "6"})
+
+ tests = appendIntegerTests(tests, "ordinal", Other, []string{"10~25", "100", "1000", "10000", "100000", "1000000"})
+
+ locales := []string{"cy"}
+ for _, locale := range locales {
+ runTests(t, locale, "ordinal", tests)
+ }
+}
diff --git a/modules/translation/i18n/plurals/rules_test.go b/modules/translation/i18n/plurals/rules_test.go
new file mode 100644
index 0000000000000..d58b932438719
--- /dev/null
+++ b/modules/translation/i18n/plurals/rules_test.go
@@ -0,0 +1,81 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// This file is heavily inspired by https://github.com/nicksnyder/go-i18n/tree/main/v2/internal/plural
+
+package plurals
+
+import (
+ "testing"
+)
+
+func TestRules(t *testing.T) {
+ expectedRule := &Rule{}
+
+ testCases := []struct {
+ name string
+ rules Rules
+ locale string
+ rule *Rule
+ }{
+ {
+ name: "exact match",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "en": expectedRule,
+ "es": {},
+ }},
+ locale: "en",
+ rule: expectedRule,
+ },
+ {
+ name: "inexact match",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "en": expectedRule,
+ }},
+ locale: "en-US",
+ rule: expectedRule,
+ },
+ {
+ name: "portuguese doesn't match european portuguese",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "pt-PT": {},
+ }},
+ locale: "pt",
+ rule: nil,
+ },
+ {
+ name: "european portuguese preferred",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "pt": {},
+ "pt-PT": expectedRule,
+ }},
+ locale: "pt-PT",
+ rule: expectedRule,
+ },
+ {
+ name: "zh-Hans",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "zh": expectedRule,
+ }},
+ locale: "zh-Hans",
+ rule: expectedRule,
+ },
+ {
+ name: "zh-Hant",
+ rules: Rules{"cardinal": map[string]*Rule{
+ "zh": expectedRule,
+ }},
+ locale: "zh-Hant",
+ rule: expectedRule,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ if rule := testCase.rules.Rule(testCase.locale); rule != testCase.rule {
+ panic(rule)
+ }
+ })
+ }
+}
diff --git a/modules/translation/i18n/translatable.go b/modules/translation/i18n/translatable.go
index 2392a41a90a4f..a3c7358f172ac 100644
--- a/modules/translation/i18n/translatable.go
+++ b/modules/translation/i18n/translatable.go
@@ -8,7 +8,8 @@ import "fmt"
// Locale represents an interface to translation
type Locale interface {
- Tr(string, ...interface{}) string
+ Tr(key string, args ...interface{}) string
+ TrPlural(cnt interface{}, key string, args ...interface{}) string
}
// TranslatableFormatted structs provide their own translated string when formatted in translation
@@ -16,8 +17,8 @@ type TranslatableFormatted interface {
TranslatedFormat(l Locale, s fmt.State, c rune)
}
-// TranslatableString structs provide their own translated string when formatted as a string in translation
-type TranslatableString interface {
+// TranslatableStringer structs provide their own translated string when formatted as a string in translation
+type TranslatableStringer interface {
TranslatedString(l Locale) string
}
@@ -32,7 +33,7 @@ func (f formatWrapper) Format(s fmt.State, c rune) {
type stringWrapper struct {
l Locale
- t TranslatableString
+ t TranslatableStringer
}
func (s stringWrapper) String() string {
From e30503e83266df01e00c1b3b3aafea88561d8a0a Mon Sep 17 00:00:00 2001
From: Andrew Thornton
Date: Sat, 9 Jul 2022 21:38:41 +0100
Subject: [PATCH 03/15] reduce copying
Signed-off-by: Andrew Thornton
---
modules/translation/i18n/i18n.go | 25 ++--
.../i18n/plurals/generate/condition_parser.go | 2 +-
.../i18n/plurals/generate/main/generate.go | 9 +-
modules/translation/i18n/plurals/rule_test.go | 2 +-
modules/translation/i18n/plurals/rules.go | 59 ++++++--
modules/translation/i18n/plurals/rules_gen.go | 133 +++++++++---------
.../translation/i18n/plurals/rules_test.go | 12 +-
7 files changed, 140 insertions(+), 102 deletions(-)
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go
index 584ffb3560102..ecc90138e3026 100644
--- a/modules/translation/i18n/i18n.go
+++ b/modules/translation/i18n/i18n.go
@@ -33,7 +33,6 @@ type locale struct {
langName string
idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
- pluralRules map[plurals.RuleType]*plurals.Rule
sourceFileName string
sourceFileInfo os.FileInfo
@@ -52,11 +51,10 @@ type LocaleStore struct {
trKeyToIdxMap map[string]int
defaultLang string
- pluralRules plurals.Rules
}
func NewLocaleStore(isProd bool) *LocaleStore {
- store := &LocaleStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int), pluralRules: plurals.DefaultRules()}
+ store := &LocaleStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)}
if !isProd {
store.reloadMu = &sync.RWMutex{}
}
@@ -93,12 +91,6 @@ func (store *LocaleStore) AddLocaleByIni(langName, langDesc string, source inter
store.langNames = append(store.langNames, langName)
store.langDescs = append(store.langDescs, langDesc)
- l.pluralRules = map[plurals.RuleType]*plurals.Rule{}
- for typ, ruleMap := range store.pluralRules {
- rule := ruleMap[l.langName]
- l.pluralRules[typ] = rule
- }
-
store.localeMap[l.langName] = l
return nil
@@ -328,22 +320,25 @@ func (l *locale) HasMessage(key string) bool {
}
func (l *locale) TrOrdinal(cnt interface{}, key string, args ...interface{}) string {
- return l.TrPlurals(cnt, string(plurals.Ordinal), key, args...)
+ return l.trPlurals(cnt, plurals.DefaultRules.Ordinal(l.langName), key, args...)
}
func (l *locale) TrPlural(cnt interface{}, key string, args ...interface{}) string {
- return l.TrPlurals(cnt, string(plurals.Cardinal), key, args...)
+ return l.trPlurals(cnt, plurals.DefaultRules.Rule(l.langName), key, args...)
}
func (l *locale) TrPlurals(cnt interface{}, ruleType, key string, args ...interface{}) string {
- operands, err := plurals.NewOperands(cnt)
- if err != nil {
+ return l.trPlurals(cnt, plurals.DefaultRules.RuleByType(plurals.RuleType(ruleType), l.langName), key, args...)
+}
+
+func (l *locale) trPlurals(cnt interface{}, rule *plurals.Rule, key string, args ...interface{}) string {
+ if rule == nil {
// if we fail to parse fall back to the standard
return l.Tr(key, args...)
}
- rule := l.pluralRules[plurals.RuleType(ruleType)]
- if rule == nil {
+ operands, err := plurals.NewOperands(cnt)
+ if err != nil {
// if we fail to parse fall back to the standard
return l.Tr(key, args...)
}
diff --git a/modules/translation/i18n/plurals/generate/condition_parser.go b/modules/translation/i18n/plurals/generate/condition_parser.go
index a5c20fe63a2bb..cc151decf6952 100644
--- a/modules/translation/i18n/plurals/generate/condition_parser.go
+++ b/modules/translation/i18n/plurals/generate/condition_parser.go
@@ -12,7 +12,7 @@ import (
"strings"
)
-// As noted above relation is a lot simpler than the original full rules imply:
+// As noted below relation is a lot simpler than the original full rules imply:
//
// relation = expr ('=' | '!=') range_list
// expr = operand ('%' value)?
diff --git a/modules/translation/i18n/plurals/generate/main/generate.go b/modules/translation/i18n/plurals/generate/main/generate.go
index 66d16cfd0a169..5e392273ddec5 100644
--- a/modules/translation/i18n/plurals/generate/main/generate.go
+++ b/modules/translation/i18n/plurals/generate/main/generate.go
@@ -120,11 +120,13 @@ var codeTemplate = template.Must(template.New("codeTemplate").Parse(`// This fil
package plurals
// DefaultRules returns a map of Rules generated from CLDR language data.
-func DefaultRules() Rules {
- rules := Rules{}
+var DefaultRules *Rules
+
+func init() {
+ DefaultRules := &Rules{}
{{range $p, $plurals := .Plurals}}
{{range .LocaleGroups}}
- addPluralRules(rules, {{printf "%q" $plurals.Type}}, {{printf "%#v" .SplitLocales}}, &Rule{
+ addPluralRules(DefaultRules, {{printf "%q" $plurals.Type}}, {{printf "%#v" .SplitLocales}}, &Rule{
PluralForms: newPluralFormSet({{range $i, $e := .Rules}}{{if $i}}, {{end}}{{$e.CountTitle}}{{end}}),
PluralFormFunc: func(ops *Operands) Form { {{range .Rules}}{{if .GoCondition}}
// {{.Condition}}
@@ -135,7 +137,6 @@ func DefaultRules() Rules {
},
}){{end}}
{{end}}
- return rules
}
`))
diff --git a/modules/translation/i18n/plurals/rule_test.go b/modules/translation/i18n/plurals/rule_test.go
index 9ec5107437353..70611f7ec5146 100644
--- a/modules/translation/i18n/plurals/rule_test.go
+++ b/modules/translation/i18n/plurals/rule_test.go
@@ -22,7 +22,7 @@ func runTests(t *testing.T, pluralRuleID, typ string, tests []pluralFormTest) {
if pluralRuleID == "root" {
return
}
- pluralRules := DefaultRules()
+ pluralRules := DefaultRules
if rule := pluralRules.RuleByType(RuleType(typ), pluralRuleID); rule != nil {
for _, test := range tests {
ops, err := NewOperands(test.num)
diff --git a/modules/translation/i18n/plurals/rules.go b/modules/translation/i18n/plurals/rules.go
index 05ea88b700bfb..03411cb0234d2 100644
--- a/modules/translation/i18n/plurals/rules.go
+++ b/modules/translation/i18n/plurals/rules.go
@@ -25,15 +25,31 @@ type Rule struct {
PluralFormFunc func(*Operands) Form
}
-func addPluralRules(rules Rules, typ RuleType, ids []string, ps *Rule) {
+func addPluralRules(rules *Rules, typ RuleType, ids []string, ps *Rule) {
for _, id := range ids {
if id == "root" {
continue
}
- if rules[typ] == nil {
- rules[typ] = map[string]*Rule{}
+ switch typ {
+ case Cardinal:
+ if rules.CardinalMap == nil {
+ rules.CardinalMap = map[string]*Rule{}
+ }
+ rules.CardinalMap[id] = ps
+ case Ordinal:
+ if rules.OrdinalMap == nil {
+ rules.OrdinalMap = map[string]*Rule{}
+ }
+ rules.OrdinalMap[id] = ps
+ default:
+ if rules.Others == nil {
+ rules.Others = map[RuleType]map[string]*Rule{}
+ }
+ if rules.Others[typ] == nil {
+ rules.Others[typ] = map[string]*Rule{}
+ }
+ rules.Others[typ][id] = ps
}
- rules[typ][id] = ps
}
}
@@ -45,18 +61,37 @@ func newPluralFormSet(pluralForms ...Form) map[Form]struct{} {
return set
}
-type Rules map[RuleType]map[string]*Rule
+type Rules struct {
+ CardinalMap map[string]*Rule
+ OrdinalMap map[string]*Rule
+ Others map[RuleType]map[string]*Rule
+}
// Rule returns the closest matching plural rule for the language tag
// or nil if no rule could be found.
func (r Rules) Rule(locale string) *Rule {
for {
- if rule, ok := r["cardinal"][locale]; ok {
+ if rule, ok := r.CardinalMap[locale]; ok {
+ return rule
+ }
+ idx := strings.LastIndex(locale, "-")
+ if idx < 0 {
+ return r.CardinalMap["en"]
+ }
+ locale = locale[:idx]
+ }
+}
+
+// Rule returns the closest matching plural rule for the language tag
+// or nil if no rule could be found.
+func (r Rules) Ordinal(locale string) *Rule {
+ for {
+ if rule, ok := r.OrdinalMap[locale]; ok {
return rule
}
idx := strings.LastIndex(locale, "-")
if idx < 0 {
- return r["cardinal"]["en"]
+ return r.OrdinalMap["en"]
}
locale = locale[:idx]
}
@@ -65,13 +100,19 @@ func (r Rules) Rule(locale string) *Rule {
// Rule returns the closest matching plural rule for the language tag
// or nil if no rule could be found.
func (r Rules) RuleByType(typ RuleType, locale string) *Rule {
+ switch typ {
+ case Cardinal:
+ return r.Rule(locale)
+ case Ordinal:
+ return r.Ordinal(locale)
+ }
for {
- if rule, ok := r[typ][locale]; ok {
+ if rule, ok := r.Others[typ][locale]; ok {
return rule
}
idx := strings.LastIndex(locale, "-")
if idx < 0 {
- return r[typ]["en"]
+ return r.Others[typ]["en"]
}
locale = locale[:idx]
}
diff --git a/modules/translation/i18n/plurals/rules_gen.go b/modules/translation/i18n/plurals/rules_gen.go
index fde14ec90dd38..e820abf82724c 100644
--- a/modules/translation/i18n/plurals/rules_gen.go
+++ b/modules/translation/i18n/plurals/rules_gen.go
@@ -6,16 +6,18 @@
package plurals
// DefaultRules returns a map of Rules generated from CLDR language data.
-func DefaultRules() Rules {
- rules := Rules{}
+var DefaultRules *Rules
- addPluralRules(rules, "cardinal", []string{"bm", "bo", "dz", "hnj", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "osa", "root", "sah", "ses", "sg", "su", "th", "to", "tpi", "vi", "wo", "yo", "yue", "zh"}, &Rule{
+func init() {
+ DefaultRules := &Rules{}
+
+ addPluralRules(DefaultRules, "cardinal", []string{"bm", "bo", "dz", "hnj", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "osa", "root", "sah", "ses", "sg", "su", "th", "to", "tpi", "vi", "wo", "yo", "yue", "zh"}, &Rule{
PluralForms: newPluralFormSet(Other),
PluralFormFunc: func(ops *Operands) Form {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"am", "as", "bn", "doi", "fa", "gu", "hi", "kn", "pcm", "zu"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"am", "as", "bn", "doi", "fa", "gu", "hi", "kn", "pcm", "zu"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0 or n = 1
@@ -26,7 +28,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ff", "hy", "kab"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ff", "hy", "kab"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0,1
@@ -36,7 +38,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "ji", "lij", "nl", "sc", "scn", "sv", "sw", "ur", "yi"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "ji", "lij", "nl", "sc", "scn", "sv", "sw", "ur", "yi"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -46,7 +48,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"si"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"si"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0,1 or i = 0 and f = 1
@@ -57,7 +59,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ak", "bho", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ak", "bho", "guw", "ln", "mg", "nso", "pa", "ti", "wa"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0..1
@@ -67,7 +69,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"tzm"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"tzm"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0..1 or n = 11..99
@@ -78,7 +80,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"af", "an", "asa", "az", "bal", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"af", "an", "asa", "az", "bal", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "eu", "fo", "fur", "gsw", "ha", "haw", "hu", "jgo", "jmc", "ka", "kaj", "kcg", "kk", "kkj", "kl", "ks", "ksb", "ku", "ky", "lb", "lg", "mas", "mgo", "ml", "mn", "mr", "nah", "nb", "nd", "ne", "nn", "nnh", "no", "nr", "ny", "nyn", "om", "or", "os", "pap", "ps", "rm", "rof", "rwk", "saq", "sd", "sdh", "seh", "sn", "so", "sq", "ss", "ssy", "st", "syr", "ta", "te", "teo", "tig", "tk", "tn", "tr", "ts", "ug", "uz", "ve", "vo", "vun", "wae", "xh", "xog"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -88,7 +90,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"da"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"da"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1 or t != 0 and i = 0,1
@@ -99,7 +101,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"is"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"is"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0
@@ -110,7 +112,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"mk"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"mk"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
@@ -121,7 +123,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ceb", "fil", "tl"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ceb", "fil", "tl"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9
@@ -133,7 +135,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"lv", "prg"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"lv", "prg"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19
@@ -151,7 +153,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"lag"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"lag"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0
@@ -165,7 +167,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ksh"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ksh"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0
@@ -179,7 +181,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"iu", "naq", "sat", "se", "sma", "smi", "smj", "smn", "sms"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"iu", "naq", "sat", "se", "sma", "smi", "smj", "smn", "sms"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -193,7 +195,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"shi"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"shi"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0 or n = 1
@@ -208,7 +210,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"mo", "ro"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"mo", "ro"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -224,7 +226,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"bs", "hr", "sh", "sr"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"bs", "hr", "sh", "sr"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11
@@ -240,7 +242,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"fr"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"fr"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0,1
@@ -255,7 +257,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"pt"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"pt"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0..1
@@ -270,7 +272,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"it", "pt_PT"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"it", "pt_PT"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -285,7 +287,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"es"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"es"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -300,7 +302,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"gd"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"gd"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,11
@@ -318,7 +320,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"sl"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"sl"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 100 = 1
@@ -337,7 +339,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"dsb", "hsb"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"dsb", "hsb"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 100 = 1 or f % 100 = 1
@@ -358,7 +360,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"he", "iw"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"he", "iw"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -376,7 +378,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"cs", "sk"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"cs", "sk"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -394,7 +396,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"pl"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"pl"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1 and v = 0
@@ -414,7 +416,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"be"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"be"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 1 and n % 100 != 11
@@ -434,7 +436,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"lt"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"lt"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 1 and n % 100 != 11..19
@@ -452,7 +454,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"mt"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"mt"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -471,7 +473,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ru", "uk"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ru", "uk"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 10 = 1 and i % 100 != 11
@@ -491,7 +493,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"br"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"br"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 1 and n % 100 != 11,71,91
@@ -513,7 +515,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ga"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ga"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -535,7 +537,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"gv"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"gv"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// v = 0 and i % 10 = 1
@@ -557,7 +559,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"kw"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"kw"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0
@@ -585,7 +587,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"ar", "ars"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"ar", "ars"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0
@@ -611,7 +613,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "cardinal", []string{"cy"}, &Rule{
+ addPluralRules(DefaultRules, "cardinal", []string{"cy"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0
@@ -638,13 +640,13 @@ func DefaultRules() Rules {
},
})
- addPluralRules(rules, "ordinal", []string{"af", "am", "an", "ar", "bg", "bs", "ce", "cs", "da", "de", "dsb", "el", "es", "et", "eu", "fa", "fi", "fy", "gl", "gsw", "he", "hr", "hsb", "ia", "id", "in", "is", "iw", "ja", "km", "kn", "ko", "ky", "lt", "lv", "ml", "mn", "my", "nb", "nl", "no", "pa", "pl", "prg", "ps", "pt", "root", "ru", "sd", "sh", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tpi", "tr", "ur", "uz", "yue", "zh", "zu"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"af", "am", "an", "ar", "bg", "bs", "ce", "cs", "da", "de", "dsb", "el", "es", "et", "eu", "fa", "fi", "fy", "gl", "gsw", "he", "hr", "hsb", "ia", "id", "in", "is", "iw", "ja", "km", "kn", "ko", "ky", "lt", "lv", "ml", "mn", "my", "nb", "nl", "no", "pa", "pl", "prg", "ps", "pt", "root", "ru", "sd", "sh", "si", "sk", "sl", "sr", "sw", "ta", "te", "th", "tpi", "tr", "ur", "uz", "yue", "zh", "zu"}, &Rule{
PluralForms: newPluralFormSet(Other),
PluralFormFunc: func(ops *Operands) Form {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"sv"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"sv"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 1,2 and n % 100 != 11,12
@@ -654,7 +656,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"bal", "fil", "fr", "ga", "hy", "lo", "mo", "ms", "ro", "tl", "vi"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"bal", "fil", "fr", "ga", "hy", "lo", "mo", "ms", "ro", "tl", "vi"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -664,7 +666,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"hu"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"hu"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,5
@@ -674,7 +676,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"ne"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"ne"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1..4
@@ -684,7 +686,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"be"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"be"}, &Rule{
PluralForms: newPluralFormSet(Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 2,3 and n % 100 != 12,13
@@ -694,7 +696,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"uk"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"uk"}, &Rule{
PluralForms: newPluralFormSet(Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 3 and n % 100 != 13
@@ -704,7 +706,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"tk"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"tk"}, &Rule{
PluralForms: newPluralFormSet(Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 6,9 or n = 10
@@ -715,7 +717,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"kk"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"kk"}, &Rule{
PluralForms: newPluralFormSet(Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 6 or n % 10 = 9 or n % 10 = 0 and n != 0
@@ -727,7 +729,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"it", "sc", "scn"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"it", "sc", "scn"}, &Rule{
PluralForms: newPluralFormSet(Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 11,8,80,800
@@ -737,7 +739,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"lij"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"lij"}, &Rule{
PluralForms: newPluralFormSet(Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 11,8,80..89,800..899
@@ -747,7 +749,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"ka"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"ka"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 1
@@ -762,7 +764,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"sq"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"sq"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -776,7 +778,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"kw"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"kw"}, &Rule{
PluralForms: newPluralFormSet(One, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1..4 or n % 100 = 1..4,21..24,41..44,61..64,81..84
@@ -792,7 +794,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"en"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"en"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n % 10 = 1 and n % 100 != 11
@@ -810,7 +812,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"mr"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"mr"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -828,7 +830,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"gd"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"gd"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,11
@@ -846,7 +848,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"ca"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"ca"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,3
@@ -864,7 +866,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"mk"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"mk"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i % 10 = 1 and i % 100 != 11
@@ -882,7 +884,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"az"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"az"}, &Rule{
PluralForms: newPluralFormSet(One, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// i % 10 = 1,2,5,7,8 or i % 100 = 20,50,70,80
@@ -904,7 +906,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"gu", "hi"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"gu", "hi"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1
@@ -926,7 +928,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"as", "bn"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"as", "bn"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,5,7,8,9,10
@@ -948,7 +950,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"or"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"or"}, &Rule{
PluralForms: newPluralFormSet(One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 1,5,7..9
@@ -970,7 +972,7 @@ func DefaultRules() Rules {
return Other
},
})
- addPluralRules(rules, "ordinal", []string{"cy"}, &Rule{
+ addPluralRules(DefaultRules, "ordinal", []string{"cy"}, &Rule{
PluralForms: newPluralFormSet(Zero, One, Two, Few, Many, Other),
PluralFormFunc: func(ops *Operands) Form {
// n = 0,7,8,9
@@ -997,5 +999,4 @@ func DefaultRules() Rules {
},
})
- return rules
}
diff --git a/modules/translation/i18n/plurals/rules_test.go b/modules/translation/i18n/plurals/rules_test.go
index d58b932438719..7af8757d824a8 100644
--- a/modules/translation/i18n/plurals/rules_test.go
+++ b/modules/translation/i18n/plurals/rules_test.go
@@ -21,7 +21,7 @@ func TestRules(t *testing.T) {
}{
{
name: "exact match",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"en": expectedRule,
"es": {},
}},
@@ -30,7 +30,7 @@ func TestRules(t *testing.T) {
},
{
name: "inexact match",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"en": expectedRule,
}},
locale: "en-US",
@@ -38,7 +38,7 @@ func TestRules(t *testing.T) {
},
{
name: "portuguese doesn't match european portuguese",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"pt-PT": {},
}},
locale: "pt",
@@ -46,7 +46,7 @@ func TestRules(t *testing.T) {
},
{
name: "european portuguese preferred",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"pt": {},
"pt-PT": expectedRule,
}},
@@ -55,7 +55,7 @@ func TestRules(t *testing.T) {
},
{
name: "zh-Hans",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"zh": expectedRule,
}},
locale: "zh-Hans",
@@ -63,7 +63,7 @@ func TestRules(t *testing.T) {
},
{
name: "zh-Hant",
- rules: Rules{"cardinal": map[string]*Rule{
+ rules: Rules{CardinalMap: map[string]*Rule{
"zh": expectedRule,
}},
locale: "zh-Hant",
From b26dcf01a1b8de2c6598e87081b4bd8ebfd7800c Mon Sep 17 00:00:00 2001
From: Andrew Thornton
Date: Sun, 10 Jul 2022 15:43:54 +0100
Subject: [PATCH 04/15] use assign not :=
Signed-off-by: Andrew Thornton
---
modules/translation/i18n/plurals/generate/main/generate.go | 2 +-
modules/translation/i18n/plurals/rules_gen.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/translation/i18n/plurals/generate/main/generate.go b/modules/translation/i18n/plurals/generate/main/generate.go
index 5e392273ddec5..23a4bbdd4f65c 100644
--- a/modules/translation/i18n/plurals/generate/main/generate.go
+++ b/modules/translation/i18n/plurals/generate/main/generate.go
@@ -123,7 +123,7 @@ package plurals
var DefaultRules *Rules
func init() {
- DefaultRules := &Rules{}
+ DefaultRules = &Rules{}
{{range $p, $plurals := .Plurals}}
{{range .LocaleGroups}}
addPluralRules(DefaultRules, {{printf "%q" $plurals.Type}}, {{printf "%#v" .SplitLocales}}, &Rule{
diff --git a/modules/translation/i18n/plurals/rules_gen.go b/modules/translation/i18n/plurals/rules_gen.go
index e820abf82724c..0ac032d3021ea 100644
--- a/modules/translation/i18n/plurals/rules_gen.go
+++ b/modules/translation/i18n/plurals/rules_gen.go
@@ -9,7 +9,7 @@ package plurals
var DefaultRules *Rules
func init() {
- DefaultRules := &Rules{}
+ DefaultRules = &Rules{}
addPluralRules(DefaultRules, "cardinal", []string{"bm", "bo", "dz", "hnj", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "osa", "root", "sah", "ses", "sg", "su", "th", "to", "tpi", "vi", "wo", "yo", "yue", "zh"}, &Rule{
PluralForms: newPluralFormSet(Other),
From 08cb8428e2079d55b23e5300e1b35845148ceb08 Mon Sep 17 00:00:00 2001
From: Andrew Thornton
Date: Sun, 10 Jul 2022 17:27:21 +0100
Subject: [PATCH 05/15] update templates to use TrPlural
Signed-off-by: Andrew Thornton
---
.../doc/features/localization.en-us.md | 47 +++++++
modules/test/context_tests.go | 8 ++
modules/translation/i18n/i18n.go | 9 ++
modules/translation/i18n/translatable.go | 1 +
modules/translation/translation.go | 4 +
options/locale/locale_en-US.ini | 116 ++++++++++--------
routers/web/repo/migrate.go | 2 +-
routers/web/repo/repo.go | 2 +-
routers/web/repo/setting.go | 2 +-
templates/mail/issue/default.tmpl | 2 +-
templates/repo/activity.tmpl | 50 ++++----
templates/repo/blame.tmpl | 2 +-
templates/repo/create.tmpl | 2 +-
.../repo/issue/view_content/comments.tmpl | 6 +-
templates/repo/issue/view_content/pull.tmpl | 4 +-
templates/repo/sub_menu.tmpl | 6 +-
templates/repo/view_file.tmpl | 2 +-
templates/shared/issuelist.tmpl | 8 +-
18 files changed, 177 insertions(+), 96 deletions(-)
diff --git a/docs/content/doc/features/localization.en-us.md b/docs/content/doc/features/localization.en-us.md
index a5b7a52f89891..7b82326f6eb91 100644
--- a/docs/content/doc/features/localization.en-us.md
+++ b/docs/content/doc/features/localization.en-us.md
@@ -29,3 +29,50 @@ Any language listed in the above Crowdin project will be supported as long as 25
After a translation has been accepted, it will be reflected in the main repository after the next Crowdin sync, which is generally after any PR is merged.
At the time of writing, this means that a changed translation may not appear until the following Gitea release.
If you use a bleeding edge build, it should appear as soon as you update after the change is synced.
+
+## Plurals
+
+Prior to version 1.18, Gitea handled plurals using the .TrN function which has some
+built in rules for managing plurals but is unable to properly all languages.
+
+From 1.18 we will migrate to use the CLDR formulation.
+
+Translation keys which handle plurals will be marked with a `_plural` suffix with
+additional suffices as per the CLDR form (which the exception that the `Other` form is
+left unsuffixed), e.g.
+
+```ini
+form.reach_limit_of_creation_plural = You have already reached your limit of %d repositories.
+form.reach_limit_of_creation_plural_one = You have already reached your limit of %d repository.
+```
+
+These keys should be used with the `.TrPlural` function. (Ordinals are also handled with `.TrOrdinal`.)
+
+Each language has different numbers of plural forms, in English (and a number of other
+languages) there are two plural forms:
+
+* `_plural_one`: This matches the singular form. (CLDR `One`.)
+* `_plural`: This matches the plural form. (CLDR `Other`.)
+
+Other languages, e.g. Mandarin, have no plural forms, and others many more.
+
+The possible suffices and forms are:
+
+* `_plural`: CLDR `Other` - the most common form and will often match to standard plural form.
+* `_plural_zero`: CLDR `Zero` - matches a zeroth form, which in Latvian would match the form used for 10-20, 30 and so on.
+* `_plural_one`: CLDR `One` - matches the singular form in English, but in Latvian matches the form used for 1, 21, 31 and so on.
+* `_plural_two`: CLDR `Two` - matches the dual form used in for example Arabic for 2 items, but also more complexly in Celtic languages.
+* `_plural_few`: CLDR `Few` - matches the form used in Arabic for 3-10, 103-110, and the ternary form in Celtic languages. In Russian and Ukranian for 2-4, 22-24.
+* `_plural_many`: CLDR `Many` - matches the form used for large numbers in romance lanaguages like French, e.g. 1 000 000 *de* chat*s*, but in Russian and Ukranian it handles 0, 5~19, 100, 1000 and so on.
+
+Some plural forms are only relevant if the object being counted is of a certain
+grammatical gender or in certain tenses. If a language suggests a non-Other form e.g.
+Two but the `_plural_two` key is absent - the system will fall back to the `_plural`
+form.
+
+Translators may want to review the CLDR information for their language or look at
+`modules/translation/i18n/plurals/generate/plurals.xml`.
+
+Ordinal forms, e.g. 1st, 2nd, 3rd and so on can be handled with `.TrOrdinal`. These
+have the same forms as the plural forms, and we will use `_ordinal` as a base suffix
+in future.
diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go
index a08439e93f4cf..b3eb0eebfd1fa 100644
--- a/modules/test/context_tests.go
+++ b/modules/test/context_tests.go
@@ -107,6 +107,14 @@ func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface
return key1
}
+func (l mockLocale) TrPlural(_cnt interface{}, key string, _args ...interface{}) string {
+ return key
+}
+
+func (l mockLocale) TrOrdinal(_cnt interface{}, key string, _args ...interface{}) string {
+ return key
+}
+
type mockResponseWriter struct {
httptest.ResponseRecorder
size int
diff --git a/modules/translation/i18n/i18n.go b/modules/translation/i18n/i18n.go
index ecc90138e3026..abed73ff492e8 100644
--- a/modules/translation/i18n/i18n.go
+++ b/modules/translation/i18n/i18n.go
@@ -377,3 +377,12 @@ func ResetDefaultLocales(isProd bool) {
func Tr(lang, trKey string, trArgs ...interface{}) string {
return DefaultLocales.Tr(lang, trKey, trArgs...)
}
+
+// Tr use default locales to translate content to target language.
+func GetLocale(lang string) Locale {
+ l, ok := DefaultLocales.localeMap[lang]
+ if !ok {
+ l, ok = DefaultLocales.localeMap[DefaultLocales.defaultLang]
+ }
+ return l
+}
diff --git a/modules/translation/i18n/translatable.go b/modules/translation/i18n/translatable.go
index a3c7358f172ac..2504e07ea76a2 100644
--- a/modules/translation/i18n/translatable.go
+++ b/modules/translation/i18n/translatable.go
@@ -10,6 +10,7 @@ import "fmt"
type Locale interface {
Tr(key string, args ...interface{}) string
TrPlural(cnt interface{}, key string, args ...interface{}) string
+ TrOrdinal(cnt interface{}, key string, args ...interface{}) string
}
// TranslatableFormatted structs provide their own translated string when formatted in translation
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
index fcc101d963435..2384e2c5cc6b9 100644
--- a/modules/translation/translation.go
+++ b/modules/translation/translation.go
@@ -23,6 +23,8 @@ type Locale interface {
Language() string
Tr(string, ...interface{}) string
TrN(cnt interface{}, key1, keyN string, args ...interface{}) string
+ TrPlural(cnt interface{}, key string, args ...interface{}) string
+ TrOrdinal(cnt interface{}, key string, args ...interface{}) string
}
// LangType represents a lang type
@@ -118,6 +120,7 @@ func Match(tags ...language.Tag) language.Tag {
// locale represents the information of localization.
type locale struct {
+ i18n.Locale
Lang, LangName string // these fields are used directly in templates: .i18n.Lang
}
@@ -128,6 +131,7 @@ func NewLocale(lang string) Locale {
langName = l.Name
}
return &locale{
+ Locale: i18n.GetLocale(langName),
Lang: lang,
LangName: langName,
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 36e2ae677becb..2ad1921654775 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -377,8 +377,8 @@ issue_assigned.issue = @%[1]s assigned you to issue %[2]s in repository %[3]s.
issue.x_mentioned_you = @%s mentioned you:
issue.action.force_push = %[1]s force-pushed the %[2]s from %[3]s to %[4]s.
-issue.action.push_1 = @%[1]s pushed %[3]d commit to %[2]s
-issue.action.push_n = @%[1]s pushed %[3]d commits to %[2]s
+issue.action.push_plural_one = @%[1]s pushed %[3]d commit to %[2]s
+issue.action.push_plural = @%[1]s pushed %[3]d commits to %[2]s
issue.action.close = @%[1]s closed #%[2]d.
issue.action.reopen = @%[1]s reopened #%[2]d.
issue.action.merge = @%[1]s merged #%[2]d into %[3]s.
@@ -924,8 +924,8 @@ archive.title = This repo is archived. You can view files and clone it, but cann
archive.issue.nocomment = This repo is archived. You cannot comment on issues.
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.
-form.reach_limit_of_creation_1 = You have already reached your limit of %d repository.
-form.reach_limit_of_creation_n = You have already reached your limit of %d repositories.
+form.reach_limit_of_creation_plural_one = You have already reached your limit of %d repository.
+form.reach_limit_of_creation_plural = You have already reached your limit of %d repositories.
form.name_reserved = The repository name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
@@ -1005,6 +1005,8 @@ broken_message = The Git data underlying this repository cannot be read. Contact
code = Code
code.desc = Access source code, files, commits and branches.
branch = Branch
+branch_plural = Branches
+branch_plural_one = Branch
tree = Tree
clear_ref = `Clear current reference`
filter_branch_and_tag = Filter branch or tag
@@ -1022,9 +1024,13 @@ org_labels_desc_manage = manage
milestones = Milestones
commits = Commits
commit = Commit
+commit_plural = Commits
+commit_plural_one = Commit
release = Release
releases = Releases
tag = Tag
+tag_plural = Tags
+tag_plural_one = Tag
released_this = released this
file.title = %s at %s
file_raw = Raw
@@ -1060,6 +1066,8 @@ download_file = Download file
normal_view = Normal View
line = line
lines = lines
+line_plural_one = line
+line_plural = lines
editor.new_file = New File
editor.upload_file = Upload File
@@ -1239,8 +1247,12 @@ issues.label_templates.use = Use Label Set
issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v
issues.add_label = added the %s label %s
issues.add_labels = added the %s labels %s
+issues.add_label_plural_one = added the %s label %s
+issues.add_label_plural = added the %s labels %s
issues.remove_label = removed the %s label %s
issues.remove_labels = removed the %s labels %s
+issues.remove_label_plural_one = removed the %s label %s
+issues.remove_label_plural = removed the %s labels %s
issues.add_remove_labels = added %s and removed %s labels %s
issues.add_milestone_at = `added this to the %s milestone %s`
issues.add_project_at = `added this to the %s project %s`
@@ -1410,8 +1422,8 @@ issues.due_date = Due Date
issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'."
issues.error_modifying_due_date = "Failed to modify the due date."
issues.error_removing_due_date = "Failed to remove the due date."
-issues.push_commit_1 = "added %d commit %s"
-issues.push_commits_n = "added %d commits %s"
+issues.push_commit_plural_one = "added %d commit %s"
+issues.push_commit_plural = "added %d commits %s"
issues.force_push_codes = `force-pushed %[1]s from %[2]s
to %[4]s
%[6]s`
issues.due_date_form = "yyyy-mm-dd"
issues.due_date_form_add = "Add due date"
@@ -1540,19 +1552,19 @@ pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals ye
pulls.blocked_by_rejection = "This Pull Request has changes requested by an official reviewer."
pulls.blocked_by_official_review_requests = "This Pull Request has official review requests."
pulls.blocked_by_outdated_branch = "This Pull Request is blocked because it's outdated."
-pulls.blocked_by_changed_protected_files_1= "This Pull Request is blocked because it changes a protected file:"
-pulls.blocked_by_changed_protected_files_n= "This Pull Request is blocked because it changes protected files:"
+pulls.blocked_by_changed_protected_files_plural_one = "This Pull Request is blocked because it changes a protected file:"
+pulls.blocked_by_changed_protected_files_plural = "This Pull Request is blocked because it changes protected files:"
pulls.can_auto_merge_desc = This pull request can be merged automatically.
pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts.
pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts.
-pulls.num_conflicting_files_1 = "%d conflicting file"
-pulls.num_conflicting_files_n = "%d conflicting files"
-pulls.approve_count_1 = "%d approval"
-pulls.approve_count_n = "%d approvals"
-pulls.reject_count_1 = "%d change request"
-pulls.reject_count_n = "%d change requests"
-pulls.waiting_count_1 = "%d waiting review"
-pulls.waiting_count_n = "%d waiting reviews"
+pulls.num_conflicting_files_plural_one = "%d conflicting file"
+pulls.num_conflicting_files_plural = "%d conflicting files"
+pulls.approve_count_plural_one = "%d approval"
+pulls.approve_count_plural = "%d approvals"
+pulls.reject_count_plural_one = "%d change request"
+pulls.reject_count_plural = "%d change requests"
+pulls.waiting_count_plural_one = "%d waiting review"
+pulls.waiting_count_plural = "%d waiting reviews"
pulls.wrong_commit_id = "commit id must be a commit id on the target branch"
pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled.
@@ -1695,61 +1707,61 @@ activity.period.quarterly = 3 months
activity.period.semiyearly = 6 months
activity.period.yearly = 1 year
activity.overview = Overview
-activity.active_prs_count_1 = %d Active Pull Request
-activity.active_prs_count_n = %d Active Pull Requests
-activity.merged_prs_count_1 = Merged Pull Request
-activity.merged_prs_count_n = Merged Pull Requests
-activity.opened_prs_count_1 = Proposed Pull Request
-activity.opened_prs_count_n = Proposed Pull Requests
-activity.title.user_1 = %d user
-activity.title.user_n = %d users
-activity.title.prs_1 = %d Pull request
-activity.title.prs_n = %d Pull requests
+activity.active_prs_count_plural_one = %d Active Pull Request
+activity.active_prs_count_plural = %d Active Pull Requests
+activity.merged_prs_count_plural_one = Merged Pull Request
+activity.merged_prs_count_plural = Merged Pull Requests
+activity.opened_prs_count_plural_one = Proposed Pull Request
+activity.opened_prs_count_plural = Proposed Pull Requests
+activity.title.user_plural_one = %d user
+activity.title.user_plural = %d users
+activity.title.prs_plural_one = %d Pull request
+activity.title.prs_plural = %d Pull requests
activity.title.prs_merged_by = %s merged by %s
activity.title.prs_opened_by = %s proposed by %s
activity.merged_prs_label = Merged
activity.opened_prs_label = Proposed
-activity.active_issues_count_1 = %d Active Issue
-activity.active_issues_count_n = %d Active Issues
-activity.closed_issues_count_1 = Closed Issue
-activity.closed_issues_count_n = Closed Issues
-activity.title.issues_1 = %d Issue
-activity.title.issues_n = %d Issues
+activity.active_issues_count_plural_one = %d Active Issue
+activity.active_issues_count_plural = %d Active Issues
+activity.closed_issues_count_plural_one = Closed Issue
+activity.closed_issues_count_plural = Closed Issues
+activity.title.issues_plural_one = %d Issue
+activity.title.issues_plural = %d Issues
activity.title.issues_closed_from = %s closed from %s
activity.title.issues_created_by = %s created by %s
activity.closed_issue_label = Closed
-activity.new_issues_count_1 = New Issue
-activity.new_issues_count_n = New Issues
+activity.new_issues_count_plural_one = New Issue
+activity.new_issues_count_plural = New Issues
activity.new_issue_label = Opened
-activity.title.unresolved_conv_1 = %d Unresolved Conversation
-activity.title.unresolved_conv_n = %d Unresolved Conversations
+activity.title.unresolved_conv_plural_one = %d Unresolved Conversation
+activity.title.unresolved_conv_plural = %d Unresolved Conversations
activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet.
activity.unresolved_conv_label = Open
-activity.title.releases_1 = %d Release
-activity.title.releases_n = %d Releases
+activity.title.releases_plural_one = %d Release
+activity.title.releases_plural = %d Releases
activity.title.releases_published_by = %s published by %s
activity.published_release_label = Published
activity.no_git_activity = There has not been any commit activity in this period.
activity.git_stats_exclude_merges = Excluding merges,
-activity.git_stats_author_1 = %d author
-activity.git_stats_author_n = %d authors
-activity.git_stats_pushed_1 = has pushed
-activity.git_stats_pushed_n = have pushed
-activity.git_stats_commit_1 = %d commit
-activity.git_stats_commit_n = %d commits
+activity.git_stats_author_plural_one = %d author
+activity.git_stats_author_plural = %d authors
+activity.git_stats_pushed_plural_one = has pushed
+activity.git_stats_pushed_plural = have pushed
+activity.git_stats_commit_plural_one = %d commit
+activity.git_stats_commit_plural = %d commits
activity.git_stats_push_to_branch = to %s and
activity.git_stats_push_to_all_branches = to all branches.
activity.git_stats_on_default_branch = On %s,
-activity.git_stats_file_1 = %d file
-activity.git_stats_file_n = %d files
-activity.git_stats_files_changed_1 = has changed
-activity.git_stats_files_changed_n = have changed
+activity.git_stats_file_plural_one = %d file
+activity.git_stats_file_plural = %d files
+activity.git_stats_files_changed_plural_one = has changed
+activity.git_stats_files_changed_plural = have changed
activity.git_stats_additions = and there have been
-activity.git_stats_addition_1 = %d addition
-activity.git_stats_addition_n = %d additions
+activity.git_stats_addition_plural_one = %d addition
+activity.git_stats_addition_plural = %d additions
activity.git_stats_and_deletions = and
-activity.git_stats_deletion_1 = %d deletion
-activity.git_stats_deletion_n = %d deletions
+activity.git_stats_deletion_plural_one = %d deletion
+activity.git_stats_deletion_plural = %d deletions
search = Search
search.search_repo = Search repository
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index 393f8ed3d9316..27da66d50099f 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -82,7 +82,7 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error,
ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form)
case repo_model.IsErrReachLimitOfRepo(err):
maxCreationLimit := owner.MaxCreationLimit()
- msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
+ msg := ctx.TrPlural(maxCreationLimit, "repo.form.reach_limit_of_creation_plural", maxCreationLimit)
ctx.RenderWithErr(msg, tpl, form)
case repo_model.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index c2c79e4a0df1c..cfef423f7f1ba 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -168,7 +168,7 @@ func handleCreateError(ctx *context.Context, owner *user_model.User, err error,
switch {
case repo_model.IsErrReachLimitOfRepo(err):
maxCreationLimit := owner.MaxCreationLimit()
- msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
+ msg := ctx.TrPlural(maxCreationLimit, "repo.form.reach_limit_of_creation_plural", maxCreationLimit)
ctx.RenderWithErr(msg, tpl, form)
case repo_model.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go
index 5ded0561c65b5..87f71fc125ab7 100644
--- a/routers/web/repo/setting.go
+++ b/routers/web/repo/setting.go
@@ -636,7 +636,7 @@ func SettingsPost(ctx *context.Context) {
if !ctx.Repo.Owner.CanCreateRepo() {
maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
- msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
+ msg := ctx.TrPlural(maxCreationLimit, "repo.form.reach_limit_of_creation_plural", maxCreationLimit)
ctx.Flash.Error(msg)
ctx.Redirect(repo.Link() + "/settings")
return
diff --git a/templates/mail/issue/default.tmpl b/templates/mail/issue/default.tmpl
index d9f7aff4cce39..60438664e4f18 100644
--- a/templates/mail/issue/default.tmpl
+++ b/templates/mail/issue/default.tmpl
@@ -30,7 +30,7 @@
{{.locale.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink | Str2html}}
{{else}}
- {{.locale.TrN (len .Comment.Commits) "mail.issue.action.push_1" "mail.issue.action.push_n" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}}
+ {{.locale.TrPlural (len .Comment.Commits) "mail.issue.action.push_plural" .Doer.Name .Comment.Issue.PullRequest.HeadBranch (len .Comment.Commits) | Str2html}}
{{end}}
{{end}}
diff --git a/templates/repo/activity.tmpl b/templates/repo/activity.tmpl
index 37cdf3ef02b19..4d34846b86e38 100644
--- a/templates/repo/activity.tmpl
+++ b/templates/repo/activity.tmpl
@@ -41,7 +41,7 @@
{{end}}
- {{.locale.TrN .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n" .Activity.ActivePRCount | Safe }}
+ {{.locale.TrPlural .Activity.ActivePRCount "repo.activity.active_prs_count_plural" .Activity.ActivePRCount | Safe }}
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
@@ -56,7 +56,7 @@
{{end}}
- {{.locale.TrN .Activity.ActiveIssueCount "repo.activity.active_issues_count_1" "repo.activity.active_issues_count_n" .Activity.ActiveIssueCount | Safe }}
+ {{.locale.TrPlural .Activity.ActiveIssueCount "repo.activity.active_issues_count_plural" .Activity.ActiveIssueCount | Safe }}
{{end}}
@@ -64,21 +64,21 @@
{{if .Permission.CanRead $.UnitTypePullRequests}}
{{svg "octicon-git-pull-request"}} {{.Activity.MergedPRCount}}
- {{.locale.TrN .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n"}}
+ {{.locale.TrPlural .Activity.MergedPRCount "repo.activity.merged_prs_count_plural"}}
{{svg "octicon-git-branch"}} {{.Activity.OpenedPRCount}}
- {{.locale.TrN .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n"}}
+ {{.locale.TrPlural .Activity.OpenedPRCount "repo.activity.opened_prs_count_plural"}}
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
{{svg "octicon-issue-closed"}} {{.Activity.ClosedIssueCount}}
- {{.locale.TrN .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n"}}
+ {{.locale.TrPlural .Activity.ClosedIssueCount "repo.activity.closed_issues_count_plural"}}
{{svg "octicon-issue-opened"}} {{.Activity.OpenedIssueCount}}
- {{.locale.TrN .Activity.OpenedIssueCount "repo.activity.new_issues_count_1" "repo.activity.new_issues_count_n"}}
+ {{.locale.TrPlural .Activity.OpenedIssueCount "repo.activity.new_issues_count_plural"}}
{{end}}
@@ -94,19 +94,19 @@
{{.locale.Tr "repo.activity.git_stats_exclude_merges" }}
- {{.locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_author_1" "repo.activity.git_stats_author_n" .Activity.Code.AuthorCount}}
- {{.locale.TrN .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_1" "repo.activity.git_stats_pushed_n"}}
- {{.locale.TrN .Activity.Code.CommitCount "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCount}}
+ {{.locale.TrPlural .Activity.Code.AuthorCount "repo.activity.git_stats_author_plural" .Activity.Code.AuthorCount}}
+ {{.locale.TrPlural .Activity.Code.AuthorCount "repo.activity.git_stats_pushed_plural"}}
+ {{.locale.TrPlural .Activity.Code.CommitCount "repo.activity.git_stats_commit_plural" .Activity.Code.CommitCount}}
{{.locale.Tr "repo.activity.git_stats_push_to_branch" .Repository.DefaultBranch }}
- {{.locale.TrN .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_1" "repo.activity.git_stats_commit_n" .Activity.Code.CommitCountInAllBranches}}
+ {{.locale.TrPlural .Activity.Code.CommitCountInAllBranches "repo.activity.git_stats_commit_plural" .Activity.Code.CommitCountInAllBranches}}
{{.locale.Tr "repo.activity.git_stats_push_to_all_branches" }}
{{.locale.Tr "repo.activity.git_stats_on_default_branch" .Repository.DefaultBranch }}
- {{.locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_file_1" "repo.activity.git_stats_file_n" .Activity.Code.ChangedFiles}}
- {{.locale.TrN .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_1" "repo.activity.git_stats_files_changed_n"}}
+ {{.locale.TrPlural .Activity.Code.ChangedFiles "repo.activity.git_stats_file_plural" .Activity.Code.ChangedFiles}}
+ {{.locale.TrPlural .Activity.Code.ChangedFiles "repo.activity.git_stats_files_changed_plural"}}
{{.locale.Tr "repo.activity.git_stats_additions" }}
- {{.locale.TrN .Activity.Code.Additions "repo.activity.git_stats_addition_1" "repo.activity.git_stats_addition_n" .Activity.Code.Additions}}
+ {{.locale.TrPlural .Activity.Code.Additions "repo.activity.git_stats_addition_plural" .Activity.Code.Additions}}
{{.locale.Tr "repo.activity.git_stats_and_deletions" }}
- {{.locale.TrN .Activity.Code.Deletions "repo.activity.git_stats_deletion_1" "repo.activity.git_stats_deletion_n" .Activity.Code.Deletions}}.
+ {{.locale.TrPlural .Activity.Code.Deletions "repo.activity.git_stats_deletion_plural" .Activity.Code.Deletions}}.
@@ -119,8 +119,8 @@
@@ -141,8 +141,8 @@
@@ -160,8 +160,8 @@
@@ -179,8 +179,8 @@
@@ -198,8 +198,8 @@
@@ -216,7 +216,7 @@
{{if gt .Activity.UnresolvedIssueCount 0}}
{{.locale.Tr "repo.activity.unresolved_conv_desc"}}
diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl
index 6b3f3eddd7b3b..2de850c96bce6 100644
--- a/templates/repo/blame.tmpl
+++ b/templates/repo/blame.tmpl
@@ -3,7 +3,7 @@