golang.org/x/text@v0.14.0/message/catalog/catalog_test.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 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, // not an empty list. 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{}, // trigger sequence path. 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 // TODO: implement macros for fixed catalogs. 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 // no match 289 }) 290 291 type noMatchMessage struct{} 292 293 func (noMatchMessage) Compile(e *catmsg.Encoder) error { 294 e.EncodeMessageType(msgNoMatch) 295 return catmsg.ErrIncomplete 296 }