type i18n

This commit is contained in:
Jesse Duffield 2020-10-04 11:00:48 +11:00
parent 7d9aa97f96
commit 37bb89dac3
104 changed files with 2049 additions and 15185 deletions

View file

@ -1,19 +0,0 @@
Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,136 +0,0 @@
package i18n
import (
"fmt"
"io/ioutil"
"github.com/nicksnyder/go-i18n/v2/internal/plural"
"golang.org/x/text/language"
)
// UnmarshalFunc unmarshals data into v.
type UnmarshalFunc func(data []byte, v interface{}) error
// Bundle stores a set of messages and pluralization rules.
// Most applications only need a single bundle
// that is initialized early in the application's lifecycle.
// It is not goroutine safe to modify the bundle while Localizers
// are reading from it.
type Bundle struct {
defaultLanguage language.Tag
unmarshalFuncs map[string]UnmarshalFunc
messageTemplates map[language.Tag]map[string]*MessageTemplate
pluralRules plural.Rules
tags []language.Tag
matcher language.Matcher
}
// artTag is the language tag used for artifical languages
// https://en.wikipedia.org/wiki/Codes_for_constructed_languages
var artTag = language.MustParse("art")
// NewBundle returns a bundle with a default language and a default set of plural rules.
func NewBundle(defaultLanguage language.Tag) *Bundle {
b := &Bundle{
defaultLanguage: defaultLanguage,
pluralRules: plural.DefaultRules(),
}
b.pluralRules[artTag] = b.pluralRules.Rule(language.English)
b.addTag(defaultLanguage)
return b
}
// RegisterUnmarshalFunc registers an UnmarshalFunc for format.
func (b *Bundle) RegisterUnmarshalFunc(format string, unmarshalFunc UnmarshalFunc) {
if b.unmarshalFuncs == nil {
b.unmarshalFuncs = make(map[string]UnmarshalFunc)
}
b.unmarshalFuncs[format] = unmarshalFunc
}
// LoadMessageFile loads the bytes from path
// and then calls ParseMessageFileBytes.
func (b *Bundle) LoadMessageFile(path string) (*MessageFile, error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return b.ParseMessageFileBytes(buf, path)
}
// MustLoadMessageFile is similar to LoadTranslationFile
// except it panics if an error happens.
func (b *Bundle) MustLoadMessageFile(path string) {
if _, err := b.LoadMessageFile(path); err != nil {
panic(err)
}
}
// ParseMessageFileBytes parses the bytes in buf to add translations to the bundle.
//
// The format of the file is everything after the last ".".
//
// The language tag of the file is everything after the second to last "." or after the last path separator, but before the format.
func (b *Bundle) ParseMessageFileBytes(buf []byte, path string) (*MessageFile, error) {
messageFile, err := ParseMessageFileBytes(buf, path, b.unmarshalFuncs)
if err != nil {
return nil, err
}
if err := b.AddMessages(messageFile.Tag, messageFile.Messages...); err != nil {
return nil, err
}
return messageFile, nil
}
// MustParseMessageFileBytes is similar to ParseMessageFileBytes
// except it panics if an error happens.
func (b *Bundle) MustParseMessageFileBytes(buf []byte, path string) {
if _, err := b.ParseMessageFileBytes(buf, path); err != nil {
panic(err)
}
}
// AddMessages adds messages for a language.
// It is useful if your messages are in a format not supported by ParseMessageFileBytes.
func (b *Bundle) AddMessages(tag language.Tag, messages ...*Message) error {
pluralRule := b.pluralRules.Rule(tag)
if pluralRule == nil {
return fmt.Errorf("no plural rule registered for %s", tag)
}
if b.messageTemplates == nil {
b.messageTemplates = map[language.Tag]map[string]*MessageTemplate{}
}
if b.messageTemplates[tag] == nil {
b.messageTemplates[tag] = map[string]*MessageTemplate{}
b.addTag(tag)
}
for _, m := range messages {
b.messageTemplates[tag][m.ID] = NewMessageTemplate(m)
}
return nil
}
// MustAddMessages is similar to AddMessages except it panics if an error happens.
func (b *Bundle) MustAddMessages(tag language.Tag, messages ...*Message) {
if err := b.AddMessages(tag, messages...); err != nil {
panic(err)
}
}
func (b *Bundle) addTag(tag language.Tag) {
for _, t := range b.tags {
if t == tag {
// Tag already exists
return
}
}
b.tags = append(b.tags, tag)
b.matcher = language.NewMatcher(b.tags)
}
// LanguageTags returns the list of language tags
// of all the translations loaded into the bundle
func (b *Bundle) LanguageTags() []language.Tag {
return b.tags
}

