A Ruby spanish translation DCOP server
I've been here in Gran Canaria for nearly six months now, and my spanish hasn't progressed as fast as I hoped it would. Learning another language is really hard! It makes me realise how tough it must be for non-native english speakers to contribute to english based Free Software projects such as KDE.
I hadn't learned a language since I was at school over 30 years ago, and I just didn't know what it would be like, or how long it would take. But I thought by now I would actually be able to understand what people were saying even if I couldn't speak back fluently. But actually I'm still having trouble following the most basic conversations :(. It seems like it will take a year or so to get into the swing of it. Although, I should really be spending my evenings learning vocabulary and grammar, but of course I tend to either end up hacking or drinking instead.
In the meantime, while I attempt to master spanish, I need to be able to translate emails, program specs etc in a quick and dirty way without needing to look everything up in a paper based dictionary. I often use the google translation service, which is better than nothing, although often the translations are so poor they're really funny.. Also you have to get to the google translation page, select the spanish to english option, and then paste you text in. So I've hacked up a quick and dirty DCOP server in Korundum to access google translate - I can just send it a spanish string, and it then translates it via google and sends the translation back. The advantage of wrapping a web service in DCOP is that once you have the server, and app which can talk DCOP can easily use the server. Here is the code for the DCOP server, along with a GUI based client which can translate the contents of a KDE::TextEdit field, and another client that takes a text file and translates each field in the text file in turn.
Here is the top level of the DCOP server, I just created a Ruby DCOP project in KDevelop:
require 'Korundum'
require 'spanishserver.rb'aboutdata = KDE::AboutData.new("spanishserver", "KDE",
"0.1", "A Spanish to English translation service",
KDE::AboutData::License_GPL, "(C) 2006, Richard Dale")
KDE::CmdLineArgs.init(ARGV, aboutdata)
if !KDE::UniqueApplication.start
puts "spanishserver is already running!"
exit(0)
end
app = KDE::UniqueApplication.new
service = SpanishTranslator.new
app.exec
This is the code for the server itself. It subclasses KDE::HTMLPart and uses it for sending out the url request, and getting the reply back as HTML. The first thing you need to do is to have a look at the page's HTML with konqueror's 'View Document Source' option. The google page has a pop-up menu to select with pair of languages to translate, and it adds a string such as '&langpair=es|en' to the uri the page sends. The text to be translated is in a textfield called 'text', and it gets added to the uri as '?text=some text'.
The server hooks up to the browser extension's 'loadingProgress(int)' signal, and when the progress reaches 100 percent, it means the page is complete, and the reply can be sent back to the DCOP client. The html text can be extracted from the KDE::HTMLPart with a 'src = htmlDocument.toString.string' statement, and then I use a simple regular expression to pull out the translated text. Note that the messages to and from the DCOP server are asynchronous; the initial message requesting the translation includes a DCOP reference of where to send the reply. This allows the DCOP server to process the request in the background without keeping the client waiting. Then the statement '@client.translation($1, @direction)' sends the translation back to the client, which must have a DCOP slot of type 'void translation(QString,QString)':
class SpanishTranslator < KDE::HTMLPart
k_dcop 'void translate(QString, QString, DCOPRef)'
slots 'progress(int)'def initialize
super
connect(browserExtension(), SIGNAL('loadingProgress(int)'),
self, SLOT('progress(int)'))
end
def translate(text, direction, client)
@direction = direction
@client = client
@url = KDE::URL.new("http://translate.google.com/translate_t?text=%s&langpair=%s" %
[text, direction])
openURL(@url)
end
def progress(percent)
if percent == 100 && !@url.nil?
src = htmlDocument.toString.string
if src =~ %r{<TEXTAREA.*>([^>]*)</TEXTAREA>.*<TEXTAREA.*>([^>]*)</TEXTAREA>}
@client.translation($1, @direction)
@url = nil
end
end
end
end
Here is a simple client with two text fields, one spanish and one english, which allows you to translate in both directions. It also has a couple of buttons, which access the KDE clipboard via klipper and translate it. Note that is has a DCOP slot called 'void translation(QString,QString)' which is used as a callback from the DCOP server with the completed translation.
class TranslatorClient < KDE::MainWindow
k_dcop 'void translation(QString,QString)'
slots :to_english, :to_spanish, :translate_clipboard end about = KDE::AboutData.new("translatorclient", "TranslatorClient", "0.1")
KDE::CmdLineArgs.init(ARGV, about)
app = KDE::Application.new
translator = TranslatorClient.new
app.mainWidget = translator
translator.caption = app.makeStdCaption("Spanish Translation Client")
translator.show
app.execrequire 'Korundum'
def initialize(parent = nil)
super
@spanish_clipboard = KDE::PushButton.new("Clipboard", self)
@spanish_text = KDE::TextEdit.new(self)
@english_clipboard = KDE::PushButton.new("Clipboard", self)
@english_text = KDE::TextEdit.new(self)
labels = Qt::VBoxLayout.new do |l|
l.addWidget(Qt::Label.new("Spanish", self))
l.addWidget(Qt::Label.new("English", self))
end
buttons = Qt::VBoxLayout.new do |b|
b.addWidget(@spanish_clipboard)
b.addWidget(@english_clipboard)
end
texts = Qt::VBoxLayout.new do |t|
t.addWidget(@spanish_text)
t.addWidget(@english_text)
end
setUpLayout()
layout.direction = Qt::BoxLayout::LeftToRight
layout.addLayout(labels)
layout.addLayout(buttons)
layout.addLayout(texts)
connect(@spanish_text, SIGNAL(:returnPressed), self, SLOT(:to_english))
connect(@spanish_clipboard, SIGNAL(:clicked), self, SLOT(:translate_clipboard))
connect(@english_text, SIGNAL(:returnPressed), self, SLOT(:to_spanish))
connect(@english_clipboard, SIGNAL(:clicked), self, SLOT(:translate_clipboard))
@translation_server = KDE::DCOPRef.new("spanishserver", "SpanishTranslator")
@dcop_self = KDE::DCOPRef.new("translatorclient-%d" % $$, "TranslatorClient")
@klipper = KDE::DCOPRef.new("klipper", "klipper")
end
def to_english
@translation_server.translate(@spanish_text.text, "es|en", @dcop_self)
end
def to_spanish
@translation_server.translate(@english_text.text, "en|es", @dcop_self)
end
def translate_clipboard
if sender() == @english_clipboard
@english_text.text = @klipper.getClipboardContents
to_spanish
elsif sender() == @spanish_clipboard
@spanish_text.text = @klipper.getClipboardContents
to_english
end
end
def translation(text, direction)
if direction == "es|en"
@english_text.text = text
elsif direction == "en|es"
@spanish_text.text = text
end
end
Finally, here is the code for doing a 'batch' translation of each field within a wiki page; it sends each field in turn to the DCOP server, and just writes the translated text to standard output:
class TranslateWiki < KDE::TextEdit
k_dcop 'void translation(QString,QString)'
slots :translate, :next_line end aboutdata = KDE::AboutData.new("twikitranslator", "KDE",
"0.1", "A Spanish to English twiki translator",
KDE::AboutData::License_GPL, "(C) 2006, Richard Dale")
KDE::CmdLineArgs.init(ARGV, aboutdata)
app = KDE::Application.new
t = TranslateWiki.new
app.mainWidget = t
t.show
app.execrequire 'Korundum'
def initialize
super
@server = KDE::DCOPRef.new("spanishserver", "SpanishTranslator")
@dcop_self = KDE::DCOPRef.new("twikitranslator-%d" % $$, "TranslateWiki")
connect(self, SIGNAL(:returnPressed), SLOT(:translate))
end
def translate
@line_number = 0
@file = File.open(self.text.chop).readlines
Qt::Timer.singleShot(10, self, SLOT(:next_line))
end
def next_line
line = ""
loop do
if @line_number >= @file.length
return $kapp.quit
end
line = @file[@line_number]
line.chop!
@line_start = ""
if line =~ /^(\s*\*\s*(\[\w+:\s*)?)(.*)/
@line_start = $1 + " "
line = $3
end
@line_number += 1
if line =~ /^\s*$/
puts @line_start
elsif line =~ /^(---)|(%TOC%)|(\*\s*2006)/
puts line
else
break
end
end
@server.translate(line, "es|en", @dcop_self)
end
def translation(translation, direction)
puts @line_start + translation
Qt::Timer.singleShot(10, self, SLOT(:next_line))
end
Although the code here translates text, the same general idea can be used to access any website which doesn't have any sort of SOAP or REST based service that you can hook up to. For example, there are many sites such as kde apps org, which have an internal search option and using the same technique you can easily build a DCOP server to access those.
Regards -- Senor Dale Cerveza