Files
openchamber/packages/web/server/lib/walkthrough/languages.test.js
T
Bohdan Triapitsyn ce09ca6190 fix(walkthrough): offer German, and catch the next locale that is only half added
German was added to the interface but not to the walkthrough's own language
list, and nothing failed: the picker offered Deutsch because it is built from
the interface locales, the server resolved the tag to English, and a German
reader paid for a walkthrough written in English while the picker still said
Deutsch.

The two lists cannot be one — the server cannot import from packages/ui — so
a test reads i18n/runtime.ts and compares them, in both directions and through
normalizeLanguage. Drift this quiet needs a test rather than vigilance.
2026-08-03 13:55:51 +03:00

56 lines
2.4 KiB
JavaScript

import fs from 'fs';
import { fileURLToPath } from 'url';
import { describe, expect, it } from 'vitest';
import { normalizeLanguage, __testing } from './languages.js';
// The languages a walkthrough may be written in have to agree with the locales
// the interface ships, because the picker offers exactly those and the server
// decides what the prompt asks for. The two lists cannot be one list — the
// server cannot import from `packages/ui` — so they are compared here instead.
//
// This exists because German was added to the interface and not here. Nothing
// broke loudly: the picker offered Deutsch, `normalizeLanguage` quietly resolved
// it to English, and a German user paid for a walkthrough written in English
// while the picker still said Deutsch. A drift this quiet needs a test, not
// vigilance.
const RUNTIME_TS = fileURLToPath(new URL('../../../../ui/src/lib/i18n/runtime.ts', import.meta.url));
const interfaceLocales = () => {
const source = fs.readFileSync(RUNTIME_TS, 'utf8');
const match = source.match(/export const LOCALES = \[([^\]]*)\]/);
if (!match) throw new Error(`Could not find LOCALES in ${RUNTIME_TS}`);
return match[1]
.split(',')
.map((entry) => entry.trim().replace(/^['"]|['"]$/g, ''))
.filter(Boolean);
};
describe('supported languages', () => {
it('covers every locale the interface offers', () => {
const missing = interfaceLocales().filter((locale) => !Object.hasOwn(__testing.LANGUAGE_NAMES, locale));
expect(missing, `add these to LANGUAGE_NAMES in languages.js: ${missing.join(', ')}`).toEqual([]);
});
it('offers nothing the interface cannot label', () => {
const locales = new Set(interfaceLocales());
const extra = Object.keys(__testing.LANGUAGE_NAMES).filter((tag) => !locales.has(tag));
// A language here that the interface does not know is not harmful, but it
// is unreachable: the picker is built from the interface list.
expect(extra, `unreachable from the picker: ${extra.join(', ')}`).toEqual([]);
});
it('resolves every interface locale to itself rather than to the default', () => {
for (const locale of interfaceLocales()) {
expect(normalizeLanguage(locale)).toBe(locale);
}
});
it('names every supported language in English, for the prompt', () => {
for (const [tag, name] of Object.entries(__testing.LANGUAGE_NAMES)) {
expect(name, tag).toMatch(/^[A-Z][A-Za-z ]+$/);
}
});
});