-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmlang.go
161 lines (133 loc) · 3.33 KB
/
mlang.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
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
package mlang
import (
"encoding/json"
"fmt"
"io"
"github.com/morikuni/failure/v2"
"golang.org/x/exp/slog"
"github.com/morikuni/go-mlang/internal"
)
// Language can be any comparable types.
// Basically, golang.org/x/text/language.Tag is used, but you can use any your original types as well.
type Language = any
type randomLanguageType struct{}
var (
randomLanguage = randomLanguageType{}
defaultLanguage Language = randomLanguage
)
func SetDefaultLanguage(lang Language) {
defaultLanguage = lang
}
type Message interface {
failure.Field
isMessage()
Get(lang Language) (string, bool)
MustGet(lang Language) string
}
// Dict is the set of messages for each language. The type M is usually string.
// Template is used for dynamical messages that is evaluated at a timing
// language specified.
type Dict[M string | Template] map[Language]M
var (
_ failure.ErrorFormatter = Dict[string]{}
_ slog.LogValuer = Dict[string]{}
_ json.Marshaler = Dict[string]{}
_ fmt.Stringer = Dict[string]{}
)
func (d Dict[M]) isMessage() {}
func (d Dict[M]) Get(lang Language) (string, bool) {
if msg, ok := d[lang]; ok {
return eval(msg, lang)
}
return "", false
}
func (d Dict[M]) MustGet(lang Language) string {
if msg, ok := d[lang]; ok {
return mustEval(msg, lang)
}
if msg, ok := d[defaultLanguage]; ok {
return mustEval(msg, lang)
}
// fallback to random language
for l, msg := range d {
return mustEval(msg, l) // use `l`, not `lang` to ensure using the same language.
}
panic("mlang.Dict: empty")
}
func (d Dict[M]) SetErrorField(field failure.FieldSetter) {
field.Set(internal.FailureKey, Message(d))
}
// FormatError implements failure.ErrorFormatter.
func (d Dict[M]) FormatError(w failure.ErrorWriter) {
_, _ = io.WriteString(w, d.String()) // Randomly picked language is used.
}
// LogValue implements slog.LogValuer.
func (d Dict[M]) LogValue() slog.Value {
return slog.StringValue(d.String())
}
// MarshalJSON implements json.Marshaler.
func (d Dict[M]) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d Dict[M]) String() string {
return d.MustGet(defaultLanguage) // Randomly picked language is used.
}
func eval(msg any, lang Language) (string, bool) {
switch m := msg.(type) {
case Template:
return m.eval(lang)
case string:
return m, true
default:
panic("unreachable")
}
}
func mustEval(msg any, lang Language) string {
switch m := msg.(type) {
case Template:
return m.mustEval(lang)
case string:
return m
default:
panic("unreachable")
}
}
// Template is a message template.
type Template struct {
format string
args []any
}
func NewTemplate(format string, args ...any) Template {
return Template{
format: format,
args: args,
}
}
func (tmp Template) eval(lang Language) (string, bool) {
args := make([]any, len(tmp.args))
for i, arg := range tmp.args {
switch arg := arg.(type) {
case Message:
var ok bool
args[i], ok = arg.Get(lang)
if !ok {
return "", false
}
default:
args[i] = arg
}
}
return fmt.Sprintf(tmp.format, args...), true
}
func (tmp Template) mustEval(lang Language) string {
args := make([]any, len(tmp.args))
for i, arg := range tmp.args {
switch arg := arg.(type) {
case Message:
args[i] = arg.MustGet(lang)
default:
args[i] = arg
}
}
return fmt.Sprintf(tmp.format, args...)
}