// 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]) if spec := getPluralSpec(tag); spec != nil { langs = append(langs, &Language{NormalizeTag(tag), spec}) } start = end + 1 } } if start > 0 { tag := strings.TrimSpace(src[start:]) if spec := getPluralSpec(tag); spec != nil { langs = append(langs, &Language{NormalizeTag(tag), spec}) } return dedupe(langs) } if spec := getPluralSpec(src); spec != nil { 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) }