232 lines
5.9 KiB
Go
232 lines
5.9 KiB
Go
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
|
|
}
|
|
|
|
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.
|
|
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
|
|
}
|
|
}
|
|
clone.service = cloneTranslator(clone.service, clone.Locale)
|
|
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
|
|
}
|