refactor(10.2-01): nest framework packages under modules

- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
Jakub Zych
2026-09-28 02:21:02 +02:00
parent ac1f6d14f4
commit 5e50b166ef
277 changed files with 303 additions and 303 deletions

View File

@@ -0,0 +1,333 @@
package phrasebook
import (
"io/fs"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"testing"
"testing/fstest"
"git.golem15.com/golem15/summercms/modules/backpack"
)
func TestPhase10Forms(t *testing.T) {
cat := NewCatalog()
if err := cat.Load("acme.demo", fstest.MapFS{
"lang/en/lang.yaml": {Data: []byte(`plain: Hello :name
plural:
one: ":count apple"
other: ":count apples"
pipe: ":count apple|:count apples"
exact: "{0} No apples|[1,*] :count apples"
`)},
"lang/pl/lang.yaml": {Data: []byte(`pipe: ":count jabłko|:count jabłka|:count jabłek|:count jabłka"
`)},
}); err != nil {
t.Fatal(err)
}
tr := NewTranslator(cat, Options{Locale: "en", Fallback: "en"})
check := func(locale, key string, want map[string]string) {
t.Helper()
got, ok := tr.Forms(locale, key)
if !ok {
t.Fatalf("%s %s: not convertible", locale, key)
}
if len(got) != len(want) {
t.Fatalf("%s %s = %v want %v", locale, key, got, want)
}
for cat, text := range want {
if got[cat] != text {
t.Fatalf("%s %s = %v want %v", locale, key, got, want)
}
}
}
check("en", "acme.demo::lang.plain", map[string]string{"other": "Hello :name"})
check("en", "acme.demo::lang.plural", map[string]string{"one": ":count apple", "other": ":count apples"})
check("en", "acme.demo::lang.pipe", map[string]string{"one": ":count apple", "other": ":count apples"})
check("pl", "acme.demo::lang.pipe", map[string]string{"one": ":count jabłko", "few": ":count jabłka", "many": ":count jabłek", "other": ":count jabłka"})
// pl falls back to en for a key it does not define.
check("pl", "acme.demo::lang.plain", map[string]string{"other": "Hello :name"})
if forms, ok := tr.Forms("en", "acme.demo::lang.exact"); ok {
t.Fatalf("exact/range pipe converted to %v", forms)
}
if forms, ok := tr.Forms("en", "acme.demo::lang.missing"); ok {
t.Fatalf("missing key converted to %v", forms)
}
got, ok := tr.Forms("en", "acme.demo::lang.plural")
if !ok {
t.Fatal("plural not convertible")
}
got["one"] = "mutated"
if again, _ := tr.Forms("en", "acme.demo::lang.plural"); again["one"] != ":count apple" {
t.Fatal("Forms returned the catalog's own map")
}
}
type overridePlugin struct {
id string
lang fs.FS
overrides fs.FS
}
func (p overridePlugin) ID() string { return p.id }
func (p overridePlugin) LangFS() fs.FS { return p.lang }
func (p overridePlugin) LangOverridesFS() fs.FS { return p.overrides }
func activateWith(t *testing.T, plugins ...overridePlugin) (*Translator, error) {
t.Helper()
app := backpack.New(nil)
if err := Activate(app, plugins); err != nil {
return nil, err
}
tr, ok := app.Lookup[*Translator]()
if !ok || tr == nil {
t.Fatal("translator was not published")
}
return tr, nil
}
func TestPhase10LangOverride(t *testing.T) {
demo := fstest.MapFS{"lang/en/lang.yaml": {Data: []byte("title: Demo\n")}}
tr, err := activateWith(t, overridePlugin{id: "acme.demo", lang: demo, overrides: fstest.MapFS{
"lang/pl/backend/lang.yaml": {Data: []byte("form:\n save: Zachowaj\n extra: Dodatkowy\n")},
"lang/de/backend/lang.yaml": {Data: []byte("form:\n save: Speichern\n")},
"lang/en/acme.demo/lang.yaml": {Data: []byte("title: Demo app\n")},
}})
if err != nil {
t.Fatalf("activate: %v", err)
}
for _, tc := range []struct{ locale, key, want string }{
{"pl", "backend::lang.form.save", "Zachowaj"},
{"pl", "backend::lang.form.extra", "Dodatkowy"},
{"pl", "backend::lang.form.cancel", "Anuluj"},
{"de", "backend::lang.form.save", "Speichern"},
{"en", "backend::lang.form.save", "Save"},
{"en", "acme.demo::lang.title", "Demo app"},
} {
if got := tr.GetIn(tc.locale, tc.key, nil); got != tc.want {
t.Fatalf("%s %s = %q want %q", tc.locale, tc.key, got, tc.want)
}
}
if bundle := tr.Bundle("de", "backend::lang."); bundle["backend::lang.form.save"]["other"] != "Speichern" || bundle["backend::lang.form.cancel"]["other"] != "Cancel" {
t.Fatalf("de bundle did not merge the fallback: save=%v cancel=%v", bundle["backend::lang.form.save"], bundle["backend::lang.form.cancel"])
}
for _, tc := range []struct {
name string
overrides fstest.MapFS
want []string
}{
{"missing namespace segment", fstest.MapFS{"lang/pl/lang.yaml": {Data: []byte("a: b\n")}}, []string{"acme.demo", "malformed override path"}},
{"not yaml", fstest.MapFS{"lang/pl/backend/lang.txt": {Data: []byte("a: b\n")}}, []string{"acme.demo", "malformed override path"}},
{"too deep", fstest.MapFS{"lang/pl/backend/sub/lang.yaml": {Data: []byte("a: b\n")}}, []string{"acme.demo", "malformed override path"}},
{"unknown namespace", fstest.MapFS{"lang/pl/acme.missing/lang.yaml": {Data: []byte("a: b\n")}}, []string{"acme.demo", "acme.missing"}},
{"unconvertible backend key", fstest.MapFS{"lang/en/backend/lang.yaml": {Data: []byte("list:\n results: \"{0} No results|[1,*] :count results\"\n")}}, []string{"backend::lang.list.results", "CLDR"}},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := activateWith(t, overridePlugin{id: "acme.demo", lang: demo, overrides: tc.overrides})
if err == nil {
t.Fatal("activation accepted a bad override")
}
for _, want := range tc.want {
if !strings.Contains(err.Error(), want) {
t.Fatalf("err=%v missing %q", err, want)
}
}
})
}
}
var spaKey = regexp.MustCompile(`backend::lang\.[A-Za-z0-9_.]*[A-Za-z0-9_]`)
// TestPhase10SPAKeysResolve walks the admin SPA sources and resolves every
// backend::lang key literal in pl and en, each without falling back to the
// other locale.
func TestPhase10SPAKeysResolve(t *testing.T) {
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("caller")
}
root := filepath.Join(filepath.Dir(file), "..", "admin", "src")
keys := map[string]bool{}
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !(strings.HasSuffix(path, ".ts") || strings.HasSuffix(path, ".vue")) {
return nil
}
raw, err := os.ReadFile(path)
if err != nil {
return err
}
for _, key := range spaKey.FindAllString(string(raw), -1) {
keys[key] = true
}
return nil
})
if err != nil {
t.Fatal(err)
}
if len(keys) < 10 {
t.Fatalf("found only %d backend::lang keys under %s", len(keys), root)
}
sorted := make([]string, 0, len(keys))
for key := range keys {
sorted = append(sorted, key)
}
sort.Strings(sorted)
cat := NewCatalog()
if err := cat.Load("backend", backendLang()); err != nil {
t.Fatal(err)
}
for _, locale := range []string{"pl", "en"} {
tr := NewTranslator(cat, Options{Locale: locale, Fallback: locale})
for _, key := range sorted {
forms, ok := tr.Forms(locale, key)
if !ok || forms["other"] == "" {
t.Errorf("%s: %s does not resolve", locale, key)
}
}
}
}
// TestPhase10LangPrecedence pins the D-20 layering: an override beats the
// plugin's own strings and the framework's backend strings, a later plugin's
// override beats an earlier one, and an override can add a whole locale.
func TestPhase10LangPrecedence(t *testing.T) {
demoLang := fstest.MapFS{
"lang/en/lang.yaml": {Data: []byte("title: Demo\nsubtitle: Widgets\n")},
"lang/pl/lang.yaml": {Data: []byte("title: Demo PL\n")},
}
first := overridePlugin{id: "acme.demo", lang: demoLang, overrides: fstest.MapFS{
"lang/pl/backend/lang.yaml": {Data: []byte("form:\n save: Pierwszy\n cancel: Pierwsze anuluj\n")},
"lang/pl/acme.demo/lang.yaml": {Data: []byte("title: Demo nadpisane\n")},
}}
second := overridePlugin{id: "acme.site", overrides: fstest.MapFS{
"lang/pl/backend/lang.yaml": {Data: []byte("form:\n save: Drugi\n")},
"lang/fr/backend/lang.yaml": {Data: []byte("form:\n save: Enregistrer\n")},
"lang/fr/acme.demo/lang.yaml": {Data: []byte("title: Démo\n")},
}}
tr, err := activateWith(t, first, second)
if err != nil {
t.Fatalf("activate: %v", err)
}
for _, tc := range []struct{ locale, key, want string }{
// Framework string untouched by any override.
{"pl", "backend::lang.form.delete", "Usuń"},
// Override beats framework; the later plugin's override wins.
{"pl", "backend::lang.form.save", "Drugi"},
{"pl", "backend::lang.form.cancel", "Pierwsze anuluj"},
// Override beats the plugin's own string; the plugin beats its fallback.
{"pl", "acme.demo::lang.title", "Demo nadpisane"},
{"en", "acme.demo::lang.title", "Demo"},
{"pl", "acme.demo::lang.subtitle", "Widgets"},
// A locale added only through overrides.
{"fr", "backend::lang.form.save", "Enregistrer"},
{"fr", "acme.demo::lang.title", "Démo"},
{"fr", "backend::lang.form.delete", "Delete"},
} {
if got := tr.GetIn(tc.locale, tc.key, nil); got != tc.want {
t.Fatalf("%s %s = %q want %q", tc.locale, tc.key, got, tc.want)
}
}
if got := tr.Resolved("fr", "backend::lang."); got != "fr" {
t.Fatalf("Resolved(fr) = %q", got)
}
if got := tr.Resolved("de", "backend::lang."); got != "en" {
t.Fatalf("Resolved(de) = %q, want the en fallback", got)
}
}
// TestPhase10BundleMerge proves Bundle layers the requested locale over its
// region parent and the fallback, filters by prefix and skips keys that do
// not convert to CLDR forms.
func TestPhase10BundleMerge(t *testing.T) {
cat := NewCatalog()
if err := cat.Load("acme.demo", fstest.MapFS{
"lang/en/lang.yaml": {Data: []byte("a: A en\nb: B en\nc: C en\nexact: \"{0} none|[1,*] :count\"\n")},
"lang/pt/lang.yaml": {Data: []byte("a: A pt\nb: B pt\n")},
"lang/pt-BR/lang.yaml": {Data: []byte("a: A pt-BR\n")},
}); err != nil {
t.Fatal(err)
}
if err := cat.Load("acme.other", fstest.MapFS{"lang/en/lang.yaml": {Data: []byte("a: other\n")}}); err != nil {
t.Fatal(err)
}
tr := NewTranslator(cat, Options{Locale: "en", Fallback: "en"})
bundle := tr.Bundle("pt-BR", "acme.demo::")
want := map[string]string{
"acme.demo::lang.a": "A pt-BR",
"acme.demo::lang.b": "B pt",
"acme.demo::lang.c": "C en",
}
if len(bundle) != len(want) {
t.Fatalf("bundle = %v", bundle)
}
for key, text := range want {
if bundle[key]["other"] != text {
t.Fatalf("%s = %v want %q", key, bundle[key], text)
}
}
if _, ok := bundle["acme.demo::lang.exact"]; ok {
t.Fatal("an exact/range pipe entered the bundle")
}
if got := tr.Resolved("pt-BR", "acme.demo::"); got != "pt-BR" {
t.Fatalf("Resolved = %q", got)
}
var nilTr *Translator
if b := nilTr.Bundle("en", ""); len(b) != 0 {
t.Fatalf("nil translator bundle = %v", b)
}
if _, ok := nilTr.Forms("en", "x"); ok {
t.Fatal("nil translator produced forms")
}
}
// TestPhase10FormsShapes converts every entry shape the catalog stores: plain
// text, a YAML plural map, a category pipe, and pipes that cannot convert
// (exact, range, or without an other form).
func TestPhase10FormsShapes(t *testing.T) {
for _, tc := range []struct {
name string
e entry
want map[string]string
ok bool
}{
{"plain", entry{text: "Hi"}, map[string]string{"other": "Hi"}, true},
{"empty plain", entry{}, map[string]string{"other": ""}, true},
{"plural map", entry{plurals: map[string]string{"one": "1", "other": "n"}}, map[string]string{"one": "1", "other": "n"}, true},
{"category pipe", entry{pipes: []pipePart{{cat: "one", text: "1"}, {cat: "other", text: "n"}}}, map[string]string{"one": "1", "other": "n"}, true},
{"pipe without other", entry{pipes: []pipePart{{cat: "one", text: "1"}}}, nil, false},
{"pipe with an uncategorised part", entry{pipes: []pipePart{{text: "x"}, {cat: "other", text: "n"}}}, nil, false},
} {
t.Run(tc.name, func(t *testing.T) {
got, ok := entryForms(tc.e)
if ok != tc.ok {
t.Fatalf("ok=%v want %v (%v)", ok, tc.ok, got)
}
if len(got) != len(tc.want) {
t.Fatalf("forms=%v want %v", got, tc.want)
}
for cat, text := range tc.want {
if got[cat] != text {
t.Fatalf("forms=%v want %v", got, tc.want)
}
}
})
}
one, two := 0.0, 1.0
if _, ok := entryForms(entry{pipes: []pipePart{{exact: &one, text: "none"}, {cat: "other", text: "n"}}}); ok {
t.Fatal("exact pipe converted")
}
if _, ok := entryForms(entry{pipes: []pipePart{{lo: &two, text: "some"}, {cat: "other", text: "n"}}}); ok {
t.Fatal("range pipe converted")
}
}