diff --git a/frontend/messages/de.json b/frontend/messages/de.json index e5929a88..2d819091 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -1054,6 +1054,7 @@ "timeline_bucket_show_more": "+ {count} weitere Briefe anzeigen", "timeline_bucket_show_less": "Weniger anzeigen", "timeline_letter_glyph_label": "Brief", + "timeline_cluster_letter_count": "{count} Briefe", "timeline_tag_chip_label": "Thema", "timeline_layer_historical_suffix": "historisch", "timeline_strip_density_caption": "Monats-Dichte", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 2388aae4..a9ed5ce8 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -1054,6 +1054,7 @@ "timeline_bucket_show_more": "+ {count} more letters", "timeline_bucket_show_less": "Show fewer", "timeline_letter_glyph_label": "Letter", + "timeline_cluster_letter_count": "{count} letters", "timeline_tag_chip_label": "Topic", "timeline_layer_historical_suffix": "historical", "timeline_strip_density_caption": "Monthly density", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 5f99f5b6..15239a92 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -1054,6 +1054,7 @@ "timeline_bucket_show_more": "+ {count} cartas más", "timeline_bucket_show_less": "Mostrar menos", "timeline_letter_glyph_label": "Carta", + "timeline_cluster_letter_count": "{count} cartas", "timeline_tag_chip_label": "Tema", "timeline_layer_historical_suffix": "histórico", "timeline_strip_density_caption": "Densidad mensual", diff --git a/frontend/src/lib/messages.spec.ts b/frontend/src/lib/messages.spec.ts index ecfeb9dd..1003bf37 100644 --- a/frontend/src/lib/messages.spec.ts +++ b/frontend/src/lib/messages.spec.ts @@ -100,6 +100,14 @@ describe('message key parity', () => { expect(es).toMatchObject({ timeline_tag_chip_label: 'Tema' }); }); + // #850 finding #6: the event-card letter count carries an sr-only "{count} Briefe" so the + // bare "· 2" never announces to a screen reader without context. + it('zeitstrahl cluster letter-count key is present in all locales (#850 finding #6)', () => { + expect(de).toHaveProperty('timeline_cluster_letter_count'); + expect(en).toHaveProperty('timeline_cluster_letter_count'); + expect(es).toHaveProperty('timeline_cluster_letter_count'); + }); + // #780 REQ-010: the layer-filter strings are Paraglide keys in every locale. // timeline_filter_trigger (0 active) and timeline_filter_trigger_active ({count}, // ≥1 active) are distinct keys so the trigger never reads "Filter (0 aktiv)". diff --git a/frontend/src/lib/timeline/EventCluster.svelte b/frontend/src/lib/timeline/EventCluster.svelte index 9fa1f2e8..bbf7da3c 100644 --- a/frontend/src/lib/timeline/EventCluster.svelte +++ b/frontend/src/lib/timeline/EventCluster.svelte @@ -86,7 +86,11 @@ const hiddenCount = $derived(letters.length - CLUSTER_PREVIEW); >{event.title} - {eventSubtitle} · {count} + {eventSubtitle} + + + {m.timeline_cluster_letter_count({ count })} + {#if canEdit} @@ -111,7 +115,10 @@ const hiddenCount = $derived(letters.length - CLUSTER_PREVIEW); {title} - · {count} + + + {m.timeline_cluster_letter_count({ count })} + {/if} diff --git a/frontend/src/lib/timeline/EventCluster.svelte.spec.ts b/frontend/src/lib/timeline/EventCluster.svelte.spec.ts index ee19aa2d..ed8f109b 100644 --- a/frontend/src/lib/timeline/EventCluster.svelte.spec.ts +++ b/frontend/src/lib/timeline/EventCluster.svelte.spec.ts @@ -108,6 +108,16 @@ describe('EventCluster — contained event card (#850)', () => { expect(srOnly?.textContent).toBe(m.timeline_letter_glyph_label()); }); + it('gives the letter count an sr-only "{count} Briefe" label so "· 2" is not announced bare (finding #6)', () => { + render(EventCluster, { letters: letters(2), title: 'Briefe von der Front' }); + const count = document.querySelector('[data-testid="event-count"]') as HTMLElement; + // the visible "· 2" stays aria-hidden; the sr-only sibling carries the meaning + expect(count.querySelector('[aria-hidden="true"]')?.textContent).toContain('· 2'); + expect(count.querySelector('.sr-only')?.textContent).toBe( + m.timeline_cluster_letter_count({ count: 2 }) + ); + }); + it('renders an HTML-bearing event title verbatim as text, never as markup (REQ-010)', () => { render(EventCluster, { letters: letters(1),