package phrasebook import ( "io/fs" "strings" "testing" "testing/fstest" "git.golem15.com/golem15/summercms/backpack" "git.golem15.com/golem15/summercms/compass" ) func TestTranslationSmoke(t *testing.T) { t.Parallel() fsys := fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("greeting: Hello\nlabels:\n pluginName: Hello plugin\n")}, "lang/pl/lang.yaml": {Data: []byte("greeting: Cześć\nlabels:\n pluginName: Wtyczka Hello\n")}, } cat := NewCatalog() if err := cat.Load("golem15.hello", fsys); err != nil { t.Fatalf("Load: %v", err) } tr := NewTranslator(cat, Options{}) if got := tr.GetIn("en", "golem15.hello::lang.labels.pluginName", nil); got != "Hello plugin" { t.Fatalf("en nested = %q", got) } if got := tr.GetIn("pl", "golem15.hello::lang.labels.pluginName", nil); got != "Wtyczka Hello" { t.Fatalf("pl nested = %q", got) } if got := tr.GetIn("en", "golem15.hello::lang.greeting", nil); got != "Hello" { t.Fatalf("en greeting = %q", got) } if got := tr.GetIn("pl", "golem15.hello::lang.greeting", nil); got != "Cześć" { t.Fatalf("pl greeting = %q", got) } t.Run("malformed path", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en.yaml": {Data: []byte("greeting: Hello\n")}, }) if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "malformed path") { t.Fatalf("malformed path error = %v", err) } }) t.Run("non-string leaf", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("count: 1\n")}, }) if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "lang/en/lang.yaml") || !strings.Contains(err.Error(), "non-string leaf") { t.Fatalf("non-string leaf error = %v", err) } }) t.Run("duplicate key", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("labels:\n name: a\nlabels.name: b\n")}, }) if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "duplicate key") { t.Fatalf("duplicate key error = %v", err) } }) t.Run("duplicate namespace owner", func(t *testing.T) { cat := NewCatalog() fsys := fstest.MapFS{"lang/en/lang.yaml": {Data: []byte("greeting: Hello\n")}} if err := cat.Load("golem15.hello", fsys); err != nil { t.Fatalf("first Load: %v", err) } err := cat.Load("golem15.hello", fsys) if err == nil || !strings.Contains(err.Error(), "golem15.hello") || !strings.Contains(err.Error(), "duplicate namespace owner") { t.Fatalf("duplicate namespace error = %v", err) } }) t.Run("activate publishes translator", func(t *testing.T) { app := backpack.New(&compass.Config{}) p := &langPlugin{id: "golem15.hello", fsys: fsys} if err := Activate(app, []*langPlugin{p}); err != nil { t.Fatalf("Activate: %v", err) } tr, ok := app.Lookup[*Translator]() if !ok { t.Fatal("translator not published") } if got := tr.GetIn("pl", "golem15.hello::lang.greeting", nil); got != "Cześć" { t.Fatalf("activated pl = %q", got) } }) } type langPlugin struct { id string fsys fstest.MapFS } func (p *langPlugin) ID() string { return p.id } func (p *langPlugin) Requires() []string { return nil } func (p *langPlugin) Register(*backpack.App) error { return nil } func (p *langPlugin) Boot(*backpack.App) error { return nil } func (p *langPlugin) LangFS() fs.FS { return p.fsys } func TestPluralSmoke(t *testing.T) { t.Parallel() fsys := fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("" + "albums:\n one: \":count album\"\n other: \":count albums\"\n" + "items: \"{0} No items|{1} One item|[2,*] :count items\"\n" + "helloName: \"hi :name :Name :NAME\"\n" + "raw:\n other: \"{{.Count}} bottles\"\n")}, "lang/pl/lang.yaml": {Data: []byte("" + "albums:\n one: \":count album\"\n few: \":count albumy\"\n many: \":count albumów\"\n other: \":count albumu\"\n" + "posts: \":count wpis|:count wpisy|:count wpisów|:count wpisu\"\n")}, } cat := NewCatalog() if err := cat.Load("golem15.hello", fsys); err != nil { t.Fatalf("Load: %v", err) } tr := NewTranslator(cat, Options{}) pl := map[any]string{1: "1 album", 2: "2 albumy", 5: "5 albumów", 22: "22 albumy", 0: "0 albumów", 1.5: "1.5 albumu"} for n, want := range pl { if got := tr.ChoiceIn("pl", "golem15.hello::lang.albums", n, nil); got != want { t.Fatalf("pl albums %v = %q, want %q", n, got, want) } } en := map[any]string{1: "1 album", 2: "2 albums", 0: "0 albums", 1.5: "1.5 albums"} for n, want := range en { if got := tr.ChoiceIn("en", "golem15.hello::lang.albums", n, nil); got != want { t.Fatalf("en albums %v = %q, want %q", n, got, want) } } if got := tr.ChoiceIn("pl", "golem15.hello::lang.posts", 1, nil); got != "1 wpis" { t.Fatalf("pl posts 1 = %q", got) } if got := tr.ChoiceIn("pl", "golem15.hello::lang.posts", 2, nil); got != "2 wpisy" { t.Fatalf("pl posts 2 = %q", got) } if got := tr.ChoiceIn("pl", "golem15.hello::lang.posts", 5, nil); got != "5 wpisów" { t.Fatalf("pl posts 5 = %q", got) } if got := tr.ChoiceIn("pl", "golem15.hello::lang.posts", 22, nil); got != "22 wpisy" { t.Fatalf("pl posts 22 = %q", got) } if got := tr.ChoiceIn("en", "golem15.hello::lang.items", 0, nil); got != "No items" { t.Fatalf("items 0 = %q", got) } if got := tr.ChoiceIn("en", "golem15.hello::lang.items", 1, nil); got != "One item" { t.Fatalf("items 1 = %q", got) } if got := tr.ChoiceIn("en", "golem15.hello::lang.items", 5, nil); got != "5 items" { t.Fatalf("items 5 = %q", got) } if got := tr.ChoiceIn("en", "golem15.hello::lang.helloName", 1, map[string]string{"name": "alice"}); got != "hi alice Alice ALICE" { t.Fatalf("params = %q", got) } if got := tr.ChoiceIn("en", "golem15.hello::lang.raw", 2, nil); got != "{{.Count}} bottles" { t.Fatalf("go-i18n must not evaluate YAML as a template, got %q", got) } t.Run("invalid category for locale", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("albums:\n few: x\n other: y\n")}, }) if err == nil || !strings.Contains(err.Error(), "invalid plural category") { t.Fatalf("want invalid category, got %v", err) } }) t.Run("missing other", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("albums:\n one: x\n")}, }) if err == nil || !strings.Contains(err.Error(), "requires other") { t.Fatalf("want missing other, got %v", err) } }) t.Run("malformed pipe", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("items: \"{x} nope|{1} one\"\n")}, }) if err == nil || !strings.Contains(err.Error(), "malformed pipe") { t.Fatalf("want malformed pipe, got %v", err) } }) t.Run("incomplete pipe", func(t *testing.T) { err := NewCatalog().Load("golem15.hello", fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("items: \":count post|:count posts|:count extra\"\n")}, }) if err == nil || !strings.Contains(err.Error(), "incomplete pipe") { t.Fatalf("want incomplete pipe, got %v", err) } }) }