Danke für den Fisch [Übersetzen mit Babel Fish] (Linux-Magazin, Dezember 2000)

Der Übersetzungscomputer Babel Fish ist zwar kein Sprachgenie, hilft aber den Inhalt fremdsprachiger Dateien zu verstehen. Das heute vorgestellte Kommandozeilentool dockt an ihn an.

Wer auf altavista.com auf den Translate-Knopf drückt, erhält das Web-Formular zu Babel Fish, einem Programm, das ein halbes Dutzend Sprachen versteht und ganze Sätze übersetzen kann. Nun steckt die maschinelle Übersetzungskunst seit Jahrzehnten in den Kinderschuhen und es ist noch kein Durchbruch in Sicht, also ist das Ganze nicht ganz ernstzunehmen. Dennoch: In Sprachen, in denen man nur ``Guten Tag'' und ``Ein Bier bitte'' sagen kann, kann der Babel Fish helfen.

Das Webformular auf babel.altavista.com versteht in eine Eingabebox getippten Text sowie Links auf zu übersetzende Webdokumente. Statt jedes Mal den Browser hochzufahren und den zu übersetzenden Text als URL oder mittels Cut-and-Paste in das Eingabefeld zu transferieren geht es heute darum, mittels eines Perl Skripts über das Internet bei Babel Fish anzudocken, den Inhalt lokaler Dateien zu übersetzen und anschließend auszugeben.

Das vorgestellte Perlskript trans tut genau dies. Wie fast immer stellt sich nach kurzem Nachforschen auf dem CPAN heraus, dass es dort schon ein passendes Perl-Modul gibt, in diesem Fall ist es WWW::Babelfish von Dan Urist, das die Arbeit an trans auf ein Minimum reduziert.

Das Modul WWW::Babelfish arbeitet mit einem Babelfish-Objekt, dessen translate()-Methode neben einigen Übersetzungsparametern auch den zu übersetzenden Text als String entgegennimmt.

Der faule Babel Fish auf dem Web schneidet Texte rigoros nach 1000 Zeichen ab, WWW::Babelfish umgeht dies aber geschickt, indem es Texte in Teile mit weniger als 1000 Buchstaben zerlegt und sie einzeln an Babel Fish schickt.

Vielsprachiger Agent

Babelfish unterstützt derzeit beidseitige Konvertierungen zwischen den Sprachen Deutsch-Englisch und Deutsch-Französisch. Im Zusammenhang mit Englisch arbeitet er außerdem mit Italienisch, Portugiesisch, Spanisch und Russisch.

Ein Aufruf von trans ganz ohne Parameter zeigt an, welche Parameter es normalerweise erwartet:

    usage: trans \
        [efgpirs]2[efgpirs] file ...
        e: English
        f: French
        g: German
        i: Italian
        p: Portuguese
        r: Russian
        s: Spanish

Damit das vorgestellte Skript trans auch weiss, aus welcher und in welche Sprache es übersetzen soll, gibt der erste Kommandozeilenparameter die Richtung an: g2e (German-English) ist Deutsch-Englisch, f2g (French-German) ist Französisch-Deutsch.

Der zu übersetzende Text steht in einer oder mehreren Dateien, deren Namen als Parameter nachfolgen. Der folgende Aufruf übersetzt zum Beispiel den französischen Inhalt der Datei /tmp/french.txt ins Deutsche und gibt das Ergebnis auf der Standardausgabe aus:

    $ trans f2d /tmp/french.txt

In alter Unix-Tradition geht es auch, den Dateinamen wegzulassen, dann nimmt trans die Daten aus der Standardeingabe entgegen:

    $ echo "Der Ball ist rund" | \
      trans g2e
    The ball is round

Das Skript in Listing trans zieht in Zeile 3 das Modul WWW::Babelfish herein, das vorher entsprechend den Anweisungen im Abschnitt Installation vom CPAN geholt und installiert werden muss.

