Xgettext mit Locale-XGettext erweitern

Das Program xgettext, Teil von GNU Gettext, konnte ursprünglich Strings nur aus C-Dateien in PO-Dateien extrahieren. Mittlerweile wurde es um 26 weitere Sprachen erweitert. Aber was tun, wenn das auch nicht reicht?

Motivation für Locale::XGettext

Unterstützung für eine weitere Programmiersprache in xgettext zu implementieren ist zwar nicht unbedingt trivial, aber prinzipiell machbar. Allerdings ist es aus verschiedenen Gründen oft nicht opportun:

  • Der entsprechende Code muss in C geschrieben werden. C ist aber nicht zwangsläufig erste Wahl beim Schreiben eines Parsers.
  • Jemand muss den Code warten.
  • Die Sprachunterstützung muss allgemein verwendbar sein, wenn sie in GNU gettext aufgenommen werden soll. Dagegen kann man die Sache bei einem Parser, der nur in einem einzigen Projekt oder für einen bestimmten Anwendungsfall funktionieren muss, etwas entspannter angehen.
  • Die Unterstützung für die neue Sprache ist erst nach einem neuen Release verfügbar ist. Bis sie dann in den einzelnen Betriebssystemen/Distributionen verfügbar ist, dauert es noch einen Tick länger.
  • Eventuell sollen die Strings überhaupt nicht aus Quelldateien, sondern aus einer Datenbank, einer Website oder einem Content-Management-System usw. extrahiert werden.
  • Der Anwendungsfall kann auch einfach zu speziell sein. Typisches Beispiel ist das Extrahieren aus HTML, für das es nicht den einen, einzig wahren Weg gibt.

In solen Situationen sind schon unzählige Parser zur Erzeugung von .pot-Dateien entstanden, oft als simple, Einmal-Lösungen mit hartkodierten Defaults und Annahmen. Dann stellt sich allmählich heraus, dass ein paar Dinge doch konfigurierbar sein sollten, es wird mehr und mehr Schnickschnack implementiert, von einem Projekt ins nächste kopiert, dann ein Bug gefixt, und vergessen, den Fix zurückzuportieren, alles schon erlebt, oder?

Vor einiger Zeit war ich in einer solchen Situation. Ich schrieb an Template-Plugin-Gettext, einem Gettext-Plug-In für Template Toolkit, einem populären und mächtigen Template-System für Perl. Fast gleichzeitig musste ich ein Übersetzungs-Framework für imperia CMS schreiben. Nach einiger Zeit war ich es so leid, Komponenten zwischen den Projekten hin- und her zu kopieren, dass ich beschloss, endlich eine generische Lösung zu bauen. Das Ergebnis ist Locale-XGettext.

Installation

Bevor man einen Extraktor schreiben kann, muss die Bibliothek installiert werden. Auf https://github.com/gflohr/Locale-XGettext#installation sind verschiedene Wege beschrieben, wie das geht.

Grundsätzliche Verwendung

Das folgende Beispiel ist in Perl geschreiben, weil Locale::XGettext selbst in Perl geschrieben ist. Extraktor lassen sich aber auch in C, Java, Python oder Ruby (und mit ein bisschen Mühe auch noch vielen weiteren Sprachen) schreiben. Weiterführende Informationen dazu finden sich in einer Einleitung zur Anbindung verschiedener Programmierspachen an Locale-XGettet auf Github.

Für eine Minimalimplementierung eines Skripts xgettext-sample reichen bereits einige wenige Zeilen Code:

#! /usr/bin/env perl

use strict;

Locale::XGettext::Sample->newFromArgv(\@ARGV)->run->output;

package Locale::XGettext::Sample;

use strict;

use base 'Locale::XGettext';

Zeile 5 enthält den eigentlichen Wrapper-Code. Dort wird eine Instanz der Klasse Locale::XGettext::Sample mit dem Konstruktor newFromArgv() erzeugt, dem alle Kommandozeilenargumente übergeben werden. Dann werden verkettet die Methoden run(), die den Extraktor startet, und output(), die die PO-Datei erzeugt, aufgerufen. Ab Zeile 7 folgt eine Stub-Implementierung, die eigentlich nichts implementiert, sondern nur von der Basisklasse Locale::XGettext erbt.

