diff --git a/apps/web/src/app/dashboard/[guildId]/board/page.tsx b/apps/web/src/app/dashboard/[guildId]/board/page.tsx
index ef39340..77b9db8 100644
--- a/apps/web/src/app/dashboard/[guildId]/board/page.tsx
+++ b/apps/web/src/app/dashboard/[guildId]/board/page.tsx
@@ -10,7 +10,7 @@ import { requireUser } from "@/lib/auth";
import { resolveStatus, statusOptions } from "@/lib/forms";
import { getGuildSubmissions, getReviewScope } from "@/lib/guild";
import { signGuildRealtimeToken } from "@/lib/realtime-token";
-import { getDict } from "@/i18n";
+import { getDict, getLocale } from "@/i18n";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -101,8 +101,12 @@ export default async function BoardPage({ params }: { params: Promise<{ guildId:
moved: t.board.moved,
empty: t.board.columnEmpty,
anonymous: t.anonymous,
- countOne: t.countSubmission,
- countMany: t.countSubmissions,
+ count: {
+ one: t.countSubmission,
+ few: t.countSubmissionsFew,
+ other: t.countSubmissions,
+ },
+ locale: await getLocale(),
saveFailed: t.board.saveFailed,
dismiss: dict.kit.dismiss,
}}
diff --git a/apps/web/src/app/dashboard/[guildId]/submissions/page.tsx b/apps/web/src/app/dashboard/[guildId]/submissions/page.tsx
index e19b4f5..95cfb07 100644
--- a/apps/web/src/app/dashboard/[guildId]/submissions/page.tsx
+++ b/apps/web/src/app/dashboard/[guildId]/submissions/page.tsx
@@ -11,8 +11,9 @@ import { Button } from "@/components/ui/button";
import { requireUser } from "@/lib/auth";
import { resolveStatus, statusOptions } from "@/lib/forms";
import { countArchivedSubmissions, getGuildSubmissions, getReviewScope } from "@/lib/guild";
+import { countForm } from "@/lib/plural";
import { signGuildRealtimeToken } from "@/lib/realtime-token";
-import { getDict } from "@/i18n";
+import { getDict, getLocale } from "@/i18n";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -78,7 +79,11 @@ export default async function GuildSubmissionsPage({
{liveToken && }
diff --git a/apps/web/src/components/board/kanban-board.tsx b/apps/web/src/components/board/kanban-board.tsx
index 4c4eada..94eac9e 100644
--- a/apps/web/src/components/board/kanban-board.tsx
+++ b/apps/web/src/components/board/kanban-board.tsx
@@ -8,6 +8,7 @@ import { useEffect, useId, useRef, useState } from "react";
import { Notice, RowMenu } from "@/components/dashboard-kit";
import { LocalDateTime } from "@/components/public/local-datetime";
import { dotColors } from "@/lib/color-dot";
+import { countForm, type CountForms } from "@/lib/plural";
export interface BoardColumn {
key: string;
@@ -33,8 +34,9 @@ export interface BoardLabels {
moved: string;
empty: string;
anonymous: string;
- countOne: string;
- countMany: string;
+ /** The noun after a column's card count, in the forms the locale needs. */
+ count: CountForms;
+ locale: string;
saveFailed: string;
dismiss: string;
}
@@ -150,7 +152,7 @@ export function KanbanBoard({
{cards.length}
{" "}
- {cards.length === 1 ? labels.countOne : labels.countMany}
+ {countForm(cards.length, labels.locale, labels.count)}
diff --git a/apps/web/src/i18n/dictionaries.ts b/apps/web/src/i18n/dictionaries.ts
index 362cafd..bfd70d3 100644
--- a/apps/web/src/i18n/dictionaries.ts
+++ b/apps/web/src/i18n/dictionaries.ts
@@ -497,6 +497,7 @@ const en = {
countForms: "forms",
countSubmission: "submission",
countSubmissions: "submissions",
+ countSubmissionsFew: "submissions",
newForm: "New form",
noForms: "No forms yet. Create your first one.",
edit: "Edit",
@@ -1842,6 +1843,7 @@ const de: Dictionary = {
countForms: "Formulare",
countSubmission: "Einreichung",
countSubmissions: "Einreichungen",
+ countSubmissionsFew: "Einreichungen",
newForm: "Neues Formular",
noForms: "Noch keine Formulare. Erstelle dein erstes.",
edit: "Bearbeiten",
@@ -3198,6 +3200,7 @@ const hu: Dictionary = {
countForms: "űrlap",
countSubmission: "beküldés",
countSubmissions: "beküldés",
+ countSubmissionsFew: "beküldés",
newForm: "Új űrlap",
noForms: "Még nincsenek űrlapok. Hozd létre az elsőt.",
edit: "Szerkesztés",
@@ -4542,6 +4545,7 @@ const fr: Dictionary = {
countForms: "formulaires",
countSubmission: "soumission",
countSubmissions: "soumissions",
+ countSubmissionsFew: "soumissions",
newForm: "Nouveau formulaire",
noForms: "Aucun formulaire. Créez le premier.",
edit: "Modifier",
@@ -5892,6 +5896,7 @@ const es: Dictionary = {
countForms: "formularios",
countSubmission: "envío",
countSubmissions: "envíos",
+ countSubmissionsFew: "envíos",
newForm: "Nuevo formulario",
noForms: "Aún no hay formularios. Crea el primero.",
edit: "Editar",
@@ -7233,6 +7238,7 @@ const pt: Dictionary = {
countForms: "formulários",
countSubmission: "candidatura",
countSubmissions: "candidaturas",
+ countSubmissionsFew: "candidaturas",
newForm: "Novo formulário",
noForms: "Nenhum formulário ainda. Crie o seu primeiro.",
edit: "Editar",
@@ -8572,6 +8578,7 @@ const pl: Dictionary = {
countForms: "formularzy",
countSubmission: "zgłoszenie",
countSubmissions: "zgłoszeń",
+ countSubmissionsFew: "zgłoszenia",
newForm: "Nowy formularz",
noForms: "Brak formularzy. Utwórz pierwszy.",
edit: "Edytuj",
diff --git a/apps/web/src/lib/plural.test.ts b/apps/web/src/lib/plural.test.ts
new file mode 100644
index 0000000..9dfcf17
--- /dev/null
+++ b/apps/web/src/lib/plural.test.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it } from "vitest";
+
+import { countForm } from "./plural";
+
+const pl = { one: "zgłoszenie", few: "zgłoszenia", other: "zgłoszeń" };
+const en = { one: "submission", few: "submissions", other: "submissions" };
+
+describe("countForm", () => {
+ it("picks the Polish form for 1, 2 to 4 and 5 and up", () => {
+ expect(countForm(1, "pl", pl)).toBe("zgłoszenie");
+ for (const n of [2, 3, 4, 22, 34]) expect(countForm(n, "pl", pl)).toBe("zgłoszenia");
+ for (const n of [5, 11, 12, 13, 14, 25, 100]) expect(countForm(n, "pl", pl)).toBe("zgłoszeń");
+ });
+
+ it("keeps singular and plural in English", () => {
+ expect(countForm(1, "en", en)).toBe("submission");
+ expect(countForm(3, "en", en)).toBe("submissions");
+ });
+});
diff --git a/apps/web/src/lib/plural.ts b/apps/web/src/lib/plural.ts
new file mode 100644
index 0000000..e98a46d
--- /dev/null
+++ b/apps/web/src/lib/plural.ts
@@ -0,0 +1,13 @@
+/**
+ * A noun in the forms a count can need. `few` is the Polish 2 to 4 (also 22
+ * to 24 and so on); languages without that form repeat `other` there.
+ */
+export type CountForms = { one: string; few: string; other: string };
+
+/** The form of a noun that goes with `count` in `locale`, by the CLDR plural rules. */
+export function countForm(count: number, locale: string, forms: CountForms): string {
+ const category = new Intl.PluralRules(locale).select(count);
+ if (category === "one") return forms.one;
+ if (category === "few") return forms.few;
+ return forms.other;
+}