From d90dd083f219fe345e688d86387f5b101a041144 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 23:30:11 +0000 Subject: [PATCH] fix(grammar): honour word display forms in count handler Co-Authored-By: Virgil --- handler.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++-- handler_test.go | 4 ++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/handler.go b/handler.go index dd95c61..73cdfbb 100644 --- a/handler.go +++ b/handler.go @@ -1,6 +1,9 @@ package i18n import ( + "strings" + "unicode" + "dappco.re/go/core" ) @@ -42,11 +45,12 @@ func (h CountHandler) Match(key string) bool { func (h CountHandler) Handle(key string, args []any, next func() string) string { noun := core.TrimPrefix(key, "i18n.count.") + lang := currentLangForGrammar() if len(args) > 0 { count := toInt(args[0]) - return core.Sprintf("%s %s", FormatNumber(int64(count)), Pluralize(noun, count)) + return core.Sprintf("%s %s", FormatNumber(int64(count)), countWordForm(lang, noun, count)) } - return noun + return renderWord(lang, noun) } // DoneHandler handles i18n.done.{verb} -> "File deleted" patterns. @@ -128,6 +132,51 @@ func DefaultHandlers() []KeyHandler { } } +func countWordForm(lang, noun string, count int) string { + display := renderWord(lang, noun) + if display == "" { + return Pluralize(noun, count) + } + if count == 1 { + return display + } + if isUpperAcronymPlural(display) { + return display + } + return Pluralize(display, count) +} + +func isUpperAcronymPlural(s string) bool { + if len(s) < 2 || !strings.HasSuffix(s, "s") { + return false + } + hasLetter := false + for _, r := range s[:len(s)-1] { + if !unicode.IsLetter(r) { + continue + } + hasLetter = true + if !unicode.IsUpper(r) { + return false + } + } + return hasLetter +} + +func isAllUpper(s string) bool { + hasLetter := false + for _, r := range s { + if !unicode.IsLetter(r) { + continue + } + hasLetter = true + if !unicode.IsUpper(r) { + return false + } + } + return hasLetter +} + // RunHandlerChain executes a chain of handlers for a key. func RunHandlerChain(handlers []KeyHandler, key string, args []any, fallback func() string) string { for i, h := range handlers { diff --git a/handler_test.go b/handler_test.go index be2e65b..3869d2c 100644 --- a/handler_test.go +++ b/handler_test.go @@ -72,7 +72,11 @@ func TestCountHandler(t *testing.T) { {"i18n.count.file", []any{5}, "5 files"}, {"i18n.count.file", []any{0}, "0 files"}, {"i18n.count.child", []any{3}, "3 children"}, + {"i18n.count.url", []any{2}, "2 URLs"}, + {"i18n.count.api", []any{2}, "2 APIs"}, + {"i18n.count.cpus", []any{2}, "2 CPUs"}, {"i18n.count.file", nil, "file"}, + {"i18n.count.url", nil, "URL"}, } for _, tt := range tests { -- 2.45.3