From be1cf9bba833449ff8d668240e1529be552cadc9 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 00:28:29 +0000 Subject: [PATCH] fix(i18n): preserve structured missing-key args Co-Authored-By: Virgil --- hooks_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ service.go | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/hooks_test.go b/hooks_test.go index 0fa6355..7698abc 100644 --- a/hooks_test.go +++ b/hooks_test.go @@ -208,6 +208,55 @@ func TestOnMissingKey_Good(t *testing.T) { assert.Equal(t, "hooks_test.go", filepath.Base(captured.CallerFile)) } +func TestOnMissingKey_Good_SubjectArgs(t *testing.T) { + svc, err := New() + require.NoError(t, err) + prev := Default() + SetDefault(svc) + t.Cleanup(func() { + SetDefault(prev) + }) + svc.SetMode(ModeCollect) + + var captured MissingKey + OnMissingKey(func(m MissingKey) { + captured = m + }) + + _ = T("missing.subject.key", S("file", "config.yaml").Count(3).In("workspace").Formal()) + + assert.Equal(t, "missing.subject.key", captured.Key) + assert.Equal(t, "config.yaml", captured.Args["Subject"]) + assert.Equal(t, "file", captured.Args["Noun"]) + assert.Equal(t, 3, captured.Args["Count"]) + assert.Equal(t, "workspace", captured.Args["Location"]) + assert.Equal(t, FormalityFormal, captured.Args["Formality"]) +} + +func TestOnMissingKey_Good_TranslationContextArgs(t *testing.T) { + svc, err := New() + require.NoError(t, err) + prev := Default() + SetDefault(svc) + t.Cleanup(func() { + SetDefault(prev) + }) + svc.SetMode(ModeCollect) + + var captured MissingKey + OnMissingKey(func(m MissingKey) { + captured = m + }) + + _ = T("missing.context.key", C("navigation").WithGender("feminine").In("workspace").Formal()) + + assert.Equal(t, "missing.context.key", captured.Key) + assert.Equal(t, "navigation", captured.Args["Context"]) + assert.Equal(t, "feminine", captured.Args["Gender"]) + assert.Equal(t, "workspace", captured.Args["Location"]) + assert.Equal(t, FormalityFormal, captured.Args["Formality"]) +} + func TestDispatchMissingKey_Good_NoHandler(t *testing.T) { // Store nil handler (using correct type) missingKeyHandler.Store(MissingKeyHandler(nil)) diff --git a/service.go b/service.go index 4618041..8295646 100644 --- a/service.go +++ b/service.go @@ -545,12 +545,7 @@ func (s *Service) handleMissingKey(key string, args []any) string { case ModeStrict: panic(core.Sprintf("i18n: missing translation key %q", key)) case ModeCollect: - var argsMap map[string]any - if len(args) > 0 { - if m, ok := args[0].(map[string]any); ok { - argsMap = m - } - } + argsMap := missingKeyArgs(args) dispatchMissingKey(key, argsMap) return "[" + key + "]" default: @@ -558,6 +553,40 @@ func (s *Service) handleMissingKey(key string, args []any) string { } } +func missingKeyArgs(args []any) map[string]any { + if len(args) == 0 { + return nil + } + switch v := args[0].(type) { + case map[string]any: + return v + case *TranslationContext: + return missingKeyContextArgs(v) + case *Subject: + return missingKeySubjectArgs(v) + default: + return nil + } +} + +func missingKeyContextArgs(ctx *TranslationContext) map[string]any { + if ctx == nil { + return nil + } + data := templateDataForRendering(ctx) + result, _ := data.(map[string]any) + return result +} + +func missingKeySubjectArgs(subj *Subject) map[string]any { + if subj == nil { + return nil + } + data := templateDataForRendering(subj) + result, _ := data.(map[string]any) + return result +} + // Raw translates without i18n.* namespace magic. func (s *Service) Raw(messageID string, args ...any) string { s.mu.RLock()