feat(i18n): add debug mode and NewSubject alias
- Add SetDebug()/Debug() methods for showing key prefixes in output - Debug mode shows: "[cli.success] Success" instead of "Success" - Add NewSubject() as alias for S() for readability - Both T() and C() respect debug mode Debug mode is useful for: - Identifying which translation keys are used where - Verifying correct key usage during development - QA testing of translation coverage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fc74d4df9c
commit
fa6d62e385
4 changed files with 134 additions and 1 deletions
|
|
@ -32,6 +32,13 @@ func S(noun string, value any) *Subject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSubject is an alias for S() for readability in longer expressions.
|
||||||
|
//
|
||||||
|
// NewSubject("file", path).Count(3).In("workspace")
|
||||||
|
func NewSubject(noun string, value any) *Subject {
|
||||||
|
return S(noun, value)
|
||||||
|
}
|
||||||
|
|
||||||
// Count sets the count for pluralization.
|
// Count sets the count for pluralization.
|
||||||
// Used to determine singular/plural forms in templates.
|
// Used to determine singular/plural forms in templates.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ func TestSubject_Good(t *testing.T) {
|
||||||
assert.Equal(t, "", s.location)
|
assert.Equal(t, "", s.location)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("NewSubject alias", func(t *testing.T) {
|
||||||
|
s := NewSubject("repo", "core-php")
|
||||||
|
assert.Equal(t, "repo", s.Noun)
|
||||||
|
assert.Equal(t, "core-php", s.Value)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("with count", func(t *testing.T) {
|
t.Run("with count", func(t *testing.T) {
|
||||||
s := S("file", "*.go").Count(5)
|
s := S("file", "*.go").Count(5)
|
||||||
assert.Equal(t, 5, s.GetCount())
|
assert.Equal(t, 5, s.GetCount())
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ type Service struct {
|
||||||
fallbackLang string
|
fallbackLang string
|
||||||
availableLangs []language.Tag
|
availableLangs []language.Tag
|
||||||
mode Mode // Translation mode (Normal, Strict, Collect)
|
mode Mode // Translation mode (Normal, Strict, Collect)
|
||||||
|
debug bool // Debug mode shows key prefixes
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,6 +242,17 @@ func SetDefault(s *Service) {
|
||||||
defaultService = s
|
defaultService = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDebug enables or disables debug mode on the default service.
|
||||||
|
// In debug mode, translations show their keys: [key] translation
|
||||||
|
//
|
||||||
|
// SetDebug(true)
|
||||||
|
// T("cli.success") // "[cli.success] Success"
|
||||||
|
func SetDebug(enabled bool) {
|
||||||
|
if svc := Default(); svc != nil {
|
||||||
|
svc.SetDebug(enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// T translates a message using the default service.
|
// T translates a message using the default service.
|
||||||
// For semantic intents (core.* namespace), pass a Subject as the first argument.
|
// For semantic intents (core.* namespace), pass a Subject as the first argument.
|
||||||
//
|
//
|
||||||
|
|
@ -368,6 +380,24 @@ func (s *Service) Mode() Mode {
|
||||||
return s.mode
|
return s.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDebug enables or disables debug mode.
|
||||||
|
// In debug mode, translations are prefixed with their key:
|
||||||
|
//
|
||||||
|
// [cli.success] Success
|
||||||
|
// [core.delete] Delete config.yaml?
|
||||||
|
func (s *Service) SetDebug(enabled bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.debug = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug returns whether debug mode is enabled.
|
||||||
|
func (s *Service) Debug() bool {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.debug
|
||||||
|
}
|
||||||
|
|
||||||
// T translates a message by its ID.
|
// T translates a message by its ID.
|
||||||
// Optional template data can be passed for interpolation.
|
// Optional template data can be passed for interpolation.
|
||||||
//
|
//
|
||||||
|
|
@ -431,6 +461,11 @@ func (s *Service) T(messageID string, args ...any) string {
|
||||||
text = applyTemplate(text, data)
|
text = applyTemplate(text, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug mode: prefix with key
|
||||||
|
if s.debug {
|
||||||
|
return "[" + messageID + "] " + text
|
||||||
|
}
|
||||||
|
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,13 +529,27 @@ func (s *Service) C(intent string, subject *Subject) *Composed {
|
||||||
// Create template data from subject
|
// Create template data from subject
|
||||||
data := newTemplateData(subject)
|
data := newTemplateData(subject)
|
||||||
|
|
||||||
return &Composed{
|
result := &Composed{
|
||||||
Question: executeIntentTemplate(intentDef.Question, data),
|
Question: executeIntentTemplate(intentDef.Question, data),
|
||||||
Confirm: executeIntentTemplate(intentDef.Confirm, data),
|
Confirm: executeIntentTemplate(intentDef.Confirm, data),
|
||||||
Success: executeIntentTemplate(intentDef.Success, data),
|
Success: executeIntentTemplate(intentDef.Success, data),
|
||||||
Failure: executeIntentTemplate(intentDef.Failure, data),
|
Failure: executeIntentTemplate(intentDef.Failure, data),
|
||||||
Meta: intentDef.Meta,
|
Meta: intentDef.Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug mode: prefix each form with the intent key
|
||||||
|
s.mu.RLock()
|
||||||
|
debug := s.debug
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if debug {
|
||||||
|
prefix := "[" + intent + "] "
|
||||||
|
result.Question = prefix + result.Question
|
||||||
|
result.Confirm = prefix + result.Confirm
|
||||||
|
result.Success = prefix + result.Success
|
||||||
|
result.Failure = prefix + result.Failure
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeIntentTemplate executes an intent template with the given data.
|
// executeIntentTemplate executes an intent template with the given data.
|
||||||
|
|
|
||||||
|
|
@ -164,3 +164,74 @@ func TestNestedKeys(t *testing.T) {
|
||||||
result = svc.T("cmd.dev.work.flag.status")
|
result = svc.T("cmd.dev.work.flag.status")
|
||||||
assert.Equal(t, "Show status only, don't push", result)
|
assert.Equal(t, "Show status only, don't push", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDebugMode(t *testing.T) {
|
||||||
|
t.Run("default is disabled", func(t *testing.T) {
|
||||||
|
svc, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, svc.Debug())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("T with debug mode", func(t *testing.T) {
|
||||||
|
svc, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Without debug
|
||||||
|
result := svc.T("cli.success")
|
||||||
|
assert.Equal(t, "Success", result)
|
||||||
|
|
||||||
|
// Enable debug
|
||||||
|
svc.SetDebug(true)
|
||||||
|
assert.True(t, svc.Debug())
|
||||||
|
|
||||||
|
// With debug - shows key prefix
|
||||||
|
result = svc.T("cli.success")
|
||||||
|
assert.Equal(t, "[cli.success] Success", result)
|
||||||
|
|
||||||
|
// Disable debug
|
||||||
|
svc.SetDebug(false)
|
||||||
|
result = svc.T("cli.success")
|
||||||
|
assert.Equal(t, "Success", result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("C with debug mode", func(t *testing.T) {
|
||||||
|
svc, err := New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
subject := S("file", "config.yaml")
|
||||||
|
|
||||||
|
// Without debug
|
||||||
|
result := svc.C("core.delete", subject)
|
||||||
|
assert.NotContains(t, result.Question, "[core.delete]")
|
||||||
|
|
||||||
|
// Enable debug
|
||||||
|
svc.SetDebug(true)
|
||||||
|
|
||||||
|
// With debug - shows key prefix on all forms
|
||||||
|
result = svc.C("core.delete", subject)
|
||||||
|
assert.Contains(t, result.Question, "[core.delete]")
|
||||||
|
assert.Contains(t, result.Success, "[core.delete]")
|
||||||
|
assert.Contains(t, result.Failure, "[core.delete]")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("package-level SetDebug", func(t *testing.T) {
|
||||||
|
// Reset default
|
||||||
|
defaultService = nil
|
||||||
|
defaultOnce = sync.Once{}
|
||||||
|
defaultErr = nil
|
||||||
|
|
||||||
|
err := Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Enable debug via package function
|
||||||
|
SetDebug(true)
|
||||||
|
assert.True(t, Default().Debug())
|
||||||
|
|
||||||
|
// Translate
|
||||||
|
result := T("cli.success")
|
||||||
|
assert.Equal(t, "[cli.success] Success", result)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
SetDebug(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue