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>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user