Переведи меня нежно

временами на форуме(и не только) всплывают разговоры о том как и чем перевести выделенный текст или даже о написании нужной приблуды
встряхнув немного пыли с кода и приведя в божеский вид решил поделится простеньким скриптом дабы не страдать самому а разделить ношу поровну

работает как под Xorg так и под Wayland
переводит текст который выделен(только для Xorg) и/или скопированный (для Xorg и Wayland) в буфер
перевод осуществляется с помощью онлайн сервисов перевода Google и Yandex (для яндекс нужен ключ который легко можно получить по ссылке)


#!/usr/bin/env bash

### Зависимости
# ---------------------------------------
# jq            - работа с json в консоли           [обязательно]
# xsel          - рaбота с буфером в Xorg           [опционально] (для dscp=xorg)
# wl-clipboard  - работа с буфером в Wayland        [опционально] (для dscp=wayland)
# iso-codes     - cписок стран и языков             [опционально] (для --codes-lang, --codes-lang2)
# par (AUR)     - фильтр для форматирования текста  [опционально] (для ui=terminal)
# alacritty     - терминал                          [опционально] (для ui=terminal)
# kdialog       - графический диалог kde            [опционально] (для ui=kdialog)
# zenity        - графический диалог gnome          [опционально] (для ui=zenity)
# yad           - графический диалог gnome          [опционально] (для ui=yad)
# tk            - графический диалог tk(wish)       [опционально] (для ui=wish)

# Переводчики
# google      можно использовать(бесплатно) сразу но не через стандартное API для разработчиков
# yandex      нужно получить(бесплатно) api ключ
#             получить ключ от яндекс translate API
#             https://translate.yandex.ru/developers/keys
#             бесплатный тариф: объем переводимого текста - в размере
#             до 1 000 000 символов в сутки, но не более 10 000 000 символов месяц.

### Значения по умолчанию
#------------------------ config start -----------------------------
langIn=auto               # <auto> автоопределение языка или переводить с
langOut=ru                # переводить на
bufferOut=no              # помещать перевод в буфер
filterIn=no               # применить фильтр для входящего текста (фильтры необходимо прописываются ниже в коде - см. Фильтр(in))
translator=google         # сервис онлайн перевода, один из <google, yandex>
dscp=xorg                 # протокол графического сервера, один из <wayland, xorg>
ui=zenity                 # интерфейс для вывода перевода, один из <cli, terminal, kdialog, zenity, yad, wish>
WxH=800x300               # размер ширины и высоты окна с переводом, в пикселях
CxR=80x12                 # количество колонок и строк в окне терминала с переводом
closingTime=0             # секунд до закрытия окна, 0 - не закрывать
wcm=no                    # показать число символов для входящего текста и его перевода, будет отображаться в заголовке графического интерфейса
yandexApiKey='**************'
#------------------------ config end --------------------------------
# данные значения также можно(не обязательно) поместить в конфиг-файл
# конфиг-файл может находится в одном из указанных мест:
# - рядом с данным скриптом
# - по пути указанном в переменной config, смотри ниже
# - расположение заданно с помощью ключа --config

### Конфиг-файл
# config="${0%/*}/langmi.conf"                       # относительный путь к конфигу (возле скрипта)
# config=$(dirname $(readlink -e "$0"))/langmi.conf  # абсолютный путь к конфигу    (возле скрипта)
# config=''                                          # пользовательский абсолютный путь (указать свой)

### Подключение конфиг-файла если таковой имеется
[[ -f "$config" ]] && source "$config" && __config=" --config $config"

[[ "$bufferOut" == "yes"      ]] && _b=" -b"
[[ "$filterIn"  == "yes"      ]] && __filterIn=" --filter"
[[ "$wcm"       == "yes"      ]] && __wcm=" --wcm"

__size=" --WxH $WxH"
if [[ "$ui"     == "terminal" ]]
  then __size=" --CxR $CxR"
  elif [[ "$ui" == "cli"      ]]
    then __size=""
fi

