// @vitest-environment jsdom // // A count in copy goes through i18next's plural forms (`count`), never a bare `n` — // the share dialog's expiry read "1 days". import { describe, expect, it } from "vitest"; import i18n from "./index"; const mods = import.meta.glob("./locales/*/*.json", { eager: true }) as Record< string, { default: Record } >; // Every leaf string as [dotted key, value]. function leaves(obj: Record, prefix = ""): [string, string][] { return Object.entries(obj).flatMap(([k, v]) => { const key = prefix ? `${prefix}.${k}` : k; if (v && typeof v === "object") return leaves(v as Record, key); return typeof v === "string" ? [[key, v] as [string, string]] : []; }); } describe("counted copy", () => { it("the share link's expiry is singular for one day", async () => { await i18n.changeLanguage("en"); expect(i18n.t("downloads.share.days", { count: 1 })).toBe("1 day"); expect(i18n.t("downloads.share.days", { count: 7 })).toBe("7 days"); await i18n.changeLanguage("hu"); expect(i18n.t("downloads.share.days", { count: 1 })).toBe("1 nap"); await i18n.changeLanguage("en"); }); it("a link's view count is singular for one view", async () => { await i18n.changeLanguage("en"); expect(i18n.t("downloads.share.views", { count: 1 })).toBe("1 view"); expect(i18n.t("downloads.share.views", { count: 0 })).toBe("0 views"); }); }); // "1 seasons", "Synced 1 subscriptions" — a counted string written as one plain key reads plural at // one. So the rule is held for every string, not key by key: a value that carries {{count}} is a plural // pair (`_one` / `_other`), even where the two read the same today, so the next wording is a plural too. describe("every counted string is a plural pair", () => { it("no {{count}} in a key without a plural suffix, in any language", () => { const bare = Object.entries(mods).flatMap(([path, mod]) => leaves(mod.default) .filter(([k, v]) => v.includes("{{count}}") && !/_(zero|one|two|few|many|other)$/.test(k)) .map(([k]) => `${path.replace(/^.*locales\//, "")}: ${k}`), ); expect(bare).toEqual([]); }); it("reads singular at one", async () => { await i18n.changeLanguage("en"); expect(i18n.t("plex.seasons", { count: 1 })).toBe("1 season"); expect(i18n.t("plex.seasons", { count: 3 })).toBe("3 seasons"); expect(i18n.t("channels.notify.synced", { count: 1 })).toBe("Synced 1 subscription"); expect(i18n.t("playlists.pushPartial", { count: 1 })).toBe("Synced, but 1 video was skipped"); expect(i18n.t("playlists.pushPartial", { count: 2 })).toBe("Synced, but 2 videos were skipped"); }); });