feat: language picker with flags, paging and follow mode, translation run in the floating window
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:
1 parent
5f6bd4508e
commit
7fa88e4025
82 files changed
+978
-187
No files matched your search
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user