From ee7e9d1abffad34f4f4d29cf1a7857b2aaf58c9d Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 01:35:04 +0000 Subject: [PATCH] feat: RegisterCommands accepts locale FS for automatic translation loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both WithCommands() and RegisterCommands() now accept an optional fs.FS for translations. The CLI collects them via RegisteredLocales() and the i18n service loads them on startup. Packages just pass their embed.FS — no i18n import needed: cli.RegisterCommands(AddDevCommands, locales.FS) Co-Authored-By: Virgil --- pkg/cli/commands.go | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 5709fdd..2a60b7e 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -3,6 +3,7 @@ package cli import ( "context" + "io/fs" "iter" "sync" @@ -18,15 +19,24 @@ import ( // cli.WithCommands("config", config.AddConfigCommands), // cli.WithCommands("doctor", doctor.AddDoctorCommands), // ) -func WithCommands(name string, register func(root *Command)) core.Option { +// WithCommands creates a framework Option that registers a command group. +// Optionally pass a locale fs.FS as the third argument to provide translations. +// +// cli.WithCommands("dev", dev.AddDevCommands, locales.FS) +func WithCommands(name string, register func(root *Command), localeFS ...fs.FS) core.Option { return core.WithName("cmd."+name, func(c *core.Core) (any, error) { - return &commandService{core: c, register: register}, nil + svc := &commandService{core: c, register: register} + if len(localeFS) > 0 { + svc.localeFS = localeFS[0] + } + return svc, nil }) } type commandService struct { core *core.Core register func(root *Command) + localeFS fs.FS } func (s *commandService) OnStartup(_ context.Context) error { @@ -36,6 +46,11 @@ func (s *commandService) OnStartup(_ context.Context) error { return nil } +// Locales implements core.LocaleProvider. +func (s *commandService) Locales() fs.FS { + return s.localeFS +} + // CommandRegistration is a function that adds commands to the root. type CommandRegistration func(root *cobra.Command) @@ -43,22 +58,24 @@ var ( registeredCommands []CommandRegistration registeredCommandsMu sync.Mutex commandsAttached bool + registeredLocales []fs.FS ) // RegisterCommands registers a function that adds commands to the CLI. -// Call this in your package's init() to register commands. +// Optionally pass a locale fs.FS to provide translations for the commands. // // func init() { -// cli.RegisterCommands(AddCommands) +// cli.RegisterCommands(AddCommands, locales.FS) // } -// -// func AddCommands(root *cobra.Command) { -// root.AddCommand(myCmd) -// } -func RegisterCommands(fn CommandRegistration) { +func RegisterCommands(fn CommandRegistration, localeFS ...fs.FS) { registeredCommandsMu.Lock() defer registeredCommandsMu.Unlock() registeredCommands = append(registeredCommands, fn) + for _, lfs := range localeFS { + if lfs != nil { + registeredLocales = append(registeredLocales, lfs) + } + } // If commands already attached (CLI already running), attach immediately if commandsAttached && instance != nil && instance.root != nil { @@ -66,6 +83,13 @@ func RegisterCommands(fn CommandRegistration) { } } +// RegisteredLocales returns all locale filesystems registered by command packages. +func RegisteredLocales() []fs.FS { + registeredCommandsMu.Lock() + defer registeredCommandsMu.Unlock() + return registeredLocales +} + // RegisteredCommands returns an iterator over the registered command functions. func RegisteredCommands() iter.Seq[CommandRegistration] { return func(yield func(CommandRegistration) bool) {