Scott Baker | 2d89798 | 2019-09-24 11:50:08 -0700 | [diff] [blame^] | 1 | // Copyright 2014 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package language |
| 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "sort" |
| 10 | ) |
| 11 | |
| 12 | // The Coverage interface is used to define the level of coverage of an |
| 13 | // internationalization service. Note that not all types are supported by all |
| 14 | // services. As lists may be generated on the fly, it is recommended that users |
| 15 | // of a Coverage cache the results. |
| 16 | type Coverage interface { |
| 17 | // Tags returns the list of supported tags. |
| 18 | Tags() []Tag |
| 19 | |
| 20 | // BaseLanguages returns the list of supported base languages. |
| 21 | BaseLanguages() []Base |
| 22 | |
| 23 | // Scripts returns the list of supported scripts. |
| 24 | Scripts() []Script |
| 25 | |
| 26 | // Regions returns the list of supported regions. |
| 27 | Regions() []Region |
| 28 | } |
| 29 | |
| 30 | var ( |
| 31 | // Supported defines a Coverage that lists all supported subtags. Tags |
| 32 | // always returns nil. |
| 33 | Supported Coverage = allSubtags{} |
| 34 | ) |
| 35 | |
| 36 | // TODO: |
| 37 | // - Support Variants, numbering systems. |
| 38 | // - CLDR coverage levels. |
| 39 | // - Set of common tags defined in this package. |
| 40 | |
| 41 | type allSubtags struct{} |
| 42 | |
| 43 | // Regions returns the list of supported regions. As all regions are in a |
| 44 | // consecutive range, it simply returns a slice of numbers in increasing order. |
| 45 | // The "undefined" region is not returned. |
| 46 | func (s allSubtags) Regions() []Region { |
| 47 | reg := make([]Region, numRegions) |
| 48 | for i := range reg { |
| 49 | reg[i] = Region{regionID(i + 1)} |
| 50 | } |
| 51 | return reg |
| 52 | } |
| 53 | |
| 54 | // Scripts returns the list of supported scripts. As all scripts are in a |
| 55 | // consecutive range, it simply returns a slice of numbers in increasing order. |
| 56 | // The "undefined" script is not returned. |
| 57 | func (s allSubtags) Scripts() []Script { |
| 58 | scr := make([]Script, numScripts) |
| 59 | for i := range scr { |
| 60 | scr[i] = Script{scriptID(i + 1)} |
| 61 | } |
| 62 | return scr |
| 63 | } |
| 64 | |
| 65 | // BaseLanguages returns the list of all supported base languages. It generates |
| 66 | // the list by traversing the internal structures. |
| 67 | func (s allSubtags) BaseLanguages() []Base { |
| 68 | base := make([]Base, 0, numLanguages) |
| 69 | for i := 0; i < langNoIndexOffset; i++ { |
| 70 | // We included "und" already for the value 0. |
| 71 | if i != nonCanonicalUnd { |
| 72 | base = append(base, Base{langID(i)}) |
| 73 | } |
| 74 | } |
| 75 | i := langNoIndexOffset |
| 76 | for _, v := range langNoIndex { |
| 77 | for k := 0; k < 8; k++ { |
| 78 | if v&1 == 1 { |
| 79 | base = append(base, Base{langID(i)}) |
| 80 | } |
| 81 | v >>= 1 |
| 82 | i++ |
| 83 | } |
| 84 | } |
| 85 | return base |
| 86 | } |
| 87 | |
| 88 | // Tags always returns nil. |
| 89 | func (s allSubtags) Tags() []Tag { |
| 90 | return nil |
| 91 | } |
| 92 | |
| 93 | // coverage is used used by NewCoverage which is used as a convenient way for |
| 94 | // creating Coverage implementations for partially defined data. Very often a |
| 95 | // package will only need to define a subset of slices. coverage provides a |
| 96 | // convenient way to do this. Moreover, packages using NewCoverage, instead of |
| 97 | // their own implementation, will not break if later new slice types are added. |
| 98 | type coverage struct { |
| 99 | tags func() []Tag |
| 100 | bases func() []Base |
| 101 | scripts func() []Script |
| 102 | regions func() []Region |
| 103 | } |
| 104 | |
| 105 | func (s *coverage) Tags() []Tag { |
| 106 | if s.tags == nil { |
| 107 | return nil |
| 108 | } |
| 109 | return s.tags() |
| 110 | } |
| 111 | |
| 112 | // bases implements sort.Interface and is used to sort base languages. |
| 113 | type bases []Base |
| 114 | |
| 115 | func (b bases) Len() int { |
| 116 | return len(b) |
| 117 | } |
| 118 | |
| 119 | func (b bases) Swap(i, j int) { |
| 120 | b[i], b[j] = b[j], b[i] |
| 121 | } |
| 122 | |
| 123 | func (b bases) Less(i, j int) bool { |
| 124 | return b[i].langID < b[j].langID |
| 125 | } |
| 126 | |
| 127 | // BaseLanguages returns the result from calling s.bases if it is specified or |
| 128 | // otherwise derives the set of supported base languages from tags. |
| 129 | func (s *coverage) BaseLanguages() []Base { |
| 130 | if s.bases == nil { |
| 131 | tags := s.Tags() |
| 132 | if len(tags) == 0 { |
| 133 | return nil |
| 134 | } |
| 135 | a := make([]Base, len(tags)) |
| 136 | for i, t := range tags { |
| 137 | a[i] = Base{langID(t.lang)} |
| 138 | } |
| 139 | sort.Sort(bases(a)) |
| 140 | k := 0 |
| 141 | for i := 1; i < len(a); i++ { |
| 142 | if a[k] != a[i] { |
| 143 | k++ |
| 144 | a[k] = a[i] |
| 145 | } |
| 146 | } |
| 147 | return a[:k+1] |
| 148 | } |
| 149 | return s.bases() |
| 150 | } |
| 151 | |
| 152 | func (s *coverage) Scripts() []Script { |
| 153 | if s.scripts == nil { |
| 154 | return nil |
| 155 | } |
| 156 | return s.scripts() |
| 157 | } |
| 158 | |
| 159 | func (s *coverage) Regions() []Region { |
| 160 | if s.regions == nil { |
| 161 | return nil |
| 162 | } |
| 163 | return s.regions() |
| 164 | } |
| 165 | |
| 166 | // NewCoverage returns a Coverage for the given lists. It is typically used by |
| 167 | // packages providing internationalization services to define their level of |
| 168 | // coverage. A list may be of type []T or func() []T, where T is either Tag, |
| 169 | // Base, Script or Region. The returned Coverage derives the value for Bases |
| 170 | // from Tags if no func or slice for []Base is specified. For other unspecified |
| 171 | // types the returned Coverage will return nil for the respective methods. |
| 172 | func NewCoverage(list ...interface{}) Coverage { |
| 173 | s := &coverage{} |
| 174 | for _, x := range list { |
| 175 | switch v := x.(type) { |
| 176 | case func() []Base: |
| 177 | s.bases = v |
| 178 | case func() []Script: |
| 179 | s.scripts = v |
| 180 | case func() []Region: |
| 181 | s.regions = v |
| 182 | case func() []Tag: |
| 183 | s.tags = v |
| 184 | case []Base: |
| 185 | s.bases = func() []Base { return v } |
| 186 | case []Script: |
| 187 | s.scripts = func() []Script { return v } |
| 188 | case []Region: |
| 189 | s.regions = func() []Region { return v } |
| 190 | case []Tag: |
| 191 | s.tags = func() []Tag { return v } |
| 192 | default: |
| 193 | panic(fmt.Sprintf("language: unsupported set type %T", v)) |
| 194 | } |
| 195 | } |
| 196 | return s |
| 197 | } |