View file

@ -1,24 +0,0 @@
// Package i18n provides support for looking up messages
// according to a set of locale preferences.
//
// Create a Bundle to use for the lifetime of your application.
// bundle := i18n.NewBundle(language.English)
//
// Load translations into your bundle during initialization.
// bundle.LoadMessageFile("en-US.yaml")
//
// Create a Localizer to use for a set of language preferences.
// func(w http.ResponseWriter, r *http.Request) {
// lang := r.FormValue("lang")
// accept := r.Header.Get("Accept-Language")
// localizer := i18n.NewLocalizer(bundle, lang, accept)
// }
//
// Use the Localizer to lookup messages.
// localizer.MustLocalize(&i18n.LocalizeConfig{
// DefaultMessage: &i18n.Message{
// ID: "HelloWorld",
// Other: "Hello World!",
// },
// })
package i18n

View file

@ -1,234 +0,0 @@
package i18n
import (
"fmt"
"text/template"
"github.com/nicksnyder/go-i18n/v2/internal/plural"
"golang.org/x/text/language"
)
// Localizer provides Localize and MustLocalize methods that return localized messages.
type Localizer struct {
// bundle contains the messages that can be returned by the Localizer.
bundle *Bundle
// tags is the list of language tags that the Localizer checks
// in order when localizing a message.
tags []language.Tag
}
// NewLocalizer returns a new Localizer that looks up messages
// in the bundle according to the language preferences in langs.
// It can parse Accept-Language headers as defined in http://www.ietf.org/rfc/rfc2616.txt.
func NewLocalizer(bundle *Bundle, langs ...string) *Localizer {
return &Localizer{
bundle: bundle,
tags: parseTags(langs),
}
}
func parseTags(langs []string) []language.Tag {
tags := []language.Tag{}
for _, lang := range langs {
t, _, err := language.ParseAcceptLanguage(lang)
if err != nil {
continue
}
tags = append(tags, t...)
}
return tags
}
// LocalizeConfig configures a call to the Localize method on Localizer.
type LocalizeConfig struct {
// MessageID is the id of the message to lookup.
// This field is ignored if DefaultMessage is set.
MessageID string
// TemplateData is the data passed when executing the message's template.
// If TemplateData is nil and PluralCount is not nil, then the message template
// will be executed with data that contains the plural count.
TemplateData interface{}
// PluralCount determines which plural form of the message is used.
PluralCount interface{}
// DefaultMessage is used if the message is not found in any message files.
DefaultMessage *Message
// Funcs is used to extend the Go template engine's built in functions
Funcs template.FuncMap
}
type invalidPluralCountErr struct {
messageID string
pluralCount interface{}
err error
}
func (e *invalidPluralCountErr) Error() string {
return fmt.Sprintf("invalid plural count %#v for message id %q: %s", e.pluralCount, e.messageID, e.err)
}
// MessageNotFoundErr is returned from Localize when a message could not be found.
type MessageNotFoundErr struct {
messageID string
}
func (e *MessageNotFoundErr) Error() string {
return fmt.Sprintf("message %q not found", e.messageID)
}
type pluralizeErr struct {
messageID string
tag language.Tag
}
func (e *pluralizeErr) Error() string {
return fmt.Sprintf("unable to pluralize %q because there no plural rule for %q", e.messageID, e.tag)
}
type messageIDMismatchErr struct {
messageID string
defaultMessageID string
}
func (e *messageIDMismatchErr) Error() string {
return fmt.Sprintf("message id %q does not match default message id %q", e.messageID, e.defaultMessageID)
}
// Localize returns a localized message.
func (l *Localizer) Localize(lc *LocalizeConfig) (string, error) {
msg, _, err := l.LocalizeWithTag(lc)
return msg, err
}
// Localize returns a localized message.
func (l *Localizer) LocalizeMessage(msg *Message) (string, error) {
return l.Localize(&LocalizeConfig{
DefaultMessage: msg,
})
}
// TODO: uncomment this (and the test) when extract has been updated to extract these call sites too.
// Localize returns a localized message.
// func (l *Localizer) LocalizeMessageID(messageID string) (string, error) {
// return l.Localize(&LocalizeConfig{
// MessageID: messageID,
// })
// }
// LocalizeWithTag returns a localized message and the language tag.
// It may return a best effort localized message even if an error happens.
func (l *Localizer) LocalizeWithTag(lc *LocalizeConfig) (string, language.Tag, error) {
messageID := lc.MessageID
if lc.DefaultMessage != nil {
if messageID != "" && messageID != lc.DefaultMessage.ID {
return "", language.Und, &messageIDMismatchErr{messageID: messageID, defaultMessageID: lc.DefaultMessage.ID}
}
messageID = lc.DefaultMessage.ID
}
var operands *plural.Operands
templateData := lc.TemplateData
if lc.PluralCount != nil {
var err error
operands, err = plural.NewOperands(lc.PluralCount)
if err != nil {
return "", language.Und, &invalidPluralCountErr{messageID: messageID, pluralCount: lc.PluralCount, err: err}
}
if templateData == nil {
templateData = map[string]interface{}{
"PluralCount": lc.PluralCount,
}
}
}
tag, template := l.getTemplate(messageID, lc.DefaultMessage)
if template == nil {
return "", language.Und, &MessageNotFoundErr{messageID: messageID}
}
pluralForm := l.pluralForm(tag, operands)
if pluralForm == plural.Invalid {
return "", language.Und, &pluralizeErr{messageID: messageID, tag: tag}
}
msg, err := template.Execute(pluralForm, templateData, lc.Funcs)
if err != nil {
// Attempt to fallback to "Other" pluralization in case translations are incomplete.
if pluralForm != plural.Other {
msg2, err2 := template.Execute(plural.Other, templateData, lc.Funcs)
if err2 == nil {
return msg2, tag, err
}
}
return "", language.Und, err
}
return msg, tag, nil
}
func (l *Localizer) getTemplate(id string, defaultMessage *Message) (language.Tag, *MessageTemplate) {
// Fast path.
// Optimistically assume this message id is defined in each language.
fastTag, template := l.matchTemplate(id, defaultMessage, l.bundle.matcher, l.bundle.tags)
if template != nil {
return fastTag, template
}
if len(l.bundle.tags) <= 1 {
return l.bundle.defaultLanguage, nil
}
// Slow path.
// We didn't find a translation for the tag suggested by the default matcher
// so we need to create a new matcher that contains only the tags in the bundle
// that have this message.
foundTags := make([]language.Tag, 0, len(l.bundle.messageTemplates)+1)
foundTags = append(foundTags, l.bundle.defaultLanguage)
for t, templates := range l.bundle.messageTemplates {
template := templates[id]
if template == nil || template.Other == "" {
continue
}
foundTags = append(foundTags, t)
}
return l.matchTemplate(id, defaultMessage, language.NewMatcher(foundTags), foundTags)
}
func (l *Localizer) matchTemplate(id string, defaultMessage *Message, matcher language.Matcher, tags []language.Tag) (language.Tag, *MessageTemplate) {
_, i, _ := matcher.Match(l.tags...)
tag := tags[i]
templates := l.bundle.messageTemplates[tag]
if templates != nil && templates[id] != nil {
return tag, templates[id]
}
if tag == l.bundle.defaultLanguage && defaultMessage != nil {
return tag, NewMessageTemplate(defaultMessage)
}
return tag, nil
}
func (l *Localizer) pluralForm(tag language.Tag, operands *plural.Operands) plural.Form {
if operands == nil {
return plural.Other
}
pluralRule := l.bundle.pluralRules.Rule(tag)
if pluralRule == nil {
return plural.Invalid
}
return pluralRule.PluralFormFunc(operands)
}
// MustLocalize is similar to Localize, except it panics if an error happens.
func (l *Localizer) MustLocalize(lc *LocalizeConfig) string {
localized, err := l.Localize(lc)
if err != nil {
panic(err)
}
return localized
}

