Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Создание WordToast: свой переводчик в браузере с ИИ и интервальными повторениями

Создание WordToast: свой переводчик в браузере с ИИ и интервальными повторениями

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

В этой публикации я подробно разберу архитектуру утилиты, включая интеграцию встроенного Translator API в Chrome, прямые запросы к LLM без промежуточного сервера и отрисовку изолированных интерфейсных карточек поверх сторонних веб-страниц.

Исходный код и логика работы движков перевода WordToast
Схема обработки запросов и переключения между переводчиками
Панель настройки горячих клавиш и параметров расширения
Окно конфигурации горячих клавиш и параметров расширения

Несколько движков и цепочка откатов

Архитектура перевода построена на последовательном опросе доступных провайдеров. Всего в системе задействовано пять движков: публичный эндпоинт 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.

01.