fix(html): clone mutable translators in context copies
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
575150d686
commit
7cbf678738
6 changed files with 68 additions and 10 deletions
|
|
@ -10,6 +10,10 @@ type Translator interface {
|
|||
T(key string, args ...any) string
|
||||
}
|
||||
|
||||
type translatorCloner interface {
|
||||
Clone() Translator
|
||||
}
|
||||
|
||||
// context.go: Context carries rendering state through the node tree.
|
||||
// Example: NewContext("en-GB") initialises locale-specific rendering state.
|
||||
// Locale and translator selection happen at construction time.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@ func (t *localeTranslator) SetLanguage(language string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *localeTranslator) Clone() Translator {
|
||||
if t == nil {
|
||||
return (*localeTranslator)(nil)
|
||||
}
|
||||
|
||||
clone := *t
|
||||
return &clone
|
||||
}
|
||||
|
||||
func TestContext_NewContextWithService_AppliesLocale(t *testing.T) {
|
||||
svc := &localeTranslator{}
|
||||
ctx := NewContextWithService(svc, "fr-FR")
|
||||
|
|
@ -159,8 +168,8 @@ func TestContext_CloneCopiesDataWithoutSharingMap(t *testing.T) {
|
|||
if clone == ctx {
|
||||
t.Fatal("Clone should return a distinct context instance")
|
||||
}
|
||||
if clone.service != ctx.service {
|
||||
t.Fatal("Clone should preserve the active translator")
|
||||
if clone.service == ctx.service {
|
||||
t.Fatal("Clone should duplicate cloneable translators")
|
||||
}
|
||||
if clone.Locale != ctx.Locale {
|
||||
t.Fatalf("Clone should preserve locale, got %q want %q", clone.Locale, ctx.Locale)
|
||||
|
|
@ -193,6 +202,31 @@ func TestContext_CloneDoesNotShareDefaultTranslator(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext_CloneClonesMutableTranslator(t *testing.T) {
|
||||
svc := &localeTranslator{}
|
||||
ctx := NewContextWithService(svc, "en-GB")
|
||||
|
||||
clone := ctx.Clone()
|
||||
if clone == nil {
|
||||
t.Fatal("Clone should return a context")
|
||||
}
|
||||
if clone.service == ctx.service {
|
||||
t.Fatal("Clone should isolate cloneable translators")
|
||||
}
|
||||
|
||||
clone.SetLocale("fr-FR")
|
||||
|
||||
if got := svc.language; got != "en" {
|
||||
t.Fatalf("Clone should not mutate the original translator, got %q", got)
|
||||
}
|
||||
if got := Text("prompt.yes").Render(ctx); got != "y" {
|
||||
t.Fatalf("Clone should leave original context translation unchanged, got %q", got)
|
||||
}
|
||||
if got := Text("prompt.yes").Render(clone); got != "o" {
|
||||
t.Fatalf("Clone should reapply locale to the cloned translator, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_WithDataReturnsClonedContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
ctx.SetData("theme", "dark")
|
||||
|
|
@ -251,11 +285,14 @@ func TestContext_WithLocaleReturnsClonedContext(t *testing.T) {
|
|||
if got := next.Locale; got != "fr-FR" {
|
||||
t.Fatalf("WithLocale should set the requested locale on the clone, got %q", got)
|
||||
}
|
||||
if got := next.service; got != ctx.service {
|
||||
t.Fatal("WithLocale should preserve the active translator on the clone")
|
||||
if got := next.service; got == ctx.service {
|
||||
t.Fatal("WithLocale should duplicate cloneable translators on the clone")
|
||||
}
|
||||
if svc.language != "fr" {
|
||||
t.Fatalf("WithLocale should reapply locale to the cloned service, got %q", svc.language)
|
||||
if svc.language != "en" {
|
||||
t.Fatalf("WithLocale should not mutate the original translator, got %q", svc.language)
|
||||
}
|
||||
if got := Text("prompt.yes").Render(next); got != "o" {
|
||||
t.Fatalf("WithLocale should reapply locale to the cloned service, got %q", got)
|
||||
}
|
||||
if got := next.Data["theme"]; got != "dark" {
|
||||
t.Fatalf("WithLocale should preserve existing data on the clone, got %v", got)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ func (t *defaultTranslator) SetLanguage(language string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *defaultTranslator) Clone() Translator {
|
||||
if t == nil {
|
||||
return (*defaultTranslator)(nil)
|
||||
}
|
||||
|
||||
clone := *t
|
||||
return &clone
|
||||
}
|
||||
|
||||
func newDefaultTranslator() Translator {
|
||||
return &defaultTranslator{}
|
||||
}
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -14,12 +14,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
|||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ func cloneTranslator(svc Translator, locale string) Translator {
|
|||
return nil
|
||||
}
|
||||
|
||||
if cloner, ok := svc.(translatorCloner); ok && cloner != nil {
|
||||
if clone := cloner.Clone(); clone != nil {
|
||||
return clone
|
||||
}
|
||||
}
|
||||
|
||||
if current, ok := svc.(*i18n.Service); ok && current != nil {
|
||||
clone := &i18n.Service{}
|
||||
applyLocaleToService(clone, locale)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ func cloneTranslator(svc Translator, _ string) Translator {
|
|||
return nil
|
||||
}
|
||||
|
||||
if cloner, ok := svc.(translatorCloner); ok && cloner != nil {
|
||||
if clone := cloner.Clone(); clone != nil {
|
||||
return clone
|
||||
}
|
||||
}
|
||||
|
||||
if current, ok := svc.(*defaultTranslator); ok && current != nil {
|
||||
clone := *current
|
||||
return &clone
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue