Открытое бесплатное chrome расширение для изучающих японский язык (OCR + translation + annotation)

от автора

В этой статье я хотел бы рассказать как решил создать свой первый проект.

В течении пары лет я с переменным успехом в свободном режиме изучал японский язык и постоянно пытался применять подход с погружением в языковую среду. Так я, к примеру, добавил японский в раскладки клавиатуры, смотрел простые видео и подкасты, пытался читать легкие тексты.

Но японский язык (также как и китайский и, частично, корейский) имеет довольно высокий входной барьер для чтения, потому что нужно не только выучить два алфавита, но и запомнить как минимум несколько сотен кандзи (300-500).

Постоянная проблема с переключением от контента на вкладку словаря, чтобы найти один неизвестный или забытый кандзи, тратила время и сильно сбивала настрой. При этом поиск по кандзи в таких словарях обычно идет по радикалам или количеству строк, так что иногда найти какую то мелочь там реально долго.

Я обнаружил несколько крутых ресурсов, скажем Yomichan, который распознает текст под курсором мыши и предоставляют перевод и аннотацию из подгруженный словарей.

К сожалению, ни один из бесплатных вариантов не использовал OCR, чтобы можно было проверить любую картинку, панель манги или субтитры на остановленном видео, прямо из той же вкладки. Поэтому я решил попробовать создать нечто подобное 🙂

ScanLingua (имя, кстати, предложено chatGPT).

Я выбрал формат chrome extension и попытался применить то, что усвоил на нескольких курсах JS и React, и попробовал разные подходы, так что оно не сильно последовательное, но этот проект в том числе и для портфолио, в общем демонстрируем все что можем.

Структура проекта

Расширение состоит из двух основных частей: servise_worker and content_script, которые являются отдельными React приложениями.

servise_worker отвечает за manifest, fetch запросы и рендер основного popup расширения, а content_script рулит тем что происходит внутри активного таба (overlay и его логика, popup с результатами).

При этом content_script собран одним файлом в servise_worker/public. 

Приложения обмениваются необходимыми данными через chrome messaging API с уникальными ID:

// content_script  async function requestTranslation () {     const requestId = getRandomInt(100000)      const response = await chrome.runtime.sendMessage({type: "request-translation", requestId});      const res = await new Promise((resolve, reject) => {         requests.set(requestId, resolve)          setTimeout(reject, 60*1000) //60sec     })     return res }  // service_worker  chrome.runtime.onMessage.addListener(gotMessage)   async function gotMessage(request, sendResponse) {     if (request.type == "request-translation") {         const rawTranslation = await translateZone(visionText)         const translation = await unEscape(rawTranslation)         await sendTranslation(request.requestId, translation)     } }  async function sendTranslation(requestId, translation) {     const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});     const response = await chrome.tabs.sendMessage(tab.id, {type: "your-translation", requestId, translation}); } 

Active tab overlay и canvas

Для реализации функционала по выделению части active tab, я начал с создания основного overlay div и пяти детей (top, bottom, right, left, zone).

А затем передал им координаты mousedown и mousemove (x1, y1, x2, y2) чтобы зона менялась динамически. Чтобы кропнуть выделенную зону используется captureVisibleTab в котором затем рисуется OffscreenCanvas по имеющимися координатам.

Поначалу я просто сконвертировал полученные данные в base64 и опробовал OCR API, но результат выглядел так себе:

Зона убежала в сторону + добавился зум. И вот так я не сразу, но узнал о devicePixelRatio, а также то, что на моем ноутбуке он не 1, а 2. В целом хорошо что наткнулся на это рано, иначе просто вылезло бы позже. Добавил проверку и поправку на ratio и зона встала на место.

Стили

Основной проблемой со стилизацией была необходимость изолировать стиль всплывающего окна от стиля активного таба в который оно внедряется. В целом по выбор был между iFrame и ручным оверрайдом конфликтующих стилей.

Учитывая что всплывающее окно будет только с одной кнопкой и текстом, я решил обойтись оверрайдом + реализовал CSS-in-JS с помощью React Styled Components так как по требованиям к структуре расширения content_script должен быть собран в один .js файл.

Использованные API

Для OCR и перевода ScanLingua использует Google vision и translation API.

Эти API не бесплатные, но vision дает 1000 бесплатных запросов, а translation 500 000 бесплатных символов перевода в месяц, чего вполне достаточно для частного использования. Пользователю необходимо сгенерировать API ключ и посматривать за лимитами (ссылки на инструкцию по созданию ключа можно найти в репозитории, ссылки в расширении и на сайте проекта). Чтобы помочь пользователю отслеживать использование ключа на домашней вкладке расширения предусмотрен простой счетчик.

Для предоставления аннотации я использовал бесплатный открытый словарь Jotoba (Jotoba API бесплатное, поэтому его доступность не гарантируется)

Что дальше?

Расширять функционал (добавить аннотацию для китайского и корейского языков, настроить интеграцию с Anki), править логику и исправлять ошибки, UI.

Расширение еще сырое, но в целом уже юзабельное. Proof of concept release размещен в магазине:

Репозиторий:

Также накидал небольшой адаптивный product page проекта:

Буду рад отзывам и комментариям!


ссылка на оригинал статьи https://habr.com/ru/articles/730566/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *