diff --git a/internal/library/duplicate_resolve.go b/internal/library/duplicate_resolve.go index bc39bc35..276de865 100644 --- a/internal/library/duplicate_resolve.go +++ b/internal/library/duplicate_resolve.go @@ -15,6 +15,7 @@ import ( "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" "git.fabledsword.com/bvandeusen/minstrel/internal/notifications" syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync" + "git.fabledsword.com/bvandeusen/minstrel/internal/words" ) // Duplicate resolver (M498 #5435, #5437; M502 #5517). @@ -177,7 +178,7 @@ func ResolveDuplicates( if res.Merged > 0 { notifyAdmins(ctx, notifications.KindDuplicatesResolved, notifications.Payload{ Count: int64(res.Merged), - Detail: fmt.Sprintf("Merged %d duplicate %s.", res.Merged, pluralWord(res.Merged, "copy", "copies")), + Detail: "Merged " + words.Count(res.Merged, "duplicate copy", "duplicate copies") + ".", }) } return res, nil @@ -351,13 +352,6 @@ func autoMerge( return nil } -func pluralWord(n int, one, many string) string { - if n == 1 { - return one - } - return many -} - // DuplicateResolveWorker runs a resolver pass every hour. type DuplicateResolveWorker struct { pool *pgxpool.Pool diff --git a/internal/notifications/render.go b/internal/notifications/render.go index 570cb897..993bfea7 100644 --- a/internal/notifications/render.go +++ b/internal/notifications/render.go @@ -3,6 +3,8 @@ package notifications import ( "encoding/json" "fmt" + + "git.fabledsword.com/bvandeusen/minstrel/internal/words" ) // Rendered is a notification as people read it. The server renders it once, @@ -138,13 +140,10 @@ func Render(kind Kind, payload []byte) Rendered { } func plural(n int64, one, many string) string { - if n == 1 { - return "1 " + one - } if n < 1 { // A coalesced kind always carries a count; this only guards a // malformed row from reading "0 tracks went missing". return "Some " + many } - return fmt.Sprintf("%d %s", n, many) + return words.Count(n, one, many) } diff --git a/internal/wanted/match.go b/internal/wanted/match.go index f98f7b1f..6fe0452d 100644 --- a/internal/wanted/match.go +++ b/internal/wanted/match.go @@ -13,6 +13,7 @@ import ( "git.fabledsword.com/bvandeusen/minstrel/internal/musicbrainz" "git.fabledsword.com/bvandeusen/minstrel/internal/release" "git.fabledsword.com/bvandeusen/minstrel/internal/tags" + "git.fabledsword.com/bvandeusen/minstrel/internal/words" ) // Matching a library album to its MusicBrainz release group (#5569). @@ -262,8 +263,8 @@ func (s *Service) matchAlbum(ctx context.Context, a dbq.ListAlbumsToMatchRow, tr if considered == 0 { return albumMatch{Detail: fmt.Sprintf("MusicBrainz has no album called %q by %s.", a.Title, a.ArtistName)}, nil } - detail := fmt.Sprintf("MusicBrainz has %d %s called %q by %s, but none has its tracks: the closest, %q, agrees on %d of its %d", - considered, plural(considered, "album", "albums"), a.Title, a.ArtistName, best.Title, max(bestN, 0), len(tracks)) + detail := fmt.Sprintf("MusicBrainz has %s called %q by %s, but none has its tracks: the closest, %q, agrees on %d of its %d", + words.Count(considered, "album", "albums"), a.Title, a.ArtistName, best.Title, max(bestN, 0), len(tracks)) switch { case bestN < 0: detail += " (MusicBrainz lists no edition of it)" @@ -403,10 +404,3 @@ func abs(n int) int { } return n } - -func plural(n int, one, many string) string { - if n == 1 { - return one - } - return many -} diff --git a/internal/words/words.go b/internal/words/words.go new file mode 100644 index 00000000..4f890cfe --- /dev/null +++ b/internal/words/words.go @@ -0,0 +1,15 @@ +// Package words says counts in English for what the server writes to people: +// notifications, emails, the reasons a scan or a match gives. +package words + +import "strconv" + +// Count says n with the noun that agrees with it: "1 track", "3 tracks". The +// noun may carry its verb ("track went missing" / "tracks went missing"). +// The web app's plural() is the same thing on the other side (M505). +func Count[N ~int | ~int32 | ~int64](n N, one, many string) string { + if n == 1 { + return "1 " + one + } + return strconv.FormatInt(int64(n), 10) + " " + many +} diff --git a/internal/words/words_test.go b/internal/words/words_test.go new file mode 100644 index 00000000..411bb673 --- /dev/null +++ b/internal/words/words_test.go @@ -0,0 +1,19 @@ +package words + +import "testing" + +func TestCount(t *testing.T) { + cases := []struct { + got, want string + }{ + {Count(1, "track", "tracks"), "1 track"}, + {Count(0, "track", "tracks"), "0 tracks"}, + {Count(int64(3), "track went missing", "tracks went missing"), "3 tracks went missing"}, + {Count(int32(-1), "copy", "copies"), "-1 copies"}, + } + for _, c := range cases { + if c.got != c.want { + t.Errorf("got %q, want %q", c.got, c.want) + } + } +}