feat(i18n): add service state snapshot aliases
Some checks failed
Security Scan / security (push) Successful in 18s
Test / test (push) Has been cancelled

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 12:48:49 +00:00
parent 158f71443c
commit 9d4af96d3d
6 changed files with 147 additions and 0 deletions

View file

@ -268,6 +268,19 @@ func (s *CoreService) CurrentDebug() bool {
return s.Debug()
}
// State returns a copy-safe snapshot of the wrapped service configuration.
func (s *CoreService) State() ServiceState {
if s == nil || s.svc == nil {
return State()
}
return s.svc.State()
}
// CurrentState is a more explicit alias for State.
func (s *CoreService) CurrentState() ServiceState {
return s.State()
}
// AddHandler appends handlers to the wrapped service's chain.
func (s *CoreService) AddHandler(handlers ...KeyHandler) {
s.svc.AddHandler(handlers...)

View file

@ -47,6 +47,7 @@ The API exposes `Current*` aliases for the most common service getters so call s
- `CurrentDebug()`
- `CurrentHandlers()`
- `CurrentPrompt()`
- `State()` / `CurrentState()` snapshot of the full service configuration
### Options

22
i18n.go
View file

@ -178,6 +178,28 @@ func CurrentDebug() bool {
return Debug()
}
// State returns a copy-safe snapshot of the default service configuration.
func State() ServiceState {
return defaultServiceValue(ServiceState{
Language: "en",
AvailableLanguages: []string{},
Mode: ModeNormal,
Fallback: "en",
Formality: FormalityNeutral,
Direction: DirLTR,
IsRTL: false,
Debug: false,
Handlers: []KeyHandler{},
}, func(svc *Service) ServiceState {
return svc.State()
})
}
// CurrentState is a more explicit alias for State.
func CurrentState() ServiceState {
return State()
}
// Debug reports whether debug mode is enabled on the default service.
//
// Example:

View file

@ -234,6 +234,33 @@ func TestDebug_Good(t *testing.T) {
assert.True(t, Debug())
}
func TestCurrentState_Good(t *testing.T) {
svc, err := NewWithLoader(messageBaseFallbackLoader{})
require.NoError(t, err)
prev := Default()
SetDefault(svc)
t.Cleanup(func() {
SetDefault(prev)
})
state := CurrentState()
assert.Equal(t, svc.Language(), state.Language)
assert.Equal(t, svc.AvailableLanguages(), state.AvailableLanguages)
assert.Equal(t, svc.Mode(), state.Mode)
assert.Equal(t, svc.Fallback(), state.Fallback)
assert.Equal(t, svc.Formality(), state.Formality)
assert.Equal(t, svc.Location(), state.Location)
assert.Equal(t, svc.Direction(), state.Direction)
assert.Equal(t, svc.IsRTL(), state.IsRTL)
assert.Equal(t, svc.Debug(), state.Debug)
assert.Len(t, state.Handlers, len(svc.Handlers()))
state.AvailableLanguages[0] = "zz"
assert.NotEqual(t, "zz", CurrentState().AvailableLanguages[0])
state.Handlers[0] = nil
assert.NotNil(t, CurrentState().Handlers[0])
}
// --- SetMode / CurrentMode ---
func TestSetMode_Good(t *testing.T) {

View file

@ -190,6 +190,9 @@ func TestServiceCurrentStateAliases(t *testing.T) {
if got, want := svc.CurrentHandlers(), svc.Handlers(); len(got) != len(want) {
t.Fatalf("CurrentHandlers() len = %d, want %d", len(got), len(want))
}
if got, want := svc.CurrentState(), svc.State(); len(got.AvailableLanguages) != len(want.AvailableLanguages) || len(got.Handlers) != len(want.Handlers) {
t.Fatalf("CurrentState() = %+v, want %+v", got, want)
}
}
func TestServiceCurrentStateAliasesReturnCopies(t *testing.T) {
@ -215,6 +218,22 @@ func TestServiceCurrentStateAliasesReturnCopies(t *testing.T) {
if svc.CurrentHandlers()[0] == nil {
t.Fatal("CurrentHandlers() returned a shared slice; first handler mutated to nil")
}
state := svc.CurrentState()
if len(state.AvailableLanguages) == 0 {
t.Fatal("CurrentState() returned no available languages")
}
if len(state.Handlers) == 0 {
t.Fatal("CurrentState() returned no handlers")
}
state.AvailableLanguages[0] = "zz"
if got := svc.CurrentState().AvailableLanguages[0]; got == "zz" {
t.Fatalf("CurrentState() returned a shared available languages slice; first element mutated to %q", got)
}
state.Handlers[0] = nil
if svc.CurrentState().Handlers[0] == nil {
t.Fatal("CurrentState() returned a shared handlers slice; first handler mutated to nil")
}
}
func TestServiceAvailableLanguagesSorted(t *testing.T) {

65
state.go Normal file
View file

@ -0,0 +1,65 @@
package i18n
// ServiceState captures the current configuration of a service in one
// copy-safe snapshot.
type ServiceState struct {
Language string
AvailableLanguages []string
Mode Mode
Fallback string
Formality Formality
Location string
Direction TextDirection
IsRTL bool
Debug bool
Handlers []KeyHandler
}
func (s *Service) State() ServiceState {
if s == nil {
return ServiceState{
Language: "en",
AvailableLanguages: []string{},
Mode: ModeNormal,
Fallback: "en",
Formality: FormalityNeutral,
Direction: DirLTR,
IsRTL: false,
Debug: false,
Handlers: []KeyHandler{},
}
}
s.mu.RLock()
defer s.mu.RUnlock()
langs := make([]string, len(s.availableLangs))
for i, tag := range s.availableLangs {
langs[i] = tag.String()
}
handlers := make([]KeyHandler, len(s.handlers))
copy(handlers, s.handlers)
dir := DirLTR
if IsRTLLanguage(s.currentLang) {
dir = DirRTL
}
return ServiceState{
Language: s.currentLang,
AvailableLanguages: langs,
Mode: s.mode,
Fallback: s.fallbackLang,
Formality: s.formality,
Location: s.location,
Direction: dir,
IsRTL: dir == DirRTL,
Debug: s.debug,
Handlers: handlers,
}
}
// CurrentState is a more explicit alias for State.
func (s *Service) CurrentState() ServiceState {
return s.State()
}