version=0.7
Help="Использование:
  langmi [options]

  -v, --version       показать версию программы
  -h, --help          показать список параметров командной строки
  -c, --codes-lang    показать список всех двух-буквенных кодов названий языков (en)
      --codes-lang2   показать список всех двух-буквенных кодов названий языков (под локаль)
      --codes-google  показать список поддерживаемых языков - google
      --codes-yandex  показать список поддерживаемых языков - yandex

  Ключи:
  -l, --lang          <код языка>:<код языка> см. --codes-*
      --lang-in       <код языка> С которого переводить или <auto> автоопределение
      --lang-out      <код языка> НА который переводить
  -s, --text-in       <текст> для перевода
  -b, --buffer-out    помеcтить перевод в буфер
      --filter-in     применить фильтр для входящего текста
  -p, --dscp          <wayland, xorg> протокол графического сервера
  -t, --translator    <google, yandex> сервис онлайн перевода
      --config        <путь> к конфиг-файлу
      --ui            <cli, terminal, kdialog, zenity, yad, wish> интерфейс
      --wcm           показать количество символов в заголовке окна перевода
      --WxH           <ширина>x<высота> окна перевода в пикселях
      --СxR           <колонок>x<строк> в окне терминала для вывода перевода
  -n, --closing-time  <секунд> до закрытия окна с переводом

Параметры по умолчанию:
  -l $langIn:$langOut --ui ${ui}${__size} -p $dscp -t $translator -n ${closingTime}${__config}${_b}${__filterIn}${__wcm}

"

### Обработка параметров командной строки
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
  -v | --version      ) echo   $version ;exit              ;;
  -h | --help         ) echo   -n -e "$Help" ;exit         ;;
  -c | --codes-lang   ) jq     -r       '."639-2" | .[] | if .alpha_2 then "\(.alpha_2) \(.name)" else empty end' /usr/share/iso-codes/json/iso_639-2.json ;exit;;
       --codes-lang2  ) eval   "$(jq -r '."639-2" | .[] | if .alpha_2 then @sh "printf \"%s \" \(.alpha_2);gettext --domain=iso_639-2 \(.name);echo" else empty end' /usr/share/iso-codes/json/iso_639-2.json)" ;exit;;
       --codes-google ) curl   -s "https://cloud.google.com/translate/docs/languages" |awk '/^<td/' |awk '{getline s;print $0,s}' |awk -F"[<|>]" '{print $9,$3}' ;exit;;
       --codes-yandex ) curl   -s "https://translate.yandex.net/api/v1.5/tr.json/getLangs" --data "ui=${langOut}&key=${yandexApiKey}" |jq -r '.langs | to_entries | .[] | "\(.key)\t\(.value)"' ;exit;;
  -l | --lang         ) shift; eval $(echo "$1" |awk -F: '{print "langIn="$1";langOut="$2}') ;;
       --lang-in      ) shift; langIn=$1                   ;;
       --lang-out     ) shift; langOut=$1                  ;;
       --ui           ) shift; ui=$1                       ;;
       --WxH          ) shift; WxH=$1                      ;;
       --CxR          ) shift; CxR=$1                      ;;
  -p | --dscp         ) shift; dscp=$1                     ;;
  -t | --translator   ) shift; translator=$1               ;;
  -s | --text-in      ) shift; textIn="$1"                 ;;
       --filter-in    ) filterIn="yes"                     ;;
  -b | --buffer-out   ) bufferOut="yes"                    ;;
  -n | --closing-time ) shift; closingTime="$1"            ;;
       --config       ) shift; [[ -f "$1" ]] && source "$1";;
       --wcm          ) wcm="yes"                          ;;
esac; shift; done
if [[ "$1" == '--' ]]; then shift; fi

# читаем stdin
read -d $? -t 0.2 textIn

### Сохранение буфера в переменную если текст получаем не через консоль(--text-in или stdin)
if [[ -z "$textIn" ]]; then
  case $dscp in
    wayland ) textIn="$(wl-paste -n)" ;;
    xorg    ) textIn="$(xsel -o)"     ;;
  esac
