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' }); }); });