Google Chrome Extensions: быстрый переводчик своими руками

image

Недавно заметил, что пусть мой английский не так уж и плох, я всё равно довольно часто отвлекаюсь на перевод отдельных незнакомых слов. И так как мне надоело каждый раз тратить на это свое время я решил написать расширение-переводчик. Можно сказать:
Но такие уже есть!
Да, есть, но, во-первых, я раньше не писал расширения для браузеров и хотел попробовать, во-вторых, создавать что-то самому всегда веселее чем пользоваться готовым. Так что кому это интересно так же как и мне — добро пожаловать под кат.

А саму статью я решил написать, потому что многую информацию приходилось собирать по частям из дальних уголков интернета и я решил все немного объединить.

Итак, подсаживаемся поближе к камину открываем Notepad++, VS Code или любой другой удобный редактор и начинаем.

1. Подготовка необходимых файлов


Нам понадобятся:

  1. Manifest.json — здесь будет храниться версия нашего расширения, название, список используемых файлов, разрешения.
  2. Popup.html — это лицо нашего расширения, тут мы напишем popup-станичку, которая будет отображаться при клике на иконку нашего расширения
  3. Background.html — это все фоновые процессы нашего расширения.
  4. 4 иконки: 16х16, 36х36, 48х48, 128х128 — это иконка вашего приложения, хром и сам может подгонять иконку под нужный размер, но вы можете выставить разные иконки и в зависимости от этого они будут разными в контекстном меню, возле адресной строки, в меню расширений и т.д.

Сразу небольшая сноска о том, как загружать расширения в google chrome:
  1. Сохраняем все файлы расширения в отдельную папку
  2. Открываем Google Chrome -> Опции -> Настройки -> Расширения
  3. Ставим галочку режим разработчика, нажимаем загрузить распакованное расширение.
    image
  4. Указываем путь до папки с расширением, нажимаем ОК


2. Притча о манифесте и popup


И создал программист манифест и увидел программист, что это хорошо.

Наш manifest.json должен выглядеть
примерно так:
{
    "manifest_version": 2,

    "name": "Translator",
    "version": "1.0",

    "icons": {
        "16": "16x16.png",
        "32": "32x32.png",
        "48": "48x48.png",
        "128": "128x128.png"
    },

    "permissions": [
        "http://translate.yandex.net/*",
        "contextMenus"
    ],

    "browser_action": {
        "default_title": "Open translator",
        "default_icon": "48x48.png",
        "default_popup": "popup.html"
    },

    "background": {
    "page": "background.html"
    }
}


Теперь по-порядку:

  • manifest_version — версия нашего манифеста, сейчас актуальна 2 версия.
  • name — название расширения
  • version — версия расширения, тут должны быть только цифры, но в любом формате: 1.0 или 1.0.0.1 или 1.1.2, можно писать как больше нравится.
  • icons — список иконок нашего расширения
  • permissions — разрешения нашего расширения, ссылка даёт доступ к определенному ресурсу, contextMenus даёт доступ к контекстному меню.
  • browser_action: default_title — текст, который будет появляться при наведении мышки на иконку расширения, default_icon — иконка приложения по умолчанию, default_popup — popup-окошко по-умолчанию.
  • background — тут можно подключить фоновую страницу, если она есть или фоновые скрипты.

С манифестом разобрались, теперь можно перейти и к более интересному занятию. Перед тем как мы начнем создавать View нашего расширения — нам нужно скачать дополнительные файлы всех цветов и расцветок. Вообще это необязательно и кто-то может написать расширение на чистом JS, без сторонних стилей, но я мне захотелось использовать Jquery и Bootstrap.

Я решил особенно не заморачиваться с внешним видом и быстренько сверстал более-менее симпатичное окошечко прилично выглядящего переводчика.

popup.html
<!DOCTYPE html>
<html>
<head>
    <script src="jquery.js"></script>
    <script src="popup.js"></script>
    <script src="bootstrap.js"></script>
    <link rel="stylesheet" href="bootstrap.css">
    <link rel="stylesheet" href="bootstrap-theme.css">
    <link rel="stylesheet" href="popup.css">
</head>
<body>
<header>
  <img id="logo" src="mixx.png">
</header>

<div id="wrapper">
    <div class="li">
        <input id="input" class="form-control">
    </div>
    <div class="li" style="margin-bottom: 10px;">
        <button id="btn_submit" type="submit" class="btn btn-default" style="margin-top: 10px;">Перевести</button>
    </div>
</div>

<footer id="options" role="button">
    <label id="result"></label>
</footer>

</body>
</html>


Не знаю, насколько лично ты, читатель, подкован в html, js и css, но на данном уровне я не ставлю целью объяснить тебе принципы этих языков, все что я использую и не поясняю довольно примитивно и легко гуглится.

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

Создадим файл popup.css и добавим немного стилей:

popup.css
body
{
  min-width: 250px;
  margin: 0px;
  font-family: Segoe UI, Arial, sans-serif;
  font-size: 13px;
  background-color: #f8f6f2;
}

