fix(i18n): clarify de and en texts, add i18n tests
Build, Test & Push Frontend / quality-check (push) Successful in 39s
Build, Test & Push Frontend / docker (push) Successful in 22s

Review all de and en texts for clear wording. Use informal "du" and one
glossary (Nachweis, Punkte, Spieldauer). Add tests for matching
placeholders, matching hint links and points texts that say earn, not pay.

Refs #41 #42
This commit is contained in:
lobo committed 2026-10-11 11:29:59 +02:00
1 parent 4b25e7f5fe
commit 2794696daa
3 files changed
+270 -241

No files matched your search

+29
View File
@@ -7,6 +7,12 @@ vi.mock('i18next-http-backend', () => ({
default: {type: 'backend', init() {}, read(language, namespace, callback) { callback(null, {}); }},
}));
const readTexts = (file) =>
JSON.parse(readFileSync(path.resolve(import.meta.dirname, '../public/i18n', file), 'utf8'));
const flatTexts = (value, prefix = '') => Object.entries(value).flatMap(([key, child]) =>
(typeof child === 'object' ? flatTexts(child, `${prefix}${key}.`) : [[`${prefix}${key}`, child]]));
describe('translation files', () => {
it('are loaded under a URL that changes with their content, so no browser keeps an old copy', async () => {
const {default: i18n} = await import('./i18n.js');
@@ -48,6 +54,29 @@ describe('translation keys', () => {
const read = (file) => keys(JSON.parse(readFileSync(path.join(dir, file), 'utf8'))).sort();
expect(read('de.json')).toEqual(read('en.json'));
});
it('de_And_En_HaveTheSamePlaceholdersPerKey', () => {
const placeholders = (file) => Object.fromEntries(flatTexts(readTexts(file))
.map(([key, text]) => [key, (text.match(/\{\{\w+\}\}/g) ?? []).sort()]));
expect(placeholders('de.json')).toEqual(placeholders('en.json'));
});
it('hintLinks_AreTheSameInDeAndEn', () => {
const hints = (file) => Object.fromEntries(Object.entries(readTexts(file).HINT)
.map(([key, text]) => [key, {
targets: [...text.matchAll(/\]\(([^)]*)\)/g)].map((match) => match[1]),
bold: (text.match(/\*\*/g) ?? []).length,
}]));
expect(hints('de.json')).toEqual(hints('en.json'));
});
});
describe('points texts', () => {
it('pointsTexts_SayEarnNotPay', () => {
const texts = (file) => flatTexts(readTexts(file).POINTS).map(([, text]) => text);
for (const text of texts('de.json')) expect(text).not.toMatch(/\bzahl|Beweis|bezahlt/);
for (const text of texts('en.json')) expect(text).not.toMatch(/\bpays?\b|\bpaid\b/);
});
});
describe('people texts', () => {