You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

421 lines
13 KiB

  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. //go:generate go run maketables.go -output tables.go
  5. // Package display provides display names for languages, scripts and regions in
  6. // a requested language.
  7. //
  8. // The data is based on CLDR's localeDisplayNames. It includes the names of the
  9. // draft level "contributed" or "approved". The resulting tables are quite
  10. // large. The display package is designed so that users can reduce the linked-in
  11. // table sizes by cherry picking the languages one wishes to support. There is a
  12. // Dictionary defined for a selected set of common languages for this purpose.
  13. package display // import "golang.org/x/text/language/display"
  14. import (
  15. "fmt"
  16. "strings"
  17. "golang.org/x/text/internal/format"
  18. "golang.org/x/text/language"
  19. )
  20. /*
  21. TODO:
  22. All fairly low priority at the moment:
  23. - Include alternative and variants as an option (using func options).
  24. - Option for returning the empty string for undefined values.
  25. - Support variants, currencies, time zones, option names and other data
  26. provided in CLDR.
  27. - Do various optimizations:
  28. - Reduce size of offset tables.
  29. - Consider compressing infrequently used languages and decompress on demand.
  30. */
  31. // A Formatter formats a tag in the current language. It is used in conjunction
  32. // with the message package.
  33. type Formatter struct {
  34. lookup func(tag int, x interface{}) string
  35. x interface{}
  36. }
  37. // Format implements "golang.org/x/text/internal/format".Formatter.
  38. func (f Formatter) Format(state format.State, verb rune) {
  39. // TODO: there are a lot of inefficiencies in this code. Fix it when we
  40. // language.Tag has embedded compact tags.
  41. t := state.Language()
  42. _, index, _ := matcher.Match(t)
  43. str := f.lookup(index, f.x)
  44. if str == "" {
  45. // TODO: use language-specific punctuation.
  46. // TODO: use codePattern instead of language?
  47. if unknown := f.lookup(index, language.Und); unknown != "" {
  48. fmt.Fprintf(state, "%v (%v)", unknown, f.x)
  49. } else {
  50. fmt.Fprintf(state, "[language: %v]", f.x)
  51. }
  52. } else {
  53. state.Write([]byte(str))
  54. }
  55. }
  56. // Language returns a Formatter that renders the name for lang in the
  57. // current language. x may be a language.Base or a language.Tag.
  58. // It renders lang in the default language if no translation for the current
  59. // language is supported.
  60. func Language(lang interface{}) Formatter {
  61. return Formatter{langFunc, lang}
  62. }
  63. // Region returns a Formatter that renders the name for region in the current
  64. // language. region may be a language.Region or a language.Tag.
  65. // It renders region in the default language if no translation for the current
  66. // language is supported.
  67. func Region(region interface{}) Formatter {
  68. return Formatter{regionFunc, region}
  69. }
  70. // Script returns a Formatter that renders the name for script in the current
  71. // language. script may be a language.Script or a language.Tag.
  72. // It renders script in the default language if no translation for the current
  73. // language is supported.
  74. func Script(script interface{}) Formatter {
  75. return Formatter{scriptFunc, script}
  76. }
  77. // Script returns a Formatter that renders the name for tag in the current
  78. // language. tag may be a language.Tag.
  79. // It renders tag in the default language if no translation for the current
  80. // language is supported.
  81. func Tag(tag interface{}) Formatter {
  82. return Formatter{tagFunc, tag}
  83. }
  84. // A Namer is used to get the name for a given value, such as a Tag, Language,
  85. // Script or Region.
  86. type Namer interface {
  87. // Name returns a display string for the given value. A Namer returns an
  88. // empty string for values it does not support. A Namer may support naming
  89. // an unspecified value. For example, when getting the name for a region for
  90. // a tag that does not have a defined Region, it may return the name for an
  91. // unknown region. It is up to the user to filter calls to Name for values
  92. // for which one does not want to have a name string.
  93. Name(x interface{}) string
  94. }
  95. var (
  96. // Supported lists the languages for which names are defined.
  97. Supported language.Coverage
  98. // The set of all possible values for which names are defined. Note that not
  99. // all Namer implementations will cover all the values of a given type.
  100. // A Namer will return the empty string for unsupported values.
  101. Values language.Coverage
  102. matcher language.Matcher
  103. )
  104. func init() {
  105. tags := make([]language.Tag, numSupported)
  106. s := supported
  107. for i := range tags {
  108. p := strings.IndexByte(s, '|')
  109. tags[i] = language.Raw.Make(s[:p])
  110. s = s[p+1:]
  111. }
  112. matcher = language.NewMatcher(tags)
  113. Supported = language.NewCoverage(tags)
  114. Values = language.NewCoverage(langTagSet.Tags, supportedScripts, supportedRegions)
  115. }
  116. // Languages returns a Namer for naming languages. It returns nil if there is no
  117. // data for the given tag. The type passed to Name must be either language.Base
  118. // or language.Tag. Note that the result may differ between passing a tag or its
  119. // base language. For example, for English, passing "nl-BE" would return Flemish
  120. // whereas passing "nl" returns "Dutch".
  121. func Languages(t language.Tag) Namer {
  122. if _, index, conf := matcher.Match(t); conf != language.No {
  123. return languageNamer(index)
  124. }
  125. return nil
  126. }
  127. type languageNamer int
  128. func langFunc(i int, x interface{}) string {
  129. return nameLanguage(languageNamer(i), x)
  130. }
  131. func (n languageNamer) name(i int) string {
  132. return lookup(langHeaders[:], int(n), i)
  133. }
  134. // Name implements the Namer interface for language names.
  135. func (n languageNamer) Name(x interface{}) string {
  136. return nameLanguage(n, x)
  137. }
  138. // nonEmptyIndex walks up the parent chain until a non-empty header is found.
  139. // It returns -1 if no index could be found.
  140. func nonEmptyIndex(h []header, index int) int {
  141. for ; index != -1 && h[index].data == ""; index = int(parents[index]) {
  142. }
  143. return index
  144. }
  145. // Scripts returns a Namer for naming scripts. It returns nil if there is no
  146. // data for the given tag. The type passed to Name must be either a
  147. // language.Script or a language.Tag. It will not attempt to infer a script for
  148. // tags with an unspecified script.
  149. func Scripts(t language.Tag) Namer {
  150. if _, index, conf := matcher.Match(t); conf != language.No {
  151. if index = nonEmptyIndex(scriptHeaders[:], index); index != -1 {
  152. return scriptNamer(index)
  153. }
  154. }
  155. return nil
  156. }
  157. type scriptNamer int
  158. func scriptFunc(i int, x interface{}) string {
  159. return nameScript(scriptNamer(i), x)
  160. }
  161. func (n scriptNamer) name(i int) string {
  162. return lookup(scriptHeaders[:], int(n), i)
  163. }
  164. // Name implements the Namer interface for script names.
  165. func (n scriptNamer) Name(x interface{}) string {
  166. return nameScript(n, x)
  167. }
  168. // Regions returns a Namer for naming regions. It returns nil if there is no
  169. // data for the given tag. The type passed to Name must be either a
  170. // language.Region or a language.Tag. It will not attempt to infer a region for
  171. // tags with an unspecified region.
  172. func Regions(t language.Tag) Namer {
  173. if _, index, conf := matcher.Match(t); conf != language.No {
  174. if index = nonEmptyIndex(regionHeaders[:], index); index != -1 {
  175. return regionNamer(index)
  176. }
  177. }
  178. return nil
  179. }
  180. type regionNamer int
  181. func regionFunc(i int, x interface{}) string {
  182. return nameRegion(regionNamer(i), x)
  183. }
  184. func (n regionNamer) name(i int) string {
  185. return lookup(regionHeaders[:], int(n), i)
  186. }
  187. // Name implements the Namer interface for region names.
  188. func (n regionNamer) Name(x interface{}) string {
  189. return nameRegion(n, x)
  190. }
  191. // Tags returns a Namer for giving a full description of a tag. The names of
  192. // scripts and regions that are not already implied by the language name will
  193. // in appended within parentheses. It returns nil if there is not data for the
  194. // given tag. The type passed to Name must be a tag.
  195. func Tags(t language.Tag) Namer {
  196. if _, index, conf := matcher.Match(t); conf != language.No {
  197. return tagNamer(index)
  198. }
  199. return nil
  200. }
  201. type tagNamer int
  202. func tagFunc(i int, x interface{}) string {
  203. return nameTag(languageNamer(i), scriptNamer(i), regionNamer(i), x)
  204. }
  205. // Name implements the Namer interface for tag names.
  206. func (n tagNamer) Name(x interface{}) string {
  207. return nameTag(languageNamer(n), scriptNamer(n), regionNamer(n), x)
  208. }
  209. // lookup finds the name for an entry in a global table, traversing the
  210. // inheritance hierarchy if needed.
  211. func lookup(table []header, dict, want int) string {
  212. for dict != -1 {
  213. if s := table[dict].name(want); s != "" {
  214. return s
  215. }
  216. dict = int(parents[dict])
  217. }
  218. return ""
  219. }
  220. // A Dictionary holds a collection of Namers for a single language. One can
  221. // reduce the amount of data linked in to a binary by only referencing
  222. // Dictionaries for the languages one needs to support instead of using the
  223. // generic Namer factories.
  224. type Dictionary struct {
  225. parent *Dictionary
  226. lang header
  227. script header
  228. region header
  229. }
  230. // Tags returns a Namer for giving a full description of a tag. The names of
  231. // scripts and regions that are not already implied by the language name will
  232. // in appended within parentheses. It returns nil if there is not data for the
  233. // given tag. The type passed to Name must be a tag.
  234. func (d *Dictionary) Tags() Namer {
  235. return dictTags{d}
  236. }
  237. type dictTags struct {
  238. d *Dictionary
  239. }
  240. // Name implements the Namer interface for tag names.
  241. func (n dictTags) Name(x interface{}) string {
  242. return nameTag(dictLanguages{n.d}, dictScripts{n.d}, dictRegions{n.d}, x)
  243. }
  244. // Languages returns a Namer for naming languages. It returns nil if there is no
  245. // data for the given tag. The type passed to Name must be either language.Base
  246. // or language.Tag. Note that the result may differ between passing a tag or its
  247. // base language. For example, for English, passing "nl-BE" would return Flemish
  248. // whereas passing "nl" returns "Dutch".
  249. func (d *Dictionary) Languages() Namer {
  250. return dictLanguages{d}
  251. }
  252. type dictLanguages struct {
  253. d *Dictionary
  254. }
  255. func (n dictLanguages) name(i int) string {
  256. for d := n.d; d != nil; d = d.parent {
  257. if s := d.lang.name(i); s != "" {
  258. return s
  259. }
  260. }
  261. return ""
  262. }
  263. // Name implements the Namer interface for language names.
  264. func (n dictLanguages) Name(x interface{}) string {
  265. return nameLanguage(n, x)
  266. }
  267. // Scripts returns a Namer for naming scripts. It returns nil if there is no
  268. // data for the given tag. The type passed to Name must be either a
  269. // language.Script or a language.Tag. It will not attempt to infer a script for
  270. // tags with an unspecified script.
  271. func (d *Dictionary) Scripts() Namer {
  272. return dictScripts{d}
  273. }
  274. type dictScripts struct {
  275. d *Dictionary
  276. }
  277. func (n dictScripts) name(i int) string {
  278. for d := n.d; d != nil; d = d.parent {
  279. if s := d.script.name(i); s != "" {
  280. return s
  281. }
  282. }
  283. return ""
  284. }
  285. // Name implements the Namer interface for script names.
  286. func (n dictScripts) Name(x interface{}) string {
  287. return nameScript(n, x)
  288. }
  289. // Regions returns a Namer for naming regions. It returns nil if there is no
  290. // data for the given tag. The type passed to Name must be either a
  291. // language.Region or a language.Tag. It will not attempt to infer a region for
  292. // tags with an unspecified region.
  293. func (d *Dictionary) Regions() Namer {
  294. return dictRegions{d}
  295. }
  296. type dictRegions struct {
  297. d *Dictionary
  298. }
  299. func (n dictRegions) name(i int) string {
  300. for d := n.d; d != nil; d = d.parent {
  301. if s := d.region.name(i); s != "" {
  302. return s
  303. }
  304. }
  305. return ""
  306. }
  307. // Name implements the Namer interface for region names.
  308. func (n dictRegions) Name(x interface{}) string {
  309. return nameRegion(n, x)
  310. }
  311. // A SelfNamer implements a Namer that returns the name of language in this same
  312. // language. It provides a very compact mechanism to provide a comprehensive
  313. // list of languages to users in their native language.
  314. type SelfNamer struct {
  315. // Supported defines the values supported by this Namer.
  316. Supported language.Coverage
  317. }
  318. var (
  319. // Self is a shared instance of a SelfNamer.
  320. Self *SelfNamer = &self
  321. self = SelfNamer{language.NewCoverage(selfTagSet.Tags)}
  322. )
  323. // Name returns the name of a given language tag in the language identified by
  324. // this tag. It supports both the language.Base and language.Tag types.
  325. func (n SelfNamer) Name(x interface{}) string {
  326. t, _ := language.All.Compose(x)
  327. base, scr, reg := t.Raw()
  328. baseScript := language.Script{}
  329. if (scr == language.Script{} && reg != language.Region{}) {
  330. // For looking up in the self dictionary, we need to select the
  331. // maximized script. This is even the case if the script isn't
  332. // specified.
  333. s1, _ := t.Script()
  334. if baseScript = getScript(base); baseScript != s1 {
  335. scr = s1
  336. }
  337. }
  338. i, scr, reg := selfTagSet.index(base, scr, reg)
  339. if i == -1 {
  340. return ""
  341. }
  342. // Only return the display name if the script matches the expected script.
  343. if (scr != language.Script{}) {
  344. if (baseScript == language.Script{}) {
  345. baseScript = getScript(base)
  346. }
  347. if baseScript != scr {
  348. return ""
  349. }
  350. }
  351. return selfHeaders[0].name(i)
  352. }
  353. // getScript returns the maximized script for a base language.
  354. func getScript(b language.Base) language.Script {
  355. tag, _ := language.Raw.Compose(b)
  356. scr, _ := tag.Script()
  357. return scr
  358. }