2017-11-20 06:10:04 +01:00
|
|
|
// 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"
|
2017-12-16 20:25:18 +01:00
|
|
|
"path"
|
2017-11-20 06:10:04 +01:00
|
|
|
"reflect"
|
2017-12-16 20:25:18 +01:00
|
|
|
"strings"
|
2017-11-20 06:10:04 +01:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/text/internal/catmsg"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
)
|
|
|
|
|
|
|
|
type entry struct {
|
|
|
|
tag, key string
|
|
|
|
msg interface{}
|
|
|
|
}
|
|
|
|
|
2017-12-16 20:25:18 +01:00
|
|
|
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{{
|
2017-11-20 06:10:04 +01:00
|
|
|
desc: "empty catalog",
|
|
|
|
lookup: []entry{
|
|
|
|
{"en", "key", ""},
|
|
|
|
{"en", "", ""},
|
|
|
|
{"nl", "", ""},
|
|
|
|
},
|
2017-12-16 20:25:18 +01:00
|
|
|
match: []string{
|
|
|
|
"gr -> und",
|
|
|
|
"en-US -> und",
|
|
|
|
"af -> und",
|
|
|
|
},
|
|
|
|
tags: nil, // not an empty list.
|
2017-11-20 06:10:04 +01:00
|
|
|
}, {
|
|
|
|
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!"},
|
|
|
|
},
|
2017-12-16 20:25:18 +01:00
|
|
|
match: []string{
|
|
|
|
"gr -> en",
|
|
|
|
"en-US -> en",
|
|
|
|
},
|
|
|
|
tags: langs("en"),
|
2017-11-20 06:10:04 +01:00
|
|
|
}, {
|
|
|
|
desc: "hierarchical languages",
|
|
|
|
cat: []entry{
|
|
|
|
{"en", "hello", "Hello!"},
|
|
|
|
{"en-GB", "hello", "Hellø!"},
|
|
|
|
{"en-US", "hello", "Howdy!"},
|
|
|
|
{"en", "greetings", "Greetings!"},
|
2017-12-16 20:25:18 +01:00
|
|
|
{"gsw", "hello", "Grüetzi!"},
|
2017-11-20 06:10:04 +01:00
|
|
|
},
|
|
|
|
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!"},
|
|
|
|
},
|
2017-12-16 20:25:18 +01:00
|
|
|
fallback: "gsw",
|
|
|
|
match: []string{
|
|
|
|
"gr -> gsw",
|
|
|
|
"en-US -> en-US",
|
|
|
|
},
|
|
|
|
tags: langs("gsw, en, en-GB, en-US"),
|
2017-11-20 06:10:04 +01:00
|
|
|
}, {
|
|
|
|
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."},
|
|
|
|
},
|
2017-12-16 20:25:18 +01:00
|
|
|
tags: langs("en"),
|
2017-11-20 06:10:04 +01:00
|
|
|
}, {
|
|
|
|
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!"},
|
2017-12-16 20:25:18 +01:00
|
|
|
},
|
|
|
|
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
|
2017-11-20 06:10:04 +01:00
|
|
|
|
2017-12-16 20:25:18 +01:00
|
|
|
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 {
|
2017-11-20 06:10:04 +01:00
|
|
|
tag := language.MustParse(e.tag)
|
|
|
|
switch msg := e.msg.(type) {
|
|
|
|
case string:
|
2017-12-16 20:25:18 +01:00
|
|
|
|
2017-11-20 06:10:04 +01:00
|
|
|
cat.SetString(tag, e.key, msg)
|
|
|
|
case Message:
|
|
|
|
cat.Set(tag, e.key, msg)
|
|
|
|
case []Message:
|
|
|
|
cat.Set(tag, e.key, msg...)
|
|
|
|
}
|
|
|
|
}
|
2017-12-16 20:25:18 +01:00
|
|
|
setMacros(cat)
|
|
|
|
return cat
|
2017-11-20 06:10:04 +01:00
|
|
|
}
|
|
|
|
|
2017-12-16 20:25:18 +01:00
|
|
|
type dictionary map[string]string
|
2017-11-20 06:10:04 +01:00
|
|
|
|
2017-12-16 20:25:18 +01:00
|
|
|
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 = firstInSequence(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
|
2017-11-20 06:10:04 +01:00
|
|
|
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 {
|
2017-12-16 20:25:18 +01:00
|
|
|
t.Run(path.Join(tc.desc, e.tag, e.key), func(t *testing.T) {
|
2017-11-20 06:10:04 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2017-12-16 20:25:18 +01:00
|
|
|
}
|
2017-11-20 06:10:04 +01:00
|
|
|
}
|
2017-12-16 20:25:18 +01:00
|
|
|
t.Run("Builder", func(t *testing.T) { test(t, initBuilder) })
|
|
|
|
t.Run("Catalog", func(t *testing.T) { test(t, initCatalog) })
|
2017-11-20 06:10:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|