Мой блог
Создание WordToast: свой переводчик в браузере с ИИ и интервальными повторениями
Подробности изложены в материале первоисточника. Когда популярное китайское расширение Dadda Translate прекратило работу из-за изменений в сторонних API, я решил переписать аналогичный инструмент с нуля. Так появился WordToast для Firefox, Chrome и Edge, объединяющий локальный перевод на устройстве, поддержку нейросетей и систему интервальных повторений.
В этой публикации я подробно разберу архитектуру утилиты, включая интеграцию встроенного Translator API в Chrome, прямые запросы к LLM без промежуточного сервера и отрисовку изолированных интерфейсных карточек поверх сторонних веб-страниц.

Несколько движков и цепочка откатов
Архитектура перевода построена на последовательном опросе доступных провайдеров. Всего в системе задействовано пять движков: публичный эндпоинт Google Translate, сервис MyMemory, DeepL по персональному ключу пользователя, встроенные алгоритмы браузера Chrome, а также подключенные языковые модели.
Все они выстроены в единую цепочку. Если первый источник возвращает ошибку, упирается в лимит запросов или отдает пустой ответ, система автоматически переключается на следующий вариант. Для одиночных английских слов дополнительно задействуется специализированный словарь dictionaryapi.dev, который подтягивает части речи, детальные определения, примеры использования и аудиопроизношение.
Особенности бесплатных эндпоинтов и таймауты
Поскольку сторонние бесплатные API могут отвечать с задержкой до десяти секунд или возвращать пятисотые ошибки, для каждого запроса жестко прописан индивидуальный таймаут. Пользовательская карточка мгновенно отображает базовый перевод, в то время как словарная база подгружается асинхронно следом.
Отдельное внимание стоит уделять работе с DeepL. Бесплатные ключи имеют характерный суффикс :fx и отправляют запросы на адрес api-free.deepl.com, тогда как платные учетные записи обращаются к api.deepl.com. Игнорирование этого нюанса приводит к стандартной ошибке доступа 403.
В отличие от оригинального китайского расширения, которое скачивало сторонние скрипты и выполняло их через опасную конструкцию eval для извлечения токенов безопасности, мой проект полностью лишен внешнего динамического кода, что отвечает современным требованиям безопасности Manifest V3.
Локальный перевод через Translator API в Chrome
Начиная со сборки Chrome 138, в браузере появились встроенные инструменты Translator и LanguageDetector, позволяющие выполнять лингвистические задачи локально, без отправки пользовательских данных на внешние сервера. Для утилиты, анализирующей выделенный на сайтах текст, это важнейший фактор обеспечения приватности.
Реализация локального перевода выглядит следующим образом:
const g = globalThis as any;
async function onDeviceTranslate(text: string, to: string) {
const [best] = await (await g.LanguageDetector.create()).detect(text);
if (!best || best.confidence < 0.3 || best.detectedLanguage === "und") throw new Error("language not detected");
const from = best.detectedLanguage.split("-")[0];
const availability = await g.Translator.availability({ sourceLanguage: from, targetLanguage: to });
if (availability === "unavailable") throw new Error(`${from}→${to} unsupported`);
const translator = await g.Translator.create({ sourceLanguage: from, targetLanguage: to });
return translator.translate(text);
}
Нюансы реализации локального движка
В ходе разработки я столкнулся с несколькими ограничениями. Во-первых, Translator API недоступен в фоновом service worker расширения Manifest V3 и функционирует исключительно в контексте обычных страниц, поэтому вызовы выполняются через content-скрипты.
Во-вторых, первичное скачивание языковой модели требует прямой инициации пользователем. Метод создания переводчика без предварительного клика может завершиться сбоем, поэтому объекты переводчиков кешируются по языковым парам, а неудачные попытки не сохраняются в кеше для повторного обращения.
Кроме того, короткие текстовые фрагменты или одиночные слова часто вызывают низкую уверенность детектора языка. Если уровень уверенности опускается ниже порога 0.3, скрипт не пытается угадать значение, а безопасно передает задачу следующему движку в цепочке. В браузере Firefox данный интерфейс пока скрыт в глубоких настройках.
Интеграция LLM напрямую из расширения
Кнопка контекстного анализа позволяет отправить выделенное слово вместе с окружающим предложением напрямую в языковую модель для глубокого разбора нюансов использования. Поддерживаются OpenAI, Claude, Gemini, любые OpenAI-совместимые шлюзы вроде OpenRouter, а также локально запущенная через Ollama нейросеть.
Архитектура не использует собственный сервер-посредник: запросы отправляются с клиента с использованием ключа самого пользователя. Я сознательно отказался от тяжелых SDK, заменив их компактной функцией формирования запросов под конкретного провайдера.
Специфика работы с Claude и локальными моделями
При подключении Anthropic API из браузерного окружения срабатывает защита CORS, требующая явного подтверждения передачи клиентского ключа. В заголовки запроса обязательно добавляется специальный флаг авторизации опасного прямого доступа из браузера:
headers: {
"content-type" "application/json",
"x-api-key": cfg.apiKey,
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true"
}
Для взаимодействия с локальной Ollama требуется стабильный доступ к порту локальной машины через эндпоинт http://localhost:11434, для чего универсальный кастомный провайдер принимает любые произвольные базовые URL.
Изоляция интерфейса карточек поверх чужих страниц
Интерфейс всплывающих переводчиков и карточек повторения рендерится непосредственно в теле веб-страницы, подвергаясь риску конфликтов с чужими стилями и строгими правилами безопасности контента (CSP).
Для полной изоляции применяется специальный корневой тег с Shadow DOM, сбросом параметров через all: initial и максимальным приоритетом слоя z-index. Для внедрения стилей используются сконструированные таблицы, обходящие ограничения директив style-src:
const root = host.attachShadow({ mode: "open" });
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
root.adoptedStyleSheets = [sheet];
} catch {
const style = document.createElement("style");
style.textContent = css;
root.appendChild(style);
}
Координаты появления карточки рассчитываются на основе метода getBoundingClientRect для выделенного фрагмента текста или через геометрию полей ввода. Если снизу экрана недостаточно свободного пространства, интерфейс автоматически разворачивается вверх.
Интервальные повторения и экспорт словаря
Сохраненные лингвистические единицы фиксируются вместе с контекстным предложением и исходным URL веб-страницы. В дальнейшем они периодически появляются в углу экрана в виде компактных карточек с кнопками оценки запоминания.
Интервальный алгоритм включает следующие ступени задержки: 5 и 30 минут на начальных этапах, после чего переходит к классической схеме с интервалами в 12 часов, сутки, 3, 7, 21 и 60 дней. Игнорирование уведомления приводит к его повторному показу через двадцать минут.
Фоновый запуск реализован через системные будильники chrome.alarms, поскольку стандартные фоновые процессы Manifest V3 не поддерживают постоянные таймеры. Накопленные данные хранятся в защищенном локальном хранилище и легко выгружаются в форматах CSV, JSON или TSV для импорта в Anki.
