<#1042 Add cross-device preferred language> Pull r...
# osctrl
g
#1042 Add cross-device preferred language Pull request opened by javuto Add cross-device preferred language (backend column + SPA sync) Problem The language preference was localStorage-only: switching languages on one device did not follow the operator to another device or browser. Change Backend (additive; startup AutoMigrate adds the column): •
AdminUser.PreferredLanguage
— the operator's UI language tag (BCP-47 base, e.g. "es"). Documented as advisory state: never load-bearing for authentication or authorization. •
UserManager.ChangePreferredLanguage
, following the existing ChangeEmail/ChangeFullname pattern. •
types.SupportedLanguages
allowlist shared by the PATCH handler — mirrors the frontend registry; unsupported tags are rejected with 400 so the column cannot store arbitrary data. •
GET/PATCH /api/v1/users/me
extended with
preferred_language
(empty value = unchanged, same semantics as the other patch fields). The existing "updated own profile" audit entry covers the write. • OpenAPI regenerated (
make openapi
);
make openapi-check
passes. Frontend sync protocol: • Boot source stays localStorage — synchronous, no flash of wrong language, first paint never blocked on the network. • Reconcile: once the shared
users-me
query resolves, the
_app
layout applies the server value when it disagrees with the local one. The server is the tie-breaker because every explicit switch mirrors to it — it always reflects the latest choice on any device. No-op when both agree; unsupported or empty server values ignored. • Mirror:
setLanguage
fire-and-forgets
PATCH /users/me
after each switch, deduplicated, and failures (offline/401) are tolerated — the session-local preference still applies. Security/operational impact • Auth-area change reviewed against the security lens: the column is write-only via the authenticated self-profile PATCH, validated against a fixed allowlist, never consulted by any auth decision. • Additive and reversible: existing deployments pick up the column via AutoMigrate; removing it breaks nothing. Validation • Go: full
go test ./...
passes — new ChangePreferredLanguage tests (set + clear); TestCreateUser sqlmock fixture updated for the new column. • Frontend: typecheck clean, 369/369 tests — 7 new sync tests covering reconcile apply/no-op/unsupported/empty, mirror call, dedup, and offline survival. • Production build verified. jmpsec/osctrl