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:
2026-09-29 22:39:57 +02:00
parent 67c9f10153
commit aac79ffa53
105 changed files with 1001 additions and 494 deletions
+4 -4
View File
@@ -19,10 +19,10 @@ import { SURFACE } from "./ui/surfaces";
// A collection tile's sub-line: item count · total runtime (each dropped when unknown, e.g. show-only
// collections have no movie runtime).
function collectionMeta(c: PlexCollection, t: TFunction): string {
function collectionMeta(c: PlexCollection, t: TFunction, lang: string): string {
const parts: string[] = [];
if (c.child_count != null) parts.push(t("plex.browse.items", { count: c.child_count }));
const rt = formatRuntime(c.total_runtime_s);
const rt = formatRuntime(c.total_runtime_s, lang);
if (rt) parts.push(rt);
return parts.join(" · ");
}
@@ -162,7 +162,7 @@ function NewCollectionPanel({
// A grid of Plex collections (searchable — the union can run to hundreds); a tile opens its info page.
export function PlexCollectionsBrowse({ onPick }: { onPick: (id: string, title: string) => void }) {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const qc = useQueryClient();
const isAdmin = qc.getQueryData<{ role?: string }>(qk.me())?.role === "admin";
const [search, setSearch] = useState("");
@@ -223,7 +223,7 @@ export function PlexCollectionsBrowse({ onPick }: { onPick: (id: string, title:
navIndex={i}
title={c.title}
art={c.art}
subtitle={collectionMeta(c, t)}
subtitle={collectionMeta(c, t, i18n.language)}
topRight={
isIntrinsicReadOnly(c) ? (
<Lock