refactor: move I18nService to go-i18n, simplify log wrapper
I18nService now lives in go-i18n as NewCoreService() — any binary can use it without importing cli. Log convenience functions use go-log directly. Removed LogService/NewLogService/daemon_cmd wrappers. Root go.mod: 1 direct forge dep (core/go). Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
55b556d1af
commit
2efcbd59ec
6 changed files with 20 additions and 268 deletions
1
go.mod
1
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
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
|
||||
|
|
|
|||
162
pkg/cli/i18n.go
162
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)
|
||||
}
|
||||
|
|
|
|||
111
pkg/cli/log.go
111
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...) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue