5270bad76e
gates / gates (push) Successful in 23s
Slice 1 translated the dashboard's markup. The sentences the program BUILDS were still Hungarian literals in Go, so an English household clicked an English button and was answered in Hungarian. 226 of them move into the bundle here. A flash was the hard part: it travels inside the redirect URL and is rendered by a DIFFERENT request, so it now carries a bundle key plus its parameters. A link minted by an older controller carries prose and is shown verbatim — never a raw key, never dropped. Also converted: page data and view-model text, the internal/api JSON answers, the alert banners (Alert.MessageKey, rendered on the way out of GetAlerts), 237 country names at display, and the four page titles built around an app name (R-566 closed). Hungarian is byte-identical, and that is measured rather than read: scripts/i18n_go_parity.py freezes every Go literal at the base commit (7 467) and refuses a key whose Hungarian is not that text, byte for byte. Three decoys, each seen to convict. Its own first version filtered the capture through an ASCII-Hungarian word list and missed seven real literals — the R-565 class. The filter is gone. Nothing on the wire moved, and wire goldens now hold it there: the report's health warnings and every notify event message stay Hungarian, because the hub MAILS the controller's sentence when it has no entry of its own. Slice 3 (R-558) owns those. MinAgent: 0.131.0 (unchanged). No hub release needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
85 lines
3.2 KiB
Go
85 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
|
)
|
|
|
|
// ── The JSON answers follow the language too (v0.252.0, slice 2 — R-557) ───────────────────────
|
|
//
|
|
// Design: felhom.eu/documentation/architecture/10-localisation.md. The dashboard's page markup has
|
|
// followed the household's language since slice 1, but a large part of what a customer READS on
|
|
// those pages never passes through a template: the page's own script fetches /api/… and shows the
|
|
// `message` or `error` the answer carries. Until now those were Hungarian literals, so an English
|
|
// dashboard answered in Hungarian the moment anything was clicked.
|
|
//
|
|
// These helpers mirror internal/web's (i18n_web.go) exactly, and for the same reason: the Hungarian
|
|
// text is the SAME BYTES the literal carried, pinned by scripts/i18n_go_parity.py against a frozen
|
|
// capture of the base commit.
|
|
//
|
|
// NOT everything here is display. `internal/report` and `internal/notify` put Hungarian on the wire,
|
|
// where it is read by the hub and mailed to the household — those stay Hungarian in every language
|
|
// until slice 3 (R-558) teaches the hub which language to compose in, and their bytes are pinned by
|
|
// the wire goldens in those packages.
|
|
|
|
// langFor decides the language of one API request: `?lang=hu|en` (a testing override, never
|
|
// persisted), else the household's saved setting, else Hungarian. Identical in meaning to
|
|
// web.Server.langFor, deliberately — the page and the calls its script makes must never disagree.
|
|
func (r *Router) langFor(req *http.Request) string {
|
|
if req != nil {
|
|
if q := req.URL.Query().Get("lang"); i18n.IsSupported(q) {
|
|
return q
|
|
}
|
|
}
|
|
if r.sett != nil {
|
|
return r.sett.GetLanguage()
|
|
}
|
|
return i18n.Default
|
|
}
|
|
|
|
// bundle returns the process-wide bundle, or nil if it somehow failed to load. It is embedded in the
|
|
// binary, so a running box cannot reach the nil case; the callers still degrade to the key rather
|
|
// than panicking.
|
|
func bundle() *i18n.Bundle {
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return b
|
|
}
|
|
|
|
// msg returns a message in the request's language, with the message's own printf verbs filled in.
|
|
func (r *Router) msg(req *http.Request, key string, a ...interface{}) string {
|
|
return r.msgLang(r.langFor(req), key, a...)
|
|
}
|
|
|
|
// msgLang is msg for a language that is already known.
|
|
func (r *Router) msgLang(lang, key string, a ...interface{}) string {
|
|
b := bundle()
|
|
if b == nil {
|
|
return key
|
|
}
|
|
if len(a) == 0 {
|
|
return b.Msg(lang, key)
|
|
}
|
|
return b.Msgf(lang, key, a...)
|
|
}
|
|
|
|
// countryName returns a country's name in lang, falling back to the name the cloudflare table
|
|
// carries when the bundle has no entry for the code.
|
|
//
|
|
// The TABLE is not translated and must not be: internal/report puts country CODES on the wire, and
|
|
// cloudflare/countries.go is also what validates them. Only the NAME shown in the geo picker follows
|
|
// the language, which is why this lives here and not in that package.
|
|
func (r *Router) countryName(lang, code, fallback string) string {
|
|
b := bundle()
|
|
if b == nil {
|
|
return fallback
|
|
}
|
|
if v, _, ok := b.Text(lang, "country."+code); ok {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|