Genug der Erklärungen. Machen wir das Skript ausführbar und schauen, was es sagt:

$ chmod +x xgettext-sample
$ ./xgettext-sample
./xgettext-sample: no input file given
Try './xgettext-sample --help' for more information!
$ ./xgettext-sample --help
Usage: ./xgettext-sample [OPTION] [INPUTFILE]...

Extract translatable strings from given input files.

Mandatory arguments to long options are mandatory for short options too.
Similarly for optional arguments.

Input file location:
  INPUTFILE ...               input files
  -f, --files-from=FILE       get list of input files from FILE
  -D, --directory=DIRECTORY   add DIRECTORY to list for input files search
...

Kommt das bekannt vor? So sieht die Ausgabe des Originalprogramms xgettext aus:

$ xgettext
xgettext: no input file given
Try 'xgettext --help' for more information.
$ xgettext --help
Usage: xgettext [OPTION] [INPUTFILE]...

Extract translatable strings from given input files.

Mandatory arguments to long options are mandatory for short options too.
Similarly for optional arguments.

Input file location:
  INPUTFILE ...               input files
  -f, --files-from=FILE       get list of input files from FILE
  -D, --directory=DIRECTORY   add DIRECTORY to list for input files search
...

Unser 5-Zeiler hat also fast die gleiche beeindruckende Kommandozeilenschnittstelle wie xgettext. Und nicht nur das! Die Optionen funktionieren sogar. Naja, fast:

$ ./xgettext-sample does-not-exist.txt
Error resolving file name 'does-not-exist.txt': No such file or directory!

Gibt man eine nicht existierende Eingabedatei zum Lesen an, wird eine Fehlermeldung ausgegeben. In Ermangelung einer echten Eingabedatei geben wir deshalb einfach das Skript selber an:

$ ./xgettext-sample xgettext-sample 
Can't locate object method "readFile" via package "Locale::XGettext::Sample" at /opt/local/lib/perl5/site_perl/5.24/Locale/XGettext.pm line 186.

Jetzt beschwert sich Perl, dass die Methode readFile() nicht implementiert wurde. Das beheben wir zunächst mit einer Stub-Implementierung. Dazu hängen wir folgenden Programmcode am Ende von xgettext-sample an:

sub readFile {
    my ($self, $filename) = @_;

    # FIXME! Parse $filename and extract translatable strings!
}

So sehen Methoden in Perl aus. Die Methode readFile() erwartet zwei Argumente. Das erste ist immer die Instanz, normalerweise $self benannt. Man kann ihr aber auch den Namen $this oder was auch immer man möchte, geben. Das zweite Argument ist der Name der Datei, die geparst werden soll. Diese Methode wird von Locale::XGettext für jede angegebene Eingabedatei aufgerufen.

Wird das Skript jetzt erneut gestartet, sollte die Fehlermeldung verschwunden sein. Aber es ist auch nichts passiert. Weshalb? Weil keine Strings gefunden wurden, und Locale::XGettext --- genau wie xgettext --- keine Ausgabedatei ohne zu übersetzende Strings erzeugt, es sei denn, es wurde auf der Kommandozeile explizit so angegeben:

$ ./xgettext-sample --force-po xgettext-sample

Jetzt wurde eine PO-Datei messages.po im aktuellen Verzeichnis erzeugt. Sie enthält allerdings nur den PO-Header und keine Strings.

Das ändern wir nun. Statt tatsächlich eine Datei zu parsen, werden wir für unser Beispiel einfach beliebige Strings zurückgeben. Dazu wird die Methode readFile wie folgt geändert:

sub readFile {
    my ($self, $filename) = @_;

    $self->addEntry('msgid', 'Hello, world!');
    $self->addEntry(msgid => 'Goodbye, solitude!');
    $self->addEntry({msgid => 'A hash reference'});
}

