| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package catalog | |
| import ( | |
| "bytes" | |
| "path" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| "golang.org/x/text/internal/catmsg" | |
| "golang.org/x/text/language" | |
| ) | |
| type entry struct { | |
| tag, key string | |
| msg interface{} | |
| } | |
| func langs(s string) []language.Tag { | |
| t, _, _ := language.ParseAcceptLanguage(s) | |
| return t | |
| } | |
| type testCase struct { | |
| desc string | |
| cat []entry | |
| lookup []entry | |
| fallback string | |
| match []string | |
| tags []language.Tag | |
| } | |
| var testCases = []testCase{{ | |
| desc: "empty catalog", | |
| lookup: []entry{ | |
| {"en", "key", ""}, | |
| {"en", "", ""}, | |
| {"nl", "", ""}, | |
| }, | |
| match: []string{ | |
| "gr -> und", | |
| "en-US -> und", | |
| "af -> und", | |
| }, | |
| tags: nil, // not an empty list. | |
| }, { | |
| desc: "one entry", | |
| cat: []entry{ | |
| {"en", "hello", "Hello!"}, | |
| }, | |
| lookup: []entry{ | |
| {"und", "hello", ""}, | |
| {"nl", "hello", ""}, | |
| {"en", "hello", "Hello!"}, | |
| {"en-US", "hello", "Hello!"}, | |
| {"en-GB", "hello", "Hello!"}, | |
| {"en-oxendict", "hello", "Hello!"}, | |
| {"en-oxendict-u-ms-metric", "hello", "Hello!"}, | |
| }, | |
| match: []string{ | |
| "gr -> en", | |
| "en-US -> en-u-rg-uszzzz", | |
| }, | |
| tags: langs("en"), | |
| }, { | |
| desc: "hierarchical languages", | |
| cat: []entry{ | |
| {"en", "hello", "Hello!"}, | |
| {"en-GB", "hello", "Hellø!"}, | |
| {"en-US", "hello", "Howdy!"}, | |
| {"en", "greetings", "Greetings!"}, | |
| {"gsw", "hello", "Grüetzi!"}, | |
| }, | |
| lookup: []entry{ | |
| {"und", "hello", ""}, | |
| {"nl", "hello", ""}, | |
| {"en", "hello", "Hello!"}, | |
| {"en-US", "hello", "Howdy!"}, | |
| {"en-GB", "hello", "Hellø!"}, | |
| {"en-oxendict", "hello", "Hello!"}, | |
| {"en-US-oxendict-u-ms-metric", "hello", "Howdy!"}, | |
| {"und", "greetings", ""}, | |
| {"nl", "greetings", ""}, | |
| {"en", "greetings", "Greetings!"}, | |
| {"en-US", "greetings", "Greetings!"}, | |
| {"en-GB", "greetings", "Greetings!"}, | |
| {"en-oxendict", "greetings", "Greetings!"}, | |
| {"en-US-oxendict-u-ms-metric", "greetings", "Greetings!"}, | |
| }, | |
| fallback: "gsw", | |
| match: []string{ | |
| "gr -> gsw", | |
| "en-US -> en-US", | |
| }, | |
| tags: langs("gsw, en, en-GB, en-US"), | |
| }, { | |
| desc: "variables", | |
| cat: []entry{ | |
| {"en", "hello %s", []Message{ | |
| Var("person", String("Jane")), | |
| String("Hello ${person}!"), | |
| }}, | |
| {"en", "hello error", []Message{ | |
| Var("person", String("Jane")), | |
| noMatchMessage{}, // trigger sequence path. | |
| String("Hello ${person."), | |
| }}, | |
| {"en", "fallback to var value", []Message{ | |
| Var("you", noMatchMessage{}, noMatchMessage{}), | |
| String("Hello ${you}."), | |
| }}, | |
| {"en", "scopes", []Message{ | |
| Var("person1", String("Mark")), | |
| Var("person2", String("Jane")), | |
| Var("couple", | |
| Var("person1", String("Joe")), | |
| String("${person1} and ${person2}")), | |
| String("Hello ${couple}."), | |
| }}, | |
| {"en", "missing var", String("Hello ${missing}.")}, | |
| }, | |
| lookup: []entry{ | |
| {"en", "hello %s", "Hello Jane!"}, | |
| {"en", "hello error", "Hello $!(MISSINGBRACE)"}, | |
| {"en", "fallback to var value", "Hello you."}, | |
| {"en", "scopes", "Hello Joe and Jane."}, | |
| {"en", "missing var", "Hello missing."}, | |
| }, | |
| tags: langs("en"), | |
| }, { | |
| desc: "macros", | |
| cat: []entry{ | |
| {"en", "macro1", String("Hello ${macro1(1)}.")}, | |
| {"en", "macro2", String("Hello ${ macro1(2) }!")}, | |
| {"en", "macroWS", String("Hello ${ macro1( 2 ) }!")}, | |
| {"en", "missing", String("Hello ${ missing(1 }.")}, | |
| {"en", "badnum", String("Hello ${ badnum(1b) }.")}, | |
| {"en", "undefined", String("Hello ${ undefined(1) }.")}, | |
| {"en", "macroU", String("Hello ${ macroU(2) }!")}, | |
| }, | |
| lookup: []entry{ | |
| {"en", "macro1", "Hello Joe."}, | |
| {"en", "macro2", "Hello Joe!"}, | |
| {"en-US", "macroWS", "Hello Joe!"}, | |
| {"en-NL", "missing", "Hello $!(MISSINGPAREN)."}, | |
| {"en", "badnum", "Hello $!(BADNUM)."}, | |
| {"en", "undefined", "Hello undefined."}, | |
| {"en", "macroU", "Hello macroU!"}, | |
| }, | |
| tags: langs("en"), | |
| }} | |
| func setMacros(b *Builder) { | |
| b.SetMacro(language.English, "macro1", String("Joe")) | |
| b.SetMacro(language.Und, "macro2", String("${macro1(1)}")) | |
| b.SetMacro(language.English, "macroU", noMatchMessage{}) | |
| } | |
| type buildFunc func(t *testing.T, tc testCase) Catalog | |
| func initBuilder(t *testing.T, tc testCase) Catalog { | |
| options := []Option{} | |
| if tc.fallback != "" { | |
| options = append(options, Fallback(language.MustParse(tc.fallback))) | |
| } | |
| cat := NewBuilder(options...) | |
| for _, e := range tc.cat { | |
| tag := language.MustParse(e.tag) | |
| switch msg := e.msg.(type) { | |
| case string: | |
| cat.SetString(tag, e.key, msg) | |
| case Message: | |
| cat.Set(tag, e.key, msg) | |
| case []Message: | |
| cat.Set(tag, e.key, msg...) | |
| } | |
| } | |
| setMacros(cat) | |
| return cat | |
| } | |
| type dictionary map[string]string | |
| func (d dictionary) Lookup(key string) (data string, ok bool) { | |
| data, ok = d[key] | |
| return data, ok | |
| } | |
| func initCatalog(t *testing.T, tc testCase) Catalog { | |
| m := map[string]Dictionary{} | |
| for _, e := range tc.cat { | |
| m[e.tag] = dictionary{} | |
| } | |
| for _, e := range tc.cat { | |
| var msg Message | |
| switch x := e.msg.(type) { | |
| case string: | |
| msg = String(x) | |
| case Message: | |
| msg = x | |
| case []Message: | |
| msg = catmsg.FirstOf(x) | |
| } | |
| data, _ := catmsg.Compile(language.MustParse(e.tag), nil, msg) | |
| m[e.tag].(dictionary)[e.key] = data | |
| } | |
| options := []Option{} | |
| if tc.fallback != "" { | |
| options = append(options, Fallback(language.MustParse(tc.fallback))) | |
| } | |
| c, err := NewFromMap(m, options...) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| // TODO: implement macros for fixed catalogs. | |
| b := NewBuilder() | |
| setMacros(b) | |
| c.(*catalog).macros.index = b.macros.index | |
| return c | |
| } | |
| func TestMatcher(t *testing.T) { | |
| test := func(t *testing.T, init buildFunc) { | |
| for _, tc := range testCases { | |
| for _, s := range tc.match { | |
| a := strings.Split(s, "->") | |
| t.Run(path.Join(tc.desc, a[0]), func(t *testing.T) { | |
| cat := init(t, tc) | |
| got, _ := language.MatchStrings(cat.Matcher(), a[0]) | |
| want := language.MustParse(strings.TrimSpace(a[1])) | |
| if got != want { | |
| t.Errorf("got %q; want %q", got, want) | |
| } | |
| }) | |
| } | |
| } | |
| } | |
| t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) | |
| t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) | |
| } | |
| func TestCatalog(t *testing.T) { | |
| test := func(t *testing.T, init buildFunc) { | |
| for _, tc := range testCases { | |
| cat := init(t, tc) | |
| wantTags := tc.tags | |
| if got := cat.Languages(); !reflect.DeepEqual(got, wantTags) { | |
| t.Errorf("%s:Languages: got %v; want %v", tc.desc, got, wantTags) | |
| } | |
| for _, e := range tc.lookup { | |
| t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) { | |
| tag := language.MustParse(e.tag) | |
| buf := testRenderer{} | |
| ctx := cat.Context(tag, &buf) | |
| want := e.msg.(string) | |
| err := ctx.Execute(e.key) | |
| gotFound := err != ErrNotFound | |
| wantFound := want != "" | |
| if gotFound != wantFound { | |
| t.Fatalf("err: got %v (%v); want %v", gotFound, err, wantFound) | |
| } | |
| if got := buf.buf.String(); got != want { | |
| t.Errorf("Lookup:\ngot %q\nwant %q", got, want) | |
| } | |
| }) | |
| } | |
| } | |
| } | |
| t.Run("Builder", func(t *testing.T) { test(t, initBuilder) }) | |
| t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) }) | |
| } | |
| type testRenderer struct { | |
| buf bytes.Buffer | |
| } | |
| func (f *testRenderer) Arg(i int) interface{} { return nil } | |
| func (f *testRenderer) Render(s string) { f.buf.WriteString(s) } | |
| var msgNoMatch = catmsg.Register("no match", func(d *catmsg.Decoder) bool { | |
| return false // no match | |
| }) | |
| type noMatchMessage struct{} | |
| func (noMatchMessage) Compile(e *catmsg.Encoder) error { | |
| e.EncodeMessageType(msgNoMatch) | |
| return catmsg.ErrIncomplete | |
| } |