Listing 1: trans

    01 #!/usr/bin/perl
    02 
    03 use WWW::Babelfish;
    04 
    05     # Vorgegaukelter User-Agent
    06 use constant AGENT => 
    07              'Mozilla/4.73 [en] (X11; U; Linux)';
    08 
    09     # Unterstützte Sprachen
    10 my @languages = qw(English French German Italian 
    11                    Portuguese Russian Spanish);
    12 
    13     # Hash aufbauen, der Sprachkürzel der
    14     # Sprache zuordnet (g=>German, e=>English, ..)
    15 foreach my $language (@languages) {
    16     my $initial = substr($language, 0, 1);
    17     $i2full{lc($initial)} = $language;
    18 }
    19 
    20     # Alle Kürzel in einem String (efgpirs)
    21 my $chars = join '', keys %i2full;
    22 
    23     # Umwandlungsrichtung von der
    24     # Kommandozeile (g2e, e2f, ...)
    25 my $way  = shift;
    26 
    27 usage() unless defined $way;
    28 
    29 usage("Scheme $way not supported") unless 
    30  ($from, $to) = $way =~ /^([$chars])2([$chars])$/;
    31 
    32     # Zu übersetzenden Text einlesen
    33 my $data = join '', <>;
    34 
    35     # Verbindung zu Babelfish aufnehmen
    36 my $babel = WWW::Babelfish->new(agent => AGENT);
    37 usage("Cannot connect to Babelfish") unless 
    38     defined $babel;
    39 
    40     # Übersetzen lassen
    41 my $transtext = $babel->translate( 
    42     source      => $i2full{$from},
    43     destination => $i2full{$to},
    44     text        => $data
    45 );
    46 
    47 die("Error: " . $babel->error) unless 
    48     defined($transtext);
    49 
    50 print $transtext, "\n";
    51 
    52 ##################################################
    53 sub usage {
    54 ##################################################
    55     my $msg  = shift;
    56     my $prog = $0;
    57 
    58     print "usage: $prog ", 
    59           "[${chars}]2[${chars}] file ...\n";
    60     foreach $c (sort split //, $chars) {
    61         print "  $c: $i2full{$c}\n";
    62     }
    63     exit(1);
    64 }

Die unterstützten Sprachen stehen in den Zeilen 10 und 11 im Array @languages. Der praktische Operator qw zur Listendefinition trennt den eingschlossenen String an den Wortgrenzen (Leerzeichen und Umbrüche) und liefert eine Liste zurück, die jedes Wort als Element enthält.

Um später elegant über die Kürzel (z.B. g) auf die vollständigen Sprachnamen (z.B. German) zugreifen zu können, bauen die Zeilen 15 bis 18 einen Hash %i2full auf, der die Kürzel als Schlüssel und die Sprachnamen als Werte enthält. Hierzu greift sich die substr-Funktion jeweils das erste Zeichen des Sprachnamens und die lc-Funktion konvertiert es in einen Kleinbuchstaben.

Zeile 21 baut für später alle verfügbaren Kürzel zu einem String $chars zusammen, der mit my auf das gegenwärtige Package beschränkt wird, aber auch in der Unterfunktion usage zur Verfügung steht.

$way holt sich in Zeile 25 mit shift den ersten Kommandozeilenparameter, der die Richtung der Übersetzung angibt. Falls keiner vorliegt, hat der Benutzer offensichtlich die Syntax von trans nicht begriffen und die usage-Funktion gibt eine Bedienungsanleitung aus und bricht das Programm ab.

Der reguläre Ausdruck in Zeile 30 /^([$chars])2([$chars])$/; interpoliert zu /^([efgpirs])2([efgpirs])$/; und prüft, ob der Richtungsanzeiger dem Format x2y entspricht, wobei x und y einen der Werte e, f, g, p, i, r oder s annehmen. Da der Ausdruck im Listen-Kontext steht und auf der linken Seite eine Liste mit den Elementen $from und $to steht, liegen dort bei einem erfolgreichen Match anschließend die in den Klammern des regulären Ausdrucks eingefangenen Werte. Bei g2e wäre dies g in $from und e in $to. Schlägt der Match hingegen fehl, ist das Resultat eine leere Liste, die im boolschen Kontext von unless als falsch interpretiert wird.

Zeile 33 liest den zu übersetzenden Text ein -- entweder von Dateien, deren Namen auf der Kommandozeile stehen oder von der Standardeingabe, falls keine Dateinamen vorliegen. Die join-Funktion verbindet die Zeilen zu einem langen String, unter Beibehaltung der Zeilenumbrüche, versteht sich.

