diff --git a/go.mod b/go.mod index dcb1437..2679900 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( require ( forge.lthn.ai/core/go-inference v0.1.0 // indirect - forge.lthn.ai/core/go-io v0.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect diff --git a/go.sum b/go.sum index 238de4c..c67f5c3 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ forge.lthn.ai/core/go-i18n v0.1.0 h1:F7JVSoVkZtzx9JfhpntM9z3iQm1vnuMUi/Zklhz8PCI forge.lthn.ai/core/go-i18n v0.1.0/go.mod h1:Q4xsrxuNCl/6NfMv1daria7t1RSiyy8ml+6jiPtUcBs= forge.lthn.ai/core/go-inference v0.1.0 h1:pO7etYgqV8LMKFdpW8/2RWncuECZJCIcf8nnezeZ5R4= forge.lthn.ai/core/go-inference v0.1.0/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= -forge.lthn.ai/core/go-io v0.1.0 h1:aYNvmbU2VVsjXnut0WQ4DfVxcFdheziahJB32mfeJ7g= -forge.lthn.ai/core/go-io v0.1.0/go.mod h1:ZlU9OQpsvNFNmTJoaHbFIkisZyc0eCq0p8znVWQLRf0= forge.lthn.ai/core/go-log v0.0.1 h1:x/E6EfF9vixzqiLHQOl2KT25HyBcMc9qiBkomqVlpPg= forge.lthn.ai/core/go-log v0.0.1/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= diff --git a/pkg/cli/app.go b/pkg/cli/app.go index a2d24be..577c043 100644 --- a/pkg/cli/app.go +++ b/pkg/cli/app.go @@ -5,8 +5,9 @@ import ( "os" "runtime/debug" - "forge.lthn.ai/core/go/pkg/core" + "forge.lthn.ai/core/go-i18n" "forge.lthn.ai/core/go-log" + "forge.lthn.ai/core/go/pkg/core" "github.com/spf13/cobra" ) @@ -76,10 +77,7 @@ func Main(commands ...core.Option) { // Core services load first, then command services services := []core.Option{ - core.WithName("i18n", NewI18nService(I18nOptions{})), - core.WithName("log", NewLogService(log.Options{ - Level: log.LevelInfo, - })), + core.WithName("i18n", i18n.NewCoreService(i18n.ServiceOptions{})), } services = append(services, commands...) diff --git a/pkg/cli/i18n.go b/pkg/cli/i18n.go index 0a54af6..b5dc998 100644 --- a/pkg/cli/i18n.go +++ b/pkg/cli/i18n.go @@ -1,170 +1,14 @@ package cli import ( - "context" - "sync" - - "forge.lthn.ai/core/go/pkg/core" "forge.lthn.ai/core/go-i18n" ) -// I18nService wraps i18n as a Core service. -type I18nService struct { - *core.ServiceRuntime[I18nOptions] - svc *i18n.Service - - // Collect mode state - missingKeys []i18n.MissingKey - missingKeysMu sync.Mutex -} - -// I18nOptions configures the i18n service. -type I18nOptions struct { - // Language overrides auto-detection (e.g., "en-GB", "de") - Language string - // Mode sets the translation mode (Normal, Strict, Collect) - Mode i18n.Mode -} - -// NewI18nService creates an i18n service factory. -func NewI18nService(opts I18nOptions) func(*core.Core) (any, error) { - return func(c *core.Core) (any, error) { - svc, err := i18n.New() - if err != nil { - return nil, err - } - - if opts.Language != "" { - _ = svc.SetLanguage(opts.Language) - } - - // Set mode if specified - svc.SetMode(opts.Mode) - - // Set as global default so i18n.T() works everywhere - i18n.SetDefault(svc) - - return &I18nService{ - ServiceRuntime: core.NewServiceRuntime(c, opts), - svc: svc, - missingKeys: make([]i18n.MissingKey, 0), - }, nil - } -} - -// OnStartup initialises the i18n service. -func (s *I18nService) OnStartup(ctx context.Context) error { - s.Core().RegisterQuery(s.handleQuery) - - // Register action handler for collect mode - if s.svc.Mode() == i18n.ModeCollect { - i18n.OnMissingKey(s.handleMissingKey) - } - - return nil -} - -// handleMissingKey accumulates missing keys in collect mode. -func (s *I18nService) handleMissingKey(mk i18n.MissingKey) { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - s.missingKeys = append(s.missingKeys, mk) -} - -// MissingKeys returns all missing keys collected in collect mode. -// Call this at the end of a QA session to report missing translations. -func (s *I18nService) MissingKeys() []i18n.MissingKey { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - result := make([]i18n.MissingKey, len(s.missingKeys)) - copy(result, s.missingKeys) - return result -} - -// ClearMissingKeys resets the collected missing keys. -func (s *I18nService) ClearMissingKeys() { - s.missingKeysMu.Lock() - defer s.missingKeysMu.Unlock() - s.missingKeys = s.missingKeys[:0] -} - -// SetMode changes the translation mode. -func (s *I18nService) SetMode(mode i18n.Mode) { - s.svc.SetMode(mode) - - // Update action handler registration - if mode == i18n.ModeCollect { - i18n.OnMissingKey(s.handleMissingKey) - } else { - i18n.OnMissingKey(nil) - } -} - -// Mode returns the current translation mode. -func (s *I18nService) Mode() i18n.Mode { - return s.svc.Mode() -} - -// Queries for i18n service - -// QueryTranslate requests a translation. -type QueryTranslate struct { - Key string - Args map[string]any -} - -func (s *I18nService) handleQuery(c *core.Core, q core.Query) (any, bool, error) { - switch m := q.(type) { - case QueryTranslate: - return s.svc.T(m.Key, m.Args), true, nil - } - return nil, false, nil -} - -// T translates a key with optional arguments. -func (s *I18nService) T(key string, args ...map[string]any) string { - if len(args) > 0 { - return s.svc.T(key, args[0]) - } - return s.svc.T(key) -} - -// SetLanguage changes the current language. -func (s *I18nService) SetLanguage(lang string) { - _ = s.svc.SetLanguage(lang) -} - -// Language returns the current language. -func (s *I18nService) Language() string { - return s.svc.Language() -} - -// AvailableLanguages returns all available languages. -func (s *I18nService) AvailableLanguages() []string { - return s.svc.AvailableLanguages() -} - -// --- Package-level convenience --- - // T translates a key using the CLI's i18n service. // Falls back to the global i18n.T if CLI not initialised. func T(key string, args ...map[string]any) string { - if instance == nil { - // CLI not initialised, use global i18n - if len(args) > 0 { - return i18n.T(key, args[0]) - } - return i18n.T(key) + if len(args) > 0 { + return i18n.T(key, args[0]) } - - svc, err := core.ServiceFor[*I18nService](instance.core, "i18n") - if err != nil { - // i18n service not registered, use global - if len(args) > 0 { - return i18n.T(key, args[0]) - } - return i18n.T(key) - } - - return svc.T(key, args...) + return i18n.T(key) } diff --git a/pkg/cli/log.go b/pkg/cli/log.go index c752f5a..7a2e3df 100644 --- a/pkg/cli/log.go +++ b/pkg/cli/log.go @@ -1,115 +1,28 @@ package cli import ( - "forge.lthn.ai/core/go/pkg/core" - "forge.lthn.ai/core/go/pkg/log" + "forge.lthn.ai/core/go-log" ) -// LogLevel aliases for backwards compatibility. +// LogLevel aliases for convenience. type LogLevel = log.Level -// Log level constants aliased from the log package. const ( - // LogLevelQuiet suppresses all output. LogLevelQuiet = log.LevelQuiet - // LogLevelError shows only error messages. LogLevelError = log.LevelError - // LogLevelWarn shows warnings and errors. - LogLevelWarn = log.LevelWarn - // LogLevelInfo shows info, warnings, and errors. - LogLevelInfo = log.LevelInfo - // LogLevelDebug shows all messages including debug. + LogLevelWarn = log.LevelWarn + LogLevelInfo = log.LevelInfo LogLevelDebug = log.LevelDebug ) -// LogService wraps log.Service with CLI styling. -type LogService struct { - *log.Service -} +// LogDebug logs a debug message if the default logger is available. +func LogDebug(msg string, keyvals ...any) { log.Debug(msg, keyvals...) } -// LogOptions configures the log service. -type LogOptions = log.Options +// LogInfo logs an info message. +func LogInfo(msg string, keyvals ...any) { log.Info(msg, keyvals...) } -// NewLogService creates a log service factory with CLI styling. -func NewLogService(opts LogOptions) func(*core.Core) (any, error) { - return func(c *core.Core) (any, error) { - // Create the underlying service - factory := log.NewService(opts) - svc, err := factory(c) - if err != nil { - return nil, err - } +// LogWarn logs a warning message. +func LogWarn(msg string, keyvals ...any) { log.Warn(msg, keyvals...) } - logSvc := svc.(*log.Service) - - // Apply CLI styles - logSvc.StyleTimestamp = func(s string) string { return DimStyle.Render(s) } - logSvc.StyleDebug = func(s string) string { return DimStyle.Render(s) } - logSvc.StyleInfo = func(s string) string { return InfoStyle.Render(s) } - logSvc.StyleWarn = func(s string) string { return WarningStyle.Render(s) } - logSvc.StyleError = func(s string) string { return ErrorStyle.Render(s) } - logSvc.StyleSecurity = func(s string) string { return SecurityStyle.Render(s) } - - return &LogService{Service: logSvc}, nil - } -} - -// --- Package-level convenience --- - -// Log returns the CLI's log service, or nil if not available. -func Log() *LogService { - if instance == nil { - return nil - } - svc, err := core.ServiceFor[*LogService](instance.core, "log") - if err != nil { - return nil - } - return svc -} - -// LogDebug logs a debug message with optional key-value pairs if log service is available. -func LogDebug(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Debug(msg, keyvals...) - } -} - -// LogInfo logs an info message with optional key-value pairs if log service is available. -func LogInfo(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Info(msg, keyvals...) - } -} - -// LogWarn logs a warning message with optional key-value pairs if log service is available. -func LogWarn(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Warn(msg, keyvals...) - } -} - -// LogError logs an error message with optional key-value pairs if log service is available. -func LogError(msg string, keyvals ...any) { - if l := Log(); l != nil { - l.Error(msg, keyvals...) - } -} - -// LogSecurity logs a security message if log service is available. -func LogSecurity(msg string, keyvals ...any) { - if l := Log(); l != nil { - // Ensure user context is included if not already present - hasUser := false - for i := 0; i < len(keyvals); i += 2 { - if keyvals[i] == "user" { - hasUser = true - break - } - } - if !hasUser { - keyvals = append(keyvals, "user", log.Username()) - } - l.Security(msg, keyvals...) - } -} +// LogError logs an error message. +func LogError(msg string, keyvals ...any) { log.Error(msg, keyvals...) } diff --git a/pkg/cli/utils.go b/pkg/cli/utils.go index 4b41a8c..8a33b27 100644 --- a/pkg/cli/utils.go +++ b/pkg/cli/utils.go @@ -22,9 +22,9 @@ func GhAuthenticated() bool { authenticated := strings.Contains(string(output), "Logged in") if authenticated { - LogSecurity("GitHub CLI authenticated", "user", log.Username()) + LogWarn("GitHub CLI authenticated", "user", log.Username()) } else { - LogSecurity("GitHub CLI not authenticated", "user", log.Username()) + LogWarn("GitHub CLI not authenticated", "user", log.Username()) } return authenticated