import { describe, it, expect } from 'vitest'; import de from '../../messages/de.json'; import en from '../../messages/en.json'; import es from '../../messages/es.json'; describe('message key parity', () => { it('de, en, and es have identical key sets', () => { const deKeys = Object.keys(de).sort(); const enKeys = Object.keys(en).sort(); const esKeys = Object.keys(es).sort(); expect(enKeys).toEqual(deKeys); expect(esKeys).toEqual(deKeys); }); it('viewer navigation keys are present in all locales', () => { const requiredViewerKeys = [ 'viewer_previous_page', 'viewer_next_page', 'viewer_zoom_out', 'viewer_zoom_in' ]; for (const key of requiredViewerKeys) { expect(de, `missing key in de: ${key}`).toHaveProperty(key); expect(en, `missing key in en: ${key}`).toHaveProperty(key); expect(es, `missing key in es: ${key}`).toHaveProperty(key); } }); it('transcribe mark-for-training key is present in all locales', () => { expect(de).toHaveProperty('transcribe_mark_for_training'); expect(en).toHaveProperty('transcribe_mark_for_training'); expect(es).toHaveProperty('transcribe_mark_for_training'); }); it('layout menu open/close keys are present in all locales', () => { expect(de).toHaveProperty('layout_menu_open'); expect(de).toHaveProperty('layout_menu_close'); expect(en).toHaveProperty('layout_menu_open'); expect(en).toHaveProperty('layout_menu_close'); expect(es).toHaveProperty('layout_menu_open'); expect(es).toHaveProperty('layout_menu_close'); }); // REQ-024: the timeline layer/life-event labels feed sr-only / aria text, so // they are localized per locale (the original German-only MVP decision was // reversed for accessibility). Pin the values so en/es can never silently // drift back to the German source strings. it('timeline layer/derived labels are localized per locale (REQ-024)', () => { expect(de).toMatchObject({ timeline_layer_world: 'Weltgeschehen', timeline_layer_family: 'Familie', timeline_derived_birth: 'Geburt', timeline_derived_death: 'Tod', timeline_derived_marriage: 'Heirat' }); expect(en).toMatchObject({ timeline_layer_world: 'World events', timeline_layer_family: 'Family', timeline_derived_birth: 'Birth', timeline_derived_death: 'Death', timeline_derived_marriage: 'Marriage' }); expect(es).toMatchObject({ timeline_layer_world: 'Acontecimientos mundiales', timeline_layer_family: 'Familia', timeline_derived_birth: 'Nacimiento', timeline_derived_death: 'Fallecimiento', timeline_derived_marriage: 'Matrimonio' }); }); // #833 REQ-015: the new visual-fidelity strings (meta line, provenance token, // ✉ label, world-band suffix, density caption) are Paraglide keys present in // every locale so no surface ever falls back to a missing translation. it('zeitstrahl visual-fidelity keys are present in all locales (#833 REQ-015)', () => { const requiredKeys = [ 'timeline_grouping_date', 'timeline_provenance_derived', 'timeline_provenance_curated', 'timeline_letter_glyph_label', 'timeline_layer_historical_suffix', 'timeline_strip_density_caption', '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); expect(en, `missing key in en: ${key}`).toHaveProperty(key); expect(es, `missing key in es: ${key}`).toHaveProperty(key); } }); // #835 REQ-013: the letter chip's sr-only theme label is a Paraglide key in every // locale so color is never the only cue; the tag NAME is rendered as data, not translated. it('zeitstrahl tag-chip label key is present in all locales (#835 REQ-013)', () => { expect(de).toMatchObject({ timeline_tag_chip_label: 'Thema' }); expect(en).toMatchObject({ timeline_tag_chip_label: 'Topic' }); expect(es).toMatchObject({ timeline_tag_chip_label: 'Tema' }); }); // #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)". it('zeitstrahl layer-filter keys are present in all locales (#780 REQ-010)', () => { const requiredKeys = [ 'timeline_filter_label_layers', 'timeline_filter_layer_personal', 'timeline_filter_layer_historical', 'timeline_filter_layer_letters', 'timeline_filter_trigger', 'timeline_filter_trigger_active', 'timeline_filter_reset', 'timeline_filter_empty_state' ]; for (const key of requiredKeys) { expect(de, `missing key in de: ${key}`).toHaveProperty(key); expect(en, `missing key in en: ${key}`).toHaveProperty(key); expect(es, `missing key in es: ${key}`).toHaveProperty(key); } // the active-count key carries the established {count} placeholder expect(de.timeline_filter_trigger_active).toContain('{count}'); expect(en.timeline_filter_trigger_active).toContain('{count}'); expect(es.timeline_filter_trigger_active).toContain('{count}'); }); // #842: the two curator-affordance CTA labels (Zeitstrahl header + person page) // are Paraglide keys present in every locale; the edit pencils reuse btn_edit. it('curator-affordance CTA keys are present in all locales (#842)', () => { for (const key of ['timeline_add_event', 'person_add_event']) { expect(de, `missing key in de: ${key}`).toHaveProperty(key); expect(en, `missing key in en: ${key}`).toHaveProperty(key); expect(es, `missing key in es: ${key}`).toHaveProperty(key); } }); // #827 REQ-012: the grouping toggle + bucket strings are new Paraglide keys in // every locale; the pre-existing timeline_grouping_date / timeline_tag_chip_label / // timeline_filter_* set is reused, never re-added. it('zeitstrahl grouping + bucket keys are present in all locales (#827 REQ-012)', () => { const requiredKeys = [ 'timeline_grouping_event', 'timeline_grouping_thema', 'timeline_grouping_aria_label', 'timeline_grouping_segment_date', 'timeline_grouping_segment_event', 'timeline_grouping_segment_thema', 'timeline_grouping_segment_date_short', 'timeline_grouping_segment_event_short', 'timeline_grouping_segment_thema_short', 'timeline_grouping_disabled_reason', 'timeline_grouping_multitag_hint', 'timeline_bucket_other_letters', 'timeline_bucket_no_topic' ]; for (const key of requiredKeys) { expect(de, `missing key in de: ${key}`).toHaveProperty(key); expect(en, `missing key in en: ${key}`).toHaveProperty(key); expect(es, `missing key in es: ${key}`).toHaveProperty(key); } // the pre-existing meta-line + chip keys are reused by #827, not re-declared expect(de).toHaveProperty('timeline_grouping_date'); expect(de).toHaveProperty('timeline_tag_chip_label'); }); });