Refactor the i18n package for extensibility without breaking changes: - Add KeyHandler interface for pluggable namespace handlers - Add Loader interface for format-agnostic translation loading - Add TranslationContext for translation disambiguation - Implement 6 built-in handlers (Label, Progress, Count, Done, Fail, Numeric) - Update T() to use handler chain instead of hardcoded logic - Add handler management methods (AddHandler, PrependHandler, ClearHandlers) File reorganisation: - types.go: all type definitions - loader.go: Loader interface + FSLoader (from mutate.go, checks.go) - handler.go: KeyHandler interface + built-in handlers - context.go: TranslationContext + C() builder - hooks.go: renamed from actions.go - service.go: merged interfaces.go content Deleted: interfaces.go, mode.go, mutate.go, checks.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
173 lines
5.5 KiB
Go
173 lines
5.5 KiB
Go
package i18n
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestLabelHandler(t *testing.T) {
|
|
h := LabelHandler{}
|
|
|
|
t.Run("matches i18n.label prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.label.status"))
|
|
assert.True(t, h.Match("i18n.label.version"))
|
|
assert.False(t, h.Match("i18n.progress.build"))
|
|
assert.False(t, h.Match("cli.label.status"))
|
|
})
|
|
|
|
t.Run("handles label", func(t *testing.T) {
|
|
result := h.Handle("i18n.label.status", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "Status:", result)
|
|
})
|
|
}
|
|
|
|
func TestProgressHandler(t *testing.T) {
|
|
h := ProgressHandler{}
|
|
|
|
t.Run("matches i18n.progress prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.progress.build"))
|
|
assert.True(t, h.Match("i18n.progress.check"))
|
|
assert.False(t, h.Match("i18n.label.status"))
|
|
})
|
|
|
|
t.Run("handles progress without subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.progress.build", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "Building...", result)
|
|
})
|
|
|
|
t.Run("handles progress with subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.progress.check", []any{"config"}, func() string { return "fallback" })
|
|
assert.Equal(t, "Checking config...", result)
|
|
})
|
|
}
|
|
|
|
func TestCountHandler(t *testing.T) {
|
|
h := CountHandler{}
|
|
|
|
t.Run("matches i18n.count prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.count.file"))
|
|
assert.True(t, h.Match("i18n.count.repo"))
|
|
assert.False(t, h.Match("i18n.label.count"))
|
|
})
|
|
|
|
t.Run("handles count with number", func(t *testing.T) {
|
|
result := h.Handle("i18n.count.file", []any{5}, func() string { return "fallback" })
|
|
assert.Equal(t, "5 files", result)
|
|
})
|
|
|
|
t.Run("handles singular count", func(t *testing.T) {
|
|
result := h.Handle("i18n.count.file", []any{1}, func() string { return "fallback" })
|
|
assert.Equal(t, "1 file", result)
|
|
})
|
|
|
|
t.Run("handles no args", func(t *testing.T) {
|
|
result := h.Handle("i18n.count.file", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "file", result)
|
|
})
|
|
}
|
|
|
|
func TestDoneHandler(t *testing.T) {
|
|
h := DoneHandler{}
|
|
|
|
t.Run("matches i18n.done prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.done.delete"))
|
|
assert.True(t, h.Match("i18n.done.save"))
|
|
assert.False(t, h.Match("i18n.fail.delete"))
|
|
})
|
|
|
|
t.Run("handles done with subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.done.delete", []any{"config.yaml"}, func() string { return "fallback" })
|
|
// ActionResult title-cases the subject
|
|
assert.Equal(t, "Config.Yaml deleted", result)
|
|
})
|
|
|
|
t.Run("handles done without subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.done.delete", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "Deleted", result)
|
|
})
|
|
}
|
|
|
|
func TestFailHandler(t *testing.T) {
|
|
h := FailHandler{}
|
|
|
|
t.Run("matches i18n.fail prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.fail.delete"))
|
|
assert.True(t, h.Match("i18n.fail.save"))
|
|
assert.False(t, h.Match("i18n.done.delete"))
|
|
})
|
|
|
|
t.Run("handles fail with subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.fail.delete", []any{"config.yaml"}, func() string { return "fallback" })
|
|
assert.Equal(t, "Failed to delete config.yaml", result)
|
|
})
|
|
|
|
t.Run("handles fail without subject", func(t *testing.T) {
|
|
result := h.Handle("i18n.fail.delete", nil, func() string { return "fallback" })
|
|
assert.Contains(t, result, "Failed to delete")
|
|
})
|
|
}
|
|
|
|
func TestNumericHandler(t *testing.T) {
|
|
h := NumericHandler{}
|
|
|
|
t.Run("matches i18n.numeric prefix", func(t *testing.T) {
|
|
assert.True(t, h.Match("i18n.numeric.number"))
|
|
assert.True(t, h.Match("i18n.numeric.bytes"))
|
|
assert.False(t, h.Match("i18n.count.file"))
|
|
})
|
|
|
|
t.Run("handles number format", func(t *testing.T) {
|
|
result := h.Handle("i18n.numeric.number", []any{1234567}, func() string { return "fallback" })
|
|
assert.Equal(t, "1,234,567", result)
|
|
})
|
|
|
|
t.Run("handles bytes format", func(t *testing.T) {
|
|
result := h.Handle("i18n.numeric.bytes", []any{1024}, func() string { return "fallback" })
|
|
assert.Equal(t, "1 KB", result)
|
|
})
|
|
|
|
t.Run("handles ordinal format", func(t *testing.T) {
|
|
result := h.Handle("i18n.numeric.ordinal", []any{3}, func() string { return "fallback" })
|
|
assert.Equal(t, "3rd", result)
|
|
})
|
|
|
|
t.Run("falls through on no args", func(t *testing.T) {
|
|
result := h.Handle("i18n.numeric.number", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "fallback", result)
|
|
})
|
|
|
|
t.Run("falls through on unknown format", func(t *testing.T) {
|
|
result := h.Handle("i18n.numeric.unknown", []any{123}, func() string { return "fallback" })
|
|
assert.Equal(t, "fallback", result)
|
|
})
|
|
}
|
|
|
|
func TestDefaultHandlers(t *testing.T) {
|
|
handlers := DefaultHandlers()
|
|
assert.Len(t, handlers, 6)
|
|
}
|
|
|
|
func TestRunHandlerChain(t *testing.T) {
|
|
handlers := DefaultHandlers()
|
|
|
|
t.Run("label handler matches", func(t *testing.T) {
|
|
result := RunHandlerChain(handlers, "i18n.label.status", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "Status:", result)
|
|
})
|
|
|
|
t.Run("progress handler matches", func(t *testing.T) {
|
|
result := RunHandlerChain(handlers, "i18n.progress.build", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "Building...", result)
|
|
})
|
|
|
|
t.Run("falls back for unknown key", func(t *testing.T) {
|
|
result := RunHandlerChain(handlers, "cli.unknown", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "fallback", result)
|
|
})
|
|
|
|
t.Run("empty handler chain uses fallback", func(t *testing.T) {
|
|
result := RunHandlerChain(nil, "any.key", nil, func() string { return "fallback" })
|
|
assert.Equal(t, "fallback", result)
|
|
})
|
|
}
|