import { describe, expect, it } from 'vitest'; import de from './de.json'; import en from './en.json'; /** * tenderRadar-parity.spec — Plan 14-05 (CONFIG-03, D-10). Structural * de/en key-set equality guard for the `tenderRadar` i18n namespace: fails * listing any key present in one locale but missing in the other. Does NOT * assert on translation content — only that both locales define the exact * same set of (recursively-flattened) keys, so a future edit can never add * a string to one locale and silently forget the other. */ /** * Recursively flattens a nested message object into dot-joined leaf key * paths (e.g. `{ filter: { plzLabel: '...' } }` -> `['filter.plzLabel']`). */ function flattenKeys(obj: unknown, prefix = ''): string[] { if (obj === null || typeof obj !== 'object') { return [prefix]; } return Object.entries(obj as Record).flatMap(([key, value]) => flattenKeys(value, prefix ? `${prefix}.${key}` : key), ); } describe('tenderRadar i18n namespace — de/en key parity', () => { it('de.json and en.json both define a tenderRadar namespace', () => { expect(de).toHaveProperty('tenderRadar'); expect(en).toHaveProperty('tenderRadar'); }); it('has an identical (recursively-flattened) key set in de and en', () => { const deKeys = flattenKeys(de.tenderRadar).sort(); const enKeys = flattenKeys(en.tenderRadar).sort(); const missingInEn = deKeys.filter((key) => !enKeys.includes(key)); const missingInDe = enKeys.filter((key) => !deKeys.includes(key)); expect(missingInEn, `Keys present in de.json but missing in en.json: ${missingInEn.join(', ')}`).toEqual([]); expect(missingInDe, `Keys present in en.json but missing in de.json: ${missingInDe.join(', ')}`).toEqual([]); expect(deKeys).toEqual(enKeys); }); it('every leaf value under tenderRadar is a non-empty string in both locales', () => { const checkLeaves = (obj: unknown, path = '') => { if (obj === null || typeof obj !== 'object') { expect(typeof obj, `${path} should be a string`).toBe('string'); expect((obj as string).length, `${path} should not be empty`).toBeGreaterThan(0); return; } for (const [key, value] of Object.entries(obj as Record)) { checkLeaves(value, path ? `${path}.${key}` : key); } }; checkLeaves(de.tenderRadar); checkLeaves(en.tenderRadar); }); });