header
{
  height: 45px;
  margin-bottom: 40px;
  border-bottom: 1px solid #e1ddd8;
}

#logo
{
  display: block;
  position: relative;
  top: 15px;
  margin: 0px auto;
}

#wrapper
{
  padding: 0px 20px;
}

footer
{
  cursor: pointer;
  padding: 10px 35px;
  border-top: 1px solid #e1ddd8;
}

footer:hover
{
  background: linear-gradient(to bottom, rgba(70, 50, 0, 0.1), rgba(70, 50, 0, 0.1));
}

.li
{
  list-style-type: none;
  border-top: 1px dashed #a5a4a1;
}

label
{
  vertical-align: middle;
}


Теперь наше расширение должно выглядеть примерно так:

image

Теперь можно переходить к самой логике переводчика, создаем файл
popup.js
$(document).ready(function(){

    $('#btn_submit').click(function(e){ /* Функция начнет свою работу, как только пользователь клинет по кнопке с id = "btn_submit" */

        translate($('#input').val());
    });
});


function translate(input) {
    
        var url = "https://translate.yandex.net/api/v1.5/tr.json/translate"; /* Обратите внимание, что ссылка по новому апи яндекса, начиная с версии 1.5 строится иначе, чем раньше */

        var key = "trnsl.1.1.20170124T214153Z.11b2724899c0a9fc.6d5c7e3a02107ce1349d21bbc6dc9dd4a86dc62a"; /* это ключ вашего приложения, который можно получить на официальном сайте яндекса, без него апи работать <b>не будет!</b>  https://tech.yandex.ru/keys/get/?service=trnsl (получение ключа)*/

        var parent = /[а-яёЁ]/i;

        var language = (parent.test(input))? 'ru-en':'en-ru';

        $.getJSON(url, {lang: language, key: key, text: input}, function(res){ /* Ответом мы получаем массив */
            $('#result').text("");
            for (var i in res.text) {
               $('#result').text($('#result').text() + res.text[i] + " ");
            }
        });
}


Сохраним и загрузим наше расширение в хром. Иногда кнопка обновить расширения не помогает и тогда нужно удалить расширение и загрузить заново.

Проверяем работу, у нас должно получится что-то подобное:

image

Вроде уже неплохо постарались, но чего-то не хватает. Добавим реакцию на клавишу Enter.

