diff --git a/core_service.go b/core_service.go index 309ec20..1c88b91 100644 --- a/core_service.go +++ b/core_service.go @@ -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...) diff --git a/docs/forward-api.md b/docs/forward-api.md index 6bc6c3a..1a81a4b 100644 --- a/docs/forward-api.md +++ b/docs/forward-api.md @@ -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 diff --git a/i18n.go b/i18n.go index 2fede52..043a123 100644 --- a/i18n.go +++ b/i18n.go @@ -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: diff --git a/i18n_test.go b/i18n_test.go index 785a292..865631c 100644 --- a/i18n_test.go +++ b/i18n_test.go @@ -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) { diff --git a/service_test.go b/service_test.go index 5478877..c8c268c 100644 --- a/service_test.go +++ b/service_test.go @@ -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) { diff --git a/state.go b/state.go new file mode 100644 index 0000000..5f0a0c7 --- /dev/null +++ b/state.go @@ -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() +}