Das Babelfish-Objekt

Zeile 36 erzeugt ein neues WWW:Babelfish-Objekt und weist es mit dem agent-Parameterpaar an, sich als Netscape-Browser auszugeben. Hierzu dient die in Zeile 6 mit Perls use constant-Pragma angegebene Konstante. So definiert man Funktionen, die Ausssehen wie Makros und von Perl so optimiert werden, dass sie konstanten Skalaren in nichts nachstehen.

Laut Dokumentation liefert der WWW:Babelfish-Konstruktor den Wert undef zurück, falls etwas schief ging, was Zeile 37 zum Abbruch nutzen würde.

Zeile 41 schließlich sendet alle Daten an den Babel Fish auf dem Web. Die translate-Methode nimmt die vollen (englischen) Namen für Ausgangs- und Zielsprache (Parameternamen source und destination) entgegen, sowie den zu übersetzenden Text als Stringwert für den Parameter text.

Das Objekt schickt die Daten an das Formular, interpretiert das zurückkommende HTML und extrahiert daraus wieder das Ergebnis -- alles ohne unser Zutun. In $transtext liegt anschließend das Ergebnis. Ein Wert von undef deutet einen Fehler an, den Zeile 47 abfängt und mittels der error-Methode des WWW::Babelfish-Objekts als Meldung anzeigt. Zeile 50 gibt das Ergebnis schließlich auf der Standardausgabe aus.

Bedienungsanleitung

Damit neue Benutzer die Bedienung von trans leicht erlernen, gibt die ab Zeile 53 definierte usage-Funktion eine ihr übergebene Nachricht und anschließend eine kurze Bedienungsanleitung aus. Anschließend bricht sie mit exit(1) das Programm ab.

Das oben schon einmal abgedruckte Kurz-Manual generiert usage dynamisch aus dem Inhalt der Variablen $chars und %short, die erlaubte Kürzel und eine Kürzel-zu-Sprachname-Tabelle enthalten.

Die Perlfunktion split in Zeile 60 spaltet mit // als Pattern einen String in seine Einzelzeichen auf und liefert einen Array zurück, der jedes Zeichen als Element enthält. Die sort-Funktion bringt den Array mit Kleinbuchstaben in alphabetische Reihenfolge und der Hash %short liefert die dazugehörigen Sprachnamen.

Die Reihe der unterstützten Sprachen könnte man übrigens auch mit der Methode languages() des WWW::Babelfish-Objektes abfragen, die einen Array mit allen aktuellen Sprachen zurückliefert. trans tut dies nicht, da die Auswahl relativ statisch ist.

Installation

WWW::Babelfish gibt's auf dem CPAN unter

    WWW-Babelfish-0.09.tar.gz

und setzt das libwww-Bundle sowie das Modul IO::String voraus. Installiert wird, wie immer mit der CPAN-Shell und

    perl -MCPAN -eshell
    cpan> install libwww
    cpan> install IO::String
    cpan> install WWW::Babelfish

Sternstunden der Übersetzungstechnik

Ein paar Beispiele, um die Funktion von Babelfish zu testen: Hierzu rufen wir trans nur mit dem Sprachrichtungsparameter auf, geben den zu übersetzenden Text anschließend über die Standardeingabe ein und schließen mit ^D (Control+D) ab:

    $ trans g2e 
    Einen Radi und eine Mass 
    Bier, aber schnell!
    ^D
    A Radi and measure beer, but fast!

Na ja, nicht schlecht, aber halt nicht perfekt. Auch von Englisch nach Französisch gibt's eine Schnittstelle:

    $ trans e2f
    waiter, where the hell 
    are my frog legs?
    ^D
    le serveur, oł l'enfer sont 
    mes cuisses de grenouille?

Oder von Englisch nach Deutsch:

    $ trans e2g
    waiter, this american beer 
    tastes terrible!
    ^D
    Kellner, dieses Amerikanerbier 
    schmeckt schrecklich!

Na bitte, sogar Humor hat das Teil. Viel Spaß damit!

Referenzen

[1]
Zum Thema Babelfisch: Douglas Adams, Per Anhalter durch die Galaxis, Heyne, ISBN 3453146972 http://www.amazon.de/exec/obidos/ASIN/3453146972/perlmeistercom04

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.