1
2
3
4
5 package catalog
6
7 import (
8 "bytes"
9 "path"
10 "reflect"
11 "strings"
12 "testing"
13
14 "golang.org/x/text/internal/catmsg"
15 "golang.org/x/text/language"
16 )
17
18 type entry struct {
19 tag, key string
20 msg interface{}
21 }
22
23 func langs(s string) []language.Tag {
24 t, _, _ := language.ParseAcceptLanguage(s)
25 return t
26 }
27
28 type testCase struct {
29 desc string
30 cat []entry
31 lookup []entry
32 fallback string
33 match []string
34 tags []language.Tag
35 }
36
37 var testCases = []testCase{{
38 desc: "empty catalog",
39 lookup: []entry{
40 {"en", "key", ""},
41 {"en", "", ""},
42 {"nl", "", ""},
43 },
44 match: []string{
45 "gr -> und",
46 "en-US -> und",
47 "af -> und",
48 },
49 tags: nil,
50 }, {
51 desc: "one entry",
52 cat: []entry{
53 {"en", "hello", "Hello!"},
54 },
55 lookup: []entry{
56 {"und", "hello", ""},
57 {"nl", "hello", ""},
58 {"en", "hello", "Hello!"},
59 {"en-US", "hello", "Hello!"},
60 {"en-GB", "hello", "Hello!"},
61 {"en-oxendict", "hello", "Hello!"},
62 {"en-oxendict-u-ms-metric", "hello", "Hello!"},
63 },
64 match: []string{
65 "gr -> en",
66 "en-US -> en-u-rg-uszzzz",
67 },
68 tags: langs("en"),
69 }, {
70 desc: "hierarchical languages",
71 cat: []entry{
72 {"en", "hello", "Hello!"},
73 {"en-GB", "hello", "Hellø!"},
74 {"en-US", "hello", "Howdy!"},
75 {"en", "greetings", "Greetings!"},
76 {"gsw", "hello", "Grüetzi!"},
77 },
78 lookup: []entry{
79 {"und", "hello", ""},
80 {"nl", "hello", ""},
81 {"en", "hello", "Hello!"},
82 {"en-US", "hello", "Howdy!"},
83 {"en-GB", "hello", "Hellø!"},
84 {"en-oxendict", "hello", "Hello!"},
85 {"en-US-oxendict-u-ms-metric", "hello", "Howdy!"},
86
87 {"und", "greetings", ""},
88 {"nl", "greetings", ""},
89 {"en", "greetings", "Greetings!"},
90 {"en-US", "greetings", "Greetings!"},
91 {"en-GB", "greetings", "Greetings!"},
92 {"en-oxendict", "greetings", "Greetings!"},
93 {"en-US-oxendict-u-ms-metric", "greetings", "Greetings!"},
94 },
95 fallback: "gsw",
96 match: []string{
97 "gr -> gsw",
98 "en-US -> en-US",
99 },
100 tags: langs("gsw, en, en-GB, en-US"),
101 }, {
102 desc: "variables",
103 cat: []entry{
104 {"en", "hello %s", []Message{
105 Var("person", String("Jane")),
106 String("Hello ${person}!"),
107 }},
108 {"en", "hello error", []Message{
109 Var("person", String("Jane")),
110 noMatchMessage{},
111 String("Hello ${person."),
112 }},
113 {"en", "fallback to var value", []Message{
114 Var("you", noMatchMessage{}, noMatchMessage{}),
115 String("Hello ${you}."),
116 }},
117 {"en", "scopes", []Message{
118 Var("person1", String("Mark")),
119 Var("person2", String("Jane")),
120 Var("couple",
121 Var("person1", String("Joe")),
122 String("${person1} and ${person2}")),
123 String("Hello ${couple}."),
124 }},
125 {"en", "missing var", String("Hello ${missing}.")},
126 },
127 lookup: []entry{
128 {"en", "hello %s", "Hello Jane!"},
129 {"en", "hello error", "Hello $!(MISSINGBRACE)"},
130 {"en", "fallback to var value", "Hello you."},
131 {"en", "scopes", "Hello Joe and Jane."},
132 {"en", "missing var", "Hello missing."},
133 },
134 tags: langs("en"),
135 }, {
136 desc: "macros",
137 cat: []entry{
138 {"en", "macro1", String("Hello ${macro1(1)}.")},
139 {"en", "macro2", String("Hello ${ macro1(2) }!")},
140 {"en", "macroWS", String("Hello ${ macro1( 2 ) }!")},
141 {"en", "missing", String("Hello ${ missing(1 }.")},
142 {"en", "badnum", String("Hello ${ badnum(1b) }.")},
143 {"en", "undefined", String("Hello ${ undefined(1) }.")},
144 {"en", "macroU", String("Hello ${ macroU(2) }!")},
145 },
146 lookup: []entry{
147 {"en", "macro1", "Hello Joe."},
148 {"en", "macro2", "Hello Joe!"},
149 {"en-US", "macroWS", "Hello Joe!"},
150 {"en-NL", "missing", "Hello $!(MISSINGPAREN)."},
151 {"en", "badnum", "Hello $!(BADNUM)."},
152 {"en", "undefined", "Hello undefined."},
153 {"en", "macroU", "Hello macroU!"},
154 },
155 tags: langs("en"),
156 }}
157
158 func setMacros(b *Builder) {
159 b.SetMacro(language.English, "macro1", String("Joe"))
160 b.SetMacro(language.Und, "macro2", String("${macro1(1)}"))
161 b.SetMacro(language.English, "macroU", noMatchMessage{})
162 }
163
164 type buildFunc func(t *testing.T, tc testCase) Catalog
165
166 func initBuilder(t *testing.T, tc testCase) Catalog {
167 options := []Option{}
168 if tc.fallback != "" {
169 options = append(options, Fallback(language.MustParse(tc.fallback)))
170 }
171 cat := NewBuilder(options...)
172 for _, e := range tc.cat {
173 tag := language.MustParse(e.tag)
174 switch msg := e.msg.(type) {
175 case string:
176
177 cat.SetString(tag, e.key, msg)
178 case Message:
179 cat.Set(tag, e.key, msg)
180 case []Message:
181 cat.Set(tag, e.key, msg...)
182 }
183 }
184 setMacros(cat)
185 return cat
186 }
187
188 type dictionary map[string]string
189
190 func (d dictionary) Lookup(key string) (data string, ok bool) {
191 data, ok = d[key]
192 return data, ok
193 }
194
195 func initCatalog(t *testing.T, tc testCase) Catalog {
196 m := map[string]Dictionary{}
197 for _, e := range tc.cat {
198 m[e.tag] = dictionary{}
199 }
200 for _, e := range tc.cat {
201 var msg Message
202 switch x := e.msg.(type) {
203 case string:
204 msg = String(x)
205 case Message:
206 msg = x
207 case []Message:
208 msg = firstInSequence(x)
209 }
210 data, _ := catmsg.Compile(language.MustParse(e.tag), nil, msg)
211 m[e.tag].(dictionary)[e.key] = data
212 }
213 options := []Option{}
214 if tc.fallback != "" {
215 options = append(options, Fallback(language.MustParse(tc.fallback)))
216 }
217 c, err := NewFromMap(m, options...)
218 if err != nil {
219 t.Fatal(err)
220 }
221
222 b := NewBuilder()
223 setMacros(b)
224 c.(*catalog).macros.index = b.macros.index
225 return c
226 }
227
228 func TestMatcher(t *testing.T) {
229 test := func(t *testing.T, init buildFunc) {
230 for _, tc := range testCases {
231 for _, s := range tc.match {
232 a := strings.Split(s, "->")
233 t.Run(path.Join(tc.desc, a[0]), func(t *testing.T) {
234 cat := init(t, tc)
235 got, _ := language.MatchStrings(cat.Matcher(), a[0])
236 want := language.MustParse(strings.TrimSpace(a[1]))
237 if got != want {
238 t.Errorf("got %q; want %q", got, want)
239 }
240 })
241 }
242 }
243 }
244 t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
245 t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
246 }
247
248 func TestCatalog(t *testing.T) {
249 test := func(t *testing.T, init buildFunc) {
250 for _, tc := range testCases {
251 cat := init(t, tc)
252 wantTags := tc.tags
253 if got := cat.Languages(); !reflect.DeepEqual(got, wantTags) {
254 t.Errorf("%s:Languages: got %v; want %v", tc.desc, got, wantTags)
255 }
256
257 for _, e := range tc.lookup {
258 t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) {
259 tag := language.MustParse(e.tag)
260 buf := testRenderer{}
261 ctx := cat.Context(tag, &buf)
262 want := e.msg.(string)
263 err := ctx.Execute(e.key)
264 gotFound := err != ErrNotFound
265 wantFound := want != ""
266 if gotFound != wantFound {
267 t.Fatalf("err: got %v (%v); want %v", gotFound, err, wantFound)
268 }
269 if got := buf.buf.String(); got != want {
270 t.Errorf("Lookup:\ngot %q\nwant %q", got, want)
271 }
272 })
273 }
274 }
275 }
276 t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
277 t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
278 }
279
280 type testRenderer struct {
281 buf bytes.Buffer
282 }
283
284 func (f *testRenderer) Arg(i int) interface{} { return nil }
285 func (f *testRenderer) Render(s string) { f.buf.WriteString(s) }
286
287 var msgNoMatch = catmsg.Register("no match", func(d *catmsg.Decoder) bool {
288 return false
289 })
290
291 type noMatchMessage struct{}
292
293 func (noMatchMessage) Compile(e *catmsg.Encoder) error {
294 e.EncodeMessageType(msgNoMatch)
295 return catmsg.ErrIncomplete
296 }
297
View as plain text