Pick a language from a menu, with its flag and how its translation was made #119
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#119
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
What exists
Settings → Language and time puts the time zone in a
<select>and the language intwenty-four radio rows in a card, grouped under three headings — Reviewed by a speaker,
Awaiting review by a speaker, Partly translated — with the flag drawn as an
<img>andthe name carrying its own
lang.That is not an oversight, and the reason is written down in three places
(
settings/locale.html,accounts/forms.py::LanguageSelect,docs/TRANSLATING.md): an<option>holds text and nothing else. It takeslangon itself, so a<select>can saythis option is Greek, and that is all it can do. Consequently, in a native dropdown:
aria-hidden, so a screen reader reads"Greek flag, Ελληνικά" where the flag was meant to be decoration beside a name that
already says what it is;
it sits inside an element marked
lang="el"while being a symbol or an English word.That is a WCAG 2.2 3.1.2 failure introduced deliberately;
The list solved all three by not being a dropdown. What the request is actually about is
the shape: twenty-four rows is a wall where the time zone beside it is one line, and it is
now thirty-nine rows and will be more.
(The platform is moving: Chromium's customisable
<select>—appearance: base-select—does allow real markup inside an option. It is one engine, and an accessibility promise made
at AA cannot rest on one engine. Worth revisiting, not worth waiting for.)
What this asks for
The shape of the request, met without the element that cannot carry it: a disclosure
that collapses to one row like the time zone field and opens to the rows that exist today.
Postulo already has this pattern twice — the account menu in the header, and the row actions
in Server settings → People, which #91 moved to "the same disclosure the account menu uses,
so it opens on a keyboard without help". The same thing here gets all of it:
<details>/<summary>, closed by default, showing the current language{% flag %}<img>,alt="",aria-hidden="true"<span lang="…">, exactly as nowaria-hidden, with ansr-onlyphrase beside it in the interface language and outside thelang-marked span<details>opens without JavaScript and the radios inside still submitWorth being careful about
The symbol may not be the only thing that says it. A glyph alone is a 1.3.3 failure for
anybody who cannot see it and a plain guess for anybody who has not learnt it. Every symbol
needs a text equivalent that is read out, and the visible legend should stay somewhere on
the page.
"LLM" is not what the flag means. The existing group is called Awaiting review by a
speaker for a documented reason:
pt-brwas seeded frompt-ptand adapted by hand, whichis not machine translation, and the thing true of every entry in that group is that no
speaker has read it. The symbol should say draft / unreviewed, not machine-made.
Keyboard behaviour is the whole cost of not using
<select>. A native dropdown givesarrow keys, Home/End, type-ahead, Escape and a focus trap for free. The disclosure has to be
given them, and the browser suite has to walk it with a keyboard and through axe in both
themes and both directions.
The current language has to be visible while closed — flag, name and status — or the
setting page stops answering "what am I using?" without a click.
Right to left. The panel is positioned with logical properties only; the template lint
already fails a physical one.
The request asked for a flag, a language name and a symbol in one dropdown — which is
exactly the combination an
<option>cannot hold. So the shape is what changed, and itis the shape the issue said the request was really about: a disclosure, closed to one line
like the time zone beside it, open to the rows that were there before.
Every row from the table in the issue:
<details data-menu>, closed, showing the language in use{% flag %}image,alt="",aria-hidden<span lang="…">, exactly as beforearia-hidden, with ansr-onlyphrase in the interface language, outside thelangspanOne thing got better rather than merely surviving. The percentage a partly-translated
language shows was being appended to the language's own name, inside the span marked as
being in that language, because a dropdown had nowhere else to put it. It has somewhere else
now.
The five warnings
The symbol may not be the only thing that says it. Each has words read out beside it, and
the legend is on the page rather than in a tooltip — which is not a thing a keyboard or a
touchscreen reaches.
"LLM" is not what the flag means. The wording is written, not yet read by a speaker.
Nothing in the interface says machine or LLM, and a test asserts that.
Keyboard behaviour is the whole cost. Paid, and in a browser: arrow keys come from the
radios; Home, End, type-ahead on each language's own name, Escape and returning the focus are
given back in
app.js. Opening it also puts the focus on the current language rather thanleaving somebody to tab past the list. Eight browser tests walk it, and the accessibility
sweep covers the page in both themes.
The current language visible while closed. Flag, name and state, asserted twice — once in
the rendered HTML and once through a browser after saving.
Right to left. Logical properties only; the template lint already fails a physical one.
One thing found on the way
The browser suite broke — not on this page, on
test_people_menu, with a429from thesign-in limiter. Every browser test signs in, all from one address, and the limiter cannot
tell a suite from an attacker; adding eight tests crossed the allowance, and which test
failed depended on how many had run first. The limiter is now emptied before each browser
test rather than switched off, so it is still the real one and
tests/security/test_rate_limits.pystill holds it to its numbers.13 unit tests, 8 browser tests, 4 new strings in all 39 European catalogues.
Shipped in
86315eaon0.3.0, withmainkept level. The note about Chromium'scustomisable
<select>still stands: worth revisiting when it is not one engine.