d73171b052
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>
60 lines
2.4 KiB
TypeScript
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);
|
|
});
|
|
});
|