From 0a4a000769f54993f71c92693de8c3c1e2781c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 21:46:46 +0200 Subject: [PATCH 01/11] i18: update Polish translations --- src/i18n/translations/en.json | 22 +++--------- src/i18n/translations/pl.json | 63 ++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index 4e53d07..b76ec52 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -86,8 +86,6 @@ "advancing_teams": "Advancing teams", "submit": "Plan tournament", "submitting": "Planning...", - "placeholder_title": "Tournament ladder", - "placeholder_description": "Tournament ladder placeholder.", "no_phases_error": "Failed to load tournament phases.", "validation_positive_integer": "This value must be a positive integer.", "validation_power_of_two": "Advancing teams must be a power of 2.", @@ -101,7 +99,7 @@ "total_teams_hint": "The total number of teams taking part in the tournament.", "advancing_teams_hint": "Defines how many teams advance from the group phase to the finals phase. Must be a power of 2.", "hint_title": "Tournament Ladder", - "hint_content": "Each box represents a debate. You can click on a specific box to see the debate details. The ladder shows the structure and flow of the tournament, including the rounds and how debates are organised." + "hint_content": "Each box represents a debate. You can click on a specific box to see the debate details. The ladder visualizes the flow of the tournament." }, "debates": { "title": "Debates" @@ -141,21 +139,9 @@ "load_verdicts_error": "Unable to load verdict information", "panel_error_title": "Verdict panel got an error, oops:", "majority": { - "proposition": { - "prefix": "The", - "highlight": "Proposition", - "suffix": "is the winning team!" - }, - "opposition": { - "prefix": "The", - "highlight": "Opposition", - "suffix": "is the winning team!" - }, - "no_verdict": { - "prefix": "There is", - "highlight": "no verdict", - "suffix": "yet." - } + "proposition": "The Proposition is the winning team!", + "opposition": "The Opposition is the winning team!", + "no_verdict": "There is no verdict available yet." } } } diff --git a/src/i18n/translations/pl.json b/src/i18n/translations/pl.json index 6be9052..02144b0 100644 --- a/src/i18n/translations/pl.json +++ b/src/i18n/translations/pl.json @@ -71,24 +71,77 @@ "title": "granda: Zaplanuj swój turniej debat", "description": "Komputerowo wspomagane planowanie turniejów debat." }, + "generic": { + "error": "Błąd", + "error_loading_content": "Wystąpił błąd.", + "error_message": "Szczegóły: {message}" + }, "ladder": { "title": "Drabinka turniejowa", "planning_title": "Planowanie turnieju", "planning_description": "Ustaw podstawowe parametry, aby wygenerować drabinkę turniejową.", "group_phase_rounds": "Liczba rund fazy grupowej", "groups_count": "Liczba grup", + "total_teams": "Łączna liczba drużyn", "advancing_teams": "Liczba awansujących drużyn", "submit": "Zaplanuj turniej", "submitting": "Planowanie...", - "placeholder_title": "Drabinka turniejowa", - "placeholder_description": "Placeholder drabinki turniejowej.", "no_phases_error": "Nie udało się pobrać faz turnieju.", - "validation_positive_integer": "Ta wartość musi być dodatnią liczbą całkowitą.", + "validation_positive_integer": "Parametry muszą być dodatnimi liczbami całkowitymi.", "validation_power_of_two": "Liczba awansujących drużyn musi być potęgą 2.", + "validation_groups_and_teams": "Liczba grup nie może być mniejsza niż łączna liczba drużyn.", "request_failed": "Nie udało się zaplanować turnieju.", - "success": "Planowanie turnieju zakończone powodzeniem." + "success": "Planowanie turnieju zakończone powodzeniem.", + "unconfigured_debate": "Nieskonfigurowana debata", + "advancing_teams_placeholder": "2, 4, 8, 16…", + "group_rounds_hint": "Określa, ile rund nastąpi po sobie w fazie grupowej.", + "groups_count_hint": "Podczas każdej rundy w każdej grupie odbywa się osobna debata. Ta liczba określa, ile debat odbędzie się jednocześnie. Musi być mniejsza niż łączna liczba drużyn.", + "total_teams_hint": "Łączna liczba drużyn biorących udział w turnieju.", + "advancing_teams_hint": "Liczba drużyn, które awansują do fazy pucharowej. Musi być potęgą 2.", + "hint_title": "Drabinka turniejowa", + "hint_content": "Każdy kafelek reprezentuje debatę. Możesz kliknąć na kafelek, by zobaczyć szczegóły debaty. Drabinka przedstawia przebieg turnieju." + }, + "debates": { + "title": "Debaty" + }, + "round_config": { + "title": "Konfiguracja rundy {name}", + "motion_title": "Teza", + "motion_description": "O czym będzie ta debata?", + "motion_placeholder": "(Wymagane)", + "infoslide_title": "Infoslajd", + "infoslide_description": "Dodatkowe informacje na temat tezy", + "apply": "Zastosuj" }, "debate_details": { - "unconfigured_debate": "Debata bez tezy" + "unconfigured_debate": "Debata bez tezy", + "proceed_to_debate": "Przejdź do debaty!", + "marshal_panel": "Panel marszałka" + }, + "create_tournament": { + "title": "Utwórz turniej", + "full_name": "Pełna nazwa", + "short_name": "Skrócona nazwa", + "close": "Zamknij", + "full_name_placeholder": "(Wymagane)" + }, + "verdict_panel": { + "proposition": "Propozycja", + "opposition": "Opozycja", + "submit": "Wyślij", + "select_verdict_error": "Wybierz stronę przed wysłaniem werdyktu.", + "submit_error": "Nie udało się wysłać werdyktu", + "failed_submit": "Nie udało się utworzyć werdyktu", + "failed_update": "Nie udało się zaktualizować werdyktu", + "no_existing_verdict": "Aktualizowany werdykt nie istnieje", + "failed_load_permissions": "Nie udało się wczytać uprawnień", + "failed_load_verdicts": "Nie udało się wczytać werdyktów", + "load_verdicts_error": "Nie udało się wczytać informacji o werdykcie", + "panel_error_title": "Błąd w panelu werdyktu", + "majority": { + "proposition": "Wygrała drużyna Propozycji!", + "opposition": "Wygrała drużyna Opozycji!", + "no_verdict": "Jeszcze nie opublikowano werdyktu" + } } } From 14facb4f55f03dfab08f4527142b18bb4fb636f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 22:05:18 +0200 Subject: [PATCH 02/11] refactor: simplify verdict rendering --- src/components/tournament/VerdictPanel.tsx | 68 ++++++---------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/src/components/tournament/VerdictPanel.tsx b/src/components/tournament/VerdictPanel.tsx index b75c5f1..92ba4b3 100644 --- a/src/components/tournament/VerdictPanel.tsx +++ b/src/components/tournament/VerdictPanel.tsx @@ -50,9 +50,9 @@ const VerdictPanel: React.FC = ({ return null; } - if (counts.proposition > counts.opposition) return "Proposition"; - if (counts.opposition > counts.proposition) return "Opposition"; - return "Tie"; + if (counts.proposition > counts.opposition) return "proposition"; + if (counts.opposition > counts.proposition) return "opposition"; + return "no_verdict"; }, [verdicts]); useEffect(() => { @@ -260,66 +260,34 @@ const VerdictPanel: React.FC = ({
- {majorityVerdict === "Proposition" ? ( - <> - - {t("majority.proposition.prefix")} - - - - {t("majority.proposition.highlight")} - - - - {t("majority.proposition.suffix")} - - - ) : majorityVerdict === "Opposition" ? ( - <> - - {t("majority.opposition.prefix")} - - - - {t("majority.opposition.highlight")} - - - - {t("majority.opposition.suffix")} - - - ) : ( - <> - - {t("majority.no_verdict.prefix")} - - - - {t("majority.no_verdict.highlight")} - - - - {t("majority.no_verdict.suffix")} - - - )} + <> + + {t.rich(`majority.${majorityVerdict ?? "no_verdict"}`, { + highlight: (chunks) => ( + + {chunks} + + ), + })} + +
) : (
Date: Wed, 10 Jun 2026 22:23:53 +0200 Subject: [PATCH 03/11] i18n: add missing translations --- .../t/[path]/debates/[debate_id]/page.tsx | 36 +++++++------------ src/components/tournament/MarshalPanel.tsx | 1 - src/components/tournament/VerdictPanel.tsx | 2 +- src/i18n/translations/en.json | 19 +++++++++- src/i18n/translations/pl.json | 19 +++++++++- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx b/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx index 419b4a8..c85e2c3 100644 --- a/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx +++ b/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx @@ -118,41 +118,29 @@ export default async function DebateDetailsPage({

- {" "} - {motion}{" "} + {motion}

ret
-
-
- {" "} - {/*{phaseName} | {roundName}{" "}*/} -
-
+
- -
- {" "} - Information about assigned Marshal will be presented here.{" "} -
+ +
{t("time_and_place_info.placeholder_text")}
+
+ + +
{t("teams_info.placeholder_text")}
- -
- {" "} - Information about assigned Judges will be presented here.{" "} -
+ +
{t("marshal_info.placeholder_text")}
- -
- {" "} - Information about time and place of the debate will be presented - here.{" "} -
+ +
{t("judges_info.placeholder_text")}
diff --git a/src/components/tournament/MarshalPanel.tsx b/src/components/tournament/MarshalPanel.tsx index e80c2f0..41e5674 100644 --- a/src/components/tournament/MarshalPanel.tsx +++ b/src/components/tournament/MarshalPanel.tsx @@ -15,7 +15,6 @@ export function MarshalPanel({ motion }: MarshalPanelProps) { return ( = ({ } return ( - + {isJudge ? (
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index b76ec52..a016bfb 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -116,7 +116,23 @@ "debate_details": { "unconfigured_debate": "Debate without a motion", "proceed_to_debate": "Proceed to Debate!", - "marshal_panel": "Marshal Panel" + "marshal_panel": "Marshal Panel", + "marshal_info": { + "title": "Marshal", + "placeholder_text": "Information about assigned Marshal will be presented here." + }, + "judges_info": { + "title": "Judges", + "placeholder_text": "Information about assigned Judges will be presented here." + }, + "time_and_place_info": { + "title": "Time and place", + "placeholder_text": "Information about time and place of the debate will be presented here" + }, + "teams_info": { + "title": "Teams", + "placeholder_text": "Information about competing teams will be presented here" + } }, "create_tournament": { "title": "Create Tournament", @@ -126,6 +142,7 @@ "full_name_placeholder": "(Required)" }, "verdict_panel": { + "title": "Verdict", "proposition": "Proposition", "opposition": "Opposition", "submit": "Submit", diff --git a/src/i18n/translations/pl.json b/src/i18n/translations/pl.json index 02144b0..c75151e 100644 --- a/src/i18n/translations/pl.json +++ b/src/i18n/translations/pl.json @@ -116,7 +116,23 @@ "debate_details": { "unconfigured_debate": "Debata bez tezy", "proceed_to_debate": "Przejdź do debaty!", - "marshal_panel": "Panel marszałka" + "marshal_panel": "Panel marszałka", + "marshal_info": { + "title": "Marszałek", + "placeholder_text": "Informacja o marszałku przypisanym do debaty pojawi się tutaj." + }, + "judges_info": { + "title": "Sędziowie", + "placeholder_text": "Informacje o panelu sędziowskim przypisanym do tej debaty pojawią się tutaj." + }, + "time_and_place_info": { + "title": "Czas i miejsce", + "placeholder_text": "Informacje o czasie oraz miejscu debaty pojawią się tutaj." + }, + "teams_info": { + "title": "Teams", + "placeholder_text": "Informacje o drużynach biorących udział w debacie pojawią się tutaj." + } }, "create_tournament": { "title": "Utwórz turniej", @@ -126,6 +142,7 @@ "full_name_placeholder": "(Wymagane)" }, "verdict_panel": { + "title": "Werdykt", "proposition": "Propozycja", "opposition": "Opozycja", "submit": "Wyślij", From 60c1ecf48842dae81d99ed464c93257a3c18e991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 22:31:53 +0200 Subject: [PATCH 04/11] test: fix locator in page fixture --- test/e2e/e2eUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/e2eUtils.ts b/test/e2e/e2eUtils.ts index edd5b48..d93e684 100644 --- a/test/e2e/e2eUtils.ts +++ b/test/e2e/e2eUtils.ts @@ -214,7 +214,7 @@ const createTournament = async (page: Page) => { await page.waitForURL(/\/en\/t\/[^/]+/); await page.getByRole("link", { name: "Overview" }).click(); - await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Overview" })).toBeVisible(); }; export class TestContainers { From b6f16d187e45a9680d278662f987a40981d29b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 11:19:47 +0200 Subject: [PATCH 05/11] fix: use ghcr in testcontainers --- test/e2e/e2eUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/e2eUtils.ts b/test/e2e/e2eUtils.ts index d93e684..2e52755 100644 --- a/test/e2e/e2eUtils.ts +++ b/test/e2e/e2eUtils.ts @@ -229,7 +229,7 @@ export class TestContainers { .withNetworkAliases("postgres") .start(); const connectionString = `postgresql://${db.getUsername()}:${db.getPassword()}@postgres:5432/${db.getDatabase()}`; - const server = await new GenericContainer("tau:latest") + const server = await new GenericContainer("ghcr.io/debatecore/tau:latest") .withNetwork(network) .withExposedPorts(DEFAULT_BACKEND_PORT) .withWaitStrategy(Wait.forHttp("/health", DEFAULT_BACKEND_PORT)) From 9fc67058130bdd1a905eb735c4dd6c38817119c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 22:41:34 +0200 Subject: [PATCH 06/11] chore: bump version number --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e51c1b4..60fb158 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "granda-front", - "version": "0.3.13", + "version": "0.3.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granda-front", - "version": "0.3.13", + "version": "0.3.14", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-select": "^2.2.6", diff --git a/package.json b/package.json index 4f2207a..d53bb34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granda-front", - "version": "0.3.13", + "version": "0.3.14", "private": true, "scripts": { "dev": "next dev --turbopack", From ab49d15f6261f687bca4b1605a656caee3329bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Wed, 10 Jun 2026 23:09:04 +0200 Subject: [PATCH 07/11] fix: show verdict information to non-judges --- src/components/tournament/VerdictPanel.tsx | 69 ++++++++++---------- test/e2e/tournament-ladder-rendering.test.ts | 2 +- test/e2e/verdictPanel.test.ts | 18 ++--- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/components/tournament/VerdictPanel.tsx b/src/components/tournament/VerdictPanel.tsx index 9c334c8..91ab119 100644 --- a/src/components/tournament/VerdictPanel.tsx +++ b/src/components/tournament/VerdictPanel.tsx @@ -24,7 +24,7 @@ const VerdictPanel: React.FC = ({ tournamentId, debateId, }) => { - const [isJudge, setIsJudge] = useState(null); + const [canSubmitVerdict, setCanSubmitVerdit] = useState(null); const [verdicts, setVerdicts] = useState([]); const [selectedVote, setSelectedVote] = useState(null); const [submitting, setSubmitting] = useState(false); @@ -74,7 +74,7 @@ const VerdictPanel: React.FC = ({ }), ]) .then(([permissionData, verdictsData]) => { - setIsJudge(Boolean(permissionData)); + setCanSubmitVerdit(Boolean(permissionData)); const loadedVerdicts: VerdictRecord[] = Array.isArray(verdictsData) ? verdictsData : []; @@ -184,9 +184,37 @@ const VerdictPanel: React.FC = ({ ); } + const VerdictInformation = () => { + return ( +
+
+ <> + + {t.rich(`majority.${majorityVerdict ?? "no_verdict"}`, { + highlight: (chunks) => ( + + {chunks} + + ), + })} + + +
+
+ ); + }; + return ( - {isJudge ? ( + {canSubmitVerdict ? (
- -
-
- <> - - {t.rich(`majority.${majorityVerdict ?? "no_verdict"}`, { - highlight: (chunks) => ( - - {chunks} - - ), - })} - - -
-
+
) : ( -
+ )}
); diff --git a/test/e2e/tournament-ladder-rendering.test.ts b/test/e2e/tournament-ladder-rendering.test.ts index 732c6db..e40f47d 100644 --- a/test/e2e/tournament-ladder-rendering.test.ts +++ b/test/e2e/tournament-ladder-rendering.test.ts @@ -60,7 +60,7 @@ testInTournamentAsAdmin( page.getByText("You can click on a specific box"), ).toBeVisible(); await expect( - page.getByText("The ladder shows the structure and flow"), + page.getByText("The ladder visualizes the flow of the tournament"), ).toBeVisible(); // WHEN diff --git a/test/e2e/verdictPanel.test.ts b/test/e2e/verdictPanel.test.ts index d2bf75b..71fd262 100644 --- a/test/e2e/verdictPanel.test.ts +++ b/test/e2e/verdictPanel.test.ts @@ -7,6 +7,9 @@ import { createUserAndCastVote, } from "./e2eUtils"; +const PROPOSITION_WINNING_TEXT = "The Proposition is the"; +const OPPOSITION_WINNING_TEXT = "The Opposition is the"; + test.describe("verdict panel", () => { testInTournamentAsAdmin( "judge permission renders verdict submission options", @@ -49,8 +52,9 @@ test.describe("verdict panel", () => { .first() .click(); - await expect(page.getByText("Verdict panel")).toBeVisible(); + await expect(page.getByText("Verdict", { exact: true })).toBeVisible(); await expect(page.getByText("Opposition")).not.toBeVisible(); + await expect(page.getByText("no verdict")).toBeVisible(); }, ); @@ -86,8 +90,7 @@ test.describe("verdict panel", () => { .click(); await page.waitForURL(/debates/); - const winningText = page.getByText("ThePropositionis the winning"); - await expect(winningText).toBeVisible(); + await expect(page.getByText(PROPOSITION_WINNING_TEXT)).toBeVisible(); }, ); @@ -120,8 +123,7 @@ test.describe("verdict panel", () => { await page.getByRole("button", { name: "Proposition" }).click(); await page.getByRole("button", { name: "Submit" }).click(); - const winningText = page.getByText("ThePropositionis the winning"); - await expect(winningText).toBeVisible(); + await expect(page.getByText(PROPOSITION_WINNING_TEXT)).toBeVisible(); }, ); @@ -154,14 +156,12 @@ test.describe("verdict panel", () => { await page.getByRole("button", { name: "Proposition" }).click(); await page.getByRole("button", { name: "Submit" }).click(); - const winningText = page.getByText("ThePropositionis the winning"); - await expect(winningText).toBeVisible(); + await expect(page.getByText(PROPOSITION_WINNING_TEXT)).toBeVisible(); await page.getByRole("button", { name: "Opposition" }).click(); await page.getByRole("button", { name: "Submit" }).click(); - const otherWinningText = page.getByText("TheOppositionis the winning"); - await expect(otherWinningText).toBeVisible(); + await expect(page.getByText(OPPOSITION_WINNING_TEXT)).toBeVisible(); }, ); }); From 2e04c255c83cf37fe02ad30f7e50220975d3174b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Fri, 12 Jun 2026 18:13:16 +0200 Subject: [PATCH 08/11] chore: bump version number --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 464f106..f360f82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "granda-front", - "version": "0.3.14", + "version": "0.3.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granda-front", - "version": "0.3.14", + "version": "0.3.15", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-select": "^2.2.6", diff --git a/package.json b/package.json index d53bb34..f69d1f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granda-front", - "version": "0.3.14", + "version": "0.3.15", "private": true, "scripts": { "dev": "next dev --turbopack", From bf8dac4c349ff1b63e54b8a497f796db2dfcfd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Fri, 12 Jun 2026 22:22:14 +0200 Subject: [PATCH 09/11] i18n: add missing polish translation, create a translation script --- .gitignore | 4 +- scripts/i18ndiffer/.python-version | 1 + scripts/i18ndiffer/README.md | 6 ++ scripts/i18ndiffer/pyproject.toml | 9 +++ scripts/i18ndiffer/src/__init__.py | 0 scripts/i18ndiffer/src/main.py | 47 ++++++++++++ scripts/i18ndiffer/test/__init__.py | 0 scripts/i18ndiffer/test/test_diffing.py | 98 +++++++++++++++++++++++++ scripts/i18ndiffer/uv.lock | 75 +++++++++++++++++++ src/i18n/translations/pl.json | 12 ++- 10 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 scripts/i18ndiffer/.python-version create mode 100644 scripts/i18ndiffer/README.md create mode 100644 scripts/i18ndiffer/pyproject.toml create mode 100644 scripts/i18ndiffer/src/__init__.py create mode 100644 scripts/i18ndiffer/src/main.py create mode 100644 scripts/i18ndiffer/test/__init__.py create mode 100644 scripts/i18ndiffer/test/test_diffing.py create mode 100644 scripts/i18ndiffer/uv.lock diff --git a/.gitignore b/.gitignore index 304f809..9d27c33 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,6 @@ node_modules/ /playwright/.auth/ # eslint -.eslintcache \ No newline at end of file +.eslintcache +scripts/i18ndiffer/.pytest_cache/ +scripts/i18ndiffer/src/__pycache__/ diff --git a/scripts/i18ndiffer/.python-version b/scripts/i18ndiffer/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/scripts/i18ndiffer/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/scripts/i18ndiffer/README.md b/scripts/i18ndiffer/README.md new file mode 100644 index 0000000..c2f41e3 --- /dev/null +++ b/scripts/i18ndiffer/README.md @@ -0,0 +1,6 @@ +This utility can be used to add missing lines to i18n files while preserving their line-by-line structure. + +Usage: +```bash +diff -y [file1] [file2] | uv run src/main.py [file1] [file2] +``` diff --git a/scripts/i18ndiffer/pyproject.toml b/scripts/i18ndiffer/pyproject.toml new file mode 100644 index 0000000..40e4acf --- /dev/null +++ b/scripts/i18ndiffer/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "i18ndiffer" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "pytest>=9.0.3", +] diff --git a/scripts/i18ndiffer/src/__init__.py b/scripts/i18ndiffer/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/i18ndiffer/src/main.py b/scripts/i18ndiffer/src/main.py new file mode 100644 index 0000000..c169e2c --- /dev/null +++ b/scripts/i18ndiffer/src/main.py @@ -0,0 +1,47 @@ +import re +import sys + + +def main(): + path1, path2 = sys.argv[1], sys.argv[2] + diff_file: str = sys.stdin.read() + diff_lines = diff_file.split("\n") + lines1, lines2 = read_lines_from_files(path1, path2) + file1, file2 = insert_missing_lines(lines1, lines2, diff_lines) + + with open(path1, "w") as f: + f.writelines(file1) + with open(path2, "w") as f: + f.writelines(file2) + + +def insert_missing_lines(lines1: list[str], lines2: list[str], diff_lines: list[str]): + for i in range(len(diff_lines)): + if diff_lines[i].endswith("<"): + lines2.insert(i, prepare_placeholder_from(lines1[i])) + elif diff_lines[i].lstrip().startswith(">"): + lines1.insert(i, prepare_placeholder_from(lines2[i])) + + return lines1, lines2 + + +def read_lines_from_files(path1, path2): + lines1 = [] + lines2 = [] + + with open(path1, "r") as f1: + lines1 = f1.readlines() + f1.close() + with open(path2, "r") as f2: + lines2 = f2.readlines() + f2.close() + return lines1, lines2 + + +def prepare_placeholder_from(line: str) -> str: + capture_key_without_value_pattern = r'(\s*".+":\s*")(.+".*)' + return re.sub(capture_key_without_value_pattern, r"\1[CHANGE ME!] \2", line) + + +if __name__ == "__main__": + main() diff --git a/scripts/i18ndiffer/test/__init__.py b/scripts/i18ndiffer/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/i18ndiffer/test/test_diffing.py b/scripts/i18ndiffer/test/test_diffing.py new file mode 100644 index 0000000..85ef95e --- /dev/null +++ b/scripts/i18ndiffer/test/test_diffing.py @@ -0,0 +1,98 @@ +import re + +from src.main import insert_missing_lines, prepare_placeholder_from + + +def test_preparing_placeholders_basic(): + assert ( + prepare_placeholder_from(' "msg_handle_empty": "Handle must not be empty.",') + == ' "msg_handle_empty": "[CHANGE ME!] Handle must not be empty.",' + ) + + +def test_preparing_placeholders_with_curly_braces(): + assert ( + prepare_placeholder_from(' "error_message": "Details: {message}"') + == ' "error_message": "[CHANGE ME!] Details: {message}"' + ) + + +def test_instert_lines_right_to_left(): + diff_file = [ + ' "debate_details": { "debate_details": {', + ' "unconfigured_debate": "Debata bez tezy" | "unconfigured_debate": "Debate without a motion",', + ' > "proceed_to_debate": "Proceed to Debate!",', + ' > "marshal_panel": "Marshal Panel"', + " > },", + ' > "create_tournament": {', + ' > "title": "Create Tournament",', + ' > "full_name": "Full name",', + ] + + file1 = [ + ' "debate_details": {', + ' "unconfigured_debate": "Debata bez tezy"', + " }", + "}", + ] + file2 = [ + ' "debate_details": {', + ' "unconfigured_debate": "Debate without a motion",', + ' "proceed_to_debate": "Proceed to Debate!",', + ' "marshal_panel": "Marshal Panel"', + " },", + ' "create_tournament": {', + ' "title": "Create Tournament",', + ' "full_name": "Full name",', + ' "short_name": "Short name",', + ' "close": "Close",', + ' "full_name_placeholder": "(Required)"', + " },", + ] + + file1, file2 = insert_missing_lines(file1, file2, diff_file) + + empty_value_pattern = r'(\s*".+":\s*"".*)' + + value_to_change_pattern = r'(\s*".+":\s*"\[CHANGE ME!\].+".*)' + for line in file1: + if not re.findall(r"debate|{|}", line): + assert re.match(value_to_change_pattern, line) + + +def test_insert_lines_left_to_right(): + diff_file = [ + ' "metadata": { "metadata": {', + ' "title": "granda: Debate Tournament Planner", | "title": "granda: Zaplanuj swój turniej debat",', + ' "description": "Computer aided debate tournament organizi | "description": "Komputerowo wspomagane planowanie turniej', + " }, <", + ' "generic": { <', + ' "error": "Error", <', + ' "error_loading_content": "An error occurred while loading <', + ' "error_message": "Details: {message}" <', + " }, },", + ] + file1 = [ + ' "metadata": {', + ' "title": "granda: Debate Tournament Planner",', + ' "description": "Computer aided debate tournament organizing experience enrichment."', + " },", + ' "generic": {', + ' "error": "Error",', + ' "error_loading_content": "An error occurred while loading the content.",', + ' "error_message": "Details: {message}"', + " },", + ] + file2 = [ + ' "metadata": {', + ' "title": "granda: Zaplanuj swój turniej debat",', + ' "description": "Komputerowo wspomagane planowanie turniejów debat."', + " },", + ] + + file1, file2 = insert_missing_lines(file1, file2, diff_file) + + value_to_change_pattern = r'(\s*".+":\s*"\[CHANGE ME!\].+".*)' + for line in file2: + if line.__contains__("error"): + assert re.match(value_to_change_pattern, line) diff --git a/scripts/i18ndiffer/uv.lock b/scripts/i18ndiffer/uv.lock new file mode 100644 index 0000000..cb6098e --- /dev/null +++ b/scripts/i18ndiffer/uv.lock @@ -0,0 +1,75 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "i18ndiffer" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", specifier = ">=9.0.3" }] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] diff --git a/src/i18n/translations/pl.json b/src/i18n/translations/pl.json index c75151e..9b9faeb 100644 --- a/src/i18n/translations/pl.json +++ b/src/i18n/translations/pl.json @@ -65,7 +65,17 @@ "footer_text": "Komputerowe wspomaganie debat dostarcza ." }, "overview": { - "title": "Panel główny" + "title": "Panel główny", + "welcome": "Witaj w turnieju {tournamentName}!", + "get_started": "Aby rozpocząć, przejdź do Drabinki Turniejowej, aby zaplanować przebieg turnieju.", + "leaderboard_placeholder": "Wyniki drużyn (już wkrótce!)", + "leaderboard_desc": "Tutaj znajdzie się tabela z wynikami drużyn biorących udział w turnieju.", + "debates_placeholder": "Nadchodzące debaty (już wkrótce!)", + "debates_desc": "Tutaj znajdą się informacje o najbliższych zaplanowanych debatach.", + "event_log_placeholder": "Dziennik zdarzeń (już wkrótce!)", + "event_log_desc": "Tutaj zapisywane będą działania wykonywane przez użytkowników biorących udział w turnieju.", + "rules_placeholder": "Zasady turnieju (już wkrótce!)", + "rules_desc": "Tutaj pojawią się skrócone zasady turnieju." }, "metadata": { "title": "granda: Zaplanuj swój turniej debat", From 7599fc5af10121e6d644f0ac02e048502a2be763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Fri, 12 Jun 2026 22:24:29 +0200 Subject: [PATCH 10/11] chore: bump version number --- .gitignore | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 9d27c33..6d8036d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,5 @@ node_modules/ # eslint .eslintcache -scripts/i18ndiffer/.pytest_cache/ -scripts/i18ndiffer/src/__pycache__/ +.pytest_cache/ +__pycache__/ diff --git a/package-lock.json b/package-lock.json index f360f82..3944405 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "granda-front", - "version": "0.3.15", + "version": "0.3.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granda-front", - "version": "0.3.15", + "version": "0.3.16", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-select": "^2.2.6", diff --git a/package.json b/package.json index f69d1f4..0fdcebf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granda-front", - "version": "0.3.15", + "version": "0.3.16", "private": true, "scripts": { "dev": "next dev --turbopack", From dc85668aab853139392a783f18888faf17c362cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dobrzy=C5=84ski?= Date: Sat, 13 Jun 2026 20:54:54 +0200 Subject: [PATCH 11/11] refactor: fix a typo --- package-lock.json | 4 ++-- package.json | 2 +- src/components/tournament/VerdictPanel.tsx | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3944405..232a843 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "granda-front", - "version": "0.3.16", + "version": "0.3.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granda-front", - "version": "0.3.16", + "version": "0.3.17", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-select": "^2.2.6", diff --git a/package.json b/package.json index 0fdcebf..c767d80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granda-front", - "version": "0.3.16", + "version": "0.3.17", "private": true, "scripts": { "dev": "next dev --turbopack", diff --git a/src/components/tournament/VerdictPanel.tsx b/src/components/tournament/VerdictPanel.tsx index 91ab119..e37b69f 100644 --- a/src/components/tournament/VerdictPanel.tsx +++ b/src/components/tournament/VerdictPanel.tsx @@ -24,7 +24,9 @@ const VerdictPanel: React.FC = ({ tournamentId, debateId, }) => { - const [canSubmitVerdict, setCanSubmitVerdit] = useState(null); + const [canSubmitVerdict, setCanSubmitVerdict] = useState( + null, + ); const [verdicts, setVerdicts] = useState([]); const [selectedVote, setSelectedVote] = useState(null); const [submitting, setSubmitting] = useState(false); @@ -74,7 +76,7 @@ const VerdictPanel: React.FC = ({ }), ]) .then(([permissionData, verdictsData]) => { - setCanSubmitVerdit(Boolean(permissionData)); + setCanSubmitVerdict(Boolean(permissionData)); const loadedVerdicts: VerdictRecord[] = Array.isArray(verdictsData) ? verdictsData : [];