fix(i18n): numbers, units, dates and order in the UI language, and after a switch (E30 S3)
Every locale formatter takes the language as an argument. Under the React
Compiler a formatter that read the i18n singleton was memoized on its other
arguments, so a card kept "2 hr 4 min" after a switch to Hungarian; plexLang()
had the same flaw and left Plex queries in the old language. Components now
take the language from useTranslation, and the signature makes forgetting it
a type error.
- ratings, file sizes, stream stats: the language's decimal mark ("8,3")
- view and subscriber counts compact in the language ("1,2 E")
- seconds as the language's unit ("6 mp") in the sliders and the stats
- a person's birth and death dates as dates, not ISO strings
- A-Z lists by the shown name in the language's alphabet (compareText),
the tag catalog re-sorted by its translated names
- lint local/locale-format (was date-format) also refuses Intl.NumberFormat
outside lib/format.ts, localeCompare, and a component reading the language
off the i18n singleton
This commit is contained in:
+8
-1
@@ -531,7 +531,7 @@ pick a density step; it never changes the anatomy or loses an action. [Nielsen #
|
||||
- **Messages.** A toast or error of one sentence has no full stop and no "✓": "Couldn't sync
|
||||
subscriptions" / "Nem sikerült szinkronizálni". Two sentences keep their stops. Help text is prose.
|
||||
- **Quotes** around a name are curly: “abc” in English, „abc” in Hungarian.
|
||||
- **Dates**, in the UI language, only from `lib/format.ts` (lint: `local/date-format`): recency in a list
|
||||
- **Dates**, in the UI language, only from `lib/format.ts` (lint: `local/locale-format`): recency in a list
|
||||
is relative ("3d ago" / "3 napja") through `ui/RelativeTime`, which carries the exact minute on hover;
|
||||
a date is "Sep 27, 2026" / "2026. szept. 27."; a moment "Sep 27, 2026, 08:30 AM" /
|
||||
"2026. szept. 27. 08:30"; seconds only in the server log, the audit and the recorder.
|
||||
@@ -540,6 +540,12 @@ pick a density step; it never changes the anatomy or loses an action. [Nielsen #
|
||||
label holding one is written in that same form ("Under 90 min"). Never a bare letter ("90m", "24h",
|
||||
"7d"; test-held). In a sentence, and in a list of time spans named in words ("24 hours", "1 week"), the
|
||||
unit is a word. A clock-style length on a picture stays "2:34:11".
|
||||
- **Numbers** in the UI language, from `lib/format.ts` (same lint): the decimal mark and grouping are the
|
||||
language's ("8.3" / "8,3", "1,234" / "1 234"); a big count is compact ("1.2K" / "1,2 E"); a size keeps
|
||||
its symbol ("5.0 GB" / "5,0 GB"), as do a stream's (Mbps) and "%" / "px". Never `toFixed` on screen.
|
||||
- **Order.** An A→Z list is in the UI language's alphabet ("Ö" after "O", "Cs" after "Cu" in Hungarian),
|
||||
by the name the reader SEES: `compareText` (`lib/collate.ts`) on the client, the ICU collation of
|
||||
`app/collation.py` on the server, so both sort alike.
|
||||
- A label says what happens ("Delete playlist"), not "OK"; a destructive confirm names the object.
|
||||
|
||||
## Anti-patterns
|
||||
@@ -624,6 +630,7 @@ pick a density step; it never changes the anatomy or loses an action. [Nielsen #
|
||||
| 2026-09-27 | C4 as built | `initialFocus` required on every `Modal`; the commit row in `ModalFooter`, portalled under the body from any depth; the fades driven by the body's scroll timeline; `interactive-widget=resizes-content` | An optional prop defaulting to the ✕; a `footer` prop (the issue composer's buttons live three components down); always-on fades | A default is how 21 dialogs came to open on the ✕; lifting the composer's state to reach a prop is the larger change; a resting fade dimmed the first line |
|
||||
| 2026-09-27 | C3 + C5 as built | A tap toggles the bar through `pointerType` from `pointerdown`; a double-tap takes back its first tap's toggle; the card's pair is the video card's `MediaResumeButtons` (Continue / Restart), a sibling over the picture; the YT description is an ⓘ click popover | A delayed single tap (wait out the double-tap window); new "Resume / From the start" words for Plex; the pair inside the picture's button | A delayed tap feels dead; one media card says one pair of words; a button inside a button is invalid and the hover row already sits beside the picture |
|
||||
| 2026-09-27 | C2 as built | The side panel's drawer is `ui/Modal` `drawer-start`; drawer = a coarse pointer or below 768; the pin offered in its header ≥ 1024 | A drawer of its own inside `SidePanel` | Esc, Back, the scrim, the trap and the scroll lock are the dialog's; a second implementation is how two shells drift |
|
||||
| 2026-09-29 | Numbers and order in the UI language (E30 S3) | Every number through `lib/format.ts` in the UI language (decimal mark, grouping, compact, units); A→Z by the shown name in the UI language's alphabet, client and server alike; the language comes off `useTranslation`, never the singleton | `toFixed`, hand-built "1.2K", `localeCompare` in the browser's locale, byte-order server sorts | A Hungarian reader saw "8.3", "1.2K", "Életrajzi" after "Z"; the singleton read also left values in the old language after a switch (React Compiler) |
|
||||
| 2026-09-27 | Narrow rows by container | The download row, the playlist table and the Channels tag row stack by a container query; a fact line never wraps | Breakpoints on the window | The iPad zoomed in, split screen and a column beside a rail are narrow while the window is not; the zoomed download row broke "18.9 / MB" apart |
|
||||
| 2026-09-27 | Phones deferred | A phone-only layout (the hamburger nav, a folded feed band) waits for its own epic; E28 stays desktop + tablet | Fix the phone views inside E28 | Principle 2; the first phone attempts made the feed worse, and the epic's review grows with every sprint |
|
||||
| 2026-09-27 | Conformance check (S3g) | A four-step check before a UI change is done: primitive at a named rung, the chapter re-read, the rendered page measured by kind and look, every state seen | A reviewer's eye per sprint | The S1 probe found 69 button looks; after S3 every control kind is drawn by its primitive, and the check keeps a new look from arriving unnoticed |
|
||||
|
||||
Reference in New Issue
Block a user