fi

### Фильтр(in)
# фильтры должны идти в подряд без комментариев
if [[ "$filterIn" == "yes" ]]; then
  textIn="$(echo -e -n "$textIn" \
  # | фильтр1  \
  # | фильтр2  \
  )"
fi

TextOutFile="$(mktemp -t translated.XXX)"

### Обращение к сервису перевода и запись ответа в файл
case $translator in
  google )
    TextOutJson="$(curl -s "https://translate.googleapis.com/translate_a/single" --data "client=gtx&dt=t&sl=${langIn}&tl=${langOut}" --data-urlencode "q=$textIn")"
    echo -n "$TextOutJson" | jq -j '.[0] | .[] | .[0]' > "$TextOutFile"
    [[ $langIn == "auto" ]] && langIn="auto($(echo -n "$TextOutJson" | jq -r '.[2]'))"
    ;;
  yandex )
    [[ $langIn == "auto" ]] && langInOut="${langOut}" || langInOut="${langIn}-${langOut}"
    TextOutJson="$(curl -s "https://translate.yandex.net/api/v1.5/tr.json/translate" --data "key=${yandexApiKey}&lang=${langInOut}" --data-urlencode "text=$textIn")"
    echo -n "$TextOutJson" | jq -j '.text[]' > "$TextOutFile"
    [[ $langIn == "auto" ]] && langIn="auto($(echo -n "$TextOutJson" | jq -r '.lang' | awk -F- '{print $1}'))"
    ;;
esac

### Сохранение перевода В буфер
if [[ "$bufferOut" == "yes" ]]; then
  case $dscp in
    wayland ) wl-copy  < "$TextOutFile" ;;
    xorg    ) xsel -bi < "$TextOutFile" ;;
  esac
fi

# Заголовок окна
if [[ "$wcm" == "yes" ]]
  then  Title="$translator :: $langIn[$(echo -e -n $textIn |wc -m)] → $langOut[$(cat  $TextOutFile |wc -m)]"
  else  Title="$translator :: $langIn → $langOut"
fi

### Вывод перевода через UI
eval $(echo "$WxH" |awk -Fx '{print "W="$1";H="$2}')
case $ui in
  cli     ) cat "$TextOutFile" ;;
  terminal) eval $(echo "$CxR" |awk -Fx '{print "C="$1";R="$2}')
            # alacritty -d $C $R -t "$Title" -e $SHELL -c "cat $TextOutFile | par -w ${C}d | less -~" | ( [[ $closingTime > 0 ]] && sleep $closingTime && kill $(pgrep -l --parent $$ | awk '$2 == "alacritty" {print $1}') &> /dev/null) ;;
            R=$(cat $TextOutFile | par -w ${C}d | tee ${TextOutFile}1 | wc -l)
            alacritty -d $C $R -t "$Title" -e $SHELL -c "less -s -~ ${TextOutFile}1"                  | ( [[ $closingTime > 0 ]] && sleep $closingTime && kill $(pgrep -l --parent $$ | awk '$2 == "alacritty" {print $1}') &> /dev/null)
            rm ${TextOutFile}1
            ;;
  kdialog ) kdialog --title "$Title" --geometry $WxH        --textbox="$TextOutFile"  | ( [[ $closingTime > 0 ]] && sleep $closingTime && kill $(pgrep -l --parent $$ | awk '$2 == "kdialog" {print $1}') &> /dev/null) ;;
  zenity  ) zenity  --title "$Title" --width=$W --height=$H --filename="$TextOutFile" --text-info --timeout=$closingTime ;;
  yad     ) yad     --title "$Title" --width=$W --height=$H --filename="$TextOutFile" --text-info --timeout=$closingTime --wrap ;;
  wish    ) echo 'encoding system utf-8
            wm title    .  {'$Title'}
            wm geometry .  '$WxH'
            bind        .  <Key-Escape> {exit 0}
            text        .t -wrap   word -font {-size 10} -yscrollcommand {.s set}
                        .t insert  1.0 [exec cat "'$TextOutFile'"]
            scrollbar   .s -orient vertical -command {.t yview}
            button      .b -text   Exit     -command exit
            pack        .b -side   bottom   -fill x
            pack        .s -side   right    -fill y
            pack        .t -expand yes      -fill both
            if '$closingTime'>0 {after [expr {int('$closingTime'*1000)}] exit}' |wish ;;
