Files
ThothII/tools/tht/internal/config/shell.go
T
Codex d8a29bfbdd Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
2026-09-13 14:26:39 +02:00

91 lines
2.7 KiB
Go

package config
import (
"errors"
"regexp"
"strings"
)
// Shell is the public presentation configuration, independent of deployment topology and auth.
type Shell struct {
Mode string `yaml:"mode" json:"mode"`
DefaultLocale string `yaml:"defaultLocale" json:"defaultLocale"`
Adapter string `yaml:"adapter,omitempty" json:"adapter,omitempty"`
}
// NormalizeDefaults resolves omitted installation settings before projecting them to the browser.
func (s *Shell) NormalizeDefaults() error {
if s.Mode == "" {
s.Mode = "embedded"
}
if s.DefaultLocale == "" {
s.DefaultLocale = "en"
}
if s.Mode != "embedded" && s.Mode != "full" {
return errors.New("shell.mode must be full or embedded")
}
if !wellFormedLocale(s.DefaultLocale) {
return errors.New("shell.defaultLocale must be a well-formed BCP47 language tag")
}
if s.Adapter != "" && s.Adapter != "omics-portal" {
return errors.New("shell.adapter must be omics-portal")
}
if s.Mode == "full" {
s.Adapter = ""
return nil
}
if s.Adapter == "" {
s.Adapter = "omics-portal"
}
return nil
}
// RFC 5646 section 2.1 grammar. Capture variants and extensions to reject duplicates.
// Validate syntax without consulting a language registry or the frontend's available catalogs.
var localePattern = regexp.MustCompile(`^(?:` +
`(?:[a-z]{2,3}(?:-[a-z]{3}){0,3}|[a-z]{4}|[a-z]{5,8})` + // language / extlang
`(?:-[a-z]{4})?(?:-(?:[a-z]{2}|[0-9]{3}))?` + // script / region
`((?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*)` + // variants
`((?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*)` + // extensions
`(?:-x(?:-[a-z0-9]{1,8})+)?` + // private use after a language
`|x(?:-[a-z0-9]{1,8})+)$`) // private use alone
func wellFormedLocale(locale string) bool {
// BCP47 is ASCII. Check before case folding, which could otherwise map Unicode to ASCII.
for _, character := range locale {
if character > 127 {
return false
}
}
tag := strings.ToLower(locale)
// These grandfathered irregular tags do not match the normal language-tag grammar.
switch tag {
case "en-gb-oed", "i-ami", "i-bnn", "i-default", "i-enochian", "i-hak", "i-klingon",
"i-lux", "i-mingo", "i-navajo", "i-pwn", "i-tao", "i-tay", "i-tsu",
"sgn-be-fr", "sgn-be-nl", "sgn-ch-de":
return true
}
parts := localePattern.FindStringSubmatch(tag)
if parts == nil {
return false
}
variants := make(map[string]bool)
for _, variant := range strings.Split(strings.TrimPrefix(parts[1], "-"), "-") {
if variants[variant] {
return false
}
variants[variant] = true
}
singletons := make(map[string]bool)
for _, subtag := range strings.Split(parts[2], "-") {
if len(subtag) != 1 {
continue
}
if singletons[subtag] {
return false
}
singletons[subtag] = true
}
return true
}