From 8f977ed63589a8838ade2f4690bec6b94e353066 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 30 Jan 2026 17:33:18 +0000 Subject: [PATCH] feat(i18n): add N() helper for numeric formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit N(format, value) wraps T("i18n.numeric.{format}", value). Supported formats: - N("number", 1234567) → "1,234,567" - N("decimal", 1234.5) → "1,234.5" - N("percent", 0.85) → "85%" - N("bytes", 1536000) → "1.5 MB" - N("ordinal", 1) → "1st" Co-Authored-By: Claude Opus 4.5 --- pkg/i18n/i18n.go | 11 ++++++++ pkg/i18n/service.go | 68 ++++++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index a1e00f3a..56352b99 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -56,6 +56,17 @@ func _(messageID string, args ...any) string { return messageID } +// N formats a number using the i18n.numeric.* namespace. +// Wrapper for T("i18n.numeric.{format}", value). +// +// N("number", 1234567) // T("i18n.numeric.number", 1234567) +// N("percent", 0.85) // T("i18n.numeric.percent", 0.85) +// N("bytes", 1536000) // T("i18n.numeric.bytes", 1536000) +// N("ordinal", 1) // T("i18n.numeric.ordinal", 1) +func N(format string, value any) string { + return T("i18n.numeric."+format, value) +} + // --- Template helpers --- // executeIntentTemplate executes an intent template with the given data. diff --git a/pkg/i18n/service.go b/pkg/i18n/service.go index 84700048..2c90f891 100644 --- a/pkg/i18n/service.go +++ b/pkg/i18n/service.go @@ -314,36 +314,48 @@ func (s *Service) handleI18nNamespace(key string, args []any) string { return ActionFailed(verb, "") } - // i18n.number → FormatNumber(n) - if key == "i18n.number" && len(args) > 0 { - return FormatNumber(toInt64(args[0])) + // i18n.numeric.* namespace (for N() helper) + if strings.HasPrefix(key, "i18n.numeric.") && len(args) > 0 { + format := strings.TrimPrefix(key, "i18n.numeric.") + switch format { + case "number", "int": + return FormatNumber(toInt64(args[0])) + case "decimal", "float": + return FormatDecimal(toFloat64(args[0])) + case "percent", "pct": + return FormatPercent(toFloat64(args[0])) + case "bytes", "size": + return FormatBytes(toInt64(args[0])) + case "ordinal", "ord": + return FormatOrdinal(toInt(args[0])) + case "ago": + if len(args) >= 2 { + if unit, ok := args[1].(string); ok { + return FormatAgo(toInt(args[0]), unit) + } + } + } } - // i18n.decimal → FormatDecimal(f) - if key == "i18n.decimal" && len(args) > 0 { - return FormatDecimal(toFloat64(args[0])) - } - - // i18n.percent → FormatPercent(f) - if key == "i18n.percent" && len(args) > 0 { - return FormatPercent(toFloat64(args[0])) - } - - // i18n.bytes → FormatBytes(n) - if key == "i18n.bytes" && len(args) > 0 { - return FormatBytes(toInt64(args[0])) - } - - // i18n.ordinal → FormatOrdinal(n) - if key == "i18n.ordinal" && len(args) > 0 { - return FormatOrdinal(toInt(args[0])) - } - - // i18n.ago → FormatAgo(count, unit) - if key == "i18n.ago" && len(args) >= 2 { - count := toInt(args[0]) - if unit, ok := args[1].(string); ok { - return FormatAgo(count, unit) + // Legacy i18n.{format} shortcuts (kept for compatibility) + if len(args) > 0 { + switch key { + case "i18n.number": + return FormatNumber(toInt64(args[0])) + case "i18n.decimal": + return FormatDecimal(toFloat64(args[0])) + case "i18n.percent": + return FormatPercent(toFloat64(args[0])) + case "i18n.bytes": + return FormatBytes(toInt64(args[0])) + case "i18n.ordinal": + return FormatOrdinal(toInt(args[0])) + case "i18n.ago": + if len(args) >= 2 { + if unit, ok := args[1].(string); ok { + return FormatAgo(toInt(args[0]), unit) + } + } } }