2016-04-10 23:39:38 +02:00
|
|
|
// Package language defines languages that implement CLDR pluralization.
|
|
|
|
package language
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Language is a written human language.
|
|
|
|
type Language struct {
|
|
|
|
// Tag uniquely identifies the language as defined by RFC 5646.
|
|
|
|
//
|
|
|
|
// Most language tags are a two character language code (ISO 639-1)
|
|
|
|
// optionally followed by a dash and a two character country code (ISO 3166-1).
|
|
|
|
// (e.g. en, pt-br)
|
|
|
|
Tag string
|
|
|
|
*PluralSpec
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Language) String() string {
|
|
|
|
return l.Tag
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchingTags returns the set of language tags that map to this Language.
|
|
|
|
// e.g. "zh-hans-cn" yields {"zh", "zh-hans", "zh-hans-cn"}
|
|
|
|
// BUG: This should be computed once and stored as a field on Language for efficiency,
|
|
|
|
// but this would require changing how Languages are constructed.
|
|
|
|
func (l *Language) MatchingTags() []string {
|
|
|
|
parts := strings.Split(l.Tag, "-")
|
|
|
|
var prefix, matches []string
|
|
|
|
for _, part := range parts {
|
|
|
|
prefix = append(prefix, part)
|
|
|
|
match := strings.Join(prefix, "-")
|
|
|
|
matches = append(matches, match)
|
|
|
|
}
|
|
|
|
return matches
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse returns a slice of supported languages found in src or nil if none are found.
|
|
|
|
// It can parse language tags and Accept-Language headers.
|
|
|
|
func Parse(src string) []*Language {
|
|
|
|
var langs []*Language
|
|
|
|
start := 0
|
|
|
|
for end, chr := range src {
|
|
|
|
switch chr {
|
|
|
|
case ',', ';', '.':
|
|
|
|
tag := strings.TrimSpace(src[start:end])
|
2020-08-10 00:29:54 +02:00
|
|
|
if spec := GetPluralSpec(tag); spec != nil {
|
2016-04-10 23:39:38 +02:00
|
|
|
langs = append(langs, &Language{NormalizeTag(tag), spec})
|
|
|
|
}
|
|
|
|
start = end + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if start > 0 {
|
|
|
|
tag := strings.TrimSpace(src[start:])
|
2020-08-10 00:29:54 +02:00
|
|
|
if spec := GetPluralSpec(tag); spec != nil {
|
2016-04-10 23:39:38 +02:00
|
|
|
langs = append(langs, &Language{NormalizeTag(tag), spec})
|
|
|
|
}
|
|
|
|
return dedupe(langs)
|
|
|
|
}
|
2020-08-10 00:29:54 +02:00
|
|
|
if spec := GetPluralSpec(src); spec != nil {
|
2016-04-10 23:39:38 +02:00
|
|
|
langs = append(langs, &Language{NormalizeTag(src), spec})
|
|
|
|
}
|
|
|
|
return langs
|
|
|
|
}
|
|
|
|
|
|
|
|
func dedupe(langs []*Language) []*Language {
|
|
|
|
found := make(map[string]struct{}, len(langs))
|
|
|
|
deduped := make([]*Language, 0, len(langs))
|
|
|
|
for _, lang := range langs {
|
|
|
|
if _, ok := found[lang.Tag]; !ok {
|
|
|
|
found[lang.Tag] = struct{}{}
|
|
|
|
deduped = append(deduped, lang)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return deduped
|
|
|
|
}
|
|
|
|
|
|
|
|
// MustParse is similar to Parse except it panics instead of retuning a nil Language.
|
|
|
|
func MustParse(src string) []*Language {
|
|
|
|
langs := Parse(src)
|
|
|
|
if len(langs) == 0 {
|
|
|
|
panic(fmt.Errorf("unable to parse language from %q", src))
|
|
|
|
}
|
|
|
|
return langs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add adds support for a new language.
|
|
|
|
func Add(l *Language) {
|
|
|
|
tag := NormalizeTag(l.Tag)
|
|
|
|
pluralSpecs[tag] = l.PluralSpec
|
|
|
|
}
|
|
|
|
|
|
|
|
// NormalizeTag returns a language tag with all lower-case characters
|
|
|
|
// and dashes "-" instead of underscores "_"
|
|
|
|
func NormalizeTag(tag string) string {
|
|
|
|
tag = strings.ToLower(tag)
|
|
|
|
return strings.Replace(tag, "_", "-", -1)
|
|
|
|
}
|