Files
mrleo1nidandClaude Sonnet 5.5 a1f5ef8e2d
CI / frontend (push) Failing after 20s
CI / backend (push) Successful in 51s
feat: language dropdowns on versions and on the game page
- Each ready version gets a "Languages" dropdown next to Play: the original language
  first, then translations with their completion; picking one starts the version in
  that language
- The game header gets the same dropdown, aggregated over published, ready versions.
  Each language points at the version that has it best (most complete, the main
  version on a tie); an original language counts as complete
- The old row of language tags linking to the translation editor is replaced
- Backend: versions carry source_name (the name of the original language)
- pages/languages.ts holds the aggregation logic with tests; LanguageMenu closes on
  outside click and Escape

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 18:41:26 +03:00

63 lines
3.4 KiB
JavaScript

import assert from "node:assert/strict";
import { fileURLToPath } from "node:url";
import { test } from "node:test";
import { build } from "esbuild";
const compiled = await build({ entryPoints: [fileURLToPath(new URL("../src/pages/languages.ts", import.meta.url))],
bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" });
const { versionLanguages, aggregateLanguages } =
await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString("base64")}`);
const version = (id, extra = {}) => ({ id, status: "ready", visibility: "published", source_lang: "en", source_name: "English",
languages: [], ...extra });
const ru = (progress) => ({ code: "ru", name: "Русский", progress });
test("a version lists its original language first, then its translations", () => {
const entries = versionLanguages(version(1, { languages: [ru(0.5), { code: "de", name: "Deutsch", progress: 1 }] }));
assert.deepEqual(entries.map((e) => [e.code, e.source, e.progress]), [["en", true, 1], ["ru", false, 0.5], ["de", false, 1]]);
assert.ok(entries.every((e) => e.versionId === 1));
});
test("an unknown original language is not shown, and a translation never repeats it", () => {
assert.deepEqual(versionLanguages(version(1, { source_lang: "und", source_name: null })), []);
assert.deepEqual(versionLanguages(version(1, { source_lang: null })), []);
const same = versionLanguages(version(1, { languages: [{ code: "en", name: "English", progress: 0.2 }] }));
assert.equal(same.length, 1);
assert.equal(same[0].source, true);
});
test("the game lists the languages of every published ready version", () => {
const entries = aggregateLanguages([
version(1),
version(2, { languages: [ru(0.4)] }),
version(3, { languages: [{ code: "fr", name: "Français", progress: 0.9 }] }),
], 1);
assert.deepEqual(entries.map((e) => e.code), ["en", "fr", "ru"], "original first, then by progress");
assert.equal(entries.find((e) => e.code === "ru").versionId, 2);
});
test("drafts, unfinished and failed versions do not add languages", () => {
const entries = aggregateLanguages([
version(1),
version(2, { visibility: "draft", languages: [ru(1)] }),
version(3, { status: "converting", languages: [{ code: "de", name: "Deutsch", progress: 1 }] }),
version(4, { status: "failed", languages: [{ code: "fr", name: "Français", progress: 1 }] }),
], 1);
assert.deepEqual(entries.map((e) => e.code), ["en"]);
});
test("when several versions have a language the most complete wins, the main one on a tie", () => {
const versions = [version(1, { languages: [ru(0.5)] }), version(2, { languages: [ru(0.9)] }), version(3, { languages: [ru(0.9)] })];
assert.equal(aggregateLanguages(versions, 1).find((e) => e.code === "ru").versionId, 2, "more complete");
assert.equal(aggregateLanguages(versions, 3).find((e) => e.code === "ru").versionId, 3, "main version on a tie");
});
test("an original language of one version counts as complete even if another has it as a translation", () => {
const entries = aggregateLanguages([version(1, { source_lang: "ru", source_name: "Русский" }),
version(2, { languages: [ru(0.3)] })], 1);
const russian = entries.find((e) => e.code === "ru");
assert.equal(russian.source, true);
assert.equal(russian.versionId, 1);
assert.equal(entries[0].code, "ru", "an original language is listed first");
});