Merge pull request #376 from MacRimi/fix/tui-placeholder-protection

Keep {placeholders} out of the translator in the TUI cache
This commit is contained in:
MacRimi
2026-09-22 21:54:09 +02:00
committed by GitHub
8 changed files with 59 additions and 2 deletions
+45 -2
View File
@@ -94,6 +94,11 @@ PROTECTED_TECHNICAL_TERMS = (
"GPU",
"CPU",
"AMD",
# Proxmox's own words for a container and a virtual machine. Read as
# initials they get reordered — "CT" comes back as "TC" in Spanish —
# and the reader is told about something Proxmox does not call that.
"CT",
"VM",
)
TECHNICAL_TERM_RE = re.compile(
"|".join(
@@ -106,6 +111,9 @@ TRANSLATE_CALL_RE = re.compile(
r"""translate\s+(?P<quote>["'])(?P<text>(?:\\.|(?! (?P=quote) ).)*?)(?P=quote)""",
re.VERBOSE | re.DOTALL,
)
# Same shape the Monitor's generator uses, so a message reads identically
# whichever of the two catalogues it comes from.
PLACEHOLDER_RE = re.compile(r"\{[A-Za-z_][A-Za-z0-9_]*\}")
# Providers that answer the same thing every time for the same input, so a
# second attempt cannot produce a different result. `appimage` shells out to a
# binary that may reach a network service, so it is not on the list.
@@ -133,11 +141,44 @@ def protect_catalog_titles(directories) -> None:
if not titles:
return
terms = tuple(sorted(set(PROTECTED_TECHNICAL_TERMS) | titles, key=len, reverse=True))
# Same word boundaries as the module-level pattern. Without them a short
# term matches inside longer words, and every application title in the
# catalogue joins the glossary here.
TECHNICAL_TERM_RE = re.compile(
"|".join(re.escape(term) for term in terms), re.IGNORECASE)
"|".join(
rf"(?<![A-Za-z0-9_]){re.escape(term)}(?![A-Za-z0-9_])"
for term in terms
),
re.IGNORECASE,
)
print(f"Protected application names: {len(titles)}", flush=True)
def protect_placeholders(text: str) -> tuple[str, list[str]]:
"""Replace each ``{name}`` with a token before translation.
A provider reads the name inside the braces as a word and translates
it: ``{count}`` comes back as ``{conta}`` in Portuguese and ``{počet}``
in Slovak. The consumer then finds no placeholder to substitute and
falls back to English, so the translation was paid for and never used.
The token carries no underscores because argos splits on them.
"""
found: list[str] = []
def _swap(match: re.Match[str]) -> str:
found.append(match.group(0))
return f"PMXPH{len(found) - 1:03d}"
return PLACEHOLDER_RE.sub(_swap, text), found
def restore_placeholders(text: str, found: list[str]) -> str:
"""Put the original ``{name}`` back where each token landed."""
for index, original in enumerate(found):
text = text.replace(f"PMXPH{index:03d}", original)
return text
def protect_technical_terms(text: str) -> tuple[str, list[str]]:
"""Replace glossary terms with stable tokens before translation."""
protected: list[str] = []
@@ -433,7 +474,8 @@ def translate_text(
timeout: int,
appimage_path: Path,
) -> str:
protected_text, protected_terms = protect_technical_terms(text)
protected_text, placeholders = protect_placeholders(text)
protected_text, protected_terms = protect_technical_terms(protected_text)
if provider == "argos":
translated = translate_argos(protected_text, dest_lang)
elif provider == "googletrans":
@@ -447,6 +489,7 @@ def translate_text(
else:
raise ValueError(f"Unknown provider: {provider}")
translated = restore_technical_terms(clean_translation(translated), protected_terms)
translated = restore_placeholders(translated, placeholders)
return restore_sentence_spacing(translated) if translated else text
+2
View File
@@ -892,6 +892,8 @@
"Completed with errors —": "Mit Fehlern abgeschlossen –",
"Completed.": "Vollendet.",
"Completed. Devices added to VM {vmid}: {count}.": "Abgeschlossen. Zur VM {vmid} hinzugefügte Geräte: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Abgeschlossen. Zu CT {ctid} hinzugefügte Festplatten: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Abgeschlossen. Zu VM {vmid} hinzugefügte Festplatten: {count}.",
"Completed. Press Enter to return to menu...": "Vollendet. Drücken Sie die Eingabetaste, um zum Menü zurückzukehren...",
"Completing its final cleanup...": "Abschluss der endgültigen Bereinigung...",
"Completing pending package configurations...": "Ausstehende Paketkonfigurationen werden abgeschlossen...",
+2
View File
@@ -893,6 +893,8 @@
"Completed with errors —": "Completado con errores.",
"Completed.": "Terminado.",
"Completed. Devices added to VM {vmid}: {count}.": "Completado. Dispositivos agregados a la VM {vmid}: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Completado. Discos añadidos a CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Completado. Discos añadidos a VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Terminado. Presione Enter para regresar al menú...",
"Completing its final cleanup...": "Completando su limpieza final...",
"Completing pending package configurations...": "Completando las configuraciones de paquetes pendientes...",
+2
View File
@@ -893,6 +893,8 @@
"Completed with errors —": "Complété avec des erreurs —",
"Completed.": "Complété.",
"Completed. Devices added to VM {vmid}: {count}.": "Terminé. Appareils ajoutés à la VM {vmid} : {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Terminé. Disques ajoutés à CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Terminé. Disques ajoutés à VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Complété. Appuyez sur Entrée pour revenir au menu...",
"Completing its final cleanup...": "Compléter son nettoyage final...",
"Completing pending package configurations...": "Finalisation des configurations de packages en attente...",
+2
View File
@@ -893,6 +893,8 @@
"Completed with errors —": "Completato con errori —",
"Completed.": "Completato.",
"Completed. Devices added to VM {vmid}: {count}.": "completato. Dispositivi aggiunti alla VM {vmid}: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Completato. I dischi aggiunti a CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Completato. I dischi aggiunti a VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Completato. Premere Invio per tornare al menu...",
"Completing its final cleanup...": "Completa la sua pulizia finale...",
"Completing pending package configurations...": "completamento delle configurazioni dei pacchetti in sospeso...",
+2
View File
@@ -893,6 +893,8 @@
"Completed with errors —": "Concluído com erros -",
"Completed.": "Concluído.",
"Completed. Devices added to VM {vmid}: {count}.": "Concluído. Dispositivos adicionados à VM {vmid}: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Concluído. Discos adicionados ao CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Concluído. Discos adicionados ao VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Concluído. Pressione Enter para retornar ao menu...",
"Completing its final cleanup...": "Completando sua limpeza final...",
"Completing pending package configurations...": "Concluindo configurações de pacotes pendentes...",
+2
View File
@@ -892,6 +892,8 @@
"Completed with errors —": "Dokončené s chybami -",
"Completed.": "Dokončené.",
"Completed. Devices added to VM {vmid}: {count}.": "Dokončené. Zariadenia pridané do VM {vmid}: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Dokončené. Disky pridané do CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Dokončené. Disky pridané do VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Dokončené. Stlačte Enter pre návrat do menu...",
"Completing its final cleanup...": "Dokončiť záverečné čistenie...",
"Completing pending package configurations...": "Dokončenie čakajúcich konfigurácií balíka...",
+2
View File
@@ -891,6 +891,8 @@
"Completed with errors —": "Kompletterad med fel —",
"Completed.": "Avslutad.",
"Completed. Devices added to VM {vmid}: {count}.": "Klar. Enheter som har lagts till VM {vmid}: {count}.",
"Completed. Disks added to CT {ctid}: {count}.": "Slutfört. Disker läggs till CT {ctid}: {count}.",
"Completed. Disks added to VM {vmid}: {count}.": "Slutfört. Disker läggs till VM {vmid}: {count}.",
"Completed. Press Enter to return to menu...": "Avslutad. Tryck på Enter för att återgå till menyn...",
"Completing its final cleanup...": "Slutför sin sista rengöring...",
"Completing pending package configurations...": "Slutför väntande paketkonfigurationer...",