esac

rm "$TextOutFile"
exit 0
вешаем всё это дело на хоткей и наслаждаемся жизнью

под вяленым гномом работают оба варианта как dscp=wayland , так и dscp=xorg

upd1
обновил скрипт
+ реализована возможность использовать конфиг файл [необязателен]
+ частично реализована работа с ключами командной строки
+ добавлен еще один графический интерфейс (Tk [wish] ) для вывода перевода

upd2
обновление
+ добавлен графический интерфейс yad
+ реализована работа через конвейер
$ echo "hello" | ./langmi
$ echo "hello" | ./langmi --ui cli
$ ./langmi -h | ./langmi --ui cli --lang-in ru --lang-out en
+ добавлены ключи и их реализация
-b, --buffer-out помещать перевод В буфер
-e, --closing-time секунд до закрытия окна с переводом
-k, --get-keys показать список ключей и их значения
--config использовать указанный конфиг-файл

upd3
  • добавлен ключ –lang для более лаконичного ввода параметров
  • -l, –lang язык in:out
  • добавлен ключ –WxH вместо двух отдельных guiW и guiH
  • –WxH размер окна перевода
  • удалён ключ –get-keys, а взамен значитально переработана справка включающая возможности данного ключа
  • для графического интерфейса yad включен перенос(wrap),
  • оказалось(в отличии от zenity) по умолчанию wrap отключен из за чего длинный перевод прокручивался не вниз а в право
  • добалвена возможность включения фильтров(для форматирования текста) как для входящего текста так и самого перевода
  • –filter-in применить фильтр для входящего текста –filter-out применить фильтр для переведенного текста

upd4
  • убран сервис перевода DeepL (если есть у кого идеи как побороть их нежелание нормально делиться милости просим )
  • добавлена возможность автоопределения входящего языка, указывается как auto, например –lang-in=auto или –lang=auto:ru, установленно по умолчанию
  • опять немного переработана справка, думаю это уже окончательный вид
  • добавлены ключи –codes-google и –codes-yandex выводящих список поддерживаемых языков для указанных сервисов перевода
  • удалён filterOut как излишний
  • добавлен ключ –wcm который показывает в заголовке окна перевода сколько входящих(то что переводим) и исходящих(перевод) символов, по умолчанию отключено wcm=no
  • ну и всякие мелкие исправления по мелочи

upd5
основное нововедение касается возможности использовать терминал для отображения перевода что позволяет избавится от тяжеловестных графических интерфейсов.
Данная возможность полностью реализована для терминала alacritty, хотя ни что не мешает вместо него использовать какой-нибуть другой терминал, изменения должны быть минимальны
Закрытие происходит через нажатие клавишы q (используется less для просмотра), для alacritty также можно настроить закрытие через клавишу ESC, добавив в конфиг ~/.config/alacritty/alacritty.yml строку:
  - { key: Escape, action: Quit }

за включение отвечает
флаг
--ui terminal
конфиг
ui=terminal

помимо прочего добавлен флаг(+ в конфиге)
--CxR отвечающий за количество колонок и строк в окне терминала с переводом

только не путать ui=terminal и ui=cli , cli используется для непосредственной работе в терминале или использование в скриптах, также там не используется форматирование текста а передается как есть, в terminal же используется par шикарная утилита для форматировани, до этого использовал гнутую fmt но она плохо работает с русским да и не имеет поддержки юникода

upd6
в текущем скрипте поправил несколько мелких ошибок, и реализовал возможность автоматического подгона высоты(строк) терминала(ui=terminal) под выводимый перевод

