Реализация автоматического перевода сообщений
Я реализовал автоматический перевод сообщений с русского на украинский в своём чате в Telegram. Не буду вдаваться в подробности, зачем это понадобилось. Некоторым пользователям эта функция понравилась, а других раздражала или вызывала сомнения в её целесообразности. Скажем так, я взялся за этот технический вызов ради личного роста. На работе я в основном занимаюсь организационными задачами, которые меня изматывают, и я не слишком в них искусен и не испытываю к ним особого энтузиазма.
Поэтому, скажем так, я взялся за этот проект, чтобы освоить новую технологию, и получаю удовольствие от приобретения новых навыков и от того, что делаю что-то самостоятельно. Это подчеркивает, что русский — чужой язык в украинском пространстве, и ненавязчиво подталкивает людей перейти на украинский или, возможно, даже совсем покинуть чат. Ладно, перейдём к технической стороне.
У меня есть бот, который работает на PHP — языке программирования, которым я пользуюсь чаще всего. Сначала я искал решение для определения языка сообщений, потому что не хотел отправлять каждое сообщение в API перевода. Я наткнулся на библиотеку LanguageDetector на GitHub (landrok/language-detector). Интегрировав её в бота, я решил вычислять баллы для каждого языка и сравнивать их. Это было необходимо, поскольку при определении языка с помощью библиотеки часто путались языки с кириллицей. Возможно, нужны более обширные наборы данных, но я не смог найти их сразу.
Поэкспериментировав и протестировав на коротких фразах, я вычитал украинский балл из российского. Если разница превышала 0,016, а общий российский балл превышал 0,05, сообщение считалось русским. В итоге я остановился на значениях 0,02 и 0,1, чтобы уменьшить частоту срабатывания перехода для коротких слов и фраз, где перевод всё равно не сильно отличался бы.
Для самого перевода я выбрал DeepL, так как это на данный момент лучший вариант на рынке, насколько мне известно. Отправлять запросы в API DeepL для перевода сообщений оказалось несложно. Я оформил бесплатный доступ к API, который предоставляет полмиллиона символов в месяц, чего должно хватить для чата, где большинство сообщений и так уже на украинском языке.
Сначала я настроил бота на автоматическое определение языка, но впоследствии заставил его переводить с русского. Это было необходимо, так как система определения языка не всегда правильно распознавала русский и иногда отправляла на перевод сообщения на украинском языке. Поскольку сообщения уже были на украинском, DeepL иногда определял язык ввода как другой кириллический, например болгарский, что приводило к забавным результатам перевода.
После дня тестирования функции перевода я заметил, что определение языка часто давало сбой и отправляло украинские сообщения на перевод. Я попытался найти лучшие решения, но не смог найти доступного API. Впрочем, просматривая проблемы (issues) в репозитории определителя языка на GitHub, я наткнулся на рекомендацию PHP-расширения Compact Language Detector 2 (fntlnz/cld2-php-ext), которое, как говорили, было лучшим.
Сначала я немного побоялся, потому что компиляция расширения из исходного кода казалась сложной, и в прошлом у меня были с этим проблемы. Я решил попробовать. К сожалению, компиляция не удалась, а мои попытки найти решение через поиск в интернете оказались тщетными. Но, вернувшись в раздел issues репозитория, я узнал, что расширение несовместимо с PHP 8, но есть pull-запрос, чтобы сделать его совместимым (hiteule/cld2-php-ext/tree/support-php8). Поэтому я загрузил расширение из ветки этого PR, и оно успешно скомпилировалось.
Затем я осознал, что мой бот работает на PHP 7, тогда как CLI — на PHP 8. Я подумывал переключить версию PHP в CLI, но вместо этого решил включить PHP 8. Это изменение должно было повысить производительность, а проблем с совместимостью не было. В процессе я также настроил SSH-ключ на GitHub, чего раньше не делал. Это позволило мне клонировать репозитории через SSH и стало для меня новым опытом на GitHub. К счастью, это оказалось несложно, так как я часто использую SSH для других задач и уже добавлял ключи в другие сервисы.
Алгоритм определения языка CLD2 значительно лучше работает с полными предложениями, правильно распознавая язык. Однако ему сложно определить язык для коротких фраз и отдельных слов, и в таких случаях он не выдает никаких предположений. Поэтому я реализовал механизм отката к предыдущему Language Detector в ситуациях, когда язык не удавалось определить или когда оценка определения русского языка была ниже 97%. Я заметил, что когда показатель точности определения был ниже 97%, это мог быть либо какой-то неправильный украинский, либо фраза, не требующая перевода.
Но даже в таких случаях случались сбои в определении языка, а это означало, что переводы отображались даже тогда, когда никакого перевода не происходило. Поэкспериментировав, я придумал решение, которое сводилось к тому, что я оставлял из оригинального и переведенного текста только строчные кириллические буквы, а затем сравнивал строки из этих букв. Перевод не отображался, если строки совпадали на 85% или более. Этот подход отсеивал случаи, когда текст оставался неизменным после перевода или имел лишь незначительные отличия. Он также решил проблему с украинским апострофом, который по-разному отображается на разных клавиатурах и в разных системах.
Этот проект вернул меня к тому, что мне по душе. За последний месяц я неоднократно сталкивался с советом сосредоточиваться на задачах, которые тебя увлекают и в которых ты хорошо разбираешься. Ощущение выполненного дела, которое я получил от реализации этой небольшой функции, превзошло любое прохождение видеоигры. К сожалению, практическая польза этой реализации ограничена, и, несмотря на первоначальные положительные отзывы, теперь я сталкиваюсь с постоянным негативным фидбеком. Я даже настроил бота так, чтобы он публиковал переводы без ответа на исходное сообщение. Чтобы предотвратить подобные ситуации, мне нужно стать лучшим product owner'ом, но этот вызов я оставлю для работы, а не для своего хобби.