Добавляем код в popup.js
$("#input").keyup(function(event){
    if(event.keyCode == 13){ /* 13 - виртуальный код клавиши Enter
        $("#btn_submit").click();
    }
});


Проверяем и видим, что теперь можно нажимать enter, а не кликать по кнопке. Но нужно добавить еще минимум одну функцию, которая присутствует в большинстве переводчиков — это копирование переведенного текста в буфер.

Добавляем код в popup.js
var options = document.getElementById("options");
if(options) {
    options.addEventListener("click", function() /* Начинаем ждать клика по панели footer */
  {
    var range = document.createRange(); */ Возвращает новый объект типа Range. */
    var value = document.querySelector("#result");  /* Селектор выбирает элемент с id = "result" */
    range.selectNode(value); /* Выбор элемента */
    window.getSelection().addRange(range);  

    try {  
    var successful = document.execCommand('copy');  
    var msg = successful ? 'successful' : 'unsuccessful';  
    console.log('Copy email command was ' + msg);

  } catch(err) {
    console.log('Oops, unable to copy');  
  }
  window.getSelection().removeAllRanges();
 });


Ну что, вроде неплохо постарались над view, теперь у нас есть неплохой переводчик, более того написанный нами! Но не всех удовлетворит такой результат, для таких же неутомимых искателей приключений, которые пытаются довести инструмент если не до совершенства, но хотя бы сделать его максимально удобным — не остановимся на этом!

Сага о background.js


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

Background.html — фоновая страница и одна из ее возможностей — добавлять пункт в контекстное меню. В сам background.html мы лишь подключаем скрипты, они то нам и нужны.

background.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script src="jquery.js"></script>
    <script src="background.js"></script>
</head>


А вот с background.js нам предстоит уже больше повеселиться. Создаем пункт нашего приложения в меню:

Добавляем в background.js
chrome.contextMenus.create({
            'title': 'Перевести с Translator', /* Текст пунтка меню */
            'contexts':['selection'], /* Тип пункта меню */
            'onclick': function() {} /* Запомните это место, вместо этой функции мы будем вставлять код перевода */
});


Теперь когда мы перезагрузим расширение и выделив какой-нибудь текст щёлкнем правой кнопкой мыши — мы увидим, что в контекстном меню появился новый пункт:

image

Теперь нужно придумать как переводить этот текст. Сначала я хотел, чтобы при выделении текста открывалось наше popup-окошко и перевод был там, но столкнулся с тем, что Google ограничило возможность открытия окошка только при нажатии на иконку. Тогда я взял в руки меч решил сделать перевод в контекстном меню, как это сделано в Яндекс браузере:

image

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

В background.js на место старой пустой функции вставьте эту строчку:

Вставьте код в background.js
function(info, tab) { translate(info.selectionText) }


И отдельно добавьте функцию translate:

Вставьте код в background.js
function translate(input) {
    
        var url = "https://translate.yandex.net/api/v1.5/tr.json/translate";

        var key = "trnsl.1.1.20170124T214153Z.11b2724899c0a9fc.6d5c7e3a02107ce1349d21bbc6dc9dd4a86dc62a";

        var parent = /[а-яёЁ]/i;

        var language = (parent.test(input))? 'ru-en':'en-ru';

        $.getJSON(url, {lang: language, key: key, text: input}, function(res){
                alert(res.text);
        });
}


Проверим работу и увидим, что когда мы выделяем текст и нажимаем в контекстном меню кнопку перевести, появляется вот такое окошко:

image

Ну вот и все, друзья! Если кто-то подскажет лучший способ реализации перевода с помощью контекстного меню — буду рад услышать.



За информацию о реализации подобных вещей выражаю свою благодарность статьям на Habrahabr и ответам на StackOverflow.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 24

    +1
    В Java статья попала случайно, или я что-то не понял?
      0
      Да, случайно указал Java вместо JavaScript, уже поправил :)
      –3
      А в firefox можно просто открыть мобильную версию онлайн переводчика в боковой панели.
        +3
        Естественно, а в Яндекс-браузере в контекстном меню появляется перевод, но статья о том как разработать переводчик, а не воспользоваться готовым :)
          0
          как и в Vivaldi ;)
            –1
            что такое боковая панель? Там встроен мобильный переводчик?
            0
            в firefox есть расширение которое перевод вставляет прямо в dom рядом с выделенным словом. Очень удобно.
              +1
              Добрый день!
              Не совсем понятно, что Вам помешало посмотреть исходный код этого расширения и сделать по аналогии?
              Все-таки всплывающее окно это не совсем удобно.
                0
                Да, конечно, всплывающее окно неудобно. Но у меня почему-то не появляется окошко при выделении текста. Сам изначально хотел так и сделать, но даже с гугловским расширением не заработало. В ближайшие дни покопаюсь в нём, если найду что-нибудь полезное — обновлю статью :)
                  +1
                  Вот это исследование было бы очень полезным: почему с «фирменными» расширениями эта «фича» работает, а с самописными — нет. ifalur — верно заметил для FireFox, ИМХО, здесь принцип тот же — интеграция в DOM. Возможно, что-то не прописано в манифесте?
                    0
                    Возможно что-то не прописано, но даже у гугловского расширения это окошко работает очень глючно и далеко не всегда открывается. (Во всяком случае у меня)
                      0
                      Я боюсь Вы «замучили» свой Chrome )))
                      Как вариант — попробовать это и проверить версию — на 55.0.2883.75 работает стабильно (пользуюсь регулярно)
                0
                можно сделать форму в верстке и забиндиться на её submit? там и enter работать будет без keyCode
                  0
                  Не уверен, но по-моему формы не работают в расширениях, если я ошибаюсь — поправьте меня, буду рад :)
                    0
                    сам не в курсе, поэтому и спросил
                  0

                  Молодец,
                  http://software.uz/ru/blog/view?id=20
                  Пост 2016 года)
                  Я тоже делал чтото подобное

                    0
                    Спасибо, за информацию. Попробовал, отличное решение, но устарел апи (сейчас яндекс только с апи кей работает) и само решение работает не на всех сайтах, у меня на многих сайтах попросту не вставлял элемент, пока не знаю с чем связано.
                    0
                    писал такой же переводчик для FF+Яндекс.переводчик https://addons.mozilla.org/en-US/firefox/addon/quizlet-helper/ нужно только выделить нужное слово мышью
                      +1
                      Все классно, но есть мелочи…

                      Например вместо этого:
                      var parent = /[а-яёЁ]/i;
                      var language = (parent.test(input))? 'ru-en':'en-ru';
                      


                      Правильнее было бы:
                      var pattern = /[а-яёЁ]/i;
                      var language = (pattern.test(input))? 'ru-en':'en-ru';
                      


                      А ещё лучше вот так: (потому как переменная используется один раз)
                      var language = (/[а-яёЁ]/i.test(input))? 'ru-en':'en-ru';
                      


                      А так спасибо за идею, есть моменты которые я не знал )))
                        0

                        Ну как говорится: «Нет хуже причины для использования имени "с", как та, что имена "a" и "b" уже заняты».


                        Ради этого и писал статью, рад что помог :)

                        0
                        У вас перед вторым пунктом у гиперссылки в надписе написано 'gogle'
                          0

                          спасибо, поправил

                          +1
                          Очень хороший пост, спасибо, вдохновили, пойду делать chrome extension))
                            0

                            Очень рад, удачи :)

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

                          Самое читаемое