Pull to refresh

Пишем апплет-переводчик для Gnome [python]

Python *

Предисловие



Сидел я как-то тихим вечером, читал англоязычные сайты. Когда понадобилось перевести пару фраз, я как обычно открыл новую вкладку, набрал translate.google.ru, скопировал, вставил текст, нажал перевести, прочитал. И ведь такую длинную последовательность действий приходится проделывать каждый раз, когда есть необходимость перевести текст, пару фраз или целый абзац. Да, можно использовать какой-либо клиент для перевода, но окно программы придется открывать или доставать из-под других окон. Да, последовательность короче, но все равно длинная. Было бы здорово сократить последовательность действий до скопировать, нажать кнопку. Так появилась идея для апплета. Его логика такая:
  • На панели появляется кнопка «GTranslate»
  • Если вам надо перевести текст, то копируете его и нажимаете на кнопку
  • Автоопределение языка


И я начал читать различные статьи по написанию апплетов, в том числе и на хабре. Во всех использовалась gtk.Label(), но это и правильно, те апплеты использовались для вывода информации. Наш же апплет никакую информацию на панель выводить не будет, он всего лишь посредник между нами и диалоговым окном с переводом. Поэтому будем использовать gtk.Button(). Итак, приступим!

Диалоговое окно



Изначально для вывода текста использовался элемент easygui.codebox, но easygui в стандартной поставке нету и заставлять её ставить не очень-то хотелось. Решено было написать свой простенький элемент управления. Он должен был иметь три контрола: текст на начальном языке, текст на переведенном языке (это должен был быть textbox для возможности копирования перевода) и, конечно же, кнопка с надписью «OK». Благодаря вот этим двум туторам появилось диалоговое окно.

Код, в общем-то, понятный, немного прокомментированный.
import pygtk
pygtk.require("2.0")
import gtk

"""Спасибо этим туторам! Без них я бы этого не сделал =)"""<br/>
#http://www.pygtk.org/docs/pygtk/class-gdkwindow.html<br/>
#http://www.moeraki.com/pygtktutorial/pygtk2tutorial/

"""Этот класс описывает диалог для вывода переведенного текста"""<br/>
class ShowTextDialog():
  def __init__(self):
    self.default_window_name="GTranslate [Neon Mercury]"
    
    self.window=gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.set_title(self.default_window_name)
    self.window.connect("delete_event", self.delete_event)
    self.window.set_border_width(10)
    self.box=gtk.VBox()
    self.window.add(self.box)
    
    """Эта Label будет содержать непереведенный текст"""<br/>
    self.info=gtk.Label()
    self.box.pack_start(self.info, True, False, 0)
    
    """А из этого поля можно скопировать уже переведенный текст, если потребуется"""<br/>
    self.textview=gtk.TextView()
    self.box.pack_start(self.textview, True, True, 0)    
      
    self.button=gtk.Button("OK")
    self.button.connect("clicked", self.button_clicked)
    self.box.pack_start(self.button, False, False, 0)
    
    self.info.show()
    self.textview.show()
    self.button.show()
    self.box.show()
    
    self.window.move(100, 100) 
    self.window.resize(640, 480)
    
  """info - текст для label, textbox_info - текст для textview"""<br/>
  """Показывает диалог"""<br/>
  def show(self, info, textbox_info, main_window_title="GTranslate [Neon Mercury]"):
    info=self.process_text(info)
    textbox_info=self.process_text(textbox_info)
    self.info.set_text(info)
    self.textview.get_buffer().set_text(textbox_info)
    self.window.set_title(main_window_title)
    self.window.show()
  
  def hide(self):
    self.window.hide()
    
  def button_clicked(self, widget, data=None):
    self.hide()
    
  """По нажатии на кнопку, просто скрываем диалог"""<br/>
  def delete_event(self, widget, event, data=None):
    self.hide()
    
  """Обрабатывает текст. Google отдает текст одной строкой, даже если он содержит 65536 символов :)"""<br/>
  """Если в ручную не разбить этот текст на строки, то наше окно будет очень длинным!"""<br/>
  """Вот каждый max_len символ мы вставляем перевод строки. Если вдруг перевод строки встретиться раньше"""<br/>
  """max_len символа, то обнуляем счетчик."""<br/>
  def process_text(self, text, max_len=160):
    result=""<br/>
    i=0
    for char in text:
      i+=1
      if i>max_len and char==" ":
        result+="\n"<br/>
        i=0
      else:
        if char=="\n":<br/>
          i=0
        result+=char<br/>
    return result