View file

@ -1,221 +0,0 @@
package i18n
import (
"fmt"
"strings"
)
// Message is a string that can be localized.
type Message struct {
// ID uniquely identifies the message.
ID string
// Hash uniquely identifies the content of the message
// that this message was translated from.
Hash string
// Description describes the message to give additional
// context to translators that may be relevant for translation.
Description string
// LeftDelim is the left Go template delimiter.
LeftDelim string
// RightDelim is the right Go template delimiter.``
RightDelim string
// Zero is the content of the message for the CLDR plural form "zero".
Zero string
// One is the content of the message for the CLDR plural form "one".
One string
// Two is the content of the message for the CLDR plural form "two".
Two string
// Few is the content of the message for the CLDR plural form "few".
Few string
// Many is the content of the message for the CLDR plural form "many".
Many string
// Other is the content of the message for the CLDR plural form "other".
Other string
}
// NewMessage parses data and returns a new message.
func NewMessage(data interface{}) (*Message, error) {
m := &Message{}
if err := m.unmarshalInterface(data); err != nil {
return nil, err
}
return m, nil
}
// MustNewMessage is similar to NewMessage except it panics if an error happens.
func MustNewMessage(data interface{}) *Message {
m, err := NewMessage(data)
if err != nil {
panic(err)
}
return m
}
// unmarshalInterface unmarshals a message from data.
func (m *Message) unmarshalInterface(v interface{}) error {
strdata, err := stringMap(v)
if err != nil {
return err
}
for k, v := range strdata {
switch strings.ToLower(k) {
case "id":
m.ID = v
case "description":
m.Description = v
case "hash":
m.Hash = v
case "leftdelim":
m.LeftDelim = v
case "rightdelim":
m.RightDelim = v
case "zero":
m.Zero = v
case "one":
m.One = v
case "two":
m.Two = v
case "few":
m.Few = v
case "many":
m.Many = v
case "other":
m.Other = v
}
}
return nil
}
type keyTypeErr struct {
key interface{}
}
func (err *keyTypeErr) Error() string {
return fmt.Sprintf("expected key to be a string but got %#v", err.key)
}
type valueTypeErr struct {
value interface{}
}
func (err *valueTypeErr) Error() string {
return fmt.Sprintf("unsupported type %#v", err.value)
}
func stringMap(v interface{}) (map[string]string, error) {
switch value := v.(type) {
case string:
return map[string]string{
"other": value,
}, nil
case map[string]string:
return value, nil
case map[string]interface{}:
strdata := make(map[string]string, len(value))
for k, v := range value {
err := stringSubmap(k, v, strdata)
if err != nil {
return nil, err
}
}
return strdata, nil
case map[interface{}]interface{}:
strdata := make(map[string]string, len(value))
for k, v := range value {
kstr, ok := k.(string)
if !ok {
return nil, &keyTypeErr{key: k}
}
err := stringSubmap(kstr, v, strdata)
if err != nil {
return nil, err
}
}
return strdata, nil
default:
return nil, &valueTypeErr{value: value}
}
}
func stringSubmap(k string, v interface{}, strdata map[string]string) error {
if k == "translation" {
switch vt := v.(type) {
case string:
strdata["other"] = vt
default:
v1Message, err := stringMap(v)
if err != nil {
return err
}
for kk, vv := range v1Message {
strdata[kk] = vv
}
}
return nil
}
switch vt := v.(type) {
case string:
strdata[k] = vt
return nil
case nil:
return nil
default:
return fmt.Errorf("expected value for key %q be a string but got %#v", k, v)
}
}
// isMessage tells whether the given data is a message, or a map containing
// nested messages.
// A map is assumed to be a message if it contains any of the "reserved" keys:
// "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"
// with a string value.
// e.g.,
// - {"message": {"description": "world"}} is a message
// - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored)
// - {"notmessage": {"description": {"hello": "world"}}} is not
// - {"notmessage": {"foo": "bar"}} is not
func isMessage(v interface{}) bool {
reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"}
switch data := v.(type) {
case string:
return true
case map[string]interface{}:
for _, key := range reservedKeys {
val, ok := data[key]
if !ok {
continue
}
_, ok = val.(string)
if !ok {
continue
}
// v is a message if it contains a "reserved" key holding a string value
return true
}
case map[interface{}]interface{}:
for _, key := range reservedKeys {
val, ok := data[key]
if !ok {
continue
}
_, ok = val.(string)
if !ok {
continue
}
// v is a message if it contains a "reserved" key holding a string value
return true
}
}
return false
}

