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.
Поделиться с друзьями
-->

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


  1. vlanko
    30.01.2017 12:09
    +1

    В Java статья попала случайно, или я что-то не понял?


    1. Noxormy
      30.01.2017 15:11

      Да, случайно указал Java вместо JavaScript, уже поправил :)


  1. lain8dono
    30.01.2017 13:01
    -3

    А в firefox можно просто открыть мобильную версию онлайн переводчика в боковой панели.


    1. Noxormy
      30.01.2017 15:12
      +3

      Естественно, а в Яндекс-браузере в контекстном меню появляется перевод, но статья о том как разработать переводчик, а не воспользоваться готовым :)


    1. animhotep
      30.01.2017 15:24

      как и в Vivaldi ;)


    1. Varim
      30.01.2017 19:42
      -1

      что такое боковая панель? Там встроен мобильный переводчик?


  1. ifalur
    30.01.2017 14:41

    в firefox есть расширение которое перевод вставляет прямо в dom рядом с выделенным словом. Очень удобно.


  1. Balintrue
    30.01.2017 15:17
    +1

    Добрый день!
    Не совсем понятно, что Вам помешало посмотреть исходный код этого расширения и сделать по аналогии?
    Все-таки всплывающее окно это не совсем удобно.


    1. Noxormy
      30.01.2017 15:21

      Да, конечно, всплывающее окно неудобно. Но у меня почему-то не появляется окошко при выделении текста. Сам изначально хотел так и сделать, но даже с гугловским расширением не заработало. В ближайшие дни покопаюсь в нём, если найду что-нибудь полезное — обновлю статью :)


      1. Balintrue
        30.01.2017 15:46
        +1

        Вот это исследование было бы очень полезным: почему с «фирменными» расширениями эта «фича» работает, а с самописными — нет. ifalur — верно заметил для FireFox, ИМХО, здесь принцип тот же — интеграция в DOM. Возможно, что-то не прописано в манифесте?


        1. Noxormy
          30.01.2017 16:08

          Возможно что-то не прописано, но даже у гугловского расширения это окошко работает очень глючно и далеко не всегда открывается. (Во всяком случае у меня)


          1. Balintrue
            30.01.2017 22:12

            Я боюсь Вы «замучили» свой Chrome )))
            Как вариант — попробовать это и проверить версию — на 55.0.2883.75 работает стабильно (пользуюсь регулярно)


  1. deamondz
    30.01.2017 18:12

    можно сделать форму в верстке и забиндиться на её submit? там и enter работать будет без keyCode


    1. Noxormy
      30.01.2017 18:13

      Не уверен, но по-моему формы не работают в расширениях, если я ошибаюсь — поправьте меня, буду рад :)


      1. deamondz
        31.01.2017 09:58

        сам не в курсе, поэтому и спросил


  1. davron2813
    30.01.2017 19:03

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


    1. Noxormy
      30.01.2017 19:05

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


  1. Fisk
    30.01.2017 19:13

    писал такой же переводчик для FF+Яндекс.переводчик https://addons.mozilla.org/en-US/firefox/addon/quizlet-helper/ нужно только выделить нужное слово мышью


  1. keilman
    31.01.2017 14:23
    +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';
    


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


    1. Noxormy
      31.01.2017 14:27

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


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


  1. ProgramerNikita
    04.02.2017 15:13

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


    1. Noxormy
      06.02.2017 19:06

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


  1. ProgramerNikita
    04.02.2017 15:19
    +1

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


    1. Noxormy
      06.02.2017 19:05

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