Die Zeilen 4-6 sind neu und zeigen drei verschiedene Arten, um PO-Einträge in die Ausgabe zu übertragen. Sie sind alle äquivalent, und man kann sich die Syntax aussuchen, die einem am besten zusagt.

Wird das Skript erneut gestartet, diesmal ohne --force-po passiert:

$ ./xgettext-sample xgettext-sample

Nichts! Aber keine Nachrichten sind gute Nachrichten. Es wurde nämlich eine Datei messages.po erzeugt:

$ tail messages.po

msgid "Hello, world!"
msgstr ""

msgid "Goodbye, solitude!"
msgstr ""

msgid "A hash reference"
msgstr ""

In vielen Fällen ist das bereits genug an Boilerplate, und man kann mit der Implementierung des eigentlichen Parsers beginnen. Die erzeugte PO-Datei ist zwar noch ziemlich schlicht, aber voll funktional.

Oder man spielt noch etwas mit den Kommandozeilen-Optionen herum. Mit -D bzw. --directory können weitere Verzeichnisse für die Suche von Eingabedateien angegeben werden. Oder man kann die Liste der Eingabedateien mittels -f bzw. --files-from aus Textdateien lesen (im Gegensatz zu GNU gettext, kann man diese Option auch mehrfach verwenden). Die Ausgabe von --help gibt weitere Anregungen für eigene Ideen.

Hinzufügen von Quelltext-Referenzen, Flags, usw.

Die PO-Einträge in der Ausgabedatei sind im Moment noch nackt. Gewöhnt ist man zumindest an Referenzen auf die Quelltext-Dateien und oft auch Flags wie c-format, no-c-format und so weiter. Das lässt sich allerdings sehr einfach implementieren:

sub readFile {
    my ($self, $filename) = @_;

    my $lineno = 1;
    $self->addEntry(msgid => 'Hello, world!',
                    reference => "$filename:$lineno",
                    flags => 'greet-format');
    ++$lineno;
    $self->addEntry(msgid => 'Goodbye, solitude!',
                    reference =" $filename:$lineno",
                    flags => 'no-greet-format,goodbye-format');
}

Jetzt werden solche weitere Eigenschaften übergeben. Mit reference wird die Herkunft eines Strings im Quelltext angegeben, mit flags eine komma-separierte Liste von Flags.

Probieren wir das nun aus:

$ ./xgettext-sample xgettext-sample
$ tail messages.po
#: xgettext-sample:1
#, greet-format
msgid "Hello, world!"
msgstr ""

#: xgettext-sample:2
#, no-greet-format, goodbye-format
msgid "Goodbye, solitude!"
msgstr ""

So sieht es schon mehr aus, wie wir es gewohnt sind.

Allerdings sollte man Flags von Locale::XGettext lieber automatisch setzen lassen. Dazu weiter unten mehr.

Unterstützung für Schlüsselwörter

Für die meisten Sprachen, sollten die Schlüsselwörter, mit denen Strings als übersetzbar markiert werden, konfigurierbar sein. In der Implementierung für C beispielsweise sind unter anderem die Default-Keywords gettext, ngettext, ... hinterlegt. Es lässt sich auch angeben, an welcher Position in der Argumentliste der String erwartet wird. Gleiches gilt für die Position einer eventuellen Pluralform oder des Nachrichtent-Kontexts.

$ ./xgettext-sample --keyword=npgettext:1c,2,3 xgettext-sample

Das muss so gelesen werden: Zusätzlich zu den Standard-Schlüsselwörtern für die betreffende Sprache, soll auch npgettext() akzeptiert werden. Das erste Argument enthält den Nachrichten-Kontext (das c steht für den Kontext context). Die Singularform ist Argument Nummer zwei und die Pluralform findet sich im dritten Argument.

Locale::XGettext verarbeitet diese Schlüsselwort-Definitionen korrekt. Der jeweilige Parser muss sie aber natürlich auch beachten. Um das zu zeigen, würden wir einen echten Parser benötigen. Deshalb beschränken wir uns auf die Schnittstellen, mit der alle benötigten Informationen ermittelt werden können.

