v0.259.0 — the claim page and the backup warnings answer in the reader's language (R-596, R-598)
gates / gates (push) Successful in 28s

The 2026-09-20 English drill ended one screen short: the claim page was English
and its answers were Hungarian, so a household who mistyped the code from their
e-mail could not tell a typo from a dead code. Fourteen call sites carrying nine
messages now go through s.msg; the backup page's two protection warnings — which
are promises about whether the customer's files are safe — follow the same route.

Hungarian is byte-identical, proved structurally by the go-parity gate against the
frozen base capture and red-proofed on a single added full stop.

data["Title"] was DEAD (claim.html is standalone; .Title is layout.html's) and is
deleted rather than translated — a translated dead field is a permanent false
signal about where the page's title comes from.

Six existing copy-contract tests were kept, not weakened: each now resolves its key
through the real bundle, so it still convicts on a reworded Hungarian sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-21 07:45:56 +02:00
parent f6909492cc
commit 0fe04bb6d5
15 changed files with 736 additions and 143 deletions
+15 -14
View File
@@ -151,16 +151,16 @@ func tierWindow(cadenceSecs int64) time.Duration {
// buildTierViews turns the agent's per-tier state into page rows and derives the three legacy tile
// fields from SUCCESSES only: HasBackup/Success/Size/Target/StartedAt from the primary tier's newest
// success; Due when any set-up tier has no current success; Offsite only on a current PBS success.
func buildTierViews(v *guestBackupView, tiers []agentapi.TierBackupState, cadence map[string]int64, now time.Time) {
func buildTierViews(v *guestBackupView, tiers []agentapi.TierBackupState, cadence map[string]int64, now time.Time, msg func(key string, a ...interface{}) string) {
v.Tiers = nil
v.Due, v.Offsite, v.HasBackup = false, false, false
var newestOK time.Time
for _, t := range tiers {
tv := guestTierView{Target: t.Target, IsPBS: strings.Contains(strings.ToLower(t.Target), "pbs")}
if tv.IsPBS {
tv.Label = "Biztonsági szerver – külön hardver (PBS)"
tv.Label = msg("backup.tier.pbs")
} else {
tv.Label = "Helyi tároló (" + t.Target + ")"
tv.Label = msg("backup.tier.local", t.Target)
}
tv.NotSetUp = t.Storage == "absent"
if t.LastSuccess != nil {
@@ -200,16 +200,17 @@ func buildTierViews(v *guestBackupView, tiers []agentapi.TierBackupState, cadenc
// loadGuestBackup fetches the agent's whole-guest backup view (best-effort). Returns a view with
// Available=false (+ a note) when the agent isn't configured/reachable — the page still renders.
func (s *Server) loadGuestBackup(ctx context.Context) *guestBackupView {
func (s *Server) loadGuestBackup(ctx context.Context, lang string) *guestBackupView {
msg := func(key string, a ...interface{}) string { return s.msgLang(lang, key, a...) }
v := &guestBackupView{CanTrigger: s.backupTrigger != nil}
client, err := s.agentClient()
if err != nil {
v.Note = "A host-ügynök nincs konfigurálva ezen a gépen."
v.Note = msg("backup.guest.agent_unconfigured")
return v
}
st, err := client.BackupStatus(ctx)
if err != nil {
v.Note = "A host-ügynök jelenleg nem elérhető."
v.Note = msg("backup.guest.agent_unreachable")
return v
}
v.Available = true
@@ -222,7 +223,7 @@ func (s *Server) loadGuestBackup(ctx context.Context) *guestBackupView {
v.Archive = st.Backup.Archive
v.Mode = st.Backup.Mode
v.StopMode = st.Backup.Mode == "stop"
v.Target = backupTargetLabel(st.Backup)
v.Target = backupTargetLabel(st.Backup, msg)
v.Offsite = backupIsPBS(st.Backup)
if t, perr := time.Parse(time.RFC3339, st.Backup.StartedAt); perr == nil {
v.StartedAt = t
@@ -245,7 +246,7 @@ func (s *Server) loadGuestBackup(ctx context.Context) *guestBackupView {
cadence[t.Target] = t.CadenceSeconds
}
}
buildTierViews(v, st.Tiers, cadence, time.Now())
buildTierViews(v, st.Tiers, cadence, time.Now(), msg)
}
// Restore-test (the "verified restorable" trust signal; nil until one runs).
if rt, rerr := client.RestoreTestStatus(ctx); rerr == nil && rt != nil {
@@ -270,14 +271,14 @@ func backupIsPBS(b *agentapi.BackupRecord) bool {
// backupTargetLabel maps the agent's backup target to a customer-facing Hungarian label. The PBS
// case calls out that the backup is on SEPARATE HARDWARE (real disaster recovery — survives a host
// disk/hardware failure), which is the whole point of re-pointing the backup offsite.
func backupTargetLabel(b *agentapi.BackupRecord) string {
func backupTargetLabel(b *agentapi.BackupRecord, msg func(key string, a ...interface{}) string) string {
if backupIsPBS(b) {
return "Biztonsági szerver – külön hardver (PBS)"
return msg("backup.tier.pbs")
}
if b.TargetID != "" {
return "Helyi tároló (" + b.TargetID + ")"
return msg("backup.tier.local", b.TargetID)
}
return "Helyi tároló"
return msg("backup.tier.local_plain")
}
// ServeBackupAPI dispatches /api/guest-backup/* (whole-guest manual trigger + status poll). A
@@ -298,12 +299,12 @@ func (s *Server) ServeBackupAPI(w http.ResponseWriter, r *http.Request) {
// returns immediately (the backup runs async, minutes); the page polls /api/backup/status.
func (s *Server) handleBackupTriggerAPI(w http.ResponseWriter, r *http.Request) {
if s.backupTrigger == nil {
writeDiskJSON(w, http.StatusServiceUnavailable, false, "a rendszermentés nem érhető el ezen a gépen", nil)
writeDiskJSON(w, http.StatusServiceUnavailable, false, s.msg(r, "backup.guest.err.unavailable"), nil)
return
}
if err := s.backupTrigger.TriggerNow(); err != nil {
if errors.Is(err, quiesce.ErrBackupInProgress) {
writeDiskJSON(w, http.StatusConflict, false, "mentés már folyamatban van", nil)
writeDiskJSON(w, http.StatusConflict, false, s.msg(r, "backup.guest.err.in_progress"), nil)
return
}
s.logger.Printf("[ERROR] [web] backup trigger failed: %v", err)