From 398babe58458d5649025f39664ed41bad9302245 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Jun 2026 12:15:11 +0200 Subject: [PATCH] fix(timeline): pluralize the zeitstrahl meta-line counts A count of one rendered "1 Briefe" / "1 Ereignisse". Add _singular companion keys (de/en/es) and select them when the count is exactly one, following the project's _singular/_plural convention. Co-Authored-By: Claude Opus 4.8 --- frontend/messages/de.json | 2 ++ frontend/messages/en.json | 2 ++ frontend/messages/es.json | 2 ++ frontend/src/lib/messages.spec.ts | 4 +++- frontend/src/routes/zeitstrahl/+page.svelte | 15 ++++++++++++--- .../src/routes/zeitstrahl/page.svelte.spec.ts | 18 +++++++++++++++++- 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/frontend/messages/de.json b/frontend/messages/de.json index c3692488..91bc789d 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -1053,6 +1053,8 @@ "timeline_layer_historical_suffix": "historisch", "timeline_strip_density_caption": "Monats-Dichte", "timeline_events_count": "{count} Ereignisse", + "timeline_letters_count_singular": "1 Brief", + "timeline_events_count_singular": "1 Ereignis", "event_editor_new_title": "Neues Ereignis", "event_editor_edit_title": "Ereignis bearbeiten", "event_editor_section_when": "Wann", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index eb3a08ea..b9add670 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -1053,6 +1053,8 @@ "timeline_layer_historical_suffix": "historical", "timeline_strip_density_caption": "Monthly density", "timeline_events_count": "{count} events", + "timeline_letters_count_singular": "1 letter", + "timeline_events_count_singular": "1 event", "event_editor_new_title": "New event", "event_editor_edit_title": "Edit event", "event_editor_section_when": "When", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index c2d6df92..f015c6d5 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -1053,6 +1053,8 @@ "timeline_layer_historical_suffix": "histórico", "timeline_strip_density_caption": "Densidad mensual", "timeline_events_count": "{count} eventos", + "timeline_letters_count_singular": "1 carta", + "timeline_events_count_singular": "1 evento", "event_editor_new_title": "Nuevo evento", "event_editor_edit_title": "Editar evento", "event_editor_section_when": "Cuándo", diff --git a/frontend/src/lib/messages.spec.ts b/frontend/src/lib/messages.spec.ts index 3d98c413..eadd4030 100644 --- a/frontend/src/lib/messages.spec.ts +++ b/frontend/src/lib/messages.spec.ts @@ -80,7 +80,9 @@ describe('message key parity', () => { 'timeline_letter_glyph_label', 'timeline_layer_historical_suffix', 'timeline_strip_density_caption', - 'timeline_events_count' + 'timeline_events_count', + 'timeline_letters_count_singular', + 'timeline_events_count_singular' ]; for (const key of requiredKeys) { expect(de, `missing key in de: ${key}`).toHaveProperty(key); diff --git a/frontend/src/routes/zeitstrahl/+page.svelte b/frontend/src/routes/zeitstrahl/+page.svelte index 098cf91e..75dad484 100644 --- a/frontend/src/routes/zeitstrahl/+page.svelte +++ b/frontend/src/routes/zeitstrahl/+page.svelte @@ -18,12 +18,21 @@ const metaLine = $derived.by(() => { if (meta.firstYear !== null && meta.lastYear !== null) { segments.push(`${meta.firstYear}–${meta.lastYear}`); } - // A zero-count segment ("0 Briefe") reads as a data error — drop it. + // A zero-count segment ("0 Briefe") reads as a data error — drop it; a count + // of one takes the singular key ("1 Brief"), per the project plural convention. if (meta.letterCount > 0) { - segments.push(m.timeline_letters_count({ count: meta.letterCount })); + segments.push( + meta.letterCount === 1 + ? m.timeline_letters_count_singular() + : m.timeline_letters_count({ count: meta.letterCount }) + ); } if (meta.eventCount > 0) { - segments.push(m.timeline_events_count({ count: meta.eventCount })); + segments.push( + meta.eventCount === 1 + ? m.timeline_events_count_singular() + : m.timeline_events_count({ count: meta.eventCount }) + ); } segments.push(m.timeline_grouping_date()); return segments.join(' · '); diff --git a/frontend/src/routes/zeitstrahl/page.svelte.spec.ts b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts index f1d40d9a..b951f7c4 100644 --- a/frontend/src/routes/zeitstrahl/page.svelte.spec.ts +++ b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts @@ -68,7 +68,7 @@ describe('/zeitstrahl page', () => { const sub = document.querySelector('[data-testid="timeline-meta"]'); expect(sub).not.toBeNull(); expect(sub?.textContent).not.toContain('–'); - expect(sub?.textContent).toContain(m.timeline_letters_count({ count: 1 })); + expect(sub?.textContent).toContain(m.timeline_letters_count_singular()); }); it('omits the entire sub-line for an empty timeline (REQ-002)', () => { @@ -94,4 +94,20 @@ describe('/zeitstrahl page', () => { expect(sub).not.toBeNull(); expect(sub?.textContent).not.toContain(m.timeline_events_count({ count: 0 })); }); + + it('uses singular count labels for exactly one letter and one event (REQ-002)', () => { + render(Page, { + data: pageData( + makeTimelineDTO({ + years: [makeYear(1914, [makeEntry({ documentId: 'a' }), event('Geburt')])] + }) + ) + }); + const sub = document.querySelector('[data-testid="timeline-meta"]'); + expect(sub?.textContent).toContain(m.timeline_letters_count_singular()); + expect(sub?.textContent).toContain(m.timeline_events_count_singular()); + // never the "1 Briefe"/"1 Ereignisse" plural forms for a count of one + expect(sub?.textContent).not.toContain(m.timeline_letters_count({ count: 1 })); + expect(sub?.textContent).not.toContain(m.timeline_events_count({ count: 1 })); + }); });