sub readFile {
    my ($self, $filename) = @_;

    # Assume that we have found the invocation of a function
    # "npgettext()" in the code.
    my $func_name = 'npgettext';

    # Does not really help...
    my $option = $self->option('keyword');

    # But this does:
    my $keywords = $self->keywords;
    if (exists $keywords->{$func_name}) {
        my $singular = $keywords->{$func_name}->singular;
        my $plural = $keywords->{$func_name}->plural;
        my $context = $keywords->{$func_name}->context;
      
        # And now use this information to find the right arguments.
    }
}

In Zeile 9 wird der Wert der Kommandozeilen-Option keyword ermittelt. Das reicht allerdings nicht, denn man müsste dann den Options-String selber parsen und mit den Standard-Schlüsselwörtern der Sprache aggregieren.

Einfacher geht es durch den Aufruf der Methode keywords() (Zeile 12), die alle gültigen Schlüsselwort-Definitionen in einem Hash zurückliefert. Locale::XGettext hat dabei die auf der Kommandozeile angegebenen Schlüsselwort-Definitionen (siehe weiter unten) bereits mit den Defaults der jeweiligen Sprache aggregiert.

In Zeile 13 wird getestet, ob ein bestimmtes Schlüsselwort definiert ist, denn falls nein, kann man sich schenken, die Argumente zu betrachten . Falls es definiert ist, kann man die Position von Singular, Plural und Kontext in der Argumentenliste abfragen. Die Position der Singular-Form ist immer eine Ganzzahl größer Null. Die Position des Plural- oder Kontext-Arguments kann auch Null sein, wenn das betreffende Schlüsselwort keinen Plural oder Kontext kennt.

Weiterhin liefert die Methode comment() einen eventuell definierten automatischen Kommentar für das jeweilige Schlüsselwort zurück oder undef (bzw. NULL, nil, None ...), falls ein solcher nicht exisitiert. Allerdings fügt Locale::XGettext diese automatischen Kommentare von alleine hinzu, und man braucht sich darüber in aller Regel nicht den Kopf zu zerbrechen.

Default-Schlüsselwörter

Falls die Sprache der Eingabedateien Default-Schlüsselwörter hat, können diese mit der Methode defaultKeywords() bekannt gemacht werden:

sub defaultKeywords {
  return [
    'gettext',
    'ngettext:1,2',
    'npgettext:1c,2,3',
    'dgettext:2',
    'dngettext:2,3',
    'dnpgettext:2c,3,4'
  ];
}

Das sollte nunmehr selbsterklärend sein. Zu beachten ist (Zeile 3!), dass die Position des Singular-Arguments auch einfach weggelassen werden kann, wenn die Funktion nur ein einziges Argument hat.

Automatische Kommentare

PO-Dateien können an beliebigen Stellen Kommentare enthalten, die mit der bekannten Syntax Kommentartext ... eingeleitet werden. Automatische Kommentare werden von verschiedenen Tools automatisch für gewisse Einträge eingefügt. Sie sind stets durch die Zeichenfolge #. am Zeilenanfang gekennzeichnet.

Übersetzerkommentare

In einer lokalisierten Perl-Datei kann man zuweilen folgendes Konstrukt sehen:

if ($dayno == 6) {
    # TRANSLATORS: This is the abbreviation for "Sunday"!
    $day = gettext("Sun");
}

Hier wurde offensichtlich einer sehr verbreiteten Konvention gefolgt: Die Zeichenkette Sun ohne Kontext könnte fälschlicherweise für den englischen Namen des Sterns, der uns gerade auf den Kopf scheint, gehalten werden. Ein sogenannter Übersetzerkommentar verhindert dieses Missverständnis, indem klargestellt wird, dass es sich um einen Wochentag handelt.

Damit das funktioniert, muss Locale::XGettext allerdings wissen, welcher Kommentar einem String im Quelltext vorausging. Das lässt sich so bewerkstelligen:

