feat: language dropdowns on versions and on the game page
CI / frontend (push) Failing after 20s
CI / backend (push) Successful in 51s

- 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>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 18:41:26 +03:00
1 parent a93f33c93d
commit a1f5ef8e2d
9 files changed
+205 -16

No files matched your search

+1
View File
@@ -82,6 +82,7 @@ class VersionOut(ORM):
kind: str = "original"
# from the version's game.json (filled by the game detail endpoint)
source_lang: str | None = None
source_name: str | None = None
languages: list[dict[str, Any]] = []
+4 -2
View File
@@ -127,10 +127,12 @@ def _image_file(images: dict[str, Any], img: dict[str, Any] | None) -> str | Non
def version_languages(manifest: dict[str, Any] | None) -> dict[str, Any]:
"""Source language and translations of a version, for listings."""
if not manifest or "game.json" not in manifest.get("files", {}):
return {"source_lang": None, "languages": []}
return {"source_lang": None, "source_name": None, "languages": []}
game = file_json(manifest, "game.json")
source = game.get("sourceLang")
return {
"source_lang": game.get("sourceLang"),
"source_lang": source,
"source_name": LANG_NAMES.get(source, source) if source and source != "und" else None,
"languages": [{"code": code, "name": info.get("name", code),
"progress": info.get("progress", 0)}
for code, info in sorted((game.get("locales") or {}).items())],
+20
View File
@@ -0,0 +1,20 @@
from app import translation as tr
def manifest():
return {"files": {"game.json": {"hash": "x", "size": 1}}}
def test_source_language_name_and_translations(monkeypatch):
game = {"sourceLang": "en", "locales": {"ru": {"name": "Русский", "progress": 0.5}, "de": {"progress": 1}}}
monkeypatch.setattr(tr, "file_json", lambda m, path: game)
result = tr.version_languages(manifest())
assert result["source_lang"] == "en"
assert result["source_name"] == tr.LANG_NAMES["en"]
assert [(lang["code"], lang["progress"]) for lang in result["languages"]] == [("de", 1), ("ru", 0.5)]
def test_unknown_or_missing_source_language_has_no_name(monkeypatch):
monkeypatch.setattr(tr, "file_json", lambda m, path: {"sourceLang": "und"})
assert tr.version_languages(manifest())["source_name"] is None
assert tr.version_languages(None) == {"source_lang": None, "source_name": None, "languages": []}