v0.258.0: the last four Hungarian things an English household met
gates / gates (push) Successful in 24s

R-589 the update badge, R-590 the data-folder backup sentence, R-573 the two channel
banners, R-572 two dead helpers. All four were built in Go, which is why neither the
template parity fixtures nor TestI18nEnglishPages could see them; slice 5's LIVE proof
is what found them.

R-590 is the one that matters most: it is a promise about the customer's files. It
said, in Hungarian and under an already-English folder label, that a drop-zone is
temporary and unbacked. The test now asserts the CONSEQUENCE in both languages — and
that the two languages do not produce the same string, which would mean the English
fell back.

R-572 is not what its row said. The row claimed a template renders "vasárnap" on an
English page; measured, NO template and no Go file called pruneLabel or
nextPruneLabel. They were dead func-map entries returning Hungarian, so they are
deleted rather than translated — translating dead code would add machinery with no
reader and a test pinning a fiction. Deletion is fail-loud and that was proven: a
template naming the removed function panics loadTemplates at startup.

Five red-proofs. One of them says something about the gate rather than the code: the
Go-parity gate does not measure localeFuncs keys against the base capture, so the
citation to TestLocaleFuncsHungarianBundleMatchesFuncMap is what carries them — the
test was extended to make that citation true.

MinAgent: 0.131.0 (unchanged). Hungarian byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-20 18:10:53 +02:00
parent ecefdf2dc5
commit 7c4a33b6d1
12 changed files with 425 additions and 74 deletions
+54 -15
View File
@@ -5,6 +5,7 @@ import (
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/appbackup"
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
@@ -27,7 +28,7 @@ func TestAppInfo_DataPathCardRenders(t *testing.T) {
cards := []DataPathCard{{
Label: "Beolvasandó dokumentumok",
Link: importFolderLink("demo-felhom.eu", "paperless"),
Consequence: consequenceFor(appbackup.ClassExcluded, stacks.RoleImport),
Consequence: consequenceFor(appbackup.ClassExcluded, stacks.RoleImport, bundleMsg(t, "hu")),
IsImport: true,
FreeSpace: "44.8 GB szabad",
}}
@@ -69,26 +70,64 @@ func TestAppInfo_NoDataPathCardsRendersNothing(t *testing.T) {
}
}
// bundleMsg is the production lookup the handler uses, bound to one language.
func bundleMsg(t *testing.T, lang string) func(string) string {
t.Helper()
b, err := i18n.Shared()
if err != nil {
t.Fatalf("bundle: %v", err)
}
return func(key string) string { return b.Msg(lang, key) }
}
// Fork-4: the consequence line is CLASS-driven, so the UI can never promise a backup the engines do
// not make. `excluded` is dropped at every tier — it must say so.
//
// R-590 (v0.258.0): the sentence now follows the household's language, so the CONSEQUENCE is
// asserted in BOTH — the mechanism (does it read the bundle?) is not the property that matters. It
// is a promise about the customer's files: an English household that cannot read „nem készül róla
// biztonsági mentés" may leave originals in a drop-zone the backup filter discards at every tier.
func TestConsequenceIsClassDriven(t *testing.T) {
imp := consequenceFor(appbackup.ClassExcluded, stacks.RoleImport)
if !strings.Contains(imp, "törli innen") || !strings.Contains(imp, "nem készül róla biztonsági mentés") {
t.Errorf("an excluded import folder must say it is temporary AND unbacked: %q", imp)
cases := []struct {
lang string
tempAndUnbacked []string // an excluded import folder must say BOTH of these
promise string // a kept folder must say this
denial string // and must NOT say this
}{
{"hu", []string{"törli innen", "nem készül róla biztonsági mentés"},
"Biztonsági mentés készül", "nem készül"},
{"en", []string{"deletes them from here", "is not backed up"},
"It is backed up", "not backed up"},
}
for _, cls := range []appbackup.BindClass{appbackup.ClassMandatory, appbackup.ClassOptional} {
lib := consequenceFor(cls, stacks.RoleLibrary)
if !strings.Contains(lib, "Biztonsági mentés készül") {
t.Errorf("class %q must promise a backup: %q", cls, lib)
for _, c := range cases {
msg := bundleMsg(t, c.lang)
imp := consequenceFor(appbackup.ClassExcluded, stacks.RoleImport, msg)
for _, want := range c.tempAndUnbacked {
if !strings.Contains(imp, want) {
t.Errorf("[%s] an excluded import folder must say %q: %q", c.lang, want, imp)
}
}
if strings.Contains(lib, "nem készül") {
t.Errorf("class %q must NOT say unbacked: %q", cls, lib)
for _, cls := range []appbackup.BindClass{appbackup.ClassMandatory, appbackup.ClassOptional} {
lib := consequenceFor(cls, stacks.RoleLibrary, msg)
if !strings.Contains(lib, c.promise) {
t.Errorf("[%s] class %q must promise a backup: %q", c.lang, cls, lib)
}
if strings.Contains(lib, c.denial) {
t.Errorf("[%s] class %q must NOT say unbacked: %q", c.lang, cls, lib)
}
}
// An unclassified bind says NOTHING rather than guessing — an unverified backup promise
// about a customer's files is worse than no sentence. In every language.
if got := consequenceFor("", stacks.RoleLibrary, msg); got != "" {
t.Errorf("[%s] an unclassified path must produce no promise, got %q", c.lang, got)
}
}
// An unclassified bind says NOTHING rather than guessing — an unverified backup promise about a
// customer's files is worse than no sentence.
if got := consequenceFor("", stacks.RoleLibrary); got != "" {
t.Errorf("an unclassified path must produce no promise, got %q", got)
// And the two languages must not say the same thing — that would mean the bundle fell back and
// the English household is reading Hungarian, which is the defect this row was opened for.
hu := consequenceFor(appbackup.ClassMandatory, stacks.RoleLibrary, bundleMsg(t, "hu"))
en := consequenceFor(appbackup.ClassMandatory, stacks.RoleLibrary, bundleMsg(t, "en"))
if hu == en {
t.Errorf("the English consequence line fell back to Hungarian: %q", en)
}
}
@@ -99,7 +138,7 @@ func TestBuildDataPathCards_UndeployedYieldsNothing(t *testing.T) {
Meta: stacks.Metadata{Slug: "paperless-ngx", DataPaths: []stacks.DataPath{
{Path: "paperless", Root: appbackup.RootImport, Role: stacks.RoleImport, Label: "x"},
}}}
if got := s.buildDataPathCards(st); got != nil {
if got := s.buildDataPathCards(st, "hu"); got != nil {
t.Errorf("an undeployed app must get no folder card, got %v", got)
}
}