лёгкий переводчик

Вопросы о переводах документации, вики, man страниц. Разработки.
Ответить
Arhei
Сообщения: 27
Зарегистрирован: 04.07.2025

#

создано по мотивам данной темы

сам скрипт:
#!/usr/bin/env bash

# описание:     перевод выделенного текста и отображение его через терминал foot в sway
# зависимости:  sway foot wl-clipboard curl jq ttf-jetbrains-mono

NAME="${0##*/}"
DIR="/tmp/$NAME" && mkdir -p "$DIR"
FileIn="$DIR/in.txt"
FileOut="$DIR/out.txt"


COLUMNSxLINES=33x17
POSITION="center"	# or "X Y"
FONT="JetBrainsMono:size=15"

# ColorBackground="000000"
# ColorForeground="826b58"
# ColorIn="\e[1;34m"
# ColorOut="\e[1;37m"
ColorIn="\e[1;34;40m"
ColorOut="\e[1;30;44m"
ColorError="\e[1;37;41m"
ColorReset="\e[0m"
LangDetect=true
LangIn=eng
LangOut=rus
PrimaryClipboard=true

# shellcheck source=/dev/null
touch "$DIR/conf" && source "$DIR/conf"

tput civis	# убрать курсор
tput bold	# текст жирным



if [[ ! $z ]]; then
	export z=1
	# sway: правило для отображения окна по его app_id
	swaymsg for_window [app_id="$NAME"] floating enable
	swaymsg for_window [app_id="$NAME"] sticky   enable
	swaymsg for_window [app_id="$NAME"] move position "$POSITION"
	
	# закрыть открытое окно при повторном запуске программы
	WindowID=$(swaymsg -t get_tree | jq --arg name "$NAME" --raw-output '..|select(.app_id == $name)? | .pid')
	[[ -n "$WindowID" ]] && kill "$WindowID" && exit
	
	rm -rf "$FileIn.old"
	
	[[ $PrimaryClipboard == true ]] && BUFFER_TYPE="--primary"

	exec foot 	--hold \
				-t foot-direct \
			  	--app-id="$NAME" \
				--font="$FONT" \
				--window-size-chars="$COLUMNSxLINES" \
				--override=pad="10x10 center" \
				--override="mouse.hide-when-typing=true" \
				--override="colors.alpha=1" \
			  	-e wl-paste "$BUFFER_TYPE" --type "text/plain;charset=utf-8" --watch "$NAME"

				# --override="colors.background=$ColorBackground" \
				# --override="colors.foreground=$ColorForeground" \
fi

# читаем буфер и пишем в файл
cat - > "$FileIn"

TextIn=$(cat "$FileIn")
[[ "$TextIn" == "" ]] && exit
[[ "$TextIn" =~ [[:alpha:]] ]] || exit

# если выделенный текст идентичен предыдущему то не переводить
cmp --quiet "$FileIn" "$FileIn.old" && exit
cp "$FileIn" "$FileIn.old"


# фильтры для входящего текста
sed -i -e '/./,$!d' -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$FileIn"    	# удалить начальные и конечные пустые строки
awk 'NF{c=1} (c++)<3' "$FileIn" | sponge "$FileIn"               	# удалить больше одной пустой строки
awk 'NF>0{printf "%s ",$0;next} {printf "\n\n"}' "$FileIn" | sponge "$FileIn" 
tr -s ' ' < "$FileIn" | sponge "$FileIn"                         	# удаляем больше одного пробела


