// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:generate go run gen.go gen_index.go -output tables.go //go:generate go run gen_parents.go package compact // TODO: Remove above NOTE after: // - verifying that tables are dropped correctly (most notably matcher tables). import ( "strings" "golang.org/x/text/internal/language" ) // Tag represents a BCP 47 language tag. It is used to specify an instance of a // specific language or locale. All language tag values are guaranteed to be // well-formed. type Tag struct { // NOTE: exported tags will become part of the public API. language ID locale ID full fullTag // always a language.Tag for now. } const _und = 0 type fullTag interface { IsRoot() bool Parent() language.Tag } // Make a compact Tag from a fully specified internal language Tag. func Make(t language.Tag) (tag Tag) { if region := t.TypeForKey("rg"); len(region) == 6 && region[2:] == "zzzz" { if r, err := language.ParseRegion(region[:2]); err == nil { tFull := t t, _ = t.SetTypeForKey("rg", "") // TODO: should we not consider "va" for the language tag? var exact1, exact2 bool tag.language, exact1 = FromTag(t) t.RegionID = r tag.locale, exact2 = FromTag(t) if !exact1 || !exact2 { tag.full = tFull } return tag } } lang, ok := FromTag(t) tag.language = lang tag.locale = lang if !ok { tag.full = t } return tag } // Tag returns an internal language Tag version of this tag. func (t Tag) Tag() language.Tag { if t.full != nil { return t.full.(language.Tag) } tag := t.language.Tag() if t.language != t.locale { loc := t.locale.Tag() tag, _ = tag.SetTypeForKey("rg", strings.ToLower(loc.RegionID.String())+"zzzz") } return tag } // IsCompact reports whether this tag is fully defined in terms of ID. func (t *Tag) IsCompact() bool { return t.full == nil } // MayHaveVariants reports whether a tag may have variants. If it returns false // it is guaranteed the tag does not have variants. func (t Tag) MayHaveVariants() bool { return t.full != nil || int(t.language) >= len(coreTags) } // MayHaveExtensions reports whether a tag may have extensions. If it returns // false it is guaranteed the tag does not have them. func (t Tag) MayHaveExtensions() bool { return t.full != nil || int(t.language) >= len(coreTags) || t.language != t.locale } // IsRoot returns true if t is equal to language "und". func (t Tag) IsRoot() bool { if t.full != nil { return t.full.IsRoot() } return t.language == _und } // Parent returns the CLDR parent of t. In CLDR, missing fields in data for a // specific language are substituted with fields from the parent language. // The parent for a language may change for newer versions of CLDR. func (t Tag) Parent() Tag { if t.full != nil { return Make(t.full.Parent()) } if t.language != t.locale { // Simulate stripping -u-rg-xxxxxx return Tag{language: t.language, locale: t.language} } // TODO: use parent lookup table once cycle from internal package is // removed. Probably by internalizing the table and declaring this fast // enough. // lang := compactID(internal.Parent(uint16(t.language))) lang, _ := FromTag(t.language.Tag().Parent()) return Tag{language: lang, locale: lang} } // returns token t and the rest of the string. func nextToken(s string) (t, tail string) { p := strings.Index(s[1:], "-") if p == -1 { return s[1:], "" } p++ return s[1:p], s[p:] } // LanguageID returns an index, where 0 <= index < NumCompactTags, for tags // for which data exists in the text repository.The index will change over time // and should not be stored in persistent storage. If t does not match a compact // index, exact will be false and the compact index will be returned for the // first match after repeatedly taking the Parent of t. func LanguageID(t Tag) (id ID, exact bool) { return t.language, t.full == nil } // RegionalID returns the ID for the regional variant of this tag. This index is // used to indicate region-specific overrides, such as default currency, default // calendar and week data, default time cycle, and default measurement system // and unit preferences. // // For instance, the tag en-GB-u-rg-uszzzz specifies British English with US // settings for currency, number formatting, etc. The CompactIndex for this tag // will be that for en-GB, while the RegionalID will be the one corresponding to // en-US. func RegionalID(t Tag) (id ID, exact bool) { return t.locale, t.full == nil } // LanguageTag returns t stripped of regional variant indicators. // // At the moment this means it is stripped of a regional and variant subtag "rg" // and "va" in the "u" extension. func (t Tag) LanguageTag() Tag { if t.full == nil { return Tag{language: t.language, locale: t.language} } tt := t.Tag() tt.SetTypeForKey("rg", "") tt.SetTypeForKey("va", "") return Make(tt) } // RegionalTag returns the regional variant of the tag. // // At the moment this means that the region is set from the regional subtag // "rg" in the "u" extension. func (t Tag) RegionalTag() Tag { rt := Tag{language: t.locale, locale: t.locale} if t.full == nil { return rt } b := language.Builder{} tag := t.Tag() // tag, _ = tag.SetTypeForKey("rg", "") b.SetTag(t.locale.Tag()) if v := tag.Variants(); v != "" { for _, v := range strings.Split(v, "-") { b.AddVariant(v) } } for _, e := range tag.Extensions() { b.AddExt(e) } return t } // FromTag reports closest matching ID for an internal language Tag. func FromTag(t language.Tag) (id ID, exact bool) { // TODO: perhaps give more frequent tags a lower index. // TODO: we could make the indexes stable. This will excluded some // possibilities for optimization, so don't do this quite yet. exact = true b, s, r := t.Raw() if t.HasString() { if t.IsPrivateUse() { // We have no entries for user-defined tags. return 0, false } hasExtra := false if t.HasVariants() { if t.HasExtensions() { build := language.Builder{} build.SetTag(language.Tag{LangID: b, ScriptID: s, RegionID: r}) build.AddVariant(t.Variants()) exact = false t = build.Make() } hasExtra = true } else if _, ok := t.Extension('u'); ok { // TODO: va may mean something else. Consider not considering it. // Strip all but the 'va' entry. old := t variant := t.TypeForKey("va") t = language.Tag{LangID: b, ScriptID: s, RegionID: r} if variant != "" { t, _ = t.SetTypeForKey("va", variant) hasExtra = true } exact = old == t } else { exact = false } if hasExtra { // We have some variants. for i, s := range specialTags { if s == t { return ID(i + len(coreTags)), exact } } exact = false } } if x, ok := getCoreIndex(t); ok { return x, exact } exact = false if r != 0 && s == 0 { // Deal with cases where an extra script is inserted for the region. t, _ := t.Maximize() if x, ok := getCoreIndex(t); ok { return x, exact } } for t = t.Parent(); t != root; t = t.Parent() { // No variants specified: just compare core components. // The key has the form lllssrrr, where l, s, and r are nibbles for // respectively the langID, scriptID, and regionID. if x, ok := getCoreIndex(t); ok { return x, exact } } return 0, exact } var root = language.Tag{}