sub readFile {
    my ($self, $filename) = @_;

    $self->addEntry(msgid => "Sun",
                    automatic => " TRANSLATORS: The week day!");
}

Hier sollte der vollständige (auch mehrzeilige) Kommentar vor dem Schlüsselwort übergeben werden. Diesen Kommentar sollte man nicht selber parsen, sondern lediglich die Kommentarzeichen für die jeweilige Sprache entfernen. Locale::XGettext übernimmt dann den Rest.

Die Kommentar-Markierung TRANSLATORS: ist allerdings lediglich eine Konvention, die explizit auf der Kommandozeile angegeben werden muss:

$ ./xgettext-sample --add-comment="TRANSLATORS:" xgettext-sample
$ cat messages.po
...
#. TRANSLATORS: The week day!
msgid "Sun"
msgstr ""

Nochmal: Bitte Kommentare nicht selber parsen! Wird --add-comments='' (also mit leerem Argument) angegeben, sollen alle Kommentare, die Schlüsselwörtern vorausgehen, in die Ausgabe-PO-Datei übernommen werden. Weiterhin werden auch einige spezielle Kommentare, die den String xgettext: enthalten auf spezielle Art behandelt. Diese Logik muss man nicht selbst implementieren.

Schlüsselwort-spezifische Kommentare

Es lässt sich ebenfalls angeben, dass Übersetzungen, die mit bestimmten Schlüsselwörtern markiert wurden, einen automatischen Kommentar in der PO-Datei enthalten sollen:

./xgettext-sample --keyword=greet:1,'"Hello, translator!"'

Dabei ist zu beachten, dass die doppelten Anführungszeichen vor einer Expansion durch die Shell geschützt werden müssen.

Damit diese Automatik funkioniert, muss Locale::XGettext allerdings wissen, mit welchem Schlüsselwort ein bestimmter String markiert war, was sehr einfach ist:

sub readFile {
    my ($self, $filename) = @_;

    $self->addEntry(msgid => "Hello, world!",
                    keyword => "greet");
    $self->addEntry(msgid => "Goodbye, solitude!",
                    keyword => "wave_goodbye");
}

Wird der Extraktor jetzt erneut mit den Schlüsselwortdefinitionen von weiter oben aufgerufen, ändert sich die Ausgabedatei messages.po entsprechend:

#. Hello, translator!
msgid "Hello, world!"
msgstr ""

msgid "Goodbye, solitude!"
msgstr ""

Der erste String war mit dem Schlüsselwort greet markiert (wir tun jedenfalls so, als ob) und erhält den automatischen Kommentar, der auf der Kommandozeile angegeben wurde. Der zweite String war mit einem anderen Schlüsselwort markiert, und erhält keinen solchen Kommentar.

Schlüsselwort-spezifische Flags

Ähnlich dem automatischen Einfügen von Kommentare, können auch Flags für bestimmte Schlüsselwörter automatisch eingefügt werden:

$ ./xgettext-sample --keyword=greet:1,'"Hello, translator!"' \
    --flag=greet:1:no-perl-brace-format xgettext-sample
$ cat messages.po
...
#. Hello, translator!
#, no-perl-brace-format
msgid "Hello, world!"
msgstr ""

msgid "Goodbye, solitude!"
msgstr ""

Der erste String erhält jetzt zusätzlich noch das Flag no-perl-brace-format.

Hier gibt es allerdings einen Unterschied zu GNU Gettext. Die Default-Flags für C sind zum Beispiel so definiert:

...
    "gettext:1:pass-c-format",
    ...
    "printf:1:c-format",
    ...
Das lässt sich am einfachsten mit einem Beispiel in C verstehen:
printf(gettext("Filename '%s', line number %d\n"), filename, lineno);

Das erste (und einzige) Argument von printf() ist stets ein C-Format-String. Das erste und einzige Argument von gettext() dagegen ist ein gewöhnlicher String. Allerdings, wenn der Rückgabewert von gettext() als erstes Argument an printf() übergeben wird, wird die Eigenschaft des printf()-Arguments an gettext() zurückgegeben.

