Files
tessera-ctl/apps/web/src/messages/tenderRadar-parity.spec.ts
T
schalli d73171b052 feat(14-05): add tenderRadar i18n namespace (DE + EN) with key-parity guard
Introduces the tenderRadar top-level namespace in de.json/en.json covering
page, results, filter (incl. Bundesland/CPV division labels), savedSearch,
detail, coverage, settings, sourceConfig, rssFeeds and emailAlerts groups.
EN translations authored with consistent Vergabe-domain terminology
(Ausschreibung->tender, Vergabestelle->contracting authority, Frist->
deadline, Auftragswert->estimated value). tenderRadar-parity.spec.ts
enforces recursively-flattened de/en key-set equality so no follow-up edit
can silently add a string to only one locale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:07:41 +02:00

60 lines
2.4 KiB
TypeScript

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<string, unknown>).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<string, unknown>)) {
checkLeaves(value, path ? `${path}.${key}` : key);
}
};
checkLeaves(de.tenderRadar);
checkLeaves(en.tenderRadar);
});
});