fix(ui,i18n): E30 review round 1 — audit rows in the reader's language, one HU name per thing

- audit log: a job run named from its result data (nested phases too), a skipped run says so, a
  secret's reset reads as a reset, choices named as Configuration names them, interval / batch /
  counts localized; the inbox shares the same jobRunText
- HU: remux is "Közvetlen közvetítés" (it shared "Közvetlen lejátszás" with direct play); backfill is
  behúzás, enrich is dúsítás, collection is kollekció, the allow-list is fehérlista; the last
  "jobnak" / "transzkódolás"; a test holds the options of one choice to distinct names
- contract: every skipped / reason word a job returns is named in both languages
- person page: the bio wait is timed from the open and a failing ask stops the polling; ClampedText
  (ui) shared with the collection page, whose short summary lost its dead More
- a playlist names season 0 the Specials; the channel re-slot compares in the reader's alphabet; an
  invite's date is in the admin's language; the language-ordered lists are asked again once a
  language switch is saved (saveLanguage)
- poster grids' column minimum in rem; lint refuses minmax(Npx,…)
This commit is contained in:
2026-09-30 03:26:58 +02:00
parent ab963c81d2
commit fa55e01ef0
46 changed files with 624 additions and 187 deletions
@@ -10,6 +10,7 @@
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
import { render, screen, cleanup, waitFor, fireEvent } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { formatDateTime } from "../lib/format";
vi.mock("react-i18next", async (importOriginal) => {
const actual = await importOriginal<typeof import("react-i18next")>();
@@ -18,7 +19,8 @@ vi.mock("react-i18next", async (importOriginal) => {
useTranslation: () => ({
// Echo the interpolation too, so a message that forgets its {{email}} is visible in the test.
t: (key: string, vars?: Record<string, unknown>) =>
vars?.email ? `${key}:${String(vars.email)}` : key,
(vars?.email ? `${key}:${String(vars.email)}` : key) +
(vars?.time ? `@${String(vars.time)}` : ""),
i18n: { language: "en" },
}),
};
@@ -315,6 +317,19 @@ describe("inviting someone the admin whitelisted", () => {
expect(dialog.container.textContent).toContain("settings.invites.inviteConfirmAgain");
});
it("dates the earlier invite in the admin's language, not the invitee's (E30 review)", async () => {
listMock.mockResolvedValue([
invite({ status: "approved", invited_at: "2026-09-05T09:00:00Z", lang: "hu" }),
]);
await open();
fireEvent.click(screen.getByLabelText("settings.invites.inviteLabel:asker@example.com"));
await waitFor(() => expect(confirmMock).toHaveBeenCalled());
const dialog = render(<>{confirmMock.mock.calls[0]![0].message}</>);
const at = "2026-09-05T09:00:00Z";
expect(dialog.container.textContent).toContain(`@${formatDateTime(at, "en")}`);
expect(formatDateTime(at, "en")).not.toBe(formatDateTime(at, "hu"));
});
it("offers none on a denied row, which would contradict the decision", async () => {
listMock.mockResolvedValue([invite({ status: "denied" })]);
await open();