JsonOut="$(
curl -s 'https://api.reverso.net/translate/v1/translation' \
  --compressed \
  -X POST \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0' \
  -H 'Accept: application/json, text/plain, */*' \
  -H 'Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3' \
  -H 'Accept-Encoding: gzip, deflate, br, zstd' \
  -H 'Content-Type: application/json' \
  -H 'X-Reverso-Origin: translation.web' \
  -H 'Origin: https://www.reverso.net' \
  -H 'DNT: 1' \
  -H 'Sec-GPC: 1' \
  -H 'Connection: keep-alive' \
  -H 'Referer: https://www.reverso.net/' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-site' \
  -H 'TE: trailers' \
  --data-raw "$(
  jq --null-input \
     --arg languageDetection "$LangDetect" \
     --arg from  "$LangIn" \
     --arg to    "$LangOut" \
     --arg input "$TextIn" \
  '{"format":"text","from":$from,"to":$to,"input":$input,"options":{"sentenceSplitter":true,"origin":"translation.web","contextResults":false,"languageDetection":$languageDetection}}')"
)"
printf "%s" "$JsonOut" | jq --raw-output0 '.translation[]' > "$FileOut"


error=$(printf "%s" "$JsonOut" | jq --raw-output '.error')
if [[ "$error" != "null" ]]; then
	echo -en "$ColorError"
	StatusBarError="-- [err]"
	dashes=""
	while ((--COLUMNS - 8)) ; do dashes="${dashes}-" ; done
	echo -n "$StatusBarError $dashes"
	echo "$error"
	echo -e "$ColorReset"
	exit
fi

LangIn2=$LangIn
LangOut2=$LangOut
LangIn2=$(printf "%s" "$JsonOut" | jq --raw-output '.languageDetection.detectedLanguage')
reversed=$(printf "%s" "$JsonOut" | jq --raw-output '.languageDetection.isDirectionChanged')
[[ $reversed == "true" ]] && LangOut2=$LangIn || LangOut2=$LangOut


# вывод статус бара
StatusBar="-- [$LangIn2:$LangOut2(reverso)]"
StatusBarChars=$(echo -n "$StatusBar" |wc -m)
dashes=""
while ((--COLUMNS - StatusBarChars)) ; do dashes="${dashes}-" ;done
echo "$StatusBar $dashes"

# вывод переводимого и переведённого текста 
echo -en "$ColorIn"
cat 	"$FileIn"
echo -e "\n$ColorOut"
cat 	"$FileOut"
echo -e "\n$ColorReset"

# запоминаем(до перезагрузки) размер консоли
echo "COLUMNSxLINES=${COLUMNS}x${LINES}" > "$DIR/conf"

# запоминаем(до перезагрузки) положения окна в sway
SwayBarInfo=$(swaymsg -t get_bar_config "$(swaymsg -t get_bar_config | jq -r '.[]')")
BAR_MODE=$(echo "$SwayBarInfo" | jq -r '.mode')
BAR_POSITION=$(echo "$SwayBarInfo" | jq -r '.position')
BAR_HEIGHT=$(echo "$SwayBarInfo" | jq -r '.bar_height')
[[ "$BAR_MODE" == "dock" && "$BAR_POSITION" == "top" ]] || BAR_HEIGHT=0
echo "POSITION=$(swaymsg -t get_tree | jq --arg name "$NAME" --arg bar_height "$BAR_HEIGHT" '.. | select(.app_id == $name )? | .rect | "\(.x) \(.y - ($bar_height|tonumber))"')" >> "$DIR/conf"

exit
вешаем вызов скрипта на кнопку и радуемся
у сябя повесил на одну из боковых кнопок мыши
~/.config/sway/
bindsym --whole-window BTN_SIDE exec lalang.sh
вариант пока довольно ограниченный но будем дорабатывать
что он уже может
- перевод с помощью онлайн сервиса reverso.net (будут и другие)
- выбор буфера для перевода, параметр PrimaryClipboard (true - первичный буффер, когда достаточно просто выделить текст; false - стандартный буффер, тот же Ctrl-C который отправляет в буфер)
- запоминает(до перезагрузки) где было расположенно окно(sway) и его(foot) размер; в принципе можно будет сделать его более универсальным(без привязки к WM), но пока так.
- запускаеться в слушающем(выбранный буффер) режиме, то есть переводит по мере поступления; закрываеться так же как и запускаеться

если будут конструктивные предложение то добро пожаловать
vall
Аватара пользователя
Администрация
Сообщения: 911
Зарегистрирован: 09.08.2022

#

Коллега Arhei, Вас с почином на этом ресурсе. Форум с "разморозкой" данного раздела.

Приглашаю заинтересованных пользователей пробовать и высказываться по вопросу.
lnx
Сообщения: 242
Зарегистрирован: 24.08.2022

#

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

1. из-за различных сетевых осложнений, типа действий роскомкозлов, регулярно вместо результата вылетает пустое окно - сервер не успевает получить-ответить и выводится пустое окно. Второе-третье нажатие кнопок результат дает. Поэтому, думаю, следует добавить какой-то триггер проверки получения непустого результата и, возможно, повторной автоотправки.
2. шрифты. Я бы был проще, дефолтными шрифтами. Доставлять что-то для скриптов я не сторонник
3. эта версия скрипта требует создания не только временных файлов, но и директориев, лучше бы без этого. У меня, например, не заработало со словами "Failed to spawn transl_a_rus2.sh: No such file or directory" при том, что новый файл точно исполняемый, лежит рядом со старым, имя отличается цифрой 2 на конце и в sway config правки внесены путем добавления двоечки
4. открывшееся окошко foot содержит текст. Бывает нужно не только прочитать, но и скопировать - попытка выделения приводит к автодублированию в этом же окошке (в моем случае)
screen_20250728-112251.jpg

Себе, после частого и долгого использования предыдущей версии, задал размеры для прибития окошка узкой полосой на всю высоту экрана справа -
swaymsg for_window [app_id="foot-translate"] move position 1135 0

		--font="Mono:size=9" \
	--window-size-chars=40x50 \
	--override=pad="10x10 center" \

И большое спасибо за работу. И оцень надеюсь на расширение списка сервисов.
lnx
Сообщения: 242
Зарегистрирован: 24.08.2022

#

Сервис reverso ужасен. Они каждый день на своем сайте пркручивают какую-нибудь новую якобы антироботовую дурь, это поверх антиробота клаудфлайр, и в итоге получаю пустое окно. Если по первости было достаточно разок, другой повторить, то ныне хоть до дна забейся клавишами, пустой черный экран. И при входе на веб-морду сервиса каждый день новые скрипты, которые надо "разрешать"

Как временное решение вот версия для использование гугла, авторство не мое, от меня лишь костыли для работы в чистом wayland (раздел Сохранение буфера в переменную - инвертировать комменты в случае X11 или гибридности) -
#!/usr/bin/env bash

# Зависимости
# ---------------------------------------
# xsel          - рaбота с буфером в Xorg
# wl-clipboard  - работа с буфером в Wayland
# jq            - работа с json в консоли
# kdialog       - графический диалог kde   [необязательно]
# zenity        - графический диалог gnome [необязательно]
#pacman -S zenity xsel wl-clipboard  jq

### Вводная
langIn=en
langOut=ru
translator=google # google, edeepl, yandex
#translator=edepel # google, edeepl, yandex
#translator=yandex # google, edeepl, yandex

dscp=Xorg      # Wayland, Xorg # display server communications protocols
ui=zenity         # kdialog, zenity, cli
guiW=800
guiH=300
Title="$translator ${langIn}::${langOut}"
TextOutFile="$(mktemp -t translated.XXX)"
DIR=$(dirname $(readlink -e "$0"))

## Сохранение буфера в переменную
TextIn="$(wl-paste --primary --no-newline)"
#case $dscp in
 #   Wayland ) TextIn="$(wl-paste --primary --no-newline)" ;;
 #   #TextIn="$(wl-paste --primary --no-newline)"
 #   Xorg    ) TextIn="$(xsel -o)"     ;;
#esac
### Обращение к сервису перевода и запись ответа в файл
case $translator in
    google )
                UrlTranslate="https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=${langIn}&tl=${langOut}"
                TextOutJson="$(curl -s -H "user-agent: Mozilla/5.0" --get "${UrlTranslate}" --data-urlencode "q=${TextIn}")"
                echo -n "${TextOutJson}" | jq -j '.[0] | .[] | .[0]' > "$TextOutFile"
                ;;

    edeepl )
                data='{"jsonrpc":"2.0","method": "LMT_handle_jobs","params":{"jobs":[{"kind":"default","raw_en_sentence":"'"${TextIn}"'","raw_en_context_before":[],"raw_en_context_after":[],"quality":"fast"}],"lang":{"user_preferred_langs":["${langIn}","${langOut}"],"source_lang_user_selected":"'${langIn}'","target_lang":"'${langOut}'"},"priority":-1,"timestamp":1557063997314},"id":79120002}'
                HEADER=(
                  --compressed
                  -H 'Origin: https://www.deepl.com'
                  -H 'Referer: https://www.deepl.com/translator'
                  -H 'Accept: */*'
                  -H 'Content-Type: text/plain'
                  -H 'Accept-Language: en-us'
                  -H 'User-Agent: Mozilla/5.0'
                )
                curl -s 'https://www.deepl.com/PHP/backend/clientState.php?request_type=jsonrpc&il=EN' "${HEADER[@]}"--data-binary '{"jsonrpc":"2.0","method":"getClientState","params":{"v":"20180814"},"id":79120001}' >|/dev/null
                TextOutJson=$(curl -s 'https://www2.deepl.com/jsonrpc' "${HEADER[@]}" --data-binary $"$data")
                echo -n "$TextOutJson" | jq -j '.result.translations[0].beams[0].postprocessed_sentence' > "${TextOutFile}"
                 ;;
    yandex )
                ## https://translate.yandex.ru/developers/keys
                # apiKey='*************************************'
                apiKey="$(cat ${DIR}/yandexApiKey.txt)"
                UrlTranslate="https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&lang=${langIn}-${langOut}"
                # curl -s "${UrlTranslate}&text=Hello"
                TextOutJson="$(curl -s --get "${UrlTranslate}" --data-urlencode "text=${TextIn}")"
                echo -n "${TextOutJson}" | jq -j -r '.text[]' > "$TextOutFile"
                ;;
