Столкнувшись с исключением, иногда не понимаешь: "divizion by zero" — что это? Начинающие разработчики часто не могут понять причину ошибки из-за неправильного понимания её а русском языке. Опытные разработчики также сталкиваются с неизвестными им исключениями, а часто лезть в переводчик для понимания ошибки не хочется. Сегодня я напишу модуль для быстрого перевода таких ошибок, и все непойманные исключения и предупреждения в Python будут выводиться на русском языке.
Для начала установим библиотеку, которая будет обращаться к Google Translator для перевода всех ошибок и предупреждений в Python:
pip install deep_translator
Также нам потребуется работать с цветами для вывода ошибок, поэтому нам потребуется установить colorama. Вот команда:
pip install colorama
Теперь приступим к написанию кода! Я разбил код модуля на несколько файлов, каждый из которых направлен на выполнение совей задачи. Вот код:
error_translator.py:
import sys import warnings import os import json import time import re from deep_translator import GoogleTranslator SOURCE_LANG = 'auto' TARGET_LANG = 'ru' CACHE_DIR = os.path.join(os.path.dirname(__file__), 'error_cache') CACHE_FILE = os.path.join(CACHE_DIR, 'translations.json') MAX_CACHE_ENTRIES = 100000 translator = GoogleTranslator(source=SOURCE_LANG, target=TARGET_LANG) def _load_cache(): if not os.path.exists(CACHE_FILE): return {} try: with open(CACHE_FILE, 'r', encoding='utf-8') as f: return json.load(f) except: return {} def _save_cache(cache): os.makedirs(CACHE_DIR, exist_ok=True) if len(cache) > MAX_CACHE_ENTRIES: sorted_items = sorted(cache.items(), key=lambda x: x[1].get('timestamp', 0)) cache = dict(sorted_items[-MAX_CACHE_ENTRIES:]) with open(CACHE_FILE, 'w', encoding='utf-8') as f: json.dump(cache, f, ensure_ascii=False, indent=2) def cached_translate(text): if not text: return text cache = _load_cache() if text in cache: return cache[text]['translation'] try: translated = translator.translate(text) except Exception: return text cache[text] = {'translation': translated, 'timestamp': time.time()} _save_cache(cache) return translated def translate_exc_message(msg): cleaned = re.sub(r'0x[0-9a-fA-F]+', '...', msg) return cached_translate(cleaned) original_excepthook = sys.excepthook def translating_excepthook(exc_type, exc_value, exc_tb): if exc_value is not None: translated = translate_exc_message(str(exc_value)) try: new_exc = exc_type(translated) except Exception: new_exc = exc_value else: new_exc = exc_value original_excepthook(exc_type, new_exc, exc_tb) original_showwarning = warnings.showwarning def translating_showwarning(message, category, filename, lineno, file=None, line=None): translated = translate_exc_message(str(message)) original_showwarning(translated, category, filename, lineno, file, line) sys.excepthook = translating_excepthook warnings.showwarning = translating_showwarning
run.py:
import sys import subprocess import re import os from pathlib import Path import colorama colorama.init() sys.path.insert(0, str(Path(__file__).parent)) from error_translator import cached_translate sys.stdout.reconfigure(encoding='utf-8', errors='replace') sys.stderr.reconfigure(encoding='utf-8', errors='replace') RED = colorama.Fore.RED RESET = colorama.Style.RESET_ALL error_header_pattern = re.compile( r'^(Traceback \(most recent call last\):|Exception in|During handling of|The above exception was)' ) in_error_block = False pending_warning_source = False def translate_line(line): global in_error_block, pending_warning_source if pending_warning_source: pending_warning_source = False if line.startswith(' ') or line.startswith('\t'): return RED + line.rstrip('\n') + RESET + '\n' else: in_error_block = False return line if error_header_pattern.match(line): in_error_block = True return RED + line.rstrip('\n') + RESET + '\n' if not in_error_block and line.startswith(' File '): in_error_block = True return RED + line.rstrip('\n') + RESET + '\n' warning_match = re.search(r'(:\d+:)\s*(\w+Warning):\s*(.*)', line) if warning_match: if not in_error_block: in_error_block = True pending_warning_source = True prefix = line[:warning_match.start(1)] lineno = warning_match.group(1) category = warning_match.group(2) msg = warning_match.group(3) if msg: translated_msg = cached_translate(msg) return f"{RED}{prefix}{lineno} {category}: {translated_msg}{RESET}\n" else: return RED + line.rstrip('\n') + RESET + '\n' if in_error_block: if line.startswith(' File ') or line.startswith(' ') or line.startswith('\t'): return RED + line.rstrip('\n') + RESET + '\n' m = re.match(r'^(\s*(?:\w+\.)*\w*(?:Error|Warning|Exception)):\s*(.*)', line) if m: exc_part, msg = m.groups() if msg: translated = f"{RED}{exc_part}: {cached_translate(msg)}{RESET}\n" else: translated = RED + line.rstrip('\n') + RESET + '\n' in_error_block = False return translated if line.strip() == '': return RED + '\n' in_error_block = False return line return line def run_target(target_script): env = os.environ.copy() env['PYTHONUNBUFFERED'] = '1' p = subprocess.Popen( [sys.executable, target_script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace', env=env, bufsize=1 ) for line in p.stdout: sys.stdout.write(translate_line(line)) sys.stdout.flush() p.wait() sys.exit(p.returncode)
translate_errors.py:
TARGET = "test.py"; from run import run_target; run_target(TARGET)
Работает это так: при запуске модуля translate_errors.py он запускает наш основной скрипт, название которого мы указали в константе TARGET в отдельном процессе. После выполнения скрипта программиста наш модуль проверяет каждую строчку вывода. Как только модуль натыкается на ошибку, он отправляет её в Google для перевода. Переведённый текст ошибки встаёт на место старого английского, после чего программа выводит переведённую на русский ошибку в консоль.

Не стоит забывать, что это работает не только с ошибками, но и с предупреждениями. Создадим скрипт test_warning.py и вставим приведённый ниже код, а затем запускаем translate_errors.py:
import warnings warnings.warn("This is a test warning")

В модуле также предусмотрено автоматические кэширование всех переведённых ошибок на диск (ограничение: до 100000 записей, однако его можно изменять в коде модуля). Для того, чтобы переводчик работал, нужно стабильное интернет-соединение. Если интернет будет отсутствовать, перевод выполняться не будет (за исключением случаев, когда ошибка уже записана на диск и берётся из кэшированных записей)!
Если что-то не будет работать, пишите в комментарии — разберёмся. Ниже можно пройти опрос. Он поможет найти баги в работе проекта и улучшить его для всех. По итогам опроса мы обновим код, исправив все баги и выполним предложения по улучшению. Надеюсь, эта статья была полезной!
