""" Translation layer — translates between the user's language and English. The main LLM always receives and produces English. This module handles conversion in both directions so the UI shows the user's language. Formatting rules: Text inside (parentheses) and *asterisks* is roleplay markup — it must be preserved as-is so the parser can still identify actions and thoughts. """ import logging import time from huggingface_hub import InferenceClient logger = logging.getLogger(__name__) _TO_EN = ( "You are a professional translator. " "Translate the following text to English. " "Translate ALL content, including text inside parentheses ( ) and asterisks *. " "Keep the parentheses ( ) and asterisks * markers exactly in place — do not remove or add them. " "Return ONLY the translated text, no explanations or extra text." ) _FROM_EN = ( "You are a professional translator. " "Translate the following text to {language}. " "Translate ALL content, including text inside parentheses ( ) and asterisks *. " "Keep the parentheses ( ) and asterisks * markers exactly in place — do not remove or add them. " "Return ONLY the translated text, no explanations or extra text." ) def translate_to_english( text: str, source_language: str, client: InferenceClient, model: str, ) -> str: """ Translate `text` from `source_language` to English. Returns the original text unchanged if already English or on any failure. """ if not text.strip() or source_language.strip().lower() == "english": logger.debug("[Translator] to_english skipped (already English or empty).") return text logger.info(f"[Translator] ▶ {source_language}→EN | model='{model}' | {len(text)} chars") start = time.time() try: response = client.chat_completion( messages=[ {"role": "system", "content": _TO_EN}, {"role": "user", "content": text}, ], model=model, max_tokens=1024, stream=False, temperature=0.1, ) result = response.choices[0].message.content.strip() elapsed = time.time() - start logger.info(f"[Translator] ✓ {source_language}→EN | {elapsed:.1f}s | {len(result)} chars") logger.debug(f"[Translator] {source_language}→EN: {text[:80]!r} → {result[:80]!r}") return result except Exception as e: logger.error(f"[Translator] to_english FAILED | model='{model}' | error: {e}", exc_info=True) return text def translate_from_english( text: str, target_language: str, client: InferenceClient, model: str, ) -> str: """ Translate `text` from English to `target_language`. Returns the original text unchanged if target is English or on any failure. """ if not text.strip() or target_language.strip().lower() == "english": logger.debug("[Translator] from_english skipped (target is English or empty).") return text logger.info(f"[Translator] ▶ EN→{target_language} | model='{model}' | {len(text)} chars") start = time.time() try: system = _FROM_EN.format(language=target_language) response = client.chat_completion( messages=[ {"role": "system", "content": system}, {"role": "user", "content": text}, ], model=model, max_tokens=1024, stream=False, temperature=0.1, ) result = response.choices[0].message.content.strip() elapsed = time.time() - start logger.info(f"[Translator] ✓ EN→{target_language} | {elapsed:.1f}s | {len(result)} chars") logger.debug(f"[Translator] EN→{target_language}: {text[:80]!r} → {result[:80]!r}") return result except Exception as e: logger.error(f"[Translator] from_english FAILED | model='{model}' | error: {e}", exc_info=True) return text