esac

## Сохранение перевода В буфер
case $dscp in
    Wayland ) wl-copy < "${TextOutFile}"    ;;
    Xorg    ) xsel -i -b < "${TextOutFile}" ;;
esac


# sway: правило для отображения окна по его app_id
swaymsg for_window [app_id="foot-translate"] floating enable 
swaymsg for_window [app_id="foot-translate"] sticky   enable 
swaymsg for_window [app_id="foot-translate"] move position 1135 0
#swaymsg for_window [app_id="foot-translate"] move position 80ppt 0ppt



# закрыть предыдущее окно по его app_id
_PID=$(swaymsg -t get_tree | jq -r '..|objects|select(.app_id == "foot-translate")| .pid')
[[ -n "$_PID" ]] && kill "$_PID"

# вывод перевода через терминал foot
foot \
	--hold \
	--app-id=foot-translate \
	--font="Mono:size=9" \
	--window-size-chars=40x50 \
	--override=pad="10x10 center" \
	-e cat "${TextOutFile}"


### Вывод перевода через UI
#case $ui in
#    kdialog ) kdialog --title "${Title}" --geometry ${guiW}x${guiH}+${guiW}+${guiH} --textbox="${TextOutFile}";;
#   zenity  ) zenity  --title "${Title}" --width=$guiW --height=$guiH --text-info --filename="${TextOutFile}";;
#    cli     ) cat "${TextOutFile}";; # вывод на консоль
#esac


rm "${TextOutFile}"
exit 0


Ответить