Files
webmail/lib/__tests__/plain-language-strings.test.ts
T

68 lines
2.6 KiB
TypeScript

// @vitest-environment node
import fs from 'fs';
import path from 'path';
import { describe, expect, it } from 'vitest';
import { PLAIN_LANGUAGE_LOADERS } from '@/i18n/plain-language';
// locales/<code>/plain.json holds simpler versions of common.json strings for
// plain-language mode (CO-14). An override only works while its key path
// exists in common.json: a renamed or removed key would leave a dead override
// that nobody sees, so every path is checked here.
const localesDir = path.resolve(__dirname, '../../locales');
type Tree = Record<string, unknown>;
function leaves(node: unknown, prefix = ''): Array<[string, unknown]> {
if (node && typeof node === 'object' && !Array.isArray(node)) {
return Object.entries(node as Tree).flatMap(([key, value]) => leaves(value, prefix ? `${prefix}.${key}` : key));
}
return [[prefix, node]];
}
const load = (locale: string, file: string): Tree =>
JSON.parse(fs.readFileSync(path.join(localesDir, locale, file), 'utf-8'));
// ICU argument names ({name}, {count, plural, ...}); plural selectors are not arguments.
function icuArgs(text: string): string[] {
const names = [...text.matchAll(/\{\s*(\w+)/g)].map((m) => m[1]);
return [...new Set(names)].filter((n) => !['zero', 'one', 'two', 'few', 'many', 'other'].includes(n)).sort();
}
const plainLocales = fs.readdirSync(localesDir).filter((l) => fs.existsSync(path.join(localesDir, l, 'plain.json')));
describe('plain-language strings', () => {
it('exist for English and German only, each with a loader', () => {
expect(plainLocales.sort()).toEqual(['de', 'en']);
expect(Object.keys(PLAIN_LANGUAGE_LOADERS).sort()).toEqual(['de', 'en']);
});
for (const locale of plainLocales) {
describe(locale, () => {
const plain = leaves(load(locale, 'plain.json'));
const common = new Map(leaves(load(locale, 'common.json')));
it('has strings', () => {
expect(plain.length).toBeGreaterThan(0);
});
it('only overrides keys that exist in common.json', () => {
const dead = plain.map(([key]) => key).filter((key) => !common.has(key));
expect(dead).toEqual([]);
});
it('only holds non-empty strings', () => {
const bad = plain.filter(([, value]) => typeof value !== 'string' || !value.trim()).map(([key]) => key);
expect(bad).toEqual([]);
});
it('keeps the placeholders of the string it replaces', () => {
const mismatched = plain
.filter(([key, value]) => JSON.stringify(icuArgs(String(value))) !== JSON.stringify(icuArgs(String(common.get(key)))))
.map(([key]) => key);
expect(mismatched).toEqual([]);
});
});
}
});