locales.test.ts 1.2 KB

1234567891011121314151617181920212223242526272829303132333435
  1. import { describe, it, expect } from 'vitest';
  2. import en from '../../i18n/locales/en';
  3. import de from '../../i18n/locales/de';
  4. /**
  5. * Recursively extracts all keys from a nested object as dot-notation paths.
  6. * Example: { foo: { bar: 'baz' } } => ['foo.bar']
  7. */
  8. const getKeys = (obj: object, prefix = ''): string[] => {
  9. return Object.entries(obj).flatMap(([key, value]) => {
  10. const path = prefix ? `${prefix}.${key}` : key;
  11. return typeof value === 'object' && value !== null
  12. ? getKeys(value, path)
  13. : [path];
  14. });
  15. };
  16. describe('i18n locale parity', () => {
  17. const enKeys = new Set(getKeys(en));
  18. const deKeys = new Set(getKeys(de));
  19. it('German locale has all English keys', () => {
  20. const missingInGerman = [...enKeys].filter((k) => !deKeys.has(k)).sort();
  21. expect(missingInGerman, `Missing ${missingInGerman.length} key(s) in German locale`).toEqual([]);
  22. });
  23. it('English locale has all German keys', () => {
  24. const missingInEnglish = [...deKeys].filter((k) => !enKeys.has(k)).sort();
  25. expect(missingInEnglish, `Missing ${missingInEnglish.length} key(s) in English locale`).toEqual([]);
  26. });
  27. it('both locales have the same number of keys', () => {
  28. expect(enKeys.size).toBe(deKeys.size);
  29. });
  30. });