* This source code was highlighted with Source Code Highlighter.


Библиотека для перевода



Для перевода текста также была написана отдельная библиотека. Она также очень проста и имеет три функции. Первая заменяет наиболее частоиспользуемые HTML-мнемоники на их эквивалент. Их безумно много, поэтому написал простенький скрипт, который распарсил википедию и, фактически, написал за меня всю функцию. Её я не буду на хабре постить, она очень длинная, в исходниках все будет. Вторая функция, определяющая исходный язык текста, тоже простая. Мы просто берем каждый символ текста. Если он из английского алфавита, то увеличиваем один счетчик, если из русского, то другой. То есть мы определяем доминирующий язык в тексте и считаем его исходным. Третья функция и есть переводчик. Она определяет исходный язык, посылает запрос гуглю и получает переведенный текст.
"""Функция определяет основной язык текста. По-умолчанию английский."""<br/>
"""Считаем сколько символов англ. алфавита в тексте, сколько рус. алфавита."""<br/>
"""Кого больше - тот и выиграл!"""<br/>
def determine_languages(text):
  en_chars_count=0
  ru_chars_count=0
  for char in text:
    if (char>="a" and char<="z") or (char>="A" and char<="Z"):
      en_chars_count+=1
    elif (char>="а" and char<="я") or (char>="А" and char<="Я"):
      ru_chars_count+=1
  source_lang="en"<br/>
  dest_lang="ru"<br/>
  if ru_chars_count>en_chars_count:
    source_lang, dest_lang="ru", "en"<br/>
  return source_lang, dest_lang

"""Основная функция для перевода текста."""<br/>
def translate(text):
  source_lang, dest_lang=determine_languages(text)  
  params={"ie":"UTF-8", "text":text,
      "sl":source_lang, "tl":dest_lang}
  params=urllib.urlencode(params)
  headers={"Content-Length":"%d" % len(params)}
  
  connection=httplib.HTTPConnection("translate.google.ru")
  connection.request("POST", "/translate_t", params, headers)
  response=connection.getresponse()
  answer=response.read()
  index=answer.find("id=result_box")
  if index!=-1:    
    index+=24
  """Использовать регулярку не хотелось бы для одного поиска"""<br/>
    translated_text=answer[index:answer.find("</div", index)]
    translated_text=replace_html_mnemonics(translated_text)
    translated_text=translated_text.decode("koi8-r")
    return translated_text
  else:
    return ""


* This source code was highlighted with Source Code Highlighter.


Апплет



Ну и вот собственно код самого апплета:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import gtk
import gnomeapplet

import NeonDialogs
from GTranslateLib import translate

"""Спасибо этим туторам! Без них я бы этого не сделал =)"""<br/>
#http://www.citforum.ru/programming/python/gnome_applets/

"""Класс нашего апплета"""<br/>
class GTranslate(gnomeapplet.Applet):
  def __init__(self, applet, iid):
    self.applet=applet
    self.applet.set_name("GTranslate")
    self.box=gtk.HBox()
    self.applet.add(self.box)
    self.button=gtk.Button("GTranslate")
    self.box.add(self.button)
    self.applet.show_all()
    
    self.button.connect("clicked", self.on_button)
    self.dialog=NeonDialogs.ShowTextDialog()
    
  """Если юзер хочет перевести текст и кликнул по нашей кнопке, то """<br/>
  """берем текст из буфера обмена, переводим и выводим через самописный диалог."""<br/>
  def on_button(self, widget, data=None):
    text=self.get_text_from_clipboard()
    translated_text=translate(text)
    self.dialog.show(text, translated_text)
    
  """Этот пункт со справкой на будущее...пока не используется."""<br/>
  def on_ppm_about(self, event, data=None):
    gnome.ui.About("GTranslate", "1.0", "GPL General Public License",
            "Скопируйте текст в буфер обмена и получите переведенный на другой язык текст!",
            ["Neon Mercury <wishmaster2009@gmail.com>",]).show()
    
  """Просто получает текст из буфера обмена. Вроде объект clipboard можно не пересоздавать каждый раз, """<br/>       
  """но почему-то работает только так. Надо почитать маны."""
  def get_text_from_clipboard(self):
    clipboard=gtk.Clipboard(display=gtk.gdk.display_get_default(), selection="CLIPBOARD")
    text=clipboard.wait_for_text()
    clipboard.store()    
    return text
           
