-
-
Notifications
You must be signed in to change notification settings - Fork 260
Expand file tree
/
Copy pathlanguage.go
More file actions
executable file
·180 lines (156 loc) · 3.86 KB
/
language.go
File metadata and controls
executable file
·180 lines (156 loc) · 3.86 KB
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package carbon
import (
"embed"
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
)
//go:embed lang
var fs embed.FS
// localeCache caches parsed locale resources to avoid repeated file loading and JSON parsing
var localeCache sync.Map
// cachedResources holds the cached resources for each language.
type cachedResources struct {
once sync.Once
resources map[string]string
err error
}
// Language defines a Language struct.
type Language struct {
dir string
locale string
resources map[string]string
Error error
rw *sync.RWMutex
}
// NewLanguage returns a new Language instance.
func NewLanguage() *Language {
return &Language{
dir: "lang",
locale: DefaultLocale,
resources: make(map[string]string),
rw: new(sync.RWMutex),
}
}
// Copy returns a new copy of the current Language instance
func (lang *Language) Copy() *Language {
if lang == nil {
return nil
}
newLang := &Language{
dir: lang.dir,
locale: lang.locale,
Error: lang.Error,
rw: new(sync.RWMutex),
}
if lang.resources == nil {
return newLang
}
lang.rw.RLock()
resources := lang.resources
lang.rw.RUnlock()
newLang.resources = make(map[string]string, len(resources))
for k, v := range resources {
newLang.resources[k] = v
}
return newLang
}
// SetLocale sets language locale.
func (lang *Language) SetLocale(locale string) *Language {
if lang == nil || lang.Error != nil {
return lang
}
if locale == "" {
lang.Error = ErrEmptyLocale()
return lang
}
// Early return if locale hasn't changed and resources are already loaded
lang.rw.RLock()
if lang.locale == locale && lang.resources != nil && len(lang.resources) > 0 {
lang.rw.RUnlock()
return lang
}
lang.rw.RUnlock()
fileName := fmt.Sprintf("%s/%s.json", lang.dir, locale)
load, _ := localeCache.LoadOrStore(fileName, new(cachedResources))
entry := load.(*cachedResources)
entry.once.Do(func() {
bs, err := fs.ReadFile(fileName)
if err != nil {
entry.err = fmt.Errorf("%w: %w", ErrNotExistLocale(fileName), err)
return
}
var resources map[string]string
_ = json.Unmarshal(bs, &resources)
entry.resources = resources
})
if entry.err != nil {
lang.Error = entry.err
return lang
}
// Create a copy of the cached resources to avoid modifying the cache
// Pre-allocate with exact capacity for better memory efficiency
newResources := make(map[string]string, len(entry.resources))
for k, v := range entry.resources {
newResources[k] = v
}
lang.rw.Lock()
lang.locale = locale
lang.resources = newResources
lang.rw.Unlock()
return lang
}
// SetResources sets language resources.
func (lang *Language) SetResources(resources map[string]string) *Language {
if lang == nil || lang.Error != nil {
return lang
}
if len(resources) == 0 {
lang.Error = ErrEmptyResources()
return lang
}
lang.rw.Lock()
defer lang.rw.Unlock()
for k, v := range resources {
lang.resources[k] = v
}
return lang
}
// returns a translated string.
func (lang *Language) translate(unit string, value int64) string {
if lang == nil || lang.resources == nil {
return ""
}
lang.rw.RLock()
resources := lang.resources
lang.rw.RUnlock()
// If resources is empty, set default locale and retry
if len(resources) == 0 {
lang.SetLocale(DefaultLocale)
lang.rw.RLock()
resources = lang.resources
lang.rw.RUnlock()
}
if resources == nil || len(resources) == 0 {
return ""
}
resource, exists := resources[unit]
if !exists {
return ""
}
slice := strings.Split(resource, "|")
number := getAbsValue(value)
str := strconv.FormatInt(value, 10)
if len(slice) == 1 {
return strings.Replace(slice[0], "%d", str, 1)
}
if int64(len(slice)) <= number {
return strings.Replace(slice[len(slice)-1], "%d", str, 1)
}
if !strings.Contains(slice[number-1], "%d") && value < 0 {
return "-" + slice[number-1]
}
return strings.Replace(slice[number-1], "%d", str, 1)
}