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:
@@ -240,7 +240,8 @@ export function DownloadRow({
|
||||
more?: ReactNode;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language;
|
||||
const running = job.status === "running";
|
||||
const linkTitle = !hasOwnTitle(job) && !!job.source_url;
|
||||
const sourceLink = linkTitle ? null : job.source_url;
|
||||
@@ -294,16 +295,16 @@ export function DownloadRow({
|
||||
<span className="text-muted uppercase"> · {job.asset.container}</span>
|
||||
) : null}
|
||||
{job.asset?.size_bytes ? (
|
||||
<span className="text-muted"> · {formatBytes(job.asset.size_bytes)}</span>
|
||||
<span className="text-muted"> · {formatBytes(job.asset.size_bytes, lang)}</span>
|
||||
) : null}
|
||||
{job.asset?.duration_s ? (
|
||||
<span className="text-muted"> · {formatDuration(job.asset.duration_s)}</span>
|
||||
) : null}
|
||||
{job.status === "done" && job.created_at ? (
|
||||
<span className="text-muted" title={formatDateTime(job.created_at)}>
|
||||
<span className="text-muted" title={formatDateTime(job.created_at, lang)}>
|
||||
{" · "}
|
||||
<CalendarClock className="inline w-3.5 h-3.5 align-[-0.15em]" />{" "}
|
||||
{relativeTime(job.created_at)}
|
||||
{relativeTime(job.created_at, lang)}
|
||||
</span>
|
||||
) : null}
|
||||
{job.status === "error" ? (
|
||||
@@ -360,8 +361,8 @@ export function DownloadRow({
|
||||
</div>
|
||||
<div className="text-xs text-muted mt-0.5 flex gap-2">
|
||||
<span>{job.progress}%</span>
|
||||
{job.speed_bps ? <span>{formatSpeed(job.speed_bps)}</span> : null}
|
||||
{job.eta_s ? <span>{formatEta(job.eta_s)}</span> : null}
|
||||
{job.speed_bps ? <span>{formatSpeed(job.speed_bps, lang)}</span> : null}
|
||||
{job.eta_s ? <span>{formatEta(t, job.eta_s)}</span> : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -373,7 +374,8 @@ export function DownloadRow({
|
||||
}
|
||||
|
||||
export function UsageBar() {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const lang = i18n.language;
|
||||
// Poll live (like the library list) so the footprint updates the moment the worker finishes an
|
||||
// edit/download — enqueue only creates a queued job (0 bytes), the size lands asynchronously, so
|
||||
// a one-shot fetch at enqueue time would show a stale 0 B until a manual reload. The footprint
|
||||
@@ -391,8 +393,8 @@ export function UsageBar() {
|
||||
<div className="flex justify-between text-sm mb-1.5">
|
||||
<span className="font-medium">{t("downloads.usage.title")}</span>
|
||||
<span className="text-muted">
|
||||
{formatBytes(u.footprint_bytes)}
|
||||
{u.unlimited ? "" : ` / ${formatBytes(u.max_bytes)}`} ·{" "}
|
||||
{formatBytes(u.footprint_bytes, lang)}
|
||||
{u.unlimited ? "" : ` / ${formatBytes(u.max_bytes, lang)}`} ·{" "}
|
||||
{u.unlimited
|
||||
? t("downloads.usage.unlimited")
|
||||
: t("downloads.usage.items", { used: u.active_jobs, max: u.max_jobs })}
|
||||
|
||||
Reference in New Issue
Block a user