для данного скрипта это скорее всего будут последние правки
Спасибо, воспользовался, под иксами.
Как я понял, при наличии скопированного в буфер и потом при просто выделении (первичный буфер) могут быть конфликты - случаются выдачи null.
Спасибо, очень полезно. Уже вставил в свой dwm, который король хоткеев. Сократил многое, конечно. )
жадные они, всего-то было с десяток запусков скрипта и уже получил -
В связи с большим количеством запросов, поступающих с Вашего IP-адреса,
доступ к бесплатному сервису DeepL Переводчик был временно отключен.
Попробуйте еще раз позже, подпишитесь на DeepL Pro или свяжитесь с нами
по адресу block@DeepL.com, указав свой IP-адрес. [IP: ]
wau, нифигасе ))) ты по переводил....
Псевдографический инсталлятор Arch Linux ver. 3.8.2
Благодарности принимаются на ЯД 410012815723874
wau
Как я понял, при наличии скопированного в буфер и потом при просто выделении (первичный буфер) могут быть конфликты - случаются выдачи null.
попробуйте заменить xsel на xclip где буфер сохраняется в переменную, может поможет
wau
доступ к бесплатному сервису DeepL Переводчик был временно отключен.
это из за ограничений deepl, тоже словил когда временно заблокировало, null в программе и та надпись что вы привели на их сайте, проходит довольно быстро, это происходит если слать им через программу большой текст, а слова и маленькие предложения вроде нормально пропускает, если напрямую через их сайт слать большой текст таких проблем нету, хм, надо попробовать разобраться как это обойти

nafanja
для однородности все переменные прописать в фигурные скобки ${x}.
в принципе можно, раньше так и делал, правда из за обилия этих скобочек немного падает восприятие кода

nafanja
кое какие стартовые параметры могут и автоматически определяться langIn langOut dscp и ui.
ну langOut понятно как определять заглянув к примеру в тот же localectl, в старом скрипте даже была такая функция, хотя большого смысла в ней не вижу, для простенького скрипта излишне, а для суровой программы недостаточно, например, русскоговорящий приемлемо владеющий англ. установит английскую локаль и us/ru раскладку и пойми тут какой язык родной рус или англ.
для langIn еще туманнее, единственное что неплохо будет добавить это авто определение языка переводимого текста, для яндекс точно есть да и у других вроде видел
upd: dscp и ui тоже в принципе можно определить но простенький скрипт такими темпами перестанет быть простеньким )

nafanja
можно так же и добавить к набору ui еще и notify-send.
когда еще под гномом сидел и тестировал переводчик первым делом пробовал notify-send, но во первых он не очень хорошо подходит для вывода много строчных текстовых данных, во вторых он не везде работает, в том же sway.

nafanja
а еще можно пользовательский конфиг добавить…
был такой, правда json-овский, реши урезать для упрощения, думаю здесь подойдёт обычный текстовый в баш стиле чтобы можно было через source подключить
пожалуй что таки использование в скрипте едепела нерабочее решение, а жаль.
red, сегодня решил опробовать скрипт-переводчик - понравился ... просто и удобно
Но есть одно предложение - как относишься к тому, чтобы при каждом переводе не открывать новое окно диалога, а очищать старое и выводить перевод в этом же окне? Возможно это и лишнее и только усложнит скрипт.
Ошибки не исчезают с опытом - они просто умнеют
vasek, red дал пример простого и элегантного решения задачи быстро посмотреть перевод слова/предложения, не! доколебались со скобками, а имена че не прописными, а языки бы менять и т.д. и т.п. В заглавии ж сказано - "нежно" )))
red еще раз спасибо!
а deepl только меня в этом скрипте обнуляет? - теперь даже null не пишет, просто пустое окно. При этом в браузере работает.
В сркрипте в части формирования обращения к едеплу есть вроде как идентификаторы -
стр. 39

"timestamp":1557063997314},"id":79120002}

стр. 49

"params":{"v":"20180814"},"id":79120001}'

что это за идентификаторы?
 
Зарегистрироваться или войдите чтобы оставить сообщение.