View file

@ -1,65 +0,0 @@
package i18n
import (
"fmt"
"text/template"
"github.com/nicksnyder/go-i18n/v2/internal"
"github.com/nicksnyder/go-i18n/v2/internal/plural"
)
// MessageTemplate is an executable template for a message.
type MessageTemplate struct {
*Message
PluralTemplates map[plural.Form]*internal.Template
}
// NewMessageTemplate returns a new message template.
func NewMessageTemplate(m *Message) *MessageTemplate {
pluralTemplates := map[plural.Form]*internal.Template{}
setPluralTemplate(pluralTemplates, plural.Zero, m.Zero, m.LeftDelim, m.RightDelim)
setPluralTemplate(pluralTemplates, plural.One, m.One, m.LeftDelim, m.RightDelim)
setPluralTemplate(pluralTemplates, plural.Two, m.Two, m.LeftDelim, m.RightDelim)
setPluralTemplate(pluralTemplates, plural.Few, m.Few, m.LeftDelim, m.RightDelim)
setPluralTemplate(pluralTemplates, plural.Many, m.Many, m.LeftDelim, m.RightDelim)
setPluralTemplate(pluralTemplates, plural.Other, m.Other, m.LeftDelim, m.RightDelim)
if len(pluralTemplates) == 0 {
return nil
}
return &MessageTemplate{
Message: m,
PluralTemplates: pluralTemplates,
}
}
func setPluralTemplate(pluralTemplates map[plural.Form]*internal.Template, pluralForm plural.Form, src, leftDelim, rightDelim string) {
if src != "" {
pluralTemplates[pluralForm] = &internal.Template{
Src: src,
LeftDelim: leftDelim,
RightDelim: rightDelim,
}
}
}
type pluralFormNotFoundError struct {
pluralForm plural.Form
messageID string
}
func (e pluralFormNotFoundError) Error() string {
return fmt.Sprintf("message %q has no plural form %q", e.messageID, e.pluralForm)
}
// Execute executes the template for the plural form and template data.
func (mt *MessageTemplate) Execute(pluralForm plural.Form, data interface{}, funcs template.FuncMap) (string, error) {
t := mt.PluralTemplates[pluralForm]
if t == nil {
return "", pluralFormNotFoundError{
pluralForm: pluralForm,
messageID: mt.Message.ID,
}
}
return t.Execute(funcs, data)
}