def applet_factory(applet, iid):
  GTranslate(applet, iid)
  return True  

"""Просто заглушка для дебага. В релизе вызвать нельзя (без правки скрипта)."""<br/>
def run_in_window():
  main_window=gtk.Window(gtk.WINDOW_TOPLEVEL)  
  main_window.set_title("GTranslate [debug mode]")
  main_window.connect("destroy", gtk.main_quit)
  app=gnomeapplet.Applet()
  applet_factory(app, None)
  app.reparent(main_window)
  main_window.show_all()
  gtk.main()
 
"""Запускает наш апплет"""
def run_in_panel():
  gnomeapplet.bonobo_factory("OAFIID:GTranslate_Factory",
                GTranslate.__gtype__,
                "GTranslate", "1.0", applet_factory)
  
"""Если хотим ловить жучков, то меняем run_in_panel() на run_in_window()"""<br/>
run_in_panel()


* This source code was highlighted with Source Code Highlighter.


.server файл



Если вы писали/копипастили апплет сами, то ниже приведен .server файл для этого апплета.
<oaf_info>

<oaf_server iid="OAFIID:GTranslate_Factory"
          type="exe"          location="/home/neon/applets/GTranslate/GTranslate.py"><br/>

  <oaf_attribute name="repo_ids" type="stringv"><br/>
    <item value="IDL:Bonobo/GenericFactory:1.0" /><br/>
    <item value="IDL:Bonobo/Unknown:1.0" />

  </oaf_attribute><br/>
  <oaf_attribute name="name" type="string" value="GTranslate factory" /><br/>
  <oaf_attribute name="name-ru" type="string" value="Фабрика GTranslate" /><br/>

  <oaf_attribute name="description" type="string" value="Factory of GTranslate" /><br/>
  <oaf_attribute name="description-ru" type="string" value="Фабрика GTranslate" /><br/>

</oaf_server><br/>
<oaf_server iid="OAFIID:GTranslate"
          type="factory"          location="OAFIID:GTranslate_Factory"><br/>
  <oaf_attribute name="repo_ids" type="stringv"><br/>

    <item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0" /><br/>
    <item value="IDL:Bonobo/Control:1.0" /><br/>
    <item value="IDL:Bonobo/Unknown:1.0" /><br/>

  </oaf_attribute><br/>
  <oaf_attribute name="name" type="string" value="GTranslate" /><br/>
  <oaf_attribute name="name-ru" type="string" value="GTranslate" /><br/>

  <oaf_attribute name="description" type="string" value="GTranslate service of text translation." /><br/>
  <oaf_attribute name="description-ru" type="string" value="Сервис перевода текста GTranslate." /><br/>

  <oaf_attribute name="panel:category" type="string" value="Accessories" /><br/>
  <oaf_attribute name="panel:icon" type="string" value="/home/neon/applets/GTranslate/GTranslate.png" /><br/>

</oaf_server><br/>
</oaf_info><br/>

* This source code was highlighted with Source Code Highlighter.


Одно замечание: выделенные строки замените на свои. Первая — путь к главному файлу апплета (GTranslate.py). Вторая — это картинка апплета (Необязательно, но красиво). Не забудьте сделать файл GTranslate.py исполняемым.

Заключение



Вот(tar.gz) (rar) архив с исходниками апплета и скриптом установки. Для установки апплета в систему необходимо запустить файл install.py с правами root'a (для копирования файлов в /usr/lib/bonobo/servers/).

Теперь для перевода достаточно выделить текст и нажать кнопку GTranslate на панели. Удачи!

UPD: Перенес в блог PyGTK

Tags:
Hubs:
Total votes 41: ↑36 and ↓5 +31
Views 2.9K
Comments Comments 66