Der Grund dafür ist, dass C-Strings potenziell gefährlich sind. Ein Programm kann leicht abstürzen, wenn die dem Formatstring folgenden Argumente nicht zum Format selbst passen. Würde zum Beispiel der obige String mit Zeile %d in Datei '%s'\n übersetzt, führte das fast zwangsläufig zu einem Segmentation Fault, weil die Zeilennummer als Adresse eines Strings interpretiert würde.

Die Default-Flags in GNU Gettext verhindern dies effizient. Sie erzwingen, dass auch übersetzte Formatstrings, noch zu den übergebenen Argumenten passen. Das ist prinzipiell gut.

Auf der anderen Seite ist dieses Szenario relativ spezifisch für C, und es verkompliziert die Implementierung von String-Extraktoren erheblich. Locale::XGettext unterstützt deshalb nicht unmittelbar die Überprüfung verschachtelter Funktionsaufrufe. Aus dem gleichen Grund ignoriert es auch das Präfix pass- bei Flag-Definitionen. Eigentlich wäre es konsistenter, solche Flag-Definitionen als Ganzes zu ignorieren. Allerdings würde das bei ohne Nachdenken kopiertem Code öfters zu Problemen führen.

Es steht aber natürlich jedem frei, mehrstufige Argumentenprüfung in eigenen Parsern einzubauen. Nur Locale::XGettext kann dabei leider nicht behilflich sein.

Strings aus anderen Datenquellen extrahieren

Ein anderes häufig auftretendes Szenario sieht so aus, dass String nicht aus Quelldateien stammen, sondern aus einer Datenbank oder ähnlichen Systemen. Locale::XGettext unterstützt das:

sub extractFromNonFiles {
    my ($self, $filename) = @_;

    my $dbname = $self->option('database_name');
    
    # Query the database and use addEntry() to feed strings into
    # Locale::XGettext.
    # ...
}

Die Methode extractFromNoneFiles() wird aufgerufen, nachdem alle Eingabedateien verarbeitet worden sind. In der Fallback-Implementierung tut die Methode nichts. Man kann sie allerdings überschreiben, und dann beliebige weitere Strings aus anderen Datenquellen an Locale::XGettext weiter verfüttern.

Sekunde! Was ist denn die Option database_name in Zeile 4 des Code-Beispiels weiter oben? Eine solche Option gibt es doch gar nicht!

Es kommt darauf an:

Modellieren der Kommandozeilen-Schnittstelle

Die Standard-Schnittstelle für die Kommandozeile passt nicht für jeden Extraktor. Mit Locale::XGettext kann sie aber passend gemacht werden.

Sprachspezifische Optionen

Die Kommandozeilen-Schnittstelle lässt sich durch Zufügen von sprachabhängigen Optionen einfach erweitern:

sub languageSpecificOptions {
    return
        "database-name=s",
        "database_name",
        "    --database-name",
        "specify the name of the database",

        "database-user=s",
        "database_user",
        "    --database-user",
        "specify the user to connect with to the database",

        "database-pass=s",
        "database_pass",
        "    --database-pass",
        "specify the database password";
}

Überprüfen wir jetzt die Hilfe-Ausgabe des Extraktors:

$ ./xgettext-sample --help
...
Language specific options:
  -kWORD, --keyword=WORD      look for WORD as an additional keyword
  -k, --keyword               do not to use default keywords
      --flag=WORD:ARG:FLAG    additional flag for strings inside the argument
                              number ARG of keyword WORD
      --database-name         specify the name of the database
      --database-user         specify the user to connect with to the database
      --database-pass         specify the database password
...

Es wurden drei neue Optionen hinzugefügt, jeweils in Gruppen von vier Strings:

  1. Die Defintion der Option, zum Beispiel "database-name=s" für eine Option "--database-name" die ein String-Argument erfordert. Die genauen Einzelheiten, lassen sich der Dokumenation von Getopt::LongOptions entnehmen.
  2. Der "Name" der Option. Das ist das Argument, das der Methode "option()" übergeben wird, um den jeweiligen Wert zu ermitteln.
  3. Der linke Teil der Hilfsausgabe für diese Option.
  4. Der rechte Teil der Hilfsausgabe für diese Option.

