mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-05 01:52:46 +00:00
421 lines
13 KiB
Go
421 lines
13 KiB
Go
|
// Copyright 2014 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 maketables.go -output tables.go
|
||
|
|
||
|
// Package display provides display names for languages, scripts and regions in
|
||
|
// a requested language.
|
||
|
//
|
||
|
// The data is based on CLDR's localeDisplayNames. It includes the names of the
|
||
|
// draft level "contributed" or "approved". The resulting tables are quite
|
||
|
// large. The display package is designed so that users can reduce the linked-in
|
||
|
// table sizes by cherry picking the languages one wishes to support. There is a
|
||
|
// Dictionary defined for a selected set of common languages for this purpose.
|
||
|
package display // import "golang.org/x/text/language/display"
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/text/internal/format"
|
||
|
"golang.org/x/text/language"
|
||
|
)
|
||
|
|
||
|
/*
|
||
|
TODO:
|
||
|
All fairly low priority at the moment:
|
||
|
- Include alternative and variants as an option (using func options).
|
||
|
- Option for returning the empty string for undefined values.
|
||
|
- Support variants, currencies, time zones, option names and other data
|
||
|
provided in CLDR.
|
||
|
- Do various optimizations:
|
||
|
- Reduce size of offset tables.
|
||
|
- Consider compressing infrequently used languages and decompress on demand.
|
||
|
*/
|
||
|
|
||
|
// A Formatter formats a tag in the current language. It is used in conjunction
|
||
|
// with the message package.
|
||
|
type Formatter struct {
|
||
|
lookup func(tag int, x interface{}) string
|
||
|
x interface{}
|
||
|
}
|
||
|
|
||
|
// Format implements "golang.org/x/text/internal/format".Formatter.
|
||
|
func (f Formatter) Format(state format.State, verb rune) {
|
||
|
// TODO: there are a lot of inefficiencies in this code. Fix it when we
|
||
|
// language.Tag has embedded compact tags.
|
||
|
t := state.Language()
|
||
|
_, index, _ := matcher.Match(t)
|
||
|
str := f.lookup(index, f.x)
|
||
|
if str == "" {
|
||
|
// TODO: use language-specific punctuation.
|
||
|
// TODO: use codePattern instead of language?
|
||
|
if unknown := f.lookup(index, language.Und); unknown != "" {
|
||
|
fmt.Fprintf(state, "%v (%v)", unknown, f.x)
|
||
|
} else {
|
||
|
fmt.Fprintf(state, "[language: %v]", f.x)
|
||
|
}
|
||
|
} else {
|
||
|
state.Write([]byte(str))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Language returns a Formatter that renders the name for lang in the
|
||
|
// current language. x may be a language.Base or a language.Tag.
|
||
|
// It renders lang in the default language if no translation for the current
|
||
|
// language is supported.
|
||
|
func Language(lang interface{}) Formatter {
|
||
|
return Formatter{langFunc, lang}
|
||
|
}
|
||
|
|
||
|
// Region returns a Formatter that renders the name for region in the current
|
||
|
// language. region may be a language.Region or a language.Tag.
|
||
|
// It renders region in the default language if no translation for the current
|
||
|
// language is supported.
|
||
|
func Region(region interface{}) Formatter {
|
||
|
return Formatter{regionFunc, region}
|
||
|
}
|
||
|
|
||
|
// Script returns a Formatter that renders the name for script in the current
|
||
|
// language. script may be a language.Script or a language.Tag.
|
||
|
// It renders script in the default language if no translation for the current
|
||
|
// language is supported.
|
||
|
func Script(script interface{}) Formatter {
|
||
|
return Formatter{scriptFunc, script}
|
||
|
}
|
||
|
|
||
|
// Tag returns a Formatter that renders the name for tag in the current
|
||
|
// language. tag may be a language.Tag.
|
||
|
// It renders tag in the default language if no translation for the current
|
||
|
// language is supported.
|
||
|
func Tag(tag interface{}) Formatter {
|
||
|
return Formatter{tagFunc, tag}
|
||
|
}
|
||
|
|
||
|
// A Namer is used to get the name for a given value, such as a Tag, Language,
|
||
|
// Script or Region.
|
||
|
type Namer interface {
|
||
|
// Name returns a display string for the given value. A Namer returns an
|
||
|
// empty string for values it does not support. A Namer may support naming
|
||
|
// an unspecified value. For example, when getting the name for a region for
|
||
|
// a tag that does not have a defined Region, it may return the name for an
|
||
|
// unknown region. It is up to the user to filter calls to Name for values
|
||
|
// for which one does not want to have a name string.
|
||
|
Name(x interface{}) string
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// Supported lists the languages for which names are defined.
|
||
|
Supported language.Coverage
|
||
|
|
||
|
// The set of all possible values for which names are defined. Note that not
|
||
|
// all Namer implementations will cover all the values of a given type.
|
||
|
// A Namer will return the empty string for unsupported values.
|
||
|
Values language.Coverage
|
||
|
|
||
|
matcher language.Matcher
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
tags := make([]language.Tag, numSupported)
|
||
|
s := supported
|
||
|
for i := range tags {
|
||
|
p := strings.IndexByte(s, '|')
|
||
|
tags[i] = language.Raw.Make(s[:p])
|
||
|
s = s[p+1:]
|
||
|
}
|
||
|
matcher = language.NewMatcher(tags)
|
||
|
Supported = language.NewCoverage(tags)
|
||
|
|
||
|
Values = language.NewCoverage(langTagSet.Tags, supportedScripts, supportedRegions)
|
||
|
}
|
||
|
|
||
|
// Languages returns a Namer for naming languages. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either language.Base
|
||
|
// or language.Tag. Note that the result may differ between passing a tag or its
|
||
|
// base language. For example, for English, passing "nl-BE" would return Flemish
|
||
|
// whereas passing "nl" returns "Dutch".
|
||
|
func Languages(t language.Tag) Namer {
|
||
|
if _, index, conf := matcher.Match(t); conf != language.No {
|
||
|
return languageNamer(index)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type languageNamer int
|
||
|
|
||
|
func langFunc(i int, x interface{}) string {
|
||
|
return nameLanguage(languageNamer(i), x)
|
||
|
}
|
||
|
|
||
|
func (n languageNamer) name(i int) string {
|
||
|
return lookup(langHeaders[:], int(n), i)
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for language names.
|
||
|
func (n languageNamer) Name(x interface{}) string {
|
||
|
return nameLanguage(n, x)
|
||
|
}
|
||
|
|
||
|
// nonEmptyIndex walks up the parent chain until a non-empty header is found.
|
||
|
// It returns -1 if no index could be found.
|
||
|
func nonEmptyIndex(h []header, index int) int {
|
||
|
for ; index != -1 && h[index].data == ""; index = int(parents[index]) {
|
||
|
}
|
||
|
return index
|
||
|
}
|
||
|
|
||
|
// Scripts returns a Namer for naming scripts. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either a
|
||
|
// language.Script or a language.Tag. It will not attempt to infer a script for
|
||
|
// tags with an unspecified script.
|
||
|
func Scripts(t language.Tag) Namer {
|
||
|
if _, index, conf := matcher.Match(t); conf != language.No {
|
||
|
if index = nonEmptyIndex(scriptHeaders[:], index); index != -1 {
|
||
|
return scriptNamer(index)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type scriptNamer int
|
||
|
|
||
|
func scriptFunc(i int, x interface{}) string {
|
||
|
return nameScript(scriptNamer(i), x)
|
||
|
}
|
||
|
|
||
|
func (n scriptNamer) name(i int) string {
|
||
|
return lookup(scriptHeaders[:], int(n), i)
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for script names.
|
||
|
func (n scriptNamer) Name(x interface{}) string {
|
||
|
return nameScript(n, x)
|
||
|
}
|
||
|
|
||
|
// Regions returns a Namer for naming regions. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either a
|
||
|
// language.Region or a language.Tag. It will not attempt to infer a region for
|
||
|
// tags with an unspecified region.
|
||
|
func Regions(t language.Tag) Namer {
|
||
|
if _, index, conf := matcher.Match(t); conf != language.No {
|
||
|
if index = nonEmptyIndex(regionHeaders[:], index); index != -1 {
|
||
|
return regionNamer(index)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type regionNamer int
|
||
|
|
||
|
func regionFunc(i int, x interface{}) string {
|
||
|
return nameRegion(regionNamer(i), x)
|
||
|
}
|
||
|
|
||
|
func (n regionNamer) name(i int) string {
|
||
|
return lookup(regionHeaders[:], int(n), i)
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for region names.
|
||
|
func (n regionNamer) Name(x interface{}) string {
|
||
|
return nameRegion(n, x)
|
||
|
}
|
||
|
|
||
|
// Tags returns a Namer for giving a full description of a tag. The names of
|
||
|
// scripts and regions that are not already implied by the language name will
|
||
|
// in appended within parentheses. It returns nil if there is not data for the
|
||
|
// given tag. The type passed to Name must be a tag.
|
||
|
func Tags(t language.Tag) Namer {
|
||
|
if _, index, conf := matcher.Match(t); conf != language.No {
|
||
|
return tagNamer(index)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type tagNamer int
|
||
|
|
||
|
func tagFunc(i int, x interface{}) string {
|
||
|
return nameTag(languageNamer(i), scriptNamer(i), regionNamer(i), x)
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for tag names.
|
||
|
func (n tagNamer) Name(x interface{}) string {
|
||
|
return nameTag(languageNamer(n), scriptNamer(n), regionNamer(n), x)
|
||
|
}
|
||
|
|
||
|
// lookup finds the name for an entry in a global table, traversing the
|
||
|
// inheritance hierarchy if needed.
|
||
|
func lookup(table []header, dict, want int) string {
|
||
|
for dict != -1 {
|
||
|
if s := table[dict].name(want); s != "" {
|
||
|
return s
|
||
|
}
|
||
|
dict = int(parents[dict])
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// A Dictionary holds a collection of Namers for a single language. One can
|
||
|
// reduce the amount of data linked in to a binary by only referencing
|
||
|
// Dictionaries for the languages one needs to support instead of using the
|
||
|
// generic Namer factories.
|
||
|
type Dictionary struct {
|
||
|
parent *Dictionary
|
||
|
lang header
|
||
|
script header
|
||
|
region header
|
||
|
}
|
||
|
|
||
|
// Tags returns a Namer for giving a full description of a tag. The names of
|
||
|
// scripts and regions that are not already implied by the language name will
|
||
|
// in appended within parentheses. It returns nil if there is not data for the
|
||
|
// given tag. The type passed to Name must be a tag.
|
||
|
func (d *Dictionary) Tags() Namer {
|
||
|
return dictTags{d}
|
||
|
}
|
||
|
|
||
|
type dictTags struct {
|
||
|
d *Dictionary
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for tag names.
|
||
|
func (n dictTags) Name(x interface{}) string {
|
||
|
return nameTag(dictLanguages{n.d}, dictScripts{n.d}, dictRegions{n.d}, x)
|
||
|
}
|
||
|
|
||
|
// Languages returns a Namer for naming languages. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either language.Base
|
||
|
// or language.Tag. Note that the result may differ between passing a tag or its
|
||
|
// base language. For example, for English, passing "nl-BE" would return Flemish
|
||
|
// whereas passing "nl" returns "Dutch".
|
||
|
func (d *Dictionary) Languages() Namer {
|
||
|
return dictLanguages{d}
|
||
|
}
|
||
|
|
||
|
type dictLanguages struct {
|
||
|
d *Dictionary
|
||
|
}
|
||
|
|
||
|
func (n dictLanguages) name(i int) string {
|
||
|
for d := n.d; d != nil; d = d.parent {
|
||
|
if s := d.lang.name(i); s != "" {
|
||
|
return s
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for language names.
|
||
|
func (n dictLanguages) Name(x interface{}) string {
|
||
|
return nameLanguage(n, x)
|
||
|
}
|
||
|
|
||
|
// Scripts returns a Namer for naming scripts. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either a
|
||
|
// language.Script or a language.Tag. It will not attempt to infer a script for
|
||
|
// tags with an unspecified script.
|
||
|
func (d *Dictionary) Scripts() Namer {
|
||
|
return dictScripts{d}
|
||
|
}
|
||
|
|
||
|
type dictScripts struct {
|
||
|
d *Dictionary
|
||
|
}
|
||
|
|
||
|
func (n dictScripts) name(i int) string {
|
||
|
for d := n.d; d != nil; d = d.parent {
|
||
|
if s := d.script.name(i); s != "" {
|
||
|
return s
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for script names.
|
||
|
func (n dictScripts) Name(x interface{}) string {
|
||
|
return nameScript(n, x)
|
||
|
}
|
||
|
|
||
|
// Regions returns a Namer for naming regions. It returns nil if there is no
|
||
|
// data for the given tag. The type passed to Name must be either a
|
||
|
// language.Region or a language.Tag. It will not attempt to infer a region for
|
||
|
// tags with an unspecified region.
|
||
|
func (d *Dictionary) Regions() Namer {
|
||
|
return dictRegions{d}
|
||
|
}
|
||
|
|
||
|
type dictRegions struct {
|
||
|
d *Dictionary
|
||
|
}
|
||
|
|
||
|
func (n dictRegions) name(i int) string {
|
||
|
for d := n.d; d != nil; d = d.parent {
|
||
|
if s := d.region.name(i); s != "" {
|
||
|
return s
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// Name implements the Namer interface for region names.
|
||
|
func (n dictRegions) Name(x interface{}) string {
|
||
|
return nameRegion(n, x)
|
||
|
}
|
||
|
|
||
|
// A SelfNamer implements a Namer that returns the name of language in this same
|
||
|
// language. It provides a very compact mechanism to provide a comprehensive
|
||
|
// list of languages to users in their native language.
|
||
|
type SelfNamer struct {
|
||
|
// Supported defines the values supported by this Namer.
|
||
|
Supported language.Coverage
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
// Self is a shared instance of a SelfNamer.
|
||
|
Self *SelfNamer = &self
|
||
|
|
||
|
self = SelfNamer{language.NewCoverage(selfTagSet.Tags)}
|
||
|
)
|
||
|
|
||
|
// Name returns the name of a given language tag in the language identified by
|
||
|
// this tag. It supports both the language.Base and language.Tag types.
|
||
|
func (n SelfNamer) Name(x interface{}) string {
|
||
|
t, _ := language.All.Compose(x)
|
||
|
base, scr, reg := t.Raw()
|
||
|
baseScript := language.Script{}
|
||
|
if (scr == language.Script{} && reg != language.Region{}) {
|
||
|
// For looking up in the self dictionary, we need to select the
|
||
|
// maximized script. This is even the case if the script isn't
|
||
|
// specified.
|
||
|
s1, _ := t.Script()
|
||
|
if baseScript = getScript(base); baseScript != s1 {
|
||
|
scr = s1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i, scr, reg := selfTagSet.index(base, scr, reg)
|
||
|
if i == -1 {
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// Only return the display name if the script matches the expected script.
|
||
|
if (scr != language.Script{}) {
|
||
|
if (baseScript == language.Script{}) {
|
||
|
baseScript = getScript(base)
|
||
|
}
|
||
|
if baseScript != scr {
|
||
|
return ""
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return selfHeaders[0].name(i)
|
||
|
}
|
||
|
|
||
|
// getScript returns the maximized script for a base language.
|
||
|
func getScript(b language.Base) language.Script {
|
||
|
tag, _ := language.Raw.Compose(b)
|
||
|
scr, _ := tag.Script()
|
||
|
return scr
|
||
|
}
|