View file

@ -1,166 +0,0 @@
package i18n
import (
"encoding/json"
"errors"
"fmt"
"os"
"golang.org/x/text/language"
)
// MessageFile represents a parsed message file.
type MessageFile struct {
Path string
Tag language.Tag
Format string
Messages []*Message
}
// ParseMessageFileBytes returns the messages parsed from file.
func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]UnmarshalFunc) (*MessageFile, error) {
lang, format := parsePath(path)
tag := language.Make(lang)
messageFile := &MessageFile{
Path: path,
Tag: tag,
Format: format,
}
if len(buf) == 0 {
return messageFile, nil
}
unmarshalFunc := unmarshalFuncs[messageFile.Format]
if unmarshalFunc == nil {
if messageFile.Format == "json" {
unmarshalFunc = json.Unmarshal
} else {
return nil, fmt.Errorf("no unmarshaler registered for %s", messageFile.Format)
}
}
var err error
var raw interface{}
if err = unmarshalFunc(buf, &raw); err != nil {
return nil, err
}
if messageFile.Messages, err = recGetMessages(raw, isMessage(raw), true); err != nil {
return nil, err
}
return messageFile, nil
}
const nestedSeparator = "."
var errInvalidTranslationFile = errors.New("invalid translation file, expected key-values, got a single value")
// recGetMessages looks for translation messages inside "raw" parameter,
// scanning nested maps using recursion.
func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Message, error) {
var messages []*Message
var err error
switch data := raw.(type) {
case string:
if isInitialCall {
return nil, errInvalidTranslationFile
}
m, err := NewMessage(data)
return []*Message{m}, err
case map[string]interface{}:
if isMapMessage {
m, err := NewMessage(data)
return []*Message{m}, err
}
messages = make([]*Message, 0, len(data))
for id, data := range data {
// recursively scan map items
messages, err = addChildMessages(id, data, messages)
if err != nil {
return nil, err
}
}
case map[interface{}]interface{}:
if isMapMessage {
m, err := NewMessage(data)
return []*Message{m}, err
}
messages = make([]*Message, 0, len(data))
for id, data := range data {
strid, ok := id.(string)
if !ok {
return nil, fmt.Errorf("expected key to be string but got %#v", id)
}
// recursively scan map items
messages, err = addChildMessages(strid, data, messages)
if err != nil {
return nil, err
}
}
case []interface{}:
// Backward compatibility for v1 file format.
messages = make([]*Message, 0, len(data))
for _, data := range data {
// recursively scan slice items
childMessages, err := recGetMessages(data, isMessage(data), false)
if err != nil {
return nil, err
}
messages = append(messages, childMessages...)
}
default:
return nil, fmt.Errorf("unsupported file format %T", raw)
}
return messages, nil
}
func addChildMessages(id string, data interface{}, messages []*Message) ([]*Message, error) {
isChildMessage := isMessage(data)
childMessages, err := recGetMessages(data, isChildMessage, false)
if err != nil {
return nil, err
}
for _, m := range childMessages {
if isChildMessage {
if m.ID == "" {
m.ID = id // start with innermost key
}
} else {
m.ID = id + nestedSeparator + m.ID // update ID with each nested key on the way
}
messages = append(messages, m)
}
return messages, nil
}
func parsePath(path string) (langTag, format string) {
formatStartIdx := -1
for i := len(path) - 1; i >= 0; i-- {
c := path[i]
if os.IsPathSeparator(c) {
if formatStartIdx != -1 {
langTag = path[i+1 : formatStartIdx]
}
return
}
if path[i] == '.' {
if formatStartIdx != -1 {
langTag = path[i+1 : formatStartIdx]
return
}
if formatStartIdx == -1 {
format = path[i+1:]
formatStartIdx = i
}
}
}
if formatStartIdx != -1 {
langTag = path[:formatStartIdx]
}
return
}

