package html // context.go: Translator provides Text() lookups for a rendering context. // Example: type service struct{} // // func (service) T(key string, args ...any) string { return key } // // ctx := NewContextWithService(service{}, "en-GB") type Translator interface { T(key string, args ...any) string } // 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. type Context struct { Identity string Locale string Entitlements func(feature string) bool Data map[string]any service Translator } // Clone returns a shallow copy of the context with an independent Data map. // Example: next := ctx.Clone().SetData("theme", "dark"). func (ctx *Context) Clone() *Context { if ctx == nil { return nil } clone := *ctx if ctx.Data != nil { clone.Data = make(map[string]any, len(ctx.Data)) for key, value := range ctx.Data { clone.Data[key] = value } } return &clone } func applyLocaleToService(svc Translator, locale string) { if svc == nil || locale == "" { return } if setter, ok := svc.(interface{ SetLanguage(string) error }); ok { base := locale for i := 0; i < len(base); i++ { if base[i] == '-' || base[i] == '_' { base = base[:i] break } } _ = setter.SetLanguage(base) } } // ensureContextDefaults initialises lazily-created context fields. func ensureContextDefaults(ctx *Context) { if ctx == nil { return } if ctx.Data == nil { ctx.Data = make(map[string]any) } } // normaliseContext ensures render paths always have a usable context. // A nil input is replaced with a fresh default context. func normaliseContext(ctx *Context) *Context { if ctx != nil { ensureContextDefaults(ctx) return ctx } return NewContext() } // context.go: NewContext creates a new rendering context with sensible defaults. // Example: ctx := NewContext("en-GB"). // An optional locale may be provided as the first argument. func NewContext(locale ...string) *Context { ctx := &Context{ Data: make(map[string]any), service: newDefaultTranslator(), } if len(locale) > 0 { ctx.Locale = locale[0] applyLocaleToService(ctx.service, ctx.Locale) } return ctx } // context.go: NewContextWithService creates a rendering context backed by a specific translator. // Example: ctx := NewContextWithService(svc, "fr-FR"). // An optional locale may be provided as the second argument. func NewContextWithService(svc Translator, locale ...string) *Context { ctx := &Context{ Data: make(map[string]any), service: svc, } if len(locale) > 0 { ctx.Locale = locale[0] } applyLocaleToService(ctx.service, ctx.Locale) return ctx } // SetService swaps the translator used by the context and reapplies the // current locale to it. // Example: ctx.SetService(svc). func (ctx *Context) SetService(svc Translator) *Context { if ctx == nil { return nil } ensureContextDefaults(ctx) ctx.service = svc applyLocaleToService(ctx.service, ctx.Locale) return ctx } // SetEntitlements updates the feature gate callback used by Entitled nodes and // returns the same context. // Example: ctx.SetEntitlements(func(feature string) bool { return feature == "premium" }). func (ctx *Context) SetEntitlements(entitlements func(feature string) bool) *Context { if ctx == nil { return nil } ensureContextDefaults(ctx) ctx.Entitlements = entitlements return ctx } // SetIdentity updates the context identity and returns the same context. // Example: ctx.SetIdentity("user-123"). func (ctx *Context) SetIdentity(identity string) *Context { if ctx == nil { return nil } ensureContextDefaults(ctx) ctx.Identity = identity return ctx } // SetData stores an arbitrary per-request value on the context and returns the // same context. // Example: ctx.SetData("theme", "dark"). func (ctx *Context) SetData(key string, value any) *Context { if ctx == nil { return nil } ensureContextDefaults(ctx) ctx.Data[key] = value return ctx } // WithData returns a cloned context with one additional data value set. // Example: next := ctx.WithData("theme", "dark"). func (ctx *Context) WithData(key string, value any) *Context { clone := ctx.Clone() if clone == nil { return nil } clone.SetData(key, value) return clone } // WithIdentity returns a cloned context with a different identity value. // Example: next := ctx.WithIdentity("user-123"). func (ctx *Context) WithIdentity(identity string) *Context { clone := ctx.Clone() if clone == nil { return nil } clone.SetIdentity(identity) return clone } // WithLocale returns a cloned context with a different locale value. // Example: next := ctx.WithLocale("fr-FR"). func (ctx *Context) WithLocale(locale string) *Context { clone := ctx.Clone() if clone == nil { return nil } clone.SetLocale(locale) return clone } // WithService returns a cloned context with a different translator. // Example: next := ctx.WithService(svc). func (ctx *Context) WithService(svc Translator) *Context { clone := ctx.Clone() if clone == nil { return nil } clone.SetService(svc) return clone } // WithEntitlements returns a cloned context with a different feature gate callback. // Example: next := ctx.WithEntitlements(func(feature string) bool { return feature == "premium" }). func (ctx *Context) WithEntitlements(entitlements func(feature string) bool) *Context { clone := ctx.Clone() if clone == nil { return nil } clone.SetEntitlements(entitlements) return clone } // SetLocale updates the context locale and reapplies it to the active // translator. // Example: ctx.SetLocale("en-US"). func (ctx *Context) SetLocale(locale string) *Context { if ctx == nil { return nil } ensureContextDefaults(ctx) ctx.Locale = locale applyLocaleToService(ctx.service, ctx.Locale) return ctx }