feat(quick-261016-0fm): admin language follows the stored preference
- principalFrom sets PreferredLocale from metadata admin_locale; personal API token principals clear it, so token clients keep the request locale - the guarded admin group runs locale.from-principal after the backend guard, overriding the LocaleResolver or Accept-Language locale - GET /lang honours a supported ?locale= and reports meta.locales (cabana.LangEnvelope, cabana.LangMeta) - Docker test for the override, refusals and /lang; docs and OpenAPI
This commit is contained in:
@@ -23,7 +23,7 @@ A plugin route under the admin prefix also fails the start-up: the SPA and the a
|
||||
|
||||
## How the SPA talks to the server
|
||||
|
||||
The SPA signs in through the admin API and keeps the token in the HttpOnly cookie described on [Users and permissions](users-and-permissions.md). For each screen it loads the controller's localized schema (`schema/list`, `schema/form`), then the records, and renders the fields and columns the schema names. Strings come from `GET <prefix>/api/v1/lang`, the `backend::lang` bundle in the request locale, with CLDR plural forms.
|
||||
The SPA signs in through the admin API and keeps the token in the HttpOnly cookie described on [Users and permissions](users-and-permissions.md). For each screen it loads the controller's localized schema (`schema/list`, `schema/form`), then the records, and renders the fields and columns the schema names. Strings come from `GET <prefix>/api/v1/lang`, the `backend::lang` bundle with CLDR plural forms, in the administrator's chosen language (sent as `?locale=`) or else the request locale.
|
||||
|
||||
Each record form makes a session key when it opens: 32 random bytes, base64url encoded. The form sends it in the `X-Session-Key` header with every file upload, file list and file removal, and with the final create or update save. Uploads and removals are deferred: the server holds them against the key and the admin, and the save that carries the same key commits them in its transaction. Until then the form counts as unsaved, so leaving it asks first, and a new record's files go to record id `0`. The form will not save while an upload is still in flight. Uploads use `XMLHttpRequest` for progress events and carry the same `X-Requested-With` header and cookie as every other call, plus an `X-Upload-Id` so a retry returns the already stored file. Files of a protected relation are fetched through the admin API with the key and shown from object URLs. The key travels only in headers, never in a URL. See [File uploads](forms.md#file-uploads) for the `fileupload` field.
|
||||
|
||||
|
||||
@@ -79,6 +79,6 @@ The locale lives on the request context, not in a global. For every route, [surf
|
||||
|
||||
## Strings for the admin
|
||||
|
||||
The admin SPA receives its strings from the server. `phrasebook.Translator.Bundle` returns every key under a prefix as CLDR plural forms, merged over the fallback chain, and `phrasebook.Translator.Forms` returns one key. `phrasebook.Translator.Locales` lists the locales that define keys under a prefix; `Locales("backend::lang.")` is the set of admin languages an administrator can choose. Start-up fails if an admin (`backend::`) string cannot be expressed as CLDR forms, so a pipe string with a condition the SPA cannot evaluate is caught before any admin sees it.
|
||||
The admin SPA receives its strings from the server. `phrasebook.Translator.Bundle` returns every key under a prefix as CLDR plural forms, merged over the fallback chain, and `phrasebook.Translator.Forms` returns one key. `phrasebook.Translator.Locales` lists the locales that define keys under a prefix; `Locales("backend::lang.")` is the set of admin languages an administrator can choose. The admin language follows the administrator's own preference first: the language chosen in the SPA's user menu is stored per administrator and, on every admin API route behind the guard, replaces the request locale (whatever `Accept-Language` or a `surf.LocaleResolver` picked) through `locale.from-principal`. Without a preference, and always for requests made with a personal API token, the request locale applies. `GET <prefix>/api/v1/lang` takes a `locale` query parameter naming one of those languages, ignores any other value, and lists them in `meta.locales`. Start-up fails if an admin (`backend::`) string cannot be expressed as CLDR forms, so a pipe string with a condition the SPA cannot evaluate is caught before any admin sees it.
|
||||
|
||||
The framework ships its validation messages (`lagoon::validate` for `lagoon.Validate`, and the full WinterCMS validator catalog `lagoon::validation` for `lagoon.ValidateRequest`) and admin strings (`backend::lang`) in English and Polish.
|
||||
|
||||
Reference in New Issue
Block a user