View file

@ -1,3 +0,0 @@
// Package plural provides support for pluralizing messages
// according to CLDR rules http://cldr.unicode.org/index/cldr-spec/plural-rules
package plural

View file

@ -1,16 +0,0 @@
package plural
// Form represents a language pluralization form as defined here:
// http://cldr.unicode.org/index/cldr-spec/plural-rules
type Form string
// All defined plural forms.
const (
Invalid Form = ""
Zero Form = "zero"
One Form = "one"
Two Form = "two"
Few Form = "few"
Many Form = "many"
Other Form = "other"
)

View file

@ -1,120 +0,0 @@
package plural
import (
"fmt"
"strconv"
"strings"
)
// 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
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 {
for _, i := range any {
if o.I == i && o.T == 0 {
return true
}
}
return false
}
// 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 {
modI := o.I % mod
for _, i := range any {
if modI == i && o.T == 0 {
return true
}
}
return false
}
// 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 && from <= o.I && o.I <= 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 && from <= modI && modI <= to
}
// NewOperands returns the operands for number.
func NewOperands(number interface{}) (*Operands, error) {
switch number := number.(type) {
case int:
return newOperandsInt64(int64(number)), nil
case int8:
return newOperandsInt64(int64(number)), nil
case int16:
return newOperandsInt64(int64(number)), nil
case int32:
return newOperandsInt64(int64(number)), nil
case int64:
return newOperandsInt64(number), nil
case string:
return newOperandsString(number)
case float32, float64:
return nil, fmt.Errorf("floats should be formatted into a string")
default:
return nil, fmt.Errorf("invalid type %T; expected integer or string", number)
}
}
func newOperandsInt64(i int64) *Operands {
if i < 0 {
i = -i
}
return &Operands{float64(i), i, 0, 0, 0, 0}
}
func newOperandsString(s string) (*Operands, error) {
if s[0] == '-' {
s = s[1:]
}
n, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
ops := &Operands{N: n}
parts := strings.SplitN(s, ".", 2)
ops.I, err = strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return nil, err
}
if len(parts) == 1 {
return ops, nil
}
fraction := parts[1]
ops.V = int64(len(fraction))
for i := ops.V - 1; i >= 0; i-- {
if fraction[i] != '0' {
ops.W = i + 1
break
}
}
if ops.V > 0 {
f, err := strconv.ParseInt(fraction, 10, 0)
if err != nil {
return nil, err
}
ops.F = f
}
if ops.W > 0 {
t, err := strconv.ParseInt(fraction[:ops.W], 10, 0)
if err != nil {
return nil, err
}
ops.T = t
}
return ops, nil
}