Wenn einem die automatisch generierte Hilfsausgabe nicht gefällt, kann man einfach die Methode printLanguageSpecificOptions() überschreiben, und die Kontrolle selbst übernehmen:

sub printLanguageSpecificOptions {
    my ($self) = @_;

    print "  --foo=bar    you know what to do\n";
}

Beschreibung des erwarteten Eingabeformats

Einfach:

sub fileInformation {
    return "Input files must be valid foomatic files.";
}

Testen wir es:

$ ./xgettext-sample --help
Usage: ./xgettext-sample [OPTION] [INPUTFILE]...

Extract translatable strings from given input files.

Input files must be valid foomatic files.
...

Beschreibung der Extraktor-Features

Nicht jede Kommandozeilen-Option ergibt für jeden Extraktor Sinn. Deshalb kann man durch Überschreiben verschiedener Methode, einige Features an- und abschalten.

sub needInputFiles {
    return;  # Nothing is false.

    # This is also false:
    #return 0;
    #return '';
    # Everything else is true:
    #return 42;
}

Wenn die Methode needInputFiles() einen Wert zurückgibt, der von Perl als false interpretiert wird, erwartet Locale::XGettext keine Eingabe*dateien* mehr. Die Hilfsausgabe wird entsprechend angepasst, und readFile() wird auch nicht mehr aufgerufen (nur noch extractFromNoneFiles()).

Die folgenden weiteren booleschen Methoden werden zur Zeit unterstützt:

  • needInputFiles(): Sie oben. Default ist true.
  • canKeywords(): Unterstützt der Extraktor Schlüsselwörter? Sollte -k bzw. --keywords funtionieren? Default ist true.
  • canFlags(): Unterstützt der Extraktor automatische Flags? Sollte --flags funtionieren? Default ist true.
  • canExtractAll(): Kann der Extraktor sämtliche Strings und nicht nur als übersetzbar markierte extrahieren? Die Option -a, --extract-all wird je nach Rückgabewert an- und abgeschaltet.

Die Option --extract-all funktioniert allerdings nicht von alleine. Um sie zu unterstützen, muss der Optionswert mit option() abgefragt und entsprechend entschieden werden, welche Strings zurückgegeben werden.

Pläne für die Zukunft

Das Zusammenspiel von Locale::XGettext mit anderen Sprachen, insbesondere C und Java könnte deutlich vereinfacht werden, wenn JSON-Strings als Alternative zu verschachtelten Datenstrukturen erlaubt wären. Dieses Feature wird vermutlich in einer der nächsten Versionen zur Verfügung stehen.

Ein weiteres Problem rührt daher, dass Locale::XGettext für das Lesen und Schreiben von PO-Dateien auf die Perl-Bibliothek Locale::PO zurückgreift. Das API von Locale::PO hat allerdings eine Reihe von Problemen, und erlaubt keine Einflussnahme auf das PO-Ausgabeformat. Ohne dieses Feature kann Locale::XGettext viele Ausgabeoptionen von GNU Gettexts xgettext nicht unterstützen.

API-Status

Locale::XGettext ist relativ gut getestet und wird bereits in einer Reihe von Projekten erfolgreich eingesetzt. Die derzeit geplanten Features können alle in rückwärtskompatibler Weise implementiert werden. Dennoch, die derzeitige Versionssnummer ist 0.1, und es sollte klar sein, was das bedeutet.

Zusammenfassung

Locale::XGettext stellt eine Ergänzung zu GNU xgettext dar. Mithilfe der Bibliothek lassen sich String-Extraktoren realisieren, die weitestgehend kompatibel zu GNU Gettext sind. Diese Extraktoren können in verschiedenen Programmiersprachen geschrieben werden. Unmittelbar unterstützt werden derzeit Perl, C, Java, Python und Ruby.


blog comments powered by Disqus