Skip navigation.
KDE Developer's Journals

A Ruby spanish translation DCOP server

richard dale's picture

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 Sad. 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.

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.exec

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:

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.exec

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

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
quintesse's picture

Spanish

Don't worry, when I was learning Spanish the thing that cost me the most time was trying to understand them Eye-wink

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 Smiling

krake's picture

You're a genius...

...and a saviour Smiling

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 Eye-wink

Thanks a lot!

bensch's picture

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

richard dale's picture

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.

bensch's picture

Works perfectly

Hi Dale,
I'm amazed by how easy all the DCOPRef code was.
Of course it worked perfectly.

Cheers,
Ben

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.