package web import ( "net/http/httptest" "os" "path/filepath" "regexp" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/i18n" ) var goStringLit = regexp.MustCompile(`"(?:[^"\\]|\\.)*"`) // R-516 (2026-10-06 night) — the formal („ön") forms that lived in GO LITERALS, where the bundle gate // cannot read them: the escrow and share handlers, the export upload, the network-storage attach and // remove errors, the uid refusal. They are bundle keys now, te-form in Hungarian, with English. // // Left on purpose (named so the scan's exemptions are not a wish): internal/setup and // internal/recovery (the setup wizard and the household's recovery-info.txt — R-554, the operator's // call); internal/notify and internal/fillwatch (the SMART and fill-watch texts are the hub event's // MESSAGE — wire text the hub mails as is; converting them needs the household-copy producer shape). // // COMPANION RED-PROOF (observed): put back „próbálja újra" in share_handlers.go → the source scan fails // naming the file and the stem. Restored. func TestR516_GoLiteralFormalFormsAreGone(t *testing.T) { b, err := i18n.Shared() if err != nil { t.Fatal(err) } want := map[string]string{ "api.escrow.too_many_attempts": "Túl sok sikertelen próbálkozás, próbáld újra 1 perc múlva.", "api.escrow.no_active_job": "Nincs aktív helyreállítási folyamat — előbb indítsd el a kódkészítést.", "api.share.form_invalid": "Érvénytelen űrlap — töltsd újra az oldalt.", "api.export.upload_size_mismatch": "A feltöltött méret nem egyezik — próbáld újra.", "api.netstorage.add.smb_share": "A megadott SMB-megosztás nem található a szerveren. Ellenőrizd a megosztás nevét.", "api.netstorage.add.busy": "Már folyamatban van egy csatlakoztatás. Várd meg, amíg befejeződik.", "api.netstorage.uid_range": "Az alkalmazás felhasználói azonosítója (uid) érvénytelen. Adj meg 1 és 65533 közötti értéket — a legtöbb médiaalkalmazás az 1000-est használja.", "api.netstorage.add.not_network_fs": "A hálózati tárhely csatolása a rendszeren belül nem jött létre megfelelően. Próbáld újra a csatlakoztatást; ha a hiba ismétlődik, jelezd az üzemeltetőnek.", } for key, w := range want { if got, fellBack, ok := b.Text("hu", key); !ok || fellBack || got != w { t.Errorf("hu %s: %q, want %q", key, got, w) } if _, fellBack, ok := b.Text("en", key); !ok || fellBack { t.Errorf("en %s: missing (fell back to Hungarian)", key) } } // Source scan over the Go literals this row converted. ASCII-folded, so an accent cannot hide one. fold := strings.NewReplacer("á", "a", "é", "e", "í", "i", "ó", "o", "ö", "o", "ő", "o", "ú", "u", "ü", "u", "ű", "u") stems := []string{"probalja", "toltse ujra", "allitson be", "varja meg", "inditsa", "ellenorizze", "hasznalja az eszkoz", "kapcsolja be", "allitsa a", "jelezze az", "tavolitsa el vagy", "adjon meg", "valasszon csatlakoztatott"} skip := map[string]bool{"setup": true, "recovery": true, "notify": true, "fillwatch": true} root := filepath.Join("..") scanned := 0 err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { if skip[info.Name()] || info.Name() == "testdata" { return filepath.SkipDir } return nil } if !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") { return nil } data, rerr := os.ReadFile(p) if rerr != nil { return rerr } scanned++ for i, line := range strings.Split(string(data), "\n") { tl := strings.TrimSpace(line) if strings.HasPrefix(tl, "//") || !strings.Contains(tl, `"`) { continue } // Only literals with a space are sentences; a bundle KEY (no space) may carry a stem in its name. var sentences []string for _, m := range goStringLit.FindAllString(tl, -1) { if strings.Contains(m, " ") { sentences = append(sentences, m) } } lv := fold.Replace(strings.ToLower(strings.Join(sentences, " "))) for _, st := range stems { if strings.Contains(lv, st) { t.Errorf("%s:%d still carries the formal %q: %s", p, i+1, st, tl) } } } return nil }) if err != nil { t.Fatal(err) } if scanned < 50 { t.Fatalf("the scan read only %d Go files — it judged nothing", scanned) } // Positive control: the fold turns a formal literal into a matching stem. if lv := fold.Replace(strings.ToLower(`"Próbálja újra."`)); !strings.Contains(lv, "probalja") { t.Fatal("the ASCII fold does not work — the scan proves nothing") } } // A detached attach job's failure is rendered in the READER's language, not frozen in the language of // the moment it failed. // // COMPANION RED-PROOF (observed): make handleNetStorageAddStatus return job.Message again → the English // reader gets the Hungarian sentence and this fails. Restored. func TestR516_NetAddFailureFollowsTheReader(t *testing.T) { s := testServer(t) job := &netAddJob{Name: "media", Phase: netAddPhaseFailed, Category: "unreachable", server: "FELHOM", mappedUID: 1000} job.Message = s.netAddMessage("hu", job.Category, job.server, job.mappedUID) s.netAdd.set(job) rr := httptest.NewRecorder() s.handleNetStorageAddStatus(rr, httptest.NewRequest("GET", "/api/storage/network/add/status?lang=en", nil)) body := rr.Body.String() if !strings.Contains(body, "The server cannot be reached (FELHOM)") || !strings.Contains(body, "looks like a Windows network name") { t.Fatalf("an English reader must get the English failure with its tip: %s", body) } rr = httptest.NewRecorder() s.handleNetStorageAddStatus(rr, httptest.NewRequest("GET", "/api/storage/network/add/status?lang=hu", nil)) if !strings.Contains(rr.Body.String(), "Ellenőrizd az IP-címet") { t.Fatalf("a Hungarian reader must get the te-form sentence: %s", rr.Body.String()) } }