feat: language picker with flags, paging and follow mode, translation run in the floating window
CI / frontend (push) Failing after 27s
CI / backend (push) Failing after 49s

Language picker with search and SVG flags, real pagination with locate, one global machine translation run shown next to uploads.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 19:24:20 +03:00
1 parent 5f6bd4508e
commit 7fa88e4025
82 files changed
+978 -187

No files matched your search

+5 -50
View File
@@ -18,7 +18,6 @@ router = APIRouter(prefix="/api/versions/{version_id}/i18n", tags=["i18n"])
Player = Annotated[User, Depends(require("games.play"))]
Translator = Annotated[User, Depends(require("games.play", "translations.edit"))]
TODO_STATES = {"untranslated", "outdated", "fuzzy", "machine"}
LANG_RE = r"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
FONT_EXTS = (".ttf", ".otf", ".woff", ".woff2")
MAX_FONT_BYTES = 30 * 1024 * 1024
@@ -84,56 +83,12 @@ async def overview(version_id: int, user: Player, db: DB) -> dict[str, Any]:
@router.get("/{lang}/entries")
async def entries(version_id: int, lang: str, user: Player, db: DB, kind: str = "lines",
state: str = "all", module: str = "", q: str = "", offset: int = 0,
limit: int = Query(50, ge=1, le=200)) -> dict[str, Any]:
state: str = "all", module: str = "", q: str = "", offset: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200), locate: str = "") -> dict[str, Any]:
v = await ready_version(db, user, version_id)
needle = q.strip().lower()
def matches(st: str, *texts: str) -> bool:
if state == "todo" and st not in TODO_STATES:
return False
if state not in ("all", "todo") and st != state:
return False
return not needle or any(needle in t.lower() for t in texts if t)
def build() -> dict[str, Any]:
idx = tr.text_index(v.manifest)
lines, strings, _ = tr.locale_tables(v.manifest, lang)
files = v.manifest["files"]
items: list[dict[str, Any]] = []
total = 0
if kind == "strings":
for src, usage in idx.strings.items():
e = strings.get(src) or {}
st = e.get("state", "translated") if e.get("t") else "untranslated"
if not matches(st, src, e.get("t", "")):
continue
total += 1
if offset < total <= offset + limit:
items.append({"key": src, "usage": usage, "source": src,
"translation": e.get("t", ""), "state": st})
else:
for info in idx.lines:
if module and info.module != module:
continue
e = lines.get(info.tid)
st = tr.line_state(e, info)
if not matches(st, info.source, (e or {}).get("t", ""), info.who):
continue
total += 1
if offset < total <= offset + limit:
scene = files.get(info.scene) if info.scene else None
items.append({
"key": info.tid, "nodeId": info.node_id, "module": info.module,
"label": info.label, "who": info.who, "source": info.source,
"resource": info.resource,
"prev": info.prev, "next": info.next,
"scene": f"/blobs/{scene['hash']}" if scene else None,
"translation": (e or {}).get("t", ""), "state": st,
})
return {"total": total, "items": items}
return await run_in_threadpool(build)
return await run_in_threadpool(
lambda: tr.entries_page(v.manifest, lang, kind=kind, state=state, module=module, q=q,
offset=offset, limit=limit, locate=locate))
@router.get("/{lang}/meta")