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.execThis 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
endHere 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.
require 'Korundum'
class TranslatorClient < KDE::MainWindow
k_dcop 'void translation(QString,QString)'
slots :to_english, :to_spanish, :translate_clipboard
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
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.execFinally, 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:
require 'Korundum'
class TranslateWiki < KDE::TextEdit
k_dcop 'void translation(QString,QString)'
slots :translate, :next_line
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
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.execAlthough 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

Spanish
Don't worry, when I was learning Spanish the thing that cost me the most time was trying to understand them
It was okay if they only talked directly to me but when they started talking among eachother it seemed like an entirely different language!
Finding a nice chica might speed things up tho, it did for me
You're a genius...
...and a saviour
I have been pondering for weeks which Ruby example to demonstrate in an upcoming talk about KDE bindings, but the pondering is over!
Now I just have to how it works in all its details
Thanks a lot!
Maybe use qt designer to setup the GUI instead of doing it by ha
Hi Dale, Krake,
All i gotta say is "holy f**king s**t". This is an amazing tutorial.
I'm definitely implementing this as soon as i get home.
Btw, if it's possible, I would suggest using qt designer to layout the ui for TranslatorClient instead of hardcoding it. I think it's better to make the tutorial cover a broader range of topics...
Cheers,
Ben
Re: examples
A talk on KDE bindings sounds good - I'm happy to help out with any questions about that. Note that I use SLOT(:my_slot) syntax which is only available in the svn version of korundum, and you may need to change them to SLOT('my_slot()') instead - they are exactly equivalent to each other.
Works perfectly
Hi Dale,
I'm amazed by how easy all the DCOPRef code was.
Of course it worked perfectly.
Cheers,
Ben