-
Notifications
You must be signed in to change notification settings - Fork 20
/
sort.go
120 lines (97 loc) · 3.61 KB
/
sort.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package i18n
import (
"bytes"
"sort"
"strings"
"golang.org/x/text/collate"
"golang.org/x/text/language"
"golang.org/x/text/unicode/norm"
)
// i18nSorted is a stuct which satisfies the sort interface for sorting
// strings alphabetically according to locale
type i18nSorter struct {
toBeSorted []interface{}
getComparisonValueFunc func(interface{}) string
lessThanFunc func(i, j int) bool
collator *collate.Collator
}
// Len satisfied the sort interface
func (s *i18nSorter) Len() int {
return len(s.toBeSorted)
}
// Swap satisfied the sort interface
func (s *i18nSorter) Swap(i, j int) {
s.toBeSorted[i], s.toBeSorted[j] = s.toBeSorted[j], s.toBeSorted[i]
}
// Less satisfied the sort interface. It uses a collator if available to do a
// string comparison. Otherwise is uses unicode normalization.
func (s *i18nSorter) Less(i, j int) bool {
iValue := strings.ToLower(s.getComparisonValueFunc(s.toBeSorted[i]))
jValue := strings.ToLower(s.getComparisonValueFunc(s.toBeSorted[j]))
// if it's a local sort, use the collator
if s.collator != nil {
return s.collator.CompareString(iValue, jValue) == -1
}
// for universal sorts, normalize the unicode to sort
normalizer := norm.NFKD
iValue = normalizer.String(iValue)
jValue = normalizer.String(jValue)
return bytes.Compare([]byte(iValue), []byte(jValue)) == -1
}
// SortUniversal sorts a generic slice alphabetically in such a way that it
// should be mostly correct for most locales. It should be used in the following
// 2 cases:
// - As a fallback for SortLocale, when a collator for a specific locale
// cannot be found
// - When a locale is not available, or a sorting needs to be done in a
// locale-agnostic way
// It uses unicode normalization.
// The func argument tells this function what string value to do the comparisons
// on.
func SortUniversal(toBeSorted []interface{}, getComparisonValueFunction func(interface{}) string) {
sorter := &i18nSorter{
toBeSorted: toBeSorted,
getComparisonValueFunc: getComparisonValueFunction,
}
sort.Sort(sorter)
}
// SortLocal sorts a generic slice alphabetically for a specific locale. It
// uses collation information if available for the specific locale requested.
// It falls back to SortUniversal otherwise. The func argument tells this
// function what string value to do the comparisons on.
func SortLocal(locale string, toBeSorted []interface{}, getComparisonValueFunction func(interface{}) string) {
if locale == "" {
SortUniversal(toBeSorted, getComparisonValueFunction)
return
}
collator := getCollator(locale)
if collator == nil {
SortUniversal(toBeSorted, getComparisonValueFunction)
return
}
sorter := &i18nSorter{
toBeSorted: toBeSorted,
getComparisonValueFunc: getComparisonValueFunction,
collator: collator,
}
sort.Sort(sorter)
}
// getCollator returns a collate package Collator pointer. This can result in a
// panic, so this function must recover from that if it happens.
func getCollator(locale string) *collate.Collator {
defer func() {
recover()
}()
tag := language.Make(locale)
if tag == language.Und {
return nil
}
return collate.New(tag)
}
// Sort sorts a generic slice alphabetically for this translator's locale. It
// uses collation information if available for the specific locale requested.
// It falls back to SortUniversal otherwise. The func argument tells this
// function what string value to do the comparisons on.
func (t *Translator) Sort(toBeSorted []interface{}, getComparisonValueFunction func(interface{}) string) {
SortLocal(t.locale, toBeSorted, getComparisonValueFunction)
}