Столкнувшись с исключением, иногда не понимаешь: «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")

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

pfemidi
06.05.2026 18:06Столкнувшись с исключением, иногда не понимаешь: "divizion by zero" — что это?
Как это что? Дивизион нулей это, вот что! Если на русский переводить. То есть имя им легион, и все нули.

Vladislav2951
06.05.2026 18:06Изучать языки программирования без хотя бы базового английского – это как изучать химию без таблицы Менделеева или математику без таблицы умножения.
База есть база.
А если очень хочется прогать на русском языке, то welcome to 1C)))

grin2102
06.05.2026 18:06А я думаю,что этот модуль будет очень полезным для меня. Навыки английского языка это хорошо, но хотелось бы упростить процесс работы. А можно подключить не Google Translate , а другой переводчик? К примеру, от Яндекса

goodchal23 Автор
06.05.2026 18:06Да, можно. Но у Яндекса нет бесплатного API, как у Гугла. Сначала нужно регистрироваться в Яндекс Облаке и создать платёжный аккаунт, там получить API-ключ и создать переменную окружения. В коде error_translator нужно заменить использование GoogleTranslator на YandexTranslator:
import sys import warnings import os import json import time import re from deep_translator import YandexTranslator 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 = YandexTranslator(source=SOURCE_LANG, target=TARGET_LANG)Затем можно запускать translate_errors с выбранным скриптом для перевода ошибок, однако если не создать переменную окружения с API-ключом, будет вылезать что-то в этом духе:
Traceback (most recent call last): File "C:\Users\...\...\error-translator\translate_errors.py", line 1, in <module> TARGET = "test.py"; from run import run_target; run_target(TARGET) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\...\...\error-translator\run.py", line 11, in <module> from error_translator import cached_translate File "C:\Users\...\...\error-translator\error_translator.py", line 15, in <module> translator = YandexTranslator(source=SOURCE_LANG, target=TARGET_LANG) File "C:\Users\...\...\error-translator\.venv\Lib\site-packages\deep_translator\yandex.py", line 41, in __init__ raise ApiKeyException(YANDEX_ENV_VAR) deep_translator.exceptions.ApiKeyException: None --> You have to pass your api_key! You can do this by passing the key as a parameter/argument to the translator class or by setting the environment variable YANDEX_API_KEY Example: export YANDEX_API_KEY="your_api_key" Process finished with exit code 1Я бы рекомендовал изучить вам документацию, возможно, там есть другие бесплатные переводчики. Но Яндекс пока только платно.

astentx
06.05.2026 18:06Чаще всего наоборот сталкиваюсь с тем, что приходится искать способ запуска приложений, которые мне нужно как-то отлаживать, в английской локали. Потому что русскоязычная часть интернета практически по всем ЯП/фреймворкам/библиотекам/инструментам настолько маленькая, что вероятность найти ответ на свою проблему, сформулированную на русском языке, стремится к нулю. Особенно с учётом все возрастающей скорости генерации новых инструментов. Просто скопипастить ошибку из лога на английском и в крайнем случае перевести в браузере через ПКМ намного проще. А лучше все же подтянуть язык для чтения технических текстов, это не так сложно. Ведь опять же, вся документация на английском. У Майков автоперевод в доках есть, иной раз такие перлы выдаёт, что и понять невозможно, о чем тут речь.
Dair_Targ
Вот даже не знаю, с чего б я начал ревью. Допустим этот код хотим использовать в реальном серверном приложении (иначе зачем кэш на 100к разных ошибок?)
Текст ошибок потениально содержит пользовательские данные. Неконтролируемая отправка их в Google Translate - сторонний сервис в иностранной юрисдикции - грозит штрафами до 10 млн. рублей.
Приложение может быть развёрнуто в окружении, где файловая система - read only.
Чтение json-файла на 100к записей на каждую(!) ошибку - это очень медленно.
Цвета и прочий ascii art часто крайне мешают при интеграции с другими приложениями, такими как сборщики логов. Как и перевод, цвет лучше делать не силами приложеиня, а добавлять во всяких внешних графанах/sentry/...
Если 90% кода и ошибок написаны английскими словами, может быть стоит язык до уровня A2 подтянуть?
----
В целом хорошее упражнение для начала, но это именно упражнение. Использование в реальной работе этого гарантированно приведёт ко множеству возможностей набраться опыта.
goodchal23 Автор
Насчёт использования в продакшене — вы правы. В будущем попробую добавить локальный машинный перевод, чтобы все данные оставались в безопасности. Что касается ascii-арта и цвета — я сделал это для удобства, чтобы ошибка сразу бросалась в глаза, но проблему учту и добавлю возможность отключения заливки цветом. JSON заменю на SQLite для более быстрой обработки. Также предусмотрю обработку случаев с read-only хранилищем.