fix(i18n): preserve structured missing-key args
All checks were successful
Security Scan / security (push) Successful in 12s
Test / test (push) Successful in 1m31s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 00:28:29 +00:00
parent a1a7b2d6fe
commit be1cf9bba8
2 changed files with 84 additions and 6 deletions

View file

@ -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))

View file

@ -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()