mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
68 lines
2.6 KiB
TypeScript
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([]);
|
|
});
|
|
});
|
|
}
|
|
});
|