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
+6 -4
View File
@@ -114,6 +114,7 @@ function ComposerBody({
total: number;
}) {
const { t, i18n } = useTranslation();
const lang = i18n.language;
const qc = useQueryClient();
const pageName = (page: string) => {
const def = moduleDef(page as Page);
@@ -304,8 +305,8 @@ function ComposerBody({
<div ref={root} className="flex flex-col gap-4">
<p className="text-sm text-muted">
{t("recorder.composer.meta", {
start: formatDateTimeSec(started.toISOString()),
minutes: formatUnit(minutes, "minute"),
start: formatDateTimeSec(started.toISOString(), lang),
minutes: formatUnit(minutes, "minute", lang),
level: t(`settings.diagnostics.levels.${session.level}`),
})}
</p>
@@ -607,7 +608,8 @@ function Attachments({
onReview: (source: Source) => void;
reviewed: MutableRefObject<Source | null>;
}) {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const lang = i18n.language;
const qc = useQueryClient();
const shots = shotsQ.data?.items ?? [];
// A removed screenshot's ✕ goes with it: the focus moves to the file picker, which stays.
@@ -685,7 +687,7 @@ function Attachments({
/>
<div className="flex min-w-0 flex-1 flex-col">
<span className="text-sm">{t("recorder.composer.screenshot", { n: i + 1 })}</span>
<span className="text-xs text-muted tabular-nums">{formatBytes(shot.size)}</span>
<span className="text-xs text-muted tabular-nums">{formatBytes(shot.size, lang)}</span>
</div>
{!readOnly && (
<IconButton