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
@@ -0,0 +1,34 @@
// @vitest-environment jsdom
//
// E30 S3: a Plex query follows a language switch. `plexLang()` read the i18n singleton, and under the
// React Compiler a value with no reactive input is computed once: after switching to Hungarian the
// comfort shelf kept asking for its English cards. The language now comes off useTranslation. Real
// i18n (the key-echo mock does not re-render on a switch, which is the very thing under test).
import { describe, it, expect, afterEach, vi } from "vitest";
import type { ReactNode } from "react";
import { renderHook, act, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { setLanguage } from "../i18n";
import { api } from "../lib/api";
import { useComfortPin } from "./comfortPin";
afterEach(() => {
vi.restoreAllMocks();
setLanguage("en");
});
function wrapper({ children }: { children: ReactNode }) {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
}
describe("the comfort shelf after a language switch", () => {
it("asks for its cards in the new language", async () => {
const get = vi.spyOn(api, "comfortPins").mockImplementation(() => new Promise(() => {}));
setLanguage("en");
renderHook(() => useComfortPin(true), { wrapper });
await waitFor(() => expect(get).toHaveBeenCalledWith("en"));
act(() => setLanguage("hu"));
await waitFor(() => expect(get).toHaveBeenCalledWith("hu"));
});
});