View file

@ -1,44 +0,0 @@
package plural
import (
"golang.org/x/text/language"
)
// 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, ids []string, ps *Rule) {
for _, id := range ids {
if id == "root" {
continue
}
tag := language.MustParse(id)
rules[tag] = ps
}
}
func newPluralFormSet(pluralForms ...Form) map[Form]struct{} {
set := make(map[Form]struct{}, len(pluralForms))
for _, plural := range pluralForms {
set[plural] = struct{}{}
}
return set
}
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
}

View file

@ -1,589 +0,0 @@
// This file is generated by i18n/plural/codegen/generate.sh; DO NOT EDIT
package plural
// DefaultRules returns a map of Rules generated from CLDR language data.
func DefaultRules() Rules {
rules := Rules{}
addPluralRules(rules, []string{"bm", "bo", "dz", "id", "ig", "ii", "in", "ja", "jbo", "jv", "jw", "kde", "kea", "km", "ko", "lkt", "lo", "ms", "my", "nqo", "root", "sah", "ses", "sg", "th", "to", "vi", "wo", "yo", "yue", "zh"}, &Rule{
PluralForms: newPluralFormSet(Other),
PluralFormFunc: func(ops *Operands) Form {
return Other
},
})
addPluralRules(rules, []string{"am", "as", "bn", "fa", "gu", "hi", "kn", "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, []string{"ff", "fr", "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, []string{"pt"}, &Rule{
PluralForms: newPluralFormSet(One, Other),
PluralFormFunc: func(ops *Operands) Form {
// i = 0..1
if intInRange(ops.I, 0, 1) {
return One
}
return Other
},
})
addPluralRules(rules, []string{"ast", "ca", "de", "en", "et", "fi", "fy", "gl", "ia", "io", "it", "ji", "nl", "pt_PT", "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, []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, []string{"ak", "bh", "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, []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, []string{"af", "asa", "az", "bem", "bez", "bg", "brx", "ce", "cgg", "chr", "ckb", "dv", "ee", "el", "eo", "es", "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, []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, []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, []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, []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, []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, []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, []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, []string{"iu", "naq", "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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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, []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
},
})
return rules
}

View file

@ -1,24 +0,0 @@
package plural
import "golang.org/x/text/language"
// Rules is a set of plural rules by language tag.
type Rules map[language.Tag]*Rule
// Rule returns the closest matching plural rule for the language tag
// or nil if no rule could be found.
func (r Rules) Rule(tag language.Tag) *Rule {
t := tag
for {
if rule := r[t]; rule != nil {
return rule
}
t = t.Parent()
if t.IsRoot() {
break
}
}
base, _ := tag.Base()
baseTag, _ := language.Parse(base.String())
return r[baseTag]
}

View file

@ -1,51 +0,0 @@
package internal
import (
"bytes"
"strings"
"sync"
gotemplate "text/template"
)
// Template stores the template for a string.
type Template struct {
Src string
LeftDelim string
RightDelim string
parseOnce sync.Once
parsedTemplate *gotemplate.Template
parseError error
}
func (t *Template) Execute(funcs gotemplate.FuncMap, data interface{}) (string, error) {
leftDelim := t.LeftDelim
if leftDelim == "" {
leftDelim = "{{"
}
if !strings.Contains(t.Src, leftDelim) {
// Fast path to avoid parsing a template that has no actions.
return t.Src, nil
}
var gt *gotemplate.Template
var err error
if funcs == nil {
t.parseOnce.Do(func() {
// If funcs is nil, then we only need to parse this template once.
t.parsedTemplate, t.parseError = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Parse(t.Src)
})
gt, err = t.parsedTemplate, t.parseError
} else {
gt, err = gotemplate.New("").Delims(t.LeftDelim, t.RightDelim).Funcs(funcs).Parse(t.Src)
}
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := gt.Execute(&buf, data); err != nil {
return "", err
}
return buf.String(), nil
}