language fixes
This commit is contained in:
parent
fc17f51752
commit
0f5d695960
1 changed files with 73 additions and 61 deletions
|
|
@ -58,12 +58,15 @@ _TRANSLATE_RATE_LIMIT_MAX_RETRIES = 3
|
||||||
|
|
||||||
_PROTECTED_CODES = frozenset({"xx"})
|
_PROTECTED_CODES = frozenset({"xx"})
|
||||||
|
|
||||||
|
# In-memory set of language codes currently being updated (sync / create).
|
||||||
|
_UPDATING_CODES: Set[str] = set()
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# ISO 639-1 label map (used when creating a language without explicit label)
|
# ISO 639-1 label map (used when creating a language without explicit label)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_ISO_LABELS: Dict[str, str] = {
|
_ISO_LABELS: Dict[str, str] = {
|
||||||
"de": "Deutsch", "en": "English", "fr": "Français", "it": "Italiano",
|
"de": "Deutsch", "gsw": "Schweizerdeutsch", "en": "English", "fr": "Français", "it": "Italiano",
|
||||||
"es": "Español", "pt": "Português", "nl": "Nederlands", "pl": "Polski",
|
"es": "Español", "pt": "Português", "nl": "Nederlands", "pl": "Polski",
|
||||||
"cs": "Čeština", "sk": "Slovenčina", "sv": "Svenska", "no": "Norsk",
|
"cs": "Čeština", "sk": "Slovenčina", "sv": "Svenska", "no": "Norsk",
|
||||||
"da": "Dansk", "fi": "Suomi", "hu": "Magyar", "ro": "Română",
|
"da": "Dansk", "fi": "Suomi", "hu": "Magyar", "ro": "Română",
|
||||||
|
|
@ -487,15 +490,17 @@ async def list_language_codes():
|
||||||
entries = _rowEntries(r)
|
entries = _rowEntries(r)
|
||||||
uiCount = sum(1 for e in entries if e.get("context", "ui") == "ui")
|
uiCount = sum(1 for e in entries if e.get("context", "ui") == "ui")
|
||||||
gatewayCount = len(entries) - uiCount
|
gatewayCount = len(entries) - uiCount
|
||||||
|
code = r["id"]
|
||||||
out.append(
|
out.append(
|
||||||
{
|
{
|
||||||
"code": r["id"],
|
"code": code,
|
||||||
"label": r.get("label"),
|
"label": r.get("label"),
|
||||||
"status": r.get("status"),
|
"status": r.get("status"),
|
||||||
"isDefault": bool(r.get("isDefault")),
|
"isDefault": bool(r.get("isDefault")),
|
||||||
"entriesCount": len(entries),
|
"entriesCount": len(entries),
|
||||||
"uiCount": uiCount,
|
"uiCount": uiCount,
|
||||||
"gatewayCount": gatewayCount,
|
"gatewayCount": gatewayCount,
|
||||||
|
"updating": code in _UPDATING_CODES,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return sorted(out, key=lambda x: (not x.get("isDefault"), x["code"]))
|
return sorted(out, key=lambda x: (not x.get("isDefault"), x["code"]))
|
||||||
|
|
@ -520,9 +525,9 @@ class CreateLanguageBody(BaseModel):
|
||||||
|
|
||||||
def _validate_iso2_code(code: str) -> str:
|
def _validate_iso2_code(code: str) -> str:
|
||||||
c = code.strip().lower()
|
c = code.strip().lower()
|
||||||
if not re.fullmatch(r"[a-z]{2}", c):
|
if not re.fullmatch(r"[a-z]{2,3}", c):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail=routeApiMsg("Nur ISO-639-1 Zwei-Buchstaben-Codes erlaubt.")
|
status_code=400, detail=routeApiMsg("Nur ISO-639 Sprachcodes (2–3 Buchstaben) erlaubt.")
|
||||||
)
|
)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
@ -536,6 +541,7 @@ def _run_create_language_job(userId: str, code: str, label: str, currentUser: Us
|
||||||
|
|
||||||
|
|
||||||
async def _run_create_language_job_async(userId: str, code: str, label: str, currentUser: User, mandateId: str) -> None:
|
async def _run_create_language_job_async(userId: str, code: str, label: str, currentUser: User, mandateId: str) -> None:
|
||||||
|
_UPDATING_CODES.add(code)
|
||||||
try:
|
try:
|
||||||
db = _publicMgmtDb()
|
db = _publicMgmtDb()
|
||||||
rows = db.getRecordset(UiLanguageSet, recordFilter={"id": code})
|
rows = db.getRecordset(UiLanguageSet, recordFilter={"id": code})
|
||||||
|
|
@ -590,6 +596,8 @@ async def _run_create_language_job_async(userId: str, code: str, label: str, cur
|
||||||
title="Sprachset fehlgeschlagen",
|
title="Sprachset fehlgeschlagen",
|
||||||
message=f"Fehler bei «{code}»: {e}",
|
message=f"Fehler bei «{code}»: {e}",
|
||||||
)
|
)
|
||||||
|
finally:
|
||||||
|
_UPDATING_CODES.discard(code)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/sets")
|
@router.post("/sets")
|
||||||
|
|
@ -678,71 +686,75 @@ async def _syncLanguageWithXx(db, code: str, userId: Optional[str], adminUser: O
|
||||||
"""
|
"""
|
||||||
if code == "xx":
|
if code == "xx":
|
||||||
raise HTTPException(status_code=400, detail=routeApiMsg("Das xx-Set wird über 'UI-Keys einlesen' aktualisiert."))
|
raise HTTPException(status_code=400, detail=routeApiMsg("Das xx-Set wird über 'UI-Keys einlesen' aktualisiert."))
|
||||||
rows = db.getRecordset(UiLanguageSet, recordFilter={"id": code})
|
_UPDATING_CODES.add(code)
|
||||||
if not rows:
|
try:
|
||||||
raise HTTPException(status_code=404, detail=routeApiMsg("Sprachset nicht gefunden"))
|
rows = db.getRecordset(UiLanguageSet, recordFilter={"id": code})
|
||||||
xxEntries = _loadMasterXxEntries(db)
|
if not rows:
|
||||||
if not xxEntries:
|
raise HTTPException(status_code=404, detail=routeApiMsg("Sprachset nicht gefunden"))
|
||||||
raise HTTPException(status_code=503, detail=routeApiMsg("Basisset (xx) nicht vorhanden."))
|
xxEntries = _loadMasterXxEntries(db)
|
||||||
|
if not xxEntries:
|
||||||
|
raise HTTPException(status_code=503, detail=routeApiMsg("Basisset (xx) nicht vorhanden."))
|
||||||
|
|
||||||
row = dict(rows[0])
|
row = dict(rows[0])
|
||||||
curEntries = _rowEntries(row)
|
curEntries = _rowEntries(row)
|
||||||
curById = {_entryId(e): e for e in curEntries}
|
curById = {_entryId(e): e for e in curEntries}
|
||||||
xxById = {_entryId(e): e for e in xxEntries}
|
xxById = {_entryId(e): e for e in xxEntries}
|
||||||
|
|
||||||
masterIds = set(xxById.keys())
|
masterIds = set(xxById.keys())
|
||||||
currentIds = set(curById.keys())
|
currentIds = set(curById.keys())
|
||||||
removedIds = currentIds - masterIds
|
removedIds = currentIds - masterIds
|
||||||
addedIds = masterIds - currentIds
|
addedIds = masterIds - currentIds
|
||||||
|
|
||||||
translatedCount = 0
|
translatedCount = 0
|
||||||
if addedIds:
|
if addedIds:
|
||||||
toTranslate = {xxById[eid]["key"]: xxById[eid].get("value", "") for eid in addedIds}
|
toTranslate = {xxById[eid]["key"]: xxById[eid].get("value", "") for eid in addedIds}
|
||||||
langLabel = row.get("label") or code
|
langLabel = row.get("label") or code
|
||||||
billingCb = None
|
billingCb = None
|
||||||
if adminUser:
|
if adminUser:
|
||||||
memberIds = _userMemberMandateIds(adminUser)
|
memberIds = _userMemberMandateIds(adminUser)
|
||||||
if memberIds:
|
if memberIds:
|
||||||
billingCb = _makeBillingCallback(adminUser, memberIds[0])
|
billingCb = _makeBillingCallback(adminUser, memberIds[0])
|
||||||
try:
|
try:
|
||||||
translated = await _translateBatch(toTranslate, langLabel, code, billingCallback=billingCb)
|
translated = await _translateBatch(toTranslate, langLabel, code, billingCallback=billingCb)
|
||||||
translatedCount = sum(1 for eid in addedIds if xxById[eid]["key"] in translated)
|
translatedCount = sum(1 for eid in addedIds if xxById[eid]["key"] in translated)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("AI translation during sync failed for %s: %s", code, e)
|
logger.error("AI translation during sync failed for %s: %s", code, e)
|
||||||
translated = {}
|
translated = {}
|
||||||
|
|
||||||
for eid in addedIds:
|
for eid in addedIds:
|
||||||
xxEntry = xxById[eid]
|
xxEntry = xxById[eid]
|
||||||
curById[eid] = {
|
curById[eid] = {
|
||||||
"context": xxEntry["context"],
|
"context": xxEntry["context"],
|
||||||
"key": xxEntry["key"],
|
"key": xxEntry["key"],
|
||||||
"value": translated.get(xxEntry["key"], f"[{xxEntry['key']}]"),
|
"value": translated.get(xxEntry["key"], f"[{xxEntry['key']}]"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for eid in removedIds:
|
for eid in removedIds:
|
||||||
del curById[eid]
|
del curById[eid]
|
||||||
|
|
||||||
for eid in masterIds & currentIds:
|
for eid in masterIds & currentIds:
|
||||||
curById[eid]["context"] = xxById[eid]["context"]
|
curById[eid]["context"] = xxById[eid]["context"]
|
||||||
|
|
||||||
newEntries = sorted(curById.values(), key=lambda e: (e["key"].lower(), e.get("context", "")))
|
newEntries = sorted(curById.values(), key=lambda e: (e["key"].lower(), e.get("context", "")))
|
||||||
|
|
||||||
now = getUtcTimestamp()
|
now = getUtcTimestamp()
|
||||||
untranslated = len(addedIds) - translatedCount
|
untranslated = len(addedIds) - translatedCount
|
||||||
row["entries"] = newEntries
|
row["entries"] = newEntries
|
||||||
if "keys" in row:
|
if "keys" in row:
|
||||||
del row["keys"]
|
del row["keys"]
|
||||||
row["status"] = "complete" if untranslated == 0 else "incomplete"
|
row["status"] = "complete" if untranslated == 0 else "incomplete"
|
||||||
row["sysModifiedAt"] = now
|
row["sysModifiedAt"] = now
|
||||||
row["sysModifiedBy"] = userId
|
row["sysModifiedBy"] = userId
|
||||||
db.recordModify(UiLanguageSet, code, row)
|
db.recordModify(UiLanguageSet, code, row)
|
||||||
return {
|
return {
|
||||||
"code": code,
|
"code": code,
|
||||||
"added": sorted({xxById[eid]["key"] for eid in addedIds}),
|
"added": sorted({xxById[eid]["key"] for eid in addedIds}),
|
||||||
"removed": sorted({eid[0] for eid in removedIds}),
|
"removed": sorted({eid[0] for eid in removedIds}),
|
||||||
"translated": translatedCount,
|
"translated": translatedCount,
|
||||||
"entriesCount": len(newEntries),
|
"entriesCount": len(newEntries),
|
||||||
}
|
}
|
||||||
|
finally:
|
||||||
|
_UPDATING_CODES.discard(code)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/sets/sync-xx")
|
@router.put("/sets/sync-xx")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue