mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 23:41:08 +02:00
test(cycles): i18n key parity across all 5 locales
Loads de/en/it/fr/es cycles locale files and asserts their flattened key paths are identical. Catches stub copies drifting silently when new keys are added to de/en and forgotten in the others. Also asserts every leaf value is a non-empty string so a missing translation can't masquerade as null or an empty string. Uses 'de' as the reference and renames vitest's 'it' to 'test' to avoid shadowing the 'it.json' import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b0a9dfeedb
commit
0896b1afd1
1 changed files with 72 additions and 0 deletions
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* i18n parity test for the cycles module.
|
||||
*
|
||||
* Ensures all 5 locale files (de/en/it/fr/es) have identical key
|
||||
* structure — stub copies of en.json would otherwise drift silently
|
||||
* as keys are added to de/en and forgotten in the others.
|
||||
*
|
||||
* The test does NOT enforce that values are different (stubs are
|
||||
* allowed); it only enforces that the *shape* (nested key paths)
|
||||
* matches exactly across all locales.
|
||||
*/
|
||||
|
||||
import { describe, expect, it as test } from 'vitest';
|
||||
import de from './de.json';
|
||||
import en from './en.json';
|
||||
import itLocale from './it.json';
|
||||
import fr from './fr.json';
|
||||
import es from './es.json';
|
||||
|
||||
type Dict = Record<string, unknown>;
|
||||
|
||||
/** Flatten an object into dot-separated leaf key paths. */
|
||||
function flattenKeys(obj: Dict, prefix = ''): string[] {
|
||||
const keys: string[] = [];
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
const path = prefix ? `${prefix}.${k}` : k;
|
||||
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
||||
keys.push(...flattenKeys(v as Dict, path));
|
||||
} else {
|
||||
keys.push(path);
|
||||
}
|
||||
}
|
||||
return keys.sort();
|
||||
}
|
||||
|
||||
const locales = {
|
||||
de: de as Dict,
|
||||
en: en as Dict,
|
||||
it: itLocale as Dict,
|
||||
fr: fr as Dict,
|
||||
es: es as Dict,
|
||||
};
|
||||
|
||||
describe('cycles i18n parity', () => {
|
||||
const referenceKeys = flattenKeys(locales.de);
|
||||
|
||||
test('de has a non-empty set of keys', () => {
|
||||
expect(referenceKeys.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
for (const [lang, dict] of Object.entries(locales)) {
|
||||
if (lang === 'de') continue;
|
||||
test(`${lang} matches de key structure`, () => {
|
||||
const langKeys = flattenKeys(dict);
|
||||
expect(langKeys).toEqual(referenceKeys);
|
||||
});
|
||||
}
|
||||
|
||||
test('no locale has empty string values', () => {
|
||||
for (const [lang, dict] of Object.entries(locales)) {
|
||||
const flat = flattenKeys(dict);
|
||||
for (const keyPath of flat) {
|
||||
const value = keyPath.split('.').reduce<unknown>((acc, segment) => {
|
||||
if (acc && typeof acc === 'object') return (acc as Dict)[segment];
|
||||
return undefined;
|
||||
}, dict);
|
||||
expect(value, `${lang}.${keyPath}`).not.toBe('');
|
||||
expect(typeof value, `${lang}.${keyPath}`).toBe('string');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue